[alsa-devel] [PATCH] ASoC: Add National Semiconductor LM49352 Audio Codec support
Hello,
Patch for adding National Semiconductor LM49352 Audio Codec (http://www.national.com/ds/LM/LM49352.pdf) support along with Samsung_6410 (i.e. SMDK6410) platform support for LM49352.
[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.
Thanks Swami
On Mon, Mar 07, 2011 at 04:03:55AM -0800, Reddy, MR Swami wrote:
Please fix your MUA to word wrap within paragraphs at less than 80 columns. I've reflowed your mail for legibility. There are some suggestions in Documentation/email-clients.txt.
Patch for adding National Semiconductor LM49352 Audio Codec (http://www.national.com/ds/LM/LM49352.pdf) support along with Samsung_6410 (i.e. SMDK6410) platform support for LM49352.
[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.
You need to follow the standard kernel patch submission process, which is documented in Documentation/SubmittingPatches. In particular you should:
- Conform to the coding standards in CodingStandards (checkpatch.pl is helpful for detecting obvious issues). A good high level check is if your code should visually resemble the rest of the code base. - Submit your code against the current development kernel version. The trees will be listed in MAINTAINERS, or -next is a good approximation. - Send your patch in-line rather than as an attachment. - Send your patch against the full Linux tree, not a subdirectory of it.
You should also split your code up into a series of independant patches - in general each individual driver should be a single patch so your machine and CODEC drivers should be split up. If the machine driver is just for a flying wire system you should remove it, otherwise please also submit the arch/arm parts.
I've not done a detailed review of the code due to the above high level issues.
And also let me know the forward-porting (to the latest ALSA version APIs) steps/process. Thanks in advance.
You need to figure out a process that works for you. In general inspecting the kernel history is usually very useful for stuff like this, or you could just start from scratch and copy over the active bits of code.
Thank you very much for your feedback. I will update the patch as per the comments and submit the update patch for review.
Thanks Swami
-----Original Message----- From: Mark Brown [mailto:broonie@opensource.wolfsonmicro.com] Sent: Monday, March 07, 2011 6:48 PM To: Reddy, MR Swami Cc: alsa-devel@alsa-project.org; Liam Girdwood Subject: Re: [PATCH] ASoC: Add National Semiconductor LM49352 Audio Codec support
On Mon, Mar 07, 2011 at 04:03:55AM -0800, Reddy, MR Swami wrote:
Please fix your MUA to word wrap within paragraphs at less than 80 columns. I've reflowed your mail for legibility. There are some suggestions in Documentation/email-clients.txt.
Patch for adding National Semiconductor LM49352 Audio Codec (http://www.national.com/ds/LM/LM49352.pdf) support along with Samsung_6410 (i.e. SMDK6410) platform support for LM49352.
[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.
You need to follow the standard kernel patch submission process, which is documented in Documentation/SubmittingPatches. In particular you should:
- Conform to the coding standards in CodingStandards (checkpatch.pl is helpful for detecting obvious issues). A good high level check is if your code should visually resemble the rest of the code base. - Submit your code against the current development kernel version. The trees will be listed in MAINTAINERS, or -next is a good approximation. - Send your patch in-line rather than as an attachment. - Send your patch against the full Linux tree, not a subdirectory of it.
You should also split your code up into a series of independant patches - in general each individual driver should be a single patch so your machine and CODEC drivers should be split up. If the machine driver is just for a flying wire system you should remove it, otherwise please also submit the arch/arm parts.
I've not done a detailed review of the code due to the above high level issues.
And also let me know the forward-porting (to the latest ALSA version APIs) steps/process. Thanks in advance.
You need to figure out a process that works for you. In general inspecting the kernel history is usually very useful for stuff like this, or you could just start from scratch and copy over the active bits of code.
From: M R Swami Reddy MR.Swami.Reddy@nsc.com
Patch for adding National Semiconductor LM49352 codec support.
Signed-off-by: M R Swami Reddy MR.Swami.Reddy@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@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
On Wed, Mar 16, 2011 at 12:38:57AM -0700, Reddy, MR Swami wrote:
Tested this patch on SMDK6410 platform with Kernel- 2.6.24 and ALSA version is - 1.0.15.
As previously mentioned you need to submit code against current Linux versions. Linux 2.6.24 is over three years old and development of the kernel has moved on substantially in that time.
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.
As previously mentioned this is pretty much up to you - you can apply a range of techniques, either from starting from scratch or reviewing the kernel changelogs for the core and other drivers and making gradual changes to forward port. Which approach works best for you is largely a matter of personal preference. If you have questions on specific things then please feel free to ask.
I've had a brief glance at the driver and there are a number of obvious coding style issues, please before resubmitting review the coding style you are using and ensure that your driver is following a similar coding style to the existing kernel code. For example comments like this:
+/***************************************************************************
- 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)
are not at all idiomatic for the kernel.
From: Mark Brown [mailto:broonie@opensource.wolfsonmicro.com]
As previously mentioned this is pretty much up to you - you can apply a range of techniques, either from starting from >scratch or reviewing the kernel changelogs for the core and other drivers and making gradual changes to forward port. >Which approach works best for you is largely a matter of personal preference. If you have questions on specific things >then please feel free to ask.
OK. I will look into the kernel changelogs and update the LM49352 driver to use the latest ALSA APIs and sync with latest kernel sources. If there are any other comments (ie apart from the coding standards), please let me know.
I've had a brief glance at the driver and there are a number of obvious coding style issues, please before resubmitting >review the coding style you are using and ensure that your driver is following a similar coding style to the existing >kernel code. For example comments like this:
I ran the scripts/chcekpatch.pl script for lm49352.c and lm49352.h file, there no warnings/errors. OK, I could remove the comments for all functions in the next patch submission.
Thanks Swami
On Wed, Mar 16, 2011 at 06:38:15AM -0700, Reddy, MR Swami wrote:
From: Mark Brown [mailto:broonie@opensource.wolfsonmicro.com]
Please fix your MUA to word wrap at less than 80 columns within paragraphs.
As previously mentioned this is pretty much up to you - you can apply a range of techniques, either from starting from >scratch or reviewing the kernel changelogs for the core and other drivers and making gradual changes to forward port. >Which approach works best for you is largely a matter of personal preference. If you have questions on specific things >then please feel free to ask.
OK. I will look into the kernel changelogs and update the LM49352 driver to use the latest ALSA APIs and sync with latest kernel sources. If there are any other comments (ie apart from the coding standards), please let me know.
Given the great age of the kernel you were developing against I didn't really look at the code, there has been substantial change in the kernel since then.
I've had a brief glance at the driver and there are a number of obvious coding style issues, please before resubmitting >review the coding style you are using and ensure that your driver is following a similar coding style to the existing >kernel code. For example comments like this:
I ran the scripts/chcekpatch.pl script for lm49352.c and lm49352.h file, there no warnings/errors. OK, I could remove the comments for all functions in the next patch submission.
It's not about passing checkpatch, it's about making sure your code looks like other similar code. If you look at your code next to another, similar driver and it's easy to tell which is which visually there's an issue. checkpatch is a useful tool but it's only part of making sure your code is idiomatic for the kernel.
participants (2)
-
Mark Brown
-
Reddy, MR Swami