diff mbox

[1/4] ASoC: ALSA ASoC CQ0093 Voice Codec Driver

Message ID 1253647738-32527-1-git-send-email-miguel.aguilar@ridgerun.com (mailing list archive)
State Changes Requested
Headers show

Commit Message

miguel.aguilar@ridgerun.com Sept. 22, 2009, 7:28 p.m. UTC
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

Comments

miguel.aguilar@ridgerun.com Sept. 22, 2009, 9:42 p.m. UTC | #1
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.
diff mbox

Patch

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