diff options
author | Takashi Iwai <tiwai@suse.de> | 2010-08-05 11:17:01 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2010-08-05 11:17:01 +0200 |
commit | e71981343ad29b5d929f82ac56c0b27b8ea0e540 (patch) | |
tree | 47135be4252faecbc0e5508658a58f8afd197fff /sound | |
parent | 2603798070a80d76e7e6d2992ba4ec74addcec90 (diff) | |
parent | bda7d2a862e6b788bca2d02d38a07966a9c92e48 (diff) | |
download | lwn-e71981343ad29b5d929f82ac56c0b27b8ea0e540.tar.gz lwn-e71981343ad29b5d929f82ac56c0b27b8ea0e540.zip |
Merge branch 'topic/asoc' into for-linus
Diffstat (limited to 'sound')
104 files changed, 8717 insertions, 626 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index b1749bc67979..3e598e756e54 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -28,9 +28,13 @@ source "sound/soc/atmel/Kconfig" source "sound/soc/au1x/Kconfig" source "sound/soc/blackfin/Kconfig" source "sound/soc/davinci/Kconfig" +source "sound/soc/ep93xx/Kconfig" source "sound/soc/fsl/Kconfig" source "sound/soc/imx/Kconfig" +source "sound/soc/jz4740/Kconfig" +source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" +source "sound/soc/kirkwood/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" source "sound/soc/s6000/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 1470141d4167..eb183443eee4 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -6,9 +6,13 @@ obj-$(CONFIG_SND_SOC) += atmel/ obj-$(CONFIG_SND_SOC) += au1x/ obj-$(CONFIG_SND_SOC) += blackfin/ obj-$(CONFIG_SND_SOC) += davinci/ +obj-$(CONFIG_SND_SOC) += ep93xx/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += imx/ +obj-$(CONFIG_SND_SOC) += jz4740/ +obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ +obj-$(CONFIG_SND_SOC) += kirkwood/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += s3c24xx/ obj-$(CONFIG_SND_SOC) += s6000/ diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c index f6b3cc04b34b..dc5249fba85c 100644 --- a/sound/soc/atmel/atmel-pcm.c +++ b/sound/soc/atmel/atmel-pcm.c @@ -77,7 +77,6 @@ struct atmel_runtime_data { size_t period_size; dma_addr_t period_ptr; /* physical address of next period */ - int periods; /* period index of period_ptr */ /* PDC register save */ u32 pdc_xpr_save; diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index 0b59806905d1..c85844d4845b 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -549,7 +549,6 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n", ssc_p->daifmt); return -EINVAL; - break; } pr_debug("atmel_ssc_hw_params: " "RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n", diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c index a61ccd2d505f..d14a5a91a465 100644 --- a/sound/soc/au1x/psc-ac97.c +++ b/sound/soc/au1x/psc-ac97.c @@ -375,12 +375,10 @@ static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev) } ret = -EBUSY; - wd->ioarea = request_mem_region(r->start, r->end - r->start + 1, - "au1xpsc_ac97"); - if (!wd->ioarea) + if (!request_mem_region(r->start, resource_size(r), pdev->name)) goto out0; - wd->mmio = ioremap(r->start, 0xffff); + wd->mmio = ioremap(r->start, resource_size(r)); if (!wd->mmio) goto out1; @@ -410,8 +408,7 @@ static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev) snd_soc_unregister_dai(&au1xpsc_ac97_dai); out1: - release_resource(wd->ioarea); - kfree(wd->ioarea); + release_mem_region(r->start, resource_size(r)); out0: kfree(wd); return ret; @@ -420,6 +417,7 @@ out0: static int __devexit au1xpsc_ac97_drvremove(struct platform_device *pdev) { struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev); + struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (wd->dmapd) au1xpsc_pcm_destroy(wd->dmapd); @@ -433,8 +431,7 @@ static int __devexit au1xpsc_ac97_drvremove(struct platform_device *pdev) au_sync(); iounmap(wd->mmio); - release_resource(wd->ioarea); - kfree(wd->ioarea); + release_mem_region(r->start, resource_size(r)); kfree(wd); au1xpsc_ac97_workdata = NULL; /* MDEV */ diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c index 24454c98d0ee..6083fe7799fa 100644 --- a/sound/soc/au1x/psc-i2s.c +++ b/sound/soc/au1x/psc-i2s.c @@ -321,12 +321,10 @@ static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev) } ret = -EBUSY; - wd->ioarea = request_mem_region(r->start, r->end - r->start + 1, - "au1xpsc_i2s"); - if (!wd->ioarea) + if (!request_mem_region(r->start, resource_size(r), pdev->name)) goto out0; - wd->mmio = ioremap(r->start, 0xffff); + wd->mmio = ioremap(r->start, resource_size(r)); if (!wd->mmio) goto out1; @@ -362,8 +360,7 @@ static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev) snd_soc_unregister_dai(&au1xpsc_i2s_dai); out1: - release_resource(wd->ioarea); - kfree(wd->ioarea); + release_mem_region(r->start, resource_size(r)); out0: kfree(wd); return ret; @@ -372,6 +369,7 @@ out0: static int __devexit au1xpsc_i2s_drvremove(struct platform_device *pdev) { struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev); + struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (wd->dmapd) au1xpsc_pcm_destroy(wd->dmapd); @@ -384,8 +382,7 @@ static int __devexit au1xpsc_i2s_drvremove(struct platform_device *pdev) au_sync(); iounmap(wd->mmio); - release_resource(wd->ioarea); - kfree(wd->ioarea); + release_mem_region(r->start, resource_size(r)); kfree(wd); au1xpsc_i2s_workdata = NULL; /* MDEV */ diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h index 32d3807d3f5a..093775d4dc3e 100644 --- a/sound/soc/au1x/psc.h +++ b/sound/soc/au1x/psc.h @@ -32,7 +32,6 @@ struct au1xpsc_audio_data { unsigned long rate; unsigned long pm[2]; - struct resource *ioarea; struct mutex lock; struct platform_device *dmapd; }; diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c index 523b7fc33f4e..c0eba5109980 100644 --- a/sound/soc/blackfin/bf5xx-ac97.c +++ b/sound/soc/blackfin/bf5xx-ac97.c @@ -255,8 +255,7 @@ EXPORT_SYMBOL_GPL(soc_ac97_ops); #ifdef CONFIG_PM static int bf5xx_ac97_suspend(struct snd_soc_dai *dai) { - struct sport_device *sport = - (struct sport_device *)dai->private_data; + struct sport_device *sport = dai->private_data; pr_debug("%s : sport %d\n", __func__, dai->id); if (!dai->active) @@ -271,8 +270,7 @@ static int bf5xx_ac97_suspend(struct snd_soc_dai *dai) static int bf5xx_ac97_resume(struct snd_soc_dai *dai) { int ret; - struct sport_device *sport = - (struct sport_device *)dai->private_data; + struct sport_device *sport = dai->private_data; pr_debug("%s : sport %d\n", __func__, dai->id); if (!dai->active) diff --git a/sound/soc/blackfin/bf5xx-tdm.c b/sound/soc/blackfin/bf5xx-tdm.c index 4b360124083e..24c14269f4bc 100644 --- a/sound/soc/blackfin/bf5xx-tdm.c +++ b/sound/soc/blackfin/bf5xx-tdm.c @@ -210,8 +210,7 @@ static int bf5xx_tdm_set_channel_map(struct snd_soc_dai *dai, #ifdef CONFIG_PM static int bf5xx_tdm_suspend(struct snd_soc_dai *dai) { - struct sport_device *sport = - (struct sport_device *)dai->private_data; + struct sport_device *sport = dai->private_data; if (!dai->active) return 0; @@ -225,8 +224,7 @@ static int bf5xx_tdm_suspend(struct snd_soc_dai *dai) static int bf5xx_tdm_resume(struct snd_soc_dai *dai) { int ret; - struct sport_device *sport = - (struct sport_device *)dai->private_data; + struct sport_device *sport = dai->private_data; if (!dai->active) return 0; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5da30eb6ad00..83f5c67d3c41 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -22,9 +22,11 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4642 if I2C select SND_SOC_AK4671 if I2C select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC + select SND_SOC_CS42L51 if I2C select SND_SOC_CS4270 if I2C - select SND_SOC_MAX9877 if I2C select SND_SOC_DA7210 if I2C + select SND_SOC_JZ4740 if SOC_JZ4740 + select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C @@ -48,6 +50,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8727 select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8741 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI @@ -120,13 +123,13 @@ config SND_SOC_AK4671 config SND_SOC_CQ0093VC tristate +config SND_SOC_CS42L51 + tristate + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate -config SND_SOC_DA7210 - tristate - # Cirrus Logic CS4270 Codec VD = 3.3V Errata # Select if you are affected by the errata where the part will not function # if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will @@ -138,9 +141,15 @@ config SND_SOC_CS4270_VD33_ERRATA config SND_SOC_CX20442 tristate +config SND_SOC_JZ4740_CODEC + tristate + config SND_SOC_L3 tristate +config SND_SOC_DA7210 + tristate + config SND_SOC_PCM3008 tristate @@ -206,6 +215,9 @@ config SND_SOC_WM8728 config SND_SOC_WM8731 tristate +config SND_SOC_WM8741 + tristate + config SND_SOC_WM8750 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 91429eab0707..53524095759c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -9,6 +9,7 @@ snd-soc-ak4535-objs := ak4535.o snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-cq93vc-objs := cq93vc.o +snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o @@ -34,6 +35,7 @@ snd-soc-wm8711-objs := wm8711.o snd-soc-wm8727-objs := wm8727.o snd-soc-wm8728-objs := wm8728.o snd-soc-wm8731-objs := wm8731.o +snd-soc-wm8741-objs := wm8741.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o snd-soc-wm8776-objs := wm8776.o @@ -56,6 +58,7 @@ snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o +snd-soc-jz4740-codec-objs := jz4740.o # Amp snd-soc-max9877-objs := max9877.o @@ -74,10 +77,12 @@ 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_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_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.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_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o @@ -99,6 +104,7 @@ obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o obj-$(CONFIG_SND_SOC_WM8727) += snd-soc-wm8727.o obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o +obj-$(CONFIG_SND_SOC_WM8741) += snd-soc-wm8741.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c index 217538423225..a01006c8c606 100644 --- a/sound/soc/codecs/ad1836.c +++ b/sound/soc/codecs/ad1836.c @@ -272,6 +272,7 @@ static int ad1836_register(struct ad1836_priv *ad1836) if (ad1836_codec) { dev_err(codec->dev, "Another ad1836 is registered\n"); + kfree(ad1836); return -EINVAL; } diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index c8ca1142b2f4..1def75e4862f 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -24,6 +24,7 @@ /* codec private data */ struct ad193x_priv { + unsigned int sysclk; struct snd_soc_codec codec; u8 reg_cache[AD193X_NUM_REGS]; }; @@ -251,15 +252,32 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +static int ad193x_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 ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec); + switch (freq) { + case 12288000: + case 18432000: + case 24576000: + case 36864000: + ad193x->sysclk = freq; + return 0; + } + return -EINVAL; +} + static int ad193x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - int word_len = 0, reg = 0; + int word_len = 0, reg = 0, master_rate = 0; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec); /* bit size */ switch (params_format(params)) { @@ -275,6 +293,25 @@ static int ad193x_hw_params(struct snd_pcm_substream *substream, break; } + switch (ad193x->sysclk) { + case 12288000: + master_rate = AD193X_PLL_INPUT_256; + break; + case 18432000: + master_rate = AD193X_PLL_INPUT_384; + break; + case 24576000: + master_rate = AD193X_PLL_INPUT_512; + break; + case 36864000: + master_rate = AD193X_PLL_INPUT_768; + break; + } + + reg = snd_soc_read(codec, AD193X_PLL_CLK_CTRL0); + reg = (reg & AD193X_PLL_INPUT_MASK) | master_rate; + snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, reg); + reg = snd_soc_read(codec, AD193X_DAC_CTRL2); reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len; snd_soc_write(codec, AD193X_DAC_CTRL2, reg); @@ -348,6 +385,7 @@ static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) /* pll input: mclki/xi */ snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */ snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04); + ad193x->sysclk = 12288000; ret = snd_soc_register_codec(codec); if (ret != 0) { @@ -383,6 +421,7 @@ static struct snd_soc_dai_ops ad193x_dai_ops = { .hw_params = ad193x_hw_params, .digital_mute = ad193x_mute, .set_tdm_slot = ad193x_set_tdm_slot, + .set_sysclk = ad193x_set_dai_sysclk, .set_fmt = ad193x_set_dai_fmt, }; diff --git a/sound/soc/codecs/ad193x.h b/sound/soc/codecs/ad193x.h index a03c880d52f9..654ba64ae04c 100644 --- a/sound/soc/codecs/ad193x.h +++ b/sound/soc/codecs/ad193x.h @@ -11,6 +11,11 @@ #define AD193X_PLL_CLK_CTRL0 0x800 #define AD193X_PLL_POWERDOWN 0x01 +#define AD193X_PLL_INPUT_MASK (~0x6) +#define AD193X_PLL_INPUT_256 (0 << 1) +#define AD193X_PLL_INPUT_384 (1 << 1) +#define AD193X_PLL_INPUT_512 (2 << 1) +#define AD193X_PLL_INPUT_768 (3 << 1) #define AD193X_PLL_CLK_CTRL1 0x801 #define AD193X_DAC_CTRL0 0x802 #define AD193X_DAC_POWERDOWN 0x01 diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 7528a54102b5..3d7dc55305ec 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -22,20 +22,13 @@ * AK4643 is tested. */ -#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/slab.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> +#include <sound/tlv.h> #include "ak4642.h" @@ -111,6 +104,23 @@ struct snd_soc_codec_device soc_codec_dev_ak4642; +/* + * Playback Volume (table 39) + * + * max : 0x00 : +12.0 dB + * ( 0.5 dB step ) + * min : 0xFE : -115.0 dB + * mute: 0xFF + */ +static const DECLARE_TLV_DB_SCALE(out_tlv, -11500, 50, 1); + +static const struct snd_kcontrol_new ak4642_snd_controls[] = { + + SOC_DOUBLE_R_TLV("Digital Playback Volume", L_DVC, R_DVC, + 0, 0xFF, 1, out_tlv), +}; + + /* codec private data */ struct ak4642_priv { struct snd_soc_codec codec; @@ -204,7 +214,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, * * PLL, Master Mode * Audio I/F Format :MSB justified (ADC & DAC) - * Digital Volume: -8dB * Bass Boost Level : Middle * * This operation came from example code of @@ -214,8 +223,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, ak4642_write(codec, 0x0e, 0x19); ak4642_write(codec, 0x09, 0x91); ak4642_write(codec, 0x0c, 0x91); - ak4642_write(codec, 0x0a, 0x28); - ak4642_write(codec, 0x0d, 0x28); ak4642_write(codec, 0x00, 0x64); snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK, PMHP); snd_soc_update_bits(codec, PW_MGMT2, HPMTN, HPMTN); @@ -491,8 +498,10 @@ static int ak4642_i2c_probe(struct i2c_client *i2c, codec->control_data = i2c; ret = ak4642_init(ak4642); - if (ret < 0) + if (ret < 0) { printk(KERN_ERR "failed to initialise AK4642\n"); + kfree(ak4642); + } return ret; } @@ -548,6 +557,9 @@ static int ak4642_probe(struct platform_device *pdev) goto pcm_err; } + snd_soc_add_controls(ak4642_codec, ak4642_snd_controls, + ARRAY_SIZE(ak4642_snd_controls)); + dev_info(&pdev->dev, "AK4642 Audio Codec %s", AK4642_VERSION); return ret; diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c new file mode 100644 index 000000000000..dd9b8550c402 --- /dev/null +++ b/sound/soc/codecs/cs42l51.c @@ -0,0 +1,763 @@ +/* + * cs42l51.c + * + * ASoC Driver for Cirrus Logic CS42L51 codecs + * + * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com> + * + * Based on cs4270.c - Copyright (c) Freescale Semiconductor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This 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. + * + * For now: + * - Only I2C is support. Not SPI + * - master mode *NOT* supported + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <linux/i2c.h> + +#include "cs42l51.h" + +enum master_slave_mode { + MODE_SLAVE, + MODE_SLAVE_AUTO, + MODE_MASTER, +}; + +struct cs42l51_private { + unsigned int mclk; + unsigned int audio_mode; /* The mode (I2S or left-justified) */ + enum master_slave_mode func; + struct snd_soc_codec codec; + u8 reg_cache[CS42L51_NUMREGS]; +}; + +static struct snd_soc_codec *cs42l51_codec; + +#define CS42L51_FORMATS ( \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) + +static int cs42l51_fill_cache(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache + 1; + struct i2c_client *i2c_client = codec->control_data; + s32 length; + + length = i2c_smbus_read_i2c_block_data(i2c_client, + CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache); + if (length != CS42L51_NUMREGS) { + dev_err(&i2c_client->dev, + "I2C read failure, addr=0x%x (ret=%d vs %d)\n", + i2c_client->addr, length, CS42L51_NUMREGS); + return -EIO; + } + + return 0; +} + +static int cs42l51_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct snd_soc_codec *codec; + struct cs42l51_private *cs42l51; + int ret = 0; + int reg; + + if (cs42l51_codec) + return -EBUSY; + + /* Verify that we have a CS42L51 */ + ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to read I2C\n"); + goto error; + } + + if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) && + (ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) { + dev_err(&i2c_client->dev, "Invalid chip id\n"); + ret = -ENODEV; + goto error; + } + + dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n", + ret & 7); + + cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL); + if (!cs42l51) { + dev_err(&i2c_client->dev, "could not allocate codec\n"); + return -ENOMEM; + } + codec = &cs42l51->codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->dev = &i2c_client->dev; + codec->name = "CS42L51"; + codec->owner = THIS_MODULE; + codec->dai = &cs42l51_dai; + codec->num_dai = 1; + snd_soc_codec_set_drvdata(codec, cs42l51); + + codec->control_data = i2c_client; + codec->reg_cache = cs42l51->reg_cache; + codec->reg_cache_size = CS42L51_NUMREGS; + i2c_set_clientdata(i2c_client, codec); + + ret = cs42l51_fill_cache(codec); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to fill register cache\n"); + goto error_alloc; + } + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to set cache I/O: %d\n", ret); + goto error_alloc; + } + + /* + * DAC configuration + * - Use signal processor + * - auto mute + * - vol changes immediate + * - no de-emphasize + */ + reg = CS42L51_DAC_CTL_DATA_SEL(1) + | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0); + ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg); + if (ret < 0) + goto error_alloc; + + cs42l51_dai.dev = codec->dev; + cs42l51_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto error_alloc; + } + + ret = snd_soc_register_dai(&cs42l51_dai); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to register DAIe\n"); + goto error_reg; + } + + return 0; + +error_reg: + snd_soc_unregister_codec(codec); +error_alloc: + kfree(cs42l51); +error: + return ret; +} + +static int cs42l51_i2c_remove(struct i2c_client *client) +{ + struct cs42l51_private *cs42l51 = i2c_get_clientdata(client); + snd_soc_unregister_dai(&cs42l51_dai); + snd_soc_unregister_codec(cs42l51_codec); + cs42l51_codec = NULL; + kfree(cs42l51); + return 0; +} + + +static const struct i2c_device_id cs42l51_id[] = { + {"cs42l51", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs42l51_id); + +static struct i2c_driver cs42l51_i2c_driver = { + .driver = { + .name = "CS42L51 I2C", + .owner = THIS_MODULE, + }, + .id_table = cs42l51_id, + .probe = cs42l51_i2c_probe, + .remove = cs42l51_i2c_remove, +}; + +static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3; + + switch (value) { + default: + case 0: + ucontrol->value.integer.value[0] = 0; + break; + /* same value : (L+R)/2 and (R+L)/2 */ + case 1: + case 2: + ucontrol->value.integer.value[0] = 1; + break; + case 3: + ucontrol->value.integer.value[0] = 2; + break; + } + + return 0; +} + +#define CHAN_MIX_NORMAL 0x00 +#define CHAN_MIX_BOTH 0x55 +#define CHAN_MIX_SWAP 0xFF + +static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned char val; + + switch (ucontrol->value.integer.value[0]) { + default: + case 0: + val = CHAN_MIX_NORMAL; + break; + case 1: + val = CHAN_MIX_BOTH; + break; + case 2: + val = CHAN_MIX_SWAP; + break; + } + + snd_soc_write(codec, CS42L51_PCM_MIXER, val); + + return 1; +} + +static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0); +static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); +/* This is a lie. after -102 db, it stays at -102 */ +/* maybe a range would be better */ +static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0); + +static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); +static const char *chan_mix[] = { + "L R", + "L+R", + "R L", +}; + +static const struct soc_enum cs42l51_chan_mix = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix); + +static const struct snd_kcontrol_new cs42l51_snd_controls[] = { + SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", + CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, + 7, 0xffffff99, 0x18, adc_pcm_tlv), + SOC_DOUBLE_R("PCM Playback Switch", + CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1), + SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", + CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL, + 8, 0xffffff19, 0x18, aout_tlv), + SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", + CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, + 7, 0xffffff99, 0x18, adc_pcm_tlv), + SOC_DOUBLE_R("ADC Mixer Switch", + CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1), + SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0), + SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0), + SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0), + SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0), + SOC_DOUBLE_TLV("Mic Boost Volume", + CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), + SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv), + SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv), + SOC_ENUM_EXT("PCM channel mixer", + cs42l51_chan_mix, + cs42l51_get_chan_mix, cs42l51_set_chan_mix), +}; + +/* + * to power down, one must: + * 1.) Enable the PDN bit + * 2.) enable power-down for the select channels + * 3.) disable the PDN bit. + */ +static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + unsigned long value; + + value = snd_soc_read(w->codec, CS42L51_POWER_CTL1); + value &= ~CS42L51_POWER_CTL1_PDN; + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + value |= CS42L51_POWER_CTL1_PDN; + break; + default: + case SND_SOC_DAPM_POST_PMD: + break; + } + snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1, + CS42L51_POWER_CTL1_PDN, value); + + return 0; +} + +static const char *cs42l51_dac_names[] = {"Direct PCM", + "DSP PCM", "ADC"}; +static const struct soc_enum cs42l51_dac_mux_enum = + SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names); +static const struct snd_kcontrol_new cs42l51_dac_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum); + +static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left", + "MIC Left", "MIC+preamp Left"}; +static const struct soc_enum cs42l51_adcl_mux_enum = + SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names); +static const struct snd_kcontrol_new cs42l51_adcl_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum); + +static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right", + "MIC Right", "MIC+preamp Right"}; +static const struct soc_enum cs42l51_adcr_mux_enum = + SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names); +static const struct snd_kcontrol_new cs42l51_adcr_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum); + +static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), + SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture", + CS42L51_POWER_CTL1, 1, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture", + CS42L51_POWER_CTL1, 2, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback", + CS42L51_POWER_CTL1, 5, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback", + CS42L51_POWER_CTL1, 6, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + + /* analog/mic */ + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("MICR"), + + SND_SOC_DAPM_MIXER("Mic Preamp Left", + CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0), + SND_SOC_DAPM_MIXER("Mic Preamp Right", + CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0), + + /* HP */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + + /* mux */ + SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, + &cs42l51_dac_mux_controls), + SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0, + &cs42l51_adcl_mux_controls), + SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0, + &cs42l51_adcr_mux_controls), +}; + +static const struct snd_soc_dapm_route cs42l51_routes[] = { + {"HPL", NULL, "Left DAC"}, + {"HPR", NULL, "Right DAC"}, + + {"Left ADC", NULL, "Left PGA"}, + {"Right ADC", NULL, "Right PGA"}, + + {"Mic Preamp Left", NULL, "MICL"}, + {"Mic Preamp Right", NULL, "MICR"}, + + {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" }, + {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" }, + {"PGA-ADC Mux Left", "MIC Left", "MICL" }, + {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" }, + {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" }, + {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" }, + {"PGA-ADC Mux Right", "MIC Right", "MICR" }, + {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" }, + + {"Left PGA", NULL, "PGA-ADC Mux Left"}, + {"Right PGA", NULL, "PGA-ADC Mux Right"}, +}; + +static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + dev_err(codec->dev, "invalid DAI format\n"); + ret = -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + cs42l51->func = MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + cs42l51->func = MODE_SLAVE_AUTO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +struct cs42l51_ratios { + unsigned int ratio; + unsigned char speed_mode; + unsigned char mclk; +}; + +static struct cs42l51_ratios slave_ratios[] = { + { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 }, + { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, + { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 }, + { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 }, + { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, + { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 }, + { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 }, + { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, + { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 }, + { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, + { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 }, +}; + +static struct cs42l51_ratios slave_auto_ratios[] = { + { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, + { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 }, + { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, + { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 }, + { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, + { 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 }, + { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, + { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 }, +}; + +static int cs42l51_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 cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); + struct cs42l51_ratios *ratios = NULL; + int nr_ratios = 0; + unsigned int rates = 0; + unsigned int rate_min = -1; + unsigned int rate_max = 0; + int i; + + cs42l51->mclk = freq; + + switch (cs42l51->func) { + case MODE_MASTER: + return -EINVAL; + case MODE_SLAVE: + ratios = slave_ratios; + nr_ratios = ARRAY_SIZE(slave_ratios); + break; + case MODE_SLAVE_AUTO: + ratios = slave_auto_ratios; + nr_ratios = ARRAY_SIZE(slave_auto_ratios); + break; + } + + for (i = 0; i < nr_ratios; i++) { + unsigned int rate = freq / ratios[i].ratio; + rates |= snd_pcm_rate_to_rate_bit(rate); + if (rate < rate_min) + rate_min = rate; + if (rate > rate_max) + rate_max = rate; + } + rates &= ~SNDRV_PCM_RATE_KNOT; + + if (!rates) { + dev_err(codec->dev, "could not find a valid sample rate\n"); + return -EINVAL; + } + + codec_dai->playback.rates = rates; + codec_dai->playback.rate_min = rate_min; + codec_dai->playback.rate_max = rate_max; + + codec_dai->capture.rates = rates; + codec_dai->capture.rate_min = rate_min; + codec_dai->capture.rate_max = rate_max; + + return 0; +} + +static int cs42l51_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); + int ret; + unsigned int i; + unsigned int rate; + unsigned int ratio; + struct cs42l51_ratios *ratios = NULL; + int nr_ratios = 0; + int intf_ctl, power_ctl, fmt; + + switch (cs42l51->func) { + case MODE_MASTER: + return -EINVAL; + case MODE_SLAVE: + ratios = slave_ratios; + nr_ratios = ARRAY_SIZE(slave_ratios); + break; + case MODE_SLAVE_AUTO: + ratios = slave_auto_ratios; + nr_ratios = ARRAY_SIZE(slave_auto_ratios); + break; + } + + /* Figure out which MCLK/LRCK ratio to use */ + rate = params_rate(params); /* Sampling rate, in Hz */ + ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */ + for (i = 0; i < nr_ratios; i++) { + if (ratios[i].ratio == ratio) + break; + } + + if (i == nr_ratios) { + /* We did not find a matching ratio */ + dev_err(codec->dev, "could not find matching ratio\n"); + return -EINVAL; + } + + intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL); + power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL); + + intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S + | CS42L51_INTF_CTL_DAC_FORMAT(7)); + power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3) + | CS42L51_MIC_POWER_CTL_MCLK_DIV2); + + switch (cs42l51->func) { + case MODE_MASTER: + intf_ctl |= CS42L51_INTF_CTL_MASTER; + power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); + break; + case MODE_SLAVE: + power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); + break; + case MODE_SLAVE_AUTO: + power_ctl |= CS42L51_MIC_POWER_CTL_AUTO; + break; + } + + switch (cs42l51->audio_mode) { + case SND_SOC_DAIFMT_I2S: + intf_ctl |= CS42L51_INTF_CTL_ADC_I2S; + intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S); + break; + case SND_SOC_DAIFMT_LEFT_J: + intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24); + break; + case SND_SOC_DAIFMT_RIGHT_J: + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + fmt = CS42L51_DAC_DIF_RJ16; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + case SNDRV_PCM_FORMAT_S18_3BE: + fmt = CS42L51_DAC_DIF_RJ18; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S20_3BE: + fmt = CS42L51_DAC_DIF_RJ20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_BE: + fmt = CS42L51_DAC_DIF_RJ24; + break; + default: + dev_err(codec->dev, "unknown format\n"); + return -EINVAL; + } + intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt); + break; + default: + dev_err(codec->dev, "unknown format\n"); + return -EINVAL; + } + + if (ratios[i].mclk) + power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2; + + ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl); + if (ret < 0) + return ret; + + ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl); + if (ret < 0) + return ret; + + return 0; +} + +static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int reg; + int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE; + + reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL); + + if (mute) + reg |= mask; + else + reg &= ~mask; + + return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg); +} + +static struct snd_soc_dai_ops cs42l51_dai_ops = { + .hw_params = cs42l51_hw_params, + .set_sysclk = cs42l51_set_dai_sysclk, + .set_fmt = cs42l51_set_dai_fmt, + .digital_mute = cs42l51_dai_mute, +}; + +struct snd_soc_dai cs42l51_dai = { + .name = "CS42L51 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = CS42L51_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = CS42L51_FORMATS, + }, + .ops = &cs42l51_dai_ops, +}; +EXPORT_SYMBOL_GPL(cs42l51_dai); + + +static int cs42l51_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (!cs42l51_codec) { + dev_err(&pdev->dev, "CS42L51 codec not yet registered\n"); + return -EINVAL; + } + + socdev->card->codec = cs42l51_codec; + codec = socdev->card->codec; + + /* Register PCMs */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create PCMs\n"); + return ret; + } + + snd_soc_add_controls(codec, cs42l51_snd_controls, + ARRAY_SIZE(cs42l51_snd_controls)); + snd_soc_dapm_new_controls(codec, cs42l51_dapm_widgets, + ARRAY_SIZE(cs42l51_dapm_widgets)); + snd_soc_dapm_add_routes(codec, cs42l51_routes, + ARRAY_SIZE(cs42l51_routes)); + + return 0; +} + + +static int cs42l51_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_device_cs42l51 = { + .probe = cs42l51_probe, + .remove = cs42l51_remove +}; +EXPORT_SYMBOL_GPL(soc_codec_device_cs42l51); + +static int __init cs42l51_init(void) +{ + int ret; + + ret = i2c_add_driver(&cs42l51_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "%s: can't add i2c driver\n", __func__); + return ret; + } + return 0; +} +module_init(cs42l51_init); + +static void __exit cs42l51_exit(void) +{ + i2c_del_driver(&cs42l51_i2c_driver); +} +module_exit(cs42l51_exit); + +MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>"); +MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l51.h b/sound/soc/codecs/cs42l51.h new file mode 100644 index 000000000000..8f0bd9786ad2 --- /dev/null +++ b/sound/soc/codecs/cs42l51.h @@ -0,0 +1,163 @@ +/* + * cs42l51.h + * + * ASoC Driver for Cirrus Logic CS42L51 codecs + * + * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com> + * + * 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. + */ +#ifndef _CS42L51_H +#define _CS42L51_H + +#define CS42L51_CHIP_ID 0x1B +#define CS42L51_CHIP_REV_A 0x00 +#define CS42L51_CHIP_REV_B 0x01 + +#define CS42L51_CHIP_REV_ID 0x01 +#define CS42L51_MK_CHIP_REV(a, b) ((a)<<3|(b)) + +#define CS42L51_POWER_CTL1 0x02 +#define CS42L51_POWER_CTL1_PDN_DACB (1<<6) +#define CS42L51_POWER_CTL1_PDN_DACA (1<<5) +#define CS42L51_POWER_CTL1_PDN_PGAB (1<<4) +#define CS42L51_POWER_CTL1_PDN_PGAA (1<<3) +#define CS42L51_POWER_CTL1_PDN_ADCB (1<<2) +#define CS42L51_POWER_CTL1_PDN_ADCA (1<<1) +#define CS42L51_POWER_CTL1_PDN (1<<0) + +#define CS42L51_MIC_POWER_CTL 0x03 +#define CS42L51_MIC_POWER_CTL_AUTO (1<<7) +#define CS42L51_MIC_POWER_CTL_SPEED(x) (((x)&3)<<5) +#define CS42L51_QSM_MODE 3 +#define CS42L51_HSM_MODE 2 +#define CS42L51_SSM_MODE 1 +#define CS42L51_DSM_MODE 0 +#define CS42L51_MIC_POWER_CTL_3ST_SP (1<<4) +#define CS42L51_MIC_POWER_CTL_PDN_MICB (1<<3) +#define CS42L51_MIC_POWER_CTL_PDN_MICA (1<<2) +#define CS42L51_MIC_POWER_CTL_PDN_BIAS (1<<1) +#define CS42L51_MIC_POWER_CTL_MCLK_DIV2 (1<<0) + +#define CS42L51_INTF_CTL 0x04 +#define CS42L51_INTF_CTL_LOOPBACK (1<<7) +#define CS42L51_INTF_CTL_MASTER (1<<6) +#define CS42L51_INTF_CTL_DAC_FORMAT(x) (((x)&7)<<3) +#define CS42L51_DAC_DIF_LJ24 0x00 +#define CS42L51_DAC_DIF_I2S 0x01 +#define CS42L51_DAC_DIF_RJ24 0x02 +#define CS42L51_DAC_DIF_RJ20 0x03 +#define CS42L51_DAC_DIF_RJ18 0x04 +#define CS42L51_DAC_DIF_RJ16 0x05 +#define CS42L51_INTF_CTL_ADC_I2S (1<<2) +#define CS42L51_INTF_CTL_DIGMIX (1<<1) +#define CS42L51_INTF_CTL_MICMIX (1<<0) + +#define CS42L51_MIC_CTL 0x05 +#define CS42L51_MIC_CTL_ADC_SNGVOL (1<<7) +#define CS42L51_MIC_CTL_ADCD_DBOOST (1<<6) +#define CS42L51_MIC_CTL_ADCA_DBOOST (1<<5) +#define CS42L51_MIC_CTL_MICBIAS_SEL (1<<4) +#define CS42L51_MIC_CTL_MICBIAS_LVL(x) (((x)&3)<<2) +#define CS42L51_MIC_CTL_MICB_BOOST (1<<1) +#define CS42L51_MIC_CTL_MICA_BOOST (1<<0) + +#define CS42L51_ADC_CTL 0x06 +#define CS42L51_ADC_CTL_ADCB_HPFEN (1<<7) +#define CS42L51_ADC_CTL_ADCB_HPFRZ (1<<6) +#define CS42L51_ADC_CTL_ADCA_HPFEN (1<<5) +#define CS42L51_ADC_CTL_ADCA_HPFRZ (1<<4) +#define CS42L51_ADC_CTL_SOFTB (1<<3) +#define CS42L51_ADC_CTL_ZCROSSB (1<<2) +#define CS42L51_ADC_CTL_SOFTA (1<<1) +#define CS42L51_ADC_CTL_ZCROSSA (1<<0) + +#define CS42L51_ADC_INPUT 0x07 +#define CS42L51_ADC_INPUT_AINB_MUX(x) (((x)&3)<<6) +#define CS42L51_ADC_INPUT_AINA_MUX(x) (((x)&3)<<4) +#define CS42L51_ADC_INPUT_INV_ADCB (1<<3) +#define CS42L51_ADC_INPUT_INV_ADCA (1<<2) +#define CS42L51_ADC_INPUT_ADCB_MUTE (1<<1) +#define CS42L51_ADC_INPUT_ADCA_MUTE (1<<0) + +#define CS42L51_DAC_OUT_CTL 0x08 +#define CS42L51_DAC_OUT_CTL_HP_GAIN(x) (((x)&7)<<5) +#define CS42L51_DAC_OUT_CTL_DAC_SNGVOL (1<<4) +#define CS42L51_DAC_OUT_CTL_INV_PCMB (1<<3) +#define CS42L51_DAC_OUT_CTL_INV_PCMA (1<<2) +#define CS42L51_DAC_OUT_CTL_DACB_MUTE (1<<1) +#define CS42L51_DAC_OUT_CTL_DACA_MUTE (1<<0) + +#define CS42L51_DAC_CTL 0x09 +#define CS42L51_DAC_CTL_DATA_SEL(x) (((x)&3)<<6) +#define CS42L51_DAC_CTL_FREEZE (1<<5) +#define CS42L51_DAC_CTL_DEEMPH (1<<3) +#define CS42L51_DAC_CTL_AMUTE (1<<2) +#define CS42L51_DAC_CTL_DACSZ(x) (((x)&3)<<0) + +#define CS42L51_ALC_PGA_CTL 0x0A +#define CS42L51_ALC_PGB_CTL 0x0B +#define CS42L51_ALC_PGX_ALCX_SRDIS (1<<7) +#define CS42L51_ALC_PGX_ALCX_ZCDIS (1<<6) +#define CS42L51_ALC_PGX_PGX_VOL(x) (((x)&0x1f)<<0) + +#define CS42L51_ADCA_ATT 0x0C +#define CS42L51_ADCB_ATT 0x0D + +#define CS42L51_ADCA_VOL 0x0E +#define CS42L51_ADCB_VOL 0x0F +#define CS42L51_PCMA_VOL 0x10 +#define CS42L51_PCMB_VOL 0x11 +#define CS42L51_MIX_MUTE_ADCMIX (1<<7) +#define CS42L51_MIX_VOLUME(x) (((x)&0x7f)<<0) + +#define CS42L51_BEEP_FREQ 0x12 +#define CS42L51_BEEP_VOL 0x13 +#define CS42L51_BEEP_CONF 0x14 + +#define CS42L51_TONE_CTL 0x15 +#define CS42L51_TONE_CTL_TREB(x) (((x)&0xf)<<4) +#define CS42L51_TONE_CTL_BASS(x) (((x)&0xf)<<0) + +#define CS42L51_AOUTA_VOL 0x16 +#define CS42L51_AOUTB_VOL 0x17 +#define CS42L51_PCM_MIXER 0x18 +#define CS42L51_LIMIT_THRES_DIS 0x19 +#define CS42L51_LIMIT_REL 0x1A +#define CS42L51_LIMIT_ATT 0x1B +#define CS42L51_ALC_EN 0x1C +#define CS42L51_ALC_REL 0x1D +#define CS42L51_ALC_THRES 0x1E +#define CS42L51_NOISE_CONF 0x1F + +#define CS42L51_STATUS 0x20 +#define CS42L51_STATUS_SP_CLKERR (1<<6) +#define CS42L51_STATUS_SPEA_OVFL (1<<5) +#define CS42L51_STATUS_SPEB_OVFL (1<<4) +#define CS42L51_STATUS_PCMA_OVFL (1<<3) +#define CS42L51_STATUS_PCMB_OVFL (1<<2) +#define CS42L51_STATUS_ADCA_OVFL (1<<1) +#define CS42L51_STATUS_ADCB_OVFL (1<<0) + +#define CS42L51_CHARGE_FREQ 0x21 + +#define CS42L51_FIRSTREG 0x01 +/* + * Hack: with register 0x21, it makes 33 registers. Looks like someone in the + * i2c layer doesn't like i2c smbus block read of 33 regs. Workaround by using + * 32 regs + */ +#define CS42L51_LASTREG 0x20 +#define CS42L51_NUMREGS (CS42L51_LASTREG - CS42L51_FIRSTREG + 1) + +extern struct snd_soc_dai cs42l51_dai; +extern struct snd_soc_codec_device soc_codec_device_cs42l51; +#endif diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 75af2d6e0e78..3c51d6a57523 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -15,23 +15,15 @@ * option) any later version. */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/kernel.h> -#include <linux/init.h> #include <linux/delay.h> -#include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> #include <linux/slab.h> -#include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> -#include <sound/soc.h> #include <sound/soc-dapm.h> -#include <sound/tlv.h> #include <sound/initval.h> -#include <asm/div64.h> +#include <sound/tlv.h> #include "da7210.h" @@ -145,6 +137,29 @@ #define DA7210_VERSION "0.0.1" +/* + * Playback Volume + * + * max : 0x3F (+15.0 dB) + * (1.5 dB step) + * min : 0x11 (-54.0 dB) + * mute : 0x10 + * reserved : 0x00 - 0x0F + * + * ** FIXME ** + * + * Reserved area are considered as "mute". + * -> min = -79.5 dB + */ +static const DECLARE_TLV_DB_SCALE(hp_out_tlv, -7950, 150, 1); + +static const struct snd_kcontrol_new da7210_snd_controls[] = { + + SOC_DOUBLE_R_TLV("HeadPhone Playback Volume", + DA7210_HP_L_VOL, DA7210_HP_R_VOL, + 0, 0x3F, 0, hp_out_tlv), +}; + /* Codec private data */ struct da7210_priv { struct snd_soc_codec codec; @@ -227,10 +242,6 @@ static int da7210_startup(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = dai->codec; if (is_play) { - /* PlayBack Volume 40 */ - snd_soc_update_bits(codec, DA7210_HP_L_VOL, 0x3F, 40); - snd_soc_update_bits(codec, DA7210_HP_R_VOL, 0x3F, 40); - /* Enable Out */ snd_soc_update_bits(codec, DA7210_OUTMIX_L, 0x1F, 0x10); snd_soc_update_bits(codec, DA7210_OUTMIX_R, 0x1F, 0x10); @@ -488,7 +499,7 @@ static int da7210_init(struct da7210_priv *da7210) ret = snd_soc_register_dai(&da7210_dai); if (ret) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - goto init_err; + goto codec_err; } /* FIXME @@ -574,6 +585,8 @@ static int da7210_init(struct da7210_priv *da7210) return ret; +codec_err: + snd_soc_unregister_codec(codec); init_err: kfree(codec->reg_cache); codec->reg_cache = NULL; @@ -601,8 +614,10 @@ static int __devinit da7210_i2c_probe(struct i2c_client *i2c, codec->control_data = i2c; ret = da7210_init(da7210); - if (ret < 0) + if (ret < 0) { pr_err("Failed to initialise da7210 audio codec\n"); + kfree(da7210); + } return ret; } @@ -656,6 +671,9 @@ static int da7210_probe(struct platform_device *pdev) if (ret < 0) goto pcm_err; + snd_soc_add_controls(da7210_codec, da7210_snd_controls, + ARRAY_SIZE(da7210_snd_controls)); + dev_info(&pdev->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION); pcm_err: diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c new file mode 100644 index 000000000000..66557de1e4fe --- /dev/null +++ b/sound/soc/codecs/jz4740.c @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2009-2010, 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 version 2 as + * published by the Free Software Foundation. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc-dapm.h> +#include <sound/soc.h> + +#define JZ4740_REG_CODEC_1 0x0 +#define JZ4740_REG_CODEC_2 0x1 + +#define JZ4740_CODEC_1_LINE_ENABLE BIT(29) +#define JZ4740_CODEC_1_MIC_ENABLE BIT(28) +#define JZ4740_CODEC_1_SW1_ENABLE BIT(27) +#define JZ4740_CODEC_1_ADC_ENABLE BIT(26) +#define JZ4740_CODEC_1_SW2_ENABLE BIT(25) +#define JZ4740_CODEC_1_DAC_ENABLE BIT(24) +#define JZ4740_CODEC_1_VREF_DISABLE BIT(20) +#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19) +#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18) +#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17) +#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16) +#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14) +#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13) +#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12) +#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10)) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8) +#define JZ4740_CODEC_1_SUSPEND BIT(1) +#define JZ4740_CODEC_1_RESET BIT(0) + +#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29 +#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28 +#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27 +#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26 +#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25 +#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24 +#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14 +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8 + +#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000 +#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003 + +#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16 +#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0 + +static const uint32_t jz4740_codec_regs[] = { + 0x021b2302, 0x00170803, +}; + +struct jz4740_codec { + void __iomem *base; + struct resource *mem; + + uint32_t reg_cache[2]; + struct snd_soc_codec codec; +}; + +static inline struct jz4740_codec *codec_to_jz4740(struct snd_soc_codec *codec) +{ + return container_of(codec, struct jz4740_codec, codec); +} + +static unsigned int jz4740_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec); + return readl(jz4740_codec->base + (reg << 2)); +} + +static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec); + + jz4740_codec->reg_cache[reg] = val; + writel(val, jz4740_codec->base + (reg << 2)); + + return 0; +} + +static const struct snd_kcontrol_new jz4740_codec_controls[] = { + SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0), + SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0), + SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1), + SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0), +}; + +static const struct snd_kcontrol_new jz4740_codec_output_controls[] = { + SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_kcontrol_new jz4740_codec_input_controls[] = { + SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0), + SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0), + + SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1, + jz4740_codec_output_controls, + ARRAY_SIZE(jz4740_codec_output_controls)), + + SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0, + jz4740_codec_input_controls, + ARRAY_SIZE(jz4740_codec_input_controls)), + SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("LIN"), + SND_SOC_DAPM_INPUT("RIN"), +}; + +static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = { + {"Line Input", NULL, "LIN"}, + {"Line Input", NULL, "RIN"}, + + {"Input Mixer", "Line Capture Switch", "Line Input"}, + {"Input Mixer", "Mic Capture Switch", "MIC"}, + + {"ADC", NULL, "Input Mixer"}, + + {"Output Mixer", "Bypass Switch", "Input Mixer"}, + {"Output Mixer", "DAC Switch", "DAC"}, + + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, +}; + +static int jz4740_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + uint32_t val; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + switch (params_rate(params)) { + case 8000: + val = 0; + break; + case 11025: + val = 1; + break; + case 12000: + val = 2; + break; + case 16000: + val = 3; + break; + case 22050: + val = 4; + break; + case 24000: + val = 5; + break; + case 32000: + val = 6; + break; + case 44100: + val = 7; + break; + case 48000: + val = 8; + break; + default: + return -EINVAL; + } + + val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_SAMPLE_RATE_MASK, val); + + return 0; +} + +static struct snd_soc_dai_ops jz4740_codec_dai_ops = { + .hw_params = jz4740_codec_hw_params, +}; + +struct snd_soc_dai jz4740_codec_dai = { + .name = "jz4740", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .ops = &jz4740_codec_dai_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(jz4740_codec_dai); + +static void jz4740_codec_wakeup(struct snd_soc_codec *codec) +{ + int i; + uint32_t *cache = codec->reg_cache; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET); + udelay(2); + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0); + + for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i) + jz4740_codec_write(codec, i, cache[i]); +} + +static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + unsigned int mask; + unsigned int value; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = 0; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_STANDBY: + /* The only way to clear the suspend flag is to reset the codec */ + if (codec->bias_level == SND_SOC_BIAS_OFF) + jz4740_codec_wakeup(codec); + + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_OFF: + mask = JZ4740_CODEC_1_SUSPEND; + value = JZ4740_CODEC_1_SUSPEND; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value); + break; + default: + break; + } + + codec->bias_level = level; + + return 0; +} + +static struct snd_soc_codec *jz4740_codec_codec; + +static int jz4740_codec_dev_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = jz4740_codec_codec; + + BUG_ON(!codec); + + socdev->card->codec = codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret) { + dev_err(&pdev->dev, "Failed to create pcms: %d\n", ret); + return ret; + } + + snd_soc_add_controls(codec, jz4740_codec_controls, + ARRAY_SIZE(jz4740_codec_controls)); + + snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets, + ARRAY_SIZE(jz4740_codec_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes, + ARRAY_SIZE(jz4740_codec_dapm_routes)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int jz4740_codec_dev_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static int jz4740_codec_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int jz4740_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); +} + +#else +#define jz4740_codec_suspend NULL +#define jz4740_codec_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_jz4740_codec = { + .probe = jz4740_codec_dev_probe, + .remove = jz4740_codec_dev_remove, + .suspend = jz4740_codec_suspend, + .resume = jz4740_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_jz4740_codec); + +static int __devinit jz4740_codec_probe(struct platform_device *pdev) +{ + int ret; + struct jz4740_codec *jz4740_codec; + struct snd_soc_codec *codec; + struct resource *mem; + + jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL); + if (!jz4740_codec) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "Failed to get mmio memory resource\n"); + ret = -ENOENT; + goto err_free_codec; + } + + mem = request_mem_region(mem->start, resource_size(mem), pdev->name); + if (!mem) { + dev_err(&pdev->dev, "Failed to request mmio memory region\n"); + ret = -EBUSY; + goto err_free_codec; + } + + jz4740_codec->base = ioremap(mem->start, resource_size(mem)); + if (!jz4740_codec->base) { + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); + ret = -EBUSY; + goto err_release_mem_region; + } + jz4740_codec->mem = mem; + + jz4740_codec_dai.dev = &pdev->dev; + + codec = &jz4740_codec->codec; + + codec->dev = &pdev->dev; + codec->name = "jz4740"; + codec->owner = THIS_MODULE; + + codec->read = jz4740_codec_read; + codec->write = jz4740_codec_write; + codec->set_bias_level = jz4740_codec_set_bias_level; + codec->bias_level = SND_SOC_BIAS_OFF; + + codec->dai = &jz4740_codec_dai; + codec->num_dai = 1; + + codec->reg_cache = jz4740_codec->reg_cache; + codec->reg_cache_size = 2; + memcpy(codec->reg_cache, jz4740_codec_regs, sizeof(jz4740_codec_regs)); + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + jz4740_codec_codec = codec; + + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE); + + platform_set_drvdata(pdev, jz4740_codec); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec\n"); + goto err_iounmap; + } + + ret = snd_soc_register_dai(&jz4740_codec_dai); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec dai\n"); + goto err_unregister_codec; + } + + jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; + +err_unregister_codec: + snd_soc_unregister_codec(codec); +err_iounmap: + iounmap(jz4740_codec->base); +err_release_mem_region: + release_mem_region(mem->start, resource_size(mem)); +err_free_codec: + kfree(jz4740_codec); + + return ret; +} + +static int __devexit jz4740_codec_remove(struct platform_device *pdev) +{ + struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev); + struct resource *mem = jz4740_codec->mem; + + snd_soc_unregister_dai(&jz4740_codec_dai); + snd_soc_unregister_codec(&jz4740_codec->codec); + + iounmap(jz4740_codec->base); + release_mem_region(mem->start, resource_size(mem)); + + platform_set_drvdata(pdev, NULL); + kfree(jz4740_codec); + + return 0; +} + +static struct platform_driver jz4740_codec_driver = { + .probe = jz4740_codec_probe, + .remove = __devexit_p(jz4740_codec_remove), + .driver = { + .name = "jz4740-codec", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_codec_init(void) +{ + return platform_driver_register(&jz4740_codec_driver); +} +module_init(jz4740_codec_init); + +static void __exit jz4740_codec_exit(void) +{ + platform_driver_unregister(&jz4740_codec_driver); +} +module_exit(jz4740_codec_exit); + +MODULE_DESCRIPTION("JZ4740 SoC internal codec driver"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:jz4740-codec"); diff --git a/sound/soc/codecs/jz4740.h b/sound/soc/codecs/jz4740.h new file mode 100644 index 000000000000..b5a0691be763 --- /dev/null +++ b/sound/soc/codecs/jz4740.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009, 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 version 2 as + * published by the Free Software Foundation. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __SND_SOC_CODECS_JZ4740_CODEC_H__ +#define __SND_SOC_CODECS_JZ4740_CODEC_H__ + +extern struct snd_soc_dai jz4740_codec_dai; +extern struct snd_soc_codec_device soc_codec_dev_jz4740_codec; + +#endif diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c index a63191141052..9119836051a4 100644 --- a/sound/soc/codecs/spdif_transciever.c +++ b/sound/soc/codecs/spdif_transciever.c @@ -16,8 +16,10 @@ #include <linux/module.h> #include <linux/moduleparam.h> +#include <linux/slab.h> #include <sound/soc.h> #include <sound/pcm.h> +#include <sound/initval.h> #include "spdif_transciever.h" @@ -26,6 +28,48 @@ MODULE_LICENSE("GPL"); #define STUB_RATES SNDRV_PCM_RATE_8000_96000 #define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE +static struct snd_soc_codec *spdif_dit_codec; + +static int spdif_dit_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + if (spdif_dit_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = spdif_dit_codec; + codec = spdif_dit_codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto err_create_pcms; + } + + return 0; + +err_create_pcms: + return ret; +} + +static int spdif_dit_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_spdif_dit = { + .probe = spdif_dit_codec_probe, + .remove = spdif_dit_codec_remove, +}; EXPORT_SYMBOL_GPL(soc_codec_dev_spdif_dit); + struct snd_soc_dai dit_stub_dai = { .name = "DIT", .playback = { @@ -40,13 +84,61 @@ EXPORT_SYMBOL_GPL(dit_stub_dai); static int spdif_dit_probe(struct platform_device *pdev) { + struct snd_soc_codec *codec; + int ret; + + if (spdif_dit_codec) { + dev_err(&pdev->dev, "Another Codec is registered\n"); + ret = -EINVAL; + goto err_reg_codec; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + codec->dev = &pdev->dev; + + mutex_init(&codec->mutex); + + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->name = "spdif-dit"; + codec->owner = THIS_MODULE; + codec->dai = &dit_stub_dai; + codec->num_dai = 1; + + spdif_dit_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err_reg_codec; + } + dit_stub_dai.dev = &pdev->dev; - return snd_soc_register_dai(&dit_stub_dai); + ret = snd_soc_register_dai(&dit_stub_dai); + if (ret < 0) { + dev_err(codec->dev, "Failed to register dai: %d\n", ret); + goto err_reg_dai; + } + + return 0; + +err_reg_dai: + snd_soc_unregister_codec(codec); +err_reg_codec: + kfree(spdif_dit_codec); + return ret; } static int spdif_dit_remove(struct platform_device *pdev) { snd_soc_unregister_dai(&dit_stub_dai); + snd_soc_unregister_codec(spdif_dit_codec); + kfree(spdif_dit_codec); + spdif_dit_codec = NULL; return 0; } diff --git a/sound/soc/codecs/spdif_transciever.h b/sound/soc/codecs/spdif_transciever.h index 296f2eb6c4ef..1e102124f546 100644 --- a/sound/soc/codecs/spdif_transciever.h +++ b/sound/soc/codecs/spdif_transciever.h @@ -12,6 +12,7 @@ #ifndef CODEC_STUBS_H #define CODEC_STUBS_H +extern struct snd_soc_codec_device soc_codec_dev_spdif_dit; extern struct snd_soc_dai dit_stub_dai; #endif /* CODEC_STUBS_H */ diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index b0bae3508b29..0a4b0fef3355 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -560,13 +560,16 @@ static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: /* vref/mid, osc on, dac unmute */ + reg &= ~(TLV320AIC23_DEVICE_PWR_OFF | TLV320AIC23_OSC_OFF | \ + TLV320AIC23_DAC_OFF); tlv320aic23_write(codec, TLV320AIC23_PWR, reg); break; case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: /* everything off except vref/vmid, */ - tlv320aic23_write(codec, TLV320AIC23_PWR, reg | 0x0040); + tlv320aic23_write(codec, TLV320AIC23_PWR, reg | \ + TLV320AIC23_CLK_OFF); break; case SND_SOC_BIAS_OFF: /* everything off, dac mute, inactive */ @@ -615,7 +618,6 @@ static int tlv320aic23_suspend(struct platform_device *pdev, struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -632,7 +634,6 @@ static int tlv320aic23_resume(struct platform_device *pdev) u16 val = tlv320aic23_read_reg_cache(codec, reg); tlv320aic23_write(codec, reg, val); } - tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 65adc77eada1..8651b01ed223 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -49,8 +49,6 @@ #define NSAMPLE_MAX 5700 -#define LATENCY_TIME_MS 20 - #define MODE7_LTHR 10 #define MODE7_UTHR (DAC33_BUFFER_SIZE_SAMPLES - 10) @@ -62,6 +60,9 @@ #define US_TO_SAMPLES(rate, us) \ (rate / (1000000 / us)) +#define UTHR_FROM_PERIOD_SIZE(samples, playrate, burstrate) \ + ((samples * 5000) / ((burstrate * 5000) / (burstrate - playrate))) + static void dac33_calculate_times(struct snd_pcm_substream *substream); static int dac33_prepare_chip(struct snd_pcm_substream *substream); @@ -107,6 +108,10 @@ struct tlv320dac33_priv { * this */ enum dac33_fifo_modes fifo_mode;/* FIFO mode selection */ unsigned int nsample; /* burst read amount from host */ + int mode1_latency; /* latency caused by the i2c writes in + * us */ + int auto_fifo_config; /* Configure the FIFO based on the + * period size */ u8 burst_bclkdiv; /* BCLK divider value in burst mode */ unsigned int burst_rate; /* Interface speed in Burst modes */ @@ -120,6 +125,8 @@ struct tlv320dac33_priv { * samples */ unsigned int mode7_us_to_lthr; /* Time to reach lthr from uthr */ + unsigned int uthr; + enum dac33_state state; }; @@ -442,6 +449,39 @@ static int dac33_set_nsample(struct snd_kcontrol *kcontrol, return ret; } +static int dac33_get_uthr(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = dac33->uthr; + + return 0; +} + +static int dac33_set_uthr(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + if (dac33->substream) + return -EBUSY; + + if (dac33->uthr == ucontrol->value.integer.value[0]) + return 0; + + if (ucontrol->value.integer.value[0] < (MODE7_LTHR + 10) || + ucontrol->value.integer.value[0] > MODE7_UTHR) + ret = -EINVAL; + else + dac33->uthr = ucontrol->value.integer.value[0]; + + return ret; +} + static int dac33_get_fifo_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -503,13 +543,18 @@ static const struct snd_kcontrol_new dac33_snd_controls[] = { DAC33_LINEL_TO_LLO_VOL, DAC33_LINER_TO_RLO_VOL, 0, 127, 1), }; -static const struct snd_kcontrol_new dac33_nsample_snd_controls[] = { - SOC_SINGLE_EXT("nSample", 0, 0, 5900, 0, - dac33_get_nsample, dac33_set_nsample), +static const struct snd_kcontrol_new dac33_mode_snd_controls[] = { SOC_ENUM_EXT("FIFO Mode", dac33_fifo_mode_enum, dac33_get_fifo_mode, dac33_set_fifo_mode), }; +static const struct snd_kcontrol_new dac33_fifo_snd_controls[] = { + SOC_SINGLE_EXT("nSample", 0, 0, 5900, 0, + dac33_get_nsample, dac33_set_nsample), + SOC_SINGLE_EXT("UTHR", 0, 0, MODE7_UTHR, 0, + dac33_get_uthr, dac33_set_uthr), +}; + /* Analog bypass */ static const struct snd_kcontrol_new dac33_dapm_abypassl_control = SOC_DAPM_SINGLE("Switch", DAC33_LINEL_TO_LLO_VOL, 7, 1, 1); @@ -612,7 +657,7 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) switch (dac33->fifo_mode) { case DAC33_FIFO_MODE1: dac33_write16(codec, DAC33_NSAMPLE_MSB, - DAC33_THRREG(dac33->nsample + dac33->alarm_threshold)); + DAC33_THRREG(dac33->nsample)); /* Take the timestamps */ spin_lock_irq(&dac33->lock); @@ -761,6 +806,10 @@ static void dac33_shutdown(struct snd_pcm_substream *substream, struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); dac33->substream = NULL; + + /* Reset the nSample restrictions */ + dac33->nsample_min = 0; + dac33->nsample_max = NSAMPLE_MAX; } static int dac33_hw_params(struct snd_pcm_substream *substream, @@ -985,7 +1034,7 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) * Configure the threshold levels, and leave 10 sample space * at the bottom, and also at the top of the FIFO */ - dac33_write16(codec, DAC33_UTHR_MSB, DAC33_THRREG(MODE7_UTHR)); + dac33_write16(codec, DAC33_UTHR_MSB, DAC33_THRREG(dac33->uthr)); dac33_write16(codec, DAC33_LTHR_MSB, DAC33_THRREG(MODE7_LTHR)); break; default: @@ -1003,57 +1052,71 @@ static void dac33_calculate_times(struct snd_pcm_substream *substream) struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + unsigned int period_size = substream->runtime->period_size; + unsigned int rate = substream->runtime->rate; unsigned int nsample_limit; /* In bypass mode we don't need to calculate */ if (!dac33->fifo_mode) return; - /* Number of samples (16bit, stereo) in one period */ - dac33->nsample_min = snd_pcm_lib_period_bytes(substream) / 4; - - /* Number of samples (16bit, stereo) in ALSA buffer */ - dac33->nsample_max = snd_pcm_lib_buffer_bytes(substream) / 4; - /* Subtract one period from the total */ - dac33->nsample_max -= dac33->nsample_min; - - /* Number of samples for LATENCY_TIME_MS / 2 */ - dac33->alarm_threshold = substream->runtime->rate / - (1000 / (LATENCY_TIME_MS / 2)); - - /* Find and fix up the lowest nsmaple limit */ - nsample_limit = substream->runtime->rate / (1000 / LATENCY_TIME_MS); - - if (dac33->nsample_min < nsample_limit) - dac33->nsample_min = nsample_limit; - - if (dac33->nsample < dac33->nsample_min) - dac33->nsample = dac33->nsample_min; - - /* - * Find and fix up the highest nsmaple limit - * In order to not overflow the DAC33 buffer substract the - * alarm_threshold value from the size of the DAC33 buffer - */ - nsample_limit = DAC33_BUFFER_SIZE_SAMPLES - dac33->alarm_threshold; - - if (dac33->nsample_max > nsample_limit) - dac33->nsample_max = nsample_limit; - - if (dac33->nsample > dac33->nsample_max) - dac33->nsample = dac33->nsample_max; - switch (dac33->fifo_mode) { case DAC33_FIFO_MODE1: + /* Number of samples under i2c latency */ + dac33->alarm_threshold = US_TO_SAMPLES(rate, + dac33->mode1_latency); + if (dac33->auto_fifo_config) { + if (period_size <= dac33->alarm_threshold) + /* + * Configure nSamaple to number of periods, + * which covers the latency requironment. + */ + dac33->nsample = period_size * + ((dac33->alarm_threshold / period_size) + + (dac33->alarm_threshold % period_size ? + 1 : 0)); + else + dac33->nsample = period_size; + } else { + /* nSample time shall not be shorter than i2c latency */ + dac33->nsample_min = dac33->alarm_threshold; + /* + * nSample should not be bigger than alsa buffer minus + * size of one period to avoid overruns + */ + dac33->nsample_max = substream->runtime->buffer_size - + period_size; + nsample_limit = DAC33_BUFFER_SIZE_SAMPLES - + dac33->alarm_threshold; + if (dac33->nsample_max > nsample_limit) + dac33->nsample_max = nsample_limit; + + /* Correct the nSample if it is outside of the ranges */ + if (dac33->nsample < dac33->nsample_min) + dac33->nsample = dac33->nsample_min; + if (dac33->nsample > dac33->nsample_max) + dac33->nsample = dac33->nsample_max; + } + dac33->mode1_us_burst = SAMPLES_TO_US(dac33->burst_rate, dac33->nsample); dac33->t_stamp1 = 0; dac33->t_stamp2 = 0; break; case DAC33_FIFO_MODE7: + if (dac33->auto_fifo_config) { + dac33->uthr = UTHR_FROM_PERIOD_SIZE( + period_size, + rate, + dac33->burst_rate) + 9; + if (dac33->uthr > MODE7_UTHR) + dac33->uthr = MODE7_UTHR; + if (dac33->uthr < (MODE7_LTHR + 10)) + dac33->uthr = (MODE7_LTHR + 10); + } dac33->mode7_us_to_lthr = - SAMPLES_TO_US(substream->runtime->rate, - MODE7_UTHR - MODE7_LTHR + 1); + SAMPLES_TO_US(substream->runtime->rate, + dac33->uthr - MODE7_LTHR + 1); dac33->t_stamp1 = 0; break; default: @@ -1104,7 +1167,7 @@ static snd_pcm_sframes_t dac33_dai_delay( struct snd_soc_codec *codec = socdev->card->codec; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); unsigned long long t0, t1, t_now; - unsigned int time_delta; + unsigned int time_delta, uthr; int samples_out, samples_in, samples; snd_pcm_sframes_t delay = 0; @@ -1182,6 +1245,7 @@ static snd_pcm_sframes_t dac33_dai_delay( case DAC33_FIFO_MODE7: spin_lock(&dac33->lock); t0 = dac33->t_stamp1; + uthr = dac33->uthr; spin_unlock(&dac33->lock); t_now = ktime_to_us(ktime_get()); @@ -1194,7 +1258,7 @@ static snd_pcm_sframes_t dac33_dai_delay( * Either the timestamps are messed or equal. Report * maximum delay */ - delay = MODE7_UTHR; + delay = uthr; goto out; } @@ -1208,8 +1272,8 @@ static snd_pcm_sframes_t dac33_dai_delay( substream->runtime->rate, time_delta); - if (likely(MODE7_UTHR > samples_out)) - delay = MODE7_UTHR - samples_out; + if (likely(uthr > samples_out)) + delay = uthr - samples_out; else delay = 0; } else { @@ -1227,8 +1291,8 @@ static snd_pcm_sframes_t dac33_dai_delay( time_delta); delay = MODE7_LTHR + samples_in - samples_out; - if (unlikely(delay > MODE7_UTHR)) - delay = MODE7_UTHR; + if (unlikely(delay > uthr)) + delay = uthr; } break; default: @@ -1347,10 +1411,15 @@ static int dac33_soc_probe(struct platform_device *pdev) snd_soc_add_controls(codec, dac33_snd_controls, ARRAY_SIZE(dac33_snd_controls)); - /* Only add the nSample controls, if we have valid IRQ number */ - if (dac33->irq >= 0) - snd_soc_add_controls(codec, dac33_nsample_snd_controls, - ARRAY_SIZE(dac33_nsample_snd_controls)); + /* Only add the FIFO controls, if we have valid IRQ number */ + if (dac33->irq >= 0) { + snd_soc_add_controls(codec, dac33_mode_snd_controls, + ARRAY_SIZE(dac33_mode_snd_controls)); + /* FIFO usage controls only, if autoio config is not selected */ + if (!dac33->auto_fifo_config) + snd_soc_add_controls(codec, dac33_fifo_snd_controls, + ARRAY_SIZE(dac33_fifo_snd_controls)); + } dac33_add_widgets(codec); @@ -1481,9 +1550,14 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, /* Pre calculate the burst rate */ dac33->burst_rate = BURST_BASEFREQ_HZ / dac33->burst_bclkdiv / 32; dac33->keep_bclk = pdata->keep_bclk; + dac33->auto_fifo_config = pdata->auto_fifo_config; + dac33->mode1_latency = pdata->mode1_latency; + if (!dac33->mode1_latency) + dac33->mode1_latency = 10000; /* 10ms */ dac33->irq = client->irq; dac33->nsample = NSAMPLE_MAX; dac33->nsample_max = NSAMPLE_MAX; + dac33->uthr = MODE7_UTHR; /* Disable FIFO use by default */ dac33->fifo_mode = DAC33_FIFO_BYPASS; diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index b4fcdb01fc49..7b618bbff884 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -43,37 +43,37 @@ */ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = { 0x00, /* this register not used */ - 0x91, /* REG_CODEC_MODE (0x1) */ - 0xc3, /* REG_OPTION (0x2) */ + 0x00, /* REG_CODEC_MODE (0x1) */ + 0x00, /* REG_OPTION (0x2) */ 0x00, /* REG_UNKNOWN (0x3) */ 0x00, /* REG_MICBIAS_CTL (0x4) */ - 0x20, /* REG_ANAMICL (0x5) */ + 0x00, /* REG_ANAMICL (0x5) */ 0x00, /* REG_ANAMICR (0x6) */ 0x00, /* REG_AVADC_CTL (0x7) */ 0x00, /* REG_ADCMICSEL (0x8) */ 0x00, /* REG_DIGMIXING (0x9) */ - 0x0c, /* REG_ATXL1PGA (0xA) */ - 0x0c, /* REG_ATXR1PGA (0xB) */ - 0x00, /* REG_AVTXL2PGA (0xC) */ - 0x00, /* REG_AVTXR2PGA (0xD) */ + 0x0f, /* REG_ATXL1PGA (0xA) */ + 0x0f, /* REG_ATXR1PGA (0xB) */ + 0x0f, /* REG_AVTXL2PGA (0xC) */ + 0x0f, /* REG_AVTXR2PGA (0xD) */ 0x00, /* REG_AUDIO_IF (0xE) */ 0x00, /* REG_VOICE_IF (0xF) */ - 0x00, /* REG_ARXR1PGA (0x10) */ - 0x00, /* REG_ARXL1PGA (0x11) */ - 0x6c, /* REG_ARXR2PGA (0x12) */ - 0x6c, /* REG_ARXL2PGA (0x13) */ - 0x00, /* REG_VRXPGA (0x14) */ + 0x3f, /* REG_ARXR1PGA (0x10) */ + 0x3f, /* REG_ARXL1PGA (0x11) */ + 0x3f, /* REG_ARXR2PGA (0x12) */ + 0x3f, /* REG_ARXL2PGA (0x13) */ + 0x25, /* REG_VRXPGA (0x14) */ 0x00, /* REG_VSTPGA (0x15) */ 0x00, /* REG_VRX2ARXPGA (0x16) */ 0x00, /* REG_AVDAC_CTL (0x17) */ 0x00, /* REG_ARX2VTXPGA (0x18) */ - 0x00, /* REG_ARXL1_APGA_CTL (0x19) */ - 0x00, /* REG_ARXR1_APGA_CTL (0x1A) */ - 0x4a, /* REG_ARXL2_APGA_CTL (0x1B) */ - 0x4a, /* REG_ARXR2_APGA_CTL (0x1C) */ + 0x32, /* REG_ARXL1_APGA_CTL (0x19) */ + 0x32, /* REG_ARXR1_APGA_CTL (0x1A) */ + 0x32, /* REG_ARXL2_APGA_CTL (0x1B) */ + 0x32, /* REG_ARXR2_APGA_CTL (0x1C) */ 0x00, /* REG_ATX2ARXPGA (0x1D) */ 0x00, /* REG_BT_IF (0x1E) */ - 0x00, /* REG_BTPGA (0x1F) */ + 0x55, /* REG_BTPGA (0x1F) */ 0x00, /* REG_BTSTPGA (0x20) */ 0x00, /* REG_EAR_CTL (0x21) */ 0x00, /* REG_HS_SEL (0x22) */ @@ -85,32 +85,32 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = { 0x00, /* REG_PRECKR_CTL (0x28) */ 0x00, /* REG_HFL_CTL (0x29) */ 0x00, /* REG_HFR_CTL (0x2A) */ - 0x00, /* REG_ALC_CTL (0x2B) */ + 0x05, /* REG_ALC_CTL (0x2B) */ 0x00, /* REG_ALC_SET1 (0x2C) */ 0x00, /* REG_ALC_SET2 (0x2D) */ 0x00, /* REG_BOOST_CTL (0x2E) */ 0x00, /* REG_SOFTVOL_CTL (0x2F) */ - 0x00, /* REG_DTMF_FREQSEL (0x30) */ + 0x13, /* REG_DTMF_FREQSEL (0x30) */ 0x00, /* REG_DTMF_TONEXT1H (0x31) */ 0x00, /* REG_DTMF_TONEXT1L (0x32) */ 0x00, /* REG_DTMF_TONEXT2H (0x33) */ 0x00, /* REG_DTMF_TONEXT2L (0x34) */ - 0x00, /* REG_DTMF_TONOFF (0x35) */ - 0x00, /* REG_DTMF_WANONOFF (0x36) */ + 0x79, /* REG_DTMF_TONOFF (0x35) */ + 0x11, /* REG_DTMF_WANONOFF (0x36) */ 0x00, /* REG_I2S_RX_SCRAMBLE_H (0x37) */ 0x00, /* REG_I2S_RX_SCRAMBLE_M (0x38) */ 0x00, /* REG_I2S_RX_SCRAMBLE_L (0x39) */ 0x06, /* REG_APLL_CTL (0x3A) */ 0x00, /* REG_DTMF_CTL (0x3B) */ - 0x00, /* REG_DTMF_PGA_CTL2 (0x3C) */ - 0x00, /* REG_DTMF_PGA_CTL1 (0x3D) */ + 0x44, /* REG_DTMF_PGA_CTL2 (0x3C) */ + 0x69, /* REG_DTMF_PGA_CTL1 (0x3D) */ 0x00, /* REG_MISC_SET_1 (0x3E) */ 0x00, /* REG_PCMBTMUX (0x3F) */ 0x00, /* not used (0x40) */ 0x00, /* not used (0x41) */ 0x00, /* not used (0x42) */ 0x00, /* REG_RX_PATH_SEL (0x43) */ - 0x00, /* REG_VDL_APGA_CTL (0x44) */ + 0x32, /* REG_VDL_APGA_CTL (0x44) */ 0x00, /* REG_VIBRA_CTL (0x45) */ 0x00, /* REG_VIBRA_SET (0x46) */ 0x00, /* REG_VIBRA_PWM_SET (0x47) */ @@ -143,6 +143,9 @@ struct twl4030_priv { u8 earpiece_enabled; u8 predrivel_enabled, predriver_enabled; u8 carkitl_enabled, carkitr_enabled; + + /* Delay needed after enabling the digimic interface */ + unsigned int digimic_delay; }; /* @@ -244,58 +247,95 @@ static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) udelay(10); } -static void twl4030_init_chip(struct snd_soc_codec *codec) +static inline void twl4030_check_defaults(struct snd_soc_codec *codec) { - u8 *cache = codec->reg_cache; - int i; + int i, difference = 0; + u8 val; + + dev_dbg(codec->dev, "Checking TWL audio default configuration\n"); + for (i = 1; i <= TWL4030_REG_MISC_SET_2; i++) { + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, i); + if (val != twl4030_reg[i]) { + difference++; + dev_dbg(codec->dev, + "Reg 0x%02x: chip: 0x%02x driver: 0x%02x\n", + i, val, twl4030_reg[i]); + } + } + dev_dbg(codec->dev, "Found %d non maching registers. %s\n", + difference, difference ? "Not OK" : "OK"); +} - /* clear CODECPDZ prior to setting register defaults */ - twl4030_codec_enable(codec, 0); +static inline void twl4030_reset_registers(struct snd_soc_codec *codec) +{ + int i; /* set all audio section registers to reasonable defaults */ for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++) if (i != TWL4030_REG_APLL_CTL) - twl4030_write(codec, i, cache[i]); + twl4030_write(codec, i, twl4030_reg[i]); } -static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable) +static void twl4030_init_chip(struct platform_device *pdev) { + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct twl4030_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->card->codec; struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); - int status = -1; + u8 reg, byte; + int i = 0; - if (enable) { - twl4030->apll_enabled++; - if (twl4030->apll_enabled == 1) - status = twl4030_codec_enable_resource( - TWL4030_CODEC_RES_APLL); - } else { - twl4030->apll_enabled--; - if (!twl4030->apll_enabled) - status = twl4030_codec_disable_resource( - TWL4030_CODEC_RES_APLL); - } + /* Check defaults, if instructed before anything else */ + if (setup && setup->check_defaults) + twl4030_check_defaults(codec); - if (status >= 0) - twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, status); -} + /* Reset registers, if no setup data or if instructed to do so */ + if (!setup || (setup && setup->reset_registers)) + twl4030_reset_registers(codec); -static void twl4030_power_up(struct snd_soc_codec *codec) -{ - struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); - u8 anamicl, regmisc1, byte; - int i = 0; + /* Refresh APLL_CTL register from HW */ + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte, + TWL4030_REG_APLL_CTL); + twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, byte); + + /* anti-pop when changing analog gain */ + reg = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1); + twl4030_write(codec, TWL4030_REG_MISC_SET_1, + reg | TWL4030_SMOOTH_ANAVOL_EN); - if (twl4030->codec_powered) + twl4030_write(codec, TWL4030_REG_OPTION, + TWL4030_ATXL1_EN | TWL4030_ATXR1_EN | + TWL4030_ARXL2_EN | TWL4030_ARXR2_EN); + + /* REG_ARXR2_APGA_CTL reset according to the TRM: 0dB, DA_EN */ + twl4030_write(codec, TWL4030_REG_ARXR2_APGA_CTL, 0x32); + + /* Machine dependent setup */ + if (!setup) return; - /* set CODECPDZ to turn on codec */ - twl4030_codec_enable(codec, 1); + twl4030->digimic_delay = setup->digimic_delay; + + /* Configuration for headset ramp delay from setup data */ + if (setup->sysclk != twl4030->sysclk) + dev_warn(codec->dev, + "Mismatch in APLL mclk: %u (configured: %u)\n", + setup->sysclk, twl4030->sysclk); + + reg = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); + reg &= ~TWL4030_RAMP_DELAY; + reg |= (setup->ramp_delay_value << 2); + twl4030_write_reg_cache(codec, TWL4030_REG_HS_POPN_SET, reg); /* initiate offset cancellation */ - anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); + twl4030_codec_enable(codec, 1); + + reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); + reg &= ~TWL4030_OFFSET_CNCL_SEL; + reg |= setup->offset_cncl_path; twl4030_write(codec, TWL4030_REG_ANAMICL, - anamicl | TWL4030_CNCL_OFFSET_START); + reg | TWL4030_CNCL_OFFSET_START); /* wait for offset cancellation to complete */ do { @@ -310,23 +350,28 @@ static void twl4030_power_up(struct snd_soc_codec *codec) /* Make sure that the reg_cache has the same value as the HW */ twl4030_write_reg_cache(codec, TWL4030_REG_ANAMICL, byte); - /* anti-pop when changing analog gain */ - regmisc1 = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1); - twl4030_write(codec, TWL4030_REG_MISC_SET_1, - regmisc1 | TWL4030_SMOOTH_ANAVOL_EN); - - /* toggle CODECPDZ as per TRM */ twl4030_codec_enable(codec, 0); - twl4030_codec_enable(codec, 1); } -/* - * Unconditional power down - */ -static void twl4030_power_down(struct snd_soc_codec *codec) +static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable) { - /* power down */ - twl4030_codec_enable(codec, 0); + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); + int status = -1; + + if (enable) { + twl4030->apll_enabled++; + if (twl4030->apll_enabled == 1) + status = twl4030_codec_enable_resource( + TWL4030_CODEC_RES_APLL); + } else { + twl4030->apll_enabled--; + if (!twl4030->apll_enabled) + status = twl4030_codec_disable_resource( + TWL4030_CODEC_RES_APLL); + } + + if (status >= 0) + twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, status); } /* Earpiece */ @@ -500,10 +545,11 @@ static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control = static const struct snd_kcontrol_new twl4030_dapm_abypassv_control = SOC_DAPM_SINGLE("Switch", TWL4030_REG_VDL_APGA_CTL, 2, 1, 0); -/* Digital bypass gain, 0 mutes the bypass */ +/* Digital bypass gain, mute instead of -30dB */ static const unsigned int twl4030_dapm_dbypass_tlv[] = { - TLV_DB_RANGE_HEAD(2), - 0, 3, TLV_DB_SCALE_ITEM(-2400, 0, 1), + TLV_DB_RANGE_HEAD(3), + 0, 1, TLV_DB_SCALE_ITEM(-3000, 600, 1), + 2, 3, TLV_DB_SCALE_ITEM(-2400, 0, 0), 4, 7, TLV_DB_SCALE_ITEM(-1800, 600, 0), }; @@ -531,36 +577,6 @@ static const struct snd_kcontrol_new twl4030_dapm_dbypassv_control = TWL4030_REG_VSTPGA, 0, 0x29, 0, twl4030_dapm_dbypassv_tlv); -static int micpath_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) -{ - struct soc_enum *e = (struct soc_enum *)w->kcontrols->private_value; - unsigned char adcmicsel, micbias_ctl; - - adcmicsel = twl4030_read_reg_cache(w->codec, TWL4030_REG_ADCMICSEL); - micbias_ctl = twl4030_read_reg_cache(w->codec, TWL4030_REG_MICBIAS_CTL); - /* Prepare the bits for the given TX path: - * shift_l == 0: TX1 microphone path - * shift_l == 2: TX2 microphone path */ - if (e->shift_l) { - /* TX2 microphone path */ - if (adcmicsel & TWL4030_TX2IN_SEL) - micbias_ctl |= TWL4030_MICBIAS2_CTL; /* digimic */ - else - micbias_ctl &= ~TWL4030_MICBIAS2_CTL; - } else { - /* TX1 microphone path */ - if (adcmicsel & TWL4030_TX1IN_SEL) - micbias_ctl |= TWL4030_MICBIAS1_CTL; /* digimic */ - else - micbias_ctl &= ~TWL4030_MICBIAS1_CTL; - } - - twl4030_write(w->codec, TWL4030_REG_MICBIAS_CTL, micbias_ctl); - - return 0; -} - /* * Output PGA builder: * Handle the muting and unmuting of the given output (turning off the @@ -814,6 +830,16 @@ static int headsetrpga_event(struct snd_soc_dapm_widget *w, return 0; } +static int digimic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec); + + if (twl4030->digimic_delay) + mdelay(twl4030->digimic_delay); + return 0; +} + /* * Some of the gain controls in TWL (mostly those which are associated with * the outputs) are implemented in an interesting way: @@ -1374,14 +1400,10 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { /* Analog/Digital mic path selection. TX1 Left/Right: either analog Left/Right or Digimic0 TX2 Left/Right: either analog Left/Right or Digimic1 */ - SND_SOC_DAPM_MUX_E("TX1 Capture Route", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_micpathtx1_control, micpath_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD| - SND_SOC_DAPM_POST_REG), - SND_SOC_DAPM_MUX_E("TX2 Capture Route", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_micpathtx2_control, micpath_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD| - SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_MUX("TX1 Capture Route", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_micpathtx1_control), + SND_SOC_DAPM_MUX("TX2 Capture Route", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_micpathtx2_control), /* Analog input mixers for the capture amplifiers */ SND_SOC_DAPM_MIXER("Analog Left", @@ -1398,10 +1420,17 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_PGA("ADC Physical Right", TWL4030_REG_AVADC_CTL, 1, 0, NULL, 0), - SND_SOC_DAPM_PGA("Digimic0 Enable", - TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0), - SND_SOC_DAPM_PGA("Digimic1 Enable", - TWL4030_REG_ADCMICSEL, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA_E("Digimic0 Enable", + TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0, + digimic_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("Digimic1 Enable", + TWL4030_REG_ADCMICSEL, 3, 0, NULL, 0, + digimic_event, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SUPPLY("micbias1 select", TWL4030_REG_MICBIAS_CTL, 5, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("micbias2 select", TWL4030_REG_MICBIAS_CTL, 6, 0, + NULL, 0), SND_SOC_DAPM_MICBIAS("Mic Bias 1", TWL4030_REG_MICBIAS_CTL, 0, 0), SND_SOC_DAPM_MICBIAS("Mic Bias 2", TWL4030_REG_MICBIAS_CTL, 1, 0), @@ -1419,8 +1448,11 @@ static const struct snd_soc_dapm_route intercon[] = { /* Supply for the digital part (APLL) */ {"Digital Voice Playback Mixer", NULL, "APLL Enable"}, - {"Digital R1 Playback Mixer", NULL, "AIF Enable"}, - {"Digital L1 Playback Mixer", NULL, "AIF Enable"}, + {"DAC Left1", NULL, "AIF Enable"}, + {"DAC Right1", NULL, "AIF Enable"}, + {"DAC Left2", NULL, "AIF Enable"}, + {"DAC Right1", NULL, "AIF Enable"}, + {"Digital R2 Playback Mixer", NULL, "AIF Enable"}, {"Digital L2 Playback Mixer", NULL, "AIF Enable"}, @@ -1491,10 +1523,10 @@ static const struct snd_soc_dapm_route intercon[] = { /* outputs */ /* Must be always connected (for AIF and APLL) */ - {"Virtual HiFi OUT", NULL, "Digital L1 Playback Mixer"}, - {"Virtual HiFi OUT", NULL, "Digital R1 Playback Mixer"}, - {"Virtual HiFi OUT", NULL, "Digital L2 Playback Mixer"}, - {"Virtual HiFi OUT", NULL, "Digital R2 Playback Mixer"}, + {"Virtual HiFi OUT", NULL, "DAC Left1"}, + {"Virtual HiFi OUT", NULL, "DAC Right1"}, + {"Virtual HiFi OUT", NULL, "DAC Left2"}, + {"Virtual HiFi OUT", NULL, "DAC Right2"}, /* Must be always connected (for APLL) */ {"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"}, /* Physical outputs */ @@ -1531,6 +1563,9 @@ static const struct snd_soc_dapm_route intercon[] = { {"Digimic0 Enable", NULL, "DIGIMIC0"}, {"Digimic1 Enable", NULL, "DIGIMIC1"}, + {"DIGIMIC0", NULL, "micbias1 select"}, + {"DIGIMIC1", NULL, "micbias2 select"}, + /* TX1 Left capture path */ {"TX1 Capture Route", "Analog", "ADC Physical Left"}, {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"}, @@ -1605,10 +1640,10 @@ static int twl4030_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) - twl4030_power_up(codec); + twl4030_codec_enable(codec, 1); break; case SND_SOC_BIAS_OFF: - twl4030_power_down(codec); + twl4030_codec_enable(codec, 0); break; } codec->bias_level = level; @@ -1794,13 +1829,6 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - if (mode != old_mode) { - /* change rate and set CODECPDZ */ - twl4030_codec_enable(codec, 0); - twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); - twl4030_codec_enable(codec, 1); - } - /* sample size */ old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); format = old_format; @@ -1818,16 +1846,20 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - if (format != old_format) { - - /* clear CODECPDZ before changing format (codec requirement) */ - twl4030_codec_enable(codec, 0); - - /* change format */ - twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); - - /* set CODECPDZ afterwards */ - twl4030_codec_enable(codec, 1); + if (format != old_format || mode != old_mode) { + if (twl4030->codec_powered) { + /* + * If the codec is powered, than we need to toggle the + * codec power. + */ + twl4030_codec_enable(codec, 0); + twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); + twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); + twl4030_codec_enable(codec, 1); + } else { + twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); + twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); + } } /* Store the important parameters for the DAI configuration and set @@ -1877,6 +1909,7 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 old_format, format; /* get format */ @@ -1911,15 +1944,17 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, } if (format != old_format) { - - /* clear CODECPDZ before changing format (codec requirement) */ - twl4030_codec_enable(codec, 0); - - /* change format */ - twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); - - /* set CODECPDZ afterwards */ - twl4030_codec_enable(codec, 1); + if (twl4030->codec_powered) { + /* + * If the codec is powered, than we need to toggle the + * codec power. + */ + twl4030_codec_enable(codec, 0); + twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); + twl4030_codec_enable(codec, 1); + } else { + twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); + } } return 0; @@ -2011,6 +2046,7 @@ static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 old_mode, mode; /* Enable voice digital filters */ @@ -2035,10 +2071,17 @@ static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, } if (mode != old_mode) { - /* change rate and set CODECPDZ */ - twl4030_codec_enable(codec, 0); - twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); - twl4030_codec_enable(codec, 1); + if (twl4030->codec_powered) { + /* + * If the codec is powered, than we need to toggle the + * codec power. + */ + twl4030_codec_enable(codec, 0); + twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); + twl4030_codec_enable(codec, 1); + } else { + twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); + } } return 0; @@ -2068,6 +2111,7 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 old_format, format; /* get format */ @@ -2099,10 +2143,17 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, } if (format != old_format) { - /* change format and set CODECPDZ */ - twl4030_codec_enable(codec, 0); - twl4030_write(codec, TWL4030_REG_VOICE_IF, format); - twl4030_codec_enable(codec, 1); + if (twl4030->codec_powered) { + /* + * If the codec is powered, than we need to toggle the + * codec power. + */ + twl4030_codec_enable(codec, 0); + twl4030_write(codec, TWL4030_REG_VOICE_IF, format); + twl4030_codec_enable(codec, 1); + } else { + twl4030_write(codec, TWL4030_REG_VOICE_IF, format); + } } return 0; @@ -2202,31 +2253,15 @@ static struct snd_soc_codec *twl4030_codec; static int twl4030_soc_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct twl4030_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec; - struct twl4030_priv *twl4030; int ret; BUG_ON(!twl4030_codec); codec = twl4030_codec; - twl4030 = snd_soc_codec_get_drvdata(codec); socdev->card->codec = codec; - /* Configuration for headset ramp delay from setup data */ - if (setup) { - unsigned char hs_pop; - - if (setup->sysclk != twl4030->sysclk) - dev_warn(&pdev->dev, - "Mismatch in APLL mclk: %u (configured: %u)\n", - setup->sysclk, twl4030->sysclk); - - hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); - hs_pop &= ~TWL4030_RAMP_DELAY; - hs_pop |= (setup->ramp_delay_value << 2); - twl4030_write_reg_cache(codec, TWL4030_REG_HS_POPN_SET, hs_pop); - } + twl4030_init_chip(pdev); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); @@ -2247,6 +2282,8 @@ static int twl4030_soc_remove(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; + /* Reset registers to their chip default before leaving */ + twl4030_reset_registers(codec); twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); @@ -2287,6 +2324,7 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev) codec->read = twl4030_read_reg_cache; codec->write = twl4030_write; codec->set_bias_level = twl4030_set_bias_level; + codec->idle_bias_off = 1; codec->dai = twl4030_dai; codec->num_dai = ARRAY_SIZE(twl4030_dai); codec->reg_cache_size = sizeof(twl4030_reg); @@ -2302,9 +2340,7 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev) /* Set the defaults, and power up the codec */ twl4030->sysclk = twl4030_codec_get_mclk() / 1000; - twl4030_init_chip(codec); codec->bias_level = SND_SOC_BIAS_OFF; - twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); ret = snd_soc_register_codec(codec); if (ret != 0) { @@ -2322,7 +2358,7 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev) return 0; error_codec: - twl4030_power_down(codec); + twl4030_codec_enable(codec, 0); kfree(codec->reg_cache); error_cache: kfree(twl4030); diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index f206d242ca31..6c57430f6e24 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -41,7 +41,11 @@ extern struct snd_soc_codec_device soc_codec_dev_twl4030; struct twl4030_setup_data { unsigned int ramp_delay_value; + unsigned int digimic_delay; /* in ms */ unsigned int sysclk; + unsigned int offset_cncl_path; + unsigned int check_defaults:1; + unsigned int reset_registers:1; unsigned int hs_extmute:1; void (*set_hs_extmute)(int mute); }; diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index af36346ff336..64a807f1a8a1 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -360,6 +360,13 @@ static int headset_power_mode(struct snd_soc_codec *codec, int high_perf) return 0; } +static int twl6040_hs_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + msleep(1); + return 0; +} + static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -371,6 +378,8 @@ static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w, else priv->non_lp--; + msleep(1); + return 0; } @@ -471,20 +480,6 @@ static const struct snd_kcontrol_new hfdacl_switch_controls = static const struct snd_kcontrol_new hfdacr_switch_controls = SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 2, 1, 0); -/* Headset driver switches */ -static const struct snd_kcontrol_new hsl_driver_switch_controls = - SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSLCTL, 2, 1, 0); - -static const struct snd_kcontrol_new hsr_driver_switch_controls = - SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSRCTL, 2, 1, 0); - -/* Handsfree driver switches */ -static const struct snd_kcontrol_new hfl_driver_switch_controls = - SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 4, 1, 0); - -static const struct snd_kcontrol_new hfr_driver_switch_controls = - SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 4, 1, 0); - static const struct snd_kcontrol_new ep_driver_switch_controls = SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0); @@ -548,10 +543,14 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { TWL6040_REG_DMICBCTL, 4, 0), /* DACs */ - SND_SOC_DAPM_DAC("HSDAC Left", "Headset Playback", - TWL6040_REG_HSLCTL, 0, 0), - SND_SOC_DAPM_DAC("HSDAC Right", "Headset Playback", - TWL6040_REG_HSRCTL, 0, 0), + SND_SOC_DAPM_DAC_E("HSDAC Left", "Headset Playback", + TWL6040_REG_HSLCTL, 0, 0, + twl6040_hs_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("HSDAC Right", "Headset Playback", + TWL6040_REG_HSRCTL, 0, 0, + twl6040_hs_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_DAC_E("HFDAC Left", "Handsfree Playback", TWL6040_REG_HFLCTL, 0, 0, twl6040_power_mode_event, @@ -571,18 +570,19 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { SND_SOC_DAPM_SWITCH("HFDAC Right Playback", SND_SOC_NOPM, 0, 0, &hfdacr_switch_controls), - SND_SOC_DAPM_SWITCH("Headset Left Driver", - SND_SOC_NOPM, 0, 0, &hsl_driver_switch_controls), - SND_SOC_DAPM_SWITCH("Headset Right Driver", - SND_SOC_NOPM, 0, 0, &hsr_driver_switch_controls), - SND_SOC_DAPM_SWITCH_E("Handsfree Left Driver", - SND_SOC_NOPM, 0, 0, &hfl_driver_switch_controls, + /* Analog playback drivers */ + SND_SOC_DAPM_PGA_E("Handsfree Left Driver", + TWL6040_REG_HFLCTL, 4, 0, NULL, 0, twl6040_power_mode_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SWITCH_E("Handsfree Right Driver", - SND_SOC_NOPM, 0, 0, &hfr_driver_switch_controls, + SND_SOC_DAPM_PGA_E("Handsfree Right Driver", + TWL6040_REG_HFRCTL, 4, 0, NULL, 0, twl6040_power_mode_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA("Headset Left Driver", + TWL6040_REG_HSLCTL, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Right Driver", + TWL6040_REG_HSRCTL, 2, 0, NULL, 0), SND_SOC_DAPM_SWITCH_E("Earphone Driver", SND_SOC_NOPM, 0, 0, &ep_driver_switch_controls, twl6040_power_mode_event, @@ -616,8 +616,8 @@ static const struct snd_soc_dapm_route intercon[] = { {"HSDAC Left Playback", "Switch", "HSDAC Left"}, {"HSDAC Right Playback", "Switch", "HSDAC Right"}, - {"Headset Left Driver", "Switch", "HSDAC Left Playback"}, - {"Headset Right Driver", "Switch", "HSDAC Right Playback"}, + {"Headset Left Driver", NULL, "HSDAC Left Playback"}, + {"Headset Right Driver", NULL, "HSDAC Right Playback"}, {"HSOL", NULL, "Headset Left Driver"}, {"HSOR", NULL, "Headset Right Driver"}, @@ -928,7 +928,7 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai, case 19200000: /* mclk input, pll disabled */ hppllctl |= TWL6040_MCLK_19200KHZ | - TWL6040_HPLLSQRBP | + TWL6040_HPLLSQRENA | TWL6040_HPLLBP; break; case 26000000: diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index 28aac53c97bb..f3b4c1d6a82d 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -28,19 +28,6 @@ #include "uda134x.h" -#define POWER_OFF_ON_STANDBY 1 -/* - ALSA SOC usually puts the device in standby mode when it's not used - for sometime. If you define POWER_OFF_ON_STANDBY the driver will - turn off the ADC/DAC when this callback is invoked and turn it back - on when needed. Unfortunately this will result in a very light bump - (it can be audible only with good earphones). If this bothers you - just comment this line, you will have slightly higher power - consumption . Please note that sending the L3 command for ADC is - enough to make the bump, so it doesn't make difference if you - completely take off power from the codec. - */ - #define UDA134X_RATES SNDRV_PCM_RATE_8000_48000 #define UDA134X_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE) @@ -58,7 +45,7 @@ static const char uda134x_reg[UDA134X_REGS_NUM] = { /* Extended address registers */ 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, /* Status, data regs */ - 0x00, 0x83, 0x00, 0x40, 0x80, 0x00, + 0x00, 0x83, 0x00, 0x40, 0x80, 0xC0, 0x00, }; /* @@ -117,6 +104,7 @@ static int uda134x_write(struct snd_soc_codec *codec, unsigned int reg, case UDA134X_DATA000: case UDA134X_DATA001: case UDA134X_DATA010: + case UDA134X_DATA011: addr = UDA134X_DATA0_ADDR; break; case UDA134X_DATA1: @@ -353,8 +341,22 @@ static int uda134x_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: /* ADC, DAC on */ - reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1); - uda134x_write(codec, UDA134X_STATUS1, reg | 0x03); + switch (pd->model) { + case UDA134X_UDA1340: + case UDA134X_UDA1344: + case UDA134X_UDA1345: + reg = uda134x_read_reg_cache(codec, UDA134X_DATA011); + uda134x_write(codec, UDA134X_DATA011, reg | 0x03); + break; + case UDA134X_UDA1341: + reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1); + uda134x_write(codec, UDA134X_STATUS1, reg | 0x03); + break; + default: + printk(KERN_ERR "UDA134X SoC codec: " + "unsupported model %d\n", pd->model); + return -EINVAL; + } break; case SND_SOC_BIAS_PREPARE: /* power on */ @@ -367,8 +369,22 @@ static int uda134x_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: /* ADC, DAC power off */ - reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1); - uda134x_write(codec, UDA134X_STATUS1, reg & ~(0x03)); + switch (pd->model) { + case UDA134X_UDA1340: + case UDA134X_UDA1344: + case UDA134X_UDA1345: + reg = uda134x_read_reg_cache(codec, UDA134X_DATA011); + uda134x_write(codec, UDA134X_DATA011, reg & ~(0x03)); + break; + case UDA134X_UDA1341: + reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1); + uda134x_write(codec, UDA134X_STATUS1, reg & ~(0x03)); + break; + default: + printk(KERN_ERR "UDA134X SoC codec: " + "unsupported model %d\n", pd->model); + return -EINVAL; + } break; case SND_SOC_BIAS_OFF: /* power off */ @@ -531,9 +547,7 @@ static int uda134x_soc_probe(struct platform_device *pdev) codec->num_dai = 1; codec->read = uda134x_read_reg_cache; codec->write = uda134x_write; -#ifdef POWER_OFF_ON_STANDBY - codec->set_bias_level = uda134x_set_bias_level; -#endif + INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -544,6 +558,14 @@ static int uda134x_soc_probe(struct platform_device *pdev) uda134x_reset(codec); + if (pd->is_powered_on_standby) { + codec->set_bias_level = NULL; + uda134x_set_bias_level(codec, SND_SOC_BIAS_ON); + } else { + codec->set_bias_level = uda134x_set_bias_level; + uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + } + /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { diff --git a/sound/soc/codecs/uda134x.h b/sound/soc/codecs/uda134x.h index 94f440490b31..205f03b3eaf8 100644 --- a/sound/soc/codecs/uda134x.h +++ b/sound/soc/codecs/uda134x.h @@ -23,9 +23,10 @@ #define UDA134X_DATA000 10 #define UDA134X_DATA001 11 #define UDA134X_DATA010 12 -#define UDA134X_DATA1 13 +#define UDA134X_DATA011 13 +#define UDA134X_DATA1 14 -#define UDA134X_REGS_NUM 14 +#define UDA134X_REGS_NUM 15 #define STATUS0_DAIFMT_MASK (~(7<<1)) #define STATUS0_SYSCLK_MASK (~(3<<4)) diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c index 002e289d1255..4bcd168794e1 100644 --- a/sound/soc/codecs/wm2000.c +++ b/sound/soc/codecs/wm2000.c @@ -795,6 +795,8 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, dev_set_drvdata(&i2c->dev, wm2000); wm2000->anc_eng_ena = 1; + wm2000->anc_active = 1; + wm2000->spk_ena = 1; wm2000->i2c = i2c; wm2000_reset(wm2000); diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index 37242a7d3077..0ad039b4adf5 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -482,7 +482,8 @@ static int wm8523_register(struct wm8523_priv *wm8523, if (wm8523_codec) { dev_err(codec->dev, "Another WM8523 is registered\n"); - return -EINVAL; + ret = -EINVAL; + goto err; } mutex_init(&codec->mutex); @@ -570,18 +571,19 @@ static int wm8523_register(struct wm8523_priv *wm8523, ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - return ret; + goto err_enable; } ret = snd_soc_register_dai(&wm8523_dai); if (ret != 0) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - snd_soc_unregister_codec(codec); - return ret; + goto err_codec; } return 0; +err_codec: + snd_soc_unregister_codec(codec); err_enable: regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies); err_get: diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c index effb14eee7d4..e2dba07f0260 100644 --- a/sound/soc/codecs/wm8711.c +++ b/sound/soc/codecs/wm8711.c @@ -439,7 +439,8 @@ static int wm8711_register(struct wm8711_priv *wm8711, if (wm8711_codec) { dev_err(codec->dev, "Another WM8711 is registered\n"); - return -EINVAL; + ret = -EINVAL; + goto err; } mutex_init(&codec->mutex); diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c new file mode 100644 index 000000000000..b9ea8904ad4b --- /dev/null +++ b/sound/soc/codecs/wm8741.c @@ -0,0 +1,579 @@ +/* + * wm8741.c -- WM8741 ALSA SoC Audio driver + * + * Copyright 2010 Wolfson Microelectronics plc + * + * Author: Ian Lartey <ian@opensource.wolfsonmicro.com> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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/regulator/consumer.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "wm8741.h" + +static struct snd_soc_codec *wm8741_codec; +struct snd_soc_codec_device soc_codec_dev_wm8741; + +#define WM8741_NUM_SUPPLIES 2 +static const char *wm8741_supply_names[WM8741_NUM_SUPPLIES] = { + "AVDD", + "DVDD", +}; + +#define WM8741_NUM_RATES 4 + +/* codec private data */ +struct wm8741_priv { + struct snd_soc_codec codec; + u16 reg_cache[WM8741_REGISTER_COUNT]; + struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES]; + unsigned int sysclk; + unsigned int rate_constraint_list[WM8741_NUM_RATES]; + struct snd_pcm_hw_constraint_list rate_constraint; +}; + +static const u16 wm8741_reg_defaults[WM8741_REGISTER_COUNT] = { + 0x0000, /* R0 - DACLLSB Attenuation */ + 0x0000, /* R1 - DACLMSB Attenuation */ + 0x0000, /* R2 - DACRLSB Attenuation */ + 0x0000, /* R3 - DACRMSB Attenuation */ + 0x0000, /* R4 - Volume Control */ + 0x000A, /* R5 - Format Control */ + 0x0000, /* R6 - Filter Control */ + 0x0000, /* R7 - Mode Control 1 */ + 0x0002, /* R8 - Mode Control 2 */ + 0x0000, /* R9 - Reset */ + 0x0002, /* R32 - ADDITONAL_CONTROL_1 */ +}; + + +static int wm8741_reset(struct snd_soc_codec *codec) +{ + return snd_soc_write(codec, WM8741_RESET, 0); +} + +static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, -12700, 13, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 400, 0); + +static const struct snd_kcontrol_new wm8741_snd_controls[] = { +SOC_DOUBLE_R_TLV("Fine Playback Volume", WM8741_DACLLSB_ATTENUATION, + WM8741_DACRLSB_ATTENUATION, 1, 255, 1, dac_tlv_fine), +SOC_DOUBLE_R_TLV("Playback Volume", WM8741_DACLMSB_ATTENUATION, + WM8741_DACRMSB_ATTENUATION, 0, 511, 1, dac_tlv), +}; + +static const struct snd_soc_dapm_widget wm8741_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DACL", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_DAC("DACR", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("VOUTLP"), +SND_SOC_DAPM_OUTPUT("VOUTLN"), +SND_SOC_DAPM_OUTPUT("VOUTRP"), +SND_SOC_DAPM_OUTPUT("VOUTRN"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + { "VOUTLP", NULL, "DACL" }, + { "VOUTLN", NULL, "DACL" }, + { "VOUTRP", NULL, "DACR" }, + { "VOUTRN", NULL, "DACR" }, +}; + +static int wm8741_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8741_dapm_widgets, + ARRAY_SIZE(wm8741_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + return 0; +} + +static struct { + int value; + int ratio; +} lrclk_ratios[WM8741_NUM_RATES] = { + { 1, 256 }, + { 2, 384 }, + { 3, 512 }, + { 4, 768 }, +}; + + +static int wm8741_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC - enforce this. + */ + if (!wm8741->sysclk) { + dev_err(codec->dev, + "No MCLK configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &wm8741->rate_constraint); + + return 0; +} + +static int wm8741_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); + u16 iface = snd_soc_read(codec, WM8741_FORMAT_CONTROL) & 0x1FC; + int i; + + /* Find a supported LRCLK ratio */ + for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { + if (wm8741->sysclk / params_rate(params) == + lrclk_ratios[i].ratio) + break; + } + + /* Should never happen, should be handled by constraints */ + if (i == ARRAY_SIZE(lrclk_ratios)) { + dev_err(codec->dev, "MCLK/fs ratio %d unsupported\n", + wm8741->sysclk / params_rate(params)); + return -EINVAL; + } + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0001; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0002; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x0003; + break; + default: + dev_dbg(codec->dev, "wm8741_hw_params: Unsupported bit size param = %d", + params_format(params)); + return -EINVAL; + } + + dev_dbg(codec->dev, "wm8741_hw_params: bit size param = %d", + params_format(params)); + + snd_soc_write(codec, WM8741_FORMAT_CONTROL, iface); + return 0; +} + +static int wm8741_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 wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + int i; + + dev_dbg(codec->dev, "wm8741_set_dai_sysclk info: freq=%dHz\n", freq); + + wm8741->sysclk = freq; + + wm8741->rate_constraint.count = 0; + + for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { + dev_dbg(codec->dev, "index = %d, ratio = %d, freq = %d", + i, lrclk_ratios[i].ratio, freq); + + val = freq / lrclk_ratios[i].ratio; + /* Check that it's a standard rate since core can't + * cope with others and having the odd rates confuses + * constraint matching. + */ + switch (val) { + case 32000: + case 44100: + case 48000: + case 64000: + case 88200: + case 96000: + dev_dbg(codec->dev, "Supported sample rate: %dHz\n", + val); + wm8741->rate_constraint_list[i] = val; + wm8741->rate_constraint.count++; + break; + default: + dev_dbg(codec->dev, "Skipping sample rate: %dHz\n", + val); + } + } + + /* Need at least one supported rate... */ + if (wm8741->rate_constraint.count == 0) + return -EINVAL; + + return 0; +} + +static int wm8741_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = snd_soc_read(codec, WM8741_FORMAT_CONTROL) & 0x1C3; + + /* check master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0004; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + 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: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0020; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0030; + break; + default: + return -EINVAL; + } + + + dev_dbg(codec->dev, "wm8741_set_dai_fmt: Format=%x, Clock Inv=%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK, + ((fmt & SND_SOC_DAIFMT_INV_MASK))); + + snd_soc_write(codec, WM8741_FORMAT_CONTROL, iface); + return 0; +} + +#define WM8741_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000) + +#define WM8741_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 wm8741_dai_ops = { + .startup = wm8741_startup, + .hw_params = wm8741_hw_params, + .set_sysclk = wm8741_set_dai_sysclk, + .set_fmt = wm8741_set_dai_fmt, +}; + +struct snd_soc_dai wm8741_dai = { + .name = "WM8741", + .playback = { + .stream_name = "Playback", + .channels_min = 2, /* Mono modes not yet supported */ + .channels_max = 2, + .rates = WM8741_RATES, + .formats = WM8741_FORMATS, + }, + .ops = &wm8741_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm8741_dai); + +#ifdef CONFIG_PM +static int wm8741_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + u16 *cache = codec->reg_cache; + int i; + + /* RESTORE REG Cache */ + for (i = 0; i < WM8741_REGISTER_COUNT; i++) { + if (cache[i] == wm8741_reg_defaults[i] || WM8741_RESET == i) + continue; + snd_soc_write(codec, i, cache[i]); + } + return 0; +} +#else +#define wm8741_suspend NULL +#define wm8741_resume NULL +#endif + +static int wm8741_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8741_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8741_codec; + codec = wm8741_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8741_snd_controls, + ARRAY_SIZE(wm8741_snd_controls)); + wm8741_add_widgets(codec); + + return ret; + +pcm_err: + return ret; +} + +static int wm8741_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8741 = { + .probe = wm8741_probe, + .remove = wm8741_remove, + .resume = wm8741_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8741); + +static int wm8741_register(struct wm8741_priv *wm8741, + enum snd_soc_control_type control) +{ + int ret; + struct snd_soc_codec *codec = &wm8741->codec; + int i; + + if (wm8741_codec) { + dev_err(codec->dev, "Another WM8741 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + snd_soc_codec_set_drvdata(codec, wm8741); + codec->name = "WM8741"; + codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = NULL; + codec->dai = &wm8741_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8741_REGISTER_COUNT; + codec->reg_cache = &wm8741->reg_cache; + + wm8741->rate_constraint.list = &wm8741->rate_constraint_list[0]; + wm8741->rate_constraint.count = + ARRAY_SIZE(wm8741->rate_constraint_list); + + memcpy(codec->reg_cache, wm8741_reg_defaults, + sizeof(wm8741->reg_cache)); + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(wm8741->supplies); i++) + wm8741->supplies[i].supply = wm8741_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8741->supplies), + wm8741->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8741->supplies), + wm8741->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_get; + } + + ret = wm8741_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + goto err_enable; + } + + wm8741_dai.dev = codec->dev; + + /* Change some default settings - latch VU */ + wm8741->reg_cache[WM8741_DACLLSB_ATTENUATION] |= WM8741_UPDATELL; + wm8741->reg_cache[WM8741_DACLMSB_ATTENUATION] |= WM8741_UPDATELM; + wm8741->reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERL; + wm8741->reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERM; + + wm8741_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8741_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + dev_dbg(codec->dev, "Successful registration\n"); + return 0; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); + +err_get: + regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); + +err: + kfree(wm8741); + return ret; +} + +static void wm8741_unregister(struct wm8741_priv *wm8741) +{ + regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); + + snd_soc_unregister_dai(&wm8741_dai); + snd_soc_unregister_codec(&wm8741->codec); + kfree(wm8741); + wm8741_codec = NULL; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static __devinit int wm8741_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8741_priv *wm8741; + struct snd_soc_codec *codec; + + wm8741 = kzalloc(sizeof(struct wm8741_priv), GFP_KERNEL); + if (wm8741 == NULL) + return -ENOMEM; + + codec = &wm8741->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8741); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8741_register(wm8741, SND_SOC_I2C); +} + +static __devexit int wm8741_i2c_remove(struct i2c_client *client) +{ + struct wm8741_priv *wm8741 = i2c_get_clientdata(client); + wm8741_unregister(wm8741); + return 0; +} + +static const struct i2c_device_id wm8741_i2c_id[] = { + { "wm8741", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8741_i2c_id); + + +static struct i2c_driver wm8741_i2c_driver = { + .driver = { + .name = "WM8741", + .owner = THIS_MODULE, + }, + .probe = wm8741_i2c_probe, + .remove = __devexit_p(wm8741_i2c_remove), + .id_table = wm8741_i2c_id, +}; +#endif + +static int __init wm8741_modinit(void) +{ + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8741_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8741 I2C driver: %d\n", + ret); + } +#endif + return 0; +} +module_init(wm8741_modinit); + +static void __exit wm8741_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8741_i2c_driver); +#endif +} +module_exit(wm8741_exit); + +MODULE_DESCRIPTION("ASoC WM8741 driver"); +MODULE_AUTHOR("Ian Lartey <ian@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8741.h b/sound/soc/codecs/wm8741.h new file mode 100644 index 000000000000..fdef6ecd1f6f --- /dev/null +++ b/sound/soc/codecs/wm8741.h @@ -0,0 +1,214 @@ +/* + * wm8741.h -- WM8423 ASoC driver + * + * Copyright 2010 Wolfson Microelectronics, plc + * + * Author: Ian Lartey <ian@opensource.wolfsonmicro.com> + * + * Based on wm8753.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 _WM8741_H +#define _WM8741_H + +/* + * Register values. + */ +#define WM8741_DACLLSB_ATTENUATION 0x00 +#define WM8741_DACLMSB_ATTENUATION 0x01 +#define WM8741_DACRLSB_ATTENUATION 0x02 +#define WM8741_DACRMSB_ATTENUATION 0x03 +#define WM8741_VOLUME_CONTROL 0x04 +#define WM8741_FORMAT_CONTROL 0x05 +#define WM8741_FILTER_CONTROL 0x06 +#define WM8741_MODE_CONTROL_1 0x07 +#define WM8741_MODE_CONTROL_2 0x08 +#define WM8741_RESET 0x09 +#define WM8741_ADDITIONAL_CONTROL_1 0x20 + +#define WM8741_REGISTER_COUNT 11 +#define WM8741_MAX_REGISTER 0x20 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - DACLLSB_ATTENUATION + */ +#define WM8741_UPDATELL 0x0020 /* UPDATELL */ +#define WM8741_UPDATELL_MASK 0x0020 /* UPDATELL */ +#define WM8741_UPDATELL_SHIFT 5 /* UPDATELL */ +#define WM8741_UPDATELL_WIDTH 1 /* UPDATELL */ +#define WM8741_LAT_4_0_MASK 0x001F /* LAT[4:0] - [4:0] */ +#define WM8741_LAT_4_0_SHIFT 0 /* LAT[4:0] - [4:0] */ +#define WM8741_LAT_4_0_WIDTH 5 /* LAT[4:0] - [4:0] */ + +/* + * R1 (0x01) - DACLMSB_ATTENUATION + */ +#define WM8741_UPDATELM 0x0020 /* UPDATELM */ +#define WM8741_UPDATELM_MASK 0x0020 /* UPDATELM */ +#define WM8741_UPDATELM_SHIFT 5 /* UPDATELM */ +#define WM8741_UPDATELM_WIDTH 1 /* UPDATELM */ +#define WM8741_LAT_9_5_0_MASK 0x001F /* LAT[9:5] - [4:0] */ +#define WM8741_LAT_9_5_0_SHIFT 0 /* LAT[9:5] - [4:0] */ +#define WM8741_LAT_9_5_0_WIDTH 5 /* LAT[9:5] - [4:0] */ + +/* + * R2 (0x02) - DACRLSB_ATTENUATION + */ +#define WM8741_UPDATERL 0x0020 /* UPDATERL */ +#define WM8741_UPDATERL_MASK 0x0020 /* UPDATERL */ +#define WM8741_UPDATERL_SHIFT 5 /* UPDATERL */ +#define WM8741_UPDATERL_WIDTH 1 /* UPDATERL */ +#define WM8741_RAT_4_0_MASK 0x001F /* RAT[4:0] - [4:0] */ +#define WM8741_RAT_4_0_SHIFT 0 /* RAT[4:0] - [4:0] */ +#define WM8741_RAT_4_0_WIDTH 5 /* RAT[4:0] - [4:0] */ + +/* + * R3 (0x03) - DACRMSB_ATTENUATION + */ +#define WM8741_UPDATERM 0x0020 /* UPDATERM */ +#define WM8741_UPDATERM_MASK 0x0020 /* UPDATERM */ +#define WM8741_UPDATERM_SHIFT 5 /* UPDATERM */ +#define WM8741_UPDATERM_WIDTH 1 /* UPDATERM */ +#define WM8741_RAT_9_5_0_MASK 0x001F /* RAT[9:5] - [4:0] */ +#define WM8741_RAT_9_5_0_SHIFT 0 /* RAT[9:5] - [4:0] */ +#define WM8741_RAT_9_5_0_WIDTH 5 /* RAT[9:5] - [4:0] */ + +/* + * R4 (0x04) - VOLUME_CONTROL + */ +#define WM8741_AMUTE 0x0080 /* AMUTE */ +#define WM8741_AMUTE_MASK 0x0080 /* AMUTE */ +#define WM8741_AMUTE_SHIFT 7 /* AMUTE */ +#define WM8741_AMUTE_WIDTH 1 /* AMUTE */ +#define WM8741_ZFLAG_MASK 0x0060 /* ZFLAG - [6:5] */ +#define WM8741_ZFLAG_SHIFT 5 /* ZFLAG - [6:5] */ +#define WM8741_ZFLAG_WIDTH 2 /* ZFLAG - [6:5] */ +#define WM8741_IZD 0x0010 /* IZD */ +#define WM8741_IZD_MASK 0x0010 /* IZD */ +#define WM8741_IZD_SHIFT 4 /* IZD */ +#define WM8741_IZD_WIDTH 1 /* IZD */ +#define WM8741_SOFT 0x0008 /* SOFT MUTE */ +#define WM8741_SOFT_MASK 0x0008 /* SOFT MUTE */ +#define WM8741_SOFT_SHIFT 3 /* SOFT MUTE */ +#define WM8741_SOFT_WIDTH 1 /* SOFT MUTE */ +#define WM8741_ATC 0x0004 /* ATC */ +#define WM8741_ATC_MASK 0x0004 /* ATC */ +#define WM8741_ATC_SHIFT 2 /* ATC */ +#define WM8741_ATC_WIDTH 1 /* ATC */ +#define WM8741_ATT2DB 0x0002 /* ATT2DB */ +#define WM8741_ATT2DB_MASK 0x0002 /* ATT2DB */ +#define WM8741_ATT2DB_SHIFT 1 /* ATT2DB */ +#define WM8741_ATT2DB_WIDTH 1 /* ATT2DB */ +#define WM8741_VOL_RAMP 0x0001 /* VOL_RAMP */ +#define WM8741_VOL_RAMP_MASK 0x0001 /* VOL_RAMP */ +#define WM8741_VOL_RAMP_SHIFT 0 /* VOL_RAMP */ +#define WM8741_VOL_RAMP_WIDTH 1 /* VOL_RAMP */ + +/* + * R5 (0x05) - FORMAT_CONTROL + */ +#define WM8741_PWDN 0x0080 /* PWDN */ +#define WM8741_PWDN_MASK 0x0080 /* PWDN */ +#define WM8741_PWDN_SHIFT 7 /* PWDN */ +#define WM8741_PWDN_WIDTH 1 /* PWDN */ +#define WM8741_REV 0x0040 /* REV */ +#define WM8741_REV_MASK 0x0040 /* REV */ +#define WM8741_REV_SHIFT 6 /* REV */ +#define WM8741_REV_WIDTH 1 /* REV */ +#define WM8741_BCP 0x0020 /* BCP */ +#define WM8741_BCP_MASK 0x0020 /* BCP */ +#define WM8741_BCP_SHIFT 5 /* BCP */ +#define WM8741_BCP_WIDTH 1 /* BCP */ +#define WM8741_LRP 0x0010 /* LRP */ +#define WM8741_LRP_MASK 0x0010 /* LRP */ +#define WM8741_LRP_SHIFT 4 /* LRP */ +#define WM8741_LRP_WIDTH 1 /* LRP */ +#define WM8741_FMT_MASK 0x000C /* FMT - [3:2] */ +#define WM8741_FMT_SHIFT 2 /* FMT - [3:2] */ +#define WM8741_FMT_WIDTH 2 /* FMT - [3:2] */ +#define WM8741_IWL_MASK 0x0003 /* IWL - [1:0] */ +#define WM8741_IWL_SHIFT 0 /* IWL - [1:0] */ +#define WM8741_IWL_WIDTH 2 /* IWL - [1:0] */ + +/* + * R6 (0x06) - FILTER_CONTROL + */ +#define WM8741_ZFLAG_HI 0x0080 /* ZFLAG_HI */ +#define WM8741_ZFLAG_HI_MASK 0x0080 /* ZFLAG_HI */ +#define WM8741_ZFLAG_HI_SHIFT 7 /* ZFLAG_HI */ +#define WM8741_ZFLAG_HI_WIDTH 1 /* ZFLAG_HI */ +#define WM8741_DEEMPH_MASK 0x0060 /* DEEMPH - [6:5] */ +#define WM8741_DEEMPH_SHIFT 5 /* DEEMPH - [6:5] */ +#define WM8741_DEEMPH_WIDTH 2 /* DEEMPH - [6:5] */ +#define WM8741_DSDFILT_MASK 0x0018 /* DSDFILT - [4:3] */ +#define WM8741_DSDFILT_SHIFT 3 /* DSDFILT - [4:3] */ +#define WM8741_DSDFILT_WIDTH 2 /* DSDFILT - [4:3] */ +#define WM8741_FIRSEL_MASK 0x0007 /* FIRSEL - [2:0] */ +#define WM8741_FIRSEL_SHIFT 0 /* FIRSEL - [2:0] */ +#define WM8741_FIRSEL_WIDTH 3 /* FIRSEL - [2:0] */ + +/* + * R7 (0x07) - MODE_CONTROL_1 + */ +#define WM8741_MODE8X 0x0080 /* MODE8X */ +#define WM8741_MODE8X_MASK 0x0080 /* MODE8X */ +#define WM8741_MODE8X_SHIFT 7 /* MODE8X */ +#define WM8741_MODE8X_WIDTH 1 /* MODE8X */ +#define WM8741_OSR_MASK 0x0060 /* OSR - [6:5] */ +#define WM8741_OSR_SHIFT 5 /* OSR - [6:5] */ +#define WM8741_OSR_WIDTH 2 /* OSR - [6:5] */ +#define WM8741_SR_MASK 0x001C /* SR - [4:2] */ +#define WM8741_SR_SHIFT 2 /* SR - [4:2] */ +#define WM8741_SR_WIDTH 3 /* SR - [4:2] */ +#define WM8741_MODESEL_MASK 0x0003 /* MODESEL - [1:0] */ +#define WM8741_MODESEL_SHIFT 0 /* MODESEL - [1:0] */ +#define WM8741_MODESEL_WIDTH 2 /* MODESEL - [1:0] */ + +/* + * R8 (0x08) - MODE_CONTROL_2 + */ +#define WM8741_DSD_GAIN 0x0040 /* DSD_GAIN */ +#define WM8741_DSD_GAIN_MASK 0x0040 /* DSD_GAIN */ +#define WM8741_DSD_GAIN_SHIFT 6 /* DSD_GAIN */ +#define WM8741_DSD_GAIN_WIDTH 1 /* DSD_GAIN */ +#define WM8741_SDOUT 0x0020 /* SDOUT */ +#define WM8741_SDOUT_MASK 0x0020 /* SDOUT */ +#define WM8741_SDOUT_SHIFT 5 /* SDOUT */ +#define WM8741_SDOUT_WIDTH 1 /* SDOUT */ +#define WM8741_DOUT 0x0010 /* DOUT */ +#define WM8741_DOUT_MASK 0x0010 /* DOUT */ +#define WM8741_DOUT_SHIFT 4 /* DOUT */ +#define WM8741_DOUT_WIDTH 1 /* DOUT */ +#define WM8741_DIFF_MASK 0x000C /* DIFF - [3:2] */ +#define WM8741_DIFF_SHIFT 2 /* DIFF - [3:2] */ +#define WM8741_DIFF_WIDTH 2 /* DIFF - [3:2] */ +#define WM8741_DITHER_MASK 0x0003 /* DITHER - [1:0] */ +#define WM8741_DITHER_SHIFT 0 /* DITHER - [1:0] */ +#define WM8741_DITHER_WIDTH 2 /* DITHER - [1:0] */ + +/* + * R32 (0x20) - ADDITONAL_CONTROL_1 + */ +#define WM8741_DSD_LEVEL 0x0002 /* DSD_LEVEL */ +#define WM8741_DSD_LEVEL_MASK 0x0002 /* DSD_LEVEL */ +#define WM8741_DSD_LEVEL_SHIFT 1 /* DSD_LEVEL */ +#define WM8741_DSD_LEVEL_WIDTH 1 /* DSD_LEVEL */ +#define WM8741_DSD_NO_NOTCH 0x0001 /* DSD_NO_NOTCH */ +#define WM8741_DSD_NO_NOTCH_MASK 0x0001 /* DSD_NO_NOTCH */ +#define WM8741_DSD_NO_NOTCH_SHIFT 0 /* DSD_NO_NOTCH */ +#define WM8741_DSD_NO_NOTCH_WIDTH 1 /* DSD_NO_NOTCH */ + +#define WM8741_SYSCLK 0 + +extern struct snd_soc_dai wm8741_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8741; + +#endif diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 9407e193fcc3..e2c05e3e323a 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -884,6 +884,7 @@ static int wm8750_i2c_remove(struct i2c_client *client) static const struct i2c_device_id wm8750_i2c_id[] = { { "wm8750", 0 }, + { "wm8987", 0 }, /* WM8987 is register compatible with WM8750 */ { } }; MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id); @@ -925,14 +926,22 @@ static int __devexit wm8750_spi_remove(struct spi_device *spi) return 0; } +static const struct spi_device_id wm8750_spi_id[] = { + { "wm8750", 0 }, + { "wm8987", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, wm8750_spi_id); + static struct spi_driver wm8750_spi_driver = { .driver = { - .name = "wm8750", + .name = "WM8750 SPI Codec", .bus = &spi_bus_type, .owner = THIS_MODULE, }, .probe = wm8750_spi_probe, .remove = __devexit_p(wm8750_spi_remove), + .id_table = wm8750_spi_id, }; #endif diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 87f14f8675fa..f7dcabf6283c 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2433,7 +2433,8 @@ static int wm8904_register(struct wm8904_priv *wm8904, if (wm8904_codec) { dev_err(codec->dev, "Another WM8904 is registered\n"); - return -EINVAL; + ret = -EINVAL; + goto err; } mutex_init(&codec->mutex); @@ -2462,7 +2463,8 @@ static int wm8904_register(struct wm8904_priv *wm8904, default: dev_err(codec->dev, "Unknown device type %d\n", wm8904->devtype); - return -EINVAL; + ret = -EINVAL; + goto err; } memcpy(codec->reg_cache, wm8904_reg, sizeof(wm8904_reg)); @@ -2566,18 +2568,19 @@ static int wm8904_register(struct wm8904_priv *wm8904, ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - return ret; + goto err_enable; } ret = snd_soc_register_dai(&wm8904_dai); if (ret != 0) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - snd_soc_unregister_codec(codec); - return ret; + goto err_codec; } return 0; +err_codec: + snd_soc_unregister_codec(codec); err_enable: regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies); err_get: diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index e3c4bbfaae27..f0c11138e610 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -845,6 +845,7 @@ static void wm8940_unregister(struct wm8940_priv *wm8940) static int wm8940_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { + int ret; struct wm8940_priv *wm8940; struct snd_soc_codec *codec; @@ -858,7 +859,11 @@ static int wm8940_i2c_probe(struct i2c_client *i2c, codec->control_data = i2c; codec->dev = &i2c->dev; - return wm8940_register(wm8940, SND_SOC_I2C); + ret = wm8940_register(wm8940, SND_SOC_I2C); + if (ret < 0) + kfree(wm8940); + + return ret; } static int __devexit wm8940_i2c_remove(struct i2c_client *client) diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index fedb76452f1b..5f025593d84d 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -964,7 +964,8 @@ static int wm8955_register(struct wm8955_priv *wm8955, if (wm8955_codec) { dev_err(codec->dev, "Another WM8955 is registered\n"); - return -EINVAL; + ret = -EINVAL; + goto err; } mutex_init(&codec->mutex); @@ -1047,18 +1048,19 @@ static int wm8955_register(struct wm8955_priv *wm8955, ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - return ret; + goto err_enable; } ret = snd_soc_register_dai(&wm8955_dai); if (ret != 0) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - snd_soc_unregister_codec(codec); - return ret; + goto err_codec; } return 0; +err_codec: + snd_soc_unregister_codec(codec); err_enable: regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); err_get: diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 7233cc68435a..3c6ee61f6c95 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -79,12 +79,13 @@ struct wm8960_priv { struct snd_soc_dapm_widget *lout1; struct snd_soc_dapm_widget *rout1; struct snd_soc_dapm_widget *out3; + bool deemph; + int playback_fs; }; #define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) /* enumerated controls */ -static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", "Right Inverted", "Stereo Inversion"}; static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; @@ -93,7 +94,6 @@ static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; static const struct soc_enum wm8960_enum[] = { - SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph), SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), @@ -102,6 +102,59 @@ static const struct soc_enum wm8960_enum[] = { SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), }; +static const int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8960_set_deemph(struct snd_soc_codec *codec) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8960->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8960->playback_fs) < + abs(deemph_settings[best] - wm8960->playback_fs)) + best = i; + } + + val = best << 1; + } else { + val = 0; + } + + dev_dbg(codec->dev, "Set deemphasis %d\n", val); + + return snd_soc_update_bits(codec, WM8960_DACCTL1, + 0x6, val); +} + +static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + return wm8960->deemph; +} + +static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int deemph = ucontrol->value.enumerated.item[0]; + + if (deemph > 1) + return -EINVAL; + + wm8960->deemph = deemph; + + return wm8960_set_deemph(codec); +} + static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); @@ -131,23 +184,24 @@ SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), -SOC_ENUM("ADC Polarity", wm8960_enum[1]), -SOC_ENUM("Playback De-emphasis", wm8960_enum[0]), +SOC_ENUM("ADC Polarity", wm8960_enum[0]), SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), SOC_ENUM("DAC Polarity", wm8960_enum[2]), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8960_get_deemph, wm8960_put_deemph), -SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]), -SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]), +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]), SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), -SOC_ENUM("ALC Function", wm8960_enum[5]), +SOC_ENUM("ALC Function", wm8960_enum[4]), SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), -SOC_ENUM("ALC Mode", wm8960_enum[6]), +SOC_ENUM("ALC Mode", wm8960_enum[5]), SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), @@ -433,6 +487,21 @@ static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +static struct { + int rate; + unsigned int val; +} alc_rates[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11250, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + static int wm8960_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -440,7 +509,9 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; + int i; /* bit size */ switch (params_format(params)) { @@ -454,6 +525,18 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, break; } + /* Update filters for the new rate */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + wm8960->playback_fs = params_rate(params); + wm8960_set_deemph(codec); + } else { + for (i = 0; i < ARRAY_SIZE(alc_rates); i++) + if (alc_rates[i].rate == params_rate(params)) + snd_soc_update_bits(codec, + WM8960_ADDCTL3, 0x7, + alc_rates[i].val); + } + /* set iface */ snd_soc_write(codec, WM8960_IFACE1, iface); return 0; diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 5b9a756242f1..2549d3a297ab 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -1102,7 +1102,7 @@ static int wm8961_register(struct wm8961_priv *wm8961) ret = wm8961_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset\n"); - return ret; + goto err; } /* Enable class W */ @@ -1147,18 +1147,19 @@ static int wm8961_register(struct wm8961_priv *wm8961) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - return ret; + goto err; } ret = snd_soc_register_dai(&wm8961_dai); if (ret != 0) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - snd_soc_unregister_codec(codec); - return ret; + goto err_codec; } return 0; +err_codec: + snd_soc_unregister_codec(codec); err: kfree(wm8961); return ret; diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index a2c4b2f37cca..1468fe10cbbe 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -670,7 +670,8 @@ static __devinit int wm8974_register(struct wm8974_priv *wm8974) if (wm8974_codec) { dev_err(codec->dev, "Another WM8974 is registered\n"); - return -EINVAL; + ret = -EINVAL; + goto err; } mutex_init(&codec->mutex); diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 51d5f433215c..8a1ad778e7e3 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -1076,7 +1076,6 @@ static __devinit int wm8978_register(struct wm8978_priv *wm8978) err_codec: snd_soc_unregister_codec(codec); err: - kfree(wm8978); return ret; } @@ -1085,13 +1084,13 @@ static __devexit void wm8978_unregister(struct wm8978_priv *wm8978) wm8978_set_bias_level(&wm8978->codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dai(&wm8978_dai); snd_soc_unregister_codec(&wm8978->codec); - kfree(wm8978); wm8978_codec = NULL; } static __devinit int wm8978_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { + int ret; struct wm8978_priv *wm8978; struct snd_soc_codec *codec; @@ -1107,13 +1106,18 @@ static __devinit int wm8978_i2c_probe(struct i2c_client *i2c, codec->dev = &i2c->dev; - return wm8978_register(wm8978); + ret = wm8978_register(wm8978); + if (ret < 0) + kfree(wm8978); + + return ret; } static __devexit int wm8978_i2c_remove(struct i2c_client *client) { struct wm8978_priv *wm8978 = i2c_get_clientdata(client); wm8978_unregister(wm8978); + kfree(wm8978); return 0; } diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index c018772cc430..dd8d909788c1 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -30,8 +30,6 @@ #include "wm8990.h" -#define WM8990_VERSION "0.2" - /* codec private data */ struct wm8990_priv { unsigned int sysclk; @@ -1511,8 +1509,6 @@ static int wm8990_probe(struct platform_device *pdev) struct wm8990_priv *wm8990; int ret; - pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION); - setup = socdev->codec_data; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index e84a1177f350..a87046a96f2a 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -1677,6 +1677,26 @@ static struct { static int wm8994_readable(unsigned int reg) { + switch (reg) { + case WM8994_GPIO_1: + case WM8994_GPIO_2: + case WM8994_GPIO_3: + case WM8994_GPIO_4: + case WM8994_GPIO_5: + case WM8994_GPIO_6: + case WM8994_GPIO_7: + case WM8994_GPIO_8: + case WM8994_GPIO_9: + case WM8994_GPIO_10: + case WM8994_GPIO_11: + case WM8994_INTERRUPT_STATUS_1: + case WM8994_INTERRUPT_STATUS_2: + case WM8994_INTERRUPT_RAW_STATUS_2: + return 1; + default: + break; + } + if (reg >= ARRAY_SIZE(access_masks)) return 0; return access_masks[reg].readable != 0; @@ -2341,6 +2361,20 @@ SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING, 0, 1, 0), }; +static const struct snd_kcontrol_new aif1adc2l_mix[] = { +SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif1adc2r_mix[] = { +SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + static const struct snd_kcontrol_new aif2dac2l_mix[] = { SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, 5, 1, 0), @@ -2472,6 +2506,7 @@ static const struct snd_kcontrol_new aif3adc_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_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), @@ -2506,6 +2541,11 @@ SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0, SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0, aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)), +SND_SOC_DAPM_MIXER("AIF1ADC2L Mixer", SND_SOC_NOPM, 0, 0, + aif1adc2l_mix, ARRAY_SIZE(aif1adc2l_mix)), +SND_SOC_DAPM_MIXER("AIF1ADC2R Mixer", SND_SOC_NOPM, 0, 0, + aif1adc2r_mix, ARRAY_SIZE(aif1adc2r_mix)), + SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0, aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)), SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0, @@ -2668,6 +2708,14 @@ static const struct snd_soc_dapm_route intercon[] = { { "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" }, { "AIF1ADC1R Mixer", "AIF2 Switch", "AIF2DACR" }, + { "AIF1ADC2L", NULL, "AIF1ADC2L Mixer" }, + { "AIF1ADC2L Mixer", "DMIC Switch", "DMIC2L" }, + { "AIF1ADC2L Mixer", "AIF2 Switch", "AIF2DACL" }, + + { "AIF1ADC2R", NULL, "AIF1ADC2R Mixer" }, + { "AIF1ADC2R Mixer", "DMIC Switch", "DMIC2R" }, + { "AIF1ADC2R Mixer", "AIF2 Switch", "AIF2DACR" }, + /* Pin level routing for AIF3 */ { "AIF1DAC1L", NULL, "AIF1DAC Mux" }, { "AIF1DAC1R", NULL, "AIF1DAC Mux" }, @@ -2946,11 +2994,14 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, return 0; } +static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 }; + static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = dai->codec; struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int i; switch (dai->id) { case 1: @@ -2988,6 +3039,25 @@ static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai, dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id); break; + case WM8994_SYSCLK_OPCLK: + /* Special case - a division (times 10) is given and + * no effect on main clocking. + */ + if (freq) { + for (i = 0; i < ARRAY_SIZE(opclk_divs); i++) + if (opclk_divs[i] == freq) + break; + if (i == ARRAY_SIZE(opclk_divs)) + return -EINVAL; + snd_soc_update_bits(codec, WM8994_CLOCKING_2, + WM8994_OPCLK_DIV_MASK, i); + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2, + WM8994_OPCLK_ENA, WM8994_OPCLK_ENA); + } else { + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2, + WM8994_OPCLK_ENA, 0); + } + default: return -EINVAL; } @@ -4004,6 +4074,11 @@ static int wm8994_codec_probe(struct platform_device *pdev) 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT, 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT); + /* Unconditionally enable AIF1 ADC TDM mode; it only affects + * behaviour on idle TDM clock cycles. */ + snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1, + WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM); + wm8994_update_class_w(codec); ret = snd_soc_register_codec(codec); diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 7072dc539354..2e0ca67a8df7 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -20,6 +20,9 @@ extern struct snd_soc_dai wm8994_dai[]; #define WM8994_SYSCLK_FLL1 3 #define WM8994_SYSCLK_FLL2 4 +/* OPCLK is also configured with set_dai_sysclk, specify division*10 as rate. */ +#define WM8994_SYSCLK_OPCLK 5 + #define WM8994_FLL1 1 #define WM8994_FLL2 2 diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 13186fb4dcb4..76b37ff6c264 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -1356,7 +1356,7 @@ static int wm9081_register(struct wm9081_priv *wm9081, ret = snd_soc_codec_set_cache_io(codec, 8, 16, control); if (ret != 0) { dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); - return ret; + goto err; } reg = snd_soc_read(codec, WM9081_SOFTWARE_RESET); @@ -1369,7 +1369,7 @@ static int wm9081_register(struct wm9081_priv *wm9081, ret = wm9081_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset\n"); - return ret; + goto err; } wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1388,18 +1388,19 @@ static int wm9081_register(struct wm9081_priv *wm9081, ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - return ret; + goto err; } ret = snd_soc_register_dai(&wm9081_dai); if (ret != 0) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - snd_soc_unregister_codec(codec); - return ret; + goto err_codec; } return 0; +err_codec: + snd_soc_unregister_codec(codec); err: kfree(wm9081); return ret; diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 16f1a57da08a..2cb81538cd91 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -410,6 +410,8 @@ static int hp_event(struct snd_soc_dapm_widget *w, WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY, 0); + snd_soc_write(codec, WM8993_DC_SERVO_0, 0); + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, 0); diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index adadcd3aa1b1..9e8932abf158 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -26,6 +26,7 @@ #include <mach/asp.h> #include "davinci-pcm.h" +#include "davinci-i2s.h" /* @@ -68,16 +69,21 @@ #define DAVINCI_MCBSP_RCR_RDATDLY(v) ((v) << 16) #define DAVINCI_MCBSP_RCR_RFIG (1 << 18) #define DAVINCI_MCBSP_RCR_RWDLEN2(v) ((v) << 21) +#define DAVINCI_MCBSP_RCR_RFRLEN2(v) ((v) << 24) +#define DAVINCI_MCBSP_RCR_RPHASE BIT(31) #define DAVINCI_MCBSP_XCR_XWDLEN1(v) ((v) << 5) #define DAVINCI_MCBSP_XCR_XFRLEN1(v) ((v) << 8) #define DAVINCI_MCBSP_XCR_XDATDLY(v) ((v) << 16) #define DAVINCI_MCBSP_XCR_XFIG (1 << 18) #define DAVINCI_MCBSP_XCR_XWDLEN2(v) ((v) << 21) +#define DAVINCI_MCBSP_XCR_XFRLEN2(v) ((v) << 24) +#define DAVINCI_MCBSP_XCR_XPHASE BIT(31) #define DAVINCI_MCBSP_SRGR_FWID(v) ((v) << 8) #define DAVINCI_MCBSP_SRGR_FPER(v) ((v) << 16) #define DAVINCI_MCBSP_SRGR_FSGM (1 << 28) +#define DAVINCI_MCBSP_SRGR_CLKSM BIT(29) #define DAVINCI_MCBSP_PCR_CLKRP (1 << 0) #define DAVINCI_MCBSP_PCR_CLKXP (1 << 1) @@ -116,6 +122,7 @@ static const unsigned char double_fmt[SNDRV_PCM_FORMAT_S32_LE + 1] = { }; struct davinci_mcbsp_dev { + struct device *dev; struct davinci_pcm_dma_params dma_params[2]; void __iomem *base; #define MOD_DSP_A 0 @@ -144,6 +151,11 @@ struct davinci_mcbsp_dev { * won't end up being swapped because of the underrun. */ unsigned enable_channel_combine:1; + + unsigned int fmt; + int clk_div; + int clk_input_pin; + bool i2s_accurate_sck; }; static inline void davinci_mcbsp_write_reg(struct davinci_mcbsp_dev *dev, @@ -254,10 +266,12 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, struct davinci_mcbsp_dev *dev = cpu_dai->private_data; unsigned int pcr; unsigned int srgr; + /* Attention srgr is updated by hw_params! */ srgr = DAVINCI_MCBSP_SRGR_FSGM | DAVINCI_MCBSP_SRGR_FPER(DEFAULT_BITPERSAMPLE * 2 - 1) | DAVINCI_MCBSP_SRGR_FWID(DEFAULT_BITPERSAMPLE - 1); + dev->fmt = fmt; /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: @@ -268,11 +282,26 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, DAVINCI_MCBSP_PCR_CLKRM; break; case SND_SOC_DAIFMT_CBM_CFS: - /* McBSP CLKR pin is the input for the Sample Rate Generator. - * McBSP FSR and FSX are driven by the Sample Rate Generator. */ - pcr = DAVINCI_MCBSP_PCR_SCLKME | - DAVINCI_MCBSP_PCR_FSXM | - DAVINCI_MCBSP_PCR_FSRM; + pcr = DAVINCI_MCBSP_PCR_FSRM | DAVINCI_MCBSP_PCR_FSXM; + /* + * Selection of the clock input pin that is the + * input for the Sample Rate Generator. + * McBSP FSR and FSX are driven by the Sample Rate + * Generator. + */ + switch (dev->clk_input_pin) { + case MCBSP_CLKS: + pcr |= DAVINCI_MCBSP_PCR_CLKXM | + DAVINCI_MCBSP_PCR_CLKRM; + break; + case MCBSP_CLKR: + pcr |= DAVINCI_MCBSP_PCR_SCLKME; + break; + default: + dev_err(dev->dev, "bad clk_input_pin\n"); + return -EINVAL; + } + break; case SND_SOC_DAIFMT_CBM_CFM: /* codec is master */ @@ -372,6 +401,18 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, return 0; } +static int davinci_i2s_dai_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct davinci_mcbsp_dev *dev = cpu_dai->private_data; + + if (div_id != DAVINCI_MCBSP_CLKGDV) + return -ENODEV; + + dev->clk_div = div; + return 0; +} + static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -380,8 +421,8 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, struct davinci_pcm_dma_params *dma_params = &dev->dma_params[substream->stream]; struct snd_interval *i = NULL; - int mcbsp_word_length; - unsigned int rcr, xcr, srgr; + int mcbsp_word_length, master; + unsigned int rcr, xcr, srgr, clk_div, freq, framesize; u32 spcr; snd_pcm_format_t fmt; unsigned element_cnt = 1; @@ -396,12 +437,59 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } - i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); - srgr = DAVINCI_MCBSP_SRGR_FSGM; - srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1); + master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK; + fmt = params_format(params); + mcbsp_word_length = asp_word_length[fmt]; - i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS); - srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1); + switch (master) { + case SND_SOC_DAIFMT_CBS_CFS: + freq = clk_get_rate(dev->clk); + srgr = DAVINCI_MCBSP_SRGR_FSGM | + DAVINCI_MCBSP_SRGR_CLKSM; + srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length * + 8 - 1); + if (dev->i2s_accurate_sck) { + clk_div = 256; + do { + framesize = (freq / (--clk_div)) / + params->rate_num * + params->rate_den; + } while (((framesize < 33) || (framesize > 4095)) && + (clk_div)); + clk_div--; + srgr |= DAVINCI_MCBSP_SRGR_FPER(framesize - 1); + } else { + /* symmetric waveforms */ + clk_div = freq / (mcbsp_word_length * 16) / + params->rate_num * params->rate_den; + srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length * + 16 - 1); + } + clk_div &= 0xFF; + srgr |= clk_div; + break; + case SND_SOC_DAIFMT_CBM_CFS: + srgr = DAVINCI_MCBSP_SRGR_FSGM; + clk_div = dev->clk_div - 1; + srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length * 8 - 1); + srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length * 16 - 1); + clk_div &= 0xFF; + srgr |= clk_div; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Clock and frame sync given from external sources */ + i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + srgr = DAVINCI_MCBSP_SRGR_FSGM; + srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1); + pr_debug("%s - %d FWID set: re-read srgr = %X\n", + __func__, __LINE__, snd_interval_value(i) - 1); + + i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS); + srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1); + break; + default: + return -EINVAL; + } davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr); rcr = DAVINCI_MCBSP_RCR_RFIG; @@ -426,12 +514,41 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, element_cnt = 1; fmt = double_fmt[fmt]; } + switch (master) { + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(0); + xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(0); + rcr |= DAVINCI_MCBSP_RCR_RPHASE; + xcr |= DAVINCI_MCBSP_XCR_XPHASE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(element_cnt - 1); + xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(element_cnt - 1); + break; + default: + return -EINVAL; + } } dma_params->acnt = dma_params->data_type = data_type[fmt]; dma_params->fifo_level = 0; mcbsp_word_length = asp_word_length[fmt]; - rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(element_cnt - 1); - xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(element_cnt - 1); + + switch (master) { + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(0); + xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(0); + break; + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(element_cnt - 1); + xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(element_cnt - 1); + break; + default: + return -EINVAL; + } rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) | DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length); @@ -442,6 +559,10 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr); else davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr); + + pr_debug("%s - %d srgr=%X\n", __func__, __LINE__, srgr); + pr_debug("%s - %d xcr=%X\n", __func__, __LINE__, xcr); + pr_debug("%s - %d rcr=%X\n", __func__, __LINE__, rcr); return 0; } @@ -500,6 +621,7 @@ static struct snd_soc_dai_ops davinci_i2s_dai_ops = { .trigger = davinci_i2s_trigger, .hw_params = davinci_i2s_hw_params, .set_fmt = davinci_i2s_set_dai_fmt, + .set_clkdiv = davinci_i2s_dai_set_clkdiv, }; @@ -526,6 +648,8 @@ static int davinci_i2s_probe(struct platform_device *pdev) struct snd_platform_data *pdata = pdev->dev.platform_data; struct davinci_mcbsp_dev *dev; struct resource *mem, *ioarea, *res; + enum dma_event_q asp_chan_q = EVENTQ_0; + enum dma_event_q ram_chan_q = EVENTQ_1; int ret; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -552,7 +676,17 @@ static int davinci_i2s_probe(struct platform_device *pdev) pdata->sram_size_playback; dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].sram_size = pdata->sram_size_capture; + dev->clk_input_pin = pdata->clk_input_pin; + dev->i2s_accurate_sck = pdata->i2s_accurate_sck; + asp_chan_q = pdata->asp_chan_q; + ram_chan_q = pdata->ram_chan_q; } + + dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].asp_chan_q = asp_chan_q; + dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].ram_chan_q = ram_chan_q; + dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].asp_chan_q = asp_chan_q; + dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].ram_chan_q = ram_chan_q; + dev->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(dev->clk)) { ret = -ENODEV; @@ -584,6 +718,7 @@ static int davinci_i2s_probe(struct platform_device *pdev) goto err_free_mem; } dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel = res->start; + dev->dev = &pdev->dev; davinci_i2s_dai.private_data = dev; davinci_i2s_dai.capture.dma_data = dev->dma_params; diff --git a/sound/soc/davinci/davinci-i2s.h b/sound/soc/davinci/davinci-i2s.h index 241648ce8873..0b1e77b8c279 100644 --- a/sound/soc/davinci/davinci-i2s.h +++ b/sound/soc/davinci/davinci-i2s.h @@ -12,6 +12,11 @@ #ifndef _DAVINCI_I2S_H #define _DAVINCI_I2S_H +/* McBSP dividers */ +enum davinci_mcbsp_div { + DAVINCI_MCBSP_CLKGDV, /* Sample rate generator divider */ +}; + extern struct snd_soc_dai davinci_i2s_dai; #endif diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index d3955096d872..b24720894af6 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -890,7 +890,8 @@ static int davinci_mcasp_probe(struct platform_device *pdev) dev->rxnumevt = pdata->rxnumevt; dma_data = &dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]; - dma_data->eventq_no = pdata->eventq_no; + dma_data->asp_chan_q = pdata->asp_chan_q; + dma_data->ram_chan_q = pdata->ram_chan_q; dma_data->dma_addr = (dma_addr_t) (pdata->tx_dma_offset + io_v2p(dev->base)); @@ -904,7 +905,8 @@ static int davinci_mcasp_probe(struct platform_device *pdev) dma_data->channel = res->start; dma_data = &dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]; - dma_data->eventq_no = pdata->eventq_no; + dma_data->asp_chan_q = pdata->asp_chan_q; + dma_data->ram_chan_q = pdata->ram_chan_q; dma_data->dma_addr = (dma_addr_t)(pdata->rx_dma_offset + io_v2p(dev->base)); diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c index 2dc406f42fe7..a7124116d2e0 100644 --- a/sound/soc/davinci/davinci-pcm.c +++ b/sound/soc/davinci/davinci-pcm.c @@ -381,7 +381,7 @@ static int request_ping_pong(struct snd_pcm_substream *substream, /* Request ram master channel */ link = prtd->ram_channel = edma_alloc_channel(EDMA_CHANNEL_ANY, davinci_pcm_dma_irq, substream, - EVENTQ_1); + prtd->params->ram_chan_q); if (link < 0) goto exit1; @@ -477,7 +477,8 @@ static int davinci_pcm_dma_request(struct snd_pcm_substream *substream) /* Request asp master DMA channel */ link = prtd->asp_channel = edma_alloc_channel(params->channel, - davinci_pcm_dma_irq, substream, EVENTQ_0); + davinci_pcm_dma_irq, substream, + prtd->params->asp_chan_q); if (link < 0) goto exit1; @@ -800,7 +801,7 @@ static void davinci_pcm_free(struct snd_pcm *pcm) dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area, buf->addr); buf->area = NULL; - iram_dma = (struct snd_dma_buffer *)buf->private_data; + iram_dma = buf->private_data; if (iram_dma) { sram_free(iram_dma->area, iram_dma->bytes); kfree(iram_dma); diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h index 0764944cf10f..b799a02333d8 100644 --- a/sound/soc/davinci/davinci-pcm.h +++ b/sound/soc/davinci/davinci-pcm.h @@ -21,7 +21,8 @@ struct davinci_pcm_dma_params { unsigned short acnt; dma_addr_t dma_addr; /* device physical address for DMA */ unsigned sram_size; - enum dma_event_q eventq_no; /* event queue number */ + enum dma_event_q asp_chan_q; /* event queue number for ASP channel */ + enum dma_event_q ram_chan_q; /* event queue number for RAM channel */ unsigned char data_type; /* xfer data type */ unsigned char convert_mono_stereo; unsigned int fifo_level; diff --git a/sound/soc/davinci/davinci-vcif.c b/sound/soc/davinci/davinci-vcif.c index 9aa980d38231..48678533da7a 100644 --- a/sound/soc/davinci/davinci-vcif.c +++ b/sound/soc/davinci/davinci-vcif.c @@ -203,7 +203,7 @@ static int davinci_vcif_probe(struct platform_device *pdev) int ret; davinci_vcif_dev = kzalloc(sizeof(struct davinci_vcif_dev), GFP_KERNEL); - if (!davinci_vc) { + if (!davinci_vcif_dev) { dev_dbg(&pdev->dev, "could not allocate memory for private data\n"); return -ENOMEM; diff --git a/sound/soc/ep93xx/Kconfig b/sound/soc/ep93xx/Kconfig new file mode 100644 index 000000000000..f617f560f46b --- /dev/null +++ b/sound/soc/ep93xx/Kconfig @@ -0,0 +1,18 @@ +config SND_EP93XX_SOC + tristate "SoC Audio support for the Cirrus Logic EP93xx series" + depends on ARCH_EP93XX && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the EP93xx I2S interface. + +config SND_EP93XX_SOC_I2S + tristate + +config SND_EP93XX_SOC_SNAPPERCL15 + tristate "SoC Audio support for Bluewater Systems Snapper CL15 module" + depends on SND_EP93XX_SOC && MACH_SNAPPER_CL15 + select SND_EP93XX_SOC_I2S + select SND_SOC_TLV320AIC23 + help + Say Y or M here if you want to add support for I2S audio on the + Bluewater Systems Snapper CL15 module. diff --git a/sound/soc/ep93xx/Makefile b/sound/soc/ep93xx/Makefile new file mode 100644 index 000000000000..272e60f57b9a --- /dev/null +++ b/sound/soc/ep93xx/Makefile @@ -0,0 +1,11 @@ +# EP93xx Platform Support +snd-soc-ep93xx-objs := ep93xx-pcm.o +snd-soc-ep93xx-i2s-objs := ep93xx-i2s.o + +obj-$(CONFIG_SND_EP93XX_SOC) += snd-soc-ep93xx.o +obj-$(CONFIG_SND_EP93XX_SOC_I2S) += snd-soc-ep93xx-i2s.o + +# EP93XX Machine Support +snd-soc-snappercl15-objs := snappercl15.o + +obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15) += snd-soc-snappercl15.o diff --git a/sound/soc/ep93xx/ep93xx-i2s.c b/sound/soc/ep93xx/ep93xx-i2s.c new file mode 100644 index 000000000000..00b946632184 --- /dev/null +++ b/sound/soc/ep93xx/ep93xx-i2s.c @@ -0,0 +1,487 @@ +/* + * linux/sound/soc/ep93xx-i2s.c + * EP93xx I2S driver + * + * Copyright (C) 2010 Ryan Mallon <ryan@bluewatersys.com> + * + * Based on the original driver by: + * Copyright (C) 2007 Chase Douglas <chasedouglas@gmail> + * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> + * + * 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/init.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <mach/hardware.h> +#include <mach/ep93xx-regs.h> +#include <mach/dma.h> + +#include "ep93xx-pcm.h" +#include "ep93xx-i2s.h" + +#define EP93XX_I2S_TXCLKCFG 0x00 +#define EP93XX_I2S_RXCLKCFG 0x04 +#define EP93XX_I2S_GLCTRL 0x0C + +#define EP93XX_I2S_TXLINCTRLDATA 0x28 +#define EP93XX_I2S_TXCTRL 0x2C +#define EP93XX_I2S_TXWRDLEN 0x30 +#define EP93XX_I2S_TX0EN 0x34 + +#define EP93XX_I2S_RXLINCTRLDATA 0x58 +#define EP93XX_I2S_RXCTRL 0x5C +#define EP93XX_I2S_RXWRDLEN 0x60 +#define EP93XX_I2S_RX0EN 0x64 + +#define EP93XX_I2S_WRDLEN_16 (0 << 0) +#define EP93XX_I2S_WRDLEN_24 (1 << 0) +#define EP93XX_I2S_WRDLEN_32 (2 << 0) + +#define EP93XX_I2S_LINCTRLDATA_R_JUST (1 << 2) /* Right justify */ + +#define EP93XX_I2S_CLKCFG_LRS (1 << 0) /* lrclk polarity */ +#define EP93XX_I2S_CLKCFG_CKP (1 << 1) /* Bit clock polarity */ +#define EP93XX_I2S_CLKCFG_REL (1 << 2) /* First bit transition */ +#define EP93XX_I2S_CLKCFG_MASTER (1 << 3) /* Master mode */ +#define EP93XX_I2S_CLKCFG_NBCG (1 << 4) /* Not bit clock gating */ + +struct ep93xx_i2s_info { + struct clk *mclk; + struct clk *sclk; + struct clk *lrclk; + struct ep93xx_pcm_dma_params *dma_params; + struct resource *mem; + void __iomem *regs; +}; + +struct ep93xx_pcm_dma_params ep93xx_i2s_dma_params[] = { + [SNDRV_PCM_STREAM_PLAYBACK] = { + .name = "i2s-pcm-out", + .dma_port = EP93XX_DMA_M2P_PORT_I2S1, + }, + [SNDRV_PCM_STREAM_CAPTURE] = { + .name = "i2s-pcm-in", + .dma_port = EP93XX_DMA_M2P_PORT_I2S1, + }, +}; + +static inline void ep93xx_i2s_write_reg(struct ep93xx_i2s_info *info, + unsigned reg, unsigned val) +{ + __raw_writel(val, info->regs + reg); +} + +static inline unsigned ep93xx_i2s_read_reg(struct ep93xx_i2s_info *info, + unsigned reg) +{ + return __raw_readl(info->regs + reg); +} + +static void ep93xx_i2s_enable(struct ep93xx_i2s_info *info, int stream) +{ + unsigned base_reg; + int i; + + if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 && + (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) { + /* Enable clocks */ + clk_enable(info->mclk); + clk_enable(info->sclk); + clk_enable(info->lrclk); + + /* Enable i2s */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1); + } + + /* Enable fifos */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + for (i = 0; i < 3; i++) + ep93xx_i2s_write_reg(info, base_reg + (i * 4), 1); +} + +static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream) +{ + unsigned base_reg; + int i; + + /* Disable fifos */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + for (i = 0; i < 3; i++) + ep93xx_i2s_write_reg(info, base_reg + (i * 4), 0); + + if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 && + (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) { + /* Disable i2s */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 0); + + /* Disable clocks */ + clk_disable(info->lrclk); + clk_disable(info->sclk); + clk_disable(info->mclk); + } +} + +static int ep93xx_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct ep93xx_i2s_info *info = rtd->dai->cpu_dai->private_data; + + snd_soc_dai_set_dma_data(cpu_dai, substream, + &info->dma_params[substream->stream]); + return 0; +} + +static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ep93xx_i2s_info *info = rtd->dai->cpu_dai->private_data; + + ep93xx_i2s_disable(info, substream->stream); +} + +static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct ep93xx_i2s_info *info = cpu_dai->private_data; + unsigned int clk_cfg, lin_ctrl; + + clk_cfg = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG); + lin_ctrl = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXLINCTRLDATA); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + clk_cfg |= EP93XX_I2S_CLKCFG_REL; + lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST; + break; + + case SND_SOC_DAIFMT_LEFT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST; + break; + + case SND_SOC_DAIFMT_RIGHT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + lin_ctrl |= EP93XX_I2S_LINCTRLDATA_R_JUST; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* CPU is master */ + clk_cfg |= EP93XX_I2S_CLKCFG_MASTER; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + /* Codec is master */ + clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Negative bit clock, lrclk low on left word */ + clk_cfg &= ~(EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL); + break; + + case SND_SOC_DAIFMT_NB_IF: + /* Negative bit clock, lrclk low on right word */ + clk_cfg &= ~EP93XX_I2S_CLKCFG_CKP; + clk_cfg |= EP93XX_I2S_CLKCFG_REL; + break; + + case SND_SOC_DAIFMT_IB_NF: + /* Positive bit clock, lrclk low on left word */ + clk_cfg |= EP93XX_I2S_CLKCFG_CKP; + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + break; + + case SND_SOC_DAIFMT_IB_IF: + /* Positive bit clock, lrclk low on right word */ + clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL; + break; + } + + /* Write new register values */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXCLKCFG, clk_cfg); + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCLKCFG, clk_cfg); + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXLINCTRLDATA, lin_ctrl); + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, lin_ctrl); + return 0; +} + +static int ep93xx_i2s_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_dai *cpu_dai = rtd->dai->cpu_dai; + struct ep93xx_i2s_info *info = cpu_dai->private_data; + unsigned word_len, div, sdiv, lrdiv; + int found = 0, err; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = EP93XX_I2S_WRDLEN_16; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + word_len = EP93XX_I2S_WRDLEN_24; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + word_len = EP93XX_I2S_WRDLEN_32; + break; + + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXWRDLEN, word_len); + else + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXWRDLEN, word_len); + + /* + * Calculate the sdiv (bit clock) and lrdiv (left/right clock) values. + * If the lrclk is pulse length is larger than the word size, then the + * bit clock will be gated for the unused bits. + */ + div = (clk_get_rate(info->mclk) / params_rate(params)) * + params_channels(params); + for (sdiv = 2; sdiv <= 4; sdiv += 2) + for (lrdiv = 32; lrdiv <= 128; lrdiv <<= 1) + if (sdiv * lrdiv == div) { + found = 1; + goto out; + } +out: + if (!found) + return -EINVAL; + + err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv); + if (err) + return err; + + err = clk_set_rate(info->lrclk, clk_get_rate(info->sclk) / lrdiv); + if (err) + return err; + + ep93xx_i2s_enable(info, substream->stream); + return 0; +} + +static int ep93xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ep93xx_i2s_info *info = cpu_dai->private_data; + + if (dir == SND_SOC_CLOCK_IN || clk_id != 0) + return -EINVAL; + + return clk_set_rate(info->mclk, freq); +} + +#ifdef CONFIG_PM +static int ep93xx_i2s_suspend(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = dai->private_data; + + if (!dai->active) + return; + + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE); +} + +static int ep93xx_i2s_resume(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = dai->private_data; + + if (!dai->active) + return; + + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE); +} +#else +#define ep93xx_i2s_suspend NULL +#define ep93xx_i2s_resume NULL +#endif + +static struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .startup = ep93xx_i2s_startup, + .shutdown = ep93xx_i2s_shutdown, + .hw_params = ep93xx_i2s_hw_params, + .set_sysclk = ep93xx_i2s_set_sysclk, + .set_fmt = ep93xx_i2s_set_dai_fmt, +}; + +#define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai ep93xx_i2s_dai = { + .name = "ep93xx-i2s", + .id = 0, + .symmetric_rates= 1, + .suspend = ep93xx_i2s_suspend, + .resume = ep93xx_i2s_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = EP93XX_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = EP93XX_I2S_FORMATS, + }, + .ops = &ep93xx_i2s_dai_ops, +}; +EXPORT_SYMBOL_GPL(ep93xx_i2s_dai); + +static int ep93xx_i2s_probe(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info; + struct resource *res; + int err; + + info = kzalloc(sizeof(struct ep93xx_i2s_info), GFP_KERNEL); + if (!info) { + err = -ENOMEM; + goto fail; + } + + ep93xx_i2s_dai.dev = &pdev->dev; + ep93xx_i2s_dai.private_data = info; + info->dma_params = ep93xx_i2s_dma_params; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + err = -ENODEV; + goto fail; + } + + info->mem = request_mem_region(res->start, resource_size(res), + pdev->name); + if (!info->mem) { + err = -EBUSY; + goto fail; + } + + info->regs = ioremap(info->mem->start, resource_size(info->mem)); + if (!info->regs) { + err = -ENXIO; + goto fail_release_mem; + } + + info->mclk = clk_get(&pdev->dev, "mclk"); + if (IS_ERR(info->mclk)) { + err = PTR_ERR(info->mclk); + goto fail_unmap_mem; + } + + info->sclk = clk_get(&pdev->dev, "sclk"); + if (IS_ERR(info->sclk)) { + err = PTR_ERR(info->sclk); + goto fail_put_mclk; + } + + info->lrclk = clk_get(&pdev->dev, "lrclk"); + if (IS_ERR(info->lrclk)) { + err = PTR_ERR(info->lrclk); + goto fail_put_sclk; + } + + err = snd_soc_register_dai(&ep93xx_i2s_dai); + if (err) + goto fail_put_lrclk; + + return 0; + +fail_put_lrclk: + clk_put(info->lrclk); +fail_put_sclk: + clk_put(info->sclk); +fail_put_mclk: + clk_put(info->mclk); +fail_unmap_mem: + iounmap(info->regs); +fail_release_mem: + release_mem_region(info->mem->start, resource_size(info->mem)); + kfree(info); +fail: + return err; +} + +static int __devexit ep93xx_i2s_remove(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info = ep93xx_i2s_dai.private_data; + + snd_soc_unregister_dai(&ep93xx_i2s_dai); + clk_put(info->lrclk); + clk_put(info->sclk); + clk_put(info->mclk); + iounmap(info->regs); + release_mem_region(info->mem->start, resource_size(info->mem)); + kfree(info); + return 0; +} + +static struct platform_driver ep93xx_i2s_driver = { + .probe = ep93xx_i2s_probe, + .remove = __devexit_p(ep93xx_i2s_remove), + .driver = { + .name = "ep93xx-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init ep93xx_i2s_init(void) +{ + return platform_driver_register(&ep93xx_i2s_driver); +} + +static void __exit ep93xx_i2s_exit(void) +{ + platform_driver_unregister(&ep93xx_i2s_driver); +} + +module_init(ep93xx_i2s_init); +module_exit(ep93xx_i2s_exit); + +MODULE_ALIAS("platform:ep93xx-i2s"); +MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>"); +MODULE_DESCRIPTION("EP93XX I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/ep93xx/ep93xx-i2s.h b/sound/soc/ep93xx/ep93xx-i2s.h new file mode 100644 index 000000000000..3bd4ebfaa1de --- /dev/null +++ b/sound/soc/ep93xx/ep93xx-i2s.h @@ -0,0 +1,18 @@ +/* + * linux/sound/soc/ep93xx-i2s.h + * EP93xx I2S driver + * + * Copyright (C) 2010 Ryan Mallon <ryan@bluewatersys.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _EP93XX_SND_SOC_I2S_H +#define _EP93XX_SND_SOC_I2S_H + +extern struct snd_soc_dai ep93xx_i2s_dai; + +#endif /* _EP93XX_SND_SOC_I2S_H */ diff --git a/sound/soc/ep93xx/ep93xx-pcm.c b/sound/soc/ep93xx/ep93xx-pcm.c new file mode 100644 index 000000000000..4ba938400791 --- /dev/null +++ b/sound/soc/ep93xx/ep93xx-pcm.c @@ -0,0 +1,319 @@ +/* + * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface + * + * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> + * Copyright (C) 2006 Applied Data Systems + * + * Rewritten for the SoC audio subsystem (Based on PXA2xx code): + * Copyright (c) 2008 Ryan Mallon <ryan@bluewatersys.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/dma.h> +#include <mach/hardware.h> +#include <mach/ep93xx-regs.h> + +#include "ep93xx-pcm.h" + +static const struct snd_pcm_hardware ep93xx_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = SNDRV_PCM_RATE_8000, + .rate_max = SNDRV_PCM_RATE_48000, + + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + + .buffer_bytes_max = 131072, + .period_bytes_min = 32, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 32, + .fifo_size = 32, +}; + +struct ep93xx_runtime_data +{ + struct ep93xx_dma_m2p_client cl; + struct ep93xx_pcm_dma_params *params; + int pointer_bytes; + struct tasklet_struct period_tasklet; + int periods; + struct ep93xx_dma_buffer buf[32]; +}; + +static void ep93xx_pcm_period_elapsed(unsigned long data) +{ + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + snd_pcm_period_elapsed(substream); +} + +static void ep93xx_pcm_buffer_started(void *cookie, + struct ep93xx_dma_buffer *buf) +{ +} + +static void ep93xx_pcm_buffer_finished(void *cookie, + struct ep93xx_dma_buffer *buf, + int bytes, int error) +{ + struct snd_pcm_substream *substream = cookie; + struct ep93xx_runtime_data *rtd = substream->runtime->private_data; + + if (buf == rtd->buf + rtd->periods - 1) + rtd->pointer_bytes = 0; + else + rtd->pointer_bytes += buf->size; + + if (!error) { + ep93xx_dma_m2p_submit_recursive(&rtd->cl, buf); + tasklet_schedule(&rtd->period_tasklet); + } else { + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + } +} + +static int ep93xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_rtd->dai->cpu_dai; + struct ep93xx_pcm_dma_params *dma_params; + struct ep93xx_runtime_data *rtd; + int ret; + + dma_params = snd_soc_dai_get_dma_data(cpu_dai, substream); + snd_soc_set_runtime_hwparams(substream, &ep93xx_pcm_hardware); + + rtd = kmalloc(sizeof(*rtd), GFP_KERNEL); + if (!rtd) + return -ENOMEM; + + memset(&rtd->period_tasklet, 0, sizeof(rtd->period_tasklet)); + rtd->period_tasklet.func = ep93xx_pcm_period_elapsed; + rtd->period_tasklet.data = (unsigned long)substream; + + rtd->cl.name = dma_params->name; + rtd->cl.flags = dma_params->dma_port | EP93XX_DMA_M2P_IGNORE_ERROR | + ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + EP93XX_DMA_M2P_TX : EP93XX_DMA_M2P_RX); + rtd->cl.cookie = substream; + rtd->cl.buffer_started = ep93xx_pcm_buffer_started; + rtd->cl.buffer_finished = ep93xx_pcm_buffer_finished; + ret = ep93xx_dma_m2p_client_register(&rtd->cl); + if (ret < 0) { + kfree(rtd); + return ret; + } + + substream->runtime->private_data = rtd; + return 0; +} + +static int ep93xx_pcm_close(struct snd_pcm_substream *substream) +{ + struct ep93xx_runtime_data *rtd = substream->runtime->private_data; + + ep93xx_dma_m2p_client_unregister(&rtd->cl); + kfree(rtd); + return 0; +} + +static int ep93xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ep93xx_runtime_data *rtd = runtime->private_data; + size_t totsize = params_buffer_bytes(params); + size_t period = params_period_bytes(params); + int i; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = totsize; + + rtd->periods = (totsize + period - 1) / period; + for (i = 0; i < rtd->periods; i++) { + rtd->buf[i].bus_addr = runtime->dma_addr + (i * period); + rtd->buf[i].size = period; + if ((i + 1) * period > totsize) + rtd->buf[i].size = totsize - (i * period); + } + + return 0; +} + +static int ep93xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int ep93xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ep93xx_runtime_data *rtd = substream->runtime->private_data; + int ret; + int i; + + ret = 0; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + rtd->pointer_bytes = 0; + for (i = 0; i < rtd->periods; i++) + ep93xx_dma_m2p_submit(&rtd->cl, rtd->buf + i); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ep93xx_dma_m2p_flush(&rtd->cl); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t ep93xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ep93xx_runtime_data *rtd = substream->runtime->private_data; + + /* FIXME: implement this with sub-period granularity */ + return bytes_to_frames(runtime, rtd->pointer_bytes); +} + +static int ep93xx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops ep93xx_pcm_ops = { + .open = ep93xx_pcm_open, + .close = ep93xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = ep93xx_pcm_hw_params, + .hw_free = ep93xx_pcm_hw_free, + .trigger = ep93xx_pcm_trigger, + .pointer = ep93xx_pcm_pointer, + .mmap = ep93xx_pcm_mmap, +}; + +static int ep93xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = ep93xx_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + buf->bytes = size; + + return (buf->area == NULL) ? -ENOMEM : 0; +} + +static void ep93xx_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area, + buf->addr); + buf->area = NULL; + } +} + +static u64 ep93xx_pcm_dmamask = 0xffffffff; + +static int ep93xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &ep93xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = ep93xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (dai->capture.channels_min) { + ret = ep93xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + + return 0; +} + +struct snd_soc_platform ep93xx_soc_platform = { + .name = "ep93xx-audio", + .pcm_ops = &ep93xx_pcm_ops, + .pcm_new = &ep93xx_pcm_new, + .pcm_free = &ep93xx_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(ep93xx_soc_platform); + +static int __init ep93xx_soc_platform_init(void) +{ + return snd_soc_register_platform(&ep93xx_soc_platform); +} + +static void __exit ep93xx_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&ep93xx_soc_platform); +} + +module_init(ep93xx_soc_platform_init); +module_exit(ep93xx_soc_platform_exit); + +MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>"); +MODULE_DESCRIPTION("EP93xx ALSA PCM interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/ep93xx/ep93xx-pcm.h b/sound/soc/ep93xx/ep93xx-pcm.h new file mode 100644 index 000000000000..4ffdd3f62fe9 --- /dev/null +++ b/sound/soc/ep93xx/ep93xx-pcm.h @@ -0,0 +1,22 @@ +/* + * sound/soc/ep93xx/ep93xx-pcm.h - EP93xx ALSA PCM interface + * + * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> + * Copyright (C) 2006 Applied Data Systems + * + * 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 _EP93XX_SND_SOC_PCM_H +#define _EP93XX_SND_SOC_PCM_H + +struct ep93xx_pcm_dma_params { + char *name; + int dma_port; +}; + +extern struct snd_soc_platform ep93xx_soc_platform; + +#endif /* _EP93XX_SND_SOC_PCM_H */ diff --git a/sound/soc/ep93xx/snappercl15.c b/sound/soc/ep93xx/snappercl15.c new file mode 100644 index 000000000000..64955340ff75 --- /dev/null +++ b/sound/soc/ep93xx/snappercl15.c @@ -0,0 +1,150 @@ +/* + * snappercl15.c -- SoC audio for Bluewater Systems Snapper CL15 module + * + * Copyright (C) 2008 Bluewater Systems Ltd + * Author: Ryan Mallon <ryan@bluewatersys.com> + * + * 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/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "../codecs/tlv320aic23.h" +#include "ep93xx-pcm.h" +#include "ep93xx-i2s.h" + +#define CODEC_CLOCK 5644800 + +static int snappercl15_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int err; + + err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBS_CFS); + + err = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBS_CFS); + if (err) + return err; + + err = snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, + SND_SOC_CLOCK_IN); + if (err) + return err; + + err = snd_soc_dai_set_sysclk(cpu_dai, 0, CODEC_CLOCK, + SND_SOC_CLOCK_OUT); + if (err) + return err; + + return 0; +} + +static struct snd_soc_ops snappercl15_ops = { + .hw_params = snappercl15_hw_params, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +static int snappercl15_tlv320aic23_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, + ARRAY_SIZE(tlv320aic23_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + return 0; +} + +static struct snd_soc_dai_link snappercl15_dai = { + .name = "tlv320aic23", + .stream_name = "AIC23", + .cpu_dai = &ep93xx_i2s_dai, + .codec_dai = &tlv320aic23_dai, + .init = snappercl15_tlv320aic23_init, + .ops = &snappercl15_ops, +}; + +static struct snd_soc_card snd_soc_snappercl15 = { + .name = "Snapper CL15", + .platform = &ep93xx_soc_platform, + .dai_link = &snappercl15_dai, + .num_links = 1, +}; + +static struct snd_soc_device snappercl15_snd_devdata = { + .card = &snd_soc_snappercl15, + .codec_dev = &soc_codec_dev_tlv320aic23, +}; + +static struct platform_device *snappercl15_snd_device; + +static int __init snappercl15_init(void) +{ + int ret; + + if (!machine_is_snapper_cl15()) + return -ENODEV; + + ret = ep93xx_i2s_acquire(EP93XX_SYSCON_DEVCFG_I2SONAC97, + EP93XX_SYSCON_I2SCLKDIV_ORIDE | + EP93XX_SYSCON_I2SCLKDIV_SPOL); + if (ret) + return ret; + + snappercl15_snd_device = platform_device_alloc("soc-audio", -1); + if (!snappercl15_snd_device) + return -ENOMEM; + + platform_set_drvdata(snappercl15_snd_device, &snappercl15_snd_devdata); + snappercl15_snd_devdata.dev = &snappercl15_snd_device->dev; + ret = platform_device_add(snappercl15_snd_device); + if (ret) + platform_device_put(snappercl15_snd_device); + + return ret; +} + +static void __exit snappercl15_exit(void) +{ + platform_device_unregister(snappercl15_snd_device); + ep93xx_i2s_release(); +} + +module_init(snappercl15_init); +module_exit(snappercl15_exit); + +MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>"); +MODULE_DESCRIPTION("ALSA SoC Snapper CL15"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c index 4f455bd6851f..676841cbae98 100644 --- a/sound/soc/fsl/mpc5200_psc_i2s.c +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -16,7 +16,6 @@ #include <asm/mpc52xx_psc.h> -#include "mpc5200_psc_i2s.h" #include "mpc5200_dma.h" /** diff --git a/sound/soc/fsl/mpc5200_psc_i2s.h b/sound/soc/fsl/mpc5200_psc_i2s.h deleted file mode 100644 index ce55e070fdf3..000000000000 --- a/sound/soc/fsl/mpc5200_psc_i2s.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Freescale MPC5200 PSC in I2S mode - * ALSA SoC Digital Audio Interface (DAI) driver - * - */ - -#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ -#define __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ - -extern struct snd_soc_dai psc_i2s_dai[]; - -#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ */ diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index 252defea93b5..52dac5e3874c 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -1,4 +1,4 @@ -config SND_IMX_SOC +menuconfig SND_IMX_SOC tristate "SoC Audio for Freescale i.MX CPUs" depends on ARCH_MXC select SND_PCM @@ -8,14 +8,12 @@ config SND_IMX_SOC Say Y or M if you want to add support for codecs attached to the i.MX SSI interface. -config SND_MXC_SOC_SSI - tristate +if SND_IMX_SOC config SND_MXC_SOC_WM1133_EV1 tristate "Audio on the the i.MX31ADS with WM1133-EV1 fitted" - depends on SND_IMX_SOC && MACH_MX31ADS_WM1133_EV1 && EXPERIMENTAL + depends on MACH_MX31ADS_WM1133_EV1 && EXPERIMENTAL select SND_SOC_WM8350 - select SND_MXC_SOC_SSI help Enable support for audio on the i.MX31ADS with the WM1133-EV1 PMIC board with WM8835x fitted. @@ -23,8 +21,17 @@ config SND_MXC_SOC_WM1133_EV1 config SND_SOC_PHYCORE_AC97 tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards" depends on MACH_PCM043 || MACH_PCA100 - select SND_MXC_SOC_SSI select SND_SOC_WM9712 help Say Y if you want to add support for SoC audio on Phytec phyCORE and phyCARD boards in AC97 mode + +config SND_SOC_EUKREA_TLV320 + tristate "Eukrea TLV320" + depends on MACH_EUKREA_MBIMX27_BASEBOARD || MACH_EUKREA_MBIMXSD_BASEBOARD + select SND_SOC_TLV320AIC23 + help + Enable I2S based access to the TLV320AIC23B codec attached + to the SSI interface + +endif # SND_IMX_SOC diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index 2d203635ac11..7bc57baf2b0e 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -8,8 +8,10 @@ endif obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o # i.MX Machine Support +snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o snd-soc-phycore-ac97-objs := phycore-ac97.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o +obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o diff --git a/sound/soc/imx/eukrea-tlv320.c b/sound/soc/imx/eukrea-tlv320.c new file mode 100644 index 000000000000..f15dfbdc47ee --- /dev/null +++ b/sound/soc/imx/eukrea-tlv320.c @@ -0,0 +1,137 @@ +/* + * eukrea-tlv320.c -- SoC audio for eukrea_cpuimxXX in I2S mode + * + * Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com> + * + * based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c + * which is Copyright 2009 Simtec Electronics + * and on sound/soc/imx/phycore-ac97.c which is + * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.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/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <asm/mach-types.h> + +#include "../codecs/tlv320aic23.h" +#include "imx-ssi.h" + +#define CODEC_CLOCK 12000000 + +static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret) { + pr_err("%s: failed set cpu dai format\n", __func__); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret) { + pr_err("%s: failed set codec dai format\n", __func__); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + CODEC_CLOCK, SND_SOC_CLOCK_OUT); + if (ret) { + pr_err("%s: failed setting codec sysclk\n", __func__); + return ret; + } + snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0); + + ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, + SND_SOC_CLOCK_IN); + if (ret) { + pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops eukrea_tlv320_snd_ops = { + .hw_params = eukrea_tlv320_hw_params, +}; + +static struct snd_soc_dai_link eukrea_tlv320_dai = { + .name = "tlv320aic23", + .stream_name = "TLV320AIC23", + .codec_dai = &tlv320aic23_dai, + .ops = &eukrea_tlv320_snd_ops, +}; + +static struct snd_soc_card eukrea_tlv320 = { + .name = "cpuimx-audio", + .platform = &imx_soc_platform, + .dai_link = &eukrea_tlv320_dai, + .num_links = 1, +}; + +static struct snd_soc_device eukrea_tlv320_snd_devdata = { + .card = &eukrea_tlv320, + .codec_dev = &soc_codec_dev_tlv320aic23, +}; + +static struct platform_device *eukrea_tlv320_snd_device; + +static int __init eukrea_tlv320_init(void) +{ + int ret; + + if (!machine_is_eukrea_cpuimx27() && !machine_is_eukrea_cpuimx25sd() + && !machine_is_eukrea_cpuimx35sd()) + /* return happy. We might run on a totally different machine */ + return 0; + + eukrea_tlv320_snd_device = platform_device_alloc("soc-audio", -1); + if (!eukrea_tlv320_snd_device) + return -ENOMEM; + + eukrea_tlv320_dai.cpu_dai = &imx_ssi_pcm_dai[0]; + + platform_set_drvdata(eukrea_tlv320_snd_device, &eukrea_tlv320_snd_devdata); + eukrea_tlv320_snd_devdata.dev = &eukrea_tlv320_snd_device->dev; + ret = platform_device_add(eukrea_tlv320_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + platform_device_put(eukrea_tlv320_snd_device); + } + + return ret; +} + +static void __exit eukrea_tlv320_exit(void) +{ + platform_device_unregister(eukrea_tlv320_snd_device); +} + +module_init(eukrea_tlv320_init); +module_exit(eukrea_tlv320_exit); + +MODULE_AUTHOR("Eric Bénard <eric@eukrea.com>"); +MODULE_DESCRIPTION("CPUIMX ALSA SoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-pcm-dma-mx2.c b/sound/soc/imx/imx-pcm-dma-mx2.c index 05f19c9284f4..0a595da4811d 100644 --- a/sound/soc/imx/imx-pcm-dma-mx2.c +++ b/sound/soc/imx/imx-pcm-dma-mx2.c @@ -292,12 +292,16 @@ static int snd_imx_open(struct snd_pcm_substream *substream) int ret; iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); + if (iprtd == NULL) + return -ENOMEM; runtime->private_data = iprtd; ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { + kfree(iprtd); return ret; + } snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); return 0; diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/imx/imx-pcm-fiq.c index 6b518e07eea9..b2bf27282cd2 100644 --- a/sound/soc/imx/imx-pcm-fiq.c +++ b/sound/soc/imx/imx-pcm-fiq.c @@ -192,6 +192,8 @@ static int snd_imx_open(struct snd_pcm_substream *substream) int ret; iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); + if (iprtd == NULL) + return -ENOMEM; runtime->private_data = iprtd; iprtd->substream = substream; @@ -202,8 +204,10 @@ static int snd_imx_open(struct snd_pcm_substream *substream) ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { + kfree(iprtd); return ret; + } snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); return 0; diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c index 80b4fee2442b..50f51624c535 100644 --- a/sound/soc/imx/imx-ssi.c +++ b/sound/soc/imx/imx-ssi.c @@ -83,8 +83,6 @@ static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, /* * SSI DAI format configuration. * Should only be called when port is inactive (i.e. SSIEN = 0). - * Note: We don't use the I2S modes but instead manually configure the - * SSI for I2S because the I2S mode is only a register preset. */ static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { @@ -99,6 +97,10 @@ static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) /* data on rising edge of bclk, frame low 1clk before data */ strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0; scr |= SSI_SCR_NET; + if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) { + scr &= ~SSI_I2S_MODE_MASK; + scr |= SSI_SCR_I2S_MODE_SLAVE; + } break; case SND_SOC_DAIFMT_LEFT_J: /* data on rising edge of bclk, frame high with data */ @@ -143,6 +145,11 @@ static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) strcr |= SSI_STCR_TFEN0; + if (ssi->flags & IMX_SSI_NET) + scr |= SSI_SCR_NET; + if (ssi->flags & IMX_SSI_SYN) + scr |= SSI_SCR_SYN; + writel(strcr, ssi->base + SSI_STCR); writel(strcr, ssi->base + SSI_SRCR); writel(scr, ssi->base + SSI_SCR); diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig new file mode 100644 index 000000000000..5351cba66c9e --- /dev/null +++ b/sound/soc/jz4740/Kconfig @@ -0,0 +1,23 @@ +config SND_JZ4740_SOC + tristate "SoC Audio for Ingenic JZ4740 SoC" + depends on MACH_JZ4740 && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the JZ4740 I2S interface. You will also need to select the audio + interfaces to support below. + +config SND_JZ4740_SOC_I2S + depends on SND_JZ4740_SOC + tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC" + help + Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740 + based boards. + +config SND_JZ4740_SOC_QI_LB60 + tristate "SoC Audio support for Qi LB60" + depends on SND_JZ4740_SOC && JZ4740_QI_LB60 + select SND_JZ4740_SOC_I2S + select SND_SOC_JZ4740_CODEC + help + Say Y if you want to add support for ASoC audio on the Qi LB60 board + a.k.a Qi Ben NanoNote. diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile new file mode 100644 index 000000000000..be873c1b0c20 --- /dev/null +++ b/sound/soc/jz4740/Makefile @@ -0,0 +1,13 @@ +# +# Jz4740 Platform Support +# +snd-soc-jz4740-objs := jz4740-pcm.o +snd-soc-jz4740-i2s-objs := jz4740-i2s.o + +obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o +obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o + +# Jz4740 Machine Support +snd-soc-qi-lb60-objs := qi_lb60.o + +obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c new file mode 100644 index 000000000000..eb518f0c5e01 --- /dev/null +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -0,0 +1,540 @@ +/* + * Copyright (C) 2010, 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. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/clk.h> +#include <linux/delay.h> + +#include <linux/dma-mapping.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 "jz4740-i2s.h" +#include "jz4740-pcm.h" + +#define JZ_REG_AIC_CONF 0x00 +#define JZ_REG_AIC_CTRL 0x04 +#define JZ_REG_AIC_I2S_FMT 0x10 +#define JZ_REG_AIC_FIFO_STATUS 0x14 +#define JZ_REG_AIC_I2S_STATUS 0x1c +#define JZ_REG_AIC_CLK_DIV 0x30 +#define JZ_REG_AIC_FIFO 0x34 + +#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_MASK (0xf << 12) +#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_MASK (0xf << 8) +#define JZ_AIC_CONF_OVERFLOW_PLAY_LAST BIT(6) +#define JZ_AIC_CONF_INTERNAL_CODEC BIT(5) +#define JZ_AIC_CONF_I2S BIT(4) +#define JZ_AIC_CONF_RESET BIT(3) +#define JZ_AIC_CONF_BIT_CLK_MASTER BIT(2) +#define JZ_AIC_CONF_SYNC_CLK_MASTER BIT(1) +#define JZ_AIC_CONF_ENABLE BIT(0) + +#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12 +#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8 + +#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19) +#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16) +#define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15) +#define JZ_AIC_CTRL_ENABLE_TX_DMA BIT(14) +#define JZ_AIC_CTRL_MONO_TO_STEREO BIT(11) +#define JZ_AIC_CTRL_SWITCH_ENDIANNESS BIT(10) +#define JZ_AIC_CTRL_SIGNED_TO_UNSIGNED BIT(9) +#define JZ_AIC_CTRL_FLUSH BIT(8) +#define JZ_AIC_CTRL_ENABLE_ROR_INT BIT(6) +#define JZ_AIC_CTRL_ENABLE_TUR_INT BIT(5) +#define JZ_AIC_CTRL_ENABLE_RFS_INT BIT(4) +#define JZ_AIC_CTRL_ENABLE_TFS_INT BIT(3) +#define JZ_AIC_CTRL_ENABLE_LOOPBACK BIT(2) +#define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1) +#define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0) + +#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19 +#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16 + +#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12) +#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4) +#define JZ_AIC_I2S_FMT_MSB BIT(0) + +#define JZ_AIC_I2S_STATUS_BUSY BIT(2) + +#define JZ_AIC_CLK_DIV_MASK 0xf + +struct jz4740_i2s { + struct resource *mem; + void __iomem *base; + dma_addr_t phys_base; + + struct clk *clk_aic; + struct clk *clk_i2s; + + struct jz4740_pcm_config pcm_config_playback; + struct jz4740_pcm_config pcm_config_capture; +}; + +static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s, + unsigned int reg) +{ + return readl(i2s->base + reg); +} + +static inline void jz4740_i2s_write(const struct jz4740_i2s *i2s, + unsigned int reg, uint32_t value) +{ + writel(value, i2s->base + reg); +} + +static inline struct jz4740_i2s *jz4740_dai_to_i2s(struct snd_soc_dai *dai) +{ + return dai->private_data; +} + +static int jz4740_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf, ctrl; + + if (dai->active) + return 0; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + ctrl |= JZ_AIC_CTRL_FLUSH; + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + clk_enable(i2s->clk_i2s); + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf |= JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + return 0; +} + +static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + if (!dai->active) + return; + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf &= ~JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + clk_disable(i2s->clk_i2s); +} + +static int jz4740_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + + uint32_t ctrl; + uint32_t mask; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mask = JZ_AIC_CTRL_ENABLE_PLAYBACK | JZ_AIC_CTRL_ENABLE_TX_DMA; + else + mask = JZ_AIC_CTRL_ENABLE_CAPTURE | JZ_AIC_CTRL_ENABLE_RX_DMA; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ctrl |= mask; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ctrl &= ~mask; + break; + default: + return -EINVAL; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + return 0; +} + +static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + + uint32_t format = 0; + uint32_t conf; + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + + conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + conf |= JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER; + format |= JZ_AIC_I2S_FMT_ENABLE_SYS_CLK; + break; + case SND_SOC_DAIFMT_CBM_CFS: + conf |= JZ_AIC_CONF_SYNC_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: + conf |= JZ_AIC_CONF_BIT_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_MSB: + format |= JZ_AIC_I2S_FMT_MSB; + break; + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + jz4740_i2s_write(i2s, JZ_REG_AIC_I2S_FMT, format); + + return 0; +} + +static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + enum jz4740_dma_width dma_width; + struct jz4740_pcm_config *pcm_config; + unsigned int sample_size; + uint32_t ctrl; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + sample_size = 0; + dma_width = JZ4740_DMA_WIDTH_8BIT; + break; + case SNDRV_PCM_FORMAT_S16: + sample_size = 1; + dma_width = JZ4740_DMA_WIDTH_16BIT; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ctrl &= ~JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK; + ctrl |= sample_size << JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET; + if (params_channels(params) == 1) + ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO; + else + ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO; + + pcm_config = &i2s->pcm_config_playback; + pcm_config->dma_config.dst_width = dma_width; + + } else { + ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK; + ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET; + + pcm_config = &i2s->pcm_config_capture; + pcm_config->dma_config.src_width = dma_width; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + snd_soc_dai_set_dma_data(dai, substream, pcm_config); + + return 0; +} + +static int jz4740_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + struct clk *parent; + int ret = 0; + + switch (clk_id) { + case JZ4740_I2S_CLKSRC_EXT: + parent = clk_get(NULL, "ext"); + clk_set_parent(i2s->clk_i2s, parent); + break; + case JZ4740_I2S_CLKSRC_PLL: + parent = clk_get(NULL, "pll half"); + clk_set_parent(i2s->clk_i2s, parent); + ret = clk_set_rate(i2s->clk_i2s, freq); + break; + default: + return -EINVAL; + } + clk_put(parent); + + return ret; +} + +static int jz4740_i2s_suspend(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + if (dai->active) { + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf &= ~JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + clk_disable(i2s->clk_i2s); + } + + clk_disable(i2s->clk_aic); + + return 0; +} + +static int jz4740_i2s_resume(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + clk_enable(i2s->clk_aic); + + if (dai->active) { + clk_enable(i2s->clk_i2s); + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf |= JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + } + + return 0; +} + +static int jz4740_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai); + uint32_t conf; + + conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) | + (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) | + JZ_AIC_CONF_OVERFLOW_PLAY_LAST | + JZ_AIC_CONF_I2S | + JZ_AIC_CONF_INTERNAL_CODEC; + + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET); + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + return 0; +} + +static struct snd_soc_dai_ops jz4740_i2s_dai_ops = { + .startup = jz4740_i2s_startup, + .shutdown = jz4740_i2s_shutdown, + .trigger = jz4740_i2s_trigger, + .hw_params = jz4740_i2s_hw_params, + .set_fmt = jz4740_i2s_set_fmt, + .set_sysclk = jz4740_i2s_set_sysclk, +}; + +#define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE) + +struct snd_soc_dai jz4740_i2s_dai = { + .name = "jz4740-i2s", + .probe = jz4740_i2s_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .symmetric_rates = 1, + .ops = &jz4740_i2s_dai_ops, + .suspend = jz4740_i2s_suspend, + .resume = jz4740_i2s_resume, +}; +EXPORT_SYMBOL_GPL(jz4740_i2s_dai); + +static void __devinit jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s) +{ + struct jz4740_dma_config *dma_config; + + /* Playback */ + dma_config = &i2s->pcm_config_playback.dma_config; + dma_config->src_width = JZ4740_DMA_WIDTH_32BIT, + dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE; + dma_config->request_type = JZ4740_DMA_TYPE_AIC_TRANSMIT; + dma_config->flags = JZ4740_DMA_SRC_AUTOINC; + dma_config->mode = JZ4740_DMA_MODE_SINGLE; + i2s->pcm_config_playback.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO; + + /* Capture */ + dma_config = &i2s->pcm_config_capture.dma_config; + dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT, + dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE; + dma_config->request_type = JZ4740_DMA_TYPE_AIC_RECEIVE; + dma_config->flags = JZ4740_DMA_DST_AUTOINC; + dma_config->mode = JZ4740_DMA_MODE_SINGLE; + i2s->pcm_config_capture.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO; +} + +static int __devinit jz4740_i2s_dev_probe(struct platform_device *pdev) +{ + struct jz4740_i2s *i2s; + int ret; + + i2s = kzalloc(sizeof(*i2s), GFP_KERNEL); + + if (!i2s) + return -ENOMEM; + + i2s->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!i2s->mem) { + ret = -ENOENT; + goto err_free; + } + + i2s->mem = request_mem_region(i2s->mem->start, resource_size(i2s->mem), + pdev->name); + if (!i2s->mem) { + ret = -EBUSY; + goto err_free; + } + + i2s->base = ioremap_nocache(i2s->mem->start, resource_size(i2s->mem)); + if (!i2s->base) { + ret = -EBUSY; + goto err_release_mem_region; + } + + i2s->phys_base = i2s->mem->start; + + i2s->clk_aic = clk_get(&pdev->dev, "aic"); + if (IS_ERR(i2s->clk_aic)) { + ret = PTR_ERR(i2s->clk_aic); + goto err_iounmap; + } + + i2s->clk_i2s = clk_get(&pdev->dev, "i2s"); + if (IS_ERR(i2s->clk_i2s)) { + ret = PTR_ERR(i2s->clk_i2s); + goto err_clk_put_aic; + } + + clk_enable(i2s->clk_aic); + + jz4740_i2c_init_pcm_config(i2s); + + jz4740_i2s_dai.private_data = i2s; + ret = snd_soc_register_dai(&jz4740_i2s_dai); + + if (ret) { + dev_err(&pdev->dev, "Failed to register DAI\n"); + goto err_clk_put_i2s; + } + + platform_set_drvdata(pdev, i2s); + + return 0; + +err_clk_put_i2s: + clk_disable(i2s->clk_aic); + clk_put(i2s->clk_i2s); +err_clk_put_aic: + clk_put(i2s->clk_aic); +err_iounmap: + iounmap(i2s->base); +err_release_mem_region: + release_mem_region(i2s->mem->start, resource_size(i2s->mem)); +err_free: + kfree(i2s); + + return ret; +} + +static int __devexit jz4740_i2s_dev_remove(struct platform_device *pdev) +{ + struct jz4740_i2s *i2s = platform_get_drvdata(pdev); + + snd_soc_unregister_dai(&jz4740_i2s_dai); + + clk_disable(i2s->clk_aic); + clk_put(i2s->clk_i2s); + clk_put(i2s->clk_aic); + + iounmap(i2s->base); + release_mem_region(i2s->mem->start, resource_size(i2s->mem)); + + platform_set_drvdata(pdev, NULL); + kfree(i2s); + + return 0; +} + +static struct platform_driver jz4740_i2s_driver = { + .probe = jz4740_i2s_dev_probe, + .remove = __devexit_p(jz4740_i2s_dev_remove), + .driver = { + .name = "jz4740-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_i2s_init(void) +{ + return platform_driver_register(&jz4740_i2s_driver); +} +module_init(jz4740_i2s_init); + +static void __exit jz4740_i2s_exit(void) +{ + platform_driver_unregister(&jz4740_i2s_driver); +} +module_exit(jz4740_i2s_exit); + +MODULE_AUTHOR("Lars-Peter Clausen, <lars@metafoo.de>"); +MODULE_DESCRIPTION("Ingenic JZ4740 SoC I2S driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:jz4740-i2s"); diff --git a/sound/soc/jz4740/jz4740-i2s.h b/sound/soc/jz4740/jz4740-i2s.h new file mode 100644 index 000000000000..da22ed88a589 --- /dev/null +++ b/sound/soc/jz4740/jz4740-i2s.h @@ -0,0 +1,18 @@ +/* + * 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 _JZ4740_I2S_H +#define _JZ4740_I2S_H + +/* I2S clock source */ +#define JZ4740_I2S_CLKSRC_EXT 0 +#define JZ4740_I2S_CLKSRC_PLL 1 + +#define JZ4740_I2S_BIT_CLK 0 + +extern struct snd_soc_dai jz4740_i2s_dai; + +#endif diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c new file mode 100644 index 000000000000..ee68d850c8dd --- /dev/null +++ b/sound/soc/jz4740/jz4740-pcm.c @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2010, 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. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/mach-jz4740/dma.h> +#include "jz4740-pcm.h" + +struct jz4740_runtime_data { + unsigned long dma_period; + dma_addr_t dma_start; + dma_addr_t dma_pos; + dma_addr_t dma_end; + + struct jz4740_dma_chan *dma; + + dma_addr_t fifo_addr; +}; + +/* identify hardware playback capabilities */ +static const struct snd_pcm_hardware jz4740_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + + .rates = SNDRV_PCM_RATE_8000_48000, + .channels_min = 1, + .channels_max = 2, + .period_bytes_min = 16, + .period_bytes_max = 2 * PAGE_SIZE, + .periods_min = 2, + .periods_max = 128, + .buffer_bytes_max = 128 * 2 * PAGE_SIZE, + .fifo_size = 32, +}; + +static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd, + struct snd_pcm_substream *substream) +{ + unsigned long count; + + if (prtd->dma_pos == prtd->dma_end) + prtd->dma_pos = prtd->dma_start; + + if (prtd->dma_pos + prtd->dma_period > prtd->dma_end) + count = prtd->dma_end - prtd->dma_pos; + else + count = prtd->dma_period; + + jz4740_dma_disable(prtd->dma); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos); + jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr); + } else { + jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr); + jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos); + } + + jz4740_dma_set_transfer_count(prtd->dma, count); + + prtd->dma_pos += count; + + jz4740_dma_enable(prtd->dma); +} + +static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err, + void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + + snd_pcm_period_elapsed(substream); + + jz4740_pcm_start_transfer(prtd, substream); +} + +static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct jz4740_pcm_config *config; + + config = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); + + if (!config) + return 0; + + if (!prtd->dma) { + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->dma = jz4740_dma_request(substream, "PCM Capture"); + else + prtd->dma = jz4740_dma_request(substream, "PCM Playback"); + } + + if (!prtd->dma) + return -EBUSY; + + jz4740_dma_configure(prtd->dma, &config->dma_config); + prtd->fifo_addr = config->fifo_addr; + + jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + prtd->dma_period = params_period_bytes(params); + prtd->dma_start = runtime->dma_addr; + prtd->dma_pos = prtd->dma_start; + prtd->dma_end = prtd->dma_start + runtime->dma_bytes; + + return 0; +} + +static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct jz4740_runtime_data *prtd = substream->runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, NULL); + if (prtd->dma) { + jz4740_dma_free(prtd->dma); + prtd->dma = NULL; + } + + return 0; +} + +static int jz4740_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct jz4740_runtime_data *prtd = substream->runtime->private_data; + + if (!prtd->dma) + return -EBUSY; + + prtd->dma_pos = prtd->dma_start; + + return 0; +} + +static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + jz4740_pcm_start_transfer(prtd, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + jz4740_dma_disable(prtd->dma); + break; + default: + break; + } + + return 0; +} + +static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + unsigned long byte_offset; + snd_pcm_uframes_t offset; + struct jz4740_dma_chan *dma = prtd->dma; + + /* prtd->dma_pos points to the end of the current transfer. So by + * subtracting prdt->dma_start we get the offset to the end of the + * current period in bytes. By subtracting the residue of the transfer + * we get the current offset in bytes. */ + byte_offset = prtd->dma_pos - prtd->dma_start; + byte_offset -= jz4740_dma_get_residue(dma); + + offset = bytes_to_frames(runtime, byte_offset); + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +static int jz4740_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware); + + runtime->private_data = prtd; + + return 0; +} + +static int jz4740_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct jz4740_runtime_data *prtd = runtime->private_data; + + kfree(prtd); + + return 0; +} + +static int jz4740_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static struct snd_pcm_ops jz4740_pcm_ops = { + .open = jz4740_pcm_open, + .close = jz4740_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = jz4740_pcm_hw_params, + .hw_free = jz4740_pcm_hw_free, + .prepare = jz4740_pcm_prepare, + .trigger = jz4740_pcm_trigger, + .pointer = jz4740_pcm_pointer, + .mmap = jz4740_pcm_mmap, +}; + +static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = jz4740_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + buf->area = dma_alloc_noncoherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + + return 0; +} + +static void jz4740_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area, + buf->addr); + buf->area = NULL; + } +} + +static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32); + +int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &jz4740_pcm_dmamask; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (dai->playback.channels_min) { + ret = jz4740_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto err; + } + + if (dai->capture.channels_min) { + ret = jz4740_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto err; + } + +err: + return ret; +} + +struct snd_soc_platform jz4740_soc_platform = { + .name = "jz4740-pcm", + .pcm_ops = &jz4740_pcm_ops, + .pcm_new = jz4740_pcm_new, + .pcm_free = jz4740_pcm_free, +}; +EXPORT_SYMBOL_GPL(jz4740_soc_platform); + +static int __devinit jz4740_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&jz4740_soc_platform); +} + +static int __devexit jz4740_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&jz4740_soc_platform); + return 0; +} + +static struct platform_driver jz4740_pcm_driver = { + .probe = jz4740_pcm_probe, + .remove = __devexit_p(jz4740_pcm_remove), + .driver = { + .name = "jz4740-pcm", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_soc_platform_init(void) +{ + return platform_driver_register(&jz4740_pcm_driver); +} +module_init(jz4740_soc_platform_init); + +static void __exit jz4740_soc_platform_exit(void) +{ + return platform_driver_unregister(&jz4740_pcm_driver); +} +module_exit(jz4740_soc_platform_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/jz4740/jz4740-pcm.h b/sound/soc/jz4740/jz4740-pcm.h new file mode 100644 index 000000000000..e3f221e2779c --- /dev/null +++ b/sound/soc/jz4740/jz4740-pcm.h @@ -0,0 +1,22 @@ +/* + * + * 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 _JZ4740_PCM_H +#define _JZ4740_PCM_H + +#include <linux/dma-mapping.h> +#include <asm/mach-jz4740/dma.h> + +/* platform data */ +extern struct snd_soc_platform jz4740_soc_platform; + +struct jz4740_pcm_config { + struct jz4740_dma_config dma_config; + phys_addr_t fifo_addr; +}; + +#endif diff --git a/sound/soc/jz4740/qi_lb60.c b/sound/soc/jz4740/qi_lb60.c new file mode 100644 index 000000000000..f15f4918f15f --- /dev/null +++ b/sound/soc/jz4740/qi_lb60.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2009, 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 version 2 as + * published by the Free Software Foundation. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/gpio.h> + +#include "../codecs/jz4740.h" +#include "jz4740-pcm.h" +#include "jz4740-i2s.h" + + +#define QI_LB60_SND_GPIO JZ_GPIO_PORTB(29) +#define QI_LB60_AMP_GPIO JZ_GPIO_PORTD(4) + +static int qi_lb60_spk_event(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *ctrl, int event) +{ + int on = 0; + if (event & SND_SOC_DAPM_POST_PMU) + on = 1; + else if (event & SND_SOC_DAPM_PRE_PMD) + on = 0; + + gpio_set_value(QI_LB60_SND_GPIO, on); + gpio_set_value(QI_LB60_AMP_GPIO, on); + + return 0; +} + +static const struct snd_soc_dapm_widget qi_lb60_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", qi_lb60_spk_event), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route qi_lb60_routes[] = { + {"Mic", NULL, "MIC"}, + {"Speaker", NULL, "LOUT"}, + {"Speaker", NULL, "ROUT"}, +}; + +#define QI_LB60_DAIFMT (SND_SOC_DAIFMT_I2S | \ + SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBM_CFM) + +static int qi_lb60_codec_init(struct snd_soc_codec *codec) +{ + int ret; + struct snd_soc_dai *cpu_dai = codec->socdev->card->dai_link->cpu_dai; + + snd_soc_dapm_nc_pin(codec, "LIN"); + snd_soc_dapm_nc_pin(codec, "RIN"); + + ret = snd_soc_dai_set_fmt(cpu_dai, QI_LB60_DAIFMT); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cpu dai format: %d\n", ret); + return ret; + } + + snd_soc_dapm_new_controls(codec, qi_lb60_widgets, ARRAY_SIZE(qi_lb60_widgets)); + snd_soc_dapm_add_routes(codec, qi_lb60_routes, ARRAY_SIZE(qi_lb60_routes)); + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link qi_lb60_dai = { + .name = "jz4740", + .stream_name = "jz4740", + .cpu_dai = &jz4740_i2s_dai, + .codec_dai = &jz4740_codec_dai, + .init = qi_lb60_codec_init, +}; + +static struct snd_soc_card qi_lb60 = { + .name = "QI LB60", + .dai_link = &qi_lb60_dai, + .num_links = 1, + .platform = &jz4740_soc_platform, +}; + +static struct snd_soc_device qi_lb60_snd_devdata = { + .card = &qi_lb60, + .codec_dev = &soc_codec_dev_jz4740_codec, +}; + +static struct platform_device *qi_lb60_snd_device; + +static int __init qi_lb60_init(void) +{ + int ret; + + qi_lb60_snd_device = platform_device_alloc("soc-audio", -1); + + if (!qi_lb60_snd_device) + return -ENOMEM; + + ret = gpio_request(QI_LB60_SND_GPIO, "SND"); + if (ret) { + pr_err("qi_lb60 snd: Failed to request SND GPIO(%d): %d\n", + QI_LB60_SND_GPIO, ret); + goto err_device_put; + } + + ret = gpio_request(QI_LB60_AMP_GPIO, "AMP"); + if (ret) { + pr_err("qi_lb60 snd: Failed to request AMP GPIO(%d): %d\n", + QI_LB60_AMP_GPIO, ret); + goto err_gpio_free_snd; + } + + gpio_direction_output(QI_LB60_SND_GPIO, 0); + gpio_direction_output(QI_LB60_AMP_GPIO, 0); + + platform_set_drvdata(qi_lb60_snd_device, &qi_lb60_snd_devdata); + qi_lb60_snd_devdata.dev = &qi_lb60_snd_device->dev; + + ret = platform_device_add(qi_lb60_snd_device); + if (ret) { + pr_err("qi_lb60 snd: Failed to add snd soc device: %d\n", ret); + goto err_unset_pdata; + } + + return 0; + +err_unset_pdata: + platform_set_drvdata(qi_lb60_snd_device, NULL); +/*err_gpio_free_amp:*/ + gpio_free(QI_LB60_AMP_GPIO); +err_gpio_free_snd: + gpio_free(QI_LB60_SND_GPIO); +err_device_put: + platform_device_put(qi_lb60_snd_device); + + return ret; +} +module_init(qi_lb60_init); + +static void __exit qi_lb60_exit(void) +{ + gpio_free(QI_LB60_AMP_GPIO); + gpio_free(QI_LB60_SND_GPIO); + platform_device_unregister(qi_lb60_snd_device); +} +module_exit(qi_lb60_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig new file mode 100644 index 000000000000..16ec2a2dba4d --- /dev/null +++ b/sound/soc/kirkwood/Kconfig @@ -0,0 +1,20 @@ +config SND_KIRKWOOD_SOC + tristate "SoC Audio for the Marvell Kirkwood chip" + depends on ARCH_KIRKWOOD + help + Say Y or M if you want to add support for codecs attached to + the Kirkwood I2S interface. You will also need to select the + audio interfaces to support below. + +config SND_KIRKWOOD_SOC_I2S + tristate + +config SND_KIRKWOOD_SOC_OPENRD + tristate "SoC Audio support for Kirkwood Openrd Client" + depends on SND_KIRKWOOD_SOC && MACH_OPENRD_CLIENT + select SND_KIRKWOOD_SOC_I2S + select SND_SOC_CS42L51 + help + Say Y if you want to add support for SoC audio on + Openrd Client. + diff --git a/sound/soc/kirkwood/Makefile b/sound/soc/kirkwood/Makefile new file mode 100644 index 000000000000..33a16dcab5b5 --- /dev/null +++ b/sound/soc/kirkwood/Makefile @@ -0,0 +1,9 @@ +snd-soc-kirkwood-objs := kirkwood-dma.o +snd-soc-kirkwood-i2s-objs := kirkwood-i2s.o + +obj-$(CONFIG_SND_KIRKWOOD_SOC) += snd-soc-kirkwood.o +obj-$(CONFIG_SND_KIRKWOOD_SOC_I2S) += snd-soc-kirkwood-i2s.o + +snd-soc-openrd-objs := kirkwood-openrd.o + +obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o diff --git a/sound/soc/kirkwood/kirkwood-dma.c b/sound/soc/kirkwood/kirkwood-dma.c new file mode 100644 index 000000000000..a30205be3e2b --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-dma.c @@ -0,0 +1,383 @@ +/* + * kirkwood-dma.c + * + * (c) 2010 Arnaud Patard <apatard@mandriva.com> + * + * 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/device.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/mbus.h> +#include <sound/soc.h> +#include "kirkwood-dma.h" +#include "kirkwood.h" + +#define KIRKWOOD_RATES \ + (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +#define KIRKWOOD_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct kirkwood_dma_priv { + struct snd_pcm_substream *play_stream; + struct snd_pcm_substream *rec_stream; + struct kirkwood_dma_data *data; +}; + +static struct snd_pcm_hardware kirkwood_dma_snd_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE), + .formats = KIRKWOOD_FORMATS, + .rates = KIRKWOOD_RATES, + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS, + .period_bytes_min = KIRKWOOD_SND_MIN_PERIOD_BYTES, + .period_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES, + .periods_min = KIRKWOOD_SND_MIN_PERIODS, + .periods_max = KIRKWOOD_SND_MAX_PERIODS, + .fifo_size = 0, +}; + +static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL; + +static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id) +{ + struct kirkwood_dma_priv *prdata = dev_id; + struct kirkwood_dma_data *priv = prdata->data; + unsigned long mask, status, cause; + + mask = readl(priv->io + KIRKWOOD_INT_MASK); + status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask; + + cause = readl(priv->io + KIRKWOOD_ERR_CAUSE); + if (unlikely(cause)) { + printk(KERN_WARNING "%s: got err interrupt 0x%lx\n", + __func__, cause); + writel(cause, priv->io + KIRKWOOD_ERR_CAUSE); + return IRQ_HANDLED; + } + + /* we've enabled only bytes interrupts ... */ + if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \ + KIRKWOOD_INT_CAUSE_REC_BYTES)) { + printk(KERN_WARNING "%s: unexpected interrupt %lx\n", + __func__, status); + return IRQ_NONE; + } + + /* ack int */ + writel(status, priv->io + KIRKWOOD_INT_CAUSE); + + if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES) + snd_pcm_period_elapsed(prdata->play_stream); + + if (status & KIRKWOOD_INT_CAUSE_REC_BYTES) + snd_pcm_period_elapsed(prdata->rec_stream); + + return IRQ_HANDLED; +} + +static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win, + unsigned long dma, + struct mbus_dram_target_info *dram) +{ + int i; + + /* First disable and clear windows */ + writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); + writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); + + /* try to find matching cs for current dma address */ + for (i = 0; i < dram->num_cs; i++) { + struct mbus_dram_window *cs = dram->cs + i; + if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) { + writel(cs->base & 0xffff0000, + base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); + writel(((cs->size - 1) & 0xffff0000) | + (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1, + base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); + } + } +} + +static int kirkwood_dma_open(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; + struct kirkwood_dma_data *priv; + struct kirkwood_dma_priv *prdata = cpu_dai->private_data; + unsigned long addr; + + priv = snd_soc_dai_get_dma_data(cpu_dai, substream); + snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw); + + /* Ensure that all constraints linked to dma burst are fullfilled */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + priv->burst * 2, + KIRKWOOD_AUDIO_BUF_MAX-1); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + priv->burst); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + priv->burst); + if (err < 0) + return err; + + if (soc_runtime->dai->cpu_dai->private_data == NULL) { + prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL); + if (prdata == NULL) + return -ENOMEM; + + prdata->data = priv; + + err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED, + "kirkwood-i2s", prdata); + if (err) { + kfree(prdata); + return -EBUSY; + } + + soc_runtime->dai->cpu_dai->private_data = prdata; + + /* + * Enable Error interrupts. We're only ack'ing them but + * it's usefull for diagnostics + */ + writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK); + } + + addr = virt_to_phys(substream->dma_buffer.area); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prdata->play_stream = substream; + kirkwood_dma_conf_mbus_windows(priv->io, + KIRKWOOD_PLAYBACK_WIN, addr, priv->dram); + } else { + prdata->rec_stream = substream; + kirkwood_dma_conf_mbus_windows(priv->io, + KIRKWOOD_RECORD_WIN, addr, priv->dram); + } + + return 0; +} + +static int kirkwood_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; + struct kirkwood_dma_priv *prdata = cpu_dai->private_data; + struct kirkwood_dma_data *priv; + + priv = snd_soc_dai_get_dma_data(cpu_dai, substream); + + if (!prdata || !priv) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prdata->play_stream = NULL; + else + prdata->rec_stream = NULL; + + if (!prdata->play_stream && !prdata->rec_stream) { + writel(0, priv->io + KIRKWOOD_ERR_MASK); + free_irq(priv->irq, prdata); + kfree(prdata); + soc_runtime->dai->cpu_dai->private_data = NULL; + } + + return 0; +} + +static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + return 0; +} + +static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int kirkwood_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; + struct kirkwood_dma_data *priv; + unsigned long size, count; + + priv = snd_soc_dai_get_dma_data(cpu_dai, substream); + + /* compute buffer size in term of "words" as requested in specs */ + size = frames_to_bytes(runtime, runtime->buffer_size); + size = (size>>2)-1; + count = snd_pcm_lib_period_bytes(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT); + writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR); + writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE); + } else { + writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT); + writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR); + writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE); + } + + + return 0; +} + +static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream + *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; + struct kirkwood_dma_data *priv; + snd_pcm_uframes_t count; + + priv = snd_soc_dai_get_dma_data(cpu_dai, substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + count = bytes_to_frames(substream->runtime, + readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT)); + else + count = bytes_to_frames(substream->runtime, + readl(priv->io + KIRKWOOD_REC_BYTE_COUNT)); + + return count; +} + +struct snd_pcm_ops kirkwood_dma_ops = { + .open = kirkwood_dma_open, + .close = kirkwood_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = kirkwood_dma_hw_params, + .hw_free = kirkwood_dma_hw_free, + .prepare = kirkwood_dma_prepare, + .pointer = kirkwood_dma_pointer, +}; + +static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = kirkwood_dma_snd_hw.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + buf->private_data = NULL; + + return 0; +} + +static int kirkwood_dma_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + int ret; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &kirkwood_dma_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = kirkwood_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (dai->capture.channels_min) { + ret = kirkwood_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + + return 0; +} + +static void kirkwood_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +struct snd_soc_platform kirkwood_soc_platform = { + .name = "kirkwood-dma", + .pcm_ops = &kirkwood_dma_ops, + .pcm_new = kirkwood_dma_new, + .pcm_free = kirkwood_dma_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(kirkwood_soc_platform); + +static int __init kirkwood_soc_platform_init(void) +{ + return snd_soc_register_platform(&kirkwood_soc_platform); +} +module_init(kirkwood_soc_platform_init); + +static void __exit kirkwood_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&kirkwood_soc_platform); +} +module_exit(kirkwood_soc_platform_exit); + +MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>"); +MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/kirkwood/kirkwood-dma.h b/sound/soc/kirkwood/kirkwood-dma.h new file mode 100644 index 000000000000..ba4454cd34f1 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-dma.h @@ -0,0 +1,17 @@ +/* + * kirkwood-dma.h + * + * (c) 2010 Arnaud Patard <apatard@mandriva.com> + * + * 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 _KIRKWOOD_DMA_H +#define _KIRKWOOD_DMA_H + +extern struct snd_soc_platform kirkwood_soc_platform; + +#endif diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c new file mode 100644 index 000000000000..981ffc2a13c8 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -0,0 +1,495 @@ +/* + * kirkwood-i2s.c + * + * (c) 2010 Arnaud Patard <apatard@mandriva.com> + * + * 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 <linux/io.h> +#include <linux/slab.h> +#include <linux/mbus.h> +#include <linux/delay.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <plat/audio.h> +#include "kirkwood-i2s.h" +#include "kirkwood.h" + +#define DRV_NAME "kirkwood-i2s" + +#define KIRKWOOD_I2S_RATES \ + (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +#define KIRKWOOD_I2S_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + + +struct snd_soc_dai kirkwood_i2s_dai; +static struct kirkwood_dma_data *priv; + +static int kirkwood_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + unsigned long mask; + unsigned long value; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + mask = KIRKWOOD_I2S_CTL_RJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + mask = KIRKWOOD_I2S_CTL_LJ; + break; + case SND_SOC_DAIFMT_I2S: + mask = KIRKWOOD_I2S_CTL_I2S; + break; + default: + return -EINVAL; + } + + /* + * Set same format for playback and record + * This avoids some troubles. + */ + value = readl(priv->io+KIRKWOOD_I2S_PLAYCTL); + value &= ~KIRKWOOD_I2S_CTL_JUST_MASK; + value |= mask; + writel(value, priv->io+KIRKWOOD_I2S_PLAYCTL); + + value = readl(priv->io+KIRKWOOD_I2S_RECCTL); + value &= ~KIRKWOOD_I2S_CTL_JUST_MASK; + value |= mask; + writel(value, priv->io+KIRKWOOD_I2S_RECCTL); + + return 0; +} + +static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate) +{ + unsigned long value; + + value = KIRKWOOD_DCO_CTL_OFFSET_0; + switch (rate) { + default: + case 44100: + value |= KIRKWOOD_DCO_CTL_FREQ_11; + break; + case 48000: + value |= KIRKWOOD_DCO_CTL_FREQ_12; + break; + case 96000: + value |= KIRKWOOD_DCO_CTL_FREQ_24; + break; + } + writel(value, io + KIRKWOOD_DCO_CTL); + + /* wait for dco locked */ + do { + cpu_relax(); + value = readl(io + KIRKWOOD_DCO_SPCR_STATUS); + value &= KIRKWOOD_DCO_SPCR_STATUS; + } while (value == 0); +} + +static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + unsigned int i2s_reg, reg; + unsigned long i2s_value, value; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_reg = KIRKWOOD_I2S_PLAYCTL; + reg = KIRKWOOD_PLAYCTL; + } else { + i2s_reg = KIRKWOOD_I2S_RECCTL; + reg = KIRKWOOD_RECCTL; + } + + /* set dco conf */ + kirkwood_set_dco(priv->io, params_rate(params)); + + i2s_value = readl(priv->io+i2s_reg); + i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK; + + value = readl(priv->io+reg); + value &= ~KIRKWOOD_PLAYCTL_SIZE_MASK; + + /* + * Size settings in play/rec i2s control regs and play/rec control + * regs must be the same. + */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16; + value |= KIRKWOOD_PLAYCTL_SIZE_16_C; + break; + /* + * doesn't work... S20_3LE != kirkwood 20bit format ? + * + case SNDRV_PCM_FORMAT_S20_3LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20; + value |= KIRKWOOD_PLAYCTL_SIZE_20; + break; + */ + case SNDRV_PCM_FORMAT_S24_LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24; + value |= KIRKWOOD_PLAYCTL_SIZE_24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32; + value |= KIRKWOOD_PLAYCTL_SIZE_32; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + value &= ~KIRKWOOD_PLAYCTL_MONO_MASK; + if (params_channels(params) == 1) + value |= KIRKWOOD_PLAYCTL_MONO_BOTH; + else + value |= KIRKWOOD_PLAYCTL_MONO_OFF; + } + + writel(i2s_value, priv->io+i2s_reg); + writel(value, priv->io+reg); + + return 0; +} + +static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + unsigned long value; + + /* + * specs says KIRKWOOD_PLAYCTL must be read 2 times before + * changing it. So read 1 time here and 1 later. + */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* stop audio, enable interrupts */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value |= KIRKWOOD_PLAYCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* configure audio & enable i2s playback */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value &= ~KIRKWOOD_PLAYCTL_BURST_MASK; + value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE + | KIRKWOOD_PLAYCTL_SPDIF_EN); + + if (priv->burst == 32) + value |= KIRKWOOD_PLAYCTL_BURST_32; + else + value |= KIRKWOOD_PLAYCTL_BURST_128; + value |= KIRKWOOD_PLAYCTL_I2S_EN; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* stop audio, disable interrupts */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* disable all playbacks */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN); + writel(value, priv->io + KIRKWOOD_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE); + writel(value, priv->io + KIRKWOOD_PLAYCTL); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + unsigned long value; + + value = readl(priv->io + KIRKWOOD_RECCTL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* stop audio, enable interrupts */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value |= KIRKWOOD_RECCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_RECCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value |= KIRKWOOD_INT_CAUSE_REC_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* configure audio & enable i2s record */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~KIRKWOOD_RECCTL_BURST_MASK; + value &= ~KIRKWOOD_RECCTL_MONO; + value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE + | KIRKWOOD_RECCTL_SPDIF_EN); + + if (priv->burst == 32) + value |= KIRKWOOD_RECCTL_BURST_32; + else + value |= KIRKWOOD_RECCTL_BURST_128; + value |= KIRKWOOD_RECCTL_I2S_EN; + + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* stop audio, disable interrupts */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE; + writel(value, priv->io + KIRKWOOD_RECCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value &= ~KIRKWOOD_INT_CAUSE_REC_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* disable all records */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN); + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + value = readl(priv->io + KIRKWOOD_RECCTL); + value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE; + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE); + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return kirkwood_i2s_play_trigger(substream, cmd, dai); + else + return kirkwood_i2s_rec_trigger(substream, cmd, dai); + + return 0; +} + +static int kirkwood_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + unsigned long value; + unsigned int reg_data; + + /* put system in a "safe" state : */ + /* disable audio interrupts */ + writel(0xffffffff, priv->io + KIRKWOOD_INT_CAUSE); + writel(0, priv->io + KIRKWOOD_INT_MASK); + + reg_data = readl(priv->io + 0x1200); + reg_data &= (~(0x333FF8)); + reg_data |= 0x111D18; + writel(reg_data, priv->io + 0x1200); + + msleep(500); + + reg_data = readl(priv->io + 0x1200); + reg_data &= (~(0x333FF8)); + reg_data |= 0x111D18; + writel(reg_data, priv->io + 0x1200); + + /* disable playback/record */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value &= ~(KIRKWOOD_PLAYCTL_I2S_EN|KIRKWOOD_PLAYCTL_SPDIF_EN); + writel(value, priv->io + KIRKWOOD_PLAYCTL); + + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN); + writel(value, priv->io + KIRKWOOD_RECCTL); + + return 0; + +} + +static void kirkwood_i2s_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ +} + +static struct snd_soc_dai_ops kirkwood_i2s_dai_ops = { + .trigger = kirkwood_i2s_trigger, + .hw_params = kirkwood_i2s_hw_params, + .set_fmt = kirkwood_i2s_set_fmt, +}; + + +struct snd_soc_dai kirkwood_i2s_dai = { + .name = DRV_NAME, + .id = 0, + .probe = kirkwood_i2s_probe, + .remove = kirkwood_i2s_remove, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = KIRKWOOD_I2S_RATES, + .formats = KIRKWOOD_I2S_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = KIRKWOOD_I2S_RATES, + .formats = KIRKWOOD_I2S_FORMATS,}, + .ops = &kirkwood_i2s_dai_ops, +}; +EXPORT_SYMBOL_GPL(kirkwood_i2s_dai); + +static __devinit int kirkwood_i2s_dev_probe(struct platform_device *pdev) +{ + struct resource *mem; + struct kirkwood_asoc_platform_data *data = + pdev->dev.platform_data; + int err; + + priv = kzalloc(sizeof(struct kirkwood_dma_data), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "allocation failed\n"); + err = -ENOMEM; + goto error; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "platform_get_resource failed\n"); + err = -ENXIO; + goto err_alloc; + } + + priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME); + if (!priv->mem) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + err = -EBUSY; + goto error; + } + + priv->io = ioremap(priv->mem->start, SZ_16K); + if (!priv->io) { + dev_err(&pdev->dev, "ioremap failed\n"); + err = -ENOMEM; + goto err_iomem; + } + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq <= 0) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + err = -ENXIO; + goto err_ioremap; + } + + if (!data || !data->dram) { + dev_err(&pdev->dev, "no platform data ?!\n"); + err = -EINVAL; + goto err_ioremap; + } + + priv->dram = data->dram; + priv->burst = data->burst; + + kirkwood_i2s_dai.capture.dma_data = priv; + kirkwood_i2s_dai.playback.dma_data = priv; + + return snd_soc_register_dai(&kirkwood_i2s_dai); + +err_ioremap: + iounmap(priv->io); +err_iomem: + release_mem_region(priv->mem->start, SZ_16K); +err_alloc: + kfree(priv); +error: + return err; +} + +static __devexit int kirkwood_i2s_dev_remove(struct platform_device *pdev) +{ + if (priv) { + iounmap(priv->io); + release_mem_region(priv->mem->start, SZ_16K); + kfree(priv); + } + snd_soc_unregister_dai(&kirkwood_i2s_dai); + return 0; +} + +static struct platform_driver kirkwood_i2s_driver = { + .probe = kirkwood_i2s_dev_probe, + .remove = kirkwood_i2s_dev_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init kirkwood_i2s_init(void) +{ + return platform_driver_register(&kirkwood_i2s_driver); +} +module_init(kirkwood_i2s_init); + +static void __exit kirkwood_i2s_exit(void) +{ + platform_driver_unregister(&kirkwood_i2s_driver); +} +module_exit(kirkwood_i2s_exit); + +/* Module information */ +MODULE_AUTHOR("Arnaud Patard, <apatard@mandriva.com>"); +MODULE_DESCRIPTION("Kirkwood I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:kirkwood-i2s"); diff --git a/sound/soc/kirkwood/kirkwood-i2s.h b/sound/soc/kirkwood/kirkwood-i2s.h new file mode 100644 index 000000000000..c5595c616d7a --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-i2s.h @@ -0,0 +1,17 @@ +/* + * kirkwood-i2s.h + * + * (c) 2010 Arnaud Patard <apatard@mandriva.com> + * + * 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 _KIRKWOOD_I2S_H +#define _KIRKWOOD_I2S_H + +extern struct snd_soc_dai kirkwood_i2s_dai; + +#endif diff --git a/sound/soc/kirkwood/kirkwood-openrd.c b/sound/soc/kirkwood/kirkwood-openrd.c new file mode 100644 index 000000000000..0353d06bc41a --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-openrd.c @@ -0,0 +1,126 @@ +/* + * kirkwood-openrd.c + * + * (c) 2010 Arnaud Patard <apatard@mandriva.com> + * + * 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/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <mach/kirkwood.h> +#include <plat/audio.h> +#include <asm/mach-types.h> +#include "kirkwood-i2s.h" +#include "kirkwood-dma.h" +#include "../codecs/cs42l51.h" + +static int openrd_client_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + unsigned int freq, fmt; + + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) + return ret; + + switch (params_rate(params)) { + default: + case 44100: + freq = 11289600; + break; + case 48000: + freq = 12288000; + break; + case 96000: + freq = 24576000; + break; + } + + return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN); + +} + +static struct snd_soc_ops openrd_client_ops = { + .hw_params = openrd_client_hw_params, +}; + + +static struct snd_soc_dai_link openrd_client_dai[] = { +{ + .name = "CS42L51", + .stream_name = "CS42L51 HiFi", + .cpu_dai = &kirkwood_i2s_dai, + .codec_dai = &cs42l51_dai, + .ops = &openrd_client_ops, +}, +}; + + +static struct snd_soc_card openrd_client = { + .name = "OpenRD Client", + .platform = &kirkwood_soc_platform, + .dai_link = openrd_client_dai, + .num_links = ARRAY_SIZE(openrd_client_dai), +}; + +static struct snd_soc_device openrd_client_snd_devdata = { + .card = &openrd_client, + .codec_dev = &soc_codec_device_cs42l51, +}; + +static struct platform_device *openrd_client_snd_device; + +static int __init openrd_client_init(void) +{ + int ret; + + if (!machine_is_openrd_client()) + return 0; + + openrd_client_snd_device = platform_device_alloc("soc-audio", -1); + if (!openrd_client_snd_device) + return -ENOMEM; + + platform_set_drvdata(openrd_client_snd_device, + &openrd_client_snd_devdata); + openrd_client_snd_devdata.dev = &openrd_client_snd_device->dev; + + ret = platform_device_add(openrd_client_snd_device); + if (ret) { + printk(KERN_ERR "%s: platform_device_add failed\n", __func__); + platform_device_put(openrd_client_snd_device); + } + + return ret; +} + +static void __exit openrd_client_exit(void) +{ + platform_device_unregister(openrd_client_snd_device); +} + +module_init(openrd_client_init); +module_exit(openrd_client_exit); + +/* Module information */ +MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>"); +MODULE_DESCRIPTION("ALSA SoC OpenRD Client"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:soc-audio"); diff --git a/sound/soc/kirkwood/kirkwood.h b/sound/soc/kirkwood/kirkwood.h new file mode 100644 index 000000000000..bb6e6a5648c9 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood.h @@ -0,0 +1,129 @@ +/* + * kirkwood.h + * + * (c) 2010 Arnaud Patard <apatard@mandriva.com> + * + * 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 _KIRKWOOD_AUDIO_H +#define _KIRKWOOD_AUDIO_H + +#define KIRKWOOD_RECORD_WIN 0 +#define KIRKWOOD_PLAYBACK_WIN 1 +#define KIRKWOOD_MAX_AUDIO_WIN 2 + +#define KIRKWOOD_AUDIO_WIN_BASE_REG(win) (0xA00 + ((win)<<3)) +#define KIRKWOOD_AUDIO_WIN_CTRL_REG(win) (0xA04 + ((win)<<3)) + + +#define KIRKWOOD_RECCTL 0x1000 +#define KIRKWOOD_RECCTL_SPDIF_EN (1<<11) +#define KIRKWOOD_RECCTL_I2S_EN (1<<10) +#define KIRKWOOD_RECCTL_PAUSE (1<<9) +#define KIRKWOOD_RECCTL_MUTE (1<<8) +#define KIRKWOOD_RECCTL_BURST_MASK (3<<5) +#define KIRKWOOD_RECCTL_BURST_128 (2<<5) +#define KIRKWOOD_RECCTL_BURST_32 (1<<5) +#define KIRKWOOD_RECCTL_MONO (1<<4) +#define KIRKWOOD_RECCTL_MONO_CHAN_RIGHT (1<<3) +#define KIRKWOOD_RECCTL_MONO_CHAN_LEFT (0<<3) +#define KIRKWOOD_RECCTL_SIZE_MASK (7<<0) +#define KIRKWOOD_RECCTL_SIZE_16 (7<<0) +#define KIRKWOOD_RECCTL_SIZE_16_C (3<<0) +#define KIRKWOOD_RECCTL_SIZE_20 (2<<0) +#define KIRKWOOD_RECCTL_SIZE_24 (1<<0) +#define KIRKWOOD_RECCTL_SIZE_32 (0<<0) + +#define KIRKWOOD_REC_BUF_ADDR 0x1004 +#define KIRKWOOD_REC_BUF_SIZE 0x1008 +#define KIRKWOOD_REC_BYTE_COUNT 0x100C + +#define KIRKWOOD_PLAYCTL 0x1100 +#define KIRKWOOD_PLAYCTL_PLAY_BUSY (1<<16) +#define KIRKWOOD_PLAYCTL_BURST_MASK (3<<11) +#define KIRKWOOD_PLAYCTL_BURST_128 (2<<11) +#define KIRKWOOD_PLAYCTL_BURST_32 (1<<11) +#define KIRKWOOD_PLAYCTL_PAUSE (1<<9) +#define KIRKWOOD_PLAYCTL_SPDIF_MUTE (1<<8) +#define KIRKWOOD_PLAYCTL_MONO_MASK (3<<5) +#define KIRKWOOD_PLAYCTL_MONO_BOTH (3<<5) +#define KIRKWOOD_PLAYCTL_MONO_OFF (0<<5) +#define KIRKWOOD_PLAYCTL_I2S_MUTE (1<<7) +#define KIRKWOOD_PLAYCTL_SPDIF_EN (1<<4) +#define KIRKWOOD_PLAYCTL_I2S_EN (1<<3) +#define KIRKWOOD_PLAYCTL_SIZE_MASK (7<<0) +#define KIRKWOOD_PLAYCTL_SIZE_16 (7<<0) +#define KIRKWOOD_PLAYCTL_SIZE_16_C (3<<0) +#define KIRKWOOD_PLAYCTL_SIZE_20 (2<<0) +#define KIRKWOOD_PLAYCTL_SIZE_24 (1<<0) +#define KIRKWOOD_PLAYCTL_SIZE_32 (0<<0) + +#define KIRKWOOD_PLAY_BUF_ADDR 0x1104 +#define KIRKWOOD_PLAY_BUF_SIZE 0x1108 +#define KIRKWOOD_PLAY_BYTE_COUNT 0x110C + +#define KIRKWOOD_DCO_CTL 0x1204 +#define KIRKWOOD_DCO_CTL_OFFSET_MASK (0xFFF<<2) +#define KIRKWOOD_DCO_CTL_OFFSET_0 (0x800<<2) +#define KIRKWOOD_DCO_CTL_FREQ_MASK (3<<0) +#define KIRKWOOD_DCO_CTL_FREQ_11 (0<<0) +#define KIRKWOOD_DCO_CTL_FREQ_12 (1<<0) +#define KIRKWOOD_DCO_CTL_FREQ_24 (2<<0) + +#define KIRKWOOD_DCO_SPCR_STATUS 0x120c +#define KIRKWOOD_DCO_SPCR_STATUS_DCO_LOCK (1<<16) + +#define KIRKWOOD_ERR_CAUSE 0x1300 +#define KIRKWOOD_ERR_MASK 0x1304 + +#define KIRKWOOD_INT_CAUSE 0x1308 +#define KIRKWOOD_INT_MASK 0x130C +#define KIRKWOOD_INT_CAUSE_PLAY_BYTES (1<<14) +#define KIRKWOOD_INT_CAUSE_REC_BYTES (1<<13) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_END (1<<7) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_3Q (1<<6) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_HALF (1<<5) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_1Q (1<<4) +#define KIRKWOOD_INT_CAUSE_DMA_REC_END (1<<3) +#define KIRKWOOD_INT_CAUSE_DMA_REC_3Q (1<<2) +#define KIRKWOOD_INT_CAUSE_DMA_REC_HALF (1<<1) +#define KIRKWOOD_INT_CAUSE_DMA_REC_1Q (1<<0) + +#define KIRKWOOD_REC_BYTE_INT_COUNT 0x1310 +#define KIRKWOOD_PLAY_BYTE_INT_COUNT 0x1314 +#define KIRKWOOD_BYTE_INT_COUNT_MASK 0xffffff + +#define KIRKWOOD_I2S_PLAYCTL 0x2508 +#define KIRKWOOD_I2S_RECCTL 0x2408 +#define KIRKWOOD_I2S_CTL_JUST_MASK (0xf<<26) +#define KIRKWOOD_I2S_CTL_LJ (0<<26) +#define KIRKWOOD_I2S_CTL_I2S (5<<26) +#define KIRKWOOD_I2S_CTL_RJ (8<<26) +#define KIRKWOOD_I2S_CTL_SIZE_MASK (3<<30) +#define KIRKWOOD_I2S_CTL_SIZE_16 (3<<30) +#define KIRKWOOD_I2S_CTL_SIZE_20 (2<<30) +#define KIRKWOOD_I2S_CTL_SIZE_24 (1<<30) +#define KIRKWOOD_I2S_CTL_SIZE_32 (0<<30) + +#define KIRKWOOD_AUDIO_BUF_MAX (16*1024*1024) + +/* Theses values come from the marvell alsa driver */ +/* need to find where they come from */ +#define KIRKWOOD_SND_MIN_PERIODS 8 +#define KIRKWOOD_SND_MAX_PERIODS 16 +#define KIRKWOOD_SND_MIN_PERIOD_BYTES 0x4000 +#define KIRKWOOD_SND_MAX_PERIOD_BYTES 0x4000 + +struct kirkwood_dma_data { + struct resource *mem; + void __iomem *io; + int irq; + int burst; + struct mbus_dram_target_info *dram; +}; + +#endif diff --git a/sound/soc/nuc900/Kconfig b/sound/soc/nuc900/Kconfig new file mode 100644 index 000000000000..a0ed1c618f60 --- /dev/null +++ b/sound/soc/nuc900/Kconfig @@ -0,0 +1,27 @@ +## +## NUC900 series AC97 API +## +config SND_SOC_NUC900 + tristate "SoC Audio for NUC900 series" + depends on ARCH_W90X900 + help + This option enables support for AC97 mode on the NUC900 SoC. + +config SND_SOC_NUC900_AC97 + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + + +## +## Boards +## +config SND_SOC_NUC900EVB + tristate "NUC900 AC97 support for demo board" + depends on SND_SOC_NUC900 + select SND_SOC_NUC900_AC97 + select SND_SOC_AC97_CODEC + help + Select this option to enable audio (AC97) on the + NUC900 demoboard. diff --git a/sound/soc/nuc900/Makefile b/sound/soc/nuc900/Makefile new file mode 100644 index 000000000000..7e46c7150316 --- /dev/null +++ b/sound/soc/nuc900/Makefile @@ -0,0 +1,11 @@ +# NUC900 series audio +snd-soc-nuc900-pcm-objs := nuc900-pcm.o +snd-soc-nuc900-ac97-objs := nuc900-ac97.o + +obj-$(CONFIG_SND_SOC_NUC900) += snd-soc-nuc900-pcm.o +obj-$(CONFIG_SND_SOC_NUC900_AC97) += snd-soc-nuc900-ac97.o + +# Boards +snd-soc-nuc900-audio-objs := nuc900-audio.o + +obj-$(CONFIG_SND_SOC_NUC900EVB) += snd-soc-nuc900-audio.o diff --git a/sound/soc/nuc900/nuc900-ac97.c b/sound/soc/nuc900/nuc900-ac97.c new file mode 100644 index 000000000000..caa7c901bc2e --- /dev/null +++ b/sound/soc/nuc900/nuc900-ac97.c @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2009-2010 Nuvoton technology corporation. + * + * Wan ZongShun <mcuos.com@gmail.com> + * + * 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;version 2 of the License. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/suspend.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <linux/device.h> +#include <linux/clk.h> + +#include <mach/mfp.h> + +#include "nuc900-audio.h" + +static DEFINE_MUTEX(ac97_mutex); +struct nuc900_audio *nuc900_ac97_data; + +static int nuc900_checkready(void) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + + if (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS0) & CODEC_READY)) + return -EPERM; + + return 0; +} + +/* AC97 controller reads codec register */ +static unsigned short nuc900_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long timeout = 0x10000, val; + + mutex_lock(&ac97_mutex); + + val = nuc900_checkready(); + if (!!val) { + dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); + goto out; + } + + /* set the R_WB bit and write register index */ + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, R_WB | reg); + + /* set the valid frame bit and valid slots */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + val |= (VALID_FRAME | SLOT1_VALID); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val); + + udelay(100); + + /* polling the AC_R_FINISH */ + while (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_R_FINISH) + && timeout--) + mdelay(1); + + if (!timeout) { + dev_err(nuc900_audio->dev, "AC97 read register time out !\n"); + val = -EPERM; + goto out; + } + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0) ; + val &= ~SLOT1_VALID; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val); + + if (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS1) >> 2 != reg) { + dev_err(nuc900_audio->dev, + "R_INDEX of REG_ACTL_ACIS1 not match!\n"); + } + + udelay(100); + val = (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS2) & 0xFFFF); + +out: + mutex_unlock(&ac97_mutex); + return val; +} + +/* AC97 controller writes to codec register */ +static void nuc900_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long tmp, timeout = 0x10000; + + mutex_lock(&ac97_mutex); + + tmp = nuc900_checkready(); + if (!!tmp) + dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); + + /* clear the R_WB bit and write register index */ + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, reg); + + /* write register value */ + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS2, val); + + /* set the valid frame bit and valid slots */ + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp |= SLOT1_VALID | SLOT2_VALID | VALID_FRAME; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + udelay(100); + + /* polling the AC_W_FINISH */ + while ((AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_W_FINISH) + && timeout--) + mdelay(1); + + if (!timeout) + dev_err(nuc900_audio->dev, "AC97 write register time out !\n"); + + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp &= ~(SLOT1_VALID | SLOT2_VALID); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + mutex_unlock(&ac97_mutex); + +} + +static void nuc900_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long val; + + mutex_lock(&ac97_mutex); + + /* warm reset AC 97 */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val |= AC_W_RES; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); + + udelay(100); + + val = nuc900_checkready(); + if (!!val) + dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); + + mutex_unlock(&ac97_mutex); +} + +static void nuc900_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long val; + + mutex_lock(&ac97_mutex); + + /* reset Audio Controller */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val |= ACTL_RESET_BIT; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val &= (~ACTL_RESET_BIT); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + /* reset AC-link interface */ + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val |= AC_RESET; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val &= ~AC_RESET; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + /* cold reset AC 97 */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val |= AC_C_RES; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val &= (~AC_C_RES); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); + + udelay(100); + + mutex_unlock(&ac97_mutex); + +} + +/* AC97 controller operations */ +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = nuc900_ac97_read, + .write = nuc900_ac97_write, + .reset = nuc900_ac97_cold_reset, + .warm_reset = nuc900_ac97_warm_reset, +} +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int nuc900_ac97_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + int ret; + unsigned long val, tmp; + + ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp |= (SLOT3_VALID | SLOT4_VALID | VALID_FRAME); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); + tmp |= (P_DMA_END_IRQ | P_DMA_MIDDLE_IRQ); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, tmp); + val |= AC_PLAY; + } else { + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); + tmp |= (R_DMA_END_IRQ | R_DMA_MIDDLE_IRQ); + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, tmp); + val |= AC_RECORD; + } + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp &= ~(SLOT3_VALID | SLOT4_VALID); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, RESET_PRSR); + val &= ~AC_PLAY; + } else { + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, RESET_PRSR); + val &= ~AC_RECORD; + } + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int nuc900_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long val; + + mutex_lock(&ac97_mutex); + + /* enable unit clock */ + clk_enable(nuc900_audio->clk); + + /* enable audio controller and AC-link interface */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + val |= (IIS_AC_PIN_SEL | ACLINK_EN); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); + + mutex_unlock(&ac97_mutex); + + return 0; +} + +static void nuc900_ac97_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + + clk_disable(nuc900_audio->clk); +} + +static struct snd_soc_dai_ops nuc900_ac97_dai_ops = { + .trigger = nuc900_ac97_trigger, +}; + +struct snd_soc_dai nuc900_ac97_dai = { + .name = "nuc900-ac97", + .probe = nuc900_ac97_probe, + .remove = nuc900_ac97_remove, + .ac97_control = 1, + .playback = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + }, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &nuc900_ac97_dai_ops, +} +EXPORT_SYMBOL_GPL(nuc900_ac97_dai); + +static int __devinit nuc900_ac97_drvprobe(struct platform_device *pdev) +{ + struct nuc900_audio *nuc900_audio; + int ret; + + if (nuc900_ac97_data) + return -EBUSY; + + nuc900_audio = kzalloc(sizeof(struct nuc900_audio), GFP_KERNEL); + if (!nuc900_audio) + return -ENOMEM; + + spin_lock_init(&nuc900_audio->lock); + + nuc900_audio->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!nuc900_audio->res) { + ret = -ENODEV; + goto out0; + } + + if (!request_mem_region(nuc900_audio->res->start, + resource_size(nuc900_audio->res), pdev->name)) { + ret = -EBUSY; + goto out0; + } + + nuc900_audio->mmio = ioremap(nuc900_audio->res->start, + resource_size(nuc900_audio->res)); + if (!nuc900_audio->mmio) { + ret = -ENOMEM; + goto out1; + } + + nuc900_audio->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(nuc900_audio->clk)) { + ret = PTR_ERR(nuc900_audio->clk); + goto out2; + } + + nuc900_audio->irq_num = platform_get_irq(pdev, 0); + if (!nuc900_audio->irq_num) { + ret = -EBUSY; + goto out2; + } + + nuc900_ac97_data = nuc900_audio; + + nuc900_audio->dev = nuc900_ac97_dai.dev = &pdev->dev; + + ret = snd_soc_register_dai(&nuc900_ac97_dai); + if (ret) + goto out3; + + mfp_set_groupg(nuc900_audio->dev); /* enbale ac97 multifunction pin*/ + + return 0; + +out3: + clk_put(nuc900_audio->clk); +out2: + iounmap(nuc900_audio->mmio); +out1: + release_mem_region(nuc900_audio->res->start, + resource_size(nuc900_audio->res)); +out0: + kfree(nuc900_audio); + return ret; +} + +static int __devexit nuc900_ac97_drvremove(struct platform_device *pdev) +{ + + snd_soc_unregister_dai(&nuc900_ac97_dai); + + clk_put(nuc900_ac97_data->clk); + iounmap(nuc900_ac97_data->mmio); + release_mem_region(nuc900_ac97_data->res->start, + resource_size(nuc900_ac97_data->res)); + + nuc900_ac97_data = NULL; + + return 0; +} + +static struct platform_driver nuc900_ac97_driver = { + .driver = { + .name = "nuc900-audio", + .owner = THIS_MODULE, + }, + .probe = nuc900_ac97_drvprobe, + .remove = __devexit_p(nuc900_ac97_drvremove), +}; + +static int __init nuc900_ac97_init(void) +{ + return platform_driver_register(&nuc900_ac97_driver); +} + +static void __exit nuc900_ac97_exit(void) +{ + platform_driver_unregister(&nuc900_ac97_driver); +} + +module_init(nuc900_ac97_init); +module_exit(nuc900_ac97_exit); + +MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); +MODULE_DESCRIPTION("NUC900 AC97 SoC driver!"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nuc900-ac97"); diff --git a/sound/soc/nuc900/nuc900-audio.c b/sound/soc/nuc900/nuc900-audio.c new file mode 100644 index 000000000000..72e6f518f7b2 --- /dev/null +++ b/sound/soc/nuc900/nuc900-audio.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010 Nuvoton technology corporation. + * + * Wan ZongShun <mcuos.com@gmail.com> + * + * 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;version 2 of the License. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "../codecs/ac97.h" +#include "nuc900-audio.h" + +static struct snd_soc_dai_link nuc900evb_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &nuc900_ac97_dai, + .codec_dai = &ac97_dai, +}; + +static struct snd_soc_card nuc900evb_audio_machine = { + .name = "NUC900EVB_AC97", + .dai_link = &nuc900evb_ac97_dai, + .num_links = 1, + .platform = &nuc900_soc_platform, +}; + +static struct snd_soc_device nuc900evb_ac97_devdata = { + .card = &nuc900evb_audio_machine, + .codec_dev = &soc_codec_dev_ac97, +}; + +static struct platform_device *nuc900evb_asoc_dev; + +static int __init nuc900evb_audio_init(void) +{ + int ret; + + ret = -ENOMEM; + nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1); + if (!nuc900evb_asoc_dev) + goto out; + + /* nuc900 board audio device */ + platform_set_drvdata(nuc900evb_asoc_dev, &nuc900evb_ac97_devdata); + + nuc900evb_ac97_devdata.dev = &nuc900evb_asoc_dev->dev; + ret = platform_device_add(nuc900evb_asoc_dev); + + if (ret) { + platform_device_put(nuc900evb_asoc_dev); + nuc900evb_asoc_dev = NULL; + } + +out: + return ret; +} + +static void __exit nuc900evb_audio_exit(void) +{ + platform_device_unregister(nuc900evb_asoc_dev); +} + +module_init(nuc900evb_audio_init); +module_exit(nuc900evb_audio_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("NUC900 Series ASoC audio support"); +MODULE_AUTHOR("Wan ZongShun"); diff --git a/sound/soc/nuc900/nuc900-audio.h b/sound/soc/nuc900/nuc900-audio.h new file mode 100644 index 000000000000..3038f519729f --- /dev/null +++ b/sound/soc/nuc900/nuc900-audio.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2010 Nuvoton technology corporation. + * + * Wan ZongShun <mcuos.com@gmail.com> + * + * 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;version 2 of the License. + * + */ + +#ifndef _NUC900_AUDIO_H +#define _NUC900_AUDIO_H + +#include <linux/io.h> + +/* Audio Control Registers */ +#define ACTL_CON 0x00 +#define ACTL_RESET 0x04 +#define ACTL_RDSTB 0x08 +#define ACTL_RDST_LENGTH 0x0C +#define ACTL_RDSTC 0x10 +#define ACTL_RSR 0x14 +#define ACTL_PDSTB 0x18 +#define ACTL_PDST_LENGTH 0x1C +#define ACTL_PDSTC 0x20 +#define ACTL_PSR 0x24 +#define ACTL_IISCON 0x28 +#define ACTL_ACCON 0x2C +#define ACTL_ACOS0 0x30 +#define ACTL_ACOS1 0x34 +#define ACTL_ACOS2 0x38 +#define ACTL_ACIS0 0x3C +#define ACTL_ACIS1 0x40 +#define ACTL_ACIS2 0x44 +#define ACTL_COUNTER 0x48 + +/* bit definition of REG_ACTL_CON register */ +#define R_DMA_IRQ 0x1000 +#define T_DMA_IRQ 0x0800 +#define IIS_AC_PIN_SEL 0x0100 +#define FIFO_TH 0x0080 +#define ADC_EN 0x0010 +#define M80_EN 0x0008 +#define ACLINK_EN 0x0004 +#define IIS_EN 0x0002 + +/* bit definition of REG_ACTL_RESET register */ +#define W5691_PLAY 0x20000 +#define ACTL_RESET_BIT 0x10000 +#define RECORD_RIGHT_CHNNEL 0x08000 +#define RECORD_LEFT_CHNNEL 0x04000 +#define PLAY_RIGHT_CHNNEL 0x02000 +#define PLAY_LEFT_CHNNEL 0x01000 +#define DAC_PLAY 0x00800 +#define ADC_RECORD 0x00400 +#define M80_PLAY 0x00200 +#define AC_RECORD 0x00100 +#define AC_PLAY 0x00080 +#define IIS_RECORD 0x00040 +#define IIS_PLAY 0x00020 +#define DAC_RESET 0x00010 +#define ADC_RESET 0x00008 +#define M80_RESET 0x00004 +#define AC_RESET 0x00002 +#define IIS_RESET 0x00001 + +/* bit definition of REG_ACTL_ACCON register */ +#define AC_BCLK_PU_EN 0x20 +#define AC_R_FINISH 0x10 +#define AC_W_FINISH 0x08 +#define AC_W_RES 0x04 +#define AC_C_RES 0x02 + +/* bit definition of ACTL_RSR register */ +#define R_FIFO_EMPTY 0x04 +#define R_DMA_END_IRQ 0x02 +#define R_DMA_MIDDLE_IRQ 0x01 + +/* bit definition of ACTL_PSR register */ +#define P_FIFO_EMPTY 0x04 +#define P_DMA_END_IRQ 0x02 +#define P_DMA_MIDDLE_IRQ 0x01 + +/* bit definition of ACTL_ACOS0 register */ +#define SLOT1_VALID 0x01 +#define SLOT2_VALID 0x02 +#define SLOT3_VALID 0x04 +#define SLOT4_VALID 0x08 +#define VALID_FRAME 0x10 + +/* bit definition of ACTL_ACOS1 register */ +#define R_WB 0x80 + +#define CODEC_READY 0x10 +#define RESET_PRSR 0x00 +#define AUDIO_WRITE(addr, val) __raw_writel(val, addr) +#define AUDIO_READ(addr) __raw_readl(addr) + +struct nuc900_audio { + void __iomem *mmio; + spinlock_t lock; + dma_addr_t dma_addr[2]; + unsigned long buffersize[2]; + unsigned long irq_num; + struct snd_pcm_substream *substream; + struct resource *res; + struct clk *clk; + struct device *dev; + +}; + +extern struct nuc900_audio *nuc900_ac97_data; +extern struct snd_soc_dai nuc900_ac97_dai; +extern struct snd_soc_platform nuc900_soc_platform; + +#endif /*end _NUC900_AUDIO_H */ diff --git a/sound/soc/nuc900/nuc900-pcm.c b/sound/soc/nuc900/nuc900-pcm.c new file mode 100644 index 000000000000..e81e803b3a63 --- /dev/null +++ b/sound/soc/nuc900/nuc900-pcm.c @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2010 Nuvoton technology corporation. + * + * Wan ZongShun <mcuos.com@gmail.com> + * + * 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;version 2 of the License. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/hardware.h> + +#include "nuc900-audio.h" + +static const struct snd_pcm_hardware nuc900_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 4*1024, + .period_bytes_min = 1*1024, + .period_bytes_max = 4*1024, + .periods_min = 1, + .periods_max = 1024, +}; + +static int nuc900_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&nuc900_audio->lock, flags); + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + nuc900_audio->substream = substream; + nuc900_audio->dma_addr[substream->stream] = runtime->dma_addr; + nuc900_audio->buffersize[substream->stream] = + params_buffer_bytes(params); + + spin_unlock_irqrestore(&nuc900_audio->lock, flags); + + return ret; +} + +static void nuc900_update_dma_register(struct snd_pcm_substream *substream, + dma_addr_t dma_addr, size_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + void __iomem *mmio_addr, *mmio_len; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mmio_addr = nuc900_audio->mmio + ACTL_PDSTB; + mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH; + } else { + mmio_addr = nuc900_audio->mmio + ACTL_RDSTB; + mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH; + } + + AUDIO_WRITE(mmio_addr, dma_addr); + AUDIO_WRITE(mmio_len, count); +} + +static void nuc900_dma_start(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long val; + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + val |= (T_DMA_IRQ | R_DMA_IRQ); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); +} + +static void nuc900_dma_stop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long val; + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + val &= ~(T_DMA_IRQ | R_DMA_IRQ); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); +} + +static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct nuc900_audio *nuc900_audio = substream->runtime->private_data; + unsigned long val; + + spin_lock(&nuc900_audio->lock); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + + if (val & R_DMA_IRQ) { + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); + + if (val & R_DMA_MIDDLE_IRQ) { + val |= R_DMA_MIDDLE_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); + } + + if (val & R_DMA_END_IRQ) { + val |= R_DMA_END_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); + } + } else if (val & T_DMA_IRQ) { + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); + + if (val & P_DMA_MIDDLE_IRQ) { + val |= P_DMA_MIDDLE_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); + } + + if (val & P_DMA_END_IRQ) { + val |= P_DMA_END_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); + } + } else { + dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n"); + spin_unlock(&nuc900_audio->lock); + return IRQ_HANDLED; + } + + spin_unlock(&nuc900_audio->lock); + + snd_pcm_period_elapsed(substream); + + return IRQ_HANDLED; +} + +static int nuc900_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int nuc900_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long flags, val; + + spin_lock_irqsave(&nuc900_audio->lock, flags); + + nuc900_update_dma_register(substream, + nuc900_audio->dma_addr[substream->stream], + nuc900_audio->buffersize[substream->stream]); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + + switch (runtime->channels) { + case 1: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); + val |= PLAY_RIGHT_CHNNEL; + } else { + val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); + val |= RECORD_RIGHT_CHNNEL; + } + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + break; + case 2: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); + else + val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + break; + default: + return -EINVAL; + } + spin_unlock_irqrestore(&nuc900_audio->lock, flags); + return 0; +} + +static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + nuc900_dma_start(substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + nuc900_dma_stop(substream); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +int nuc900_dma_getposition(struct snd_pcm_substream *substream, + dma_addr_t *src, dma_addr_t *dst) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + + if (src != NULL) + *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC); + + if (dst != NULL) + *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC); + + return 0; +} + +static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + dma_addr_t src, dst; + unsigned long res; + + nuc900_dma_getposition(substream, &src, &dst); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + res = dst - runtime->dma_addr; + else + res = src - runtime->dma_addr; + + return bytes_to_frames(substream->runtime, res); +} + +static int nuc900_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio; + + snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware); + + nuc900_audio = nuc900_ac97_data; + + if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt, + IRQF_DISABLED, "nuc900-dma", substream)) + return -EBUSY; + + runtime->private_data = nuc900_audio; + + return 0; +} + +static int nuc900_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + + free_irq(nuc900_audio->irq_num, substream); + + return 0; +} + +static int nuc900_dma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops nuc900_dma_ops = { + .open = nuc900_dma_open, + .close = nuc900_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = nuc900_dma_hw_params, + .hw_free = nuc900_dma_hw_free, + .prepare = nuc900_dma_prepare, + .trigger = nuc900_dma_trigger, + .pointer = nuc900_dma_pointer, + .mmap = nuc900_dma_mmap, +}; + +static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32); +static int nuc900_dma_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + if (!card->dev->dma_mask) + card->dev->dma_mask = &nuc900_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, 4 * 1024, (4 * 1024) - 1); + + return 0; +} + +struct snd_soc_platform nuc900_soc_platform = { + .name = "nuc900-dma", + .pcm_ops = &nuc900_dma_ops, + .pcm_new = nuc900_dma_new, + .pcm_free = nuc900_dma_free_dma_buffers, +} +EXPORT_SYMBOL_GPL(nuc900_soc_platform); + +static int __init nuc900_soc_platform_init(void) +{ + return snd_soc_register_platform(&nuc900_soc_platform); +} + +static void __exit nuc900_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&nuc900_soc_platform); +} + +module_init(nuc900_soc_platform_init); +module_exit(nuc900_soc_platform_exit); + +MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>"); +MODULE_DESCRIPTION("nuc900 Audio DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 6f44cb4d30b8..86f213905e2c 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -59,6 +59,7 @@ struct omap_mcbsp_data { int configured; unsigned int in_freq; int clk_div; + int wlen; }; #define to_mcbsp(priv) container_of((priv), struct omap_mcbsp_data, bus_id) @@ -154,20 +155,51 @@ static void omap_mcbsp_set_threshold(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); + struct omap_pcm_dma_data *dma_data; int dma_op_mode = omap_mcbsp_get_dma_op_mode(mcbsp_data->bus_id); - int samples; + int words; + + dma_data = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */ if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) - samples = snd_pcm_lib_period_bytes(substream) >> 1; + /* + * Configure McBSP threshold based on either: + * packet_size, when the sDMA is in packet mode, or + * based on the period size. + */ + if (dma_data->packet_size) + words = dma_data->packet_size; + else + words = snd_pcm_lib_period_bytes(substream) / + (mcbsp_data->wlen / 8); else - samples = 1; + words = 1; /* Configure McBSP internal buffer usage */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - omap_mcbsp_set_tx_threshold(mcbsp_data->bus_id, samples - 1); + omap_mcbsp_set_tx_threshold(mcbsp_data->bus_id, words); else - omap_mcbsp_set_rx_threshold(mcbsp_data->bus_id, samples - 1); + omap_mcbsp_set_rx_threshold(mcbsp_data->bus_id, words); +} + +static int omap_mcbsp_hwrule_min_buffersize(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *buffer_size = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct omap_mcbsp_data *mcbsp_data = rule->private; + struct snd_interval frames; + int size; + + snd_interval_any(&frames); + size = omap_mcbsp_get_fifo_size(mcbsp_data->bus_id); + + frames.min = size / channels->min; + frames.integer = 1; + return snd_interval_refine(buffer_size, &frames); } static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream, @@ -182,33 +214,35 @@ static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream, if (!cpu_dai->active) err = omap_mcbsp_request(bus_id); + /* + * OMAP3 McBSP FIFO is word structured. + * McBSP2 has 1024 + 256 = 1280 word long buffer, + * McBSP1,3,4,5 has 128 word long buffer + * This means that the size of the FIFO depends on the sample format. + * For example on McBSP3: + * 16bit samples: size is 128 * 2 = 256 bytes + * 32bit samples: size is 128 * 4 = 512 bytes + * It is simpler to place constraint for buffer and period based on + * channels. + * McBSP3 as example again (16 or 32 bit samples): + * 1 channel (mono): size is 128 frames (128 words) + * 2 channels (stereo): size is 128 / 2 = 64 frames (2 * 64 words) + * 4 channels: size is 128 / 4 = 32 frames (4 * 32 words) + */ if (cpu_is_omap343x()) { - int dma_op_mode = omap_mcbsp_get_dma_op_mode(bus_id); - int max_period; - /* - * McBSP2 in OMAP3 has 1024 * 32-bit internal audio buffer. - * Set constraint for minimum buffer size to the same than FIFO - * size in order to avoid underruns in playback startup because - * HW is keeping the DMA request active until FIFO is filled. - */ - if (bus_id == 1) - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_BUFFER_BYTES, - 4096, UINT_MAX); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - max_period = omap_mcbsp_get_max_tx_threshold(bus_id); - else - max_period = omap_mcbsp_get_max_rx_threshold(bus_id); - - max_period++; - max_period <<= 1; - - if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIOD_BYTES, - 32, max_period); + * Rule for the buffer size. We should not allow + * smaller buffer than the FIFO size to avoid underruns + */ + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + omap_mcbsp_hwrule_min_buffersize, + mcbsp_data, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1); + + /* Make sure, that the period size is always even */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2); } return err; @@ -289,11 +323,14 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs; - int dma, bus_id = mcbsp_data->bus_id, id = cpu_dai->id; + struct omap_pcm_dma_data *dma_data; + int dma, bus_id = mcbsp_data->bus_id; int wlen, channels, wpf, sync_mode = OMAP_DMA_SYNC_ELEMENT; + int pkt_size = 0; unsigned long port; unsigned int format, div, framesize, master; + dma_data = &omap_mcbsp_dai_dma_params[cpu_dai->id][substream->stream]; if (cpu_class_is_omap1()) { dma = omap1_dma_reqs[bus_id][substream->stream]; port = omap1_mcbsp_port[bus_id][substream->stream]; @@ -306,35 +343,74 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, } else if (cpu_is_omap343x()) { dma = omap24xx_dma_reqs[bus_id][substream->stream]; port = omap34xx_mcbsp_port[bus_id][substream->stream]; - omap_mcbsp_dai_dma_params[id][substream->stream].set_threshold = - omap_mcbsp_set_threshold; - /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */ - if (omap_mcbsp_get_dma_op_mode(bus_id) == - MCBSP_DMA_MODE_THRESHOLD) - sync_mode = OMAP_DMA_SYNC_FRAME; } else { return -ENODEV; } - omap_mcbsp_dai_dma_params[id][substream->stream].name = - substream->stream ? "Audio Capture" : "Audio Playback"; - omap_mcbsp_dai_dma_params[id][substream->stream].dma_req = dma; - omap_mcbsp_dai_dma_params[id][substream->stream].port_addr = port; - omap_mcbsp_dai_dma_params[id][substream->stream].sync_mode = sync_mode; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: - omap_mcbsp_dai_dma_params[id][substream->stream].data_type = - OMAP_DMA_DATA_TYPE_S16; + dma_data->data_type = OMAP_DMA_DATA_TYPE_S16; + wlen = 16; break; case SNDRV_PCM_FORMAT_S32_LE: - omap_mcbsp_dai_dma_params[id][substream->stream].data_type = - OMAP_DMA_DATA_TYPE_S32; + dma_data->data_type = OMAP_DMA_DATA_TYPE_S32; + wlen = 32; break; default: return -EINVAL; } + if (cpu_is_omap343x()) { + dma_data->set_threshold = omap_mcbsp_set_threshold; + /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */ + if (omap_mcbsp_get_dma_op_mode(bus_id) == + MCBSP_DMA_MODE_THRESHOLD) { + int period_words, max_thrsh; + + period_words = params_period_bytes(params) / (wlen / 8); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + max_thrsh = omap_mcbsp_get_max_tx_threshold( + mcbsp_data->bus_id); + else + max_thrsh = omap_mcbsp_get_max_rx_threshold( + mcbsp_data->bus_id); + /* + * If the period contains less or equal number of words, + * we are using the original threshold mode setup: + * McBSP threshold = sDMA frame size = period_size + * Otherwise we switch to sDMA packet mode: + * McBSP threshold = sDMA packet size + * sDMA frame size = period size + */ + if (period_words > max_thrsh) { + int divider = 0; + + /* + * Look for the biggest threshold value, which + * divides the period size evenly. + */ + divider = period_words / max_thrsh; + if (period_words % max_thrsh) + divider++; + while (period_words % divider && + divider < period_words) + divider++; + if (divider == period_words) + return -EINVAL; + + pkt_size = period_words / divider; + sync_mode = OMAP_DMA_SYNC_PACKET; + } else { + sync_mode = OMAP_DMA_SYNC_FRAME; + } + } + } - snd_soc_dai_set_dma_data(cpu_dai, substream, - &omap_mcbsp_dai_dma_params[id][substream->stream]); + dma_data->name = substream->stream ? "Audio Capture" : "Audio Playback"; + dma_data->dma_req = dma; + dma_data->port_addr = port; + dma_data->sync_mode = sync_mode; + dma_data->packet_size = pkt_size; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); if (mcbsp_data->configured) { /* McBSP already configured by another stream */ @@ -360,7 +436,6 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: /* Set word lengths */ - wlen = 16; regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_16); regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_16); regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_16); @@ -368,7 +443,6 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, break; case SNDRV_PCM_FORMAT_S32_LE: /* Set word lengths */ - wlen = 32; regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_32); regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_32); regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_32); @@ -409,6 +483,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, } omap_mcbsp_config(bus_id, &mcbsp_data->regs); + mcbsp_data->wlen = wlen; mcbsp_data->configured = 1; return 0; diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c index 87ce842fa2e8..9eecac135bbb 100644 --- a/sound/soc/omap/omap3pandora.c +++ b/sound/soc/omap/omap3pandora.c @@ -43,12 +43,14 @@ static struct regulator *omap3pandora_dac_reg; -static int omap3pandora_cmn_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, unsigned int fmt) +static int omap3pandora_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; int ret; /* Set codec DAI configuration */ @@ -91,24 +93,6 @@ static int omap3pandora_cmn_hw_params(struct snd_pcm_substream *substream, return 0; } -static int omap3pandora_out_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - return omap3pandora_cmn_hw_params(substream, params, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFS); -} - -static int omap3pandora_in_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - return omap3pandora_cmn_hw_params(substream, params, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); -} - static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { @@ -231,12 +215,8 @@ static int omap3pandora_in_init(struct snd_soc_codec *codec) return snd_soc_dapm_sync(codec); } -static struct snd_soc_ops omap3pandora_out_ops = { - .hw_params = omap3pandora_out_hw_params, -}; - -static struct snd_soc_ops omap3pandora_in_ops = { - .hw_params = omap3pandora_in_hw_params, +static struct snd_soc_ops omap3pandora_ops = { + .hw_params = omap3pandora_hw_params, }; /* Digital audio interface glue - connects codec <--> CPU */ @@ -246,14 +226,14 @@ static struct snd_soc_dai_link omap3pandora_dai[] = { .stream_name = "HiFi Out", .cpu_dai = &omap_mcbsp_dai[0], .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], - .ops = &omap3pandora_out_ops, + .ops = &omap3pandora_ops, .init = omap3pandora_out_init, }, { .name = "TWL4030", .stream_name = "Line/Mic In", .cpu_dai = &omap_mcbsp_dai[1], .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], - .ops = &omap3pandora_in_ops, + .ops = &omap3pandora_ops, .init = omap3pandora_in_init, } }; diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c index 47d831ef2dbb..88052d29617f 100644 --- a/sound/soc/omap/rx51.c +++ b/sound/soc/omap/rx51.c @@ -27,6 +27,7 @@ #include <linux/gpio.h> #include <linux/platform_device.h> #include <sound/core.h> +#include <sound/jack.h> #include <sound/pcm.h> #include <sound/soc.h> #include <sound/soc-dapm.h> @@ -37,14 +38,22 @@ #include "omap-pcm.h" #include "../codecs/tlv320aic3x.h" +#define RX51_TVOUT_SEL_GPIO 40 +#define RX51_JACK_DETECT_GPIO 177 /* * REVISIT: TWL4030 GPIO base in RX-51. Now statically defined to 192. This * gpio is reserved in arch/arm/mach-omap2/board-rx51-peripherals.c */ #define RX51_SPEAKER_AMP_TWL_GPIO (192 + 7) +enum { + RX51_JACK_DISABLED, + RX51_JACK_TVOUT, /* tv-out */ +}; + static int rx51_spk_func; static int rx51_dmic_func; +static int rx51_jack_func; static void rx51_ext_control(struct snd_soc_codec *codec) { @@ -57,6 +66,9 @@ static void rx51_ext_control(struct snd_soc_codec *codec) else snd_soc_dapm_disable_pin(codec, "DMic"); + gpio_set_value(RX51_TVOUT_SEL_GPIO, + rx51_jack_func == RX51_JACK_TVOUT); + snd_soc_dapm_sync(codec); } @@ -162,6 +174,40 @@ static int rx51_set_input(struct snd_kcontrol *kcontrol, return 1; } +static int rx51_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_jack_func; + + return 0; +} + +static int rx51_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_jack_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_jack_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +static struct snd_soc_jack rx51_av_jack; + +static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = { + { + .gpio = RX51_JACK_DETECT_GPIO, + .name = "avdet-gpio", + .report = SND_JACK_VIDEOOUT, + .invert = 1, + .debounce_time = 200, + }, +}; + static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = { SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event), SND_SOC_DAPM_MIC("DMic", NULL), @@ -177,10 +223,12 @@ static const struct snd_soc_dapm_route audio_map[] = { static const char *spk_function[] = {"Off", "On"}; static const char *input_function[] = {"ADC", "Digital Mic"}; +static const char *jack_function[] = {"Off", "TV-OUT"}; static const struct soc_enum rx51_enum[] = { SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function), }; static const struct snd_kcontrol_new aic34_rx51_controls[] = { @@ -188,10 +236,13 @@ static const struct snd_kcontrol_new aic34_rx51_controls[] = { rx51_get_spk, rx51_set_spk), SOC_ENUM_EXT("Input Select", rx51_enum[1], rx51_get_input, rx51_set_input), + SOC_ENUM_EXT("Jack Function", rx51_enum[2], + rx51_get_jack, rx51_set_jack), }; static int rx51_aic34_init(struct snd_soc_codec *codec) { + struct snd_soc_card *card = codec->socdev->card; int err; /* Set up NC codec pins */ @@ -214,7 +265,16 @@ static int rx51_aic34_init(struct snd_soc_codec *codec) snd_soc_dapm_sync(codec); - return 0; + /* AV jack detection */ + err = snd_soc_jack_new(card, "AV Jack", + SND_JACK_VIDEOOUT, &rx51_av_jack); + if (err) + return err; + err = snd_soc_jack_add_gpios(&rx51_av_jack, + ARRAY_SIZE(rx51_av_jack_gpios), + rx51_av_jack_gpios); + + return err; } /* Digital audio interface glue - connects codec <--> CPU */ @@ -259,6 +319,11 @@ static int __init rx51_soc_init(void) if (!machine_is_nokia_rx51()) return -ENODEV; + err = gpio_request(RX51_TVOUT_SEL_GPIO, "tvout_sel"); + if (err) + goto err_gpio_tvout_sel; + gpio_direction_output(RX51_TVOUT_SEL_GPIO, 0); + rx51_snd_device = platform_device_alloc("soc-audio", -1); if (!rx51_snd_device) { err = -ENOMEM; @@ -277,13 +342,19 @@ static int __init rx51_soc_init(void) err2: platform_device_put(rx51_snd_device); err1: + gpio_free(RX51_TVOUT_SEL_GPIO); +err_gpio_tvout_sel: return err; } static void __exit rx51_soc_exit(void) { + snd_soc_jack_free_gpios(&rx51_av_jack, ARRAY_SIZE(rx51_av_jack_gpios), + rx51_av_jack_gpios); + platform_device_unregister(rx51_snd_device); + gpio_free(RX51_TVOUT_SEL_GPIO); } module_init(rx51_soc_init); diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 2a7cc222d098..213963ac3c28 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -1,6 +1,6 @@ config SND_S3C24XX_SOC tristate "SoC Audio for the Samsung S3CXXXX chips" - depends on ARCH_S3C2410 || ARCH_S3C64XX + depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 select S3C64XX_DMA if ARCH_S3C64XX help Say Y or M if you want to add support for codecs attached to @@ -120,8 +120,14 @@ config SND_S3C24XX_SOC_SIMTEC_HERMES config SND_SOC_SMDK_WM9713 tristate "SoC AC97 Audio support for SMDK with WM9713" - depends on SND_S3C24XX_SOC && MACH_SMDK6410 + depends on SND_S3C24XX_SOC && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110) select SND_SOC_WM9713 select SND_S3C_SOC_AC97 help Sat Y if you want to add support for SoC audio on the SMDK. + +config SND_S3C64XX_SOC_SMARTQ + tristate "SoC I2S Audio support for SmartQ board" + depends on SND_S3C24XX_SOC && MACH_SMARTQ + select SND_S3C64XX_SOC_I2S + select SND_SOC_WM8750 diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index 81d8dc503f87..50172c385d90 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -29,6 +29,7 @@ snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o snd-soc-smdk64xx-wm8580-objs := smdk64xx_wm8580.o snd-soc-smdk-wm9713-objs := smdk_wm9713.o +snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o @@ -41,3 +42,4 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o obj-$(CONFIG_SND_SOC_SMDK_WM9713) += snd-soc-smdk-wm9713.o +obj-$(CONFIG_SND_S3C64XX_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o diff --git a/sound/soc/s3c24xx/s3c-ac97.c b/sound/soc/s3c24xx/s3c-ac97.c index ecf4fd04ae96..31f6d45b6384 100644 --- a/sound/soc/s3c24xx/s3c-ac97.c +++ b/sound/soc/s3c24xx/s3c-ac97.c @@ -31,7 +31,6 @@ #define AC_CMD_DATA(x) (x & 0xffff) struct s3c_ac97_info { - unsigned state; struct clk *ac97_clk; void __iomem *regs; struct mutex lock; diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 13311c8cf965..64376b2aac73 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -32,7 +32,8 @@ #undef S3C_IIS_V2_SUPPORTED -#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) +#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) \ + || defined(CONFIG_CPU_S5PV210) #define S3C_IIS_V2_SUPPORTED #endif diff --git a/sound/soc/s3c24xx/smartq_wm8987.c b/sound/soc/s3c24xx/smartq_wm8987.c new file mode 100644 index 000000000000..b480348140b0 --- /dev/null +++ b/sound/soc/s3c24xx/smartq_wm8987.c @@ -0,0 +1,295 @@ +/* sound/soc/s3c24xx/smartq_wm8987.c + * + * Copyright 2010 Maurus Cuelenaere <mcuelenaere@gmail.com> + * + * Based on smdk6410_wm8987.c + * Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com + * Graeme Gregory - graeme.gregory@wolfsonmicro.com + * + * 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/platform_device.h> +#include <linux/gpio.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> + +#include <asm/mach-types.h> + +#include "s3c-dma.h" +#include "s3c64xx-i2s.h" + +#include "../codecs/wm8750.h" + +/* + * WM8987 is register compatible with WM8750, so using that as base driver. + */ + +static struct snd_soc_card snd_soc_smartq; + +static int smartq_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct s3c_i2sv2_rate_calc div; + unsigned int clk = 0; + int ret; + + s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params), + s3c_i2sv2_get_clock(cpu_dai)); + + switch (params_rate(params)) { + case 8000: + case 16000: + case 32000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + clk = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_RCLK, div.fs_div); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_PRESCALER, + div.clk_div - 1); + if (ret < 0) + return ret; + + return 0; +} + +/* + * SmartQ WM8987 HiFi DAI operations. + */ +static struct snd_soc_ops smartq_hifi_ops = { + .hw_params = smartq_hifi_hw_params, +}; + +static struct snd_soc_jack smartq_jack; + +static struct snd_soc_jack_pin smartq_jack_pins[] = { + /* Disable speaker when headphone is plugged in */ + { + .pin = "Internal Speaker", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio smartq_jack_gpios[] = { + { + .gpio = S3C64XX_GPL(12), + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 200, + }, +}; + +static const struct snd_kcontrol_new wm8987_smartq_controls[] = { + SOC_DAPM_PIN_SWITCH("Internal Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), +}; + +static int smartq_speaker_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, + int event) +{ + gpio_set_value(S3C64XX_GPK(12), SND_SOC_DAPM_EVENT_OFF(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Internal Speaker", smartq_speaker_event), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LOUT2"}, + {"Headphone Jack", NULL, "ROUT2"}, + + {"Internal Speaker", NULL, "LOUT2"}, + {"Internal Speaker", NULL, "ROUT2"}, + + {"Mic Bias", NULL, "Internal Mic"}, + {"LINPUT2", NULL, "Mic Bias"}, +}; + +static int smartq_wm8987_init(struct snd_soc_codec *codec) +{ + int err = 0; + + /* Add SmartQ specific widgets */ + snd_soc_dapm_new_controls(codec, wm8987_dapm_widgets, + ARRAY_SIZE(wm8987_dapm_widgets)); + + /* add SmartQ specific controls */ + err = snd_soc_add_controls(codec, wm8987_smartq_controls, + ARRAY_SIZE(wm8987_smartq_controls)); + + if (err < 0) + return err; + + /* setup SmartQ specific audio path */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* set endpoints to not connected */ + snd_soc_dapm_nc_pin(codec, "LINPUT1"); + snd_soc_dapm_nc_pin(codec, "RINPUT1"); + snd_soc_dapm_nc_pin(codec, "OUT3"); + snd_soc_dapm_nc_pin(codec, "ROUT1"); + + /* set endpoints to default off mode */ + snd_soc_dapm_enable_pin(codec, "Internal Speaker"); + snd_soc_dapm_enable_pin(codec, "Internal Mic"); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + + err = snd_soc_dapm_sync(codec); + if (err) + return err; + + /* Headphone jack detection */ + err = snd_soc_jack_new(&snd_soc_smartq, "Headphone Jack", + SND_JACK_HEADPHONE, &smartq_jack); + if (err) + return err; + + err = snd_soc_jack_add_pins(&smartq_jack, ARRAY_SIZE(smartq_jack_pins), + smartq_jack_pins); + if (err) + return err; + + err = snd_soc_jack_add_gpios(&smartq_jack, + ARRAY_SIZE(smartq_jack_gpios), + smartq_jack_gpios); + + return err; +} + +static struct snd_soc_dai_link smartq_dai[] = { + { + .name = "wm8987", + .stream_name = "SmartQ Hi-Fi", + .cpu_dai = &s3c64xx_i2s_dai[0], + .codec_dai = &wm8750_dai, + .init = smartq_wm8987_init, + .ops = &smartq_hifi_ops, + }, +}; + +static struct snd_soc_card snd_soc_smartq = { + .name = "SmartQ", + .platform = &s3c24xx_soc_platform, + .dai_link = smartq_dai, + .num_links = ARRAY_SIZE(smartq_dai), +}; + +static struct snd_soc_device smartq_snd_devdata = { + .card = &snd_soc_smartq, + .codec_dev = &soc_codec_dev_wm8750, +}; + +static struct platform_device *smartq_snd_device; + +static int __init smartq_init(void) +{ + int ret; + + if (!machine_is_smartq7() && !machine_is_smartq5()) { + pr_info("Only SmartQ is supported by this ASoC driver\n"); + return -ENODEV; + } + + smartq_snd_device = platform_device_alloc("soc-audio", -1); + if (!smartq_snd_device) + return -ENOMEM; + + platform_set_drvdata(smartq_snd_device, &smartq_snd_devdata); + smartq_snd_devdata.dev = &smartq_snd_device->dev; + + ret = platform_device_add(smartq_snd_device); + if (ret) { + platform_device_put(smartq_snd_device); + return ret; + } + + /* Initialise GPIOs used by amplifiers */ + ret = gpio_request(S3C64XX_GPK(12), "amplifiers shutdown"); + if (ret) { + dev_err(&smartq_snd_device->dev, "Failed to register GPK12\n"); + goto err_unregister_device; + } + + /* Disable amplifiers */ + ret = gpio_direction_output(S3C64XX_GPK(12), 1); + if (ret) { + dev_err(&smartq_snd_device->dev, "Failed to configure GPK12\n"); + goto err_free_gpio_amp_shut; + } + + return 0; + +err_free_gpio_amp_shut: + gpio_free(S3C64XX_GPK(12)); +err_unregister_device: + platform_device_unregister(smartq_snd_device); + + return ret; +} + +static void __exit smartq_exit(void) +{ + snd_soc_jack_free_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios), + smartq_jack_gpios); + + platform_device_unregister(smartq_snd_device); +} + +module_init(smartq_init); +module_exit(smartq_exit); + +/* Module information */ +MODULE_AUTHOR("Maurus Cuelenaere <mcuelenaere@gmail.com>"); +MODULE_DESCRIPTION("ALSA SoC SmartQ WM8987"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/smdk_wm9713.c b/sound/soc/s3c24xx/smdk_wm9713.c index 24fd39f38ccb..5527b9e88c98 100644 --- a/sound/soc/s3c24xx/smdk_wm9713.c +++ b/sound/soc/s3c24xx/smdk_wm9713.c @@ -25,6 +25,9 @@ static struct snd_soc_card smdk; * Default CFG switch settings to use this driver: * * SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off + * SMDKC100: Set CFG6 1-3 On, CFG7 1 On + * SMDKC110: Set CFGB10 1-2 Off, CFGB12 1-3 On + * SMDKV210: Set CFGB10 1-2 Off, CFGB12 1-3 On */ /* diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c index 5b9ac1759bd2..59e3fa7bcb05 100644 --- a/sound/soc/s6000/s6000-i2s.c +++ b/sound/soc/s6000/s6000-i2s.c @@ -451,16 +451,15 @@ static int __devinit s6000_i2s_probe(struct platform_device *pdev) goto err_release_none; } - region = request_mem_region(scbmem->start, - scbmem->end - scbmem->start + 1, - pdev->name); + region = request_mem_region(scbmem->start, resource_size(scbmem), + pdev->name); if (!region) { dev_err(&pdev->dev, "I2S SCB region already claimed\n"); ret = -EBUSY; goto err_release_none; } - mmio = ioremap(scbmem->start, scbmem->end - scbmem->start + 1); + mmio = ioremap(scbmem->start, resource_size(scbmem)); if (!mmio) { dev_err(&pdev->dev, "can't ioremap SCB region\n"); ret = -ENOMEM; @@ -474,9 +473,8 @@ static int __devinit s6000_i2s_probe(struct platform_device *pdev) goto err_release_map; } - region = request_mem_region(sifmem->start, - sifmem->end - sifmem->start + 1, - pdev->name); + region = request_mem_region(sifmem->start, resource_size(sifmem), + pdev->name); if (!region) { dev_err(&pdev->dev, "I2S SIF region already claimed\n"); ret = -EBUSY; @@ -490,8 +488,8 @@ static int __devinit s6000_i2s_probe(struct platform_device *pdev) goto err_release_sif; } - region = request_mem_region(dma1->start, dma1->end - dma1->start + 1, - pdev->name); + region = request_mem_region(dma1->start, resource_size(dma1), + pdev->name); if (!region) { dev_err(&pdev->dev, "I2S DMA region already claimed\n"); ret = -EBUSY; @@ -500,9 +498,8 @@ static int __devinit s6000_i2s_probe(struct platform_device *pdev) dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1); if (dma2) { - region = request_mem_region(dma2->start, - dma2->end - dma2->start + 1, - pdev->name); + region = request_mem_region(dma2->start, resource_size(dma2), + pdev->name); if (!region) { dev_err(&pdev->dev, "I2S DMA region already claimed\n"); @@ -561,15 +558,15 @@ err_release_dev: kfree(dev); err_release_dma2: if (dma2) - release_mem_region(dma2->start, dma2->end - dma2->start + 1); + release_mem_region(dma2->start, resource_size(dma2)); err_release_dma1: - release_mem_region(dma1->start, dma1->end - dma1->start + 1); + release_mem_region(dma1->start, resource_size(dma1)); err_release_sif: - release_mem_region(sifmem->start, (sifmem->end - sifmem->start) + 1); + release_mem_region(sifmem->start, resource_size(sifmem)); err_release_map: iounmap(mmio); err_release_scb: - release_mem_region(scbmem->start, (scbmem->end - scbmem->start) + 1); + release_mem_region(scbmem->start, resource_size(scbmem)); err_release_none: return ret; } @@ -590,19 +587,18 @@ static void __devexit s6000_i2s_remove(struct platform_device *pdev) kfree(dev); region = platform_get_resource(pdev, IORESOURCE_DMA, 0); - release_mem_region(region->start, region->end - region->start + 1); + release_mem_region(region->start, resource_size(region)); region = platform_get_resource(pdev, IORESOURCE_DMA, 1); if (region) - release_mem_region(region->start, - region->end - region->start + 1); + release_mem_region(region->start, resource_size(region)); region = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(region->start, (region->end - region->start) + 1); + release_mem_region(region->start, resource_size(region)); iounmap(mmio); region = platform_get_resource(pdev, IORESOURCE_IO, 0); - release_mem_region(region->start, (region->end - region->start) + 1); + release_mem_region(region->start, resource_size(region)); } static struct platform_driver s6000_i2s_driver = { diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index a1d14bc3c76f..52d7e8ed9c1f 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -48,7 +48,7 @@ config SND_SH7760_AC97 config SND_FSI_AK4642 bool "FSI-AK4642 sound support" - depends on SND_SOC_SH4_FSI + depends on SND_SOC_SH4_FSI && I2C_SH_MOBILE select SND_SOC_AK4642 help This option enables generic sound support for the @@ -56,7 +56,7 @@ config SND_FSI_AK4642 config SND_FSI_DA7210 bool "FSI-DA7210 sound support" - depends on SND_SOC_SH4_FSI + depends on SND_SOC_SH4_FSI && I2C_SH_MOBILE select SND_SOC_DA7210 help This option enables generic sound support for the diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c index be018542314e..dad575a22622 100644 --- a/sound/soc/sh/fsi-ak4642.c +++ b/sound/soc/sh/fsi-ak4642.c @@ -9,16 +9,7 @@ * for more details. */ -#include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/platform_device.h> -#include <linux/i2c.h> -#include <linux/io.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/soc.h> -#include <sound/soc-dapm.h> - #include <sound/sh_fsi.h> #include <../sound/soc/codecs/ak4642.h> @@ -38,7 +29,7 @@ static int fsi_ak4642_dai_init(struct snd_soc_codec *codec) static struct snd_soc_dai_link fsi_dai_link = { .name = "AK4642", .stream_name = "AK4642", - .cpu_dai = &fsi_soc_dai[0], /* fsi */ + .cpu_dai = &fsi_soc_dai[FSI_PORT_A], .codec_dai = &ak4642_dai, .init = fsi_ak4642_dai_init, .ops = NULL, @@ -62,7 +53,7 @@ static int __init fsi_ak4642_init(void) { int ret = -ENOMEM; - fsi_snd_device = platform_device_alloc("soc-audio", -1); + fsi_snd_device = platform_device_alloc("soc-audio", FSI_PORT_A); if (!fsi_snd_device) goto out; diff --git a/sound/soc/sh/fsi-da7210.c b/sound/soc/sh/fsi-da7210.c index 33b4d177f466..121bbb07bb03 100644 --- a/sound/soc/sh/fsi-da7210.c +++ b/sound/soc/sh/fsi-da7210.c @@ -10,16 +10,7 @@ * option) any later version. */ -#include <linux/interrupt.h> #include <linux/platform_device.h> -#include <linux/io.h> -#include <linux/i2c.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> -#include <sound/soc-dapm.h> - #include <sound/sh_fsi.h> #include "../codecs/da7210.h" @@ -33,7 +24,7 @@ static int fsi_da7210_init(struct snd_soc_codec *codec) static struct snd_soc_dai_link fsi_da7210_dai = { .name = "DA7210", .stream_name = "DA7210", - .cpu_dai = &fsi_soc_dai[1], /* FSI B */ + .cpu_dai = &fsi_soc_dai[FSI_PORT_B], .codec_dai = &da7210_dai, .init = fsi_da7210_init, }; @@ -56,7 +47,7 @@ static int __init fsi_da7210_sound_init(void) { int ret; - fsi_da7210_snd_device = platform_device_alloc("soc-audio", -1); + fsi_da7210_snd_device = platform_device_alloc("soc-audio", FSI_PORT_B); if (!fsi_da7210_snd_device) return -ENOMEM; diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index ec4acac49ebd..58c6bec642de 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -12,21 +12,12 @@ * published by the Free Software Foundation. */ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/platform_device.h> #include <linux/delay.h> -#include <linux/list.h> #include <linux/pm_runtime.h> #include <linux/io.h> #include <linux/slab.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/initval.h> #include <sound/soc.h> -#include <sound/pcm_params.h> #include <sound/sh_fsi.h> -#include <asm/atomic.h> #define DO_FMT 0x0000 #define DOFF_CTL 0x0004 @@ -39,9 +30,11 @@ #define DIDT 0x0020 #define DODT 0x0024 #define MUTE_ST 0x0028 -#define REG_END MUTE_ST - +#define OUT_SEL 0x0030 +#define REG_END OUT_SEL +#define A_MST_CTLR 0x0180 +#define B_MST_CTLR 0x01A0 #define CPU_INT_ST 0x01F4 #define CPU_IEMSK 0x01F8 #define CPU_IMSK 0x01FC @@ -52,18 +45,18 @@ #define CLK_RST 0x0210 #define SOFT_RST 0x0214 #define FIFO_SZ 0x0218 -#define MREG_START CPU_INT_ST +#define MREG_START A_MST_CTLR #define MREG_END FIFO_SZ /* DO_FMT */ /* DI_FMT */ -#define CR_FMT(param) ((param) << 4) -# define CR_MONO 0x0 -# define CR_MONO_D 0x1 -# define CR_PCM 0x2 -# define CR_I2S 0x3 -# define CR_TDM 0x4 -# define CR_TDM_D 0x5 +#define CR_MONO (0x0 << 4) +#define CR_MONO_D (0x1 << 4) +#define CR_PCM (0x2 << 4) +#define CR_I2S (0x3 << 4) +#define CR_TDM (0x4 << 4) +#define CR_TDM_D (0x5 << 4) +#define CR_SPDIF 0x00100120 /* DOFF_CTL */ /* DIFF_CTL */ @@ -75,6 +68,14 @@ #define ERR_UNDER 0x00000001 #define ST_ERR (ERR_OVER | ERR_UNDER) +/* CKG1 */ +#define ACKMD_MASK 0x00007000 +#define BPFMD_MASK 0x00000700 + +/* A/B MST_CTLR */ +#define BP (1 << 4) /* Fix the signal of Biphase output */ +#define SE (1 << 0) /* Fix the master clock */ + /* CLK_RST */ #define B_CLK 0x00000010 #define A_CLK 0x00000001 @@ -119,9 +120,13 @@ struct fsi_priv { int period_len; int buffer_len; int periods; + + u32 mst_ctrl; }; -struct fsi_regs { +struct fsi_core { + int ver; + u32 int_st; u32 iemsk; u32 imsk; @@ -132,7 +137,7 @@ struct fsi_master { int irq; struct fsi_priv fsia; struct fsi_priv fsib; - struct fsi_regs *regs; + struct fsi_core *core; struct sh_fsi_platform_info *info; spinlock_t lock; }; @@ -169,24 +174,30 @@ static void __fsi_reg_mask_set(u32 reg, u32 mask, u32 data) static void fsi_reg_write(struct fsi_priv *fsi, u32 reg, u32 data) { - if (reg > REG_END) + if (reg > REG_END) { + pr_err("fsi: register access err (%s)\n", __func__); return; + } __fsi_reg_write((u32)(fsi->base + reg), data); } static u32 fsi_reg_read(struct fsi_priv *fsi, u32 reg) { - if (reg > REG_END) + if (reg > REG_END) { + pr_err("fsi: register access err (%s)\n", __func__); return 0; + } return __fsi_reg_read((u32)(fsi->base + reg)); } static void fsi_reg_mask_set(struct fsi_priv *fsi, u32 reg, u32 mask, u32 data) { - if (reg > REG_END) + if (reg > REG_END) { + pr_err("fsi: register access err (%s)\n", __func__); return; + } __fsi_reg_mask_set((u32)(fsi->base + reg), mask, data); } @@ -196,8 +207,10 @@ static void fsi_master_write(struct fsi_master *master, u32 reg, u32 data) unsigned long flags; if ((reg < MREG_START) || - (reg > MREG_END)) + (reg > MREG_END)) { + pr_err("fsi: register access err (%s)\n", __func__); return; + } spin_lock_irqsave(&master->lock, flags); __fsi_reg_write((u32)(master->base + reg), data); @@ -210,8 +223,10 @@ static u32 fsi_master_read(struct fsi_master *master, u32 reg) unsigned long flags; if ((reg < MREG_START) || - (reg > MREG_END)) + (reg > MREG_END)) { + pr_err("fsi: register access err (%s)\n", __func__); return 0; + } spin_lock_irqsave(&master->lock, flags); ret = __fsi_reg_read((u32)(master->base + reg)); @@ -226,8 +241,10 @@ static void fsi_master_mask_set(struct fsi_master *master, unsigned long flags; if ((reg < MREG_START) || - (reg > MREG_END)) + (reg > MREG_END)) { + pr_err("fsi: register access err (%s)\n", __func__); return; + } spin_lock_irqsave(&master->lock, flags); __fsi_reg_mask_set((u32)(master->base + reg), mask, data); @@ -349,8 +366,8 @@ static void fsi_irq_enable(struct fsi_priv *fsi, int is_play) u32 data = fsi_port_ab_io_bit(fsi, is_play); struct fsi_master *master = fsi_get_master(fsi); - fsi_master_mask_set(master, master->regs->imsk, data, data); - fsi_master_mask_set(master, master->regs->iemsk, data, data); + fsi_master_mask_set(master, master->core->imsk, data, data); + fsi_master_mask_set(master, master->core->iemsk, data, data); } static void fsi_irq_disable(struct fsi_priv *fsi, int is_play) @@ -358,18 +375,18 @@ static void fsi_irq_disable(struct fsi_priv *fsi, int is_play) u32 data = fsi_port_ab_io_bit(fsi, is_play); struct fsi_master *master = fsi_get_master(fsi); - fsi_master_mask_set(master, master->regs->imsk, data, 0); - fsi_master_mask_set(master, master->regs->iemsk, data, 0); + fsi_master_mask_set(master, master->core->imsk, data, 0); + fsi_master_mask_set(master, master->core->iemsk, data, 0); } static u32 fsi_irq_get_status(struct fsi_master *master) { - return fsi_master_read(master, master->regs->int_st); + return fsi_master_read(master, master->core->int_st); } static void fsi_irq_clear_all_status(struct fsi_master *master) { - fsi_master_write(master, master->regs->int_st, 0x0000000); + fsi_master_write(master, master->core->int_st, 0); } static void fsi_irq_clear_status(struct fsi_priv *fsi) @@ -381,7 +398,30 @@ static void fsi_irq_clear_status(struct fsi_priv *fsi) data |= fsi_port_ab_io_bit(fsi, 1); /* clear interrupt factor */ - fsi_master_mask_set(master, master->regs->int_st, data, 0); + fsi_master_mask_set(master, master->core->int_st, data, 0); +} + +/************************************************************************ + + + SPDIF master clock function + +These functions are used later FSI2 +************************************************************************/ +static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable) +{ + struct fsi_master *master = fsi_get_master(fsi); + u32 val = BP | SE; + + if (master->core->ver < 2) { + pr_err("fsi: register access err (%s)\n", __func__); + return; + } + + if (enable) + fsi_master_mask_set(master, fsi->mst_ctrl, val, val); + else + fsi_master_mask_set(master, fsi->mst_ctrl, val, 0); } /************************************************************************ @@ -662,8 +702,8 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct fsi_priv *fsi = fsi_get_priv(substream); - const char *msg; u32 flags = fsi_get_info_flags(fsi); + struct fsi_master *master = fsi_get_master(fsi); u32 fmt; u32 reg; u32 data; @@ -700,36 +740,40 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, fmt = is_play ? SH_FSI_GET_OFMT(flags) : SH_FSI_GET_IFMT(flags); switch (fmt) { case SH_FSI_FMT_MONO: - msg = "MONO"; - data = CR_FMT(CR_MONO); + data = CR_MONO; fsi->chan = 1; break; case SH_FSI_FMT_MONO_DELAY: - msg = "MONO Delay"; - data = CR_FMT(CR_MONO_D); + data = CR_MONO_D; fsi->chan = 1; break; case SH_FSI_FMT_PCM: - msg = "PCM"; - data = CR_FMT(CR_PCM); + data = CR_PCM; fsi->chan = 2; break; case SH_FSI_FMT_I2S: - msg = "I2S"; - data = CR_FMT(CR_I2S); + data = CR_I2S; fsi->chan = 2; break; case SH_FSI_FMT_TDM: - msg = "TDM"; fsi->chan = is_play ? SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags); - data = CR_FMT(CR_TDM) | (fsi->chan - 1); + data = CR_TDM | (fsi->chan - 1); break; case SH_FSI_FMT_TDM_DELAY: - msg = "TDM Delay"; fsi->chan = is_play ? SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags); - data = CR_FMT(CR_TDM_D) | (fsi->chan - 1); + data = CR_TDM_D | (fsi->chan - 1); + break; + case SH_FSI_FMT_SPDIF: + if (master->core->ver < 2) { + dev_err(dai->dev, "This FSI can not use SPDIF\n"); + return -EINVAL; + } + data = CR_SPDIF; + fsi->chan = 2; + fsi_spdif_clk_ctrl(fsi, 1); + fsi_reg_mask_set(fsi, OUT_SEL, 0x0010, 0x0010); break; default: dev_err(dai->dev, "unknown format.\n"); @@ -737,12 +781,6 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, } fsi_reg_write(fsi, reg, data); - /* - * clear clk reset if master mode - */ - if (is_master) - fsi_clk_ctrl(fsi, 1); - /* irq clear */ fsi_irq_disable(fsi, is_play); fsi_irq_clear_status(fsi); @@ -789,10 +827,93 @@ static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd, return ret; } +static int fsi_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsi_priv *fsi = fsi_get_priv(substream); + struct fsi_master *master = fsi_get_master(fsi); + int (*set_rate)(int is_porta, int rate) = master->info->set_rate; + int fsi_ver = master->core->ver; + int is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + int ret; + + /* if slave mode, set_rate is not needed */ + if (!fsi_is_master_mode(fsi, is_play)) + return 0; + + /* it is error if no set_rate */ + if (!set_rate) + return -EIO; + + ret = set_rate(fsi_is_port_a(fsi), params_rate(params)); + if (ret > 0) { + u32 data = 0; + + switch (ret & SH_FSI_ACKMD_MASK) { + default: + /* FALL THROUGH */ + case SH_FSI_ACKMD_512: + data |= (0x0 << 12); + break; + case SH_FSI_ACKMD_256: + data |= (0x1 << 12); + break; + case SH_FSI_ACKMD_128: + data |= (0x2 << 12); + break; + case SH_FSI_ACKMD_64: + data |= (0x3 << 12); + break; + case SH_FSI_ACKMD_32: + if (fsi_ver < 2) + dev_err(dai->dev, "unsupported ACKMD\n"); + else + data |= (0x4 << 12); + break; + } + + switch (ret & SH_FSI_BPFMD_MASK) { + default: + /* FALL THROUGH */ + case SH_FSI_BPFMD_32: + data |= (0x0 << 8); + break; + case SH_FSI_BPFMD_64: + data |= (0x1 << 8); + break; + case SH_FSI_BPFMD_128: + data |= (0x2 << 8); + break; + case SH_FSI_BPFMD_256: + data |= (0x3 << 8); + break; + case SH_FSI_BPFMD_512: + data |= (0x4 << 8); + break; + case SH_FSI_BPFMD_16: + if (fsi_ver < 2) + dev_err(dai->dev, "unsupported ACKMD\n"); + else + data |= (0x7 << 8); + break; + } + + fsi_reg_mask_set(fsi, CKG1, (ACKMD_MASK | BPFMD_MASK) , data); + udelay(10); + fsi_clk_ctrl(fsi, 1); + ret = 0; + } + + return ret; + +} + static struct snd_soc_dai_ops fsi_dai_ops = { .startup = fsi_dai_startup, .shutdown = fsi_dai_shutdown, .trigger = fsi_dai_trigger, + .hw_params = fsi_dai_hw_params, }; /************************************************************************ @@ -965,11 +1086,6 @@ static int fsi_probe(struct platform_device *pdev) unsigned int irq; int ret; - if (0 != pdev->id) { - dev_err(&pdev->dev, "current fsi support id 0 only now\n"); - return -ENODEV; - } - id_entry = pdev->id_entry; if (!id_entry) { dev_err(&pdev->dev, "unknown fsi device\n"); @@ -998,14 +1114,21 @@ static int fsi_probe(struct platform_device *pdev) goto exit_kfree; } + /* master setting */ master->irq = irq; master->info = pdev->dev.platform_data; + master->core = (struct fsi_core *)id_entry->driver_data; + spin_lock_init(&master->lock); + + /* FSI A setting */ master->fsia.base = master->base; master->fsia.master = master; + master->fsia.mst_ctrl = A_MST_CTLR; + + /* FSI B setting */ master->fsib.base = master->base + 0x40; master->fsib.master = master; - master->regs = (struct fsi_regs *)id_entry->driver_data; - spin_lock_init(&master->lock); + master->fsib.mst_ctrl = B_MST_CTLR; pm_runtime_enable(&pdev->dev); pm_runtime_resume(&pdev->dev); @@ -1085,21 +1208,27 @@ static struct dev_pm_ops fsi_pm_ops = { .runtime_resume = fsi_runtime_nop, }; -static struct fsi_regs fsi_regs = { +static struct fsi_core fsi1_core = { + .ver = 1, + + /* Interrupt */ .int_st = INT_ST, .iemsk = IEMSK, .imsk = IMSK, }; -static struct fsi_regs fsi2_regs = { +static struct fsi_core fsi2_core = { + .ver = 2, + + /* Interrupt */ .int_st = CPU_INT_ST, .iemsk = CPU_IEMSK, .imsk = CPU_IMSK, }; static struct platform_device_id fsi_id_table[] = { - { "sh_fsi", (kernel_ulong_t)&fsi_regs }, - { "sh_fsi2", (kernel_ulong_t)&fsi2_regs }, + { "sh_fsi", (kernel_ulong_t)&fsi1_core }, + { "sh_fsi2", (kernel_ulong_t)&fsi2_core }, }; static struct platform_driver fsi_driver = { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 998569d60330..5299932db0b6 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -84,7 +84,7 @@ static int run_delayed_work(struct delayed_work *dwork) /* codec register dump */ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf) { - int i, step = 1, count = 0; + int ret, i, step = 1, count = 0; if (!codec->reg_cache_size) return 0; @@ -101,12 +101,24 @@ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf) if (count >= PAGE_SIZE - 1) break; - if (codec->display_register) + if (codec->display_register) { count += codec->display_register(codec, buf + count, PAGE_SIZE - count, i); - else - count += snprintf(buf + count, PAGE_SIZE - count, - "%4x", codec->read(codec, i)); + } else { + /* If the read fails it's almost certainly due to + * the register being volatile and the device being + * powered off. + */ + ret = codec->read(codec, i); + if (ret >= 0) + count += snprintf(buf + count, + PAGE_SIZE - count, + "%4x", ret); + else + count += snprintf(buf + count, + PAGE_SIZE - count, + "<no data: %d>", ret); + } if (count >= PAGE_SIZE - 1) break; @@ -2353,6 +2365,99 @@ int snd_soc_limit_volume(struct snd_soc_codec *codec, EXPORT_SYMBOL_GPL(snd_soc_limit_volume); /** + * snd_soc_info_volsw_2r_sx - double with tlv and variable data size + * mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Returns 0 for success. + */ +int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + int min = mc->min; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = max-min; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r_sx); + +/** + * snd_soc_get_volsw_2r_sx - double with tlv and variable data size + * mixer get callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Returns 0 for success. + */ +int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int mask = (1<<mc->shift)-1; + int min = mc->min; + int val = snd_soc_read(codec, mc->reg) & mask; + int valr = snd_soc_read(codec, mc->rreg) & mask; + + ucontrol->value.integer.value[0] = ((val & 0xff)-min) & mask; + ucontrol->value.integer.value[1] = ((valr & 0xff)-min) & mask; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx); + +/** + * snd_soc_put_volsw_2r_sx - double with tlv and variable data size + * mixer put callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Returns 0 for success. + */ +int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int mask = (1<<mc->shift)-1; + int min = mc->min; + int ret; + unsigned int val, valr, oval, ovalr; + + val = ((ucontrol->value.integer.value[0]+min) & 0xff); + val &= mask; + valr = ((ucontrol->value.integer.value[1]+min) & 0xff); + valr &= mask; + + oval = snd_soc_read(codec, mc->reg) & mask; + ovalr = snd_soc_read(codec, mc->rreg) & mask; + + ret = 0; + if (oval != val) { + ret = snd_soc_write(codec, mc->reg, val); + if (ret < 0) + return ret; + } + if (ovalr != valr) { + ret = snd_soc_write(codec, mc->rreg, valr); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r_sx); + +/** * snd_soc_dai_set_sysclk - configure DAI system or master clock. * @dai: DAI * @clk_id: DAI specific clock ID |