[PATCH 3/3] Add a codec driver for SI476X MFD

From: Andrey Smirnov
Date: Thu Sep 13 2012 - 18:46:01 EST


This commit add a sound codec driver for Silicon Laboratories 476x
series of AM/FM radio chips.

Signed-off-by: Andrey Smirnov <andrey.smirnov@xxxxxxxxxxxxxxxxxxxx>
---
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/si476x.c | 346 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 352 insertions(+)
create mode 100644 sound/soc/codecs/si476x.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 9f8e859..71ab390 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -70,6 +70,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_UDA134X
select SND_SOC_UDA1380 if I2C
select SND_SOC_WL1273 if MFD_WL1273_CORE
+ select SND_SOC_SI476X if MFD_SI476X_CORE
select SND_SOC_WM1250_EV1 if I2C
select SND_SOC_WM2000 if I2C
select SND_SOC_WM2200 if I2C
@@ -326,6 +327,9 @@ config SND_SOC_UDA1380
config SND_SOC_WL1273
tristate

+config SND_SOC_SI476X
+ tristate
+
config SND_SOC_WM1250_EV1
tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 34148bb..aecf09b 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -61,6 +61,7 @@ snd-soc-twl6040-objs := twl6040.o
snd-soc-uda134x-objs := uda134x.o
snd-soc-uda1380-objs := uda1380.o
snd-soc-wl1273-objs := wl1273.o
+snd-soc-si476x-objs := si476x.o
snd-soc-wm1250-ev1-objs := wm1250-ev1.o
snd-soc-wm2000-objs := wm2000.o
snd-soc-wm2200-objs := wm2200.o
@@ -177,6 +178,7 @@ obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o
obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o
obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o
obj-$(CONFIG_SND_SOC_WL1273) += snd-soc-wl1273.o
+obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o
obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o
obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o
obj-$(CONFIG_SND_SOC_WM2200) += snd-soc-wm2200.o
diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c
new file mode 100644
index 0000000..beea2ca
--- /dev/null
+++ b/sound/soc/codecs/si476x.c
@@ -0,0 +1,346 @@
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include <linux/mfd/si476x-core.h>
+
+#define SI476X_AUDIO_VOLUME 0x0300
+#define SI476X_AUDIO_MUTE 0x0301
+#define SI476X_DIGITAL_IO_OUTPUT_FORMAT 0x0203
+#define SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE 0x0202
+
+#define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK ~((0b111 << 11) | (0b111 << 8))
+#define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK ~(0b1111110)
+
+
+/* codec private data */
+struct si476x_codec {
+ struct si476x_core *core;
+};
+
+static unsigned int si476x_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ int err;
+ struct si476x_codec *si476x = snd_soc_codec_get_drvdata(codec);
+ struct si476x_core *core = si476x->core;
+
+ si476x_core_lock(core);
+ err = si476x_core_cmd_get_property(core, reg);
+ si476x_core_unlock(core);
+
+ return err;
+}
+
+static int si476x_codec_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val)
+{
+ int err;
+ struct si476x_codec *si476x = snd_soc_codec_get_drvdata(codec);
+ struct si476x_core *core = si476x->core;
+
+ si476x_core_lock(core);
+ err = si476x_core_cmd_set_property(core, reg, val);
+ si476x_core_unlock(core);
+
+ return err;
+}
+
+
+
+static int si476x_codec_set_daudio_params(struct snd_soc_codec *codec,
+ int width, int rate)
+{
+ int err;
+ u16 digital_io_output_format = \
+ snd_soc_read(codec,
+ SI476X_DIGITAL_IO_OUTPUT_FORMAT);
+
+ if ((rate < 32000) || (rate > 48000)) {
+ dev_dbg(codec->dev, "Rate: %d is not supported\n", rate);
+ return -EINVAL;
+ }
+
+ err = snd_soc_write(codec, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE,
+ rate);
+ if (err < 0) {
+ dev_err(codec->dev, "Failed to set sample rate\n");
+ return err;
+ }
+
+ digital_io_output_format &= SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK;
+ digital_io_output_format |= (width << 11) | (width << 8);
+
+ return snd_soc_write(codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
+ digital_io_output_format);
+}
+
+static int si476x_codec_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] =
+ snd_soc_read(codec, SI476X_AUDIO_VOLUME);
+ return 0;
+}
+
+static int si476x_codec_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ snd_soc_write(codec, SI476X_AUDIO_VOLUME,
+ ucontrol->value.integer.value[0]);
+ return 1;
+}
+
+#define SI476X_MAX_VOLUME 63
+
+static const struct snd_kcontrol_new si476x_controls[] = {
+ SOC_SINGLE_EXT("Analog Volume", 0, 0, SI476X_MAX_VOLUME, 0,
+ si476x_codec_volume_get, si476x_codec_volume_put),
+};
+
+enum si476x_daudio_formats {
+ SI476X_DAUDIO_MODE_I2S = (0x0 << 1),
+ SI476X_DAUDIO_MODE_DSP_A = (0x6 << 1),
+ SI476X_DAUDIO_MODE_DSP_B = (0x7 << 1),
+ SI476X_DAUDIO_MODE_LEFT_J = (0x8 << 1),
+ SI476X_DAUDIO_MODE_RIGHT_J = (0x9 << 1),
+
+ SI476X_DAUDIO_MODE_IB = (1 << 5),
+ SI476X_DAUDIO_MODE_IF = (1 << 6),
+};
+
+static int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 digital_io_output_format = \
+ snd_soc_read(codec,
+ SI476X_DIGITAL_IO_OUTPUT_FORMAT);
+
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+ return -EINVAL;
+
+ digital_io_output_format &= SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ digital_io_output_format |= SI476X_DAUDIO_MODE_DSP_A;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ digital_io_output_format |= SI476X_DAUDIO_MODE_DSP_B;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ digital_io_output_format |= SI476X_DAUDIO_MODE_I2S;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ digital_io_output_format |= SI476X_DAUDIO_MODE_RIGHT_J;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ digital_io_output_format |= SI476X_DAUDIO_MODE_LEFT_J;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ digital_io_output_format |= SI476X_DAUDIO_MODE_IB;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ digital_io_output_format |= SI476X_DAUDIO_MODE_IB |
+ SI476X_DAUDIO_MODE_IF;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ digital_io_output_format |= SI476X_DAUDIO_MODE_IB;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ digital_io_output_format |= SI476X_DAUDIO_MODE_IF;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return snd_soc_write(codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
+ digital_io_output_format);
+}
+
+static int si476x_codec_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ if (mute)
+ snd_soc_write(codec_dai->codec, SI476X_AUDIO_MUTE, 0x3);
+
+ return 0;
+}
+
+
+enum si476x_pcm_format {
+ SI476X_PCM_FORMAT_S8 = 2,
+ SI476X_PCM_FORMAT_S16_LE = 4,
+ SI476X_PCM_FORMAT_S20_3LE = 5,
+ SI476X_PCM_FORMAT_S24_LE = 6,
+};
+
+static int si476x_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int rate, width, err;
+
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ width = SI476X_PCM_FORMAT_S8;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ width = SI476X_PCM_FORMAT_S16_LE;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ width = SI476X_PCM_FORMAT_S20_3LE;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ width = SI476X_PCM_FORMAT_S24_LE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ err = si476x_codec_set_daudio_params(rtd->codec, width, rate);
+
+ return err;
+}
+
+
+
+static int si476x_codec_probe(struct snd_soc_codec *codec)
+{
+ struct si476x_core **core = codec->dev->platform_data;
+ struct si476x_codec *si476x;
+
+ if (!core) {
+ dev_err(codec->dev, "Platform data is missing.\n");
+ return -EINVAL;
+ }
+
+ si476x = kzalloc(sizeof(*si476x), GFP_KERNEL);
+ if (si476x == NULL) {
+ dev_err(codec->dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ si476x->core = *core;
+
+ snd_soc_codec_set_drvdata(codec, si476x);
+
+ return 0;
+}
+
+static int si476x_codec_remove(struct snd_soc_codec *codec)
+{
+ struct si476x_codec *si476x = snd_soc_codec_get_drvdata(codec);
+
+ kfree(si476x);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops si476x_dai_ops = {
+ .hw_params = si476x_codec_hw_params,
+ .digital_mute = si476x_codec_digital_mute,
+ .set_fmt = si476x_codec_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver si476x_dai = {
+ .name = "si476x-codec",
+
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+
+ .rates = SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_3LE |
+ SNDRV_PCM_FMTBIT_S24_LE
+ },
+ .ops = &si476x_dai_ops,
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_si476x = {
+ .probe = si476x_codec_probe,
+ .remove = si476x_codec_remove,
+ .read = si476x_codec_read,
+ .write = si476x_codec_write,
+};
+
+static int __devinit si476x_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_si476x,
+ &si476x_dai, 1);
+}
+
+static int __devexit si476x_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_ALIAS("platform:si476x-codec");
+
+static struct platform_driver si476x_platform_driver = {
+ .driver = {
+ .name = "si476x-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = si476x_platform_probe,
+ .remove = __devexit_p(si476x_platform_remove),
+};
+
+static int __init si476x_init(void)
+{
+ return platform_driver_register(&si476x_platform_driver);
+}
+module_init(si476x_init);
+
+static void __exit si476x_exit(void)
+{
+ platform_driver_unregister(&si476x_platform_driver);
+}
+module_exit(si476x_exit);
+
+MODULE_AUTHOR("Andrey Smirnov <andrey.smirnov@xxxxxxxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("ASoC Si4761/64 codec driver");
+MODULE_LICENSE("GPL");
--
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/