[alsa-devel] [PATCH 1/2] ASoC: new ADAV801/ADAV803 codec driver
From: Yi Li yi.li@analog.com
Signed-off-by: Yi Li yi.li@analog.com Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Mike Frysinger vapier@gentoo.org --- sound/soc/codecs/Kconfig | 6 +- sound/soc/codecs/Makefile | 2 + sound/soc/codecs/adav80x.c | 631 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/adav80x.h | 93 +++++++ 4 files changed, 731 insertions(+), 1 deletions(-) create mode 100644 sound/soc/codecs/adav80x.c create mode 100644 sound/soc/codecs/adav80x.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5db82d2..ae01cba 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -15,8 +15,9 @@ config SND_SOC_ALL_CODECS select SND_SOC_AD1836 if SPI_MASTER select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI select SND_SOC_AD1980 if SND_SOC_AC97_BUS - select SND_SOC_ADS117X select SND_SOC_AD73311 if I2C + select SND_SOC_ADAV80X if SND_SOC_I2C_AND_SPI + select SND_SOC_ADS117X select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C select SND_SOC_AK4642 if I2C @@ -103,6 +104,9 @@ config SND_SOC_AD1980 config SND_SOC_AD73311 tristate +config SND_SOC_ADAV80X + tristate + config SND_SOC_ADS117X tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 7593eeb..f5b3d5b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -3,6 +3,7 @@ snd-soc-ad1836-objs := ad1836.o snd-soc-ad193x-objs := ad193x.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o +snd-soc-adav80x-objs := adav80x.o snd-soc-ads117x-objs := ads117x.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o @@ -69,6 +70,7 @@ obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c new file mode 100644 index 0000000..778473c --- /dev/null +++ b/sound/soc/codecs/adav80x.c @@ -0,0 +1,631 @@ +/* + * ADAV80X Audio Codec driver supporting ADAV801, ADAV803 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/soc-dapm.h> +#include "adav80x.h" + + +struct adav80x_priv { + struct snd_soc_codec codec; + u16 reg_cache[ADAV80X_NUM_REGS]; + int clk_src; /* clock source for ADC, DAC and internal clock */ +}; + +static struct snd_soc_codec *adav80x_codec; +struct snd_soc_codec_device soc_codec_dev_adav80x; +static int adav80x_register(struct adav80x_priv *adav80x, int bus_type); +static void adav80x_unregister(struct adav80x_priv *adav80x); + +static const struct snd_soc_dapm_widget adav80x_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", ADAV80X_DAC_CTRL1, 7, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", ADAV80X_ADC_CTRL1, 5, 1), + SND_SOC_DAPM_SUPPLY("PLL_PWR", ADAV80X_PLL_CTRL1, 1, 1, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DAC", NULL, "PLL_PWR" }, + { "ADC", NULL, "PLL_PWR" }, +}; + +/* + * ADAV80X volume/mute/de-emphasis etc. controls + */ +static const char *adav80x_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"}; + +static const struct soc_enum adav80x_deemp_enum = +SOC_ENUM_SINGLE(ADAV80X_DAC_CTRL2, 0, 4, adav80x_deemp); + +static const struct snd_kcontrol_new adav80x_snd_controls[] = { + /* DAC volume control */ + SOC_DOUBLE_R("DAC Volume", ADAV80X_DAC_L_VOL, + ADAV80X_DAC_R_VOL, 0, 0xFF, 0), + /* DAC peak volume detect, read clears it */ + SOC_DOUBLE_R("DAC Peak Volume", ADAV80X_DAC_L_PEAK_VOL, + ADAV80X_DAC_R_PEAK_VOL, 0, 0x3F, 0), + + /* ADC volume control */ + SOC_DOUBLE_R("ADC Volume", ADAV80X_ADC_L_VOL, + ADAV80X_ADC_R_VOL, 0, 0xFF, 0), + /* ADC peak volume detect */ + SOC_DOUBLE_R("ADC Peak Volume", ADAV80X_ADC_L_PEAK_VOL, + ADAV80X_ADC_R_PEAK_VOL, 0, 0x3F, 0), + + /* ADC mute */ + SOC_DOUBLE("ADC Switch", ADAV80X_ADC_CTRL1, 2, 3, 1, 1), + + /* DAC mute */ + SOC_DOUBLE("DAC Switch", ADAV80X_DAC_CTRL1, 0, 1, 1, 1), + + /* ADC high-pass filter */ + SOC_SINGLE("ADC High Pass Filter Switch", ADAV80X_ADC_CTRL1, 6, 1, 0), + + /* DAC de-emphasis */ + SOC_ENUM("Playback Deemphasis", adav80x_deemp_enum), +}; + +/* + * DAI ops entries + */ + +static int adav80x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 rec_ctl = 0, playback_ctl = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* use internal clock 1 */ + playback_ctl |= 0x10; + rec_ctl |= 0x20; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + /* DAC word length depends on input, ADC need to set word length */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + playback_ctl |= 0x1; + rec_ctl |= 0x1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + playback_ctl |= 0x4; + rec_ctl |= 0x3; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + /* Use default - seems no polarity setting */ + /* DAC control reg 1: CHSEL[1:0], POL[1:0] ? */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, ADAV80X_PLAYBACK_CTRL, playback_ctl); + snd_soc_write(codec, ADAV80X_REC_CTRL, rec_ctl); + + return 0; +} + +static int adav80x_set_adc_clock(struct snd_soc_codec *codec, + int clk_id, unsigned int sample_rate) +{ + int reg; + + if (clk_id == ADAV80X_CLK_PLL1) { + /* ADC assumes that the MCLK ratre is 256 times the sample rate. + We also assumes PLL1 clock rate to be (256 * Fs), + So set ADC MCLK divider to be 1 */ + + reg = snd_soc_read(codec, ADAV80X_ADC_CTRL1); + /* ADC Modulator clock is 6.144MHz Max, + need to set devidor properly */ + if (sample_rate == 96000) + reg |= 0x80; + else if (sample_rate == 48000) + reg &= 0x7F; + else + /* Unsupported sample rate */ + return -1; + + snd_soc_write(codec, ADAV80X_ADC_CTRL1, reg); + } + + return 0; +} + +static int adav80x_set_dac_clock(struct snd_soc_codec *codec, + int clk_id, unsigned int sample_rate) +{ + int reg; + + if (clk_id == ADAV80X_CLK_PLL1) { + /* PLL1 clock rate is assumed to be 256 * Fs */ + + reg = snd_soc_read(codec, ADAV80X_DAC_CTRL2); + if (sample_rate == 96000) + /* Set the MCLK divider to be MCLK/2, + and MCLK = 128 * Fs */ + reg |= 0x24; + else + reg &= 0x11; + + snd_soc_write(codec, ADAV80X_DAC_CTRL2, reg); + } + + return 0; +} + +static int adav80x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int word_len = 0; + int rate = params_rate(params); + + 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 adav80x_priv *adav80x = codec->private_data; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = 3; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + word_len = 2; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + word_len = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + word_len = 0; + break; + } + + + /* Playback port does not need to set word length? */ + + /* Record Port Control */ + snd_soc_update_bits(codec, ADAV80X_REC_CTRL, 0x3<<2, word_len<<2); + + /* Set up clock */ + if (adav80x->clk_src == ADAV80X_CLK_PLL1) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + adav80x_set_dac_clock(codec, ADAV80X_CLK_PLL1, rate); + else + adav80x_set_adc_clock(codec, ADAV80X_CLK_PLL1, rate); + } + + return 0; +} + +#ifdef CONFIG_PM +static int adav80x_soc_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +static int adav80x_soc_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define adav80x_soc_suspend NULL +#define adav80x_soc_resume NULL +#endif + +static int adav80x_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int reg; + + reg = snd_soc_read(codec, ADAV80X_DAC_CTRL1); + reg = (mute > 0) ? reg & ~0x3 : reg | 0x3; + snd_soc_write(codec, ADAV80X_DAC_CTRL1, reg); + + return 0; +} + + +static int adav80x_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct adav80x_priv *adav80x = codec->private_data; + int reg = 0; + + /* For now, we only enable PLL1 with XIN as source */ + if (source != ADAV80X_CLK_XIN) + return -1; + + if (freq_in && freq_out) { + /* XIN - assumes 27MHz */ + if (freq_in != 27000000) + return -1; + + if (pll_id != ADAV80X_CLK_PLL1) + return -1; + + /* freq_out = sample_rate * 256 */ + switch (freq_out) { + case 32000: + reg = 0x8; + break; + case 44100: + reg = 0xC; + break; + case 48000: + reg = 0x0; + break; + case 64000: + reg = 0x9; + break; + case 88200: + reg = 0xD; + break; + case 96000: + reg = 0x1; + break; + } + + /* Set PLL1 clock */ + snd_soc_write(codec, ADAV80X_PLL_CTRL2, reg); + + if (adav80x->clk_src == ADAV80X_CLK_PLL1) + return 0; + else + adav80x->clk_src = ADAV80X_CLK_PLL1; + + /* select XIN as PLL1 clock source */ + snd_soc_write(codec, ADAV80X_PLL_CLK_SRC, 0x0); + /* set PLL1 as clock source for internal clock, DAC, ADC */ + snd_soc_write(codec, ADAV80X_ICLK_CTRL1, 0x4A); + snd_soc_write(codec, ADAV80X_ICLK_CTRL2, 0x10); + /* Power on PLL1, power down PLL2, power on XTAL */ + snd_soc_write(codec, ADAV80X_PLL_CTRL1, 0x8); + + } else { + if (adav80x->clk_src == ADAV80X_CLK_XIN) + return 0; + else + adav80x->clk_src = ADAV80X_CLK_XIN; + + /* Turn off PLL, power on XTAL */ + snd_soc_write(codec, ADAV80X_PLL_CTRL1, 0xC); + + /* DAC, ADC, ICLK clock source - XIN */ + snd_soc_write(codec, ADAV80X_ICLK_CTRL1, 0x0); + snd_soc_write(codec, ADAV80X_ICLK_CTRL2, 0x0); + } + + return 0; +} + +static struct snd_soc_dai_ops adav80x_dai_ops = { + .hw_params = adav80x_hw_params, + .set_fmt = adav80x_set_dai_fmt, + .digital_mute = adav80x_mute, + .set_pll = adav80x_set_dai_pll, +}; + +/* codec DAI instance */ +/* DAC sample rates: 32/44.1/48/96/192kHz, ADC sample reate: 48/96kHz - TBD */ +struct snd_soc_dai adav80x_dai = { + .name = "ADAV80X", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_96000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &adav80x_dai_ops, +}; +EXPORT_SYMBOL_GPL(adav80x_dai); + + +static int adav80x_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (adav80x_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = adav80x_codec; + codec = adav80x_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, adav80x_snd_controls, + ARRAY_SIZE(adav80x_snd_controls)); + snd_soc_dapm_new_controls(codec, adav80x_dapm_widgets, + ARRAY_SIZE(adav80x_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + +pcm_err: + return ret; +} + +/* power down chip */ +static int adav80x_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_adav80x = { + .probe = adav80x_probe, + .remove = adav80x_remove, + .suspend = adav80x_soc_suspend, + .resume = adav80x_soc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_adav80x); + +static int adav80x_register(struct adav80x_priv *adav80x, int bus_type) +{ + int ret; + struct snd_soc_codec *codec = &adav80x->codec; + + if (adav80x_codec) { + dev_err(codec->dev, "Another adav80x is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + codec->private_data = adav80x; + codec->reg_cache = adav80x->reg_cache; + codec->reg_cache_size = ADAV80X_NUM_REGS; + codec->name = "ADAV80X"; + codec->owner = THIS_MODULE; + codec->dai = &adav80x_dai; + codec->num_dai = 1; + + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + adav80x_dai.dev = codec->dev; + adav80x_codec = codec; + + + if (bus_type == SND_SOC_I2C) + /* addr(7-bit left shifted 1), data(8bit)*/ + ret = snd_soc_codec_set_cache_io(codec, 8, 8, bus_type); + else + /* register format: addr(7bit), data(9bit) */ + /* set spi bits_per_word to 8 instead of 16, + since addr need to be transfer before data */ + ret = snd_soc_codec_set_cache_io(codec, 7, 9, bus_type); + if (ret < 0) { + dev_err(codec->dev, "failed to set cache I/O: %d\n", + ret); + kfree(adav80x); + return ret; + } + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + kfree(adav80x); + return ret; + } + + ret = snd_soc_register_dai(&adav80x_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + kfree(adav80x); + return ret; + } + + /* default setting for adav80x */ + + /* Power down SYSCLK output, power down S/PDIF receiver */ + snd_soc_write(codec, ADAV80X_PLL_OUTE, 0x27); + + /* Disable S/PDIF transmitter */ + snd_soc_write(codec, ADAV80X_TX_CTRL, 0x0); + + /* Datapath: ADC->record, AUX_IN->SRC->AUX_OUT */ + snd_soc_write(codec, ADAV80X_DPATH_CTRL1, 0xC4); + /* Datapath: playback->DAC, DIR->DIT */ + snd_soc_write(codec, ADAV80X_DPATH_CTRL2, 0x11); + + /* Soft-mute SRC output */ + snd_soc_write(codec, ADAV80X_GDELAY_MUTE, 0x80); + + /* DAC: de-emphasis: none, MCLCK divider: 1, MCLK=256xFs */ + /* snd_soc_write(codec, ADAV80X_DAC_CTRL2, 0x0); */ + /* Disable DAC zero flag */ + snd_soc_write(codec, ADAV80X_DAC_CTRL3, 0x6); + /* DAC: volume */ + snd_soc_write(codec, ADAV80X_DAC_L_VOL, 0xFF); + snd_soc_write(codec, ADAV80X_DAC_R_VOL, 0xFF); + + /* ADC: power up, unmute adc channles */ + /* snd_soc_write(codec, ADAV80X_ADC_CTRL1, 0x0); */ + /* MCLCK divider: 1 */ + /* snd_soc_write(codec, ADAV80X_ADC_CTRL2, 0x0); */ + /* ADC: volumn */ + snd_soc_write(codec, ADAV80X_ADC_L_VOL, 0xFF); + snd_soc_write(codec, ADAV80X_ADC_R_VOL, 0xFF); + + /* Disable ALC */ + snd_soc_write(codec, ADAV80X_ALC_CTRL1, 0x0); + + return 0; +} + +static void adav80x_unregister(struct adav80x_priv *adav80x) +{ + snd_soc_unregister_dai(&adav80x_dai); + snd_soc_unregister_codec(&adav80x->codec); + kfree(adav80x); + adav80x_codec = NULL; +} + +static int __devinit adav80x_bus_probe(struct device *dev, void *ctrl_data, + int bus_type) +{ + struct snd_soc_codec *codec; + struct adav80x_priv *adav80x; + + adav80x = kzalloc(sizeof(struct adav80x_priv), GFP_KERNEL); + if (adav80x == NULL) + return -ENOMEM; + + codec = &adav80x->codec; + codec->control_data = ctrl_data; + codec->dev = dev; + + dev_set_drvdata(dev, adav80x); + + return adav80x_register(adav80x, bus_type); +} + +int adav80x_bus_remove(struct device *dev) +{ + struct adav80x_priv *adav80x = dev_get_drvdata(dev); + + adav80x_unregister(adav80x); + return 0; +} + +#if defined(CONFIG_SPI_MASTER) +static int __devinit adav80x_spi_probe(struct spi_device *spi) +{ + return adav80x_bus_probe(&spi->dev, spi, SND_SOC_SPI); +} + +static int __devexit adav80x_spi_remove(struct spi_device *spi) +{ + return adav80x_bus_remove(&spi->dev); +} + +static struct spi_driver adav80x_spi_driver = { + .driver = { + .name = "adav80x", + .owner = THIS_MODULE, + }, + .probe = adav80x_spi_probe, + .remove = __devexit_p(adav80x_spi_remove), +}; +#endif + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static const struct i2c_device_id adav80x_id[] = { + { "adav803", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adav80x_id); + +static int __devinit adav80x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return adav80x_bus_probe(&client->dev, client, SND_SOC_I2C); +} + +static int __devexit adav80x_i2c_remove(struct i2c_client *client) +{ + return adav80x_bus_remove(&client->dev); +} + +static struct i2c_driver adav80x_i2c_driver = { + .driver = { + .name = "adav80x", + }, + .probe = adav80x_i2c_probe, + .remove = __devexit_p(adav80x_i2c_remove), + .id_table = adav80x_id, +}; +#endif + +static int __init adav80x_init(void) +{ + int ret; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&adav80x_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register ADAV80X I2C driver: %d\n", + ret); + } +#elif defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&adav80x_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register ADAV80X SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(adav80x_init); + +static void __exit adav80x_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&adav80x_i2c_driver); +#elif defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&adav80x_spi_driver); +#endif +} +module_exit(adav80x_exit); + +MODULE_DESCRIPTION("ASoC adav80x driver"); +MODULE_AUTHOR("Yi Li"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adav80x.h b/sound/soc/codecs/adav80x.h new file mode 100644 index 0000000..b4a5a4d --- /dev/null +++ b/sound/soc/codecs/adav80x.h @@ -0,0 +1,93 @@ +extern struct snd_soc_dai adav80x_dai; +extern struct snd_soc_codec_device soc_codec_dev_adav80x; + +/* Clock source */ +#define ADAV80X_CLK_XIN 1 +#define ADAV80X_CLK_PLL1 2 +#define ADAV80X_CLK_PLL2 3 +#define ADAV80X_CLK_INTERNAL1 4 +#define ADAV80X_CLK_INTERNAL2 5 +#define ADAV80X_CLK_MCLKI 6 + + +#if (CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* ADAV80X I2C interface requires left-shifting reg addr for 1-bit */ +#define ADAV80X_NUM_REGS (0x7E<<1) + +#define ADAV80X_PLAYBACK_CTRL (0x4<<1) +#define ADAV80X_AUX_IN_CTRL (0x5<<1) +#define ADAV80X_REC_CTRL (0x6<<1) +#define ADAV80X_AUX_OUT_CTRL (0x7<<1) +#define ADAV80X_GDELAY_MUTE (0x8<<1) + + +#define ADAV80X_TX_CTRL (0xC<<1) + +#define ADAV80X_DPATH_CTRL1 (0x62<<1) +#define ADAV80X_DPATH_CTRL2 (0x63<<1) +#define ADAV80X_DAC_CTRL1 (0x64<<1) +#define ADAV80X_DAC_CTRL2 (0x65<<1) +#define ADAV80X_DAC_CTRL3 (0x66<<1) +#define ADAV80X_DAC_CTRL4 (0x67<<1) +#define ADAV80X_DAC_L_VOL (0x68<<1) +#define ADAV80X_DAC_R_VOL (0x69<<1) +#define ADAV80X_DAC_L_PEAK_VOL (0x6A<<1) +#define ADAV80X_DAC_R_PEAK_VOL (0x6B<<1) + + +#define ADAV80X_ADC_CTRL1 (0x6E<<1) +#define ADAV80X_ADC_CTRL2 (0x6F<<1) +#define ADAV80X_ADC_L_VOL (0x70<<1) +#define ADAV80X_ADC_R_VOL (0x71<<1) +#define ADAV80X_ADC_L_PEAK_VOL (0x72<<1) +#define ADAV80X_ADC_R_PEAK_VOL (0x73<<1) +#define ADAV80X_PLL_CTRL1 (0x74<<1) +#define ADAV80X_PLL_CTRL2 (0x75<<1) +#define ADAV80X_ICLK_CTRL1 (0x76<<1) +#define ADAV80X_ICLK_CTRL2 (0x77<<1) +#define ADAV80X_PLL_CLK_SRC (0x78<<1) + +#define ADAV80X_PLL_OUTE (0x7A<<1) +#define ADAV80X_ALC_CTRL1 (0x7B<<1) + +#elif defined(CONFIG_SPI_MASTER) +/* from 0x0 to 0x7D */ +#define ADAV80X_NUM_REGS 0x7E + +#define ADAV80X_PLAYBACK_CTRL 0x4 +#define ADAV80X_AUX_IN_CTRL 0x5 +#define ADAV80X_REC_CTRL 0x6 +#define ADAV80X_AUX_OUT_CTRL 0x7 +#define ADAV80X_GDELAY_MUTE 0x8 + + +#define ADAV80X_TX_CTRL 0xC + +#define ADAV80X_DPATH_CTRL1 0x62 +#define ADAV80X_DPATH_CTRL2 0x63 +#define ADAV80X_DAC_CTRL1 0x64 +#define ADAV80X_DAC_CTRL2 0x65 +#define ADAV80X_DAC_CTRL3 0x66 +#define ADAV80X_DAC_CTRL4 0x67 +#define ADAV80X_DAC_L_VOL 0x68 +#define ADAV80X_DAC_R_VOL 0x69 +#define ADAV80X_DAC_L_PEAK_VOL 0x6A +#define ADAV80X_DAC_R_PEAK_VOL 0x6B + + +#define ADAV80X_ADC_CTRL1 0x6E +#define ADAV80X_ADC_CTRL2 0x6F +#define ADAV80X_ADC_L_VOL 0x70 +#define ADAV80X_ADC_R_VOL 0x71 +#define ADAV80X_ADC_L_PEAK_VOL 0x72 +#define ADAV80X_ADC_R_PEAK_VOL 0x73 +#define ADAV80X_PLL_CTRL1 0x74 +#define ADAV80X_PLL_CTRL2 0x75 +#define ADAV80X_ICLK_CTRL1 0x76 +#define ADAV80X_ICLK_CTRL2 0x77 +#define ADAV80X_PLL_CLK_SRC 0x78 + +#define ADAV80X_PLL_OUTE 0x7A +#define ADAV80X_ALC_CTRL1 0x7B + +#endif
From: Yi Li yi.li@analog.com
Signed-off-by: Yi Li yi.li@analog.com Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Mike Frysinger vapier@gentoo.org --- sound/soc/blackfin/Kconfig | 8 ++ sound/soc/blackfin/Makefile | 2 + sound/soc/blackfin/bf5xx-adav80x.c | 154 ++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 0 deletions(-) create mode 100644 sound/soc/blackfin/bf5xx-adav80x.c
diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index 8ef2502..75bbc90 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig @@ -32,6 +32,14 @@ config SND_BFIN_AD73311_SE Enter the GPIO used to control AD73311's SE pin. Acceptable values are 0 to 7
+config SND_BF5XX_SOC_ADAV80X + tristate "SoC ADAV801/3 Audio support" + depends on SND_BF5XX_I2S && (SPI_MASTER || I2C) + select SND_BF5XX_SOC_I2S + select SND_SOC_ADAV80X + help + Say Y if you want to add support for ADAV80X SoC audio. + config SND_BF5XX_TDM tristate "SoC I2S(TDM mode) Audio for the ADI BF5xx chip" depends on (BLACKFIN && SND_SOC) diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index 49af3f3..872dac8 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -21,9 +21,11 @@ snd-ad1980-objs := bf5xx-ad1980.o snd-ssm2602-objs := bf5xx-ssm2602.o snd-ad73311-objs := bf5xx-ad73311.o snd-ad193x-objs := bf5xx-ad193x.o +snd-adav80x-objs := bf5xx-adav80x.o
obj-$(CONFIG_SND_BF5XX_SOC_AD1836) += snd-ad1836.o obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o +obj-$(CONFIG_SND_BF5XX_SOC_ADAV80X) += snd-adav80x.o diff --git a/sound/soc/blackfin/bf5xx-adav80x.c b/sound/soc/blackfin/bf5xx-adav80x.c new file mode 100644 index 0000000..720a885 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-adav80x.c @@ -0,0 +1,154 @@ +/* + * File: sound/soc/blackfin/bf5xx-adav80x.c + * Author: Yi Li yi.li@analog.com + * + * Created: Tue June 06 2010 + * Description: board driver for ADAV80X sound chip + * + * Modified: + * Copyright 2010 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> + +#include <asm/dma.h> +#include <asm/portmux.h> +#include <linux/gpio.h> +#include "../codecs/adav80x.h" +#include "bf5xx-sport.h" +#include "bf5xx-i2s-pcm.h" +#include "bf5xx-i2s.h" + +static struct snd_soc_card bf5xx_adav80x; + +static int bf5xx_adav80x_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; + unsigned int clk = 0; + int ret = 0; + + pr_debug("%s rate %d format %x\n", __func__, params_rate(params), + params_format(params)); + + switch (params_rate(params)) { + case 32000: + case 44100: + case 48000: + case 96000: + clk = params_rate(params); + break; + } + + /* + * CODEC is master for BCLK and LRC in this configuration. + */ + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + 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_CBM_CFM); + if (ret < 0) + return ret; + + /* For the ADAV80X evaluation board, use the on board crystal + as XIN, and use XIN as source for PLL1 */ + ret = snd_soc_dai_set_pll(codec_dai, ADAV80X_CLK_PLL1, + ADAV80X_CLK_XIN, 27000000, clk); + + /* If you want to use XIN as clock source */ + /* ret = snd_soc_dai_set_pll(codec_dai, 0, ADAV80X_CLK_XIN, + 27000000, 0); */ + if (ret < 0) + return ret; + return 0; +} + +static struct snd_soc_ops bf5xx_adav80x_ops = { + .hw_params = bf5xx_adav80x_hw_params, +}; + +static struct snd_soc_dai_link bf5xx_adav80x_dai = { + .name = "adav80x", + .stream_name = "ADAV80X", + .cpu_dai = &bf5xx_i2s_dai, + .codec_dai = &adav80x_dai, + .ops = &bf5xx_adav80x_ops, +}; + +static struct snd_soc_card bf5xx_adav80x = { + .name = "bf5xx_adav80x", + .platform = &bf5xx_i2s_soc_platform, + .dai_link = &bf5xx_adav80x_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_adav80x_snd_devdata = { + .card = &bf5xx_adav80x, + .codec_dev = &soc_codec_dev_adav80x, +}; + +static struct platform_device *bf5xx_adav80x_snd_device; + +static int __init bf5xx_adav80x_init(void) +{ + int ret; + + bf5xx_adav80x_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf5xx_adav80x_snd_device) + return -ENOMEM; + + platform_set_drvdata(bf5xx_adav80x_snd_device, + &bf5xx_adav80x_snd_devdata); + bf5xx_adav80x_snd_devdata.dev = &bf5xx_adav80x_snd_device->dev; + ret = platform_device_add(bf5xx_adav80x_snd_device); + + if (ret) + platform_device_put(bf5xx_adav80x_snd_device); + + return ret; +} + +static void __exit bf5xx_adav80x_exit(void) +{ + platform_device_unregister(bf5xx_adav80x_snd_device); +} + +module_init(bf5xx_adav80x_init); +module_exit(bf5xx_adav80x_exit); + +/* Module information */ +MODULE_AUTHOR("Yi Li"); +MODULE_DESCRIPTION("ALSA SoC ADAV80X Blackfin Board"); +MODULE_LICENSE("GPL");
On 7 Aug 2010, at 16:27, Mike Frysinger vapier@gentoo.org wrote:
+config SND_BF5XX_SOC_ADAV80X
- tristate "SoC ADAV801/3 Audio support"
- depends on SND_BF5XX_I2S && (SPI_MASTER || I2C)
- select SND_BF5XX_SOC_I2S
- select SND_SOC_ADAV80X
- help
Say Y if you want to add support for ADAV80X SoC audio.
This help text is really not terribly descriptive - this is support for that CODEC on a particular board, not support for the CODEC itself.
As with the other Blackfin machine drivers you really should look at reporting the particular reference board this is on rather than providing a single driver which assumes a particular hookup for the system. Things like this...
- /* For the ADAV80X evaluation board, use the on board crystal
as XIN, and use XIN as source for PLL1 */
- ret = snd_soc_dai_set_pll(codec_dai, ADAV80X_CLK_PLL1,
ADAV80X_CLK_XIN, 27000000, clk);
...aren't going to be true for every system.
- /* If you want to use XIN as clock source */
- /* ret = snd_soc_dai_set_pll(codec_dai, 0, ADAV80X_CLK_XIN,
27000000, 0); */
This should probably be highlighted by an ifdef or even Kconfig option. Some sort comment explaining why people might want to choose one option or the other would also be useful.
On 7 Aug 2010, at 16:27, Mike Frysinger vapier@gentoo.org wrote:
- select SND_SOC_ADS117X select SND_SOC_AD73311 if I2C
- select SND_SOC_ADAV80X if SND_SOC_I2C_AND_SPI
- select SND_SOC_ADS117X
The restarting should really have been done separately or flagged in the changelog - part of making things easy to review is avoiding surprises.
+static struct snd_soc_codec *adav80x_codec; +struct snd_soc_codec_device soc_codec_dev_adav80x; +static int adav80x_register(struct adav80x_priv *adav80x, int bus_type); +static void adav80x_unregister(struct adav80x_priv *adav80x);
This also needs to be redone against multi-component as with the driver I reviewed earlier today. Please avoid posting any further drivers that haven't been done against multi-component, it'll be merged into -next pretty much as soon as the merge window closes.
- /* DAC peak volume detect, read clears it */
- SOC_DOUBLE_R("DAC Peak Volume", ADAV80X_DAC_L_PEAK_VOL,
ADAV80X_DAC_R_PEAK_VOL, 0, 0x3F, 0),
This really needs a new, read only, control type defining. I'm also not seeing these registers being marked as volatile which means that these controls will always return cached values rather than actual data read from the chip.
- /* ADC volume control */
- SOC_DOUBLE_R("ADC Volume", ADAV80X_ADC_L_VOL,
ADAV80X_ADC_R_VOL, 0, 0xFF, 0),
Use Playback and Capture instead of DAC and ADC.
/* ADC Modulator clock is 6.144MHz Max,
need to set devidor properly */
if (sample_rate == 96000)
reg |= 0x80;
else if (sample_rate == 48000)
reg &= 0x7F;
else
/* Unsupported sample rate */
return -1;
A switch statement would flow better here and you should return a proper error code.
- if (clk_id == ADAV80X_CLK_PLL1) {
/* PLL1 clock rate is assumed to be 256 * Fs */
reg = snd_soc_read(codec, ADAV80X_DAC_CTRL2);
if (sample_rate == 96000)
/* Set the MCLK divider to be MCLK/2,
and MCLK = 128 * Fs */
reg |= 0x24;
else
reg &= 0x11;
snd_soc_write(codec, ADAV80X_DAC_CTRL2, reg);
- }
What if the clock is not PLL1?
+#ifdef CONFIG_PM +static int adav80x_soc_suspend(struct platform_device *pdev,
pm_message_t state)
+{
- return 0;
+}
+static int adav80x_soc_resume(struct platform_device *pdev) +{
- return 0;
+}
Remove empty functions.
- reg = snd_soc_read(codec, ADAV80X_DAC_CTRL1);
- reg = (mute > 0) ? reg & ~0x3 : reg | 0x3;
- snd_soc_write(codec, ADAV80X_DAC_CTRL1, reg);
The ternery operator isn't great for legibility.
- if (freq_in && freq_out) {
/* XIN - assumes 27MHz */
if (freq_in != 27000000)
return -1;
if (pll_id != ADAV80X_CLK_PLL1)
return -1;
Return proper error codes.
/* freq_out = sample_rate * 256 */
switch (freq_out) {
case 32000:
reg = 0x8;
break;
case 44100:
reg = 0xC;
break;
case 48000:
reg = 0x0;
break;
case 64000:
reg = 0x9;
break;
case 88200:
reg = 0xD;
break;
case 96000:
reg = 0x1;
break;
}
Looking at this the output frequencies you're handling are actually the sample rates rather than 256fs like the comment says. You also need to handle unknown frequencies here.
if (adav80x->clk_src == ADAV80X_CLK_XIN)
return 0;
else
adav80x->clk_src = ADAV80X_CLK_XIN;
/* Turn off PLL, power on XTAL */
snd_soc_write(codec, ADAV80X_PLL_CTRL1, 0xC);
/* DAC, ADC, ICLK clock source - XIN */
snd_soc_write(codec, ADAV80X_ICLK_CTRL1, 0x0);
snd_soc_write(codec, ADAV80X_ICLK_CTRL2, 0x0);
Normally I'd expect to see the clock source switched over to XIN before rather than after the PLL is stopped to ensure a continuity in clocking.
- /* Power down SYSCLK output, power down S/PDIF receiver */
- snd_soc_write(codec, ADAV80X_PLL_OUTE, 0x27);
- /* Disable S/PDIF transmitter */
- snd_soc_write(codec, ADAV80X_TX_CTRL, 0x0);
DAPM should be able to take care of all this automatically.
- /* Datapath: ADC->record, AUX_IN->SRC->AUX_OUT */
- snd_soc_write(codec, ADAV80X_DPATH_CTRL1, 0xC4);
Remove this and pretty much all of the rest of this function - just leave the chip with the default settings and let the user configure whatever use case they want on their system. The values you set here may not be appropriate for other users.
+#if (CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* ADAV80X I2C interface requires left-shifting reg addr for 1-bit */ +#define ADAV80X_NUM_REGS (0x7E<<1)
This is all going to break if the user elects to build both I2C and SPI support into the same kernel and in any case even for a compile time selection having two totally different sets of register definitions is pretty inelegant.
ive put this feedback into a tracker item: http://blackfin.uclinux.org/gf/tracker/6159 -mike
participants (3)
-
Mark Brown
-
Mike Frysinger
-
Mike Frysinger