[alsa-devel] [PATCH 0/8] RX-51 audio drivers

Hello everyone,
Here is a patch series with most of audio support for RX51.
Basically it includes asoc machine driver for rx51, tpa6130a2 amplifier driver (asoc version), board file configuration and setup and the sidetone feature of mcbsp.
On top of that, there are few patches to use regulator framework to control vmmc2.
This series is based on linux-omap. So basically after applying it, you can boot the device and have sound support.
Even though this patches were tested using linux-omap, the series is applicable on top of sound-2.6 tree as well.
Please review. And as usual, comments are welcome.
BR,
--- Eduardo Valentin
Eduardo Valentin (6): ASoC: OMAP: RX-51 Machine driver and AIC34b_dummy driver OMAP: RX51: Add audio board file board-rx51-peripherals: split vaux3 and vmmc2 supplies RX-51: Audio: Add usage of regulator framework to control VMMC2 ASoC: tlv320aic3x: add initial usage of regulator framework to control avdd_dac ASoC: tpa6130a2: Control vdd using regulator framework
Eero Nurkkala (1): McBSP: OMAP3: Add Sidetone feature
Peter Ujfalusi (1): ASoC: TPA6130A2 amplifier driver
arch/arm/mach-omap2/Makefile | 1 + arch/arm/mach-omap2/board-rx51-audio.c | 132 +++++ arch/arm/mach-omap2/board-rx51-peripherals.c | 22 +- arch/arm/mach-omap2/mcbsp.c | 2 + arch/arm/plat-omap/include/mach/mcbsp.h | 43 ++ arch/arm/plat-omap/mcbsp.c | 379 ++++++++++++- include/sound/tpa6130a2-plat.h | 30 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tlv320aic3x.c | 26 + sound/soc/codecs/tpa6130a2.c | 396 +++++++++++++ sound/soc/codecs/tpa6130a2.h | 62 ++ sound/soc/omap/Kconfig | 10 + sound/soc/omap/Makefile | 2 + sound/soc/omap/aic34b_dummy.c | 271 +++++++++ sound/soc/omap/aic34b_dummy.h | 32 + sound/soc/omap/rx51.c | 793 ++++++++++++++++++++++++++ sound/soc/omap/rx51.h | 29 + 18 files changed, 2225 insertions(+), 11 deletions(-) create mode 100644 arch/arm/mach-omap2/board-rx51-audio.c create mode 100644 include/sound/tpa6130a2-plat.h create mode 100644 sound/soc/codecs/tpa6130a2.c create mode 100644 sound/soc/codecs/tpa6130a2.h create mode 100644 sound/soc/omap/aic34b_dummy.c create mode 100644 sound/soc/omap/aic34b_dummy.h create mode 100644 sound/soc/omap/rx51.c create mode 100644 sound/soc/omap/rx51.h
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

