[alsa-devel] [PATCH V2 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.
This patchset has been tested for linux-kernel-version 2.6.38 and on ARM platform.
Please review the same and consider for mainline inclusion.
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 | 359 +++++++++++++++++++++++++++ sound/soc/spear/Kconfig | 19 ++ sound/soc/spear/Makefile | 6 + sound/soc/spear/evb_sta529.c | 104 ++++++++ sound/soc/spear/spear13xx-i2s.c | 523 +++++++++++++++++++++++++++++++++++++++ sound/soc/spear/spear13xx-i2s.h | 19 ++ sound/soc/spear/spear13xx-pcm.c | 500 +++++++++++++++++++++++++++++++++++++ sound/soc/spear/spear13xx-pcm.h | 50 ++++ 12 files changed, 1589 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 | 359 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 6943e24..7b959af 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -40,6 +40,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_SN95031 if INTEL_SCU_IPC select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C + 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 @@ -206,6 +207,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 379bc55..971275b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -26,6 +26,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 @@ -114,6 +115,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..205c245 --- /dev/null +++ b/sound/soc/codecs/sta529.c @@ -0,0 +1,359 @@ +/* + * 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> + +/* 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 0x00 +#define I2S_DATA_FORMAT 0x02 +#define RIGHT_J_DATA_FORMAT 0x04 +#define RIGHT_J_DATA_FORMAT 0x04 +#define CODEC_MUTE_VAL 0x80 + +#define DEFAULT_MASTER_VOL 0x50 +#define DEFAULT_VOL 0x00 +#define DEFAULT_BIN_HEADPHONE 0xD8 +#define DEFAULT_S2P_REG_0 0x32 +#define DEFAULT_S2P_REG_1 0x41 +#define DEFAULT_P2S_REG_0 0x92 +#define DEFAULT_P2S_REG_1 0x41 +#define DEFAULT_MIC_SEL 0x52 + +#define POWER_CNTLMSAK 0x40 +#define POWER_STDBY 0x40 +#define MUTE_ON_MASK 0x80 +#define MUTE_OFF 0x80 +#define POWER_UP 0x00 +#define FFX_CLK_DIS 0x21 + +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 struct snd_kcontrol_new sta529_snd_controls[] = { + SOC_SINGLE("Master Playback Volume", STA529_MVOL, 0, 127, 1), + SOC_SINGLE("Left Playback Volume", STA529_LVOL, 0, 127, 1), + SOC_SINGLE("Right Playback Volume", STA529_RVOL, 0, 127, 1), +}; + +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; + int val; + + /* 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; + } + + val = snd_soc_read(codec, STA529_S2PCFG0); + val |= mode; + /*this setting will be used with actual h/w */ + snd_soc_write(codec, STA529_S2PCFG0, val); + + 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); + break; + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK, + POWER_STDBY); + snd_soc_update_bits(codec, STA529_FFXCFG0, MUTE_ON_MASK, + MUTE_OFF); + + 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 void sta529_init(struct snd_soc_codec *codec) +{ + /* DAC default master volume */ + snd_soc_write(codec, STA529_MVOL, DEFAULT_MASTER_VOL); + /* DAC default left volume */ + snd_soc_write(codec, STA529_LVOL, DEFAULT_VOL); + /* DAC default right volume */ + snd_soc_write(codec, STA529_RVOL, DEFAULT_VOL); + /* By default route to binary headphones */ + snd_soc_write(codec, STA529_FFXCFG1, DEFAULT_BIN_HEADPHONE); + /* default value for Serial-to-parallel audio interface configuration */ + snd_soc_write(codec, STA529_S2PCFG0, DEFAULT_S2P_REG_0); + snd_soc_write(codec, STA529_S2PCFG1, DEFAULT_S2P_REG_1); + /* default value for parallel-to-serial audio interface configuration */ + snd_soc_write(codec, STA529_P2SCFG0, DEFAULT_P2S_REG_0); + snd_soc_write(codec, STA529_P2SCFG1, DEFAULT_P2S_REG_1); + + /* select microphone input by default*/ + snd_soc_write(codec, STA529_ADCCFG, DEFAULT_MIC_SEL); + /* FFX system clock disabled */ + snd_soc_write(codec, STA529_MISC, FFX_CLK_DIS); + +} + +static struct snd_soc_dai_ops sta529_dai_ops = { + .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; + + codec->hw_write = (hw_write_t)i2c_master_send; + codec->hw_read = NULL; + 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_init(codec); + sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + 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) +{ + + sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + sta529_set_bias_level(codec, codec->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"); +MODULE_ALIAS("platform:sta529-codec");
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 | 523 +++++++++++++++++++++++++++++++++++++++ sound/soc/spear/spear13xx-i2s.h | 19 ++ sound/soc/spear/spear13xx-pcm.c | 500 +++++++++++++++++++++++++++++++++++++ sound/soc/spear/spear13xx-pcm.h | 50 ++++ 4 files changed, 1092 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..33d2d11 --- /dev/null +++ b/sound/soc/spear/spear13xx-i2s.c @@ -0,0 +1,523 @@ +/* + * 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); + dev->active = 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 */ + 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 */ + 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 | 104 ++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 104 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..4c8bfc3 --- /dev/null +++ b/sound/soc/spear/evb_sta529.c @@ -0,0 +1,104 @@ +/* + * 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; + struct snd_soc_dai *cpu_dai = rtd->cpu_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-codec", + .dai_link = &evb_dai, + .num_links = 1, +}; + +static struct platform_device *evb_snd_device; + +static int __init spear_init(void) +{ + int ret; + + /* Create and register platform device */ + evb_snd_device = platform_device_alloc("soc-audio", 0); + if (!evb_snd_device) { + printk(KERN_ERR "platform_device_alloc fails\n"); + return -ENOMEM; + } + + platform_set_drvdata(evb_snd_device, &snd_soc_evb); + ret = platform_device_add(evb_snd_device); + if (ret) { + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(evb_snd_device); + } + + return ret; +} +module_init(spear_init); + +static void __exit spear_exit(void) +{ + platform_device_unregister(evb_snd_device); +} +module_exit(spear_exit); + +MODULE_AUTHOR("Rajeev Kumar"); +MODULE_DESCRIPTION("ST SPEAR EVB ASoC driver"); +MODULE_LICENSE("GPL");
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 | 6 ++++++ 2 files changed, 25 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..951f4d5 --- /dev/null +++ b/sound/soc/spear/Kconfig @@ -0,0 +1,19 @@ +config SND_SOC_SPEAR + 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 + 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..d144eb2 --- /dev/null +++ b/sound/soc/spear/Makefile @@ -0,0 +1,6 @@ +# SPEAR Platform Support +obj-$(CONFIG_SND_SOC_SPEAR) += spear13xx-pcm.o +obj-$(CONFIG_SND_SOC_SPEAR_I2S) += spear13xx-i2s.o + +# SPEAR Machine Support +obj-$(CONFIG_SND_SOC_SPEAR_EVM) += evb_sta529.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 Tue, Mar 29, 2011 at 04:47:02PM +0530, Rajeev Kumar wrote:
+++ b/sound/soc/spear/Makefile @@ -0,0 +1,6 @@ +# SPEAR Platform Support +obj-$(CONFIG_SND_SOC_SPEAR) += spear13xx-pcm.o +obj-$(CONFIG_SND_SOC_SPEAR_I2S) += spear13xx-i2s.o
+# SPEAR Machine Support +obj-$(CONFIG_SND_SOC_SPEAR_EVM) += evb_sta529.o
As previously mentioned you should give all the modules snd-soc- prefixes to their names.
Hi Mark
On 3/30/2011 5:03 AM, Mark Brown wrote:
On Tue, Mar 29, 2011 at 04:47:02PM +0530, Rajeev Kumar wrote:
+++ b/sound/soc/spear/Makefile @@ -0,0 +1,6 @@ +# SPEAR Platform Support +obj-$(CONFIG_SND_SOC_SPEAR) += spear13xx-pcm.o +obj-$(CONFIG_SND_SOC_SPEAR_I2S) += spear13xx-i2s.o
+# SPEAR Machine Support +obj-$(CONFIG_SND_SOC_SPEAR_EVM) += evb_sta529.o
As previously mentioned you should give all the modules snd-soc- prefixes to their names.
Ok it should be like
snd-soc-evb-objs := evb_sta529.o
obj-$(CONFIG_SND_SPEAR_SOC_EVM) += snd-soc-evb.o
Best Regards Rajeev
On Tue, Mar 29, 2011 at 04:47:01PM +0530, Rajeev Kumar wrote:
- /* Create and register platform device */
- evb_snd_device = platform_device_alloc("soc-audio", 0);
- if (!evb_snd_device) {
printk(KERN_ERR "platform_device_alloc fails\n");
return -ENOMEM;
- }
It's better to use snd_soc_register_card() rather than using the soc-audio device with current kernels
Hi Mark
On 3/30/2011 5:02 AM, Mark Brown wrote:
On Tue, Mar 29, 2011 at 04:47:01PM +0530, Rajeev Kumar wrote:
- /* Create and register platform device */
- evb_snd_device = platform_device_alloc("soc-audio", 0);
- if (!evb_snd_device) {
printk(KERN_ERR "platform_device_alloc fails\n");
return -ENOMEM;
- }
It's better to use snd_soc_register_card() rather than using the soc-audio device with current kernels
This patch set has been compiled and tested for Linux-kernel-version 2.6.38,since SPEAr patch is already under review in russel king branch. Once it is ported I will circulate the next patch set containing these specific changes.
Best Rgds Rajeev
Hi Mark, This patch set has been compiled and tested for Linux-kernel-version 2.6.38-rc4, since SPEAr patches for this linux version are already under review in Russel King's ARM branch.
We plan to start porting the SPEAr patches on latest kernel after the earlier SPEAr patches are ACKED. After the same, I will circulate the next SPEAr ASoC patch set containing changes specific to this new kernel version.
Best Regards, Rajeev
On 3/30/2011 10:25 AM, rajeev wrote:
Hi Mark
On 3/30/2011 5:02 AM, Mark Brown wrote:
On Tue, Mar 29, 2011 at 04:47:01PM +0530, Rajeev Kumar wrote:
- /* Create and register platform device */
- evb_snd_device = platform_device_alloc("soc-audio", 0);
- if (!evb_snd_device) {
printk(KERN_ERR "platform_device_alloc fails\n");
return -ENOMEM;
- }
It's better to use snd_soc_register_card() rather than using the soc-audio device with current kernels
This patch set has been compiled and tested for Linux-kernel-version 2.6.38,since SPEAr patch is already under review in russel king branch. Once it is ported I will circulate the next patch set containing these specific changes.
Best Rgds Rajeev
On Tue, Mar 29, 2011 at 04:46:59PM +0530, Rajeev Kumar wrote:
+static const struct snd_kcontrol_new sta529_snd_controls[] = {
- SOC_SINGLE("Master Playback Volume", STA529_MVOL, 0, 127, 1),
- SOC_SINGLE("Left Playback Volume", STA529_LVOL, 0, 127, 1),
- SOC_SINGLE("Right Playback Volume", STA529_RVOL, 0, 127, 1),
+};
Left and Right should be a single stereo control (usually done with SOC_DOUBLE). It'd also be better to provide gain information with the _TLV variants of the controls.
Does the master control actually provide a separate control or does it update both left and right channels simultaneously? If the latter then normally you'd just omit it from the driver as the driver can easily do the stereo updates?
- val = snd_soc_read(codec, STA529_S2PCFG0);
- val |= mode;
- /*this setting will be used with actual h/w */
- snd_soc_write(codec, STA529_S2PCFG0, val);
snd_soc_update_bits() - you're not clearing any bits here so if you change modes things will go wrong.
- case SND_SOC_BIAS_STANDBY:
- case SND_SOC_BIAS_OFF:
snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK,
POWER_STDBY);
snd_soc_update_bits(codec, STA529_FFXCFG0, MUTE_ON_MASK,
MUTE_OFF);
Managing the mute in these states seems odd - could do with some comments in the code if nothing else.
+static void sta529_init(struct snd_soc_codec *codec) +{
- /* DAC default master volume */
- snd_soc_write(codec, STA529_MVOL, DEFAULT_MASTER_VOL);
- /* DAC default left volume */
- snd_soc_write(codec, STA529_LVOL, DEFAULT_VOL);
- /* DAC default right volume */
- snd_soc_write(codec, STA529_RVOL, DEFAULT_VOL);
- /* By default route to binary headphones */
- snd_soc_write(codec, STA529_FFXCFG1, DEFAULT_BIN_HEADPHONE);
- /* default value for Serial-to-parallel audio interface configuration */
- /* select microphone input by default*/
- snd_soc_write(codec, STA529_ADCCFG, DEFAULT_MIC_SEL);
None of the above should be configured here, leave them at the default values (and offer them as runtime controls if they're not there already). The settings appropriate for one machine may not be appropriate for another.
+static int sta529_resume(struct snd_soc_codec *codec) +{
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- sta529_set_bias_level(codec, codec->suspend_bias_level);
- return 0;
+}
I'd expect this to restore the register cache somewhere.
+MODULE_DESCRIPTION("ASoC STA529 codec driver"); +MODULE_AUTHOR("Rajeev Kumar rajeev-dlh.kumar@st.com"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sta529-codec");
This isn't a platform driver, remove the MODULE_ALIAS.
Hi Mark Please find my answer inlined
On 3/30/2011 4:57 AM, Mark Brown wrote:
On Tue, Mar 29, 2011 at 04:46:59PM +0530, Rajeev Kumar wrote:
+static const struct snd_kcontrol_new sta529_snd_controls[] = {
- SOC_SINGLE("Master Playback Volume", STA529_MVOL, 0, 127, 1),
- SOC_SINGLE("Left Playback Volume", STA529_LVOL, 0, 127, 1),
- SOC_SINGLE("Right Playback Volume", STA529_RVOL, 0, 127, 1),
+};
Left and Right should be a single stereo control (usually done with SOC_DOUBLE). It'd also be better to provide gain information with the _TLV variants of the controls.
ok
Does the master control actually provide a separate control or does it update both left and right channels simultaneously? If the latter then normally you'd just omit it from the driver as the driver can easily do the stereo updates?
master control provide a separate control.
- val = snd_soc_read(codec, STA529_S2PCFG0);
- val |= mode;
- /*this setting will be used with actual h/w */
- snd_soc_write(codec, STA529_S2PCFG0, val);
snd_soc_update_bits() - you're not clearing any bits here so if you change modes things will go wrong.
OK
- case SND_SOC_BIAS_STANDBY:
- case SND_SOC_BIAS_OFF:
snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK,
POWER_STDBY);
snd_soc_update_bits(codec, STA529_FFXCFG0, MUTE_ON_MASK,
MUTE_OFF);
Managing the mute in these states seems odd - could do with some comments in the code if nothing else.
OK,Macro name in in-correct.
+static void sta529_init(struct snd_soc_codec *codec) +{
- /* DAC default master volume */
- snd_soc_write(codec, STA529_MVOL, DEFAULT_MASTER_VOL);
- /* DAC default left volume */
- snd_soc_write(codec, STA529_LVOL, DEFAULT_VOL);
- /* DAC default right volume */
- snd_soc_write(codec, STA529_RVOL, DEFAULT_VOL);
- /* By default route to binary headphones */
- snd_soc_write(codec, STA529_FFXCFG1, DEFAULT_BIN_HEADPHONE);
- /* default value for Serial-to-parallel audio interface configuration */
- /* select microphone input by default*/
- snd_soc_write(codec, STA529_ADCCFG, DEFAULT_MIC_SEL);
None of the above should be configured here, leave them at the default values (and offer them as runtime controls if they're not there already). The settings appropriate for one machine may not be appropriate for another.
Ok
+static int sta529_resume(struct snd_soc_codec *codec) +{
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- sta529_set_bias_level(codec, codec->suspend_bias_level);
- return 0;
+}
I'd expect this to restore the register cache somewhere.
Ok
+MODULE_DESCRIPTION("ASoC STA529 codec driver"); +MODULE_AUTHOR("Rajeev Kumar rajeev-dlh.kumar@st.com"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sta529-codec");
This isn't a platform driver, remove the MODULE_ALIAS. .
Ooops,
Best Regards Rajeev
participants (3)
-
Mark Brown
-
rajeev
-
Rajeev Kumar