[alsa-devel] [PATCH 0/4] Blackfin supports for ALSA/ASOC
Hi folks,
It's very happy to release this ASOC Blackfin ports. Please review our drivers and feel free to ask us update it.
In this patchset, we post ASOC Blackfin supports, SSM2602 audio codec driver and other 2 ALSA/ASOC patches.
Thanks a lot -Bryan
From: Cliff Cai cliff.cai@analog.com
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Bryan Wu cooloney@kernel.org --- include/linux/i2c-id.h | 1 + sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ssm2602.c | 837 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ssm2602.h | 132 +++++++ 5 files changed, 975 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/ssm2602.c create mode 100644 sound/soc/codecs/ssm2602.h
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index e1b46bc..aeb221b 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -82,6 +82,7 @@ #define I2C_DRIVERID_CS4270 94 /* Cirrus Logic 4270 audio codec */ #define I2C_DRIVERID_M52790 95 /* Mitsubishi M52790SP/FP AV switch */ #define I2C_DRIVERID_CS5345 96 /* cs5345 audio processor */ +#define I2C_DRIVERID_SSM2602 97 /* BF52xC built in audio codec */
#define I2C_DRIVERID_OV7670 1048 /* Omnivision 7670 camera */ #define I2C_DRIVERID_AD5252 1049 diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 221e1da..67ed3f2 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -15,6 +15,9 @@ config SND_SOC_AD1980 tristate depends on SND_SOC
+config SND_SOC_SSM2602 + tristate + config SND_SOC_WM8731 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 65e2a7d..29bcea6 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,6 +1,7 @@ snd-soc-ac97-objs := ac97.o snd-soc-ad1980-objs := ad1980.o snd-soc-ak4535-objs := ak4535.o +snd-soc-ssm2602-objs := ssm2602.o snd-soc-uda1380-objs := uda1380.o snd-soc-wm8510-objs := wm8510.o snd-soc-wm8731-objs := wm8731.o @@ -15,6 +16,7 @@ snd-soc-tlv320aic3x-objs := tlv320aic3x.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c new file mode 100644 index 0000000..8dc22a1 --- /dev/null +++ b/sound/soc/codecs/ssm2602.c @@ -0,0 +1,837 @@ +/* + * File: sound/soc/codecs/ssm2602.c + * Author: Cliff Cai Cliff.Cai@analog.com + * + * Created: Tue June 06 2008 + * Description: Driver for ssm2602 sound chip built in ADSP-BF52xC + * + * Rev: $Id: ssm2602.c 4104 2008-06-06 06:51:48Z cliff $ + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.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 "ssm2602.h" + +#define AUDIO_NAME "ssm2602" +#define SSM2602_VERSION "0.1" + +/* + * Debug + */ + +#define SSM2602_DEBUG 0 + +#ifdef SSM2602_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) + +struct snd_soc_codec_device soc_codec_dev_ssm2602; + +/* codec private data */ +struct ssm2602_priv { + unsigned int sysclk; +}; + +/* + * ssm2602 register cache + * We can't read the ssm2602 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = { + 0x0017, 0x0017, 0x0079, 0x0079, + 0x0000, 0x0000, 0x0000, 0x000a, + 0x0000, 0x0000 +}; + +/* + * read ssm2602 register cache + */ +static inline unsigned int ssm2602_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == SSM2602_RESET) + return 0; + if (reg >= SSM2602_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write ssm2602 register cache + */ +static inline void ssm2602_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= SSM2602_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the ssm2602 register space + */ +static int ssm2602_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 ssm2602 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + ssm2602_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define ssm2602_reset(c) ssm2602_write(c, SSM2602_RESET, 0) +/*Appending several "None"s just for OSS mixer use*/ +static const char *ssm2602_input_select[] = {"Line", "Mic", "None", "None", "None", + "None", "None", "None"}; +static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum ssm2602_enum[] = { + SOC_ENUM_SINGLE(SSM2602_APANA, 2, 2, ssm2602_input_select), + SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph), +}; + +static const struct snd_kcontrol_new ssm2602_snd_controls[] = { + +SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V, + 7, 1, 0), + +SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0), +SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), + +SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), +SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), + +SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), + +SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1), +SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0), + +SOC_ENUM("Capture Source", ssm2602_enum[0]), + +SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]), +}; + +/* add non dapm controls */ +static int ssm2602_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ssm2602_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ssm2602_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Output Mixer */ +static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ssm2602_input_mux_controls = +SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]); + +static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1, + &ssm2602_output_mixer_controls[0], + ARRAY_SIZE(ssm2602_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1), +SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls), +SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1), +SND_SOC_DAPM_INPUT("MICIN"), +SND_SOC_DAPM_INPUT("RLINEIN"), +SND_SOC_DAPM_INPUT("LLINEIN"), +}; + +static const char *intercon[][3] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* input mux */ + {"Input Mux", "Line", "Line Input"}, + {"Input Mux", "Mic", "Mic Bias"}, + {"ADC", NULL, "Input Mux"}, + + /* inputs */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, + {"Mic Bias", NULL, "MICIN"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int ssm2602_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ssm2602_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &ssm2602_dapm_widgets[i]); + /* set up audio path interconnects */ + for (i = 0; intercon[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, intercon[i][0], + intercon[i][1], intercon[i][2]); + } + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return 0; +} + +static int ssm2602_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3; + int i = get_coeff(ssm2602->sysclk, params_rate(params)); + u16 srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + ssm2602_write(codec, SSM2602_ACTIVE, 0); + ssm2602_write(codec, SSM2602_SRATE, srate); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + ssm2602_write(codec, SSM2602_IFACE, iface); + ssm2602_write(codec, SSM2602_ACTIVE, ACTIVATE_CODEC); + return 0; +} + +static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + /* set active */ + ssm2602_write(codec, SSM2602_ACTIVE, ACTIVATE_CODEC); + + return 0; +} + +static void ssm2602_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + /* deactivate */ + if (!codec->active) { + udelay(50); + ssm2602_write(codec, SSM2602_ACTIVE, 0); + } +} + +static int ssm2602_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = ssm2602_read_reg_cache(codec, SSM2602_APDIGI) & 0xfff7; + if (mute) + ssm2602_write(codec, SSM2602_APDIGI, mute_reg | ENABLE_DAC_MUTE); + else + ssm2602_write(codec, SSM2602_APDIGI, mute_reg); + return 0; +} + +static int ssm2602_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + ssm2602->sysclk = freq; + return 0; + } + return -EINVAL; +} + + +static int ssm2602_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + ssm2602_write(codec, SSM2602_IFACE, iface); + return 0; +} + +static int ssm2602_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 reg = ssm2602_read_reg_cache(codec, SSM2602_PWR) & 0xff7f; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, osc on, dac unmute */ + ssm2602_write(codec, SSM2602_PWR, 0); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, */ + ssm2602_write(codec, SSM2602_PWR, reg | CLK_OUT_PDN); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + ssm2602_write(codec, SSM2602_ACTIVE, 0); + ssm2602_write(codec, SSM2602_PWR, 0xffff); + break; + } + codec->dapm_state = event; + return 0; +} + +#define SSM2602_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 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + +struct snd_soc_codec_dai ssm2602_dai = { + .name = "SSM2602", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .ops = { + .prepare = ssm2602_pcm_prepare, + .hw_params = ssm2602_hw_params, + .shutdown = ssm2602_shutdown, + }, + .dai_ops = { + .digital_mute = ssm2602_mute, + .set_sysclk = ssm2602_set_dai_sysclk, + .set_fmt = ssm2602_set_dai_fmt, + } +}; +EXPORT_SYMBOL_GPL(ssm2602_dai); + +static int ssm2602_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + ssm2602_write(codec, SSM2602_ACTIVE, 0); + ssm2602_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int ssm2602_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(ssm2602_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + ssm2602_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + ssm2602_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the ssm2602 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ssm2602_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "SSM2602"; + codec->owner = THIS_MODULE; + codec->read = ssm2602_read_reg_cache; + codec->write = ssm2602_write; + codec->dapm_event = ssm2602_dapm_event; + codec->dai = &ssm2602_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(ssm2602_reg); + codec->reg_cache = kmemdup(ssm2602_reg, sizeof(ssm2602_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + ssm2602_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ssm2602: failed to create pcms\n"); + goto pcm_err; + } + /*power on device*/ + ssm2602_write(codec, SSM2602_ACTIVE, 0); + /* set the update bits */ + reg = ssm2602_read_reg_cache(codec, SSM2602_LINVOL); + ssm2602_write(codec, SSM2602_LINVOL, reg | LRIN_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_RINVOL); + ssm2602_write(codec, SSM2602_RINVOL, reg | RLIN_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_LOUT1V); + ssm2602_write(codec, SSM2602_LOUT1V, reg | LRHP_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_ROUT1V); + ssm2602_write(codec, SSM2602_ROUT1V, reg | RLHP_BOTH); + /*select Line in as default input*/ + ssm2602_write(codec, SSM2602_APANA, ENABLE_MIC_BOOST2 | SELECT_DAC | ENABLE_MIC_BOOST); + ssm2602_write(codec, SSM2602_PWR, 0); + + ssm2602_add_controls(codec); + ssm2602_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ssm2602: 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 *ssm2602_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) && !defined(CONFIG_SND_SOC_SSM2602_SPI) + +/* + * ssm2602 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver ssm2602_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int ssm2602_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = ssm2602_socdev; + struct ssm2602_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + 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 = ssm2602_init(socdev); + if (ret < 0) { + err("failed to initialise ssm2602\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int ssm2602_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 ssm2602_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, ssm2602_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver ssm2602_i2c_driver = { + .driver = { + .name = "SSM2602 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_SSM2602, + .attach_adapter = ssm2602_i2c_attach, + .detach_client = ssm2602_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "SSM2602", + .driver = &ssm2602_i2c_driver, +}; +#endif + +#if defined(CONFIG_SPI_MASTER) && defined(CONFIG_SND_SOC_SSM2602_SPI) +static int __devinit ssm2602_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = ssm2602_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = ssm2602_init(socdev); + if (ret < 0) + err("failed to initialise ssm2602\n"); + + return ret; +} + +static int __devexit ssm2602_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver ssm2602_spi_driver = { + .driver = { + .name = "ssm2602", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ssm2602_spi_probe, + .remove = __devexit_p(ssm2602_spi_remove), +}; + +static int ssm2602_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u16 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = (data[0] << 8) + data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif /* CONFIG_SPI_MASTER */ + +static int ssm2602_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct ssm2602_setup_data *setup; + struct snd_soc_codec *codec; + struct ssm2602_priv *ssm2602; + int ret = 0; + + printk(KERN_INFO "ssm2602 Audio Codec %s", SSM2602_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ssm2602 = kzalloc(sizeof(struct ssm2602_priv), GFP_KERNEL); + if (ssm2602 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ssm2602; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ssm2602_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) && !defined(CONFIG_SND_SOC_SSM2602_SPI) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&ssm2602_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#elif defined(CONFIG_SPI_MASTER) && defined(CONFIG_SND_SOC_SSM2602_SPI) + codec->hw_write = (hw_write_t)ssm2602_spi_write; + ret = spi_register_driver(&ssm2602_spi_driver); + if (ret != 0) + printk(KERN_ERR "can't add spi driver"); +#else + /* Add other interfaces here */ + +#endif + return ret; +} + +/* power down chip */ +static int ssm2602_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + ssm2602_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) && !defined(CONFIG_SND_SOC_SSM2602_SPI) + i2c_del_driver(&ssm2602_i2c_driver); +#elif defined(CONFIG_SPI_MASTER) && defined(CONFIG_SND_SOC_SSM2602_SPI) + spi_unregister_driver(&ssm2602_spi_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ssm2602 = { + .probe = ssm2602_probe, + .remove = ssm2602_remove, + .suspend = ssm2602_suspend, + .resume = ssm2602_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ssm2602); + +MODULE_DESCRIPTION("ASoC ssm2602 driver"); +MODULE_AUTHOR("Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2602.h b/sound/soc/codecs/ssm2602.h new file mode 100644 index 0000000..f7064a7 --- /dev/null +++ b/sound/soc/codecs/ssm2602.h @@ -0,0 +1,132 @@ +/* + * File: sound/soc/codecs/ssm2602.h + * Author: Cliff Cai Cliff.Cai@analog.com + * + * Created: Tue June 06 2008 + * Description: Driver for SSM2602 sound chip built in ADSP-BF52xC + * + * Rev: $Id: ssm2602.c 4104 2008-06-06 06:51:48Z cliff $ + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SSM2602_H +#define _SSM2602_H + +/* SSM2602 Codec Register definitions */ + +#define SSM2602_LINVOL 0x00 +#define SSM2602_RINVOL 0x01 +#define SSM2602_LOUT1V 0x02 +#define SSM2602_ROUT1V 0x03 +#define SSM2602_APANA 0x04 +#define SSM2602_APDIGI 0x05 +#define SSM2602_PWR 0x06 +#define SSM2602_IFACE 0x07 +#define SSM2602_SRATE 0x08 +#define SSM2602_ACTIVE 0x09 +#define SSM2602_RESET 0x0f + +/*SSM2602 Codec Register Field definitions + *(Mask value to extract the corresponding Register field) + */ + +/*Left ADC Volume Control (SSM2602_REG_LEFT_ADC_VOL)*/ +#define LIN_VOL 0x01F /* Left Channel PGA Volume control */ +#define LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */ +#define LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */ + +/*Right ADC Volume Control (SSM2602_REG_RIGHT_ADC_VOL)*/ +#define RIN_VOL 0x01F /* Right Channel PGA Volume control */ +#define RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */ +#define RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */ + +/*Left DAC Volume Control (SSM2602_REG_LEFT_DAC_VOL)*/ +#define LHP_VOL 0x07F /* Left Channel Headphone volume control */ +#define ENABLE_LZC 0x080 /* Left Channel Zero cross detect enable */ +#define LRHP_BOTH 0x100 /* Left Channel Headphone volume update */ + +/*Right DAC Volume Control (SSM2602_REG_RIGHT_DAC_VOL)*/ +#define RHP_VOL 0x07F /* Right Channel Headphone volume control */ +#define ENABLE_RZC 0x080 /* Right Channel Zero cross detect enable */ +#define RLHP_BOTH 0x100 /* Right Channel Headphone volume update */ + +/*Analogue Audio Path Control (SSM2602_REG_ANALOGUE_PATH)*/ +#define ENABLE_MIC_BOOST 0x001 /* Primary Microphone Amplifier gain booster control */ +#define ENABLE_MIC_MUTE 0x002 /* Microphone Mute Control */ +#define ADC_IN_SELECT 0x004 /* Microphone/Line IN select to ADC (1=MIC, 0=Line In) */ +#define ENABLE_BYPASS 0x008 /* Line input bypass to line output */ +#define SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */ +#define ENABLE_SIDETONE 0x020 /* Enable/Disable Side Tone */ +#define SIDETONE_ATTN 0x0C0 /* Side Tone Attenuation */ +#define ENABLE_MIC_BOOST2 0x100 /* Secondary Microphone Amplifier gain booster control */ + +/*Digital Audio Path Control (SSM2602_REG_DIGITAL_PATH)*/ +#define ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */ +#define DE_EMPHASIS 0x006 /* De-Emphasis Control */ +#define ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */ +#define STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */ + +/*Power Down Control (SSM2602_REG_POWER) + *(1=Enable PowerDown, 0=Disable PowerDown) + */ +#define LINE_IN_PDN 0x001 /* Line Input Power Down */ +#define MIC_PDN 0x002 /* Microphone Input & Bias Power Down */ +#define ADC_PDN 0x004 /* ADC Power Down */ +#define DAC_PDN 0x008 /* DAC Power Down */ +#define OUT_PDN 0x010 /* Outputs Power Down */ +#define OSC_PDN 0x020 /* Oscillator Power Down */ +#define CLK_OUT_PDN 0x040 /* CLKOUT Power Down */ +#define POWER_OFF 0x080 /* POWEROFF Mode */ + +/*Digital Audio Interface Format (SSM2602_REG_DIGITAL_IFACE)*/ +#define IFACE_FORMAT 0x003 /* Digital Audio input format control */ +#define AUDIO_DATA_LEN 0x00C /* Audio Data word length control */ +#define DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */ +#define DAC_LR_SWAP 0x020 /* Swap DAC data control */ +#define ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */ +#define BCLK_INVERT 0x080 /* Bit Clock Inversion control */ + +/*Sampling Control (SSM2602_REG_SAMPLING_CTRL)*/ +#define ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */ +#define BOS_RATE 0x002 /* Base Over-Sampling rate */ +#define SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */ +#define CORECLK_DIV2 0x040 /* Core Clock divider select */ +#define CLKOUT_DIV2 0x080 /* Clock Out divider select */ + +/*Active Control (SSM2602_REG_ACTIVE_CTRL)*/ +#define ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */ + +/*********************************************************************/ + +#define SSM2602_CACHEREGNUM 10 + +#define SSM2602_SYSCLK 0 +#define SSM2602_DAI 0 + +struct ssm2602_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai ssm2602_dai; +extern struct snd_soc_codec_device soc_codec_dev_ssm2602; + +#endif
On Wed, Aug 27, 2008 at 05:39:26PM +0800, Bryan Wu wrote:
This looks basically good, thanks - I've picked up a few things below but they are mostly either minor or reflect the fact that the patch looks like it's been developed against current release kernels but there's been some churn recently in the ASoC APIs which need updates.
#define I2C_DRIVERID_CS5345 96 /* cs5345 audio processor */ +#define I2C_DRIVERID_SSM2602 97 /* BF52xC built in audio codec */
It should be possible to just remove this - it shouldn't be needed.
--- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig
Current kernels have a Kconfig option SND_SOC_ALL_CODECS which should have your codec added - this allows codec drivers to be built without boards for test purposes.
+#define SSM2602_DEBUG 0
+#ifdef SSM2602_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)
Please convert these to use the standard pr_ macros (or ideally the dev_ ones where possible) for debug prints.
+#define ssm2602_reset(c) ssm2602_write(c, SSM2602_RESET, 0) +/*Appending several "None"s just for OSS mixer use*/ +static const char *ssm2602_input_select[] = {"Line", "Mic", "None", "None", "None",
"None", "None", "None"};
+static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
Please keep the lines under 80 characters where reasonable. A little more while space (blank lines and at the start and end of comments) would be nice too.
+static const char *intercon[][3] = {
This should be converted to an array of struct snd_soc_dapm_route - there's a new bulk registration API been added which saves everything open coding all these and improves the error checking.
+static int ssm2602_add_widgets(struct snd_soc_codec *codec) +{
- int i;
- for (i = 0; i < ARRAY_SIZE(ssm2602_dapm_widgets); i++)
snd_soc_dapm_new_control(codec, &ssm2602_dapm_widgets[i]);
There's now a snd_soc_dapm_new_controls() for registering DAPM controls en masse as well.
- /* set up audio path interconnects */
- for (i = 0; intercon[i][0] != NULL; i++) {
snd_soc_dapm_connect_input(codec, intercon[i][0],
intercon[i][1], intercon[i][2]);
- }
This can be replaced with snd_soc_dapm_add_routes().
+static int ssm2602_mute(struct snd_soc_codec_dai *dai, int mute) +{
- struct snd_soc_codec *codec = dai->codec;
- u16 mute_reg = ssm2602_read_reg_cache(codec, SSM2602_APDIGI) & 0xfff7;
- if (mute)
ssm2602_write(codec, SSM2602_APDIGI, mute_reg | ENABLE_DAC_MUTE);
- else
ssm2602_write(codec, SSM2602_APDIGI, mute_reg);
- return 0;
+}
More blank lines would be nice here and elsewhere but it's not critical.
+static int ssm2602_dapm_event(struct snd_soc_codec *codec, int event) +{
- u16 reg = ssm2602_read_reg_cache(codec, SSM2602_PWR) & 0xff7f;
- switch (event) {
- case SNDRV_CTL_POWER_D0: /* full On */
This won't compile with current kernels. The dapm_event() interface has been replaced by the very similar but hopefully clearer set_bias_level() interface which use a different set of constants.
+static int ssm2602_suspend(struct platform_device *pdev, pm_message_t state) +{
- struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
- ssm2602_write(codec, SSM2602_ACTIVE, 0);
That *should* be redundant - ALSA should stop the playback streams before suspending - but equally well it shouldn't hurt.
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) && !defined(CONFIG_SND_SOC_SSM2602_SPI)
Hrm. Why does the SPI support disable the I2C support? I'd expect to be able to build a kernel with the chip supported on both (for use on multiple boards).
+/*
- ssm2602 2 wire address is determined by GPIO5
- state during powerup.
- low = 0x1a
- high = 0x1b
- */
+static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
+/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD;
Jean Delvare is currently in the process of convering all the ASoC codec drivers to new style I2C drivers so we probably shouldn't add any more old style I2C codec drivers. The update is fairly straightforward - see his patches yesterday for WM8731 and WM8750 for example conversions.
+#elif defined(CONFIG_SPI_MASTER) && defined(CONFIG_SND_SOC_SSM2602_SPI)
- codec->hw_write = (hw_write_t)ssm2602_spi_write;
- ret = spi_register_driver(&ssm2602_spi_driver);
- if (ret != 0)
printk(KERN_ERR "can't add spi driver");
You could add a flag in the device data to identify if the device is on SPI here (that's what the check for i2c_address is intended for).
On Wed, Aug 27, 2008 at 6:54 PM, Mark Brown broonie@sirena.org.uk wrote:
On Wed, Aug 27, 2008 at 05:39:26PM +0800, Bryan Wu wrote:
This looks basically good, thanks - I've picked up a few things below but they are mostly either minor or reflect the fact that the patch looks like it's been developed against current release kernels but there's been some churn recently in the ASoC APIs which need updates.
#define I2C_DRIVERID_CS5345 96 /* cs5345 audio processor */ +#define I2C_DRIVERID_SSM2602 97 /* BF52xC built in audio codec */
It should be possible to just remove this - it shouldn't be needed.
OK, I'll take care of this i2c stuff here.
--- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig
Current kernels have a Kconfig option SND_SOC_ALL_CODECS which should have your codec added - this allows codec drivers to be built without boards for test purposes.
Is this option in alsa git tree or in the mainline? I fail to find it in upstream mainline. And do you mean I don't need to add SND_SOC_SSM2602 at all?
+#define SSM2602_DEBUG 0
+#ifdef SSM2602_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)
Please convert these to use the standard pr_ macros (or ideally the dev_ ones where possible) for debug prints.
Right, I killed this local definition and replaced them to pr_xxxx.
+#define ssm2602_reset(c) ssm2602_write(c, SSM2602_RESET, 0) +/*Appending several "None"s just for OSS mixer use*/ +static const char *ssm2602_input_select[] = {"Line", "Mic", "None", "None", "None",
"None", "None", "None"};
+static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
Please keep the lines under 80 characters where reasonable. A little more while space (blank lines and at the start and end of comments) would be nice too.
Yes, I will fix this issue by running checkpatch.pl. And for other API conflicts and I2C interface upgrading stuffs, I will leave them to Cliff.
Thanks -Bryan
On Thu, Aug 28, 2008 at 01:55:33PM +0800, Bryan Wu wrote:
On Wed, Aug 27, 2008 at 6:54 PM, Mark Brown broonie@sirena.org.uk wrote:
Current kernels have a Kconfig option SND_SOC_ALL_CODECS which should have your codec added - this allows codec drivers to be built without boards for test purposes.
Is this option in alsa git tree or in the mainline? I fail to find it in upstream mainline. And do you mean I don't need to add SND_SOC_SSM2602 at all?
It's in ALSA git, queued for the 2.6.28 merge window. See the topic/asoc branch of:
git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6.git
for the current ASoC merge queue. The other API changes will all be queued there too.
There's quite a bit of churn in ASoC at the minute but additional API changes will be taken care of by whoever does them once your driver is merged so it should save you some work if you're able to get this in soon.
Yes, I will fix this issue by running checkpatch.pl. And for other API conflicts and I2C interface upgrading stuffs, I will leave them to Cliff.
OK. Jean might be willing to do the I2C API conversion for you (I've CCed him in).
Hi Bryan, Mark,
On Thu, 28 Aug 2008 11:06:18 +0100, Mark Brown wrote:
On Thu, Aug 28, 2008 at 01:55:33PM +0800, Bryan Wu wrote:
Yes, I will fix this issue by running checkpatch.pl. And for other API conflicts and I2C interface upgrading stuffs, I will leave them to Cliff.
OK. Jean might be willing to do the I2C API conversion for you (I've CCed him in).
Sure, I can help with that if needed. OTOH, I've posted patches converting 2 drivers already [1] [2], and apparently all the codec drivers follow the same model so it shouldn't be too difficult for driver authors to do the same on their code. But anyway I'll convert the remaining drivers as my time permits. The plan is to have everything converted for kernel 2.6.28 (at which point the old I2C API will be deprecated.)
[1] http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010198.html [2] http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010199.html
From: Cliff Cai cliff.cai@analog.com
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Bryan Wu cooloney@kernel.org --- sound/soc/codecs/Kconfig | 5 +++ sound/soc/codecs/wm8731.c | 71 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 3 deletions(-)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 67ed3f2..21bb277 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -21,6 +21,11 @@ config SND_SOC_SSM2602 config SND_SOC_WM8731 tristate
+config SND_SOC_WM8731_SPI + bool "Control interface: Use SPI instead of I2C" + depends on SND_SOC_WM8731 + default n + config SND_SOC_WM8750 tristate
diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 9402fca..56a0ff1 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -19,6 +19,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> +#include <linux/spi/spi.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -562,7 +563,7 @@ pcm_err:
static struct snd_soc_device *wm8731_socdev;
-#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) && !defined (CONFIG_SND_SOC_WM8731_SPI)
/* * WM8731 2 wire address is determined by GPIO5 @@ -652,6 +653,62 @@ static struct i2c_client client_template = { }; #endif
+#if defined(CONFIG_SPI_MASTER) && defined(CONFIG_SND_SOC_WM8731_SPI) +static int __devinit wm8731_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8731_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8731_init(socdev); + if (ret < 0) { + err("failed to initialise WM8731\n"); + } + + return ret; +} + +static int __devexit wm8731_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8731_spi_driver = { + .driver = { + .name = "wm8731", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8731_spi_probe, + .remove = __devexit_p(wm8731_spi_remove), +}; + +static int wm8731_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u16 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = (data[0] << 8) + data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif /* CONFIG_SPI_MASTER */ + static int wm8731_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -680,7 +737,7 @@ static int wm8731_probe(struct platform_device *pdev) INIT_LIST_HEAD(&codec->dapm_paths);
wm8731_socdev = socdev; -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) && !defined (CONFIG_SND_SOC_WM8731_SPI) if (setup->i2c_address) { normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; @@ -688,8 +745,14 @@ static int wm8731_probe(struct platform_device *pdev) if (ret != 0) printk(KERN_ERR "can't add i2c driver"); } +#elif defined (CONFIG_SPI_MASTER) && defined (CONFIG_SND_SOC_WM8731_SPI) + codec->hw_write = (hw_write_t)wm8731_spi_write; + ret = spi_register_driver(&wm8731_spi_driver); + if (ret != 0) + printk(KERN_ERR "can't add spi driver"); #else /* Add other interfaces here */ + #endif
if (ret != 0) { @@ -710,8 +773,10 @@ static int wm8731_remove(struct platform_device *pdev)
snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) && !defined (CONFIG_SND_SOC_WM8731_SPI) i2c_del_driver(&wm8731_i2c_driver); +#elif defined (CONFIG_SPI_MASTER) && defined (CONFIG_SND_SOC_WM8731_SPI) + spi_unregister_driver(&wm8731_spi_driver); #endif kfree(codec->private_data); kfree(codec);
On Wed, Aug 27, 2008 at 05:39:27PM +0800, Bryan Wu wrote:
This looks good, thanks but...
-#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) && !defined (CONFIG_SND_SOC_WM8731_SPI) if (setup->i2c_address) { normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; @@ -688,8 +745,14 @@ static int wm8731_probe(struct platform_device *pdev) if (ret != 0) printk(KERN_ERR "can't add i2c driver"); } +#elif defined (CONFIG_SPI_MASTER) && defined (CONFIG_SND_SOC_WM8731_SPI)
- codec->hw_write = (hw_write_t)wm8731_spi_write;
- ret = spi_register_driver(&wm8731_spi_driver);
- if (ret != 0)
printk(KERN_ERR "can't add spi driver");
...as for the SSM2602 it'd be good if it were possible to build a kernel which supports both I2C and SPI. If you like I could do this for you - there's likely to be merge issues due to the update to the new I2C API anyway?
On Wed, Aug 27, 2008 at 7:01 PM, Mark Brown broonie@sirena.org.uk wrote:
On Wed, Aug 27, 2008 at 05:39:27PM +0800, Bryan Wu wrote:
This looks good, thanks but...
-#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) && !defined (CONFIG_SND_SOC_WM8731_SPI) if (setup->i2c_address) { normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; @@ -688,8 +745,14 @@ static int wm8731_probe(struct platform_device *pdev) if (ret != 0) printk(KERN_ERR "can't add i2c driver"); } +#elif defined (CONFIG_SPI_MASTER) && defined (CONFIG_SND_SOC_WM8731_SPI)
codec->hw_write = (hw_write_t)wm8731_spi_write;
ret = spi_register_driver(&wm8731_spi_driver);
if (ret != 0)
printk(KERN_ERR "can't add spi driver");
...as for the SSM2602 it'd be good if it were possible to build a kernel which supports both I2C and SPI. If you like I could do this for you - there's likely to be merge issues due to the update to the new I2C API anyway?
Oh, do you mean SSM2602 or WM8731? I also noticed the i2c API updates patch from Jean and I'd like to use the new one
Thanks -Bryan
On Thu, Aug 28, 2008 at 11:46:24AM +0800, Bryan Wu wrote:
On Wed, Aug 27, 2008 at 7:01 PM, Mark Brown broonie@sirena.org.uk wrote:
...as for the SSM2602 it'd be good if it were possible to build a kernel which supports both I2C and SPI. If you like I could do this for you - there's likely to be merge issues due to the update to the new I2C API anyway?
Oh, do you mean SSM2602 or WM8731? I also noticed the i2c API updates patch from Jean and I'd like to use the new one
Sorry, that could've been clearer - I meant wm8731. Obviously if you want to update that too then that would be great but I figured it'd make life easier for you if I were to ensure that one gets merged OK. Which way do you prefer?
At Thu, 28 Aug 2008 11:14:35 +0100, Mark Brown wrote:
On Thu, Aug 28, 2008 at 11:46:24AM +0800, Bryan Wu wrote:
On Wed, Aug 27, 2008 at 7:01 PM, Mark Brown broonie@sirena.org.uk wrote:
...as for the SSM2602 it'd be good if it were possible to build a kernel which supports both I2C and SPI. If you like I could do this for you - there's likely to be merge issues due to the update to the new I2C API anyway?
Oh, do you mean SSM2602 or WM8731? I also noticed the i2c API updates patch from Jean and I'd like to use the new one
Sorry, that could've been clearer - I meant wm8731. Obviously if you want to update that too then that would be great but I figured it'd make life easier for you if I were to ensure that one gets merged OK. Which way do you prefer?
For me, it's easier when they come from you all together ;)
I still postponed to merge Jean's latest patches. Care to check that don't conflict your work and send them together?
thanks,
Takashi
On Thu, Aug 28, 2008 at 02:47:01PM +0200, Takashi Iwai wrote:
I still postponed to merge Jean's latest patches. Care to check that don't conflict your work and send them together?
No problem from my point of view, though all I'll do is construct a patch series with Jean's changes at the start so from that point of view you may as well merge them directly - it doesn't change the work that needs to be done.
We have just started trying this out as an alternative to i2c, and picked out the following:
On Wednesday 27 August 2008 10:39, Bryan Wu wrote:
-#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) && !defined (CONFIG_SND_SOC_WM8731_SPI)
I think this needs extra brackets round the ||'s before the && thus:
+#if (defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)) && !defined (CONFIG_SND_SOC_WM8731_SPI)
in order to force use of SPI when (CONFIG_SND_SOC_WM8731_SPI) and (CONFIG_I2C) are both defined. Or have we misunderstood the intention? This is in 2 other places also in the patch.
Alan
On Mon, 2008-09-01 at 14:52 +0100, Alan Horstmann wrote:
We have just started trying this out as an alternative to i2c, and picked out the following:
On Wednesday 27 August 2008 10:39, Bryan Wu wrote:
-#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) && !defined (CONFIG_SND_SOC_WM8731_SPI)
I think this needs extra brackets round the ||'s before the && thus:
+#if (defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)) && !defined (CONFIG_SND_SOC_WM8731_SPI)
in order to force use of SPI when (CONFIG_SND_SOC_WM8731_SPI) and (CONFIG_I2C) are both defined. Or have we misunderstood the intention? This is in 2 other places also in the patch.
It may be simpler to only have CONFIG_SND_SOC_WM8731_SPI and CONFIG_SND_SOC_WM8731_I2C definitions for all the codec drivers. These would be set by machine Kconfig.
Liam
On Mon, Sep 01, 2008 at 02:52:08PM +0100, Alan Horstmann wrote:
in order to force use of SPI when (CONFIG_SND_SOC_WM8731_SPI) and (CONFIG_I2C) are both defined. Or have we misunderstood the intention? This is in 2 other places also in the patch.
I've reworked the patch to allow a single kernel image to support both I2C and SPI which sidesteps this problem. I'll post this later today.
On Monday 01 September 2008 15:02, you wrote:
On Mon, Sep 01, 2008 at 02:52:08PM +0100, Alan Horstmann wrote:
in order to force use of SPI when (CONFIG_SND_SOC_WM8731_SPI) and (CONFIG_I2C) are both defined. Or have we misunderstood the intention? This is in 2 other places also in the patch.
I've reworked the patch to allow a single kernel image to support both I2C and SPI which sidesteps this problem. I'll post this later today.
I am aware am new to the asoc stuff, and hesitate to comment, but it would seem to me that Liams suggestion:
It may be simpler to only have CONFIG_SND_SOC_WM8731_SPI and CONFIG_SND_SOC_WM8731_I2C definitions for all the codec drivers. These would be set by machine Kconfig.
Liam
is better, since it means one or the other, or both sets of code can be built. Since in most applications the hardware is always wired up on a known bus, it avoids unused code built in. Otherwise we would have to hand edit out sections of code. (CONFIG_I2C) may be set because of other devices on that bus. Does the codec ONLY work in spi master?
Then (CONFIG_SND_SOC_WM8731) is not really needed since it is
CONFIG_SND_SOC_WM8731_I2C || CONFIG_SND_SOC_WM8731_SPI
Essentially, where a hardware device can be connected in different ways needing different code, a config option for each way is needed, rather than being guessed from bus config options.
Alan
On Tue, Sep 02, 2008 at 11:22:23AM +0100, Alan Horstmann wrote:
On Monday 01 September 2008 15:02, you wrote:
I've reworked the patch to allow a single kernel image to support both I2C and SPI which sidesteps this problem. I'll post this later today.
I am aware am new to the asoc stuff, and hesitate to comment, but it would seem to me that Liams suggestion:
It may be simpler to only have CONFIG_SND_SOC_WM8731_SPI and CONFIG_SND_SOC_WM8731_I2C definitions for all the codec drivers. These would be set by machine Kconfig.
is better, since it means one or the other, or both sets of code can be built. Since in most applications the hardware is always wired up on a known bus, it avoids unused code built in. Otherwise we would have to hand edit out sections of code. (CONFIG_I2C) may be set because of other devices on that
Given the very small amount of code involved in attaching the codec to SPI or I2C I wasn't too worried about the additional cost in the driver itself if the kernel already contains all the bus code. That said, I do agree that it could be useful to have ths level of control and I'd certainly ack a patch which added it.
Note that the drivers will still need to support both simultaneously in order to allow support for multiple boards in a single kernel image (which was the main problem with the original patch). There's quite a push to reduce the number of builds required to get coverage on ARM at the minute partly since it is useful for distributions and partly because it facilitates architecture wide development.
bus. Does the codec ONLY work in spi master?
The SPI_MASTER config option refers to the controller rather than the target - if there is nothing able to act as a SPI master then nothing will be able to control the codec.
Then (CONFIG_SND_SOC_WM8731) is not really needed since it is
CONFIG_SND_SOC_WM8731_I2C || CONFIG_SND_SOC_WM8731_SPI
Essentially, where a hardware device can be connected in different ways needing different code, a config option for each way is needed, rather than being guessed from bus config options.
I'm not sure I'd go so far as *needed* - it's useful for modular kernels where both I2C and SPI are availaible but one or the other is not loaded in some configurations so you end up pulling the bus core in.
From: Cliff Cai cliff.cai@analog.com
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Bryan Wu cooloney@kernel.org --- sound/core/pcm_native.c | 8 ++++++++ 1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index c49b9d9..cb202a1 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3391,6 +3391,12 @@ out: } #endif /* CONFIG_SND_SUPPORT_OLD_API */
+unsigned long dummy_get_unmapped_area(struct file *file, unsigned long addr, + unsigned long len, unsigned long pgoff, unsigned long flags) +{ + return 0; +} + /* * Register section */ @@ -3407,6 +3413,7 @@ const struct file_operations snd_pcm_f_ops[2] = { .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, + .get_unmapped_area = dummy_get_unmapped_area, }, { .owner = THIS_MODULE, @@ -3419,5 +3426,6 @@ const struct file_operations snd_pcm_f_ops[2] = { .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, + .get_unmapped_area = dummy_get_unmapped_area, } };
At Wed, 27 Aug 2008 17:39:28 +0800, Bryan Wu wrote:
From: Cliff Cai cliff.cai@analog.com
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Bryan Wu cooloney@kernel.org
sound/core/pcm_native.c | 8 ++++++++ 1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index c49b9d9..cb202a1 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3391,6 +3391,12 @@ out: } #endif /* CONFIG_SND_SUPPORT_OLD_API */
+unsigned long dummy_get_unmapped_area(struct file *file, unsigned long addr,
unsigned long len, unsigned long pgoff, unsigned long flags)
+{
- return 0;
+}
Always zero is confirmed to work for other architectures, too?
/*
- Register section
*/ @@ -3407,6 +3413,7 @@ const struct file_operations snd_pcm_f_ops[2] = { .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync,
}, { .owner = THIS_MODULE,.get_unmapped_area = dummy_get_unmapped_area,
@@ -3419,5 +3426,6 @@ const struct file_operations snd_pcm_f_ops[2] = { .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync,
}.get_unmapped_area = dummy_get_unmapped_area,
};
I don't think adding this dummy get_unmapped_area unconditionally for every driver is good. This overrides the default mm->get_unmaped_area.
Takashi
-----Original Message----- From: Takashi Iwai [mailto:tiwai@suse.de] Sent: Thursday, August 28, 2008 8:45 PM To: Bryan Wu Cc: perex@perex.cz; liam.girdwood@wolfsonmicro.com; broonie@opensource.wolfsonmicro.com; alsa-devel@alsa-project.org; linux-kernel@vger.kernel.org; Cliff Cai Subject: Re: [PATCH 4/4] ALSA: add dummy function to support shared mmap in nommu Blackfin arch
At Wed, 27 Aug 2008 17:39:28 +0800, Bryan Wu wrote:
From: Cliff Cai cliff.cai@analog.com
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Bryan Wu cooloney@kernel.org
sound/core/pcm_native.c | 8 ++++++++ 1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index c49b9d9..cb202a1 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3391,6 +3391,12 @@ out: } #endif /* CONFIG_SND_SUPPORT_OLD_API */
+unsigned long dummy_get_unmapped_area(struct file *file, unsigned
long addr,
unsigned long len, unsigned long
pgoff, unsigned long
+flags) {
- return 0;
+}
Always zero is confirmed to work for other architectures, too?
I'm not sure about this but for non-mmu arch it's enough and harmless.
/*
- Register section
*/ @@ -3407,6 +3413,7 @@ const struct file_operations snd_pcm_f_ops[2] =
{
.compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync,
}, { .owner = THIS_MODULE,.get_unmapped_area = dummy_get_unmapped_area,
@@ -3419,5 +3426,6 @@ const struct file_operations snd_pcm_f_ops[2] =
{
.compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync,
}.get_unmapped_area = dummy_get_unmapped_area,
};
I don't think adding this dummy get_unmapped_area unconditionally for
every driver is good. This overrides the default
mm->get_unmaped_area.
But without this dummy function,shared mmap on nommu arch would fail,refer to validate_mmap_request() in mm/nommu.c. May be we can add a kernel config item to include or not include this function depending on non-mmu or mmu arch.
Cliff
At Tue, 2 Sep 2008 11:16:37 +0800, Cai, Cliff wrote:
@@ -3419,5 +3426,6 @@ const struct file_operations snd_pcm_f_ops[2] =
{
.compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync,
}.get_unmapped_area = dummy_get_unmapped_area,
};
I don't think adding this dummy get_unmapped_area unconditionally for
every driver is good. This overrides the default
mm->get_unmaped_area.
But without this dummy function,shared mmap on nommu arch would fail,refer to validate_mmap_request() in mm/nommu.c.
Yes, I know it's needed for nommu. That's why I wrote "unconditionally" in the above.
May be we can add a kernel config item to include or not include this function depending on non-mmu or mmu arch.
Checking CONFIG_MMU may suffice?
Takashi
On Tue, Sep 2, 2008 at 5:41 PM, Takashi Iwai tiwai@suse.de wrote:
At Tue, 2 Sep 2008 11:16:37 +0800, Cai, Cliff wrote:
@@ -3419,5 +3426,6 @@ const struct file_operations snd_pcm_f_ops[2] =
{
.compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync,
}.get_unmapped_area = dummy_get_unmapped_area,
};
I don't think adding this dummy get_unmapped_area unconditionally for
every driver is good. This overrides the default
mm->get_unmaped_area.
But without this dummy function,shared mmap on nommu arch would fail,refer to validate_mmap_request() in mm/nommu.c.
Yes, I know it's needed for nommu. That's why I wrote "unconditionally" in the above.
May be we can add a kernel config item to include or not include this function depending on non-mmu or mmu arch.
Checking CONFIG_MMU may suffice?
Right, I post a v2 patch including this checking.
Thanks -Bryan
participants (8)
-
Alan Horstmann
-
Bryan Wu
-
Cai, Cliff
-
Jean Delvare
-
Liam Girdwood
-
Mark Brown
-
Mark Brown
-
Takashi Iwai