From: Peter Ujfalusi peter.ujfalusi@nokia.com
Driver for Texas Instruments TPA6130A2 headphone stereo amplifier.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@nokia.com Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com --- include/sound/tpa6130a2-plat.h | 30 +++ sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tpa6130a2.c | 380 ++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tpa6130a2.h | 62 +++++++ 5 files changed, 478 insertions(+), 0 deletions(-) create mode 100644 include/sound/tpa6130a2-plat.h create mode 100644 sound/soc/codecs/tpa6130a2.c create mode 100644 sound/soc/codecs/tpa6130a2.h
diff --git a/include/sound/tpa6130a2-plat.h b/include/sound/tpa6130a2-plat.h new file mode 100644 index 0000000..d315728 --- /dev/null +++ b/include/sound/tpa6130a2-plat.h @@ -0,0 +1,30 @@ +/* + * TPA6130A2 driver platform header + * + * Copyright (C) Nokia Corporation + * + * Written by Peter Ujfalusi peter.ujfalusi@nokia.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef TPA6130A2_PLAT_H +#define TPA6130A2_PLAT_H + +struct tpa6130a2_platform_data { + int (*set_power)(int state); +}; + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0edca93..2437fd3 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TLV320AIC3X if I2C + select SND_SOC_TPA6130A2 if I2C select SND_SOC_TWL4030 if TWL4030_CORE select SND_SOC_UDA134X select SND_SOC_UDA1380 if I2C @@ -220,3 +221,6 @@ config SND_SOC_WM9713 # Amp config SND_SOC_MAX9877 tristate + +config SND_SOC_TPA6130A2 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fb4af28..498c024 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -47,6 +47,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o
# Amp snd-soc-max9877-objs := max9877.o +snd-soc-tpa6130a2-objs := tpa6130a2.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o @@ -97,3 +98,4 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
# Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o +obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c new file mode 100644 index 0000000..d246aad --- /dev/null +++ b/sound/soc/codecs/tpa6130a2.c @@ -0,0 +1,380 @@ +/* + * ALSA SoC Texas Instruments TPA6130A2 headset stereo amplifier driver + * + * Copyright (C) Nokia Corporation + * + * Author: Peter Ujfalusi peter.ujfalusi@nokia.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/sysfs.h> +#include <sound/tpa6130a2-plat.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> + +#include "tpa6130a2.h" + +struct i2c_client *tpa6130a2_client; + +/* This struct is used to save the context */ +struct tpa6130a2_data { + /* mutex protect access to tpa6130a2_data structure */ + struct mutex mutex; + unsigned char regs[TPA6130A2_CACHEREGNUM]; + unsigned char power_state; + int (*set_power)(int state); +}; + +static int tpa6130a2_i2c_read(int reg) +{ + struct tpa6130a2_data *data; + int val; + + BUG_ON(tpa6130a2_client == NULL); + + data = i2c_get_clientdata(tpa6130a2_client); + + /* If powered off, return the cached value */ + if (data->power_state) { + val = i2c_smbus_read_byte_data(tpa6130a2_client, reg); + if (val < 0) + dev_err(&tpa6130a2_client->dev, "Read failed\n"); + else + data->regs[reg] = val; + } else { + val = data->regs[reg]; + } + + return val; +} + +static int tpa6130a2_i2c_write(int reg, u8 value) +{ + struct tpa6130a2_data *data; + int val = 0; + + BUG_ON(tpa6130a2_client == NULL); + + data = i2c_get_clientdata(tpa6130a2_client); + + if (data->power_state) { + val = i2c_smbus_write_byte_data(tpa6130a2_client, reg, value); + if (val < 0) + dev_err(&tpa6130a2_client->dev, "Write failed\n"); + } + + /* Either powered on or off, we save the context */ + data->regs[reg] = value; + + return val; +} + +static u8 tpa6130a2_read(int reg) +{ + struct tpa6130a2_data *data; + + BUG_ON(tpa6130a2_client == NULL); + + data = i2c_get_clientdata(tpa6130a2_client); + + return data->regs[reg]; +} + +static void tpa6130a2_initialize(void) +{ + struct tpa6130a2_data *data; + int i; + + BUG_ON(tpa6130a2_client == NULL); + + data = i2c_get_clientdata(tpa6130a2_client); + + for (i = 1; i < TPA6130A2_REG_VERSION; i++) + tpa6130a2_i2c_write(i, data->regs[i]); +} + +void tpa6130a2_power(int power) +{ + struct tpa6130a2_data *data; + u8 val; + + data = i2c_get_clientdata(tpa6130a2_client); + + mutex_lock(&data->mutex); + if (power) { + /* Power on */ + if (data->set_power) { + data->set_power(1); + data->power_state = 1; + tpa6130a2_initialize(); + } + /* Clear SWS */ + val = tpa6130a2_read(TPA6130A2_REG_CONTROL); + val &= ~TPA6130A2_SWS; + tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); + } else { + /* set SWS */ + val = tpa6130a2_read(TPA6130A2_REG_CONTROL); + val |= TPA6130A2_SWS; + tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); + /* Power off */ + if (data->set_power) { + data->set_power(0); + data->power_state = 0; + } + } + mutex_unlock(&data->mutex); +} + +static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int mask = mc->max; + unsigned int invert = mc->invert; + struct tpa6130a2_data *data; + + BUG_ON(tpa6130a2_client == NULL); + + data = i2c_get_clientdata(tpa6130a2_client); + + mutex_lock(&data->mutex); + + ucontrol->value.integer.value[0] = + (tpa6130a2_read(reg) >> shift) & mask; + + if (invert) + ucontrol->value.integer.value[0] = + mask - ucontrol->value.integer.value[0]; + + mutex_unlock(&data->mutex); + return 0; +} + +static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int mask = mc->max; + unsigned int invert = mc->invert; + unsigned int val = (ucontrol->value.integer.value[0] & mask); + unsigned int val_reg; + struct tpa6130a2_data *data; + + BUG_ON(tpa6130a2_client == NULL); + + data = i2c_get_clientdata(tpa6130a2_client); + + if (invert) + val = mask - val; + + mutex_lock(&data->mutex); + + val_reg = tpa6130a2_read(reg); + if (((val_reg >> shift) & mask) == val) { + mutex_unlock(&data->mutex); + return 0; + } + + val_reg &= ~(mask << shift); + val_reg |= val << shift; + tpa6130a2_i2c_write(reg, val_reg); + + mutex_unlock(&data->mutex); + + return 1; +} + +/* + * TPA6130 volume. From -59.5 to 4 dB with increasing step size when going + * down in gain. Justify scale so that it is quite correct from -20 dB and + * up. This setting shows -30 dB at minimum, -12.95 dB at 49 % (actual + * is -10.3 dB) and 4.65 dB at maximum (actual is 4 dB). + */ +static const unsigned int tpa6130_tlv[] = { + TLV_DB_RANGE_HEAD(10), + 0, 1, TLV_DB_SCALE_ITEM(-5950, 600, 0), + 2, 3, TLV_DB_SCALE_ITEM(-5000, 250, 0), + 4, 5, TLV_DB_SCALE_ITEM(-4550, 160, 0), + 6, 7, TLV_DB_SCALE_ITEM(-4140, 190, 0), + 8, 9, TLV_DB_SCALE_ITEM(-3650, 120, 0), + 10, 11, TLV_DB_SCALE_ITEM(-3330, 160, 0), + 12, 13, TLV_DB_SCALE_ITEM(-3040, 180, 0), + 14, 20, TLV_DB_SCALE_ITEM(-2710, 110, 0), + 21, 37, TLV_DB_SCALE_ITEM(-1960, 74, 0), + 38, 63, TLV_DB_SCALE_ITEM(-720, 45, 0), +}; + +static const struct snd_kcontrol_new tpa6130a2_controls[] = { + SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0, + tpa6130a2_get_reg, tpa6130a2_set_reg, + tpa6130_tlv), +}; + +static int tpa6130a2_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + tpa6130a2_power(1); + else + tpa6130a2_power(0); + + return 0; +} + +static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { + /* Outputs */ + SND_SOC_DAPM_HP("TPA6130A2 Headphone", tpa6130a2_hp_event), +}; + +int tpa6130a2_add_controls(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, tpa6130a2_dapm_widgets, + ARRAY_SIZE(tpa6130a2_dapm_widgets)); + + return snd_soc_add_controls(codec, tpa6130a2_controls, + ARRAY_SIZE(tpa6130a2_controls)); + +} +EXPORT_SYMBOL(tpa6130a2_add_controls); + +static int tpa6130a2_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + struct device *dev; + struct tpa6130a2_data *data; + struct tpa6130a2_platform_data *pdata; + + dev = &client->dev; + + tpa6130a2_client = client; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) { + err = -ENOMEM; + goto fail1; + } + + i2c_set_clientdata(tpa6130a2_client, data); + + if (client->dev.platform_data == NULL) { + dev_err(dev, "Platform data not set\n"); + dump_stack(); + err = -ENODEV; + goto fail2; + } + + pdata = (struct tpa6130a2_platform_data *)client->dev.platform_data; + /* Set default register values */ + data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS | + TPA6130A2_HP_EN_R | + TPA6130A2_HP_EN_L; + data->regs[TPA6130A2_REG_VOL_MUTE] = TPA6130A2_VOLUME(20); + + mutex_init(&data->mutex); + + data->set_power = pdata->set_power; + if (!pdata->set_power) { + data->power_state = 1; + tpa6130a2_initialize(); + } + tpa6130a2_power(1); + + /* Read version */ + err = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) & + TPA6130A2_VERSION_MASK; + if ((err != 1) && (err != 2)) { + dev_err(dev, "Unexpected headphone amplifier chip version " + "of 0x%02x, was expecting 0x01 or 0x02\n", err); + err = -ENODEV; + + goto fail3; + } + + dev_info(dev, "Version %d detected\n", err); + + /* Disable the chip */ + tpa6130a2_power(0); + + return 0; +fail3: + tpa6130a2_power(0); +fail2: + kfree(data); + i2c_set_clientdata(tpa6130a2_client, NULL); +fail1: + tpa6130a2_client = 0; + + return err; +} + +static int tpa6130a2_remove(struct i2c_client *client) +{ + struct tpa6130a2_data *data = i2c_get_clientdata(client); + + tpa6130a2_power(0); + kfree(data); + tpa6130a2_client = 0; + + return 0; +} + +static const struct i2c_device_id tpa6130a2_id[] = { + { "tpa6130a2", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tpa6130a2_id); + +static struct i2c_driver tpa6130a2_i2c_driver = { + .driver = { + .name = "tpa6130a2", + .owner = THIS_MODULE, + }, + .probe = tpa6130a2_probe, + .remove = __devexit_p(tpa6130a2_remove), + .id_table = tpa6130a2_id, +}; + +static int __init tpa6130a2_init(void) +{ + return i2c_add_driver(&tpa6130a2_i2c_driver); +} + +static void __exit tpa6130a2_exit(void) +{ + i2c_del_driver(&tpa6130a2_i2c_driver); +} + +MODULE_AUTHOR("Peter Ujfalusi"); +MODULE_DESCRIPTION("TPA6130A2 Headphone amplifier driver"); +MODULE_LICENSE("GPL"); + +module_init(tpa6130a2_init); +module_exit(tpa6130a2_exit); diff --git a/sound/soc/codecs/tpa6130a2.h b/sound/soc/codecs/tpa6130a2.h new file mode 100644 index 0000000..6a794f1 --- /dev/null +++ b/sound/soc/codecs/tpa6130a2.h @@ -0,0 +1,62 @@ +/* + * ALSA SoC TPA6130A2 amplifier driver + * + * Copyright (C) Nokia Corporation + * + * Author: Peter Ujfalusi peter.ujfalusi@nokia.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TPA6130A2_H__ +#define __TPA6130A2_H__ + +/* Register addresses */ +#define TPA6130A2_REG_CONTROL 0x01 +#define TPA6130A2_REG_VOL_MUTE 0x02 +#define TPA6130A2_REG_OUT_IMPEDANCE 0x03 +#define TPA6130A2_REG_VERSION 0x04 + +#define TPA6130A2_CACHEREGNUM (TPA6130A2_REG_VERSION + 1) + +/* Register bits */ +/* TPA6130A2_REG_CONTROL (0x01) */ +#define TPA6130A2_SWS (0x01 << 0) +#define TPA6130A2_TERMAL (0x01 << 1) +#define TPA6130A2_MODE(x) (x << 4) +#define TPA6130A2_MODE_STEREO (0x00) +#define TPA6130A2_MODE_DUAL_MONO (0x01) +#define TPA6130A2_MODE_BRIDGE (0x02) +#define TPA6130A2_MODE_MASK (0x03) +#define TPA6130A2_HP_EN_R (0x01 << 6) +#define TPA6130A2_HP_EN_L (0x01 << 7) + +/* TPA6130A2_REG_VOL_MUTE (0x02) */ +#define TPA6130A2_VOLUME(x) ((x & 0x3f) << 0) +#define TPA6130A2_MUTE_R (0x01 << 6) +#define TPA6130A2_MUTE_L (0x01 << 7) + +/* TPA6130A2_REG_OUT_IMPEDANCE (0x03) */ +#define TPA6130A2_HIZ_R (0x01 << 0) +#define TPA6130A2_HIZ_L (0x01 << 1) + +/* TPA6130A2_REG_VERSION (0x04) */ +#define TPA6130A2_VERSION_MASK (0x0f) + +extern int tpa6130a2_add_controls(struct snd_soc_codec *codec); +extern void tpa6130a2_power(int power); + +#endif /* __TPA6130A2_H__ */

On Thu, 2009-10-08 at 13:58 +0200, Valentin Eduardo (Nokia-D/Helsinki) wrote:
+/*
- TPA6130 volume. From -59.5 to 4 dB with increasing step size when going
- down in gain. Justify scale so that it is quite correct from -20 dB and
- up. This setting shows -30 dB at minimum, -12.95 dB at 49 % (actual
- is -10.3 dB) and 4.65 dB at maximum (actual is 4 dB).
- */
The comment is misleading from all it says. For me it seems that the scale is quite correct from -59.5 to 4 dB or so. Also the minimum is -59.5 dB, not -30.
+static const unsigned int tpa6130_tlv[] = {
TLV_DB_RANGE_HEAD(10),
0, 1, TLV_DB_SCALE_ITEM(-5950, 600, 0),
2, 3, TLV_DB_SCALE_ITEM(-5000, 250, 0),
4, 5, TLV_DB_SCALE_ITEM(-4550, 160, 0),
6, 7, TLV_DB_SCALE_ITEM(-4140, 190, 0),
8, 9, TLV_DB_SCALE_ITEM(-3650, 120, 0),
10, 11, TLV_DB_SCALE_ITEM(-3330, 160, 0),
12, 13, TLV_DB_SCALE_ITEM(-3040, 180, 0),
14, 20, TLV_DB_SCALE_ITEM(-2710, 110, 0),
21, 37, TLV_DB_SCALE_ITEM(-1960, 74, 0),
38, 63, TLV_DB_SCALE_ITEM(-720, 45, 0),
+};
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thursday 08 October 2009 15:30:29 Nurkkala Eero.An (EXT-Offcode/Oulu) wrote:
On Thu, 2009-10-08 at 13:58 +0200, Valentin Eduardo (Nokia-D/Helsinki)
wrote:
+/*
- TPA6130 volume. From -59.5 to 4 dB with increasing step size when
going + * down in gain. Justify scale so that it is quite correct from -20 dB and + * up. This setting shows -30 dB at minimum, -12.95 dB at 49 % (actual + * is -10.3 dB) and 4.65 dB at maximum (actual is 4 dB).
- */
The comment is misleading from all it says. For me it seems that the scale is quite correct from -59.5 to 4 dB or so. Also the minimum is -59.5 dB, not -30.
Yes, the scale is really close to the reality, the comment need to be fixed.

On Thu, Oct 08, 2009 at 02:58:50PM +0300, Eduardo Valentin wrote:
+struct tpa6130a2_platform_data {
- int (*set_power)(int state);
+};
Why is this a callback and not just a GPIO number? That'd seem simpler for users.
+int tpa6130a2_add_controls(struct snd_soc_codec *codec) +{
- snd_soc_dapm_new_controls(codec, tpa6130a2_dapm_widgets,
ARRAY_SIZE(tpa6130a2_dapm_widgets));
- return snd_soc_add_controls(codec, tpa6130a2_controls,
ARRAY_SIZE(tpa6130a2_controls));
+} +EXPORT_SYMBOL(tpa6130a2_add_controls);
All the ASoC APIs are EXPORT_SYMBOL_GPL().
- pdata = (struct tpa6130a2_platform_data *)client->dev.platform_data;
- /* Set default register values */
- data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS |
TPA6130A2_HP_EN_R |
TPA6130A2_HP_EN_L;
This looks like you could implement stereo paths with individual power control?
- data->regs[TPA6130A2_REG_VOL_MUTE] = TPA6130A2_VOLUME(20);
The standard thing is to use hardware defaults and leave decisions like this up to user space.
- data->set_power = pdata->set_power;
- if (!pdata->set_power) {
data->power_state = 1;
tpa6130a2_initialize();
- }
- tpa6130a2_power(1);
- /* Read version */
- err = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) &
TPA6130A2_VERSION_MASK;
- if ((err != 1) && (err != 2)) {
dev_err(dev, "Unexpected headphone amplifier chip version "
"of 0x%02x, was expecting 0x01 or 0x02\n", err);
err = -ENODEV;
This seems a bit excessive - is it really likely that the register map would be changed incompatibly in a future version? -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thursday 08 October 2009 15:52:13 ext Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:50PM +0300, Eduardo Valentin wrote:
+struct tpa6130a2_platform_data {
- int (*set_power)(int state);
+};
Why is this a callback and not just a GPIO number? That'd seem simpler for users.
Well, same amount of code, but in different place if the power is enabled through a GPIO. But if the power is controlled via different means (nothing comes to mind, but there are exotic systems out there), in this way it is simple to handle
+int tpa6130a2_add_controls(struct snd_soc_codec *codec) +{
- snd_soc_dapm_new_controls(codec, tpa6130a2_dapm_widgets,
ARRAY_SIZE(tpa6130a2_dapm_widgets));
- return snd_soc_add_controls(codec, tpa6130a2_controls,
ARRAY_SIZE(tpa6130a2_controls));
+} +EXPORT_SYMBOL(tpa6130a2_add_controls);
All the ASoC APIs are EXPORT_SYMBOL_GPL().
Right.
- pdata = (struct tpa6130a2_platform_data *)client->dev.platform_data;
- /* Set default register values */
- data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS |
TPA6130A2_HP_EN_R |
TPA6130A2_HP_EN_L;
This looks like you could implement stereo paths with individual power control?
Can we put something in between DAPM_OUTPUT and DAPM_HP to handle the stereo path correctly? We could have two paths from codec to the "TPA6130A2 Headphone", which is needed to actually control the power of the amplifier. At the end we are not really gaining much, but one more level of switch on the path. We could have simple mute alsa controls in tpa6130a2_controls for the amplifier itself...
- data->regs[TPA6130A2_REG_VOL_MUTE] = TPA6130A2_VOLUME(20);
The standard thing is to use hardware defaults and leave decisions like this up to user space.
OK, The reset value is 0 (-59.5 dB)
- data->set_power = pdata->set_power;
- if (!pdata->set_power) {
data->power_state = 1;
tpa6130a2_initialize();
- }
- tpa6130a2_power(1);
- /* Read version */
- err = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) &
TPA6130A2_VERSION_MASK;
- if ((err != 1) && (err != 2)) {
dev_err(dev, "Unexpected headphone amplifier chip version "
"of 0x%02x, was expecting 0x01 or 0x02\n", err);
err = -ENODEV;
This seems a bit excessive - is it really likely that the register map would be changed incompatibly in a future version?
Hmm, we have only seen these versions of the chip, and we know that the driver works with these. Also we don't have any information on different versions, but I would think that the register map is not changing (the reset values for some registers are different)

On Thu, Oct 08, 2009 at 04:38:24PM +0300, Peter Ujfalusi wrote:
On Thursday 08 October 2009 15:52:13 ext Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:50PM +0300, Eduardo Valentin wrote:
+struct tpa6130a2_platform_data {
- int (*set_power)(int state);
+};
Why is this a callback and not just a GPIO number? That'd seem simpler for users.
Well, same amount of code, but in different place if the power is enabled
Until someone adds a second board, at which point they need to copy the code to acquire and release the GPIO.
through a GPIO. But if the power is controlled via different means (nothing comes to mind, but there are exotic systems out there), in this way it is simple to handle
I suspect we can deal with that adequately when it crops up, for example by having the driver set up a default callback function if a GPIO is specified instead of a callback.
- pdata = (struct tpa6130a2_platform_data *)client->dev.platform_data;
- /* Set default register values */
- data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS |
TPA6130A2_HP_EN_R |
TPA6130A2_HP_EN_L;
This looks like you could implement stereo paths with individual power control?
Can we put something in between DAPM_OUTPUT and DAPM_HP to handle the stereo path correctly?
Yes.
We could have two paths from codec to the "TPA6130A2 Headphone", which is needed to actually control the power of the amplifier.
Or make "TPA6130A2 Headphone" be a SND_SOC_DAPM_SUPPLY() connected to the individual channels for the headphone outputs, which should do the right thing.
At the end we are not really gaining much, but one more level of switch on the path. We could have simple mute alsa controls in tpa6130a2_controls for the amplifier itself...
It'd mean that mono outputs would only need to enable one of the channels. Depending on the hardware feeding the amp this may behave better - there may be some noise or leakage on the idle channel which it'd be better to avoid amplifying - and it should certainly use a little less power.
- err = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) &
TPA6130A2_VERSION_MASK;
- if ((err != 1) && (err != 2)) {
dev_err(dev, "Unexpected headphone amplifier chip version "
"of 0x%02x, was expecting 0x01 or 0x02\n", err);
err = -ENODEV;
This seems a bit excessive - is it really likely that the register map would be changed incompatibly in a future version?
Hmm, we have only seen these versions of the chip, and we know that the driver works with these. Also we don't have any information on different versions, but I would think that the register map is not changing (the reset values for some registers are different)
It's fairly common to see some incompatible changes in early silicon revisions but once things get properly launched it's fairly unusual. I'd be more inclined to print a warning here rather than fail - chances are that the driver will work fine with any new revisions that are produced. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thursday 08 October 2009 16:53:42 ext Mark Brown wrote:
On Thu, Oct 08, 2009 at 04:38:24PM +0300, Peter Ujfalusi wrote:
On Thursday 08 October 2009 15:52:13 ext Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:50PM +0300, Eduardo Valentin wrote:
+struct tpa6130a2_platform_data {
- int (*set_power)(int state);
+};
Why is this a callback and not just a GPIO number? That'd seem simpler for users.
Well, same amount of code, but in different place if the power is enabled
Until someone adds a second board, at which point they need to copy the code to acquire and release the GPIO.
through a GPIO. But if the power is controlled via different means (nothing comes to mind, but there are exotic systems out there), in this way it is simple to handle
I suspect we can deal with that adequately when it crops up, for example by having the driver set up a default callback function if a GPIO is specified instead of a callback.
Good point. I'll replace the .set_power with power_gpio.
- pdata = (struct tpa6130a2_platform_data
*)client->dev.platform_data; + /* Set default register values */
- data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS |
TPA6130A2_HP_EN_R |
TPA6130A2_HP_EN_L;
This looks like you could implement stereo paths with individual power control?
Can we put something in between DAPM_OUTPUT and DAPM_HP to handle the stereo path correctly?
Yes.
Great. I can add one SND_SOC_DAPM_PGA_E per channel, which enables/disables the channel. Adding a widget with actual alsa control seams to be problematic, since those are working with the codec's registers, so adding such a widget would require to implement a new set of .info .get and .set functions for the TPA alone.
We could have two paths from codec to the "TPA6130A2 Headphone", which is needed to actually control the power of the amplifier.
Or make "TPA6130A2 Headphone" be a SND_SOC_DAPM_SUPPLY() connected to the individual channels for the headphone outputs, which should do the right thing.
I see, using the event/event_flags with the DAPM_SUPPLY should do it. So is it OK if I modify the tpa6130a2 DAPM path to be like this: PGA_E("TPA6130A2 Left") -> SUPPLY("TPA6130A2 power") -> HP("TPA6130A2 Headphone")
PGA_E("TPA6130A2 Right") -> SUPPLY("TPA6130A2 power") -> HP("TPA6130A2 Headphone")
Or should I add individual HP for the two channels: HP("TPA6130A2 Headphone Left") HP("TPA6130A2 Headphone Right")
Than in machine driver just connect (for example rx51): {"TPA6130A2 Left", NULL, "LLOUT"}, {"TPA6130A2 Right", NULL, "RLOUT"},
At the end we are not really gaining much, but one more level of switch on the path. We could have simple mute alsa controls in tpa6130a2_controls for the amplifier itself...
It'd mean that mono outputs would only need to enable one of the channels. Depending on the hardware feeding the amp this may behave better - there may be some noise or leakage on the idle channel which it'd be better to avoid amplifying - and it should certainly use a little less power.
OK, Does my proposal above feasible?
- err = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) &
TPA6130A2_VERSION_MASK;
- if ((err != 1) && (err != 2)) {
dev_err(dev, "Unexpected headphone amplifier chip version "
"of 0x%02x, was expecting 0x01 or 0x02\n", err);
err = -ENODEV;
This seems a bit excessive - is it really likely that the register map would be changed incompatibly in a future version?
Hmm, we have only seen these versions of the chip, and we know that the driver works with these. Also we don't have any information on different versions, but I would think that the register map is not changing (the reset values for some registers are different)
It's fairly common to see some incompatible changes in early silicon revisions but once things get properly launched it's fairly unusual. I'd be more inclined to print a warning here rather than fail - chances are that the driver will work fine with any new revisions that are produced.
Right, going to be warning (with text change).
Thanks, Péter -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Fri, Oct 09, 2009 at 09:53:21AM +0300, Peter Ujfalusi wrote:
On Thursday 08 October 2009 16:53:42 ext Mark Brown wrote:
Adding a widget with actual alsa control seams to be problematic, since those are working with the codec's registers, so adding such a widget would require to implement a new set of .info .get and .set functions for the TPA alone.
Yes, chips like this are the major reason for needing to support having more than one CODEC device in the core.
Or should I add individual HP for the two channels: HP("TPA6130A2 Headphone Left") HP("TPA6130A2 Headphone Right")
Than in machine driver just connect (for example rx51): {"TPA6130A2 Left", NULL, "LLOUT"}, {"TPA6130A2 Right", NULL, "RLOUT"},
That's what I was thinking of, yes.

From: Eduardo Valentin eduardo.valentin@nokia.com
Introduce RX-51 Machine driver for ASoC and AIC34b_dummy (block B) i2c driver.
Also move the request_gpio of speaker_enabled from board-rx51-peripherals.c to this machine driver.
These drivers were originally written by Jarkko Nikula.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com --- arch/arm/mach-omap2/board-rx51-peripherals.c | 2 - sound/soc/omap/Kconfig | 10 + sound/soc/omap/Makefile | 2 + sound/soc/omap/aic34b_dummy.c | 271 +++++++++ sound/soc/omap/aic34b_dummy.h | 32 + sound/soc/omap/rx51.c | 793 ++++++++++++++++++++++++++ sound/soc/omap/rx51.h | 29 + 7 files changed, 1137 insertions(+), 2 deletions(-) create mode 100644 sound/soc/omap/aic34b_dummy.c create mode 100644 sound/soc/omap/aic34b_dummy.h create mode 100644 sound/soc/omap/rx51.c create mode 100644 sound/soc/omap/rx51.h
diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index c1af532..b227475 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -262,8 +262,6 @@ static int rx51_twlgpio_setup(struct device *dev, unsigned gpio, unsigned n) /* FIXME this gpio setup is just a placeholder for now */ gpio_request(gpio + 6, "backlight_pwm"); gpio_direction_output(gpio + 6, 0); - gpio_request(gpio + 7, "speaker_en"); - gpio_direction_output(gpio + 7, 1);
/* set up MMC adapters, linking their regulators to them */ twl4030_mmc_init(mmc); diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 2dee983..bdcd4be 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -15,6 +15,16 @@ config SND_OMAP_SOC_N810 help Say Y if you want to add support for SoC audio on Nokia N810.
+config SND_OMAP_SOC_RX51 + tristate "SoC Audio support for Nokia RX51" + depends on SND_OMAP_SOC && MACH_NOKIA_RX51 + select OMAP_MCBSP + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC3X + select SND_SOC_TPA6130A2 + help + Say Y if you want to add support for SoC audio on Nokia RX51. + config SND_OMAP_SOC_AMS_DELTA tristate "SoC Audio support for Amstrad E3 (Delta) videophone" depends on SND_OMAP_SOC && MACH_AMS_DELTA diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 02d6947..7dec270 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -16,8 +16,10 @@ snd-soc-sdp3430-objs := sdp3430.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap3beagle-objs := omap3beagle.o snd-soc-zoom2-objs := zoom2.o +snd-soc-rx51-objs := rx51.o
obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o aic34b_dummy.o obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o diff --git a/sound/soc/omap/aic34b_dummy.c b/sound/soc/omap/aic34b_dummy.c new file mode 100644 index 0000000..17c4d9c --- /dev/null +++ b/sound/soc/omap/aic34b_dummy.c @@ -0,0 +1,271 @@ +/* + * aic34b_dummy.c -- Dummy driver for AIC34 block B parts used in Nokia RX51 + * + * Purpose for this driver is to cover few audio connections on Nokia RX51 HW + * which are connected into block B of TLV320AIC34 dual codec. + * + * Copyright (C) 2008 - 2009 Nokia Corporation + * + * Contact: Peter Ujfalusi peter.ujfalusi@nokia.com + * Eduardo Valentin eduardo.valentin@nokia.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * TODO: + * - Get rid of this driver, at least when ASoC v2 is merged and when + * we can support multiple codec instances in tlv320aic3x.c driver. + * This driver is hacked only for Nokia RX51 HW. + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <sound/soc.h> + +#include "../codecs/tlv320aic3x.h" + +struct i2c_client *aic34b_client; +static DEFINE_MUTEX(aic34b_mutex); +static DEFINE_MUTEX(button_press_mutex); +static ktime_t button_press_denial_start; +static int aic34b_volume; +static int button_press_denied; +static int aic34b_bias; + + +static int aic34b_read(struct i2c_client *client, unsigned int reg, + u8 *value) +{ + int err; + + err = i2c_smbus_read_byte_data(client, reg); + *value = err; + return (err >= 0) ? 0 : err; +} + +static int aic34b_write(struct i2c_client *client, unsigned int reg, + u8 value) +{ + u8 data[2]; + + data[0] = reg & 0xff; + data[1] = value & 0xff; + + return (i2c_master_send(client, data, 2) == 2) ? 0 : -EIO; +} + +/* + * Introduce a derivative FIR filter to detect unnecessary button + * presses caused by a change in the MICBIAS. The filter returns + * TRUE in the event there has not been a change in MICBIAS within + * the time window (500ms). If the rate of change within the window + * is >= 1, all button presses are denied. In addition, if bias is + * zero, then all button presses are also denied explicitly. + */ +int allow_button_press(void) +{ + /* If bias is not on, no chance for button presses */ + if (!aic34b_bias) + return 0; + + /* If explicitly granted a button press */ + if (!button_press_denied) { + return 1; + } else { + int64_t delta; + /* This is the FIR portion with specified time window */ + mutex_lock(&button_press_mutex); + delta = ktime_to_ns(ktime_sub(ktime_get(), + button_press_denial_start)); + + if (delta < 0) { + button_press_denied = 0; + /* If the clock ever wraps */ + button_press_denial_start.tv.sec = 0; + button_press_denial_start.tv.nsec = 0; + mutex_unlock(&button_press_mutex); + return 1; + } + do_div(delta, 1000000); + /* Time window is 500ms */ + if (delta >= 500) { + button_press_denied = 0; + mutex_unlock(&button_press_mutex); + return 1; + } + mutex_unlock(&button_press_mutex); + } + + /* There was a change in MICBIAS within time window */ + return 0; +} +EXPORT_SYMBOL(allow_button_press); + +static void deny_button_press(void) +{ + mutex_lock(&button_press_mutex); + button_press_denied = 1; + button_press_denial_start = ktime_get(); + mutex_unlock(&button_press_mutex); +} + +void aic34b_set_mic_bias(int bias) +{ + if (aic34b_client == NULL) + return; + + mutex_lock(&aic34b_mutex); + aic34b_write(aic34b_client, MICBIAS_CTRL, (bias & 0x3) << 6); + aic34b_bias = bias; + deny_button_press(); + mutex_unlock(&aic34b_mutex); +} +EXPORT_SYMBOL(aic34b_set_mic_bias); + +int aic34b_set_volume(u8 volume) +{ + u8 val; + + if (aic34b_client == NULL) + return 0; + + mutex_lock(&aic34b_mutex); + + /* Volume control for Right PGA to HPLOUT */ + aic34b_read(aic34b_client, 49, &val); + val &= ~0x7f; + aic34b_write(aic34b_client, 49, val | (~volume & 0x7f)); + + /* Volume control for Right PGA to HPLCOM */ + aic34b_read(aic34b_client, 56, &val); + val &= ~0x7f; + aic34b_write(aic34b_client, 56, val | (~volume & 0x7f)); + + aic34b_volume = volume; + mutex_unlock(&aic34b_mutex); + + return 0; +} +EXPORT_SYMBOL(aic34b_set_volume); + +void aic34b_ear_enable(int enable) +{ + u8 val; + + if (aic34b_client == NULL) + return; + + mutex_lock(&aic34b_mutex); + if (enable) { + /* Connect LINE2R to RADC */ + aic34b_write(aic34b_client, LINE2R_2_RADC_CTRL, 0x80); + /* Unmute Right ADC-PGA */ + aic34b_write(aic34b_client, RADC_VOL, 0x00); + /* Right PGA -> HPLOUT */ + aic34b_read(aic34b_client, 49, &val); + aic34b_write(aic34b_client, 49, val | 0x80); + /* Unmute HPLOUT with 1 dB gain */ + aic34b_write(aic34b_client, HPLOUT_CTRL, 0x19); + /* Right PGA -> HPLCOM */ + aic34b_read(aic34b_client, 56, &val); + aic34b_write(aic34b_client, 56, val | 0x80); + /* Unmute HPLCOM with 1 dB gain */ + aic34b_write(aic34b_client, HPLCOM_CTRL, 0x19); + } else { + /* Disconnect LINE2R from RADC */ + aic34b_write(aic34b_client, LINE2R_2_RADC_CTRL, 0xF8); + /* Mute Right ADC-PGA */ + aic34b_write(aic34b_client, RADC_VOL, 0x80); + /* Detach Right PGA from HPLOUT */ + aic34b_write(aic34b_client, 49, (~aic34b_volume & 0x7f)); + /* Power down HPLOUT */ + aic34b_write(aic34b_client, HPLOUT_CTRL, 0x06); + /* Detach Right PGA from HPLCOM */ + aic34b_write(aic34b_client, 56, (~aic34b_volume & 0x7f)); + /* Power down HPLCOM */ + aic34b_write(aic34b_client, HPLCOM_CTRL, 0x06); + /* Deny any possible keypresses for a second */ + deny_button_press(); + /* To regain low power consumption, reset is needed */ + aic34b_write(aic34b_client, AIC3X_RESET, SOFT_RESET); + /* And need to restore volume level */ + aic34b_write(aic34b_client, 49, (~aic34b_volume & 0x7f)); + aic34b_write(aic34b_client, 56, (~aic34b_volume & 0x7f)); + /* Need to restore MICBIAS if set */ + if (aic34b_bias) + aic34b_write(aic34b_client, MICBIAS_CTRL, + (aic34b_bias & 0x3) << 6); + } + mutex_unlock(&aic34b_mutex); +} +EXPORT_SYMBOL(aic34b_ear_enable); + +static int aic34b_dummy_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 val; + + if (aic34b_read(client, AIC3X_PLL_PROGA_REG, &val) || val != 0x10) { + /* Chip not present */ + return -ENODEV; + } + aic34b_client = client; + + /* Configure LINE2R for differential mode */ + aic34b_read(client, LINE2R_2_RADC_CTRL, &val); + aic34b_write(client, LINE2R_2_RADC_CTRL, val | 0x80); + + return 0; +} + +static int aic34b_dummy_remove(struct i2c_client *client) +{ + aic34b_client = NULL; + + return 0; +} + +static const struct i2c_device_id aic34b_dummy_id[] = { + { "aic34b_dummy", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aic34b_dummy_id); + +static struct i2c_driver aic34b_dummy_driver = { + .driver = { + .name = "aic34b_dummy" + }, + .probe = aic34b_dummy_probe, + .remove = aic34b_dummy_remove, + .id_table = aic34b_dummy_id, +}; + +static int __init aic34b_dummy_init(void) +{ + return i2c_add_driver(&aic34b_dummy_driver); +} + +static void __exit aic34b_dummy_exit(void) +{ + i2c_del_driver(&aic34b_dummy_driver); +} + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("Dummy driver for AIC34 block B parts used on Nokia RX51"); +MODULE_LICENSE("GPL"); + +module_init(aic34b_dummy_init); +module_exit(aic34b_dummy_exit); diff --git a/sound/soc/omap/aic34b_dummy.h b/sound/soc/omap/aic34b_dummy.h new file mode 100644 index 0000000..2d386bf --- /dev/null +++ b/sound/soc/omap/aic34b_dummy.h @@ -0,0 +1,32 @@ +/* + * aic34b_dummy.h + * + * Copyright (C) 2008 - 2009 Nokia Corporation + * + * Contact: Peter Ujfalusi peter.ujfalusi@nokia.com + * Eduardo Valentin eduardo.valentin@nokia.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __AIC34B_DUMMY__ +#define __AIC34B_DUMMY__ + +extern void aic34b_ear_enable(int enable); +void aic34b_set_mic_bias(int bias); +int aic34b_set_volume(u8 volume); + +#endif diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c new file mode 100644 index 0000000..74bafb2 --- /dev/null +++ b/sound/soc/omap/rx51.c @@ -0,0 +1,793 @@ +/* + * rx51.c -- SoC audio for Nokia RX51 + * + * Copyright (C) 2008 - 2009 Nokia Corporation + * + * Contact: Peter Ujfalusi peter.ujfalusi@nokia.com + * Eduardo Valentin eduardo.valentin@nokia.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include <sound/tlv.h> + +#include <linux/i2c/twl4030.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <linux/gpio.h> +#include <mach/mcbsp.h> + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/tlv320aic3x.h" +#include "../codecs/tpa6130a2.h" +#include "aic34b_dummy.h" + +#define RX51_CODEC_RESET_GPIO 60 +#define RX51_TVOUT_SEL_GPIO 40 +#define RX51_ECI_SWITCH_1_GPIO 178 +#define RX51_ECI_SWITCH_2_GPIO 182 +/* REVISIT: TWL4030 GPIO base in RX51. Now statically defined to 192 */ +#define RX51_SPEAKER_AMP_TWL_GPIO (192 + 7) + +enum { + RX51_JACK_DISABLED, + RX51_JACK_HP, /* headphone: stereo output, no mic */ + RX51_JACK_HS, /* headset: stereo output with mic */ + RX51_JACK_MIC, /* mic input only */ + RX51_JACK_ECI, /* ECI headset */ + RX51_JACK_TVOUT, /* stereo output with tv-out */ +}; + +static int rx51_spk_func; +static int rx51_jack_func; +static int rx51_fmtx_func; +static int rx51_dmic_func; +static int rx51_ear_func; +static struct snd_jack *rx51_jack; + +static DEFINE_MUTEX(eci_mutex); +static int rx51_eci_mode = 1; +static int rx51_dapm_jack_bias; +static int tpa6130_enable; +static int aic34b_volume; + +static void rx51_set_eci_switches(int mode) +{ + switch (mode) { + case 0: /* Bias off */ + case 1: /* Bias according to rx51_dapm_jack_bias */ + case 4: /* Bias on */ + /* Codec connected to mic/bias line */ + gpio_set_value(RX51_ECI_SWITCH_1_GPIO, 0); + gpio_set_value(RX51_ECI_SWITCH_2_GPIO, 1); + break; + case 2: + /* ECI INT#2 detect connected to mic/bias line */ + gpio_set_value(RX51_ECI_SWITCH_1_GPIO, 0); + gpio_set_value(RX51_ECI_SWITCH_2_GPIO, 0); + break; + case 3: + /* ECI RX/TX connected to mic/bias line */ + gpio_set_value(RX51_ECI_SWITCH_1_GPIO, 1); + gpio_set_value(RX51_ECI_SWITCH_2_GPIO, 0); + break; + } +} + +static void rx51_set_jack_bias(void) +{ + int enable_bias = 0; + + mutex_lock(&eci_mutex); + if ((rx51_eci_mode == 1 && rx51_dapm_jack_bias) || rx51_eci_mode == 4) + enable_bias = 1; + else if (rx51_eci_mode == 1 && rx51_jack_func == RX51_JACK_ECI) + enable_bias = 1; + mutex_unlock(&eci_mutex); + if (enable_bias) + aic34b_set_mic_bias(2); /* 2.5 V */ + else + aic34b_set_mic_bias(0); +} + +static void rx51_set_jack_bias_handler(struct work_struct *unused) +{ + rx51_set_jack_bias(); +} +DECLARE_WORK(rx51_jack_bias_work, rx51_set_jack_bias_handler); + +static void rx51_ext_control(struct snd_soc_codec *codec) +{ + int hp = 0, mic = 0, tvout = 0; + + switch (rx51_jack_func) { + case RX51_JACK_ECI: + case RX51_JACK_HS: + mic = 1; + case RX51_JACK_HP: + hp = 1; + break; + case RX51_JACK_MIC: + mic = 1; + break; + case RX51_JACK_TVOUT: + hp = 1; + tvout = 1; + break; + } + + gpio_set_value(RX51_TVOUT_SEL_GPIO, tvout); + + if (rx51_spk_func) + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); + if (hp) + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + if (mic) + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + else + snd_soc_dapm_disable_pin(codec, "Mic Jack"); + if (rx51_fmtx_func) + snd_soc_dapm_enable_pin(codec, "FM Transmitter"); + else + snd_soc_dapm_disable_pin(codec, "FM Transmitter"); + if (rx51_dmic_func) + snd_soc_dapm_enable_pin(codec, "DMic"); + else + snd_soc_dapm_disable_pin(codec, "DMic"); + if (rx51_ear_func) + snd_soc_dapm_enable_pin(codec, "Earphone"); + else + snd_soc_dapm_disable_pin(codec, "Earphone"); + + snd_soc_dapm_sync(codec); +} + +int rx51_set_eci_mode(int mode) +{ + if (mode < 0 || mode > 4) + return -EINVAL; + + mutex_lock(&eci_mutex); + if (rx51_eci_mode == mode) { + mutex_unlock(&eci_mutex); + return 0; + } + + rx51_eci_mode = mode; + rx51_set_eci_switches(rx51_eci_mode); + mutex_unlock(&eci_mutex); + + rx51_set_jack_bias(); + + return 0; +} +EXPORT_SYMBOL(rx51_set_eci_mode); + +static ssize_t eci_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", rx51_eci_mode); +} + +static ssize_t eci_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int mode, retval; + if (sscanf(buf, "%d", &mode) != 1) + return -EINVAL; + retval = rx51_set_eci_mode(mode); + + return (retval < 0) ? retval : count; +} + +static DEVICE_ATTR(eci_mode, S_IRUGO | S_IWUSR, + eci_mode_show, eci_mode_store); + +void rx51_jack_report(int status) +{ + snd_jack_report(rx51_jack, status); +} +EXPORT_SYMBOL(rx51_jack_report); + +static int rx51_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->card->codec; + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); + + rx51_ext_control(codec); + + return 0; +} + +static int rx51_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int err; + + /* Set codec DAI configuration */ + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) + return err; + + /* Set cpu DAI configuration */ + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) + return err; + + /* Set the codec system clock for DAC and ADC */ + return snd_soc_dai_set_sysclk(codec_dai, 0, 19200000, + SND_SOC_CLOCK_IN); +} + +static int rx51_bt_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + /* Set cpu DAI configuration */ + return cpu_dai->ops->set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); +} + +static struct snd_soc_ops rx51_bt_ops = { + .hw_params = rx51_bt_hw_params, +}; + +static struct snd_soc_ops rx51_ops = { + .startup = rx51_startup, + .hw_params = rx51_hw_params, +}; + +static int rx51_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_spk_func; + + return 0; +} + +static int rx51_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_spk_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_spk_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +static int rx51_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 1); + else + gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 0); + + return 0; +} + +static int rx51_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_jack_func; + + return 0; +} + +static int rx51_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_jack_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_jack_func = ucontrol->value.integer.value[0]; + + mutex_lock(&eci_mutex); + if (rx51_jack_func == RX51_JACK_ECI) { + /* Set ECI switches according to ECI mode */ + rx51_set_eci_switches(rx51_eci_mode); + schedule_work(&rx51_jack_bias_work); + } else { + /* + * Let the codec always be connected to mic/bias line when + * jack is in non-ECI function + */ + rx51_set_eci_switches(1); + schedule_work(&rx51_jack_bias_work); + } + mutex_unlock(&eci_mutex); + + rx51_ext_control(codec); + + return 1; +} + +static int rx51_jack_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(k); + /* + * Note: HP amp and fmtx must not be enabled at the same + * time. We keep a shadow copy of the desired tpa_enable value but + * keep the hpamp really disabled whenever fmtx is enabled. If + * hpamp is requested on but fmtx is enabled, hpamp is kept + * disabled and enabled later from rx51_set_fmtx function when + * user disables fmtx. + */ + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (!rx51_fmtx_func) + snd_soc_dapm_enable_pin(codec, "TPA6130A2 Headphone"); + tpa6130_enable = 1; + } else { + tpa6130_enable = 1; + snd_soc_dapm_disable_pin(codec, "TPA6130A2 Headphone"); + tpa6130_enable = 0; + } + + return 0; +} + +static int rx51_jack_mic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + rx51_dapm_jack_bias = 1; + else + rx51_dapm_jack_bias = 0; + schedule_work(&rx51_jack_bias_work); + + return 0; +} + +static int rx51_get_fmtx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_fmtx_func; + + return 0; +} + +static int rx51_set_fmtx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_fmtx_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_fmtx_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + /* fmtx and tpa must not be enabled at the same time */ + if (rx51_fmtx_func && tpa6130_enable) + snd_soc_dapm_disable_pin(codec, "TPA6130A2 Headphone"); + if (!rx51_fmtx_func && tpa6130_enable) + snd_soc_dapm_enable_pin(codec, "TPA6130A2 Headphone"); + + return 1; +} + +static int rx51_get_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_dmic_func; + + return 0; +} + +static int rx51_set_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_dmic_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_dmic_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +static int rx51_get_ear(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_ear_func; + + return 0; +} + +static int rx51_set_ear(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_ear_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_ear_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +static int rx51_ear_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + aic34b_ear_enable(1); + else + aic34b_ear_enable(0); + + return 0; +} + +enum { + RX51_EXT_API_AIC34B, +}; +#define SOC_RX51_EXT_SINGLE_TLV(xname, ext_api, max, 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 = rx51_ext_info_volsw, \ + .get = rx51_ext_get_volsw, \ + .put = rx51_ext_put_volsw, \ + .private_value = (ext_api) << 26 | (max) << 16, \ +} + +static int rx51_ext_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int max = (kcontrol->private_value >> 16) & 0xff; + + if (max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = max; + + return 0; +} + +static int rx51_ext_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ext_api = (kcontrol->private_value >> 26) & 0x0f; + + switch (ext_api) { + case RX51_EXT_API_AIC34B: + ucontrol->value.integer.value[0] = aic34b_volume; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rx51_ext_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ext_api = (kcontrol->private_value >> 26) & 0x0f; + int change = 0; + + switch (ext_api) { + case RX51_EXT_API_AIC34B: + change = (aic34b_volume != ucontrol->value.integer.value[0]); + aic34b_volume = ucontrol->value.integer.value[0]; + aic34b_set_volume(aic34b_volume); + break; + default: + return -EINVAL; + } + + return change; +} + +static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event), + SND_SOC_DAPM_SPK("Headphone Jack", rx51_jack_hp_event), + SND_SOC_DAPM_MIC("Mic Jack", rx51_jack_mic_event), + SND_SOC_DAPM_OUTPUT("FM Transmitter"), + SND_SOC_DAPM_MIC("DMic", NULL), + SND_SOC_DAPM_SPK("Earphone", rx51_ear_event), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Ext Spk", NULL, "HPLOUT"}, + {"Ext Spk", NULL, "HPROUT"}, + + {"TPA6130A2 Headphone", NULL, "LLOUT"}, + {"TPA6130A2 Headphone", NULL, "RLOUT"}, + {"LINE1L", NULL, "Mic Jack"}, + + {"FM Transmitter", NULL, "LLOUT"}, + {"FM Transmitter", NULL, "RLOUT"}, + + {"Earphone", NULL, "MONO_LOUT"}, + + {"DMic Rate 64", NULL, "Mic Bias 2V"}, + {"Mic Bias 2V", NULL, "DMic"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *jack_function[] = {"Off", "Headphone", "Headset", + "Mic", "ECI Headset", "TV-OUT"}; +static const char *fmtx_function[] = {"Off", "On"}; +static const char *input_function[] = {"ADC", "Digital Mic"}; +static const char *ear_function[] = {"Off", "On"}; + +static const struct soc_enum rx51_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fmtx_function), fmtx_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ear_function), ear_function), +}; + +/* + * TLV320AIC3x output stage volumes. From -78.3 to 0 dB. Muted below -78.3 dB. + * Step size is approximately 0.5 dB over most of the scale but increasing + * near the very low levels. + * Define dB scale so that it is mostly correct for range about -55 to 0 dB + * but having increasing dB difference below that (and where it doesn't count + * so much). This setting shows -50 dB (actual is -50.3 dB) for register + * value 100 and -58.5 dB (actual is -78.3 dB) for register value 117. + */ +static DECLARE_TLV_DB_SCALE(aic3x_output_stage_tlv, -5900, 50, 1); + +static const struct snd_kcontrol_new aic34_rx51_controls[] = { + SOC_ENUM_EXT("Speaker Function", rx51_enum[0], + rx51_get_spk, rx51_set_spk), + SOC_ENUM_EXT("Jack Function", rx51_enum[1], + rx51_get_jack, rx51_set_jack), + SOC_ENUM_EXT("FMTX Function", rx51_enum[2], + rx51_get_fmtx, rx51_set_fmtx), + SOC_ENUM_EXT("Input Select", rx51_enum[3], + rx51_get_input, rx51_set_input), + SOC_ENUM_EXT("Earphone Function", rx51_enum[4], + rx51_get_ear, rx51_set_ear), + SOC_RX51_EXT_SINGLE_TLV("Earphone Playback Volume", + RX51_EXT_API_AIC34B, 118, + aic3x_output_stage_tlv), +}; + +static int rx51_aic34_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* Add TPA6130A2 controls */ + tpa6130a2_add_controls(codec); + + /* set up NC codec pins */ + snd_soc_dapm_nc_pin(codec, "MIC3L"); + snd_soc_dapm_nc_pin(codec, "MIC3R"); + snd_soc_dapm_nc_pin(codec, "LINE1R"); + + /* Create jack for accessory reporting */ + err = snd_jack_new(codec->card, "Jack", SND_JACK_MECHANICAL | + SND_JACK_HEADSET | SND_JACK_AVOUT, &rx51_jack); + if (err < 0) + return err; + + /* Add RX51 specific controls */ + for (i = 0; i < ARRAY_SIZE(aic34_rx51_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&aic34_rx51_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + /* Add RX51 specific widgets */ + snd_soc_dapm_new_controls(codec, aic34_dapm_widgets, + ARRAY_SIZE(aic34_dapm_widgets)); + + /* Set up RX51 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_enable_pin(codec, "Earphone"); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* Since all codec control is done by Bluetooth hardware + only some constrains need to be set for it */ +struct snd_soc_dai btcodec_dai = { + .name = "Bluetooth codec", + .playback = { + .stream_name = "BT Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "BT Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link rx51_dai[] = { + { + .name = "TLV320AIC34", + .stream_name = "AIC34", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &aic3x_dai, + .init = rx51_aic34_init, + .ops = &rx51_ops, + }, { + .name = "Bluetooth PCM", + .stream_name = "Bluetooth", + .cpu_dai = &omap_mcbsp_dai[1], + .codec_dai = &btcodec_dai, + .ops = &rx51_bt_ops, + } +}; + +/* Audio private data */ +static struct aic3x_setup_data rx51_aic34_setup = { + .gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED, + .gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT, +}; + +/* Audio card */ +static struct snd_soc_card rx51_sound_card = { + .name = "RX51", + .dai_link = rx51_dai, + .num_links = ARRAY_SIZE(rx51_dai), + .platform = &omap_soc_platform, +}; + +/* Audio subsystem */ +static struct snd_soc_device rx51_snd_devdata = { + .card = &rx51_sound_card, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &rx51_aic34_setup, +}; + +static struct platform_device *rx51_snd_device; + +#define REMAP_OFFSET 2 +#define DEDICATED_OFFSET 3 +#define VMMC2_DEV_GRP 0x2B +#define VMMC2_285V 0x0a + +static int __init rx51_soc_init(void) +{ + int err; + struct device *dev; + + if (!machine_is_nokia_rx51()) + return -ENODEV; + + if (gpio_request(RX51_CODEC_RESET_GPIO, NULL) < 0) + BUG(); + if (gpio_request(RX51_TVOUT_SEL_GPIO, "tvout_sel") < 0) + BUG(); + if (gpio_request(RX51_ECI_SWITCH_1_GPIO, "ECI switch 1") < 0) + BUG(); + if (gpio_request(RX51_ECI_SWITCH_2_GPIO, "ECI switch 2") < 0) + BUG(); + gpio_direction_output(RX51_CODEC_RESET_GPIO, 0); + gpio_direction_output(RX51_TVOUT_SEL_GPIO, 0); + gpio_direction_output(RX51_ECI_SWITCH_1_GPIO, 0); + gpio_direction_output(RX51_ECI_SWITCH_2_GPIO, 1); + + gpio_set_value(RX51_CODEC_RESET_GPIO, 0); + udelay(1); + gpio_set_value(RX51_CODEC_RESET_GPIO, 1); + msleep(1); + + if (gpio_request(RX51_SPEAKER_AMP_TWL_GPIO, NULL) < 0) + BUG(); + gpio_direction_output(RX51_SPEAKER_AMP_TWL_GPIO, 0); + + err = snd_soc_register_dai(&btcodec_dai); + if (err) + return err; + + rx51_snd_device = platform_device_alloc("soc-audio", -1); + if (!rx51_snd_device) { + err = -ENOMEM; + goto err0; + } + + platform_set_drvdata(rx51_snd_device, &rx51_snd_devdata); + rx51_snd_devdata.dev = &rx51_snd_device->dev; + err = platform_device_add(rx51_snd_device); + if (err) + goto err1; + + dev = &rx51_snd_device->dev; + + *(unsigned int *)rx51_dai[0].cpu_dai->private_data = 1; + *(unsigned int *)rx51_dai[1].cpu_dai->private_data = 2; + + err = device_create_file(dev, &dev_attr_eci_mode); + if (err) + goto err2; + + return err; +err2: + platform_device_del(rx51_snd_device); +err1: + platform_device_put(rx51_snd_device); +err0: + snd_soc_unregister_dai(&btcodec_dai); + + return err; + +} + +static void __exit rx51_soc_exit(void) +{ + platform_device_unregister(rx51_snd_device); + snd_soc_unregister_dai(&btcodec_dai); +} + +module_init(rx51_soc_init); +module_exit(rx51_soc_exit); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ALSA SoC Nokia RX51"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/rx51.h b/sound/soc/omap/rx51.h new file mode 100644 index 0000000..ee55260 --- /dev/null +++ b/sound/soc/omap/rx51.h @@ -0,0 +1,29 @@ +#ifndef _RX51_H_ +#define _RX51_H_ + +/* + * rx51.h - SoC audio for Nokia RX51 + * + * Copyright (C) 2008 - 2009 Nokia Corporation + * + * Contact: Peter Ujfalusi peter.ujfalusi@nokia.com + * Eduardo Valentin eduardo.valentin@nokia.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; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +int rx51_set_eci_mode(int mode); +void rx51_jack_report(int status); + +#endif /* _RX51_H_ */

On Thu, 2009-10-08 at 13:58 +0200, Valentin Eduardo (Nokia-D/Helsinki) wrote:
From: Eduardo Valentin eduardo.valentin@nokia.com
Introduce RX-51 Machine driver for ASoC and AIC34b_dummy (block B) i2c driver.
Also move the request_gpio of speaker_enabled from board-rx51-peripherals.c to this machine driver.
These drivers were originally written by Jarkko Nikula.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com
arch/arm/mach-omap2/board-rx51-peripherals.c | 2 - sound/soc/omap/Kconfig | 10 + sound/soc/omap/Makefile | 2 + sound/soc/omap/aic34b_dummy.c | 271 +++++++++ sound/soc/omap/aic34b_dummy.h | 32 + sound/soc/omap/rx51.c | 793 ++++++++++++++++++++++++++ sound/soc/omap/rx51.h | 29 + 7 files changed, 1137 insertions(+), 2 deletions(-) create mode 100644 sound/soc/omap/aic34b_dummy.c create mode 100644 sound/soc/omap/aic34b_dummy.h create mode 100644 sound/soc/omap/rx51.c create mode 100644 sound/soc/omap/rx51.h
diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index c1af532..b227475 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -262,8 +262,6 @@ static int rx51_twlgpio_setup(struct device *dev, unsigned gpio, unsigned n) /* FIXME this gpio setup is just a placeholder for now */ gpio_request(gpio + 6, "backlight_pwm"); gpio_direction_output(gpio + 6, 0);
gpio_request(gpio + 7, "speaker_en");
gpio_direction_output(gpio + 7, 1); /* set up MMC adapters, linking their regulators to them */ twl4030_mmc_init(mmc);
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 2dee983..bdcd4be 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -15,6 +15,16 @@ config SND_OMAP_SOC_N810 help Say Y if you want to add support for SoC audio on Nokia N810.
+config SND_OMAP_SOC_RX51
tristate "SoC Audio support for Nokia RX51"
depends on SND_OMAP_SOC && MACH_NOKIA_RX51
select OMAP_MCBSP
select SND_OMAP_SOC_MCBSP
select SND_SOC_TLV320AIC3X
select SND_SOC_TPA6130A2
help
Say Y if you want to add support for SoC audio on Nokia RX51.
config SND_OMAP_SOC_AMS_DELTA tristate "SoC Audio support for Amstrad E3 (Delta) videophone" depends on SND_OMAP_SOC && MACH_AMS_DELTA diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 02d6947..7dec270 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -16,8 +16,10 @@ snd-soc-sdp3430-objs := sdp3430.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap3beagle-objs := omap3beagle.o snd-soc-zoom2-objs := zoom2.o +snd-soc-rx51-objs := rx51.o
obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o aic34b_dummy.o obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o diff --git a/sound/soc/omap/aic34b_dummy.c b/sound/soc/omap/aic34b_dummy.c new file mode 100644 index 0000000..17c4d9c --- /dev/null +++ b/sound/soc/omap/aic34b_dummy.c @@ -0,0 +1,271 @@ +/*
- aic34b_dummy.c -- Dummy driver for AIC34 block B parts used in Nokia RX51
- Purpose for this driver is to cover few audio connections on Nokia RX51 HW
- which are connected into block B of TLV320AIC34 dual codec.
- Copyright (C) 2008 - 2009 Nokia Corporation
- Contact: Peter Ujfalusi peter.ujfalusi@nokia.com
Eduardo Valentin <eduardo.valentin@nokia.com>
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- 02110-1301 USA
- TODO:
- Get rid of this driver, at least when ASoC v2 is merged and when
- we can support multiple codec instances in tlv320aic3x.c driver.
- This driver is hacked only for Nokia RX51 HW.
- */
+#include <linux/module.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <sound/soc.h>
+#include "../codecs/tlv320aic3x.h"
+struct i2c_client *aic34b_client; +static DEFINE_MUTEX(aic34b_mutex); +static DEFINE_MUTEX(button_press_mutex); +static ktime_t button_press_denial_start; +static int aic34b_volume; +static int button_press_denied; +static int aic34b_bias;
+static int aic34b_read(struct i2c_client *client, unsigned int reg,
u8 *value)
+{
int err;
err = i2c_smbus_read_byte_data(client, reg);
*value = err;
return (err >= 0) ? 0 : err;
+}
+static int aic34b_write(struct i2c_client *client, unsigned int reg,
u8 value)
+{
u8 data[2];
data[0] = reg & 0xff;
data[1] = value & 0xff;
return (i2c_master_send(client, data, 2) == 2) ? 0 : -EIO;
+}
+/*
- Introduce a derivative FIR filter to detect unnecessary button
- presses caused by a change in the MICBIAS. The filter returns
- TRUE in the event there has not been a change in MICBIAS within
- the time window (500ms). If the rate of change within the window
- is >= 1, all button presses are denied. In addition, if bias is
- zero, then all button presses are also denied explicitly.
- */
+int allow_button_press(void) +{
/* If bias is not on, no chance for button presses */
if (!aic34b_bias)
return 0;
/* If explicitly granted a button press */
if (!button_press_denied) {
return 1;
} else {
int64_t delta;
/* This is the FIR portion with specified time window */
mutex_lock(&button_press_mutex);
delta = ktime_to_ns(ktime_sub(ktime_get(),
button_press_denial_start));
if (delta < 0) {
button_press_denied = 0;
/* If the clock ever wraps */
button_press_denial_start.tv.sec = 0;
button_press_denial_start.tv.nsec = 0;
mutex_unlock(&button_press_mutex);
return 1;
}
do_div(delta, 1000000);
/* Time window is 500ms */
if (delta >= 500) {
button_press_denied = 0;
mutex_unlock(&button_press_mutex);
return 1;
}
mutex_unlock(&button_press_mutex);
}
/* There was a change in MICBIAS within time window */
return 0;
+} +EXPORT_SYMBOL(allow_button_press);
+static void deny_button_press(void) +{
mutex_lock(&button_press_mutex);
button_press_denied = 1;
button_press_denial_start = ktime_get();
mutex_unlock(&button_press_mutex);
+}
+void aic34b_set_mic_bias(int bias) +{
if (aic34b_client == NULL)
return;
mutex_lock(&aic34b_mutex);
aic34b_write(aic34b_client, MICBIAS_CTRL, (bias & 0x3) << 6);
aic34b_bias = bias;
deny_button_press();
mutex_unlock(&aic34b_mutex);
+} +EXPORT_SYMBOL(aic34b_set_mic_bias);
+int aic34b_set_volume(u8 volume) +{
u8 val;
if (aic34b_client == NULL)
return 0;
mutex_lock(&aic34b_mutex);
/* Volume control for Right PGA to HPLOUT */
aic34b_read(aic34b_client, 49, &val);
val &= ~0x7f;
aic34b_write(aic34b_client, 49, val | (~volume & 0x7f));
/* Volume control for Right PGA to HPLCOM */
aic34b_read(aic34b_client, 56, &val);
val &= ~0x7f;
aic34b_write(aic34b_client, 56, val | (~volume & 0x7f));
aic34b_volume = volume;
mutex_unlock(&aic34b_mutex);
return 0;
+} +EXPORT_SYMBOL(aic34b_set_volume);
+void aic34b_ear_enable(int enable) +{
u8 val;
if (aic34b_client == NULL)
return;
mutex_lock(&aic34b_mutex);
if (enable) {
/* Connect LINE2R to RADC */
aic34b_write(aic34b_client, LINE2R_2_RADC_CTRL, 0x80);
/* Unmute Right ADC-PGA */
aic34b_write(aic34b_client, RADC_VOL, 0x00);
/* Right PGA -> HPLOUT */
aic34b_read(aic34b_client, 49, &val);
aic34b_write(aic34b_client, 49, val | 0x80);
/* Unmute HPLOUT with 1 dB gain */
aic34b_write(aic34b_client, HPLOUT_CTRL, 0x19);
/* Right PGA -> HPLCOM */
aic34b_read(aic34b_client, 56, &val);
aic34b_write(aic34b_client, 56, val | 0x80);
/* Unmute HPLCOM with 1 dB gain */
aic34b_write(aic34b_client, HPLCOM_CTRL, 0x19);
} else {
/* Disconnect LINE2R from RADC */
aic34b_write(aic34b_client, LINE2R_2_RADC_CTRL, 0xF8);
/* Mute Right ADC-PGA */
aic34b_write(aic34b_client, RADC_VOL, 0x80);
/* Detach Right PGA from HPLOUT */
aic34b_write(aic34b_client, 49, (~aic34b_volume & 0x7f));
/* Power down HPLOUT */
aic34b_write(aic34b_client, HPLOUT_CTRL, 0x06);
/* Detach Right PGA from HPLCOM */
aic34b_write(aic34b_client, 56, (~aic34b_volume & 0x7f));
/* Power down HPLCOM */
aic34b_write(aic34b_client, HPLCOM_CTRL, 0x06);
/* Deny any possible keypresses for a second */
deny_button_press();
/* To regain low power consumption, reset is needed */
aic34b_write(aic34b_client, AIC3X_RESET, SOFT_RESET);
/* And need to restore volume level */
aic34b_write(aic34b_client, 49, (~aic34b_volume & 0x7f));
aic34b_write(aic34b_client, 56, (~aic34b_volume & 0x7f));
/* Need to restore MICBIAS if set */
if (aic34b_bias)
aic34b_write(aic34b_client, MICBIAS_CTRL,
(aic34b_bias & 0x3) << 6);
}
mutex_unlock(&aic34b_mutex);
+} +EXPORT_SYMBOL(aic34b_ear_enable);
+static int aic34b_dummy_probe(struct i2c_client *client,
const struct i2c_device_id *id)
+{
u8 val;
if (aic34b_read(client, AIC3X_PLL_PROGA_REG, &val) || val != 0x10) {
/* Chip not present */
return -ENODEV;
}
aic34b_client = client;
/* Configure LINE2R for differential mode */
aic34b_read(client, LINE2R_2_RADC_CTRL, &val);
aic34b_write(client, LINE2R_2_RADC_CTRL, val | 0x80);
return 0;
+}
+static int aic34b_dummy_remove(struct i2c_client *client) +{
aic34b_client = NULL;
return 0;
+}
+static const struct i2c_device_id aic34b_dummy_id[] = {
{ "aic34b_dummy", 0 },
{ }
+}; +MODULE_DEVICE_TABLE(i2c, aic34b_dummy_id);
+static struct i2c_driver aic34b_dummy_driver = {
.driver = {
.name = "aic34b_dummy"
},
.probe = aic34b_dummy_probe,
.remove = aic34b_dummy_remove,
.id_table = aic34b_dummy_id,
+};
+static int __init aic34b_dummy_init(void) +{
return i2c_add_driver(&aic34b_dummy_driver);
+}
+static void __exit aic34b_dummy_exit(void) +{
i2c_del_driver(&aic34b_dummy_driver);
+}
+MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("Dummy driver for AIC34 block B parts used on Nokia RX51"); +MODULE_LICENSE("GPL");
+module_init(aic34b_dummy_init); +module_exit(aic34b_dummy_exit); diff --git a/sound/soc/omap/aic34b_dummy.h b/sound/soc/omap/aic34b_dummy.h new file mode 100644 index 0000000..2d386bf --- /dev/null +++ b/sound/soc/omap/aic34b_dummy.h @@ -0,0 +1,32 @@ +/*
- aic34b_dummy.h
- Copyright (C) 2008 - 2009 Nokia Corporation
- Contact: Peter Ujfalusi peter.ujfalusi@nokia.com
Eduardo Valentin <eduardo.valentin@nokia.com>
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- 02110-1301 USA
- */
+#ifndef __AIC34B_DUMMY__ +#define __AIC34B_DUMMY__
+extern void aic34b_ear_enable(int enable); +void aic34b_set_mic_bias(int bias); +int aic34b_set_volume(u8 volume);
+#endif diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c new file mode 100644 index 0000000..74bafb2 --- /dev/null +++ b/sound/soc/omap/rx51.c @@ -0,0 +1,793 @@ +/*
- rx51.c -- SoC audio for Nokia RX51
- Copyright (C) 2008 - 2009 Nokia Corporation
- Contact: Peter Ujfalusi peter.ujfalusi@nokia.com
Eduardo Valentin <eduardo.valentin@nokia.com>
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- 02110-1301 USA
- */
+#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include <sound/tlv.h>
+#include <linux/i2c/twl4030.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <linux/gpio.h> +#include <mach/mcbsp.h>
+#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/tlv320aic3x.h" +#include "../codecs/tpa6130a2.h" +#include "aic34b_dummy.h"
+#define RX51_CODEC_RESET_GPIO 60 +#define RX51_TVOUT_SEL_GPIO 40 +#define RX51_ECI_SWITCH_1_GPIO 178 +#define RX51_ECI_SWITCH_2_GPIO 182 +/* REVISIT: TWL4030 GPIO base in RX51. Now statically defined to 192 */ +#define RX51_SPEAKER_AMP_TWL_GPIO (192 + 7)
+enum {
RX51_JACK_DISABLED,
RX51_JACK_HP, /* headphone: stereo output, no mic */
RX51_JACK_HS, /* headset: stereo output with mic */
RX51_JACK_MIC, /* mic input only */
RX51_JACK_ECI, /* ECI headset */
RX51_JACK_TVOUT, /* stereo output with tv-out */
+};
+static int rx51_spk_func; +static int rx51_jack_func; +static int rx51_fmtx_func; +static int rx51_dmic_func; +static int rx51_ear_func; +static struct snd_jack *rx51_jack;
+static DEFINE_MUTEX(eci_mutex); +static int rx51_eci_mode = 1; +static int rx51_dapm_jack_bias; +static int tpa6130_enable; +static int aic34b_volume;
+static void rx51_set_eci_switches(int mode) +{
switch (mode) {
case 0: /* Bias off */
case 1: /* Bias according to rx51_dapm_jack_bias */
case 4: /* Bias on */
/* Codec connected to mic/bias line */
gpio_set_value(RX51_ECI_SWITCH_1_GPIO, 0);
gpio_set_value(RX51_ECI_SWITCH_2_GPIO, 1);
break;
case 2:
/* ECI INT#2 detect connected to mic/bias line */
gpio_set_value(RX51_ECI_SWITCH_1_GPIO, 0);
gpio_set_value(RX51_ECI_SWITCH_2_GPIO, 0);
break;
case 3:
/* ECI RX/TX connected to mic/bias line */
gpio_set_value(RX51_ECI_SWITCH_1_GPIO, 1);
gpio_set_value(RX51_ECI_SWITCH_2_GPIO, 0);
break;
}
+}
+static void rx51_set_jack_bias(void) +{
int enable_bias = 0;
mutex_lock(&eci_mutex);
if ((rx51_eci_mode == 1 && rx51_dapm_jack_bias) || rx51_eci_mode == 4)
enable_bias = 1;
else if (rx51_eci_mode == 1 && rx51_jack_func == RX51_JACK_ECI)
enable_bias = 1;
mutex_unlock(&eci_mutex);
if (enable_bias)
aic34b_set_mic_bias(2); /* 2.5 V */
else
aic34b_set_mic_bias(0);
+}
+static void rx51_set_jack_bias_handler(struct work_struct *unused) +{
rx51_set_jack_bias();
+} +DECLARE_WORK(rx51_jack_bias_work, rx51_set_jack_bias_handler);
+static void rx51_ext_control(struct snd_soc_codec *codec) +{
int hp = 0, mic = 0, tvout = 0;
switch (rx51_jack_func) {
case RX51_JACK_ECI:
case RX51_JACK_HS:
mic = 1;
case RX51_JACK_HP:
hp = 1;
break;
case RX51_JACK_MIC:
mic = 1;
break;
case RX51_JACK_TVOUT:
hp = 1;
tvout = 1;
break;
}
gpio_set_value(RX51_TVOUT_SEL_GPIO, tvout);
if (rx51_spk_func)
snd_soc_dapm_enable_pin(codec, "Ext Spk");
else
snd_soc_dapm_disable_pin(codec, "Ext Spk");
if (hp)
snd_soc_dapm_enable_pin(codec, "Headphone Jack");
else
snd_soc_dapm_disable_pin(codec, "Headphone Jack");
if (mic)
snd_soc_dapm_enable_pin(codec, "Mic Jack");
else
snd_soc_dapm_disable_pin(codec, "Mic Jack");
if (rx51_fmtx_func)
snd_soc_dapm_enable_pin(codec, "FM Transmitter");
else
snd_soc_dapm_disable_pin(codec, "FM Transmitter");
if (rx51_dmic_func)
snd_soc_dapm_enable_pin(codec, "DMic");
else
snd_soc_dapm_disable_pin(codec, "DMic");
if (rx51_ear_func)
snd_soc_dapm_enable_pin(codec, "Earphone");
else
snd_soc_dapm_disable_pin(codec, "Earphone");
snd_soc_dapm_sync(codec);
+}
+int rx51_set_eci_mode(int mode) +{
if (mode < 0 || mode > 4)
return -EINVAL;
mutex_lock(&eci_mutex);
if (rx51_eci_mode == mode) {
mutex_unlock(&eci_mutex);
return 0;
}
rx51_eci_mode = mode;
rx51_set_eci_switches(rx51_eci_mode);
mutex_unlock(&eci_mutex);
rx51_set_jack_bias();
return 0;
+} +EXPORT_SYMBOL(rx51_set_eci_mode);
+static ssize_t eci_mode_show(struct device *dev, struct device_attribute *attr,
char *buf)
+{
return sprintf(buf, "%d\n", rx51_eci_mode);
+}
+static ssize_t eci_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
+{
int mode, retval;
if (sscanf(buf, "%d", &mode) != 1)
return -EINVAL;
retval = rx51_set_eci_mode(mode);
return (retval < 0) ? retval : count;
+}
+static DEVICE_ATTR(eci_mode, S_IRUGO | S_IWUSR,
eci_mode_show, eci_mode_store);
+void rx51_jack_report(int status) +{
snd_jack_report(rx51_jack, status);
+} +EXPORT_SYMBOL(rx51_jack_report);
+static int rx51_startup(struct snd_pcm_substream *substream) +{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_codec *codec = rtd->socdev->card->codec;
snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
rx51_ext_control(codec);
return 0;
+}
+static int rx51_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
int err;
/* Set codec DAI configuration */
err = snd_soc_dai_set_fmt(codec_dai,
SND_SOC_DAIFMT_DSP_A |
SND_SOC_DAIFMT_IB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (err < 0)
return err;
/* Set cpu DAI configuration */
err = snd_soc_dai_set_fmt(cpu_dai,
SND_SOC_DAIFMT_DSP_A |
SND_SOC_DAIFMT_IB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (err < 0)
return err;
/* Set the codec system clock for DAC and ADC */
return snd_soc_dai_set_sysclk(codec_dai, 0, 19200000,
SND_SOC_CLOCK_IN);
+}
+static int rx51_bt_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
/* Set cpu DAI configuration */
return cpu_dai->ops->set_fmt(cpu_dai,
SND_SOC_DAIFMT_DSP_A |
SND_SOC_DAIFMT_IB_NF |
SND_SOC_DAIFMT_CBM_CFM);
+}
+static struct snd_soc_ops rx51_bt_ops = {
.hw_params = rx51_bt_hw_params,
+};
+static struct snd_soc_ops rx51_ops = {
.startup = rx51_startup,
.hw_params = rx51_hw_params,
+};
+static int rx51_get_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
ucontrol->value.integer.value[0] = rx51_spk_func;
return 0;
+}
+static int rx51_set_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
if (rx51_spk_func == ucontrol->value.integer.value[0])
return 0;
rx51_spk_func = ucontrol->value.integer.value[0];
rx51_ext_control(codec);
return 1;
+}
+static int rx51_spk_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
+{
if (SND_SOC_DAPM_EVENT_ON(event))
gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 1);
else
gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 0);
return 0;
+}
+static int rx51_get_jack(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
ucontrol->value.integer.value[0] = rx51_jack_func;
return 0;
+}
+static int rx51_set_jack(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
if (rx51_jack_func == ucontrol->value.integer.value[0])
return 0;
rx51_jack_func = ucontrol->value.integer.value[0];
mutex_lock(&eci_mutex);
if (rx51_jack_func == RX51_JACK_ECI) {
/* Set ECI switches according to ECI mode */
rx51_set_eci_switches(rx51_eci_mode);
schedule_work(&rx51_jack_bias_work);
} else {
/*
* Let the codec always be connected to mic/bias line when
* jack is in non-ECI function
*/
rx51_set_eci_switches(1);
schedule_work(&rx51_jack_bias_work);
}
mutex_unlock(&eci_mutex);
rx51_ext_control(codec);
return 1;
+}
+static int rx51_jack_hp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(k);
/*
* Note: HP amp and fmtx must not be enabled at the same
* time. We keep a shadow copy of the desired tpa_enable value but
* keep the hpamp really disabled whenever fmtx is enabled. If
* hpamp is requested on but fmtx is enabled, hpamp is kept
* disabled and enabled later from rx51_set_fmtx function when
* user disables fmtx.
*/
if (SND_SOC_DAPM_EVENT_ON(event)) {
if (!rx51_fmtx_func)
snd_soc_dapm_enable_pin(codec, "TPA6130A2 Headphone");
tpa6130_enable = 1;
} else {
tpa6130_enable = 1;
snd_soc_dapm_disable_pin(codec, "TPA6130A2 Headphone");
tpa6130_enable = 0;
}
return 0;
+}
+static int rx51_jack_mic_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
+{
if (SND_SOC_DAPM_EVENT_ON(event))
rx51_dapm_jack_bias = 1;
else
rx51_dapm_jack_bias = 0;
schedule_work(&rx51_jack_bias_work);
return 0;
+}
+static int rx51_get_fmtx(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
ucontrol->value.integer.value[0] = rx51_fmtx_func;
return 0;
+}
+static int rx51_set_fmtx(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
if (rx51_fmtx_func == ucontrol->value.integer.value[0])
return 0;
rx51_fmtx_func = ucontrol->value.integer.value[0];
rx51_ext_control(codec);
/* fmtx and tpa must not be enabled at the same time */
if (rx51_fmtx_func && tpa6130_enable)
snd_soc_dapm_disable_pin(codec, "TPA6130A2 Headphone");
if (!rx51_fmtx_func && tpa6130_enable)
snd_soc_dapm_enable_pin(codec, "TPA6130A2 Headphone");
return 1;
+}
+static int rx51_get_input(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
ucontrol->value.integer.value[0] = rx51_dmic_func;
return 0;
+}
+static int rx51_set_input(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
if (rx51_dmic_func == ucontrol->value.integer.value[0])
return 0;
rx51_dmic_func = ucontrol->value.integer.value[0];
rx51_ext_control(codec);
return 1;
+}
+static int rx51_get_ear(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
ucontrol->value.integer.value[0] = rx51_ear_func;
return 0;
+}
+static int rx51_set_ear(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
if (rx51_ear_func == ucontrol->value.integer.value[0])
return 0;
rx51_ear_func = ucontrol->value.integer.value[0];
rx51_ext_control(codec);
return 1;
+}
+static int rx51_ear_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
+{
if (SND_SOC_DAPM_EVENT_ON(event))
aic34b_ear_enable(1);
else
aic34b_ear_enable(0);
return 0;
+}
+enum {
RX51_EXT_API_AIC34B,
+}; +#define SOC_RX51_EXT_SINGLE_TLV(xname, ext_api, max, 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 = rx51_ext_info_volsw, \
.get = rx51_ext_get_volsw, \
.put = rx51_ext_put_volsw, \
.private_value = (ext_api) << 26 | (max) << 16, \
+}
+static int rx51_ext_info_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
+{
int max = (kcontrol->private_value >> 16) & 0xff;
if (max == 1)
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
else
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = max;
return 0;
+}
+static int rx51_ext_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
int ext_api = (kcontrol->private_value >> 26) & 0x0f;
switch (ext_api) {
case RX51_EXT_API_AIC34B:
ucontrol->value.integer.value[0] = aic34b_volume;
break;
default:
return -EINVAL;
}
return 0;
+}
+static int rx51_ext_put_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
int ext_api = (kcontrol->private_value >> 26) & 0x0f;
int change = 0;
switch (ext_api) {
case RX51_EXT_API_AIC34B:
change = (aic34b_volume != ucontrol->value.integer.value[0]);
aic34b_volume = ucontrol->value.integer.value[0];
aic34b_set_volume(aic34b_volume);
break;
default:
return -EINVAL;
}
return change;
+}
+static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event),
SND_SOC_DAPM_SPK("Headphone Jack", rx51_jack_hp_event),
SND_SOC_DAPM_MIC("Mic Jack", rx51_jack_mic_event),
SND_SOC_DAPM_OUTPUT("FM Transmitter"),
SND_SOC_DAPM_MIC("DMic", NULL),
SND_SOC_DAPM_SPK("Earphone", rx51_ear_event),
+};
+static const struct snd_soc_dapm_route audio_map[] = {
{"Ext Spk", NULL, "HPLOUT"},
{"Ext Spk", NULL, "HPROUT"},
{"TPA6130A2 Headphone", NULL, "LLOUT"},
{"TPA6130A2 Headphone", NULL, "RLOUT"},
{"LINE1L", NULL, "Mic Jack"},
{"FM Transmitter", NULL, "LLOUT"},
{"FM Transmitter", NULL, "RLOUT"},
{"Earphone", NULL, "MONO_LOUT"},
{"DMic Rate 64", NULL, "Mic Bias 2V"},
{"Mic Bias 2V", NULL, "DMic"},
+};
+static const char *spk_function[] = {"Off", "On"}; +static const char *jack_function[] = {"Off", "Headphone", "Headset",
"Mic", "ECI Headset", "TV-OUT"};
+static const char *fmtx_function[] = {"Off", "On"}; +static const char *input_function[] = {"ADC", "Digital Mic"}; +static const char *ear_function[] = {"Off", "On"};
+static const struct soc_enum rx51_enum[] = {
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function),
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fmtx_function), fmtx_function),
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function),
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ear_function), ear_function),
+};
+/*
- TLV320AIC3x output stage volumes. From -78.3 to 0 dB. Muted below -78.3 dB.
- Step size is approximately 0.5 dB over most of the scale but increasing
- near the very low levels.
- Define dB scale so that it is mostly correct for range about -55 to 0 dB
- but having increasing dB difference below that (and where it doesn't count
- so much). This setting shows -50 dB (actual is -50.3 dB) for register
- value 100 and -58.5 dB (actual is -78.3 dB) for register value 117.
- */
+static DECLARE_TLV_DB_SCALE(aic3x_output_stage_tlv, -5900, 50, 1);
+static const struct snd_kcontrol_new aic34_rx51_controls[] = {
SOC_ENUM_EXT("Speaker Function", rx51_enum[0],
rx51_get_spk, rx51_set_spk),
SOC_ENUM_EXT("Jack Function", rx51_enum[1],
rx51_get_jack, rx51_set_jack),
SOC_ENUM_EXT("FMTX Function", rx51_enum[2],
rx51_get_fmtx, rx51_set_fmtx),
SOC_ENUM_EXT("Input Select", rx51_enum[3],
rx51_get_input, rx51_set_input),
SOC_ENUM_EXT("Earphone Function", rx51_enum[4],
rx51_get_ear, rx51_set_ear),
SOC_RX51_EXT_SINGLE_TLV("Earphone Playback Volume",
RX51_EXT_API_AIC34B, 118,
aic3x_output_stage_tlv),
+};
+static int rx51_aic34_init(struct snd_soc_codec *codec) +{
int i, err;
/* Add TPA6130A2 controls */
tpa6130a2_add_controls(codec);
/* set up NC codec pins */
snd_soc_dapm_nc_pin(codec, "MIC3L");
snd_soc_dapm_nc_pin(codec, "MIC3R");
snd_soc_dapm_nc_pin(codec, "LINE1R");
/* Create jack for accessory reporting */
err = snd_jack_new(codec->card, "Jack", SND_JACK_MECHANICAL |
SND_JACK_HEADSET | SND_JACK_AVOUT, &rx51_jack);
if (err < 0)
return err;
/* Add RX51 specific controls */
for (i = 0; i < ARRAY_SIZE(aic34_rx51_controls); i++) {
err = snd_ctl_add(codec->card,
snd_soc_cnew(&aic34_rx51_controls[i], codec, NULL));
if (err < 0)
return err;
}
/* Add RX51 specific widgets */
snd_soc_dapm_new_controls(codec, aic34_dapm_widgets,
ARRAY_SIZE(aic34_dapm_widgets));
/* Set up RX51 specific audio path audio_map */
snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
snd_soc_dapm_enable_pin(codec, "Earphone");
snd_soc_dapm_sync(codec);
return 0;
+}
+/* Since all codec control is done by Bluetooth hardware
- only some constrains need to be set for it */
+struct snd_soc_dai btcodec_dai = {
.name = "Bluetooth codec",
.playback = {
.stream_name = "BT Playback",
.channels_min = 1,
.channels_max = 1,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.stream_name = "BT Capture",
.channels_min = 1,
.channels_max = 1,
.rates = SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+};
+/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link rx51_dai[] = {
{
.name = "TLV320AIC34",
.stream_name = "AIC34",
.cpu_dai = &omap_mcbsp_dai[0],
.codec_dai = &aic3x_dai,
.init = rx51_aic34_init,
.ops = &rx51_ops,
}, {
.name = "Bluetooth PCM",
.stream_name = "Bluetooth",
.cpu_dai = &omap_mcbsp_dai[1],
.codec_dai = &btcodec_dai,
.ops = &rx51_bt_ops,
}
+};
+/* Audio private data */ +static struct aic3x_setup_data rx51_aic34_setup = {
.gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED,
.gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT,
+};
+/* Audio card */ +static struct snd_soc_card rx51_sound_card = {
.name = "RX51",
.dai_link = rx51_dai,
.num_links = ARRAY_SIZE(rx51_dai),
.platform = &omap_soc_platform,
+};
+/* Audio subsystem */ +static struct snd_soc_device rx51_snd_devdata = {
.card = &rx51_sound_card,
.codec_dev = &soc_codec_dev_aic3x,
.codec_data = &rx51_aic34_setup,
+};
+static struct platform_device *rx51_snd_device;
+#define REMAP_OFFSET 2 +#define DEDICATED_OFFSET 3 +#define VMMC2_DEV_GRP 0x2B +#define VMMC2_285V 0x0a
These defines appear unused?
+static int __init rx51_soc_init(void) +{
int err;
struct device *dev;
if (!machine_is_nokia_rx51())
return -ENODEV;
if (gpio_request(RX51_CODEC_RESET_GPIO, NULL) < 0)
BUG();
if (gpio_request(RX51_TVOUT_SEL_GPIO, "tvout_sel") < 0)
BUG();
if (gpio_request(RX51_ECI_SWITCH_1_GPIO, "ECI switch 1") < 0)
BUG();
if (gpio_request(RX51_ECI_SWITCH_2_GPIO, "ECI switch 2") < 0)
BUG();
gpio_direction_output(RX51_CODEC_RESET_GPIO, 0);
gpio_direction_output(RX51_TVOUT_SEL_GPIO, 0);
gpio_direction_output(RX51_ECI_SWITCH_1_GPIO, 0);
gpio_direction_output(RX51_ECI_SWITCH_2_GPIO, 1);
gpio_set_value(RX51_CODEC_RESET_GPIO, 0);
udelay(1);
gpio_set_value(RX51_CODEC_RESET_GPIO, 1);
msleep(1);
if (gpio_request(RX51_SPEAKER_AMP_TWL_GPIO, NULL) < 0)
BUG();
gpio_direction_output(RX51_SPEAKER_AMP_TWL_GPIO, 0);
err = snd_soc_register_dai(&btcodec_dai);
if (err)
return err;
rx51_snd_device = platform_device_alloc("soc-audio", -1);
if (!rx51_snd_device) {
err = -ENOMEM;
goto err0;
}
platform_set_drvdata(rx51_snd_device, &rx51_snd_devdata);
rx51_snd_devdata.dev = &rx51_snd_device->dev;
err = platform_device_add(rx51_snd_device);
if (err)
goto err1;
dev = &rx51_snd_device->dev;
*(unsigned int *)rx51_dai[0].cpu_dai->private_data = 1;
*(unsigned int *)rx51_dai[1].cpu_dai->private_data = 2;
err = device_create_file(dev, &dev_attr_eci_mode);
if (err)
goto err2;
return err;
+err2:
platform_device_del(rx51_snd_device);
+err1:
platform_device_put(rx51_snd_device);
+err0:
snd_soc_unregister_dai(&btcodec_dai);
return err;
+}
+static void __exit rx51_soc_exit(void) +{
platform_device_unregister(rx51_snd_device);
snd_soc_unregister_dai(&btcodec_dai);
+}
+module_init(rx51_soc_init); +module_exit(rx51_soc_exit);
+MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ALSA SoC Nokia RX51"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/rx51.h b/sound/soc/omap/rx51.h new file mode 100644 index 0000000..ee55260 --- /dev/null +++ b/sound/soc/omap/rx51.h @@ -0,0 +1,29 @@ +#ifndef _RX51_H_ +#define _RX51_H_
+/*
- rx51.h - SoC audio for Nokia RX51
- Copyright (C) 2008 - 2009 Nokia Corporation
- Contact: Peter Ujfalusi peter.ujfalusi@nokia.com
Eduardo Valentin <eduardo.valentin@nokia.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; version 2 of the License.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
+int rx51_set_eci_mode(int mode); +void rx51_jack_report(int status);
+#endif /* _RX51_H_ */
1.6.4.183.g04423
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, Oct 08, 2009 at 02:31:26PM +0200, Nurkkala Eero.An (EXT-Offcode/Oulu) wrote:
On Thu, 2009-10-08 at 13:58 +0200, Valentin Eduardo (Nokia-D/Helsinki) wrote:
From: Eduardo Valentin eduardo.valentin@nokia.com
Introduce RX-51 Machine driver for ASoC and AIC34b_dummy (block B) i2c driver.
Also move the request_gpio of speaker_enabled from board-rx51-peripherals.c to this machine driver.
These drivers were originally written by Jarkko Nikula.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com
arch/arm/mach-omap2/board-rx51-peripherals.c | 2 - sound/soc/omap/Kconfig | 10 + sound/soc/omap/Makefile | 2 + sound/soc/omap/aic34b_dummy.c | 271 +++++++++ sound/soc/omap/aic34b_dummy.h | 32 + sound/soc/omap/rx51.c | 793 ++++++++++++++++++++++++++ sound/soc/omap/rx51.h | 29 + 7 files changed, 1137 insertions(+), 2 deletions(-) create mode 100644 sound/soc/omap/aic34b_dummy.c create mode 100644 sound/soc/omap/aic34b_dummy.h create mode 100644 sound/soc/omap/rx51.c create mode 100644 sound/soc/omap/rx51.h
<snip>
+static struct platform_device *rx51_snd_device;
+#define REMAP_OFFSET 2 +#define DEDICATED_OFFSET 3 +#define VMMC2_DEV_GRP 0x2B +#define VMMC2_285V 0x0a
These defines appear unused?
yeah, will rip then off.
+static int __init rx51_soc_init(void) +{
int err;
struct device *dev;
if (!machine_is_nokia_rx51())
return -ENODEV;
if (gpio_request(RX51_CODEC_RESET_GPIO, NULL) < 0)
BUG();
if (gpio_request(RX51_TVOUT_SEL_GPIO, "tvout_sel") < 0)
BUG();
if (gpio_request(RX51_ECI_SWITCH_1_GPIO, "ECI switch 1") < 0)
BUG();
if (gpio_request(RX51_ECI_SWITCH_2_GPIO, "ECI switch 2") < 0)
BUG();
gpio_direction_output(RX51_CODEC_RESET_GPIO, 0);
gpio_direction_output(RX51_TVOUT_SEL_GPIO, 0);
gpio_direction_output(RX51_ECI_SWITCH_1_GPIO, 0);
gpio_direction_output(RX51_ECI_SWITCH_2_GPIO, 1);
gpio_set_value(RX51_CODEC_RESET_GPIO, 0);
udelay(1);
gpio_set_value(RX51_CODEC_RESET_GPIO, 1);
msleep(1);
if (gpio_request(RX51_SPEAKER_AMP_TWL_GPIO, NULL) < 0)
BUG();
gpio_direction_output(RX51_SPEAKER_AMP_TWL_GPIO, 0);
err = snd_soc_register_dai(&btcodec_dai);
if (err)
return err;
rx51_snd_device = platform_device_alloc("soc-audio", -1);
if (!rx51_snd_device) {
err = -ENOMEM;
goto err0;
}
platform_set_drvdata(rx51_snd_device, &rx51_snd_devdata);
rx51_snd_devdata.dev = &rx51_snd_device->dev;
err = platform_device_add(rx51_snd_device);
if (err)
goto err1;
dev = &rx51_snd_device->dev;
*(unsigned int *)rx51_dai[0].cpu_dai->private_data = 1;
*(unsigned int *)rx51_dai[1].cpu_dai->private_data = 2;
err = device_create_file(dev, &dev_attr_eci_mode);
if (err)
goto err2;
return err;
+err2:
platform_device_del(rx51_snd_device);
+err1:
platform_device_put(rx51_snd_device);
+err0:
snd_soc_unregister_dai(&btcodec_dai);
return err;
+}
+static void __exit rx51_soc_exit(void) +{
platform_device_unregister(rx51_snd_device);
snd_soc_unregister_dai(&btcodec_dai);
+}
+module_init(rx51_soc_init); +module_exit(rx51_soc_exit);
+MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ALSA SoC Nokia RX51"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/rx51.h b/sound/soc/omap/rx51.h new file mode 100644 index 0000000..ee55260 --- /dev/null +++ b/sound/soc/omap/rx51.h @@ -0,0 +1,29 @@ +#ifndef _RX51_H_ +#define _RX51_H_
+/*
- rx51.h - SoC audio for Nokia RX51
- Copyright (C) 2008 - 2009 Nokia Corporation
- Contact: Peter Ujfalusi peter.ujfalusi@nokia.com
Eduardo Valentin <eduardo.valentin@nokia.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; version 2 of the License.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
+int rx51_set_eci_mode(int mode); +void rx51_jack_report(int status);
+#endif /* _RX51_H_ */
1.6.4.183.g04423

On Thu, Oct 08, 2009 at 02:58:51PM +0300, Eduardo Valentin wrote:
From: Eduardo Valentin eduardo.valentin@nokia.com
Introduce RX-51 Machine driver for ASoC and AIC34b_dummy (block B) i2c driver.
What is an "AIC34b_dummy (block B)"? You probably want to split it out into a separate patch.
- TODO:
- Get rid of this driver, at least when ASoC v2 is merged and when
- we can support multiple codec instances in tlv320aic3x.c driver.
- This driver is hacked only for Nokia RX51 HW.
Could you please explain what the issue here is? A description of the hardware would go a long way here.
+EXPORT_SYMBOL(aic34b_set_mic_bias);
EXPORT_SYMBOL_GPL().
+static void rx51_set_eci_switches(int mode) +{
- switch (mode) {
- case 0: /* Bias off */
- case 1: /* Bias according to rx51_dapm_jack_bias */
- case 4: /* Bias on */
/* Codec connected to mic/bias line */
gpio_set_value(RX51_ECI_SWITCH_1_GPIO, 0);
gpio_set_value(RX51_ECI_SWITCH_2_GPIO, 1);
break;
- case 2:
/* ECI INT#2 detect connected to mic/bias line */
gpio_set_value(RX51_ECI_SWITCH_1_GPIO, 0);
gpio_set_value(RX51_ECI_SWITCH_2_GPIO, 0);
break;
- case 3:
/* ECI RX/TX connected to mic/bias line */
gpio_set_value(RX51_ECI_SWITCH_1_GPIO, 1);
gpio_set_value(RX51_ECI_SWITCH_2_GPIO, 0);
break;
- }
Some defines for the mode (instead of magic numbers) would be nice).
+void rx51_jack_report(int status) +{
- snd_jack_report(rx51_jack, status);
+} +EXPORT_SYMBOL(rx51_jack_report);
Why is this being exported?
+enum {
RX51_EXT_API_AIC34B,
+}; +#define SOC_RX51_EXT_SINGLE_TLV(xname, ext_api, max, 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 = rx51_ext_info_volsw, \
- .get = rx51_ext_get_volsw, \
- .put = rx51_ext_put_volsw, \
- .private_value = (ext_api) << 26 | (max) << 16, \
+}
This looks like it ought to be pushed down into some other driver? -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, 8 Oct 2009 14:11:16 +0100 Mark Brown broonie@opensource.wolfsonmicro.com wrote:
What is an "AIC34b_dummy (block B)"? You probably want to split it out into a separate patch.
I think this is the reason why Eduardo mentioned my name in the commit log :-)
Yep, dummy driver is for block B of AIC34 which is basically dual AIC33 in a same package.
- TODO:
- Get rid of this driver, at least when ASoC v2 is merged and when
- we can support multiple codec instances in tlv320aic3x.c driver.
- This driver is hacked only for Nokia RX51 HW.
Could you please explain what the issue here is? A description of the hardware would go a long way here.
Eduardo, please correct me if I remember wrong but block B of AIC34 is not used as an codec as such but more like an amplifier and analogic mixer with digital parts unused. So with this dummy driver the HW features were easier to support than to develop multiple intance support for codec drivers.

