[alsa-devel] [PATCH] Add ASoC TLV320 Codec driver.

Vladimir A. Barinov vbarinov at ru.mvista.com
Tue Nov 20 13:26:32 CET 2007


Hi Mark,

The TLV320 name doesn't define the specific device, it just names the TI 
brand and voltage params. There is a certain gradation of TLV320 codecs 
and they are quite different inside. They are:

1) TLV320AIC2X family that currently includes aic20, aic20K, aic21, 
aic24, aic24K, aic25 audio chips

2) TLV320AIC3X family that currently includes aic31, aic3106, aic32, 
aic33 and possible other audio chips

3) for aic2x there are some  chips that has diferent internal structure 
and couldn't be put in 1) family:
{aic22}  - quite a different by internal structure
{aic23}  - quite a different by internal structure
{aic26}  - quite a different by internal structure
{aic28, aic29} - these are also similar by register set

So you shouldn't name the tlv320aic24K codec driver as "tlv320". You 
should create one named like tlv320aic2x and in the driver name callback 
starting with "aic2x_".

Regards,
Vladimir

Mark Brown wrote:
> From: Liam Girdwood <liam at localhost.localdomain>
>
> Signed-off-by: Nicola Perrino <nicola.perrino at atlab.it>
> Signed-off-by: Liam Girdwood <lg at opensource.wolfsonmicro.com>
> Cc: i2c at lm-sensors.org
> ---
>  include/linux/i2c-id.h    |    1 +
>  sound/soc/codecs/Kconfig  |    4 +
>  sound/soc/codecs/Makefile |    2 +
>  sound/soc/codecs/tlv320.c |  603 +++++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/codecs/tlv320.h |  111 +++++++++
>  5 files changed, 721 insertions(+), 0 deletions(-)
>  create mode 100644 sound/soc/codecs/tlv320.c
>  create mode 100644 sound/soc/codecs/tlv320.h
>
> diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h
> index 5ced329..f6a1ddf 100644
> --- a/include/linux/i2c-id.h
> +++ b/include/linux/i2c-id.h
> @@ -122,6 +122,7 @@
>  #define I2C_DRIVERID_VP27SMPX	93	/* Panasonic VP27s tuner internal MPX */
>  #define I2C_DRIVERID_CS4270	94	/* Cirrus Logic 4270 audio codec */
>  #define I2C_DRIVERID_AK4535	95	/* AK4525 audio codec */
> +#define I2C_DRIVERID_TLV320	97	/* TLV 320 audio codec */
>  
>  #define I2C_DRIVERID_I2CDEV	900
>  #define I2C_DRIVERID_ARP        902    /* SMBus ARP Client              */
> diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
> index 8244107..2608499 100644
> --- a/sound/soc/codecs/Kconfig
> +++ b/sound/soc/codecs/Kconfig
> @@ -6,6 +6,10 @@ config SND_SOC_AK4535
>  	tristate
>  	depends on SND_SOC
>  
> +config SND_SOC_TLV320
> +	tristate
> +	depends on SND_SOC
> +
>  config SND_SOC_WM8731
>  	tristate
>  	depends on SND_SOC
> diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
> index 6859145..3cce68b 100644
> --- a/sound/soc/codecs/Makefile
> +++ b/sound/soc/codecs/Makefile
> @@ -1,5 +1,6 @@
>  snd-soc-ac97-objs := ac97.o
>  snd-soc-ak4535-objs := ak4535.o
> +snd-soc-tlv320-objs := tlv320.o
>  snd-soc-wm8731-objs := wm8731.o
>  snd-soc-wm8750-objs := wm8750.o
>  snd-soc-wm8753-objs := wm8753.o
> @@ -9,6 +10,7 @@ snd-soc-cs4270-objs := cs4270.o
>  
>  obj-$(CONFIG_SND_SOC_AC97_CODEC)	+= snd-soc-ac97.o
>  obj-$(CONFIG_SND_SOC_AK4535)	+= snd-soc-ak4535.o
> +obj-$(CONFIG_SND_SOC_TLV320)	+= snd-soc-tlv320.o
>  obj-$(CONFIG_SND_SOC_WM8731)	+= snd-soc-wm8731.o
>  obj-$(CONFIG_SND_SOC_WM8750)	+= snd-soc-wm8750.o
>  obj-$(CONFIG_SND_SOC_WM8753)	+= snd-soc-wm8753.o
> diff --git a/sound/soc/codecs/tlv320.c b/sound/soc/codecs/tlv320.c
> new file mode 100644
> index 0000000..58c7c3e
> --- /dev/null
> +++ b/sound/soc/codecs/tlv320.c
> @@ -0,0 +1,603 @@
> +/*
> + * tlv320.c  --  TLV 320 ALSA Soc Audio driver
> + *
> + * Copyright 2005 Wolfson Microelectronics PLC.
> + * Copyright 2006 Atlab srl.
> + *
> + * Authors: Liam Girdwood <liam.girdwood at wolfsonmicro.com>
> + *          Nicola Perrino <nicola.perrino at atlab.it>
> + *
> + *  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/version.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/delay.h>
> +#include <linux/pm.h>
> +#include <linux/i2c.h>
> +#include <linux/platform_device.h>
> +#include <sound/driver.h>
> +#include <sound/core.h>
> +#include <sound/pcm.h>
> +#include <sound/pcm_params.h>
> +#include <sound/soc.h>
> +#include <sound/soc-dapm.h>
> +#include <sound/initval.h>
> +
> +#include "tlv320.h"
> +
> +#define AUDIO_NAME "tlv320"
> +#define TLV320_VERSION "0.1"
> +
> +/*
> + * Debug
> + */
> +
> +//#define TLV320_DEBUG 0
> +
> +#ifdef TLV320_DEBUG
> +#define dbg(format, arg...) \
> +	printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg)
> +#else
> +#define dbg(format, arg...) do {} while (0)
> +#endif
> +#define err(format, arg...) \
> +	printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg)
> +#define info(format, arg...) \
> +	printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg)
> +#define warn(format, arg...) \
> +	printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg)
> +
> +
> +#define TLV320_VOICE_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 TLV320_VOICE_BITS \
> +	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
> +	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
> +
> +
> +static int caps_charge = 2000;
> +static int setting = 1;
> +module_param(caps_charge, int, 0);
> +module_param(setting, int, 0);
> +MODULE_PARM_DESC(caps_charge, "TLV320 cap charge time (msecs)");
> +
> +static struct workqueue_struct *tlv320_workq = NULL;
> +//static struct work_struct tlv320_dapm_work;
> +
> +/* codec private data */
> +struct tlv320_priv {
> +	unsigned int sysclk;
> +	unsigned int pcmclk;
> +};
> +
> +
> +#ifdef TLV320AIC24K
> +/* ADDR table */
> +static const unsigned char tlv320_reg_addr[] = {
> +	0x00,	/* CONTROL REG 0 No Operation */
> +	0x01,	/* CONTROL REG 1  */
> +        0x02,	/* CONTROL REG 2  */
> +        0x03,	/* CONTROL REG 3A */
> +        0x03,	/* CONTROL REG 3B */
> +        0x03, 	/* CONTROL REG 3C */
> +        0x03,	/* CONTROL REG 3D */
> +        0x04,	/* CONTROL REG 4  */
> +        0x04,	/* CONTROL REG 4 Bis */
> +        0x05,	/* CONTROL REG 5A */
> +        0x05,	/* CONTROL REG 5B */
> +        0x05,	/* CONTROL REG 5C */
> +        0x05,	/* CONTROL REG 5D */
> +        0x06,	/* CONTROL REG 6A */
> +        0x06,	/* CONTROL REG 6B */
> +};
> +
> +/*
> + *  DATA case digital SET1:
> + *     SSP -> DAC -> OUT
> + *     IN  -> ADC -> SSP
> + *     IN = HNSI (MIC)
> + *     OUT = HDSO (SPKG)
> + *  Usage: playback, capture streams
> + */
> +static const unsigned char tlv320_reg_data_init_set1[] = {
> +	0x00,	/* CONTROL REG 0 No Operation */
> +	0x49,	/* CONTROL REG 1  */
> +        0x20,	/* CONTROL REG 2  */
> +        0x01,	/* CONTROL REG 3A */
> +        0x40,	/* CONTROL REG 3B */
> +        0x81,	/* CONTROL REG 3C */
> +        0xc0,	/* CONTROL REG 3D */
> +        0x02,//0x42(16khz),//0x10,	/* CONTROL REG 4  */
> +        0x88,//0x90,	/* CONTROL REG 4 Bis */
> +        0x00,	/* CONTROL REG 5A */
> +        0x40,//(0dB)	/* CONTROL REG 5B */
> +        0xbf,	/* CONTROL REG 5C */
> +        0xc0,	/* CONTROL REG 5D */
> +        0x02,//(HNSI) 	/* CONTROL REG 6A */
> +        0x81 //(HDSO) 	/* CONTROL REG 6B */
> +};
> +
> +/*
> + *  DATA case digital SET2:
> + *     SSP -> DAC -> OUT
> + *     IN  -> ADC -> SSP
> + *     IN = HDSI (PHONE IN)
> + *     OUT = HNSO (PHONE OUT)
> + *  Usage: playback, capture streams
> + */
> +static const unsigned char tlv320_reg_data_init_set2[] = {
> +	0x00,	/* CONTROL REG 0 No Operation */
> +	0x49,	/* CONTROL REG 1  */
> +        0x20,	/* CONTROL REG 2  */
> +        0x01,	/* CONTROL REG 3A */
> +        0x40,	/* CONTROL REG 3B */
> +        0x81,	/* CONTROL REG 3C */
> +        0xc0,	/* CONTROL REG 3D */
> +        0x02,//0x42(16khz),//0x10,	/* CONTROL REG 4  */
> +        0x88,//0x90,	/* CONTROL REG 4 Bis */
> +        0x00,	/* CONTROL REG 5A */
> +        0x52,//(-27dB) 	/* CONTROL REG 5B */
> +        0xbf,	/* CONTROL REG 5C */
> +        0xc0,	/* CONTROL REG 5D */
> +        0x01,//(PHONE IN) 	/* CONTROL REG 6A */
> +        0x82 //(PHONE OUT) 	/* CONTROL REG 6B */
> +};
> +
> +/*
> + *  DATA case analog:
> + *     ADC, DAC, SSP off
> + *     Headset input to output (HDSI2O -> 1)
> + *     Handset input to output (HNSI2O -> 1)
> + *  Usage: room monitor
> + */
> +static const unsigned char tlv320_reg_data_init_set3[] = {
> +	0x00,	/* CONTROL REG 0 No Operation */
> +	0x08,   /* CONTROL REG 1  */
> +        0x20,   /* CONTROL REG 2  */
> +        0x11,   /* CONTROL REG 3A */
> +        0x40,	/* CONTROL REG 3B */
> +        0x80,   /* CONTROL REG 3C */
> +        0xc0,   /* CONTROL REG 3D */
> +        0x00,   /* CONTROL REG 4  */
> +        0x00,   /* CONTROL REG 5A */
> +        0x40,   /* CONTROL REG 5B */
> +        0x80,   /* CONTROL REG 5C */
> +        0xc0,   /* CONTROL REG 5D */
> +        0x60,   /* CONTROL REG 6A */
> +        0x80    /* CONTROL REG 6B */
> +};
> +
> +#else // TLV320AIC14k
> +
> +/* ADDR table */
> +static const unsigned char tlv320_reg_addr[] = {
> +	0x00,	/* CONTROL REG 0 No Operation */
> +	0x01,	/* CONTROL REG 1  */
> +        0x02,	/* CONTROL REG 2  */
> +        0x03,	/* CONTROL REG 3 */
> +        0x04,	/* CONTROL REG 4  */
> +        0x04,	/* CONTROL REG 4 Bis */
> +        0x05,	/* CONTROL REG 5A */
> +        0x05,	/* CONTROL REG 5B */
> +        0x05,	/* CONTROL REG 5C */
> +        0x05,	/* CONTROL REG 5D */
> +        0x06	/* CONTROL REG 6 */
> +};
> +
> +/*
> + *  DATA case digital:
> + *     SSP -> DAC -> OUT
> + *     IN  -> ADC -> SSP
> + *  Usage: playback, capture streams
> + */
> +static const unsigned char tlv320_reg_data_init_set1[] = {
> +	0x00,	/* CONTROL REG 0 No Operation */
> +	0x41,	/* CONTROL REG 1  */
> +        0x20,	/* CONTROL REG 2  */
> +        0x09,	/* CONTROL REG 3 */
> +        0x02,//0x42(16khz),//0x10,	/* CONTROL REG 4  */
> +        0x88,//0x90,	/* CONTROL REG 4 Bis */
> +        0x2A,	/* CONTROL REG 5A */
> +        0x6A,	/* CONTROL REG 5B */
> +        0xbc,	/* CONTROL REG 5C */
> +        0xc0,	/* CONTROL REG 5D */
> +        0x00	/* CONTROL REG 6 */
> +};
> +#endif
> +/*
> + * read tlv320 register cache
> + */
> +static inline unsigned int tlv320_read_reg_cache(struct snd_soc_codec *codec,
> +	unsigned int reg)
> +{
> +	u8 *cache = codec->reg_cache;
> +	if (reg > ARRAY_SIZE(tlv320_reg_addr))
> +		return -1;
> +	return cache[reg];
> +}
> +
> +/*
> + * write tlv320 register cache
> + */
> +static inline void tlv320_write_reg_cache(struct snd_soc_codec *codec,
> +	unsigned int reg, unsigned int value)
> +{
> +	u8 *cache = codec->reg_cache;
> +	if (reg > ARRAY_SIZE(tlv320_reg_addr))
> +		return;
> +	cache[reg] = value;
> +}
> +
> +/*
> + * read tlv320
> + */
> +static int tlv320_read (struct snd_soc_codec *codec, u8 reg)
> +{
> +	return i2c_smbus_read_byte_data(codec->control_data, reg);
> +}
> +
> +/*
> + * write tlv320
> + */
> +static int tlv320_write(struct snd_soc_codec *codec, unsigned int reg,
> +	unsigned int value)
> +{
> +	if (tlv320_reg_addr[reg] > 0x06)
> +		return -1;
> +
> +	tlv320_write_reg_cache (codec, reg, value);
> +
> +	return i2c_smbus_write_byte_data(codec->control_data, tlv320_reg_addr[reg], value);
> +}
> +
> +
> +/*
> + * write block tlv320
> + */
> +static int tlv320_write_block (struct snd_soc_codec *codec,
> +	const u8 *data, unsigned int len)
> +{
> +	int ret = -1;
> +	int i;
> +
> +	for (i=0; i<len; i++) {
> +		dbg("addr = 0x%02x, data = 0x%02x", tlv320_reg_addr[i], data[i]);
> +		if ((ret = tlv320_write(codec, i, data[i])) < 0)
> +			break;
> +	}
> +
> +	return ret;
> +}
> +
> +
> +static int tlv320_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
> +		unsigned int fmt)
> +{
> +	dbg("tlv320_set_dai_fmt enter");
> +	return 0;
> +}
> +
> +/*
> + * Set PCM DAI bit size and sample rate.
> + */
> +static int tlv320_pcm_hw_params(struct snd_pcm_substream *substream,
> +	struct snd_pcm_hw_params *params)
> +{
> +	dbg("tlv320_pcm_hw_params enter");
> +	return 0;
> +}
> +
> +
> +static int tlv320_config_pcm_sysclk(struct snd_soc_codec_dai *codec_dai,
> +		int clk_id, unsigned int freq, int dir)
> +{
> +	dbg("tlv320_config_pcm_sysclk enter");
> +	return 0;
> +}
> +
> +
> +/*
> + *  Voice over PCM DAI
> + */
> +struct snd_soc_codec_dai tlv320_dai[] = {
> +{	.name = "TLV320 Voice",
> +	.id = 1,
> +	.playback = {
> +		.stream_name = "Voice Playback",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		.rates = TLV320_VOICE_RATES,
> +		.formats = TLV320_VOICE_BITS,},
> +	.capture = {
> +		.stream_name = "Voice Capture",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		.rates = TLV320_VOICE_RATES,
> +		.formats = TLV320_VOICE_BITS,},
> +	.ops = {
> +		.hw_params = tlv320_pcm_hw_params,},
> +	.dai_ops = {
> +		.digital_mute = NULL,
> +		.set_fmt = tlv320_set_dai_fmt,
> +		.set_clkdiv = NULL,
> +		.set_pll = NULL,
> +		.set_sysclk = tlv320_config_pcm_sysclk,
> +	},
> +},
> +
> +
> +};
> +EXPORT_SYMBOL_GPL(tlv320_dai);
> +
> +
> +static void tlv320_work(struct work_struct *work)
> +{
> +#if 0
> +	struct snd_soc_codec *codec =
> +		container_of(work, struct snd_soc_codec, delayed_work.work);
> +	//wm8753_dapm_event(codec, codec->dapm_state);
> +#endif
> +}
> +
> +/*
> + * initialise the TLV320 driver
> + * register the mixer and dsp interfaces with the kernel
> + */
> +static int tlv320_init(struct snd_soc_device *socdev)
> +{
> +	struct snd_soc_codec *codec = socdev->codec;
> +	int ret = 0;
> +
> +	codec->name = "TLV320";
> +	codec->owner = THIS_MODULE;
> +	codec->read = tlv320_read_reg_cache;
> +	codec->write = tlv320_write;
> +	codec->dai = tlv320_dai;
> +	codec->num_dai = ARRAY_SIZE(tlv320_dai);
> +	codec->reg_cache_size = sizeof(tlv320_reg_addr);
> +
> +	codec->reg_cache =
> +		kmemdup(tlv320_reg_addr, sizeof(tlv320_reg_addr), GFP_KERNEL);
> +	if (codec->reg_cache == NULL)
> +		return -ENOMEM;
> +
> +	/* register pcms */
> +	ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
> +	if (ret < 0) {
> +		kfree(codec->reg_cache);
> +		return ret;
> +	}
> +
> +	queue_delayed_work(tlv320_workq,
> +		&codec->delayed_work, msecs_to_jiffies(caps_charge));
> +
> +	ret = snd_soc_register_card(socdev);
> +	if (ret < 0) {
> +		snd_soc_free_pcms(socdev);
> +		snd_soc_dapm_free(socdev);
> +	}
> +
> +	return ret;
> +}
> +
> +/* If the i2c layer weren't so broken, we could pass this kind of data
> +   around */
> +static struct snd_soc_device *tlv320_socdev;
> +
> +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
> +
> +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
> +
> +/* Magic definition of all other variables and things */
> +I2C_CLIENT_INSMOD;
> +
> +static struct i2c_driver tlv320_i2c_driver;
> +static struct i2c_client client_template;
> +
> +static int tlv320_codec_probe(struct i2c_adapter *adap, int addr, int kind)
> +{
> +	struct snd_soc_device *socdev = tlv320_socdev;
> +	struct tlv320_setup_data *setup = socdev->codec_data;
> +	struct snd_soc_codec *codec = socdev->codec;
> +	struct i2c_client *i2c;
> +	int ret, len;
> +        const unsigned char *data;
> +
> +	if (addr != setup->i2c_address)
> +		return -ENODEV;
> +
> +	client_template.adapter = adap;
> +	client_template.addr = addr;
> +
> +	i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL);
> +	if (i2c == NULL){
> +		kfree(codec);
> +		return -ENOMEM;
> +	}
> +	i2c_set_clientdata(i2c, codec);
> +	codec->control_data = i2c;
> +
> +	ret = i2c_attach_client(i2c);
> +	if (ret < 0) {
> +		err("failed to attach codec at addr %x\n", addr);
> +		goto err;
> +	}
> +
> +	ret = tlv320_init(socdev);
> +	if (ret < 0) {
> +		err("failed to initialise TLV320\n");
> +		goto err;
> +	}
> +
> +	switch(setting) {
> +	case 1:
> +		data = tlv320_reg_data_init_set1;
> +		len = sizeof(tlv320_reg_data_init_set1);
> +		break;
> +	case 2:
> +		data = tlv320_reg_data_init_set2;
> +		len = sizeof(tlv320_reg_data_init_set2);
> +		break;
> +	case 3:
> +		data = tlv320_reg_data_init_set3;
> +		len = sizeof(tlv320_reg_data_init_set3);
> +		break;
> +	default:
> +		data = tlv320_reg_data_init_set1;
> +		len = sizeof(tlv320_reg_data_init_set1);
> +		break;
> +	}
> +
> +	ret = tlv320_write_block(codec, data, len);
> +
> +	if (ret < 0) {
> +		err("attach error: init status %d\n", ret);
> +	} else {
> +		info("attach: chip tlv320 at address 0x%02x",
> +			tlv320_read(codec, 0x02) << 1);
> +	}
> +
> +        //tlv320_write(codec, CODEC_REG6B, 0x80);
> +#if 0
> +	int value;
> +	int i;
> +
> +	for (i=0; i<len; i++) {
> +		value = tlv320_read(codec, tlv320_reg_addr[i]);
> +		dbg("read addr = 0x%02x, data = 0x%02x", tlv320_reg_addr[i], value);
> +		mdelay(10);
> +	}
> +
> +#endif
> +
> +
> +	return ret;
> +
> +err:
> +	kfree(codec);
> +	kfree(i2c);
> +	return ret;
> +}
> +
> +static int tlv320_i2c_detach(struct i2c_client *client)
> +{
> +	struct snd_soc_codec *codec = i2c_get_clientdata(client);
> +	i2c_detach_client(client);
> +	kfree(codec->reg_cache);
> +	kfree(client);
> +	return 0;
> +}
> +
> +static int tlv320_i2c_attach(struct i2c_adapter *adap)
> +{
> +	return i2c_probe(adap, &addr_data, tlv320_codec_probe);
> +}
> +
> +/* tlv320 i2c codec control layer */
> +static struct i2c_driver tlv320_i2c_driver = {
> +	.driver = {
> +		.name = "tlv320 I2C Codec",
> +		.owner = THIS_MODULE,
> +	},
> +	.id =             I2C_DRIVERID_TLV320,
> +	.attach_adapter = tlv320_i2c_attach,
> +	.detach_client =  tlv320_i2c_detach,
> +	.command =        NULL,
> +};
> +
> +static struct i2c_client client_template = {
> +	.name =   "tlv320",
> +	.driver = &tlv320_i2c_driver,
> +};
> +#endif
> +
> +static int tlv320_probe(struct platform_device *pdev)
> +{
> +	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
> +	struct tlv320_setup_data *setup;
> +	struct snd_soc_codec *codec;
> +	int ret = 0;
> +	struct tlv320_priv *tlv320;
> +
> +	info("TLV320 Audio Codec %s", TLV320_VERSION);
> +
> +	setup = socdev->codec_data;
> +	codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
> +	if (codec == NULL)
> +		return -ENOMEM;
> +
> +	tlv320 = kzalloc(sizeof(struct tlv320_priv), GFP_KERNEL);
> +	if (tlv320 == NULL) {
> +		kfree(codec);
> +		return -ENOMEM;
> +	}
> +
> +	codec->private_data = tlv320;
> +
> +	socdev->codec = codec;
> +	mutex_init(&codec->mutex);
> +	INIT_LIST_HEAD(&codec->dapm_widgets);
> +	INIT_LIST_HEAD(&codec->dapm_paths);
> +	tlv320_socdev = socdev;
> +
> +	INIT_DELAYED_WORK(&codec->delayed_work, tlv320_work);
> +	tlv320_workq = create_workqueue("tlv320");
> +	if (tlv320_workq == NULL) {
> +		kfree(codec);
> +		return -ENOMEM;
> +	}
> +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
> +	if (setup->i2c_address) {
> +		normal_i2c[0] = setup->i2c_address;
> +		codec->hw_write = (hw_write_t)i2c_master_send;
> +		ret = i2c_add_driver(&tlv320_i2c_driver);
> +		if (ret != 0)
> +			printk(KERN_ERR "can't add i2c driver");
> +	}
> +#else
> +		/* Add other interfaces here */
> +#endif
> +	return ret;
> +}
> +
> +/* power down chip */
> +static int tlv320_remove(struct platform_device *pdev)
> +{
> +	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
> +	struct snd_soc_codec *codec = socdev->codec;
> +
> +	if (tlv320_workq)
> +		destroy_workqueue(tlv320_workq);
> +	snd_soc_free_pcms(socdev);
> +	snd_soc_dapm_free(socdev);
> +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
> +	i2c_del_driver(&tlv320_i2c_driver);
> +#endif
> +	kfree(codec->private_data);
> +	kfree(codec);
> +
> +	return 0;
> +}
> +
> +struct snd_soc_codec_device soc_codec_dev_tlv320 = {
> +	.probe = 	tlv320_probe,
> +	.remove = 	tlv320_remove,
> +};
> +
> +EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320);
> +
> +MODULE_DESCRIPTION("ASoC TLV320 driver");
> +MODULE_AUTHOR("Nicola Perrino");
> +MODULE_LICENSE("GPL");
> diff --git a/sound/soc/codecs/tlv320.h b/sound/soc/codecs/tlv320.h
> new file mode 100644
> index 0000000..bbcd53d
> --- /dev/null
> +++ b/sound/soc/codecs/tlv320.h
> @@ -0,0 +1,111 @@
> +/*
> + * tlv320.h  --  TLV 320 ALSA Soc Audio driver
> + *
> + * Copyright 2005 Wolfson Microelectronics PLC.
> + * Copyright 2006 Atlab srl.
> + *
> + * Authors: Liam Girdwood <liam.girdwood at wolfsonmicro.com>
> + *          Nicola Perrino <nicola.perrino at atlab.it>
> + *
> + *  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 _TLV320_H
> +#define _TLV320_H
> +
> +#define TLV320AIC24K
> +
> +
> +/* TLV320 register space */
> +#define CODEC_NOOP	0x00
> +#define CODEC_REG1	0x01
> +#define CODEC_REG2	0x02
> +#define CODEC_REG3A	0x03
> +#define CODEC_REG3B	0x04
> +#define CODEC_REG3C	0x05
> +#define CODEC_REG3D	0x06
> +#define CODEC_REG4A	0x07
> +#define CODEC_REG4B	0x08
> +#define CODEC_REG5A	0x09
> +#define CODEC_REG5B	0x0a
> +#define CODEC_REG5C	0x0b
> +#define CODEC_REG5D	0x0c
> +#define CODEC_REG6A	0x0d
> +#define CODEC_REG6B	0x0e
> +
> +
> +//	Control Register 1
> +#define REG1_CONTINUOUS			0x40
> +#define REG1_IIR_EN			0x20
> +#define REG1_MIC_BIAS_235		0x08
> +#define REG1_ANALOG_LOOP_BACK		0x04
> +#define REG1_DIGITAL_LOOP_BACK		0x02
> +#define REG1_DAC16			0x01
> +
> +//	Control Register 2
> +#define REG2_TURBO_EN			0x80
> +#define REG2_FIR_BYPASS			0x40
> +#define REG2_GPIO			0x02
> +#define REG2_GPIO_1			0x06
> +
> +//	Control Register 3A
> +#define REG3_PWDN_ALL			0x30
> +#define REG3_PWDN_ADC			0x10
> +#define REG3_PWDN_DAC			0x20
> +#define REG3_SW_RESET			0x08
> +#define REG3_SAMPLING_FACTOR1		0x01
> +#define REG3_SAMPLING_FACTOR2		0x02
> +
> +//	Control Register 3B
> +#define REG3_8KBP_EN			0x60
> +#define REG3_MUTE_OUTP1			0x42
> +#define REG3_MUTE_OUTP2			0x48
> +#define REG3_MUTE_OUTP3			0x44
> +
> +//	Control Register 4
> +#define REG4_FSDIV_M			0x85	//M=5
> +#define REG4_FSDIV_NP			0x08	//N=1, P=8
> +//#define REG4_FSDIV_NP			0x01	//N=1, P=8
> +#define REG4_FSDIV_NP1			0x02	//N=16, P=2
> +
> +//	Control Register 5
> +#define REG5A_ADC_GAIN			0x02	//3dB
> +#define REG5A_ADC_MUTE			0x0f	//Mute
> +#define REG5B_DAC_GAIN			0x42	//-3dB
> +#define REG5B_DAC_MUTE			0x4f	//Mute
> +#define REG5C_SIDETONE_MUTE		0xBF
> +
> +//	Control Register 6
> +#define REG6A_AIC24A_CH1_IN		0x08	//INP1 to ADC
> +#define REG6B_AIC24A_CH1_OUT		0x82	//OUTP2 to DAC
> +#define REG6A_AIC24A_CH2_IN		0x02	//INP2 to ADC
> +#define REG6B_AIC24A_CH2_OUT		0x81	//OUTP3 to DAC
> +
> +/* clock inputs */
> +#define TLV320_MCLK		0
> +#define TLV320_PCMCLK		1
> +
> +
> +struct tlv320_setup_data {
> +	unsigned short i2c_address;
> +};
> +
> +/* DAI ifmodes */
> +/* mode 1 IFMODE = 00 */
> +#define TLV320_DAI_MODE1_VOICE	0
> +#define TLV320_DAI_MODE1_HIFI	1
> +/* mode 2 IFMODE = 01 */
> +#define TLV320_DAI_MODE2_VOICE	2
> +/* mode 3 IFMODE = 10 */
> +#define TLV320_DAI_MODE3_HIFI	3
> +/* mode 4 IFMODE = 11 */
> +#define TLV320_DAI_MODE4_HIFI	4
> +
> +extern struct snd_soc_codec_dai tlv320_dai[5];
> +extern struct snd_soc_codec_device soc_codec_dev_tlv320;
> +
> +#endif
>   



More information about the Alsa-devel mailing list