[alsa-devel] [PATCH 0/3] new support for ad1938 codec
Hi Liam&Mark, The following series add support for ad1938 codec
*PATCH 1: ad1938 codec driver
*PATCH 2: blackfin I2S(in TDM mode) DAI driver Even though TDM mode can be as part of I2S DAI, but there are so much difference in configuration and data flow, it's very ugly to integrate I2S and TDM into a module. So we use the DAI type, but split normal I2S and TDM into individual files.
*PATCH 3: board driver to connect bf5xx with ad1938
-- Thanks, Barry
Signed-off-by: Barry Song 21cnbao@gmail.com --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ad1938.c | 652 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ad1938.h | 100 +++++++ 4 files changed, 758 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/ad1938.c create mode 100644 sound/soc/codecs/ad1938.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f049a98..ce7bcf5 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -12,6 +12,7 @@ config SND_SOC_ALL_CODECS tristate "Build all ASoC CODEC drivers" select SND_SOC_L3 select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS + select SND_SOC_AD1938 if SPI_MASTER select SND_SOC_AD1939 if I2C select SND_SOC_AD1980 if SND_SOC_AC97_BUS select SND_SOC_AD73311 if I2C @@ -70,6 +71,9 @@ config SND_SOC_ALL_CODECS config SND_SOC_AC97_CODEC tristate
+config SND_SOC_AD1938 + tristate + config SND_SOC_AD1939 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 913f2fa..2dd9490 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,4 +1,5 @@ snd-soc-ac97-objs := ac97.o +snd-soc-ad1938-objs := ad1938.o snd-soc-ad1939-objs := ad1939.o snd-soc-ad1980-objs := ad1980.o snd-soc-ak4535-objs := ak4535.o @@ -49,6 +50,7 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AD1938) += snd-soc-ad1938.o obj-$(CONFIG_SND_SOC_AD1939) += snd-soc-ad1939.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c new file mode 100644 index 0000000..3dc8091 --- /dev/null +++ b/sound/soc/codecs/ad1938.c @@ -0,0 +1,652 @@ +/* + * File: sound/soc/codecs/ad1938.c + * Author: Barry Song Barry.Song@analog.com + * + * Created: June 04 2009 + * Description: Driver for AD1938 sound chip + * + * Modified: + * Copyright 2009 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/init.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/device.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 <linux/spi/spi.h> +#include "ad1938.h" + +/* codec private data */ +struct ad1938_priv { + struct snd_soc_codec codec; + u8 reg_cache[AD1938_NUM_REGS]; + +}; + +static struct snd_soc_codec *ad1938_codec; +struct snd_soc_codec_device soc_codec_dev_ad1938; +static int ad1938_register(struct ad1938_priv *ad1938); +static void ad1938_unregister(struct ad1938_priv *ad1938); + +/* + * AD1938 volume/mute/de-emphasis etc. controls + */ +static const char *ad1938_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"}; + +static const struct soc_enum ad1938_deemp_enum = + SOC_ENUM_SINGLE(AD1938_DAC_CTRL2, 1, 4, ad1938_deemp); + +static const struct snd_kcontrol_new ad1938_snd_controls[] = { + /* DAC volume control */ + SOC_DOUBLE_R("DAC1 Volume", AD1938_DAC_L1_VOL, + AD1938_DAC_R1_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC2 Volume", AD1938_DAC_L2_VOL, + AD1938_DAC_R2_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC3 Volume", AD1938_DAC_L3_VOL, + AD1938_DAC_R3_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC4 Volume", AD1938_DAC_L4_VOL, + AD1938_DAC_R4_VOL, 0, 0xFF, 1), + + /* ADC switch control */ + SOC_DOUBLE("ADC1 Switch", AD1938_ADC_CTRL0, AD1938_ADCL1_MUTE, AD1938_ADCR1_MUTE, 1, 1), + SOC_DOUBLE("ADC2 Switch", AD1938_ADC_CTRL0, AD1938_ADCL2_MUTE, AD1938_ADCR2_MUTE, 1, 1), + + /* DAC switch control */ + SOC_DOUBLE("DAC1 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL1_MUTE, AD1938_DACR1_MUTE, 1, 1), + SOC_DOUBLE("DAC2 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL2_MUTE, AD1938_DACR2_MUTE, 1, 1), + SOC_DOUBLE("DAC3 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL3_MUTE, AD1938_DACR3_MUTE, 1, 1), + SOC_DOUBLE("DAC4 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL4_MUTE, AD1938_DACR4_MUTE, 1, 1), + + /* ADC high-pass filter */ + SOC_SINGLE("ADC High Pass Filter Switch", AD1938_ADC_CTRL0, + AD1938_ADC_HIGHPASS_FILTER, 1, 0), + + /* DAC de-emphasis */ + SOC_ENUM("Playback Deemphasis", ad1938_deemp_enum), +}; + +static const struct snd_soc_dapm_widget ad1938_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", AD1938_DAC_CTRL0, 0, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1938_ADC_CTRL0, 0, 1, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DAC", NULL, "ADC_PWR" }, + { "ADC", NULL, "ADC_PWR" }, +}; + +/* + * DAI ops entries + */ + +static int ad1938_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int reg; + + reg = codec->read(codec, AD1938_DAC_CTRL2); + reg = (mute > 0) ? reg | AD1938_DAC_MASTER_MUTE : reg & (~AD1938_DAC_MASTER_MUTE); + codec->write(codec, AD1938_DAC_CTRL2, reg); + + return 0; +} + +static inline int ad1938_pll_powerctrl(struct snd_soc_codec *codec, int cmd) +{ + int reg = codec->read(codec, AD1938_PLL_CLK_CTRL0); + reg = (cmd > 0) ? reg & (~AD1938_PLL_POWERDOWN) : reg | AD1938_PLL_POWERDOWN; + codec->write(codec, AD1938_PLL_CLK_CTRL0, reg); + + return 0; +} + +static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int mask, int slots) +{ + struct snd_soc_codec *codec = dai->codec; + int dac_reg = codec->read(codec, AD1938_DAC_CTRL1); + int adc_reg = codec->read(codec, AD1938_ADC_CTRL2); + + dac_reg &= ~AD1938_DAC_CHAN_MASK; + adc_reg &= ~AD1938_ADC_CHAN_MASK; + + switch(slots) { + case 2: + dac_reg |= AD1938_DAC_2_CHANNELS << AD1938_DAC_CHAN_SHFT; + adc_reg |= AD1938_ADC_2_CHANNELS << AD1938_ADC_CHAN_SHFT; + break; + case 4: + dac_reg |= AD1938_DAC_4_CHANNELS << AD1938_DAC_CHAN_SHFT; + adc_reg |= AD1938_ADC_4_CHANNELS << AD1938_ADC_CHAN_SHFT; + break; + case 8: + dac_reg |= AD1938_DAC_8_CHANNELS << AD1938_DAC_CHAN_SHFT; + adc_reg |= AD1938_ADC_8_CHANNELS << AD1938_ADC_CHAN_SHFT; + break; + case 16: + dac_reg |= AD1938_DAC_16_CHANNELS << AD1938_DAC_CHAN_SHFT; + adc_reg |= AD1938_ADC_16_CHANNELS << AD1938_ADC_CHAN_SHFT; + break; + default: + return -EINVAL; + } + + codec->write(codec, AD1938_DAC_CTRL1, dac_reg); + codec->write(codec, AD1938_ADC_CTRL2, adc_reg); + + return 0; +} + +static int ad1938_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int adc_reg, dac_reg; + + adc_reg = codec->read(codec, AD1938_ADC_CTRL2); + dac_reg = codec->read(codec, AD1938_DAC_CTRL1); + + /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S with TDM) + * and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + adc_reg &= ~AD1938_ADC_SERFMT_MASK; + adc_reg |= AD1938_ADC_SERFMT_TDM; + break; + case SND_SOC_DAIFMT_DSP_A: + adc_reg &= ~AD1938_ADC_SERFMT_MASK; + adc_reg |= AD1938_ADC_SERFMT_AUX; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ + adc_reg &= ~AD1938_ADC_LEFT_HIGH; + adc_reg &= ~AD1938_ADC_BCLK_INV; + dac_reg &= ~AD1938_DAC_LEFT_HIGH; + dac_reg &= ~AD1938_DAC_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */ + adc_reg |= AD1938_ADC_LEFT_HIGH; + adc_reg &= ~AD1938_ADC_BCLK_INV; + dac_reg |= AD1938_DAC_LEFT_HIGH; + dac_reg &= ~AD1938_DAC_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */ + adc_reg &= ~AD1938_ADC_LEFT_HIGH; + adc_reg |= AD1938_ADC_BCLK_INV; + dac_reg &= ~AD1938_DAC_LEFT_HIGH; + dac_reg |= AD1938_DAC_BCLK_INV; + break; + + case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */ + adc_reg |= AD1938_ADC_LEFT_HIGH; + adc_reg |= AD1938_ADC_BCLK_INV; + dac_reg |= AD1938_DAC_LEFT_HIGH; + dac_reg |= AD1938_DAC_BCLK_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ + adc_reg |= AD1938_ADC_LCR_MASTER; + adc_reg |= AD1938_ADC_BCLK_MASTER; + dac_reg |= AD1938_DAC_LCR_MASTER; + dac_reg |= AD1938_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */ + adc_reg |= AD1938_ADC_LCR_MASTER; + adc_reg &= ~AD1938_ADC_BCLK_MASTER; + dac_reg |= AD1938_DAC_LCR_MASTER; + dac_reg &= ~AD1938_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + adc_reg &= ~AD1938_ADC_LCR_MASTER; + adc_reg |= AD1938_ADC_BCLK_MASTER; + dac_reg &= ~AD1938_DAC_LCR_MASTER; + dac_reg |= AD1938_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ + adc_reg &= ~AD1938_ADC_LCR_MASTER; + adc_reg &= ~AD1938_ADC_BCLK_MASTER; + dac_reg &= ~AD1938_DAC_LCR_MASTER; + dac_reg &= ~AD1938_DAC_BCLK_MASTER; + break; + default: + return -EINVAL; + } + + codec->write(codec, AD1938_ADC_CTRL2, adc_reg); + codec->write(codec, AD1938_DAC_CTRL1, dac_reg); + + return 0; +} + +static int ad1938_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int word_len = 0, reg = 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; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = 3; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + word_len = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + word_len = 0; + break; + } + + reg = codec->read(codec, AD1938_DAC_CTRL2); + reg = (reg & (~AD1938_DAC_WORD_LEN_MASK)) | word_len; + codec->write(codec, AD1938_DAC_CTRL2, reg); + + reg = codec->read(codec, AD1938_ADC_CTRL1); + reg = (reg & (~AD1938_ADC_WORD_LEN_MASK)) | word_len; + codec->write(codec, AD1938_ADC_CTRL1, reg); + + return 0; +} + +static int ad1938_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + ad1938_pll_powerctrl(codec, 1); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + ad1938_pll_powerctrl(codec, 0); + break; + } + codec->bias_level = level; + return 0; +} + +/* + * interface to read/write ad1938 register + */ + +#define AD1938_SPI_ADDR 0x4 +#define AD1938_SPI_READ 0x1 +#define AD1938_SPI_BUFLEN 3 + +/* + * write to the ad1938 register space + */ + +static int ad1938_write_reg(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 *reg_cache = codec->reg_cache; + int ret = 0; + + if(value != reg_cache[reg]) { + uint8_t buf[AD1938_SPI_BUFLEN]; + struct spi_transfer t = { + .tx_buf = buf, + .len = AD1938_SPI_BUFLEN, + }; + struct spi_message m; + + buf[0] = AD1938_SPI_ADDR << 1; + buf[1] = reg; + buf[2] = value; + spi_message_init(&m); + spi_message_add_tail(&t, &m); + if((ret = spi_sync(codec->control_data, &m)) == 0) + reg_cache[reg] = value; + } + + return ret; +} + +/* + * read from the ad1938 register space cache + */ + +static unsigned int ad1938_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *reg_cache = codec->reg_cache; + + if (reg >= codec->reg_cache_size) + return -EINVAL; + + return reg_cache[reg]; +} + +/* + * read from the ad1938 register space + */ + +static unsigned int ad1938_read_reg(struct snd_soc_codec *codec, unsigned int reg) +{ + char w_buf[AD1938_SPI_BUFLEN]; + char r_buf[AD1938_SPI_BUFLEN]; + int ret; + + struct spi_transfer t = { + .tx_buf = w_buf, + .rx_buf = r_buf, + .len = AD1938_SPI_BUFLEN, + }; + struct spi_message m; + + w_buf[0] = (AD1938_SPI_ADDR << 1) | AD1938_SPI_READ; + w_buf[1] = reg; + w_buf[2] = 0; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(codec->control_data, &m); + if (ret == 0) + return r_buf[2]; + else + return -EIO; +} + +static int ad1938_fill_cache(struct snd_soc_codec *codec) +{ + int i; + u8 *reg_cache = codec->reg_cache; + struct spi_device *spi = codec->control_data; + + for (i = 0; i < codec->reg_cache_size; i++) { + int ret = ad1938_read_reg(codec, i); + if (ret == -EIO) { + dev_err(&spi->dev, "AD1938 SPI read failure\n"); + return ret; + } + reg_cache[i] = ret; + } + + return 0; +} + +static int __devinit ad1938_spi_probe(struct spi_device *spi) +{ + struct snd_soc_codec *codec; + struct ad1938_priv *ad1938; + + ad1938 = kzalloc(sizeof(struct ad1938_priv), GFP_KERNEL); + if (ad1938 == NULL) + return -ENOMEM; + + codec = &ad1938->codec; + codec->control_data = spi; + codec->dev = &spi->dev; + + spi->dev.driver_data = ad1938; + + return ad1938_register(ad1938); +} + +static int __devexit ad1938_spi_remove(struct spi_device *spi) +{ + struct ad1938_priv *ad1938 = spi->dev.driver_data; + + ad1938_unregister(ad1938); + return 0; +} + +static struct spi_driver ad1938_spi_driver = { + .driver = { + .name = "ad1938-spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ad1938_spi_probe, + .remove = __devexit_p(ad1938_spi_remove), +}; + +static struct snd_soc_dai_ops ad1938_dai_ops = { + .hw_params = ad1938_hw_params, + .digital_mute = ad1938_mute, + .set_tdm_slot = ad1938_set_tdm_slot, + .set_fmt = ad1938_set_dai_fmt, +}; + +/* codec DAI instance */ +struct snd_soc_dai ad1938_dai = { + .name = "AD1938", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .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 = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &ad1938_dai_ops, +}; +EXPORT_SYMBOL_GPL(ad1938_dai); + +static int ad1938_register(struct ad1938_priv *ad1938) +{ + int ret; + struct snd_soc_codec *codec = &ad1938->codec; + + if (ad1938_codec) { + dev_err(codec->dev, "Another ad1938 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->private_data = ad1938; + codec->reg_cache = ad1938->reg_cache; + codec->reg_cache_size = AD1938_NUM_REGS; + codec->name = "AD1938"; + codec->owner = THIS_MODULE; + codec->dai = &ad1938_dai; + codec->num_dai = 1; + codec->write = ad1938_write_reg; + codec->read = ad1938_read_reg_cache; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ad1938_dai.dev = codec->dev; + ad1938_codec = codec; + + /* default setting for ad1938 */ + codec->write(codec, AD1938_DAC_CHNL_MUTE, 0x0); /* unmute dac channels */ + codec->write(codec, AD1938_DAC_CTRL2, 0x1A); /* de-emphasis: 48kHz, powedown dac */ + codec->write(codec, AD1938_DAC_CTRL0, 0x21); /* powerdown dac, dac tdm mode */ + codec->write(codec, AD1938_ADC_CTRL0, 0x3); /* high-pass filter enable */ + codec->write(codec, AD1938_ADC_CTRL1, 0x43); /* sata delay=1, adc aux mode */ + codec->write(codec, AD1938_PLL_CLK_CTRL0, 0x9D); /* pll input:mclki/xi */ + codec->write(codec, AD1938_PLL_CLK_CTRL1, 0x04); + + ad1938_fill_cache(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(&ad1938_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; +} + +static void ad1938_unregister(struct ad1938_priv *ad1938) +{ + ad1938_set_bias_level(&ad1938->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&ad1938_dai); + snd_soc_unregister_codec(&ad1938->codec); + kfree(ad1938); + ad1938_codec = NULL; +} + +static int ad1938_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (ad1938_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = ad1938_codec; + codec = ad1938_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, ad1938_snd_controls, + ARRAY_SIZE(ad1938_snd_controls)); + snd_soc_dapm_new_controls(codec, ad1938_dapm_widgets, + ARRAY_SIZE(ad1938_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + snd_soc_dapm_new_widgets(codec); + + ad1938_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +/* power down chip */ +static int ad1938_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 +static int ad1938_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; + + ad1938_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ad1938_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + ad1938_set_bias_level(codec, SND_SOC_BIAS_ON); + + return 0; +} +#else +#define ad1938_suspend NULL +#define ad1938_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_ad1938 = { + .probe = ad1938_probe, + .remove = ad1938_remove, + .suspend = ad1938_suspend, + .resume = ad1938_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad1938); + +static int __init ad1938_init(void) +{ + int ret; + + ret = spi_register_driver(&ad1938_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register ad1938 SPI driver: %d\n", + ret); + } + + return ret; +} +module_init(ad1938_init); + +static void __exit ad1938_exit(void) +{ + spi_unregister_driver(&ad1938_spi_driver); +} +module_exit(ad1938_exit); + +MODULE_DESCRIPTION("ASoC ad1938 driver"); +MODULE_AUTHOR("Barry Song "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1938.h b/sound/soc/codecs/ad1938.h new file mode 100644 index 0000000..fe3c48c --- /dev/null +++ b/sound/soc/codecs/ad1938.h @@ -0,0 +1,100 @@ +/* + * File: sound/soc/codecs/ad1836.h + * Based on: + * Author: Barry Song Barry.Song@analog.com + * + * Created: May 25, 2009 + * Description: definitions for AD1938 registers + * + * Modified: + * + * 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 + */ + +#ifndef __AD1938_H__ +#define __AD1938_H__ + +#define AD1938_PLL_CLK_CTRL0 0 +#define AD1938_PLL_POWERDOWN 0x01 +#define AD1938_PLL_CLK_CTRL1 1 +#define AD1938_DAC_CTRL0 2 +#define AD1938_DAC_POWERDOWN 0x01 +#define AD1938_DAC_SERFMT_MASK 0xC0 +#define AD1938_DAC_SERFMT_STEREO (0 << 6) +#define AD1938_DAC_SERFMT_TDM (1 << 6) +#define AD1938_DAC_CTRL1 3 +#define AD1938_DAC_2_CHANNELS 0 +#define AD1938_DAC_4_CHANNELS 1 +#define AD1938_DAC_8_CHANNELS 2 +#define AD1938_DAC_16_CHANNELS 3 +#define AD1938_DAC_CHAN_SHFT 1 +#define AD1938_DAC_CHAN_MASK (3 << AD1938_DAC_CHAN_SHFT) +#define AD1938_DAC_LCR_MASTER (1 << 4) +#define AD1938_DAC_BCLK_MASTER (1 << 5) +#define AD1938_DAC_LEFT_HIGH (1 << 3) +#define AD1938_DAC_BCLK_INV (1 << 7) +#define AD1938_DAC_CTRL2 4 +#define AD1938_DAC_WORD_LEN_MASK 0xC +#define AD1938_DAC_MASTER_MUTE 1 +#define AD1938_DAC_CHNL_MUTE 5 +#define AD1938_DACL1_MUTE 0 +#define AD1938_DACR1_MUTE 1 +#define AD1938_DACL2_MUTE 2 +#define AD1938_DACR2_MUTE 3 +#define AD1938_DACL3_MUTE 4 +#define AD1938_DACR3_MUTE 5 +#define AD1938_DACL4_MUTE 6 +#define AD1938_DACR4_MUTE 7 +#define AD1938_DAC_L1_VOL 6 +#define AD1938_DAC_R1_VOL 7 +#define AD1938_DAC_L2_VOL 8 +#define AD1938_DAC_R2_VOL 9 +#define AD1938_DAC_L3_VOL 10 +#define AD1938_DAC_R3_VOL 11 +#define AD1938_DAC_L4_VOL 12 +#define AD1938_DAC_R4_VOL 13 +#define AD1938_ADC_CTRL0 14 +#define AD1938_ADC_POWERDOWN 0x01 +#define AD1938_ADC_HIGHPASS_FILTER 1 +#define AD1938_ADCL1_MUTE 2 +#define AD1938_ADCR1_MUTE 3 +#define AD1938_ADCL2_MUTE 4 +#define AD1938_ADCR2_MUTE 5 +#define AD1938_ADC_CTRL1 15 +#define AD1938_ADC_SERFMT_MASK 0x60 +#define AD1938_ADC_SERFMT_STEREO (0 << 5) +#define AD1938_ADC_SERFMT_TDM (1 << 2) +#define AD1938_ADC_SERFMT_AUX (2 << 5) +#define AD1938_ADC_WORD_LEN_MASK 0x3 +#define AD1938_ADC_CTRL2 16 +#define AD1938_ADC_2_CHANNELS 0 +#define AD1938_ADC_4_CHANNELS 1 +#define AD1938_ADC_8_CHANNELS 2 +#define AD1938_ADC_16_CHANNELS 3 +#define AD1938_ADC_CHAN_SHFT 4 +#define AD1938_ADC_CHAN_MASK (3 << AD1938_ADC_CHAN_SHFT) +#define AD1938_ADC_LCR_MASTER (1 << 3) +#define AD1938_ADC_BCLK_MASTER (1 << 6) +#define AD1938_ADC_LEFT_HIGH (1 << 2) +#define AD1938_ADC_BCLK_INV (1 << 1) + +#define AD1938_NUM_REGS 17 + +extern struct snd_soc_dai ad1938_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad1938; +#endif
On Thu, Jul 16, 2009 at 04:00:05PM +0800, Barry Song wrote:
Signed-off-by: Barry Song 21cnbao@gmail.com
This is fine from a code point of view but there are a *lot* of tab/space problems in the code. I'd suggest running checkpatch over your patches before sending them to make sure you catch issues like that. Also, the patch was against the ASoC dev branch rather than ALSA git - the most current mailine ALSA code is in the topic/asoc branch of:
git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6.git
Please submit patches againt that or my current queue which will be in:
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
Since the code is all good I've applied the patch as-is but a followup fixing the tab/space stuff would be appreciated.
Signed-off-by: Barry Song 21cnbao@gmail.com Even though TDM mode can be as part of I2S DAI, but there are so much difference in configuration and data flow, it's very ugly to integrate I2S and TDM into a module. --- sound/soc/blackfin/Kconfig | 23 +++- sound/soc/blackfin/Makefile | 6 + sound/soc/blackfin/bf5xx-tdm-pcm.c | 330 ++++++++++++++++++++++++++++++++++++ sound/soc/blackfin/bf5xx-tdm-pcm.h | 21 +++ sound/soc/blackfin/bf5xx-tdm.c | 313 ++++++++++++++++++++++++++++++++++ sound/soc/blackfin/bf5xx-tdm.h | 14 ++ 6 files changed, 706 insertions(+), 1 deletions(-) create mode 100644 sound/soc/blackfin/bf5xx-tdm-pcm.c create mode 100644 sound/soc/blackfin/bf5xx-tdm-pcm.h create mode 100644 sound/soc/blackfin/bf5xx-tdm.c create mode 100644 sound/soc/blackfin/bf5xx-tdm.h
diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index 811596f..b5c2b7a 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig @@ -7,6 +7,15 @@ config SND_BF5XX_I2S mode (supports single stereo In/Out). You will also need to select the audio interfaces to support below.
+config SND_BF5XX_TDM + tristate "SoC I2S(TDM mode) Audio for the ADI BF5xx chip" + depends on BLACKFIN && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the Blackfin SPORT (synchronous serial ports) interface in TDM + mode. + You will also need to select the audio interfaces to support below. + config SND_BF5XX_SOC_SSM2602 tristate "SoC SSM2602 Audio support for BF52x ezkit" depends on SND_BF5XX_I2S @@ -69,6 +78,10 @@ config SND_BF5XX_SOC_I2S tristate select SND_BF5XX_SOC_SPORT
+config SND_BF5XX_SOC_TDM + tristate + select SND_BF5XX_SOC_SPORT + config SND_BF5XX_SOC_AC97 tristate select AC97_BUS @@ -83,9 +96,17 @@ config SND_BF5XX_SOC_AD1980 help Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
+config SND_BF5XX_SOC_AD1938 + tristate "SoC AD1938 Audio support for Blackfin" + depends on SND_BF5XX_TDM + select SND_BF5XX_SOC_TDM + select SND_SOC_AD1938 + help + Say Y if you want to add support for AD1938 codec on Blackfin. + config SND_BF5XX_SPORT_NUM int "Set a SPORT for Sound chip" - depends on (SND_BF5XX_I2S || SND_BF5XX_AC97) + depends on (SND_BF5XX_I2S || SND_BF5XX_AC97 || SND_BF5XX_TDM) range 0 3 if BF54x range 0 1 if !BF54x default 0 diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index 97bb37a..f4d7607 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -1,21 +1,27 @@ # Blackfin Platform Support snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o +snd-bf5xx-tdm-objs := bf5xx-tdm-pcm.o snd-soc-bf5xx-sport-objs := bf5xx-sport.o snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o +snd-soc-bf5xx-tdm-objs := bf5xx-tdm.o
obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o +obj-$(CONFIG_SND_BF5XX_TDM) += snd-bf5xx-tdm.o obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o +obj-$(CONFIG_SND_BF5XX_SOC_TDM) += snd-soc-bf5xx-tdm.o
# Blackfin Machine Support snd-ad1980-objs := bf5xx-ad1980.o snd-ssm2602-objs := bf5xx-ssm2602.o snd-ad73311-objs := bf5xx-ad73311.o +snd-ad1938-objs := bf5xx-ad1938.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_AD1938) += snd-ad1938.o diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.c b/sound/soc/blackfin/bf5xx-tdm-pcm.c new file mode 100644 index 0000000..2b0458c --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.c @@ -0,0 +1,330 @@ +/* + * File: sound/soc/blackfin/bf5xx-tdm-pcm.c + * Author: Barry Song Barry.Song@analog.com + * + * Created: Tue June 06 2009 + * Description: DMA driver for tdm codec + * + * Modified: + * Copyright 2009 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/init.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/dma.h> + +#include "bf5xx-tdm-pcm.h" +#include "bf5xx-tdm.h" +#include "bf5xx-sport.h" + +#define PCM_BUFFER_MAX 0x10000 +#define FRAGMENT_SIZE_MIN (4*1024) +#define FRAGMENTS_MIN 2 +#define FRAGMENTS_MAX 32 + +static void bf5xx_dma_irq(void *data) +{ + struct snd_pcm_substream *pcm = data; + snd_pcm_period_elapsed(pcm); +} + +static const struct snd_pcm_hardware bf5xx_pcm_hardware = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = PCM_BUFFER_MAX, + .period_bytes_min = FRAGMENT_SIZE_MIN, + .period_bytes_max = PCM_BUFFER_MAX/2, + .periods_min = FRAGMENTS_MIN, + .periods_max = FRAGMENTS_MAX, +}; + +static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + size_t size = bf5xx_pcm_hardware.buffer_bytes_max; + snd_pcm_lib_malloc_pages(substream, size * 4); + + return 0; +} + +static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + + return 0; +} + +static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size); + + fragsize_bytes /= runtime->channels; + fragsize_bytes *= 8;/* inflate the fragsize to match */ + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport_set_tx_callback(sport, bf5xx_dma_irq, substream); + sport_config_tx_dma(sport, runtime->dma_area, + runtime->periods, fragsize_bytes); + } else { + sport_set_rx_callback(sport, bf5xx_dma_irq, substream); + sport_config_rx_dma(sport, runtime->dma_area, + runtime->periods, fragsize_bytes); + } + + return 0; +} + +static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_tx_start(sport); + else + sport_rx_start(sport); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_tx_stop(sport); + else + sport_rx_stop(sport); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + unsigned int diff; + snd_pcm_uframes_t frames; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + diff = sport_curr_offset_tx(sport); + frames = diff / (8*4); /* 32 bytes per frame */ + } else { + diff = sport_curr_offset_rx(sport); + frames = diff / (8*4); + } + return frames; +} + +static int bf5xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, \ + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + if (sport_handle != NULL) + runtime->private_data = sport_handle; + else { + pr_err("sport_handle is NULL\n"); + return -1; + } + return 0; + + out: + return ret; +} + +static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count) +{ + unsigned int *src; + unsigned int *dst; + int i; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + src = (unsigned int *)buf; + dst = (unsigned int *)substream->runtime->dma_area; + + dst += pos * 8; + while (count--) { + for (i = 0; i < substream->runtime->channels; i++) + *(dst + i) = *src++; + dst += 8; + } + } else { + src = (unsigned int *)substream->runtime->dma_area; + dst = (unsigned int *)buf; + + src += pos * 8; + while (count--) { + for (i = 0; i < substream->runtime->channels; i++) + *dst++ = *(src+i); + src += 8; + } + } + + return 0; +} + +static int bf5xx_pcm_silence(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count) +{ + unsigned char *buf = substream->runtime->dma_area; + buf += pos * 8 * 4; + memset(buf, '\0', count * 8 * 4); + + return 0; +} + + +struct snd_pcm_ops bf5xx_pcm_tdm_ops = { + .open = bf5xx_pcm_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = bf5xx_pcm_hw_params, + .hw_free = bf5xx_pcm_hw_free, + .prepare = bf5xx_pcm_prepare, + .trigger = bf5xx_pcm_trigger, + .pointer = bf5xx_pcm_pointer, + .copy = bf5xx_pcm_copy, + .silence = bf5xx_pcm_silence, +}; + +static int bf5xx_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 = bf5xx_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_coherent(pcm->card->dev, size * 4, + &buf->addr, GFP_KERNEL); + if (!buf->area) { + pr_err("Failed to allocate dma memory \ + Please increase uncached DMA memory region\n"); + return -ENOMEM; + } + buf->bytes = size; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_handle->tx_buf = buf->area; + else + sport_handle->rx_buf = buf->area; + + return 0; +} + +static void bf5xx_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_coherent(NULL, buf->bytes, buf->area, 0); + buf->area = NULL; + } + if (sport_handle) + sport_done(sport_handle); +} + +static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK; + +int bf5xx_pcm_tdm_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 = &bf5xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + if (dai->playback.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +struct snd_soc_platform bf5xx_tdm_soc_platform = { + .name = "bf5xx-audio", + .pcm_ops = &bf5xx_pcm_tdm_ops, + .pcm_new = bf5xx_pcm_tdm_new, + .pcm_free = bf5xx_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(bf5xx_tdm_soc_platform); + +static int __init bfin_tdm_init(void) +{ + return snd_soc_register_platform(&bf5xx_tdm_soc_platform); +} +module_init(bfin_tdm_init); + +static void __exit bfin_tdm_exit(void) +{ + snd_soc_unregister_platform(&bf5xx_tdm_soc_platform); +} +module_exit(bfin_tdm_exit); + +MODULE_AUTHOR("Barry Song"); +MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.h b/sound/soc/blackfin/bf5xx-tdm-pcm.h new file mode 100644 index 0000000..493e6df --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.h @@ -0,0 +1,21 @@ +/* + * linux/sound/arm/bf5xx-tdm-pcm.h -- ALSA PCM interface for the Blackfin + * + * Copyright 2009 Analog Device Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BF5XX_TDM_PCM_H +#define _BF5XX_TDM_PCM_H + +struct bf5xx_pcm_dma_params { + char *name; /* stream identifier */ +}; + +/* platform data */ +extern struct snd_soc_platform bf5xx_tdm_soc_platform; + +#endif diff --git a/sound/soc/blackfin/bf5xx-tdm.c b/sound/soc/blackfin/bf5xx-tdm.c new file mode 100644 index 0000000..4cfce6d --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm.c @@ -0,0 +1,313 @@ +/* + * File: sound/soc/blackfin/bf5xx-tdm.c + * Author: Barry Song Barry.Song@analog.com + * + * Created: Thurs June 04 2009 + * Description: Blackfin I2S(TDM) CPU DAI driver + * Even though TDM mode can be as part of I2S DAI, but there + * are so much difference in configuration and data flow, + * it's very ugly to integrate I2S and TDM into a module + * + * Modified: + * Copyright 2009 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/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <asm/irq.h> +#include <asm/portmux.h> +#include <linux/mutex.h> +#include <linux/gpio.h> + +#include "bf5xx-sport.h" +#include "bf5xx-tdm.h" + +struct bf5xx_tdm_port { + u16 tcr1; + u16 rcr1; + u16 tcr2; + u16 rcr2; + int configured; +}; + +static struct bf5xx_tdm_port bf5xx_tdm; +static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; + +static struct sport_param sport_params[2] = { + { + .dma_rx_chan = CH_SPORT0_RX, + .dma_tx_chan = CH_SPORT0_TX, + .err_irq = IRQ_SPORT0_ERROR, + .regs = (struct sport_register *)SPORT0_TCR1, + }, + { + .dma_rx_chan = CH_SPORT1_RX, + .dma_tx_chan = CH_SPORT1_TX, + .err_irq = IRQ_SPORT1_ERROR, + .regs = (struct sport_register *)SPORT1_TCR1, + } +}; + +/* + * Setting the TFS pin selector for SPORT 0 based on whether the selected + * port id F or G. If the port is F then no conflict should exist for the + * TFS. When Port G is selected and EMAC then there is a conflict between + * the PHY interrupt line and TFS. Current settings prevent the conflict + * by ignoring the TFS pin when Port G is selected. This allows both + * ssm2602 using Port G and EMAC concurrently. + */ +#ifdef CONFIG_BF527_SPORT0_PORTF +#define LOCAL_SPORT0_TFS (P_SPORT0_TFS) +#else +#define LOCAL_SPORT0_TFS (0) +#endif + +static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, + P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0}, + {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI, + P_SPORT1_RSCLK, P_SPORT1_TFS, 0} }; + +static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + int ret = 0; + + /* interface format:support TDM,slave mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + break; + default: + printk(KERN_ERR "%s: Unknown DAI format type\n", __func__); + ret = -EINVAL; + break; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + ret = -EINVAL; + break; + default: + printk(KERN_ERR "%s: Unknown DAI master type\n", __func__); + ret = -EINVAL; + break; + } + + return ret; +} + +static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret = 0; + + bf5xx_tdm.tcr2 &= ~0x1f; + bf5xx_tdm.rcr2 &= ~0x1f; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S32_LE: + bf5xx_tdm.tcr2 |= 31; + bf5xx_tdm.rcr2 |= 31; + sport_handle->wdsize = 4; + break; + } + + if (!bf5xx_tdm.configured) { + /* + * TX and RX are not independent,they are enabled at the + * same time, even if only one side is running. So, we + * need to configure both of them at the time when the first + * stream is opened. + * + * CPU DAI:slave mode. + */ + ret = sport_config_rx(sport_handle, bf5xx_tdm.rcr1, + bf5xx_tdm.rcr2, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, bf5xx_tdm.tcr1, + bf5xx_tdm.tcr2, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + bf5xx_tdm.configured = 1; + } + + return 0; +} + +static int bf5xx_tdm_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + int ret = 0; + + if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) { + pr_err("Requesting Peripherals failed\n"); + return -EFAULT; + } + + /* request DMA for SPORT */ + sport_handle = sport_init(&sport_params[sport_num], 4, \ + 8 * sizeof(u32), NULL); + if (!sport_handle) { + peripheral_free_list(&sport_req[sport_num][0]); + return -ENODEV; + } + + /* SPORT works in TDM mode */ + ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + goto sport_config_err; + } + + ret = sport_config_rx(sport_handle, IRFS, 0x1F, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + goto sport_config_err; + } + + ret = sport_config_tx(sport_handle, ITFS, 0x1F, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + goto sport_config_err; + } + +sport_config_err: + peripheral_free_list(&sport_req[sport_num][0]); + return ret; +} + +static void bf5xx_tdm_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + peripheral_free_list(&sport_req[sport_num][0]); +} + +#ifdef CONFIG_PM +static int bf5xx_tdm_suspend(struct snd_soc_dai *dai) +{ + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + if (!dai->active) + return 0; + if (dai->capture.active) + sport_rx_stop(sport); + if (dai->playback.active) + sport_tx_stop(sport); + return 0; +} + +static int bf5xx_tdm_resume(struct snd_soc_dai *dai) +{ + int ret; + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + if (!dai->active) + return 0; + + ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + } + + ret = sport_config_rx(sport_handle, IRFS, 0x1F, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + } + + ret = sport_config_tx(sport_handle, ITFS, 0x1F, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + } + + return 0; +} + +#else +#define bf5xx_tdm_suspend NULL +#define bf5xx_tdm_resume NULL +#endif + +static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = { + .hw_params = bf5xx_tdm_hw_params, + .set_fmt = bf5xx_tdm_set_dai_fmt, +}; + +struct snd_soc_dai bf5xx_tdm_dai = { + .name = "bf5xx-tdm", + .id = 0, + .probe = bf5xx_tdm_probe, + .remove = bf5xx_tdm_remove, + .suspend = bf5xx_tdm_suspend, + .resume = bf5xx_tdm_resume, + .playback = { + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .capture = { + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .ops = &bf5xx_tdm_dai_ops, +}; +EXPORT_SYMBOL_GPL(bf5xx_tdm_dai); + +static int __init bfin_tdm_init(void) +{ + return snd_soc_register_dai(&bf5xx_tdm_dai); +} +module_init(bfin_tdm_init); + +static void __exit bfin_tdm_exit(void) +{ + snd_soc_unregister_dai(&bf5xx_tdm_dai); +} +module_exit(bfin_tdm_exit); + +/* Module information */ +MODULE_AUTHOR("Barry Song"); +MODULE_DESCRIPTION("TDM driver for ADI Blackfin"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/blackfin/bf5xx-tdm.h b/sound/soc/blackfin/bf5xx-tdm.h new file mode 100644 index 0000000..e5157cd --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm.h @@ -0,0 +1,14 @@ +/* + * linux/sound/arm/bf5xx-i2s.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 _BF5XX_TDM_H +#define _BF5XX_TDM_H + +extern struct snd_soc_dai bf5xx_tdm_dai; + +#endif
On Thu, Jul 16, 2009 at 04:00:06PM +0800, Barry Song wrote:
Signed-off-by: Barry Song 21cnbao@gmail.com Even though TDM mode can be as part of I2S DAI, but there are so much difference in configuration and data flow, it's very ugly to integrate I2S and TDM into a module.
It'll be a day or two before I have the bandwidth to properly review the code here but one thing that springs to mind reading the commit message: is the 2 channel I2S case not just a special case of the TDM mode? Is it just that the extra work required for being able to handle TDM has a big runtime cost in buffer allocation or something and so treating it as a special case of TDM introduces unacceptable overheads. I'd just like to better understand what the problems are here, I'm perfectly willing to believe that this is a sensible solution.
On Thu, Jul 16, 2009 at 04:00:06PM +0800, Barry Song wrote:
Signed-off-by: Barry Song 21cnbao@gmail.com Even though TDM mode can be as part of I2S DAI, but there are so much difference in configuration and data flow, it's very ugly to integrate I2S and TDM into a module.
This is broadly fine, though there are some issues below.
+config SND_BF5XX_SOC_AD1938
- tristate "SoC AD1938 Audio support for Blackfin"
- depends on SND_BF5XX_TDM
- select SND_BF5XX_SOC_TDM
- select SND_SOC_AD1938
- help
Say Y if you want to add support for AD1938 codec on Blackfin.
This should be in the following patch.
+static void bf5xx_dma_irq(void *data) +{
- struct snd_pcm_substream *pcm = data;
- snd_pcm_period_elapsed(pcm);
+}
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
- .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_RESUME),
There are quite a few checkpatch issues such as this in the patch.
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct sport_device *sport = runtime->private_data;
- int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size);
- fragsize_bytes /= runtime->channels;
- fragsize_bytes *= 8;/* inflate the fragsize to match */
This could use some clarification - what is being matched here?
- snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
- ret = snd_pcm_hw_constraint_integer(runtime, \
SNDRV_PCM_HW_PARAM_PERIODS);
There's no need to use a '' here.
+static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
+{
- unsigned int *src;
- unsigned int *dst;
- int i;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
src = (unsigned int *)buf;
dst = (unsigned int *)substream->runtime->dma_area;
You shouldn't need to cast away from void.
+static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- int ret = 0;
- bf5xx_tdm.tcr2 &= ~0x1f;
- bf5xx_tdm.rcr2 &= ~0x1f;
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S32_LE:
bf5xx_tdm.tcr2 |= 31;
bf5xx_tdm.rcr2 |= 31;
sport_handle->wdsize = 4;
break;
- }
This ought to error out if it gets another format (though constraints should prevent that).
- if (!bf5xx_tdm.configured) {
/*
* TX and RX are not independent,they are enabled at the
* same time, even if only one side is running. So, we
* need to configure both of them at the time when the first
* stream is opened.
*
* CPU DAI:slave mode.
I can't see anywhere in the code where the configured flag is cleared. If there's only one configuration that is supported you may as well just set this up once on init rather than having this.
+static int __init bfin_tdm_init(void) +{
- return snd_soc_register_dai(&bf5xx_tdm_dai);
+} +module_init(bfin_tdm_init);
+static void __exit bfin_tdm_exit(void) +{
- snd_soc_unregister_dai(&bf5xx_tdm_dai);
+} +module_exit(bfin_tdm_exit);
This should be converted to use a platform device to probe the DAI, with the DAI being registered in the probe() function of the platform device. Most of the code in the existing probe() function to acquire resources should also be merged into that probe function.
Doing this will be required at some point in the future since it is needed to support some newer functionality such as better integration with suspend and resume.
Signed-off-by: Barry Song 21cnbao@gmail.com --- sound/soc/blackfin/bf5xx-ad1938.c | 142 +++++++++++++++++++++++++++++++++++++ 1 files changed, 142 insertions(+), 0 deletions(-) create mode 100644 sound/soc/blackfin/bf5xx-ad1938.c
diff --git a/sound/soc/blackfin/bf5xx-ad1938.c b/sound/soc/blackfin/bf5xx-ad1938.c new file mode 100644 index 0000000..76948eb --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ad1938.c @@ -0,0 +1,142 @@ +/* + * File: sound/soc/blackfin/bf5xx-ad1938.c + * Author: Barry Song Barry.Song@analog.com + * + * Created: Thur June 4 2009 + * Description: Board driver for ad1938 sound chip + * + * 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/blackfin.h> +#include <asm/cacheflush.h> +#include <asm/irq.h> +#include <asm/dma.h> +#include <asm/portmux.h> + +#include "../codecs/ad1938.h" +#include "bf5xx-sport.h" + +#include "bf5xx-tdm-pcm.h" +#include "bf5xx-tdm.h" + +static struct snd_soc_card bf5xx_ad1938; + +static int bf5xx_ad1938_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + cpu_dai->private_data = sport_handle; + return 0; +} + +static int bf5xx_ad1938_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 *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + int ret = 0; + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set codec DAI slots */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 8); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops bf5xx_ad1938_ops = { + .startup = bf5xx_ad1938_startup, + .hw_params = bf5xx_ad1938_hw_params, +}; + +static struct snd_soc_dai_link bf5xx_ad1938_dai = { + .name = "ad1938", + .stream_name = "AD1938", + .cpu_dai = &bf5xx_tdm_dai, + .codec_dai = &ad1938_dai, + .ops = &bf5xx_ad1938_ops, +}; + +static struct snd_soc_card bf5xx_ad1938 = { + .name = "bf5xx_ad1938", + .platform = &bf5xx_tdm_soc_platform, + .dai_link = &bf5xx_ad1938_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_ad1938_snd_devdata = { + .card = &bf5xx_ad1938, + .codec_dev = &soc_codec_dev_ad1938, +}; + +static struct platform_device *bfxx_ad1938_snd_device; + +static int __init bf5xx_ad1938_init(void) +{ + int ret; + + bfxx_ad1938_snd_device = platform_device_alloc("soc-audio", -1); + if (!bfxx_ad1938_snd_device) + return -ENOMEM; + + platform_set_drvdata(bfxx_ad1938_snd_device, &bf5xx_ad1938_snd_devdata); + bf5xx_ad1938_snd_devdata.dev = &bfxx_ad1938_snd_device->dev; + ret = platform_device_add(bfxx_ad1938_snd_device); + + if (ret) + platform_device_put(bfxx_ad1938_snd_device); + + return ret; +} + +static void __exit bf5xx_ad1938_exit(void) +{ + platform_device_unregister(bfxx_ad1938_snd_device); +} + +module_init(bf5xx_ad1938_init); +module_exit(bf5xx_ad1938_exit); + +/* Module information */ +MODULE_AUTHOR("Barry Song"); +MODULE_DESCRIPTION("ALSA SoC AD1938 board driver"); +MODULE_LICENSE("GPL"); +
On Thu, Jul 16, 2009 at 04:00:07PM +0800, Barry Song wrote:
- /* set codec DAI slots */
- ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 8);
- if (ret < 0)
return ret;
That mask looks wrong - it's saying not to use any slots at all.
Otherwise this looks good.
participants (2)
-
Barry Song
-
Mark Brown