On Fri, Oct 09, 2009 at 07:44:00AM +0200, Jarkko Nikula wrote:
On Thu, 8 Oct 2009 14:11:16 +0100 Mark Brown broonie@opensource.wolfsonmicro.com wrote:
What is an "AIC34b_dummy (block B)"? You probably want to split it out into a separate patch.
I think this is the reason why Eduardo mentioned my name in the commit log :-)
Yeah, I mentioned Jarkko because he was the person who initially wrote those drivers :-).
But I can split then if you prefer. No problem.
Yep, dummy driver is for block B of AIC34 which is basically dual AIC33 in a same package.
Yes. This is correct.
- TODO:
- Get rid of this driver, at least when ASoC v2 is merged and when
- we can support multiple codec instances in tlv320aic3x.c driver.
- This driver is hacked only for Nokia RX51 HW.
Could you please explain what the issue here is? A description of the hardware would go a long way here.
Eduardo, please correct me if I remember wrong but block B of AIC34 is not used as an codec as such but more like an amplifier and analogic mixer with digital parts unused. So with this dummy driver the HW features were easier to support than to develop multiple intance support for codec drivers.
Yes. This is also correct. It is more like an amplifier / mixer. I think that's why you named it dummy :-).
But I think I should add this info as a comment inside the code.
-- Jarkko

