[alsa-devel] [PATCH V3 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 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 | 383 ++++++++++++++++++++++++++++ sound/soc/spear/Kconfig | 19 ++ sound/soc/spear/Makefile | 12 + sound/soc/spear/evb_sta529.c | 103 ++++++++ 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, 1618 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 | 383 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b814ed0..d536740 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 49121ad..f889ef5 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..8dda5cd --- /dev/null +++ b/sound/soc/codecs/sta529.c @@ -0,0 +1,383 @@ +/* + * 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 *op_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, op_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, 0xB0, pdata); + else + snd_soc_update_bits(codec, STA529_P2SCFG1, 0xB0, pdata); + + 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->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; + + 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_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) +{ + int i; + u8 data[2]; + u8 *cache = codec->reg_cache; + + for (i = 0; i < ARRAY_SIZE(sta529_reg); i++) { + data[0] = i; + data[1] = cache[i]; + codec->hw_write(codec->control_data, data, 2); + } + + 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");
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 | 103 ++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 103 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..748efeb --- /dev/null +++ b/sound/soc/spear/evb_sta529.c @@ -0,0 +1,103 @@ +/* + * 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-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 | 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, Apr 11, 2011 at 01:30:01PM +0800, 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
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;
+}
I remember Mark had some comments on adding some logs when it's overrun, how do you think?
Hi Lu Guanqun
On 4/11/2011 6:30 PM, Lu Guanqun wrote:
I remember Mark had some comments on adding some logs when it's overrun, how do you think?
Oops, Next patch will contain this changes
Best Rgds Rajeev
On Mon, Apr 11, 2011 at 01:30:00PM +0800, 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 | 383 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b814ed0..d536740 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
I see many drivers depend on I2C, but they don't add the line above. Any reasons for it?
config SND_SOC_STAC9766 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 49121ad..f889ef5 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..8dda5cd --- /dev/null +++ b/sound/soc/codecs/sta529.c @@ -0,0 +1,383 @@ +/*
- 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 *op_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, op_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, 0xB0, pdata);
else
snd_soc_update_bits(codec, STA529_P2SCFG1, 0xB0, pdata);
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->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;
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_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) +{
int i;
u8 data[2];
u8 *cache = codec->reg_cache;
for (i = 0; i < ARRAY_SIZE(sta529_reg); i++) {
data[0] = i;
data[1] = cache[i];
codec->hw_write(codec->control_data, data, 2);
}
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);
As an idiom, I often see the above code surrounded with an #ifdef, see below code snippet from sound/soc/codecs/wm8974.c
static int __init wm8974_modinit(void) { int ret = 0; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) ret = i2c_add_driver(&wm8974_i2c_driver); if (ret != 0) { printk(KERN_ERR "Failed to register wm8974 I2C driver: %d\n", ret); } #endif return ret; }
Maybe you should add the simlar structure as well.
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");
1.6.0.2
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Mon, Apr 11, 2011 at 08:53:18PM +0800, Lu Guanqun wrote:
Please *always* delete irrelevant context from replies - it makes it much easier to find the new text.
On Mon, Apr 11, 2011 at 01:30:00PM +0800, Rajeev Kumar wrote:
+config SND_SOC_STA529
tristate
depends on I2C
I see many drivers depend on I2C, but they don't add the line above. Any reasons for it?
It won't actually do anything - select ignores depends.
+static int __init sta529_modinit(void) +{
int ret = 0;
ret = i2c_add_driver(&sta529_i2c_driver);
As an idiom, I often see the above code surrounded with an #ifdef, see below code snippet from sound/soc/codecs/wm8974.c
This is only for devices that support more than one control interface. If the device only supports one control interface there is no need to make anything conditional.
On Mon, Apr 11, 2011 at 09:35:01PM +0800, Mark Brown wrote:
On Mon, Apr 11, 2011 at 08:53:18PM +0800, Lu Guanqun wrote:
Please *always* delete irrelevant context from replies - it makes it much easier to find the new text.
Sure.
+static int __init sta529_modinit(void) +{
int ret = 0;
ret = i2c_add_driver(&sta529_i2c_driver);
As an idiom, I often see the above code surrounded with an #ifdef, see below code snippet from sound/soc/codecs/wm8974.c
This is only for devices that support more than one control interface. If the device only supports one control interface there is no need to make anything conditional.
Well, but there are many codecs which support only one control interface while having this #ifdef condition...
On Mon, Apr 11, 2011 at 11:09:37PM +0800, Lu Guanqun wrote:
On Mon, Apr 11, 2011 at 09:35:01PM +0800, Mark Brown wrote:
This is only for devices that support more than one control interface. If the device only supports one control interface there is no need to make anything conditional.
Well, but there are many codecs which support only one control interface while having this #ifdef condition...
Usually you'll find that these are devices which support multiple control interfaces but where the driver has only implemented one so far for some reason.
On Tue, Apr 12, 2011 at 07:54:20AM +0800, Mark Brown wrote:
On Mon, Apr 11, 2011 at 11:09:37PM +0800, Lu Guanqun wrote:
On Mon, Apr 11, 2011 at 09:35:01PM +0800, Mark Brown wrote:
This is only for devices that support more than one control interface. If the device only supports one control interface there is no need to make anything conditional.
Well, but there are many codecs which support only one control interface while having this #ifdef condition...
Usually you'll find that these are devices which support multiple control interfaces but where the driver has only implemented one so far for some reason.
OK. Thanks for the explanation.
On Mon, Apr 11, 2011 at 11:00:00AM +0530, Rajeev Kumar wrote:
+static const char *op_mode_text[] = { "slave", "master"};
What is op_mode? This sounds like it should be configured by set_dai_fmt()...
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("pwm select", pwm_src_enum),
- SOC_ENUM("mode select", mode_src_enum),
+};
ALSA control names are idiomatically things like "PWM Select" with capitalisation.
- /*store the label for powers down audio subsystem for suspend.This is
** used by soc core layer*/
- codec->bias_level = level;
The formatting of this comment isn't terribly idiomatic.
+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;
- }
You shouldn't need to be assigning the I/O functions if you set the control type. If the device only supports I2C that can just be hard coded.
+static int sta529_resume(struct snd_soc_codec *codec) +{
- int i;
- u8 data[2];
- u8 *cache = codec->reg_cache;
- for (i = 0; i < ARRAY_SIZE(sta529_reg); i++) {
data[0] = i;
data[1] = cache[i];
codec->hw_write(codec->control_data, data, 2);
- }
It looks like you can use the standard cache sync implementation here? snd_soc_cache_sync().
At Mon, 11 Apr 2011 07:56:22 -0700, Mark Brown wrote:
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("pwm select", pwm_src_enum),
- SOC_ENUM("mode select", mode_src_enum),
+};
ALSA control names are idiomatically things like "PWM Select" with capitalisation.
Actually, the capital words are no mandatory requirement (although it's conventionally so). It's just alsa-mixer grouping rule that requires the capital words "Play", "Capture", "Volume", "Switch", etc.
You don't have to obey Capitalism :)
Takashi
On Thu, Apr 14, 2011 at 11:54:11AM +0200, Takashi Iwai wrote:
Mark Brown wrote:
ALSA control names are idiomatically things like "PWM Select" with capitalisation.
Actually, the capital words are no mandatory requirement (although it's conventionally so). It's just alsa-mixer grouping rule that requires the capital words "Play", "Capture", "Volume", "Switch", etc.
Right, but they generally are hence idiomatically (as opposed to having a technical requirement).
You don't have to obey Capitalism :)
But the UIs tend to look neater if you do.
At Thu, 14 Apr 2011 05:18:47 -0700, Mark Brown wrote:
On Thu, Apr 14, 2011 at 11:54:11AM +0200, Takashi Iwai wrote:
Mark Brown wrote:
ALSA control names are idiomatically things like "PWM Select" with capitalisation.
Actually, the capital words are no mandatory requirement (although it's conventionally so). It's just alsa-mixer grouping rule that requires the capital words "Play", "Capture", "Volume", "Switch", etc.
Right, but they generally are hence idiomatically (as opposed to having a technical requirement).
Sure.
You don't have to obey Capitalism :)
But the UIs tend to look neater if you do.
De facto standard in the world is Capitalism, of course. (Heh, don't take it seriously. My post is just a silly spinal reflex :)
Takashi
On Thu, Apr 14, 2011 at 05:54:11PM +0800, Takashi Iwai wrote:
At Mon, 11 Apr 2011 07:56:22 -0700, Mark Brown wrote:
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("pwm select", pwm_src_enum),
- SOC_ENUM("mode select", mode_src_enum),
+};
ALSA control names are idiomatically things like "PWM Select" with capitalisation.
Actually, the capital words are no mandatory requirement (although it's conventionally so). It's just alsa-mixer grouping rule that requires the capital words "Play", "Capture", "Volume", "Switch", etc.
Hi Takashi,
Where can I find these alsa-mixer grouping rule? So that I can obey these rules and make them appear in the right view in alsamixer.
You don't have to obey Capitalism :)
Takashi _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Thu, Apr 14, 2011 at 10:19:36PM +0800, Lu Guanqun wrote:
On Thu, Apr 14, 2011 at 05:54:11PM +0800, Takashi Iwai wrote:
At Mon, 11 Apr 2011 07:56:22 -0700, Mark Brown wrote:
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("pwm select", pwm_src_enum),
- SOC_ENUM("mode select", mode_src_enum),
+};
ALSA control names are idiomatically things like "PWM Select" with capitalisation.
Actually, the capital words are no mandatory requirement (although it's conventionally so). It's just alsa-mixer grouping rule that requires the capital words "Play", "Capture", "Volume", "Switch", etc.
Hi Takashi,
Where can I find these alsa-mixer grouping rule? So that I can obey these rules and make them appear in the right view in alsamixer.
Hi Takashi,
I'm reading this comment from file `sound/aoa/codecs/tas.c`:
/* If we name this 'Input Source', it properly shows up in * alsamixer as a selection, * but it's shown under the * 'Playback' category. * If I name it 'Capture Source', it shows up in strange * ways (two bools of which one can be selected at a * time) but at least it's shown in the 'Capture' * category. * I was told that this was due to backward compatibility, * but I don't understand then why the mangling is *not* * done when I name it "Input Source"..... */
I'm wondering what's the story behind? Can you shed some lights?
You don't have to obey Capitalism :)
Takashi _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
-- guanqun _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
At Thu, 14 Apr 2011 22:28:45 +0800, Lu Guanqun wrote:
On Thu, Apr 14, 2011 at 10:19:36PM +0800, Lu Guanqun wrote:
On Thu, Apr 14, 2011 at 05:54:11PM +0800, Takashi Iwai wrote:
At Mon, 11 Apr 2011 07:56:22 -0700, Mark Brown wrote:
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("pwm select", pwm_src_enum),
- SOC_ENUM("mode select", mode_src_enum),
+};
ALSA control names are idiomatically things like "PWM Select" with capitalisation.
Actually, the capital words are no mandatory requirement (although it's conventionally so). It's just alsa-mixer grouping rule that requires the capital words "Play", "Capture", "Volume", "Switch", etc.
Hi Takashi,
Where can I find these alsa-mixer grouping rule? So that I can obey these rules and make them appear in the right view in alsamixer.
Hi Takashi,
I'm reading this comment from file `sound/aoa/codecs/tas.c`:
/* If we name this 'Input Source', it properly shows up in * alsamixer as a selection, * but it's shown under the * 'Playback' category. * If I name it 'Capture Source', it shows up in strange * ways (two bools of which one can be selected at a * time) but at least it's shown in the 'Capture' * category. * I was told that this was due to backward compatibility, * but I don't understand then why the mangling is *not* * done when I name it "Input Source"..... */
I'm wondering what's the story behind? Can you shed some lights?
In alsa-mixer abstraction, the "Capture Source" enum control is split over multiple switches (a switch per enum item). This was a historical design at the time SoundBlaster is still standard.
One of the problems with this abstraction is that it can't handle multiple capture-source controls. So, many drivers use nowadays "Input Source" control instead. But, in the earlier alsa-lib, this wasn't handled as the capture control, thus it appears as a global (both directional) control in alsamixer.
Now, this control is handled as a capture control in alsa-lib, so the appearance is corrected.
Takashi
On Fri, Apr 15, 2011 at 05:27:29PM +0800, Takashi Iwai wrote:
In alsa-mixer abstraction, the "Capture Source" enum control is split over multiple switches (a switch per enum item). This was a historical design at the time SoundBlaster is still standard.
One of the problems with this abstraction is that it can't handle multiple capture-source controls. So, many drivers use nowadays "Input Source" control instead. But, in the earlier alsa-lib, this wasn't handled as the capture control, thus it appears as a global (both directional) control in alsamixer.
Now, this control is handled as a capture control in alsa-lib, so the appearance is corrected.
Thanks for your insightful information. :) Much appreciated!
At Thu, 14 Apr 2011 22:19:36 +0800, Lu Guanqun wrote:
On Thu, Apr 14, 2011 at 05:54:11PM +0800, Takashi Iwai wrote:
At Mon, 11 Apr 2011 07:56:22 -0700, Mark Brown wrote:
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("pwm select", pwm_src_enum),
- SOC_ENUM("mode select", mode_src_enum),
+};
ALSA control names are idiomatically things like "PWM Select" with capitalisation.
Actually, the capital words are no mandatory requirement (although it's conventionally so). It's just alsa-mixer grouping rule that requires the capital words "Play", "Capture", "Volume", "Switch", etc.
Hi Takashi,
Where can I find these alsa-mixer grouping rule?
See Documentation/sound/alsa/ControlNames.txt. It's old but still valid in most cases.
Since then there are a few additions and exceptions. "Input Source" control is one of them. Also, "Enum" is now also a function word.
Takashi
Hi Mark please find my answer below.
On 4/11/2011 8:26 PM, Mark Brown wrote:
On Mon, Apr 11, 2011 at 11:00:00AM +0530, Rajeev Kumar wrote:
+static const char *op_mode_text[] = { "slave", "master"};
What is op_mode? This sounds like it should be configured by set_dai_fmt()...
OK, I will change the name.
+static const struct snd_kcontrol_new sta529_new_snd_controls[] = {
- SOC_ENUM("pwm select", pwm_src_enum),
- SOC_ENUM("mode select", mode_src_enum),
+};
ALSA control names are idiomatically things like "PWM Select" with capitalisation.
Using PWM Select
- /*store the label for powers down audio subsystem for suspend.This is
** used by soc core layer*/
- codec->bias_level = level;
The formatting of this comment isn't terribly idiomatic.
OK
+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;
- }
You shouldn't need to be assigning the I/O functions if you set the control type. If the device only supports I2C that can just be hard coded.
explained below.
+static int sta529_resume(struct snd_soc_codec *codec) +{
- int i;
- u8 data[2];
- u8 *cache = codec->reg_cache;
- for (i = 0; i < ARRAY_SIZE(sta529_reg); i++) {
data[0] = i;
data[1] = cache[i];
codec->hw_write(codec->control_data, data, 2);
- }
It looks like you can use the standard cache sync implementation here? snd_soc_cache_sync(). .
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.This version does not have snd_soc_cache_sync() function.
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 Rgds Rajeev
On Wed, Apr 20, 2011 at 09:54:47AM +0530, rajeev wrote:
On 4/11/2011 8:26 PM, Mark Brown wrote:
On Mon, Apr 11, 2011 at 11:00:00AM +0530, Rajeev Kumar wrote:
+static const char *op_mode_text[] = { "slave", "master"};
What is op_mode? This sounds like it should be configured by set_dai_fmt()...
OK, I will change the name.
You're missing the point here. The name isn't an issue.
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.This version does not have snd_soc_cache_sync() function.
This isn't how mainline works. You're not submitting code against some old, released kernel version. You're submitting code for the development version of the kernel and need to fit in with that. Code for an older kernel may not even compile with current development kernels. For integration testing -next is often a good bet.
Hi Mark
On 4/20/2011 4:26 PM, Mark Brown wrote:
On Wed, Apr 20, 2011 at 09:54:47AM +0530, rajeev wrote:
On 4/11/2011 8:26 PM, Mark Brown wrote:
On Mon, Apr 11, 2011 at 11:00:00AM +0530, Rajeev Kumar wrote:
+static const char *op_mode_text[] = { "slave", "master"};
What is op_mode? This sounds like it should be configured by set_dai_fmt()...
OK, I will change the name.
You're missing the point here. The name isn't an issue.
Oops Got the point, ThanX
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.This version does not have snd_soc_cache_sync() function.
This isn't how mainline works. You're not submitting code against some old, released kernel version. You're submitting code for the development version of the kernel and need to fit in with that. Code for an older kernel may not even compile with current development kernels. For integration testing -next is often a good bet. .
OK, I will do the changes as per your guidelines.
best rgds Rajeev
Hi Mark
On 4/20/2011 4:26 PM, Mark Brown wrote:
On Wed, Apr 20, 2011 at 09:54:47AM +0530, rajeev wrote:
On 4/11/2011 8:26 PM, Mark Brown wrote:
On Mon, Apr 11, 2011 at 11:00:00AM +0530, Rajeev Kumar wrote:
+static const char *op_mode_text[] = { "slave", "master"};
What is op_mode? This sounds like it should be configured by set_dai_fmt()...
OK, I will change the name.
You're missing the point here. The name isn't an issue.
Oops Got the point, ThanX
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.This version does not have snd_soc_cache_sync() function.
This isn't how mainline works. You're not submitting code against some old, released kernel version. You're submitting code for the development version of the kernel and need to fit in with that. Code for an older kernel may not even compile with current development kernels. For integration testing -next is often a good bet. .
OK, I will do the changes as per your guidelines.
best rgds Rajeev
On Mon, Apr 11, 2011 at 01:30:00PM +0800, Rajeev Kumar wrote:
+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, 0xB0, pdata);
else
snd_soc_update_bits(codec, STA529_P2SCFG1, 0xB0, pdata);
Hi,
Reading the spec, the bit 7 and bit 6 of these two registers are related to the length of data.
While from your code, you're masking bit 10110000, which is weird. Shouldn't it be 0xC0?
Besides, the value to snd_soc_update_bits should be shifted accordingly. So the improved one should be something like this: snd_soc_update_bits(codec, STA529_P2SCFG1, 0xC0, (pdata << 6));
participants (5)
-
Lu Guanqun
-
Mark Brown
-
rajeev
-
Rajeev Kumar
-
Takashi Iwai