[alsa-devel] [PATCH] ASoC: add SSM2604 codec driver
From: Cliff Cai cliff.cai@analog.com
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Mike Frysinger vapier@gentoo.org --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ssm2604.c | 643 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ssm2604.h | 92 +++++++ 4 files changed, 741 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/ssm2604.c create mode 100644 sound/soc/codecs/ssm2604.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5da30eb..5db82d2 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C + select SND_SOC_SSM2604 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER @@ -150,6 +151,9 @@ config SND_SOC_SPDIF config SND_SOC_SSM2602 tristate
+config SND_SOC_SSM2604 + tristate + config SND_SOC_STAC9766 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 91429ea..7593eeb 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -16,6 +16,7 @@ snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o +snd-soc-ssm2604-objs := ssm2604.o snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o @@ -81,6 +82,7 @@ obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_SSM2604) += snd-soc-ssm2604.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o diff --git a/sound/soc/codecs/ssm2604.c b/sound/soc/codecs/ssm2604.c new file mode 100644 index 0000000..7522586 --- /dev/null +++ b/sound/soc/codecs/ssm2604.c @@ -0,0 +1,643 @@ +/* + * Driver for ssm2604 sound codec + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#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/slab.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 "ssm2604.h" + +#define SSM2604_VERSION "0.1" + +struct snd_soc_codec_device soc_codec_dev_ssm2604; +static struct snd_soc_codec *ssm2604_codec; +/* codec private data */ +struct ssm2604_priv { + unsigned int sysclk; + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; + struct snd_soc_codec codec; +}; + +/* + * ssm2604 register cache + * We can't read the ssm2604 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 ssm2604_reg[SSM2604_CACHEREGNUM] = { + 0x0017, 0x0017, 0x0000, 0x0000, + 0x0000, 0x000a, 0x0000, 0x0000 +}; + +#define ssm2604_reset(c) snd_soc_write(c, SSM2604_RESET, 0) + +static const char *ssm2604_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum ssm2604_enum[] = { + SOC_ENUM_SINGLE(SSM2604_APDIGI, 1, 4, ssm2604_deemph), +}; + +static const struct snd_kcontrol_new ssm2604_snd_controls[] = { + +SOC_DOUBLE_R("Capture Volume", SSM2604_LINVOL, SSM2604_RINVOL, 0, 31, 0), +SOC_DOUBLE_R("Capture Switch", SSM2604_LINVOL, SSM2604_RINVOL, 7, 1, 1), + +SOC_SINGLE("ADC High Pass Filter Switch", SSM2604_APDIGI, 0, 1, 1), +SOC_SINGLE("Store DC Offset Switch", SSM2604_APDIGI, 4, 1, 0), + +SOC_ENUM("Capture Source", ssm2604_enum[0]), + +SOC_ENUM("Playback De-emphasis", ssm2604_enum[1]), +}; + +/* Output Mixer */ +static const struct snd_kcontrol_new ssm2604_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", SSM2604_APANA, 3, 1, 0), +}; + +static const struct snd_soc_dapm_widget ssm2604_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SSM2604_PWR, 4, 1, + &ssm2604_output_mixer_controls[0], + ARRAY_SIZE(ssm2604_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2604_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2604_PWR, 2, 1), +SND_SOC_DAPM_PGA("Line Input", SSM2604_PWR, 0, 1, NULL, 0), +SND_SOC_DAPM_INPUT("RLINEIN"), +SND_SOC_DAPM_INPUT("LLINEIN"), +}; + +static const struct snd_soc_dapm_route audio_conn[] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + + /* outputs */ + {"ROUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* input mux */ + {"Input Mux", "Line", "Line Input"}, + {"ADC", NULL, "Input Mux"}, + + /* inputs */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, +}; + +static int ssm2604_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ssm2604_dapm_widgets, + ARRAY_SIZE(ssm2604_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_conn, ARRAY_SIZE(audio_conn)); + + 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 i; +} + +static int ssm2604_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + u16 srate; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec); + struct i2c_client *i2c = codec->control_data; + u16 iface = snd_soc_read(codec, SSM2604_IFACE) & 0xfff3; + int i = get_coeff(ssm2604->sysclk, params_rate(params)); + + if (substream == ssm2604->slave_substream) { + dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n"); + return 0; + } + + /*no match is found*/ + if (i == ARRAY_SIZE(coeff_div)) + return -EINVAL; + + srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + snd_soc_write(codec, SSM2604_ACTIVE, 0); + snd_soc_write(codec, SSM2604_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; + } + snd_soc_write(codec, SSM2604_IFACE, iface); + snd_soc_write(codec, SSM2604_ACTIVE, ACTIVE_ACTIVATE_CODEC); + return 0; +} + +static int ssm2604_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec); + struct i2c_client *i2c = codec->control_data; + struct snd_pcm_runtime *master_runtime; + + /* The DAI has shared clocks so if we already have a playback or + * capture going then constrain this substream to match it. + * TODO: the ssm2604 allows pairs of non-matching PB/REC rates + */ + if (ssm2604->master_substream) { + master_runtime = ssm2604->master_substream->runtime; + dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n", + master_runtime->sample_bits, + master_runtime->rate); + + if (master_runtime->rate != 0) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + if (master_runtime->sample_bits != 0) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + ssm2604->slave_substream = substream; + } else + ssm2604->master_substream = substream; + + return 0; +} + +static int ssm2604_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + /* set active */ + snd_soc_write(codec, SSM2604_ACTIVE, ACTIVE_ACTIVATE_CODEC); + + return 0; +} + +static void ssm2604_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec); + + /* deactivate */ + if (!codec->active) + snd_soc_write(codec, SSM2604_ACTIVE, 0); + + if (ssm2604->master_substream == substream) + ssm2604->master_substream = ssm2604->slave_substream; + + ssm2604->slave_substream = NULL; +} + +static int ssm2604_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = snd_soc_read(codec, SSM2604_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE; + if (mute) + snd_soc_write(codec, SSM2604_APDIGI, + mute_reg | APDIGI_ENABLE_DAC_MUTE); + else + snd_soc_write(codec, SSM2604_APDIGI, mute_reg); + return 0; +} + +static int ssm2604_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec); + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + ssm2604->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int ssm2604_set_dai_fmt(struct snd_soc_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 |= 0x0013; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0003; + 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 */ + snd_soc_write(codec, SSM2604_IFACE, iface); + return 0; +} + +static int ssm2604_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = snd_soc_read(codec, SSM2604_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + snd_soc_write(codec, SSM2604_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + snd_soc_write(codec, SSM2604_PWR, reg | PWR_CLK_OUT_PDN); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + snd_soc_write(codec, SSM2604_ACTIVE, 0); + snd_soc_write(codec, SSM2604_PWR, 0xffff); + break; + + } + codec->bias_level = level; + return 0; +} + +#define SSM2604_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define SSM2604_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops ssm2604_dai_ops = { + .startup = ssm2604_startup, + .prepare = ssm2604_pcm_prepare, + .hw_params = ssm2604_hw_params, + .shutdown = ssm2604_shutdown, + .digital_mute = ssm2604_mute, + .set_sysclk = ssm2604_set_dai_sysclk, + .set_fmt = ssm2604_set_dai_fmt, +}; + +struct snd_soc_dai ssm2604_dai = { + .name = "SSM2604", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2604_RATES, + .formats = SSM2604_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2604_RATES, + .formats = SSM2604_FORMATS,}, + .ops = &ssm2604_dai_ops, +}; +EXPORT_SYMBOL_GPL(ssm2604_dai); + +static int ssm2604_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + ssm2604_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ssm2604_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(ssm2604_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + ssm2604_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + ssm2604_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +static int ssm2604_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int reg, ret = 0; + + socdev->card->codec = ssm2604_codec; + codec = ssm2604_codec; + + ssm2604_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "ssm2604: failed to create pcms\n"); + goto pcm_err; + } + /*power on device*/ + snd_soc_write(codec, SSM2604_ACTIVE, 0); + /* set the update bits */ + reg = snd_soc_read(codec, SSM2604_LINVOL); + snd_soc_write(codec, SSM2604_LINVOL, reg | LINVOL_LRIN_BOTH); + reg = snd_soc_read(codec, SSM2604_RINVOL); + snd_soc_write(codec, SSM2604_RINVOL, reg | RINVOL_RLIN_BOTH); + + snd_soc_write(codec, SSM2604_APANA, APANA_SELECT_DAC); + snd_soc_write(codec, SSM2604_PWR, 0); + + snd_soc_add_controls(codec, ssm2604_snd_controls, + ARRAY_SIZE(ssm2604_snd_controls)); + ssm2604_add_widgets(codec); + + return ret; + +pcm_err: + return ret; +} + +/* remove everything here */ +static int ssm2604_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ssm2604 = { + .probe = ssm2604_probe, + .remove = ssm2604_remove, + .suspend = ssm2604_suspend, + .resume = ssm2604_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ssm2604); + +static int ssm2604_register(struct ssm2604_priv *ssm2604, enum snd_soc_control_type control) +{ + struct snd_soc_codec *codec = &ssm2604->codec; + int ret = 0; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->name = "SSM2604"; + codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = ssm2604_set_bias_level; + codec->dai = &ssm2604_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(ssm2604_reg); + codec->reg_cache = kmemdup(ssm2604_reg, sizeof(ssm2604_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&ssm2604_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return ret; +} + +static void ssm2604_unregister(struct ssm2604_priv *ssm2604) +{ + struct snd_soc_codec *codec = &ssm2604->codec; + + ssm2604_set_bias_level(&ssm2604->codec, SND_SOC_BIAS_OFF); + kfree(codec->reg_cache); + snd_soc_unregister_dai(&ssm2604_dai); + snd_soc_unregister_codec(&ssm2604->codec); + kfree(ssm2604); + ssm2604_codec = NULL; +} + +static int ssm2604_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ssm2604_priv *ssm2604; + struct snd_soc_codec *codec; + + ssm2604 = kzalloc(sizeof(struct ssm2604_priv), GFP_KERNEL); + if (ssm2604 == NULL) + return -ENOMEM; + codec = &ssm2604->codec; + snd_soc_codec_set_drvdata(codec, ssm2604); + + i2c_set_clientdata(i2c, ssm2604); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + ssm2604_codec = codec; + + return ssm2604_register(ssm2604, SND_SOC_I2C); +} + +static __devexit int ssm2604_i2c_remove(struct i2c_client *client) +{ + struct ssm2604_priv *ssm2604 = i2c_get_clientdata(client); + ssm2604_unregister(ssm2604); + return 0; +} + +static const struct i2c_device_id ssm2604_i2c_id[] = { + { "ssm2604", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm2604_i2c_id); + +/* corgi i2c codec control layer */ +static struct i2c_driver ssm2604_i2c_driver = { + .driver = { + .name = "ssm2604", + .owner = THIS_MODULE, + }, + .probe = ssm2604_i2c_probe, + .remove = __devexit_p(ssm2604_i2c_remove), + .id_table = ssm2604_i2c_id, +}; + +static int __init ssm2604_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&ssm2604_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register ssm2604 I2C driver: %d\n", + ret); + } + + return ret; +} +module_init(ssm2604_modinit); + +static void __exit ssm2604_exit(void) +{ + i2c_del_driver(&ssm2604_i2c_driver); +} +module_exit(ssm2604_exit); + +MODULE_DESCRIPTION("ASoC SSM2604 driver"); +MODULE_AUTHOR("Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2604.h b/sound/soc/codecs/ssm2604.h new file mode 100644 index 0000000..3b24a08 --- /dev/null +++ b/sound/soc/codecs/ssm2604.h @@ -0,0 +1,92 @@ +/* + * Header for ssm2604 sound codec + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _SSM2604_H +#define _SSM2604_H + +/* SSM2604 Codec Register definitions */ + +#define SSM2604_LINVOL 0x00 +#define SSM2604_RINVOL 0x01 +#define SSM2604_APANA 0x04 +#define SSM2604_APDIGI 0x05 +#define SSM2604_PWR 0x06 +#define SSM2604_IFACE 0x07 +#define SSM2604_SRATE 0x08 +#define SSM2604_ACTIVE 0x09 +#define SSM2604_RESET 0x0f + +/* SSM2604 Codec Register Field definitions + * (Mask value to extract the corresponding Register field) + */ + +/* Left ADC Volume Control (SSM2604_REG_LEFT_ADC_VOL) */ +#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */ +#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */ +#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */ + +/* Right ADC Volume Control (SSM2604_REG_RIGHT_ADC_VOL) */ +#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */ +#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */ +#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */ + + +/* Analogue Audio Path Control (SSM2604_REG_ANALOGUE_PATH) */ +#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */ +#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */ + +/* Digital Audio Path Control (SSM2604_REG_DIGITAL_PATH) */ +#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */ +#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */ +#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */ +#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */ + +/* Power Down Control (SSM2604_REG_POWER) + * (1=Enable PowerDown, 0=Disable PowerDown) + */ +#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */ +#define PWR_ADC_PDN 0x004 /* ADC Power Down */ +#define PWR_DAC_PDN 0x008 /* DAC Power Down */ +#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */ +#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */ +#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */ + +/* Digital Audio Interface Format (SSM2604_REG_DIGITAL_IFACE) */ +#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */ +#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */ +#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */ +#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */ +#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */ +#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */ + +/* Sampling Control (SSM2604_REG_SAMPLING_CTRL) */ +#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */ +#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */ +#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */ +#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */ +#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */ + +/* Active Control (SSM2604_REG_ACTIVE_CTRL) */ +#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */ + +/*********************************************************************/ + +#define SSM2604_CACHEREGNUM 9 + +#define SSM2604_SYSCLK 0 +#define SSM2604_DAI 0 + +struct ssm2604_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai ssm2604_dai; +extern struct snd_soc_codec_device soc_codec_dev_ssm2604; + +#endif
On Sat, Aug 07, 2010 at 01:54:07AM -0400, Mike Frysinger wrote:
From: Cliff Cai cliff.cai@analog.com
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Mike Frysinger vapier@gentoo.org
This needs to be updated to the multi-component branch. There's a few other issues as well.
+#define SSM2604_VERSION "0.1"
Drop this, just track the version through git and kernel releases.
+static const struct soc_enum ssm2604_enum[] = {
- SOC_ENUM_SINGLE(SSM2604_APDIGI, 1, 4, ssm2604_deemph),
+};
Use individual variables for enums rather than stuffing them all in an array. The arrays are just hard to read and error prone...
+SOC_ENUM("Capture Source", ssm2604_enum[0]),
+SOC_ENUM("Playback De-emphasis", ssm2604_enum[1]),
...are you sure this code runs correctly? I see only one element in the array. It's worth checking this in your other drivers as well.
- /* The DAI has shared clocks so if we already have a playback or
* capture going then constrain this substream to match it.
* TODO: the ssm2604 allows pairs of non-matching PB/REC rates
*/
- if (ssm2604->master_substream) {
Set the symmetric_rates flag on the DAI rather than open coding this.
- /* deactivate */
- if (!codec->active)
snd_soc_write(codec, SSM2604_ACTIVE, 0);
This probably wants to be managed by DAPM (possibly a supply widget for the DAC and ADC).
+#define SSM2604_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
SNDRV_PCM_RATE_8000_96000
- snd_soc_write(codec, SSM2604_APANA, APANA_SELECT_DAC);
This could use a little explanation?
+struct ssm2604_setup_data {
- int i2c_bus;
- unsigned short i2c_address;
+};
This is definitely not needed, even with the current code.
On Sat, Aug 7, 2010 at 08:44, Mark Brown wrote:
This needs to be updated to the multi-component branch. There's a few other issues as well.
ive logged a tracker item for this stuff http://blackfin.uclinux.org/gf/tracker/6158 -mike
From: Cliff Cai cliff.cai@analog.com
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Mike Frysinger vapier@gentoo.org --- v2 - update to multi-component - touch up style a bit - drop dead structs - drop rate and sample bits constraints
sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ssm2604.c | 524 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ssm2604.h | 84 +++++++ 4 files changed, 614 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/ssm2604.c create mode 100644 sound/soc/codecs/ssm2604.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 74a4629..7de8d8d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -35,6 +35,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C + select SND_SOC_SSM2604 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER @@ -186,6 +187,9 @@ config SND_SOC_SPDIF config SND_SOC_SSM2602 tristate
+config SND_SOC_SSM2604 + tristate + config SND_SOC_STAC9766 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index cc134ea..8b32b1a 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -22,6 +22,7 @@ snd-soc-pcm3008-objs := pcm3008.o snd-soc-alc5623-objs := alc5623.o snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o +snd-soc-ssm2604-objs := ssm2604.o snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o @@ -103,6 +104,7 @@ obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_SSM2604) += snd-soc-ssm2604.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o diff --git a/sound/soc/codecs/ssm2604.c b/sound/soc/codecs/ssm2604.c new file mode 100644 index 0000000..a01c055 --- /dev/null +++ b/sound/soc/codecs/ssm2604.c @@ -0,0 +1,524 @@ +/* + * Driver for ssm2604 sound codec + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#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/slab.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 "ssm2604.h" + +/* codec private data */ +struct ssm2604_priv { + unsigned int sysclk; + u16 pwr_state; + enum snd_soc_control_type control_type; +}; + +/* + * ssm2604 register cache + * We can't read the ssm2604 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 ssm2604_reg[SSM2604_CACHEREGNUM] = { + 0x0017, 0x0017, 0x0000, 0x0000, + 0x0000, 0x000a, 0x0000, 0x0000 +}; + +#define ssm2604_reset(c) snd_soc_write(c, SSM2604_RESET, 0) + +static const char *ssm2604_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum ssm2604_enum[] = { + SOC_ENUM_SINGLE(SSM2604_APDIGI, 1, 4, ssm2604_deemph), +}; + +static const struct snd_kcontrol_new ssm2604_snd_controls[] = { + +SOC_DOUBLE_R("Capture Volume", SSM2604_LINVOL, SSM2604_RINVOL, 0, 31, 0), +SOC_DOUBLE_R("Capture Switch", SSM2604_LINVOL, SSM2604_RINVOL, 7, 1, 1), + +SOC_SINGLE("ADC High Pass Filter Switch", SSM2604_APDIGI, 0, 1, 1), +SOC_SINGLE("Store DC Offset Switch", SSM2604_APDIGI, 4, 1, 0), + +SOC_ENUM("Capture Source", ssm2604_enum[0]), + +SOC_ENUM("Playback De-emphasis", ssm2604_enum[1]), +}; + +/* Output Mixer */ +static const struct snd_kcontrol_new ssm2604_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", SSM2604_APANA, 3, 1, 0), +}; + +static const struct snd_soc_dapm_widget ssm2604_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SSM2604_PWR, 4, 1, + &ssm2604_output_mixer_controls[0], + ARRAY_SIZE(ssm2604_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2604_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2604_PWR, 2, 1), +SND_SOC_DAPM_PGA("Line Input", SSM2604_PWR, 0, 1, NULL, 0), +SND_SOC_DAPM_INPUT("RLINEIN"), +SND_SOC_DAPM_INPUT("LLINEIN"), +}; + +static const struct snd_soc_dapm_route audio_conn[] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + + /* outputs */ + {"ROUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* input mux */ + {"Input Mux", "Line", "Line Input"}, + {"ADC", NULL, "Input Mux"}, + + /* inputs */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, +}; + +static int ssm2604_add_widgets(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + snd_soc_dapm_new_controls(dapm, ssm2604_dapm_widgets, + ARRAY_SIZE(ssm2604_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, audio_conn, ARRAY_SIZE(audio_conn)); + + 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 i; +} + +static int ssm2604_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + u16 srate; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec); + u16 iface = snd_soc_read(codec, SSM2604_IFACE) & 0xfff3; + int i = get_coeff(ssm2604->sysclk, params_rate(params)); + + /*no match is found*/ + if (i == ARRAY_SIZE(coeff_div)) + return -EINVAL; + + srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + snd_soc_write(codec, SSM2604_ACTIVE, 0); + snd_soc_write(codec, SSM2604_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; + } + snd_soc_write(codec, SSM2604_IFACE, iface); + snd_soc_write(codec, SSM2604_ACTIVE, ACTIVE_ACTIVATE_CODEC); + return 0; +} + +static int ssm2604_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + /* set active */ + snd_soc_write(codec, SSM2604_ACTIVE, ACTIVE_ACTIVATE_CODEC); + + return 0; +} + +static void ssm2604_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + + /* deactivate */ + if (!codec->active) + snd_soc_write(codec, SSM2604_ACTIVE, 0); +} + +static int ssm2604_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = snd_soc_read(codec, SSM2604_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE; + if (mute) + snd_soc_write(codec, SSM2604_APDIGI, + mute_reg | APDIGI_ENABLE_DAC_MUTE); + else + snd_soc_write(codec, SSM2604_APDIGI, mute_reg); + return 0; +} + +static int ssm2604_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec); + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + ssm2604->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int ssm2604_set_dai_fmt(struct snd_soc_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 |= 0x0013; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0003; + 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 */ + snd_soc_write(codec, SSM2604_IFACE, iface); + return 0; +} + +static int ssm2604_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = snd_soc_read(codec, SSM2604_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + snd_soc_write(codec, SSM2604_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + snd_soc_write(codec, SSM2604_PWR, reg | PWR_CLK_OUT_PDN); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + snd_soc_write(codec, SSM2604_ACTIVE, 0); + snd_soc_write(codec, SSM2604_PWR, 0xffff); + break; + + } + codec->dapm.bias_level = level; + return 0; +} + +#define SSM2604_RATES SNDRV_PCM_RATE_8000_96000 + +#define SSM2604_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops ssm2604_dai_ops = { + .prepare = ssm2604_pcm_prepare, + .hw_params = ssm2604_hw_params, + .shutdown = ssm2604_shutdown, + .digital_mute = ssm2604_mute, + .set_sysclk = ssm2604_set_dai_sysclk, + .set_fmt = ssm2604_set_dai_fmt, +}; + +static struct snd_soc_dai_driver ssm2604_dai = { + .name = "ssm2604-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2604_RATES, + .formats = SSM2604_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2604_RATES, + .formats = SSM2604_FORMATS,}, + .ops = &ssm2604_dai_ops, +}; + +static int ssm2604_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + struct ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec); + + ssm2604->pwr_state = snd_soc_read(codec, SSM2604_PWR); + ssm2604_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ssm2604_resume(struct snd_soc_codec *codec) +{ + struct ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec); + int i; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(ssm2604_reg); i++) + snd_soc_write(codec, i, cache[i]); + + ssm2604_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + snd_soc_write(codec, SSM2604_PWR, ssm2604->pwr_state); + + return 0; +} + +static int ssm2604_probe(struct snd_soc_codec *codec) +{ + struct ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec); + int reg, ret = 0; + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, ssm2604->control_type); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + ret = ssm2604_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset: %d\n", ret); + return ret; + } + + /*power on device*/ + snd_soc_write(codec, SSM2604_ACTIVE, 0); + /* set the update bits */ + reg = snd_soc_read(codec, SSM2604_LINVOL); + snd_soc_write(codec, SSM2604_LINVOL, reg | LINVOL_LRIN_BOTH); + reg = snd_soc_read(codec, SSM2604_RINVOL); + snd_soc_write(codec, SSM2604_RINVOL, reg | RINVOL_RLIN_BOTH); + + snd_soc_write(codec, SSM2604_APANA, APANA_SELECT_DAC); + snd_soc_write(codec, SSM2604_PWR, 0); + + snd_soc_add_controls(codec, ssm2604_snd_controls, + ARRAY_SIZE(ssm2604_snd_controls)); + ssm2604_add_widgets(codec); + + return 0; +} + +/* remove everything here */ +static int ssm2604_remove(struct snd_soc_codec *codec) +{ + ssm2604_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_ssm2604 = { + .probe = ssm2604_probe, + .remove = ssm2604_remove, + .suspend = ssm2604_suspend, + .resume = ssm2604_resume, + .set_bias_level = ssm2604_set_bias_level, + .reg_cache_size = sizeof(ssm2604_reg), + .reg_word_size = sizeof(u16), + .reg_cache_default = ssm2604_reg, +}; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static int ssm2604_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ssm2604_priv *ssm2604; + int ret; + + ssm2604 = kzalloc(sizeof(struct ssm2604_priv), GFP_KERNEL); + if (ssm2604 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, ssm2604); + ssm2604->control_type = SND_SOC_I2C; + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_ssm2604, &ssm2604_dai, 1); + if (ret < 0) + kfree(ssm2604); + return ret; +} + +static int ssm2604_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id ssm2604_i2c_id[] = { + { "ssm2604", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm2604_i2c_id); + +/* corgi i2c codec control layer */ +static struct i2c_driver ssm2604_i2c_driver = { + .driver = { + .name = "ssm2604-codec", + .owner = THIS_MODULE, + }, + .probe = ssm2604_i2c_probe, + .remove = ssm2604_i2c_remove, + .id_table = ssm2604_i2c_id, +}; +#endif + +static int __init ssm2604_modinit(void) +{ + int ret = 0; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&ssm2604_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register ssm2604 I2C driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(ssm2604_modinit); + +static void __exit ssm2604_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&ssm2604_i2c_driver); +#endif +} +module_exit(ssm2604_exit); + +MODULE_DESCRIPTION("ASoC SSM2604 driver"); +MODULE_AUTHOR("Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2604.h b/sound/soc/codecs/ssm2604.h new file mode 100644 index 0000000..410c0dc --- /dev/null +++ b/sound/soc/codecs/ssm2604.h @@ -0,0 +1,84 @@ +/* + * Header for ssm2604 sound codec + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _SSM2604_H +#define _SSM2604_H + +/* SSM2604 Codec Register definitions */ + +#define SSM2604_LINVOL 0x00 +#define SSM2604_RINVOL 0x01 +#define SSM2604_APANA 0x04 +#define SSM2604_APDIGI 0x05 +#define SSM2604_PWR 0x06 +#define SSM2604_IFACE 0x07 +#define SSM2604_SRATE 0x08 +#define SSM2604_ACTIVE 0x09 +#define SSM2604_RESET 0x0f + +/* SSM2604 Codec Register Field definitions + * (Mask value to extract the corresponding Register field) + */ + +/* Left ADC Volume Control (SSM2604_REG_LEFT_ADC_VOL) */ +#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */ +#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */ +#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */ + +/* Right ADC Volume Control (SSM2604_REG_RIGHT_ADC_VOL) */ +#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */ +#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */ +#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */ + + +/* Analogue Audio Path Control (SSM2604_REG_ANALOGUE_PATH) */ +#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */ +#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */ + +/* Digital Audio Path Control (SSM2604_REG_DIGITAL_PATH) */ +#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */ +#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */ +#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */ +#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */ + +/* Power Down Control (SSM2604_REG_POWER) + * (1=Enable PowerDown, 0=Disable PowerDown) + */ +#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */ +#define PWR_ADC_PDN 0x004 /* ADC Power Down */ +#define PWR_DAC_PDN 0x008 /* DAC Power Down */ +#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */ +#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */ +#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */ + +/* Digital Audio Interface Format (SSM2604_REG_DIGITAL_IFACE) */ +#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */ +#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */ +#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */ +#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */ +#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */ +#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */ + +/* Sampling Control (SSM2604_REG_SAMPLING_CTRL) */ +#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */ +#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */ +#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */ +#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */ +#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */ + +/* Active Control (SSM2604_REG_ACTIVE_CTRL) */ +#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */ + +/*********************************************************************/ + +#define SSM2604_CACHEREGNUM 9 + +#define SSM2604_SYSCLK 0 +#define SSM2604_DAI 0 + +#endif
On Sat, Mar 26, 2011 at 05:07:20AM -0400, Mike Frysinger wrote:
From: Cliff Cai cliff.cai@analog.com
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Mike Frysinger vapier@gentoo.org
This mostly looks pretty good, a few issues below many of which are style things - the namespacing one is the major one that's externally visible.
+static int ssm2604_add_widgets(struct snd_soc_codec *codec) +{
- struct snd_soc_dapm_context *dapm = &codec->dapm;
- snd_soc_dapm_new_controls(dapm, ssm2604_dapm_widgets,
ARRAY_SIZE(ssm2604_dapm_widgets));
- snd_soc_dapm_add_routes(dapm, audio_conn, ARRAY_SIZE(audio_conn));
This should really use the driver data based initialisation that was added during the last development cycle.
+static int ssm2604_pcm_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->codec;
- /* set active */
- snd_soc_write(codec, SSM2604_ACTIVE, ACTIVE_ACTIVATE_CODEC);
This still looks wrong - it won't handle simultaneous playback and record properly (stopping one will stop the other) for example. If you were to make this register bit a DAPM supply for the DAC and ADC then that'd do the right thing.
If the device can't support simultaneous playback and record there should be constraints set up telling the application layer about that.
- case SND_SOC_BIAS_STANDBY:
/* everything off except vref/vmid, */
snd_soc_write(codec, SSM2604_PWR, reg | PWR_CLK_OUT_PDN);
break;
- case SND_SOC_BIAS_OFF:
/* everything off, dac mute, inactive */
These comments have been cut'n'pasted and are I suspect inaccurate.
- /* Sync reg_cache with the hardware */
- for (i = 0; i < ARRAY_SIZE(ssm2604_reg); i++)
snd_soc_write(codec, i, cache[i]);
There's snd_soc_cache_sync() which will do this for you. It'll also do additional stuff like suppress writes of default values which will speed up resume a bit.
- ssm2604_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- snd_soc_write(codec, SSM2604_PWR, ssm2604->pwr_state);
I'm not sure what the pwr_state stuff is doing but shouldn't it be handled by either DAPM or the bias level functions?
+/* corgi i2c codec control layer */ +static struct i2c_driver ssm2604_i2c_driver = {
Another cut'n'paste comment here.
- .driver = {
.name = "ssm2604-codec",
.owner = THIS_MODULE,
Drop the -codec from the name, it's not needed.
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
- ret = i2c_add_driver(&ssm2604_i2c_driver);
- if (ret != 0) {
printk(KERN_ERR "Failed to register ssm2604 I2C driver: %d\n",
ret);
- }
+#endif
If the device only supports I2C then there's no need for the ifdefs.
+/* Left ADC Volume Control (SSM2604_REG_LEFT_ADC_VOL) */ +#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */ +#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */ +#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */
Namespace any constants exported in the header - either do that or move these and the similar constants into the driver code.
On Sat, Mar 26, 2011 at 08:12, Mark Brown wrote:
On Sat, Mar 26, 2011 at 05:07:20AM -0400, Mike Frysinger wrote:
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
- ret = i2c_add_driver(&ssm2604_i2c_driver);
- if (ret != 0) {
- printk(KERN_ERR "Failed to register ssm2604 I2C driver: %d\n",
- ret);
- }
+#endif
If the device only supports I2C then there's no need for the ifdefs.
i thought in the past you wanted these things because you wanted to be able to build codecs regardless of the bus support (for testing iirc?) -mike
On Sun, Mar 27, 2011 at 12:11:54AM -0400, Mike Frysinger wrote:
On Sat, Mar 26, 2011 at 08:12, Mark Brown wrote:
If the device only supports I2C then there's no need for the ifdefs.
i thought in the past you wanted these things because you wanted to be able to build codecs regardless of the bus support (for testing iirc?)
That has never been the case, SND_SOC_ALL_CODECS has always built things only if there is a bus for them. If you support mulitple busses they should be conditional so that you only need one of them to build but if there's only one supported this is not relevant.
participants (3)
-
Mark Brown
-
Mike Frysinger
-
Mike Frysinger