On Fri, Oct 09, 2009 at 09:37:20AM +0300, Eduardo Valentin wrote:
On Fri, Oct 09, 2009 at 07:44:00AM +0200, Jarkko Nikula wrote:
Yep, dummy driver is for block B of AIC34 which is basically dual AIC33 in a same package.
Yes. This is correct.
So to software it just looks like two devices at separate I2C addresses so once multi CODEC support is present there will be no need to have specific support for it at all?
Eduardo, please correct me if I remember wrong but block B of AIC34 is not used as an codec as such but more like an amplifier and analogic mixer with digital parts unused. So with this dummy driver the HW features were easier to support than to develop multiple intance support for codec drivers.
Yes. This is also correct. It is more like an amplifier / mixer. I think that's why you named it dummy :-).
But I think I should add this info as a comment inside the code.
Some words or ASCII art in the machine driver would help a lot, I think.

From: Eero Nurkkala ext-eero.nurkkala@nokia.com
Add Sidetone feature to mcbsp instances 2 and 3 on OMAP3 based devices.
Signed-off-by: Eero Nurkkala ext-eero.nurkkala@nokia.com Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com --- arch/arm/mach-omap2/mcbsp.c | 2 + arch/arm/plat-omap/include/mach/mcbsp.h | 43 ++++ arch/arm/plat-omap/mcbsp.c | 379 ++++++++++++++++++++++++++++++- 3 files changed, 423 insertions(+), 1 deletions(-)
diff --git a/arch/arm/mach-omap2/mcbsp.c b/arch/arm/mach-omap2/mcbsp.c index a846aa1..c5b4d33 100644 --- a/arch/arm/mach-omap2/mcbsp.c +++ b/arch/arm/mach-omap2/mcbsp.c @@ -132,6 +132,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = { }, { .phys_base = OMAP34XX_MCBSP2_BASE, + .phys_base_st = OMAP34XX_MCBSP2_ST_BASE, .dma_rx_sync = OMAP24XX_DMA_MCBSP2_RX, .dma_tx_sync = OMAP24XX_DMA_MCBSP2_TX, .rx_irq = INT_24XX_MCBSP2_IRQ_RX, @@ -141,6 +142,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = { }, { .phys_base = OMAP34XX_MCBSP3_BASE, + .phys_base_st = OMAP34XX_MCBSP3_ST_BASE, .dma_rx_sync = OMAP24XX_DMA_MCBSP3_RX, .dma_tx_sync = OMAP24XX_DMA_MCBSP3_TX, .rx_irq = INT_24XX_MCBSP3_IRQ_RX, diff --git a/arch/arm/plat-omap/include/mach/mcbsp.h b/arch/arm/plat-omap/include/mach/mcbsp.h index 7e9cae3..8ecc09d 100644 --- a/arch/arm/plat-omap/include/mach/mcbsp.h +++ b/arch/arm/plat-omap/include/mach/mcbsp.h @@ -49,6 +49,9 @@
#define OMAP34XX_MCBSP1_BASE 0x48074000 #define OMAP34XX_MCBSP2_BASE 0x49022000 +#define OMAP34XX_MCBSP2_ST_BASE 0x49028000 +#define OMAP34XX_MCBSP3_BASE 0x49024000 +#define OMAP34XX_MCBSP3_ST_BASE 0x4902A000 #define OMAP34XX_MCBSP3_BASE 0x49024000 #define OMAP34XX_MCBSP4_BASE 0x49026000 #define OMAP34XX_MCBSP5_BASE 0x48096000 @@ -147,6 +150,15 @@ #define OMAP_MCBSP_REG_WAKEUPEN 0xA8 #define OMAP_MCBSP_REG_XCCR 0xAC #define OMAP_MCBSP_REG_RCCR 0xB0 +#define OMAP_MCBSP_REG_SSELCR 0xBC + +#define OMAP_ST_REG_REV 0x00 +#define OMAP_ST_REG_SYSCONFIG 0x10 +#define OMAP_ST_REG_IRQSTATUS 0x18 +#define OMAP_ST_REG_IRQENABLE 0x1C +#define OMAP_ST_REG_SGAINCR 0x24 +#define OMAP_ST_REG_SFIRCR 0x28 +#define OMAP_ST_REG_SSELCR 0x2C
#define AUDIO_MCBSP_DATAWRITE (OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1) #define AUDIO_MCBSP_DATAREAD (OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1) @@ -265,6 +277,24 @@ #define ENAWAKEUP 0x0004 #define SOFTRST 0x0002
+/********************** McBSP SSELCR bit definitions ***********************/ +#define SIDETONEEN 0x0400 + +/********************** McBSP Sidetone SYSCONFIG bit definitions ***********/ +#define ST_AUTOIDLE 0x0001 + +/********************** McBSP Sidetone SGAINCR bit definitions *************/ +#define ST_CH1GAIN(value) ((value<<16)) /* Bits 16:31 */ +#define ST_CH0GAIN(value) (value) /* Bits 0:15 */ + +/********************** McBSP Sidetone SFIRCR bit definitions **************/ +#define ST_FIRCOEFF(value) (value) /* Bits 0:15 */ + +/********************** McBSP Sidetone SSELCR bit definitions **************/ +#define ST_COEFFWRDONE 0x0004 +#define ST_COEFFWREN 0x0002 +#define ST_SIDETONEEN 0x0001 + /********************** McBSP DMA operating modes **************************/ #define MCBSP_DMA_MODE_ELEMENT 0 #define MCBSP_DMA_MODE_THRESHOLD 1 @@ -375,10 +405,22 @@ struct omap_mcbsp_platform_data { u16 rx_irq, tx_irq; struct omap_mcbsp_ops *ops; #ifdef CONFIG_ARCH_OMAP34XX + /* Sidetone block for McBSP 2 and 3 */ + unsigned long phys_base_st; u16 buffer_size; #endif };
+struct omap_mcbsp_st_data { + void __iomem *io_base_st; + int enabled; + int running; + s16 taps[128]; /* Sidetone filter coefficients */ + int nr_taps; /* Number of filter coefficients in use */ + s16 ch0gain; + s16 ch1gain; +}; + struct omap_mcbsp { struct device *dev; unsigned long phys_base; @@ -411,6 +453,7 @@ struct omap_mcbsp { struct clk *iclk; struct clk *fclk; #ifdef CONFIG_ARCH_OMAP34XX + struct omap_mcbsp_st_data *st_data; int dma_op_mode; u16 max_tx_thres; u16 max_rx_thres; diff --git a/arch/arm/plat-omap/mcbsp.c b/arch/arm/plat-omap/mcbsp.c index 88ac976..9baa4b4 100644 --- a/arch/arm/plat-omap/mcbsp.c +++ b/arch/arm/plat-omap/mcbsp.c @@ -26,6 +26,9 @@
#include <mach/dma.h> #include <mach/mcbsp.h> +#ifdef CONFIG_ARCH_OMAP34XX +#include "../mach-omap2/cm-regbits-34xx.h" +#endif
struct omap_mcbsp **mcbsp_ptr; int omap_mcbsp_count; @@ -54,6 +57,11 @@ int omap_mcbsp_read(void __iomem *io_base, u16 reg) #define omap_mcbsp_check_valid_id(id) (id < omap_mcbsp_count) #define id_to_mcbsp_ptr(id) mcbsp_ptr[id];
+#define OMAP_ST_READ(base, reg) \ + omap_mcbsp_read(base, OMAP_ST_REG_##reg) +#define OMAP_ST_WRITE(base, reg, val) \ + omap_mcbsp_write(base, OMAP_ST_REG_##reg, val) + static void omap_mcbsp_dump_reg(u8 id) { struct omap_mcbsp *mcbsp = id_to_mcbsp_ptr(id); @@ -199,6 +207,160 @@ void omap_mcbsp_config(unsigned int id, const struct omap_mcbsp_reg_cfg *config) EXPORT_SYMBOL(omap_mcbsp_config);
#ifdef CONFIG_ARCH_OMAP34XX +static void omap_st_enable(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data; + void __iomem *io_base_mcbsp; + void __iomem *io_base_st; + unsigned int w; + + io_base_mcbsp = mcbsp->io_base; + st_data = mcbsp->st_data; + io_base_st = st_data->io_base_st; + + /* + * Sidetone uses McBSP ICLK - which must not idle when sidetones + * are enabled or sidetones start sounding ugly. + */ + w = cm_read_mod_reg(OMAP3430_PER_MOD, CM_AUTOIDLE); + w &= ~(mcbsp->id - 1); + cm_write_mod_reg(w, OMAP3430_PER_MOD, CM_AUTOIDLE); + + /* Enable McBSP Sidetone */ + w = OMAP_MCBSP_READ(io_base_mcbsp, SSELCR); + OMAP_MCBSP_WRITE(io_base_mcbsp, SSELCR, w | SIDETONEEN); + + w = OMAP_ST_READ(io_base_st, SYSCONFIG); + OMAP_ST_WRITE(io_base_st, SYSCONFIG, w & ~(ST_AUTOIDLE)); + + /* Enable Sidetone from Sidetone Core */ + w = OMAP_ST_READ(io_base_st, SSELCR); + OMAP_ST_WRITE(io_base_st, SSELCR, w | ST_SIDETONEEN); +} + +static void omap_st_disable(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data; + void __iomem *io_base_mcbsp; + void __iomem *io_base_st; + unsigned int w; + + io_base_mcbsp = mcbsp->io_base; + st_data = mcbsp->st_data; + io_base_st = st_data->io_base_st; + + w = OMAP_ST_READ(io_base_st, SSELCR); + OMAP_ST_WRITE(io_base_st, SSELCR, w & ~(ST_SIDETONEEN)); + + w = OMAP_ST_READ(io_base_st, SYSCONFIG); + OMAP_ST_WRITE(io_base_st, SYSCONFIG, w | ST_AUTOIDLE); + + w = OMAP_MCBSP_READ(io_base_mcbsp, SSELCR); + OMAP_MCBSP_WRITE(io_base_mcbsp, SSELCR, w & ~(SIDETONEEN)); + + w = cm_read_mod_reg(OMAP3430_PER_MOD, CM_AUTOIDLE); + w |= (mcbsp->id - 1); + cm_write_mod_reg(w, OMAP3430_PER_MOD, CM_AUTOIDLE); +} + +static void omap_st_enable_autoidle(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data; + void __iomem *io_base_st; + unsigned int w; + + st_data = mcbsp->st_data; + io_base_st = st_data->io_base_st; + + w = OMAP_ST_READ(io_base_st, SYSCONFIG); + OMAP_ST_WRITE(io_base_st, SYSCONFIG, w | ST_AUTOIDLE); +} + +static void omap_st_fir_write(struct omap_mcbsp *mcbsp, s16 *fir) +{ + struct omap_mcbsp_st_data *st_data; + void __iomem *io_base; + u16 w, i; + + st_data = mcbsp->st_data; + io_base = st_data->io_base_st; + + w = OMAP_ST_READ(io_base, SYSCONFIG); + OMAP_ST_WRITE(io_base, SYSCONFIG, w & ~(ST_AUTOIDLE)); + + w = OMAP_ST_READ(io_base, SSELCR); + + if (w & ST_COEFFWREN) + OMAP_ST_WRITE(io_base, SSELCR, w & ~(ST_COEFFWREN)); + + OMAP_ST_WRITE(io_base, SSELCR, w | ST_COEFFWREN); + + for (i = 0; i < 128; i++) + OMAP_ST_WRITE(io_base, SFIRCR, fir[i]); + + i = 0; + + w = OMAP_ST_READ(io_base, SSELCR); + while (!(w & ST_COEFFWRDONE) && (++i < 1000)) + w = OMAP_ST_READ(io_base, SSELCR); + + OMAP_ST_WRITE(io_base, SSELCR, w & ~(ST_COEFFWREN)); + + if (i == 1000) + dev_err(mcbsp->dev, "McBSP FIR load error!\n"); +} + +static void omap_st_chgain(struct omap_mcbsp *mcbsp, s16 ch0gain, s16 ch1gain) +{ + struct omap_mcbsp_st_data *st_data; + void __iomem *io_base; + u16 w; + + st_data = mcbsp->st_data; + io_base = st_data->io_base_st; + + w = OMAP_ST_READ(io_base, SYSCONFIG); + OMAP_ST_WRITE(io_base, SYSCONFIG, w & ~(ST_AUTOIDLE)); + + w = OMAP_ST_READ(io_base, SSELCR); + + OMAP_ST_WRITE(io_base, SGAINCR, ST_CH0GAIN(ch0gain) | \ + ST_CH1GAIN(ch1gain)); +} + +static void omap_st_start(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + unsigned long flags; + + spin_lock_irqsave(&mcbsp->lock, flags); + if (st_data) { + omap_st_fir_write(mcbsp, mcbsp->st_data->taps); + omap_st_chgain(mcbsp, + mcbsp->st_data->ch0gain, + mcbsp->st_data->ch1gain); + if (st_data->enabled) + omap_st_enable(mcbsp); + else + omap_st_enable_autoidle(mcbsp); + st_data->running = 1; + } + spin_unlock_irqrestore(&mcbsp->lock, flags); +} + +static void omap_st_stop(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + unsigned long flags; + + spin_lock_irqsave(&mcbsp->lock, flags); + if (st_data && st_data->running) { + omap_st_disable(mcbsp); + st_data->running = 0; + } + spin_unlock_irqrestore(&mcbsp->lock, flags); +} + /* * omap_mcbsp_set_tx_threshold configures how to deal * with transmit threshold. the threshold value and handler can be @@ -360,6 +522,8 @@ static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp) #else static inline void omap34xx_mcbsp_request(struct omap_mcbsp *mcbsp) {} static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp) {} +static inline void omap_st_start(struct omap_mcbsp *mcbsp) {} +static inline void omap_st_stop(struct omap_mcbsp *mcbsp) {} #endif
/* @@ -516,6 +680,9 @@ void omap_mcbsp_start(unsigned int id, int tx, int rx) mcbsp = id_to_mcbsp_ptr(id); io_base = mcbsp->io_base;
+ if (cpu_is_omap34xx()) + omap_st_start(mcbsp); + mcbsp->rx_word_length = (OMAP_MCBSP_READ(io_base, RCR1) >> 5) & 0x7; mcbsp->tx_word_length = (OMAP_MCBSP_READ(io_base, XCR1) >> 5) & 0x7;
@@ -609,6 +776,9 @@ void omap_mcbsp_stop(unsigned int id, int tx, int rx) w = OMAP_MCBSP_READ(io_base, SPCR2); OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~(1 << 6)); } + + if (cpu_is_omap34xx()) + omap_st_stop(mcbsp); } EXPORT_SYMBOL(omap_mcbsp_stop);
@@ -1190,6 +1360,147 @@ unlock:
static DEVICE_ATTR(dma_op_mode, 0644, dma_op_mode_show, dma_op_mode_store);
+static ssize_t st_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + return sprintf(buf, "%d\n", st_data->enabled); +} + +static ssize_t st_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + unsigned long val; + int status; + + status = strict_strtoul(buf, 0, &val); + if (status) + return status; + + spin_lock_irq(&mcbsp->lock); + st_data->enabled = !!val; + + if (st_data->running) { + if (st_data->enabled) + omap_st_enable(mcbsp); + else + omap_st_disable(mcbsp); + } + spin_unlock_irq(&mcbsp->lock); + + return size; +} + +static DEVICE_ATTR(st_enable, 0644, st_enable_show, st_enable_store); + +static ssize_t st_taps_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + ssize_t status = 0; + int i; + + spin_lock_irq(&mcbsp->lock); + for (i = 0; i < st_data->nr_taps; i++) + status += sprintf(&buf[status], (i ? ", %d" : "%d"), + st_data->taps[i]); + if (i) + status += sprintf(&buf[status], "\n"); + spin_unlock_irq(&mcbsp->lock); + + return status; +} + +static ssize_t st_taps_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + int val, tmp, status, i = 0; + + spin_lock_irq(&mcbsp->lock); + memset(st_data->taps, 0, sizeof(st_data->taps)); + st_data->nr_taps = 0; + + do { + status = sscanf(buf, "%d%n", &val, &tmp); + if (status < 0 || status == 0) { + size = -EINVAL; + goto out; + } + if (val < -32768 || val > 32767) { + size = -EINVAL; + goto out; + } + st_data->taps[i++] = val; + buf += tmp; + if (*buf != ',') + break; + buf++; + } while (1); + + st_data->nr_taps = i; + +out: + spin_unlock_irq(&mcbsp->lock); + + return size; +} + +static DEVICE_ATTR(st_taps, 0644, st_taps_show, st_taps_store); + +static ssize_t st_chgain_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (strcmp("st_ch0gain", attr->attr.name) == 0) + return sprintf(buf, "%d\n", st_data->ch0gain); + else + return sprintf(buf, "%d\n", st_data->ch1gain); +} + +static ssize_t st_chgain_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + long val; + int status; + + status = strict_strtol(buf, 0, &val); + if (status) + return status; + if (val < -32768 || val > 32767) + return -EINVAL; + + spin_lock_irq(&mcbsp->lock); + if (strcmp("st_ch0gain", attr->attr.name) == 0) + st_data->ch0gain = val; + else + st_data->ch1gain = val; + + if (st_data->running) + omap_st_chgain(mcbsp, + mcbsp->st_data->ch0gain, + mcbsp->st_data->ch1gain); + spin_unlock_irq(&mcbsp->lock); + + return size; +} + +static DEVICE_ATTR(st_ch0gain, 0644, st_chgain_show, st_chgain_store); +static DEVICE_ATTR(st_ch1gain, 0644, st_chgain_show, st_chgain_store); + static const struct attribute *additional_attrs[] = { &dev_attr_max_tx_thres.attr, &dev_attr_max_rx_thres.attr, @@ -1211,6 +1522,62 @@ static inline void __devexit omap_additional_remove(struct device *dev) sysfs_remove_group(&dev->kobj, &additional_attr_group); }
+static const struct attribute *sidetone_attrs[] = { + &dev_attr_st_enable.attr, + &dev_attr_st_taps.attr, + &dev_attr_st_ch0gain.attr, + &dev_attr_st_ch1gain.attr, + NULL, +}; + +static const struct attribute_group sidetone_attr_group = { + .attrs = (struct attribute **)sidetone_attrs, +}; + +int __devinit omap_st_add(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_platform_data *pdata = mcbsp->pdata; + struct omap_mcbsp_st_data *st_data; + int err; + + st_data = kzalloc(sizeof(*mcbsp->st_data), GFP_KERNEL); + if (!st_data) { + err = -ENOMEM; + goto err1; + } + + st_data->io_base_st = ioremap(pdata->phys_base_st, SZ_4K); + if (!st_data->io_base_st) { + err = -ENOMEM; + goto err2; + } + + err = sysfs_create_group(&mcbsp->dev->kobj, &sidetone_attr_group); + if (err) + goto err3; + + mcbsp->st_data = st_data; + return 0; + +err3: + iounmap(st_data->io_base_st); +err2: + kfree(st_data); +err1: + return err; + +} + +static void __devexit omap_st_remove(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (st_data) { + sysfs_remove_group(&mcbsp->dev->kobj, &sidetone_attr_group); + iounmap(st_data->io_base_st); + kfree(st_data); + } +} static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp) { mcbsp->dma_op_mode = MCBSP_DMA_MODE_ELEMENT; @@ -1224,6 +1591,12 @@ static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp) if (omap_additional_add(mcbsp->dev)) dev_warn(mcbsp->dev, "Unable to create additional controls\n"); + + if (mcbsp->id == 2 || mcbsp->id == 3) + if (omap_st_add(mcbsp)) + dev_warn(mcbsp->dev, + "Unable to create sidetone controls\n"); + } else { mcbsp->max_tx_thres = -EINVAL; mcbsp->max_rx_thres = -EINVAL; @@ -1232,8 +1605,12 @@ static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp)
static inline void __devexit omap34xx_device_exit(struct omap_mcbsp *mcbsp) { - if (cpu_is_omap34xx()) + if (cpu_is_omap34xx()) { omap_additional_remove(mcbsp->dev); + + if (mcbsp->id == 2 || mcbsp->id == 3) + omap_st_remove(mcbsp); + } } #else static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp) {}

On Thu, Oct 08, 2009 at 02:58:52PM +0300, Eduardo Valentin wrote:
+static const struct attribute *sidetone_attrs[] = {
- &dev_attr_st_enable.attr,
- &dev_attr_st_taps.attr,
- &dev_attr_st_ch0gain.attr,
- &dev_attr_st_ch1gain.attr,
- NULL,
+};
This stuff, particularly the enable, probably wants to be pushed out via an ALSA API rather than via random sysfs stuff. It'd be better to publish a control API here and then use that from within ALSA. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, Oct 08, 2009 at 03:17:02PM +0200, Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:52PM +0300, Eduardo Valentin wrote:
+static const struct attribute *sidetone_attrs[] = {
- &dev_attr_st_enable.attr,
- &dev_attr_st_taps.attr,
- &dev_attr_st_ch0gain.attr,
- &dev_attr_st_ch1gain.attr,
- NULL,
+};
This stuff, particularly the enable, probably wants to be pushed out via an ALSA API rather than via random sysfs stuff. It'd be better to publish a control API here and then use that from within ALSA.
I see. The thing is this mcbsp driver is kinda of odd driver. It is currently a platform driver. And it is supposed to be not restricted to audio things (even though it is currently used only by audio). It does not export any alsa interface currently.
So, maybe we should rip off this sysfs things, export symbols inside kernel and then export to user land somewhere else from alsa code?

On Thu, Oct 08, 2009 at 04:23:33PM +0300, Eduardo Valentin wrote:
So, maybe we should rip off this sysfs things, export symbols inside kernel and then export to user land somewhere else from alsa code?
Yes, that's what I'm suggesting. Provide an API to ALSA and then let ALSA worry about the control stuff. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, 2009-10-08 at 15:17 +0200, ext Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:52PM +0300, Eduardo Valentin wrote:
+static const struct attribute *sidetone_attrs[] = {
- &dev_attr_st_enable.attr,
- &dev_attr_st_taps.attr,
- &dev_attr_st_ch0gain.attr,
- &dev_attr_st_ch1gain.attr,
- NULL,
+};
This stuff, particularly the enable, probably wants to be pushed out via an ALSA API rather than via random sysfs stuff. It'd be better to publish a control API here and then use that from within ALSA.
Hmm. What would be the way to transfer 128 x s16 words; is there an ALSA control for something like that already ? IIRC correctly, the max bytesize per control is (or used to be) something like 256 bytes or so. So that gets right at it. (that's the sidetone 128 tap FIR in question)
- Eero
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Fri, Oct 09, 2009 at 08:09:27AM +0300, Eero Nurkkala wrote:
On Thu, 2009-10-08 at 15:17 +0200, ext Mark Brown wrote:
This stuff, particularly the enable, probably wants to be pushed out via an ALSA API rather than via random sysfs stuff. It'd be better to publish a control API here and then use that from within ALSA.
Hmm. What would be the way to transfer 128 x s16 words; is there an ALSA control for something like that already ? IIRC correctly, the max bytesize per control is (or used to be) something like 256 bytes or so. So that gets right at it. (that's the sidetone 128 tap FIR in question)
For things like the FIR you probably don't want to expose the entire table directly to user space. Depending on the typical usage it may be that platform data is the appropriate mechanism, with some simpler thing presented to applications allowing switching between a limited set of settings. sysfs may end up being the best option for the FIR setup.
My main concern here is that the control goes into the ALSA domain so that the audio drivers know what's going on with these sidetone paths, particular in terms of the routing.

On Fri, 2009-10-09 at 12:44 +0200, ext Mark Brown wrote:
On Fri, Oct 09, 2009 at 08:09:27AM +0300, Eero Nurkkala wrote:
On Thu, 2009-10-08 at 15:17 +0200, ext Mark Brown wrote:
This stuff, particularly the enable, probably wants to be pushed out via an ALSA API rather than via random sysfs stuff. It'd be better to publish a control API here and then use that from within ALSA.
Hmm. What would be the way to transfer 128 x s16 words; is there an ALSA control for something like that already ? IIRC correctly, the max bytesize per control is (or used to be) something like 256 bytes or so. So that gets right at it. (that's the sidetone 128 tap FIR in question)
For things like the FIR you probably don't want to expose the entire table directly to user space. Depending on the typical usage it may be that platform data is the appropriate mechanism, with some simpler thing presented to applications allowing switching between a limited set of settings. sysfs may end up being the best option for the FIR setup.
My main concern here is that the control goes into the ALSA domain so that the audio drivers know what's going on with these sidetone paths, particular in terms of the routing.
Indeed. If I'm not totally wrong, the sidetone engineering is such, that the sinetones should be of constant volume (this may depend on the usecase). So, let's say we have the TPA6130 codec's volume used along with a sidetone:
1. A change in TPA6130 volume should lead to a change in the sidetone's gain (if it's enabled). The change of gain is directly related to the change in the TPA's volume.
That said, if the sidetone's gain is of the domain [-2,2], how could it translate into the TPA's nonlinear dB domain?. If that may be resolved somehow feasibly, this sounds like a very useful relation? Ideas?
This is all assumed that the FIR itself doesn't cause any gains to the signal level at the desired frequency domain(s).
- Eero

On Mon, Oct 12, 2009 at 09:17:41AM +0300, Eero Nurkkala wrote:
Indeed. If I'm not totally wrong, the sidetone engineering is such, that the sinetones should be of constant volume (this may depend on the usecase). So, let's say we have the TPA6130 codec's volume used along with a sidetone:
- A change in TPA6130 volume should lead to a change in the sidetone's gain (if it's enabled). The change of gain is directly related to the change in the TPA's volume.
Could you go into more detail about why there should be a relationship between the two gains?

On Mon, 2009-10-12 at 11:12 +0200, ext Mark Brown wrote:
On Mon, Oct 12, 2009 at 09:17:41AM +0300, Eero Nurkkala wrote:
Indeed. If I'm not totally wrong, the sidetone engineering is such, that the sinetones should be of constant volume (this may depend on the usecase). So, let's say we have the TPA6130 codec's volume used along with a sidetone:
- A change in TPA6130 volume should lead to a change in the sidetone's gain (if it's enabled). The change of gain is directly related to the change in the TPA's volume.
Could you go into more detail about why there should be a relationship between the two gains?
As mentioned, in some (or most) cases the absolute sinetone level (dB) is expected constant. Even a volume change should not effect the sidetone level. Thus, if there's a change in volume, the sidetone gains should be readjusted. If that can happen automatically, that would be just nice =)
I don't know whether there "should" be a relationship, but if there was, that'd be very useful. But it gets complicated as there may be several volume controls in the path. Anyway, that was just an idea that may just as well be ignored.

On Mon, Oct 12, 2009 at 12:28:25PM +0300, Eero Nurkkala wrote:
As mentioned, in some (or most) cases the absolute sinetone level (dB) is expected constant. Even a volume change should not effect the sidetone level. Thus, if there's a change in volume, the sidetone gains should be readjusted. If that can happen automatically, that would be just nice =)
That's what you said, but could you please go into a bit more detail about why you beleive that this should be the case? Is there some hardware limitation here?

On Mon, 2009-10-12 at 11:32 +0200, ext Mark Brown wrote:
On Mon, Oct 12, 2009 at 12:28:25PM +0300, Eero Nurkkala wrote:
As mentioned, in some (or most) cases the absolute sinetone level (dB) is expected constant. Even a volume change should not effect the sidetone level. Thus, if there's a change in volume, the sidetone gains should be readjusted. If that can happen automatically, that would be just nice =)
That's what you said, but could you please go into a bit more detail about why you beleive that this should be the case? Is there some hardware limitation here?
No HW limits here... it's all about the hmm, "friendliness" of sidetones.
"The more intense the side-tone, the less intensely the speaker talks." (June 12, 1948) [REF]
So changing the headset volume should not make the speaker talk more/less intensely? Of course it all depends... this is just one aspect to this.
[REF]
LOUDNESS OF SPEAKING: THE EFFECT OF THE INTENSITY OF SIDE-TONE UPON THE INTENSITY OF THE SPEAKER Kenyon College Acoustic Laboratory Gambier, Ohio School of Aviation Medicine and Research N.A.S., Pensacola, Florida

On Mon, Oct 12, 2009 at 01:28:48PM +0300, Eero Nurkkala wrote:
No HW limits here... it's all about the hmm, "friendliness" of sidetones.
This is an application level issue. We've no way of determining within the drivers if a given path is actually being used as a real sidetone or if it is being used for some other purpose. As far as the drivers are concerned these are unrelated controls.

From: Eduardo Valentin eduardo.valentin@nokia.com
Add board-rx51-audio.c with audio support for rx51 boards. Platform data included for the following drivers: si4713, aic34b_dummy and tpa6130a2.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com --- arch/arm/mach-omap2/Makefile | 1 + arch/arm/mach-omap2/board-rx51-audio.c | 132 ++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 0 deletions(-) create mode 100644 arch/arm/mach-omap2/board-rx51-audio.c
diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index 6b7702f..50f2fbe 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_MACH_OMAP_3430SDP) += board-3430sdp.o \ obj-$(CONFIG_MACH_NOKIA_N8X0) += board-n8x0.o obj-$(CONFIG_MACH_NOKIA_RX51) += board-rx51.o \ board-rx51-peripherals.o \ + board-rx51-audio.o \ mmc-twl4030.o obj-$(CONFIG_MACH_OMAP_ZOOM2) += board-zoom2.o \ mmc-twl4030.o \ diff --git a/arch/arm/mach-omap2/board-rx51-audio.c b/arch/arm/mach-omap2/board-rx51-audio.c new file mode 100644 index 0000000..cba42b5 --- /dev/null +++ b/arch/arm/mach-omap2/board-rx51-audio.c @@ -0,0 +1,132 @@ +/* + * linux/arch/arm/mach-omap2/board-rx51-audio.c + * + * Copyright (C) 2008 Nokia + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/i2c/twl4030.h> +#include <sound/tpa6130a2-plat.h> +#include <media/si4713.h> +#include <media/radio-si4713.h> +#include <linux/platform_device.h> +#include <asm/mach-types.h> + +#define RX51_FMTX_RESET_GPIO 163 +#define RX51_FMTX_IRQ 53 +#define RX51_FMRX_IRQ 43 +#define RX51_HEADPHN_EN_GPIO 98 + +static int si4713_set_power(int power) +{ + if (!power) + udelay(1); + gpio_set_value(RX51_FMTX_RESET_GPIO, power); + udelay(50); + + return 0; +} + +static struct si4713_platform_data rx51_si4713_platform_data = { + .set_power = si4713_set_power, +}; + +static void __init rx51_init_si4713(void) +{ + int r; + + r = gpio_request(RX51_FMTX_RESET_GPIO, "si4713"); + if (r < 0) { + printk(KERN_ERR "Failed to request gpio for FMTx rst\n"); + return; + } + + gpio_direction_output(RX51_FMTX_RESET_GPIO, 0); +} + +static int tpa6130a2_set_power(int state) +{ + gpio_set_value(RX51_HEADPHN_EN_GPIO, !!state); + return 0; +} + +static struct tpa6130a2_platform_data rx51_tpa6130a2_platform_data = { + .set_power = tpa6130a2_set_power, +}; + +static void __init rx51_init_tpa6130a2(void) +{ + int r; + + r = gpio_request(RX51_HEADPHN_EN_GPIO, "tpa6130a2"); + if (r < 0) { + printk(KERN_ERR "Failed to request shutdown gpio " + "for TPA6130a2 chip\n"); + } + + gpio_direction_output(RX51_HEADPHN_EN_GPIO, 0); + + return; +} + +struct i2c_board_info si4713_board_info = { + I2C_BOARD_INFO("si4713", SI4713_I2C_ADDR_BUSEN_HIGH), + .irq = OMAP_GPIO_IRQ(RX51_FMTX_IRQ), + .platform_data = &rx51_si4713_platform_data, +}; + +static struct radio_si4713_platform_data rx51_radio_si4713_platform_data = { + .i2c_bus = 2, + .subdev_board_info = &si4713_board_info, +}; + +static struct platform_device radio_fmtx = { + .name = "radio-si4713", + .id = -1, + .dev = { + .platform_data = &rx51_radio_si4713_platform_data, + }, +}; + +static struct platform_device *rx51_audio_devices[] = { + &radio_fmtx, +}; + +static struct i2c_board_info __initdata rx51_audio_i2c_board_info_2[] = { + { + I2C_BOARD_INFO("aic34b_dummy", 0x19), + }, + { + I2C_BOARD_INFO("tlv320aic3x", 0x18), + }, + { + I2C_BOARD_INFO("tpa6130a2", 0x60), + .platform_data = &rx51_tpa6130a2_platform_data, + }, +}; + +static int __init rx51_audio_init(void) +{ + if (!machine_is_nokia_rx51()) + return 0; + + platform_add_devices(rx51_audio_devices, + ARRAY_SIZE(rx51_audio_devices)); + + rx51_init_tpa6130a2(); + rx51_init_si4713(); + i2c_register_board_info(2, rx51_audio_i2c_board_info_2, + ARRAY_SIZE(rx51_audio_i2c_board_info_2)); + + return 0; +} + +subsys_initcall(rx51_audio_init);

From: Eduardo Valentin eduardo.valentin@nokia.com
Use separated supplies for vaux3 and vmmc2.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com --- arch/arm/mach-omap2/board-rx51-peripherals.c | 8 ++++++-- 1 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index b227475..92f7dfa 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -125,6 +125,10 @@ static struct regulator_consumer_supply rx51_vmmc1_supply = { .supply = "vmmc", };
+static struct regulator_consumer_supply rx51_vaux3_supply = { + .supply = "vmmc", +}; + static struct regulator_consumer_supply rx51_vmmc2_supply = { .supply = "vmmc", }; @@ -184,7 +188,7 @@ static struct regulator_init_data rx51_vaux3_mmc = { | REGULATOR_CHANGE_STATUS, }, .num_consumer_supplies = 1, - .consumer_supplies = &rx51_vmmc2_supply, + .consumer_supplies = &rx51_vaux3_supply, };
static struct regulator_init_data rx51_vaux4 = { @@ -266,7 +270,7 @@ static int rx51_twlgpio_setup(struct device *dev, unsigned gpio, unsigned n) /* set up MMC adapters, linking their regulators to them */ twl4030_mmc_init(mmc); rx51_vmmc1_supply.dev = mmc[0].dev; - rx51_vmmc2_supply.dev = mmc[1].dev; + rx51_vaux3_supply.dev = mmc[1].dev; rx51_vsim_supply.dev = mmc[1].dev;
return 0;

On Thu, Oct 08, 2009 at 02:58:54PM +0300, Eduardo Valentin wrote:
From: Eduardo Valentin eduardo.valentin@nokia.com
Use separated supplies for vaux3 and vmmc2.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com
+static struct regulator_consumer_supply rx51_vaux3_supply = {
- .supply = "vmmc",
+};
I'd expect all these supplies to have devices associated with them (see below)...
static struct regulator_consumer_supply rx51_vmmc2_supply = { .supply = "vmmc", }; @@ -184,7 +188,7 @@ static struct regulator_init_data rx51_vaux3_mmc = { | REGULATOR_CHANGE_STATUS, }, .num_consumer_supplies = 1,
- .consumer_supplies = &rx51_vmmc2_supply,
- .consumer_supplies = &rx51_vaux3_supply,
};
I may have missed it but I don't see rx51_vmmc2_supply added back anywhere in the patch?
static struct regulator_init_data rx51_vaux4 = { @@ -266,7 +270,7 @@ static int rx51_twlgpio_setup(struct device *dev, unsigned gpio, unsigned n) /* set up MMC adapters, linking their regulators to them */ twl4030_mmc_init(mmc); rx51_vmmc1_supply.dev = mmc[0].dev;
- rx51_vmmc2_supply.dev = mmc[1].dev;
- rx51_vaux3_supply.dev = mmc[1].dev;
...using dev_name rather than dev should avoid the need to do this at runtime. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, Oct 08, 2009 at 03:21:10PM +0200, Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:54PM +0300, Eduardo Valentin wrote:
From: Eduardo Valentin eduardo.valentin@nokia.com
Use separated supplies for vaux3 and vmmc2.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com
+static struct regulator_consumer_supply rx51_vaux3_supply = {
- .supply = "vmmc",
+};
I'd expect all these supplies to have devices associated with them (see below)...
static struct regulator_consumer_supply rx51_vmmc2_supply = { .supply = "vmmc", }; @@ -184,7 +188,7 @@ static struct regulator_init_data rx51_vaux3_mmc = { | REGULATOR_CHANGE_STATUS, }, .num_consumer_supplies = 1,
- .consumer_supplies = &rx51_vmmc2_supply,
- .consumer_supplies = &rx51_vaux3_supply,
};
I may have missed it but I don't see rx51_vmmc2_supply added back anywhere in the patch?
yes. That's because this patch is just to split then. As you can see in current code, this structure, rx51_vmmc2_supply, is actually used by rx51_vaux3_mmc and rx51_vmmc2. So the idea is to have separated supplies for each one. This patch just splits then. Patch 0006 of this series adds usage of rx51_vmmc2_supply.
static struct regulator_init_data rx51_vaux4 = { @@ -266,7 +270,7 @@ static int rx51_twlgpio_setup(struct device *dev, unsigned gpio, unsigned n) /* set up MMC adapters, linking their regulators to them */ twl4030_mmc_init(mmc); rx51_vmmc1_supply.dev = mmc[0].dev;
- rx51_vmmc2_supply.dev = mmc[1].dev;
- rx51_vaux3_supply.dev = mmc[1].dev;
...using dev_name rather than dev should avoid the need to do this at runtime.
I see your point. As mentioned above, this patch is just a split. I just added rx51_vaux3_supply to be owned by rx51_vaux3_mmc. And let the code behavior as it was before.
Maybe your proposal must be sent into a separated patch/patch series (?).

On Fri, Oct 09, 2009 at 09:45:29AM +0300, Eduardo Valentin wrote:
On Thu, Oct 08, 2009 at 03:21:10PM +0200, Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:54PM +0300, Eduardo Valentin wrote:
I may have missed it but I don't see rx51_vmmc2_supply added back anywhere in the patch?
yes. That's because this patch is just to split then. As you can see in current code, this structure, rx51_vmmc2_supply, is actually used by rx51_vaux3_mmc and rx51_vmmc2. So the idea is to have separated supplies for each one. This patch just splits then. Patch 0006 of this series adds usage of rx51_vmmc2_supply.
Might be an idea to change the description to something like "add a separate structure for...", it wasn't 100% clear what was going on (probably because the diff doesn't include sufficient context to make it clear).
...using dev_name rather than dev should avoid the need to do this at runtime.
I see your point. As mentioned above, this patch is just a split. I just added rx51_vaux3_supply to be owned by rx51_vaux3_mmc. And let the code behavior as it was before.
Maybe your proposal must be sent into a separated patch/patch series (?).
Yes, it's definately a separate patch - might be good to put it into the series before this one.

Hello,
On Fri, Oct 09, 2009 at 01:03:47PM +0200, Mark Brown wrote:
On Fri, Oct 09, 2009 at 09:45:29AM +0300, Eduardo Valentin wrote:
On Thu, Oct 08, 2009 at 03:21:10PM +0200, Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:54PM +0300, Eduardo Valentin wrote:
I may have missed it but I don't see rx51_vmmc2_supply added back anywhere in the patch?
yes. That's because this patch is just to split then. As you can see in current code, this structure, rx51_vmmc2_supply, is actually used by rx51_vaux3_mmc and rx51_vmmc2. So the idea is to have separated supplies for each one. This patch just splits then. Patch 0006 of this series adds usage of rx51_vmmc2_supply.
Might be an idea to change the description to something like "add a separate structure for...", it wasn't 100% clear what was going on (probably because the diff doesn't include sufficient context to make it clear).
...using dev_name rather than dev should avoid the need to do this at runtime.
I'm afraid using dev_name is not that easy. The mmc driver generates device name at runtime. That's why this board file setups .dev at runtime as well.
rx51_twlgpio_setup -> twl4030_mmc_init -> omap2_init_mmc
So, changing this supply to something static using .dev_name it is not possible with current code. That would need refactoring the whole mmc and hsmmc setup. And the device naming procedure is dependent on cpu as well. Check arch/arm/mach-omap2/device.c:omap2_init_mmc.
I see your point. As mentioned above, this patch is just a split. I just added rx51_vaux3_supply to be owned by rx51_vaux3_mmc. And let the code behavior as it was before.
Maybe your proposal must be sent into a separated patch/patch series (?).
Yes, it's definately a separate patch - might be good to put it into the series before this one.

On Mon, Oct 12, 2009 at 11:08:58AM +0300, Eduardo Valentin wrote:
I'm afraid using dev_name is not that easy. The mmc driver generates device name at runtime. That's why this board file setups .dev at runtime as well.
rx51_twlgpio_setup -> twl4030_mmc_init -> omap2_init_mmc
So, changing this supply to something static using .dev_name it is not possible with current code. That would need refactoring the whole mmc and hsmmc setup. And the device naming procedure is dependent on cpu as well. Check arch/arm/mach-omap2/device.c:omap2_init_mmc.
Oh, dear - that sounds broken for hardware that's fixed on the board. That said, the code there looks like it's supposed to come out with the same answer each time it's run? How does this work with the clock API?

* Mark Brown broonie@opensource.wolfsonmicro.com [091012 02:18]:
On Mon, Oct 12, 2009 at 11:08:58AM +0300, Eduardo Valentin wrote:
I'm afraid using dev_name is not that easy. The mmc driver generates device name at runtime. That's why this board file setups .dev at runtime as well.
rx51_twlgpio_setup -> twl4030_mmc_init -> omap2_init_mmc
So, changing this supply to something static using .dev_name it is not possible with current code. That would need refactoring the whole mmc and hsmmc setup. And the device naming procedure is dependent on cpu as well. Check arch/arm/mach-omap2/device.c:omap2_init_mmc.
Oh, dear - that sounds broken for hardware that's fixed on the board. That said, the code there looks like it's supposed to come out with the same answer each time it's run? How does this work with the clock API?
The clocks are matched using clkdev. Basically the driver just requests functional clock (fck) and interface clock (ick):
$ grep mmci arch/arm/*omap*/clock*.c arch/arm/mach-omap1/clock.c: CLK("mmci-omap.0", "fck", &mmc1_ck, CK_16XX | CK_1510 | CK_310), arch/arm/mach-omap1/clock.c: CLK("mmci-omap.0", "ick", &armper_ck.clk, CK_16XX | CK_1510 | CK_310), arch/arm/mach-omap1/clock.c: CLK("mmci-omap.1", "fck", &mmc2_ck, CK_16XX), arch/arm/mach-omap1/clock.c: CLK("mmci-omap.1", "ick", &armper_ck.clk, CK_16XX), arch/arm/mach-omap2/clock24xx.c: CLK("mmci-omap.0", "ick", &mmc_ick, CK_242X), arch/arm/mach-omap2/clock24xx.c: CLK("mmci-omap.0", "fck", &mmc_fck, CK_242X), arch/arm/mach-omap2/clock24xx.c: CLK("mmci-omap-hs.0", "ick", &mmchs1_ick, CK_243X), arch/arm/mach-omap2/clock24xx.c: CLK("mmci-omap-hs.0", "fck", &mmchs1_fck, CK_243X), arch/arm/mach-omap2/clock24xx.c: CLK("mmci-omap-hs.1", "ick", &mmchs2_ick, CK_243X), arch/arm/mach-omap2/clock24xx.c: CLK("mmci-omap-hs.1", "fck", &mmchs2_fck, CK_243X), arch/arm/mach-omap2/clock24xx.c: CLK("mmci-omap-hs.0", "mmchsdb_fck", &mmchsdb1_fck, CK_243X), arch/arm/mach-omap2/clock24xx.c: CLK("mmci-omap-hs.1", "mmchsdb_fck", &mmchsdb2_fck, CK_243X), arch/arm/mach-omap2/clock34xx.c: CLK("mmci-omap-hs.2", "fck", &mmchs3_fck, CK_3430ES2), arch/arm/mach-omap2/clock34xx.c: CLK("mmci-omap-hs.1", "fck", &mmchs2_fck, CK_343X), arch/arm/mach-omap2/clock34xx.c: CLK("mmci-omap-hs.0", "fck", &mmchs1_fck, CK_343X), arch/arm/mach-omap2/clock34xx.c: CLK("mmci-omap-hs.2", "ick", &mmchs3_ick, CK_3430ES2), arch/arm/mach-omap2/clock34xx.c: CLK("mmci-omap-hs.1", "ick", &mmchs2_ick, CK_343X), arch/arm/mach-omap2/clock34xx.c: CLK("mmci-omap-hs.0", "ick", &mmchs1_ick, CK_343X),
Regards,
Tony

On Wed, Oct 14, 2009 at 10:15:48AM -0700, Tony Lindgren wrote:
- Mark Brown broonie@opensource.wolfsonmicro.com [091012 02:18]:
On Mon, Oct 12, 2009 at 11:08:58AM +0300, Eduardo Valentin wrote:
I'm afraid using dev_name is not that easy. The mmc driver generates device name at runtime. That's why this board file setups .dev at runtime as well.
...
So, changing this supply to something static using .dev_name it is not possible with current code. That would need refactoring the whole mmc and hsmmc setup. And the device naming procedure is dependent on cpu as well. Check arch/arm/mach-omap2/device.c:omap2_init_mmc.
same answer each time it's run? How does this work with the clock API?
The clocks are matched using clkdev. Basically the driver just requests functional clock (fck) and interface clock (ick):
$ grep mmci arch/arm/*omap*/clock*.c arch/arm/mach-omap1/clock.c: CLK("mmci-omap.0", "fck", &mmc1_ck, CK_16XX | CK_1510 | CK_310), arch/arm/mach-omap1/clock.c: CLK("mmci-omap.0", "ick", &armper_ck.clk, CK_16XX | CK_1510 | CK_310),
So this is using the standard dev_name based clkdev matching which Eduardo said was impossible for the regulators. Is it just that this will actually work fine for the regulators or is there some other magic in the OMAP code that joins things up?

* Mark Brown broonie@opensource.wolfsonmicro.com [091015 02:01]:
On Wed, Oct 14, 2009 at 10:15:48AM -0700, Tony Lindgren wrote:
- Mark Brown broonie@opensource.wolfsonmicro.com [091012 02:18]:
On Mon, Oct 12, 2009 at 11:08:58AM +0300, Eduardo Valentin wrote:
I'm afraid using dev_name is not that easy. The mmc driver generates device name at runtime. That's why this board file setups .dev at runtime as well.
...
So, changing this supply to something static using .dev_name it is not possible with current code. That would need refactoring the whole mmc and hsmmc setup. And the device naming procedure is dependent on cpu as well. Check arch/arm/mach-omap2/device.c:omap2_init_mmc.
same answer each time it's run? How does this work with the clock API?
The clocks are matched using clkdev. Basically the driver just requests functional clock (fck) and interface clock (ick):
$ grep mmci arch/arm/*omap*/clock*.c arch/arm/mach-omap1/clock.c: CLK("mmci-omap.0", "fck", &mmc1_ck, CK_16XX | CK_1510 | CK_310), arch/arm/mach-omap1/clock.c: CLK("mmci-omap.0", "ick", &armper_ck.clk, CK_16XX | CK_1510 | CK_310),
So this is using the standard dev_name based clkdev matching which Eduardo said was impossible for the regulators. Is it just that this will actually work fine for the regulators or is there some other magic in the OMAP code that joins things up?
Well the mmc regulators are just passed from board-*.c files to mmc-twl4030.c which does all the low-level init needed. No other special magic going on.
Eduardro, care to check the dev_name issue one more time?
Regards,
Tony

From: Eduardo Valentin eduardo.valentin@nokia.com
This patch adds two supplies for VMMC2 on rx51 boards.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com --- arch/arm/mach-omap2/board-rx51-peripherals.c | 14 +++++++------- 1 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index 92f7dfa..9177b1c 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -129,8 +129,9 @@ static struct regulator_consumer_supply rx51_vaux3_supply = { .supply = "vmmc", };
-static struct regulator_consumer_supply rx51_vmmc2_supply = { - .supply = "vmmc", +static struct regulator_consumer_supply rx51_vmmc2_supplies[] = { + REGULATOR_SUPPLY("avdd_dac", "2-0018"), /* tlv320aic3x */ + REGULATOR_SUPPLY("vdd", "2-0060"), /* tpa6130a2*/ };
static struct regulator_consumer_supply rx51_vsim_supply = { @@ -230,8 +231,8 @@ static struct regulator_init_data rx51_vmmc2 = { | REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS, }, - .num_consumer_supplies = 1, - .consumer_supplies = &rx51_vmmc2_supply, + .num_consumer_supplies = ARRAY_SIZE(rx51_vmmc2_supplies), + .consumer_supplies = rx51_vmmc2_supplies, };
static struct regulator_init_data rx51_vsim = { @@ -442,10 +443,9 @@ static int __init rx51_i2c_init(void) if ((system_rev >= SYSTEM_REV_S_USES_VAUX3 && system_rev < 0x100) || system_rev >= SYSTEM_REV_B_USES_VAUX3) rx51_twldata.vaux3 = &rx51_vaux3_mmc; - else { + else rx51_twldata.vaux3 = &rx51_vaux3_cam; - rx51_twldata.vmmc2 = &rx51_vmmc2; - } + rx51_twldata.vmmc2 = &rx51_vmmc2; omap_register_i2c_bus(1, 2600, rx51_peripherals_i2c_board_info_1, ARRAY_SIZE(rx51_peripherals_i2c_board_info_1)); omap_register_i2c_bus(2, 100, NULL, 0);

On Thu, Oct 08, 2009 at 02:58:55PM +0300, Eduardo Valentin wrote:
+static struct regulator_consumer_supply rx51_vmmc2_supplies[] = {
- REGULATOR_SUPPLY("avdd_dac", "2-0018"), /* tlv320aic3x */
- REGULATOR_SUPPLY("vdd", "2-0060"), /* tpa6130a2*/
};
avdd_dac is the only supply added for the tlv320aic3x but, for example, the tlv320aic34 has something like 8 supplies from a quick scan of the datasheet. It'd be better to set up all of the supplies, even if only with a fixed voltage regulator supplying them, since when regulator support is added to the CODEC driver it should be requesting all the supplies it needs and therefore fail to instatiate if some are missing. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, Oct 08, 2009 at 03:26:19PM +0200, Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:55PM +0300, Eduardo Valentin wrote:
+static struct regulator_consumer_supply rx51_vmmc2_supplies[] = {
- REGULATOR_SUPPLY("avdd_dac", "2-0018"), /* tlv320aic3x */
- REGULATOR_SUPPLY("vdd", "2-0060"), /* tpa6130a2*/
};
avdd_dac is the only supply added for the tlv320aic3x but, for example, the tlv320aic34 has something like 8 supplies from a quick scan of the datasheet. It'd be better to set up all of the supplies, even if only with a fixed voltage regulator supplying them, since when regulator support is added to the CODEC driver it should be requesting all the supplies it needs and therefore fail to instatiate if some are missing.
Right. Should we add 4 instances of drvdd and 2 of iovdd? So, naming those would be like:
REGULATOR_SUPPLY("drvdd0", "2-0018"), /* tlv320aic3x */ REGULATOR_SUPPLY("drvdd1", "2-0018"), /* tlv320aic3x */ REGULATOR_SUPPLY("drvdd2", "2-0018"), /* tlv320aic3x */ REGULATOR_SUPPLY("drvdd3", "2-0018"), /* tlv320aic3x */
or even better:
REGULATOR_SUPPLY("drvdd_b4", "2-0018"), /* tlv320aic3x */ REGULATOR_SUPPLY("drvdd_a4", "2-0018"), /* tlv320aic3x */ REGULATOR_SUPPLY("drvdd_b9", "2-0018"), /* tlv320aic3x */ REGULATOR_SUPPLY("drvdd_a9", "2-0018"), /* tlv320aic3x */
what do you think?
BR

On Mon, Oct 12, 2009 at 12:04:55PM +0300, Eduardo Valentin wrote:
Right. Should we add 4 instances of drvdd and 2 of iovdd? So, naming those would be like:
No, if there's multiple pins for the supply then there's no need to represent those individually - they're required to be wired together in hardware. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

Hi,
On Mon, Oct 12, 2009 at 11:21:04AM +0200, Mark Brown wrote:
On Mon, Oct 12, 2009 at 12:04:55PM +0300, Eduardo Valentin wrote:
Right. Should we add 4 instances of drvdd and 2 of iovdd? So, naming those would be like:
No, if there's multiple pins for the supply then there's no need to represent those individually - they're required to be wired together in hardware.
Might be a stupid question probably because of my lack of knowledge of regulator framework, but if the driver code must be written in such a way that it requires all regulators for its supplies, then what happens with those which are grounded? How do you provide the regulator_init_data in this case (where the supply is actually grounded) ?
BR,

On Mon, Oct 19, 2009 at 12:13:00PM +0300, Eduardo Valentin wrote:
[Please fix your MUA to wrap before 80 columns.]
Might be a stupid question probably because of my lack of knowledge of regulator framework, but if the driver code must be written in such a way that it requires all regulators for its supplies, then what happens with those which are grounded? How do you provide the regulator_init_data in this case (where the supply is actually grounded) ?
That sounds like a fixed voltage regulator. Though for a device where some of the supplies are optional if the driver needs to know about it it may make more sense to have the driver know about that and only request supplies which it will use. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Mon, Oct 19, 2009 at 10:23:32AM +0100, Mark Brown wrote:
On Mon, Oct 19, 2009 at 12:13:00PM +0300, Eduardo Valentin wrote:
Might be a stupid question probably because of my lack of knowledge of regulator framework, but if the driver code must be written in such a way that it requires all regulators for its supplies, then what happens with those which are grounded? How do you provide the regulator_init_data in this case (where the supply is actually grounded) ?
That sounds like a fixed voltage regulator. Though for a device where some of the supplies are optional if the driver needs to know about it it may make more sense to have the driver know about that and only request supplies which it will use.
...or possibly just handle the error when it fails to obtain them. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

From: Eduardo Valentin eduardo.valentin@nokia.com
This patch adds initial usage of regulator framework to control avdd_dac inside tlv320aic3x ASoC codec driver.
The refcount to avdd_dac is increased / decreased only during probe and remove. Here it is still needed to implement proper enable/disable regulator depending on chip usage. Now if driver can get regulator for avdd_dac, then it will just let it on on probe and then leave it off on remove.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com --- sound/soc/codecs/tlv320aic3x.c | 26 ++++++++++++++++++++++++++ 1 files changed, 26 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 3395cf9..82e0a64 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -38,6 +38,7 @@ #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> +#include <linux/regulator/consumer.h> #include <linux/platform_device.h> #include <sound/core.h> #include <sound/pcm.h> @@ -56,6 +57,7 @@ struct aic3x_priv { struct snd_soc_codec codec; unsigned int sysclk; int master; + struct regulator *regulator; };
/* @@ -1286,6 +1288,11 @@ static int aic3x_unregister(struct aic3x_priv *aic3x) snd_soc_unregister_dai(&aic3x_dai); snd_soc_unregister_codec(&aic3x->codec);
+ if (aic3x->regulator) { + regulator_disable(aic3x->regulator); + regulator_put(aic3x->regulator); + } + kfree(aic3x); aic3x_codec = NULL;
@@ -1320,6 +1327,25 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, codec->control_data = i2c; codec->hw_write = (hw_write_t) i2c_master_send;
+ aic3x->regulator = regulator_get(&i2c->dev, "avdd_dac"); + if (IS_ERR(aic3x->regulator)) { + dev_warn(&i2c->dev, "No regulator to supply avdd_dac." + " Assuming always on.\n"); + aic3x->regulator = NULL; + } + + /* + * REVISIT: Need to add proper code to put into sleep mode + * avdd_dac regulator. For now, just leave it on. + */ + if (aic3x->regulator) { + int err; + + err = regulator_enable(aic3x->regulator); + if (err < 0) + return err; + } + i2c_set_clientdata(i2c, aic3x);
return aic3x_register(codec);

On Thu, 2009-10-08 at 13:58 +0200, Valentin Eduardo (Nokia-D/Helsinki) wrote:
From: Eduardo Valentin eduardo.valentin@nokia.com
This patch adds initial usage of regulator framework to control avdd_dac inside tlv320aic3x ASoC codec driver.
The refcount to avdd_dac is increased / decreased only during probe and remove. Here it is still needed to implement proper enable/disable regulator depending on chip usage. Now if driver can get regulator for avdd_dac, then it will just let it on on probe and then leave it off on remove.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com
sound/soc/codecs/tlv320aic3x.c | 26 ++++++++++++++++++++++++++ 1 files changed, 26 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 3395cf9..82e0a64 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -38,6 +38,7 @@ #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> +#include <linux/regulator/consumer.h> #include <linux/platform_device.h> #include <sound/core.h> #include <sound/pcm.h> @@ -56,6 +57,7 @@ struct aic3x_priv { struct snd_soc_codec codec; unsigned int sysclk; int master;
- struct regulator *regulator;
};
/* @@ -1286,6 +1288,11 @@ static int aic3x_unregister(struct aic3x_priv *aic3x) snd_soc_unregister_dai(&aic3x_dai); snd_soc_unregister_codec(&aic3x->codec);
- if (aic3x->regulator) {
regulator_disable(aic3x->regulator);
regulator_put(aic3x->regulator);
- }
- kfree(aic3x); aic3x_codec = NULL;
@@ -1320,6 +1327,25 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, codec->control_data = i2c; codec->hw_write = (hw_write_t) i2c_master_send;
- aic3x->regulator = regulator_get(&i2c->dev, "avdd_dac");
- if (IS_ERR(aic3x->regulator)) {
dev_warn(&i2c->dev, "No regulator to supply avdd_dac."
" Assuming always on.\n");
aic3x->regulator = NULL;
- }
- /*
* REVISIT: Need to add proper code to put into sleep mode
* avdd_dac regulator. For now, just leave it on.
*/
Will this ever be revisited =) ? If so, I think there's going to be a jungle in finding the right spots - you need to remember the bypass paths also (bias is not on necessarily). Also, this is regulator thing is highly platform dependent, not aic3x related really at all, so is this the correct place... Just a thought, dont take it too seriously ;)
if (aic3x->regulator) {
int err;
err = regulator_enable(aic3x->regulator);
if (err < 0)
return err;
}
i2c_set_clientdata(i2c, aic3x);
return aic3x_register(codec);
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, Oct 08, 2009 at 02:17:07PM +0200, Nurkkala Eero.An (EXT-Offcode/Oulu) wrote:
On Thu, 2009-10-08 at 13:58 +0200, Valentin Eduardo (Nokia-D/Helsinki) wrote:
From: Eduardo Valentin eduardo.valentin@nokia.com
This patch adds initial usage of regulator framework to control avdd_dac inside tlv320aic3x ASoC codec driver.
The refcount to avdd_dac is increased / decreased only during probe and remove. Here it is still needed to implement proper enable/disable regulator depending on chip usage. Now if driver can get regulator for avdd_dac, then it will just let it on on probe and then leave it off on remove.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com
sound/soc/codecs/tlv320aic3x.c | 26 ++++++++++++++++++++++++++ 1 files changed, 26 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 3395cf9..82e0a64 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -38,6 +38,7 @@ #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> +#include <linux/regulator/consumer.h> #include <linux/platform_device.h> #include <sound/core.h> #include <sound/pcm.h> @@ -56,6 +57,7 @@ struct aic3x_priv { struct snd_soc_codec codec; unsigned int sysclk; int master;
- struct regulator *regulator;
};
/* @@ -1286,6 +1288,11 @@ static int aic3x_unregister(struct aic3x_priv *aic3x) snd_soc_unregister_dai(&aic3x_dai); snd_soc_unregister_codec(&aic3x->codec);
- if (aic3x->regulator) {
regulator_disable(aic3x->regulator);
regulator_put(aic3x->regulator);
- }
- kfree(aic3x); aic3x_codec = NULL;
@@ -1320,6 +1327,25 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, codec->control_data = i2c; codec->hw_write = (hw_write_t) i2c_master_send;
- aic3x->regulator = regulator_get(&i2c->dev, "avdd_dac");
- if (IS_ERR(aic3x->regulator)) {
dev_warn(&i2c->dev, "No regulator to supply avdd_dac."
" Assuming always on.\n");
aic3x->regulator = NULL;
- }
- /*
* REVISIT: Need to add proper code to put into sleep mode
* avdd_dac regulator. For now, just leave it on.
*/
Will this ever be revisited =) ? If so, I think there's going to be a jungle in finding the right spots - you need to remember the bypass paths also (bias is not on necessarily). Also, this is regulator thing is highly platform dependent, not aic3x related really at all, so is this the correct place... Just a thought, dont take it too seriously ;)
Heheh.. no I don't take it too seriously, don't worry :-)
Actually I've discussed about it with Peter. Initially I wrote it inside rx51 machine driver. But then, we agreed it is actually a thing which must be controlled by the driver. The same way it is done for TPA for instance.
Of course, the presence of that regulator must not be a blocker for the driver. As you can see, if the regulator can not be queried, the driver will assume that it is always on.
I must agree with you, but would rephrase as: the presence of this regulator is board specific, but controlling when it must be enabled/disabled is a role for the driver, in this case, tlv320aic3x.
What do you think ?
if (aic3x->regulator) {
int err;
err = regulator_enable(aic3x->regulator);
if (err < 0)
return err;
}
i2c_set_clientdata(i2c, aic3x);
return aic3x_register(codec);

