[alsa-devel] [PATCH 0/8] Add ASoC drivers for SPEAr platform
This patch set adds support for ASoC sound drivers on ST's SPEAr platforms. Details of the SPEAr platforms can be seen here:
http://www.st.com/internet/mcu/product/250658.jsp
This patch set supports the following modules:
1. Audio codec sta529 (connected to I2S controller). 2. SPDIF IN dummy Codec. 3. Synopsys Designware I2S controller. 4. ST SPDIF IN controller. 5. ST SPDIF OUT controller.
This patch set has been tested for linux-kernel-version 3.3-rc3 on SPEAr platform.
In past, I had circulated the patch for the same and got some review comments. I have incorporate those commnets also. Please find the link below. http://mailman.alsa-project.org/pipermail/alsa-devel/2011-April/038738.html
Rajeev Kumar (6): sound:asoc: Add support for STA529 Audio Codec sound:asoc: Add support for synopsys i2s controller as per ASoC framework. sound:asoc: Add support for SPEAr ASoC pcm layer. sound:asoc: Add support for SPEAr ASoC machine driver. sound:asoc: Add Kconfig and Makefile to support SPEAr audio driver sound:asoc: Update Kconfig and Makefile(sound/soc/) to support SPEAr audio
Vipin Kumar (2): sound:asoc:spdif_in: Add spdif IN support sound:asoc:spdif_out: Add spdif out support
include/linux/designware_i2s.h | 63 ++++ sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sta529.c | 414 ++++++++++++++++++++++++++ sound/soc/spear/Kconfig | 46 +++ sound/soc/spear/Makefile | 16 + sound/soc/spear/designware_i2s.c | 610 ++++++++++++++++++++++++++++++++++++++ sound/soc/spear/spdif_in.c | 304 +++++++++++++++++++ sound/soc/spear/spdif_in_regs.h | 60 ++++ sound/soc/spear/spdif_out.c | 397 +++++++++++++++++++++++++ sound/soc/spear/spdif_out_regs.h | 79 +++++ sound/soc/spear/spear_evb.c | 278 +++++++++++++++++ sound/soc/spear/spear_pcm.c | 460 ++++++++++++++++++++++++++++ sound/soc/spear/spear_pcm.h | 33 ++ 16 files changed, 2769 insertions(+), 0 deletions(-) create mode 100644 include/linux/designware_i2s.h 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/designware_i2s.c create mode 100644 sound/soc/spear/spdif_in.c create mode 100644 sound/soc/spear/spdif_in_regs.h create mode 100644 sound/soc/spear/spdif_out.c create mode 100644 sound/soc/spear/spdif_out_regs.h create mode 100644 sound/soc/spear/spear_evb.c create mode 100644 sound/soc/spear/spear_pcm.c create mode 100644 sound/soc/spear/spear_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 | 414 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 421 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sta529.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 59d8efa..2c003d1 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -49,6 +49,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_SPDIF select SND_SOC_SSM2602 if SND_SOC_I2C_AND_SPI select SND_SOC_STA32X 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 @@ -252,6 +253,10 @@ config SND_SOC_SSM2602 config SND_SOC_STA32X 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 0c0e6ce..a0e43cd 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -39,6 +39,7 @@ snd-soc-sn95031-objs := sn95031.o snd-soc-spdif-objs := spdif_transciever.o spdif_receiver.o snd-soc-ssm2602-objs := ssm2602.o snd-soc-sta32x-objs := sta32x.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 @@ -143,6 +144,7 @@ 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_STA32X) += snd-soc-sta32x.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..1294947 --- /dev/null +++ b/sound/soc/codecs/sta529.c @@ -0,0 +1,414 @@ +/* + * ASoC codec driver for spear platform + * + * sound/soc/codecs/sta529.c -- spear ALSA Soc codec driver + * + * Copyright (C) 2012 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_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + 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] = { + 0x35, 0xc8, 0x50, 0x00, + 0x00, 0x00, 0x02, 0x00, + 0x02, 0x05, 0xb2, 0x41, + 0x92, 0x41, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xD2, 0x40, + 0x21, 0xef, 0x04, 0x06, + 0x41, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, +}; + +struct sta529 { + unsigned int sysclk; + enum snd_soc_control_type control_type; + void *control_data; +}; + +static const char *pwm_mode_text[] = { "binary", "headphone", "ternary", + "phase-shift"}; +static const char *interface_mode_text[] = { "slave", "master"}; + +static const struct soc_enum pwm_src_enum = +SOC_ENUM_SINGLE(STA529_FFXCFG1, 4, 4, pwm_mode_text); + +static const struct soc_enum mode_src_enum = +SOC_ENUM_SINGLE(STA529_P2SCFG0, 0, 2, interface_mode_text); + +static const struct snd_kcontrol_new sta529_new_snd_controls[] = { + SOC_ENUM("PWM Select", pwm_src_enum), + SOC_ENUM("MODE Select", mode_src_enum), +}; + +static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -9150, 50, 0); +static const DECLARE_TLV_DB_SCALE(master_vol_tlv, -12750, 50, 0); + +static const struct snd_kcontrol_new sta529_snd_controls[] = { + SOC_DOUBLE_R_TLV("Digital Playback Volume", STA529_LVOL, STA529_RVOL, 0, + 127, 0, out_gain_tlv), + SOC_SINGLE_TLV("Master Playback Volume", STA529_MVOL, 0, 127, 1, + master_vol_tlv), +}; + +static int +sta529_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK, + POWER_UP); + snd_soc_update_bits(codec, STA529_MISC, 1, 0x01); + break; + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, STA529_FFXCFG0, POWER_CNTLMSAK, + POWER_STDBY); + /* Making FFX output to zero */ + snd_soc_update_bits(codec, STA529_FFXCFG0, FFX_MASK, + FFX_OFF); + snd_soc_update_bits(codec, STA529_MISC, 1, 0x00); + + break; + } + + /* + * store the label for powers down audio subsystem for suspend.This is + * used by soc core layer + */ + codec->dapm.bias_level = level; + return 0; + +} + +static 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, play_freq_val, record_freq_val; + + 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; + } + + switch (params_rate(params)) { + case 8000: + case 11025: + play_freq_val = 0; + record_freq_val = 2; + break; + case 16000: + case 22050: + play_freq_val = 1; + record_freq_val = 0; + break; + + case 32000: + case 44100: + case 48000: + play_freq_val = 2; + record_freq_val = 0; + break; + default: + printk(KERN_WARNING, "bad rate\n"); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + snd_soc_update_bits(codec, STA529_S2PCFG1, 0xC0, (pdata << 6)); + snd_soc_update_bits(codec, STA529_MISC, 0x0C, + (play_freq_val << 4)); + } else { + snd_soc_update_bits(codec, STA529_P2SCFG1, 0xC0, (pdata << 6)); + snd_soc_update_bits(codec, STA529_MISC, 0x0C, + (record_freq_val << 2)); + + } + + sta529_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + mdelay(10); + + 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_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 struct snd_soc_dai_ops sta529_dai_ops = { + .hw_params = sta529_hw_params, + .set_fmt = sta529_set_dai_fmt, + .digital_mute = sta529_mute, +}; + +static struct snd_soc_dai_driver sta529_dai = { + .name = "sta529-audio", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STA529_RATES, + .formats = STA529_FORMAT, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = STA529_RATES, + .formats = STA529_FORMAT, + }, + .ops = &sta529_dai_ops, +}; + +static int sta529_probe(struct snd_soc_codec *codec) +{ + struct sta529 *sta529 = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, sta529->control_type); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + snd_soc_add_controls(codec, sta529_snd_controls, + ARRAY_SIZE(sta529_snd_controls)); + + snd_soc_add_controls(codec, sta529_new_snd_controls, + ARRAY_SIZE(sta529_new_snd_controls)); + return 0; +} + +/* power down chip */ +static int sta529_remove(struct snd_soc_codec *codec) +{ + sta529_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int sta529_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + sta529_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int sta529_resume(struct snd_soc_codec *codec) +{ + snd_soc_cache_sync(codec); + sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + sta529_set_bias_level(codec, codec->dapm.suspend_bias_level); + + return 0; +} + +struct snd_soc_codec_driver soc_codec_dev_sta529 = { + .probe = sta529_probe, + .remove = sta529_remove, + .set_bias_level = sta529_set_bias_level, + .suspend = sta529_suspend, + .resume = sta529_resume, + .reg_cache_size = STA529_CACHEREGNUM, + .reg_word_size = sizeof(u8), + .reg_cache_default = sta529_reg, + +}; + +static __devinit int sta529_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct sta529 *sta529; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + + sta529 = kzalloc(sizeof(struct sta529), GFP_KERNEL); + if (sta529 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, sta529); + sta529->control_data = i2c; + sta529->control_type = SND_SOC_I2C; + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_sta529, &sta529_dai, 1); + if (ret < 0) + kfree(sta529); + return ret; +} + +static int sta529_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id sta529_i2c_id[] = { + { "sta529", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sta529_i2c_id); + +static struct i2c_driver sta529_i2c_driver = { + .driver = { + .name = "sta529", + .owner = THIS_MODULE, + }, + .probe = sta529_i2c_probe, + .remove = __devexit_p(sta529_i2c_remove), + .id_table = sta529_i2c_id, +}; + +static int __init sta529_modinit(void) +{ + int ret = 0; + + ret = i2c_add_driver(&sta529_i2c_driver); + if (ret != 0) + printk(KERN_ERR "Failed to reg sta529 I2C driver: %d\n", ret); + + return ret; + +} +module_init(sta529_modinit); + +static void __exit sta529_exit(void) +{ + i2c_del_driver(&sta529_i2c_driver); +} +module_exit(sta529_exit); + +MODULE_DESCRIPTION("ASoC STA529 codec driver"); +MODULE_AUTHOR("Rajeev Kumar rajeev-dlh.kumar@st.com"); +MODULE_LICENSE("GPL");
On Tue, Mar 20, 2012 at 05:03:45PM +0530, Rajeev Kumar wrote:
This patch adds the support for STA529 audio codec. Details of the audio codec can be seen here: http://www.st.com/internet/imag_video/product/159187.jsp
Please use subject lines appropriate for the subsystem. If your commits look visually different to all the other commits in the log that's not a good sign.
+config SND_SOC_STA529
- tristate
- depends on I2C
Drop the dependency, none of the other CODECs have this for a reason...
+#include <linux/platform_device.h>
If you need this something went wrong...
+static const u8 sta529_reg[STA529_CACHEREGNUM] = {
Use regmap for register I/O.
+struct sta529 {
- unsigned int sysclk;
- enum snd_soc_control_type control_type;
- void *control_data;
Your device only supports I2C, no need for the infrastructure for other buses (though this will go away with regmap).
+static const char *pwm_mode_text[] = { "binary", "headphone", "ternary",
- "phase-shift"};
+static const char *interface_mode_text[] = { "slave", "master"};
ALSA controls always use capitalisation.
- 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);
One of these has magic numbers and the other doesn't, and the magic numbers are a little confusing as the mask is written in decimal but the binary in hex.
- 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;
- }
Return an error on unsupported sample sizes.
- default:
printk(KERN_WARNING, "bad rate\n");
return -EINVAL;
- }
dev_warn() - always use the dev_ prints where possible.
- sta529_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
- mdelay(10);
Absolutely no - why are you doing this?
+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);
You're always setting the same value here...
- 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));
Use the initialisers in the card struct.
- sta529 = kzalloc(sizeof(struct sta529), GFP_KERNEL);
- if (sta529 == NULL)
return -ENOMEM;
devm_kzalloc().
+static int __init sta529_modinit(void)
module_i2c_driver().
Hello Mark
On 3/20/2012 8:55 PM, Mark Brown wrote:
On Tue, Mar 20, 2012 at 05:03:45PM +0530, Rajeev Kumar wrote:
This patch adds the support for STA529 audio codec. Details of the audio codec can be seen here: http://www.st.com/internet/imag_video/product/159187.jsp
Please use subject lines appropriate for the subsystem. If your commits look visually different to all the other commits in the log that's not a good sign.
Ok
+config SND_SOC_STA529
- tristate
- depends on I2C
Agreed
Drop the dependency, none of the other CODECs have this for a reason...
+#include<linux/platform_device.h>
If you need this something went wrong...
Oops
+static const u8 sta529_reg[STA529_CACHEREGNUM] = {
Use regmap for register I/O.
OK
+struct sta529 {
- unsigned int sysclk;
- enum snd_soc_control_type control_type;
- void *control_data;
Your device only supports I2C, no need for the infrastructure for other buses (though this will go away with regmap).
Agreed,
+static const char *pwm_mode_text[] = { "binary", "headphone", "ternary",
- "phase-shift"};
+static const char *interface_mode_text[] = { "slave", "master"};
ALSA controls always use capitalisation.
you mean to say static const char *interface_mode_text[] = { "SLAVE", "MASTER"}
- 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);
One of these has magic numbers and the other doesn't, and the magic numbers are a little confusing as the mask is written in decimal but the binary in hex.
OK I will take mask for this.
- 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;
- }
Return an error on unsupported sample sizes.
In default , it is returning an error.
- default:
printk(KERN_WARNING, "bad rate\n");
return -EINVAL;
- }
dev_warn() - always use the dev_ prints where possible.
Agreed,
- sta529_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
- mdelay(10);
Absolutely no - why are you doing this?
In probe I am putting codec in standby mode. so to come out of this sta529_set_bias_level(codec, SND_SOC_BIAS_PREPARE) is called. Since there are some transition time between STANDBY and ON/PREAPRE, so a delay is introduced in the code.
+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);
You're always setting the same value here...
Oops, it should be snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, mute_reg);
- 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));
Use the initialisers in the card struct.
ok
- sta529 = kzalloc(sizeof(struct sta529), GFP_KERNEL);
- if (sta529 == NULL)
return -ENOMEM;
devm_kzalloc().
OK
Best Regards Rajeev
+static int __init sta529_modinit(void)
module_i2c_driver().
+static const char *pwm_mode_text[] = { "binary", "headphone", "ternary",
- "phase-shift"};
+static const char *interface_mode_text[] = { "slave", "master"};
ALSA controls always use capitalisation.
you mean to say static const char *interface_mode_text[] = { "SLAVE", "MASTER"}
No, only the first letter upper case.
- sta529_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
- mdelay(10);
Absolutely no - why are you doing this?
In probe I am putting codec in standby mode. so to come out of this sta529_set_bias_level(codec, SND_SOC_BIAS_PREPARE) is called. Since there are some transition time between STANDBY and ON/PREAPRE, so a delay is introduced in the code.
The framework will take care of taking the CODEC out of standby mode when it is required, so you shouldn't need it here. Also, if the delay is required put it into the set_bias_level function.
+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);
You're always setting the same value here...
Oops, it should be snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, mute_reg);
If you use snd_soc_update_bits, you don't have to read the register first.
Hello Clausen,
On 3/23/2012 2:37 PM, Lars-Peter Clausen wrote:
+static const char *pwm_mode_text[] = { "binary", "headphone", "ternary",
- "phase-shift"};
+static const char *interface_mode_text[] = { "slave", "master"};
ALSA controls always use capitalisation.
you mean to say static const char *interface_mode_text[] = { "SLAVE", "MASTER"}
No, only the first letter upper case.
- sta529_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
- mdelay(10);
Absolutely no - why are you doing this?
In probe I am putting codec in standby mode. so to come out of this sta529_set_bias_level(codec, SND_SOC_BIAS_PREPARE) is called. Since there are some transition time between STANDBY and ON/PREAPRE, so a delay is introduced in the code.
Actually I was hoping for the same but not able to locate the code. Ok, I will made the changes.
The framework will take care of taking the CODEC out of standby mode when it is required, so you shouldn't need it here. Also, if the delay is required put it into the set_bias_level function.
+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);
You're always setting the same value here...
Oops, it should be snd_soc_update_bits(codec, STA529_FFXCFG0, 0x80, mute_reg);
If you use snd_soc_update_bits, you don't have to read the register first.
Ok
Best Regards Rajeev
.
On 03/20/2012 12:33 PM, Rajeev Kumar wrote:
+static const char *interface_mode_text[] = { "slave", "master"};
Master/slave mode should be configured using the set_dai_fmt callback.
+static struct snd_soc_dai_ops sta529_dai_ops = {
const
- .hw_params = sta529_hw_params,
- .set_fmt = sta529_set_dai_fmt,
- .digital_mute = sta529_mute,
+};
+/* 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)
The suspend callback doesn't take the state parameter anymore.
+{
- sta529_set_bias_level(codec, SND_SOC_BIAS_OFF);
- return 0;
+}
+static int sta529_resume(struct snd_soc_codec *codec) +{
- snd_soc_cache_sync(codec);
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- sta529_set_bias_level(codec, codec->dapm.suspend_bias_level);
- return 0;
+}
+struct snd_soc_codec_driver soc_codec_dev_sta529 = {
A better name is be sta520_codec_driver [...]
+static int sta529_i2c_remove(struct i2c_client *client)
__devexit
+{
- snd_soc_unregister_codec(&client->dev);
- kfree(i2c_get_clientdata(client));
- return 0;
+}
river");
+MODULE_AUTHOR("Rajeev Kumar rajeev-dlh.kumar@st.com"); +MODULE_LICENSE("GPL");
Hello Lars,
On 3/20/2012 11:27 PM, Lars-Peter Clausen wrote:
On 03/20/2012 12:33 PM, Rajeev Kumar wrote:
+static const char *interface_mode_text[] = { "slave", "master"};
Master/slave mode should be configured using the set_dai_fmt callback.
You mean to say with the help of clock direction. I think you can do it in either way. By this, I am giving full control to user for the selection.
+static struct snd_soc_dai_ops sta529_dai_ops = {
const
Ok,
- .hw_params = sta529_hw_params,
- .set_fmt = sta529_set_dai_fmt,
- .digital_mute = sta529_mute,
+};
+/* 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)
The suspend callback doesn't take the state parameter anymore.
Oops,
+{
- sta529_set_bias_level(codec, SND_SOC_BIAS_OFF);
- return 0;
+}
+static int sta529_resume(struct snd_soc_codec *codec) +{
- snd_soc_cache_sync(codec);
- sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- sta529_set_bias_level(codec, codec->dapm.suspend_bias_level);
- return 0;
+}
+struct snd_soc_codec_driver soc_codec_dev_sta529 = {
A better name is be sta520_codec_driver
Ok,
[...]
+static int sta529_i2c_remove(struct i2c_client *client)
__devexit
+{
- snd_soc_unregister_codec(&client->dev);
- kfree(i2c_get_clientdata(client));
- return 0;
+}
river");
This comment is not clear to me , please explain.
Best Regards Rajeev
+MODULE_AUTHOR("Rajeev Kumarrajeev-dlh.kumar@st.com"); +MODULE_LICENSE("GPL");
.
On 03/23/2012 05:00 AM, Rajeev kumar wrote:
Hello Lars,
On 3/20/2012 11:27 PM, Lars-Peter Clausen wrote:
On 03/20/2012 12:33 PM, Rajeev Kumar wrote:
+static const char *interface_mode_text[] = { "slave", "master"};
Master/slave mode should be configured using the set_dai_fmt callback.
You mean to say with the help of clock direction. I think you can do it in either way. By this, I am giving full control to user for the selection.
Maybe I'm misunderstanding what this control does. But if it is about putting the CODEC in master or slave mode, so whether it should generate the frame- and bitclocks or if the other end of the DAI link should do it, then this should be configured using set_dai_fmt. Making this runtime selectable from userspace doesn't make much sense since both sides of the DAI link have to agree on who is master and who is slave. If you just change one side the link will just stop working.
[...]
+static int sta529_i2c_remove(struct i2c_client *client)
__devexit
+{
- snd_soc_unregister_codec(&client->dev);
- kfree(i2c_get_clientdata(client));
- return 0;
+}
river");
This comment is not clear to me , please explain.
Annotate the remove function with __devexit. Like this: +static int __devexit sta529_i2c_remove(struct i2c_client *client)
Hello Lars,
On 3/23/2012 2:21 PM, Lars-Peter Clausen wrote:
On 03/23/2012 05:00 AM, Rajeev kumar wrote:
Hello Lars,
On 3/20/2012 11:27 PM, Lars-Peter Clausen wrote:
On 03/20/2012 12:33 PM, Rajeev Kumar wrote:
+static const char *interface_mode_text[] = { "slave", "master"};
Master/slave mode should be configured using the set_dai_fmt callback.
You mean to say with the help of clock direction. I think you can do it in either way. By this, I am giving full control to user for the selection.
Maybe I'm misunderstanding what this control does. But if it is about putting the CODEC in master or slave mode, so whether it should generate the frame- and bitclocks or if the other end of the DAI link should do it, then this should be configured using set_dai_fmt. Making this runtime selectable from userspace doesn't make much sense since both sides of the DAI link have to agree on who is master and who is slave. If you just change one side the link will just stop working.
Yes, This will put CODEC in master or slave mode. OK I will do it in set_dai_fmt.
[...]
+static int sta529_i2c_remove(struct i2c_client *client)
__devexit
+{
- snd_soc_unregister_codec(&client->dev);
- kfree(i2c_get_clientdata(client));
- return 0;
+}
river");
This comment is not clear to me , please explain.
Annotate the remove function with __devexit. Like this: +static int __devexit sta529_i2c_remove(struct i2c_client *client)
Ok,
Best Regards Rajeev
.
This patch add support for synopsys I2S controller as per the ASoC framework.
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com --- include/linux/designware_i2s.h | 63 ++++ sound/soc/spear/designware_i2s.c | 610 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 673 insertions(+), 0 deletions(-) create mode 100644 include/linux/designware_i2s.h create mode 100644 sound/soc/spear/designware_i2s.c
diff --git a/include/linux/designware_i2s.h b/include/linux/designware_i2s.h new file mode 100644 index 0000000..5433b06 --- /dev/null +++ b/include/linux/designware_i2s.h @@ -0,0 +1,63 @@ +/* +* linux/designware_i2s.h +* +* Copyright (ST) 2011 Rajeev Kumar (rajeev-dlh.kumar@st.com) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* +*/ + +#ifndef DESIGNWARE_I2S_H +#define DESIGNWARE_I2S_H + +#include <linux/dmaengine.h> +#include <linux/types.h> + +/* + * struct i2s_clk_config_data - represent i2s clk configuration data + * @chan_nr: number of channel + * @data_width: number of bits per sample (8/16/24/32 bit) + * @sample_rate: sampling frequency (8Khz, 16Khz, 32Khz, 44Khz, 48Khz) + */ +struct i2s_clk_config_data { + int chan_nr; + u32 data_width; + u32 sample_rate; +}; + +struct i2s_platform_data { + #define PLAY (1 << 0) + #define RECORD (1 << 1) + unsigned int cap; + int channel; + u8 swidth; + u32 snd_rates; + + void *play_dma_data; + void *capture_dma_data; + bool (*filter)(struct dma_chan *chan, void *slave); + int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); +}; + +/* I2S DMA registers */ +#define I2S_RXDMA 0x01C0 +#define I2S_TXDMA 0x01C8 + +#define TWO_CHANNEL_SUPPORT 2 /* up to 2.0 */ +#define FOUR_CHANNEL_SUPPORT 4 /* up to 3.1 */ +#define SIX_CHANNEL_SUPPORT 6 /* up to 5.1 */ +#define EIGHT_CHANNEL_SUPPORT 8 /* up to 7.1 */ + +#endif /* DESIGNWARE_I2S_H */ diff --git a/sound/soc/spear/designware_i2s.c b/sound/soc/spear/designware_i2s.c new file mode 100644 index 0000000..ca83c49 --- /dev/null +++ b/sound/soc/spear/designware_i2s.c @@ -0,0 +1,610 @@ +/* + * ALSA SoC I2S Audio Layer for ST SPEAr processors + * + * sound/soc/spear/designware_i2s.c + * + * Copyright (C) 2012 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/designware_i2s.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 <linux/spear_dma.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.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 all channels */ +#define LRBR_LTHR(x) (0x40 * x + 0x020) +#define RRBR_RTHR(x) (0x40 * x + 0x024) +#define RER(x) (0x40 * x + 0x028) +#define TER(x) (0x40 * x + 0x02C) +#define RCR(x) (0x40 * x + 0x030) +#define TCR(x) (0x40 * x + 0x034) +#define ISR(x) (0x40 * x + 0x038) +#define IMR(x) (0x40 * x + 0x03C) +#define ROR(x) (0x40 * x + 0x040) +#define TOR(x) (0x40 * x + 0x044) +#define RFCR(x) (0x40 * x + 0x048) +#define TFCR(x) (0x40 * x + 0x04C) +#define RFF(x) (0x40 * x + 0x050) +#define TFF(x) (0x40 * x + 0x054) + +/* I2SCOMPRegisters */ +#define I2S_COMP_PARAM_2 0x01F0 +#define I2S_COMP_PARAM_1 0x01F4 +#define I2S_COMP_VERSION 0x01F8 +#define I2S_COMP_TYPE 0x01FC + +#define DESIGNWARE_I2S_FORMAT SNDRV_PCM_FMTBIT_S16_LE +#define MAX_CHANNEL_NUM 8 +#define MIN_CHANNEL_NUM 2 + +struct dw_i2s_dev { + void __iomem *i2s_base; + struct resource *res; + struct clk *clk; + int active; + int play_irq; + int max_channel; + int capture_irq; + unsigned int capability; + struct device *dev; + struct snd_soc_dai_driver *dai_driver; + + /* data related to DMA transfers b/w i2s and DMAC */ + u8 swidth; + u8 ccr; + u8 xfer_resolution; + struct dma_data play_dma_data; + struct dma_data capture_dma_data; + struct i2s_clk_config_data config; + int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); +}; + +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 inline void +i2s_config_channel(struct dw_i2s_dev *dev, u32 ch, u32 stream, u32 cr) +{ + u32 irq; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(dev->i2s_base, TCR(ch), dev->xfer_resolution); + i2s_write_reg(dev->i2s_base, TFCR(ch), 0x02); + irq = i2s_read_reg(dev->i2s_base, IMR(ch)); + i2s_write_reg(dev->i2s_base, IMR(ch), irq & ~0x30); + i2s_write_reg(dev->i2s_base, TER(ch), 1); + } else { + i2s_write_reg(dev->i2s_base, RCR(ch), dev->xfer_resolution); + i2s_write_reg(dev->i2s_base, RFCR(ch), 0x07); + irq = i2s_read_reg(dev->i2s_base, IMR(ch)); + i2s_write_reg(dev->i2s_base, IMR(ch), irq & ~0x03); + i2s_write_reg(dev->i2s_base, RER(ch), 1); + } +} + +static inline void +i2s_disable_channels(struct dw_i2s_dev *dev, u32 stream) +{ + u32 i = 0; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < 4; i++) + i2s_write_reg(dev->i2s_base, TER(i), 0); + } else { + for (i = 0; i < 4; i++) + i2s_write_reg(dev->i2s_base, RER(i), 0); + } +} + +static inline void +i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream) +{ + u32 i = 0; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < 4; i++) + i2s_write_reg(dev->i2s_base, TOR(i), 0); + } else { + for (i = 0; i < 4; i++) + i2s_write_reg(dev->i2s_base, ROR(i), 0); + } +} + +void i2s_start(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) +{ + + i2s_write_reg(dev->i2s_base, IER, 1); + i2s_disable_channels(dev, substream->stream); + + switch (dev->max_channel) { + case EIGHT_CHANNEL_SUPPORT: + i2s_config_channel(dev, 3, substream->stream, 0x5); + case SIX_CHANNEL_SUPPORT: + i2s_config_channel(dev, 2, substream->stream, 0x5); + case FOUR_CHANNEL_SUPPORT: + i2s_config_channel(dev, 1, substream->stream, 0x5); + case TWO_CHANNEL_SUPPORT: + i2s_config_channel(dev, 0, substream->stream, 0x2); + break; + default: + dev_err(dev->dev, "channel not supported\n"); + } + + i2s_write_reg(dev->i2s_base, CCR, dev->ccr); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, ITER, 1); + else + i2s_write_reg(dev->i2s_base, IRER, 1); + + i2s_write_reg(dev->i2s_base, CER, 1); +} + +static void +i2s_stop(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) +{ + u32 i = 0, irq; + + i2s_clear_irqs(dev, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(dev->i2s_base, ITER, 0); + + for (i = 0; i < 4; i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30); + } + } else { + i2s_write_reg(dev->i2s_base, IRER, 0); + + for (i = 0; i < 4; i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03); + } + } + + if (!dev->active) { + i2s_write_reg(dev->i2s_base, CER, 0); + i2s_write_reg(dev->i2s_base, IER, 0); + } +} + +static irqreturn_t dw_i2s_play_irq(int irq, void *_dev) +{ + struct dw_i2s_dev *dev = (struct dw_i2s_dev *)_dev; + u32 ch0, ch1; + + /* check for the tx data overrun condition */ + ch0 = i2s_read_reg(dev->i2s_base, ISR(0)) & 0x20; + ch1 = i2s_read_reg(dev->i2s_base, ISR(1)) & 0x20; + if (ch0 || ch1) { + /* disable i2s block */ + i2s_write_reg(dev->i2s_base, IER, 0); + + /* 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 */ + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK); + + /* enable rx block */ + i2s_write_reg(dev->i2s_base, ITER, 1); + + /* enable i2s block */ + i2s_write_reg(dev->i2s_base, IER, 1); + } + + return IRQ_HANDLED; +} + +static irqreturn_t dw_i2s_capture_irq(int irq, void *_dev) +{ + struct dw_i2s_dev *dev = (struct dw_i2s_dev *)_dev; + u32 ch0, ch1; + + /* check for the rx data overrun condition */ + ch0 = i2s_read_reg(dev->i2s_base, ISR(0)) & 0x02; + ch1 = i2s_read_reg(dev->i2s_base, ISR(1)) & 0x02; + if (ch0 || ch1) { + /* disable i2s block */ + i2s_write_reg(dev->i2s_base, IER, 0); + + /* 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 */ + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE); + + /* enable rx block */ + i2s_write_reg(dev->i2s_base, IRER, 1); + + /* enable i2s block */ + i2s_write_reg(dev->i2s_base, IER, 1); + } + + return IRQ_HANDLED; +} + +static int +dw_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + struct dma_data *dma_data = NULL; + + if (!(dev->capability & RECORD) && + (substream->stream == SNDRV_PCM_STREAM_CAPTURE)) + return -EINVAL; + + if (!(dev->capability & PLAY) && + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &dev->play_dma_data; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dma_data = &dev->capture_dma_data; + + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data); + + return 0; +} + +static int dw_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + struct i2s_clk_config_data *config = &dev->config; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + config->data_width = 16; + dev->ccr = 0x00; + dev->xfer_resolution = 0x02; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + config->data_width = 24; + dev->ccr = 0x08; + dev->xfer_resolution = 0x04; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + config->data_width = 32; + dev->ccr = 0x10; + dev->xfer_resolution = 0x05; + break; + + default: + dev_err(dev->dev, "designware-i2s: unsuppted PCM fmt"); + return -EINVAL; + } + + config->sample_rate = params_rate(params); + dev->max_channel = config->chan_nr = params_channels(params); + + if (dev->i2s_clk_cfg) { + if (dev->i2s_clk_cfg(config)) { + dev_err(dev->dev, "runtime audio clk config fail\n"); + if (cpu_dai->driver->ops->trigger) { + int ret = + cpu_dai->driver->ops->trigger(substream, + SNDRV_PCM_TRIGGER_STOP, + cpu_dai); + if (ret < 0) { + dev_err(dev->dev, + "trigger stop fail\n"); + return ret; + } + } + } + } + + return 0; +} + +static void +dw_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + +static int +dw_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev->active++; + i2s_start(dev, substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev->active--; + i2s_stop(dev, substream); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static struct snd_soc_dai_ops dw_i2s_dai_ops = { + .startup = dw_i2s_startup, + .shutdown = dw_i2s_shutdown, + .hw_params = dw_i2s_hw_params, + .trigger = dw_i2s_trigger, +}; + +#ifdef CONFIG_PM + +static int dw_i2s_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dw_i2s_dev *i2s_dev = dev_get_drvdata(&pdev->dev); + + clk_disable(i2s_dev->clk); + return 0; +} + +static int dw_i2s_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dw_i2s_dev *i2s_dev = dev_get_drvdata(&pdev->dev); + + clk_enable(i2s_dev->clk); + return 0; +} + +static const struct dev_pm_ops dw_i2s_dev_pm_ops = { + .suspend = dw_i2s_suspend, + .resume = dw_i2s_resume, +}; + +#define I2S_DW_DEV_PM_OPS (&dw_i2s_dev_pm_ops) +#else +#define I2S_DW_DEV_PM_OPS NULL +#endif + +static int +dw_i2s_probe(struct platform_device *pdev) +{ + const struct i2s_platform_data *pdata = pdev->dev.platform_data; + struct dw_i2s_dev *dev; + struct resource *res; + int ret; + unsigned int cap; + struct snd_soc_dai_driver *dw_i2s_dai; + + if (!pdata) { + dev_err(&pdev->dev, "Invalid platform data\n"); + return -EINVAL; + } + + cap = pdata->cap; + 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->max_channel = pdata->channel; + dev->capability = cap; + dev->swidth = pdata->swidth; + dev->i2s_clk_cfg = pdata->i2s_clk_cfg; + + /* Set DMA slaves info */ + + dev->play_dma_data.data = pdata->play_dma_data; + dev->capture_dma_data.data = pdata->capture_dma_data; + dev->play_dma_data.addr = res->start + I2S_TXDMA; + dev->capture_dma_data.addr = res->start + I2S_RXDMA; + dev->play_dma_data.max_burst = 16; + dev->capture_dma_data.max_burst = 16; + dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + dev->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + dev->play_dma_data.filter = pdata->filter; + dev->capture_dma_data.filter = pdata->filter; + + 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; + } + + dw_i2s_dai = kzalloc(sizeof(*dw_i2s_dai), GFP_KERNEL); + if (!dw_i2s_dai) { + dev_err(&pdev->dev, "mem allocation failed for dai driver\n"); + ret = -ENOMEM; + goto err_soc_dai_driver; + } + + if (cap & PLAY) { + dev_dbg(&pdev->dev, " SPEAr: play supported\n"); + dev->play_irq = platform_get_irq_byname(pdev, "play_irq"); + if (dev->play_irq < 0) + dev_warn(&pdev->dev, "play irq not defined\n"); + else { + ret = request_irq(dev->play_irq, dw_i2s_play_irq, 0, + "dw-i2s-play", dev); + if (ret) { + dev_err(&pdev->dev, + "Can't register play irq\n"); + goto err_play_irq; + } + } + + dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM; + dw_i2s_dai->playback.channels_max = dev->max_channel; + dw_i2s_dai->playback.rates = pdata->snd_rates; + dw_i2s_dai->playback.formats = DESIGNWARE_I2S_FORMAT; + } + + if (cap & RECORD) { + dev_dbg(&pdev->dev, "SPEAr: record supported\n"); + dev->capture_irq = platform_get_irq_byname(pdev, "record_irq"); + if (dev->capture_irq < 0) + dev_warn(&pdev->dev, "record irq not defined\n"); + else { + ret = request_irq(dev->capture_irq, dw_i2s_capture_irq, + 0, "dw-i2s-rec", dev); + if (ret) { + dev_err(&pdev->dev, + "Can't register capture irq\n"); + goto err_capture_irq; + } + } + + dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM; + dw_i2s_dai->capture.channels_max = dev->max_channel; + dw_i2s_dai->capture.rates = pdata->snd_rates; + dw_i2s_dai->capture.formats = DESIGNWARE_I2S_FORMAT; + } + + dw_i2s_dai->ops = &dw_i2s_dai_ops, + + dev->dev = &pdev->dev; + dev->dai_driver = dw_i2s_dai; + dev_set_drvdata(&pdev->dev, dev); + ret = snd_soc_register_dai(&pdev->dev, dw_i2s_dai); + if (ret != 0) { + dev_err(&pdev->dev, "not able to register dai\n"); + goto err_set_drvdata; + } + + return 0; + +err_set_drvdata: + dev_set_drvdata(&pdev->dev, NULL); + free_irq(dev->capture_irq, pdev); +err_capture_irq: + free_irq(dev->play_irq, pdev); +err_play_irq: + kfree(dw_i2s_dai); +err_soc_dai_driver: + 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 +dw_i2s_remove(struct platform_device *pdev) +{ + struct dw_i2s_dev *dev = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_dai(&pdev->dev); + + if (dev->play_irq) + free_irq(dev->play_irq, dev); + + if (dev->capture_irq) + free_irq(dev->capture_irq, dev); + + iounmap(dev->i2s_base); + clk_disable(dev->clk); + clk_put(dev->clk); + kfree(dev->dai_driver); + kfree(dev); + release_mem_region(dev->res->start, resource_size(dev->res)); + + return 0; +} + +static struct platform_driver dw_i2s_driver = { + .probe = dw_i2s_probe, + .remove = dw_i2s_remove, + .driver = { + .name = "designware-i2s", + .owner = THIS_MODULE, + .pm = I2S_DW_DEV_PM_OPS, + }, +}; + +static int __init dw_i2s_init(void) +{ + return platform_driver_register(&dw_i2s_driver); +} +module_init(dw_i2s_init); + +static void __exit dw_i2s_exit(void) +{ + platform_driver_unregister(&dw_i2s_driver); +} +module_exit(dw_i2s_exit); + +MODULE_AUTHOR("Rajeev Kumar rajeev-dlh.kumar@st.com"); +MODULE_DESCRIPTION("DESIGNWARE I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:designware_i2s");
On Tue, Mar 20, 2012 at 05:03:46PM +0530, Rajeev Kumar wrote:
This patch add support for synopsys I2S controller as per the ASoC framework.
If this really is a generic Synopsys block shouldn't it be in a Synopsys directory, possibly with a wrapper in the SPEAr directory for the platform integration?
+struct i2s_platform_data {
- #define PLAY (1 << 0)
- #define RECORD (1 << 1)
Namespacing here and with most of the other defines in the header (and the driver, though the ones in the code are less important). Normally ALSA headers go in include/sound.
- if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
i2s_write_reg(dev->i2s_base, TCR(ch), dev->xfer_resolution);
i2s_write_reg(dev->i2s_base, TFCR(ch), 0x02);
irq = i2s_read_reg(dev->i2s_base, IMR(ch));
i2s_write_reg(dev->i2s_base, IMR(ch), irq & ~0x30);
i2s_write_reg(dev->i2s_base, TER(ch), 1);
- } else {
i2s_write_reg(dev->i2s_base, RCR(ch), dev->xfer_resolution);
i2s_write_reg(dev->i2s_base, RFCR(ch), 0x07);
irq = i2s_read_reg(dev->i2s_base, IMR(ch));
i2s_write_reg(dev->i2s_base, IMR(ch), irq & ~0x03);
i2s_write_reg(dev->i2s_base, RER(ch), 1);
- }
It would be good to split out the bits that are common from the bits that vary per stream.
- if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
for (i = 0; i < 4; i++)
i2s_write_reg(dev->i2s_base, TER(i), 0);
- } else {
for (i = 0; i < 4; i++)
i2s_write_reg(dev->i2s_base, RER(i), 0);
Magic numbers!
+static irqreturn_t dw_i2s_play_irq(int irq, void *_dev) +{
- struct dw_i2s_dev *dev = (struct dw_i2s_dev *)_dev;
- u32 ch0, ch1;
- /* check for the tx data overrun condition */
- ch0 = i2s_read_reg(dev->i2s_base, ISR(0)) & 0x20;
- ch1 = i2s_read_reg(dev->i2s_base, ISR(1)) & 0x20;
- if (ch0 || ch1) {
/* disable i2s block */
i2s_write_reg(dev->i2s_base, IER, 0);
/* 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 */
i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
/* enable rx block */
i2s_write_reg(dev->i2s_base, ITER, 1);
/* enable i2s block */
i2s_write_reg(dev->i2s_base, IER, 1);
- }
This is somewhat unusual - normally ALSA detects underrun and overrun and restarts the stream at the application layer. This looks like it attempts to mask information about errors from the application which probably isn't waht we want to do. Why is the normal ALSA handling not sufficient?
- return IRQ_HANDLED;
This unconditionally says it handled the interrupt even if it didn't.
+static int +dw_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) +{
- struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
- struct dma_data *dma_data = NULL;
- if (!(dev->capability & RECORD) &&
(substream->stream == SNDRV_PCM_STREAM_CAPTURE))
return -EINVAL;
Indentation.
- if (dev->i2s_clk_cfg) {
if (dev->i2s_clk_cfg(config)) {
dev_err(dev->dev, "runtime audio clk config fail\n");
if (cpu_dai->driver->ops->trigger) {
int ret =
cpu_dai->driver->ops->trigger(substream,
SNDRV_PCM_TRIGGER_STOP,
cpu_dai);
if (ret < 0) {
dev_err(dev->dev,
"trigger stop fail\n");
return ret;
}
}
No, return an error if you encounter an error!
+static void +dw_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
Indentation - throughout the kernel the type goes on the same line as the function name.
+static const struct dev_pm_ops dw_i2s_dev_pm_ops = {
- .suspend = dw_i2s_suspend,
- .resume = dw_i2s_resume,
+};
No, do this at the ASoC level like other CPU drivers (runtime PM callbacks are OK).
- cap = pdata->cap;
- 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;
- }
There's devm_ versions of this stuff too.
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
devm_kzalloc()
- dev->i2s_base = ioremap(res->start, resource_size(res));
- if (!dev->i2s_base) {
devm_ioremap().
if (dev->play_irq < 0)
dev_warn(&pdev->dev, "play irq not defined\n");
else {
Braces on both sides of the if.
+static int __init dw_i2s_init(void)
module_platform_driver().
Hello Mark
On 3/20/2012 9:14 PM, Mark Brown wrote:
On Tue, Mar 20, 2012 at 05:03:46PM +0530, Rajeev Kumar wrote:
This patch add support for synopsys I2S controller as per the ASoC framework.
If this really is a generic Synopsys block shouldn't it be in a Synopsys directory, possibly with a wrapper in the SPEAr directory for the platform integration?
I will create a new directory by name dwc.
+struct i2s_platform_data {
- #define PLAY (1<< 0)
- #define RECORD (1<< 1)
Namespacing here and with most of the other defines in the header (and the driver, though the ones in the code are less important). Normally ALSA headers go in include/sound.
OK I will move it in include/sound/. Next Patch will contains name spacing also.
- if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
i2s_write_reg(dev->i2s_base, TCR(ch), dev->xfer_resolution);
i2s_write_reg(dev->i2s_base, TFCR(ch), 0x02);
irq = i2s_read_reg(dev->i2s_base, IMR(ch));
i2s_write_reg(dev->i2s_base, IMR(ch), irq& ~0x30);
i2s_write_reg(dev->i2s_base, TER(ch), 1);
- } else {
i2s_write_reg(dev->i2s_base, RCR(ch), dev->xfer_resolution);
i2s_write_reg(dev->i2s_base, RFCR(ch), 0x07);
irq = i2s_read_reg(dev->i2s_base, IMR(ch));
i2s_write_reg(dev->i2s_base, IMR(ch), irq& ~0x03);
i2s_write_reg(dev->i2s_base, RER(ch), 1);
- }
It would be good to split out the bits that are common from the bits that vary per stream.
OK,
- if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
for (i = 0; i< 4; i++)
i2s_write_reg(dev->i2s_base, TER(i), 0);
- } else {
for (i = 0; i< 4; i++)
i2s_write_reg(dev->i2s_base, RER(i), 0);
Magic numbers!
will be replaced with macro
+static irqreturn_t dw_i2s_play_irq(int irq, void *_dev) +{
- struct dw_i2s_dev *dev = (struct dw_i2s_dev *)_dev;
- u32 ch0, ch1;
- /* check for the tx data overrun condition */
- ch0 = i2s_read_reg(dev->i2s_base, ISR(0))& 0x20;
- ch1 = i2s_read_reg(dev->i2s_base, ISR(1))& 0x20;
- if (ch0 || ch1) {
/* disable i2s block */
i2s_write_reg(dev->i2s_base, IER, 0);
/* 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 */
i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
/* enable rx block */
i2s_write_reg(dev->i2s_base, ITER, 1);
/* enable i2s block */
i2s_write_reg(dev->i2s_base, IER, 1);
- }
This is somewhat unusual - normally ALSA detects underrun and overrun and restarts the stream at the application layer. This looks like it attempts to mask information about errors from the application which probably isn't waht we want to do. Why is the normal ALSA handling not5 sufficient?
Normal ALSA handling is sufficient for underrun and overrun. This handler is not required.
- return IRQ_HANDLED;
This unconditionally says it handled the interrupt even if it didn't.
Removing handler will remove this too.
+static int +dw_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) +{
- struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
- struct dma_data *dma_data = NULL;
- if (!(dev->capability& RECORD)&&
(substream->stream == SNDRV_PCM_STREAM_CAPTURE))
return -EINVAL;
Indentation.
Ok
- if (dev->i2s_clk_cfg) {
if (dev->i2s_clk_cfg(config)) {
dev_err(dev->dev, "runtime audio clk config fail\n");
if (cpu_dai->driver->ops->trigger) {
int ret =
cpu_dai->driver->ops->trigger(substream,
SNDRV_PCM_TRIGGER_STOP,
cpu_dai);
if (ret< 0) {
dev_err(dev->dev,
"trigger stop fail\n");
return ret;
}
}
No, return an error if you encounter an error!
You need not to stop controller in case clock fail ?
+static void +dw_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
Indentation - throughout the kernel the type goes on the same line asi2s the function name.
Ok
+static const struct dev_pm_ops dw_i2s_dev_pm_ops = {
- .suspend = dw_i2s_suspend,
- .resume = dw_i2s_resume,
+};
No, do this at the ASoC level like other CPU drivers (runtime PM callbacks are OK).
Ok,
- cap = pdata->cap;
- 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;
- }
There's devm_ versions of this stuff too.
OK
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
devm_kzalloc()
Ok
- dev->i2s_base = ioremap(res->start, resource_size(res));
- if (!dev->i2s_base) {
devm_ioremap().
Ok
if (dev->play_irq< 0)
dev_warn(&pdev->dev, "play irq not defined\n");
else {
Braces on both sides of the if.
Ok,
Best Regards Rajeev
+static int __init dw_i2s_init(void)
module_platform_driver().
On Mon, Mar 26, 2012 at 02:33:22PM +0530, Rajeev kumar wrote:
On 3/20/2012 9:14 PM, Mark Brown wrote:
On Tue, Mar 20, 2012 at 05:03:46PM +0530, Rajeev Kumar wrote:
if (dev->i2s_clk_cfg(config)) {
dev_err(dev->dev, "runtime audio clk config fail\n");
if (cpu_dai->driver->ops->trigger) {
int ret =
cpu_dai->driver->ops->trigger(substream,
SNDRV_PCM_TRIGGER_STOP,
cpu_dai);
if (ret< 0) {
dev_err(dev->dev,
"trigger stop fail\n");
return ret;
}
}
No, return an error if you encounter an error!
You need not to stop controller in case clock fail ?
Let the upper layers worry about that. Don't randomly change the state of the controller without them knowing about it.
Hello Mark,
On 3/26/2012 4:40 PM, Mark Brown wrote:
On Mon, Mar 26, 2012 at 02:33:22PM +0530, Rajeev kumar wrote:
On 3/20/2012 9:14 PM, Mark Brown wrote:
On Tue, Mar 20, 2012 at 05:03:46PM +0530, Rajeev Kumar wrote:
if (dev->i2s_clk_cfg(config)) {
dev_err(dev->dev, "runtime audio clk config fail\n");
if (cpu_dai->driver->ops->trigger) {
int ret =
cpu_dai->driver->ops->trigger(substream,
SNDRV_PCM_TRIGGER_STOP,
cpu_dai);
if (ret< 0) {
dev_err(dev->dev,
"trigger stop fail\n");
return ret;
}
}
No, return an error if you encounter an error!
You need not to stop controller in case clock fail ?
Let the upper layers worry about that. Don't randomly change the state of the controller without them knowing about it.
Ok
Best Regards Rajeev
This patch add support for the SPEAr ASoC pcm layer in ASoC framework.
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com --- sound/soc/spear/spear_pcm.c | 460 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/spear/spear_pcm.h | 33 +++ 2 files changed, 493 insertions(+), 0 deletions(-) create mode 100644 sound/soc/spear/spear_pcm.c create mode 100644 sound/soc/spear/spear_pcm.h
diff --git a/sound/soc/spear/spear_pcm.c b/sound/soc/spear/spear_pcm.c new file mode 100644 index 0000000..3693fde --- /dev/null +++ b/sound/soc/spear/spear_pcm.c @@ -0,0 +1,460 @@ +/* + * ALSA PCM interface for ST SPEAr Processors + * + * sound/soc/spear/spear_pcm.c + * + * Copyright (C) 2012 ST Microelectronics + * Rajeev Kumarrajeev-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/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/spear_dma.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include "spear_pcm.h" + +struct snd_pcm_hardware spear_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 | SNDRV_PCM_INFO_RESUME), + .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 */ +}; + +static void pcm_dma_complete(void *arg); + +static int spear_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(params)); + if (ret < 0) + return ret; + + return 0; +} + +static int spear_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int spear_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct spear_runtime_data *prtd = runtime->private_data; + unsigned long flags; + + spin_lock_irqsave(&prtd->lock, flags); + prtd->dma_addr = runtime->dma_addr; + + prtd->buf_index = 0; + prtd->dmacount = 0; + prtd->xfer_len = snd_pcm_lib_period_bytes(substream); + prtd->xfer_cnt = snd_pcm_lib_buffer_bytes(substream) / prtd->xfer_len; + + spin_unlock_irqrestore(&prtd->lock, flags); + + return 0; +} + +static int start_dma(struct spear_runtime_data *prtd) +{ + struct dma_slave_config conf = { + .device_fc = false, + }; + + struct dma_chan *chan; + struct dma_async_tx_descriptor *desc; + struct scatterlist sg; + enum dma_data_direction direction; + dma_addr_t addr; + struct snd_soc_pcm_runtime *rtd = prtd->substream->private_data; + struct dma_data *dma_data = + snd_soc_dai_get_dma_data(rtd->cpu_dai, prtd->substream); + addr = prtd->dma_addr + prtd->buf_index * prtd->xfer_len; + if (prtd->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + chan = prtd->dma_chan[0]; + direction = DMA_TO_DEVICE; + conf.dst_addr = dma_data->addr; + } else { + chan = prtd->dma_chan[1]; + direction = DMA_FROM_DEVICE; + conf.src_addr = dma_data->addr; + } + + conf.src_maxburst = dma_data->max_burst; + conf.dst_maxburst = dma_data->max_burst; + conf.src_addr_width = dma_data->addr_width; + conf.dst_addr_width = dma_data->addr_width; + + /* Prepare sg's */ + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(addr)), prtd->xfer_len, + addr & (PAGE_SIZE - 1)); + sg_dma_address(&sg) = addr; + + conf.direction = direction; + dmaengine_slave_config(chan, &conf); + + desc = chan->device->device_prep_slave_sg(chan, &sg, 1, direction, + DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(&chan->dev->device, "cannot prepare slave dma\n"); + return -EAGAIN; + } + + desc->callback = pcm_dma_complete; + desc->callback_param = prtd; + desc->tx_submit(desc); + + return 0; +} + +static void pcm_dma_xfer(struct spear_runtime_data *prtd, + bool from_callback) +{ + struct snd_pcm_substream *substream = prtd->substream; + struct dma_chan *chan; + unsigned long flags; + int ret; + + spin_lock_irqsave(&prtd->lock, flags); + if (!prtd->pcm_running) { + spin_unlock_irqrestore(&prtd->lock, flags); + return; + } + + BUG_ON(prtd->dmacount >= prtd->xfer_cnt); + + while (prtd->dmacount < prtd->xfer_cnt) { + + ret = start_dma(prtd); + if (ret) { + spin_unlock_irqrestore(&prtd->lock, flags); + return; + } + + prtd->dmacount++; + prtd->buf_index++; + + /* Set to zero, if crosses buffer size */ + prtd->buf_index %= prtd->xfer_cnt; + + /* Inform framework that a transfer is finished */ + if (from_callback) { + spin_unlock_irqrestore(&prtd->lock, flags); + snd_pcm_period_elapsed(substream); + spin_lock_irqsave(&prtd->lock, flags); + } + } + spin_unlock_irqrestore(&prtd->lock, flags); + + /* Issue pending should be called after locks */ + if (prtd->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + chan = prtd->dma_chan[0]; + else + chan = prtd->dma_chan[1]; + + chan->device->device_issue_pending(chan); +} + +static void pcm_dma_complete(void *arg) +{ + struct spear_runtime_data *prtd = arg; + unsigned long flags; + + spin_lock_irqsave(&prtd->lock, flags); + prtd->dmacount--; + spin_unlock_irqrestore(&prtd->lock, flags); + + if (prtd->pcm_running) + pcm_dma_xfer(prtd, true); +} + +static int spear_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct spear_runtime_data *prtd = substream->runtime->private_data; + struct dma_chan *chan; + unsigned long flags; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + spin_lock_irqsave(&prtd->lock, flags); + /* Go to last successfully transferred index + 1 */ + prtd->buf_index += prtd->xfer_cnt - prtd->dmacount; + prtd->buf_index %= prtd->xfer_cnt; + prtd->dmacount = 0; + spin_unlock_irqrestore(&prtd->lock, flags); + case SNDRV_PCM_TRIGGER_START: + spin_lock_irqsave(&prtd->lock, flags); + prtd->pcm_running = true; + spin_unlock_irqrestore(&prtd->lock, flags); + pcm_dma_xfer(prtd, false); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + spin_lock_irqsave(&prtd->lock, flags); + prtd->pcm_running = false; + spin_unlock_irqrestore(&prtd->lock, flags); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + chan = prtd->dma_chan[0]; + else + chan = prtd->dma_chan[1]; + + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t +spear_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct spear_runtime_data *prtd = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, prtd->buf_index * + prtd->xfer_len); +} + +static int pcm_alloc_dma_chan(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct spear_runtime_data *prtd = substream->runtime->private_data; + + int stream = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; + struct dma_data *dma_data = + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + prtd->dma_chan[stream] = dma_request_channel(prtd->smask, + dma_data->filter, dma_data->data); + if (!prtd->dma_chan[stream]) + return -EAGAIN; + + return 0; +} + +static void pcm_dma_free_chan(struct snd_pcm_substream *substream) +{ + struct spear_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]; + + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + dma_release_channel(chan); +} + +static int spear_pcm_open(struct snd_pcm_substream *substream) +{ + struct spear_runtime_data *prtd; + int ret; + + ret = snd_soc_set_runtime_hwparams(substream, &spear_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) + return -ENOMEM; + + spin_lock_init(&prtd->lock); + substream->runtime->private_data = prtd; + prtd->substream = substream; + dma_cap_zero(prtd->smask); + dma_cap_set(DMA_SLAVE, prtd->smask); + + ret = pcm_alloc_dma_chan(substream); + if (ret) { + dev_err(substream->pcm->card->dev, + "pcm:Failed to get dma channels\n"); + kfree(prtd); + } + + return 0; +} + +static int spear_pcm_close(struct snd_pcm_substream *substream) +{ + struct spear_runtime_data *prtd = substream->runtime->private_data; + + pcm_dma_free_chan(substream); + kfree(prtd); + + return 0; +} + +static int spear_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 spear_pcm_ops = { + .open = spear_pcm_open, + .close = spear_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = spear_pcm_hw_params, + .hw_free = spear_pcm_hw_free, + .prepare = spear_pcm_prepare, + .trigger = spear_pcm_trigger, + .pointer = spear_pcm_pointer, + .mmap = spear_pcm_mmap, +}; + +static int +spear_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); + if (!buf->area) + return -ENOMEM; + + dev_info(buf->dev.dev, + " preallocate_dma_buffer: area=%p, addr=%p, size=%d\n", + (void *)buf->area, (void *)buf->addr, size); + + buf->bytes = size; + return 0; +} + +static void spear_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 && !buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static u64 spear_pcm_dmamask = DMA_BIT_MASK(32); + +static int spear_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &spear_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (dai->driver->playback.channels_min) { + ret = spear_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK, + spear_pcm_hardware.buffer_bytes_max); + if (ret) + return ret; + } + + if (dai->driver->capture.channels_min) { + ret = spear_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE, + spear_pcm_hardware.buffer_bytes_max); + if (ret) + return ret; + } + + return 0; +} + +struct snd_soc_platform_driver spear_soc_platform = { + .ops = &spear_pcm_ops, + .pcm_new = spear_pcm_new, + .pcm_free = spear_pcm_free, +}; + +static int __devinit spear_soc_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &spear_soc_platform); +} + +static int __devexit spear_soc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver spear_pcm_driver = { + .driver = { + .name = "spear-pcm-audio", + .owner = THIS_MODULE, + }, + + .probe = spear_soc_platform_probe, + .remove = __devexit_p(spear_soc_platform_remove), +}; +static int __init snd_spear_pcm_init(void) +{ + return platform_driver_register(&spear_pcm_driver); +} +module_init(snd_spear_pcm_init); + +static void __exit snd_spear_pcm_exit(void) +{ + platform_driver_unregister(&spear_pcm_driver); +} +module_exit(snd_spear_pcm_exit); + +MODULE_AUTHOR("Rajeev Kumar rajeev-dlh.kumar@st.com"); +MODULE_DESCRIPTION("SPEAr PCM DMA module"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:spear_pcm"); diff --git a/sound/soc/spear/spear_pcm.h b/sound/soc/spear/spear_pcm.h new file mode 100644 index 0000000..07a7b3d --- /dev/null +++ b/sound/soc/spear/spear_pcm.h @@ -0,0 +1,33 @@ +/* + * ALSA PCM interface for ST SPEAr Processors + * + * sound/soc/spear/spear_pcm.h + * + * Copyright (C) 2012 ST Microelectronics + * Rajeev Kumarrajeev-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 + +struct spear_runtime_data { + struct dma_chan *dma_chan[2]; + spinlock_t lock; + struct snd_pcm_substream *substream; + dma_addr_t dma_addr; + + /* DMA related mask */ + dma_cap_mask_t smask; + + /* For Keeping track of buffers */ + unsigned long xfer_len; /* Data transfered by one transfer */ + int xfer_cnt; /* Total number of transfers to be done */ + int buf_index; /* Current buffer count */ + int dmacount; /* No. of DMA transfer ongoing */ + bool pcm_running; /* Current state of pcm, true if running */ +}; +#endif /* end of pcm header file */
On Tue, Mar 20, 2012 at 05:03:47PM +0530, Rajeev Kumar wrote:
This patch add support for the SPEAr ASoC pcm layer in ASoC framework.
This should really be rewritten in terms of the dmaengine helper library which Lars-Peter contributed for 3.4. It looks like you're not using cyclic mode here which is currently a requirement for the library - if your hardware doesn't have cyclic support then that'll need to be added.
We've already got too many independant implementations of an ALSA to dmaengine mapping, we should be cutting down on the amount of code as much as possible in order to avoid code duplication and make sure that we're able to roll out good practice and new features as easily as possible.
From: Vipin Kumar vipin.kumar@st.com
This patch implements the spdif IN driver for ST peripheral
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com Signed-off-by: Vipin Kumar Vipin.kumar@st.com --- sound/soc/spear/spdif_in.c | 304 +++++++++++++++++++++++++++++++++++++++ sound/soc/spear/spdif_in_regs.h | 60 ++++++++ 2 files changed, 364 insertions(+), 0 deletions(-) create mode 100644 sound/soc/spear/spdif_in.c create mode 100644 sound/soc/spear/spdif_in_regs.h
diff --git a/sound/soc/spear/spdif_in.c b/sound/soc/spear/spdif_in.c new file mode 100644 index 0000000..9a7b3b8 --- /dev/null +++ b/sound/soc/spear/spdif_in.c @@ -0,0 +1,304 @@ +/* + * ALSA SoC SPDIF In Audio Layer for spear processors + * + * Copyright (C) 2012 ST Microelectronics + * Vipin Kumar vipin.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/export.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spear_dma.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/spdif.h> +#include "spdif_in_regs.h" + +struct spdif_in_params { + u32 format; +}; + +struct spdif_in_dev { + struct clk *clk; + struct dma_data dma_params; + struct spdif_in_params saved_params; + void *io_base; + void (*reset_perip)(void); + int irq; +}; + +static void spdif_in_configure(struct spdif_in_dev *host) +{ + u32 ctrl = SPDIF_IN_PRTYEN | SPDIF_IN_STATEN | SPDIF_IN_USREN | + SPDIF_IN_VALEN | SPDIF_IN_BLKEN; + ctrl |= SPDIF_MODE_16BIT | SPDIF_FIFO_THRES_16; + + writel(ctrl, host->io_base + SPDIF_IN_CTRL); + writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK); +} + +static int spdif_in_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(cpu_dai); + + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)&host->dma_params); + return 0; +} + +static void spdif_in_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return; + + writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK); + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + +static void spdif_in_format(struct spdif_in_dev *host, u32 format) +{ + u32 ctrl = readl(host->io_base + SPDIF_IN_CTRL); + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + ctrl |= SPDIF_XTRACT_16BIT; + break; + + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + ctrl &= ~SPDIF_XTRACT_16BIT; + break; + } + + writel(ctrl, host->io_base + SPDIF_IN_CTRL); +} + +static int spdif_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + u32 format; + + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + format = params_format(params); + host->saved_params.format = format; + + return 0; +} + +static int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + u32 ctrl; + int ret = 0; + + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + clk_enable(host->clk); + spdif_in_configure(host); + spdif_in_format(host, host->saved_params.format); + + ctrl = readl(host->io_base + SPDIF_IN_CTRL); + ctrl |= SPDIF_IN_SAMPLE | SPDIF_IN_ENB; + writel(ctrl, host->io_base + SPDIF_IN_CTRL); + writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ctrl = readl(host->io_base + SPDIF_IN_CTRL); + ctrl &= ~(SPDIF_IN_SAMPLE | SPDIF_IN_ENB); + writel(ctrl, host->io_base + SPDIF_IN_CTRL); + writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK); + + if (host->reset_perip) + host->reset_perip(); + clk_disable(host->clk); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} + +static struct snd_soc_dai_ops spdif_in_dai_ops = { + .startup = spdif_in_startup, + .shutdown = spdif_in_shutdown, + .trigger = spdif_in_trigger, + .hw_params = spdif_in_hw_params, +}; + +struct snd_soc_dai_driver spdif_in_dai = { + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000), + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &spdif_in_dai_ops, +}; + +static irqreturn_t spdif_in_irq(int irq, void *arg) +{ + struct spdif_in_dev *host = (struct spdif_in_dev *)arg; + + u32 irq_status = readl(host->io_base + SPDIF_IN_IRQ); + + if (!irq_status) + return IRQ_NONE; + + if (irq_status & SPDIF_IRQ_FIFOWRITE) + pr_err("spdif in: fifo write error\n"); + if (irq_status & SPDIF_IRQ_EMPTYFIFOREAD) + pr_err("spdif in: empty fifo read error\n"); + if (irq_status & SPDIF_IRQ_FIFOFULL) + pr_err("spdif in: fifo full error\n"); + if (irq_status & SPDIF_IRQ_OUTOFRANGE) + pr_err("spdif in: out of range error\n"); + + writel(0, host->io_base + SPDIF_IN_IRQ); + + return IRQ_HANDLED; +} + +static int spdif_in_probe(struct platform_device *pdev) +{ + struct spdif_in_dev *host; + struct spdif_platform_data *pdata; + struct resource *res, *res_fifo; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + res_fifo = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res_fifo) + return -EINVAL; + + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name)) { + dev_warn(&pdev->dev, "Failed to get memory resourse\n"); + return -ENOENT; + } + + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) { + dev_warn(&pdev->dev, "kzalloc fail\n"); + return -ENOMEM; + } + + host->io_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!host->io_base) { + dev_warn(&pdev->dev, "ioremap failed\n"); + return -ENOMEM; + } + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) + return -EINVAL; + + host->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + pdata = dev_get_platdata(&pdev->dev); + + host->dma_params.data = pdata->dma_params; + host->dma_params.addr = res_fifo->start; + host->dma_params.max_burst = 16; + host->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_params.filter = pdata->filter; + host->reset_perip = pdata->reset_perip; + + dev_set_drvdata(&pdev->dev, host); + + ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0, + "spdif-in", host); + if (ret) { + clk_put(host->clk); + dev_warn(&pdev->dev, "request_irq failed\n"); + return ret; + } + + ret = snd_soc_register_dai(&pdev->dev, &spdif_in_dai); + if (ret != 0) { + clk_put(host->clk); + return ret; + } + + return 0; +} + +static int spdif_in_remove(struct platform_device *pdev) +{ + struct spdif_in_dev *host = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_dai(&pdev->dev); + dev_set_drvdata(&pdev->dev, NULL); + + clk_put(host->clk); + + return 0; +} + +#define SPDIF_IN_DEV_PM_OPS NULL + +static struct platform_driver spdif_in_driver = { + .probe = spdif_in_probe, + .remove = spdif_in_remove, + .driver = { + .name = "spdif-in", + .owner = THIS_MODULE, + .pm = SPDIF_IN_DEV_PM_OPS, + }, +}; + +static int __init spdif_in_init(void) +{ + return platform_driver_register(&spdif_in_driver); +} +module_init(spdif_in_init); + +static void __exit spdif_in_exit(void) +{ + platform_driver_unregister(&spdif_in_driver); +} +module_exit(spdif_in_exit); + +MODULE_AUTHOR("Vipin Kumar vipin.kumar@st.com"); +MODULE_DESCRIPTION("SPEAr SPDIF IN SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/spear/spdif_in_regs.h b/sound/soc/spear/spdif_in_regs.h new file mode 100644 index 0000000..69f3816 --- /dev/null +++ b/sound/soc/spear/spdif_in_regs.h @@ -0,0 +1,60 @@ +/* + * SPEAr SPDIF IN controller header file + * + * Copyright (ST) 2012 Vipin Kumar (vipin.kumar@st.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPDIF_IN_REGS_H +#define SPDIF_IN_REGS_H + +#define SPDIF_IN_CTRL 0x00 + #define SPDIF_IN_PRTYEN (1 << 20) + #define SPDIF_IN_STATEN (1 << 19) + #define SPDIF_IN_USREN (1 << 18) + #define SPDIF_IN_VALEN (1 << 17) + #define SPDIF_IN_BLKEN (1 << 16) + + #define SPDIF_MODE_24BIT (8 << 12) + #define SPDIF_MODE_23BIT (7 << 12) + #define SPDIF_MODE_22BIT (6 << 12) + #define SPDIF_MODE_21BIT (5 << 12) + #define SPDIF_MODE_20BIT (4 << 12) + #define SPDIF_MODE_19BIT (3 << 12) + #define SPDIF_MODE_18BIT (2 << 12) + #define SPDIF_MODE_17BIT (1 << 12) + #define SPDIF_MODE_16BIT (0 << 12) + #define SPDIF_MODE_MASK (0x0F << 12) + + #define SPDIF_IN_VALID (1 << 11) + #define SPDIF_IN_SAMPLE (1 << 10) + #define SPDIF_DATA_SWAP (1 << 9) + #define SPDIF_IN_ENB (1 << 8) + #define SPDIF_DATA_REVERT (1 << 7) + #define SPDIF_XTRACT_16BIT (1 << 6) + #define SPDIF_FIFO_THRES_16 (16 << 0) + +#define SPDIF_IN_IRQ_MASK 0x04 +#define SPDIF_IN_IRQ 0x08 + #define SPDIF_IRQ_FIFOWRITE (1 << 0) + #define SPDIF_IRQ_EMPTYFIFOREAD (1 << 1) + #define SPDIF_IRQ_FIFOFULL (1 << 2) + #define SPDIF_IRQ_OUTOFRANGE (1 << 3) + +#define SPDIF_IN_STA 0x0C + #define SPDIF_IN_LOCK (0x1 << 0) + +#endif /* SPDIF_IN_REGS_H */
On Tue, Mar 20, 2012 at 05:03:48PM +0530, Rajeev Kumar wrote:
This looks good, a few minor things but almost good to go.
This patch implements the spdif IN driver for ST peripheral
S/PDIF.
- if (irq_status & SPDIF_IRQ_FIFOWRITE)
pr_err("spdif in: fifo write error\n");
- if (irq_status & SPDIF_IRQ_EMPTYFIFOREAD)
pr_err("spdif in: empty fifo read error\n");
- if (irq_status & SPDIF_IRQ_FIFOFULL)
pr_err("spdif in: fifo full error\n");
- if (irq_status & SPDIF_IRQ_OUTOFRANGE)
pr_err("spdif in: out of range error\n");
dev_err().
- if (!devm_request_mem_region(&pdev->dev, res->start,
resource_size(res), pdev->name)) {
dev_warn(&pdev->dev, "Failed to get memory resourse\n");
return -ENOENT;
- }
- host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
- if (!host) {
dev_warn(&pdev->dev, "kzalloc fail\n");
return -ENOMEM;
- }
Good to see this - this is the sort of stuff I was looking for in the I2S driver.
- host->clk = clk_get(&pdev->dev, NULL);
- if (IS_ERR(host->clk))
return PTR_ERR(host->clk);
- pdata = dev_get_platdata(&pdev->dev);
Should really be error checking in case you didn't get your platform data.
- ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0,
"spdif-in", host);
- if (ret) {
I'm really not enthused about the idea of using devm_request_irq() here - what steps are you taking to make sure that the IRQ can't possibly fire after you've started tearing down the device. In general it's relatively hard to use devm_request_irq() safely.
+#define SPDIF_IN_DEV_PM_OPS NULL
Just remove this if it's unconditionally empty.
+static int __init spdif_in_init(void)
module_platform_driver().
Hello Mark,
On 3/20/2012 9:25 PM, Mark Brown wrote:
On Tue, Mar 20, 2012 at 05:03:48PM +0530, Rajeev Kumar wrote:
This looks good, a few minor things but almost good to go.
This patch implements the spdif IN driver for ST peripheral
S/PDIF.
Ok,
- if (irq_status& SPDIF_IRQ_FIFOWRITE)
pr_err("spdif in: fifo write error\n");
- if (irq_status& SPDIF_IRQ_EMPTYFIFOREAD)
pr_err("spdif in: empty fifo read error\n");
- if (irq_status& SPDIF_IRQ_FIFOFULL)
pr_err("spdif in: fifo full error\n");
- if (irq_status& SPDIF_IRQ_OUTOFRANGE)
pr_err("spdif in: out of range error\n");
dev_err().
Ok
- if (!devm_request_mem_region(&pdev->dev, res->start,
resource_size(res), pdev->name)) {
dev_warn(&pdev->dev, "Failed to get memory resourse\n");
return -ENOENT;
- }
- host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
- if (!host) {
dev_warn(&pdev->dev, "kzalloc fail\n");
return -ENOMEM;
- }
Good to see this - this is the sort of stuff I was looking for in the I2S driver.
Will follow this in I2S driver.
- host->clk = clk_get(&pdev->dev, NULL);
- if (IS_ERR(host->clk))
return PTR_ERR(host->clk);
- pdata = dev_get_platdata(&pdev->dev);
Should really be error checking in case you didn't get your platform data.
Agreed,
- ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0,
"spdif-in", host);
- if (ret) {
I'm really not enthused about the idea of using devm_request_irq() here
- what steps are you taking to make sure that the IRQ can't possibly
fire after you've started tearing down the device. In general it's relatively hard to use devm_request_irq() safely.
Disabling interrupt in remove will insure that interrupt will not fire after tearing down the device.
+#define SPDIF_IN_DEV_PM_OPS NULL
Just remove this if it's unconditionally empty.
OK,
Best Regards Rajeev
+static int __init spdif_in_init(void)
module_platform_driver().
From: Vipin Kumar vipin.kumar@st.com
This patch implements the spdif out driver for ST peripheral This peripheral implements IEC60958 standard
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com Signed-off-by: vipin Kumar vipin.kumar@st.com --- sound/soc/spear/spdif_out.c | 397 ++++++++++++++++++++++++++++++++++++++ sound/soc/spear/spdif_out_regs.h | 79 ++++++++ 2 files changed, 476 insertions(+), 0 deletions(-) create mode 100644 sound/soc/spear/spdif_out.c create mode 100644 sound/soc/spear/spdif_out_regs.h
diff --git a/sound/soc/spear/spdif_out.c b/sound/soc/spear/spdif_out.c new file mode 100644 index 0000000..757fac1 --- /dev/null +++ b/sound/soc/spear/spdif_out.c @@ -0,0 +1,397 @@ +/* + * ALSA SoC SPDIF Out Audio Layer for spear processors + * + * Copyright (C) 2012 ST Microelectronics + * Vipin Kumar vipin.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/export.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spear_dma.h> +#include <sound/soc.h> +#include <mach/spdif.h> +#include "spdif_out_regs.h" + +static int spdif_out_mute; + +struct spdif_out_params { + u32 rate; + u32 core_freq; +}; + +struct spdif_out_dev { + struct clk *clk; + struct dma_data dma_params; + struct spdif_out_params saved_params; + u32 running; + void *io_base; +}; + +static void spdif_out_configure(struct spdif_out_dev *host) +{ + writel(SPDIF_OUT_RESET, host->io_base + SPDIF_OUT_SOFT_RST); + mdelay(1); + writel(readl(host->io_base + SPDIF_OUT_SOFT_RST) & ~SPDIF_OUT_RESET, + host->io_base + SPDIF_OUT_SOFT_RST); + + writel(SPDIF_OUT_FDMA_TRIG_16 | SPDIF_OUT_MEMFMT_16_16 | + SPDIF_OUT_VALID_HW | SPDIF_OUT_USER_HW | + SPDIF_OUT_CHNLSTA_HW | SPDIF_OUT_PARITY_HW, + host->io_base + SPDIF_OUT_CFG); + + writel(0x7F, host->io_base + SPDIF_OUT_INT_STA_CLR); + writel(0x7F, host->io_base + SPDIF_OUT_INT_EN_CLR); +} + +static int spdif_out_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)&host->dma_params); + + ret = clk_enable(host->clk); + if (ret) + return ret; + + host->running = true; + spdif_out_configure(host); + + return 0; +} + +static void spdif_out_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(dai); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return; + + clk_disable(host->clk); + host->running = false; + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + +static void spdif_out_clock(struct spdif_out_dev *host, u32 core_freq, + u32 rate) +{ + u32 divider, ctrl; + + clk_set_rate(host->clk, core_freq); + divider = DIV_ROUND_CLOSEST(clk_get_rate(host->clk), (rate * 128)); + + ctrl = readl(host->io_base + SPDIF_OUT_CTRL); + ctrl &= ~SPDIF_DIVIDER_MASK; + ctrl |= (divider << SPDIF_DIVIDER_SHIFT) & SPDIF_DIVIDER_MASK; + writel(ctrl, host->io_base + SPDIF_OUT_CTRL); +} + +static int spdif_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(dai); + u32 rate, core_freq; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + rate = params_rate(params); + + switch (rate) { + case 8000: + case 16000: + case 32000: + case 64000: + /* + * The clock is multiplied by 10 to bring it to feasible range + * of frequencies for sscg + */ + core_freq = 64000 * 128 * 10; /* 81.92 MHz */ + break; + case 5512: + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + core_freq = 176400 * 128; /* 22.5792 MHz */ + break; + case 48000: + case 96000: + case 192000: + default: + core_freq = 192000 * 128; /* 24.576 MHz */ + break; + } + + spdif_out_clock(host, core_freq, rate); + host->saved_params.core_freq = core_freq; + host->saved_params.rate = rate; + + return 0; +} + +static int spdif_out_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(dai); + u32 ctrl; + int ret = 0; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ctrl = readl(host->io_base + SPDIF_OUT_CTRL); + ctrl &= ~SPDIF_OPMODE_MASK; + if (!spdif_out_mute) + ctrl |= SPDIF_OPMODE_AUD_DATA | + SPDIF_STATE_NORMAL; + else + ctrl |= SPDIF_OPMODE_MUTE_PCM; + writel(ctrl, host->io_base + SPDIF_OUT_CTRL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ctrl = readl(host->io_base + SPDIF_OUT_CTRL); + ctrl &= ~SPDIF_OPMODE_MASK; + ctrl |= SPDIF_OPMODE_OFF; + writel(ctrl, host->io_base + SPDIF_OUT_CTRL); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int spdif_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(dai); + u32 val; + + spdif_out_mute = mute; + val = readl(host->io_base + SPDIF_OUT_CTRL); + val &= ~SPDIF_OPMODE_MASK; + + if (mute) + val |= SPDIF_OPMODE_MUTE_PCM; + else { + if (host->running) + val |= SPDIF_OPMODE_AUD_DATA | SPDIF_STATE_NORMAL; + else + val |= SPDIF_OPMODE_OFF; + } + + writel(val, host->io_base + SPDIF_OUT_CTRL); + return 0; +} + +static int spdif_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = spdif_out_mute; + return 0; +} + +static int spdif_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct snd_soc_card *card = codec->card; + struct snd_soc_pcm_runtime *rtd = card->rtd; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + if (spdif_out_mute == ucontrol->value.integer.value[0]) + return 0; + + spdif_digital_mute(cpu_dai, ucontrol->value.integer.value[0]); + + return 1; +} +static const struct snd_kcontrol_new spdif_out_controls[] = { + SOC_SINGLE_BOOL_EXT("SPDIF Play Mute", (unsigned long)&spdif_out_mute, + spdif_mute_get, spdif_mute_put), +}; + +int spdif_soc_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = dai->card; + struct snd_soc_pcm_runtime *rtd = card->rtd; + struct snd_soc_codec *codec = rtd->codec; + + return snd_soc_add_controls(codec, spdif_out_controls, + ARRAY_SIZE(spdif_out_controls)); +} + +static struct snd_soc_dai_ops spdif_out_dai_ops = { + .digital_mute = spdif_digital_mute, + .startup = spdif_out_startup, + .shutdown = spdif_out_shutdown, + .trigger = spdif_out_trigger, + .hw_params = spdif_out_hw_params, +}; + +struct snd_soc_dai_driver spdif_out_dai = { + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .probe = spdif_soc_dai_probe, + .ops = (struct snd_soc_dai_ops *)&spdif_out_dai_ops, +}; + +static int spdif_out_probe(struct platform_device *pdev) +{ + struct spdif_out_dev *host; + struct spdif_platform_data *pdata; + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name)) { + dev_warn(&pdev->dev, "Failed to get memory resourse\n"); + return -ENOENT; + } + + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) { + dev_warn(&pdev->dev, "kzalloc fail\n"); + return -ENOMEM; + } + + host->io_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!host->io_base) { + dev_warn(&pdev->dev, "ioremap failed\n"); + return -ENOMEM; + } + + host->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + pdata = dev_get_platdata(&pdev->dev); + + host->dma_params.data = pdata->dma_params; + host->dma_params.addr = res->start + SPDIF_OUT_FIFO_DATA; + host->dma_params.max_burst = 16; + host->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_params.filter = pdata->filter; + + dev_set_drvdata(&pdev->dev, host); + + ret = snd_soc_register_dai(&pdev->dev, &spdif_out_dai); + if (ret != 0) { + clk_put(host->clk); + return ret; + } + + return 0; +} + +static int spdif_out_remove(struct platform_device *pdev) +{ + struct spdif_out_dev *host = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_dai(&pdev->dev); + dev_set_drvdata(&pdev->dev, NULL); + + clk_put(host->clk); + + return 0; +} + +#ifdef CONFIG_PM +static int spdif_out_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct spdif_out_dev *host = dev_get_drvdata(&pdev->dev); + + if (host->running) + clk_disable(host->clk); + + return 0; +} + +static int spdif_out_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct spdif_out_dev *host = dev_get_drvdata(&pdev->dev); + + if (host->running) { + clk_enable(host->clk); + spdif_out_configure(host); + spdif_out_clock(host, host->saved_params.core_freq, + host->saved_params.rate); + } + return 0; +} + +static SIMPLE_DEV_PM_OPS(spdif_out_dev_pm_ops, spdif_out_suspend, \ + spdif_out_resume); + +#define SPDIF_OUT_DEV_PM_OPS (&spdif_out_dev_pm_ops) + +#else +#define SPDIF_OUT_DEV_PM_OPS NULL + +#endif + +static struct platform_driver spdif_out_driver = { + .probe = spdif_out_probe, + .remove = spdif_out_remove, + .driver = { + .name = "spdif-out", + .owner = THIS_MODULE, + .pm = SPDIF_OUT_DEV_PM_OPS, + }, +}; + +static int __init spdif_out_init(void) +{ + return platform_driver_register(&spdif_out_driver); +} +module_init(spdif_out_init); + +static void __exit spdif_out_exit(void) +{ + platform_driver_unregister(&spdif_out_driver); +} +module_exit(spdif_out_exit); + +MODULE_AUTHOR("Vipin Kumar vipin.kumar@st.com"); +MODULE_DESCRIPTION("SPEAr SPDIF OUT SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/spear/spdif_out_regs.h b/sound/soc/spear/spdif_out_regs.h new file mode 100644 index 0000000..233cc67 --- /dev/null +++ b/sound/soc/spear/spdif_out_regs.h @@ -0,0 +1,79 @@ +/* + * SPEAr SPDIF OUT controller header file + * + * Copyright (ST) 2012 Vipin Kumar (vipin.kumar@st.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPDIF_OUT_REGS_H +#define SPDIF_OUT_REGS_H + +#define SPDIF_OUT_SOFT_RST 0x00 + #define SPDIF_OUT_RESET (1 << 0) +#define SPDIF_OUT_FIFO_DATA 0x04 +#define SPDIF_OUT_INT_STA 0x08 +#define SPDIF_OUT_INT_STA_CLR 0x0C + #define SPDIF_INT_UNDERFLOW (1 << 0) + #define SPDIF_INT_EODATA (1 << 1) + #define SPDIF_INT_EOBLOCK (1 << 2) + #define SPDIF_INT_EOLATENCY (1 << 3) + #define SPDIF_INT_EOPD_DATA (1 << 4) + #define SPDIF_INT_MEMFULLREAD (1 << 5) + #define SPDIF_INT_EOPD_PAUSE (1 << 6) + +#define SPDIF_OUT_INT_EN 0x10 +#define SPDIF_OUT_INT_EN_SET 0x14 +#define SPDIF_OUT_INT_EN_CLR 0x18 +#define SPDIF_OUT_CTRL 0x1C + #define SPDIF_OPMODE_MASK (7 << 0) + #define SPDIF_OPMODE_OFF (0 << 0) + #define SPDIF_OPMODE_MUTE_PCM (1 << 0) + #define SPDIF_OPMODE_MUTE_PAUSE (2 << 0) + #define SPDIF_OPMODE_AUD_DATA (3 << 0) + #define SPDIF_OPMODE_ENCODE (4 << 0) + #define SPDIF_STATE_NORMAL (1 << 3) + #define SPDIF_DIVIDER_MASK (0xff << 5) + #define SPDIF_DIVIDER_SHIFT (5) + #define SPDIF_SAMPLEREAD_MASK (0x1ffff << 15) + #define SPDIF_SAMPLEREAD_SHIFT (15) +#define SPDIF_OUT_STA 0x20 +#define SPDIF_OUT_PA_PB 0x24 +#define SPDIF_OUT_PC_PD 0x28 +#define SPDIF_OUT_CL1 0x2C +#define SPDIF_OUT_CR1 0x30 +#define SPDIF_OUT_CL2_CR2_UV 0x34 +#define SPDIF_OUT_PAUSE_LAT 0x38 +#define SPDIF_OUT_FRMLEN_BRST 0x3C +#define SPDIF_OUT_CFG 0x40 + #define SPDIF_OUT_MEMFMT_16_0 (0 << 5) + #define SPDIF_OUT_MEMFMT_16_16 (1 << 5) + #define SPDIF_OUT_VALID_DMA (0 << 3) + #define SPDIF_OUT_VALID_HW (1 << 3) + #define SPDIF_OUT_USER_DMA (0 << 2) + #define SPDIF_OUT_USER_HW (1 << 2) + #define SPDIF_OUT_CHNLSTA_DMA (0 << 1) + #define SPDIF_OUT_CHNLSTA_HW (1 << 1) + #define SPDIF_OUT_PARITY_HW (0 << 0) + #define SPDIF_OUT_PARITY_DMA (1 << 0) + #define SPDIF_OUT_FDMA_TRIG_2 (2 << 8) + #define SPDIF_OUT_FDMA_TRIG_6 (6 << 8) + #define SPDIF_OUT_FDMA_TRIG_8 (8 << 8) + #define SPDIF_OUT_FDMA_TRIG_10 (10 << 8) + #define SPDIF_OUT_FDMA_TRIG_12 (12 << 8) + #define SPDIF_OUT_FDMA_TRIG_16 (16 << 8) + #define SPDIF_OUT_FDMA_TRIG_18 (18 << 8) + +#endif /* SPDIF_OUT_REGS_H */
The patch add support for SPEAr ASoC machine driver.
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com --- sound/soc/spear/spear_evb.c | 278 +++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 278 insertions(+), 0 deletions(-) create mode 100644 sound/soc/spear/spear_evb.c
diff --git a/sound/soc/spear/spear_evb.c b/sound/soc/spear/spear_evb.c new file mode 100644 index 0000000..66907c0 --- /dev/null +++ b/sound/soc/spear/spear_evb.c @@ -0,0 +1,278 @@ +/* + * ASoC machine driver for SPEAr evaluation boards + * + * sound/soc/spear/spear_evb.c + * + * Copyright (C) 2012 ST Microelectronics + * Rajeev Kumarrajeev-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/io.h> +#include <linux/platform_device.h> +#include <linux/module.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/hardware.h> +#include <mach/misc_regs.h> + +static int sta529_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; + u32 channel; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBS_CFM); + if (ret < 0) + return ret; + + channel = params_channels(params); + + if (cpu_is_spear1340()) { +#ifdef CONFIG_CPU_SPEAR1340 + u32 mode = 0; + u32 val = readl(VA_SPEAR1340_PERIP_CFG); + + switch (channel) { + case 8: + mode = SPEAR1340_I2S_CHNL_7_1; + break; + case 6: + mode = SPEAR1340_I2S_CHNL_5_1; + break; + case 4: + mode = SPEAR1340_I2S_CHNL_3_1; + break; + case 2: + default: + mode = SPEAR1340_I2S_CHNL_2_0; + break; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mode = mode << SPEAR1340_I2S_CHNL_PLAY_SHIFT; + val = (val & ~SPEAR1340_I2S_CHNL_PLAY_MASK) | mode; + } else { + mode = mode << SPEAR1340_I2S_CHNL_REC_SHIFT; + val = (val & ~SPEAR1340_I2S_CHNL_REC_MASK) | mode; + } + writel(val, VA_SPEAR1340_PERIP_CFG); +#endif + } else if (cpu_is_spear1300() || cpu_is_spear1310_reva() || + cpu_is_spear900() || cpu_is_spear1310()) { +#if defined(CONFIG_CPU_SPEAR1300) || defined(CONFIG_CPU_SPEAR1310_REVA) || \ + defined(CONFIG_CPU_SPEAR900) || defined(CONFIG_CPU_SPEAR1310) + /* setting mode 0 in conf register: 32c offset */ + u32 val = readl(VA_PERIP_CFG); + val &= ~I2S_MODE_MASK; + val |= I2S_MODE_I2S2_ONE_PORT; + writel(val, VA_PERIP_CFG); +#endif + } + + return 0; +} + +/* Audio machine driver for SPEAr evb */ +static struct snd_soc_ops sta529_ops = { + .hw_params = sta529_hw_params, +}; + +/* SPEAr audio interface glue - connects codec <--> CPU <--> platform */ +static struct snd_soc_dai_link spear_evb_dai[] = { + { + .name = "sta529-pcm", + .stream_name = "pcm", + .cpu_dai_name = "designware-i2s.0", + .platform_name = "spear-pcm-audio", + .codec_dai_name = "sta529-audio", + .codec_name = "sta529-codec.0-001a", + .ops = &sta529_ops, + }, +}; + +/* SPEAr audio machine driver */ +static struct snd_soc_card spear_snd_card = { + .name = "spear-evb", + .dai_link = spear_evb_dai, + .num_links = ARRAY_SIZE(spear_evb_dai), +}; + +/* SPEAr320s audio interface glue - connects codec <--> CPU <--> platform */ +static struct snd_soc_dai_link spear320s_evb_dai[] = { + { + .name = "sta529-pcm", + .stream_name = "pcm", + .cpu_dai_name = "designware-i2s", + .platform_name = "spear-pcm-audio", + .codec_dai_name = "sta529-audio", + .codec_name = "sta529-codec.0-001a", + .ops = &sta529_ops, + }, +}; + +/* SPEAr320s audio machine driver */ +static struct snd_soc_card spear320s_snd_card = { + .name = "spear320s-evb", + .dai_link = spear320s_evb_dai, + .num_links = ARRAY_SIZE(spear320s_evb_dai), +}; + +/* LCAD audio interface glue - connects codec <--> CPU <--> platform */ +static struct snd_soc_dai_link lcad_evb_dai[] = { + { + .name = "sta529-pcm0", + .stream_name = "I2S Playback", + .cpu_dai_name = "designware-i2s.0", + .platform_name = "spear-pcm-audio", + .codec_dai_name = "sta529-audio", + .codec_name = "sta529-codec.0-001a", + .ops = &sta529_ops, + }, { + .name = "sta529-pcm1", + .stream_name = "I2S Capture", + .cpu_dai_name = "designware-i2s.1", + .platform_name = "spear-pcm-audio", + .codec_dai_name = "sta529-audio", + .codec_name = "sta529-codec.0-001a", + .ops = &sta529_ops, + }, +}; + +static struct snd_soc_card lcad_snd_card = { + .name = "lcad-evb", + .dai_link = lcad_evb_dai, + .num_links = ARRAY_SIZE(lcad_evb_dai), +}; + +/* Audio machine driver for SPEAr1340 evb */ + +/* SPEAr1340 audio interface glue - connects codec <--> CPU <--> platform */ +static struct snd_soc_dai_link spear1340_evb_dai[] = { + { + .name = "spdif-pcm0", + .stream_name = "SPDIF Playback", + .cpu_dai_name = "spdif-out", + .platform_name = "spear-pcm-audio", + .codec_dai_name = "dit-hifi", + .codec_name = "spdif-dit", + .ops = NULL, + }, { + .name = "spdif-pcm1", + .stream_name = "SPDIF Capture", + .cpu_dai_name = "spdif-in", + .platform_name = "spear-pcm-audio", + .codec_dai_name = "dir-hifi", + .codec_name = "spdif-dir", + .ops = NULL, + }, { + .name = "sta529-pcm0", + .stream_name = "I2S Playback", + .cpu_dai_name = "designware-i2s.0", + .platform_name = "spear-pcm-audio", + .codec_dai_name = "sta529-audio", + .codec_name = "sta529-codec.0-001a", + .ops = &sta529_ops, + }, { + .name = "sta529-pcm1", + .stream_name = "I2S Capture", + .cpu_dai_name = "designware-i2s.1", + .platform_name = "spear-pcm-audio", + .codec_dai_name = "sta529-audio", + .codec_name = "sta529-codec.0-001a", + .ops = &sta529_ops, + }, +}; + +static struct snd_soc_card spear1340_snd_card = { + .name = "spear1340-evb", + .dai_link = spear1340_evb_dai, + .num_links = ARRAY_SIZE(spear1340_evb_dai), +}; + +static struct platform_device *evb_snd_device; +#if defined(CONFIG_CPU_SPEAR1340) +static struct platform_device *spdif_dit_device; +static struct platform_device *spdif_dir_device; +#endif + +static int __init spear_audio_init(void) +{ + int ret; + struct snd_soc_card *spear_soc_card; + + if (machine_is_spear1340_lcad()) + spear_soc_card = &lcad_snd_card; + else if (cpu_is_spear1340()) + spear_soc_card = &spear1340_snd_card; + else if (cpu_is_spear320()) + spear_soc_card = &spear320s_snd_card; + else + spear_soc_card = &spear_snd_card; + +#if defined(CONFIG_CPU_SPEAR1340) + if (cpu_is_spear1340()) { + /* Create and register spdif platform devices */ + spdif_dit_device = platform_device_alloc("spdif-dit", -1); + if (!spdif_dit_device) { + printk(KERN_ERR "spdif transceiver " \ + "platform_device_alloc fails\n"); + return -ENOMEM; + } + ret = platform_device_add(spdif_dit_device); + if (ret) { + printk(KERN_ERR "Unable to add spdif transceiver " \ + "platform device\n"); + platform_device_put(spdif_dit_device); + } + + spdif_dir_device = platform_device_alloc("spdif-dir", -1); + if (!spdif_dir_device) { + printk(KERN_ERR "spdif receive platform_device_alloc " \ + "fails\n"); + return -ENOMEM; + } + ret = platform_device_add(spdif_dir_device); + if (ret) { + printk(KERN_ERR "Unable to add spdif receive platform" \ + "device\n"); + platform_device_put(spdif_dir_device); + } + } +#endif + /* Create and register platform device */ + evb_snd_device = platform_device_alloc("soc-audio", -1); + if (!evb_snd_device) { + printk(KERN_ERR "soc audio platform_device_alloc fails\n"); + return -ENOMEM; + } + platform_set_drvdata(evb_snd_device, spear_soc_card); + 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_audio_init); + +static void __exit spear_audio_exit(void) +{ + platform_device_unregister(evb_snd_device); +} +module_exit(spear_audio_exit); + +MODULE_AUTHOR("Rajeev Kumar rajeev-dlh.kumar@st.com"); +MODULE_DESCRIPTION("ST SPEAr EVB ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:spear_evb");
On Tue, Mar 20, 2012 at 05:03:50PM +0530, Rajeev Kumar wrote:
The patch add support for SPEAr ASoC machine driver.
This is the support for some particular board rather than for the SoC.
- if (cpu_is_spear1340()) {
+#ifdef CONFIG_CPU_SPEAR1340
u32 mode = 0;
u32 val = readl(VA_SPEAR1340_PERIP_CFG);
Ick, no! This should be in a driver for the SoC somewhere, not in the board. I've not really read most of the rest of the driver.
+static int __init spear_audio_init(void) +{
- int ret;
- struct snd_soc_card *spear_soc_card;
- if (machine_is_spear1340_lcad())
spear_soc_card = &lcad_snd_card;
- else if (cpu_is_spear1340())
spear_soc_card = &spear1340_snd_card;
- else if (cpu_is_spear320())
spear_soc_card = &spear320s_snd_card;
- else
spear_soc_card = &spear_snd_card;
Use snd_soc_register_card().
+#if defined(CONFIG_CPU_SPEAR1340)
- if (cpu_is_spear1340()) {
/* Create and register spdif platform devices */
spdif_dit_device = platform_device_alloc("spdif-dit", -1);
if (!spdif_dit_device) {
printk(KERN_ERR "spdif transceiver " \
"platform_device_alloc fails\n");
return -ENOMEM;
}
Absolutely no, all this setup of the devices for the SoC should be in your arch/arm code somewhere. This should only be a driver for the board.
This patch add Kconfig and Makefile to support SPEAr Audio driver
Signed-off-by: Rajeev Kumar rajeev-dlh.kumar@st.com --- sound/soc/spear/Kconfig | 46 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/spear/Makefile | 16 ++++++++++++++++ 2 files changed, 62 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..4ab9154 --- /dev/null +++ b/sound/soc/spear/Kconfig @@ -0,0 +1,46 @@ +config SND_SOC_SPEAR_EVM + tristate "SoC Audio support for SPEAr EVM" + select SND_SOC_DESIGNWARE_I2S + select SND_SOC_STA529 + select SND_SOC_SPEAR_PCM + help + Say Y if you want to add support for SoC audio on SPEAr + platform + +config SND_SOC_SPEAR1340_EVM + tristate "SoC Audio support for SPEAr1340 EVM" + select SND_SOC_DESIGNWARE_I2S + select SND_SOC_STA529 + select SND_SOC_SPEAR_SPDIF_OUT + select SND_SOC_SPEAR_SPDIF_IN + select SND_SOC_SPDIF + select SND_SOC_SPEAR_PCM + help + Say Y if you want to add support for SoC audio on SPEAr1340 + platform + +config SND_SOC_SPEAR_PCM + tristate "SoC Audio for the ST chip" + depends on SND_SOC_DESIGNWARE_I2S || SND_SOC_SPEAR_SPDIF_OUT || \ + SND_SOC_SPEAR_SPDIF_IN + help + Say Y or M if you want to add support for any of the audio + controllers (I2S/SPDIF). You will also need to select + the audio interface codecs to support below. + +config SND_SOC_DESIGNWARE_I2S + tristate "Synopsys I2S Device Driver" + help + Say Y or M if you want to add support for I2S driver for + Synopsys desigwnware I2S device. The device supports upto + maximum of 8 channels each for play, record or both. + +config SND_SOC_SPEAR_SPDIF_OUT + tristate "SPEAr SPDIF Out Device Driver" + help + Say Y or M if you want to add support for SPDIF OUT driver. + +config SND_SOC_SPEAR_SPDIF_IN + tristate "SPEAr SPDIF IN Device Driver" + help + Say Y or M if you want to add support for SPDIF IN driver. diff --git a/sound/soc/spear/Makefile b/sound/soc/spear/Makefile new file mode 100644 index 0000000..bc49e75 --- /dev/null +++ b/sound/soc/spear/Makefile @@ -0,0 +1,16 @@ +# SPEAr Platform Support +snd-soc-pcm-objs := spear_pcm.o +snd-soc-i2s-objs := designware_i2s.o +snd-soc-spdif-in-objs := spdif_in.o +snd-soc-spdif-out-objs := spdif_out.o + +obj-$(CONFIG_SND_SOC_SPEAR_PCM) += snd-soc-pcm.o +obj-$(CONFIG_SND_SOC_DESIGNWARE_I2S) += snd-soc-i2s.o +obj-$(CONFIG_SND_SOC_SPEAR_SPDIF_IN) += snd-soc-spdif-in.o +obj-$(CONFIG_SND_SOC_SPEAR_SPDIF_OUT) += snd-soc-spdif-out.o + +# SPEAr Machine Support +snd-soc-evb-objs := spear_evb.o + +obj-$(CONFIG_SND_SOC_SPEAR_EVM) += snd-soc-evb.o +obj-$(CONFIG_SND_SOC_SPEAR1340_EVM) += snd-soc-evb.o
This patch update Kconfig and Makefile present in soc directory to support SPEAr.
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 35e662d..3860465 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -43,6 +43,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 9ea8ac8..238da08 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -20,5 +20,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/
participants (3)
-
Lars-Peter Clausen
-
Mark Brown
-
Rajeev Kumar