[alsa-devel] [PATCH V4 0/5] Adding ASoC drivers for SPEAr13XX platform
This patchset contains five patches which add support for ASoC sound drivers on ST's SPEAr13XX platform. Details of the SPEAr13XX platforms can be seen here: http://www.st.com/internet/mcu/product/250658.jsp
The ARCH and PLATFORM specific code for SPEAr13XX are already under review in Russell King's ARM mailing list(Linux-kernel-version 2.6.38-rc4).
This patchset has been tested for linux-kernel-version 2.6.38-rc4 and on ARM platform.
Please review the same and consider for mainline inclusion.
Changes in machine driver code since V3 1.Use of snd_soc_register_card() rather than using the soc-audio device. Changes in codec code since V3: 1. use standard cache sync implementation i.e. snd_soc_cache_sync() Changes in spear Kconfig and Makefile since V2: 1.giving all the the modules snd-soc- prefixes to their names Changes in codec code since V2: 1. use SOC_DOUBLE for volume control 2. remove default configuration for codec .offer them as runtime controls. 3. restore the register cache in resume changes in platform code since V1: 1. Spilt machine driver from platform driver. 2. Updated rate supported. 3. Moved clock specific part from "spear13xx_i2s_set_dai_sysclk". function to platform code. This function is removed now. 4. Function "spear13xx_i2s_startup" removed. 5. Function "spear13xx_i2s_shutdown" removed.
Changes in codec code since V1: 1. Removed sta529 version number. 2. Removed "sta529_read_reg_cache". 3. Removed function "sta529_write_reg_cache". 4. Removed function "sta529_write". 5. Moved sta529.h file contents to sta529.c file. 6. Move clock specific part from "spear_sta_set_dai_sysclk". function to platform code. This function is removed now. 7. Renamed function names from spear_sta_* to sta_*. 8. Modified probe() function to avoid rewriting default chip values to hardware.
Rajeev Kumar (5): sound: asoc: Adding support for STA529 Audio Codec sound: asoc: Adding support for SPEAr13XX ASoC platform driver sound: asoc: Adding support for SPEAr13XX ASoC machine driver sound: asoc: Adding Kconfig and Makefile to support SPEAr13XX ASoC driver sound: asoc: Adding support for SPEAr13XX in soc
sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sta529.c | 374 ++++++++++++++++++++++++++++ sound/soc/spear/Kconfig | 19 ++ sound/soc/spear/Makefile | 12 + sound/soc/spear/evb_sta529.c | 121 +++++++++ sound/soc/spear/spear13xx-i2s.c | 524 +++++++++++++++++++++++++++++++++++++++ sound/soc/spear/spear13xx-i2s.h | 19 ++ sound/soc/spear/spear13xx-pcm.c | 500 +++++++++++++++++++++++++++++++++++++ sound/soc/spear/spear13xx-pcm.h | 50 ++++ 12 files changed, 1628 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c create mode 100644 sound/soc/spear/Kconfig create mode 100644 sound/soc/spear/Makefile create mode 100644 sound/soc/spear/evb_sta529.c create mode 100644 sound/soc/spear/spear13xx-i2s.c create mode 100644 sound/soc/spear/spear13xx-i2s.h create mode 100644 sound/soc/spear/spear13xx-pcm.c create mode 100644 sound/soc/spear/spear13xx-pcm.h
This patch adds the support for STA529 audio codec. Details of the audio codec can be seen here: http://www.st.com/internet/imag_video/product/159187.jsp
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com --- sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sta529.c | 374 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 381 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 98175a0..c5c65ba 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -42,6 +42,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_SN95031 if INTEL_SCU_IPC select SND_SOC_SPDIF select SND_SOC_SSM2602 if SND_SOC_I2C_AND_SPI + select SND_SOC_STA529 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER @@ -216,6 +217,10 @@ config SND_SOC_SPDIF config SND_SOC_SSM2602 tristate
+config SND_SOC_STA529 + tristate + depends on I2C + config SND_SOC_STAC9766 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fd85584..cb0060a 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -28,6 +28,7 @@ snd-soc-alc5623-objs := alc5623.o snd-soc-sn95031-objs := sn95031.o snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o +snd-soc-sta529-objs := sta529.o snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o @@ -120,6 +121,7 @@ obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c new file mode 100644 index 0000000..e9aac26 --- /dev/null +++ b/sound/soc/codecs/sta529.c @@ -0,0 +1,374 @@ +/* + * ASoC codec driver for spear platform + * + * sound/soc/codecs/sta529.c -- spear ALSA Soc codec driver + * + * Copyright (C) 2011 ST Microelectronics + * Rajeev Kumar rajeev-dlh.kumar@st.com + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> + +/* STA529 Register offsets */ +#define STA529_FFXCFG0 0x00 +#define STA529_FFXCFG1 0x01 +#define STA529_MVOL 0x02 +#define STA529_LVOL 0x03 +#define STA529_RVOL 0x04 +#define STA529_TTF0 0x05 +#define STA529_TTF1 0x06 +#define STA529_TTP0 0x07 +#define STA529_TTP1 0x08 +#define STA529_S2PCFG0 0x0A +#define STA529_S2PCFG1 0x0B +#define STA529_P2SCFG0 0x0C +#define STA529_P2SCFG1 0x0D +#define STA529_PLLCFG0 0x14 +#define STA529_PLLCFG1 0x15 +#define STA529_PLLCFG2 0x16 +#define STA529_PLLCFG3 0x17 +#define STA529_PLLPFE 0x18 +#define STA529_PLLST 0x19 +#define STA529_ADCCFG 0x1E /*mic_select*/ +#define STA529_CKOCFG 0x1F +#define STA529_MISC 0x20 +#define STA529_PADST0 0x21 +#define STA529_PADST1 0x22 +#define STA529_FFXST 0x23 +#define STA529_PWMIN1 0x2D +#define STA529_PWMIN2 0x2E +#define STA529_POWST 0x32 + +#define STA529_CACHEREGNUM 0x33 /*total num of reg. in sta529*/ + +#define STA529_RATES SNDRV_PCM_RATE_48000 +#define STA529_FORMAT SNDRV_PCM_FMTBIT_S16_LE +#define S2PC_VALUE 0x98 +#define CLOCK_OUT 0x60 +#define LEFT_J_DATA_FORMAT 0x10 +#define I2S_DATA_FORMAT 0x12 +#define RIGHT_J_DATA_FORMAT 0x14 +#define CODEC_MUTE_VAL 0x80 + +#define POWER_CNTLMSAK 0x40 +#define POWER_STDBY 0x40 +#define FFX_MASK 0x80 +#define FFX_OFF 0x80 +#define POWER_UP 0x00 + +static const u8 sta529_reg[STA529_CACHEREGNUM] = { + 0x75, 0xf8, 0x50, 0x00, + 0x00, 0x00, 0x02, 0x00, + 0x02, 0x02, 0xD2, 0x91, + 0xD3, 0x91, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x52, 0x40, + 0x21, 0xef, 0x04, 0x06, + 0x41, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, +}; + +struct sta529 { + unsigned int sysclk; + enum snd_soc_control_type control_type; + void *control_data; +}; + +static const char *pwm_mode_text[] = { "binary", "headphone", "ternary", + "phase-shift"}; +static const char *interface_mode_text[] = { "slave", "master"}; + +static const struct soc_enum pwm_src_enum = +SOC_ENUM_SINGLE(STA529_FFXCFG1, 4, 4, pwm_mode_text); + +static const struct soc_enum mode_src_enum = +SOC_ENUM_SINGLE(STA529_P2SCFG0, 0, 2, interface_mode_text); + +static const struct snd_kcontrol_new sta529_new_snd_controls[] = { + SOC_ENUM("PWM Select", pwm_src_enum), + SOC_ENUM("MODE Select", mode_src_enum), +}; + +static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -9150, 50, 0); +static const DECLARE_TLV_DB_SCALE(master_vol_tlv, -12750, 50, 0); + +static const struct snd_kcontrol_new sta529_snd_controls[] = { + SOC_DOUBLE_R_TLV("Digital Playback Volume", STA529_LVOL, STA529_RVOL, 0, + 127, 0, out_gain_tlv), + SOC_SINGLE_TLV("Master Playback Volume", STA529_MVOL, 0, 127, 1, + master_vol_tlv), +}; + +static int sta529_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + int pdata = 0; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + pdata = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + pdata = 2; + break; + case SNDRV_PCM_FORMAT_S32_LE: + pdata = 3; + break; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_update_bits(codec, STA529_S2PCFG1, 0xC0, (pdata << 6)); + else + snd_soc_update_bits(codec, STA529_P2SCFG1, 0xC0, (pdata << 6)); + + return 0; +} + +static int sta529_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 mode = 0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + mode = LEFT_J_DATA_FORMAT; + break; + case SND_SOC_DAIFMT_I2S: + mode = I2S_DATA_FORMAT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + mode = RIGHT_J_DATA_FORMAT; + break; + default: + return -EINVAL; + } + mode |= 0x20; + snd_soc_update_bits(codec, STA529_S2PCFG0, 0xE0, mode); + + return 0; +} + +static int sta529_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + + u8 mute_reg = snd_soc_read(codec, STA529_FFXCFG0) & ~CODEC_MUTE_VAL; + + if (mute) + mute_reg |= CODEC_MUTE_VAL; + + snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, 00); + + return 0; +} + +static int +sta529_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK, + POWER_UP); + snd_soc_update_bits(codec, STA529_MISC, 1, 0x01); + break; + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK, + POWER_STDBY); + /* Making FFX output to zero */ + snd_soc_update_bits(codec, STA529_FFXCFG0, FFX_MASK, + FFX_OFF); + snd_soc_update_bits(codec, STA529_MISC, 1, 0x00); + + break; + } + + /* + * store the label for powers down audio subsystem for suspend.This is + * used by soc core layer + */ + codec->dapm.bias_level = level; + return 0; + +} + +static struct snd_soc_dai_ops sta529_dai_ops = { + .hw_params = sta529_hw_params, + .set_fmt = sta529_set_dai_fmt, + .digital_mute = sta529_mute, +}; + +static struct snd_soc_dai_driver sta529_dai = { + .name = "sta529-audio", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STA529_RATES, + .formats = STA529_FORMAT, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = STA529_RATES, + .formats = STA529_FORMAT, + }, + .ops = &sta529_dai_ops, +}; + +static int sta529_probe(struct snd_soc_codec *codec) +{ + struct sta529 *sta529 = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, sta529->control_type); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + snd_soc_add_controls(codec, sta529_snd_controls, + ARRAY_SIZE(sta529_snd_controls)); + + snd_soc_add_controls(codec, sta529_new_snd_controls, + ARRAY_SIZE(sta529_new_snd_controls)); + return 0; +} + +/* power down chip */ +static int sta529_remove(struct snd_soc_codec *codec) +{ + sta529_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int sta529_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + sta529_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int sta529_resume(struct snd_soc_codec *codec) +{ + snd_soc_cache_sync(codec); + sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + sta529_set_bias_level(codec, codec->dapm.suspend_bias_level); + + return 0; +} + +struct snd_soc_codec_driver soc_codec_dev_sta529 = { + .probe = sta529_probe, + .remove = sta529_remove, + .set_bias_level = sta529_set_bias_level, + .suspend = sta529_suspend, + .resume = sta529_resume, + .reg_cache_size = STA529_CACHEREGNUM, + .reg_word_size = sizeof(u8), + .reg_cache_default = sta529_reg, + +}; + +static __devinit int sta529_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct sta529 *sta529; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + + sta529 = kzalloc(sizeof(struct sta529), GFP_KERNEL); + if (sta529 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, sta529); + sta529->control_data = i2c; + sta529->control_type = SND_SOC_I2C; + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_sta529, &sta529_dai, 1); + if (ret < 0) + kfree(sta529); + return ret; +} + +static int sta529_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id sta529_i2c_id[] = { + { "sta529", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sta529_i2c_id); + +static struct i2c_driver sta529_i2c_driver = { + .driver = { + .name = "sta529", + .owner = THIS_MODULE, + }, + .probe = sta529_i2c_probe, + .remove = __devexit_p(sta529_i2c_remove), + .id_table = sta529_i2c_id, +}; + +static int __init sta529_modinit(void) +{ + int ret = 0; + + ret = i2c_add_driver(&sta529_i2c_driver); + if (ret != 0) + printk(KERN_ERR "Failed to reg sta529 I2C driver: %d\n", ret); + + return ret; + +} +module_init(sta529_modinit); + +static void __exit sta529_exit(void) +{ + i2c_del_driver(&sta529_i2c_driver); +} +module_exit(sta529_exit); + +MODULE_DESCRIPTION("ASoC STA529 codec driver"); +MODULE_AUTHOR("Rajeev Kumar rajeev-dlh.kumar@st.com"); +MODULE_LICENSE("GPL");
This patch adds the support for the SPEAr13XX Platform Driver (I2S based) as per the ASoC framework.
SPEAr13XX internally uses a Designware I2S IP and some glue logic on top of the same.
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com --- sound/soc/spear/spear13xx-i2s.c | 524 +++++++++++++++++++++++++++++++++++++++ sound/soc/spear/spear13xx-i2s.h | 19 ++ sound/soc/spear/spear13xx-pcm.c | 500 +++++++++++++++++++++++++++++++++++++ sound/soc/spear/spear13xx-pcm.h | 50 ++++ 4 files changed, 1093 insertions(+), 0 deletions(-) create mode 100644 sound/soc/spear/spear13xx-i2s.c create mode 100644 sound/soc/spear/spear13xx-i2s.h create mode 100644 sound/soc/spear/spear13xx-pcm.c create mode 100644 sound/soc/spear/spear13xx-pcm.h
diff --git a/sound/soc/spear/spear13xx-i2s.c b/sound/soc/spear/spear13xx-i2s.c new file mode 100644 index 0000000..a551110 --- /dev/null +++ b/sound/soc/spear/spear13xx-i2s.c @@ -0,0 +1,524 @@ +/* + * ALSA SoC I2S Audio Layer for ST spear13xx processor + * + * sound/soc/spear/spear13xx-i2s.c + * + * Copyright (C) 2011 ST Microelectronics + * Rajeev Kumar rajeev-dlh.kumar@st.com + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <mach/misc_regs.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include "spear13xx-pcm.h" +#include "spear13xx-i2s.h" + +/* common register for all channel */ +#define IER 0x000 +#define IRER 0x004 +#define ITER 0x008 +#define CER 0x00C +#define CCR 0x010 +#define RXFFR 0x014 +#define TXFFR 0x018 + +/* I2STxRxRegisters for channel 0 */ +#define LRBR0_LTHR0 0x020 +#define RRBR0_RTHR0 0x024 +#define RER0 0x028 +#define TER0 0x02C +#define RCR0 0x030 +#define TCR0 0x034 +#define ISR0 0x038 +#define IMR0 0x03C +#define ROR0 0x040 +#define TOR0 0x044 +#define RFCR0 0x048 +#define TFCR0 0x04C +#define RFF0 0x050 +#define TFF0 0x054 + +/* I2STxRxRegisters for channel 1 */ +#define LRBR1_LTHR1 0x060 +#define RRBR1_RTHR1 0x064 +#define RER1 0x068 +#define TER1 0x06C +#define RCR1 0x070 +#define TCR1 0x074 +#define ISR1 0x078 +#define IMR1 0x07C +#define ROR1 0x080 +#define TOR1 0x084 +#define RFCR1 0x088 +#define TFCR1 0x08C +#define RFF1 0x090 +#define TFF1 0x094 + +/* I2STxRxRegisters for channel 2 */ +#define LRBR2_LTHR2 0x0A0 +#define RRBR2_RTHR2 0x0A4 +#define RER2 0x0A8 +#define TER2 0x0AC +#define RCR2 0x0B0 +#define TCR2 0x0B4 +#define ISR2 0x0B8 +#define IMR2 0x0BC +#define ROR2 0x0C0 +#define TOR2 0x0C4 +#define RFCR2 0x0C8 +#define TFCR2 0x0CC +#define RFF2 0x0D0 +#define TFF2 0x0D4 + +/* I2STxRxRegisters for channel 3*/ +#define LRBR3_LTHR3 0x0E0 +#define RRBR3_RTHR3 0x0E4 +#define RER3 0x0E8 +#define TER3 0x0EC +#define RCR3 0x0F0 +#define TCR3 0x0F4 +#define ISR3 0x0F8 +#define IMR3 0x0FC +#define ROR3 0x100 +#define TOR3 0x104 +#define RFCR3 0x108 +#define TFCR3 0x10C +#define RFF3 0x110 +#define TFF3 0x114 + +/* I2SDMARegisters */ +#define RXDMA 0x01C0 +#define RRXDMA 0x01C4 +#define TXDMA 0x01C8 +#define RTXDMA 0x01CC + +/* I2SCOMPRegisters */ +#define I2S_COMP_PARAM_2 0x01F0 +#define I2S_COMP_PARAM_1 0x01F4 +#define I2S_COMP_VERSION 0x01F8 +#define I2S_COMP_TYPE 0x01FC + +#define SPEAR13XX_I2S_RATES SNDRV_PCM_RATE_8000_96000 +#define SPEAR13XX_I2S_FORMAT SNDRV_PCM_FMTBIT_S16_LE +#define MAX_CHANNEL_NUM 2 +#define MIN_CHANNEL_NUM 2 + +struct spear13xx_i2s_dev { + void __iomem *i2s_base; + struct resource *res; + struct clk *clk; + int play_irq; + int mode; + int active; + int capture_irq; + struct device *dev; + struct spear13xx_pcm_dma_params *dma_params[2]; +}; + +void get_dma_start_addr(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct spear13xx_runtime_data *prtd = substream->runtime->private_data; + struct spear13xx_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + prtd->txdma = dev->res->start + TXDMA; + prtd->rxdma = dev->res->start + RXDMA; + + substream->runtime->private_data = prtd; +} + +static inline void i2s_write_reg(void *io_base, int reg, u32 val) +{ + writel(val, io_base + reg); +} + +static inline u32 i2s_read_reg(void *io_base, int reg) +{ + return readl(io_base + reg); +} + +static void i2s_start_play(struct spear13xx_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ + u32 val; /*dma mode slection*/ + + val = readl(PERIP_CFG); + val &= ~0xFFFFFFFC; + i2s_write_reg(dev->i2s_base, TER0, 0); + i2s_write_reg(dev->i2s_base, TER1, 0); + + /* for 2.0 audio*/ + if (dev->mode <= 2) { + if (!val) { + i2s_write_reg(dev->i2s_base, TCR0, 0x2); + i2s_write_reg(dev->i2s_base, TFCR0, 0x07); + i2s_write_reg(dev->i2s_base, IMR0, 0x00); + i2s_write_reg(dev->i2s_base, TER0, 1); + } else { + i2s_write_reg(dev->i2s_base, TCR1, 0x2); + i2s_write_reg(dev->i2s_base, TFCR1, 0x07); + i2s_write_reg(dev->i2s_base, IMR1, 0x00); + i2s_write_reg(dev->i2s_base, TER1, 1); + } + } else { /*audio 2.0 onwards */ + i2s_write_reg(dev->i2s_base, TCR0, 0x5); + i2s_write_reg(dev->i2s_base, TCR1, 0x5); + + i2s_write_reg(dev->i2s_base, TFCR0, 0x07); + i2s_write_reg(dev->i2s_base, TFCR1, 0x07); + i2s_write_reg(dev->i2s_base, IMR0, 0x00); + i2s_write_reg(dev->i2s_base, IMR1, 0x00); + i2s_write_reg(dev->i2s_base, TER0, 1); + i2s_write_reg(dev->i2s_base, TER1, 1); + } + + i2s_write_reg(dev->i2s_base, ITER, 1); +} + +static void i2s_start_rec(struct spear13xx_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ + u32 val; /*dma mode slection*/ + + val = readl(PERIP_CFG); + val &= ~0xFFFFFFFC; + i2s_write_reg(dev->i2s_base, RER0, 0); + i2s_write_reg(dev->i2s_base, RER1, 0); + + /* for 2.0 audio*/ + if (dev->mode <= 2) { + if (!val) { + i2s_write_reg(dev->i2s_base, RCR0, 0x2); + i2s_write_reg(dev->i2s_base, RFCR0, 0x07); + i2s_write_reg(dev->i2s_base, IMR0, 0x00); + i2s_write_reg(dev->i2s_base, RER0, 1); + } else { + i2s_write_reg(dev->i2s_base, RCR1, 0x2); + i2s_write_reg(dev->i2s_base, RFCR1, 0x07); + i2s_write_reg(dev->i2s_base, IMR1, 0x00); + i2s_write_reg(dev->i2s_base, TER1, 1); + } + } else { /*audio 2.0 onwards */ + i2s_write_reg(dev->i2s_base, RCR0, 0x5); + i2s_write_reg(dev->i2s_base, RCR1, 0x5); + + i2s_write_reg(dev->i2s_base, RFCR0, 0x07); + i2s_write_reg(dev->i2s_base, RFCR1, 0x07); + i2s_write_reg(dev->i2s_base, IMR0, 0x00); + i2s_write_reg(dev->i2s_base, IMR1, 0x00); + i2s_write_reg(dev->i2s_base, RER0, 1); + i2s_write_reg(dev->i2s_base, RER1, 1); + } + + i2s_write_reg(dev->i2s_base, IRER, 1); +} + +static void i2s_stop(struct spear13xx_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(dev->i2s_base, ITER, 0); + i2s_write_reg(dev->i2s_base, ITER, 1); + i2s_write_reg(dev->i2s_base, IMR0, 0x30); + i2s_write_reg(dev->i2s_base, IMR1, 0x30); + } else { + i2s_write_reg(dev->i2s_base, IRER, 0); + i2s_write_reg(dev->i2s_base, IRER, 1); + i2s_write_reg(dev->i2s_base, IMR0, 0x03); + i2s_write_reg(dev->i2s_base, IMR1, 0x03); + } + if (!dev->active--) { + i2s_write_reg(dev->i2s_base, CER, 0); + i2s_write_reg(dev->i2s_base, IER, 0); + } + +} + +static irqreturn_t i2s_play_irq(int irq, void *_dev) +{ + struct spear13xx_i2s_dev *dev = (struct spear13xx_i2s_dev *)_dev; + u32 ch0, ch1; + + /* check for the tx data overrun condition */ + dev_info(dev->dev, "data overrun condition for TX channel\n"); + ch0 = i2s_read_reg(dev->i2s_base, ISR0) & 0x20; + ch1 = i2s_read_reg(dev->i2s_base, ISR1) & 0x20; + if (ch0 || ch1) { + + /* disable tx block */ + i2s_write_reg(dev->i2s_base, ITER, 0); + + /* flush all the tx fifo */ + i2s_write_reg(dev->i2s_base, TXFFR, 1); + + /* clear tx data overrun interrupt: channel 0 */ + i2s_read_reg(dev->i2s_base, TOR0); + + /* clear tx data overrun interrupt: channel 1 */ + i2s_read_reg(dev->i2s_base, TOR1); + + } + + return IRQ_HANDLED; +} + +static irqreturn_t i2s_capture_irq(int irq, void *_dev) +{ + struct spear13xx_i2s_dev *dev = (struct spear13xx_i2s_dev *)_dev; + u32 ch0, ch1; + + /* check for the rx data overrun condition */ + dev_info(dev->dev, "data overrun condition for RX channel\n"); + ch0 = i2s_read_reg(dev->i2s_base, ISR0) & 0x02; + ch1 = i2s_read_reg(dev->i2s_base, ISR1) & 0x02; + if (ch0 || ch1) { + + /* disable rx block */ + i2s_write_reg(dev->i2s_base, IRER, 0); + + /* flush all the rx fifo */ + i2s_write_reg(dev->i2s_base, RXFFR, 1); + + /* clear rx data overrun interrupt: channel 0 */ + i2s_read_reg(dev->i2s_base, ROR0); + + /* clear rx data overrun interrupt: channel 1 */ + i2s_read_reg(dev->i2s_base, ROR1); + + } + + return IRQ_HANDLED; +} + +static int spear13xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct spear13xx_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev->mode = params_channels(params); + + if (dev->mode <= 2) + i2s_write_reg(dev->i2s_base, CCR, 0x00); + else + i2s_write_reg(dev->i2s_base, CCR, 0x10); + + return 0; +} + +static int +spear13xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct spear13xx_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + i2s_write_reg(dev->i2s_base, IER, 1); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev->active++; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_start_play(dev, substream); + else + i2s_start_rec(dev, substream); + i2s_write_reg(dev->i2s_base, CER, 1); + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + i2s_stop(dev, substream); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static struct snd_soc_dai_ops spear13xx_i2s_dai_ops = { + .hw_params = spear13xx_i2s_hw_params, + .trigger = spear13xx_i2s_trigger, +}; + +struct snd_soc_dai_driver spear13xx_i2s_dai = { + .playback = { + .channels_min = MAX_CHANNEL_NUM, + .channels_max = MIN_CHANNEL_NUM, + .rates = SPEAR13XX_I2S_RATES, + .formats = SPEAR13XX_I2S_FORMAT, + }, + .capture = { + .channels_min = MAX_CHANNEL_NUM, + .channels_max = MIN_CHANNEL_NUM, + .rates = SPEAR13XX_I2S_RATES, + .formats = SPEAR13XX_I2S_FORMAT, + }, + .ops = &spear13xx_i2s_dai_ops, +}; + +static int spear13xx_i2s_probe(struct platform_device *pdev) +{ + struct spear13xx_i2s_dev *dev; + struct resource *res; + int ret; + + if (!pdev) { + dev_err(&pdev->dev, "Invalid platform device\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no i2s resource defined\n"); + return -ENODEV; + } + + if (!request_mem_region(res->start, resource_size(res), pdev->name)) { + dev_err(&pdev->dev, "i2s region already claimed\n"); + return -EBUSY; + } + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err_release_mem_region; + } + + dev->res = res; + + dev->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) { + ret = PTR_ERR(dev->clk); + goto err_kfree; + } + + ret = clk_enable(dev->clk); + if (ret < 0) + goto err_clk_put; + + dev->i2s_base = ioremap(res->start, resource_size(res)); + if (!dev->i2s_base) { + dev_err(&pdev->dev, "ioremap fail for i2s_region\n"); + ret = -ENOMEM; + goto err_clk_disable; + } + + dev->play_irq = platform_get_irq_byname(pdev, "play_irq"); + if (!dev->play_irq) { + dev_err(&pdev->dev, "play irq not defined\n"); + ret = -EBUSY; + goto err_iounmap_i2s; + } + ret = request_irq(dev->play_irq, i2s_play_irq, 0, "spear13xx-i2s", dev); + if (ret) { + dev_err(&pdev->dev, "play IRQ%d already claimed\n", + dev->play_irq); + goto err_iounmap_i2s; + } + + dev->capture_irq = platform_get_irq_byname(pdev, "record_irq"); + + if (!dev->capture_irq) { + dev_err(&pdev->dev, "record irq not defined\n"); + ret = -EBUSY; + goto err_free_play_irq; + } + + ret = request_irq(dev->capture_irq, i2s_capture_irq, 0, "spear13xx-i2s", + dev); + if (ret) { + dev_err(&pdev->dev, "capture IRQ%d already claimed\n", + dev->capture_irq); + goto err_free_play_irq; + } + + dev->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, dev); + + ret = snd_soc_register_dai(&pdev->dev, &spear13xx_i2s_dai); + if (ret != 0) + goto err_free_capture_irq; + + /* unmask i2s interrupt for channel 0 */ + i2s_write_reg(dev->i2s_base, IMR0, 0x00); + + /* unmask i2s interrupt for channel 1 */ + i2s_write_reg(dev->i2s_base, IMR1, 0x00); + + return 0; + +err_free_capture_irq: + dev_set_drvdata(&pdev->dev, NULL); + free_irq(dev->capture_irq, pdev); +err_free_play_irq: + free_irq(dev->play_irq, pdev); +err_iounmap_i2s: + iounmap(dev->i2s_base); +err_clk_disable: + clk_disable(dev->clk); +err_clk_put: + clk_put(dev->clk); +err_kfree: + kfree(dev); +err_release_mem_region: + release_mem_region(res->start, resource_size(res)); + return ret; +} + +static int spear13xx_i2s_remove(struct platform_device *pdev) +{ + struct spear13xx_i2s_dev *dev = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_dai(&pdev->dev); + free_irq(dev->capture_irq, pdev); + free_irq(dev->play_irq, pdev); + iounmap(dev->i2s_base); + clk_disable(dev->clk); + clk_put(dev->clk); + kfree(dev); + release_mem_region(dev->res->start, resource_size(dev->res)); + + return 0; +} + +static struct platform_driver spear13xx_i2s_driver = { + .probe = spear13xx_i2s_probe, + .remove = spear13xx_i2s_remove, + .driver = { + .name = "spear13xx-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init spear13xx_i2s_init(void) +{ + return platform_driver_register(&spear13xx_i2s_driver); +} +module_init(spear13xx_i2s_init); + +static void __exit spear13xx_i2s_exit(void) +{ + platform_driver_unregister(&spear13xx_i2s_driver); +} +module_exit(spear13xx_i2s_exit); + +MODULE_AUTHOR("Rajeev Kumar"); +MODULE_DESCRIPTION("SPEAr I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:spear13xx-i2s"); diff --git a/sound/soc/spear/spear13xx-i2s.h b/sound/soc/spear/spear13xx-i2s.h new file mode 100644 index 0000000..26e2ea2 --- /dev/null +++ b/sound/soc/spear/spear13xx-i2s.h @@ -0,0 +1,19 @@ +/* + * ALSA SoC I2S Audio Layer for ST spear13xx processor + * + * sound/soc/spear/spear13xx-i2s.h + * + * Copyright (C) 2011 ST Microelectronics + * Rajeev Kumar rajeev-dlh.kumar@st.com + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef SPEAR_I2S_H +#define SPEAR_I2S_H + +void get_dma_start_addr(struct snd_pcm_substream *substream); + +#endif /*end if i2s header file */ diff --git a/sound/soc/spear/spear13xx-pcm.c b/sound/soc/spear/spear13xx-pcm.c new file mode 100644 index 0000000..8746ef7 --- /dev/null +++ b/sound/soc/spear/spear13xx-pcm.c @@ -0,0 +1,500 @@ +/* + * ALSA PCM interface for ST spear Processor + * + * sound/soc/spear/spear13xx-pcm.c + * + * Copyright (C) 2011 ST Microelectronics + * Rajeev Kumar rajeev-dlh.kumar@st.com + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/dma.h> +#include "spear13xx-i2s.h" +#include "spear13xx-pcm.h" + +static u64 spear13xx_pcm_dmamask = 0xffffffff; +struct pcm_dma_data data; +#define MAX_DMA_CHAIN 2 + +struct snd_pcm_hardware spear13xx_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), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 16 * 1024, /* max buffer size */ + .period_bytes_min = 2 * 1024, /* 1 msec data minimum period size */ + .period_bytes_max = 2 * 1024, /* maximum period size */ + .periods_min = 1, /* min # periods */ + .periods_max = 8, /* max # of periods */ + .fifo_size = 0, /* fifo size in bytes */ +}; + +void pcm_init(struct device *dma_dev) +{ + + data.mem2i2s_slave.dma_dev = dma_dev; + data.i2s2mem_slave.dma_dev = dma_dev; + /* doing 16 bit audio transfer */ + data.mem2i2s_slave.reg_width = DW_DMA_SLAVE_WIDTH_16BIT; + data.mem2i2s_slave.cfg_hi = DWC_CFGH_DST_PER(DMA_REQ_I2S_TX); + data.mem2i2s_slave.cfg_lo = 0; + data.mem2i2s_slave.src_master = 1; + data.mem2i2s_slave.dst_master = 1; + data.mem2i2s_slave.src_msize = DW_DMA_MSIZE_16; + /* threshold for i2s is 7 */ + data.mem2i2s_slave.dst_msize = DW_DMA_MSIZE_16; + data.mem2i2s_slave.fc = DW_DMA_FC_D_M2P; + + data.i2s2mem_slave.reg_width = DW_DMA_SLAVE_WIDTH_16BIT; + data.i2s2mem_slave.cfg_hi = DWC_CFGH_SRC_PER(DMA_REQ_I2S_RX); + data.i2s2mem_slave.src_master = 1; + data.i2s2mem_slave.dst_master = 1; + data.i2s2mem_slave.src_msize = DW_DMA_MSIZE_16; + data.i2s2mem_slave.dst_msize = DW_DMA_MSIZE_16; + data.i2s2mem_slave.fc = DW_DMA_FC_D_P2M; + data.i2s2mem_slave.cfg_lo = 0; + +} + +static int spear13xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct spear13xx_runtime_data *prtd = runtime->private_data; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + prtd->substream = substream; + prtd->pos = 0; + return 0; +} + +static int spear13xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int spear13xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct spear13xx_runtime_data *prtd = runtime->private_data; + + prtd->dma_addr = runtime->dma_addr; + prtd->buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + prtd->period_bytes = snd_pcm_lib_period_bytes(substream); + + if (prtd->buffer_bytes == prtd->period_bytes) { + prtd->frag_bytes = prtd->period_bytes >> 1; + prtd->frags = 2; + } else { + prtd->frag_bytes = prtd->period_bytes; + prtd->frags = prtd->buffer_bytes / prtd->period_bytes; + } + prtd->frag_count = 0; + prtd->pos = 0; + return 0; +} + +static void spear13xx_dma_complete(void *arg) +{ + struct spear13xx_runtime_data *prtd = arg; + unsigned long flags; + + /* dma completion handler cannot submit new operations */ + spin_lock_irqsave(&prtd->lock, flags); + if (prtd->frag_count >= 0) { + prtd->dmacount--; + BUG_ON(prtd->dmacount < 0); + tasklet_schedule(&prtd->tasklet); + } + spin_unlock_irqrestore(&prtd->lock, flags); +} + +static struct dma_async_tx_descriptor * +spear13xx_dma_submit(struct spear13xx_runtime_data *prtd, + dma_addr_t buf_dma_addr) +{ + struct dma_chan *chan; + struct dma_async_tx_descriptor *desc; + struct scatterlist sg; + struct snd_pcm_substream *substream = prtd->substream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + chan = prtd->dma_chan[0]; + else + chan = prtd->dma_chan[1]; + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(buf_dma_addr)), + prtd->frag_bytes, buf_dma_addr & (PAGE_SIZE - 1)); + sg_dma_address(&sg) = buf_dma_addr; + desc = chan->device->device_prep_slave_sg(chan, &sg, 1, + prtd->substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + DMA_TO_DEVICE : DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(&chan->dev->device, "cannot prepare slave dma\n"); + return NULL; + } + desc->callback = spear13xx_dma_complete; + desc->callback_param = prtd; + desc->tx_submit(desc); + return desc; +} + +static void spear13xx_dma_tasklet(unsigned long data) +{ + struct spear13xx_runtime_data *prtd = + (struct spear13xx_runtime_data *)data; + struct dma_chan *chan; + struct dma_async_tx_descriptor *desc; + struct snd_pcm_substream *substream = prtd->substream; + int i; + unsigned long flags; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + chan = prtd->dma_chan[0]; + else + chan = prtd->dma_chan[1]; + + spin_lock_irqsave(&prtd->lock, flags); + + if (prtd->frag_count < 0) { + spin_unlock_irqrestore(&prtd->lock, flags); + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + /* first time */ + for (i = 0; i < MAX_DMA_CHAIN; i++) { + desc = spear13xx_dma_submit(prtd, + prtd->dma_addr + i * prtd->frag_bytes); + if (!desc) + return; + } + prtd->dmacount = MAX_DMA_CHAIN; + chan->device->device_issue_pending(chan); + spin_lock_irqsave(&prtd->lock, flags); + prtd->frag_count = MAX_DMA_CHAIN % prtd->frags; + spin_unlock_irqrestore(&prtd->lock, flags); + return; + } + + while (prtd->dmacount < MAX_DMA_CHAIN) { + prtd->dmacount++; + spin_unlock_irqrestore(&prtd->lock, flags); + desc = spear13xx_dma_submit(prtd, + prtd->dma_addr + + prtd->frag_count * prtd->frag_bytes); + if (!desc) + return; + chan->device->device_issue_pending(chan); + + spin_lock_irqsave(&prtd->lock, flags); + prtd->frag_count++; + prtd->frag_count %= prtd->frags; + prtd->pos += prtd->frag_bytes; + prtd->pos %= prtd->buffer_bytes; + if ((prtd->frag_count * prtd->frag_bytes) % + prtd->period_bytes == 0) + snd_pcm_period_elapsed(substream); + } + spin_unlock_irqrestore(&prtd->lock, flags); +} + +static int spear13xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct spear13xx_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->frag_count = -1; + tasklet_schedule(&prtd->tasklet); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + break; + default: + ret = -EINVAL; + } + return ret; +} + +static snd_pcm_uframes_t +spear13xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct spear13xx_runtime_data *prtd = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, prtd->pos); +} + +static void dma_configure(struct snd_pcm_substream *substream) +{ + struct spear13xx_runtime_data *prtd = substream->runtime->private_data; + + dma_cap_zero(prtd->mask); + dma_cap_set(DMA_SLAVE, prtd->mask); + + prtd->slaves = &data; + /* we need to pass physical address here */ + prtd->slaves->mem2i2s_slave.tx_reg = (dma_addr_t)prtd->txdma; + prtd->slaves->mem2i2s_slave.rx_reg = 0; + prtd->slaves->i2s2mem_slave.tx_reg = 0; + prtd->slaves->i2s2mem_slave.rx_reg = (dma_addr_t)prtd->rxdma; + + substream->runtime->private_data = prtd; +} + +static bool filter(struct dma_chan *chan, void *slave) +{ + chan->private = slave; + return true; +} + +static int spear13xx_pcm_dma_request(struct snd_pcm_substream *substream) +{ + struct spear13xx_runtime_data *prtd = substream->runtime->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->dma_chan[0] = dma_request_channel(prtd->mask, filter, + &prtd->slaves->mem2i2s_slave); + if (!prtd->dma_chan[0]) + return -EAGAIN; + } else { + prtd->dma_chan[1] = dma_request_channel(prtd->mask, filter, + &prtd->slaves->i2s2mem_slave); + if (!prtd->dma_chan[1]) + return -EAGAIN; + + } + substream->runtime->private_data = prtd; + + return 0; +} + +static int spear13xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct spear13xx_runtime_data *prtd; + int ret; + + ret = snd_soc_set_runtime_hwparams(substream, &spear13xx_pcm_hardware); + if (ret) + return ret; + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + spin_lock_init(&prtd->lock); + + substream->runtime->private_data = prtd; + + get_dma_start_addr(substream); + dma_configure(substream); + ret = spear13xx_pcm_dma_request(substream); + if (ret) { + dev_err(&prtd->dev, "pcm:Failed to get dma channels\n"); + kfree(prtd); + + } + + tasklet_init(&prtd->tasklet, spear13xx_dma_tasklet, + (unsigned long)prtd); + + return 0; +} + +static void dma_stop(struct snd_pcm_substream *substream) +{ + struct spear13xx_runtime_data *prtd = substream->runtime->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_release_channel(prtd->dma_chan[0]); + else + dma_release_channel(prtd->dma_chan[1]); + +} + +static int spear13xx_pcm_close(struct snd_pcm_substream *substream) +{ + struct spear13xx_runtime_data *prtd = substream->runtime->private_data; + struct dma_chan *chan; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + chan = prtd->dma_chan[0]; + else + chan = prtd->dma_chan[1]; + + prtd->frag_count = -1; + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + dma_stop(substream); + kfree(prtd); + return 0; +} + +static int spear13xx_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 spear13xx_pcm_ops = { + .open = spear13xx_pcm_open, + .close = spear13xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = spear13xx_pcm_hw_params, + .hw_free = spear13xx_pcm_hw_free, + .prepare = spear13xx_pcm_prepare, + .trigger = spear13xx_pcm_trigger, + .pointer = spear13xx_pcm_pointer, + .mmap = spear13xx_pcm_mmap, +}; + +static int +spear13xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream, + size_t size) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + 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); + dev_dbg(buf->dev.dev, + " preallocate_dma_buffer: area=%p, addr=%p, size=%d\n", + (void *)buf->area, (void *)buf->addr, size); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} + +static void spear13xx_pcm_free(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 int spear13xx_pcm_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 = &spear13xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->driver->playback.channels_min) { + ret = spear13xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK, + spear13xx_pcm_hardware.buffer_bytes_max); + if (ret) + return ret; + } + + if (dai->driver->capture.channels_min) { + ret = spear13xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE, + spear13xx_pcm_hardware.buffer_bytes_max); + if (ret) + return ret; + } + + return 0; +} + +struct snd_soc_platform_driver spear13xx_soc_platform = { + .ops = &spear13xx_pcm_ops, + .pcm_new = spear13xx_pcm_new, + .pcm_free = spear13xx_pcm_free, +}; + +static int __devinit spear13xx_soc_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &spear13xx_soc_platform); +} + +static int __devexit spear13xx_soc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver spear13xx_pcm_driver = { + .driver = { + .name = "spear-pcm-audio", + .owner = THIS_MODULE, + }, + + .probe = spear13xx_soc_platform_probe, + .remove = __devexit_p(spear13xx_soc_platform_remove), +}; + +static int __init snd_spear13xx_pcm_init(void) +{ + return platform_driver_register(&spear13xx_pcm_driver); +} +module_init(snd_spear13xx_pcm_init); + +static void __exit snd_spear13xx_pcm_exit(void) +{ + platform_driver_unregister(&spear13xx_pcm_driver); +} +module_exit(snd_spear13xx_pcm_exit); + +MODULE_AUTHOR("Rajeev Kumar"); +MODULE_DESCRIPTION("spear PCM DMA module"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:spear13xx-pcm"); diff --git a/sound/soc/spear/spear13xx-pcm.h b/sound/soc/spear/spear13xx-pcm.h new file mode 100644 index 0000000..c5e0c74 --- /dev/null +++ b/sound/soc/spear/spear13xx-pcm.h @@ -0,0 +1,50 @@ +/* + * ALSA PCM interface for ST spear Processor + * + * sound/soc/spear/spear13xx-pcm.h + * + * Copyright (C) 2011 ST Microelectronics + * Rajeev Kumar rajeev-dlh.kumar@st.com + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef SPEAR_PCM_H +#define SPEAR_PCM_H + +#include <linux/dw_dmac.h> + +struct pcm_dma_data { + struct dw_dma_slave mem2i2s_slave; + struct dw_dma_slave i2s2mem_slave; +}; + +struct spear13xx_pcm_dma_params { + char *name; /* stream identifier */ + dma_addr_t dma_addr; /* device physical address for DMA */ +}; + +struct spear13xx_runtime_data { + struct device dev; + struct pcm_dma_data *slaves; + struct dma_chan *dma_chan[2]; + struct tasklet_struct tasklet; + spinlock_t lock; + dma_addr_t txdma; + struct spear13xx_pcm_dma_params *params; /* DMA params */ + dma_addr_t rxdma; + dma_cap_mask_t mask; + int stream; + struct snd_pcm_substream *substream; + unsigned long pos; + dma_addr_t dma_addr; + unsigned long buffer_bytes; + unsigned long period_bytes; + unsigned long frag_bytes; + int frags; + int frag_count; + int dmacount; +}; +#endif /* end of pcm header file */
The patch adds support for SPEAr13XX machine driver.
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com --- sound/soc/spear/evb_sta529.c | 121 ++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 121 insertions(+), 0 deletions(-) create mode 100644 sound/soc/spear/evb_sta529.c
diff --git a/sound/soc/spear/evb_sta529.c b/sound/soc/spear/evb_sta529.c new file mode 100644 index 0000000..d43b433 --- /dev/null +++ b/sound/soc/spear/evb_sta529.c @@ -0,0 +1,121 @@ +/* + * ASoC machine driver for spear platform + * + * sound/soc/spear/evb_sta529.c + * + * Copyright (C) 2011 ST Microelectronics + * Rajeev Kumar rajeev-dlh.kumar@st.com + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/timer.h> + +#include <mach/generic.h> +#include <mach/misc_regs.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "spear13xx-i2s.h" +#include "spear13xx-pcm.h" + +static int +sta529_evb_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->codec_dai; + int ret = 0; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBS_CFM); + return ret; + +} + +static struct snd_soc_ops sta529_evb_ops = { + .hw_params = sta529_evb_hw_params, +}; + +/* spear digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link evb_dai = { + .name = "SPEARSTA529", + .stream_name = "I2S Audio", + .cpu_dai_name = "spear13xx-i2s.0", + .platform_name = "spear-pcm-audio", + .codec_dai_name = "sta529-audio", + .codec_name = "sta529.0-001a", + .ops = &sta529_evb_ops, +}; + +/* spear audio machine driver */ +static struct snd_soc_card snd_soc_evb = { + .name = "sta529", + .dai_link = &evb_dai, + .num_links = 1, +}; + +static __devinit int spear_evb_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_evb; + int ret; + + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + return ret; + } + + return 0; +} + +static int __devexit spear_evb_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +static struct platform_driver evb_driver = { + .driver = { + .name = "sta529", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, + .probe = spear_evb_probe, + .remove = __devexit_p(spear_evb_remove), +}; + +static int __init spear_audio_init(void) +{ + return platform_driver_register(&evb_driver); +} +module_init(spear_audio_init); + +static void __exit spear_audio_exit(void) +{ + platform_driver_unregister(&evb_driver); +} +module_exit(spear_audio_exit); + +MODULE_DESCRIPTION("ST SPEAR EVB ASoC driver"); +MODULE_AUTHOR("Rajeev Kumarrajeev-dlh.kumar@st.com"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sta529");
This patch adds Kconfig and Makefile to support SPEAr13XX ASoC driver
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com --- sound/soc/spear/Kconfig | 19 +++++++++++++++++++ sound/soc/spear/Makefile | 12 ++++++++++++ 2 files changed, 31 insertions(+), 0 deletions(-) create mode 100644 sound/soc/spear/Kconfig create mode 100644 sound/soc/spear/Makefile
diff --git a/sound/soc/spear/Kconfig b/sound/soc/spear/Kconfig new file mode 100644 index 0000000..666f95f --- /dev/null +++ b/sound/soc/spear/Kconfig @@ -0,0 +1,19 @@ +config SND_SOC_SPEAR_PCM + tristate "SoC Audio for the ST chip" + help + Say Y or M if you want to add support for codecs attached to + the I2S interface. You will also need + to select the audio interfaces to support below. + +config SND_SOC_SPEAR_I2S + tristate + +config SND_SOC_SPEAR_EVM + tristate "SoC Audio support for Spear EVM" + depends on SND_SOC_SPEAR_PCM + select SND_SOC_SPEAR_I2S + select SND_SOC_STA529 + help + A driver for the EVM machine. + Say Y if you want to select audio on ST SPEAR board + diff --git a/sound/soc/spear/Makefile b/sound/soc/spear/Makefile new file mode 100644 index 0000000..cc0972e --- /dev/null +++ b/sound/soc/spear/Makefile @@ -0,0 +1,12 @@ +# SPEAR Platform Support +snd-soc-pcm-objs := spear13xx-pcm.o +snd-soc-i2s-objs := spear13xx-i2s.o + +obj-$(CONFIG_SND_SOC_SPEAR_PCM) += snd-soc-pcm.o +obj-$(CONFIG_SND_SOC_SPEAR_I2S) += snd-soc-i2s.o + +# SPEAR Machine Support +snd-soc-evb-objs := evb_sta529.o + +obj-$(CONFIG_SND_SOC_SPEAR_EVM) += snd-soc-evb.o +
This patch adds the SPEAr13XX support by updating the Kconfig and Makefile present in soc directory
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + 2 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 8224db5..31cceca 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -55,6 +55,7 @@ source "sound/soc/pxa/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/spear/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 1ed61c5..dd9c624 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -18,5 +18,6 @@ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/
On Mon, Jun 06, 2011 at 11:27:35AM +0530, Rajeev Kumar wrote:
+config SND_SOC_SPEAR_EVM
- tristate "SoC Audio support for Spear EVM"
The code doesn't seem to be able to make up its mind if the board is called "evb" or "evm" - this should be consistent.
On Mon, Jun 06, 2011 at 11:27:34AM +0530, Rajeev Kumar wrote:
+static struct platform_driver evb_driver = {
- .driver = {
.name = "sta529",
This isn't a good name for the driver - the name should reflect the board. This looks like it should be a driver for the CODEC.
On Mon, Jun 06, 2011 at 11:27:33AM +0530, Rajeev Kumar wrote:
+#ifndef SPEAR_I2S_H +#define SPEAR_I2S_H
+void get_dma_start_addr(struct snd_pcm_substream *substream);
+#endif /*end if i2s header file */
This needs to be namespaced.
On 06/06/11 06:57, Rajeev Kumar wrote:
This patch adds the support for the SPEAr13XX Platform Driver (I2S based) as per the ASoC framework.
SPEAr13XX internally uses a Designware I2S IP and some glue logic on top of the same.
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com
snip
+}
+static int spear13xx_pcm_new(struct snd_card *card,
struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
Btw, this API call has recently been refactored to only accept 1 argument since you posted V4.
Thanks
Liam
On 06/06/2011 07:57 AM, Rajeev Kumar wrote:
This patch adds the support for STA529 audio codec. Details of the audio codec can be seen here: http://www.st.com/internet/imag_video/product/159187.jsp
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com
sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sta529.c | 374 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 381 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c
[...]
+struct sta529 {
- unsigned int sysclk;
sysclk is unused
- enum snd_soc_control_type control_type;
- void *control_data;
control_data is unused
+};
+static const char *pwm_mode_text[] = { "binary", "headphone", "ternary",
- "phase-shift"};
+static const char *interface_mode_text[] = { "slave", "master"};
+static const struct soc_enum pwm_src_enum = +SOC_ENUM_SINGLE(STA529_FFXCFG1, 4, 4, pwm_mode_text);
+static const struct soc_enum mode_src_enum = +SOC_ENUM_SINGLE(STA529_P2SCFG0, 0, 2, interface_mode_text);
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("PWM Select", pwm_src_enum),
- SOC_ENUM("MODE Select", mode_src_enum),
The mode should be configured by the dai_drivers set_fmt callback, and not by a control.
+};
+static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -9150, 50, 0); +static const DECLARE_TLV_DB_SCALE(master_vol_tlv, -12750, 50, 0);
+static const struct snd_kcontrol_new sta529_snd_controls[] = {
- SOC_DOUBLE_R_TLV("Digital Playback Volume", STA529_LVOL, STA529_RVOL, 0,
127, 0, out_gain_tlv),
- SOC_SINGLE_TLV("Master Playback Volume", STA529_MVOL, 0, 127, 1,
master_vol_tlv),
+};
+static int sta529_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->codec;
- int pdata = 0;
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16_LE:
pdata = 1;
break;
- case SNDRV_PCM_FORMAT_S24_LE:
pdata = 2;
break;
- case SNDRV_PCM_FORMAT_S32_LE:
pdata = 3;
break;
- }
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
snd_soc_update_bits(codec, STA529_S2PCFG1, 0xC0, (pdata << 6));
- else
snd_soc_update_bits(codec, STA529_P2SCFG1, 0xC0, (pdata << 6));
- return 0;
+}
+static int sta529_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) +{
- struct snd_soc_codec *codec = codec_dai->codec;
- u8 mode = 0;
- /* interface format */
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case SND_SOC_DAIFMT_LEFT_J:
mode = LEFT_J_DATA_FORMAT;
break;
- case SND_SOC_DAIFMT_I2S:
mode = I2S_DATA_FORMAT;
break;
- case SND_SOC_DAIFMT_RIGHT_J:
mode = RIGHT_J_DATA_FORMAT;
break;
- default:
return -EINVAL;
- }
- mode |= 0x20;
- snd_soc_update_bits(codec, STA529_S2PCFG0, 0xE0, mode);
What about the P2SCFG0 register, I would suspect, that the same data needs to be written to it.
- return 0;
+}
+static int sta529_mute(struct snd_soc_dai *dai, int mute) +{
- struct snd_soc_codec *codec = dai->codec;
- u8 mute_reg = snd_soc_read(codec, STA529_FFXCFG0) & ~CODEC_MUTE_VAL;
- if (mute)
mute_reg |= CODEC_MUTE_VAL;
- snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, 00);
I guess, it should be mute_reg instead of 00
- return 0;
+}
+static int +sta529_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
+{
- switch (level) {
- case SND_SOC_BIAS_ON:
- case SND_SOC_BIAS_PREPARE:
snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK,
POWER_UP);
snd_soc_update_bits(codec, STA529_MISC, 1, 0x01);
break;
- case SND_SOC_BIAS_STANDBY:
- case SND_SOC_BIAS_OFF:
snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK,
POWER_STDBY);
/* Making FFX output to zero */
snd_soc_update_bits(codec, STA529_FFXCFG0, FFX_MASK,
FFX_OFF);
snd_soc_update_bits(codec, STA529_MISC, 1, 0x00);
break;
- }
- /*
* store the label for powers down audio subsystem for suspend.This is
* used by soc core layer
*/
- codec->dapm.bias_level = level;
- return 0;
+}
+static struct snd_soc_dai_ops sta529_dai_ops = {
Can be const
- .hw_params = sta529_hw_params,
- .set_fmt = sta529_set_dai_fmt,
- .digital_mute = sta529_mute,
+};
[..]
+static int sta529_probe(struct snd_soc_codec *codec) +{
- struct sta529 *sta529 = snd_soc_codec_get_drvdata(codec);
- int ret;
- ret = snd_soc_codec_set_cache_io(codec, 8, 8, sta529->control_type);
If your codec only supports I2C, you can pass SND_SOC_I2C here directly and get rid of the control_type field.
- if (ret < 0) {
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
return ret;
- }
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- snd_soc_add_controls(codec, sta529_snd_controls,
ARRAY_SIZE(sta529_snd_controls));
- snd_soc_add_controls(codec, sta529_new_snd_controls,
ARRAY_SIZE(sta529_new_snd_controls));
You should use table based controls setup. i.e assign the control table to the 'controls' field of your codec_driver.
- return 0;
+}
+/* power down chip */ +static int sta529_remove(struct snd_soc_codec *codec) +{
- sta529_set_bias_level(codec, SND_SOC_BIAS_OFF);
- return 0;
+}
+static int sta529_suspend(struct snd_soc_codec *codec, pm_message_t state) +{
- sta529_set_bias_level(codec, SND_SOC_BIAS_OFF);
- return 0;
+}
+static int sta529_resume(struct snd_soc_codec *codec) +{
- snd_soc_cache_sync(codec);
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- sta529_set_bias_level(codec, codec->dapm.suspend_bias_level);
- return 0;
+}
+struct snd_soc_codec_driver soc_codec_dev_sta529 = {
- .probe = sta529_probe,
- .remove = sta529_remove,
- .set_bias_level = sta529_set_bias_level,
- .suspend = sta529_suspend,
- .resume = sta529_resume,
- .reg_cache_size = STA529_CACHEREGNUM,
- .reg_word_size = sizeof(u8),
- .reg_cache_default = sta529_reg,
+};
+static __devinit int sta529_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
+{
- struct sta529 *sta529;
- int ret;
- if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EINVAL;
- sta529 = kzalloc(sizeof(struct sta529), GFP_KERNEL);
- if (sta529 == NULL)
return -ENOMEM;
- i2c_set_clientdata(i2c, sta529);
- sta529->control_data = i2c;
- sta529->control_type = SND_SOC_I2C;
- ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_sta529, &sta529_dai, 1);
- if (ret < 0)
kfree(sta529);
- return ret;
+}
+static int sta529_i2c_remove(struct i2c_client *client)
__devexit
+{
- snd_soc_unregister_codec(&client->dev);
- kfree(i2c_get_clientdata(client));
- return 0;
+}
[...]
Your driver doesn't use any DAPM. You should at least define input and output pins and their routing, but I would expect that there is more that can be done, like dynamicly powering unused sections of the codec down, like the DAC or ADC.
Hi Lars-Peter Clausen Please find my answer inline below.
On 6/6/2011 11:53 AM, Lars-Peter Clausen wrote:
On 06/06/2011 07:57 AM, Rajeev Kumar wrote:
This patch adds the support for STA529 audio codec. Details of the audio codec can be seen here: http://www.st.com/internet/imag_video/product/159187.jsp
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com
sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sta529.c | 374 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 381 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c
[...]
+struct sta529 {
- unsigned int sysclk;
sysclk is unused
- enum snd_soc_control_type control_type;
- void *control_data;
control_data is unused
Oops, will be removed.
+};
+static const char *pwm_mode_text[] = { "binary", "headphone", "ternary",
- "phase-shift"};
+static const char *interface_mode_text[] = { "slave", "master"};
+static const struct soc_enum pwm_src_enum = +SOC_ENUM_SINGLE(STA529_FFXCFG1, 4, 4, pwm_mode_text);
+static const struct soc_enum mode_src_enum = +SOC_ENUM_SINGLE(STA529_P2SCFG0, 0, 2, interface_mode_text);
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("PWM Select", pwm_src_enum),
- SOC_ENUM("MODE Select", mode_src_enum),
The mode should be configured by the dai_drivers set_fmt callback, and not by a control.
I think giving a interface to the user is better option rather than doing it in set_fmt callback.
+};
+static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -9150, 50, 0); +static const DECLARE_TLV_DB_SCALE(master_vol_tlv, -12750, 50, 0);
+static const struct snd_kcontrol_new sta529_snd_controls[] = {
- SOC_DOUBLE_R_TLV("Digital Playback Volume", STA529_LVOL, STA529_RVOL, 0,
127, 0, out_gain_tlv),
- SOC_SINGLE_TLV("Master Playback Volume", STA529_MVOL, 0, 127, 1,
master_vol_tlv),
+};
+static int sta529_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->codec;
- int pdata = 0;
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16_LE:
pdata = 1;
break;
- case SNDRV_PCM_FORMAT_S24_LE:
pdata = 2;
break;
- case SNDRV_PCM_FORMAT_S32_LE:
pdata = 3;
break;
- }
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
snd_soc_update_bits(codec, STA529_S2PCFG1, 0xC0, (pdata << 6));
- else
snd_soc_update_bits(codec, STA529_P2SCFG1, 0xC0, (pdata << 6));
- return 0;
+}
+static int sta529_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) +{
- struct snd_soc_codec *codec = codec_dai->codec;
- u8 mode = 0;
- /* interface format */
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case SND_SOC_DAIFMT_LEFT_J:
mode = LEFT_J_DATA_FORMAT;
break;
- case SND_SOC_DAIFMT_I2S:
mode = I2S_DATA_FORMAT;
break;
- case SND_SOC_DAIFMT_RIGHT_J:
mode = RIGHT_J_DATA_FORMAT;
break;
- default:
return -EINVAL;
- }
- mode |= 0x20;
- snd_soc_update_bits(codec, STA529_S2PCFG0, 0xE0, mode);
What about the P2SCFG0 register, I would suspect, that the same data needs to be written to it.
Yes we need to update this.
- return 0;
+}
+static int sta529_mute(struct snd_soc_dai *dai, int mute) +{
- struct snd_soc_codec *codec = dai->codec;
- u8 mute_reg = snd_soc_read(codec, STA529_FFXCFG0) & ~CODEC_MUTE_VAL;
- if (mute)
mute_reg |= CODEC_MUTE_VAL;
- snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, 00);
I guess, it should be mute_reg instead of 00
No, This is value, register is STA529_FFXCFG0
- return 0;
+}
+static int +sta529_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
+{
- switch (level) {
- case SND_SOC_BIAS_ON:
- case SND_SOC_BIAS_PREPARE:
snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK,
POWER_UP);
snd_soc_update_bits(codec, STA529_MISC, 1, 0x01);
break;
- case SND_SOC_BIAS_STANDBY:
- case SND_SOC_BIAS_OFF:
snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK,
POWER_STDBY);
/* Making FFX output to zero */
snd_soc_update_bits(codec, STA529_FFXCFG0, FFX_MASK,
FFX_OFF);
snd_soc_update_bits(codec, STA529_MISC, 1, 0x00);
break;
- }
- /*
* store the label for powers down audio subsystem for suspend.This is
* used by soc core layer
*/
- codec->dapm.bias_level = level;
- return 0;
+}
+static struct snd_soc_dai_ops sta529_dai_ops = {
Can be const
It can not be. Please check snd_soc_dai_ops structure in include/sound/soc-dai.h
- .hw_params = sta529_hw_params,
- .set_fmt = sta529_set_dai_fmt,
- .digital_mute = sta529_mute,
+};
[..]
+static int sta529_probe(struct snd_soc_codec *codec) +{
- struct sta529 *sta529 = snd_soc_codec_get_drvdata(codec);
- int ret;
- ret = snd_soc_codec_set_cache_io(codec, 8, 8, sta529->control_type);
If your codec only supports I2C, you can pass SND_SOC_I2C here directly and get rid of the control_type field.
ok
- if (ret < 0) {
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
return ret;
- }
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- snd_soc_add_controls(codec, sta529_snd_controls,
ARRAY_SIZE(sta529_snd_controls));
- snd_soc_add_controls(codec, sta529_new_snd_controls,
ARRAY_SIZE(sta529_new_snd_controls));
You should use table based controls setup. i.e assign the control table to the 'controls' field of your codec_driver.
You can do it in either way.
- return 0;
+}
+/* power down chip */ +static int sta529_remove(struct snd_soc_codec *codec) +{
- sta529_set_bias_level(codec, SND_SOC_BIAS_OFF);
- return 0;
+}
+static int sta529_suspend(struct snd_soc_codec *codec, pm_message_t state) +{
- sta529_set_bias_level(codec, SND_SOC_BIAS_OFF);
- return 0;
+}
+static int sta529_resume(struct snd_soc_codec *codec) +{
- snd_soc_cache_sync(codec);
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- sta529_set_bias_level(codec, codec->dapm.suspend_bias_level);
- return 0;
+}
+struct snd_soc_codec_driver soc_codec_dev_sta529 = {
- .probe = sta529_probe,
- .remove = sta529_remove,
- .set_bias_level = sta529_set_bias_level,
- .suspend = sta529_suspend,
- .resume = sta529_resume,
- .reg_cache_size = STA529_CACHEREGNUM,
- .reg_word_size = sizeof(u8),
- .reg_cache_default = sta529_reg,
+};
+static __devinit int sta529_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
+{
- struct sta529 *sta529;
- int ret;
- if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EINVAL;
- sta529 = kzalloc(sizeof(struct sta529), GFP_KERNEL);
- if (sta529 == NULL)
return -ENOMEM;
- i2c_set_clientdata(i2c, sta529);
- sta529->control_data = i2c;
- sta529->control_type = SND_SOC_I2C;
- ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_sta529, &sta529_dai, 1);
- if (ret < 0)
kfree(sta529);
- return ret;
+}
+static int sta529_i2c_remove(struct i2c_client *client)
__devexit
+{
- snd_soc_unregister_codec(&client->dev);
- kfree(i2c_get_clientdata(client));
- return 0;
+}
[...]
Your driver doesn't use any DAPM. You should at least define input and output pins and their routing, but I would expect that there is more that can be done, like dynamicly powering unused sections of the codec down, like the DAC or ADC. .
Right now since my driver has not support for DAPM, so definitions for input and output pins are not there.Once I will implement DAPM feature in future I will send separate patch for that.
Best Rgds Rajeev
On 06/06/2011 09:08 AM, rajeev wrote:
Hi Lars-Peter Clausen Please find my answer inline below.
On 6/6/2011 11:53 AM, Lars-Peter Clausen wrote:
On 06/06/2011 07:57 AM, Rajeev Kumar wrote:
This patch adds the support for STA529 audio codec. Details of the audio codec can be seen here: http://www.st.com/internet/imag_video/product/159187.jsp
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com
sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sta529.c | 374 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 381 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c
[...]
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("PWM Select", pwm_src_enum),
- SOC_ENUM("MODE Select", mode_src_enum),
The mode should be configured by the dai_drivers set_fmt callback, and not by a control.
I think giving a interface to the user is better option rather than doing it in set_fmt callback.
Why? Both the codec_dai and the cpu_dai have to agree on who is the master and who is the slave. So letting the user select the codec mode instead of defining it in the board driver doesn't make sense, since the setup will stop working if the cpu dai isn't configured to match the codec dais mode. Furthermore you don't want to switch the mode while playback is active and generally you won't even change it at all, once it has been setup.
[...[
+static int sta529_mute(struct snd_soc_dai *dai, int mute) +{
- struct snd_soc_codec *codec = dai->codec;
- u8 mute_reg = snd_soc_read(codec, STA529_FFXCFG0) & ~CODEC_MUTE_VAL;
- if (mute)
mute_reg |= CODEC_MUTE_VAL;
- snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, 00);
I guess, it should be mute_reg instead of 00
No, This is value, register is STA529_FFXCFG0
You are always clearing the mute bit, regardless of whether mute is enabled or disabled. snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, mute_reg); is probably what you want to do.
[...]
+static struct snd_soc_dai_ops sta529_dai_ops = {
Can be const
It can not be. Please check snd_soc_dai_ops structure in include/sound/soc-dai.h
Yes, it can. Maybe you are using an outdated ASoC version. Check line 206 of include/sound/soc-dai.h in Mark's for-next branch.
[...]
- snd_soc_add_controls(codec, sta529_snd_controls,
ARRAY_SIZE(sta529_snd_controls));
- snd_soc_add_controls(codec, sta529_new_snd_controls,
ARRAY_SIZE(sta529_new_snd_controls));
You should use table based controls setup. i.e assign the control table to the 'controls' field of your codec_driver.
You can do it in either way.
Yes, you can, but you should use the codec_driver fields unless you have good reasoning not to.
[...]
Your driver doesn't use any DAPM. You should at least define input and output pins and their routing, but I would expect that there is more that can be done, like dynamicly powering unused sections of the codec down, like the DAC or ADC. .
Right now since my driver has not support for DAPM, so definitions for input and output pins are not there.Once I will implement DAPM feature in future I will send separate patch for that.
Currently there is a bug in the ASoC core, which will cause codec drivers without DAPM to be not powered down, even though if they are not used. Given that it will maybe take 5 minutes or so to add basic DAPM (Input/Output pins and ADC/DAC) it would be a good idea to add it to the inital version of the driver.
- Lars
Hi Lars
On 6/6/2011 1:05 PM, Lars-Peter Clausen wrote:
On 06/06/2011 09:08 AM, rajeev wrote:
Hi Lars-Peter Clausen Please find my answer inline below.
On 6/6/2011 11:53 AM, Lars-Peter Clausen wrote:
On 06/06/2011 07:57 AM, Rajeev Kumar wrote:
This patch adds the support for STA529 audio codec. Details of the audio codec can be seen here: http://www.st.com/internet/imag_video/product/159187.jsp
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com
sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sta529.c | 374 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 381 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c
[...]
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("PWM Select", pwm_src_enum),
- SOC_ENUM("MODE Select", mode_src_enum),
The mode should be configured by the dai_drivers set_fmt callback, and not by a control.
I think giving a interface to the user is better option rather than doing it in set_fmt callback.
Why? Both the codec_dai and the cpu_dai have to agree on who is the master and who is the slave. So letting the user select the codec mode instead of defining it in the board driver doesn't make sense, since the setup will stop working if the cpu dai isn't configured to match the codec dais mode. Furthermore you don't want to switch the mode while playback is active and generally you won't even change it at all, once it has been setup.
Ok.
[...[
+static int sta529_mute(struct snd_soc_dai *dai, int mute) +{
- struct snd_soc_codec *codec = dai->codec;
- u8 mute_reg = snd_soc_read(codec, STA529_FFXCFG0) & ~CODEC_MUTE_VAL;
- if (mute)
mute_reg |= CODEC_MUTE_VAL;
- snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, 00);
I guess, it should be mute_reg instead of 00
No, This is value, register is STA529_FFXCFG0
You are always clearing the mute bit, regardless of whether mute is enabled or disabled. snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, mute_reg); is probably what you want to do.
Ok will be corrected in next patch
[...]
+static struct snd_soc_dai_ops sta529_dai_ops = {
Can be const
It can not be. Please check snd_soc_dai_ops structure in include/sound/soc-dai.h
Yes, it can. Maybe you are using an outdated ASoC version. Check line 206 of include/sound/soc-dai.h in Mark's for-next branch.
Ok.
[...]
- snd_soc_add_controls(codec, sta529_snd_controls,
ARRAY_SIZE(sta529_snd_controls));
- snd_soc_add_controls(codec, sta529_new_snd_controls,
ARRAY_SIZE(sta529_new_snd_controls));
You should use table based controls setup. i.e assign the control table to the 'controls' field of your codec_driver.
You can do it in either way.
Yes, you can, but you should use the codec_driver fields unless you have good reasoning not to.
could you please suggest any driver for refrence?
[...]
Your driver doesn't use any DAPM. You should at least define input and output pins and their routing, but I would expect that there is more that can be done, like dynamicly powering unused sections of the codec down, like the DAC or ADC. .
Right now since my driver has not support for DAPM, so definitions for input and output pins are not there.Once I will implement DAPM feature in future I will send separate patch for that.
Currently there is a bug in the ASoC core, which will cause codec drivers without DAPM to be not powered down, even though if they are not used. Given that it will maybe take 5 minutes or so to add basic DAPM (Input/Output pins and ADC/DAC) it would be a good idea to add it to the inital version of the driver.
OK,I Wlll. Please check the steps I need to do for dapm update. 1. static const struct snd_soc_dapm_widget sta529_dapm_widgets[] = { SND_SOC_DAPM_ADC("ADC", "Capture", STA529_ADCCGF, 3, 0), SND_SOC_DAPM_OUTPUT("HPL"), SND_SOC_DAPM_OUTPUT("HPR"), SND_SOC_DAPM_OUTPUT("SPKL"), SND_SOC_DAPM_OUTPUT("SPKR"),
SND_SOC_DAPM_INPUT("MIC1"), };
2. static const struct snd_soc_dapm_route sat529_audio_map[] = { {"ADC", NULL, " ADC Mixer"},
{"HPL", NULL, "HP Left Out"}, {"HPR", NULL, "HP Right Out"}, {"SPKL", NULL, "SPK Left Out"}, {"SPKR", NULL, "SPK Right Out"}, {"Left HP Mixer", "MIC Switch", "MIC Input"}, };
3. struct snd_soc_codec_driver soc_codec_dev_sta529 = { .probe = sta529_probe, .remove = sta529_remove, .set_bias_level = sta529_set_bias_level, .suspend = sta529_suspend, .resume = sta529_resume, .reg_cache_size = STA529_CACHEREGNUM, .reg_word_size = sizeof(u8), .reg_cache_default = sta529_reg, .dapm_widgets = sta529_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(sta529_dapm_widgets), .dapm_routes = sta529_audio_map, .num_dapm_routes = ARRAY_SIZE(sta529_audio_map),
}; Please let me know is it fine for you?
Best Rgds Rajeev
- Lars
.
On 06/06/2011 12:19 PM, rajeev wrote:
Hi Lars
[...] OK,I Wlll. Please check the steps I need to do for dapm update.
- static const struct snd_soc_dapm_widget sta529_dapm_widgets[] = { SND_SOC_DAPM_ADC("ADC", "Capture", STA529_ADCCGF, 3, 0),
Even if your DAC itself does not support dynamic power management you probably still want to define it. Use SND_SOC_NOPM as the register value in this case. This will allow DAPM determine, whether the outputs are active or not. i.e. if there is audio playback on the DACs stream, DAPM will know that the outputs connected to the DAC are active. This will allow for example to turn speakers connected to the outputs dynamically on or off based on DAPM events.
SND_SOC_DAPM_OUTPUT("HPL"), SND_SOC_DAPM_OUTPUT("HPR"), SND_SOC_DAPM_OUTPUT("SPKL"), SND_SOC_DAPM_OUTPUT("SPKR"),
SND_SOC_DAPM_INPUT("MIC1"), };
static const struct snd_soc_dapm_route sat529_audio_map[] = { {"ADC", NULL, " ADC Mixer"},
{"HPL", NULL, "HP Left Out"}, {"HPR", NULL, "HP Right Out"}, {"SPKL", NULL, "SPK Left Out"}, {"SPKR", NULL, "SPK Right Out"}, {"Left HP Mixer", "MIC Switch", "MIC Input"},
};
[...] Please let me know is it fine for you?
Your routinges seem to be a bit off. You use path elements which are not defined in your widget list, like "HP Left Out", "MIC Switch", ... Since your codec seems to be quite simple, I guess what you basically want to do is route all inputs to the ADC and route the DAC to all outputs, without any mixers or switches in between.
- Lars
On Mon, Jun 06, 2011 at 11:27:32AM +0530, Rajeev Kumar wrote:
This patch adds the support for STA529 audio codec. Details of the audio codec can be seen here: http://www.st.com/internet/imag_video/product/159187.jsp
As I'm fairly sure I've said before please do try to use subject lines for your patches which are consistent with the style for the subsystem you're submitting against.
+struct sta529 {
- unsigned int sysclk;
- enum snd_soc_control_type control_type;
- void *control_data;
why do you need the control_data here? You also don't need to pass the control_type if the device is I2C only.
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("PWM Select", pwm_src_enum),
- SOC_ENUM("MODE Select", mode_src_enum),
MODE especially should be configured by _dai_fmt().
+static int sta529_mute(struct snd_soc_dai *dai, int mute) +{
- struct snd_soc_codec *codec = dai->codec;
- u8 mute_reg = snd_soc_read(codec, STA529_FFXCFG0) & ~CODEC_MUTE_VAL;
- if (mute)
mute_reg |= CODEC_MUTE_VAL;
- snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, 00);
This update completely ignores the check you've made above...
- /*
* store the label for powers down audio subsystem for suspend.This is
* used by soc core layer
*/
You want a space after the period here.
+static struct snd_soc_dai_driver sta529_dai = {
- .name = "sta529-audio",
Does the chip have non-audio funtionality?
- snd_soc_add_controls(codec, sta529_snd_controls,
ARRAY_SIZE(sta529_snd_controls));
- snd_soc_add_controls(codec, sta529_new_snd_controls,
ARRAY_SIZE(sta529_new_snd_controls));
Like Lars-Peter said use data based init here.
+static int sta529_resume(struct snd_soc_codec *codec) +{
- snd_soc_cache_sync(codec);
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- sta529_set_bias_level(codec, codec->dapm.suspend_bias_level);
The second set_bias_level() should be redundant - we'll always have been shut down by the core before we get to suspending.
+static int __init sta529_modinit(void) +{
- int ret = 0;
- ret = i2c_add_driver(&sta529_i2c_driver);
- if (ret != 0)
printk(KERN_ERR "Failed to reg sta529 I2C driver: %d\n", ret);
Error out if we can't load; ignoring the error only makes sense if we have multiple bus types and another might have succeeded.
Hi Mark
On 6/6/2011 5:25 PM, Mark Brown wrote:
On Mon, Jun 06, 2011 at 11:27:32AM +0530, Rajeev Kumar wrote:
This patch adds the support for STA529 audio codec. Details of the audio codec can be seen here: http://www.st.com/internet/imag_video/product/159187.jsp
As I'm fairly sure I've said before please do try to use subject lines for your patches which are consistent with the style for the subsystem you're submitting against.
Ok,do You mean like, ASoC: Add STA529 codec support
+struct sta529 {
- unsigned int sysclk;
- enum snd_soc_control_type control_type;
- void *control_data;
why do you need the control_data here? You also don't need to pass the control_type if the device is I2C only.
Will be removed
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("PWM Select", pwm_src_enum),
- SOC_ENUM("MODE Select", mode_src_enum),
MODE especially should be configured by _dai_fmt().
OK
+static int sta529_mute(struct snd_soc_dai *dai, int mute) +{
- struct snd_soc_codec *codec = dai->codec;
- u8 mute_reg = snd_soc_read(codec, STA529_FFXCFG0) & ~CODEC_MUTE_VAL;
- if (mute)
mute_reg |= CODEC_MUTE_VAL;
- snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, 00);
This update completely ignores the check you've made above...
It should be mute_reg instead of 00
- /*
* store the label for powers down audio subsystem for suspend.This is
* used by soc core layer
*/
You want a space after the period here.
OK
+static struct snd_soc_dai_driver sta529_dai = {
- .name = "sta529-audio",
Does the chip have non-audio funtionality?
No, you mean name to be changed to sta529.
- snd_soc_add_controls(codec, sta529_snd_controls,
ARRAY_SIZE(sta529_snd_controls));
- snd_soc_add_controls(codec, sta529_new_snd_controls,
ARRAY_SIZE(sta529_new_snd_controls));
Like Lars-Peter said use data based init here.
ok
+static int sta529_resume(struct snd_soc_codec *codec) +{
- snd_soc_cache_sync(codec);
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- sta529_set_bias_level(codec, codec->dapm.suspend_bias_level);
The second set_bias_level() should be redundant - we'll always have been shut down by the core before we get to suspending.
sta529_set_bias_level(codec, codec->dapm.suspend_bias_level) is not required
+static int __init sta529_modinit(void) +{
- int ret = 0;
- ret = i2c_add_driver(&sta529_i2c_driver);
- if (ret != 0)
printk(KERN_ERR "Failed to reg sta529 I2C driver: %d\n", ret);
Error out if we can't load; ignoring the error only makes sense if we have multiple bus types and another might have succeeded. .
Sorry I have not got the point. Best Rgds Rajeev
participants (5)
-
Lars-Peter Clausen
-
Liam Girdwood
-
Mark Brown
-
rajeev
-
Rajeev Kumar