[alsa-devel] [PATCH 01/14] ASoC: Update Poodle to current ASoC API
From: Dmitry Baryshkov dbaryshkov@gmail.com
Signed-off-by: Dmitry Baryshkov dbaryshkov@gmail.com Cc: Richard Purdie rpurdie@rpsys.net Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- This is a compile fix and it would therefore be good if it could be considered for merge into 2.6.27.
sound/soc/pxa/poodle.c | 8 ++------ 1 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c index 65a4e9a..d968cf7 100644 --- a/sound/soc/pxa/poodle.c +++ b/sound/soc/pxa/poodle.c @@ -85,17 +85,13 @@ static int poodle_startup(struct snd_pcm_substream *substream) }
/* we need to unmute the HP at shutdown as the mute burns power on poodle */ -static int poodle_shutdown(struct snd_pcm_substream *substream) +static void poodle_shutdown(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->socdev->codec; - /* set = unmute headphone */ locomo_gpio_write(&poodle_locomo_device.dev, POODLE_LOCOMO_GPIO_MUTE_L, 1); locomo_gpio_write(&poodle_locomo_device.dev, POODLE_LOCOMO_GPIO_MUTE_R, 1); - return 0; }
static int poodle_hw_params(struct snd_pcm_substream *substream, @@ -232,7 +228,7 @@ static const struct soc_enum poodle_enum[] = { SOC_ENUM_SINGLE_EXT(2, spk_function), };
-static const snd_kcontrol_new_t wm8731_poodle_controls[] = { +static const struct snd_kcontrol_new wm8731_poodle_controls[] = { SOC_ENUM_EXT("Jack Function", poodle_enum[0], poodle_get_jack, poodle_set_jack), SOC_ENUM_EXT("Speaker Function", poodle_enum[1], poodle_get_spk,
dapm_reg_event() is used by devices using SND_SOC_DAPM_REG() so needs to be exported to support building them as modules and prototyped to avoid sparse warnings and potential build issues.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc-dapm.h | 3 +++ sound/soc/soc-dapm.c | 1 + 2 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 3030fdc..c1b26fc 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -202,6 +202,9 @@ struct snd_soc_dapm_path; struct snd_soc_dapm_pin; struct snd_soc_dapm_route;
+int dapm_reg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + /* dapm controls */ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 820347c..f9d100b 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -470,6 +470,7 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w,
return 0; } +EXPORT_SYMBOL_GPL(dapm_reg_event);
/* * Scan each dapm widget for complete audio path.
From: Dmitry Baryshkov dbaryshkov@gmail.com
Signed-off-by: Dmitry Baryshkov dbaryshkov@gmail.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/pxa/pxa2xx-pcm.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index 2df03ee..45ac468 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -330,7 +330,7 @@ static void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
static u64 pxa2xx_pcm_dmamask = DMA_32BIT_MASK;
-int pxa2xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, +static int pxa2xx_soc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { int ret = 0; @@ -360,7 +360,7 @@ int pxa2xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_soc_platform pxa2xx_soc_platform = { .name = "pxa2xx-audio", .pcm_ops = &pxa2xx_pcm_ops, - .pcm_new = pxa2xx_pcm_new, + .pcm_new = pxa2xx_soc_pcm_new, .pcm_free = pxa2xx_pcm_free_dma_buffers, }; EXPORT_SYMBOL_GPL(pxa2xx_soc_platform);
Some codecs have unusual features in their register maps such as very large registers representing arrays of coefficients. Support these codecs in the register cache sysfs file by allowing them to provide a function register_display() overriding the default output for register contents.
Also ensure that we don't overflow PAGE_SIZE while writing out the register dump.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc.h | 2 ++ sound/soc/soc-core.c | 26 +++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index 1890d87..2ce530e 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -410,6 +410,8 @@ struct snd_soc_codec { void *control_data; /* codec control (i2c/3wire) data */ unsigned int (*read)(struct snd_soc_codec *, unsigned int); int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); + int (*display_register)(struct snd_soc_codec *, char *, + size_t, unsigned int); hw_write_t hw_write; hw_read_t hw_read; void *reg_cache; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 83f1190..5d3bf73 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -970,9 +970,29 @@ static ssize_t codec_reg_show(struct device *dev, step = codec->reg_cache_step;
count += sprintf(buf, "%s registers\n", codec->name); - for (i = 0; i < codec->reg_cache_size; i += step) - count += sprintf(buf + count, "%2x: %4x\n", i, - codec->read(codec, i)); + for (i = 0; i < codec->reg_cache_size; i += step) { + count += sprintf(buf + count, "%2x: ", i); + if (count >= PAGE_SIZE - 1) + break; + + if (codec->display_register) + count += codec->display_register(codec, buf + count, + PAGE_SIZE - count, i); + else + count += snprintf(buf + count, PAGE_SIZE - count, + "%4x", codec->read(codec, i)); + + if (count >= PAGE_SIZE - 1) + break; + + count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + if (count >= PAGE_SIZE - 1) + break; + } + + /* Truncate count; min() would cause a warning */ + if (count >= PAGE_SIZE) + count = PAGE_SIZE - 1;
return count; }
From: Jon Smirl jonsmirl@gmail.com
Convert bitfields in ASoC into full int width. This is a simple mechanical conversion. Two places in the DAPM code were fixed to properly use mask.
Signed-off-by: Jon Smirl jonsmirl@gmail.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc.h | 50 ++++++++++++++++++----------- sound/soc/soc-core.c | 86 ++++++++++++++++++++++++++++++-------------------- sound/soc/soc-dapm.c | 52 ++++++++++++++++++------------ 3 files changed, 114 insertions(+), 74 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index 2ce530e..e647a34 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -26,10 +26,12 @@ /* * Convenience kcontrol builders */ -#define SOC_SINGLE_VALUE(reg, shift, max, invert) ((reg) | ((shift) << 8) |\ - ((shift) << 12) | ((max) << 16) | ((invert) << 24)) -#define SOC_SINGLE_VALUE_EXT(reg, max, invert) ((reg) | ((max) << 16) |\ - ((invert) << 31)) +#define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) \ + ((unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg, .shift = xshift, .max = xmax, .invert = xinvert}) +#define SOC_SINGLE_VALUE_EXT(xreg, xmax, xinvert) \ + ((unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg, .max = xmax, .invert = xinvert}) #define SOC_SINGLE(xname, reg, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ @@ -43,45 +45,49 @@ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ .put = snd_soc_put_volsw, \ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } -#define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \ +#define SOC_DOUBLE(xname, xreg, shift_left, shift_right, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ .put = snd_soc_put_volsw, \ - .private_value = (reg) | ((shift_left) << 8) | \ - ((shift_right) << 12) | ((max) << 16) | ((invert) << 24) } -#define SOC_DOUBLE_R(xname, reg_left, reg_right, shift, max, invert) \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg, .shift = shift_left, .rshift = shift_right, \ + .max = xmax, .invert = xinvert} } +#define SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .info = snd_soc_info_volsw_2r, \ .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \ - .private_value = (reg_left) | ((shift) << 8) | \ - ((max) << 12) | ((invert) << 20) | ((reg_right) << 24) } -#define SOC_DOUBLE_TLV(xname, reg, shift_left, shift_right, max, invert, tlv_array) \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ + .max = xmax, .invert = xinvert} } +#define SOC_DOUBLE_TLV(xname, xreg, shift_left, shift_right, xmax, xinvert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ .put = snd_soc_put_volsw, \ - .private_value = (reg) | ((shift_left) << 8) | \ - ((shift_right) << 12) | ((max) << 16) | ((invert) << 24) } -#define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, shift, max, invert, tlv_array) \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg, .shift = shift_left, .rshift = shift_right,\ + .max = xmax, .invert = xinvert} } +#define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw_2r, \ .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \ - .private_value = (reg_left) | ((shift) << 8) | \ - ((max) << 12) | ((invert) << 20) | ((reg_right) << 24) } -#define SOC_DOUBLE_S8_TLV(xname, reg, min, max, tlv_array) \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ + .max = xmax, .invert = xinvert} } +#define SOC_DOUBLE_S8_TLV(xname, xreg, xmin, xmax, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw_s8, .get = snd_soc_get_volsw_s8, \ .put = snd_soc_put_volsw_s8, \ - .private_value = (reg) | (((signed char)max) << 16) | \ - (((signed char)min) << 24) } + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg, .min = xmin, .max = xmax} } #define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, xtexts) \ { .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ .mask = xmask, .texts = xtexts } @@ -518,6 +524,12 @@ struct snd_soc_pcm_runtime { struct snd_soc_device *socdev; };
+/* mixer control */ +struct soc_mixer_control { + int min, max; + uint reg, rreg, shift, rshift, invert; +}; + /* enumerated kcontrol */ struct soc_enum { unsigned short reg; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 5d3bf73..4b94f1f 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1454,9 +1454,11 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext); int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int max = (kcontrol->private_value >> 16) & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + uint shift = mc->min; + uint rshift = mc->rshift;
if (max == 1) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1482,13 +1484,15 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw); int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 24) & 0x01; + uint reg = mc->reg; + uint shift = mc->shift; + uint rshift = mc->rshift; + int max = mc->max; + uint mask = (1 << fls(max)) - 1; + uint invert = mc->invert;
ucontrol->value.integer.value[0] = (snd_soc_read(codec, reg) >> shift) & mask; @@ -1519,13 +1523,15 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw); int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 24) & 0x01; + uint reg = mc->reg; + uint shift = mc->shift; + uint rshift = mc->rshift; + int max = mc->max; + uint mask = (1 << fls(max)) - 1; + uint invert = mc->invert; unsigned short val, val2, val_mask;
val = (ucontrol->value.integer.value[0] & mask); @@ -1557,7 +1563,9 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw); int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int max = (kcontrol->private_value >> 12) & 0xff; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max;
if (max == 1) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1583,13 +1591,15 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r); int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int reg2 = (kcontrol->private_value >> 24) & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int max = (kcontrol->private_value >> 12) & 0xff; - int mask = (1<<fls(max))-1; - int invert = (kcontrol->private_value >> 20) & 0x01; + uint reg = mc->reg; + uint reg2 = mc->rreg; + uint shift = mc->shift; + int max = mc->max; + uint mask = (1<<fls(max))-1; + uint invert = mc->invert;
ucontrol->value.integer.value[0] = (snd_soc_read(codec, reg) >> shift) & mask; @@ -1618,13 +1628,15 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r); int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int reg2 = (kcontrol->private_value >> 24) & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int max = (kcontrol->private_value >> 12) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 20) & 0x01; + uint reg = mc->reg; + uint reg2 = mc->rreg; + uint shift = mc->shift; + int max = mc->max; + uint mask = (1 << fls(max)) - 1; + uint invert = mc->invert; int err; unsigned short val, val2, val_mask;
@@ -1661,8 +1673,10 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r); int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int max = (signed char)((kcontrol->private_value >> 16) & 0xff); - int min = (signed char)((kcontrol->private_value >> 24) & 0xff); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + int min = mc->min;
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2; @@ -1684,9 +1698,11 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8); int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int min = (signed char)((kcontrol->private_value >> 24) & 0xff); + uint reg = mc->reg; + int min = mc->min; int val = snd_soc_read(codec, reg);
ucontrol->value.integer.value[0] = @@ -1709,9 +1725,11 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw_s8); int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int min = (signed char)((kcontrol->private_value >> 24) & 0xff); + uint reg = mc->reg; + int min = mc->min; unsigned short val;
val = (ucontrol->value.integer.value[0]+min) & 0xff; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index f9d100b..bbdca0d 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -104,10 +104,13 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, case snd_soc_dapm_switch: case snd_soc_dapm_mixer: { int val; - int reg = w->kcontrols[i].private_value & 0xff; - int shift = (w->kcontrols[i].private_value >> 8) & 0x0f; - int mask = (w->kcontrols[i].private_value >> 16) & 0xff; - int invert = (w->kcontrols[i].private_value >> 24) & 0x01; + struct soc_mixer_control *mc = (struct soc_mixer_control *) + w->kcontrols[i].private_value; + uint reg = mc->reg; + uint shift = mc->shift; + int max = mc->max; + uint mask = (1 << fls(max)) - 1; + uint invert = mc->invert;
val = snd_soc_read(w->codec, reg); val = (val >> shift) & mask; @@ -247,16 +250,19 @@ static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power) return 0;
if (widget->num_kcontrols && k) { - int reg = k->private_value & 0xff; - int shift = (k->private_value >> 8) & 0x0f; - int mask = (k->private_value >> 16) & 0xff; - int invert = (k->private_value >> 24) & 0x01; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)k->private_value; + uint reg = mc->reg; + uint shift = mc->shift; + int max = mc->max; + uint mask = (1 << fls(max)) - 1; + uint invert = mc->invert;
if (power) { int i; /* power up has happended, increase volume to last level */ if (invert) { - for (i = mask; i > widget->saved_value; i--) + for (i = max; i > widget->saved_value; i--) snd_soc_update_bits(widget->codec, reg, mask, i); } else { for (i = 0; i < widget->saved_value; i++) @@ -1133,12 +1139,14 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int invert = (kcontrol->private_value >> 24) & 0x01; - int mask = (1 << fls(max)) - 1; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + uint reg = mc->reg; + uint shift = mc->shift; + uint rshift = mc->rshift; + int max = mc->max; + uint invert = mc->invert; + uint mask = (1 << fls(max)) - 1;
/* return the saved value if we are powered down */ if (widget->id == snd_soc_dapm_pga && !widget->power) { @@ -1176,12 +1184,14 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 24) & 0x01; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + uint reg = mc->reg; + uint shift = mc->shift; + uint rshift = mc->rshift; + int max = mc->max; + uint mask = (1 << fls(max)) - 1; + uint invert = mc->invert; unsigned short val, val2, val_mask; int ret;
From: Jon Smirl jonsmirl@gmail.com
Most of the ASoC controls refer to the maximum value that can be set for a control as mask but there is no actual requirement for all bits to be set at the highest possible value making the name mask misleading. Change the code to use max instead.
Signed-off-by: Jon Smirl jonsmirl@gmail.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc.h | 22 +++++++++++----------- sound/soc/soc-core.c | 20 ++++++++++---------- sound/soc/soc-dapm.c | 14 +++++++------- 3 files changed, 28 insertions(+), 28 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index e647a34..5ca231f 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -88,25 +88,25 @@ .put = snd_soc_put_volsw_s8, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .min = xmin, .max = xmax} } -#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, xtexts) \ +#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \ { .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ - .mask = xmask, .texts = xtexts } -#define SOC_ENUM_SINGLE(xreg, xshift, xmask, xtexts) \ - SOC_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xtexts) -#define SOC_ENUM_SINGLE_EXT(xmask, xtexts) \ -{ .mask = xmask, .texts = xtexts } + .max = xmax, .texts = xtexts } +#define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts) \ + SOC_ENUM_DOUBLE(xreg, xshift, xshift, xmax, xtexts) +#define SOC_ENUM_SINGLE_EXT(xmax, xtexts) \ +{ .max = xmax, .texts = xtexts } #define SOC_ENUM(xname, xenum) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\ .info = snd_soc_info_enum_double, \ .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \ .private_value = (unsigned long)&xenum } -#define SOC_SINGLE_EXT(xname, xreg, xshift, xmask, xinvert,\ +#define SOC_SINGLE_EXT(xname, xreg, xshift, xmax, xinvert,\ xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, \ .get = xhandler_get, .put = xhandler_put, \ - .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmask, xinvert) } -#define SOC_SINGLE_EXT_TLV(xname, xreg, xshift, xmask, xinvert,\ + .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) } +#define SOC_SINGLE_EXT_TLV(xname, xreg, xshift, xmax, xinvert,\ xhandler_get, xhandler_put, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ @@ -114,7 +114,7 @@ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw, \ .get = xhandler_get, .put = xhandler_put, \ - .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmask, xinvert) } + .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) } #define SOC_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_bool_ext, \ @@ -536,7 +536,7 @@ struct soc_enum { unsigned short reg2; unsigned char shift_l; unsigned char shift_r; - unsigned int mask; + unsigned int max; const char **texts; void *dapm; }; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 4b94f1f..11a881c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1316,10 +1316,10 @@ int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = e->shift_l == e->shift_r ? 1 : 2; - uinfo->value.enumerated.items = e->mask; + uinfo->value.enumerated.items = e->max;
- if (uinfo->value.enumerated.item > e->mask - 1) - uinfo->value.enumerated.item = e->mask - 1; + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, e->texts[uinfo->value.enumerated.item]); return 0; @@ -1342,7 +1342,7 @@ int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned short val, bitmask;
- for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; val = snd_soc_read(codec, e->reg); ucontrol->value.enumerated.item[0] @@ -1372,14 +1372,14 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, unsigned short val; unsigned short mask, bitmask;
- for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; - if (ucontrol->value.enumerated.item[0] > e->mask - 1) + if (ucontrol->value.enumerated.item[0] > e->max - 1) return -EINVAL; val = ucontrol->value.enumerated.item[0] << e->shift_l; mask = (bitmask - 1) << e->shift_l; if (e->shift_l != e->shift_r) { - if (ucontrol->value.enumerated.item[1] > e->mask - 1) + if (ucontrol->value.enumerated.item[1] > e->max - 1) return -EINVAL; val |= ucontrol->value.enumerated.item[1] << e->shift_r; mask |= (bitmask - 1) << e->shift_r; @@ -1406,10 +1406,10 @@ int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol,
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; - uinfo->value.enumerated.items = e->mask; + uinfo->value.enumerated.items = e->max;
- if (uinfo->value.enumerated.item > e->mask - 1) - uinfo->value.enumerated.item = e->mask - 1; + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, e->texts[uinfo->value.enumerated.item]); return 0; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index bbdca0d..f08be8a 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -125,13 +125,13 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, struct soc_enum *e = (struct soc_enum *)w->kcontrols[i].private_value; int val, item, bitmask;
- for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; val = snd_soc_read(w->codec, e->reg); item = (val >> e->shift_l) & (bitmask - 1);
p->connect = 0; - for (i = 0; i < e->mask; i++) { + for (i = 0; i < e->max; i++) { if (!(strcmp(p->name, e->texts[i])) && item == i) p->connect = 1; } @@ -168,7 +168,7 @@ static int dapm_connect_mux(struct snd_soc_codec *codec, struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; int i;
- for (i = 0; i < e->mask; i++) { + for (i = 0; i < e->max; i++) { if (!(strcmp(control_name, e->texts[i]))) { list_add(&path->list, &codec->dapm_paths); list_add(&path->list_sink, &dest->sources); @@ -1258,7 +1258,7 @@ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned short val, bitmask;
- for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; val = snd_soc_read(widget->codec, e->reg); ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1); @@ -1288,15 +1288,15 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, unsigned short mask, bitmask; int ret = 0;
- for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; - if (ucontrol->value.enumerated.item[0] > e->mask - 1) + if (ucontrol->value.enumerated.item[0] > e->max - 1) return -EINVAL; mux = ucontrol->value.enumerated.item[0]; val = mux << e->shift_l; mask = (bitmask - 1) << e->shift_l; if (e->shift_l != e->shift_r) { - if (ucontrol->value.enumerated.item[1] > e->mask - 1) + if (ucontrol->value.enumerated.item[1] > e->max - 1) return -EINVAL; val |= ucontrol->value.enumerated.item[1] << e->shift_r; mask |= (bitmask - 1) << e->shift_r;
From: Grant Likely grant.likely@secretlab.ca
Simple utility layer for creating ASoC machine instances based on data in the OpenFirmware device tree. OF aware platform drivers and codec drivers register themselves with this framework and the framework automatically instantiates a machine driver. At the moment, the driver is not very capable and it is expected to be extended as more features are needed for specifying the configuration in the device tree.
This is most likely temporary glue code to work around limitations in the ASoC v1 framework. When v2 is merged, most of this driver will need to be reworked.
Signed-off-by: Grant Likely grant.likely@secretlab.ca Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc-of-simple.h | 21 +++++ sound/soc/fsl/Kconfig | 3 + sound/soc/fsl/Makefile | 3 + sound/soc/fsl/soc-of-simple.c | 171 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 0 deletions(-) create mode 100644 include/sound/soc-of-simple.h create mode 100644 sound/soc/fsl/soc-of-simple.c
diff --git a/include/sound/soc-of-simple.h b/include/sound/soc-of-simple.h new file mode 100644 index 0000000..696fc51 --- /dev/null +++ b/include/sound/soc-of-simple.h @@ -0,0 +1,21 @@ +/* + * OF helpers for ALSA SoC + * + * Copyright (C) 2008, Secret Lab Technologies Ltd. + */ + +#ifndef _INCLUDE_SOC_OF_H_ +#define _INCLUDE_SOC_OF_H_ + +#include <linux/of.h> +#include <sound/soc.h> + +int of_snd_soc_register_codec(struct snd_soc_codec_device *codec_dev, + void *codec_data, struct snd_soc_dai *dai, + struct device_node *node); + +int of_snd_soc_register_platform(struct snd_soc_platform *platform, + struct device_node *node, + struct snd_soc_dai *cpu_dai); + +#endif /* _INCLUDE_SOC_OF_H_ */ diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 3368ace..398f002 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -1,3 +1,6 @@ +config SND_SOC_OF_SIMPLE + tristate + config SND_SOC_MPC8610 bool "ALSA SoC support for the MPC8610 SOC" depends on MPC8610_HPCD diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 62f680a..aa2100b 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -1,3 +1,6 @@ +# Simple machine driver that extracts configuration from the OF device tree +obj-$(CONFIG_SND_SOC_OF_SIMPLE) += soc-of-simple.o + # MPC8610 HPCD Machine Support obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += mpc8610_hpcd.o
diff --git a/sound/soc/fsl/soc-of-simple.c b/sound/soc/fsl/soc-of-simple.c new file mode 100644 index 0000000..0382fda --- /dev/null +++ b/sound/soc/fsl/soc-of-simple.c @@ -0,0 +1,171 @@ +/* + * OF helpers for ALSA SoC Layer + * + * Copyright (C) 2008, Secret Lab Technologies Ltd. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-of-simple.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Grant Likely grant.likely@secretlab.ca"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ALSA SoC OpenFirmware bindings"); + +static DEFINE_MUTEX(of_snd_soc_mutex); +static LIST_HEAD(of_snd_soc_device_list); +static int of_snd_soc_next_index; + +struct of_snd_soc_device { + int id; + struct list_head list; + struct snd_soc_device device; + struct snd_soc_machine machine; + struct snd_soc_dai_link dai_link; + struct platform_device *pdev; + struct device_node *platform_node; + struct device_node *codec_node; +}; + +static struct snd_soc_ops of_snd_soc_ops = { +}; + +static struct of_snd_soc_device * +of_snd_soc_get_device(struct device_node *codec_node) +{ + struct of_snd_soc_device *of_soc; + + list_for_each_entry(of_soc, &of_snd_soc_device_list, list) { + if (of_soc->codec_node == codec_node) + return of_soc; + } + + of_soc = kzalloc(sizeof(struct of_snd_soc_device), GFP_KERNEL); + if (!of_soc) + return NULL; + + /* Initialize the structure and add it to the global list */ + of_soc->codec_node = codec_node; + of_soc->id = of_snd_soc_next_index++; + of_soc->machine.dai_link = &of_soc->dai_link; + of_soc->machine.num_links = 1; + of_soc->device.machine = &of_soc->machine; + of_soc->dai_link.ops = &of_snd_soc_ops; + list_add(&of_soc->list, &of_snd_soc_device_list); + + return of_soc; +} + +static void of_snd_soc_register_device(struct of_snd_soc_device *of_soc) +{ + struct platform_device *pdev; + int rc; + + /* Only register the device if both the codec and platform have + * been registered */ + if ((!of_soc->device.codec_data) || (!of_soc->platform_node)) + return; + + pr_info("platform<-->codec match achieved; registering machine\n"); + + pdev = platform_device_alloc("soc-audio", of_soc->id); + if (!pdev) { + pr_err("of_soc: platform_device_alloc() failed\n"); + return; + } + + pdev->dev.platform_data = of_soc; + platform_set_drvdata(pdev, &of_soc->device); + of_soc->device.dev = &pdev->dev; + + /* The ASoC device is complete; register it */ + rc = platform_device_add(pdev); + if (rc) { + pr_err("of_soc: platform_device_add() failed\n"); + return; + } + +} + +int of_snd_soc_register_codec(struct snd_soc_codec_device *codec_dev, + void *codec_data, struct snd_soc_dai *dai, + struct device_node *node) +{ + struct of_snd_soc_device *of_soc; + int rc = 0; + + pr_info("registering ASoC codec driver: %s\n", node->full_name); + + mutex_lock(&of_snd_soc_mutex); + of_soc = of_snd_soc_get_device(node); + if (!of_soc) { + rc = -ENOMEM; + goto out; + } + + /* Store the codec data */ + of_soc->device.codec_data = codec_data; + of_soc->device.codec_dev = codec_dev; + of_soc->dai_link.name = (char *)node->name; + of_soc->dai_link.stream_name = (char *)node->name; + of_soc->dai_link.codec_dai = dai; + + /* Now try to register the SoC device */ + of_snd_soc_register_device(of_soc); + + out: + mutex_unlock(&of_snd_soc_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(of_snd_soc_register_codec); + +int of_snd_soc_register_platform(struct snd_soc_platform *platform, + struct device_node *node, + struct snd_soc_dai *cpu_dai) +{ + struct of_snd_soc_device *of_soc; + struct device_node *codec_node; + const phandle *handle; + int len, rc = 0; + + pr_info("registering ASoC platform driver: %s\n", node->full_name); + + handle = of_get_property(node, "codec-handle", &len); + if (!handle || len < sizeof(handle)) + return -ENODEV; + codec_node = of_find_node_by_phandle(*handle); + if (!codec_node) + return -ENODEV; + pr_info("looking for codec: %s\n", codec_node->full_name); + + mutex_lock(&of_snd_soc_mutex); + of_soc = of_snd_soc_get_device(codec_node); + if (!of_soc) { + rc = -ENOMEM; + goto out; + } + + of_soc->platform_node = node; + of_soc->dai_link.cpu_dai = cpu_dai; + of_soc->device.platform = platform; + of_soc->machine.name = of_soc->dai_link.cpu_dai->name; + + /* Now try to register the SoC device */ + of_snd_soc_register_device(of_soc); + + out: + mutex_unlock(&of_snd_soc_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(of_snd_soc_register_platform);
The OpenFirmware API headers don't build on all platforms so ensure that they are not included unless they are being used.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com Acked-by: Grant Likely grant.likely@secretlab.ca --- include/sound/soc-of-simple.h | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/include/sound/soc-of-simple.h b/include/sound/soc-of-simple.h index 696fc51..a064e19 100644 --- a/include/sound/soc-of-simple.h +++ b/include/sound/soc-of-simple.h @@ -7,6 +7,8 @@ #ifndef _INCLUDE_SOC_OF_H_ #define _INCLUDE_SOC_OF_H_
+#if defined(CONFIG_SND_SOC_OF_SIMPLE) || defined(CONFIG_SND_SOC_OF_SIMPLE_MODULE) + #include <linux/of.h> #include <sound/soc.h>
@@ -18,4 +20,6 @@ int of_snd_soc_register_platform(struct snd_soc_platform *platform, struct device_node *node, struct snd_soc_dai *cpu_dai);
+#endif + #endif /* _INCLUDE_SOC_OF_H_ */
From: Grant Likely grant.likely@secretlab.ca
This is an I2S bus driver for the MPC5200 PSC device. It depends on the soc-of helper functions to match a PSC device with a codec based on data in the device tree.
Signed-off-by: Grant Likely grant.likely@secretlab.ca Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/fsl/Kconfig | 7 + sound/soc/fsl/Makefile | 2 + sound/soc/fsl/mpc5200_psc_i2s.c | 884 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 893 insertions(+), 0 deletions(-) create mode 100644 sound/soc/fsl/mpc5200_psc_i2s.c
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 398f002..bba9546 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -17,3 +17,10 @@ config SND_SOC_MPC8610_HPCD default y if MPC8610_HPCD help Say Y if you want to enable audio on the Freescale MPC8610 HPCD. + +config SND_SOC_MPC5200_I2S + tristate "Freescale MPC5200 PSC in I2S mode driver" + select SND_SOC_OF_SIMPLE + depends on SND_SOC && PPC_MPC52xx + help + Say Y here to support the MPC5200 PSCs in I2S mode. diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index aa2100b..035da4a 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += mpc8610_hpcd.o # MPC8610 Platform Support obj-$(CONFIG_SND_SOC_MPC8610) += fsl_ssi.o fsl_dma.o
+obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o + diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c new file mode 100644 index 0000000..8692329 --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -0,0 +1,884 @@ +/* + * Freescale MPC5200 PSC in I2S mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-of-simple.h> + +#include <sysdev/bestcomm/bestcomm.h> +#include <sysdev/bestcomm/gen_bd.h> +#include <asm/mpc52xx_psc.h> + +MODULE_AUTHOR("Grant Likely grant.likely@secretlab.ca"); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); +MODULE_LICENSE("GPL"); + +/** + * PSC_I2S_RATES: sample rates supported by the I2S + * + * This driver currently only supports the PSC running in I2S slave mode, + * which means the codec determines the sample rate. Therefore, we tell + * ALSA that we support all rates and let the codec driver decide what rates + * are really supported. + */ +#define PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \ + SNDRV_PCM_RATE_CONTINUOUS) + +/** + * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode + */ +#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_S32_BE) + +/** + * psc_i2s_stream - Data specific to a single stream (playback or capture) + * @active: flag indicating if the stream is active + * @psc_i2s: pointer back to parent psc_i2s data structure + * @bcom_task: bestcomm task structure + * @irq: irq number for bestcomm task + * @period_start: physical address of start of DMA region + * @period_end: physical address of end of DMA region + * @period_next_pt: physical address of next DMA buffer to enqueue + * @period_bytes: size of DMA period in bytes + */ +struct psc_i2s_stream { + int active; + struct psc_i2s *psc_i2s; + struct bcom_task *bcom_task; + int irq; + struct snd_pcm_substream *stream; + dma_addr_t period_start; + dma_addr_t period_end; + dma_addr_t period_next_pt; + dma_addr_t period_current_pt; + int period_bytes; +}; + +/** + * psc_i2s - Private driver data + * @name: short name for this device ("PSC0", "PSC1", etc) + * @psc_regs: pointer to the PSC's registers + * @fifo_regs: pointer to the PSC's FIFO registers + * @irq: IRQ of this PSC + * @dev: struct device pointer + * @dai: the CPU DAI for this device + * @sicr: Base value used in serial interface control register; mode is ORed + * with this value. + * @playback: Playback stream context data + * @capture: Capture stream context data + */ +struct psc_i2s { + char name[32]; + struct mpc52xx_psc __iomem *psc_regs; + struct mpc52xx_psc_fifo __iomem *fifo_regs; + unsigned int irq; + struct device *dev; + struct snd_soc_dai dai; + spinlock_t lock; + u32 sicr; + + /* per-stream data */ + struct psc_i2s_stream playback; + struct psc_i2s_stream capture; + + /* Statistics */ + struct { + int overrun_count; + int underrun_count; + } stats; +}; + +/* + * Interrupt handlers + */ +static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) +{ + struct psc_i2s *psc_i2s = _psc_i2s; + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + u16 isr; + + isr = in_be16(®s->mpc52xx_psc_isr); + + /* Playback underrun error */ + if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) + psc_i2s->stats.underrun_count++; + + /* Capture overrun error */ + if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) + psc_i2s->stats.overrun_count++; + + out_8(®s->command, 4 << 4); /* reset the error status */ + + return IRQ_HANDLED; +} + +/** + * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer + * @s: pointer to stream private data structure + * + * Enqueues another audio period buffer into the bestcomm queue. + * + * Note: The routine must only be called when there is space available in + * the queue. Otherwise the enqueue will fail and the audio ring buffer + * will get out of sync + */ +static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) +{ + struct bcom_bd *bd; + + /* Prepare and enqueue the next buffer descriptor */ + bd = bcom_prepare_next_buffer(s->bcom_task); + bd->status = s->period_bytes; + bd->data[0] = s->period_next_pt; + bcom_submit_next_buffer(s->bcom_task, NULL); + + /* Update for next period */ + s->period_next_pt += s->period_bytes; + if (s->period_next_pt >= s->period_end) + s->period_next_pt = s->period_start; +} + +/* Bestcomm DMA irq handler */ +static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) +{ + struct psc_i2s_stream *s = _psc_i2s_stream; + + /* For each finished period, dequeue the completed period buffer + * and enqueue a new one in it's place. */ + while (bcom_buffer_done(s->bcom_task)) { + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + s->period_current_pt += s->period_bytes; + if (s->period_current_pt >= s->period_end) + s->period_current_pt = s->period_start; + psc_i2s_bcom_enqueue_next_buffer(s); + bcom_enable(s->bcom_task); + } + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + return IRQ_HANDLED; +} + +/** + * psc_i2s_startup: create a new substream + * + * This is the first function called when a stream is opened. + * + * If this is the first stream open, then grab the IRQ and program most of + * the PSC registers. + */ +static int psc_i2s_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + int rc; + + dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); + + if (!psc_i2s->playback.active && + !psc_i2s->capture.active) { + /* Setup the IRQs */ + rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED, + "psc-i2s-status", psc_i2s); + rc |= request_irq(psc_i2s->capture.irq, + &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-capture", &psc_i2s->capture); + rc |= request_irq(psc_i2s->playback.irq, + &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-playback", &psc_i2s->playback); + if (rc) { + free_irq(psc_i2s->irq, psc_i2s); + free_irq(psc_i2s->capture.irq, + &psc_i2s->capture); + free_irq(psc_i2s->playback.irq, + &psc_i2s->playback); + return -ENODEV; + } + } + + return 0; +} + +static int psc_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + u32 mode; + + dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i\n", + __func__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params)); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mode = MPC52xx_PSC_SICR_SIM_CODEC_8; + break; + case SNDRV_PCM_FORMAT_S16_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_16; + break; + case SNDRV_PCM_FORMAT_S24_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_24; + break; + case SNDRV_PCM_FORMAT_S32_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_32; + break; + default: + dev_dbg(psc_i2s->dev, "invalid format\n"); + return -EINVAL; + } + out_be32(&psc_i2s->psc_regs->sicr, psc_i2s->sicr | mode); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int psc_i2s_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +/** + * psc_i2s_trigger: start and stop the DMA transfer. + * + * This function is called by ALSA to start, stop, pause, and resume the DMA + * transfer of data. + */ +static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct psc_i2s_stream *s; + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + u16 imr; + u8 psc_cmd; + long flags; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)" + " stream_id=%i\n", + substream, cmd, substream->pstr->stream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + s->period_bytes = frames_to_bytes(runtime, + runtime->period_size); + s->period_start = virt_to_phys(runtime->dma_area); + s->period_end = s->period_start + + (s->period_bytes * runtime->periods); + s->period_next_pt = s->period_start; + s->period_current_pt = s->period_start; + s->active = 1; + + /* First; reset everything */ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + out_8(®s->command, MPC52xx_PSC_RST_RX); + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + } else { + out_8(®s->command, MPC52xx_PSC_RST_TX); + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + } + + /* Next, fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. */ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + while (!bcom_queue_full(s->bcom_task)) + psc_i2s_bcom_enqueue_next_buffer(s); + bcom_enable(s->bcom_task); + + /* Due to errata in the i2s mode; need to line up enabling + * the transmitter with a transition on the frame sync + * line */ + + spin_lock_irqsave(&psc_i2s->lock, flags); + /* first make sure it is low */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) + ; + /* then wait for the transition to high */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) + ; + /* Finally, enable the PSC. + * Receiver must always be enabled; even when we only want + * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ + psc_cmd = MPC52xx_PSC_RX_ENABLE; + if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) + psc_cmd |= MPC52xx_PSC_TX_ENABLE; + out_8(®s->command, psc_cmd); + spin_unlock_irqrestore(&psc_i2s->lock, flags); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* Turn off the PSC */ + s->active = 0; + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (!psc_i2s->playback.active) { + out_8(®s->command, 2 << 4); /* reset rx */ + out_8(®s->command, 3 << 4); /* reset tx */ + out_8(®s->command, 4 << 4); /* reset err */ + } + } else { + out_8(®s->command, 3 << 4); /* reset tx */ + out_8(®s->command, 4 << 4); /* reset err */ + if (!psc_i2s->capture.active) + out_8(®s->command, 2 << 4); /* reset rx */ + } + + bcom_disable(s->bcom_task); + while (!bcom_queue_empty(s->bcom_task)) + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + + break; + + default: + dev_dbg(psc_i2s->dev, "invalid command\n"); + return -EINVAL; + } + + /* Update interrupt enable settings */ + imr = 0; + if (psc_i2s->playback.active) + imr |= MPC52xx_PSC_IMR_TXEMP; + if (psc_i2s->capture.active) + imr |= MPC52xx_PSC_IMR_ORERR; + out_be16(®s->isr_imr.imr, imr); + + return 0; +} + +/** + * psc_i2s_shutdown: shutdown the data transfer on a stream + * + * Shutdown the PSC if there are no other substreams open. + */ +static void psc_i2s_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + + dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream); + + /* + * If this is the last active substream, disable the PSC and release + * the IRQ. + */ + if (!psc_i2s->playback.active && + !psc_i2s->capture.active) { + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); + out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */ + out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */ + out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ + out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + + /* Release irqs */ + free_irq(psc_i2s->irq, psc_i2s); + free_irq(psc_i2s->capture.irq, &psc_i2s->capture); + free_irq(psc_i2s->playback.irq, &psc_i2s->playback); + } +} + +/** + * psc_i2s_set_sysclk: set the clock frequency and direction + * + * This function is called by the machine driver to tell us what the clock + * frequency and direction are. + * + * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN), + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) + */ +static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct psc_i2s *psc_i2s = cpu_dai->private_data; + dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n", + cpu_dai, dir); + return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL; +} + +/** + * psc_i2s_set_fmt: set the serial format. + * + * This function is called by the machine driver to tell us what serial + * format to use. + * + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * + * @format: one of SND_SOC_DAIFMT_xxx + */ +static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) +{ + struct psc_i2s *psc_i2s = cpu_dai->private_data; + dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n", + cpu_dai, format); + return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; +} + +/* --------------------------------------------------------------------- + * ALSA SoC Bindings + * + * - Digital Audio Interface (DAI) template + * - create/destroy dai hooks + */ + +/** + * psc_i2s_dai_template: template CPU Digital Audio Interface + */ +static struct snd_soc_dai psc_i2s_dai_template = { + .type = SND_SOC_DAI_I2S, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .ops = { + .startup = psc_i2s_startup, + .hw_params = psc_i2s_hw_params, + .hw_free = psc_i2s_hw_free, + .shutdown = psc_i2s_shutdown, + .trigger = psc_i2s_trigger, + }, + .dai_ops = { + .set_sysclk = psc_i2s_set_sysclk, + .set_fmt = psc_i2s_set_fmt, + }, +}; + +/* --------------------------------------------------------------------- + * The PSC I2S 'ASoC platform' driver + * + * Can be referenced by an 'ASoC machine' driver + * This driver only deals with the audio bus; it doesn't have any + * interaction with the attached codec + */ + +static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .period_bytes_max = 1024 * 1024, + .period_bytes_min = 32, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 2 * 1024 * 1024, + .fifo_size = 0, +}; + +static int psc_i2s_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); + + s->stream = substream; + return 0; +} + +static int psc_i2s_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + s->stream = NULL; + return 0; +} + +static snd_pcm_uframes_t +psc_i2s_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + dma_addr_t count; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + count = s->period_current_pt - s->period_start; + + return bytes_to_frames(substream->runtime, count); +} + +static struct snd_pcm_ops psc_i2s_pcm_ops = { + .open = psc_i2s_pcm_open, + .close = psc_i2s_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .pointer = psc_i2s_pcm_pointer, +}; + +static u64 psc_i2s_pcm_dmamask = 0xffffffff; +static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + size_t size = psc_i2s_pcm_hardware.buffer_bytes_max; + int rc = 0; + + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n", + card, dai, pcm); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &psc_i2s_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (pcm->streams[0].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, + &pcm->streams[0].substream->dma_buffer); + if (rc) + goto playback_alloc_err; + } + + if (pcm->streams[1].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, + &pcm->streams[1].substream->dma_buffer); + if (rc) + goto capture_alloc_err; + } + + return 0; + + capture_alloc_err: + if (pcm->streams[0].substream) + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); + playback_alloc_err: + dev_err(card->dev, "Cannot allocate buffer(s)\n"); + return -ENOMEM; +} + +static void psc_i2s_pcm_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_pcm_substream *substream; + int stream; + + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +struct snd_soc_platform psc_i2s_pcm_soc_platform = { + .name = "mpc5200-psc-audio", + .pcm_ops = &psc_i2s_pcm_ops, + .pcm_new = &psc_i2s_pcm_new, + .pcm_free = &psc_i2s_pcm_free, +}; + +/* --------------------------------------------------------------------- + * Sysfs attributes for debugging + */ + +static ssize_t psc_i2s_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + + return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x " + "tfnum=%i tfstat=0x%.4x\n", + in_be16(&psc_i2s->psc_regs->sr_csr.status), + in_be32(&psc_i2s->psc_regs->sicr), + in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff, + in_be16(&psc_i2s->fifo_regs->rfstat), + in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff, + in_be16(&psc_i2s->fifo_regs->tfstat)); +} + +static int *psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s, const char *name) +{ + if (strcmp(name, "playback_underrun") == 0) + return &psc_i2s->stats.underrun_count; + if (strcmp(name, "capture_overrun") == 0) + return &psc_i2s->stats.overrun_count; + + return NULL; +} + +static ssize_t psc_i2s_stat_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + int *attrib; + + attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); + if (!attrib) + return 0; + + return sprintf(buf, "%i\n", *attrib); +} + +static ssize_t psc_i2s_stat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + int *attrib; + + attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); + if (!attrib) + return 0; + + *attrib = simple_strtoul(buf, NULL, 0); + return count; +} + +DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL); +DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show, psc_i2s_stat_store); +DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, psc_i2s_stat_store); + +/* --------------------------------------------------------------------- + * OF platform bus binding code: + * - Probe/remove operations + * - OF device match table + */ +static int __devinit psc_i2s_of_probe(struct of_device *op, + const struct of_device_id *match) +{ + phys_addr_t fifo; + struct psc_i2s *psc_i2s; + struct resource res; + int size, psc_id, irq, rc; + const __be32 *prop; + void __iomem *regs; + + dev_dbg(&op->dev, "probing psc i2s device\n"); + + /* Get the PSC ID */ + prop = of_get_property(op->node, "cell-index", &size); + if (!prop || size < sizeof *prop) + return -ENODEV; + psc_id = be32_to_cpu(*prop); + + /* Fetch the registers and IRQ of the PSC */ + irq = irq_of_parse_and_map(op->node, 0); + if (of_address_to_resource(op->node, 0, &res)) { + dev_err(&op->dev, "Missing reg property\n"); + return -ENODEV; + } + regs = ioremap(res.start, 1 + res.end - res.start); + if (!regs) { + dev_err(&op->dev, "Could not map registers\n"); + return -ENODEV; + } + + /* Allocate and initialize the driver private data */ + psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL); + if (!psc_i2s) { + iounmap(regs); + return -ENOMEM; + } + spin_lock_init(&psc_i2s->lock); + psc_i2s->irq = irq; + psc_i2s->psc_regs = regs; + psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs; + psc_i2s->dev = &op->dev; + psc_i2s->playback.psc_i2s = psc_i2s; + psc_i2s->capture.psc_i2s = psc_i2s; + snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1); + + /* Fill out the CPU DAI structure */ + memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai); + psc_i2s->dai.private_data = psc_i2s; + psc_i2s->dai.name = psc_i2s->name; + psc_i2s->dai.id = psc_id; + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ + fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_i2s->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512); + psc_i2s->playback.bcom_task = + bcom_psc_gen_bd_tx_init(psc_id, 10, fifo); + if (!psc_i2s->capture.bcom_task || + !psc_i2s->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); + iounmap(regs); + kfree(psc_i2s); + return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); + out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset transmitter */ + out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset receiver */ + out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ + out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + + /* Configure the serial interface mode; defaulting to CODEC8 mode */ + psc_i2s->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | + MPC52xx_PSC_SICR_CLKPOL; + if (of_get_property(op->node, "fsl,cellslave", NULL)) + psc_i2s->sicr |= MPC52xx_PSC_SICR_CELLSLAVE | + MPC52xx_PSC_SICR_GENCLK; + out_be32(&psc_i2s->psc_regs->sicr, + psc_i2s->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); + + /* Check for the codec handle. If it is not present then we + * are done */ + if (!of_get_property(op->node, "codec-handle", NULL)) + return 0; + + /* Set up mode register; + * First write: RxRdy (FIFO Alarm) generates rx FIFO irq + * Second write: register Normal mode for non loopback + */ + out_8(&psc_i2s->psc_regs->mode, 0); + out_8(&psc_i2s->psc_regs->mode, 0); + + /* Set the TX and RX fifo alarm thresholds */ + out_be16(&psc_i2s->fifo_regs->rfalarm, 0x100); + out_8(&psc_i2s->fifo_regs->rfcntl, 0x4); + out_be16(&psc_i2s->fifo_regs->tfalarm, 0x100); + out_8(&psc_i2s->fifo_regs->tfcntl, 0x7); + + /* Lookup the IRQ numbers */ + psc_i2s->playback.irq = + bcom_get_task_irq(psc_i2s->playback.bcom_task); + psc_i2s->capture.irq = + bcom_get_task_irq(psc_i2s->capture.bcom_task); + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_i2s); + + /* Register the SYSFS files */ + rc = device_create_file(psc_i2s->dev, &dev_attr_status); + rc = device_create_file(psc_i2s->dev, &dev_attr_capture_overrun); + rc = device_create_file(psc_i2s->dev, &dev_attr_playback_underrun); + if (rc) + dev_info(psc_i2s->dev, "error creating sysfs files\n"); + + /* Tell the ASoC OF helpers about it */ + of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node, + &psc_i2s->dai); + + return 0; +} + +static int __devexit psc_i2s_of_remove(struct of_device *op) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev); + + dev_dbg(&op->dev, "psc_i2s_remove()\n"); + + bcom_gen_bd_rx_release(psc_i2s->capture.bcom_task); + bcom_gen_bd_tx_release(psc_i2s->playback.bcom_task); + + iounmap(psc_i2s->psc_regs); + iounmap(psc_i2s->fifo_regs); + kfree(psc_i2s); + dev_set_drvdata(&op->dev, NULL); + + return 0; +} + +/* Match table for of_platform binding */ +static struct of_device_id psc_i2s_match[] __devinitdata = { + { .compatible = "fsl,mpc5200-psc-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, psc_i2s_match); + +static struct of_platform_driver psc_i2s_driver = { + .match_table = psc_i2s_match, + .probe = psc_i2s_of_probe, + .remove = __devexit_p(psc_i2s_of_remove), + .driver = { + .name = "mpc5200-psc-i2s", + .owner = THIS_MODULE, + }, +}; + +/* --------------------------------------------------------------------- + * Module setup and teardown; simply register the of_platform driver + * for the PSC in I2S mode. + */ +static int __init psc_i2s_init(void) +{ + return of_register_platform_driver(&psc_i2s_driver); +} +module_init(psc_i2s_init); + +static void __exit psc_i2s_exit(void) +{ + of_unregister_platform_driver(&psc_i2s_driver); +} +module_exit(psc_i2s_exit); + +
From: Grant Likely grant.likely@secretlab.ca
ASoC Codec driver for the TLV320AIC26 device. As it stands, this driver doesn't support all the modes and clocking options of the AIC16, but it is a start.
Signed-off-by: Grant Likely grant.likely@secretlab.ca Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tlv320aic26.c | 519 ++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tlv320aic26.h | 93 +++++++ 4 files changed, 618 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/tlv320aic26.c create mode 100644 sound/soc/codecs/tlv320aic26.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 1db04a2..9e09fa5 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -47,6 +47,10 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270
+config SND_SOC_TLV320AIC26 + tristate "TI TLV320AIC26 Codec support" + depends on SND_SOC && SPI + config SND_SOC_TLV320AIC3X tristate depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index d7b97ab..dc0357e 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -9,6 +9,7 @@ snd-soc-wm8990-objs := wm8990.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-cs4270-objs := cs4270.o +snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o @@ -22,4 +23,5 @@ obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c new file mode 100644 index 0000000..4621fda --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.c @@ -0,0 +1,519 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * ALSA SoC CODEC driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.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/soc-of-simple.h> +#include <sound/initval.h> + +#include "tlv320aic26.h" + +MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver"); +MODULE_AUTHOR("Grant Likely grant.likely@secretlab.ca"); +MODULE_LICENSE("GPL"); + +/* AIC26 driver private data */ +struct aic26 { + struct spi_device *spi; + struct snd_soc_codec codec; + u16 reg_cache[AIC26_NUM_REGS]; /* shadow registers */ + int master; + int datfm; + int mclk; + + /* Keyclick parameters */ + int keyclick_amplitude; + int keyclick_freq; + int keyclick_len; +}; + +/* --------------------------------------------------------------------- + * Register access routines + */ +static unsigned int aic26_reg_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd, value; + u8 buffer[2]; + int rc; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return 0; + } + + /* Do SPI transfer; first 16bits are command; remaining is + * register contents */ + cmd = AIC26_READ_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + value = (buffer[0] << 8) | buffer[1]; + + /* Update the cache before returning with the value */ + cache[reg] = value; + return value; +} + +static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return 0; + } + + return cache[reg]; +} + +static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd; + u8 buffer[4]; + int rc; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + /* Do SPI transfer; first 16bits are command; remaining is data + * to write into register */ + cmd = AIC26_WRITE_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + buffer[2] = value >> 8; + buffer[3] = value; + rc = spi_write(aic26->spi, buffer, 4); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + + /* update cache before returning */ + cache[reg] = value; + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Operations + */ +static int aic26_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 aic26 *aic26 = codec->private_data; + int fsref, divisor, wlen, pval, jval, dval, qval; + u16 reg; + + dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n", + substream, params); + dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params), + params_format(params)); + + switch (params_rate(params)) { + case 8000: fsref = 48000; divisor = AIC26_DIV_6; break; + case 11025: fsref = 44100; divisor = AIC26_DIV_4; break; + case 12000: fsref = 48000; divisor = AIC26_DIV_4; break; + case 16000: fsref = 48000; divisor = AIC26_DIV_3; break; + case 22050: fsref = 44100; divisor = AIC26_DIV_2; break; + case 24000: fsref = 48000; divisor = AIC26_DIV_2; break; + case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break; + case 44100: fsref = 44100; divisor = AIC26_DIV_1; break; + case 48000: fsref = 48000; divisor = AIC26_DIV_1; break; + default: + dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL; + } + + /* select data word length */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break; + case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break; + default: + dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + /* Configure PLL */ + pval = 1; + jval = (fsref == 44100) ? 7 : 8; + dval = (fsref == 44100) ? 5264 : 1920; + qval = 0; + reg = 0x8000 | qval << 11 | pval << 8 | jval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg); + reg = dval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + if (aic26->master) + reg |= 0x0800; + if (fsref == 48000) + reg |= 0x2000; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Audio Control 1 (FSref divisor) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1); + reg &= ~0x0fff; + reg |= wlen | aic26->datfm | (divisor << 3) | divisor; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg); + + return 0; +} + +/** + * aic26_mute - Mute control to reduce noise when changing audio format + */ +static int aic26_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic26 *aic26 = codec->private_data; + u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN); + + dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n", + dai, mute); + + if (mute) + reg |= 0x8080; + else + reg &= ~0x8080; + aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg); + + return 0; +} + +static int aic26_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i," + " freq=%i, dir=%i)\n", + codec_dai, clk_id, freq, dir); + + /* MCLK needs to fall between 2MHz and 50 MHz */ + if ((freq < 2000000) || (freq > 50000000)) + return -EINVAL; + + aic26->mclk = freq; + return 0; +} + +static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n", + codec_dai, fmt); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break; + case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break; + default: + dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break; + case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break; + case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break; + case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break; + default: + dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Definition + */ +#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) +#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +struct snd_soc_dai aic26_dai = { + .name = "tlv320aic26", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .ops = { + .hw_params = aic26_hw_params, + }, + .dai_ops = { + .digital_mute = aic26_mute, + .set_sysclk = aic26_set_sysclk, + .set_fmt = aic26_set_fmt, + }, +}; +EXPORT_SYMBOL_GPL(aic26_dai); + +/* --------------------------------------------------------------------- + * ALSA controls + */ +static const char *aic26_capture_src_text[] = {"Mic", "Aux"}; +static const struct soc_enum aic26_capture_src_enum = + SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12, 2, aic26_capture_src_text); + +static const struct snd_kcontrol_new aic26_snd_controls[] = { + /* Output */ + SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1), + SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1), + SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0), + SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1), + SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0), + SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0), + SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0), + SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0), + SOC_ENUM("Capture Source", aic26_capture_src_enum), +}; + +/* --------------------------------------------------------------------- + * SoC CODEC portion of driver: probe and release routines + */ +static int aic26_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct snd_kcontrol *kcontrol; + struct aic26 *aic26; + int i, ret, err; + + dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n"); + dev_dbg(&pdev->dev, "socdev=%p\n", socdev); + dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data); + + /* Fetch the relevant aic26 private data here (it's already been + * stored in the .codec pointer) */ + aic26 = socdev->codec_data; + if (aic26 == NULL) { + dev_err(&pdev->dev, "aic26: missing codec pointer\n"); + return -ENODEV; + } + codec = &aic26->codec; + socdev->codec = codec; + + dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n", + &pdev->dev, socdev->dev); + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to create pcms\n"); + return -ENODEV; + } + + /* register controls */ + dev_dbg(&pdev->dev, "Registering controls\n"); + for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) { + kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL); + err = snd_ctl_add(codec->card, kcontrol); + WARN_ON(err < 0); + } + + /* CODEC is setup, we can register the card now */ + dev_dbg(&pdev->dev, "Registering card\n"); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to register card\n"); + goto card_err; + } + return 0; + + card_err: + snd_soc_free_pcms(socdev); + return ret; +} + +static int aic26_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + snd_soc_free_pcms(socdev); + return 0; +} + +struct snd_soc_codec_device aic26_soc_codec_dev = { + .probe = aic26_probe, + .remove = aic26_remove, +}; + +/* --------------------------------------------------------------------- + * SPI device portion of driver: sysfs files for debugging + */ + +static ssize_t aic26_keyclick_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val, amp, freq, len; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + amp = (val >> 12) & 0x7; + freq = (125 << ((val >> 8) & 0x7)) >> 1; + len = 2 * (1 + ((val >> 4) & 0xf)); + + return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len); +} + +/* Any write to the keyclick attribute will trigger the keyclick event */ +static ssize_t aic26_keyclick_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + val |= 0x8000; + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val); + + return count; +} + +DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set); + +/* --------------------------------------------------------------------- + * SPI device portion of driver: probe and release routines and SPI + * driver registration. + */ +static int aic26_spi_probe(struct spi_device *spi) +{ + struct aic26 *aic26; + int rc, i, reg; + + dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n"); + + /* Allocate driver data */ + aic26 = kzalloc(sizeof *aic26, GFP_KERNEL); + if (!aic26) + return -ENOMEM; + + /* Initialize the driver data */ + aic26->spi = spi; + dev_set_drvdata(&spi->dev, aic26); + + /* Setup what we can in the codec structure so that the register + * access functions will work as expected. More will be filled + * out when it is probed by the SoC CODEC part of this driver */ + aic26->codec.private_data = aic26; + aic26->codec.name = "aic26"; + aic26->codec.owner = THIS_MODULE; + aic26->codec.dai = &aic26_dai; + aic26->codec.num_dai = 1; + aic26->codec.read = aic26_reg_read; + aic26->codec.write = aic26_reg_write; + aic26->master = 1; + mutex_init(&aic26->codec.mutex); + INIT_LIST_HEAD(&aic26->codec.dapm_widgets); + INIT_LIST_HEAD(&aic26->codec.dapm_paths); + aic26->codec.reg_cache_size = AIC26_NUM_REGS; + aic26->codec.reg_cache = aic26->reg_cache; + + /* Reset the codec to power on defaults */ + aic26_reg_write(&aic26->codec, AIC26_REG_RESET, 0xBB00); + + /* Power up CODEC */ + aic26_reg_write(&aic26->codec, AIC26_REG_POWER_CTRL, 0); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read(&aic26->codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + reg |= 0x0800; /* set master mode */ + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Fill register cache */ + for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++) + aic26_reg_read(&aic26->codec, i); + + /* Register the sysfs files for debugging */ + /* Create SysFS files */ + rc = device_create_file(&spi->dev, &dev_attr_keyclick); + if (rc) + dev_info(&spi->dev, "error creating sysfs files\n"); + +#if defined(CONFIG_SND_SOC_OF_SIMPLE) + /* Tell the of_soc helper about this codec */ + of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai, + spi->dev.archdata.of_node); +#endif + + dev_dbg(&spi->dev, "SPI device initialized\n"); + return 0; +} + +static int aic26_spi_remove(struct spi_device *spi) +{ + struct aic26 *aic26 = dev_get_drvdata(&spi->dev); + + kfree(aic26); + + return 0; +} + +static struct spi_driver aic26_spi = { + .driver = { + .name = "tlv320aic26", + .owner = THIS_MODULE, + }, + .probe = aic26_spi_probe, + .remove = aic26_spi_remove, +}; + +static int __init aic26_init(void) +{ + return spi_register_driver(&aic26_spi); +} +module_init(aic26_init); + +static void __exit aic26_exit(void) +{ + spi_unregister_driver(&aic26_spi); +} +module_exit(aic26_exit); diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h new file mode 100644 index 0000000..62b1f22 --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.h @@ -0,0 +1,93 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * register definitions + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#ifndef _TLV320AIC16_H_ +#define _TLV320AIC16_H_ + +/* AIC26 Registers */ +#define AIC26_READ_COMMAND_WORD(addr) ((1 << 15) | (addr << 5)) +#define AIC26_WRITE_COMMAND_WORD(addr) ((0 << 15) | (addr << 5)) +#define AIC26_PAGE_ADDR(page, offset) ((page << 6) | offset) +#define AIC26_NUM_REGS AIC26_PAGE_ADDR(3, 0) + +/* Page 0: Auxillary data registers */ +#define AIC26_REG_BAT1 AIC26_PAGE_ADDR(0, 0x05) +#define AIC26_REG_BAT2 AIC26_PAGE_ADDR(0, 0x06) +#define AIC26_REG_AUX AIC26_PAGE_ADDR(0, 0x07) +#define AIC26_REG_TEMP1 AIC26_PAGE_ADDR(0, 0x09) +#define AIC26_REG_TEMP2 AIC26_PAGE_ADDR(0, 0x0A) + +/* Page 1: Auxillary control registers */ +#define AIC26_REG_AUX_ADC AIC26_PAGE_ADDR(1, 0x00) +#define AIC26_REG_STATUS AIC26_PAGE_ADDR(1, 0x01) +#define AIC26_REG_REFERENCE AIC26_PAGE_ADDR(1, 0x03) +#define AIC26_REG_RESET AIC26_PAGE_ADDR(1, 0x04) + +/* Page 2: Audio control registers */ +#define AIC26_REG_AUDIO_CTRL1 AIC26_PAGE_ADDR(2, 0x00) +#define AIC26_REG_ADC_GAIN AIC26_PAGE_ADDR(2, 0x01) +#define AIC26_REG_DAC_GAIN AIC26_PAGE_ADDR(2, 0x02) +#define AIC26_REG_SIDETONE AIC26_PAGE_ADDR(2, 0x03) +#define AIC26_REG_AUDIO_CTRL2 AIC26_PAGE_ADDR(2, 0x04) +#define AIC26_REG_POWER_CTRL AIC26_PAGE_ADDR(2, 0x05) +#define AIC26_REG_AUDIO_CTRL3 AIC26_PAGE_ADDR(2, 0x06) + +#define AIC26_REG_FILTER_COEFF_L_N0 AIC26_PAGE_ADDR(2, 0x07) +#define AIC26_REG_FILTER_COEFF_L_N1 AIC26_PAGE_ADDR(2, 0x08) +#define AIC26_REG_FILTER_COEFF_L_N2 AIC26_PAGE_ADDR(2, 0x09) +#define AIC26_REG_FILTER_COEFF_L_N3 AIC26_PAGE_ADDR(2, 0x0A) +#define AIC26_REG_FILTER_COEFF_L_N4 AIC26_PAGE_ADDR(2, 0x0B) +#define AIC26_REG_FILTER_COEFF_L_N5 AIC26_PAGE_ADDR(2, 0x0C) +#define AIC26_REG_FILTER_COEFF_L_D1 AIC26_PAGE_ADDR(2, 0x0D) +#define AIC26_REG_FILTER_COEFF_L_D2 AIC26_PAGE_ADDR(2, 0x0E) +#define AIC26_REG_FILTER_COEFF_L_D4 AIC26_PAGE_ADDR(2, 0x0F) +#define AIC26_REG_FILTER_COEFF_L_D5 AIC26_PAGE_ADDR(2, 0x10) +#define AIC26_REG_FILTER_COEFF_R_N0 AIC26_PAGE_ADDR(2, 0x11) +#define AIC26_REG_FILTER_COEFF_R_N1 AIC26_PAGE_ADDR(2, 0x12) +#define AIC26_REG_FILTER_COEFF_R_N2 AIC26_PAGE_ADDR(2, 0x13) +#define AIC26_REG_FILTER_COEFF_R_N3 AIC26_PAGE_ADDR(2, 0x14) +#define AIC26_REG_FILTER_COEFF_R_N4 AIC26_PAGE_ADDR(2, 0x15) +#define AIC26_REG_FILTER_COEFF_R_N5 AIC26_PAGE_ADDR(2, 0x16) +#define AIC26_REG_FILTER_COEFF_R_D1 AIC26_PAGE_ADDR(2, 0x17) +#define AIC26_REG_FILTER_COEFF_R_D2 AIC26_PAGE_ADDR(2, 0x18) +#define AIC26_REG_FILTER_COEFF_R_D4 AIC26_PAGE_ADDR(2, 0x19) +#define AIC26_REG_FILTER_COEFF_R_D5 AIC26_PAGE_ADDR(2, 0x1A) + +#define AIC26_REG_PLL_PROG1 AIC26_PAGE_ADDR(2, 0x1B) +#define AIC26_REG_PLL_PROG2 AIC26_PAGE_ADDR(2, 0x1C) +#define AIC26_REG_AUDIO_CTRL4 AIC26_PAGE_ADDR(2, 0x1D) +#define AIC26_REG_AUDIO_CTRL5 AIC26_PAGE_ADDR(2, 0x1E) + +/* fsref dividers; used in register 'Audio Control 1' */ +enum aic26_divisors { + AIC26_DIV_1 = 0, + AIC26_DIV_1_5 = 1, + AIC26_DIV_2 = 2, + AIC26_DIV_3 = 3, + AIC26_DIV_4 = 4, + AIC26_DIV_5 = 5, + AIC26_DIV_5_5 = 6, + AIC26_DIV_6 = 7, +}; + +/* Digital data format */ +enum aic26_datfm { + AIC26_DATFM_I2S = 0 << 8, + AIC26_DATFM_DSP = 1 << 8, + AIC26_DATFM_RIGHTJ = 2 << 8, /* right justified */ + AIC26_DATFM_LEFTJ = 3 << 8, /* left justified */ +}; + +/* Sample word length in bits; used in register 'Audio Control 1' */ +enum aic26_wlen { + AIC26_WLEN_16 = 0 << 10, + AIC26_WLEN_20 = 1 << 10, + AIC26_WLEN_24 = 2 << 10, + AIC26_WLEN_32 = 3 << 10, +}; + +#endif /* _TLV320AIC16_H_ */
This fixes sparse warnings and allows non-OpenFirmware systems to attempt to bind to the device.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com Acked-by: Grant Likely grant.likely@secretlab.ca --- sound/soc/codecs/tlv320aic26.c | 1 + sound/soc/codecs/tlv320aic26.h | 3 +++ 2 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c index 4621fda..73b7027 100644 --- a/sound/soc/codecs/tlv320aic26.c +++ b/sound/soc/codecs/tlv320aic26.c @@ -383,6 +383,7 @@ struct snd_soc_codec_device aic26_soc_codec_dev = { .probe = aic26_probe, .remove = aic26_remove, }; +EXPORT_SYMBOL_GPL(aic26_soc_codec_dev);
/* --------------------------------------------------------------------- * SPI device portion of driver: sysfs files for debugging diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h index 62b1f22..786ba16 100644 --- a/sound/soc/codecs/tlv320aic26.h +++ b/sound/soc/codecs/tlv320aic26.h @@ -90,4 +90,7 @@ enum aic26_wlen { AIC26_WLEN_32 = 3 << 10, };
+extern struct snd_soc_dai aic26_dai; +extern struct snd_soc_codec_device aic26_soc_codec_dev; + #endif /* _TLV320AIC16_H_ */
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com Acked-by: Grant Likely grant.likely@secretlab.ca --- sound/soc/codecs/tlv320aic26.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c index 73b7027..bed8a9e 100644 --- a/sound/soc/codecs/tlv320aic26.c +++ b/sound/soc/codecs/tlv320aic26.c @@ -418,7 +418,7 @@ static ssize_t aic26_keyclick_set(struct device *dev, return count; }
-DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set); +static DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set);
/* --------------------------------------------------------------------- * SPI device portion of driver: probe and release routines and SPI
From: Cliff Cai cliff.cai@analog.com
[Mechanical updates from code review applied -- broonie]
Signed-off-by: Cliff Cai cliff.cai@analog.com Signed-off-by: Bryan Wu cooloney@kernel.org Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ad1980.c | 309 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ad1980.h | 23 ++++ 4 files changed, 337 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/ad1980.c create mode 100644 sound/soc/codecs/ad1980.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 9e09fa5..7ab74cd 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2,6 +2,9 @@ config SND_SOC_AC97_CODEC tristate select SND_AC97_CODEC
+config SND_SOC_AD1980 + tristate + config SND_SOC_AK4535 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index dc0357e..409e4dd 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,4 +1,5 @@ snd-soc-ac97-objs := ac97.o +snd-soc-ad1980-objs := ad1980.o snd-soc-ak4535-objs := ak4535.o snd-soc-uda1380-objs := uda1380.o snd-soc-wm8510-objs := wm8510.o @@ -13,6 +14,7 @@ snd-soc-tlv320aic26-objs := tlv320aic26.o 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_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c new file mode 100644 index 0000000..bfbab3d --- /dev/null +++ b/sound/soc/codecs/ad1980.c @@ -0,0 +1,309 @@ +/* + * ad1980.c -- ALSA Soc AD1980 codec support + * + * Copyright: Analog Device Inc. + * Author: Roy Huang roy.huang@analog.com + * Cliff Cai cliff.cai@analog.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "ad1980.h" + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg); +static int ac97_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val); + +/* + * AD1980 register cache + */ +static const u16 ad1980_reg[] = { + 0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6 */ + 0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e */ + 0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */ + 0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */ + 0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */ + 0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */ + 0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */ + 0x0000, 0x0000, 0x4144, 0x5370 /* 78 - 7e */ +}; + +static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line", + "Stereo Mix", "Mono Mix", "Phone"}; + +static const struct soc_enum ad1980_cap_src = + SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 7, ad1980_rec_sel); + +static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = { +SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1), + +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), + +SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1), +SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1), + +SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0), +SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1), + +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), + +SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1), +SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1), + +SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1), +SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), + +SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0), +SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0), + +SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1), + +SOC_ENUM("Capture Source", ad1980_cap_src), + +SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0), +}; + +/* add non dapm controls */ +static int ad1980_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ad1980_snd_ac97_controls); i++) { + err = snd_ctl_add(codec->card, snd_soc_cnew( + &ad1980_snd_ac97_controls[i], codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + switch (reg) { + case AC97_RESET: + case AC97_INT_PAGING: + case AC97_POWERDOWN: + case AC97_EXTENDED_STATUS: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return soc_ac97_ops.read(codec->ac97, reg); + default: + reg = reg >> 1; + + if (reg >= (ARRAY_SIZE(ad1980_reg))) + return -EINVAL; + + return cache[reg]; + } +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + + soc_ac97_ops.write(codec->ac97, reg, val); + reg = reg >> 1; + if (reg < (ARRAY_SIZE(ad1980_reg))) + cache[reg] = val; + + return 0; +} + +struct snd_soc_codec_dai ad1980_dai = { + .name = "AC97", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; +EXPORT_SYMBOL_GPL(ad1980_dai); + +static int ad1980_reset(struct snd_soc_codec *codec, int try_warm) +{ + u16 retry_cnt = 0; + +retry: + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (ac97_read(codec, AC97_RESET) == 0x0090) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + /* Set bit 16slot in register 74h, then every slot will has only 16 + * bits. This command is sent out in 20bit mode, in which case the + * first nibble of data is eaten by the addr. (Tag is always 16 bit)*/ + ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900); + + if (ac97_read(codec, AC97_RESET) != 0x0090) + goto err; + return 0; + +err: + while (retry_cnt++ < 10) + goto retry; + + printk(KERN_ERR "AD1980 AC97 reset failed\n"); + return -EIO; +} + +static int ad1980_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + u16 vendor_id2; + + printk(KERN_INFO "AD1980 SoC Audio Codec\n"); + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->codec == NULL) + return -ENOMEM; + codec = socdev->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(ad1980_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + memcpy(codec->reg_cache, ad1980_reg, sizeof(u16) * \ + ARRAY_SIZE(ad1980_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(ad1980_reg); + codec->reg_cache_step = 2; + codec->name = "AD1980"; + codec->owner = THIS_MODULE; + codec->dai = &ad1980_dai; + codec->num_dai = 1; + codec->write = ac97_write; + codec->read = ac97_read; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) { + printk(KERN_ERR "ad1980: failed to register AC97 codec\n"); + goto codec_err; + } + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + + ret = ad1980_reset(codec, 0); + if (ret < 0) { + printk(KERN_ERR "AC97 link error\n"); + goto reset_err; + } + + /* Read out vendor ID to make sure it is ad1980 */ + if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144) + goto reset_err; + + vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2); + + if (vendor_id2 != 0x5370) { + if (vendor_id2 != 0x5374) + goto reset_err; + else + printk(KERN_WARNING "ad1980: " + "Found AD1981 - only 2/2 IN/OUT Channels " + "supported\n"); + } + + ac97_write(codec, AC97_MASTER, 0x0000); /* unmute line out volume */ + ac97_write(codec, AC97_PCM, 0x0000); /* unmute PCM out volume */ + ac97_write(codec, AC97_REC_GAIN, 0x0000);/* unmute record volume */ + + ad1980_add_controls(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ad1980: failed to register card\n"); + goto reset_err; + } + + return 0; + +reset_err: + snd_soc_free_pcms(socdev); + +pcm_err: + snd_soc_free_ac97_codec(codec); + +codec_err: + kfree(codec->reg_cache); + +cache_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int ad1980_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->reg_cache); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ad1980 = { + .probe = ad1980_soc_probe, + .remove = ad1980_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad1980); + +MODULE_DESCRIPTION("ASoC ad1980 driver"); +MODULE_AUTHOR("Roy Huang, Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1980.h b/sound/soc/codecs/ad1980.h new file mode 100644 index 0000000..5d4710d --- /dev/null +++ b/sound/soc/codecs/ad1980.h @@ -0,0 +1,23 @@ +/* + * ad1980.h -- ad1980 Soc Audio driver + */ + +#ifndef _AD1980_H +#define _AD1980_H +/* Bit definition of Power-Down Control/Status Register */ +#define ADC 0x0001 +#define DAC 0x0002 +#define ANL 0x0004 +#define REF 0x0008 +#define PR0 0x0100 +#define PR1 0x0200 +#define PR2 0x0400 +#define PR3 0x0800 +#define PR4 0x1000 +#define PR5 0x2000 +#define PR6 0x4000 + +extern struct snd_soc_codec_dai ad1980_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad1980; + +#endif
Allow all the CODEC drivers to be built without a machine driver in order to facilitate testing of subsystem-wide changes and gain better coverage from automated testing efforts. This also helps things like the generic OpenFirmware machine driver for PowerPC.
Currently AC97 CODECs are not included since the current setup relies on having a controller driver available.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/codecs/Kconfig | 23 +++++++++++++++++++++++ 1 files changed, 23 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7ab74cd..1c93423 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1,3 +1,26 @@ +config SND_SOC_ALL_CODECS + tristate "Build all ASoC CODEC drivers" + select I2C + select SPI + select SND_SOC_AK4535 + select SND_SOC_UDA1380 + select SND_SOC_WM8510 + select SND_SOC_WM8731 + select SND_SOC_WM8750 + select SND_SOC_WM8753 + select SND_SOC_WM8990 + select SND_SOC_CS4270 + select SND_SOC_TLV320AIC26 + select SND_SOC_TLV320AIC3X + help + Normally ASoC codec drivers are only built if a machine driver which + uses them is also built since they are only usable with a machine + driver. Selecting this option will allow these drivers to be built + without an explicit machine driver for test and development purposes. + + If unsure select "N". + + config SND_SOC_AC97_CODEC tristate select SND_AC97_CODEC
On 7/29/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
From: Jon Smirl jonsmirl@gmail.com
Convert bitfields in ASoC into full int width. This is a simple mechanical conversion. Two places in the DAPM code were fixed to properly use mask.
Do you want me to change the uints to unsigned int?
Signed-off-by: Jon Smirl jonsmirl@gmail.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com
include/sound/soc.h | 50 ++++++++++++++++++----------- sound/soc/soc-core.c | 86 ++++++++++++++++++++++++++++++-------------------- sound/soc/soc-dapm.c | 52 ++++++++++++++++++------------ 3 files changed, 114 insertions(+), 74 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index 2ce530e..e647a34 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -26,10 +26,12 @@ /*
- Convenience kcontrol builders
*/ -#define SOC_SINGLE_VALUE(reg, shift, max, invert) ((reg) | ((shift) << 8) |\
((shift) << 12) | ((max) << 16) | ((invert) << 24))
-#define SOC_SINGLE_VALUE_EXT(reg, max, invert) ((reg) | ((max) << 16) |\
((invert) << 31))
+#define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) \
((unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .shift = xshift, .max = xmax, .invert = xinvert})
+#define SOC_SINGLE_VALUE_EXT(xreg, xmax, xinvert) \
((unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .max = xmax, .invert = xinvert})
#define SOC_SINGLE(xname, reg, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ @@ -43,45 +45,49 @@ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ .put = snd_soc_put_volsw, \ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } -#define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \ +#define SOC_DOUBLE(xname, xreg, shift_left, shift_right, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ .put = snd_soc_put_volsw, \
.private_value = (reg) | ((shift_left) << 8) | \
((shift_right) << 12) | ((max) << 16) | ((invert) << 24) }
-#define SOC_DOUBLE_R(xname, reg_left, reg_right, shift, max, invert) \
.private_value = (unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .shift = shift_left, .rshift = shift_right, \
.max = xmax, .invert = xinvert} }
+#define SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .info = snd_soc_info_volsw_2r, \ .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \
.private_value = (reg_left) | ((shift) << 8) | \
((max) << 12) | ((invert) << 20) | ((reg_right) << 24) }
-#define SOC_DOUBLE_TLV(xname, reg, shift_left, shift_right, max, invert, tlv_array) \
.private_value = (unsigned long)&(struct soc_mixer_control) \
{.reg = reg_left, .rreg = reg_right, .shift = xshift, \
.max = xmax, .invert = xinvert} }
+#define SOC_DOUBLE_TLV(xname, xreg, shift_left, shift_right, xmax, xinvert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ .put = snd_soc_put_volsw, \
.private_value = (reg) | ((shift_left) << 8) | \
((shift_right) << 12) | ((max) << 16) | ((invert) << 24) }
-#define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, shift, max, invert, tlv_array) \
.private_value = (unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .shift = shift_left, .rshift = shift_right,\
.max = xmax, .invert = xinvert} }
+#define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw_2r, \ .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \
.private_value = (reg_left) | ((shift) << 8) | \
((max) << 12) | ((invert) << 20) | ((reg_right) << 24) }
-#define SOC_DOUBLE_S8_TLV(xname, reg, min, max, tlv_array) \
.private_value = (unsigned long)&(struct soc_mixer_control) \
{.reg = reg_left, .rreg = reg_right, .shift = xshift, \
.max = xmax, .invert = xinvert} }
+#define SOC_DOUBLE_S8_TLV(xname, xreg, xmin, xmax, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw_s8, .get = snd_soc_get_volsw_s8, \ .put = snd_soc_put_volsw_s8, \
.private_value = (reg) | (((signed char)max) << 16) | \
(((signed char)min) << 24) }
.private_value = (unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .min = xmin, .max = xmax} }
#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, xtexts) \ { .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ .mask = xmask, .texts = xtexts } @@ -518,6 +524,12 @@ struct snd_soc_pcm_runtime { struct snd_soc_device *socdev; };
+/* mixer control */ +struct soc_mixer_control {
int min, max;
uint reg, rreg, shift, rshift, invert;
+};
/* enumerated kcontrol */ struct soc_enum { unsigned short reg; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 5d3bf73..4b94f1f 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1454,9 +1454,11 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext); int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) {
int max = (kcontrol->private_value >> 16) & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int max = mc->max;
uint shift = mc->min;
uint rshift = mc->rshift; if (max == 1) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
@@ -1482,13 +1484,15 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw); int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) {
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
int max = (kcontrol->private_value >> 16) & 0xff;
int mask = (1 << fls(max)) - 1;
int invert = (kcontrol->private_value >> 24) & 0x01;
uint reg = mc->reg;
uint shift = mc->shift;
uint rshift = mc->rshift;
int max = mc->max;
uint mask = (1 << fls(max)) - 1;
uint invert = mc->invert; ucontrol->value.integer.value[0] = (snd_soc_read(codec, reg) >> shift) & mask;
@@ -1519,13 +1523,15 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw); int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) {
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
int max = (kcontrol->private_value >> 16) & 0xff;
int mask = (1 << fls(max)) - 1;
int invert = (kcontrol->private_value >> 24) & 0x01;
uint reg = mc->reg;
uint shift = mc->shift;
uint rshift = mc->rshift;
int max = mc->max;
uint mask = (1 << fls(max)) - 1;
uint invert = mc->invert; unsigned short val, val2, val_mask; val = (ucontrol->value.integer.value[0] & mask);
@@ -1557,7 +1563,9 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw); int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) {
int max = (kcontrol->private_value >> 12) & 0xff;
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int max = mc->max; if (max == 1) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
@@ -1583,13 +1591,15 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r); int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) {
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int reg2 = (kcontrol->private_value >> 24) & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int max = (kcontrol->private_value >> 12) & 0xff;
int mask = (1<<fls(max))-1;
int invert = (kcontrol->private_value >> 20) & 0x01;
uint reg = mc->reg;
uint reg2 = mc->rreg;
uint shift = mc->shift;
int max = mc->max;
uint mask = (1<<fls(max))-1;
uint invert = mc->invert; ucontrol->value.integer.value[0] = (snd_soc_read(codec, reg) >> shift) & mask;
@@ -1618,13 +1628,15 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r); int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) {
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int reg2 = (kcontrol->private_value >> 24) & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int max = (kcontrol->private_value >> 12) & 0xff;
int mask = (1 << fls(max)) - 1;
int invert = (kcontrol->private_value >> 20) & 0x01;
uint reg = mc->reg;
uint reg2 = mc->rreg;
uint shift = mc->shift;
int max = mc->max;
uint mask = (1 << fls(max)) - 1;
uint invert = mc->invert; int err; unsigned short val, val2, val_mask;
@@ -1661,8 +1673,10 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r); int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) {
int max = (signed char)((kcontrol->private_value >> 16) & 0xff);
int min = (signed char)((kcontrol->private_value >> 24) & 0xff);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int max = mc->max;
int min = mc->min; uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2;
@@ -1684,9 +1698,11 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8); int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) {
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int min = (signed char)((kcontrol->private_value >> 24) & 0xff);
uint reg = mc->reg;
int min = mc->min; int val = snd_soc_read(codec, reg); ucontrol->value.integer.value[0] =
@@ -1709,9 +1725,11 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw_s8); int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) {
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int min = (signed char)((kcontrol->private_value >> 24) & 0xff);
uint reg = mc->reg;
int min = mc->min; unsigned short val; val = (ucontrol->value.integer.value[0]+min) & 0xff;
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index f9d100b..bbdca0d 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -104,10 +104,13 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, case snd_soc_dapm_switch: case snd_soc_dapm_mixer: { int val;
int reg = w->kcontrols[i].private_value & 0xff;
int shift = (w->kcontrols[i].private_value >> 8) & 0x0f;
int mask = (w->kcontrols[i].private_value >> 16) & 0xff;
int invert = (w->kcontrols[i].private_value >> 24) & 0x01;
struct soc_mixer_control *mc = (struct soc_mixer_control *)
w->kcontrols[i].private_value;
uint reg = mc->reg;
uint shift = mc->shift;
int max = mc->max;
uint mask = (1 << fls(max)) - 1;
uint invert = mc->invert; val = snd_soc_read(w->codec, reg); val = (val >> shift) & mask;
@@ -247,16 +250,19 @@ static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power) return 0;
if (widget->num_kcontrols && k) {
int reg = k->private_value & 0xff;
int shift = (k->private_value >> 8) & 0x0f;
int mask = (k->private_value >> 16) & 0xff;
int invert = (k->private_value >> 24) & 0x01;
struct soc_mixer_control *mc =
(struct soc_mixer_control *)k->private_value;
uint reg = mc->reg;
uint shift = mc->shift;
int max = mc->max;
uint mask = (1 << fls(max)) - 1;
uint invert = mc->invert; if (power) { int i; /* power up has happended, increase volume to last level */ if (invert) {
for (i = mask; i > widget->saved_value; i--)
for (i = max; i > widget->saved_value; i--) snd_soc_update_bits(widget->codec, reg, mask, i); } else { for (i = 0; i < widget->saved_value; i++)
@@ -1133,12 +1139,14 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
int max = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0x01;
int mask = (1 << fls(max)) - 1;
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
uint reg = mc->reg;
uint shift = mc->shift;
uint rshift = mc->rshift;
int max = mc->max;
uint invert = mc->invert;
uint mask = (1 << fls(max)) - 1; /* return the saved value if we are powered down */ if (widget->id == snd_soc_dapm_pga && !widget->power) {
@@ -1176,12 +1184,14 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0x0f;
int rshift = (kcontrol->private_value >> 12) & 0x0f;
int max = (kcontrol->private_value >> 16) & 0xff;
int mask = (1 << fls(max)) - 1;
int invert = (kcontrol->private_value >> 24) & 0x01;
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
uint reg = mc->reg;
uint shift = mc->shift;
uint rshift = mc->rshift;
int max = mc->max;
uint mask = (1 << fls(max)) - 1;
uint invert = mc->invert; unsigned short val, val2, val_mask; int ret;
-- 1.5.6.3
On 7/29/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Jul 29, 2008 at 09:49:30AM -0400, Jon Smirl wrote:
Do you want me to change the uints to unsigned int?
Yes, that would be good.
Other patches in this series are stacked on top of this one. Let's put it in as in and then do a new one with only uint to unsigned int replacements.
At Tue, 29 Jul 2008 10:06:40 -0400, Jon Smirl wrote:
On 7/29/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Jul 29, 2008 at 09:49:30AM -0400, Jon Smirl wrote:
Do you want me to change the uints to unsigned int?
Yes, that would be good.
Other patches in this series are stacked on top of this one. Let's put it in as in and then do a new one with only uint to unsigned int replacements.
They are already there, master and devel branches on git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6.git
Takashi
At Tue, 29 Jul 2008 09:49:30 -0400, Jon Smirl wrote:
On 7/29/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
From: Jon Smirl jonsmirl@gmail.com
Convert bitfields in ASoC into full int width. This is a simple mechanical conversion. Two places in the DAPM code were fixed to properly use mask.
Do you want me to change the uints to unsigned int?
Yes, that'll be cleaner.
thanks,
Takashi
Mark, I see this patch is already in your tree. I have rewritten it a little differently to ensure that too many results can't be returned. This is in my system and tested. I had a bug too so I know the limiting it to pagesize code works.
Signed-off-by: Jon Smirl jonsmirl@gmail.com ---
include/sound/soc.h | 1 + sound/soc/soc-core.c | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index 1890d87..b95c0c2 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -410,6 +410,7 @@ struct snd_soc_codec { void *control_data; /* codec control (i2c/3wire) data */ unsigned int (*read)(struct snd_soc_codec *, unsigned int); int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); + int (*display_register)(struct snd_soc_codec *, char *, unsigned int, unsigned int); hw_write_t hw_write; hw_read_t hw_read; void *reg_cache; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 83f1190..55f7559 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -961,7 +961,7 @@ static ssize_t codec_reg_show(struct device *dev, { struct snd_soc_device *devdata = dev_get_drvdata(dev); struct snd_soc_codec *codec = devdata->codec; - int i, step = 1, count = 0; + int i, step = 1, count;
if (!codec->reg_cache_size) return 0; @@ -969,11 +969,18 @@ static ssize_t codec_reg_show(struct device *dev, if (codec->reg_cache_step) step = codec->reg_cache_step;
- count += sprintf(buf, "%s registers\n", codec->name); - for (i = 0; i < codec->reg_cache_size; i += step) - count += sprintf(buf + count, "%2x: %4x\n", i, - codec->read(codec, i)); - + count = sprintf(buf, "%s registers\n", codec->name); + for (i = 0; i < codec->reg_cache_size; i += step) { + if (codec->display_register) + count += codec->display_register(codec, + buf + count, PAGE_SIZE - count, i); + else { + count += snprintf(buf + count, PAGE_SIZE - count, "%2x: ", i); + count += snprintf(buf + count, PAGE_SIZE - count, "%4x", + codec->read(codec, i)); + count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + } + } return count; } static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL);
At Thu, 31 Jul 2008 21:41:48 -0400, Jon Smirl wrote:
Mark, I see this patch is already in your tree. I have rewritten it a little differently to ensure that too many results can't be returned. This is in my system and tested. I had a bug too so I know the limiting it to pagesize code works.
Mark's patch was already applied. Could you re-generated your change based on that?
thanks,
Takashi
Signed-off-by: Jon Smirl jonsmirl@gmail.com
include/sound/soc.h | 1 + sound/soc/soc-core.c | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index 1890d87..b95c0c2 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -410,6 +410,7 @@ struct snd_soc_codec { void *control_data; /* codec control (i2c/3wire) data */ unsigned int (*read)(struct snd_soc_codec *, unsigned int); int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
- int (*display_register)(struct snd_soc_codec *, char *, unsigned
int, unsigned int); hw_write_t hw_write; hw_read_t hw_read; void *reg_cache; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 83f1190..55f7559 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -961,7 +961,7 @@ static ssize_t codec_reg_show(struct device *dev, { struct snd_soc_device *devdata = dev_get_drvdata(dev); struct snd_soc_codec *codec = devdata->codec;
- int i, step = 1, count = 0;
int i, step = 1, count;
if (!codec->reg_cache_size) return 0;
@@ -969,11 +969,18 @@ static ssize_t codec_reg_show(struct device *dev, if (codec->reg_cache_step) step = codec->reg_cache_step;
- count += sprintf(buf, "%s registers\n", codec->name);
- for (i = 0; i < codec->reg_cache_size; i += step)
count += sprintf(buf + count, "%2x: %4x\n", i,
codec->read(codec, i));
- count = sprintf(buf, "%s registers\n", codec->name);
- for (i = 0; i < codec->reg_cache_size; i += step) {
if (codec->display_register)
count += codec->display_register(codec,
buf + count, PAGE_SIZE - count, i);
else {
count += snprintf(buf + count, PAGE_SIZE - count, "%2x: ", i);
count += snprintf(buf + count, PAGE_SIZE - count, "%4x",
codec->read(codec, i));
count += snprintf(buf + count, PAGE_SIZE - count, "\n");
}
- } return count;
} static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL);
-- Jon Smirl jonsmirl@gmail.com
On Fri, Aug 01, 2008 at 11:42:28AM +0200, Takashi Iwai wrote:
Jon Smirl wrote:
Mark, I see this patch is already in your tree. I have rewritten it a little differently to ensure that too many results can't be returned. This is in my system and tested. I had a bug too so I know the limiting it to pagesize code works.
Mark's patch was already applied. Could you re-generated your change based on that?
In general it's easiest if ASoC patches are generated against Takashi's master branch - that's where ASoC gets merged and usually the diff between the two trees is very small.
On 8/1/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Fri, Aug 01, 2008 at 11:42:28AM +0200, Takashi Iwai wrote:
Jon Smirl wrote:
Mark, I see this patch is already in your tree. I have rewritten it a little differently to ensure that too many results can't be returned. This is in my system and tested. I had a bug too so I know the limiting it to pagesize code works.
Mark's patch was already applied. Could you re-generated your change based on that?
In general it's easiest if ASoC patches are generated against Takashi's master branch - that's where ASoC gets merged and usually the diff between the two trees is very small.
This is against Takashi's tree. I handled PAGE_LIMIT differently, instead of breaking from the loop I just let the loop finished and told snprintf it had zero bytes.
Allow external display_register()'s to skip sparse registers.
Signed-off-by: Jon Smirl jonsmirl@gmail.com
---
include/sound/soc.h | 3 +-- sound/soc/soc-core.c | 33 ++++++++++----------------------- 2 files changed, 11 insertions(+), 25 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index a1e0357..1f4a9a2 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -416,8 +416,7 @@ struct snd_soc_codec { void *control_data; /* codec control (i2c/3wire) data */ unsigned int (*read)(struct snd_soc_codec *, unsigned int); int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); - int (*display_register)(struct snd_soc_codec *, char *, - size_t, unsigned int); + int (*display_register)(struct snd_soc_codec *, char *, unsigned int, unsigned int); hw_write_t hw_write; hw_read_t hw_read; void *reg_cache; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 1563cee..fcc613c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -961,7 +961,7 @@ static ssize_t codec_reg_show(struct device *dev, { struct snd_soc_device *devdata = dev_get_drvdata(dev); struct snd_soc_codec *codec = devdata->codec; - int i, step = 1, count = 0; + int i, step = 1, count;
if (!codec->reg_cache_size) return 0; @@ -969,31 +969,18 @@ static ssize_t codec_reg_show(struct device *dev, if (codec->reg_cache_step) step = codec->reg_cache_step;
- count += sprintf(buf, "%s registers\n", codec->name); + count = sprintf(buf, "%s registers\n", codec->name); for (i = 0; i < codec->reg_cache_size; i += step) { - count += sprintf(buf + count, "%2x: ", i); - if (count >= PAGE_SIZE - 1) - break; - if (codec->display_register) - count += codec->display_register(codec, buf + count, - PAGE_SIZE - count, i); - else - count += snprintf(buf + count, PAGE_SIZE - count, - "%4x", codec->read(codec, i)); - - if (count >= PAGE_SIZE - 1) - break; - - count += snprintf(buf + count, PAGE_SIZE - count, "\n"); - if (count >= PAGE_SIZE - 1) - break; + count += codec->display_register(codec, + buf + count, PAGE_SIZE - count, i); + else { + count += snprintf(buf + count, PAGE_SIZE - count, "%2x: ", i); + count += snprintf(buf + count, PAGE_SIZE - count, "%4x", + codec->read(codec, i)); + count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + } } - - /* Truncate count; min() would cause a warning */ - if (count >= PAGE_SIZE) - count = PAGE_SIZE - 1; - return count; } static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL);
At Fri, 1 Aug 2008 10:50:37 -0400, Jon Smirl wrote:
On 8/1/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Fri, Aug 01, 2008 at 11:42:28AM +0200, Takashi Iwai wrote:
Jon Smirl wrote:
Mark, I see this patch is already in your tree. I have rewritten it a little differently to ensure that too many results can't be returned. This is in my system and tested. I had a bug too so I know the limiting it to pagesize code works.
Mark's patch was already applied. Could you re-generated your change based on that?
In general it's easiest if ASoC patches are generated against Takashi's master branch - that's where ASoC gets merged and usually the diff between the two trees is very small.
This is against Takashi's tree. I handled PAGE_LIMIT differently, instead of breaking from the loop I just let the loop finished and told snprintf it had zero bytes.
Allow external display_register()'s to skip sparse registers.
Signed-off-by: Jon Smirl jonsmirl@gmail.com
include/sound/soc.h | 3 +-- sound/soc/soc-core.c | 33 ++++++++++----------------------- 2 files changed, 11 insertions(+), 25 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index a1e0357..1f4a9a2 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -416,8 +416,7 @@ struct snd_soc_codec { void *control_data; /* codec control (i2c/3wire) data */ unsigned int (*read)(struct snd_soc_codec *, unsigned int); int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
- int (*display_register)(struct snd_soc_codec *, char *,
size_t, unsigned int);
- int (*display_register)(struct snd_soc_codec *, char *, unsigned
int, unsigned int);
This change is backward.
@@ -969,31 +969,18 @@ static ssize_t codec_reg_show(struct device *dev, if (codec->reg_cache_step) step = codec->reg_cache_step;
- count += sprintf(buf, "%s registers\n", codec->name);
- count = sprintf(buf, "%s registers\n", codec->name); for (i = 0; i < codec->reg_cache_size; i += step) {
count += sprintf(buf + count, "%2x: ", i);
if (count >= PAGE_SIZE - 1)
break;
- if (codec->display_register)
count += codec->display_register(codec, buf + count,
PAGE_SIZE - count, i);
else
count += snprintf(buf + count, PAGE_SIZE - count,
"%4x", codec->read(codec, i));
if (count >= PAGE_SIZE - 1)
break;
count += snprintf(buf + count, PAGE_SIZE - count, "\n");
if (count >= PAGE_SIZE - 1)
break;
count += codec->display_register(codec,
buf + count, PAGE_SIZE - count, i);
Please make it fit in 80 chars like the original code.
else {
count += snprintf(buf + count, PAGE_SIZE - count, "%2x: ", i);
count += snprintf(buf + count, PAGE_SIZE - count, "%4x",
codec->read(codec, i));
count += snprintf(buf + count, PAGE_SIZE - count, "\n");
These can be a single snprintf().
You should add the loop break condition (count >= PAGE_SIZE - 1) somewhere, too.
}}
- /* Truncate count; min() would cause a warning */
- if (count >= PAGE_SIZE)
count = PAGE_SIZE - 1;
Really safe to remove this?
thanks,
Takashi
On Fri, Aug 01, 2008 at 05:23:27PM +0200, Takashi Iwai wrote:
Jon Smirl wrote:
count += codec->display_register(codec, buf + count,
PAGE_SIZE - count, i);
...
count += codec->display_register(codec,
buf + count, PAGE_SIZE - count, i);
Please make it fit in 80 chars like the original code.
This also seems to be an indentation-only change but perhaps I'm missing something here?
- /* Truncate count; min() would cause a warning */
- if (count >= PAGE_SIZE)
count = PAGE_SIZE - 1;
Really safe to remove this?
No. snprintf() returns how many bytes it wanted to write, not the number of bytes it wrote, which means that if the end of the buffer is hit then count will include the overspill and sysfs will complain.
At Fri, 1 Aug 2008 16:35:40 +0100, Mark Brown wrote:
On Fri, Aug 01, 2008 at 05:23:27PM +0200, Takashi Iwai wrote:
Jon Smirl wrote:
count += codec->display_register(codec, buf + count,
PAGE_SIZE - count, i);
...
count += codec->display_register(codec,
buf + count, PAGE_SIZE - count, i);
Please make it fit in 80 chars like the original code.
This also seems to be an indentation-only change but perhaps I'm missing something here?
In the new code, display_register() prints both the index and the value. This allows you to skip the unnecessary registers.
- /* Truncate count; min() would cause a warning */
- if (count >= PAGE_SIZE)
count = PAGE_SIZE - 1;
Really safe to remove this?
No. snprintf() returns how many bytes it wanted to write, not the number of bytes it wrote, which means that if the end of the buffer is hit then count will include the overspill and sysfs will complain.
Ah, yes. Then it has to be checked.
thanks,
Takashi
Allow external register display to suppress sparse registers
Signed-off-by: Jon Smirl jonsmirl@gmail.com
---
sound/soc/soc-core.c | 22 +++++++++++----------- 1 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 1563cee..74e626f 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -961,7 +961,7 @@ static ssize_t codec_reg_show(struct device *dev, { struct snd_soc_device *devdata = dev_get_drvdata(dev); struct snd_soc_codec *codec = devdata->codec; - int i, step = 1, count = 0; + int i, step = 1, count;
if (!codec->reg_cache_size) return 0; @@ -969,23 +969,23 @@ static ssize_t codec_reg_show(struct device *dev, if (codec->reg_cache_step) step = codec->reg_cache_step;
- count += sprintf(buf, "%s registers\n", codec->name); + count = sprintf(buf, "%s registers\n", codec->name); for (i = 0; i < codec->reg_cache_size; i += step) { - count += sprintf(buf + count, "%2x: ", i); - if (count >= PAGE_SIZE - 1) - break; - if (codec->display_register) count += codec->display_register(codec, buf + count, PAGE_SIZE - count, i); - else + else { + count += sprintf(buf + count, "%2x: ", i); + if (count >= PAGE_SIZE - 1) + break; + count += snprintf(buf + count, PAGE_SIZE - count, "%4x", codec->read(codec, i)); + if (count >= PAGE_SIZE - 1) + break;
- if (count >= PAGE_SIZE - 1) - break; - - count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + } if (count >= PAGE_SIZE - 1) break; }
On Fri, Aug 01, 2008 at 12:13:52PM -0400, Jon Smirl wrote:
Allow external register display to suppress sparse registers
As I said when you originally proposed this I'm really not happy about changing to that interface.
Adding explicit information about registers to skip and having this function use it would be useful but munging it in makes the register display function non-reusable and means that other parts of ASoC will be unable to use the information about the void registers should they have a use for it.
On 8/1/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Fri, Aug 01, 2008 at 12:13:52PM -0400, Jon Smirl wrote:
Allow external register display to suppress sparse registers
As I said when you originally proposed this I'm really not happy about changing to that interface.
Adding explicit information about registers to skip and having this function use it would be useful but munging it in makes the register display function non-reusable and means that other parts of ASoC will be unable to use the information about the void registers should they have a use for it.
Why would ASoC care about the holes in my sparse register list? You can't use this info for suspend/resume, my registers are variable length too. I need quite a bit of information to build a cache of sparse variable length registers.
/* Size and number of variable length entries in the cache */ #define TAS_CACHE_1_MAX 41 #define TAS_CACHE_2_MAX 16 #define TAS_CACHE_3_MAX 2 #define TAS_CACHE_4_MAX 8 #define TAS_CACHE_5_MAX 30 #define TAS_CACHE_6_MAX 4
/* Size and offset of specific entries in the cache */ static struct { u8 size; u8 offset; } cache_map[] = { [0x00]= {1, 0xff}, /* TAS_REG_CLOCK_CONTROL */ [0x01]= {1, 0xff}, /* TAS_REG_GENERAL_STATUS */ [0x02]= {1, 0xff}, /* TAS_REG_ERROR_STATUS */ [0x03]= {1, 0}, /* TAS_REG_SYS_CONTROL_1 */ [0x04]= {1, 1}, /* TAS_REG_SYS_CONTROL_2 */ [0x05]= {1, 2}, /* TAS_REG_CH_CONFIG_1 */ [0x06]= {1, 3}, /* TAS_REG_CH_CONFIG_2 */ [0x0b]= {1, 4}, /* TAS_REG_CH_CONFIG_3 */ [0x0c]= {1, 5}, /* TAS_REG_CH_CONFIG_4 */ [0x0d]= {1, 6}, /* TAS_REG_HP_CONFIG */ [0x0e]= {1, 0xff}, /* TAS_REG_SERIAL_CONTROL */ [0x0f]= {1, 7}, /* TAS_REG_SOFT_MUTE */ [0x14]= {1, 8}, /* TAS_REG_AUTO_MUTE */ [0x15]= {1, 9}, /* TAS_REG_AUTO_MUTE_PWM */ [0x16]= {1, 10}, /* TAS_REG_MODULATE_LIMIT */ [0x1b]= {1, 11}, /* TAS_REG_IC_DELAY_1 */ [0x1c]= {1, 12}, /* TAS_REG_IC_DELAY_2 */ [0x21]= {1, 13}, /* TAS_REG_IC_DELAY_3 */ [0x22]= {1, 14}, /* TAS_REG_IC_DELAY_4 */ [0x23]= {1, 15}, /* TAS_REG_IC_OFFSET */ [0x40]= {1, 16}, /* TAS_REG_BANK_SWITCH */ [0x41]= {8, 1,}, /* TAS_REG_IN8X4_1 */ [0x42]= {8, 2,}, /* TAS_REG_IN8X4_2 */ [0x47]= {8, 3,}, /* TAS_REG_IN8X4_3 */ [0x48]= {8, TAS_CACHE_2_MAX,}, /* TAS_REG_IN8X4_4 */ [0x49]= {1, 17}, /* TAS_REG_IPMIX_1_TO_CH4 */ [0x4a]= {1, 18}, /* TAS_REG_IPMIX_2_TO_CH4 */ [0x4b]= {1, 19}, /* TAS_REG_IPMIX_3_TO_CH2 */ [0x4c]= {1, 20}, /* TAS_REG_CH3_BP_BQ2 */ [0x4d]= {1, 21}, /* TAS_REG_CH3_BP */ [0x4e]= {1, 22}, /* TAS_REG_IPMIX_4_TO_CH12 */ [0x4f]= {1, 23}, /* TAS_REG_CH4_BP_BQ2 */ [0x50]= {1, 24}, /* TAS_REG_CH4_BP */ [0x51]= {5, 1,}, /* TAS_REG_CH1_BQ_1 */ [0x52]= {5, 2,}, /* TAS_REG_CH1_BQ_2 */ [0x53]= {5, 3,}, /* TAS_REG_CH1_BQ_3 */ [0x54]= {5, 4,}, /* TAS_REG_CH1_BQ_4 */ [0x55]= {5, 5,}, /* TAS_REG_CH1_BQ_5 */ [0x56]= {5, 6,}, /* TAS_REG_CH1_BQ_6 */ [0x57]= {5, 7,}, /* TAS_REG_CH1_BQ_7 */ [0x58]= {5, 8,}, /* TAS_REG_CH2_BQ_1 */ [0x59]= {5, 9,}, /* TAS_REG_CH2_BQ_2 */ [0x5a]= {5, 10,}, /* TAS_REG_CH2_BQ_3 */ [0x5b]= {5, 11,}, /* TAS_REG_CH2_BQ_4 */ [0x5c]= {5, 12,}, /* TAS_REG_CH2_BQ_5 */ [0x5d]= {5, 13,}, /* TAS_REG_CH2_BQ_6 */ [0x5e]= {5, 14,}, /* TAS_REG_CH2_BQ_7 */ [0x7b]= {5, 15,}, /* TAS_REG_CH3_BQ_1 */ [0x7c]= {5, 16,}, /* TAS_REG_CH3_BQ_2 */ [0x7d]= {5, 17,}, /* TAS_REG_CH3_BQ_3 */ [0x7e]= {5, 18,}, /* TAS_REG_CH3_BQ_4 */ [0x7f]= {5, 19,}, /* TAS_REG_CH3_BQ_5 */ [0x80]= {5, 20,}, /* TAS_REG_CH3_BQ_6 */ [0x81]= {5, 21,}, /* TAS_REG_CH3_BQ_7 */ [0x82]= {5, 22,}, /* TAS_REG_CH4_BQ_1 */ [0x83]= {5, 23,}, /* TAS_REG_CH4_BQ_2 */ [0x84]= {5, 24,}, /* TAS_REG_CH4_BQ_3 */ [0x85]= {5, 25,}, /* TAS_REG_CH4_BQ_4 */ [0x86]= {5, 26,}, /* TAS_REG_CH4_BQ_5 */ [0x87]= {5, 27,}, /* TAS_REG_CH4_BQ_6 */ [0x88]= {5, 28,}, /* TAS_REG_CH4_BQ_7 */ [0x89]= {2, 1,}, /* TAS_REG_BT_BYPASS_CH1 */ [0x8a]= {2, 2,}, /* TAS_REG_BT_BYPASS_CH2 */ [0x8f]= {2, 3,}, /* TAS_REG_BT_BYPASS_CH3 */ [0x90]= {2, 4,}, /* TAS_REG_BT_BYPASS_CH4 */ [0x91]= {1, 25,}, /* TAS_REG_LOUDNESS_LG */ [0x92]= {2, 5,}, /* TAS_REG_LOUDNESS_LO */ [0x93]= {1, 26,}, /* TAS_REG_LOUDNESS_G */ [0x94]= {2, 6,}, /* TAS_REG_LOUDNESS_O */ [0x95]= {5, 29,}, /* TAS_REG_LOUDNESS_BQ */ [0x96]= {1, 27,}, /* TAS_REG_DRC1_CNTL_123 */ [0x97]= {1, 28,}, /* TAS_REG_DRC2_CNTL_4 */ [0x98]= {2, 7,}, /* TAS_REG_DRC1_ENERGY */ [0x99]= {4, 3,}, /* TAS_REG_DRC1_THRESHOLD */ [0x9a]= {3, 1,}, /* TAS_REG_DRC1_SLOPE */ [0x9b]= {4, 4,}, /* TAS_REG_DRC1_OFFSET */ [0x9c]= {4, 5,}, /* TAS_REG_DRC1_ATTACK */ [0x9d]= {2, 8,}, /* TAS_REG_DRC2_ENERGY */ [0x9e]= {4, 6,}, /* TAS_REG_DRC2_THRESHOLD */ [0x9f]= {2, TAS_CACHE_3_MAX,}, /* TAS_REG_DRC2_SLOPE */ [0xa0]= {4, 7,}, /* TAS_REG_DRC2_OFFSET */ [0xa1]= {4, TAS_CACHE_2_MAX,}, /* TAS_REG_DRC2_ATTACK */ [0xa2]= {2, 9,}, /* TAS_REG_DRC_BYPASS_1 */ [0xa3]= {2, 10,}, /* TAS_REG_DRC_BYPASS_2 */ [0xa8]= {2, 11,}, /* TAS_REG_DRC_BYPASS_3 */ [0xa9]= {2, 12,}, /* TAS_REG_DRC_BYPASS_4 */ [0xaa]= {2, 13,}, /* TAS_REG_SEL_OP14_S */ [0xab]= {2, 14,}, /* TAS_REG_SEL_OP14_T */ [0xb0]= {2, 15,}, /* TAS_REG_SEL_OP14_Y */ [0xb1]= {2, TAS_CACHE_2_MAX,}, /* TAS_REG_SEL_OP14_Z */ [0xcf]= {5, TAS_CACHE_5_MAX,}, /* TAS_REG_VOLUME_BQ */ [0xd0]= {1, 29}, /* TAS_REG_VOL_TB_SLEW */ [0xd1]= {1, 30}, /* TAS_REG_VOL_CH1 */ [0xd2]= {1, 31}, /* TAS_REG_VOL_CH2 */ [0xd7]= {1, 32}, /* TAS_REG_VOL_CH3 */ [0xd8]= {1, 33}, /* TAS_REG_VOL_CH4 */ [0xd9]= {1, 34}, /* TAS_REG_VOL_MASTER */ [0xda]= {1, 35}, /* TAS_REG_BASS_SET */ [0xdb]= {1, 36}, /* TAS_REG_BASS_INDEX */ [0xdc]= {1, 37}, /* TAS_REG_TREBLE_SET */ [0xdd]= {1, 38}, /* TAS_REG_TREBLE_INDEX */ [0xde]= {1, 39}, /* TAS_REG_AM_MODE */ [0xdf]= {1, 40}, /* TAS_REG_PSVC */ [0xe0]= {1, TAS_CACHE_1_MAX,}, /* TAS_REG_GENERAL_CONTROL */ };
/* location of each variable length segment in the cache array */ #define TAS_CACHE_1_BASE 0 #define TAS_CACHE_2_BASE TAS_CACHE_1_BASE + (TAS_CACHE_1_MAX + 1) #define TAS_CACHE_3_BASE TAS_CACHE_2_BASE + (TAS_CACHE_2_MAX + 1) * 2 #define TAS_CACHE_4_BASE TAS_CACHE_3_BASE + (TAS_CACHE_3_MAX + 1) * 3 #define TAS_CACHE_5_BASE TAS_CACHE_4_BASE + (TAS_CACHE_4_MAX + 1) * 4 #define TAS_CACHE_8_BASE TAS_CACHE_5_BASE + (TAS_CACHE_5_MAX + 1) * 5 #define TAS_CACHE_MAX TAS_CACHE_8_BASE + (TAS_CACHE_5_MAX + 1) * 8
/* tas5504 driver private data */ struct tas5504_priv { struct i2c_client *client; struct snd_soc_codec codec;
/* performance shadow of i2c registers */ u32 reg_cache[TAS_CACHE_MAX]; u8 valid[TAS_CACHE_MAX]; };
/* these are using custom controls */ static const struct snd_kcontrol_new tas5504_snd_controls[] = { TAS_CTL_1R(0x00, 0, 0, "Clock Control"), TAS_CTL_1R(0x01, 0, 0, "General Status"), TAS_CTL_1(0x02, 0, 0, "Error Status"), TAS_CTL_1(0x03, 0, 0, "System Control 1"), TAS_CTL_1(0x04, 0, 0, "System Control 2"), TAS_CTL_1(0x05, 1, 0, "General Config"), TAS_CTL_1(0x06, 2, 0, "General Config"), TAS_CTL_1(0x0b, 3, 0, "General Config"), TAS_CTL_1(0x0c, 4, 0, "General Config"), TAS_CTL_1(0x0d, 0, 0, "Headphone Config"), TAS_CTL_1R(0x0e, 0, 0, "Serial Data Interface"), TAS_CTL_1(0x0f, 0, 0, "Soft Mute"), TAS_CTL_1(0x14, 0, 0, "Auto Mute"), TAS_CTL_1(0x15, 0, 0, "Auto Mute PWM"), TAS_CTL_1R(0x16, 0, 0, "Modulation"), TAS_CTL_1(0x1b, 1, 0, "Interchannel Delay"), TAS_CTL_1(0x1c, 2, 0, "Interchannel Delay"), TAS_CTL_1(0x21, 3, 0, "Interchannel Delay"), TAS_CTL_1(0x22, 4, 0, "Interchannel Delay"), TAS_CTL_1(0x23, 0, 0, "Interchannel Offset"), TAS_CTL_1(0x40, 4, 0, "Bank Switching"), TAS_CTL_8(0x41, 1, 0, "Input Mixer"), TAS_CTL_8(0x42, 2, 0, "Input Mixer"), TAS_CTL_8(0x47, 3, 0, "Input Mixer"), TAS_CTL_8(0x48, 4, 0, "Input Mixer"), TAS_CTL_1(0x49, 0, 0, "Bass ipmix_1_to_ch4"), TAS_CTL_1(0x4a, 0, 0, "Bass ipmix_2_to_ch4"), TAS_CTL_1(0x4b, 0, 0, "Bass ipmix_3_to_ch12"), TAS_CTL_1(0x4c, 0, 0, "Bass ch3_bp_bq2"), TAS_CTL_1(0x4d, 0, 0, "Bass ch3_bq2"), TAS_CTL_1(0x4e, 0, 0, "Bass ipmix_4_to_ch12"), TAS_CTL_1(0x4f, 0, 0, "Bass ch4_bp_bq2"), TAS_CTL_1(0x50, 0, 0, "Bass ch4_bq2"), TAS_CTL_5(0x51, 1, 1, "Biquad"), TAS_CTL_5(0x52, 1, 2, "Biquad"), TAS_CTL_5(0x53, 1, 3, "Biquad"), TAS_CTL_5(0x54, 1, 4, "Biquad"), TAS_CTL_5(0x55, 1, 5, "Biquad"), TAS_CTL_5(0x56, 1, 6, "Biquad"), TAS_CTL_5(0x57, 1, 7, "Biquad"), TAS_CTL_5(0x58, 2, 1, "Biquad"), TAS_CTL_5(0x59, 2, 2, "Biquad"), TAS_CTL_5(0x5a, 2, 3, "Biquad"), TAS_CTL_5(0x5b, 2, 4, "Biquad"), TAS_CTL_5(0x5c, 2, 5, "Biquad"), TAS_CTL_5(0x5d, 2, 6, "Biquad"), TAS_CTL_5(0x5e, 2, 7, "Biquad"), TAS_CTL_5(0x7b, 3, 1, "Biquad"), TAS_CTL_5(0x7c, 3, 2, "Biquad"), TAS_CTL_5(0x7d, 3, 3, "Biquad"), TAS_CTL_5(0x7e, 3, 4, "Biquad"), TAS_CTL_5(0x7f, 3, 5, "Biquad"), TAS_CTL_5(0x80, 3, 6, "Biquad"), TAS_CTL_5(0x81, 3, 7, "Biquad"), TAS_CTL_5(0x82, 4, 1, "Biquad"), TAS_CTL_5(0x83, 4, 2, "Biquad"), TAS_CTL_5(0x84, 4, 3, "Biquad"), TAS_CTL_5(0x85, 4, 4, "Biquad"), TAS_CTL_5(0x86, 4, 5, "Biquad"), TAS_CTL_5(0x87, 4, 6, "Biquad"), TAS_CTL_5(0x88, 4, 7, "Biquad"), TAS_CTL_2(0x89, 1, 0, "Bass/Treble Bypass"), TAS_CTL_2(0x8a, 2, 0, "Bass/Treble Bypass"), TAS_CTL_2(0x8f, 3, 0, "Bass/Treble Bypass"), TAS_CTL_2(0x90, 4, 0, "Bass/Treble Bypass"), TAS_CTL_1(0x91, 0, 0, "Loudness Log2 Gain"), TAS_CTL_1_64(0x92, 0, 0, "Loudness Log2 Offset"), TAS_CTL_1(0x93, 0, 0, "Loudness Gain"), TAS_CTL_1_64(0x94, 0, 0, "Loudness Offset"), TAS_CTL_5(0x95, 0, 0, "Loudness Biquad"), TAS_CTL_1(0x96, 0, 0, "DRC Control 1-3"), TAS_CTL_1(0x97, 0, 0, "DRC Control 4"), TAS_CTL_2(0x97, 0, 0, "DRC Energy"), TAS_CTL_2_64(0x97, 0, 0, "DRC Threshold"), TAS_CTL_3(0x97, 0, 0, "DRC Slope"), TAS_CTL_2_64(0x97, 0, 0, "DRC Offset"), TAS_CTL_4(0x97, 0, 0, "DRC Attack/Delay"), TAS_CTL_2(0x9d, 4, 0, "DRC Energy"), TAS_CTL_2_64(0x9e, 4, 0, "DRC Threshold"), TAS_CTL_3(0x9f, 4, 0, "DRC Slope"), TAS_CTL_2_64(0xa0, 4, 0, "DRC Offset"), TAS_CTL_4(0xa1, 4, 0, "DRC Attack/Delay"), TAS_CTL_2(0xa2, 1, 0, "DRC Bypass"), TAS_CTL_2(0xa3, 2, 0, "DRC Bypass"), TAS_CTL_2(0xa8, 3, 0, "DRC Bypass"), TAS_CTL_2(0xa9, 4, 0, "DRC Bypass"), TAS_CTL_2(0xaa, 1, 0, "Output Mixer"), TAS_CTL_2(0xab, 2, 0, "Output Mixer"), TAS_CTL_3(0xb0, 3, 0, "Output Mixer"), TAS_CTL_3(0xb1, 4, 0, "Output Mixer"), TAS_CTL_5(0xcf, 0, 0, "Volume Biquad"), TAS_CTL_1(0xd0, 0, 0, "Volume Slew"), TAS_CTL_1(0xd1, 1, 0, "Volume"), TAS_CTL_1(0xd2, 2, 0, "Volume"), TAS_CTL_1(0xd7, 3, 0, "Volume"), TAS_CTL_1(0xd8, 4, 0, "Volume"), SOC_SINGLE("PCM Playback Volume", 0xd9, 0, 0x3ff, 1), TAS_CTL_1(0xda, 0, 0, "Bass Filter Set"), TAS_CTL_1(0xdb, 0, 0, "Bass Filter Index"), TAS_CTL_1(0xdc, 0, 0, "Treble Filter Set"), TAS_CTL_1(0xdd, 0, 0, "Treble Filter Index"), TAS_CTL_1(0xde, 0, 0, "AM Mode"), TAS_CTL_1(0xdf, 0, 0, "PSCV Range"), TAS_CTL_1(0xe0, 0, 0, "General Control"), };
root@phyCORE-MPC5200B-tiny:/sys cat ./devices/platform/soc-audio.0/codec_reg tas5504 registers 0: 54 1: 03 2: 78 3: 90 4: 00 5: e0 6: e0 b: e0 c: e0 d: 00 e: 05 f: 00 14: 84 15: 05 16: 02 1b: 80 1c: 00 21: e0 22: 60 23: 00 40: 0000ff00 41: fffffff6 000000cf c3809000 c39e9e90 c01df494 c0338890 00000001 000000cf 42: 00000ae3 c3052000 c3809004 c39e9eb0 00000000 00000000 00000000 00000000 47: 00000000 00000000 00000000 00000000 00000000 00000000 00800000 00000000 48: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00800000 49: 00000000 4a: 00000000 4b: 00000000 4c: 00000000 4d: 00800000 4e: 00000000 4f: 00000000 50: 00800000 51: fffffff6 000000a0 c3809000 00000000 00000000 52: 00800000 00000000 00000000 00000000 00000000 53: 00800000 00000000 00000000 00000000 00000000 54: 00800000 00000000 00000000 00000000 00000000 55: 00800000 00000000 00000000 0000883f 007f77c0 56: 00000056 003fffa8 c39e9e70 c01324a4 03000000 57: c39e9e78 c39e9e50 c38ebfc0 a1052000 00000000 58: 00000000 fffffff6 000000a1 c3809000 00000000 59: 00800000 00000000 00000000 00000000 00000000 5a: 00800000 00000000 00000000 00000000 00000000 5b: 00800000 00000000 00000000 00000000 00000000 5c: 00800000 00000000 00000000 00000000 00000000 5d: 00800000 00000000 00000000 00000000 00000000 5e: 00800000 00000000 00000000 00000000 00000000 7b: 00800000 00000000 00000000 00000000 00000000 7c: 00800000 00000000 00000000 00000000 00000000 7d: 00800000 00000000 00000000 00000000 00000000 7e: 00800000 00000000 00000000 00000000 00000000 7f: 00800000 00000000 00000000 00000000 00000000 80: 00800000 00000000 00000000 00000000 00000000 81: 00800000 00000000 00000000 00000000 00000000 82: 00800000 00000000 00000000 00000000 00000000 83: 00800000 00000000 00000000 00000000 00000000 84: 00800000 00000000 00000000 00000000 00000000 85: 00800000 00000000 00000000 00000000 00000000 86: 00800000 00000000 00000000 00000000 00000005 87: 00800000 00000000 00000000 00000000 00000000 88: 00800000 00000000 00000000 00000000 00000000 89: 00800000 00000000 8a: 00400000 0fc00000 8f: 00000005 00000028 90: 00800000 00000000 91: 0fc00000 92: 00000000 00000000 93: 00000000 94: 00000000 00000000 95: 0000d513 00000000 0fff2aed 00fe5045 0f81aa27 96: 00000000 97: 00000000 98: 0000883f 007f77c0 99: 00000000 0b20e2b2 00000000 06f9de58 9a: 00400000 0fc00000 0f900000 9b: 0000ffff ff823098 00000000 0195b2c0 9c: 0000883f 007f77c0 00000056 003fffa8 9d: 0000883f 007f77c0 9e: 00000000 0b20e2b2 00000000 06f9de58 9f: 00400000 0fc00000 a0: 0000ffff ff823098 00000000 0195b2c0 a1: 0000883f 007f77c0 00000056 003fffa8 a2: 00800000 00000000 a3: 00800000 00000000 a8: 00800000 00000000 a9: 00800000 00000000 aa: 00800000 00000000 ab: 10800000 00000000 b0: 60800000 00000000 b1: 70800000 00000000 cf: 00800000 00000000 00000000 00000000 00000000 d0: 0000013f d1: 00000048 d2: 00000048 d7: 00000048 d8: 00000048 d9: 00000245 da: 03030303 db: 12121212 dc: 03030303 dd: 12121212 de: 00000000 df: 00000002 e0: 00000000 root@phyCORE-MPC5200B-tiny:/sys
On Fri, Aug 01, 2008 at 12:27:27PM -0400, Jon Smirl wrote:
Why would ASoC care about the holes in my sparse register list? You can't use this info for suspend/resume, my registers are variable length too. I need quite a bit of information to build a cache of sparse variable length registers.
Well, this function here is one example - any other diagnostic output for registers will have the same issue, such as the more detailed information about register/control mapping which I talked about in my previous e-mail.
At Fri, 1 Aug 2008 17:16:13 +0100, Mark Brown wrote:
On Fri, Aug 01, 2008 at 12:13:52PM -0400, Jon Smirl wrote:
Allow external register display to suppress sparse registers
As I said when you originally proposed this I'm really not happy about changing to that interface.
Adding explicit information about registers to skip and having this function use it would be useful but munging it in makes the register display function non-reusable and means that other parts of ASoC will be unable to use the information about the void registers should they have a use for it.
Hm, is display_register already used in asoc tree by any drivers? If so, then it's a step backward, and I understand your statement.
OTOH, showing lots of meaningless registers is bad, too.
Looking back soc-core.c again, the primary problem is that codec_reg_show() assumes that the continuous register table beginning from 0. It'd be much easier if the driver provides a complete show method by itself for non-standard cases, e.g. sparse registers (i.e. just pass buf and let fill the whole page).
Takashi
On Fri, Aug 01, 2008 at 06:35:57PM +0200, Takashi Iwai wrote:
Hm, is display_register already used in asoc tree by any drivers? If so, then it's a step backward, and I understand your statement.
No, there are no users. It was added purely due to Jon's request.
OTOH, showing lots of meaningless registers is bad, too.
I agree - as I say, I do think supporting suppression of the display of irrelevant/nonexistant registers would be a win.
Looking back soc-core.c again, the primary problem is that codec_reg_show() assumes that the continuous register table beginning from 0. It'd be much easier if the driver provides a complete show method by itself for non-standard cases, e.g. sparse registers (i.e. just pass buf and let fill the whole page).
There are two issues:
- I don't like pushing the entire file format into the codec driver; it feels wrong since it increases the amount of non-shared code.
- Doing that prevents reuse of any part of the information elsewhere. For example, if we were to display a register value anywhere else a different interface would be required to allow codecs to override the display there.
I know there are people who'd love to see things like more detailed register/control mapping information.
On 8/1/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Fri, Aug 01, 2008 at 06:35:57PM +0200, Takashi Iwai wrote:
Hm, is display_register already used in asoc tree by any drivers? If so, then it's a step backward, and I understand your statement.
No, there are no users. It was added purely due to Jon's request.
OTOH, showing lots of meaningless registers is bad, too.
I agree - as I say, I do think supporting suppression of the display of irrelevant/nonexistant registers would be a win.
Looking back soc-core.c again, the primary problem is that codec_reg_show() assumes that the continuous register table beginning from 0. It'd be much easier if the driver provides a complete show method by itself for non-standard cases, e.g. sparse registers (i.e. just pass buf and let fill the whole page).
There are two issues:
- I don't like pushing the entire file format into the codec driver;
it feels wrong since it increases the amount of non-shared code.
- Doing that prevents reuse of any part of the information elsewhere.
For example, if we were to display a register value anywhere else a different interface would be required to allow codecs to override the display there.
But you are stuck with the override. You don't know how to display or even read the 256 bit registers present on the TI chips.
There are more fundamental issues, struct snd_kcontrol doesn't have a field for storing the register address, it is being carried around in the private_value data.
I know there are people who'd love to see things like more detailed register/control mapping information.
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Fri, Aug 01, 2008 at 01:37:09PM -0400, Jon Smirl wrote:
On 8/1/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
For example, if we were to display a register value anywhere else a different interface would be required to allow codecs to override the display there.
But you are stuck with the override. You don't know how to display or even read the 256 bit registers present on the TI chips.
Yes, absolutely - this is my point. I have no problem at all with having the ability to override the register display, it seems useful.
There are more fundamental issues, struct snd_kcontrol doesn't have a field for storing the register address, it is being carried around in the private_value data.
Yes, I don't see the implementation being enormously elegant. We should have enough information to figure it out, though.
At Tue, 29 Jul 2008 11:42:22 +0100, Mark Brown wrote:
From: Dmitry Baryshkov dbaryshkov@gmail.com
Signed-off-by: Dmitry Baryshkov dbaryshkov@gmail.com Cc: Richard Purdie rpurdie@rpsys.net Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com
This is a compile fix and it would therefore be good if it could be considered for merge into 2.6.27.
OK, applied and put to for-linus / for-next trees.
All others are for the next kernel, right?
thanks,
Takashi
On Tue, Jul 29, 2008 at 03:36:56PM +0200, Takashi Iwai wrote:
All others are for the next kernel, right?
Actually, "Export dapm_reg_event() fully" should probably be pushed in too but it's not so urgent.
There's no urgent reason to hold the drivers out of mainline but that'd bypass -next.
The core changes should definitely wait for next.
At Tue, 29 Jul 2008 14:47:13 +0100, Mark Brown wrote:
On Tue, Jul 29, 2008 at 03:36:56PM +0200, Takashi Iwai wrote:
All others are for the next kernel, right?
Actually, "Export dapm_reg_event() fully" should probably be pushed in too but it's not so urgent.
OK, let's push it too.
thanks,
Takashi
participants (3)
-
Jon Smirl
-
Mark Brown
-
Takashi Iwai