/*
* wm2000.c -- WM2000 ALSA Soc Audio driver
*
* Copyright 2008-2010 Wolfson Microelectronics PLC.
*
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* The download image for the WM2000 will be requested as
* 'wm2000_anc.bin' by default (overridable via platform data) at
* runtime and is expected to be in flat binary format. This is
* generated by Wolfson configuration tools and includes
* system-specific callibration information. If supplied as a
* sequence of ASCII-encoded hexidecimal bytes this can be converted
* into a flat binary with a command such as this on the command line:
*
* perl -e 'while (<>) { s/[\r\n]+// ; printf("%c", hex($_)); }'
* < file > wm2000_anc.bin
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/firmware.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <sound/wm2000.h>
#include "wm2000.h"
enum wm2000_anc_mode {
ANC_ACTIVE = 0,
ANC_BYPASS = 1,
ANC_STANDBY = 2,
ANC_OFF = 3,
};
struct wm2000_priv {
struct i2c_client *i2c;
enum wm2000_anc_mode anc_mode;
unsigned int anc_active:1;
unsigned int anc_eng_ena:1;
unsigned int spk_ena:1;
unsigned int mclk_div:1;
unsigned int speech_clarity:1;
int anc_download_size;
char *anc_download;
};
static struct i2c_client *wm2000_i2c;
static int wm2000_write(struct i2c_client *i2c, unsigned int reg,
unsigned int value)
{
u8 data[3];
int ret;
data[0] = (reg >> 8) & 0xff;
data[1] = reg & 0xff;
data[2] = value & 0xff;
dev_vdbg(&i2c->dev, "write %x = %x\n", reg, value);
ret = i2c_master_send(i2c, data, 3);
if (ret == 3)
return 0;
if (ret < 0)
return ret;
else
return -EIO;
}
static unsigned int wm2000_read(struct i2c_client *i2c, unsigned int r)
{
struct i2c_msg xfer[2];
u8 reg[2];
u8 data;
int ret;
/* Write register */
reg[0] = (r >> 8) & 0xff;
reg[1] = r & 0xff;
xfer[0].addr = i2c->addr;
xfer[0].flags = 0;
xfer[0].len = sizeof(reg);
xfer[0].buf = ®[0];
/* Read data */
xfer[1].addr = i2c->addr;
xfer[1].flags = I2C_M_RD;
xfer[1].len = 1;
xfer[1].buf = &data;
ret = i2c_transfer(i2c->adapter, xfer, 2);
if (ret != 2) {
dev_err(&i2c->dev, "i2c_transfer() returned %d\n", ret);
return 0;
}
dev_vdbg(&i2c->dev, "read %x from %x\n", data, r);
return data;
}
static void wm2000_reset(struct wm2000_priv *wm2000)
{
struct i2c_client *i2c = wm2000->i2c;
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR);
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
wm2000_write(i2c, WM2000_REG_ID1, 0);
wm2000->anc_mode = ANC_OFF;
}
static int wm2000_poll_bit(struct i2c_client *i2c,
unsigned int reg, u8 mask, int timeout)
{
int val;
val = wm2000_read(i2c, reg);
while (!(val & mask) && --timeout) {
msleep(1);
val = wm2000_read(i2c, reg);
}
if (timeout == 0)
return 0;
else
return 1;
}
static int wm2000_power_up(struct i2c_client *i2c, int analogue)
{
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
int ret, timeout;
BUG_ON(wm2000->anc_mode != ANC_OFF);
dev_dbg(&i2c->dev, "Beginning power up\n");
if (!wm2000->mclk_div) {
dev_dbg(&i2c->dev, "Disabling MCLK divider\n");
wm2000_write(i2c, WM2000_REG_SYS_CTL2,
WM2000_MCLK_DIV2_ENA_CLR);
} else {
dev_dbg(&i2c->dev, "Enabling MCLK divider\n");
wm2000_write(i2c, WM2000_REG_SYS_CTL2,
WM2000_MCLK_DIV2_ENA_SET);
}
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR);
wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_SET);
/* Wait for ANC engine