[alsa-devel] [PATCH v2 0/6] ASoC/MFD/Input: twl6040: Support for audio driven vibra
Hello,
Changes since v1: - Patches already taken by Mark has been removed - Patch 4: Return -EBUSY instead of -EPERM
Intro mail form v1: The following series adds support for the soc driver to route audio (PCM stream) to the vibra ports of the twl6040. We have ALSA controls for the vibra source selection (PDM, or Input/FF). The route can not be changed from Input/FF to PDM during ongoing effect coming from Input/FF. The Input/FF driver can not execute new effect, while the vibra source is configured to be PDM. TO get things working in legacy mode the omap-mcpdm, and twl6040 soc codec driver's channel count needed to be fixed.
To minimize the need for reads through the I2C bus, the MFD driver will cache the vibra control registers.
With this series we can route PCM audio to drive the twl6040 vibra output. In order to that we need to play 5 channel audio, and place the vibra sample at the 5th channel. This will change with ABE support (change == simplified).
The series has been created on top of: git://opensource.wolfsonmicro.com/linux-2.6-asoc, for-3.2 branch
If there are no objections, it would be good if this series goes via audio.
Regards, Peter --- Peter Ujfalusi (6): Input: twl6040: Simplify vibra regsiter definitions MFD: twl6040: Cache the vibra control registers MFD: twl6040: function to query the vibra status for clients Input: twl6040-vibra: Check the selected path for vibra ASoC: omap-mcpdm: Correct the supported number of channels ASoC: twl6040: Support for vibra output paths
drivers/input/misc/twl6040-vibra.c | 19 ++++++--- drivers/mfd/twl6040-core.c | 31 +++++++++++++-- include/linux/mfd/twl6040.h | 24 +++++------ sound/soc/codecs/twl6040.c | 72 ++++++++++++++++++++++++++++++++++++ sound/soc/omap/omap-mcpdm.c | 14 ++++--- 5 files changed, 131 insertions(+), 29 deletions(-)
The bits within the two control registers (for left and right channel) are identical. Use common names for the bits acros the two register. Also add the missing definition for the path selection bit.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@ti.com --- drivers/input/misc/twl6040-vibra.c | 12 ++++++------ include/linux/mfd/twl6040.h | 20 +++++++------------- 2 files changed, 13 insertions(+), 19 deletions(-)
diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c index 154b7a3..cb74185 100644 --- a/drivers/input/misc/twl6040-vibra.c +++ b/drivers/input/misc/twl6040-vibra.c @@ -74,12 +74,12 @@ static irqreturn_t twl6040_vib_irq_handler(int irq, void *data) if (status & TWL6040_VIBLOCDET) { dev_warn(info->dev, "Left Vibrator overcurrent detected\n"); twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL, - TWL6040_VIBENAL); + TWL6040_VIBENA); } if (status & TWL6040_VIBROCDET) { dev_warn(info->dev, "Right Vibrator overcurrent detected\n"); twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR, - TWL6040_VIBENAR); + TWL6040_VIBENA); }
return IRQ_HANDLED; @@ -104,16 +104,16 @@ static void twl6040_vibra_enable(struct vibra_info *info) * overcurrent detection */ twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, - TWL6040_VIBENAL | TWL6040_VIBCTRLL); + TWL6040_VIBENA | TWL6040_VIBCTRL); twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, - TWL6040_VIBENAR | TWL6040_VIBCTRLR); + TWL6040_VIBENA | TWL6040_VIBCTRL); usleep_range(3000, 3500); }
twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, - TWL6040_VIBENAL); + TWL6040_VIBENA); twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, - TWL6040_VIBENAR); + TWL6040_VIBENA);
info->enabled = true; } diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index d9e05ea..e6c755d 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -125,24 +125,18 @@ #define TWL6040_HSDACMODE (1 << 1) #define TWL6040_HSDRVMODE (1 << 3)
-/* VIBCTLL (0x18) fields */ +/* VIBCTLL/R (0x18/0x1A) fields */
-#define TWL6040_VIBENAL 0x01 -#define TWL6040_VIBCTRLL 0x04 -#define TWL6040_VIBCTRLLP 0x08 -#define TWL6040_VIBCTRLLN 0x10 +#define TWL6040_VIBENA (1 << 0) +#define TWL6040_VIBSEL (1 << 1) +#define TWL6040_VIBCTRL (1 << 2) +#define TWL6040_VIBCTRL_P (1 << 3) +#define TWL6040_VIBCTRL_N (1 << 4)
-/* VIBDATL (0x19) fields */ +/* VIBDATL/R (0x19/0x1B) fields */
#define TWL6040_VIBDAT_MAX 0x64
-/* VIBCTLR (0x1A) fields */ - -#define TWL6040_VIBENAR 0x01 -#define TWL6040_VIBCTRLR 0x04 -#define TWL6040_VIBCTRLRP 0x08 -#define TWL6040_VIBCTRLRN 0x10 - /* GPOCTL (0x1E) fields */
#define TWL6040_GPO1 0x01
The vibra control register will be used from the ASoC codec driver as well. In order to avoid latency issues caused by I2C read access, cache the two control register within the core driver, so we do not need to reach out to the chip to read it back.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@ti.com --- drivers/mfd/twl6040-core.c | 19 +++++++++++++++---- include/linux/mfd/twl6040.h | 1 + 2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/drivers/mfd/twl6040-core.c b/drivers/mfd/twl6040-core.c index 7dc8c47..75987c8 100644 --- a/drivers/mfd/twl6040-core.c +++ b/drivers/mfd/twl6040-core.c @@ -34,16 +34,24 @@ #include <linux/mfd/core.h> #include <linux/mfd/twl6040.h>
+#define VIBRACTRL_MEMBER(reg) ((reg == TWL6040_REG_VIBCTLL) ? 0 : 1) + int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg) { int ret; u8 val = 0;
mutex_lock(&twl6040->io_mutex); - ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg); - if (ret < 0) { - mutex_unlock(&twl6040->io_mutex); - return ret; + /* Vibra control registers from cache */ + if (unlikely(reg == TWL6040_REG_VIBCTLL || + reg == TWL6040_REG_VIBCTLR)) { + val = twl6040->vibra_ctrl_cache[VIBRACTRL_MEMBER(reg)]; + } else { + ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg); + if (ret < 0) { + mutex_unlock(&twl6040->io_mutex); + return ret; + } } mutex_unlock(&twl6040->io_mutex);
@@ -57,6 +65,9 @@ int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val)
mutex_lock(&twl6040->io_mutex); ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg); + /* Cache the vibra control registers */ + if (reg == TWL6040_REG_VIBCTLL || reg == TWL6040_REG_VIBCTLR) + twl6040->vibra_ctrl_cache[VIBRACTRL_MEMBER(reg)] = val; mutex_unlock(&twl6040->io_mutex);
return ret; diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index e6c755d..2f8585a 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -184,6 +184,7 @@ struct twl6040 { int audpwron; int power_count; int rev; + u8 vibra_ctrl_cache[2];
int pll; unsigned int sysclk;
On Fri, Sep 23, 2011 at 09:49:40AM +0300, Peter Ujfalusi wrote:
The vibra control register will be used from the ASoC codec driver as well. In order to avoid latency issues caused by I2C read access, cache the two control register within the core driver, so we do not need to reach out to the chip to read it back.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@ti.com
It strikes me that it might be useful to just convert the driver to regmap and get cache for everything...
Anyway, could you please restructure this series to minimize the dependency on the input subsystem? Then we can get the rest of it applied.
On Tue, Oct 11, 2011 at 03:48:24PM +0100, Mark Brown wrote:
On Fri, Sep 23, 2011 at 09:49:40AM +0300, Peter Ujfalusi wrote:
The vibra control register will be used from the ASoC codec driver as well. In order to avoid latency issues caused by I2C read access, cache the two control register within the core driver, so we do not need to reach out to the chip to read it back.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@ti.com
It strikes me that it might be useful to just convert the driver to regmap and get cache for everything...
Anyway, could you please restructure this series to minimize the dependency on the input subsystem? Then we can get the rest of it applied.
I'm OK with all patches going through MFD - I do not believe it will cause any merge issues.
For the record I still do not like returning EBUSY when device is not available - I think if device can't serve any requests it should not exists at all.
On Tuesday 11 October 2011 09:01:42 Dmitry Torokhov wrote:
I'm OK with all patches going through MFD - I do not believe it will cause any merge issues.
Thanks Dmitry.
For the record I still do not like returning EBUSY when device is not available - I think if device can't serve any requests it should not exists at all.
While I tend to agree with this, in embedded systems this might (not certainly) cause issues. Embedded SW (middleware, high level) tends to take the underlaying HW as static. Before I can do the dynamic input device creation/destruction I need to make sure that the userspace is ready for this. I'm planning to address your concern regarding to twl6040, and at the same time I'm going to change the twl4030 vibra/MFD/audio driver to behave in a same way.
-- Péter
On Tuesday 11 October 2011 15:48:24 Mark Brown wrote:
It strikes me that it might be useful to just convert the driver to regmap and get cache for everything...
Good idea, I'll take a look at regmap
Anyway, could you please restructure this series to minimize the dependency on the input subsystem? Then we can get the rest of it applied.
Will send the v3 series soon.
-- Péter
If the client only interested, if any of the vibra channels enabled, or if any of the channels are set to receive audio data via PDM.
This function targets mainly the vibra driver, so it can check if it is allowed to execute effects ot not.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@ti.com --- drivers/mfd/twl6040-core.c | 12 ++++++++++++ include/linux/mfd/twl6040.h | 3 +++ 2 files changed, 15 insertions(+), 0 deletions(-)
diff --git a/drivers/mfd/twl6040-core.c b/drivers/mfd/twl6040-core.c index 75987c8..268f80f 100644 --- a/drivers/mfd/twl6040-core.c +++ b/drivers/mfd/twl6040-core.c @@ -444,6 +444,18 @@ unsigned int twl6040_get_sysclk(struct twl6040 *twl6040) } EXPORT_SYMBOL(twl6040_get_sysclk);
+/* Get the combined status of the vibra control register */ +int twl6040_get_vibralr_status(struct twl6040 *twl6040) +{ + u8 status; + + status = twl6040->vibra_ctrl_cache[0] | twl6040->vibra_ctrl_cache[1]; + status &= (TWL6040_VIBENA | TWL6040_VIBSEL); + + return status; +} +EXPORT_SYMBOL(twl6040_get_vibralr_status); + static struct resource twl6040_vibra_rsrc[] = { { .flags = IORESOURCE_IRQ, diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index 2f8585a..87a4778 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -209,10 +209,13 @@ int twl6040_get_pll(struct twl6040 *twl6040); unsigned int twl6040_get_sysclk(struct twl6040 *twl6040); int twl6040_irq_init(struct twl6040 *twl6040); void twl6040_irq_exit(struct twl6040 *twl6040); +/* Get the combined status of the vibra control register */ +int twl6040_get_vibralr_status(struct twl6040 *twl6040);
static inline int twl6040_get_revid(struct twl6040 *twl6040) { return twl6040->rev; }
+ #endif /* End of __TWL6040_CODEC_H__ */
The VIBSELL/R bit in the VIBCTLL/R register tells the source of the data, which is going to be used to drive the attached motor(s). Do not allow effect execution if any of the channels are set to receive audio data.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@ti.com --- drivers/input/misc/twl6040-vibra.c | 7 +++++++ 1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c index cb74185..2a828e5 100644 --- a/drivers/input/misc/twl6040-vibra.c +++ b/drivers/input/misc/twl6040-vibra.c @@ -201,6 +201,13 @@ static int vibra_play(struct input_dev *input, void *data, struct vibra_info *info = input_get_drvdata(input); int ret;
+ /* Do not allow effect, while the routing is set to use audio */ + ret = twl6040_get_vibralr_status(info->twl6040); + if (ret & TWL6040_VIBSEL) { + dev_info(&input->dev, "Vibra is configured for audio\n"); + return -EBUSY; + } + info->weak_speed = effect->u.rumble.weak_magnitude; info->strong_speed = effect->u.rumble.strong_magnitude; info->direction = effect->direction < EFFECT_DIR_180_DEG ? 1 : -1;
OMAP4 McPDM supports 5 downlink (playback), and 3 uplink (capture) channels.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@ti.com --- sound/soc/omap/omap-mcpdm.c | 14 ++++++++------ 1 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/sound/soc/omap/omap-mcpdm.c b/sound/soc/omap/omap-mcpdm.c index 159a5e9..2c9fa51 100644 --- a/sound/soc/omap/omap-mcpdm.c +++ b/sound/soc/omap/omap-mcpdm.c @@ -299,15 +299,17 @@ static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream,
channels = params_channels(params); switch (channels) { + case 5: + if (stream == SNDRV_PCM_STREAM_CAPTURE) + /* up to 3 channels for capture */ + return -EINVAL; + link_mask |= 1 << 4; case 4: if (stream == SNDRV_PCM_STREAM_CAPTURE) - /* up to 2 channels for capture */ + /* up to 3 channels for capture */ return -EINVAL; link_mask |= 1 << 3; case 3: - if (stream == SNDRV_PCM_STREAM_CAPTURE) - /* up to 2 channels for capture */ - return -EINVAL; link_mask |= 1 << 2; case 2: link_mask |= 1 << 1; @@ -403,13 +405,13 @@ static struct snd_soc_dai_driver omap_mcpdm_dai = { .remove_order = SND_SOC_COMP_ORDER_EARLY, .playback = { .channels_min = 1, - .channels_max = 4, + .channels_max = 5, .rates = OMAP_MCPDM_RATES, .formats = OMAP_MCPDM_FORMATS, }, .capture = { .channels_min = 1, - .channels_max = 2, + .channels_max = 3, .rates = OMAP_MCPDM_RATES, .formats = OMAP_MCPDM_FORMATS, },
twl6040 have two vibra output drivers. They can be operated with audio stream coming through the PDM interface (fifth channel). The vibra outputs can be controlled via the input/FF driver as well. Selection between the two mode is implemented within the codec driver, the input/FF driver can only operate if the routing is set to "Input FF". Changing from "Input FF" to "Audio PDM" mode is protected as well: The switchin can only be done, if there is no running effect from the input/FF.
Signed-off-by: Peter Ujfalusi peter.ujfalusi@ti.com --- sound/soc/codecs/twl6040.c | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 72 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 68e52c9..e767cf1 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -903,6 +903,23 @@ static int twl6040_get_volsw_2r(struct snd_kcontrol *kcontrol, {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ .rshift = xshift, .max = xmax, .invert = xinvert}, }
+static int twl6040_soc_dapm_put_vibra_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val; + + /* Do not allow changes while Input/FF efect is running */ + val = twl6040_read_reg_volatile(codec, e->reg); + if (val & TWL6040_VIBENA && !(val & TWL6040_VIBSEL)) + return -EBUSY; + + return snd_soc_dapm_put_enum_double(kcontrol, ucontrol); +} + /* * MICATT volume control: * from -6 to 0 dB in 6 dB steps @@ -974,6 +991,19 @@ static const struct soc_enum twl6040_hf_enum[] = { twl6040_hf_texts), };
+static const char *twl6040_vibrapath_texts[] = { + "Input FF", "Audio PDM" +}; + +static const struct soc_enum twl6040_vibra_enum[] = { + SOC_ENUM_SINGLE(TWL6040_REG_VIBCTLL, 1, + ARRAY_SIZE(twl6040_vibrapath_texts), + twl6040_vibrapath_texts), + SOC_ENUM_SINGLE(TWL6040_REG_VIBCTLR, 1, + ARRAY_SIZE(twl6040_vibrapath_texts), + twl6040_vibrapath_texts), +}; + static const struct snd_kcontrol_new amicl_control = SOC_DAPM_ENUM("Route", twl6040_enum[0]);
@@ -1003,6 +1033,17 @@ static const struct snd_kcontrol_new auxl_switch_control = static const struct snd_kcontrol_new auxr_switch_control = SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 6, 1, 0);
+/* Vibra playback switches */ +static const struct snd_kcontrol_new vibral_mux_controls = + SOC_DAPM_ENUM_EXT("Route", twl6040_vibra_enum[0], + snd_soc_dapm_get_enum_double, + twl6040_soc_dapm_put_vibra_enum); + +static const struct snd_kcontrol_new vibrar_mux_controls = + SOC_DAPM_ENUM_EXT("Route", twl6040_vibra_enum[1], + snd_soc_dapm_get_enum_double, + twl6040_soc_dapm_put_vibra_enum); + /* Headset power mode */ static const char *twl6040_power_mode_texts[] = { "Low-Power", "High-Perfomance", @@ -1113,6 +1154,8 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("EP"), SND_SOC_DAPM_OUTPUT("AUXL"), SND_SOC_DAPM_OUTPUT("AUXR"), + SND_SOC_DAPM_OUTPUT("VIBRAL"), + SND_SOC_DAPM_OUTPUT("VIBRAR"),
/* Analog input muxes for the capture amplifiers */ SND_SOC_DAPM_MUX("Analog Left Capture Route", @@ -1165,6 +1208,9 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { TWL6040_REG_HFRCTL, 0, 0, twl6040_power_mode_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + /* Virtual DAC for vibra path (DL4 channel) */ + SND_SOC_DAPM_DAC("VIBRA DAC", "Vibra Playback", + SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MUX("Handsfree Left Playback", SND_SOC_NOPM, 0, 0, &hfl_mux_controls), @@ -1176,6 +1222,11 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { SND_SOC_DAPM_MUX("Headset Right Playback", SND_SOC_NOPM, 0, 0, &hsr_mux_controls),
+ SND_SOC_DAPM_MUX("Vibra Left Playback", SND_SOC_NOPM, 0, 0, + &vibral_mux_controls), + SND_SOC_DAPM_MUX("Vibra Right Playback", SND_SOC_NOPM, 0, 0, + &vibrar_mux_controls), + SND_SOC_DAPM_SWITCH("Earphone Playback", SND_SOC_NOPM, 0, 0, &ep_path_enable_control), SND_SOC_DAPM_SWITCH("AUXL Playback", SND_SOC_NOPM, 0, 0, @@ -1204,6 +1255,15 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { TWL6040_REG_EARCTL, 0, 0, NULL, 0, twl6040_power_mode_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV("Vibra Left Driver", + TWL6040_REG_VIBCTLL, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Vibra Right Driver", + TWL6040_REG_VIBCTLR, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Vibra Left Control", TWL6040_REG_VIBCTLL, 2, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("Vibra Right Control", TWL6040_REG_VIBCTLR, 2, 0, + NULL, 0),
/* Analog playback PGAs */ SND_SOC_DAPM_PGA("HF Left PGA", @@ -1270,6 +1330,18 @@ static const struct snd_soc_dapm_route intercon[] = {
{"AUXL", NULL, "AUXL Playback"}, {"AUXR", NULL, "AUXR Playback"}, + + /* Vibrator paths */ + {"Vibra Left Playback", "Audio PDM", "VIBRA DAC"}, + {"Vibra Right Playback", "Audio PDM", "VIBRA DAC"}, + + {"Vibra Left Driver", NULL, "Vibra Left Playback"}, + {"Vibra Right Driver", NULL, "Vibra Right Playback"}, + {"Vibra Left Driver", NULL, "Vibra Left Control"}, + {"Vibra Right Driver", NULL, "Vibra Right Control"}, + + {"VIBRAL", NULL, "Vibra Left Driver"}, + {"VIBRAR", NULL, "Vibra Right Driver"}, };
static int twl6040_add_widgets(struct snd_soc_codec *codec)
Hi Samuel, Dmitry,
On Fri, Sep 23, 2011 at 9:49 AM, Peter Ujfalusi peter.ujfalusi@ti.com wrote:
Hello,
Changes since v1:
- Patches already taken by Mark has been removed
- Patch 4: Return -EBUSY instead of -EPERM
Would you be able to comment on this series? I need to send v3 since ASoC moved ahead, and this series no longer applies cleanly.
Thank you, Péter
participants (4)
-
Dmitry Torokhov
-
Mark Brown
-
Peter Ujfalusi
-
Ujfalusi, Peter