[alsa-devel] [PATCH V2 1/1] ASoC: ak5702: add support for ak5702 -- 4ch ADC

This codec driver adds support for Asahi Kasei AK5702.
Signed-off-by: Paolo Doz paolo.doz@gmail.com --- include/sound/ak5702.h | 27 +++ sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ak5702.c | 507 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ak5702.h | 171 +++++++++++++++ 5 files changed, 711 insertions(+)
diff --git a/include/sound/ak5702.h b/include/sound/ak5702.h new file mode 100644 index 0000000..8c0d4e9 --- /dev/null +++ b/include/sound/ak5702.h @@ -0,0 +1,27 @@ +/* + * AK5702 ALSA SoC Codec driver + * + * Author: Paolo Doz paolo.doz@gmail.com + * Copyright: (C) 2012 Soft In S.r.l. + * + * 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. + */ + +#ifndef __AK5702_H +#define __AK5702_H + +/** + * enum ak5702_clock mode + */ + +enum ak5702_clock_mode { + AK5702_CLKPLL_SLAVE, + AK5702_CLKPLL_SLAVE2, + AK5702_CLKPLL_MASTER, + AK5702_CLKEXT_SLAVE, + AK5702_CLKEXT_MASTER, +}; + +#endif /* __AK5702_H */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3a84782..d8156c7 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -26,6 +26,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4641 if I2C select SND_SOC_AK4642 if I2C select SND_SOC_AK4671 if I2C + select SND_SOC_AK5702 if I2C select SND_SOC_ALC5623 if I2C select SND_SOC_ALC5632 if I2C select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC @@ -202,6 +203,9 @@ config SND_SOC_AK4642 config SND_SOC_AK4671 tristate
+config SND_SOC_AK5702 + tristate + config SND_SOC_ALC5623 tristate config SND_SOC_ALC5632 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index f6e8e36..1f88602 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -14,6 +14,7 @@ snd-soc-ak4535-objs := ak4535.o snd-soc-ak4641-objs := ak4641.o snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o +snd-soc-ak5702-objs := ak5702.o snd-soc-arizona-objs := arizona.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs42l51-objs := cs42l51.o @@ -136,6 +137,7 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o +obj-$(CONFIG_SND_SOC_AK5702) += snd-soc-ak5702.o obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o diff --git a/sound/soc/codecs/ak5702.c b/sound/soc/codecs/ak5702.c new file mode 100644 index 0000000..9d35062 --- /dev/null +++ b/sound/soc/codecs/ak5702.c @@ -0,0 +1,507 @@ +/* + * ak5702.c -- AK5702 Soc Audio driver + * + * Author: Paolo Doz paolo.doz@gmail.com + * Copyright: (C) 2012 Soft In S.r.l. + * 2009 Freescale Semiconductor, Inc. All rights reserved. + * + * 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. + * + * This code comes from the original Freescale Ak5702 Audio driver + */ + +/* #define DEBUG 1 */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.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 <linux/slab.h> +#include <sound/tlv.h> +#include <linux/regmap.h> + +#include <sound/ak5702.h> +#include "ak5702.h" + +#define AK5702_VERSION "0.5" +#define DRV_NAME "ak5702" + +/* codec private data */ +struct ak5702_priv { + struct regmap *regmap; + enum ak5702_clock_mode clock_mode; +}; + +/* + * ak5702 register cache + */ +static const struct reg_default ak5702_reg_defaults[] = { + { 0, 0x0000}, /* R0 - Power Management */ + { 1, 0x0024}, /* R1 - PLL Control */ + { 2, 0x0000}, /* R2 - Signal Select */ + { 3, 0x0001}, /* R3 - Mic Gain Control */ + { 4, 0x0023}, /* R4 - Audio Format Select */ + { 5, 0x001F}, /* R5 - fs Select */ + { 6, 0x0000}, /* R6 - Clock Output Select */ + { 7, 0x0001}, /* R7 - Volume Control */ + { 8, 0x0091}, /* R8 - Lch Input Volume Control */ + { 9, 0x0091}, /* R9 - Rch Input Volume Control */ + { 10, 0x0000}, /* R10 - Timer Select */ + { 11, 0x00E1}, /* R11 - ALC Mode Control 1 */ + { 12, 0x0000}, /* R12 - ALC Mode Control 2 */ + { 13, 0x00A0}, /* R13 - Mode Control 1 */ + { 14, 0x0000}, /* R14 - Mode Control 2 */ + { 15, 0x0000}, /* R15 - Mode Control 3 */ + { 16, 0x0000}, /* R16 - Power Management */ + { 17, 0x0000}, /* R17 - PLL Control */ + { 18, 0x0000}, /* R18 - Signal Select */ + { 19, 0x0001}, /* R19 - Mic Gain Control */ + { 20, 0x0020}, /* R20 - Audio Format Select */ + { 21, 0x0000}, /* R21 - fs Select */ + { 22, 0x0000}, /* R22 - Clock Output Select */ + { 23, 0x0001}, /* R23 - Volume Control */ + { 24, 0x0091}, /* R24 - Lch Input Volume Control */ + { 25, 0x0091}, /* R25 - Rch Input Volume Control */ + { 26, 0x0000}, /* R26 - Timer Select */ + { 27, 0x00E1}, /* R27 - ALC Mode Control 1 */ + { 28, 0x0000}, /* R28 - ALC Mode Control 2 */ + { 29, 0x0000}, /* R29 - Mode Control 1 */ + { 30, 0x0000}, /* R30 - Mode Control 2 */ +}; + +static bool ak5702_writeable(struct device *dev, unsigned int reg) +{ + return reg < AK5702_MAX_REGISTER; +} + +static const unsigned int mic_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 2, TLV_DB_SCALE_ITEM(0, 1500, 0), + 3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0), +}; + +static const DECLARE_TLV_DB_SCALE(in_tlv, -5400, 37, 1); + +static const char *ak5702_adca_left12_input[] = { "Left1", "Left2" }; +static const char *ak5702_adca_right12_input[] = { "Right1", "Right2" }; +static const char *ak5702_adca_left5_input[] = { "Left1/2", "Left5" }; +static const char *ak5702_adca_right5_input[] = { "Right1/2", "Right5" }; + +static const char *ak5702_adcb_left12_input[] = { "Left3", "Left4" }; +static const char *ak5702_adcb_right12_input[] = { "Right3", "Right4" }; +static const char *ak5702_adcb_left5_input[] = { "Left3-4", "Left5" }; +static const char *ak5702_adcb_right5_input[] = { "Right3-4", "Right5" }; + +static const struct soc_enum ak5702_enum[] = { + SOC_ENUM_SINGLE(AK5702_SIG1, 0, 2, ak5702_adca_left12_input), + SOC_ENUM_SINGLE(AK5702_SIG1, 1, 2, ak5702_adca_right12_input), + SOC_ENUM_SINGLE(AK5702_SIG2, 0, 2, ak5702_adcb_left12_input), + SOC_ENUM_SINGLE(AK5702_SIG2, 1, 2, ak5702_adcb_right12_input), + SOC_ENUM_SINGLE(AK5702_SIG1, 5, 2, ak5702_adca_left5_input), + SOC_ENUM_SINGLE(AK5702_SIG1, 6, 2, ak5702_adca_right5_input), + SOC_ENUM_SINGLE(AK5702_SIG2, 5, 2, ak5702_adcb_left5_input), + SOC_ENUM_SINGLE(AK5702_SIG2, 6, 2, ak5702_adcb_right5_input), +}; + +static const struct snd_kcontrol_new ak5702_snd_controls[] = { + SOC_ENUM("ADCA Left1/2 Capture Source", ak5702_enum[0]), + SOC_ENUM("ADCA Right1/2 Capture Source", ak5702_enum[1]), + SOC_ENUM("ADCB Left1/2 Capture Source", ak5702_enum[2]), + SOC_ENUM("ADCB Right1/2 Capture Source", ak5702_enum[3]), + SOC_ENUM("ADCA Left5 Capture Source", ak5702_enum[4]), + SOC_ENUM("ADCA Right5 Capture Source", ak5702_enum[5]), + SOC_ENUM("ADCB Left5 Capture Source", ak5702_enum[6]), + SOC_ENUM("ADCB Right5 Capture Source", ak5702_enum[7]), + SOC_SINGLE_TLV("Mic A Boost Gain", AK5702_MICG1, 0, 3, 0, mic_tlv), + SOC_SINGLE_TLV("Mic B Boost Gain", AK5702_MICG2, 0, 3, 0, mic_tlv), + SOC_DOUBLE_R_TLV("ADCA Capture Volume", AK5702_LVOL1, AK5702_RVOL1, 0, + 241, 0, in_tlv), + SOC_DOUBLE_R_TLV("ADCB Capture Volume", AK5702_LVOL2, AK5702_RVOL2, 0, + 241, 0, in_tlv), +}; + +/* ak5702 dapm widgets */ +static const struct snd_soc_dapm_widget ak5702_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADCA Left", "Capture", AK5702_PM1, 0, 0), + SND_SOC_DAPM_ADC("ADCA Right", "Capture", AK5702_PM1, 1, 0), + SND_SOC_DAPM_ADC("ADCB Left", "Capture", AK5702_PM2, 0, 0), + SND_SOC_DAPM_ADC("ADCB Right", "Capture", AK5702_PM2, 1, 0), + + SND_SOC_DAPM_INPUT("ADCA Left Input"), + SND_SOC_DAPM_INPUT("ADCA Right Input"), + SND_SOC_DAPM_INPUT("ADCB Left Input"), + SND_SOC_DAPM_INPUT("ADCB Right Input"), +}; + +static const struct snd_soc_dapm_route ak5702_dapm_routes[] = { + {"ADCA Left", NULL, "ADCA Left Input"}, + {"ADCA Right", NULL, "ADCA Right Input"}, + {"ADCB Left", NULL, "ADCB Left Input"}, + {"ADCB Right", NULL, "ADCB Right Input"}, +}; + +static int ak5702_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct ak5702_priv *ak5702 = snd_soc_codec_get_drvdata(codec); + u8 value; + + switch (level) { + case SND_SOC_BIAS_ON: + /* power on ADCA */ + value = AK5702_PM1_PMADAL | + AK5702_PM1_PMADAR | + AK5702_PM1_PMVCM; + snd_soc_write(codec, AK5702_PM1, value); + /* power up ADCB */ + value = AK5702_PM2_PMADBL | AK5702_PM2_PMADBR; + snd_soc_write(codec, AK5702_PM2, value); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) + regcache_sync(ak5702->regmap); + /* power down ADCA */ + value = AK5702_POWERDOWN | AK5702_PM1_PMVCM; + snd_soc_write(codec, AK5702_PM1, value); + /* power down ADCB */ + value = AK5702_POWERDOWN; + snd_soc_write(codec, AK5702_PM2, value); + break; + case SND_SOC_BIAS_OFF: + /* power down */ + value = AK5702_POWERDOWN; + snd_soc_write(codec, AK5702_PM1, value); + value = AK5702_POWERDOWN; + snd_soc_write(codec, AK5702_PM2, value); + regcache_mark_dirty(ak5702->regmap); + break; + } + codec->dapm.bias_level = level; + return 0; +} + +static int ak5702_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + u8 value; + struct snd_soc_codec *codec = dai->codec; + struct ak5702_priv *ak5702 = snd_soc_codec_get_drvdata(codec); + + pr_debug("Entered %s\n", __func__); + + switch (params_channels(params)) { + case 2: + snd_soc_update_bits(codec, AK5702_FMT1, AK5702_FMT1_TDM_MASK, + AK5702_FMT1_STEREO); + break; + case 8: + if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) + value = AK5702_FMT1_TDM128; + else if (params_format(params) == SNDRV_PCM_FORMAT_S32_LE) + value = AK5702_FMT1_TDM256; + else + return -EINVAL; + snd_soc_update_bits(codec, AK5702_FMT1, AK5702_FMT1_TDM_MASK, + value); + break; + default: + return -EINVAL; + } + + switch (params_rate(params)) { + case 48000: + if (ak5702->clock_mode == AK5702_CLKPLL_SLAVE2) + value = AK5702_FS1_BCLK_MODE2; + else if (ak5702->clock_mode == AK5702_CLKEXT_SLAVE) + value = AK5702_FS1_SLAVE_256FS; /* mode 3 256fs*/ + else + return -EINVAL; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, AK5702_FS1, + AK5702_FS1_FS_MASK | AK5702_FS1_BCKO_MASK, value); + return 0; +} + +static int ak5702_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ak5702_priv *ak5702 = snd_soc_codec_get_drvdata(codec); + u8 value = 0; + + pr_debug("Entered %s\n", __func__); + + switch (clk_id) { + case AK5702_CLKPLL_MASTER: + case AK5702_CLKEXT_MASTER: + if (dir != SND_SOC_CLOCK_OUT) + return -EINVAL; + break; + case AK5702_CLKPLL_SLAVE: + case AK5702_CLKPLL_SLAVE2: + if (dir != SND_SOC_CLOCK_IN) + return -EINVAL; + value = AK5702_PLL1_POWERUP | + AK5702_PLL1_SLAVE | + AK5702_PLL1_MODE2; + break; + case AK5702_CLKEXT_SLAVE: + if (dir != SND_SOC_CLOCK_IN) + return -EINVAL; + value = AK5702_PLL1_POWERDOWN | AK5702_PLL1_SLAVE; + break; + default: + return -EINVAL; + } + snd_soc_write(codec, AK5702_PLL1, value); + ak5702->clock_mode = clk_id; + return 0; +} + +static int ak5702_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 value = 0; + pr_debug("Entered %s", __func__); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_I2S: + value = AK5702_FMT1_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + value = AK5702_FMT1_MSB; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, AK5702_FMT1, AK5702_FMT1_FMT_MASK, value); + snd_soc_write(codec, AK5702_FMT2, AK5702_FMT2_STEREO); + return 0; +} + +static int ak5702_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 reg = 0; + pr_debug("Entered %s", __func__); + reg = snd_soc_read(codec, AK5702_PLL1); + switch (pll_id) { + case AK5702_PLL_POWERDOWN: + reg &= (~AK5702_PLL1_PM_MASK); + reg |= AK5702_PLL1_POWERDOWN; + break; + case AK5702_PLL_MASTER: + reg &= (~AK5702_PLL1_MODE_MASK); + reg |= AK5702_PLL1_MASTER | AK5702_PLL1_POWERUP; + break; + case AK5702_PLL_SLAVE: + reg &= (~AK5702_PLL1_MODE_MASK); + reg |= AK5702_PLL1_SLAVE | AK5702_PLL1_POWERUP; + break; + default: + return -ENODEV; + } + reg &= (~AK5702_PLL1_PLL_MASK); + switch (freq_in) { + case 11289600: + reg |= AK5702_PLL1_11289600; + break; + case 12000000: + reg |= AK5702_PLL1_12000000; + break; + case 12288000: + reg |= AK5702_PLL1_12288000; + break; + case 19200000: + reg |= AK5702_PLL1_19200000; + break; + default: + return -ENODEV; + } + snd_soc_write(codec, AK5702_PLL1, reg); + return 0; +} + +static int ak5702_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 value = 0; + pr_debug("Entered %s", __func__); + if (div_id == AK5702_BCLK_CLKDIV) { + switch (div) { + case AK5702_BCLK_DIV_32: + value = AK5702_FS1_BCKO_32FS; + break; + case AK5702_BCLK_DIV_64: + value = AK5702_FS1_BCKO_64FS; + break; + default: + return -EINVAL; + } + } + snd_soc_update_bits(codec, AK5702_FS1, AK5702_FS1_BCKO_MASK, value); + return 0; +} + +#ifdef CONFIG_PM +static int ak5702_suspend(struct snd_soc_codec *codec) +{ + ak5702_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ak5702_resume(struct snd_soc_codec *codec) +{ + ak5702_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} +#else +#define ak5702_suspend NULL +#define ak5702_resume NULL +#endif + +static const struct snd_soc_dai_ops ak5702_ops = { + .set_sysclk = ak5702_set_dai_sysclk, + .set_pll = ak5702_set_dai_pll, + .set_clkdiv = ak5702_set_dai_clkdiv, + .set_fmt = ak5702_set_dai_fmt, + .hw_params = ak5702_dai_hw_params, +}; + +struct snd_soc_dai_driver ak5702_dai = { + .name = "ak5702-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &ak5702_ops, +}; + +static int ak5702_probe(struct snd_soc_codec *codec) +{ + struct ak5702_priv *ak5702 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + dev_info(codec->dev, "AK5702 Audio Codec %s", AK5702_VERSION); + + codec->control_data = ak5702->regmap; + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_REGMAP); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + ak5702_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return ret; +} + +static int ak5702_remove(struct snd_soc_codec *codec) +{ + ak5702_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static const struct regmap_config ak5702_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK5702_MAX_REGISTER, + .writeable_reg = ak5702_writeable, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ak5702_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ak5702_reg_defaults), +}; + +static __devinit int ak5702_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ak5702_priv *ak5702; + int ret; + pr_debug("Entered %s", __func__); + + ak5702 = devm_kzalloc(&i2c->dev, sizeof(struct ak5702_priv), + GFP_KERNEL); + if (!ak5702) + return -ENOMEM; + + ak5702->regmap = devm_regmap_init_i2c(i2c, &ak5702_regmap); + if (IS_ERR(ak5702->regmap)) { + ret = PTR_ERR(ak5702->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + i2c_set_clientdata(i2c, ak5702); + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_ak5702, &ak5702_dai, 1); + if (ret != 0) + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + + return ret; +} + +static __devexit int ak5702_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id ak5702_i2c_id[] = { + { "ak5702-codec", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ak5702_i2c_id); + +static struct i2c_driver ak5702_i2c_driver = { + .driver = { + .name = "ak5702-codec", + .owner = THIS_MODULE, + }, + .probe = ak5702_i2c_probe, + .remove = __devexit_p(ak5702_i2c_remove), + .id_table = ak5702_i2c_id, +}; + +struct snd_soc_codec_driver soc_codec_dev_ak5702 = { + .probe = ak5702_probe, + .remove = ak5702_remove, + .suspend = ak5702_suspend, + .resume = ak5702_resume, + .set_bias_level = ak5702_set_bias_level, + .controls = ak5702_snd_controls, + .num_controls = ARRAY_SIZE(ak5702_snd_controls), + .dapm_widgets = ak5702_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak5702_dapm_widgets), + .dapm_routes = ak5702_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ak5702_dapm_routes), +}; + +module_i2c_driver(ak5702_i2c_driver); + +MODULE_DESCRIPTION("Asahi Kasei AK5702 ALSA SoC driver"); +MODULE_AUTHOR("Paolo Doz <paolo.doz@gmail.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak5702.h b/sound/soc/codecs/ak5702.h new file mode 100644 index 0000000..a492a61 --- /dev/null +++ b/sound/soc/codecs/ak5702.h @@ -0,0 +1,171 @@ +/* + * ak5702.h -- AK5702 Soc Audio driver + * + * Author: Paolo Doz paolo.doz@gmail.com + * Copyright: (C) 2012 Soft In S.r.l. + * 2009 Freescale Semiconductor, Inc. All rights reserved. + * + * 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. + * + * This code comes from the original Freescale Ak5702 Audio driver + */ + +#ifndef _AK5702_H +#define _AK5702_H + +/* AK5702 register space */ + +#define AK5702_PM1 0x00 +#define AK5702_PLL1 0x01 +#define AK5702_SIG1 0x02 +#define AK5702_MICG1 0x03 +#define AK5702_FMT1 0x04 +#define AK5702_FS1 0x05 +#define AK5702_CLK1 0x06 +#define AK5702_VOLCTRL1 0x07 +#define AK5702_LVOL1 0x08 +#define AK5702_RVOL1 0x09 +#define AK5702_TIMER1 0x0A +#define AK5702_ALC11 0x0B +#define AK5702_ALC12 0x0C +#define AK5702_MODE11 0x0D +#define AK5702_MODE12 0x0E +#define AK5702_MODE13 0x0F + +#define AK5702_PM2 0x10 +#define AK5702_PLL2 0x11 +#define AK5702_SIG2 0x12 +#define AK5702_MICG2 0x13 +#define AK5702_FMT2 0x14 +#define AK5702_FS2 0x15 +#define AK5702_CLK2 0x16 +#define AK5702_VOLCTRL2 0x17 +#define AK5702_LVOL2 0x18 +#define AK5702_RVOL2 0x19 +#define AK5702_TIMER2 0x1A +#define AK5702_ALC21 0x1B +#define AK5702_ALC22 0x1C +#define AK5702_MODE21 0x1D +#define AK5702_MODE22 0x1E + +#define AK5702_MAX_REGISTER 0x1F + +#define AK5702_CACHEREGNUM 0x1F + +#define AK5702_POWERDOWN 0x0 +#define AK5702_PM1_PMADAL 0x01 +#define AK5702_PM1_PMADAR 0x02 +#define AK5702_PM1_PMVCM 0x04 +#define AK5702_PM2_PMADBL 0x01 +#define AK5702_PM2_PMADBR 0x02 + +#define AK5702_PLL1_POWERDOWN 0x0 +#define AK5702_PLL1_POWERUP 0x01 +#define AK5702_PLL1_MASTER 0x02 +#define AK5702_PLL1_SLAVE 0x0 +#define AK5702_PLL1_MODE0 0x0 +#define AK5702_PLL1_MODE2 0x08 +#define AK5702_PLL1_MODE3 0x0C +#define AK5702_PLL1_MODE4 0x10 +#define AK5702_PLL1_MODE5 0x14 +#define AK5702_PLL1_MODE6 0x18 +#define AK5702_PLL1_MODE7 0x1C +#define AK5702_PLL1_MODE8 0x20 +#define AK5702_PLL1_MODE9 0x24 +#define AK5702_PLL1_MODE12 0x30 +#define AK5702_PLL1_MODE13 0x34 +#define AK5702_PLL1_MODE14 0x38 +#define AK5702_PLL1_MODE15 0x3C +#define AK5702_PLL1_11289600 0x10 +#define AK5702_PLL1_12000000 0x24 +#define AK5702_PLL1_12288000 0x14 +#define AK5702_PLL1_19200000 0x20 + +#define AK5702_FS1_MCKI_MODE0 0x0 +#define AK5702_FS1_MCKI_MODE1 0x01 +#define AK5702_FS1_MCKI_MODE2 0x02 +#define AK5702_FS1_MCKI_MODE3 0x03 +#define AK5702_FS1_MCKI_MODE4 0x04 +#define AK5702_FS1_MCKI_MODE5 0x05 +#define AK5702_FS1_MCKI_MODE6 0x06 +#define AK5702_FS1_MCKI_MODE7 0x07 +#define AK5702_FS1_MCKI_MODE10 0x0A +#define AK5702_FS1_MCKI_MODE11 0x0B +#define AK5702_FS1_MCKI_MODE14 0x0E +#define AK5702_FS1_MCKI_MODE15 0x0F + +#define AK5702_FS1_BCLK_MODE0 0x0 +#define AK5702_FS1_BCLK_MODE1 0x04 +#define AK5702_FS1_BCLK_MODE2 0x08 + +#define AK5702_FS1_SLAVE_1024FS 0x01 +#define AK5702_FS1_SLAVE_512FS 0x02 +#define AK5702_FS1_SLAVE_256FS 0x03 + +#define AK5702_SIG1_L_LIN1 0x0 +#define AK5702_SIG1_L_LIN2 0x01 +#define AK5702_SIG1_R_RIN1 0x0 +#define AK5702_SIG1_R_RIN2 0x02 +#define AK5702_SIG1_PMMPA 0x10 +#define AK5702_SIG1_L_LIN5 0x20 +#define AK5702_SIG1_R_RIN5 0x40 +#define AK5702_SIG2_L_LIN3 0x0 +#define AK5702_SIG2_L_LIN4 0x01 +#define AK5702_SIG2_R_RIN3 0x0 +#define AK5702_SIG2_R_RIN4 0x02 +#define AK5702_SIG2_PMMPB 0x10 + +#define AK5702_MICGAIN_0DB 0x0 + +#define AK5702_FMT1_I2S 0x03 +#define AK5702_FMT1_MSB 0x02 +#define AK5702_FMT1_STEREO 0x00 +#define AK5702_FMT1_TDM128 0x60 +#define AK5702_FMT1_TDM256 0xE0 +#define AK5702_FMT2_STEREO 0x20 +#define AK5702_FS1_BCKO_32FS 0x10 +#define AK5702_FS1_BCKO_64FS 0x20 +#define AK5702_CLK1_PS_256FS 0x0 +#define AK5702_CLK1_PS_128FS 0x01 +#define AK5702_CLK1_PS_64FS 0x02 +#define AK5702_CLK1_PS_32FS 0x03 +#define AK5702_VOLCTRL_INDEP 0x0 +#define AK5702_VOLCTRL_DEP 0x01 +#define AK5702_VOL_INIT 0x91 + +#define AK5702_PM1_MASK 0x07 +#define AK5702_PM2_MASK 0x03 +#define AK5702_PLL1_PM_MASK 0x01 +#define AK5702_PLL1_MODE_MASK 0x02 +#define AK5702_PLL1_PLL_MASK 0x3C +#define AK5702_FS1_BCKO_MASK 0x30 +#define AK5702_FS1_FS_MASK 0x0F +#define AK5702_CLK1_PS_MASK 0x03 +#define AK5702_FMT1_FMT_MASK 0x03 +#define AK5702_FMT1_TDM_MASK 0xC0 +#define AK5702_FMT2_OUT_MASK 0x10 +/* clock divider id */ +#define AK5702_BCLK_CLKDIV 0 +#define AK5702_MCLK_CLKDIV 1 + +/* bit clock div values */ +#define AK5702_BCLK_DIV_32 0 +#define AK5702_BCLK_DIV_64 1 + +/* m clock div values */ +#define AK5702_MCLK_DIV_32 0 +#define AK5702_MCLK_DIV_64 1 +#define AK5702_MCLK_DIV_128 2 +#define AK5702_MCLK_DIV_256 3 + +/* PLL master and slave modes */ +#define AK5702_PLL_POWERDOWN 0 +#define AK5702_PLL_MASTER 1 +#define AK5702_PLL_SLAVE 2 + +extern struct snd_soc_dai_driver ak5702_dai; +extern struct snd_soc_codec_driver soc_codec_dev_ak5702; + +#endif

