[alsa-devel] [PATCH] ASoC: Add National Semiconductor LM49352 Codec Support
Reddy, MR Swami
MR.Swami.Reddy at nsc.com
Wed Mar 16 08:38:57 CET 2011
From: M R Swami Reddy <MR.Swami.Reddy at nsc.com>
Patch for adding National Semiconductor LM49352 codec support.
Signed-off-by: M R Swami Reddy <MR.Swami.Reddy at nsc.com>
---
Tested this patch on SMDK6410 platform with Kernel- 2.6.24 and ALSA version is - 1.0.15.
Please review and let me know the comments/suggestion on this patch.
And also let me know the forward-porting (to the latest ALSA version APIs) steps/process. Thanks in advance.
diff -upNr -X linux-2.6.24-lm49352/Documentation/dontdiff linux-2.6.24/sound/soc/codecs/Kconfig linux-2.6.24-lm49352/sound/soc/codecs/Kconfig
--- linux-2.6.24/sound/soc/codecs/Kconfig 2011-03-14 14:36:03.000000000 +0530
+++ linux-2.6.24-lm49352/sound/soc/codecs/Kconfig 2010-03-27 11:33:10.000000000 +0530
@@ -2,6 +2,10 @@ config SND_SOC_AC97_CODEC
tristate
depends on SND_SOC
+config SND_SOC_LM49352
+ tristate
+ depends on SND_SOC
+
config SND_SOC_WM8731
tristate
depends on SND_SOC
diff -upNr -X linux-2.6.24-lm49352/Documentation/dontdiff linux-2.6.24/sound/soc/codecs/lm49352.c linux-2.6.24-lm49352/sound/soc/codecs/lm49352.c
--- linux-2.6.24/sound/soc/codecs/lm49352.c 1970-01-01 05:30:00.000000000 +0530
+++ linux-2.6.24-lm49352/sound/soc/codecs/lm49352.c 2011-03-14 13:03:00.000000000 +0530
@@ -0,0 +1,834 @@
+/*
+ * Copyright (c) <2011> National Semiconductor, Inc.
+ *
+ * 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.
+*/
+
+#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/tlv.h>
+#include <sound/initval.h>
+#include <asm/div64.h>
+
+#include "lm49352.h"
+
+#define AUDIO_NAME "lm49352"
+#define LM49352_VERSION "0.1"
+int lm49352_write1(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value);
+/*******
+ * Debug
+ *******/
+
+static int attach_once;
+
+#ifdef LM49352_DEBUG
+#define dbg(format, arg...) \
+ printk(KERN_DEBUG AUDIO_NAME ": " format , ## arg)
+#else
+#define dbg(format, arg...) do {} while (0)
+#endif
+#define err(format, arg...) \
+ printk(KERN_ERR AUDIO_NAME ": " format , ## arg)
+#define info(format, arg...) \
+ printk(KERN_INFO AUDIO_NAME ": " format , ## arg)
+#define warn(format, arg...) \
+ printk(KERN_WARNING AUDIO_NAME ": " format , ## arg)
+
+/* Register cache for LM49352 driver. */
+static const u16 lm49352_reg[] = {
+ 0x0121, 0x017e, 0x007d, 0x0014, /*R3*/
+ 0x0121, 0x017e, 0x007d, 0x0194, /*R7*/
+ 0x001c, 0x0002, 0x0002, 0x00c2, /*R11*/
+ 0x0182, 0x0082, 0x000a, 0x0024, /*R15*/
+ 0x0009, 0x0000, 0x00ff, 0x0000, /*R19*/
+ 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R23*/
+ 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R27*/
+ 0x01f0, 0x0040, 0x0000, 0x0000, /*R31(0x1F)*/
+ 0x0000, 0x0000, 0x0031, 0x000b, /*R35*/
+ 0x0039, 0x0000, 0x0010, 0x0032, /*R39*/
+ 0x0054, 0x0076, 0x0098, 0x0000, /*R43(0x2B)*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R47*/
+ 0x0000, 0x0000, 0x005e, 0x003e, /*R51(0x33)*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R55*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R59*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R63*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R67*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R71*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R75*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R79*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R83*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R87*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R91*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R95*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R99*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R103*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R107*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R111*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R115*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R119*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R123*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R127*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R131*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R135*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R139*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R143*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R147*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R151*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R155*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R159*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R163*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R167*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R171*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R175*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R179*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R183*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R187*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R191*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R193*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R197*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R201*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R205*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R209*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R213*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R217*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R221*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R225*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R229*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R233*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R237*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R241*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R245*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R249*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R253*/
+ 0x0000, 0x0000, /*R255*/
+};
+
+/***************************************************************************
+ * Name:
+ * lm49352_read_reg_cache- Reads specific register from reg cache.
+ *
+ * Synopsis:
+ * static inline unsigned int
+ * lm49352_read_reg_cache(struct snd_soc_codec *codec,unsigned int reg)
+ *
+ * Arguments:
+ * codec- Pointer to the struct snd_soc_codec
+ * reg- Specific register address
+ *
+ * Decription:
+ * In this function a specific register value is read from reg cache
+ * and value is returned to the called function.
+ **************************************************************************/
+
+static inline unsigned int
+lm49352_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg)
+{
+ dbg("in lm49352_read_reg_cache \n");
+ u16 *cache = codec->reg_cache;
+ BUG_ON(reg > ARRAY_SIZE(lm49352_reg));
+ dbg("in lm49352_read_reg_cache %x\n", cache[reg]);
+ return cache[reg];
+}
+/**************************************************************************
+ * Name
+ * lm49352_write_reg_cache- Writes specific register value to reg
+ * cache.
+ * Synopsis
+ * static inline void lm49352_write_reg_cache(struct
+ * snd_soc_codec *codec,unsigned int reg, unsigned int value)
+ * Arguments
+ * codec- Pointer to the struct snd_soc_codec
+ * reg- Specific register address
+ * value- The value that to be written to reg cache
+ * Decription
+ * This function is called when a specific register is to be written
+ * with the given value. This function writes the value to reg cache.
+ *************************************************************************/
+
+static inline void lm49352_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg,
+ unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ dbg("in lm49352_write_reg_cache \n");
+ dbg("value to be written is %x\n", value);
+ cache[reg] = value;
+}
+
+/**************************************************************************
+ * Name
+ * lm49352_write- Writes specific value to register and also to reg
+ * cache.
+ * Synopsis
+ * static int lm49352_write(struct snd_soc_codec *codec,
+ * unsigned int reg,unsigned int value)
+ * Arguments
+ * codec- Pointer to the struct snd_soc_codec
+ * reg- Specific register address
+ * value- The value that to be written to cache and also to given
+ * register
+ * Decription
+ * This function is called when a specific register is to be written
+ * with the given value. This function writes the value to the given
+ * register and also to reg cache.
+ *************************************************************************/
+
+static int lm49352_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+ int wrreg;
+ int wrvalue, tmp;
+ wrreg = reg;
+ wrvalue = value;
+ dbg("wrvalue %x\n", wrvalue);
+
+ /* For mute operation. */
+ if (wrreg == DAC_MUTE) {
+ /* Value read from register.*/
+ tmp = i2c_smbus_read_byte_data(codec->control_data, wrreg);
+
+ if (wrvalue) {
+ dbg("wrvalue %x\n", wrvalue);
+ tmp |= LM49352_MUTE;
+ } else {
+ dbg("wrvalue %x\n", wrvalue);
+ tmp &= ~LM49352_MUTE;
+ }
+ wrvalue = tmp;
+ }
+
+ /* New value written to LM49352 register.*/
+ i2c_smbus_write_byte_data(codec->control_data, wrreg, wrvalue);
+ dbg("%s,%d : reg : 0x%x, value : 0x%x\n", __FUNCTION__, __LINE__,
+ reg, value);
+ dbg("reg=%x value=%x \n", reg, value);
+ BUG_ON(reg > ARRAY_SIZE(lm49352_reg));
+
+ value &= 0x1ff;
+ dbg("reg value is %x\n", value);
+
+ if (value == lm49352_read_reg_cache(codec, reg))
+ return 0;
+
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+ dbg("data[0]=%x data[1]=%x \n", data[0], data[1]);
+ lm49352_write_reg_cache(codec, reg, value);
+
+ if (codec->hw_write(codec->control_data, data, 2) == 2) {
+ dbg("i2c write successful\n");
+ return 0;
+ } else {
+ dbg("i2c write failed\n");
+ return -EIO;
+ }
+
+
+}
+/**************************************************************************
+ * Name
+ * lm49352_read- Reads specific register value from reg cache.
+ * Synopsis
+ * static inline unsigned int lm49352_read(struct snd_soc_codec *codec,
+ * Arguments
+ * codec- Pointer to the struct snd_soc_codec
+ * reg- Specific register address
+ * Decription
+ * This function is called when a specific register value is to be
+ * read from reg cache.
+ *************************************************************************/
+
+static inline unsigned int lm49352_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return lm49352_read_reg_cache(codec, reg);
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+
+/**************************************************************************
+ * Name
+ * lm49352_out_vu- Sets the value of a single mixer control.
+ * Synopsis
+ * static int lm49352_out_vu(struct snd_kcontrol *kcontrol,
+ * Arguments
+ * kcontrol- Pointer to the struct snd_kcontrol
+ * ucontrol- Pointer to struct snd_ctl_elem_value
+ * Decription
+ * This function is called when a single mixer control value is
+ * to be set.
+ *************************************************************************/
+
+static int lm49352_out_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int ret;
+ u16 val;
+
+ /* Resetting the reg cache value. */
+ lm49352_write_reg_cache(codec, reg, 0);
+
+ /* Callback to set the value of a single mixer control. */
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+
+ if (ret < 0)
+ return ret;
+
+ /* Reading the written new value. */
+ val = lm49352_read_reg_cache(codec, reg);
+ /* Oring the value with 0x100 and writing back to reg cavhe. */
+ return lm49352_write(codec, reg, val | 0x0100);
+}
+
+/* Macro for defining and adding single mixer controls. */
+#define SOC_LM49352_OUT_SINGLE_R_TLV(xname, reg, shift, max, invert, tlv_array)\
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ \
+ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_get_volsw, .put = lm49352_out_vu, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+static const struct snd_kcontrol_new lm49352_snd_controls[] =
+{ /* Various controls of LM49352 Driver */
+ SOC_LM49352_OUT_SINGLE_R_TLV("Left DAC1 Playback Volume",
+ LM49352_DIGITAL_ATTENUATION_DACL1, 0, 0x3f, 0, dac_tlv),
+ SOC_LM49352_OUT_SINGLE_R_TLV("Right DAC1 Playback Volume",
+ LM49352_DIGITAL_ATTENUATION_DACR1, 0, 0x3f, 0, dac_tlv),
+ SOC_LM49352_OUT_SINGLE_R_TLV("Headphone Playback Volume",
+ ANALOG_MIXER_OUTPUT_OPTIONS, 1, 7, 1, dac_tlv),
+ SOC_LM49352_OUT_SINGLE_R_TLV("Aux Playback Volume",
+ ANALOG_MIXER_OUTPUT_OPTIONS, 4, 1, 0, dac_tlv),
+ SOC_LM49352_OUT_SINGLE_R_TLV("PC Speaker Playback Volume",
+ ANALOG_MIXER_OUTPUT_OPTIONS, 6, 3, 0, dac_tlv),
+ SOC_SINGLE("DAC1 Deemphasis Switch", LM49352_DAC_CONTROL3, 2, 1, 0),
+ SOC_SINGLE("DAC1 Left Invert Switch", LM49352_DAC_CONTROL4, 0, 1, 0),
+ SOC_SINGLE("DAC1 Right Invert Switch", LM49352_DAC_CONTROL4, 1, 1, 0),
+ SOC_SINGLE("DAC ZC Switch", DAC_BASIC, 5, 1, 0),
+ SOC_SINGLE("DAC Mute Switch", DAC_MUTE, 2, 1, 0),
+ SOC_SINGLE("ADCL Mute Switch", LM49352_ADC_CONTROL1, 2, 1, 0),
+ SOC_SINGLE("ADCR Mute Switch", LM49352_ADC_CONTROL1, 3, 1, 0),
+};
+
+/**************************************************************************
+ * Name
+ * lm49352_add_controls- Adds the controls which are defined in
+ * lm49352_snd_controls struct.
+ * Synopsis
+ * static int lm49352_add_controls(struct snd_soc_codec *codec)
+ * Arguments
+ * codec- Pointer to the struct snd_soc_codec
+ * Decription
+ * This function is called when setting mixer controls and it adds
+ * the controls defined in lm49352_snd_controls.
+ *************************************************************************/
+
+static int lm49352_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(lm49352_snd_controls); i++) {
+ /* Adding the declared controls.*/
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&lm49352_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/**************************************************************************
+ * Name
+ * lm49352_hw_params- Reads specific control information
+ * Synopsis
+ * static int lm49352_hw_params(struct snd_pcm_substream *substream,
+ * struct snd_pcm_hw_params *params)
+ * Arguments
+ * substream- Pointer to the struct snd_pcm_substream
+ * params- Pointer to struct snd_pcm_hw_params
+ * Decription
+ * This function is called while playback or record is done and it
+ * sets the DAC and ADC's clock divider value based on
+ * substreams sample rates and also gives information about bit size.
+ *************************************************************************/
+
+static int lm49352_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_link *dai = rtd->dai;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ dbg("\n in lm49352_hw_params \n");
+
+
+ /* Setting the DAC and ADC clock dividers based on substream
+ sample rate. */
+ switch (params_rate(params)) {
+ case 8000:
+ lm49352_write1(codec, DAC_CLOCK, 0x17);
+ lm49352_write1(codec, ADC_CLOCK, 0x17);
+ break;
+ case 11025:
+ lm49352_write1(codec, DAC_CLOCK, 0x10);
+ lm49352_write1(codec, ADC_CLOCK, 0x10);
+ break;
+ case 16000:
+ lm49352_write1(codec, DAC_CLOCK, 0x0b);
+ lm49352_write1(codec, ADC_CLOCK, 0x0b);
+ break;
+ case 22050:
+ lm49352_write1(codec, DAC_CLOCK, 0x08);
+ lm49352_write1(codec, ADC_CLOCK, 0x08);
+ break;
+ case 32000:
+ lm49352_write1(codec, DAC_CLOCK, 0x05);
+ lm49352_write1(codec, ADC_CLOCK, 0x05);
+ break;
+ case 44100:
+ lm49352_write1(codec, DAC_CLOCK, 0x03);
+ lm49352_write1(codec, ADC_CLOCK, 0x03);
+ break;
+ case 48000:
+ lm49352_write1(codec, DAC_CLOCK, 0x03);
+ lm49352_write1(codec, ADC_CLOCK, 0x03);
+ break;
+ case 96000:
+ lm49352_write1(codec, DAC_CLOCK, 0x01);
+ break;
+ default:
+ lm49352_write1(codec, DAC_CLOCK, 0x01);
+ lm49352_write1(codec, ADC_CLOCK, 0x03);
+ break;
+ }
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dbg(KERN_CRIT "16 bit\n");
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ dbg(KERN_CRIT "20 bit\n");
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ dbg(KERN_CRIT "24 bit\n");
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ dbg(KERN_CRIT "32 bit\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**************************************************************************
+ * Name
+ * lm49352_write1- Reads specific control information
+ * Synopsis
+ * int lm49352_write1(struct snd_soc_codec *codec, unsigned int reg,
+ * unsigned int value)
+ * Arguments
+ * codec- Pointer to the struct snd_soc_codec
+ * reg- Specific register address
+ * value- The value that to be written to given register
+ * Decription
+ * This function is called when a specific register is to be written
+ * with the given value.
+ *************************************************************************/
+ int lm49352_write1(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ dbg(" reg=%x value=%x \n", reg, value);
+ /* I2C write call for LM49352 registers.*/
+ i2c_smbus_write_byte_data(codec->control_data, reg, value);
+}
+
+/* Formates supported by LM49352 driver. */
+#define LM49352_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \
+ | SNDRV_PCM_FMTBIT_S20_3LE \
+ | SNDRV_PCM_FMTBIT_S24_LE \
+ | SNDRV_PCM_FMTBIT_S32_LE)
+
+/* LM49352 dai structure. */
+struct snd_soc_codec_dai lm49352_dai[] = {
+ {
+ .name = "LM49352",
+ .id = 0,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = LM49352_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = LM49352_FORMATS,
+ },
+ .ops = {
+ .hw_params = lm49352_hw_params,
+ },
+ },
+};
+EXPORT_SYMBOL_GPL(lm49352_dai);
+
+/**************************************************************************
+ * Name
+ * lm49352_init- Initializes LM49352 sound card.
+ * Synopsis
+ * static int lm49352_init(struct snd_soc_device *socdev)
+ * Arguments
+ * socdev- Pointer to the struct snd_soc_device
+ * Decription
+ * This function initializes the LM49352 sound card and registers
+ * new PCM for it and also registers it as a new card in ALSA.
+ * This function also writes the default value to the Lm49352's
+ * registers.
+ *************************************************************************/
+static int lm49352_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+
+ codec->name = "LM49352";
+ codec->owner = THIS_MODULE;
+ codec->read = lm49352_read_reg_cache;
+ codec->write = lm49352_write;
+ codec->dai = lm49352_dai;
+ codec->num_dai = ARRAY_SIZE(lm49352_dai);
+ codec->reg_cache_size = ARRAY_SIZE(lm49352_reg);
+ codec->reg_cache = kmemdup(lm49352_reg, sizeof(lm49352_reg),
+ GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ /* register pcms */
+ /* Creating new PCM for LM49352. */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dbg(KERN_ERR "lm49352: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* Writing default values for LM49352 registers. */
+ lm49352_add_controls(codec);
+ lm49352_write1(codec, BASIC_SETUP_PMC_SETUP, 0x13);
+ lm49352_write1(codec, BASIC_SETUP_PMC_CLOCK, 0x02);
+ lm49352_write1(codec, PLL_CLK_SEL, 0x00);
+ lm49352_write1(codec, ANALOG_MIXER_HEADPHONESL, 0x02);
+ lm49352_write1(codec, ANALOG_MIXER_HEADPHONESR, 0x01);
+ lm49352_write1(codec, ANALOG_MIXER_HP_SENSE, 0x00);
+ lm49352_write1(codec, ADC_BASIC, 0x033);
+ lm49352_write1(codec, ADC_CLOCK, 0x0b);
+ lm49352_write1(codec, DAC_BASIC, 0x31);
+ lm49352_write1(codec, DAC_IP_SELECT, 0x09);
+ lm49352_write1(codec, AUDIO_PORT1_BASIC, 0x07);
+ lm49352_write1(codec, PLL_M, 0x00);
+ lm49352_write1(codec, PLL_N, 0xec);
+ lm49352_write1(codec, PLL_N_MOD, 0x14);
+ lm49352_write1(codec, PLL_P1, 0x0d);
+ lm49352_write1(codec, ANALOG_MIXER_CLASSD, 0x03);
+ lm49352_write1(codec, ANALOG_MIXER_AUX_OUT, 0x23);
+ lm49352_write1(codec, ANALOG_MIXER_AUXL_LVL, 0x2b);
+ lm49352_write1(codec, ADC_MIXER, 0x0f);
+ lm49352_write1(codec, AUDIO_PORT1_IP, 0x05);
+ lm49352_write1(codec, ADC_EFFECTS_HPF, 0x04);
+ lm49352_write1(codec, ADC_EFFECTS_ADC_ALC4, 0x0a);
+ lm49352_write1(codec, ADC_EFFECTS_ADC_ALC5, 0x0a);
+ lm49352_write1(codec, ADC_EFFECTS_ADC_ALC6, 0x0a);
+ lm49352_write1(codec, ADC_EFFECTS_ADC_ALC7, 0x1f);
+ lm49352_write1(codec, ADC_EFFECTS_ADC_L_LEVEL, 0x33);
+ lm49352_write1(codec, ADC_EFFECTS_ADC_R_LEVEL, 0x33);
+ lm49352_write1(codec, DAC_EFFECTS_DAC_ALC1, 0x02);
+ lm49352_write1(codec, DAC_EFFECTS_DAC_ALC4, 0x0a);
+ lm49352_write1(codec, DAC_EFFECTS_DAC_ALC5, 0x0a);
+ lm49352_write1(codec, DAC_EFFECTS_DAC_ALC6, 0x0a);
+ lm49352_write1(codec, DAC_EFFECTS_DAC_ALC7, 0x33);
+ lm49352_write1(codec, DAC_EFFECTS_DAC_L_LEVEL, 0x33);
+ lm49352_write1(codec, DAC_EFFECTS_DAC_R_LEVEL, 0x33);
+ lm49352_write1(codec, ANALOG_MIXER_MIC_LVL, 0x0f);
+ lm49352_write1(codec, ANALOG_MIXER_ADC, 0x0c);
+
+ /* Registering new card instance for LM49352 sound card. */
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ dbg(KERN_ERR "lm49352: failed to register card\n");
+ goto card_err;
+ }
+ return ret;
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+
+static struct snd_soc_device *lm49352_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+
+/* Address range for Lm49352 I2C. */
+static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
+
+/* Defines I2C client address data. */
+I2C_CLIENT_INSMOD;
+
+static struct i2c_driver lm49352_i2c_driver;
+static struct i2c_client client_template;
+/***************************************************************************
+ * Name
+ * lm49352_codec_probe- Probes the I2C for given address.
+ * Synopsis
+ * static int lm49352_codec_probe(struct i2c_adapter *adap, int addr,
+ * int kind)
+ * Arguments
+ * adap- Pointer to the struct i2c_adapter
+ * addr- Default I2C address of LM49352(0x1a).
+ * kind- Kind of I2C.
+ * Decription
+ * This function is calles while doing I2C probe and it probes I2C
+ * for given address and also allocates memory for I2C.
+ * and also attaches the client to I2C.
+ *************************************************************************/
+static int lm49352_codec_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct snd_soc_device *socdev = lm49352_socdev;
+ struct lm49352_setup_data *setup = socdev->codec_data;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct i2c_client *i2c;
+ int ret;
+ dbg("in lm49352_codec_probe start\n");
+
+ if (addr != setup->i2c_address)
+ return -ENODEV;
+
+ client_template.adapter = adap;
+ client_template.addr = addr;
+
+ /* Allocating memory for LM49352 I2C data. */
+ i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL);
+
+ if (i2c == NULL) {
+ kfree(codec);
+ dbg("in lm49352_codec_probe kmemdup failed\n");
+ return -ENOMEM;
+ }
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ /* Attaching I2C client of LM49352. */
+ ret = i2c_attach_client(i2c);
+ if (ret < 0) {
+ err("failed to attach codec at addr %x\n", addr);
+ goto err;
+ }
+
+ /* Calling LM49352_ to initialise Lm49352. */
+ ret = lm49352_init(socdev);
+ if (ret < 0) {
+ err("failed to initialise LM49352\n");
+ goto err;
+ }
+ dbg("in lm49352_codec_probe end\n");
+ return ret;
+
+err:
+ kfree(codec);
+ kfree(i2c);
+ return ret;
+}
+
+/**************************************************************************
+ * Name
+ * lm49352_i2c_detach- Detaches or removes the created I2C instance.
+ * Synopsis
+ * static int lm49352_i2c_detach(struct i2c_client *client)
+ * Arguments
+ * client- Pointer to the struct i2c_client
+ * Decription
+ * This function removes or detaches the I2C instance.
+ **************************************************************************/
+
+static int lm49352_i2c_detach(struct i2c_client *client)
+{
+ dbg("in lm49352_i2c_detach \n");
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+
+ /* Detaching I2C client of LM49352. */
+ i2c_detach_client(client);
+ kfree(codec->reg_cache);
+ kfree(client);
+ return 0;
+}
+
+/**************************************************************************
+ * Name
+ * lm49352_i2c_attach- Attaches or creates I2C instance.
+ * Synopsis
+ * static int lm49352_i2c_attach(struct i2c_adapter *adap)
+ * Arguments
+ * adap- Pointer to the struct i2c_adapter
+ * Decription
+ * This function attaches a new client to I2C.
+ *************************************************************************/
+static int lm49352_i2c_attach(struct i2c_adapter *adap)
+{
+ if (attach_once == 0) {
+ attach_once++;
+ dbg("in lm49352_i2c_attach \n");
+ /* Probes I2C for given address values. */
+ return i2c_probe(adap, &addr_data, lm49352_codec_probe);
+ } else
+ return 0;
+}
+
+/* I2C driver structure for LM49352. */
+static struct i2c_driver lm49352_i2c_driver = {
+ .driver = {
+ .name = "LM49352 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .attach_adapter = lm49352_i2c_attach,
+ .detach_client = lm49352_i2c_detach,
+ .command = NULL,
+};
+
+/* I2C client structure for LM49352. */
+static struct i2c_client client_template = {
+ .name = "LM49352",
+ .driver = &lm49352_i2c_driver,
+};
+#endif
+
+/**************************************************************************
+ * Name
+ * lm49352_probe- Probes LM49352 card.
+ * Synopsis
+ * static int lm49352_probe(struct platform_device *pdev)
+ * Arguments
+ * pdev- Pointer to the struct platform_device
+ * Decription
+ * This function probes LM49352 and creates or allocates memory for
+ * codec structure and also for codec private data (lm49352) and
+ * adds I2C driver for LM49352.
+ *************************************************************************/
+
+static int lm49352_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct lm49352_setup_data *setup;
+ struct snd_soc_codec *codec;
+ int ret = 0;
+ dbg(" in lm49352_probe start \n");
+ info("LM49352 Audio Codec %s\n", LM49352_VERSION);
+
+ setup = socdev->codec_data;
+ /* Allocating memory for LM49352 codec data. */
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+ dbg("in lm49352_probe lm49352 kzalloc success \n");
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ lm49352_socdev = socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ normal_i2c[0] = setup->i2c_address;
+ dbg("i2c address is %x\n", setup->i2c_address);
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ /* Adding I2C driver for LM49352. */
+ ret = i2c_add_driver(&lm49352_i2c_driver);
+ if (ret != 0)
+ dbg(KERN_ERR "can't add i2c driver");
+ }
+#else
+
+#endif
+ return ret;
+}
+
+/**************************************************************************
+ * Name
+ * lm49352_remove- Removes Lm49352 driver structures.
+ * Synopsis
+ * static int lm49352_remove(struct platform_device *pdev)
+ * Arguments
+ * pdev- Pointer to the struct platform_device
+ * Decription
+ * This function removes all the structures created while
+ * lm49352_probe.
+ *************************************************************************/
+
+static int lm49352_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&lm49352_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+/* Codec structure for LM49352. */
+struct snd_soc_codec_device soc_codec_dev_lm49352 = {
+ .probe = lm49352_probe,
+ .remove = lm49352_remove,
+};
+
+/* Exporting soc_codec_dev_lm49352 symbol. */
+EXPORT_SYMBOL_GPL(soc_codec_dev_lm49352);
+
+MODULE_AUTHOR("M R Swami Reddy <MR.Swami.Reddy at nsc.com");
+MODULE_DESCRIPTION("ASoC LM49352 driver");
+MODULE_LICENSE("GPL");
diff -upNr -X linux-2.6.24-lm49352/Documentation/dontdiff linux-2.6.24/sound/soc/codecs/lm49352.h linux-2.6.24-lm49352/sound/soc/codecs/lm49352.h
--- linux-2.6.24/sound/soc/codecs/lm49352.h 1970-01-01 05:30:00.000000000 +0530
+++ linux-2.6.24-lm49352/sound/soc/codecs/lm49352.h 2011-03-14 13:03:17.000000000 +0530
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) <2011> National Semiconductor, Inc.
+ *
+ * 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.
+*/
+
+#ifndef _LM49352_H
+#define _LM49352_H
+
+
+/* LM49352 register space */
+#define BASIC_SETUP_PMC_SETUP 0x00
+#define BASIC_SETUP_PMC_CLOCK 0x01
+#define PLL_CLK_SEL 0x03
+#define ANALOG_MIXER_HEADPHONESL 0x11
+#define ANALOG_MIXER_HEADPHONESR 0x12
+#define ANALOG_MIXER_OUTPUT_OPTIONS 0x14
+#define ANALOG_MIXER_ADC 0x15
+#define ANALOG_MIXER_MIC_LVL 0x16
+#define ANALOG_MIXER_HP_SENSE 0x1B
+#define ADC_BASIC 0x20
+#define ADC_CLOCK 0x21
+#define DAC_BASIC 0x30
+#define DAC_MUTE 0x30
+#define DAC_CLOCK 0x31
+#define DAC_IP_SELECT 0x44
+#define AUDIO_PORT1_BASIC 0x50
+#define PLL_M 0x04
+#define PLL_N 0x05
+#define PLL_N_MOD 0x06
+#define PLL_P1 0x07
+#define ANALOG_MIXER_CLASSD 0x10
+#define ANALOG_MIXER_AUX_OUT 0x13
+#define ANALOG_MIXER_ADC 0x15
+#define ANALOG_MIXER_AUXL_LVL 0x18
+#define ADC_MIXER 0x23
+#define AUDIO_PORT1_IP 0x42
+#define ADC_EFFECTS_HPF 0x80
+#define ADC_EFFECTS_ADC_ALC4 0x84
+#define ADC_EFFECTS_ADC_ALC5 0x85
+#define ADC_EFFECTS_ADC_ALC6 0x86
+#define ADC_EFFECTS_ADC_ALC7 0x87
+#define ADC_EFFECTS_ADC_L_LEVEL 0x89
+#define ADC_EFFECTS_ADC_R_LEVEL 0x8A
+#define DAC_EFFECTS_DAC_ALC1 0xA0
+#define DAC_EFFECTS_DAC_ALC4 0xA3
+#define DAC_EFFECTS_DAC_ALC5 0xA4
+#define DAC_EFFECTS_DAC_ALC6 0xA5
+#define DAC_EFFECTS_DAC_ALC7 0xA6
+#define DAC_EFFECTS_DAC_L_LEVEL 0xA8
+#define DAC_EFFECTS_DAC_R_LEVEL 0xA9
+#define SPREAD_SPECTRUM_RESET 0xF0
+
+
+#define LM49352_DAC_CONTROL3 0x71
+#define LM49352_DAC_CONTROL4 0xff
+#define LM49352_DAC_CONTROL5 0x30
+#define LM49352_RESET 0xf0
+#define LM49352_ADC_CONTROL1 0x20
+#define LM49352_MUTE 0x0C
+#define LM49352_DIGITAL_ATTENUATION_DACL1 0xA8
+#define LM49352_DIGITAL_ATTENUATION_DACR1 0xA9
+
+extern struct snd_soc_codec_device soc_codec_dev_lm49352;
+
+struct lm49352_setup_data {
+ unsigned short i2c_address;
+};
+
+#define LM49352_DAI_PAIFRX 0
+#define LM49352_DAI_PAIFTX 1
+
+
+extern struct snd_soc_codec_dai lm49352_dai[];
+extern struct snd_soc_codec_device soc_codec_dev_lm49352;
+
+#endif
+
diff -upNr -X linux-2.6.24-lm49352/Documentation/dontdiff linux-2.6.24/sound/soc/codecs/Makefile linux-2.6.24-lm49352/sound/soc/codecs/Makefile
--- linux-2.6.24/sound/soc/codecs/Makefile 2011-03-14 14:36:10.000000000 +0530
+++ linux-2.6.24-lm49352/sound/soc/codecs/Makefile 2010-03-27 11:33:10.000000000 +0530
@@ -1,4 +1,5 @@
snd-soc-ac97-objs := ac97.o
+snd-soc-lm49352-objs := lm49352.o
snd-soc-wm8731-objs := wm8731.o
snd-soc-wm8750-objs := wm8750.o
snd-soc-wm8753-objs := wm8753.o
@@ -19,6 +20,7 @@ snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
+obj-$(CONFIG_SND_SOC_LM49352) += snd-soc-lm49352.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
More information about the Alsa-devel
mailing list