/*
* wm2000.c -- WM2000 ALSA Soc Audio driver
*
* Copyright 2008-2011 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/kernel.h>
#include <linux/init.h>
#include <linux/firmware.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/debugfs.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <sound/wm2000.h>
#include "wm2000.h"
#define WM2000_NUM_SUPPLIES 3
static const char *wm2000_supplies[WM2000_NUM_SUPPLIES] = {
"SPKVDD",
"DBVDD",
"DCVDD",
};
enum wm2000_anc_mode {
ANC_ACTIVE = 0,
ANC_BYPASS = 1,
ANC_STANDBY = 2,
ANC_OFF = 3,
};
struct wm2000_priv {
struct i2c_client *i2c;
struct regmap *regmap;
struct clk *mclk;
struct regulator_bulk_data supplies[WM2000_NUM_SUPPLIES];
enum wm2000_anc_mode anc_mode;
unsigned int anc_active:1;
unsigned int anc_eng_ena:1;
unsigned int spk_ena:1;
unsigned int speech_clarity:1;
int anc_download_size;
char *anc_download;
struct mutex lock;
};
static int wm2000_write(struct i2c_client *i2c, unsigned int reg,
unsigned int value)
{
struct wm2000_priv *wm2000 = i2c_get_clientdata(i2c);
return regmap_write(wm2000->regmap, reg, value);
}
static unsigned int wm2000_read(struct i2c_client *i2c, unsigned int r)
{
struct wm2000_priv *wm2000 = i2c_get_clientdata(i2c);
unsigned int val;
int ret;
ret = regmap_read(wm2000->regmap, r, &val);
if (ret < 0)
return -1;
return val;
}
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 = 4000;
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);
unsigned long rate;
int ret;
BUG_ON(wm2000->anc_mode != ANC_OFF);
dev_dbg(&i2c->dev, "Beginning power up\n");
ret = regulator_bulk_enable(WM2000_NUM_SUPPLIES, wm2000->supplies);
if (ret != 0) {
dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret);
return ret;
}
rate = clk_get_rate(wm2000->mclk);
if (rate <= 13500000) {
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 to become ready */
if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
WM2000_ANC_ENG_IDLE)) {
dev_err(&i2c->dev, "ANC engine failed to reset\n");
regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies);
return -ETIMEDOUT;
}
if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
WM2000_STATUS_BOOT_COMPLETE))