On Thu, Oct 08, 2009 at 03:17:07PM +0300, Eero Nurkkala wrote:
Will this ever be revisited =) ? If so, I think there's going to be a jungle in finding the right spots - you need to remember the bypass paths also (bias is not on necessarily).
The bias is always on when any path through the chip is on, this was fixed in either .31 or .30.
Also, this is regulator thing
is highly platform dependent, not aic3x related really at all, so is this the correct place... Just a thought, dont take it too seriously ;)
I'm not sure what you mean by this? -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

Mark Brown wrote:
Will this ever be revisited =) ? If so, I think there's going to be a jungle in finding the right spots - you need to remember the bypass paths also (bias is not on necessarily).
The bias is always on when any path through the chip is on, this was fixed in either .31 or .30.
Good! Thanks for the update.
Also, this is regulator thing
is highly platform dependent, not aic3x related really at all, so is this the correct place... Just a thought, dont take it too seriously ;)
I'm not sure what you mean by this?
You may power the aic3x from a fixed source, or from multiple sources, with and without any regulator in between. It's up to the HW and HW design.
Moreover, you don't _power off_ (turn the regulator off) the analog voltages of aic3x; things won't work. So it's not like a switch everybody may use. Or nothing prevent you from experiencing that...
- EEro
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On 8 Oct 2009, at 16:44, ext-Eero.Nurkkala@nokia.com wrote:
Mark Brown wrote:
Also, this is regulator
thing is highly platform dependent, not aic3x related really at all, so is this the correct place... Just a thought, dont take it too seriously ;)
I'm not sure what you mean by this?
You may power the aic3x from a fixed source, or from multiple sources, with and without any regulator in between. It's up to the HW and HW design.
The regulator API can cope with all this pretty transparently - if multiple supplies come from the same regulator the API will hide that from the consumer. There is a fixed voltage regulator driver which can be used to represent supplies with no soft control.
Moreover, you don't _power off_ (turn the regulator off) the analog voltages of aic3x; things won't work. So it's not like a switch everybody may use. Or nothing prevent you from experiencing that...
I'd expect the usage would be that after the audio subsystem has been idle for some configurable period of time the core would bring the audio subsystem down to bias off, at which point supplies could also be switched off. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, 2009-10-08 at 18:01 +0200, ext Mark Brown wrote:
On 8 Oct 2009, at 16:44, ext-Eero.Nurkkala@nokia.com wrote:
Mark Brown wrote:
Also, this is regulator
thing is highly platform dependent, not aic3x related really at all, so is this the correct place... Just a thought, dont take it too seriously ;)
I'm not sure what you mean by this?
You may power the aic3x from a fixed source, or from multiple sources, with and without any regulator in between. It's up to the HW and HW design.
The regulator API can cope with all this pretty transparently - if multiple supplies come from the same regulator the API will hide that from the consumer. There is a fixed voltage regulator driver which can be used to represent supplies with no soft control.
Moreover, you don't _power off_ (turn the regulator off) the analog voltages of aic3x; things won't work. So it's not like a switch everybody may use. Or nothing prevent you from experiencing that...
I'd expect the usage would be that after the audio subsystem has been idle for some configurable period of time the core would bring the audio subsystem down to bias off, at which point supplies could also be switched off.
Right. That would also sound like the RST line also needs also be asserted, and then rewriting all register contents upon wakeup? And also redirecting all i2c traffic to the cache instead of any real i2c writes (meanwhile the device is shut down)? Like in tpa6130?
- Eero
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Fri, Oct 09, 2009 at 07:28:03AM +0300, Eero Nurkkala wrote:
On Thu, 2009-10-08 at 18:01 +0200, ext Mark Brown wrote:
I'd expect the usage would be that after the audio subsystem has been idle for some configurable period of time the core would bring the audio subsystem down to bias off, at which point supplies could also be switched off.
Right. That would also sound like the RST line also needs also be asserted, and then rewriting all register contents upon wakeup? And also redirecting all i2c traffic to the cache instead of any real i2c writes (meanwhile the device is shut down)? Like in tpa6130?
Yes. I'd expect that if this is being implemented there'll be some degree of assistance from the core for some of this. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, Oct 08, 2009 at 02:58:56PM +0300, Eduardo Valentin wrote:
This patch adds initial usage of regulator framework to control avdd_dac inside tlv320aic3x ASoC codec driver.
If you're going to do this you should add support for all the supplies of the device, not just one of them. The extra effort required is low and it avoids compatibility problems when someone wants to set up other supplies, causing existing boards to need to specify them.
- aic3x->regulator = regulator_get(&i2c->dev, "avdd_dac");
- if (IS_ERR(aic3x->regulator)) {
dev_warn(&i2c->dev, "No regulator to supply avdd_dac."
" Assuming always on.\n");
You shouldn't split error messages over multiple lines like this, it breaks grepping to try to find where the message came from.
I'd also rather see failure to get the regulator treated as a hard error. When the regulator API compiled out it is stubbed so that for simple get/enable/disable/put usage it will return success but do nothing. If the regulator API is compiled in and we're not able to acquire regulators there's a good chance that things will break (eg, due to supplies being turned off because they appear to be unused) so flagging the error immediately is less likely to result in runtime fragility.
- /*
* REVISIT: Need to add proper code to put into sleep mode
* avdd_dac regulator. For now, just leave it on.
*/
- if (aic3x->regulator) {
int err;
err = regulator_enable(aic3x->regulator);
if (err < 0)
return err;
- }
The best way to handle this is to push the enable/disable into the bias level configuration so that the regulators are enabled when the chip goes off->standby and disabled during standby->off. This will have the same effect for the moment but will mean that we'll be able to add core support for fully powering down the audio subsystem at runtime in the future. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

From: Eduardo Valentin eduardo.valentin@nokia.com
This patch adds regulator framework control over vdd for tpa6130a2 driver.
Vdd refcount is increased every time the device is power on and decreased when device is power off. If driver fails to get regulator for "vdd" supply, then it will assume its regulator is always on.
Signed-off-by: Eduardo Valentin eduardo.valentin@nokia.com --- sound/soc/codecs/tpa6130a2.c | 16 ++++++++++++++++ 1 files changed, 16 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index d246aad..35720ce 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -26,6 +26,7 @@ #include <linux/device.h> #include <linux/i2c.h> #include <linux/sysfs.h> +#include <linux/regulator/consumer.h> #include <sound/tpa6130a2-plat.h> #include <sound/soc.h> #include <sound/soc-dapm.h> @@ -42,6 +43,7 @@ struct tpa6130a2_data { unsigned char regs[TPA6130A2_CACHEREGNUM]; unsigned char power_state; int (*set_power)(int state); + struct regulator *regulator; };
static int tpa6130a2_i2c_read(int reg) @@ -121,6 +123,8 @@ void tpa6130a2_power(int power)
mutex_lock(&data->mutex); if (power) { + if (data->regulator) + regulator_enable(data->regulator); /* Power on */ if (data->set_power) { data->set_power(1); @@ -141,6 +145,8 @@ void tpa6130a2_power(int power) data->set_power(0); data->power_state = 0; } + if (data->regulator) + regulator_disable(data->regulator); } mutex_unlock(&data->mutex); } @@ -292,6 +298,12 @@ static int tpa6130a2_probe(struct i2c_client *client, }
pdata = (struct tpa6130a2_platform_data *)client->dev.platform_data; + data->regulator = regulator_get(dev, "vdd"); + if (IS_ERR(data->regulator)) { + dev_info(dev, "Could not get regulator for vdd. " + "Executing without regulator.\n"); + data->regulator = NULL; + } /* Set default register values */ data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS | TPA6130A2_HP_EN_R | @@ -326,6 +338,8 @@ static int tpa6130a2_probe(struct i2c_client *client, return 0; fail3: tpa6130a2_power(0); + if (data->regulator) + regulator_put(data->regulator); fail2: kfree(data); i2c_set_clientdata(tpa6130a2_client, NULL); @@ -340,6 +354,8 @@ static int tpa6130a2_remove(struct i2c_client *client) struct tpa6130a2_data *data = i2c_get_clientdata(client);
tpa6130a2_power(0); + if (data->regulator) + regulator_put(data->regulator); kfree(data); tpa6130a2_client = 0;

On Thu, Oct 08, 2009 at 02:58:57PM +0300, Eduardo Valentin wrote:
- data->regulator = regulator_get(dev, "vdd");
- if (IS_ERR(data->regulator)) {
dev_info(dev, "Could not get regulator for vdd. "
"Executing without regulator.\n");
data->regulator = NULL;
- }
Similar comments to the previous patch apply to this driver - regulator usage should be unconditional, error messages should not be split over multiple lines and you should represent all the supplies separately (it looks like there's both VDD and CPVSS required here, for example) to avoid future surprises. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

On Thu, Oct 08, 2009 at 03:43:33PM +0200, Mark Brown wrote:
On Thu, Oct 08, 2009 at 02:58:57PM +0300, Eduardo Valentin wrote:
- data->regulator = regulator_get(dev, "vdd");
- if (IS_ERR(data->regulator)) {
dev_info(dev, "Could not get regulator for vdd. "
"Executing without regulator.\n");
data->regulator = NULL;
- }
Similar comments to the previous patch apply to this driver - regulator usage should be unconditional, error messages should not be split over multiple lines and you should represent all the supplies separately (it looks like there's both VDD and CPVSS required here, for example) to avoid future surprises.
Yeah. The idea here was to keep driver running even if regulators are not properly set in board files. Maybe those in which the regulator is always on. That's why I wrote it with "nicely" message.

On Thu, Oct 08, 2009 at 04:56:28PM +0300, Eduardo Valentin wrote:
On Thu, Oct 08, 2009 at 03:43:33PM +0200, Mark Brown wrote:
Similar comments to the previous patch apply to this driver - regulator usage should be unconditional, error messages should not be split over multiple lines and you should represent all the supplies separately (it looks like there's both VDD and CPVSS required here, for example) to avoid future surprises.
Yeah. The idea here was to keep driver running even if regulators are not properly set in board files. Maybe those in which the regulator is always on. That's why I wrote it with "nicely" message.
The issue is that this doesn't actually end up increasing the system robustness since in some systems the regulator control will be required and the failures can be non-obvious, such as having a shared regulator powered off at run time due to other system activity. A misconfigured regulator supply that stops probe is relatively easy to diagnose and fix compared to potential silent interactions with other subsystems.
It's simple enough to set up a fixed voltage regulator in the board for any supplies that don't have soft regulators. -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
participants (7)
-
Eduardo Valentin
-
Eero Nurkkala
-
ext-Eero.Nurkkala@nokia.com
-
Jarkko Nikula
-
Mark Brown
-
Peter Ujfalusi
-
Tony Lindgren