On Wed, Nov 28, 2012 at 04:54:49PM +0100, Paolo Doz wrote:
+enum ak5702_clock_mode {
- AK5702_CLKPLL_SLAVE,
- AK5702_CLKPLL_SLAVE2,
The number one problem with this patch is that it's not following the kernel coding style at all - please see CodingStyle, checkpatch should also complain a lot about this.
+#define AK5702_VERSION "0.5" +#define DRV_NAME "ak5702"
The kernel already has perfectly good version numbers, use them - people aren't going to keep driver specific version numbers up to date anyway.
+static const struct reg_default ak5702_reg_defaults[] = {
- { 0, 0x0000}, /* R0 - Power Management */
Looks a big odd - I'd expect spaces for both { and }.
+static bool ak5702_writeable(struct device *dev, unsigned int reg) +{
- return reg < AK5702_MAX_REGISTER;
Should be <= or the #define is misnamed.
+static const struct soc_enum ak5702_enum[] = {
- SOC_ENUM_SINGLE(AK5702_SIG1, 0, 2, ak5702_adca_left12_input),
- SOC_ENUM_SINGLE(AK5702_SIG1, 1, 2, ak5702_adca_right12_input),
Don't use a big array of enums, it's error prone and hard to read. Use individually named enums like other drivers.
- SOC_ENUM("ADCA Left1/2 Capture Source", ak5702_enum[0]),
- SOC_ENUM("ADCA Right1/2 Capture Source", ak5702_enum[1]),
- SOC_ENUM("ADCB Left1/2 Capture Source", ak5702_enum[2]),
- SOC_ENUM("ADCB Right1/2 Capture Source", ak5702_enum[3]),
- SOC_ENUM("ADCA Left5 Capture Source", ak5702_enum[4]),
- SOC_ENUM("ADCA Right5 Capture Source", ak5702_enum[5]),
- SOC_ENUM("ADCB Left5 Capture Source", ak5702_enum[6]),
- SOC_ENUM("ADCB Right5 Capture Source", ak5702_enum[7]),
These should all be DAPM controls.
- SOC_SINGLE_TLV("Mic A Boost Gain", AK5702_MICG1, 0, 3, 0, mic_tlv),
- SOC_SINGLE_TLV("Mic B Boost Gain", AK5702_MICG2, 0, 3, 0, mic_tlv),
Volume, not Gain.
/* power on ADCA */
value = AK5702_PM1_PMADAL |
AK5702_PM1_PMADAR |
AK5702_PM1_PMVCM;
snd_soc_write(codec, AK5702_PM1, value);
/* power up ADCB */
value = AK5702_PM2_PMADBL | AK5702_PM2_PMADBR;
snd_soc_write(codec, AK5702_PM2, value);
Just inline the value to write.
if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)
value = AK5702_FMT1_TDM128;
else if (params_format(params) == SNDRV_PCM_FORMAT_S32_LE)
value = AK5702_FMT1_TDM256;
else
return -EINVAL;
Use a switch statement for the check for params_format().
- case 48000:
if (ak5702->clock_mode == AK5702_CLKPLL_SLAVE2)
value = AK5702_FS1_BCLK_MODE2;
else if (ak5702->clock_mode == AK5702_CLKEXT_SLAVE)
value = AK5702_FS1_SLAVE_256FS; /* mode 3 256fs*/
else
return -EINVAL;
Similarly here.
+static int ak5702_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
int source, unsigned int freq_in, unsigned int freq_out)
+{
- struct snd_soc_codec *codec = codec_dai->codec;
- u8 reg = 0;
- pr_debug("Entered %s", __func__);
- reg = snd_soc_read(codec, AK5702_PLL1);
- switch (pll_id) {
- case AK5702_PLL_POWERDOWN:
reg &= (~AK5702_PLL1_PM_MASK);
reg |= AK5702_PLL1_POWERDOWN;
break;
pll_id should be which PLL is being changed, power down is done by setting the PLL output to zero.
- case AK5702_PLL_MASTER:
reg &= (~AK5702_PLL1_MODE_MASK);
reg |= AK5702_PLL1_MASTER | AK5702_PLL1_POWERUP;
break;
- case AK5702_PLL_SLAVE:
These probably want to be selected by source.
reg &= (~AK5702_PLL1_MODE_MASK);
reg |= AK5702_PLL1_SLAVE | AK5702_PLL1_POWERUP;
snd_soc_update_bits()
- default:
return -ENODEV;
-EINVAL.
- if (div_id == AK5702_BCLK_CLKDIV) {
switch.
- dev_info(codec->dev, "AK5702 Audio Codec %s", AK5702_VERSION);
Remove this, it's just adding noise to the boot.
- codec->control_data = ak5702->regmap;
No need for this, the framework will use dev_get_regmap().
+static const struct i2c_device_id ak5702_i2c_id[] = {
- { "ak5702-codec", 0 },
No -codec, this is a single function device and it appears to be an ADC rather than a CODEC anyway.
- .driver = {
.name = "ak5702-codec",
No -codec.
participants (2)
-
Mark Brown
-
Paolo Doz