diff options
Diffstat (limited to 'sound/soc/codecs')
32 files changed, 4833 insertions, 603 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e239345a4d5..6943e24a74a 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -29,16 +29,21 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI select SND_SOC_CX20442 select SND_SOC_DA7210 if I2C + select SND_SOC_DFBMCS320 select SND_SOC_JZ4740_CODEC if SOC_JZ4740 + select SND_SOC_LM4857 if I2C select SND_SOC_MAX98088 if I2C + select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 + select SND_SOC_SGTL5000 if I2C select SND_SOC_SN95031 if INTEL_SCU_IPC select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER + select SND_SOC_TVL320AIC32X4 if I2C select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C @@ -46,7 +51,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TWL6040 if TWL4030_CORE select SND_SOC_UDA134X select SND_SOC_UDA1380 if I2C - select SND_SOC_WL1273 if RADIO_WL1273 + select SND_SOC_WL1273 if MFD_WL1273_CORE select SND_SOC_WM2000 if I2C select SND_SOC_WM8350 if MFD_WM8350 select SND_SOC_WM8400 if MFD_WM8400 @@ -173,15 +178,25 @@ config SND_SOC_L3 config SND_SOC_DA7210 tristate +config SND_SOC_DFBMCS320 + tristate + config SND_SOC_DMIC tristate config SND_SOC_MAX98088 tristate +config SND_SOC_MAX9850 + tristate + config SND_SOC_PCM3008 tristate +#Freescale sgtl5000 codec +config SND_SOC_SGTL5000 + tristate + config SND_SOC_SN95031 tristate @@ -201,6 +216,9 @@ config SND_SOC_TLV320AIC26 tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE depends on SPI +config SND_SOC_TVL320AIC32X4 + tristate + config SND_SOC_TLV320AIC3X tristate @@ -338,6 +356,9 @@ config SND_SOC_WM9713 tristate # Amp +config SND_SOC_LM4857 + tristate + config SND_SOC_MAX9877 tristate @@ -349,4 +370,3 @@ config SND_SOC_WM2000 config SND_SOC_WM9090 tristate - diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 83b7accd703..379bc55f072 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -15,10 +15,13 @@ snd-soc-cs4270-objs := cs4270.o snd-soc-cs4271-objs := cs4271.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o +snd-soc-dfbmcs320-objs := dfbmcs320.o snd-soc-dmic-objs := dmic.o snd-soc-l3-objs := l3.o snd-soc-max98088-objs := max98088.o +snd-soc-max9850-objs := max9850.o snd-soc-pcm3008-objs := pcm3008.o +snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-alc5623-objs := alc5623.o snd-soc-sn95031-objs := sn95031.o snd-soc-spdif-objs := spdif_transciever.o @@ -27,6 +30,7 @@ snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o +snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o snd-soc-tlv320dac33-objs := tlv320dac33.o snd-soc-twl4030-objs := twl4030.o snd-soc-twl6040-objs := twl6040.o @@ -75,6 +79,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-jz4740-codec-objs := jz4740.o # Amp +snd-soc-lm4857-objs := lm4857.o snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o snd-soc-wm2000-objs := wm2000.o @@ -91,18 +96,21 @@ obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o +obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o +obj-$(CONFIG_SND_SOC_DFBMCS320) += snd-soc-dfbmcs320.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o +obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o -obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o +obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o @@ -110,6 +118,7 @@ obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o +obj-$(CONFIG_SND_SOC_TVL320AIC32X4) += snd-soc-tlv320aic32x4.o obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o @@ -157,6 +166,7 @@ obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp +obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c index c27f8f59dc6..cbf0b6d400b 100644 --- a/sound/soc/codecs/ak4104.c +++ b/sound/soc/codecs/ak4104.c @@ -294,7 +294,6 @@ static struct spi_driver ak4104_spi_driver = { static int __init ak4104_init(void) { - pr_info("Asahi Kasei AK4104 ALSA SoC Codec Driver\n"); return spi_register_driver(&ak4104_spi_driver); } module_init(ak4104_init); diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c index 347a567b01e..b8066ef10bb 100644 --- a/sound/soc/codecs/cq93vc.c +++ b/sound/soc/codecs/cq93vc.c @@ -153,7 +153,8 @@ static int cq93vc_resume(struct snd_soc_codec *codec) static int cq93vc_probe(struct snd_soc_codec *codec) { - struct davinci_vc *davinci_vc = snd_soc_codec_get_drvdata(codec); + struct davinci_vc *davinci_vc = + mfd_get_data(to_platform_device(codec->dev)); davinci_vc->cq93vc.codec = codec; codec->control_data = davinci_vc; diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index c0fccadaea9..0206a17d728 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -719,7 +719,7 @@ static int cs4270_i2c_remove(struct i2c_client *i2c_client) /* * cs4270_id - I2C device IDs supported by this driver */ -static struct i2c_device_id cs4270_id[] = { +static const struct i2c_device_id cs4270_id[] = { {"cs4270", 0}, {} }; @@ -743,8 +743,6 @@ static struct i2c_driver cs4270_i2c_driver = { static int __init cs4270_init(void) { - pr_info("Cirrus Logic CS4270 ALSA SoC Codec Driver\n"); - return i2c_add_driver(&cs4270_i2c_driver); } module_init(cs4270_init); diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index 9c5b7db0ce6..083aab96ca8 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -33,6 +33,7 @@ #define CS4271_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE) +#define CS4271_PCM_RATES SNDRV_PCM_RATE_8000_192000 /* * CS4271 registers @@ -167,27 +168,6 @@ struct cs4271_private { int gpio_disable; }; -struct cs4271_clk_cfg { - unsigned int ratio; /* MCLK / sample rate */ - u8 speed_mode; /* codec speed mode: 1x, 2x, 4x */ - u8 mclk_master; /* ratio bit mask for Master mode */ - u8 mclk_slave; /* ratio bit mask for Slave mode */ -}; - -static struct cs4271_clk_cfg cs4271_clk_tab[] = { - {64, CS4271_MODE1_MODE_4X, CS4271_MODE1_DIV_1, CS4271_MODE1_DIV_1}, - {96, CS4271_MODE1_MODE_4X, CS4271_MODE1_DIV_15, CS4271_MODE1_DIV_1}, - {128, CS4271_MODE1_MODE_2X, CS4271_MODE1_DIV_1, CS4271_MODE1_DIV_1}, - {192, CS4271_MODE1_MODE_2X, CS4271_MODE1_DIV_15, CS4271_MODE1_DIV_1}, - {256, CS4271_MODE1_MODE_1X, CS4271_MODE1_DIV_1, CS4271_MODE1_DIV_1}, - {384, CS4271_MODE1_MODE_1X, CS4271_MODE1_DIV_15, CS4271_MODE1_DIV_1}, - {512, CS4271_MODE1_MODE_1X, CS4271_MODE1_DIV_2, CS4271_MODE1_DIV_1}, - {768, CS4271_MODE1_MODE_1X, CS4271_MODE1_DIV_3, CS4271_MODE1_DIV_3}, - {1024, CS4271_MODE1_MODE_1X, CS4271_MODE1_DIV_3, CS4271_MODE1_DIV_3} -}; - -#define CS4171_NR_RATIOS ARRAY_SIZE(cs4271_clk_tab) - /* * @freq is the desired MCLK rate * MCLK rate should (c) be the sample rate, multiplied by one of the @@ -296,6 +276,45 @@ static int cs4271_put_deemph(struct snd_kcontrol *kcontrol, return cs4271_set_deemph(codec); } +struct cs4271_clk_cfg { + bool master; /* codec mode */ + u8 speed_mode; /* codec speed mode: 1x, 2x, 4x */ + unsigned short ratio; /* MCLK / sample rate */ + u8 ratio_mask; /* ratio bit mask for Master mode */ +}; + +static struct cs4271_clk_cfg cs4271_clk_tab[] = { + {1, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1}, + {1, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_15}, + {1, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_2}, + {1, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_3}, + {1, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1}, + {1, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_15}, + {1, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_2}, + {1, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_3}, + {1, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1}, + {1, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_15}, + {1, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_2}, + {1, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_3}, + {0, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_1X, 1024, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_2X, 512, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_4X, 256, CS4271_MODE1_DIV_2}, +}; + +#define CS4171_NR_RATIOS ARRAY_SIZE(cs4271_clk_tab) + static int cs4271_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -307,23 +326,28 @@ static int cs4271_hw_params(struct snd_pcm_substream *substream, unsigned int ratio, val; cs4271->rate = params_rate(params); + + /* Configure DAC */ + if (cs4271->rate < 50000) + val = CS4271_MODE1_MODE_1X; + else if (cs4271->rate < 100000) + val = CS4271_MODE1_MODE_2X; + else + val = CS4271_MODE1_MODE_4X; + ratio = cs4271->mclk / cs4271->rate; for (i = 0; i < CS4171_NR_RATIOS; i++) - if (cs4271_clk_tab[i].ratio == ratio) + if ((cs4271_clk_tab[i].master == cs4271->master) && + (cs4271_clk_tab[i].speed_mode == val) && + (cs4271_clk_tab[i].ratio == ratio)) break; - if ((i == CS4171_NR_RATIOS) || ((ratio == 1024) && cs4271->master)) { + if (i == CS4171_NR_RATIOS) { dev_err(codec->dev, "Invalid sample rate\n"); return -EINVAL; } - /* Configure DAC */ - val = cs4271_clk_tab[i].speed_mode; - - if (cs4271->master) - val |= cs4271_clk_tab[i].mclk_master; - else - val |= cs4271_clk_tab[i].mclk_slave; + val |= cs4271_clk_tab[i].ratio_mask; ret = snd_soc_update_bits(codec, CS4271_MODE1, CS4271_MODE1_MODE_MASK | CS4271_MODE1_DIV_MASK, val); @@ -392,14 +416,14 @@ static struct snd_soc_dai_driver cs4271_dai = { .stream_name = "Playback", .channels_min = 2, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_96000, + .rates = CS4271_PCM_RATES, .formats = CS4271_PCM_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 2, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_96000, + .rates = CS4271_PCM_RATES, .formats = CS4271_PCM_FORMATS, }, .ops = &cs4271_dai_ops, @@ -441,22 +465,11 @@ static int cs4271_probe(struct snd_soc_codec *codec) struct cs4271_platform_data *cs4271plat = codec->dev->platform_data; int ret; int gpio_nreset = -EINVAL; - int gpio_disable = -EINVAL; codec->control_data = cs4271->control_data; - if (cs4271plat) { - if (gpio_is_valid(cs4271plat->gpio_nreset)) - gpio_nreset = cs4271plat->gpio_nreset; - if (gpio_is_valid(cs4271plat->gpio_disable)) - gpio_disable = cs4271plat->gpio_disable; - } - - if (gpio_disable >= 0) - if (gpio_request(gpio_disable, "CS4271 Disable")) - gpio_disable = -EINVAL; - if (gpio_disable >= 0) - gpio_direction_output(gpio_disable, 0); + if (cs4271plat && gpio_is_valid(cs4271plat->gpio_nreset)) + gpio_nreset = cs4271plat->gpio_nreset; if (gpio_nreset >= 0) if (gpio_request(gpio_nreset, "CS4271 Reset")) @@ -471,7 +484,6 @@ static int cs4271_probe(struct snd_soc_codec *codec) } cs4271->gpio_nreset = gpio_nreset; - cs4271->gpio_disable = gpio_disable; /* * In case of I2C, chip address specified in board data. @@ -509,10 +521,9 @@ static int cs4271_probe(struct snd_soc_codec *codec) static int cs4271_remove(struct snd_soc_codec *codec) { struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); - int gpio_nreset, gpio_disable; + int gpio_nreset; gpio_nreset = cs4271->gpio_nreset; - gpio_disable = cs4271->gpio_disable; if (gpio_is_valid(gpio_nreset)) { /* Set codec to the reset state */ @@ -520,9 +531,6 @@ static int cs4271_remove(struct snd_soc_codec *codec) gpio_free(gpio_nreset); } - if (gpio_is_valid(gpio_disable)) - gpio_free(gpio_disable); - return 0; }; @@ -571,7 +579,7 @@ static struct spi_driver cs4271_spi_driver = { #endif /* defined(CONFIG_SPI_MASTER) */ #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) -static struct i2c_device_id cs4271_i2c_id[] = { +static const struct i2c_device_id cs4271_i2c_id[] = { {"cs4271", 0}, {} }; diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index bb4bf65b9e7..0bb424af956 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -367,7 +367,7 @@ static int cx20442_codec_remove(struct snd_soc_codec *codec) return 0; } -static const u8 cx20442_reg = CX20442_TELOUT | CX20442_MIC; +static const u8 cx20442_reg; static struct snd_soc_codec_driver cx20442_codec_dev = { .probe = cx20442_codec_probe, diff --git a/sound/soc/codecs/dfbmcs320.c b/sound/soc/codecs/dfbmcs320.c new file mode 100644 index 00000000000..704bbde6573 --- /dev/null +++ b/sound/soc/codecs/dfbmcs320.c @@ -0,0 +1,72 @@ +/* + * Driver for the DFBM-CS320 bluetooth module + * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/soc.h> + +static struct snd_soc_dai_driver dfbmcs320_dai = { + .name = "dfbmcs320-pcm", + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_dfbmcs320; + +static int __devinit dfbmcs320_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_dfbmcs320, + &dfbmcs320_dai, 1); +} + +static int __devexit dfbmcs320_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +static struct platform_driver dfmcs320_driver = { + .driver = { + .name = "dfbmcs320", + .owner = THIS_MODULE, + }, + .probe = dfbmcs320_probe, + .remove = __devexit_p(dfbmcs320_remove), +}; + +static int __init dfbmcs320_init(void) +{ + return platform_driver_register(&dfmcs320_driver); +} +module_init(dfbmcs320_init); + +static void __exit dfbmcs320_exit(void) +{ + platform_driver_unregister(&dfmcs320_driver); +} +module_exit(dfbmcs320_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("ASoC DFBM-CS320 bluethooth module driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/lm4857.c b/sound/soc/codecs/lm4857.c new file mode 100644 index 00000000000..72de47e5d04 --- /dev/null +++ b/sound/soc/codecs/lm4857.c @@ -0,0 +1,276 @@ +/* + * LM4857 AMP driver + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +struct lm4857 { + struct i2c_client *i2c; + uint8_t mode; +}; + +static const uint8_t lm4857_default_regs[] = { + 0x00, 0x00, 0x00, 0x00, +}; + +/* The register offsets in the cache array */ +#define LM4857_MVOL 0 +#define LM4857_LVOL 1 +#define LM4857_RVOL 2 +#define LM4857_CTRL 3 + +/* the shifts required to set these bits */ +#define LM4857_3D 5 +#define LM4857_WAKEUP 5 +#define LM4857_EPGAIN 4 + +static int lm4857_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + uint8_t data; + int ret; + + ret = snd_soc_cache_write(codec, reg, value); + if (ret < 0) + return ret; + + data = (reg << 6) | value; + ret = i2c_master_send(codec->control_data, &data, 1); + if (ret != 1) { + dev_err(codec->dev, "Failed to write register: %d\n", ret); + return ret; + } + + return 0; +} + +static unsigned int lm4857_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int val; + int ret; + + ret = snd_soc_cache_read(codec, reg, &val); + if (ret) + return -1; + + return val; +} + +static int lm4857_get_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = lm4857->mode; + + return 0; +} + +static int lm4857_set_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); + uint8_t value = ucontrol->value.integer.value[0]; + + lm4857->mode = value; + + if (codec->dapm.bias_level == SND_SOC_BIAS_ON) + snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, value + 6); + + return 1; +} + +static int lm4857_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, lm4857->mode + 6); + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, 0); + break; + default: + break; + } + + codec->dapm.bias_level = level; + + return 0; +} + +static const char *lm4857_mode[] = { + "Earpiece", + "Loudspeaker", + "Loudspeaker + Headphone", + "Headphone", +}; + +static const struct soc_enum lm4857_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode); + +static const struct snd_soc_dapm_widget lm4857_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("IN"), + + SND_SOC_DAPM_OUTPUT("LS"), + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_OUTPUT("EP"), +}; + +static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0); +static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0); + +static const struct snd_kcontrol_new lm4857_controls[] = { + SOC_SINGLE_TLV("Left Playback Volume", LM4857_LVOL, 0, 31, 0, + stereo_tlv), + SOC_SINGLE_TLV("Right Playback Volume", LM4857_RVOL, 0, 31, 0, + stereo_tlv), + SOC_SINGLE_TLV("Mono Playback Volume", LM4857_MVOL, 0, 31, 0, + mono_tlv), + SOC_SINGLE("Spk 3D Playback Switch", LM4857_LVOL, LM4857_3D, 1, 0), + SOC_SINGLE("HP 3D Playback Switch", LM4857_RVOL, LM4857_3D, 1, 0), + SOC_SINGLE("Fast Wakeup Playback Switch", LM4857_CTRL, + LM4857_WAKEUP, 1, 0), + SOC_SINGLE("Earpiece 6dB Playback Switch", LM4857_CTRL, + LM4857_EPGAIN, 1, 0), + + SOC_ENUM_EXT("Mode", lm4857_mode_enum, + lm4857_get_mode, lm4857_set_mode), +}; + +/* There is a demux inbetween the the input signal and the output signals. + * Currently there is no easy way to model it in ASoC and since it does not make + * much of a difference in practice simply connect the input direclty to the + * outputs. */ +static const struct snd_soc_dapm_route lm4857_routes[] = { + {"LS", NULL, "IN"}, + {"HP", NULL, "IN"}, + {"EP", NULL, "IN"}, +}; + +static int lm4857_probe(struct snd_soc_codec *codec) +{ + struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret; + + codec->control_data = lm4857->i2c; + + ret = snd_soc_add_controls(codec, lm4857_controls, + ARRAY_SIZE(lm4857_controls)); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, lm4857_dapm_widgets, + ARRAY_SIZE(lm4857_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, lm4857_routes, + ARRAY_SIZE(lm4857_routes)); + if (ret) + return ret; + + snd_soc_dapm_new_widgets(dapm); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_lm4857 = { + .write = lm4857_write, + .read = lm4857_read, + .probe = lm4857_probe, + .reg_cache_size = ARRAY_SIZE(lm4857_default_regs), + .reg_word_size = sizeof(uint8_t), + .reg_cache_default = lm4857_default_regs, + .set_bias_level = lm4857_set_bias_level, +}; + +static int __devinit lm4857_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct lm4857 *lm4857; + int ret; + + lm4857 = kzalloc(sizeof(*lm4857), GFP_KERNEL); + if (!lm4857) + return -ENOMEM; + + i2c_set_clientdata(i2c, lm4857); + + lm4857->i2c = i2c; + + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_lm4857, NULL, 0); + + if (ret) { + kfree(lm4857); + return ret; + } + + return 0; +} + +static int __devexit lm4857_i2c_remove(struct i2c_client *i2c) +{ + struct lm4857 *lm4857 = i2c_get_clientdata(i2c); + + snd_soc_unregister_codec(&i2c->dev); + kfree(lm4857); + + return 0; +} + +static const struct i2c_device_id lm4857_i2c_id[] = { + { "lm4857", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm4857_i2c_id); + +static struct i2c_driver lm4857_i2c_driver = { + .driver = { + .name = "lm4857", + .owner = THIS_MODULE, + }, + .probe = lm4857_i2c_probe, + .remove = __devexit_p(lm4857_i2c_remove), + .id_table = lm4857_i2c_id, +}; + +static int __init lm4857_init(void) +{ + return i2c_add_driver(&lm4857_i2c_driver); +} +module_init(lm4857_init); + +static void __exit lm4857_exit(void) +{ + i2c_del_driver(&lm4857_i2c_driver); +} +module_exit(lm4857_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("LM4857 amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max9850.c b/sound/soc/codecs/max9850.c new file mode 100644 index 00000000000..208d2ee6185 --- /dev/null +++ b/sound/soc/codecs/max9850.c @@ -0,0 +1,389 @@ +/* + * max9850.c -- codec driver for max9850 + * + * Copyright (C) 2011 taskit GmbH + * + * Author: Christian Glindkamp <christian.glindkamp@taskit.de> + * + * Initial development of this code was funded by + * MICRONIC Computer Systeme GmbH, http://www.mcsberlin.de/ + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#include "max9850.h" + +struct max9850_priv { + unsigned int sysclk; +}; + +/* max9850 register cache */ +static const u8 max9850_reg[MAX9850_CACHEREGNUM] = { + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* these registers are not used at the moment but provided for the sake of + * completeness */ +static int max9850_volatile_register(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + case MAX9850_STATUSA: + case MAX9850_STATUSB: + return 1; + default: + return 0; + } +} + +static const unsigned int max9850_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0x18, 0x1f, TLV_DB_SCALE_ITEM(-7450, 400, 0), + 0x20, 0x33, TLV_DB_SCALE_ITEM(-4150, 200, 0), + 0x34, 0x37, TLV_DB_SCALE_ITEM(-150, 100, 0), + 0x38, 0x3f, TLV_DB_SCALE_ITEM(250, 50, 0), +}; + +static const struct snd_kcontrol_new max9850_controls[] = { +SOC_SINGLE_TLV("Headphone Volume", MAX9850_VOLUME, 0, 0x3f, 1, max9850_tlv), +SOC_SINGLE("Headphone Switch", MAX9850_VOLUME, 7, 1, 1), +SOC_SINGLE("Mono Switch", MAX9850_GENERAL_PURPOSE, 2, 1, 0), +}; + +static const struct snd_kcontrol_new max9850_mixer_controls[] = { + SOC_DAPM_SINGLE("Line In Switch", MAX9850_ENABLE, 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget max9850_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("Charge Pump 1", MAX9850_ENABLE, 4, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("Charge Pump 2", MAX9850_ENABLE, 5, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MCLK", MAX9850_ENABLE, 6, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("SHDN", MAX9850_ENABLE, 7, 0, NULL, 0), +SND_SOC_DAPM_MIXER_NAMED_CTL("Output Mixer", MAX9850_ENABLE, 2, 0, + &max9850_mixer_controls[0], + ARRAY_SIZE(max9850_mixer_controls)), +SND_SOC_DAPM_PGA("Headphone Output", MAX9850_ENABLE, 3, 0, NULL, 0), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", MAX9850_ENABLE, 0, 0), +SND_SOC_DAPM_OUTPUT("OUTL"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("OUTR"), +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_INPUT("INL"), +SND_SOC_DAPM_INPUT("INR"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* output mixer */ + {"Output Mixer", NULL, "DAC"}, + {"Output Mixer", "Line In Switch", "Line Input"}, + + /* outputs */ + {"Headphone Output", NULL, "Output Mixer"}, + {"HPL", NULL, "Headphone Output"}, + {"HPR", NULL, "Headphone Output"}, + {"OUTL", NULL, "Output Mixer"}, + {"OUTR", NULL, "Output Mixer"}, + + /* inputs */ + {"Line Input", NULL, "INL"}, + {"Line Input", NULL, "INR"}, + + /* supplies */ + {"Output Mixer", NULL, "Charge Pump 1"}, + {"Output Mixer", NULL, "Charge Pump 2"}, + {"Output Mixer", NULL, "SHDN"}, + {"DAC", NULL, "MCLK"}, +}; + +static int max9850_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct max9850_priv *max9850 = snd_soc_codec_get_drvdata(codec); + u64 lrclk_div; + u8 sf, da; + + if (!max9850->sysclk) + return -EINVAL; + + /* lrclk_div = 2^22 * rate / iclk with iclk = mclk / sf */ + sf = (snd_soc_read(codec, MAX9850_CLOCK) >> 2) + 1; + lrclk_div = (1 << 22); + lrclk_div *= params_rate(params); + lrclk_div *= sf; + do_div(lrclk_div, max9850->sysclk); + + snd_soc_write(codec, MAX9850_LRCLK_MSB, (lrclk_div >> 8) & 0x7f); + snd_soc_write(codec, MAX9850_LRCLK_LSB, lrclk_div & 0xff); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + da = 0; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + da = 0x2; + break; + case SNDRV_PCM_FORMAT_S24_LE: + da = 0x3; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, MAX9850_DIGITAL_AUDIO, 0x3, da); + + return 0; +} + +static int max9850_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 max9850_priv *max9850 = snd_soc_codec_get_drvdata(codec); + + /* calculate mclk -> iclk divider */ + if (freq <= 13000000) + snd_soc_write(codec, MAX9850_CLOCK, 0x0); + else if (freq <= 26000000) + snd_soc_write(codec, MAX9850_CLOCK, 0x4); + else if (freq <= 40000000) + snd_soc_write(codec, MAX9850_CLOCK, 0x8); + else + return -EINVAL; + + max9850->sysclk = freq; + return 0; +} + +static int max9850_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 da = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + da |= MAX9850_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + da |= MAX9850_DLY; + break; + case SND_SOC_DAIFMT_RIGHT_J: + da |= MAX9850_RTJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + da |= MAX9850_BCINV | MAX9850_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + da |= MAX9850_BCINV; + break; + case SND_SOC_DAIFMT_NB_IF: + da |= MAX9850_INV; + break; + default: + return -EINVAL; + } + + /* set da */ + snd_soc_write(codec, MAX9850_DIGITAL_AUDIO, da); + + return 0; +} + +static int max9850_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + ret = snd_soc_cache_sync(codec); + if (ret) { + dev_err(codec->dev, + "Failed to sync cache: %d\n", ret); + return ret; + } + } + break; + case SND_SOC_BIAS_OFF: + break; + } + codec->dapm.bias_level = level; + return 0; +} + +#define MAX9850_RATES SNDRV_PCM_RATE_8000_48000 + +#define MAX9850_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops max9850_dai_ops = { + .hw_params = max9850_hw_params, + .set_sysclk = max9850_set_dai_sysclk, + .set_fmt = max9850_set_dai_fmt, +}; + +static struct snd_soc_dai_driver max9850_dai = { + .name = "max9850-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX9850_RATES, + .formats = MAX9850_FORMATS + }, + .ops = &max9850_dai_ops, +}; + +#ifdef CONFIG_PM +static int max9850_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + max9850_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int max9850_resume(struct snd_soc_codec *codec) +{ + max9850_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define max9850_suspend NULL +#define max9850_resume NULL +#endif + +static int max9850_probe(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret; + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + /* enable zero-detect */ + snd_soc_update_bits(codec, MAX9850_GENERAL_PURPOSE, 1, 1); + /* enable slew-rate control */ + snd_soc_update_bits(codec, MAX9850_VOLUME, 0x40, 0x40); + /* set slew-rate 125ms */ + snd_soc_update_bits(codec, MAX9850_CHARGE_PUMP, 0xff, 0xc0); + + snd_soc_dapm_new_controls(dapm, max9850_dapm_widgets, + ARRAY_SIZE(max9850_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon)); + + snd_soc_add_controls(codec, max9850_controls, + ARRAY_SIZE(max9850_controls)); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_max9850 = { + .probe = max9850_probe, + .suspend = max9850_suspend, + .resume = max9850_resume, + .set_bias_level = max9850_set_bias_level, + .reg_cache_size = ARRAY_SIZE(max9850_reg), + .reg_word_size = sizeof(u8), + .reg_cache_default = max9850_reg, + .volatile_register = max9850_volatile_register, +}; + +static int __devinit max9850_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max9850_priv *max9850; + int ret; + + max9850 = kzalloc(sizeof(struct max9850_priv), GFP_KERNEL); + if (max9850 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max9850); + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_max9850, &max9850_dai, 1); + if (ret < 0) + kfree(max9850); + return ret; +} + +static __devexit int max9850_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id max9850_i2c_id[] = { + { "max9850", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9850_i2c_id); + +static struct i2c_driver max9850_i2c_driver = { + .driver = { + .name = "max9850", + .owner = THIS_MODULE, + }, + .probe = max9850_i2c_probe, + .remove = __devexit_p(max9850_i2c_remove), + .id_table = max9850_i2c_id, +}; + +static int __init max9850_init(void) +{ + return i2c_add_driver(&max9850_i2c_driver); +} +module_init(max9850_init); + +static void __exit max9850_exit(void) +{ + i2c_del_driver(&max9850_i2c_driver); +} +module_exit(max9850_exit); + +MODULE_AUTHOR("Christian Glindkamp <christian.glindkamp@taskit.de>"); +MODULE_DESCRIPTION("ASoC MAX9850 codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max9850.h b/sound/soc/codecs/max9850.h new file mode 100644 index 00000000000..72b1ddb04b0 --- /dev/null +++ b/sound/soc/codecs/max9850.h @@ -0,0 +1,38 @@ +/* + * max9850.h -- codec driver for max9850 + * + * Copyright (C) 2011 taskit GmbH + * Author: Christian Glindkamp <christian.glindkamp@taskit.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _MAX9850_H +#define _MAX9850_H + +#define MAX9850_STATUSA 0x00 +#define MAX9850_STATUSB 0x01 +#define MAX9850_VOLUME 0x02 +#define MAX9850_GENERAL_PURPOSE 0x03 +#define MAX9850_INTERRUPT 0x04 +#define MAX9850_ENABLE 0x05 +#define MAX9850_CLOCK 0x06 +#define MAX9850_CHARGE_PUMP 0x07 +#define MAX9850_LRCLK_MSB 0x08 +#define MAX9850_LRCLK_LSB 0x09 +#define MAX9850_DIGITAL_AUDIO 0x0a + +#define MAX9850_CACHEREGNUM 11 + +/* MAX9850_DIGITAL_AUDIO */ +#define MAX9850_MASTER (1<<7) +#define MAX9850_INV (1<<6) +#define MAX9850_BCINV (1<<5) +#define MAX9850_DLY (1<<3) +#define MAX9850_RTJ (1<<2) + +#endif diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c new file mode 100644 index 00000000000..ff29380c9ed --- /dev/null +++ b/sound/soc/codecs/sgtl5000.c @@ -0,0 +1,1527 @@ +/* + * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver + * + * Copyright 2010-2011 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/consumer.h> +#include <sound/core.h> +#include <sound/tlv.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "sgtl5000.h" + +#define SGTL5000_DAP_REG_OFFSET 0x0100 +#define SGTL5000_MAX_REG_OFFSET 0x013A + +/* default value of sgtl5000 registers except DAP */ +static const u16 sgtl5000_regs[SGTL5000_MAX_REG_OFFSET >> 1] = { + 0xa011, /* 0x0000, CHIP_ID. 11 stand for revison 17 */ + 0x0000, /* 0x0002, CHIP_DIG_POWER. */ + 0x0008, /* 0x0004, CHIP_CKL_CTRL */ + 0x0010, /* 0x0006, CHIP_I2S_CTRL */ + 0x0000, /* 0x0008, reserved */ + 0x0008, /* 0x000A, CHIP_SSS_CTRL */ + 0x0000, /* 0x000C, reserved */ + 0x020c, /* 0x000E, CHIP_ADCDAC_CTRL */ + 0x3c3c, /* 0x0010, CHIP_DAC_VOL */ + 0x0000, /* 0x0012, reserved */ + 0x015f, /* 0x0014, CHIP_PAD_STRENGTH */ + 0x0000, /* 0x0016, reserved */ + 0x0000, /* 0x0018, reserved */ + 0x0000, /* 0x001A, reserved */ + 0x0000, /* 0x001E, reserved */ + 0x0000, /* 0x0020, CHIP_ANA_ADC_CTRL */ + 0x1818, /* 0x0022, CHIP_ANA_HP_CTRL */ + 0x0111, /* 0x0024, CHIP_ANN_CTRL */ + 0x0000, /* 0x0026, CHIP_LINREG_CTRL */ + 0x0000, /* 0x0028, CHIP_REF_CTRL */ + 0x0000, /* 0x002A, CHIP_MIC_CTRL */ + 0x0000, /* 0x002C, CHIP_LINE_OUT_CTRL */ + 0x0404, /* 0x002E, CHIP_LINE_OUT_VOL */ + 0x7060, /* 0x0030, CHIP_ANA_POWER */ + 0x5000, /* 0x0032, CHIP_PLL_CTRL */ + 0x0000, /* 0x0034, CHIP_CLK_TOP_CTRL */ + 0x0000, /* 0x0036, CHIP_ANA_STATUS */ + 0x0000, /* 0x0038, reserved */ + 0x0000, /* 0x003A, CHIP_ANA_TEST2 */ + 0x0000, /* 0x003C, CHIP_SHORT_CTRL */ + 0x0000, /* reserved */ +}; + +/* default value of dap registers */ +static const u16 sgtl5000_dap_regs[] = { + 0x0000, /* 0x0100, DAP_CONTROL */ + 0x0000, /* 0x0102, DAP_PEQ */ + 0x0040, /* 0x0104, DAP_BASS_ENHANCE */ + 0x051f, /* 0x0106, DAP_BASS_ENHANCE_CTRL */ + 0x0000, /* 0x0108, DAP_AUDIO_EQ */ + 0x0040, /* 0x010A, DAP_SGTL_SURROUND */ + 0x0000, /* 0x010C, DAP_FILTER_COEF_ACCESS */ + 0x0000, /* 0x010E, DAP_COEF_WR_B0_MSB */ + 0x0000, /* 0x0110, DAP_COEF_WR_B0_LSB */ + 0x0000, /* 0x0112, reserved */ + 0x0000, /* 0x0114, reserved */ + 0x002f, /* 0x0116, DAP_AUDIO_EQ_BASS_BAND0 */ + 0x002f, /* 0x0118, DAP_AUDIO_EQ_BAND0 */ + 0x002f, /* 0x011A, DAP_AUDIO_EQ_BAND2 */ + 0x002f, /* 0x011C, DAP_AUDIO_EQ_BAND3 */ + 0x002f, /* 0x011E, DAP_AUDIO_EQ_TREBLE_BAND4 */ + 0x8000, /* 0x0120, DAP_MAIN_CHAN */ + 0x0000, /* 0x0122, DAP_MIX_CHAN */ + 0x0510, /* 0x0124, DAP_AVC_CTRL */ + 0x1473, /* 0x0126, DAP_AVC_THRESHOLD */ + 0x0028, /* 0x0128, DAP_AVC_ATTACK */ + 0x0050, /* 0x012A, DAP_AVC_DECAY */ + 0x0000, /* 0x012C, DAP_COEF_WR_B1_MSB */ + 0x0000, /* 0x012E, DAP_COEF_WR_B1_LSB */ + 0x0000, /* 0x0130, DAP_COEF_WR_B2_MSB */ + 0x0000, /* 0x0132, DAP_COEF_WR_B2_LSB */ + 0x0000, /* 0x0134, DAP_COEF_WR_A1_MSB */ + 0x0000, /* 0x0136, DAP_COEF_WR_A1_LSB */ + 0x0000, /* 0x0138, DAP_COEF_WR_A2_MSB */ + 0x0000, /* 0x013A, DAP_COEF_WR_A2_LSB */ +}; + +/* regulator supplies for sgtl5000, VDDD is an optional external supply */ +enum sgtl5000_regulator_supplies { + VDDA, + VDDIO, + VDDD, + SGTL5000_SUPPLY_NUM +}; + +/* vddd is optional supply */ +static const char *supply_names[SGTL5000_SUPPLY_NUM] = { + "VDDA", + "VDDIO", + "VDDD" +}; + +#define LDO_CONSUMER_NAME "VDDD_LDO" +#define LDO_VOLTAGE 1200000 + +static struct regulator_consumer_supply ldo_consumer[] = { + REGULATOR_SUPPLY(LDO_CONSUMER_NAME, NULL), +}; + +static struct regulator_init_data ldo_init_data = { + .constraints = { + .min_uV = 850000, + .max_uV = 1600000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + .consumer_supplies = &ldo_consumer[0], +}; + +/* + * sgtl5000 internal ldo regulator, + * enabled when VDDD not provided + */ +struct ldo_regulator { + struct regulator_desc desc; + struct regulator_dev *dev; + int voltage; + void *codec_data; + bool enabled; +}; + +/* sgtl5000 private structure in codec */ +struct sgtl5000_priv { + int sysclk; /* sysclk rate */ + int master; /* i2s master or not */ + int fmt; /* i2s data format */ + struct regulator_bulk_data supplies[SGTL5000_SUPPLY_NUM]; + struct ldo_regulator *ldo; +}; + +/* + * mic_bias power on/off share the same register bits with + * output impedance of mic bias, when power on mic bias, we + * need reclaim it to impedance value. + * 0x0 = Powered off + * 0x1 = 2Kohm + * 0x2 = 4Kohm + * 0x3 = 8Kohm + */ +static int mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* change mic bias resistor to 4Kohm */ + snd_soc_update_bits(w->codec, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_4k, SGTL5000_BIAS_R_4k); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* + * SGTL5000_BIAS_R_8k as mask to clean the two bits + * of mic bias and output impedance + */ + snd_soc_update_bits(w->codec, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_8k, 0); + break; + } + return 0; +} + +/* + * using codec assist to small pop, hp_powerup or lineout_powerup + * should stay setting until vag_powerup is fully ramped down, + * vag fully ramped down require 400ms. + */ +static int small_pop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(w->codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(w->codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, 0); + msleep(400); + break; + default: + break; + } + + return 0; +} + +/* input sources for ADC */ +static const char *adc_mux_text[] = { + "MIC_IN", "LINE_IN" +}; + +static const struct soc_enum adc_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 2, 2, adc_mux_text); + +static const struct snd_kcontrol_new adc_mux = +SOC_DAPM_ENUM("Capture Mux", adc_enum); + +/* input sources for DAC */ +static const char *dac_mux_text[] = { + "DAC", "LINE_IN" +}; + +static const struct soc_enum dac_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, dac_mux_text); + +static const struct snd_kcontrol_new dac_mux = +SOC_DAPM_ENUM("Headphone Mux", dac_enum); + +static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("LINE_IN"), + SND_SOC_DAPM_INPUT("MIC_IN"), + + SND_SOC_DAPM_OUTPUT("HP_OUT"), + SND_SOC_DAPM_OUTPUT("LINE_OUT"), + + SND_SOC_DAPM_MICBIAS_E("Mic Bias", SGTL5000_CHIP_MIC_CTRL, 8, 0, + mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_PGA_E("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0, + small_pop_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0, + small_pop_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux), + SND_SOC_DAPM_MUX("Headphone Mux", SND_SOC_NOPM, 0, 0, &dac_mux), + + /* aif for i2s input */ + SND_SOC_DAPM_AIF_IN("AIFIN", "Playback", + 0, SGTL5000_CHIP_DIG_POWER, + 0, 0), + + /* aif for i2s output */ + SND_SOC_DAPM_AIF_OUT("AIFOUT", "Capture", + 0, SGTL5000_CHIP_DIG_POWER, + 1, 0), + + SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0), + + SND_SOC_DAPM_DAC("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0), +}; + +/* routes for sgtl5000 */ +static const struct snd_soc_dapm_route audio_map[] = { + {"Capture Mux", "LINE_IN", "LINE_IN"}, /* line_in --> adc_mux */ + {"Capture Mux", "MIC_IN", "MIC_IN"}, /* mic_in --> adc_mux */ + + {"ADC", NULL, "Capture Mux"}, /* adc_mux --> adc */ + {"AIFOUT", NULL, "ADC"}, /* adc --> i2s_out */ + + {"DAC", NULL, "AIFIN"}, /* i2s-->dac,skip audio mux */ + {"Headphone Mux", "DAC", "DAC"}, /* dac --> hp_mux */ + {"LO", NULL, "DAC"}, /* dac --> line_out */ + + {"Headphone Mux", "LINE_IN", "LINE_IN"},/* line_in --> hp_mux */ + {"HP", NULL, "Headphone Mux"}, /* hp_mux --> hp */ + + {"LINE_OUT", NULL, "LO"}, + {"HP_OUT", NULL, "HP"}, +}; + +/* custom function to fetch info of PCM playback volume */ +static int dac_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xfc - 0x3c; + return 0; +} + +/* + * custom function to get of PCM playback volume + * + * dac volume register + * 15-------------8-7--------------0 + * | R channel vol | L channel vol | + * ------------------------------- + * + * PCM volume with 0.5017 dB steps from 0 to -90 dB + * + * register values map to dB + * 0x3B and less = Reserved + * 0x3C = 0 dB + * 0x3D = -0.5 dB + * 0xF0 = -90 dB + * 0xFC and greater = Muted + * + * register value map to userspace value + * + * register value 0x3c(0dB) 0xf0(-90dB)0xfc + * ------------------------------ + * userspace value 0xc0 0 + */ +static int dac_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg; + int l; + int r; + + reg = snd_soc_read(codec, SGTL5000_CHIP_DAC_VOL); + + /* get left channel volume */ + l = (reg & SGTL5000_DAC_VOL_LEFT_MASK) >> SGTL5000_DAC_VOL_LEFT_SHIFT; + + /* get right channel volume */ + r = (reg & SGTL5000_DAC_VOL_RIGHT_MASK) >> SGTL5000_DAC_VOL_RIGHT_SHIFT; + + /* make sure value fall in (0x3c,0xfc) */ + l = clamp(l, 0x3c, 0xfc); + r = clamp(r, 0x3c, 0xfc); + + /* invert it and map to userspace value */ + l = 0xfc - l; + r = 0xfc - r; + + ucontrol->value.integer.value[0] = l; + ucontrol->value.integer.value[1] = r; + + return 0; +} + +/* + * custom function to put of PCM playback volume + * + * dac volume register + * 15-------------8-7--------------0 + * | R channel vol | L channel vol | + * ------------------------------- + * + * PCM volume with 0.5017 dB steps from 0 to -90 dB + * + * register values map to dB + * 0x3B and less = Reserved + * 0x3C = 0 dB + * 0x3D = -0.5 dB + * 0xF0 = -90 dB + * 0xFC and greater = Muted + * + * userspace value map to register value + * + * userspace value 0xc0 0 + * ------------------------------ + * register value 0x3c(0dB) 0xf0(-90dB)0xfc + */ +static int dac_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg; + int l; + int r; + + l = ucontrol->value.integer.value[0]; + r = ucontrol->value.integer.value[1]; + + /* make sure userspace volume fall in (0, 0xfc-0x3c) */ + l = clamp(l, 0, 0xfc - 0x3c); + r = clamp(r, 0, 0xfc - 0x3c); + + /* invert it, get the value can be set to register */ + l = 0xfc - l; + r = 0xfc - r; + + /* shift to get the register value */ + reg = l << SGTL5000_DAC_VOL_LEFT_SHIFT | + r << SGTL5000_DAC_VOL_RIGHT_SHIFT; + + snd_soc_write(codec, SGTL5000_CHIP_DAC_VOL, reg); + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(capture_6db_attenuate, -600, 600, 0); + +/* tlv for mic gain, 0db 20db 30db 40db */ +static const unsigned int mic_gain_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 3, TLV_DB_SCALE_ITEM(2000, 1000, 0), +}; + +/* tlv for hp volume, -51.5db to 12.0db, step .5db */ +static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0); + +static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { + /* SOC_DOUBLE_S8_TLV with invert */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = dac_info_volsw, + .get = dac_get_volsw, + .put = dac_put_volsw, + }, + + SOC_DOUBLE("Capture Volume", SGTL5000_CHIP_ANA_ADC_CTRL, 0, 4, 0xf, 0), + SOC_SINGLE_TLV("Capture Attenuate Switch (-6dB)", + SGTL5000_CHIP_ANA_ADC_CTRL, + 8, 2, 0, capture_6db_attenuate), + SOC_SINGLE("Capture ZC Switch", SGTL5000_CHIP_ANA_CTRL, 1, 1, 0), + + SOC_DOUBLE_TLV("Headphone Playback Volume", + SGTL5000_CHIP_ANA_HP_CTRL, + 0, 8, + 0x7f, 1, + headphone_volume), + SOC_SINGLE("Headphone Playback ZC Switch", SGTL5000_CHIP_ANA_CTRL, + 5, 1, 0), + + SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL, + 0, 4, 0, mic_gain_tlv), +}; + +/* mute the codec used by alsa core */ +static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 adcdac_ctrl = SGTL5000_DAC_MUTE_LEFT | SGTL5000_DAC_MUTE_RIGHT; + + snd_soc_update_bits(codec, SGTL5000_CHIP_ADCDAC_CTRL, + adcdac_ctrl, mute ? adcdac_ctrl : 0); + + return 0; +} + +/* set codec format */ +static int sgtl5000_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + u16 i2sctl = 0; + + sgtl5000->master = 0; + /* + * i2s clock and frame master setting. + * ONLY support: + * - clock and frame slave, + * - clock and frame master + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + i2sctl |= SGTL5000_I2S_MASTER; + sgtl5000->master = 1; + break; + default: + return -EINVAL; + } + + /* setting i2s data format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + i2sctl |= SGTL5000_I2S_MODE_PCM; + break; + case SND_SOC_DAIFMT_DSP_B: + i2sctl |= SGTL5000_I2S_MODE_PCM; + i2sctl |= SGTL5000_I2S_LRALIGN; + break; + case SND_SOC_DAIFMT_I2S: + i2sctl |= SGTL5000_I2S_MODE_I2S_LJ; + break; + case SND_SOC_DAIFMT_RIGHT_J: + i2sctl |= SGTL5000_I2S_MODE_RJ; + i2sctl |= SGTL5000_I2S_LRPOL; + break; + case SND_SOC_DAIFMT_LEFT_J: + i2sctl |= SGTL5000_I2S_MODE_I2S_LJ; + i2sctl |= SGTL5000_I2S_LRALIGN; + break; + default: + return -EINVAL; + } + + sgtl5000->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + i2sctl |= SGTL5000_I2S_SCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, SGTL5000_CHIP_I2S_CTRL, i2sctl); + + return 0; +} + +/* set codec sysclk */ +static int sgtl5000_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 sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case SGTL5000_SYSCLK: + sgtl5000->sysclk = freq; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * set clock according to i2s frame clock, + * sgtl5000 provide 2 clock sources. + * 1. sys_mclk. sample freq can only configure to + * 1/256, 1/384, 1/512 of sys_mclk. + * 2. pll. can derive any audio clocks. + * + * clock setting rules: + * 1. in slave mode, only sys_mclk can use. + * 2. as constraint by sys_mclk, sample freq should + * set to 32k, 44.1k and above. + * 3. using sys_mclk prefer to pll to save power. + */ +static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate) +{ + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + int clk_ctl = 0; + int sys_fs; /* sample freq */ + + /* + * sample freq should be divided by frame clock, + * if frame clock lower than 44.1khz, sample feq should set to + * 32khz or 44.1khz. + */ + switch (frame_rate) { + case 8000: + case 16000: + sys_fs = 32000; + break; + case 11025: + case 22050: + sys_fs = 44100; + break; + default: + sys_fs = frame_rate; + break; + } + + /* set divided factor of frame clock */ + switch (sys_fs / frame_rate) { + case 4: + clk_ctl |= SGTL5000_RATE_MODE_DIV_4 << SGTL5000_RATE_MODE_SHIFT; + break; + case 2: + clk_ctl |= SGTL5000_RATE_MODE_DIV_2 << SGTL5000_RATE_MODE_SHIFT; + break; + case 1: + clk_ctl |= SGTL5000_RATE_MODE_DIV_1 << SGTL5000_RATE_MODE_SHIFT; + break; + default: + return -EINVAL; + } + + /* set the sys_fs according to frame rate */ + switch (sys_fs) { + case 32000: + clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT; + break; + case 44100: + clk_ctl |= SGTL5000_SYS_FS_44_1k << SGTL5000_SYS_FS_SHIFT; + break; + case 48000: + clk_ctl |= SGTL5000_SYS_FS_48k << SGTL5000_SYS_FS_SHIFT; + break; + case 96000: + clk_ctl |= SGTL5000_SYS_FS_96k << SGTL5000_SYS_FS_SHIFT; + break; + default: + dev_err(codec->dev, "frame rate %d not supported\n", + frame_rate); + return -EINVAL; + } + + /* + * calculate the divider of mclk/sample_freq, + * factor of freq =96k can only be 256, since mclk in range (12m,27m) + */ + switch (sgtl5000->sysclk / sys_fs) { + case 256: + clk_ctl |= SGTL5000_MCLK_FREQ_256FS << + SGTL5000_MCLK_FREQ_SHIFT; + break; + case 384: + clk_ctl |= SGTL5000_MCLK_FREQ_384FS << + SGTL5000_MCLK_FREQ_SHIFT; + break; + case 512: + clk_ctl |= SGTL5000_MCLK_FREQ_512FS << + SGTL5000_MCLK_FREQ_SHIFT; + break; + default: + /* if mclk not satisify the divider, use pll */ + if (sgtl5000->master) { + clk_ctl |= SGTL5000_MCLK_FREQ_PLL << + SGTL5000_MCLK_FREQ_SHIFT; + } else { + dev_err(codec->dev, + "PLL not supported in slave mode\n"); + return -EINVAL; + } + } + + /* if using pll, please check manual 6.4.2 for detail */ + if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) { + u64 out, t; + int div2; + int pll_ctl; + unsigned int in, int_div, frac_div; + + if (sgtl5000->sysclk > 17000000) { + div2 = 1; + in = sgtl5000->sysclk / 2; + } else { + div2 = 0; + in = sgtl5000->sysclk; + } + if (sys_fs == 44100) + out = 180633600; + else + out = 196608000; + t = do_div(out, in); + int_div = out; + t *= 2048; + do_div(t, in); + frac_div = t; + pll_ctl = int_div << SGTL5000_PLL_INT_DIV_SHIFT | + frac_div << SGTL5000_PLL_FRAC_DIV_SHIFT; + + snd_soc_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl); + if (div2) + snd_soc_update_bits(codec, + SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_INPUT_FREQ_DIV2, + SGTL5000_INPUT_FREQ_DIV2); + else + snd_soc_update_bits(codec, + SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_INPUT_FREQ_DIV2, + 0); + + /* power up pll */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP, + SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP); + } else { + /* power down pll */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP, + 0); + } + + /* if using pll, clk_ctrl must be set after pll power up */ + snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl); + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + * input: params_rate, params_fmt + */ +static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + int channels = params_channels(params); + int i2s_ctl = 0; + int stereo; + int ret; + + /* sysclk should already set */ + if (!sgtl5000->sysclk) { + dev_err(codec->dev, "%s: set sysclk first!\n", __func__); + return -EFAULT; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + stereo = SGTL5000_DAC_STEREO; + else + stereo = SGTL5000_ADC_STEREO; + + /* set mono to save power */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, stereo, + channels == 1 ? 0 : stereo); + + /* set codec clock base on lrclk */ + ret = sgtl5000_set_clock(codec, params_rate(params)); + if (ret) + return ret; + + /* set i2s data format */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J) + return -EINVAL; + i2s_ctl |= SGTL5000_I2S_DLEN_16 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_32FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + i2s_ctl |= SGTL5000_I2S_DLEN_20 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + i2s_ctl |= SGTL5000_I2S_DLEN_24 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J) + return -EINVAL; + i2s_ctl |= SGTL5000_I2S_DLEN_32 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, SGTL5000_CHIP_I2S_CTRL, i2s_ctl, i2s_ctl); + + return 0; +} + +#ifdef CONFIG_REGULATOR +static int ldo_regulator_is_enabled(struct regulator_dev *dev) +{ + struct ldo_regulator *ldo = rdev_get_drvdata(dev); + + return ldo->enabled; +} + +static int ldo_regulator_enable(struct regulator_dev *dev) +{ + struct ldo_regulator *ldo = rdev_get_drvdata(dev); + struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data; + int reg; + + if (ldo_regulator_is_enabled(dev)) + return 0; + + /* set regulator value firstly */ + reg = (1600 - ldo->voltage / 1000) / 50; + reg = clamp(reg, 0x0, 0xf); + + /* amend the voltage value, unit: uV */ + ldo->voltage = (1600 - reg * 50) * 1000; + + /* set voltage to register */ + snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, + (0x1 << 4) - 1, reg); + + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINEREG_D_POWERUP, + SGTL5000_LINEREG_D_POWERUP); + + /* when internal ldo enabled, simple digital power can be disabled */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINREG_SIMPLE_POWERUP, + 0); + + ldo->enabled = 1; + return 0; +} + +static int ldo_regulator_disable(struct regulator_dev *dev) +{ + struct ldo_regulator *ldo = rdev_get_drvdata(dev); + struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data; + + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINEREG_D_POWERUP, + 0); + + /* clear voltage info */ + snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, + (0x1 << 4) - 1, 0); + + ldo->enabled = 0; + + return 0; +} + +static int ldo_regulator_get_voltage(struct regulator_dev *dev) +{ + struct ldo_regulator *ldo = rdev_get_drvdata(dev); + + return ldo->voltage; +} + +static struct regulator_ops ldo_regulator_ops = { + .is_enabled = ldo_regulator_is_enabled, + .enable = ldo_regulator_enable, + .disable = ldo_regulator_disable, + .get_voltage = ldo_regulator_get_voltage, +}; + +static int ldo_regulator_register(struct snd_soc_codec *codec, + struct regulator_init_data *init_data, + int voltage) +{ + struct ldo_regulator *ldo; + + ldo = kzalloc(sizeof(struct ldo_regulator), GFP_KERNEL); + + if (!ldo) { + dev_err(codec->dev, "failed to allocate ldo_regulator\n"); + return -ENOMEM; + } + + ldo->desc.name = kstrdup(dev_name(codec->dev), GFP_KERNEL); + if (!ldo->desc.name) { + kfree(ldo); + dev_err(codec->dev, "failed to allocate decs name memory\n"); + return -ENOMEM; + } + + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.owner = THIS_MODULE; + ldo->desc.ops = &ldo_regulator_ops; + ldo->desc.n_voltages = 1; + + ldo->codec_data = codec; + ldo->voltage = voltage; + + ldo->dev = regulator_register(&ldo->desc, codec->dev, + init_data, ldo); + if (IS_ERR(ldo->dev)) { + int ret = PTR_ERR(ldo->dev); + + dev_err(codec->dev, "failed to register regulator\n"); + kfree(ldo->desc.name); + kfree(ldo); + + return ret; + } + + return 0; +} + +static int ldo_regulator_remove(struct snd_soc_codec *codec) +{ + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + struct ldo_regulator *ldo = sgtl5000->ldo; + + if (!ldo) + return 0; + + regulator_unregister(ldo->dev); + kfree(ldo->desc.name); + kfree(ldo); + + return 0; +} +#else +static int ldo_regulator_register(struct snd_soc_codec *codec, + struct regulator_init_data *init_data, + int voltage) +{ + return -EINVAL; +} + +static int ldo_regulator_remove(struct snd_soc_codec *codec) +{ + return 0; +} +#endif + +/* + * set dac bias + * common state changes: + * startup: + * off --> standby --> prepare --> on + * standby --> prepare --> on + * + * stop: + * on --> prepare --> standby + */ +static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + int ret; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable( + ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (ret) + return ret; + udelay(10); + } + + break; + case SND_SOC_BIAS_OFF: + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +#define SGTL5000_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops sgtl5000_ops = { + .hw_params = sgtl5000_pcm_hw_params, + .digital_mute = sgtl5000_digital_mute, + .set_fmt = sgtl5000_set_dai_fmt, + .set_sysclk = sgtl5000_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver sgtl5000_dai = { + .name = "sgtl5000", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + /* + * only support 8~48K + 96K, + * TODO modify hw_param to support more + */ + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000, + .formats = SGTL5000_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000, + .formats = SGTL5000_FORMATS, + }, + .ops = &sgtl5000_ops, + .symmetric_rates = 1, +}; + +static int sgtl5000_volatile_register(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + case SGTL5000_CHIP_ID: + case SGTL5000_CHIP_ADCDAC_CTRL: + case SGTL5000_CHIP_ANA_STATUS: + return 1; + } + + return 0; +} + +#ifdef CONFIG_SUSPEND +static int sgtl5000_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +/* + * restore all sgtl5000 registers, + * since a big hole between dap and regular registers, + * we will restore them respectively. + */ +static int sgtl5000_restore_regs(struct snd_soc_codec *codec) +{ + u16 *cache = codec->reg_cache; + int i; + int regular_regs = SGTL5000_CHIP_SHORT_CTRL >> 1; + + /* restore regular registers */ + for (i = 0; i < regular_regs; i++) { + int reg = i << 1; + + /* this regs depends on the others */ + if (reg == SGTL5000_CHIP_ANA_POWER || + reg == SGTL5000_CHIP_CLK_CTRL || + reg == SGTL5000_CHIP_LINREG_CTRL || + reg == SGTL5000_CHIP_LINE_OUT_CTRL || + reg == SGTL5000_CHIP_CLK_CTRL) + continue; + + snd_soc_write(codec, reg, cache[i]); + } + + /* restore dap registers */ + for (i = SGTL5000_DAP_REG_OFFSET >> 1; + i < SGTL5000_MAX_REG_OFFSET >> 1; i++) { + int reg = i << 1; + + snd_soc_write(codec, reg, cache[i]); + } + + /* + * restore power and other regs according + * to set_power() and set_clock() + */ + snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL, + cache[SGTL5000_CHIP_LINREG_CTRL >> 1]); + + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, + cache[SGTL5000_CHIP_ANA_POWER >> 1]); + + snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL, + cache[SGTL5000_CHIP_CLK_CTRL >> 1]); + + snd_soc_write(codec, SGTL5000_CHIP_REF_CTRL, + cache[SGTL5000_CHIP_REF_CTRL >> 1]); + + snd_soc_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL, + cache[SGTL5000_CHIP_LINE_OUT_CTRL >> 1]); + return 0; +} + +static int sgtl5000_resume(struct snd_soc_codec *codec) +{ + /* Bring the codec back up to standby to enable regulators */ + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Restore registers by cached in memory */ + sgtl5000_restore_regs(codec); + return 0; +} +#else +#define sgtl5000_suspend NULL +#define sgtl5000_resume NULL +#endif /* CONFIG_SUSPEND */ + +/* + * sgtl5000 has 3 internal power supplies: + * 1. VAG, normally set to vdda/2 + * 2. chargepump, set to different value + * according to voltage of vdda and vddio + * 3. line out VAG, normally set to vddio/2 + * + * and should be set according to: + * 1. vddd provided by external or not + * 2. vdda and vddio voltage value. > 3.1v or not + * 3. chip revision >=0x11 or not. If >=0x11, not use external vddd. + */ +static int sgtl5000_set_power_regs(struct snd_soc_codec *codec) +{ + int vddd; + int vdda; + int vddio; + u16 ana_pwr; + u16 lreg_ctrl; + int vag; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + vdda = regulator_get_voltage(sgtl5000->supplies[VDDA].consumer); + vddio = regulator_get_voltage(sgtl5000->supplies[VDDIO].consumer); + vddd = regulator_get_voltage(sgtl5000->supplies[VDDD].consumer); + + vdda = vdda / 1000; + vddio = vddio / 1000; + vddd = vddd / 1000; + + if (vdda <= 0 || vddio <= 0 || vddd < 0) { + dev_err(codec->dev, "regulator voltage not set correctly\n"); + + return -EINVAL; + } + + /* according to datasheet, maximum voltage of supplies */ + if (vdda > 3600 || vddio > 3600 || vddd > 1980) { + dev_err(codec->dev, + "exceed max voltage vdda %dmv vddio %dma vddd %dma\n", + vdda, vddio, vddd); + + return -EINVAL; + } + + /* reset value */ + ana_pwr = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER); + ana_pwr |= SGTL5000_DAC_STEREO | + SGTL5000_ADC_STEREO | + SGTL5000_REFTOP_POWERUP; + lreg_ctrl = snd_soc_read(codec, SGTL5000_CHIP_LINREG_CTRL); + + if (vddio < 3100 && vdda < 3100) { + /* enable internal oscillator used for charge pump */ + snd_soc_update_bits(codec, SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_INT_OSC_EN, + SGTL5000_INT_OSC_EN); + /* Enable VDDC charge pump */ + ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP; + } else if (vddio >= 3100 && vdda >= 3100) { + /* + * if vddio and vddd > 3.1v, + * charge pump should be clean before set ana_pwr + */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VDDC_CHRGPMP_POWERUP, 0); + + /* VDDC use VDDIO rail */ + lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD; + lreg_ctrl |= SGTL5000_VDDC_MAN_ASSN_VDDIO << + SGTL5000_VDDC_MAN_ASSN_SHIFT; + } + + snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl); + + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); + + /* set voltage to register */ + snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, + (0x1 << 4) - 1, 0x8); + + /* + * if vddd linear reg has been enabled, + * simple digital supply should be clear to get + * proper VDDD voltage. + */ + if (ana_pwr & SGTL5000_LINEREG_D_POWERUP) + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINREG_SIMPLE_POWERUP, + 0); + else + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINREG_SIMPLE_POWERUP | + SGTL5000_STARTUP_POWERUP, + 0); + + /* + * set ADC/DAC VAG to vdda / 2, + * should stay in range (0.8v, 1.575v) + */ + vag = vdda / 2; + if (vag <= SGTL5000_ANA_GND_BASE) + vag = 0; + else if (vag >= SGTL5000_ANA_GND_BASE + SGTL5000_ANA_GND_STP * + (SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT)) + vag = SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT; + else + vag = (vag - SGTL5000_ANA_GND_BASE) / SGTL5000_ANA_GND_STP; + + snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL, + vag << SGTL5000_ANA_GND_SHIFT, + vag << SGTL5000_ANA_GND_SHIFT); + + /* set line out VAG to vddio / 2, in range (0.8v, 1.675v) */ + vag = vddio / 2; + if (vag <= SGTL5000_LINE_OUT_GND_BASE) + vag = 0; + else if (vag >= SGTL5000_LINE_OUT_GND_BASE + + SGTL5000_LINE_OUT_GND_STP * SGTL5000_LINE_OUT_GND_MAX) + vag = SGTL5000_LINE_OUT_GND_MAX; + else + vag = (vag - SGTL5000_LINE_OUT_GND_BASE) / + SGTL5000_LINE_OUT_GND_STP; + + snd_soc_update_bits(codec, SGTL5000_CHIP_LINE_OUT_CTRL, + vag << SGTL5000_LINE_OUT_GND_SHIFT | + SGTL5000_LINE_OUT_CURRENT_360u << + SGTL5000_LINE_OUT_CURRENT_SHIFT, + vag << SGTL5000_LINE_OUT_GND_SHIFT | + SGTL5000_LINE_OUT_CURRENT_360u << + SGTL5000_LINE_OUT_CURRENT_SHIFT); + + return 0; +} + +static int sgtl5000_enable_regulators(struct snd_soc_codec *codec) +{ + u16 reg; + int ret; + int rev; + int i; + int external_vddd = 0; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + for (i = 0; i < ARRAY_SIZE(sgtl5000->supplies); i++) + sgtl5000->supplies[i].supply = supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (!ret) + external_vddd = 1; + else { + /* set internal ldo to 1.2v */ + int voltage = LDO_VOLTAGE; + + ret = ldo_regulator_register(codec, &ldo_init_data, voltage); + if (ret) { + dev_err(codec->dev, + "Failed to register vddd internal supplies: %d\n", + ret); + return ret; + } + + sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME; + + ret = regulator_bulk_get(codec->dev, + ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + + if (ret) { + ldo_regulator_remove(codec); + dev_err(codec->dev, + "Failed to request supplies: %d\n", ret); + + return ret; + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (ret) + goto err_regulator_free; + + /* wait for all power rails bring up */ + udelay(10); + + /* read chip information */ + reg = snd_soc_read(codec, SGTL5000_CHIP_ID); + if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) != + SGTL5000_PARTID_PART_ID) { + dev_err(codec->dev, + "Device with ID register %x is not a sgtl5000\n", reg); + ret = -ENODEV; + goto err_regulator_disable; + } + + rev = (reg & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT; + dev_info(codec->dev, "sgtl5000 revision %d\n", rev); + + /* + * workaround for revision 0x11 and later, + * roll back to use internal LDO + */ + if (external_vddd && rev >= 0x11) { + int voltage = LDO_VOLTAGE; + /* disable all regulator first */ + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + /* free VDDD regulator */ + regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + + ret = ldo_regulator_register(codec, &ldo_init_data, voltage); + if (ret) + return ret; + + sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME; + + ret = regulator_bulk_get(codec->dev, + ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (ret) { + ldo_regulator_remove(codec); + dev_err(codec->dev, + "Failed to request supplies: %d\n", ret); + + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (ret) + goto err_regulator_free; + + /* wait for all power rails bring up */ + udelay(10); + } + + return 0; + +err_regulator_disable: + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); +err_regulator_free: + regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (external_vddd) + ldo_regulator_remove(codec); + return ret; + +} + +static int sgtl5000_probe(struct snd_soc_codec *codec) +{ + int ret; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + /* setup i2c data ops */ + ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + ret = sgtl5000_enable_regulators(codec); + if (ret) + return ret; + + /* power up sgtl5000 */ + ret = sgtl5000_set_power_regs(codec); + if (ret) + goto err; + + /* enable small pop, introduce 400ms delay in turning off */ + snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL, + SGTL5000_SMALL_POP, + SGTL5000_SMALL_POP); + + /* disable short cut detector */ + snd_soc_write(codec, SGTL5000_CHIP_SHORT_CTRL, 0); + + /* + * set i2s as default input of sound switch + * TODO: add sound switch to control and dapm widge. + */ + snd_soc_write(codec, SGTL5000_CHIP_SSS_CTRL, + SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT); + snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, + SGTL5000_ADC_EN | SGTL5000_DAC_EN); + + /* enable dac volume ramp by default */ + snd_soc_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, + SGTL5000_DAC_VOL_RAMP_EN | + SGTL5000_DAC_MUTE_RIGHT | + SGTL5000_DAC_MUTE_LEFT); + + snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f); + + snd_soc_write(codec, SGTL5000_CHIP_ANA_CTRL, + SGTL5000_HP_ZCD_EN | + SGTL5000_ADC_ZCD_EN); + + snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, 0); + + /* + * disable DAP + * TODO: + * Enable DAP in kcontrol and dapm. + */ + snd_soc_write(codec, SGTL5000_DAP_CTRL, 0); + + /* leading to standby state */ + ret = sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (ret) + goto err; + + snd_soc_add_controls(codec, sgtl5000_snd_controls, + ARRAY_SIZE(sgtl5000_snd_controls)); + + snd_soc_dapm_new_controls(&codec->dapm, sgtl5000_dapm_widgets, + ARRAY_SIZE(sgtl5000_dapm_widgets)); + + snd_soc_dapm_add_routes(&codec->dapm, audio_map, + ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(&codec->dapm); + + return 0; + +err: + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + ldo_regulator_remove(codec); + + return ret; +} + +static int sgtl5000_remove(struct snd_soc_codec *codec) +{ + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF); + + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + ldo_regulator_remove(codec); + + return 0; +} + +static struct snd_soc_codec_driver sgtl5000_driver = { + .probe = sgtl5000_probe, + .remove = sgtl5000_remove, + .suspend = sgtl5000_suspend, + .resume = sgtl5000_resume, + .set_bias_level = sgtl5000_set_bias_level, + .reg_cache_size = ARRAY_SIZE(sgtl5000_regs), + .reg_word_size = sizeof(u16), + .reg_cache_step = 2, + .reg_cache_default = sgtl5000_regs, + .volatile_register = sgtl5000_volatile_register, +}; + +static __devinit int sgtl5000_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sgtl5000_priv *sgtl5000; + int ret; + + sgtl5000 = kzalloc(sizeof(struct sgtl5000_priv), GFP_KERNEL); + if (!sgtl5000) + return -ENOMEM; + + /* + * copy DAP default values to default value array. + * sgtl5000 register space has a big hole, merge it + * at init phase makes life easy. + * FIXME: should we drop 'const' of sgtl5000_regs? + */ + memcpy((void *)(&sgtl5000_regs[0] + (SGTL5000_DAP_REG_OFFSET >> 1)), + sgtl5000_dap_regs, + SGTL5000_MAX_REG_OFFSET - SGTL5000_DAP_REG_OFFSET); + + i2c_set_clientdata(client, sgtl5000); + + ret = snd_soc_register_codec(&client->dev, + &sgtl5000_driver, &sgtl5000_dai, 1); + if (ret) { + dev_err(&client->dev, "Failed to register codec: %d\n", ret); + kfree(sgtl5000); + return ret; + } + + return 0; +} + +static __devexit int sgtl5000_i2c_remove(struct i2c_client *client) +{ + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + + kfree(sgtl5000); + return 0; +} + +static const struct i2c_device_id sgtl5000_id[] = { + {"sgtl5000", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, sgtl5000_id); + +static struct i2c_driver sgtl5000_i2c_driver = { + .driver = { + .name = "sgtl5000", + .owner = THIS_MODULE, + }, + .probe = sgtl5000_i2c_probe, + .remove = __devexit_p(sgtl5000_i2c_remove), + .id_table = sgtl5000_id, +}; + +static int __init sgtl5000_modinit(void) +{ + return i2c_add_driver(&sgtl5000_i2c_driver); +} +module_init(sgtl5000_modinit); + +static void __exit sgtl5000_exit(void) +{ + i2c_del_driver(&sgtl5000_i2c_driver); +} +module_exit(sgtl5000_exit); + +MODULE_DESCRIPTION("Freescale SGTL5000 ALSA SoC Codec Driver"); +MODULE_AUTHOR("Zeng Zhaoming <zhaoming.zeng@freescale.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h new file mode 100644 index 00000000000..eec3ab368f3 --- /dev/null +++ b/sound/soc/codecs/sgtl5000.h @@ -0,0 +1,400 @@ +/* + * sgtl5000.h - SGTL5000 audio codec interface + * + * Copyright 2010-2011 Freescale Semiconductor, Inc. + * + * 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 _SGTL5000_H +#define _SGTL5000_H + +/* + * Register values. + */ +#define SGTL5000_CHIP_ID 0x0000 +#define SGTL5000_CHIP_DIG_POWER 0x0002 +#define SGTL5000_CHIP_CLK_CTRL 0x0004 +#define SGTL5000_CHIP_I2S_CTRL 0x0006 +#define SGTL5000_CHIP_SSS_CTRL 0x000a +#define SGTL5000_CHIP_ADCDAC_CTRL 0x000e +#define SGTL5000_CHIP_DAC_VOL 0x0010 +#define SGTL5000_CHIP_PAD_STRENGTH 0x0014 +#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020 +#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022 +#define SGTL5000_CHIP_ANA_CTRL 0x0024 +#define SGTL5000_CHIP_LINREG_CTRL 0x0026 +#define SGTL5000_CHIP_REF_CTRL 0x0028 +#define SGTL5000_CHIP_MIC_CTRL 0x002a +#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002c +#define SGTL5000_CHIP_LINE_OUT_VOL 0x002e +#define SGTL5000_CHIP_ANA_POWER 0x0030 +#define SGTL5000_CHIP_PLL_CTRL 0x0032 +#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034 +#define SGTL5000_CHIP_ANA_STATUS 0x0036 +#define SGTL5000_CHIP_SHORT_CTRL 0x003c +#define SGTL5000_CHIP_ANA_TEST2 0x003a +#define SGTL5000_DAP_CTRL 0x0100 +#define SGTL5000_DAP_PEQ 0x0102 +#define SGTL5000_DAP_BASS_ENHANCE 0x0104 +#define SGTL5000_DAP_BASS_ENHANCE_CTRL 0x0106 +#define SGTL5000_DAP_AUDIO_EQ 0x0108 +#define SGTL5000_DAP_SURROUND 0x010a +#define SGTL5000_DAP_FLT_COEF_ACCESS 0x010c +#define SGTL5000_DAP_COEF_WR_B0_MSB 0x010e +#define SGTL5000_DAP_COEF_WR_B0_LSB 0x0110 +#define SGTL5000_DAP_EQ_BASS_BAND0 0x0116 +#define SGTL5000_DAP_EQ_BASS_BAND1 0x0118 +#define SGTL5000_DAP_EQ_BASS_BAND2 0x011a +#define SGTL5000_DAP_EQ_BASS_BAND3 0x011c +#define SGTL5000_DAP_EQ_BASS_BAND4 0x011e +#define SGTL5000_DAP_MAIN_CHAN 0x0120 +#define SGTL5000_DAP_MIX_CHAN 0x0122 +#define SGTL5000_DAP_AVC_CTRL 0x0124 +#define SGTL5000_DAP_AVC_THRESHOLD 0x0126 +#define SGTL5000_DAP_AVC_ATTACK 0x0128 +#define SGTL5000_DAP_AVC_DECAY 0x012a +#define SGTL5000_DAP_COEF_WR_B1_MSB 0x012c +#define SGTL5000_DAP_COEF_WR_B1_LSB 0x012e +#define SGTL5000_DAP_COEF_WR_B2_MSB 0x0130 +#define SGTL5000_DAP_COEF_WR_B2_LSB 0x0132 +#define SGTL5000_DAP_COEF_WR_A1_MSB 0x0134 +#define SGTL5000_DAP_COEF_WR_A1_LSB 0x0136 +#define SGTL5000_DAP_COEF_WR_A2_MSB 0x0138 +#define SGTL5000_DAP_COEF_WR_A2_LSB 0x013a + +/* + * Field Definitions. + */ + +/* + * SGTL5000_CHIP_ID + */ +#define SGTL5000_PARTID_MASK 0xff00 +#define SGTL5000_PARTID_SHIFT 8 +#define SGTL5000_PARTID_WIDTH 8 +#define SGTL5000_PARTID_PART_ID 0xa0 +#define SGTL5000_REVID_MASK 0x00ff +#define SGTL5000_REVID_SHIFT 0 +#define SGTL5000_REVID_WIDTH 8 + +/* + * SGTL5000_CHIP_DIG_POWER + */ +#define SGTL5000_ADC_EN 0x0040 +#define SGTL5000_DAC_EN 0x0020 +#define SGTL5000_DAP_POWERUP 0x0010 +#define SGTL5000_I2S_OUT_POWERUP 0x0002 +#define SGTL5000_I2S_IN_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_CLK_CTRL + */ +#define SGTL5000_RATE_MODE_MASK 0x0030 +#define SGTL5000_RATE_MODE_SHIFT 4 +#define SGTL5000_RATE_MODE_WIDTH 2 +#define SGTL5000_RATE_MODE_DIV_1 0 +#define SGTL5000_RATE_MODE_DIV_2 1 +#define SGTL5000_RATE_MODE_DIV_4 2 +#define SGTL5000_RATE_MODE_DIV_6 3 +#define SGTL5000_SYS_FS_MASK 0x000c +#define SGTL5000_SYS_FS_SHIFT 2 +#define SGTL5000_SYS_FS_WIDTH 2 +#define SGTL5000_SYS_FS_32k 0x0 +#define SGTL5000_SYS_FS_44_1k 0x1 +#define SGTL5000_SYS_FS_48k 0x2 +#define SGTL5000_SYS_FS_96k 0x3 +#define SGTL5000_MCLK_FREQ_MASK 0x0003 +#define SGTL5000_MCLK_FREQ_SHIFT 0 +#define SGTL5000_MCLK_FREQ_WIDTH 2 +#define SGTL5000_MCLK_FREQ_256FS 0x0 +#define SGTL5000_MCLK_FREQ_384FS 0x1 +#define SGTL5000_MCLK_FREQ_512FS 0x2 +#define SGTL5000_MCLK_FREQ_PLL 0x3 + +/* + * SGTL5000_CHIP_I2S_CTRL + */ +#define SGTL5000_I2S_SCLKFREQ_MASK 0x0100 +#define SGTL5000_I2S_SCLKFREQ_SHIFT 8 +#define SGTL5000_I2S_SCLKFREQ_WIDTH 1 +#define SGTL5000_I2S_SCLKFREQ_64FS 0x0 +#define SGTL5000_I2S_SCLKFREQ_32FS 0x1 /* Not for RJ mode */ +#define SGTL5000_I2S_MASTER 0x0080 +#define SGTL5000_I2S_SCLK_INV 0x0040 +#define SGTL5000_I2S_DLEN_MASK 0x0030 +#define SGTL5000_I2S_DLEN_SHIFT 4 +#define SGTL5000_I2S_DLEN_WIDTH 2 +#define SGTL5000_I2S_DLEN_32 0x0 +#define SGTL5000_I2S_DLEN_24 0x1 +#define SGTL5000_I2S_DLEN_20 0x2 +#define SGTL5000_I2S_DLEN_16 0x3 +#define SGTL5000_I2S_MODE_MASK 0x000c +#define SGTL5000_I2S_MODE_SHIFT 2 +#define SGTL5000_I2S_MODE_WIDTH 2 +#define SGTL5000_I2S_MODE_I2S_LJ 0x0 +#define SGTL5000_I2S_MODE_RJ 0x1 +#define SGTL5000_I2S_MODE_PCM 0x2 +#define SGTL5000_I2S_LRALIGN 0x0002 +#define SGTL5000_I2S_LRPOL 0x0001 /* set for which mode */ + +/* + * SGTL5000_CHIP_SSS_CTRL + */ +#define SGTL5000_DAP_MIX_LRSWAP 0x4000 +#define SGTL5000_DAP_LRSWAP 0x2000 +#define SGTL5000_DAC_LRSWAP 0x1000 +#define SGTL5000_I2S_OUT_LRSWAP 0x0400 +#define SGTL5000_DAP_MIX_SEL_MASK 0x0300 +#define SGTL5000_DAP_MIX_SEL_SHIFT 8 +#define SGTL5000_DAP_MIX_SEL_WIDTH 2 +#define SGTL5000_DAP_MIX_SEL_ADC 0x0 +#define SGTL5000_DAP_MIX_SEL_I2S_IN 0x1 +#define SGTL5000_DAP_SEL_MASK 0x00c0 +#define SGTL5000_DAP_SEL_SHIFT 6 +#define SGTL5000_DAP_SEL_WIDTH 2 +#define SGTL5000_DAP_SEL_ADC 0x0 +#define SGTL5000_DAP_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_MASK 0x0030 +#define SGTL5000_DAC_SEL_SHIFT 4 +#define SGTL5000_DAC_SEL_WIDTH 2 +#define SGTL5000_DAC_SEL_ADC 0x0 +#define SGTL5000_DAC_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_DAP 0x3 +#define SGTL5000_I2S_OUT_SEL_MASK 0x0003 +#define SGTL5000_I2S_OUT_SEL_SHIFT 0 +#define SGTL5000_I2S_OUT_SEL_WIDTH 2 +#define SGTL5000_I2S_OUT_SEL_ADC 0x0 +#define SGTL5000_I2S_OUT_SEL_I2S_IN 0x1 +#define SGTL5000_I2S_OUT_SEL_DAP 0x3 + +/* + * SGTL5000_CHIP_ADCDAC_CTRL + */ +#define SGTL5000_VOL_BUSY_DAC_RIGHT 0x2000 +#define SGTL5000_VOL_BUSY_DAC_LEFT 0x1000 +#define SGTL5000_DAC_VOL_RAMP_EN 0x0200 +#define SGTL5000_DAC_VOL_RAMP_EXPO 0x0100 +#define SGTL5000_DAC_MUTE_RIGHT 0x0008 +#define SGTL5000_DAC_MUTE_LEFT 0x0004 +#define SGTL5000_ADC_HPF_FREEZE 0x0002 +#define SGTL5000_ADC_HPF_BYPASS 0x0001 + +/* + * SGTL5000_CHIP_DAC_VOL + */ +#define SGTL5000_DAC_VOL_RIGHT_MASK 0xff00 +#define SGTL5000_DAC_VOL_RIGHT_SHIFT 8 +#define SGTL5000_DAC_VOL_RIGHT_WIDTH 8 +#define SGTL5000_DAC_VOL_LEFT_MASK 0x00ff +#define SGTL5000_DAC_VOL_LEFT_SHIFT 0 +#define SGTL5000_DAC_VOL_LEFT_WIDTH 8 + +/* + * SGTL5000_CHIP_PAD_STRENGTH + */ +#define SGTL5000_PAD_I2S_LRCLK_MASK 0x0300 +#define SGTL5000_PAD_I2S_LRCLK_SHIFT 8 +#define SGTL5000_PAD_I2S_LRCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_SCLK_MASK 0x00c0 +#define SGTL5000_PAD_I2S_SCLK_SHIFT 6 +#define SGTL5000_PAD_I2S_SCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_DOUT_MASK 0x0030 +#define SGTL5000_PAD_I2S_DOUT_SHIFT 4 +#define SGTL5000_PAD_I2S_DOUT_WIDTH 2 +#define SGTL5000_PAD_I2C_SDA_MASK 0x000c +#define SGTL5000_PAD_I2C_SDA_SHIFT 2 +#define SGTL5000_PAD_I2C_SDA_WIDTH 2 +#define SGTL5000_PAD_I2C_SCL_MASK 0x0003 +#define SGTL5000_PAD_I2C_SCL_SHIFT 0 +#define SGTL5000_PAD_I2C_SCL_WIDTH 2 + +/* + * SGTL5000_CHIP_ANA_ADC_CTRL + */ +#define SGTL5000_ADC_VOL_M6DB 0x0100 +#define SGTL5000_ADC_VOL_RIGHT_MASK 0x00f0 +#define SGTL5000_ADC_VOL_RIGHT_SHIFT 4 +#define SGTL5000_ADC_VOL_RIGHT_WIDTH 4 +#define SGTL5000_ADC_VOL_LEFT_MASK 0x000f +#define SGTL5000_ADC_VOL_LEFT_SHIFT 0 +#define SGTL5000_ADC_VOL_LEFT_WIDTH 4 + +/* + * SGTL5000_CHIP_ANA_HP_CTRL + */ +#define SGTL5000_HP_VOL_RIGHT_MASK 0x7f00 +#define SGTL5000_HP_VOL_RIGHT_SHIFT 8 +#define SGTL5000_HP_VOL_RIGHT_WIDTH 7 +#define SGTL5000_HP_VOL_LEFT_MASK 0x007f +#define SGTL5000_HP_VOL_LEFT_SHIFT 0 +#define SGTL5000_HP_VOL_LEFT_WIDTH 7 + +/* + * SGTL5000_CHIP_ANA_CTRL + */ +#define SGTL5000_LINE_OUT_MUTE 0x0100 +#define SGTL5000_HP_SEL_MASK 0x0040 +#define SGTL5000_HP_SEL_SHIFT 6 +#define SGTL5000_HP_SEL_WIDTH 1 +#define SGTL5000_HP_SEL_DAC 0x0 +#define SGTL5000_HP_SEL_LINE_IN 0x1 +#define SGTL5000_HP_ZCD_EN 0x0020 +#define SGTL5000_HP_MUTE 0x0010 +#define SGTL5000_ADC_SEL_MASK 0x0004 +#define SGTL5000_ADC_SEL_SHIFT 2 +#define SGTL5000_ADC_SEL_WIDTH 1 +#define SGTL5000_ADC_SEL_MIC 0x0 +#define SGTL5000_ADC_SEL_LINE_IN 0x1 +#define SGTL5000_ADC_ZCD_EN 0x0002 +#define SGTL5000_ADC_MUTE 0x0001 + +/* + * SGTL5000_CHIP_LINREG_CTRL + */ +#define SGTL5000_VDDC_MAN_ASSN_MASK 0x0040 +#define SGTL5000_VDDC_MAN_ASSN_SHIFT 6 +#define SGTL5000_VDDC_MAN_ASSN_WIDTH 1 +#define SGTL5000_VDDC_MAN_ASSN_VDDA 0x0 +#define SGTL5000_VDDC_MAN_ASSN_VDDIO 0x1 +#define SGTL5000_VDDC_ASSN_OVRD 0x0020 +#define SGTL5000_LINREG_VDDD_MASK 0x000f +#define SGTL5000_LINREG_VDDD_SHIFT 0 +#define SGTL5000_LINREG_VDDD_WIDTH 4 + +/* + * SGTL5000_CHIP_REF_CTRL + */ +#define SGTL5000_ANA_GND_MASK 0x01f0 +#define SGTL5000_ANA_GND_SHIFT 4 +#define SGTL5000_ANA_GND_WIDTH 5 +#define SGTL5000_ANA_GND_BASE 800 /* mv */ +#define SGTL5000_ANA_GND_STP 25 /*mv */ +#define SGTL5000_BIAS_CTRL_MASK 0x000e +#define SGTL5000_BIAS_CTRL_SHIFT 1 +#define SGTL5000_BIAS_CTRL_WIDTH 3 +#define SGTL5000_SMALL_POP 0x0001 + +/* + * SGTL5000_CHIP_MIC_CTRL + */ +#define SGTL5000_BIAS_R_MASK 0x0200 +#define SGTL5000_BIAS_R_SHIFT 8 +#define SGTL5000_BIAS_R_WIDTH 2 +#define SGTL5000_BIAS_R_off 0x0 +#define SGTL5000_BIAS_R_2K 0x1 +#define SGTL5000_BIAS_R_4k 0x2 +#define SGTL5000_BIAS_R_8k 0x3 +#define SGTL5000_BIAS_VOLT_MASK 0x0070 +#define SGTL5000_BIAS_VOLT_SHIFT 4 +#define SGTL5000_BIAS_VOLT_WIDTH 3 +#define SGTL5000_MIC_GAIN_MASK 0x0003 +#define SGTL5000_MIC_GAIN_SHIFT 0 +#define SGTL5000_MIC_GAIN_WIDTH 2 + +/* + * SGTL5000_CHIP_LINE_OUT_CTRL + */ +#define SGTL5000_LINE_OUT_CURRENT_MASK 0x0f00 +#define SGTL5000_LINE_OUT_CURRENT_SHIFT 8 +#define SGTL5000_LINE_OUT_CURRENT_WIDTH 4 +#define SGTL5000_LINE_OUT_CURRENT_180u 0x0 +#define SGTL5000_LINE_OUT_CURRENT_270u 0x1 +#define SGTL5000_LINE_OUT_CURRENT_360u 0x3 +#define SGTL5000_LINE_OUT_CURRENT_450u 0x7 +#define SGTL5000_LINE_OUT_CURRENT_540u 0xf +#define SGTL5000_LINE_OUT_GND_MASK 0x003f +#define SGTL5000_LINE_OUT_GND_SHIFT 0 +#define SGTL5000_LINE_OUT_GND_WIDTH 6 +#define SGTL5000_LINE_OUT_GND_BASE 800 /* mv */ +#define SGTL5000_LINE_OUT_GND_STP 25 +#define SGTL5000_LINE_OUT_GND_MAX 0x23 + +/* + * SGTL5000_CHIP_LINE_OUT_VOL + */ +#define SGTL5000_LINE_OUT_VOL_RIGHT_MASK 0x1f00 +#define SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT 8 +#define SGTL5000_LINE_OUT_VOL_RIGHT_WIDTH 5 +#define SGTL5000_LINE_OUT_VOL_LEFT_MASK 0x001f +#define SGTL5000_LINE_OUT_VOL_LEFT_SHIFT 0 +#define SGTL5000_LINE_OUT_VOL_LEFT_WIDTH 5 + +/* + * SGTL5000_CHIP_ANA_POWER + */ +#define SGTL5000_DAC_STEREO 0x4000 +#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000 +#define SGTL5000_STARTUP_POWERUP 0x1000 +#define SGTL5000_VDDC_CHRGPMP_POWERUP 0x0800 +#define SGTL5000_PLL_POWERUP 0x0400 +#define SGTL5000_LINEREG_D_POWERUP 0x0200 +#define SGTL5000_VCOAMP_POWERUP 0x0100 +#define SGTL5000_VAG_POWERUP 0x0080 +#define SGTL5000_ADC_STEREO 0x0040 +#define SGTL5000_REFTOP_POWERUP 0x0020 +#define SGTL5000_HP_POWERUP 0x0010 +#define SGTL5000_DAC_POWERUP 0x0008 +#define SGTL5000_CAPLESS_HP_POWERUP 0x0004 +#define SGTL5000_ADC_POWERUP 0x0002 +#define SGTL5000_LINE_OUT_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_PLL_CTRL + */ +#define SGTL5000_PLL_INT_DIV_MASK 0xf800 +#define SGTL5000_PLL_INT_DIV_SHIFT 11 +#define SGTL5000_PLL_INT_DIV_WIDTH 5 +#define SGTL5000_PLL_FRAC_DIV_MASK 0x0700 +#define SGTL5000_PLL_FRAC_DIV_SHIFT 0 +#define SGTL5000_PLL_FRAC_DIV_WIDTH 11 + +/* + * SGTL5000_CHIP_CLK_TOP_CTRL + */ +#define SGTL5000_INT_OSC_EN 0x0800 +#define SGTL5000_INPUT_FREQ_DIV2 0x0008 + +/* + * SGTL5000_CHIP_ANA_STATUS + */ +#define SGTL5000_HP_LRSHORT 0x0200 +#define SGTL5000_CAPLESS_SHORT 0x0100 +#define SGTL5000_PLL_LOCKED 0x0010 + +/* + * SGTL5000_CHIP_SHORT_CTRL + */ +#define SGTL5000_LVLADJR_MASK 0x7000 +#define SGTL5000_LVLADJR_SHIFT 12 +#define SGTL5000_LVLADJR_WIDTH 3 +#define SGTL5000_LVLADJL_MASK 0x0700 +#define SGTL5000_LVLADJL_SHIFT 8 +#define SGTL5000_LVLADJL_WIDTH 3 +#define SGTL5000_LVLADJC_MASK 0x0070 +#define SGTL5000_LVLADJC_SHIFT 4 +#define SGTL5000_LVLADJC_WIDTH 3 +#define SGTL5000_LR_SHORT_MOD_MASK 0x000c +#define SGTL5000_LR_SHORT_MOD_SHIFT 2 +#define SGTL5000_LR_SHORT_MOD_WIDTH 2 +#define SGTL5000_CM_SHORT_MOD_MASK 0x0003 +#define SGTL5000_CM_SHORT_MOD_SHIFT 0 +#define SGTL5000_CM_SHORT_MOD_WIDTH 2 + +/* + *SGTL5000_CHIP_ANA_TEST2 + */ +#define SGTL5000_MONO_DAC 0x1000 + +/* + * SGTL5000_DAP_CTRL + */ +#define SGTL5000_DAP_MIX_EN 0x0010 +#define SGTL5000_DAP_EN 0x0001 + +#define SGTL5000_SYSCLK 0x00 +#define SGTL5000_LRCLK 0x01 + +#endif diff --git a/sound/soc/codecs/sn95031.c b/sound/soc/codecs/sn95031.c index 40e285df9ae..2a30eae1881 100644 --- a/sound/soc/codecs/sn95031.c +++ b/sound/soc/codecs/sn95031.c @@ -34,17 +34,135 @@ #include <sound/soc-dapm.h> #include <sound/initval.h> #include <sound/tlv.h> +#include <sound/jack.h> #include "sn95031.h" #define SN95031_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100) #define SN95031_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) +/* adc helper functions */ + +/* enables mic bias voltage */ +static void sn95031_enable_mic_bias(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, SN95031_VAUD, BIT(2)|BIT(1)|BIT(0)); + snd_soc_update_bits(codec, SN95031_MICBIAS, BIT(2), BIT(2)); +} + +/* Enable/Disable the ADC depending on the argument */ +static void configure_adc(struct snd_soc_codec *sn95031_codec, int val) +{ + int value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1); + + if (val) { + /* Enable and start the ADC */ + value |= (SN95031_ADC_ENBL | SN95031_ADC_START); + value &= (~SN95031_ADC_NO_LOOP); + } else { + /* Just stop the ADC */ + value &= (~SN95031_ADC_START); + } + snd_soc_write(sn95031_codec, SN95031_ADC1CNTL1, value); +} + /* - * todo: - * capture paths - * jack detection - * PM functions + * finds an empty channel for conversion + * If the ADC is not enabled then start using 0th channel + * itself. Otherwise find an empty channel by looking for a + * channel in which the stopbit is set to 1. returns the index + * of the first free channel if succeeds or an error code. + * + * Context: can sleep + * */ +static int find_free_channel(struct snd_soc_codec *sn95031_codec) +{ + int ret = 0, i, value; + + /* check whether ADC is enabled */ + value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1); + + if ((value & SN95031_ADC_ENBL) == 0) + return 0; + + /* ADC is already enabled; Looking for an empty channel */ + for (i = 0; i < SN95031_ADC_CHANLS_MAX; i++) { + value = snd_soc_read(sn95031_codec, + SN95031_ADC_CHNL_START_ADDR + i); + if (value & SN95031_STOPBIT_MASK) { + ret = i; + break; + } + } + return (ret > SN95031_ADC_LOOP_MAX) ? (-EINVAL) : ret; +} + +/* Initialize the ADC for reading micbias values. Can sleep. */ +static int sn95031_initialize_adc(struct snd_soc_codec *sn95031_codec) +{ + int base_addr, chnl_addr; + int value; + static int channel_index; + + /* Index of the first channel in which the stop bit is set */ + channel_index = find_free_channel(sn95031_codec); + if (channel_index < 0) { + pr_err("No free ADC channels"); + return channel_index; + } + + base_addr = SN95031_ADC_CHNL_START_ADDR + channel_index; + + if (!(channel_index == 0 || channel_index == SN95031_ADC_LOOP_MAX)) { + /* Reset stop bit for channels other than 0 and 12 */ + value = snd_soc_read(sn95031_codec, base_addr); + /* Set the stop bit to zero */ + snd_soc_write(sn95031_codec, base_addr, value & 0xEF); + /* Index of the first free channel */ + base_addr++; + channel_index++; + } + + /* Since this is the last channel, set the stop bit + to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ + snd_soc_write(sn95031_codec, base_addr, + SN95031_AUDIO_DETECT_CODE | 0x10); + + chnl_addr = SN95031_ADC_DATA_START_ADDR + 2 * channel_index; + pr_debug("mid_initialize : %x", chnl_addr); + configure_adc(sn95031_codec, 1); + return chnl_addr; +} + + +/* reads the ADC registers and gets the mic bias value in mV. */ +static unsigned int sn95031_get_mic_bias(struct snd_soc_codec *codec) +{ + u16 adc_adr = sn95031_initialize_adc(codec); + u16 adc_val1, adc_val2; + unsigned int mic_bias; + + sn95031_enable_mic_bias(codec); + + /* Enable the sound card for conversion before reading */ + snd_soc_write(codec, SN95031_ADC1CNTL3, 0x05); + /* Re-toggle the RRDATARD bit */ + snd_soc_write(codec, SN95031_ADC1CNTL3, 0x04); + + /* Read the higher bits of data */ + msleep(1000); + adc_val1 = snd_soc_read(codec, adc_adr); + adc_adr++; + adc_val2 = snd_soc_read(codec, adc_adr); + + /* Adding lower two bits to the higher bits */ + mic_bias = (adc_val1 << 2) + (adc_val2 & 3); + mic_bias = (mic_bias * SN95031_ADC_ONE_LSB_MULTIPLIER) / 1000; + pr_debug("mic bias = %dmV\n", mic_bias); + return mic_bias; +} +EXPORT_SYMBOL_GPL(sn95031_get_mic_bias); +/*end - adc helper functions */ static inline unsigned int sn95031_read(struct snd_soc_codec *codec, unsigned int reg) @@ -241,7 +359,7 @@ static const struct snd_kcontrol_new sn95031_input4_mux_control = static const char *sn95031_micmode_text[] = {"Single Ended", "Differential"}; /* 0dB to 30dB in 10dB steps */ -static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 10, 30); +static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 10, 0); static const struct soc_enum sn95031_micmode1_enum = SOC_ENUM_SINGLE(SN95031_MICAMP1, 1, 2, sn95031_micmode_text); @@ -401,6 +519,8 @@ static const struct snd_soc_dapm_widget sn95031_dapm_widgets[] = { static const struct snd_soc_dapm_route sn95031_audio_map[] = { /* headset and earpiece map */ + { "HPOUTL", NULL, "Headset Rail"}, + { "HPOUTR", NULL, "Headset Rail"}, { "HPOUTL", NULL, "Headset Left Playback" }, { "HPOUTR", NULL, "Headset Right Playback" }, { "EPOUT", NULL, "Earpiece Playback" }, @@ -409,18 +529,16 @@ static const struct snd_soc_dapm_route sn95031_audio_map[] = { { "Earpiece Playback", NULL, "Headset Left Filter"}, { "Headset Left Filter", NULL, "HSDAC Left"}, { "Headset Right Filter", NULL, "HSDAC Right"}, - { "HSDAC Left", NULL, "Headset Rail"}, - { "HSDAC Right", NULL, "Headset Rail"}, /* speaker map */ + { "IHFOUTL", NULL, "Speaker Rail"}, + { "IHFOUTR", NULL, "Speaker Rail"}, { "IHFOUTL", "NULL", "Speaker Left Playback"}, { "IHFOUTR", "NULL", "Speaker Right Playback"}, { "Speaker Left Playback", NULL, "Speaker Left Filter"}, { "Speaker Right Playback", NULL, "Speaker Right Filter"}, { "Speaker Left Filter", NULL, "IHFDAC Left"}, { "Speaker Right Filter", NULL, "IHFDAC Right"}, - { "IHFDAC Left", NULL, "Speaker Rail"}, - { "IHFDAC Right", NULL, "Speaker Rail"}, /* vibra map */ { "VIB1OUT", NULL, "Vibra1 Playback"}, @@ -484,30 +602,30 @@ static const struct snd_soc_dapm_route sn95031_audio_map[] = { { "Txpath2 Capture Route", "ADC Right", "ADC Right"}, { "Txpath3 Capture Route", "ADC Right", "ADC Right"}, { "Txpath4 Capture Route", "ADC Right", "ADC Right"}, - { "Txpath1 Capture Route", NULL, "DMIC1"}, - { "Txpath2 Capture Route", NULL, "DMIC1"}, - { "Txpath3 Capture Route", NULL, "DMIC1"}, - { "Txpath4 Capture Route", NULL, "DMIC1"}, - { "Txpath1 Capture Route", NULL, "DMIC2"}, - { "Txpath2 Capture Route", NULL, "DMIC2"}, - { "Txpath3 Capture Route", NULL, "DMIC2"}, - { "Txpath4 Capture Route", NULL, "DMIC2"}, - { "Txpath1 Capture Route", NULL, "DMIC3"}, - { "Txpath2 Capture Route", NULL, "DMIC3"}, - { "Txpath3 Capture Route", NULL, "DMIC3"}, - { "Txpath4 Capture Route", NULL, "DMIC3"}, - { "Txpath1 Capture Route", NULL, "DMIC4"}, - { "Txpath2 Capture Route", NULL, "DMIC4"}, - { "Txpath3 Capture Route", NULL, "DMIC4"}, - { "Txpath4 Capture Route", NULL, "DMIC4"}, - { "Txpath1 Capture Route", NULL, "DMIC5"}, - { "Txpath2 Capture Route", NULL, "DMIC5"}, - { "Txpath3 Capture Route", NULL, "DMIC5"}, - { "Txpath4 Capture Route", NULL, "DMIC5"}, - { "Txpath1 Capture Route", NULL, "DMIC6"}, - { "Txpath2 Capture Route", NULL, "DMIC6"}, - { "Txpath3 Capture Route", NULL, "DMIC6"}, - { "Txpath4 Capture Route", NULL, "DMIC6"}, + { "Txpath1 Capture Route", "DMIC1", "DMIC1"}, + { "Txpath2 Capture Route", "DMIC1", "DMIC1"}, + { "Txpath3 Capture Route", "DMIC1", "DMIC1"}, + { "Txpath4 Capture Route", "DMIC1", "DMIC1"}, + { "Txpath1 Capture Route", "DMIC2", "DMIC2"}, + { "Txpath2 Capture Route", "DMIC2", "DMIC2"}, + { "Txpath3 Capture Route", "DMIC2", "DMIC2"}, + { "Txpath4 Capture Route", "DMIC2", "DMIC2"}, + { "Txpath1 Capture Route", "DMIC3", "DMIC3"}, + { "Txpath2 Capture Route", "DMIC3", "DMIC3"}, + { "Txpath3 Capture Route", "DMIC3", "DMIC3"}, + { "Txpath4 Capture Route", "DMIC3", "DMIC3"}, + { "Txpath1 Capture Route", "DMIC4", "DMIC4"}, + { "Txpath2 Capture Route", "DMIC4", "DMIC4"}, + { "Txpath3 Capture Route", "DMIC4", "DMIC4"}, + { "Txpath4 Capture Route", "DMIC4", "DMIC4"}, + { "Txpath1 Capture Route", "DMIC5", "DMIC5"}, + { "Txpath2 Capture Route", "DMIC5", "DMIC5"}, + { "Txpath3 Capture Route", "DMIC5", "DMIC5"}, + { "Txpath4 Capture Route", "DMIC5", "DMIC5"}, + { "Txpath1 Capture Route", "DMIC6", "DMIC6"}, + { "Txpath2 Capture Route", "DMIC6", "DMIC6"}, + { "Txpath3 Capture Route", "DMIC6", "DMIC6"}, + { "Txpath4 Capture Route", "DMIC6", "DMIC6"}, /* tx path */ { "TX1 Enable", NULL, "Txpath1 Capture Route"}, @@ -649,6 +767,61 @@ struct snd_soc_dai_driver sn95031_dais[] = { }, }; +static inline void sn95031_disable_jack_btn(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, SN95031_BTNCTRL2, 0x00); +} + +static inline void sn95031_enable_jack_btn(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, SN95031_BTNCTRL1, 0x77); + snd_soc_write(codec, SN95031_BTNCTRL2, 0x01); +} + +static int sn95031_get_headset_state(struct snd_soc_jack *mfld_jack) +{ + int micbias = sn95031_get_mic_bias(mfld_jack->codec); + + int jack_type = snd_soc_jack_get_type(mfld_jack, micbias); + + pr_debug("jack type detected = %d\n", jack_type); + if (jack_type == SND_JACK_HEADSET) + sn95031_enable_jack_btn(mfld_jack->codec); + return jack_type; +} + +void sn95031_jack_detection(struct mfld_jack_data *jack_data) +{ + unsigned int status; + unsigned int mask = SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_HEADSET; + + pr_debug("interrupt id read in sram = 0x%x\n", jack_data->intr_id); + if (jack_data->intr_id & 0x1) { + pr_debug("short_push detected\n"); + status = SND_JACK_HEADSET | SND_JACK_BTN_0; + } else if (jack_data->intr_id & 0x2) { + pr_debug("long_push detected\n"); + status = SND_JACK_HEADSET | SND_JACK_BTN_1; + } else if (jack_data->intr_id & 0x4) { + pr_debug("headset or headphones inserted\n"); + status = sn95031_get_headset_state(jack_data->mfld_jack); + } else if (jack_data->intr_id & 0x8) { + pr_debug("headset or headphones removed\n"); + status = 0; + sn95031_disable_jack_btn(jack_data->mfld_jack->codec); + } else { + pr_err("unidentified interrupt\n"); + return; + } + + snd_soc_jack_report(jack_data->mfld_jack, status, mask); + /*button pressed and released so we send explicit button release */ + if ((status & SND_JACK_BTN_0) | (status & SND_JACK_BTN_1)) + snd_soc_jack_report(jack_data->mfld_jack, + SND_JACK_HEADSET, mask); +} +EXPORT_SYMBOL_GPL(sn95031_jack_detection); + /* codec registration */ static int sn95031_codec_probe(struct snd_soc_codec *codec) { diff --git a/sound/soc/codecs/sn95031.h b/sound/soc/codecs/sn95031.h index e2b17d908ae..20376d234fb 100644 --- a/sound/soc/codecs/sn95031.h +++ b/sound/soc/codecs/sn95031.h @@ -96,4 +96,37 @@ #define SN95031_SSR5 0x384 #define SN95031_SSR6 0x385 +/* ADC registers */ + +#define SN95031_ADC1CNTL1 0x1C0 +#define SN95031_ADC_ENBL 0x10 +#define SN95031_ADC_START 0x08 +#define SN95031_ADC1CNTL3 0x1C2 +#define SN95031_ADCTHERM_ENBL 0x04 +#define SN95031_ADCRRDATA_ENBL 0x05 +#define SN95031_STOPBIT_MASK 16 +#define SN95031_ADCTHERM_MASK 4 +#define SN95031_ADC_CHANLS_MAX 15 /* Number of ADC channels */ +#define SN95031_ADC_LOOP_MAX (SN95031_ADC_CHANLS_MAX - 1) +#define SN95031_ADC_NO_LOOP 0x07 +#define SN95031_AUDIO_GPIO_CTRL 0x070 + +/* ADC channel code values */ +#define SN95031_AUDIO_DETECT_CODE 0x06 + +/* ADC base addresses */ +#define SN95031_ADC_CHNL_START_ADDR 0x1C5 /* increments by 1 */ +#define SN95031_ADC_DATA_START_ADDR 0x1D4 /* increments by 2 */ +/* multipier to convert to mV */ +#define SN95031_ADC_ONE_LSB_MULTIPLIER 2346 + + +struct mfld_jack_data { + int intr_id; + int micbias_vol; + struct snd_soc_jack *mfld_jack; +}; + +extern void sn95031_jack_detection(struct mfld_jack_data *jack_data); + #endif diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c new file mode 100644 index 00000000000..e93b9d1ae1d --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -0,0 +1,794 @@ +/* + * linux/sound/soc/codecs/tlv320aic32x4.c + * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin <javier.martin@vista-silicon.com> + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#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 <linux/platform_device.h> +#include <linux/cdev.h> +#include <linux/slab.h> + +#include <sound/tlv320aic32x4.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 "tlv320aic32x4.h" + +struct aic32x4_rate_divs { + u32 mclk; + u32 rate; + u8 p_val; + u8 pll_j; + u16 pll_d; + u16 dosr; + u8 ndac; + u8 mdac; + u8 aosr; + u8 nadc; + u8 madc; + u8 blck_N; +}; + +struct aic32x4_priv { + u32 sysclk; + s32 master; + u8 page_no; + void *control_data; + u32 power_cfg; + u32 micpga_routing; + bool swapdacs; +}; + +/* 0dB min, 1dB steps */ +static DECLARE_TLV_DB_SCALE(tlv_step_1, 0, 100, 0); +/* 0dB min, 0.5dB steps */ +static DECLARE_TLV_DB_SCALE(tlv_step_0_5, 0, 50, 0); + +static const struct snd_kcontrol_new aic32x4_snd_controls[] = { + SOC_DOUBLE_R_TLV("PCM Playback Volume", AIC32X4_LDACVOL, + AIC32X4_RDACVOL, 0, 0x30, 0, tlv_step_0_5), + SOC_DOUBLE_R_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN, + AIC32X4_HPRGAIN, 0, 0x1D, 0, tlv_step_1), + SOC_DOUBLE_R_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN, + AIC32X4_LORGAIN, 0, 0x1D, 0, tlv_step_1), + SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN, + AIC32X4_HPRGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN, + AIC32X4_LORGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL, + AIC32X4_RMICPGAVOL, 7, 0x01, 1), + + SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0), + SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0), + + SOC_DOUBLE_R_TLV("ADC Level Volume", AIC32X4_LADCVOL, + AIC32X4_RADCVOL, 0, 0x28, 0, tlv_step_0_5), + SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL, + AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + + SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0), + SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0), + SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, + 4, 0x07, 0), + SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, + 0, 0x03, 0), + SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, + 6, 0x03, 0), + SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, + 1, 0x1F, 0), + SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, + 0, 0x7F, 0), + SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, + 3, 0x1F, 0), + SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, + 3, 0x1F, 0), + SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, + 0, 0x1F, 0), + SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, + 0, 0x0F, 0), +}; + +static const struct aic32x4_rate_divs aic32x4_divs[] = { + /* 8k rate */ + {AIC32X4_FREQ_12000000, 8000, 1, 7, 6800, 768, 5, 3, 128, 5, 18, 24}, + {AIC32X4_FREQ_24000000, 8000, 2, 7, 6800, 768, 15, 1, 64, 45, 4, 24}, + {AIC32X4_FREQ_25000000, 8000, 2, 7, 3728, 768, 15, 1, 64, 45, 4, 24}, + /* 11.025k rate */ + {AIC32X4_FREQ_12000000, 11025, 1, 7, 5264, 512, 8, 2, 128, 8, 8, 16}, + {AIC32X4_FREQ_24000000, 11025, 2, 7, 5264, 512, 16, 1, 64, 32, 4, 16}, + /* 16k rate */ + {AIC32X4_FREQ_12000000, 16000, 1, 7, 6800, 384, 5, 3, 128, 5, 9, 12}, + {AIC32X4_FREQ_24000000, 16000, 2, 7, 6800, 384, 15, 1, 64, 18, 5, 12}, + {AIC32X4_FREQ_25000000, 16000, 2, 7, 3728, 384, 15, 1, 64, 18, 5, 12}, + /* 22.05k rate */ + {AIC32X4_FREQ_12000000, 22050, 1, 7, 5264, 256, 4, 4, 128, 4, 8, 8}, + {AIC32X4_FREQ_24000000, 22050, 2, 7, 5264, 256, 16, 1, 64, 16, 4, 8}, + {AIC32X4_FREQ_25000000, 22050, 2, 7, 2253, 256, 16, 1, 64, 16, 4, 8}, + /* 32k rate */ + {AIC32X4_FREQ_12000000, 32000, 1, 7, 1680, 192, 2, 7, 64, 2, 21, 6}, + {AIC32X4_FREQ_24000000, 32000, 2, 7, 1680, 192, 7, 2, 64, 7, 6, 6}, + /* 44.1k rate */ + {AIC32X4_FREQ_12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4}, + {AIC32X4_FREQ_24000000, 44100, 2, 7, 5264, 128, 8, 2, 64, 8, 4, 4}, + {AIC32X4_FREQ_25000000, 44100, 2, 7, 2253, 128, 8, 2, 64, 8, 4, 4}, + /* 48k rate */ + {AIC32X4_FREQ_12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4}, + {AIC32X4_FREQ_24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4}, + {AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4} +}; + +static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { + SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_HPLROUTE, 3, 1, 0), + SOC_DAPM_SINGLE("IN1_L Switch", AIC32X4_HPLROUTE, 2, 1, 0), +}; + +static const struct snd_kcontrol_new hpr_output_mixer_controls[] = { + SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_HPRROUTE, 3, 1, 0), + SOC_DAPM_SINGLE("IN1_R Switch", AIC32X4_HPRROUTE, 2, 1, 0), +}; + +static const struct snd_kcontrol_new lol_output_mixer_controls[] = { + SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_LOLROUTE, 3, 1, 0), +}; + +static const struct snd_kcontrol_new lor_output_mixer_controls[] = { + SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_LORROUTE, 3, 1, 0), +}; + +static const struct snd_kcontrol_new left_input_mixer_controls[] = { + SOC_DAPM_SINGLE("IN1_L P Switch", AIC32X4_LMICPGAPIN, 6, 1, 0), + SOC_DAPM_SINGLE("IN2_L P Switch", AIC32X4_LMICPGAPIN, 4, 1, 0), + SOC_DAPM_SINGLE("IN3_L P Switch", AIC32X4_LMICPGAPIN, 2, 1, 0), +}; + +static const struct snd_kcontrol_new right_input_mixer_controls[] = { + SOC_DAPM_SINGLE("IN1_R P Switch", AIC32X4_RMICPGAPIN, 6, 1, 0), + SOC_DAPM_SINGLE("IN2_R P Switch", AIC32X4_RMICPGAPIN, 4, 1, 0), + SOC_DAPM_SINGLE("IN3_R P Switch", AIC32X4_RMICPGAPIN, 2, 1, 0), +}; + +static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", AIC32X4_DACSETUP, 7, 0), + SND_SOC_DAPM_MIXER("HPL Output Mixer", SND_SOC_NOPM, 0, 0, + &hpl_output_mixer_controls[0], + ARRAY_SIZE(hpl_output_mixer_controls)), + SND_SOC_DAPM_PGA("HPL Power", AIC32X4_OUTPWRCTL, 5, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("LOL Output Mixer", SND_SOC_NOPM, 0, 0, + &lol_output_mixer_controls[0], + ARRAY_SIZE(lol_output_mixer_controls)), + SND_SOC_DAPM_PGA("LOL Power", AIC32X4_OUTPWRCTL, 3, 0, NULL, 0), + + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", AIC32X4_DACSETUP, 6, 0), + SND_SOC_DAPM_MIXER("HPR Output Mixer", SND_SOC_NOPM, 0, 0, + &hpr_output_mixer_controls[0], + ARRAY_SIZE(hpr_output_mixer_controls)), + SND_SOC_DAPM_PGA("HPR Power", AIC32X4_OUTPWRCTL, 4, 0, NULL, 0), + SND_SOC_DAPM_MIXER("LOR Output Mixer", SND_SOC_NOPM, 0, 0, + &lor_output_mixer_controls[0], + ARRAY_SIZE(lor_output_mixer_controls)), + SND_SOC_DAPM_PGA("LOR Power", AIC32X4_OUTPWRCTL, 2, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0, + &left_input_mixer_controls[0], + ARRAY_SIZE(left_input_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0, + &right_input_mixer_controls[0], + ARRAY_SIZE(right_input_mixer_controls)), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MICBIAS("Mic Bias", AIC32X4_MICBIAS, 6, 0), + + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LOL"), + SND_SOC_DAPM_OUTPUT("LOR"), + SND_SOC_DAPM_INPUT("IN1_L"), + SND_SOC_DAPM_INPUT("IN1_R"), + SND_SOC_DAPM_INPUT("IN2_L"), + SND_SOC_DAPM_INPUT("IN2_R"), + SND_SOC_DAPM_INPUT("IN3_L"), + SND_SOC_DAPM_INPUT("IN3_R"), +}; + +static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + /* Left Output */ + {"HPL Output Mixer", "L_DAC Switch", "Left DAC"}, + {"HPL Output Mixer", "IN1_L Switch", "IN1_L"}, + + {"HPL Power", NULL, "HPL Output Mixer"}, + {"HPL", NULL, "HPL Power"}, + + {"LOL Output Mixer", "L_DAC Switch", "Left DAC"}, + + {"LOL Power", NULL, "LOL Output Mixer"}, + {"LOL", NULL, "LOL Power"}, + + /* Right Output */ + {"HPR Output Mixer", "R_DAC Switch", "Right DAC"}, + {"HPR Output Mixer", "IN1_R Switch", "IN1_R"}, + + {"HPR Power", NULL, "HPR Output Mixer"}, + {"HPR", NULL, "HPR Power"}, + + {"LOR Output Mixer", "R_DAC Switch", "Right DAC"}, + + {"LOR Power", NULL, "LOR Output Mixer"}, + {"LOR", NULL, "LOR Power"}, + + /* Left input */ + {"Left Input Mixer", "IN1_L P Switch", "IN1_L"}, + {"Left Input Mixer", "IN2_L P Switch", "IN2_L"}, + {"Left Input Mixer", "IN3_L P Switch", "IN3_L"}, + + {"Left ADC", NULL, "Left Input Mixer"}, + + /* Right Input */ + {"Right Input Mixer", "IN1_R P Switch", "IN1_R"}, + {"Right Input Mixer", "IN2_R P Switch", "IN2_R"}, + {"Right Input Mixer", "IN3_R P Switch", "IN3_R"}, + + {"Right ADC", NULL, "Right Input Mixer"}, +}; + +static inline int aic32x4_change_page(struct snd_soc_codec *codec, + unsigned int new_page) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u8 data[2]; + int ret; + + data[0] = 0x00; + data[1] = new_page & 0xff; + + ret = codec->hw_write(codec->control_data, data, 2); + if (ret == 2) { + aic32x4->page_no = new_page; + return 0; + } else { + return ret; + } +} + +static int aic32x4_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + unsigned int page = reg / 128; + unsigned int fixed_reg = reg % 128; + u8 data[2]; + int ret; + + /* A write to AIC32X4_PSEL is really a non-explicit page change */ + if (reg == AIC32X4_PSEL) + return aic32x4_change_page(codec, val); + + if (aic32x4->page_no != page) { + ret = aic32x4_change_page(codec, page); + if (ret != 0) + return ret; + } + + data[0] = fixed_reg & 0xff; + data[1] = val & 0xff; + + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static unsigned int aic32x4_read(struct snd_soc_codec *codec, unsigned int reg) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + unsigned int page = reg / 128; + unsigned int fixed_reg = reg % 128; + int ret; + + if (aic32x4->page_no != page) { + ret = aic32x4_change_page(codec, page); + if (ret != 0) + return ret; + } + return i2c_smbus_read_byte_data(codec->control_data, fixed_reg & 0xff); +} + +static inline int aic32x4_get_divs(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(aic32x4_divs); i++) { + if ((aic32x4_divs[i].rate == rate) + && (aic32x4_divs[i].mclk == mclk)) { + return i; + } + } + printk(KERN_ERR "aic32x4: master clock and sample rate is not supported\n"); + return -EINVAL; +} + +static int aic32x4_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(&codec->dapm, aic32x4_dapm_widgets, + ARRAY_SIZE(aic32x4_dapm_widgets)); + + snd_soc_dapm_add_routes(&codec->dapm, aic32x4_dapm_routes, + ARRAY_SIZE(aic32x4_dapm_routes)); + + snd_soc_dapm_new_widgets(&codec->dapm); + return 0; +} + +static int aic32x4_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 aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + + switch (freq) { + case AIC32X4_FREQ_12000000: + case AIC32X4_FREQ_24000000: + case AIC32X4_FREQ_25000000: + aic32x4->sysclk = freq; + return 0; + } + printk(KERN_ERR "aic32x4: invalid frequency to set DAI system clock\n"); + return -EINVAL; +} + +static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u8 iface_reg_1; + u8 iface_reg_2; + u8 iface_reg_3; + + iface_reg_1 = snd_soc_read(codec, AIC32X4_IFACE1); + iface_reg_1 = iface_reg_1 & ~(3 << 6 | 3 << 2); + iface_reg_2 = snd_soc_read(codec, AIC32X4_IFACE2); + iface_reg_2 = 0; + iface_reg_3 = snd_soc_read(codec, AIC32X4_IFACE3); + iface_reg_3 = iface_reg_3 & ~(1 << 3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aic32x4->master = 1; + iface_reg_1 |= AIC32X4_BCLKMASTER | AIC32X4_WCLKMASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + aic32x4->master = 0; + break; + default: + printk(KERN_ERR "aic32x4: invalid DAI master/slave interface\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT); + iface_reg_3 |= (1 << 3); /* invert bit clock */ + iface_reg_2 = 0x01; /* add offset 1 */ + break; + case SND_SOC_DAIFMT_DSP_B: + iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT); + iface_reg_3 |= (1 << 3); /* invert bit clock */ + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface_reg_1 |= + (AIC32X4_RIGHT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT); + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg_1 |= + (AIC32X4_LEFT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT); + break; + default: + printk(KERN_ERR "aic32x4: invalid DAI interface format\n"); + return -EINVAL; + } + + snd_soc_write(codec, AIC32X4_IFACE1, iface_reg_1); + snd_soc_write(codec, AIC32X4_IFACE2, iface_reg_2); + snd_soc_write(codec, AIC32X4_IFACE3, iface_reg_3); + return 0; +} + +static int aic32x4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u8 data; + int i; + + i = aic32x4_get_divs(aic32x4->sysclk, params_rate(params)); + if (i < 0) { + printk(KERN_ERR "aic32x4: sampling rate not supported\n"); + return i; + } + + /* Use PLL as CODEC_CLKIN and DAC_MOD_CLK as BDIV_CLKIN */ + snd_soc_write(codec, AIC32X4_CLKMUX, AIC32X4_PLLCLKIN); + snd_soc_write(codec, AIC32X4_IFACE3, AIC32X4_DACMOD2BCLK); + + /* We will fix R value to 1 and will make P & J=K.D as varialble */ + data = snd_soc_read(codec, AIC32X4_PLLPR); + data &= ~(7 << 4); + snd_soc_write(codec, AIC32X4_PLLPR, + (data | (aic32x4_divs[i].p_val << 4) | 0x01)); + + snd_soc_write(codec, AIC32X4_PLLJ, aic32x4_divs[i].pll_j); + + snd_soc_write(codec, AIC32X4_PLLDMSB, (aic32x4_divs[i].pll_d >> 8)); + snd_soc_write(codec, AIC32X4_PLLDLSB, + (aic32x4_divs[i].pll_d & 0xff)); + + /* NDAC divider value */ + data = snd_soc_read(codec, AIC32X4_NDAC); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_NDAC, data | aic32x4_divs[i].ndac); + + /* MDAC divider value */ + data = snd_soc_read(codec, AIC32X4_MDAC); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_MDAC, data | aic32x4_divs[i].mdac); + + /* DOSR MSB & LSB values */ + snd_soc_write(codec, AIC32X4_DOSRMSB, aic32x4_divs[i].dosr >> 8); + snd_soc_write(codec, AIC32X4_DOSRLSB, + (aic32x4_divs[i].dosr & 0xff)); + + /* NADC divider value */ + data = snd_soc_read(codec, AIC32X4_NADC); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_NADC, data | aic32x4_divs[i].nadc); + + /* MADC divider value */ + data = snd_soc_read(codec, AIC32X4_MADC); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_MADC, data | aic32x4_divs[i].madc); + + /* AOSR value */ + snd_soc_write(codec, AIC32X4_AOSR, aic32x4_divs[i].aosr); + + /* BCLK N divider */ + data = snd_soc_read(codec, AIC32X4_BCLKN); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_BCLKN, data | aic32x4_divs[i].blck_N); + + data = snd_soc_read(codec, AIC32X4_IFACE1); + data = data & ~(3 << 4); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + data |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_DOSRMSB_SHIFT); + break; + case SNDRV_PCM_FORMAT_S24_LE: + data |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_DOSRMSB_SHIFT); + break; + case SNDRV_PCM_FORMAT_S32_LE: + data |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_DOSRMSB_SHIFT); + break; + } + snd_soc_write(codec, AIC32X4_IFACE1, data); + + return 0; +} + +static int aic32x4_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 dac_reg; + + dac_reg = snd_soc_read(codec, AIC32X4_DACMUTE) & ~AIC32X4_MUTEON; + if (mute) + snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg | AIC32X4_MUTEON); + else + snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg); + return 0; +} + +static int aic32x4_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u8 value; + + switch (level) { + case SND_SOC_BIAS_ON: + if (aic32x4->master) { + /* Switch on PLL */ + value = snd_soc_read(codec, AIC32X4_PLLPR); + snd_soc_write(codec, AIC32X4_PLLPR, + (value | AIC32X4_PLLEN)); + + /* Switch on NDAC Divider */ + value = snd_soc_read(codec, AIC32X4_NDAC); + snd_soc_write(codec, AIC32X4_NDAC, + value | AIC32X4_NDACEN); + + /* Switch on MDAC Divider */ + value = snd_soc_read(codec, AIC32X4_MDAC); + snd_soc_write(codec, AIC32X4_MDAC, + value | AIC32X4_MDACEN); + + /* Switch on NADC Divider */ + value = snd_soc_read(codec, AIC32X4_NADC); + snd_soc_write(codec, AIC32X4_NADC, + value | AIC32X4_MDACEN); + + /* Switch on MADC Divider */ + value = snd_soc_read(codec, AIC32X4_MADC); + snd_soc_write(codec, AIC32X4_MADC, + value | AIC32X4_MDACEN); + + /* Switch on BCLK_N Divider */ + value = snd_soc_read(codec, AIC32X4_BCLKN); + snd_soc_write(codec, AIC32X4_BCLKN, + value | AIC32X4_BCLKEN); + } + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (aic32x4->master) { + /* Switch off PLL */ + value = snd_soc_read(codec, AIC32X4_PLLPR); + snd_soc_write(codec, AIC32X4_PLLPR, + (value & ~AIC32X4_PLLEN)); + + /* Switch off NDAC Divider */ + value = snd_soc_read(codec, AIC32X4_NDAC); + snd_soc_write(codec, AIC32X4_NDAC, + value & ~AIC32X4_NDACEN); + + /* Switch off MDAC Divider */ + value = snd_soc_read(codec, AIC32X4_MDAC); + snd_soc_write(codec, AIC32X4_MDAC, + value & ~AIC32X4_MDACEN); + + /* Switch off NADC Divider */ + value = snd_soc_read(codec, AIC32X4_NADC); + snd_soc_write(codec, AIC32X4_NADC, + value & ~AIC32X4_NDACEN); + + /* Switch off MADC Divider */ + value = snd_soc_read(codec, AIC32X4_MADC); + snd_soc_write(codec, AIC32X4_MADC, + value & ~AIC32X4_MDACEN); + value = snd_soc_read(codec, AIC32X4_BCLKN); + + /* Switch off BCLK_N Divider */ + snd_soc_write(codec, AIC32X4_BCLKN, + value & ~AIC32X4_BCLKEN); + } + break; + case SND_SOC_BIAS_OFF: + break; + } + codec->dapm.bias_level = level; + return 0; +} + +#define AIC32X4_RATES SNDRV_PCM_RATE_8000_48000 +#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops aic32x4_ops = { + .hw_params = aic32x4_hw_params, + .digital_mute = aic32x4_mute, + .set_fmt = aic32x4_set_dai_fmt, + .set_sysclk = aic32x4_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver aic32x4_dai = { + .name = "tlv320aic32x4-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, + .ops = &aic32x4_ops, + .symmetric_rates = 1, +}; + +static int aic32x4_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int aic32x4_resume(struct snd_soc_codec *codec) +{ + aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +static int aic32x4_probe(struct snd_soc_codec *codec) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u32 tmp_reg; + + codec->hw_write = (hw_write_t) i2c_master_send; + codec->control_data = aic32x4->control_data; + + snd_soc_write(codec, AIC32X4_RESET, 0x01); + + /* Power platform configuration */ + if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { + snd_soc_write(codec, AIC32X4_MICBIAS, AIC32X4_MICBIAS_LDOIN | + AIC32X4_MICBIAS_2075V); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) { + snd_soc_write(codec, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AIC32X4_LDO_ENABLE) { + snd_soc_write(codec, AIC32X4_LDOCTL, AIC32X4_LDOCTLEN); + } + tmp_reg = snd_soc_read(codec, AIC32X4_CMMODE); + if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36) { + tmp_reg |= AIC32X4_LDOIN_18_36; + } + if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED) { + tmp_reg |= AIC32X4_LDOIN2HP; + } + snd_soc_write(codec, AIC32X4_CMMODE, tmp_reg); + + /* Do DACs need to be swapped? */ + if (aic32x4->swapdacs) { + snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2RCHN | AIC32X4_RDAC2LCHN); + } else { + snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN); + } + + /* Mic PGA routing */ + if (aic32x4->micpga_routing | AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) { + snd_soc_write(codec, AIC32X4_LMICPGANIN, AIC32X4_LMICPGANIN_IN2R_10K); + } + if (aic32x4->micpga_routing | AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) { + snd_soc_write(codec, AIC32X4_RMICPGANIN, AIC32X4_RMICPGANIN_IN1L_10K); + } + + aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + snd_soc_add_controls(codec, aic32x4_snd_controls, + ARRAY_SIZE(aic32x4_snd_controls)); + aic32x4_add_widgets(codec); + + return 0; +} + +static int aic32x4_remove(struct snd_soc_codec *codec) +{ + aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = { + .read = aic32x4_read, + .write = aic32x4_write, + .probe = aic32x4_probe, + .remove = aic32x4_remove, + .suspend = aic32x4_suspend, + .resume = aic32x4_resume, + .set_bias_level = aic32x4_set_bias_level, +}; + +static __devinit int aic32x4_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct aic32x4_pdata *pdata = i2c->dev.platform_data; + struct aic32x4_priv *aic32x4; + int ret; + + aic32x4 = kzalloc(sizeof(struct aic32x4_priv), GFP_KERNEL); + if (aic32x4 == NULL) + return -ENOMEM; + + aic32x4->control_data = i2c; + i2c_set_clientdata(i2c, aic32x4); + + if (pdata) { + aic32x4->power_cfg = pdata->power_cfg; + aic32x4->swapdacs = pdata->swapdacs; + aic32x4->micpga_routing = pdata->micpga_routing; + } else { + aic32x4->power_cfg = 0; + aic32x4->swapdacs = false; + aic32x4->micpga_routing = 0; + } + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_aic32x4, &aic32x4_dai, 1); + if (ret < 0) + kfree(aic32x4); + return ret; +} + +static __devexit int aic32x4_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id aic32x4_i2c_id[] = { + { "tlv320aic32x4", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id); + +static struct i2c_driver aic32x4_i2c_driver = { + .driver = { + .name = "tlv320aic32x4", + .owner = THIS_MODULE, + }, + .probe = aic32x4_i2c_probe, + .remove = __devexit_p(aic32x4_i2c_remove), + .id_table = aic32x4_i2c_id, +}; + +static int __init aic32x4_modinit(void) +{ + int ret = 0; + + ret = i2c_add_driver(&aic32x4_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register aic32x4 I2C driver: %d\n", + ret); + } + return ret; +} +module_init(aic32x4_modinit); + +static void __exit aic32x4_exit(void) +{ + i2c_del_driver(&aic32x4_i2c_driver); +} +module_exit(aic32x4_exit); + +MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver"); +MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h new file mode 100644 index 00000000000..aae2b244039 --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -0,0 +1,143 @@ +/* + * tlv320aic32x4.h + * + * 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 _TLV320AIC32X4_H +#define _TLV320AIC32X4_H + +/* tlv320aic32x4 register space (in decimal to match datasheet) */ + +#define AIC32X4_PAGE1 128 + +#define AIC32X4_PSEL 0 +#define AIC32X4_RESET 1 +#define AIC32X4_CLKMUX 4 +#define AIC32X4_PLLPR 5 +#define AIC32X4_PLLJ 6 +#define AIC32X4_PLLDMSB 7 +#define AIC32X4_PLLDLSB 8 +#define AIC32X4_NDAC 11 +#define AIC32X4_MDAC 12 +#define AIC32X4_DOSRMSB 13 +#define AIC32X4_DOSRLSB 14 +#define AIC32X4_NADC 18 +#define AIC32X4_MADC 19 +#define AIC32X4_AOSR 20 +#define AIC32X4_CLKMUX2 25 +#define AIC32X4_CLKOUTM 26 +#define AIC32X4_IFACE1 27 +#define AIC32X4_IFACE2 28 +#define AIC32X4_IFACE3 29 +#define AIC32X4_BCLKN 30 +#define AIC32X4_IFACE4 31 +#define AIC32X4_IFACE5 32 +#define AIC32X4_IFACE6 33 +#define AIC32X4_DOUTCTL 53 +#define AIC32X4_DINCTL 54 +#define AIC32X4_DACSPB 60 +#define AIC32X4_ADCSPB 61 +#define AIC32X4_DACSETUP 63 +#define AIC32X4_DACMUTE 64 +#define AIC32X4_LDACVOL 65 +#define AIC32X4_RDACVOL 66 +#define AIC32X4_ADCSETUP 81 +#define AIC32X4_ADCFGA 82 +#define AIC32X4_LADCVOL 83 +#define AIC32X4_RADCVOL 84 +#define AIC32X4_LAGC1 86 +#define AIC32X4_LAGC2 87 +#define AIC32X4_LAGC3 88 +#define AIC32X4_LAGC4 89 +#define AIC32X4_LAGC5 90 +#define AIC32X4_LAGC6 91 +#define AIC32X4_LAGC7 92 +#define AIC32X4_RAGC1 94 +#define AIC32X4_RAGC2 95 +#define AIC32X4_RAGC3 96 +#define AIC32X4_RAGC4 97 +#define AIC32X4_RAGC5 98 +#define AIC32X4_RAGC6 99 +#define AIC32X4_RAGC7 100 +#define AIC32X4_PWRCFG (AIC32X4_PAGE1 + 1) +#define AIC32X4_LDOCTL (AIC32X4_PAGE1 + 2) +#define AIC32X4_OUTPWRCTL (AIC32X4_PAGE1 + 9) +#define AIC32X4_CMMODE (AIC32X4_PAGE1 + 10) +#define AIC32X4_HPLROUTE (AIC32X4_PAGE1 + 12) +#define AIC32X4_HPRROUTE (AIC32X4_PAGE1 + 13) +#define AIC32X4_LOLROUTE (AIC32X4_PAGE1 + 14) +#define AIC32X4_LORROUTE (AIC32X4_PAGE1 + 15) +#define AIC32X4_HPLGAIN (AIC32X4_PAGE1 + 16) +#define AIC32X4_HPRGAIN (AIC32X4_PAGE1 + 17) +#define AIC32X4_LOLGAIN (AIC32X4_PAGE1 + 18) +#define AIC32X4_LORGAIN (AIC32X4_PAGE1 + 19) +#define AIC32X4_HEADSTART (AIC32X4_PAGE1 + 20) +#define AIC32X4_MICBIAS (AIC32X4_PAGE1 + 51) +#define AIC32X4_LMICPGAPIN (AIC32X4_PAGE1 + 52) +#define AIC32X4_LMICPGANIN (AIC32X4_PAGE1 + 54) +#define AIC32X4_RMICPGAPIN (AIC32X4_PAGE1 + 55) +#define AIC32X4_RMICPGANIN (AIC32X4_PAGE1 + 57) +#define AIC32X4_FLOATINGINPUT (AIC32X4_PAGE1 + 58) +#define AIC32X4_LMICPGAVOL (AIC32X4_PAGE1 + 59) +#define AIC32X4_RMICPGAVOL (AIC32X4_PAGE1 + 60) + +#define AIC32X4_FREQ_12000000 12000000 +#define AIC32X4_FREQ_24000000 24000000 +#define AIC32X4_FREQ_25000000 25000000 + +#define AIC32X4_WORD_LEN_16BITS 0x00 +#define AIC32X4_WORD_LEN_20BITS 0x01 +#define AIC32X4_WORD_LEN_24BITS 0x02 +#define AIC32X4_WORD_LEN_32BITS 0x03 + +#define AIC32X4_I2S_MODE 0x00 +#define AIC32X4_DSP_MODE 0x01 +#define AIC32X4_RIGHT_JUSTIFIED_MODE 0x02 +#define AIC32X4_LEFT_JUSTIFIED_MODE 0x03 + +#define AIC32X4_AVDDWEAKDISABLE 0x08 +#define AIC32X4_LDOCTLEN 0x01 + +#define AIC32X4_LDOIN_18_36 0x01 +#define AIC32X4_LDOIN2HP 0x02 + +#define AIC32X4_DACSPBLOCK_MASK 0x1f +#define AIC32X4_ADCSPBLOCK_MASK 0x1f + +#define AIC32X4_PLLJ_SHIFT 6 +#define AIC32X4_DOSRMSB_SHIFT 4 + +#define AIC32X4_PLLCLKIN 0x03 + +#define AIC32X4_MICBIAS_LDOIN 0x08 +#define AIC32X4_MICBIAS_2075V 0x60 + +#define AIC32X4_LMICPGANIN_IN2R_10K 0x10 +#define AIC32X4_RMICPGANIN_IN1L_10K 0x10 + +#define AIC32X4_LMICPGAVOL_NOGAIN 0x80 +#define AIC32X4_RMICPGAVOL_NOGAIN 0x80 + +#define AIC32X4_BCLKMASTER 0x08 +#define AIC32X4_WCLKMASTER 0x04 +#define AIC32X4_PLLEN (0x01 << 7) +#define AIC32X4_NDACEN (0x01 << 7) +#define AIC32X4_MDACEN (0x01 << 7) +#define AIC32X4_NADCEN (0x01 << 7) +#define AIC32X4_MADCEN (0x01 << 7) +#define AIC32X4_BCLKEN (0x01 << 7) +#define AIC32X4_DACEN (0x03 << 6) +#define AIC32X4_RDAC2LCHN (0x02 << 2) +#define AIC32X4_LDAC2RCHN (0x02 << 4) +#define AIC32X4_LDAC2LCHN (0x01 << 4) +#define AIC32X4_RDAC2RCHN (0x01 << 2) + +#define AIC32X4_SSTEP2WCLK 0x01 +#define AIC32X4_MUTEON 0x0C +#define AIC32X4_DACMOD2BCLK 0x01 + +#endif /* _TLV320AIC32X4_H */ diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 6c3735dcb05..eb1a0b4e09b 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -1623,6 +1623,7 @@ static const struct i2c_device_id tlv320dac33_i2c_id[] = { }, { }, }; +MODULE_DEVICE_TABLE(i2c, tlv320dac33_i2c_id); static struct i2c_driver tlv320dac33_i2c_driver = { .driver = { diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index e4d464b937d..8512800f632 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -26,6 +26,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> +#include <linux/mfd/core.h> #include <linux/i2c/twl.h> #include <linux/slab.h> #include <sound/core.h> @@ -732,7 +733,8 @@ static int aif_event(struct snd_soc_dapm_widget *w, static void headset_ramp(struct snd_soc_codec *codec, int ramp) { - struct twl4030_codec_audio_data *pdata = codec->dev->platform_data; + struct twl4030_codec_audio_data *pdata = + mfd_get_data(to_platform_device(codec->dev)); unsigned char hs_gain, hs_pop; struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); /* Base values for ramp delay calculation: 2^19 - 2^26 */ @@ -2297,7 +2299,7 @@ static struct snd_soc_codec_driver soc_codec_dev_twl4030 = { static int __devinit twl4030_codec_probe(struct platform_device *pdev) { - struct twl4030_codec_audio_data *pdata = pdev->dev.platform_data; + struct twl4030_codec_audio_data *pdata = mfd_get_data(pdev); if (!pdata) { dev_err(&pdev->dev, "platform_data is missing\n"); diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index e76847a9438..48ffd406a71 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -486,7 +486,8 @@ static struct snd_soc_dai_driver uda134x_dai = { static int uda134x_soc_probe(struct snd_soc_codec *codec) { struct uda134x_priv *uda134x; - struct uda134x_platform_data *pd = dev_get_drvdata(codec->card->dev); + struct uda134x_platform_data *pd = codec->card->dev->platform_data; + int ret; printk(KERN_INFO "UDA134X SoC Audio Codec\n"); diff --git a/sound/soc/codecs/wl1273.c b/sound/soc/codecs/wl1273.c index 861b28f543d..c8a874d0d4c 100644 --- a/sound/soc/codecs/wl1273.c +++ b/sound/soc/codecs/wl1273.c @@ -3,7 +3,7 @@ * * Author: Matti Aaltonen, <matti.j.aaltonen@nokia.com> * - * Copyright: (C) 2010 Nokia Corporation + * Copyright: (C) 2010, 2011 Nokia Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -179,7 +179,12 @@ static int snd_wl1273_get_audio_route(struct snd_kcontrol *kcontrol, return 0; } -static const char *wl1273_audio_route[] = { "Bt", "FmRx", "FmTx" }; +/* + * TODO: Implement the audio routing in the driver. Now this control + * only indicates the setting that has been done elsewhere (in the user + * space). + */ +static const char * const wl1273_audio_route[] = { "Bt", "FmRx", "FmTx" }; static int snd_wl1273_set_audio_route(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -239,7 +244,7 @@ static int snd_wl1273_fm_audio_put(struct snd_kcontrol *kcontrol, return 1; } -static const char *wl1273_audio_strings[] = { "Digital", "Analog" }; +static const char * const wl1273_audio_strings[] = { "Digital", "Analog" }; static const struct soc_enum wl1273_audio_enum = SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wl1273_audio_strings), @@ -436,7 +441,8 @@ EXPORT_SYMBOL_GPL(wl1273_get_format); static int wl1273_probe(struct snd_soc_codec *codec) { - struct wl1273_core **core = codec->dev->platform_data; + struct wl1273_core **core = + mfd_get_data(to_platform_device(codec->dev)); struct wl1273_priv *wl1273; int r; diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c index 80ddf4fd23d..a3b9cbb20ee 100644 --- a/sound/soc/codecs/wm2000.c +++ b/sound/soc/codecs/wm2000.c @@ -836,24 +836,25 @@ static void wm2000_i2c_shutdown(struct i2c_client *i2c) } #ifdef CONFIG_PM -static int wm2000_i2c_suspend(struct i2c_client *i2c, pm_message_t mesg) +static int wm2000_i2c_suspend(struct device *dev) { + struct i2c_client *i2c = to_i2c_client(dev); struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); return wm2000_anc_transition(wm2000, ANC_OFF); } -static int wm2000_i2c_resume(struct i2c_client *i2c) +static int wm2000_i2c_resume(struct device *dev) { + struct i2c_client *i2c = to_i2c_client(dev); struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); return wm2000_anc_set_mode(wm2000); } -#else -#define wm2000_i2c_suspend NULL -#define wm2000_i2c_resume NULL #endif +static SIMPLE_DEV_PM_OPS(wm2000_pm, wm2000_i2c_suspend, wm2000_i2c_resume); + static const struct i2c_device_id wm2000_i2c_id[] = { { "wm2000", 0 }, { } @@ -864,11 +865,10 @@ static struct i2c_driver wm2000_i2c_driver = { .driver = { .name = "wm2000", .owner = THIS_MODULE, + .pm = &wm2000_pm, }, .probe = wm2000_i2c_probe, .remove = __devexit_p(wm2000_i2c_remove), - .suspend = wm2000_i2c_suspend, - .resume = wm2000_i2c_resume, .shutdown = wm2000_i2c_shutdown, .id_table = wm2000_i2c_id, }; diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 3c3bc079167..736b785e375 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -22,6 +22,7 @@ #include <linux/regulator/consumer.h> #include <linux/mfd/wm8400-audio.h> #include <linux/mfd/wm8400-private.h> +#include <linux/mfd/core.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -1377,7 +1378,7 @@ static void wm8400_probe_deferred(struct work_struct *work) static int wm8400_codec_probe(struct snd_soc_codec *codec) { - struct wm8400 *wm8400 = dev_get_platdata(codec->dev); + struct wm8400 *wm8400 = mfd_get_data(to_platform_device(codec->dev)); struct wm8400_priv *priv; int ret; u16 reg; diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 79b02ae125c..3f09deea8d9 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -55,8 +55,10 @@ static int caps_charge = 2000; module_param(caps_charge, int, 0); MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); -static void wm8753_set_dai_mode(struct snd_soc_codec *codec, - struct snd_soc_dai *dai, unsigned int hifi); +static int wm8753_hifi_write_dai_fmt(struct snd_soc_codec *codec, + unsigned int fmt); +static int wm8753_voice_write_dai_fmt(struct snd_soc_codec *codec, + unsigned int fmt); /* * wm8753 register cache @@ -87,6 +89,10 @@ struct wm8753_priv { enum snd_soc_control_type control_type; unsigned int sysclk; unsigned int pcmclk; + + unsigned int voice_fmt; + unsigned int hifi_fmt; + int dai_func; }; @@ -170,9 +176,9 @@ static int wm8753_get_dai(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int mode = snd_soc_read(codec, WM8753_IOCTL); + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); - ucontrol->value.integer.value[0] = (mode & 0xc) >> 2; + ucontrol->value.integer.value[0] = wm8753->dai_func; return 0; } @@ -180,16 +186,26 @@ static int wm8753_set_dai(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int mode = snd_soc_read(codec, WM8753_IOCTL); struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + u16 ioctl; + + if (codec->active) + return -EBUSY; + + ioctl = snd_soc_read(codec, WM8753_IOCTL); + + wm8753->dai_func = ucontrol->value.integer.value[0]; + + if (((ioctl >> 2) & 0x3) == wm8753->dai_func) + return 1; + + ioctl = (ioctl & 0x1f3) | (wm8753->dai_func << 2); + snd_soc_write(codec, WM8753_IOCTL, ioctl); - if (((mode & 0xc) >> 2) == ucontrol->value.integer.value[0]) - return 0; - mode &= 0xfff3; - mode |= (ucontrol->value.integer.value[0] << 2); + wm8753_hifi_write_dai_fmt(codec, wm8753->hifi_fmt); + wm8753_voice_write_dai_fmt(codec, wm8753->voice_fmt); - wm8753->dai_func = ucontrol->value.integer.value[0]; return 1; } @@ -828,10 +844,9 @@ static int wm8753_set_dai_sysclk(struct snd_soc_dai *codec_dai, /* * Set's ADC and Voice DAC format. */ -static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 voice = snd_soc_read(codec, WM8753_PCM) & 0x01ec; /* interface format */ @@ -858,13 +873,6 @@ static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } -static int wm8753_pcm_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - wm8753_set_dai_mode(dai->codec, dai, 0); - return 0; -} - /* * Set PCM DAI bit size and sample rate. */ @@ -905,10 +913,9 @@ static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, /* * Set's PCM dai fmt and BCLK. */ -static int wm8753_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 voice, ioctl; voice = snd_soc_read(codec, WM8753_PCM) & 0x011f; @@ -999,10 +1006,9 @@ static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai, /* * Set's HiFi DAC format. */ -static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 hifi = snd_soc_read(codec, WM8753_HIFI) & 0x01e0; /* interface format */ @@ -1032,10 +1038,9 @@ static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai, /* * Set's I2S DAI format. */ -static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_i2s_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 ioctl, hifi; hifi = snd_soc_read(codec, WM8753_HIFI) & 0x011f; @@ -1098,13 +1103,6 @@ static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } -static int wm8753_i2s_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - wm8753_set_dai_mode(dai->codec, dai, 1); - return 0; -} - /* * Set PCM DAI bit size and sample rate. */ @@ -1147,61 +1145,117 @@ static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, return 0; } -static int wm8753_mode1v_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 clock; /* set clk source as pcmclk */ clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb; snd_soc_write(codec, WM8753_CLOCK, clock); - if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) - return -EINVAL; - return wm8753_pcm_set_dai_fmt(codec_dai, fmt); + return wm8753_vdac_adc_set_dai_fmt(codec, fmt); } -static int wm8753_mode1h_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) - return -EINVAL; - return wm8753_i2s_set_dai_fmt(codec_dai, fmt); + return wm8753_hdac_set_dai_fmt(codec, fmt); } -static int wm8753_mode2_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 clock; /* set clk source as pcmclk */ clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb; snd_soc_write(codec, WM8753_CLOCK, clock); - if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) - return -EINVAL; - return wm8753_i2s_set_dai_fmt(codec_dai, fmt); + return wm8753_vdac_adc_set_dai_fmt(codec, fmt); } -static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 clock; /* set clk source as mclk */ clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb; snd_soc_write(codec, WM8753_CLOCK, clock | 0x4); - if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) + if (wm8753_hdac_set_dai_fmt(codec, fmt) < 0) return -EINVAL; - if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) - return -EINVAL; - return wm8753_i2s_set_dai_fmt(codec_dai, fmt); + return wm8753_vdac_adc_set_dai_fmt(codec, fmt); } +static int wm8753_hifi_write_dai_fmt(struct snd_soc_codec *codec, + unsigned int fmt) +{ + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (wm8753->dai_func) { + case 0: + ret = wm8753_mode1h_set_dai_fmt(codec, fmt); + break; + case 1: + ret = wm8753_mode2_set_dai_fmt(codec, fmt); + break; + case 2: + case 3: + ret = wm8753_mode3_4_set_dai_fmt(codec, fmt); + break; + default: + break; + } + if (ret) + return ret; + + return wm8753_i2s_set_dai_fmt(codec, fmt); +} + +static int wm8753_hifi_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + + wm8753->hifi_fmt = fmt; + + return wm8753_hifi_write_dai_fmt(codec, fmt); +}; + +static int wm8753_voice_write_dai_fmt(struct snd_soc_codec *codec, + unsigned int fmt) +{ + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + if (wm8753->dai_func != 0) + return 0; + + ret = wm8753_mode1v_set_dai_fmt(codec, fmt); + if (ret) + return ret; + ret = wm8753_pcm_set_dai_fmt(codec, fmt); + if (ret) + return ret; + + return 0; +}; + +static int wm8753_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + + wm8753->voice_fmt = fmt; + + return wm8753_voice_write_dai_fmt(codec, fmt); +}; + static int wm8753_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; @@ -1268,57 +1322,25 @@ static int wm8753_set_bias_level(struct snd_soc_codec *codec, * 3. Voice disabled - HIFI over HIFI * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture */ -static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode1 = { - .startup = wm8753_i2s_startup, +static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode = { .hw_params = wm8753_i2s_hw_params, .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode1h_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, -}; - -static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode1 = { - .startup = wm8753_pcm_startup, - .hw_params = wm8753_pcm_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode1v_set_dai_fmt, + .set_fmt = wm8753_hifi_set_dai_fmt, .set_clkdiv = wm8753_set_dai_clkdiv, .set_pll = wm8753_set_dai_pll, .set_sysclk = wm8753_set_dai_sysclk, }; -static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode2 = { - .startup = wm8753_pcm_startup, +static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode = { .hw_params = wm8753_pcm_hw_params, .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode2_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, -}; - -static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode3 = { - .startup = wm8753_i2s_startup, - .hw_params = wm8753_i2s_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode3_4_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, -}; - -static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode4 = { - .startup = wm8753_i2s_startup, - .hw_params = wm8753_i2s_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode3_4_set_dai_fmt, + .set_fmt = wm8753_voice_set_dai_fmt, .set_clkdiv = wm8753_set_dai_clkdiv, .set_pll = wm8753_set_dai_pll, .set_sysclk = wm8753_set_dai_sysclk, }; -static struct snd_soc_dai_driver wm8753_all_dai[] = { +static struct snd_soc_dai_driver wm8753_dai[] = { /* DAI HiFi mode 1 */ { .name = "wm8753-hifi", .playback = { @@ -1326,14 +1348,16 @@ static struct snd_soc_dai_driver wm8753_all_dai[] = { .channels_min = 1, .channels_max = 2, .rates = WM8753_RATES, - .formats = WM8753_FORMATS}, + .formats = WM8753_FORMATS + }, .capture = { /* dummy for fast DAI switching */ .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = WM8753_RATES, - .formats = WM8753_FORMATS}, - .ops = &wm8753_dai_ops_hifi_mode1, + .formats = WM8753_FORMATS + }, + .ops = &wm8753_dai_ops_hifi_mode, }, /* DAI Voice mode 1 */ { .name = "wm8753-voice", @@ -1342,97 +1366,19 @@ static struct snd_soc_dai_driver wm8753_all_dai[] = { .channels_min = 1, .channels_max = 1, .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .ops = &wm8753_dai_ops_voice_mode1, -}, -/* DAI HiFi mode 2 - dummy */ -{ .name = "wm8753-hifi", -}, -/* DAI Voice mode 2 */ -{ .name = "wm8753-voice", - .playback = { - .stream_name = "Voice Playback", - .channels_min = 1, - .channels_max = 1, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .ops = &wm8753_dai_ops_voice_mode2, -}, -/* DAI HiFi mode 3 */ -{ .name = "wm8753-hifi", - .playback = { - .stream_name = "HiFi Playback", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .ops = &wm8753_dai_ops_hifi_mode3, -}, -/* DAI Voice mode 3 - dummy */ -{ .name = "wm8753-voice", -}, -/* DAI HiFi mode 4 */ -{ .name = "wm8753-hifi", - .playback = { - .stream_name = "HiFi Playback", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, + .formats = WM8753_FORMATS, + }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .ops = &wm8753_dai_ops_hifi_mode4, -}, -/* DAI Voice mode 4 - dummy */ -{ .name = "wm8753-voice", -}, -}; - -static struct snd_soc_dai_driver wm8753_dai[] = { - { - .name = "wm8753-aif0", - }, - { - .name = "wm8753-aif1", + .formats = WM8753_FORMATS, }, + .ops = &wm8753_dai_ops_voice_mode, +}, }; -static void wm8753_set_dai_mode(struct snd_soc_codec *codec, - struct snd_soc_dai *dai, unsigned int hifi) -{ - struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); - - if (wm8753->dai_func < 4) { - if (hifi) - dai->driver = &wm8753_all_dai[wm8753->dai_func << 1]; - else - dai->driver = &wm8753_all_dai[(wm8753->dai_func << 1) + 1]; - } - snd_soc_write(codec, WM8753_IOCTL, wm8753->dai_func); -} - static void wm8753_work(struct work_struct *work) { struct snd_soc_dapm_context *dapm = diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 3d4c55f3c7b..ae1cadfae84 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -223,11 +223,12 @@ struct wm8903_priv { int fs; int deemph; + int dcs_pending; + int dcs_cache[4]; + /* Reference count */ int class_w_users; - struct completion wseq; - struct snd_soc_jack *mic_jack; int mic_det; int mic_short; @@ -246,6 +247,12 @@ static int wm8903_volatile_register(struct snd_soc_codec *codec, unsigned int re case WM8903_REVISION_NUMBER: case WM8903_INTERRUPT_STATUS_1: case WM8903_WRITE_SEQUENCER_4: + case WM8903_POWER_MANAGEMENT_3: + case WM8903_POWER_MANAGEMENT_2: + case WM8903_DC_SERVO_READBACK_1: + case WM8903_DC_SERVO_READBACK_2: + case WM8903_DC_SERVO_READBACK_3: + case WM8903_DC_SERVO_READBACK_4: return 1; default: @@ -253,50 +260,6 @@ static int wm8903_volatile_register(struct snd_soc_codec *codec, unsigned int re } } -static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) -{ - u16 reg[5]; - struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); - - BUG_ON(start > 48); - - /* Enable the sequencer if it's not already on */ - reg[0] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_0); - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, - reg[0] | WM8903_WSEQ_ENA); - - dev_dbg(codec->dev, "Starting sequence at %d\n", start); - - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_3, - start | WM8903_WSEQ_START); - - /* Wait for it to complete. If we have the interrupt wired up then - * that will break us out of the poll early. - */ - do { - wait_for_completion_timeout(&wm8903->wseq, - msecs_to_jiffies(10)); - - reg[4] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_4); - } while (reg[4] & WM8903_WSEQ_BUSY); - - dev_dbg(codec->dev, "Sequence complete\n"); - - /* Disable the sequencer again if we enabled it */ - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); - - return 0; -} - -static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache) -{ - int i; - - /* There really ought to be something better we can do here :/ */ - for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++) - cache[i] = codec->hw_read(codec, i); -} - static void wm8903_reset(struct snd_soc_codec *codec) { snd_soc_write(codec, WM8903_SW_RESET_AND_ID, 0); @@ -304,11 +267,6 @@ static void wm8903_reset(struct snd_soc_codec *codec) sizeof(wm8903_reg_defaults)); } -#define WM8903_OUTPUT_SHORT 0x8 -#define WM8903_OUTPUT_OUT 0x4 -#define WM8903_OUTPUT_INT 0x2 -#define WM8903_OUTPUT_IN 0x1 - static int wm8903_cp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -318,97 +276,101 @@ static int wm8903_cp_event(struct snd_soc_dapm_widget *w, return 0; } -/* - * Event for headphone and line out amplifier power changes. Special - * power up/down sequences are required in order to maximise pop/click - * performance. - */ -static int wm8903_output_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +static int wm8903_dcs_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - u16 val; - u16 reg; - u16 dcs_reg; - u16 dcs_bit; - int shift; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); - switch (w->reg) { - case WM8903_POWER_MANAGEMENT_2: - reg = WM8903_ANALOGUE_HP_0; - dcs_bit = 0 + w->shift; + switch (event) { + case SND_SOC_DAPM_POST_PMU: + wm8903->dcs_pending |= 1 << w->shift; break; - case WM8903_POWER_MANAGEMENT_3: - reg = WM8903_ANALOGUE_LINEOUT_0; - dcs_bit = 2 + w->shift; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, WM8903_DC_SERVO_0, + 1 << w->shift, 0); break; - default: - BUG(); - return -EINVAL; /* Spurious warning from some compilers */ } - switch (w->shift) { - case 0: - shift = 0; - break; - case 1: - shift = 4; - break; - default: - BUG(); - return -EINVAL; /* Spurious warning from some compilers */ - } + return 0; +} - if (event & SND_SOC_DAPM_PRE_PMU) { - val = snd_soc_read(codec, reg); +#define WM8903_DCS_MODE_WRITE_STOP 0 +#define WM8903_DCS_MODE_START_STOP 2 - /* Short the output */ - val &= ~(WM8903_OUTPUT_SHORT << shift); - snd_soc_write(codec, reg, val); - } +static void wm8903_seq_notifier(struct snd_soc_dapm_context *dapm, + enum snd_soc_dapm_type event, int subseq) +{ + struct snd_soc_codec *codec = container_of(dapm, + struct snd_soc_codec, dapm); + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); + int dcs_mode = WM8903_DCS_MODE_WRITE_STOP; + int i, val; - if (event & SND_SOC_DAPM_POST_PMU) { - val = snd_soc_read(codec, reg); + /* Complete any pending DC servo starts */ + if (wm8903->dcs_pending) { + dev_dbg(codec->dev, "Starting DC servo for %x\n", + wm8903->dcs_pending); - val |= (WM8903_OUTPUT_IN << shift); - snd_soc_write(codec, reg, val); + /* If we've no cached values then we need to do startup */ + for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) { + if (!(wm8903->dcs_pending & (1 << i))) + continue; - val |= (WM8903_OUTPUT_INT << shift); - snd_soc_write(codec, reg, val); + if (wm8903->dcs_cache[i]) { + dev_dbg(codec->dev, + "Restore DC servo %d value %x\n", + 3 - i, wm8903->dcs_cache[i]); + + snd_soc_write(codec, WM8903_DC_SERVO_4 + i, + wm8903->dcs_cache[i] & 0xff); + } else { + dev_dbg(codec->dev, + "Calibrate DC servo %d\n", 3 - i); + dcs_mode = WM8903_DCS_MODE_START_STOP; + } + } - /* Turn on the output ENA_OUTP */ - val |= (WM8903_OUTPUT_OUT << shift); - snd_soc_write(codec, reg, val); + /* Don't trust the cache for analogue */ + if (wm8903->class_w_users) + dcs_mode = WM8903_DCS_MODE_START_STOP; - /* Enable the DC servo */ - dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0); - dcs_reg |= dcs_bit; - snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg); + snd_soc_update_bits(codec, WM8903_DC_SERVO_2, + WM8903_DCS_MODE_MASK, dcs_mode); - /* Remove the short */ - val |= (WM8903_OUTPUT_SHORT << shift); - snd_soc_write(codec, reg, val); - } + snd_soc_update_bits(codec, WM8903_DC_SERVO_0, + WM8903_DCS_ENA_MASK, wm8903->dcs_pending); - if (event & SND_SOC_DAPM_PRE_PMD) { - val = snd_soc_read(codec, reg); + switch (dcs_mode) { + case WM8903_DCS_MODE_WRITE_STOP: + break; - /* Short the output */ - val &= ~(WM8903_OUTPUT_SHORT << shift); - snd_soc_write(codec, reg, val); + case WM8903_DCS_MODE_START_STOP: + msleep(270); - /* Disable the DC servo */ - dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0); - dcs_reg &= ~dcs_bit; - snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg); + /* Cache the measured offsets for digital */ + if (wm8903->class_w_users) + break; - /* Then disable the intermediate and output stages */ - val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT | - WM8903_OUTPUT_IN) << shift); - snd_soc_write(codec, reg, val); - } + for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) { + if (!(wm8903->dcs_pending & (1 << i))) + continue; - return 0; + val = snd_soc_read(codec, + WM8903_DC_SERVO_READBACK_1 + i); + dev_dbg(codec->dev, "DC servo %d: %x\n", + 3 - i, val); + wm8903->dcs_cache[i] = val; + } + break; + + default: + pr_warn("DCS mode %d delay not set\n", dcs_mode); + break; + } + + wm8903->dcs_pending = 0; + } } /* @@ -674,6 +636,22 @@ static const struct soc_enum lsidetone_enum = static const struct soc_enum rsidetone_enum = SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text); +static const char *aif_text[] = { + "Left", "Right" +}; + +static const struct soc_enum lcapture_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 7, 2, aif_text); + +static const struct soc_enum rcapture_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 6, 2, aif_text); + +static const struct soc_enum lplay_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 5, 2, aif_text); + +static const struct soc_enum rplay_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 4, 2, aif_text); + static const struct snd_kcontrol_new wm8903_snd_controls[] = { /* Input PGAs - No TLV since the scale depends on PGA mode */ @@ -791,6 +769,18 @@ static const struct snd_kcontrol_new lsidetone_mux = static const struct snd_kcontrol_new rsidetone_mux = SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum); +static const struct snd_kcontrol_new lcapture_mux = + SOC_DAPM_ENUM("Left Capture Mux", lcapture_enum); + +static const struct snd_kcontrol_new rcapture_mux = + SOC_DAPM_ENUM("Right Capture Mux", rcapture_enum); + +static const struct snd_kcontrol_new lplay_mux = + SOC_DAPM_ENUM("Left Playback Mux", lplay_enum); + +static const struct snd_kcontrol_new rplay_mux = + SOC_DAPM_ENUM("Right Playback Mux", rplay_enum); + static const struct snd_kcontrol_new left_output_mixer[] = { SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0), SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0), @@ -854,14 +844,26 @@ SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux), SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0), SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0), -SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0), -SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0), +SND_SOC_DAPM_ADC("ADCL", NULL, WM8903_POWER_MANAGEMENT_6, 1, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, WM8903_POWER_MANAGEMENT_6, 0, 0), + +SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lcapture_mux), +SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rcapture_mux), + +SND_SOC_DAPM_AIF_OUT("AIFTXL", "Left HiFi Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFTXR", "Right HiFi Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux), SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux), -SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0), -SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0), +SND_SOC_DAPM_AIF_IN("AIFRXL", "Left Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFRXR", "Right Playback", 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("Left Playback Mux", SND_SOC_NOPM, 0, 0, &lplay_mux), +SND_SOC_DAPM_MUX("Right Playback Mux", SND_SOC_NOPM, 0, 0, &rplay_mux), + +SND_SOC_DAPM_DAC("DACL", NULL, WM8903_POWER_MANAGEMENT_6, 3, 0), +SND_SOC_DAPM_DAC("DACR", NULL, WM8903_POWER_MANAGEMENT_6, 2, 0), SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0, left_output_mixer, ARRAY_SIZE(left_output_mixer)), @@ -873,23 +875,45 @@ SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0, SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), -SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, - 1, 0, NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), -SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, - 0, 0, NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), - -SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0, - NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), -SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0, - NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("Left Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0, + 4, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("Right Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0, + 0, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("Left Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 4, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("Right Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 0, 0, + NULL, 0), + +SND_SOC_DAPM_PGA_S("HPL_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 3, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 1, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("LINEOUTL_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 7, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 6, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 5, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 3, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 2, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 1, 0, + NULL, 0), + +SND_SOC_DAPM_SUPPLY("DCS Master", WM8903_DC_SERVO_0, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_DCS", 3, SND_SOC_NOPM, 3, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("HPR_DCS", 3, SND_SOC_NOPM, 2, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("LINEOUTL_DCS", 3, SND_SOC_NOPM, 1, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("LINEOUTR_DCS", 3, SND_SOC_NOPM, 0, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0, NULL, 0), @@ -899,10 +923,18 @@ SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0, wm8903_cp_event, SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0), }; static const struct snd_soc_dapm_route intercon[] = { + { "CLK_DSP", NULL, "CLK_SYS" }, + { "Mic Bias", NULL, "CLK_SYS" }, + { "HPL_DCS", NULL, "CLK_SYS" }, + { "HPR_DCS", NULL, "CLK_SYS" }, + { "LINEOUTL_DCS", NULL, "CLK_SYS" }, + { "LINEOUTR_DCS", NULL, "CLK_SYS" }, + { "Left Input Mux", "IN1L", "IN1L" }, { "Left Input Mux", "IN2L", "IN2L" }, { "Left Input Mux", "IN3L", "IN3L" }, @@ -943,18 +975,36 @@ static const struct snd_soc_dapm_route intercon[] = { { "Left Input PGA", NULL, "Left Input Mode Mux" }, { "Right Input PGA", NULL, "Right Input Mode Mux" }, + { "Left Capture Mux", "Left", "ADCL" }, + { "Left Capture Mux", "Right", "ADCR" }, + + { "Right Capture Mux", "Left", "ADCL" }, + { "Right Capture Mux", "Right", "ADCR" }, + + { "AIFTXL", NULL, "Left Capture Mux" }, + { "AIFTXR", NULL, "Right Capture Mux" }, + { "ADCL", NULL, "Left Input PGA" }, { "ADCL", NULL, "CLK_DSP" }, { "ADCR", NULL, "Right Input PGA" }, { "ADCR", NULL, "CLK_DSP" }, + { "Left Playback Mux", "Left", "AIFRXL" }, + { "Left Playback Mux", "Right", "AIFRXR" }, + + { "Right Playback Mux", "Left", "AIFRXL" }, + { "Right Playback Mux", "Right", "AIFRXR" }, + { "DACL Sidetone", "Left", "ADCL" }, { "DACL Sidetone", "Right", "ADCR" }, { "DACR Sidetone", "Left", "ADCL" }, { "DACR Sidetone", "Right", "ADCR" }, + { "DACL", NULL, "Left Playback Mux" }, { "DACL", NULL, "DACL Sidetone" }, { "DACL", NULL, "CLK_DSP" }, + + { "DACR", NULL, "Right Playback Mux" }, { "DACR", NULL, "DACR Sidetone" }, { "DACR", NULL, "CLK_DSP" }, @@ -987,11 +1037,35 @@ static const struct snd_soc_dapm_route intercon[] = { { "Left Speaker PGA", NULL, "Left Speaker Mixer" }, { "Right Speaker PGA", NULL, "Right Speaker Mixer" }, - { "HPOUTL", NULL, "Left Headphone Output PGA" }, - { "HPOUTR", NULL, "Right Headphone Output PGA" }, + { "HPL_ENA_DLY", NULL, "Left Headphone Output PGA" }, + { "HPR_ENA_DLY", NULL, "Right Headphone Output PGA" }, + { "LINEOUTL_ENA_DLY", NULL, "Left Line Output PGA" }, + { "LINEOUTR_ENA_DLY", NULL, "Right Line Output PGA" }, + + { "HPL_DCS", NULL, "DCS Master" }, + { "HPR_DCS", NULL, "DCS Master" }, + { "LINEOUTL_DCS", NULL, "DCS Master" }, + { "LINEOUTR_DCS", NULL, "DCS Master" }, - { "LINEOUTL", NULL, "Left Line Output PGA" }, - { "LINEOUTR", NULL, "Right Line Output PGA" }, + { "HPL_DCS", NULL, "HPL_ENA_DLY" }, + { "HPR_DCS", NULL, "HPR_ENA_DLY" }, + { "LINEOUTL_DCS", NULL, "LINEOUTL_ENA_DLY" }, + { "LINEOUTR_DCS", NULL, "LINEOUTR_ENA_DLY" }, + + { "HPL_ENA_OUTP", NULL, "HPL_DCS" }, + { "HPR_ENA_OUTP", NULL, "HPR_DCS" }, + { "LINEOUTL_ENA_OUTP", NULL, "LINEOUTL_DCS" }, + { "LINEOUTR_ENA_OUTP", NULL, "LINEOUTR_DCS" }, + + { "HPL_RMV_SHORT", NULL, "HPL_ENA_OUTP" }, + { "HPR_RMV_SHORT", NULL, "HPR_ENA_OUTP" }, + { "LINEOUTL_RMV_SHORT", NULL, "LINEOUTL_ENA_OUTP" }, + { "LINEOUTR_RMV_SHORT", NULL, "LINEOUTR_ENA_OUTP" }, + + { "HPOUTL", NULL, "HPL_RMV_SHORT" }, + { "HPOUTR", NULL, "HPR_RMV_SHORT" }, + { "LINEOUTL", NULL, "LINEOUTL_RMV_SHORT" }, + { "LINEOUTR", NULL, "LINEOUTR_RMV_SHORT" }, { "LOP", NULL, "Left Speaker PGA" }, { "LON", NULL, "Left Speaker PGA" }, @@ -1019,29 +1093,71 @@ static int wm8903_add_widgets(struct snd_soc_codec *codec) static int wm8903_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - u16 reg; - switch (level) { case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: - reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0); - reg &= ~(WM8903_VMID_RES_MASK); - reg |= WM8903_VMID_RES_50K; - snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg); + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_50K); break; case SND_SOC_BIAS_STANDBY: if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { - snd_soc_write(codec, WM8903_CLOCK_RATES_2, - WM8903_CLK_SYS_ENA); - - /* Change DC servo dither level in startup sequence */ - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11); - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257); - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2); - - wm8903_run_sequence(codec, 0); - wm8903_sync_reg_cache(codec, codec->reg_cache); + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_POBCTRL | WM8903_ISEL_MASK | + WM8903_STARTUP_BIAS_ENA | + WM8903_BIAS_ENA, + WM8903_POBCTRL | + (2 << WM8903_ISEL_SHIFT) | + WM8903_STARTUP_BIAS_ENA); + + snd_soc_update_bits(codec, + WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0, + WM8903_SPK_DISCHARGE, + WM8903_SPK_DISCHARGE); + + msleep(33); + + snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5, + WM8903_SPKL_ENA | WM8903_SPKR_ENA, + WM8903_SPKL_ENA | WM8903_SPKR_ENA); + + snd_soc_update_bits(codec, + WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0, + WM8903_SPK_DISCHARGE, 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_TIE_ENA | + WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | + WM8903_VMID_SOFT_MASK | + WM8903_VMID_RES_MASK | + WM8903_VMID_BUF_ENA, + WM8903_VMID_TIE_ENA | + WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | + (2 << WM8903_VMID_SOFT_SHIFT) | + WM8903_VMID_RES_250K | + WM8903_VMID_BUF_ENA); + + msleep(129); + + snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5, + WM8903_SPKL_ENA | WM8903_SPKR_ENA, + 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_SOFT_MASK, 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_50K); + + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_BIAS_ENA | WM8903_POBCTRL, + WM8903_BIAS_ENA); /* By default no bypass paths are enabled so * enable Class W support. @@ -1054,17 +1170,32 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, WM8903_CP_DYN_V); } - reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0); - reg &= ~(WM8903_VMID_RES_MASK); - reg |= WM8903_VMID_RES_250K; - snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg); + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_250K); break; case SND_SOC_BIAS_OFF: - wm8903_run_sequence(codec, 32); - reg = snd_soc_read(codec, WM8903_CLOCK_RATES_2); - reg &= ~WM8903_CLK_SYS_ENA; - snd_soc_write(codec, WM8903_CLOCK_RATES_2, reg); + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_BIAS_ENA, 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_SOFT_MASK, + 2 << WM8903_VMID_SOFT_SHIFT); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_BUF_ENA, 0); + + msleep(290); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_TIE_ENA | WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | WM8903_VMID_RES_MASK | + WM8903_VMID_SOFT_MASK | + WM8903_VMID_BUF_ENA, 0); + + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_STARTUP_BIAS_ENA, 0); break; } @@ -1489,7 +1620,7 @@ int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, WM8903_MICDET_EINT | WM8903_MICSHRT_EINT, irq_mask); - if (det && shrt) { + if (det || shrt) { /* Enable mic detection, this may not have been set through * platform data (eg, if the defaults are OK). */ snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, @@ -1517,8 +1648,7 @@ static irqreturn_t wm8903_irq(int irq, void *data) int_val = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1) & mask; if (int_val & WM8903_WSEQ_BUSY_EINT) { - dev_dbg(codec->dev, "Write sequencer done\n"); - complete(&wm8903->wseq); + dev_warn(codec->dev, "Write sequencer done\n"); } /* @@ -1765,7 +1895,6 @@ static int wm8903_probe(struct snd_soc_codec *codec) u16 val; wm8903->codec = codec; - init_completion(&wm8903->wseq); ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); if (ret != 0) { @@ -1781,19 +1910,33 @@ static int wm8903_probe(struct snd_soc_codec *codec) } val = snd_soc_read(codec, WM8903_REVISION_NUMBER); - dev_info(codec->dev, "WM8903 revision %d\n", - val & WM8903_CHIP_REV_MASK); + dev_info(codec->dev, "WM8903 revision %c\n", + (val & WM8903_CHIP_REV_MASK) + 'A'); wm8903_reset(codec); /* Set up GPIOs and microphone detection */ if (pdata) { + bool mic_gpio = false; + for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) { if (pdata->gpio_cfg[i] == WM8903_GPIO_NO_CONFIG) continue; snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i, pdata->gpio_cfg[i] & 0xffff); + + val = (pdata->gpio_cfg[i] & WM8903_GP1_FN_MASK) + >> WM8903_GP1_FN_SHIFT; + + switch (val) { + case WM8903_GPn_FN_MICBIAS_CURRENT_DETECT: + case WM8903_GPn_FN_MICBIAS_SHORT_DETECT: + mic_gpio = true; + break; + default: + break; + } } snd_soc_write(codec, WM8903_MIC_BIAS_CONTROL_0, @@ -1804,6 +1947,14 @@ static int wm8903_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + /* If microphone detection is enabled by pdata but + * detected via IRQ then interrupts can be lost before + * the machine driver has set up microphone detection + * IRQs as the IRQs are clear on read. The detection + * will be enabled when the machine driver configures. + */ + WARN_ON(!mic_gpio && (pdata->micdet_cfg & WM8903_MICDET_ENA)); + wm8903->mic_delay = pdata->micdet_delay; } @@ -1863,9 +2014,9 @@ static int wm8903_probe(struct snd_soc_codec *codec) snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val); /* Enable DAC soft mute by default */ - val = snd_soc_read(codec, WM8903_DAC_DIGITAL_1); - val |= WM8903_DAC_MUTEMODE; - snd_soc_write(codec, WM8903_DAC_DIGITAL_1, val); + snd_soc_update_bits(codec, WM8903_DAC_DIGITAL_1, + WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE, + WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE); snd_soc_add_controls(codec, wm8903_snd_controls, ARRAY_SIZE(wm8903_snd_controls)); @@ -1894,6 +2045,7 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8903 = { .reg_word_size = sizeof(u16), .reg_cache_default = wm8903_reg_defaults, .volatile_register = wm8903_volatile_register, + .seq_notifier = wm8903_seq_notifier, }; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) @@ -1932,7 +2084,7 @@ MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id); static struct i2c_driver wm8903_i2c_driver = { .driver = { - .name = "wm8903-codec", + .name = "wm8903", .owner = THIS_MODULE, }, .probe = wm8903_i2c_probe, diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h index e8490f3edd0..db949311c0f 100644 --- a/sound/soc/codecs/wm8903.h +++ b/sound/soc/codecs/wm8903.h @@ -75,6 +75,14 @@ extern int wm8903_mic_detect(struct snd_soc_codec *codec, #define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41 #define WM8903_DC_SERVO_0 0x43 #define WM8903_DC_SERVO_2 0x45 +#define WM8903_DC_SERVO_4 0x47 +#define WM8903_DC_SERVO_5 0x48 +#define WM8903_DC_SERVO_6 0x49 +#define WM8903_DC_SERVO_7 0x4A +#define WM8903_DC_SERVO_READBACK_1 0x51 +#define WM8903_DC_SERVO_READBACK_2 0x52 +#define WM8903_DC_SERVO_READBACK_3 0x53 +#define WM8903_DC_SERVO_READBACK_4 0x54 #define WM8903_ANALOGUE_HP_0 0x5A #define WM8903_ANALOGUE_LINEOUT_0 0x5E #define WM8903_CHARGE_PUMP_0 0x62 @@ -165,7 +173,7 @@ extern int wm8903_mic_detect(struct snd_soc_codec *codec, #define WM8903_VMID_RES_50K 2 #define WM8903_VMID_RES_250K 3 -#define WM8903_VMID_RES_5K 4 +#define WM8903_VMID_RES_5K 6 /* * R8 (0x08) - Analogue DAC 0 diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 30fb48ec279..85e3e630e76 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -93,6 +93,7 @@ static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0); static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0); static const DECLARE_TLV_DB_SCALE(boost_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_SCALE(limiter_tlv, 0, 100, 0); static const struct snd_kcontrol_new wm8978_snd_controls[] = { @@ -144,19 +145,19 @@ static const struct snd_kcontrol_new wm8978_snd_controls[] = { SOC_SINGLE("DAC Playback Limiter Threshold", WM8978_DAC_LIMITER_2, 4, 7, 0), - SOC_SINGLE("DAC Playback Limiter Boost", - WM8978_DAC_LIMITER_2, 0, 15, 0), + SOC_SINGLE_TLV("DAC Playback Limiter Volume", + WM8978_DAC_LIMITER_2, 0, 12, 0, limiter_tlv), SOC_ENUM("ALC Enable Switch", alc1), SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 7, 0), SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0), - SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 7, 0), + SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 10, 0), SOC_SINGLE("ALC Capture Target", WM8978_ALC_CONTROL_2, 0, 15, 0), SOC_ENUM("ALC Capture Mode", alc3), - SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 15, 0), - SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 15, 0), + SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 10, 0), + SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 10, 0), SOC_SINGLE("ALC Capture Noise Gate Switch", WM8978_NOISE_GATE, 3, 1, 0), SOC_SINGLE("ALC Capture Noise Gate Threshold", @@ -211,8 +212,10 @@ static const struct snd_kcontrol_new wm8978_snd_controls[] = { WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 6, 1, 1), /* DAC / ADC oversampling */ - SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL, 8, 1, 0), - SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL, + 5, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL, + 5, 1, 0), }; /* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */ diff --git a/sound/soc/codecs/wm8994-tables.c b/sound/soc/codecs/wm8994-tables.c index 68e9b024dd4..a87adbd05ee 100644 --- a/sound/soc/codecs/wm8994-tables.c +++ b/sound/soc/codecs/wm8994-tables.c @@ -62,8 +62,8 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = { { 0x00FF, 0x00FF }, /* R58 - MICBIAS */ { 0x000F, 0x000F }, /* R59 - LDO 1 */ { 0x0007, 0x0007 }, /* R60 - LDO 2 */ - { 0x0000, 0x0000 }, /* R61 */ - { 0x0000, 0x0000 }, /* R62 */ + { 0xFFFF, 0xFFFF }, /* R61 */ + { 0xFFFF, 0xFFFF }, /* R62 */ { 0x0000, 0x0000 }, /* R63 */ { 0x0000, 0x0000 }, /* R64 */ { 0x0000, 0x0000 }, /* R65 */ @@ -209,9 +209,9 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = { { 0x0000, 0x0000 }, /* R205 */ { 0x0000, 0x0000 }, /* R206 */ { 0x0000, 0x0000 }, /* R207 */ - { 0x0000, 0x0000 }, /* R208 */ - { 0x0000, 0x0000 }, /* R209 */ - { 0x0000, 0x0000 }, /* R210 */ + { 0xFFFF, 0xFFFF }, /* R208 */ + { 0xFFFF, 0xFFFF }, /* R209 */ + { 0xFFFF, 0xFFFF }, /* R210 */ { 0x0000, 0x0000 }, /* R211 */ { 0x0000, 0x0000 }, /* R212 */ { 0x0000, 0x0000 }, /* R213 */ @@ -1573,7 +1573,7 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = { { 0x03C3, 0x03C3 }, /* R1569 - Sidetone */ }; -const __devinitdata u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = { +const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = { 0x8994, /* R0 - Software Reset */ 0x0000, /* R1 - Power Management (1) */ 0x6000, /* R2 - Power Management (2) */ diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 0ca81d3c64e..3dc64c8b6a5 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -102,11 +102,16 @@ struct wm8994_priv { wm8958_micdet_cb jack_cb; void *jack_cb_data; - bool jack_is_mic; - bool jack_is_video; + int micdet_irq; int revision; struct wm8994_pdata *pdata; + + unsigned int aif1clk_enable:1; + unsigned int aif2clk_enable:1; + + unsigned int aif1clk_disable:1; + unsigned int aif2clk_disable:1; }; static int wm8994_readable(struct snd_soc_codec *codec, unsigned int reg) @@ -523,7 +528,7 @@ static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 =snd_soc_codec_get_drvdata(codec); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int block = wm8994_get_retune_mobile_block(kcontrol->id.name); ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block]; @@ -1004,6 +1009,117 @@ static void wm8994_update_class_w(struct snd_soc_codec *codec) } } +static int late_enable_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (wm8994->aif1clk_enable) { + snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, + WM8994_AIF1CLK_ENA_MASK, + WM8994_AIF1CLK_ENA); + wm8994->aif1clk_enable = 0; + } + if (wm8994->aif2clk_enable) { + snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1, + WM8994_AIF2CLK_ENA_MASK, + WM8994_AIF2CLK_ENA); + wm8994->aif2clk_enable = 0; + } + break; + } + + return 0; +} + +static int late_disable_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMD: + if (wm8994->aif1clk_disable) { + snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, + WM8994_AIF1CLK_ENA_MASK, 0); + wm8994->aif1clk_disable = 0; + } + if (wm8994->aif2clk_disable) { + snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1, + WM8994_AIF2CLK_ENA_MASK, 0); + wm8994->aif2clk_disable = 0; + } + break; + } + + return 0; +} + +static int aif1clk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wm8994->aif1clk_enable = 1; + break; + case SND_SOC_DAPM_POST_PMD: + wm8994->aif1clk_disable = 1; + break; + } + + return 0; +} + +static int aif2clk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wm8994->aif2clk_enable = 1; + break; + case SND_SOC_DAPM_POST_PMD: + wm8994->aif2clk_disable = 1; + break; + } + + return 0; +} + +static int adc_mux_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + late_enable_ev(w, kcontrol, event); + return 0; +} + +static int micbias_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + late_enable_ev(w, kcontrol, event); + return 0; +} + +static int dac_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int mask = 1 << w->shift; + + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, + mask, mask); + return 0; +} + static const char *hp_mux_text[] = { "Mixer", "DAC", @@ -1272,11 +1388,68 @@ static const struct soc_enum aif2dacr_src_enum = static const struct snd_kcontrol_new aif2dacr_src_mux = SOC_DAPM_ENUM("AIF2DACR Mux", aif2dacr_src_enum); +static const struct snd_soc_dapm_widget wm8994_lateclk_revd_widgets[] = { +SND_SOC_DAPM_SUPPLY("AIF1CLK", SND_SOC_NOPM, 0, 0, aif1clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("AIF2CLK", SND_SOC_NOPM, 0, 0, aif2clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA_E("Late DAC1L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("Late DAC1R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("Late DAC2L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("Late DAC2R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), + +SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev) +}; + +static const struct snd_soc_dapm_widget wm8994_lateclk_widgets[] = { +SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0) +}; + +static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = { +SND_SOC_DAPM_DAC_E("DAC2L", NULL, SND_SOC_NOPM, 3, 0, + dac_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_DAC_E("DAC2R", NULL, SND_SOC_NOPM, 2, 0, + dac_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_DAC_E("DAC1L", NULL, SND_SOC_NOPM, 1, 0, + dac_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_DAC_E("DAC1R", NULL, SND_SOC_NOPM, 0, 0, + dac_ev, SND_SOC_DAPM_PRE_PMU), +}; + +static const struct snd_soc_dapm_widget wm8994_dac_widgets[] = { +SND_SOC_DAPM_DAC("DAC2L", NULL, WM8994_POWER_MANAGEMENT_5, 3, 0), +SND_SOC_DAPM_DAC("DAC2R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0), +SND_SOC_DAPM_DAC("DAC1L", NULL, WM8994_POWER_MANAGEMENT_5, 1, 0), +SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 0, 0), +}; + +static const struct snd_soc_dapm_widget wm8994_adc_revd_widgets[] = { +SND_SOC_DAPM_MUX_E("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux, + adc_mux_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_MUX_E("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux, + adc_mux_ev, SND_SOC_DAPM_PRE_PMU), +}; + +static const struct snd_soc_dapm_widget wm8994_adc_widgets[] = { +SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux), +SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux), +}; + static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = { SND_SOC_DAPM_INPUT("DMIC1DAT"), SND_SOC_DAPM_INPUT("DMIC2DAT"), SND_SOC_DAPM_INPUT("Clock"), +SND_SOC_DAPM_MICBIAS("MICBIAS", WM8994_MICBIAS, 2, 0), +SND_SOC_DAPM_SUPPLY_S("MICBIAS Supply", 1, SND_SOC_NOPM, 0, 0, micbias_ev, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), @@ -1284,12 +1457,9 @@ SND_SOC_DAPM_SUPPLY("DSP1CLK", WM8994_CLOCKING_1, 3, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("DSP2CLK", WM8994_CLOCKING_1, 2, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("DSPINTCLK", WM8994_CLOCKING_1, 1, 0, NULL, 0), -SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, NULL, 0), -SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0), - -SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", "AIF1 Capture", +SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", NULL, 0, WM8994_POWER_MANAGEMENT_4, 9, 0), -SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", "AIF1 Capture", +SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", NULL, 0, WM8994_POWER_MANAGEMENT_4, 8, 0), SND_SOC_DAPM_AIF_IN_E("AIF1DAC1L", NULL, 0, WM8994_POWER_MANAGEMENT_5, 9, 0, wm8958_aif_ev, @@ -1298,9 +1468,9 @@ SND_SOC_DAPM_AIF_IN_E("AIF1DAC1R", NULL, 0, WM8994_POWER_MANAGEMENT_5, 8, 0, wm8958_aif_ev, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), -SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", "AIF1 Capture", +SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", NULL, 0, WM8994_POWER_MANAGEMENT_4, 11, 0), -SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", "AIF1 Capture", +SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", NULL, 0, WM8994_POWER_MANAGEMENT_4, 10, 0), SND_SOC_DAPM_AIF_IN_E("AIF1DAC2L", NULL, 0, WM8994_POWER_MANAGEMENT_5, 11, 0, wm8958_aif_ev, @@ -1345,6 +1515,7 @@ SND_SOC_DAPM_AIF_IN_E("AIF2DACR", NULL, 0, SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("AIF2DACDAT", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIF1ADCDAT", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AIF2ADCDAT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MUX("AIF1DAC Mux", SND_SOC_NOPM, 0, 0, &aif1dac_mux), @@ -1368,14 +1539,6 @@ SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0), SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0), SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0), -SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux), -SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux), - -SND_SOC_DAPM_DAC("DAC2L", NULL, WM8994_POWER_MANAGEMENT_5, 3, 0), -SND_SOC_DAPM_DAC("DAC2R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0), -SND_SOC_DAPM_DAC("DAC1L", NULL, WM8994_POWER_MANAGEMENT_5, 1, 0), -SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 0, 0), - SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), @@ -1515,14 +1678,12 @@ static const struct snd_soc_dapm_route intercon[] = { { "AIF2ADC Mux", "AIF3DACDAT", "AIF3ADCDAT" }, /* DAC1 inputs */ - { "DAC1L", NULL, "DAC1L Mixer" }, { "DAC1L Mixer", "AIF2 Switch", "AIF2DACL" }, { "DAC1L Mixer", "AIF1.2 Switch", "AIF1DAC2L" }, { "DAC1L Mixer", "AIF1.1 Switch", "AIF1DAC1L" }, { "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" }, { "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" }, - { "DAC1R", NULL, "DAC1R Mixer" }, { "DAC1R Mixer", "AIF2 Switch", "AIF2DACR" }, { "DAC1R Mixer", "AIF1.2 Switch", "AIF1DAC2R" }, { "DAC1R Mixer", "AIF1.1 Switch", "AIF1DAC1R" }, @@ -1531,7 +1692,6 @@ static const struct snd_soc_dapm_route intercon[] = { /* DAC2/AIF2 outputs */ { "AIF2ADCL", NULL, "AIF2DAC2L Mixer" }, - { "DAC2L", NULL, "AIF2DAC2L Mixer" }, { "AIF2DAC2L Mixer", "AIF2 Switch", "AIF2DACL" }, { "AIF2DAC2L Mixer", "AIF1.2 Switch", "AIF1DAC2L" }, { "AIF2DAC2L Mixer", "AIF1.1 Switch", "AIF1DAC1L" }, @@ -1539,13 +1699,17 @@ static const struct snd_soc_dapm_route intercon[] = { { "AIF2DAC2L Mixer", "Right Sidetone Switch", "Right Sidetone" }, { "AIF2ADCR", NULL, "AIF2DAC2R Mixer" }, - { "DAC2R", NULL, "AIF2DAC2R Mixer" }, { "AIF2DAC2R Mixer", "AIF2 Switch", "AIF2DACR" }, { "AIF2DAC2R Mixer", "AIF1.2 Switch", "AIF1DAC2R" }, { "AIF2DAC2R Mixer", "AIF1.1 Switch", "AIF1DAC1R" }, { "AIF2DAC2R Mixer", "Left Sidetone Switch", "Left Sidetone" }, { "AIF2DAC2R Mixer", "Right Sidetone Switch", "Right Sidetone" }, + { "AIF1ADCDAT", NULL, "AIF1ADC1L" }, + { "AIF1ADCDAT", NULL, "AIF1ADC1R" }, + { "AIF1ADCDAT", NULL, "AIF1ADC2L" }, + { "AIF1ADCDAT", NULL, "AIF1ADC2R" }, + { "AIF2ADCDAT", NULL, "AIF2ADC Mux" }, /* AIF3 output */ @@ -1578,6 +1742,33 @@ static const struct snd_soc_dapm_route intercon[] = { { "Right Headphone Mux", "DAC", "DAC1R" }, }; +static const struct snd_soc_dapm_route wm8994_lateclk_revd_intercon[] = { + { "DAC1L", NULL, "Late DAC1L Enable PGA" }, + { "Late DAC1L Enable PGA", NULL, "DAC1L Mixer" }, + { "DAC1R", NULL, "Late DAC1R Enable PGA" }, + { "Late DAC1R Enable PGA", NULL, "DAC1R Mixer" }, + { "DAC2L", NULL, "Late DAC2L Enable PGA" }, + { "Late DAC2L Enable PGA", NULL, "AIF2DAC2L Mixer" }, + { "DAC2R", NULL, "Late DAC2R Enable PGA" }, + { "Late DAC2R Enable PGA", NULL, "AIF2DAC2R Mixer" } +}; + +static const struct snd_soc_dapm_route wm8994_lateclk_intercon[] = { + { "DAC1L", NULL, "DAC1L Mixer" }, + { "DAC1R", NULL, "DAC1R Mixer" }, + { "DAC2L", NULL, "AIF2DAC2L Mixer" }, + { "DAC2R", NULL, "AIF2DAC2R Mixer" }, +}; + +static const struct snd_soc_dapm_route wm8994_revd_intercon[] = { + { "AIF1DACDAT", NULL, "AIF2DACDAT" }, + { "AIF2DACDAT", NULL, "AIF1DACDAT" }, + { "AIF1ADCDAT", NULL, "AIF2ADCDAT" }, + { "AIF2ADCDAT", NULL, "AIF1ADCDAT" }, + { "MICBIAS", NULL, "CLK_SYS" }, + { "MICBIAS", NULL, "MICBIAS Supply" }, +}; + static const struct snd_soc_dapm_route wm8994_intercon[] = { { "AIF2DACL", NULL, "AIF2DAC Mux" }, { "AIF2DACR", NULL, "AIF2DAC Mux" }, @@ -2501,6 +2692,22 @@ static int wm8994_resume(struct snd_soc_codec *codec) { struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int i, ret; + unsigned int val, mask; + + if (wm8994->revision < 4) { + /* force a HW read */ + val = wm8994_reg_read(codec->control_data, + WM8994_POWER_MANAGEMENT_5); + + /* modify the cache only */ + codec->cache_only = 1; + mask = WM8994_DAC1R_ENA | WM8994_DAC1L_ENA | + WM8994_DAC2R_ENA | WM8994_DAC2L_ENA; + val &= mask; + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, + mask, val); + codec->cache_only = 0; + } /* Restore the registers */ ret = snd_soc_cache_sync(codec); @@ -2688,6 +2895,13 @@ static void wm8994_handle_pdata(struct wm8994_priv *wm8994) else snd_soc_add_controls(wm8994->codec, wm8994_eq_controls, ARRAY_SIZE(wm8994_eq_controls)); + + for (i = 0; i < ARRAY_SIZE(pdata->micbias); i++) { + if (pdata->micbias[i]) { + snd_soc_write(codec, WM8958_MICBIAS1 + i, + pdata->micbias[i] & 0xffff); + } + } } /** @@ -2798,47 +3012,18 @@ static void wm8958_default_micdet(u16 status, void *data) int report = 0; /* If nothing present then clear our statuses */ - if (!(status & WM8958_MICD_STS)) { - wm8994->jack_is_video = false; - wm8994->jack_is_mic = false; + if (!(status & WM8958_MICD_STS)) goto done; - } - - /* Assume anything over 475 ohms is a microphone and remember - * that we've seen one (since buttons override it) */ - if (status & 0x600) - wm8994->jack_is_mic = true; - if (wm8994->jack_is_mic) - report |= SND_JACK_MICROPHONE; - /* Video has an impedence of approximately 75 ohms; assume - * this isn't used as a button and remember it since buttons - * override it. */ - if (status & 0x40) - wm8994->jack_is_video = true; - if (wm8994->jack_is_video) - report |= SND_JACK_VIDEOOUT; + report = SND_JACK_MICROPHONE; /* Everything else is buttons; just assign slots */ - if (status & 0x4) + if (status & 0x1c0) report |= SND_JACK_BTN_0; - if (status & 0x8) - report |= SND_JACK_BTN_1; - if (status & 0x10) - report |= SND_JACK_BTN_2; - if (status & 0x20) - report |= SND_JACK_BTN_3; - if (status & 0x80) - report |= SND_JACK_BTN_4; - if (status & 0x100) - report |= SND_JACK_BTN_5; done: - snd_soc_jack_report(wm8994->micdet[0].jack, - SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | - SND_JACK_BTN_3 | SND_JACK_BTN_4 | SND_JACK_BTN_5 | - SND_JACK_MICROPHONE | SND_JACK_VIDEOOUT, - report); + snd_soc_jack_report(wm8994->micdet[0].jack, report, + SND_JACK_BTN_0 | SND_JACK_MICROPHONE); } /** @@ -2937,6 +3122,12 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) wm8994->pdata = dev_get_platdata(codec->dev->parent); wm8994->codec = codec; + if (wm8994->pdata && wm8994->pdata->micdet_irq) + wm8994->micdet_irq = wm8994->pdata->micdet_irq; + else if (wm8994->pdata && wm8994->pdata->irq_base) + wm8994->micdet_irq = wm8994->pdata->irq_base + + WM8994_IRQ_MIC1_DET; + pm_runtime_enable(codec->dev); pm_runtime_resume(codec->dev); @@ -2985,14 +3176,17 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) switch (control->type) { case WM8994: - ret = wm8994_request_irq(codec->control_data, - WM8994_IRQ_MIC1_DET, - wm8994_mic_irq, "Mic 1 detect", - wm8994); - if (ret != 0) - dev_warn(codec->dev, - "Failed to request Mic1 detect IRQ: %d\n", - ret); + if (wm8994->micdet_irq) { + ret = request_threaded_irq(wm8994->micdet_irq, NULL, + wm8994_mic_irq, + IRQF_TRIGGER_RISING, + "Mic1 detect", + wm8994); + if (ret != 0) + dev_warn(codec->dev, + "Failed to request Mic1 detect IRQ: %d\n", + ret); + } ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, @@ -3023,15 +3217,17 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) break; case WM8958: - ret = wm8994_request_irq(codec->control_data, - WM8994_IRQ_MIC1_DET, - wm8958_mic_irq, "Mic detect", - wm8994); - if (ret != 0) - dev_warn(codec->dev, - "Failed to request Mic detect IRQ: %d\n", - ret); - break; + if (wm8994->micdet_irq) { + ret = request_threaded_irq(wm8994->micdet_irq, NULL, + wm8958_mic_irq, + IRQF_TRIGGER_RISING, + "Mic detect", + wm8994); + if (ret != 0) + dev_warn(codec->dev, + "Failed to request Mic detect IRQ: %d\n", + ret); + } } /* Remember if AIFnLRCLK is configured as a GPIO. This should be @@ -3112,10 +3308,31 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) case WM8994: snd_soc_dapm_new_controls(dapm, wm8994_specific_dapm_widgets, ARRAY_SIZE(wm8994_specific_dapm_widgets)); + if (wm8994->revision < 4) { + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets, + ARRAY_SIZE(wm8994_lateclk_revd_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets, + ARRAY_SIZE(wm8994_adc_revd_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets, + ARRAY_SIZE(wm8994_dac_revd_widgets)); + } else { + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets, + ARRAY_SIZE(wm8994_lateclk_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets, + ARRAY_SIZE(wm8994_adc_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets, + ARRAY_SIZE(wm8994_dac_widgets)); + } break; case WM8958: snd_soc_add_controls(codec, wm8958_snd_controls, ARRAY_SIZE(wm8958_snd_controls)); + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets, + ARRAY_SIZE(wm8994_lateclk_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets, + ARRAY_SIZE(wm8994_adc_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets, + ARRAY_SIZE(wm8994_dac_widgets)); snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets, ARRAY_SIZE(wm8958_dapm_widgets)); break; @@ -3129,8 +3346,20 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) case WM8994: snd_soc_dapm_add_routes(dapm, wm8994_intercon, ARRAY_SIZE(wm8994_intercon)); + + if (wm8994->revision < 4) { + snd_soc_dapm_add_routes(dapm, wm8994_revd_intercon, + ARRAY_SIZE(wm8994_revd_intercon)); + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_revd_intercon, + ARRAY_SIZE(wm8994_lateclk_revd_intercon)); + } else { + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon, + ARRAY_SIZE(wm8994_lateclk_intercon)); + } break; case WM8958: + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon, + ARRAY_SIZE(wm8994_lateclk_intercon)); snd_soc_dapm_add_routes(dapm, wm8958_intercon, ARRAY_SIZE(wm8958_intercon)); break; @@ -3142,7 +3371,8 @@ err_irq: wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994); wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994); wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994); - wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994); + if (wm8994->micdet_irq) + free_irq(wm8994->micdet_irq, wm8994); err: kfree(wm8994); return ret; @@ -3159,8 +3389,8 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec) switch (control->type) { case WM8994: - wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, - wm8994); + if (wm8994->micdet_irq) + free_irq(wm8994->micdet_irq, wm8994); wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994); wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, @@ -3170,8 +3400,8 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec) break; case WM8958: - wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, - wm8994); + if (wm8994->micdet_irq) + free_irq(wm8994->micdet_irq, wm8994); break; } kfree(wm8994->retune_mobile_texts); diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 0c355bfc88f..999b8851226 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -43,6 +43,6 @@ struct wm8994_access_mask { }; extern const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE]; -extern const __devinitdata u16 wm8994_reg_defaults[WM8994_CACHE_SIZE]; +extern const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE]; #endif diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 5c224dd917d..55cdf298202 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -15,6 +15,7 @@ #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/delay.h> +#include <linux/device.h> #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> @@ -166,7 +167,7 @@ struct wm9081_priv { int fll_fref; int fll_fout; int tdm_width; - struct wm9081_retune_mobile_config *retune; + struct wm9081_pdata pdata; }; static int wm9081_volatile_register(struct snd_soc_codec *codec, unsigned int reg) @@ -388,27 +389,6 @@ SOC_DAPM_SINGLE("IN2 Switch", WM9081_ANALOGUE_MIXER, 2, 1, 0), SOC_DAPM_SINGLE("Playback Switch", WM9081_ANALOGUE_MIXER, 4, 1, 0), }; -static int speaker_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) -{ - struct snd_soc_codec *codec = w->codec; - unsigned int reg = snd_soc_read(codec, WM9081_POWER_MANAGEMENT); - - switch (event) { - case SND_SOC_DAPM_POST_PMU: - reg |= WM9081_SPK_ENA; - break; - - case SND_SOC_DAPM_PRE_PMD: - reg &= ~WM9081_SPK_ENA; - break; - } - - snd_soc_write(codec, WM9081_POWER_MANAGEMENT, reg); - - return 0; -} - struct _fll_div { u16 fll_fratio; u16 fll_outdiv; @@ -746,9 +726,8 @@ SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0, SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0), -SND_SOC_DAPM_PGA_E("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0, - speaker_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0), SND_SOC_DAPM_OUTPUT("LINEOUT"), SND_SOC_DAPM_OUTPUT("SPKN"), @@ -761,7 +740,7 @@ SND_SOC_DAPM_SUPPLY("TOCLK", WM9081_CLOCK_CONTROL_3, 2, 0, NULL, 0), }; -static const struct snd_soc_dapm_route audio_paths[] = { +static const struct snd_soc_dapm_route wm9081_audio_paths[] = { { "DAC", NULL, "CLK_SYS" }, { "DAC", NULL, "CLK_DSP" }, @@ -779,8 +758,10 @@ static const struct snd_soc_dapm_route audio_paths[] = { { "Speaker PGA", NULL, "TOCLK" }, { "Speaker PGA", NULL, "CLK_SYS" }, - { "SPKN", NULL, "Speaker PGA" }, - { "SPKP", NULL, "Speaker PGA" }, + { "Speaker", NULL, "Speaker PGA" }, + + { "SPKN", NULL, "Speaker" }, + { "SPKP", NULL, "Speaker" }, }; static int wm9081_set_bias_level(struct snd_soc_codec *codec, @@ -1081,21 +1062,22 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream, aif4 |= wm9081->bclk / wm9081->fs; /* Apply a ReTune Mobile configuration if it's in use */ - if (wm9081->retune) { - struct wm9081_retune_mobile_config *retune = wm9081->retune; + if (wm9081->pdata.num_retune_configs) { + struct wm9081_pdata *pdata = &wm9081->pdata; struct wm9081_retune_mobile_setting *s; int eq1; best = 0; - best_val = abs(retune->configs[0].rate - wm9081->fs); - for (i = 0; i < retune->num_configs; i++) { - cur_val = abs(retune->configs[i].rate - wm9081->fs); + best_val = abs(pdata->retune_configs[0].rate - wm9081->fs); + for (i = 0; i < pdata->num_retune_configs; i++) { + cur_val = abs(pdata->retune_configs[i].rate - + wm9081->fs); if (cur_val < best_val) { best_val = cur_val; best = i; } } - s = &retune->configs[best]; + s = &pdata->retune_configs[best]; dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n", s->name, s->rate); @@ -1138,10 +1120,9 @@ static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute) return 0; } -static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai, +static int wm9081_set_sysclk(struct snd_soc_codec *codec, int clk_id, unsigned int freq, int dir) { - struct snd_soc_codec *codec = codec_dai->codec; struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); switch (clk_id) { @@ -1206,7 +1187,6 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, static struct snd_soc_dai_ops wm9081_dai_ops = { .hw_params = wm9081_hw_params, - .set_sysclk = wm9081_set_sysclk, .set_fmt = wm9081_set_dai_fmt, .digital_mute = wm9081_digital_mute, .set_tdm_slot = wm9081_set_tdm_slot, @@ -1230,7 +1210,6 @@ static struct snd_soc_dai_driver wm9081_dai = { static int wm9081_probe(struct snd_soc_codec *codec) { struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); - struct snd_soc_dapm_context *dapm = &codec->dapm; int ret; u16 reg; @@ -1254,6 +1233,14 @@ static int wm9081_probe(struct snd_soc_codec *codec) return ret; } + reg = 0; + if (wm9081->pdata.irq_high) + reg |= WM9081_IRQ_POL; + if (!wm9081->pdata.irq_cmos) + reg |= WM9081_IRQ_OP_CTRL; + snd_soc_update_bits(codec, WM9081_INTERRUPT_CONTROL, + WM9081_IRQ_POL | WM9081_IRQ_OP_CTRL, reg); + wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Enable zero cross by default */ @@ -1265,17 +1252,13 @@ static int wm9081_probe(struct snd_soc_codec *codec) snd_soc_add_controls(codec, wm9081_snd_controls, ARRAY_SIZE(wm9081_snd_controls)); - if (!wm9081->retune) { + if (!wm9081->pdata.num_retune_configs) { dev_dbg(codec->dev, "No ReTune Mobile data, using normal EQ\n"); snd_soc_add_controls(codec, wm9081_eq_controls, ARRAY_SIZE(wm9081_eq_controls)); } - snd_soc_dapm_new_controls(dapm, wm9081_dapm_widgets, - ARRAY_SIZE(wm9081_dapm_widgets)); - snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); - return ret; } @@ -1319,11 +1302,19 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9081 = { .remove = wm9081_remove, .suspend = wm9081_suspend, .resume = wm9081_resume, + + .set_sysclk = wm9081_set_sysclk, .set_bias_level = wm9081_set_bias_level, + .reg_cache_size = ARRAY_SIZE(wm9081_reg_defaults), .reg_word_size = sizeof(u16), .reg_cache_default = wm9081_reg_defaults, .volatile_register = wm9081_volatile_register, + + .dapm_widgets = wm9081_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm9081_dapm_widgets), + .dapm_routes = wm9081_audio_paths, + .num_dapm_routes = ARRAY_SIZE(wm9081_audio_paths), }; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) @@ -1341,6 +1332,10 @@ static __devinit int wm9081_i2c_probe(struct i2c_client *i2c, wm9081->control_type = SND_SOC_I2C; wm9081->control_data = i2c; + if (dev_get_platdata(&i2c->dev)) + memcpy(&wm9081->pdata, dev_get_platdata(&i2c->dev), + sizeof(wm9081->pdata)); + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm9081, &wm9081_dai, 1); if (ret < 0) @@ -1363,7 +1358,7 @@ MODULE_DEVICE_TABLE(i2c, wm9081_i2c_id); static struct i2c_driver wm9081_i2c_driver = { .driver = { - .name = "wm9081-codec", + .name = "wm9081", .owner = THIS_MODULE, }, .probe = wm9081_i2c_probe, diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 613df5db0b3..7b6b3c18e29 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -82,7 +82,8 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op) } while (reg & op && count < 400); if (reg & op) - dev_err(codec->dev, "Timed out waiting for DC Servo\n"); + dev_err(codec->dev, "Timed out waiting for DC Servo %x\n", + op); } /* @@ -674,6 +675,9 @@ SND_SOC_DAPM_OUTPUT("LINEOUT2N"), }; static const struct snd_soc_dapm_route analogue_routes[] = { + { "MICBIAS1", NULL, "CLK_SYS" }, + { "MICBIAS2", NULL, "CLK_SYS" }, + { "IN1L PGA", "IN1LP Switch", "IN1LP" }, { "IN1L PGA", "IN1LN Switch", "IN1LN" }, |