[alsa-devel] [PATCH 1/4] ASoC: ALSA ASoC CQ0093 Voice Codec Driver
From: Miguel Aguilar miguel.aguilar@ridgerun.com
This patch adds support for the Voice Codec CQ0093.
This driver was tested on a DM355 EVM rev C.
Signed-off-by: Miguel Aguilar miguel.aguilar@ridgerun.com --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cq93vc.c | 377 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/cq93vc.h | 23 +++ 4 files changed, 406 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/cq93vc.c create mode 100644 sound/soc/codecs/cq93vc.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0edca93..55d1287 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -19,6 +19,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C select SND_SOC_AK4642 if I2C + select SND_SOC_CQ0093VC if ARCH_DAVINCI_DM365 select SND_SOC_CS4270 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 @@ -96,6 +97,9 @@ config SND_SOC_AK4535 config SND_SOC_AK4642 tristate
+config SND_SOC_CQ0093VC + tristate + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fb4af28..a3e713c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -6,6 +6,7 @@ snd-soc-ad73311-objs := ad73311.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o snd-soc-ak4642-objs := ak4642.o +snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-l3-objs := l3.o @@ -56,6 +57,7 @@ obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o +obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c new file mode 100644 index 0000000..e64af25 --- /dev/null +++ b/sound/soc/codecs/cq93vc.c @@ -0,0 +1,377 @@ +/* + * ALSA SoC CQ0093 Voice Codec Driver + * + * Copyright (C) 2009 Texas Instruments. + * + * Author: Miguel Aguilar miguel.aguilar@ridgerun.com + * + * Initial code: Hui Geng + * + * 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/clk.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include <mach/dm365.h> + +#include "cq93vc.h" + +#define AUDIO_NAME "cq0093" +#define CQ93VC_VERSION "0.1" + +/* + * Voice codec registers + * + * The base address for the registers used by this driver is shifted + * 0x80 from the original Voice Codec base adress since the first region + * of the Voice Codec registers is used by the Voice Codec Interface driver. + */ +#define VC_REG05 0x14 /* PGA and MIC Gain Control */ +#define VC_REG09 0x24 /* Digital Soft Mute/Attenuation Control */ +#define VC_REG12 0x30 /* Voice Codec Up/Down */ + +/* Registers bits */ +#define PGA_GAIN 0x07 +#define DIG_ATTEN 0x3F +#define MUTE 0x40 +#define POWER_ALL_ON 0xFD + +/* Codec private data */ +struct cq93vc_priv { + void __iomem *base; + unsigned int sysclk; + resource_size_t pbase; + size_t base_size; +}; + +/* + * Read to the cq0093 register space + */ +static inline unsigned int cq93vc_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct cq93vc_priv *cq93vc = codec->private_data; + + return __raw_readb(cq93vc->base + reg); +} + +/* + * Write to the cq0093 register space + */ +static int cq93vc_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct cq93vc_priv *cq93vc = codec->private_data; + + __raw_writeb(value, cq93vc->base + reg); + return 0; +} + +static int cq93vc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return 0; +} + +static const struct snd_kcontrol_new cq93vc_snd_controls[] = { + SOC_SINGLE("PGA Capture Volume", VC_REG05, 0, 0x03, 0), + SOC_SINGLE("Mono DAC Playback Volume", VC_REG09, 0, 0x3f, 0), +}; + +static int cq93vc_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 reg = cq93vc_read(codec, VC_REG09) & ~MUTE; + + if (mute) + cq93vc_write(codec, VC_REG09, reg | MUTE); + else + cq93vc_write(codec, VC_REG09, reg); + + return 0; +} + +static int cq93vc_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(cq93vc_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&cq93vc_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static int cq93vc_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cq93vc_priv *cq93vc = codec->private_data; + + switch (freq) { + case 22579200: + case 27000000: + case 33868800: + cq93vc->sysclk = freq; + return 0; + } + + return -EINVAL; +} + +static int cq93vc_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + /* all power is driven by DAPM system */ + cq93vc_write(codec, VC_REG12, POWER_ALL_ON); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* + * all power is driven by DAPM system, + * so output power is safe if bypass was set + */ + cq93vc_write(codec, VC_REG12, 0x0); + break; + case SND_SOC_BIAS_OFF: + /* force all power off */ + cq93vc_write(codec, VC_REG12, 0x0); + break; + } + codec->bias_level = level; + + return 0; +} + +#define CQ93VC_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) +#define CQ93VC_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE) + +static struct snd_soc_dai_ops cq93vc_dai_ops = { + .hw_params = cq93vc_hw_params, + .digital_mute = cq93vc_mute, + .set_sysclk = cq93vc_set_dai_sysclk, +}; + +struct snd_soc_dai cq93vc_dai = { + .name = "cq93vc", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CQ93VC_RATES, + .formats = CQ93VC_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CQ93VC_RATES, + .formats = CQ93VC_FORMATS,}, + .ops = &cq93vc_dai_ops, +}; +EXPORT_SYMBOL_GPL(cq93vc_dai); + +static int cq93vc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + cq93vc_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int cq93vc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + cq93vc_set_bias_level(codec, codec->suspend_bias_level); + + return 0; +} + +static struct snd_soc_device *cq93vc_socdev; + +static int cq93vc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + struct cq93vc_priv *cq93vc; + struct snd_soc_codec *codec; + struct resource *res, *mem; + int ret; + + dev_info(dev, "CQ0093 Voice Codec %s\n", CQ93VC_VERSION); + + cq93vc = kzalloc(sizeof(struct cq93vc_priv), GFP_KERNEL); + if (cq93vc == NULL) { + dev_dbg(dev, "%s: could not allocate memory for private data\n", + pdev->name); + return -ENOMEM; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) { + dev_dbg(dev, "%s: could not allocate memory for codec data\n", + pdev->name); + ret = -ENOMEM; + goto fail1; + } + + res = cq93vc_resources; + + cq93vc->pbase = res->start; + cq93vc->base_size = resource_size(res); + + mem = request_mem_region(cq93vc->pbase, + cq93vc->base_size, "cq93vc"); + if (!mem) { + dev_dbg(dev,"%s: voice codec registers at %08x are not free\n", + pdev->name, cq93vc->pbase); + ret = -EBUSY; + goto fail2; + } + + cq93vc->base = ioremap(cq93vc->pbase, cq93vc->base_size); + if (cq93vc->base == NULL) { + dev_dbg(dev,"%s: can't ioremap mem resource.\n", + pdev->name); + ret = -ENOMEM; + goto fail3; + } + + mutex_init(&codec->mutex); + codec->private_data = cq93vc; + codec->name = "cq93vc"; + codec->owner = THIS_MODULE; + codec->read = cq93vc_read; + codec->write = cq93vc_write; + codec->set_bias_level = cq93vc_set_bias_level; + codec->dai = &cq93vc_dai; + codec->num_dai = 1; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + socdev->card->codec = codec; + + /* Register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(dev, "%s: failed to create pcms\n", pdev->name); + goto fail4; + } + + /* Set the PGA Gain to 18 dB */ + cq93vc_write(codec, VC_REG05, PGA_GAIN); + + /* Set the DAC digital attenuation to 0 dB */ + cq93vc_write(codec, VC_REG09, DIG_ATTEN); + + /* Set controls */ + cq93vc_add_controls(codec); + + /* Off, with power on */ + cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Register sound card */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(dev, "%s: failed to register card\n", pdev->name); + goto fail5; + } + + cq93vc_socdev = socdev; + + return 0; +fail5: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +fail4: + iounmap(cq93vc->base); +fail3: + release_mem_region(cq93vc->pbase, cq93vc->base_size); +fail2: + kfree(codec); +fail1: + kfree(cq93vc); + + return ret; +} + +static int cq93vc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct cq93vc_priv *cq93vc = codec->private_data; + + /* Power down chip */ + if (codec->control_data) + cq93vc_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + iounmap(cq93vc->base); + release_mem_region(cq93vc->pbase, cq93vc->base_size); + + kfree(codec); + kfree(cq93vc); + + cq93vc_socdev = NULL; + + snd_soc_unregister_dai(&cq93vc_dai); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_cq93vc = { + .probe = cq93vc_probe, + .remove = cq93vc_remove, + .suspend = cq93vc_suspend, + .resume = cq93vc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_cq93vc); + +static __init int cq93vc_init(void) +{ + return snd_soc_register_dai(&cq93vc_dai); +} +module_init(cq93vc_init); + +static __exit void cq93vc_exit(void) +{ + snd_soc_unregister_dai(&cq93vc_dai); +} +module_exit(cq93vc_exit); + +MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC CQ0093 Voice Codec Driver"); +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cq93vc.h b/sound/soc/codecs/cq93vc.h new file mode 100644 index 0000000..8d37763 --- /dev/null +++ b/sound/soc/codecs/cq93vc.h @@ -0,0 +1,23 @@ +/* + * ALSA SoC CQ0093 Voice Codec Driver + * + * Copyright (C) 2009 Texas Instruments. + * + * Author: Miguel Aguilar miguel.aguilar@ridgerun.com + * + * Initial code: Hui Geng + * + * 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. + */ + +#ifndef _CQ93VC_H +#define _CQ93VC_H + +extern struct snd_soc_dai cq93vc_dai; +extern struct snd_soc_codec_device soc_codec_dev_cq93vc; +extern struct resource cq93vc_resources[]; + +#endif
On Tue, Sep 22, 2009 at 01:28:58PM -0600, miguel.aguilar@ridgerun.com wrote:
Looks mostly good - the main issue here is the device registration stuff, everything else is fairly minor.
- select SND_SOC_CQ0093VC if ARCH_DAVINCI_DM365
This probably should have no dependency (see below about device registration).
+#define AUDIO_NAME "cq0093" +#define CQ93VC_VERSION "0.1"
I'd just drop these.
+static int cq93vc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- return 0;
+}
Just omit this if it's empty.
+static const struct snd_kcontrol_new cq93vc_snd_controls[] = {
- SOC_SINGLE("PGA Capture Volume", VC_REG05, 0, 0x03, 0),
- SOC_SINGLE("Mono DAC Playback Volume", VC_REG09, 0, 0x3f, 0),
Any chance of adding TLV information for these? Not essential but it's nice for userspace applications.
+static int cq93vc_add_controls(struct snd_soc_codec *codec) +{
Please use snd_soc_add_controls instead.
+static int cq93vc_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
+{
- switch (level) {
- case SND_SOC_BIAS_ON:
/* all power is driven by DAPM system */
cq93vc_write(codec, VC_REG12, POWER_ALL_ON);
The code is OK but the driver isn't using DAPM.
+static int cq93vc_probe(struct platform_device *pdev) +{
You should make the driver a regular platform device - that way you won't need to do the trick with the global variable to get the resources and you won't need to register the DAIs on module_init. See wm8350 for an example of doing this with a platform device.
Mark,
Mark Brown wrote:
On Tue, Sep 22, 2009 at 01:28:58PM -0600, miguel.aguilar@ridgerun.com wrote:
Looks mostly good - the main issue here is the device registration stuff, everything else is fairly minor.
- select SND_SOC_CQ0093VC if ARCH_DAVINCI_DM365
This probably should have no dependency (see below about device registration).
[MA] OK I'll remove that dependency.
+#define AUDIO_NAME "cq0093" +#define CQ93VC_VERSION "0.1"
I'd just drop these.
[MA] Ok.
+static int cq93vc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- return 0;
+}
Just omit this if it's empty.
[MA] Ok.
+static const struct snd_kcontrol_new cq93vc_snd_controls[] = {
- SOC_SINGLE("PGA Capture Volume", VC_REG05, 0, 0x03, 0),
- SOC_SINGLE("Mono DAC Playback Volume", VC_REG09, 0, 0x3f, 0),
Any chance of adding TLV information for these? Not essential but it's nice for userspace applications.
[MA] I'll take a look if there is chance of adding TLV.
+static int cq93vc_add_controls(struct snd_soc_codec *codec) +{
Please use snd_soc_add_controls instead.
[MA] Ok.
+static int cq93vc_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
+{
- switch (level) {
- case SND_SOC_BIAS_ON:
/* all power is driven by DAPM system */
cq93vc_write(codec, VC_REG12, POWER_ALL_ON);
The code is OK but the driver isn't using DAPM.
[MA] Should it use DAPM, if so, How is the proper way to do that?
+static int cq93vc_probe(struct platform_device *pdev) +{
You should make the driver a regular platform device - that way you won't need to do the trick with the global variable to get the resources and you won't need to register the DAIs on module_init. See wm8350 for an example of doing this with a platform device.
I tried that and I got many some Virtual memory errors since there is the VCIF and the CQ0093 codecs belong to the same register domain of the DM365, the first driver which is being registered is the CQ0093 before the VCIF, but the VCIF is the one that enables the common clock for both, so that's why I get the virtual memory error. I'll take a look into wm8350 code and I'll try to make it a platform device.
On Tue, Sep 22, 2009 at 03:42:37PM -0600, Miguel Aguilar wrote:
Mark Brown wrote:
On Tue, Sep 22, 2009 at 01:28:58PM -0600, miguel.aguilar@ridgerun.com wrote:
The code is OK but the driver isn't using DAPM.
[MA] Should it use DAPM, if so, How is the proper way to do that?
Depends if there's any meaningful power management - if there is then most of the other drivers have examples of what to do, you basically tell the core about all the things you have power switches for and how they're interconnected then it will power on only the bits that are in active use at any one moment.
It's not essential, though - my comment was more there because the comments in the driver mentioned that power management was being done by DAPM when it isn't currently.
+static int cq93vc_probe(struct platform_device *pdev) +{
You should make the driver a regular platform device - that way you won't need to do the trick with the global variable to get the resources and you won't need to register the DAIs on module_init. See wm8350 for an example of doing this with a platform device.
I tried that and I got many some Virtual memory errors since there is the VCIF and the CQ0093 codecs belong to the same register domain of the DM365, the first driver which is being registered is the CQ0093 before the VCIF, but the VCIF is the one that enables the common clock for both, so that's why I get the virtual memory error. I'll take a look into wm8350 code and I'll try to make it a platform device.
Sounds like you want to make a MFD for the block as a whole and then have the CODEC and CPU parts of it as subdevices of that. drivers/mfd provides a lightweight framework for doing this.
participants (3)
-
Mark Brown
-
Miguel Aguilar
-
miguel.aguilar@ridgerun.com