aboutsummaryrefslogtreecommitdiff
path: root/sound/soc/codecs/twl6040.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs/twl6040.c')
-rw-r--r--sound/soc/codecs/twl6040.c1455
1 files changed, 751 insertions, 704 deletions
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c
index 10f6e521451..0f6067f04e2 100644
--- a/sound/soc/codecs/twl6040.c
+++ b/sound/soc/codecs/twl6040.c
@@ -24,11 +24,9 @@
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
-#include <linux/i2c.h>
-#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
-#include <linux/i2c/twl.h>
+#include <linux/mfd/twl6040.h>
#include <sound/core.h>
#include <sound/pcm.h>
@@ -40,310 +38,184 @@
#include "twl6040.h"
-#define TWL6040_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
-#define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
+enum twl6040_dai_id {
+ TWL6040_DAI_LEGACY = 0,
+ TWL6040_DAI_UL,
+ TWL6040_DAI_DL1,
+ TWL6040_DAI_DL2,
+ TWL6040_DAI_VIB,
+};
+
+#define TWL6040_RATES SNDRV_PCM_RATE_8000_96000
+#define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
+
+#define TWL6040_OUTHS_0dB 0x00
+#define TWL6040_OUTHS_M30dB 0x0F
+#define TWL6040_OUTHF_0dB 0x03
+#define TWL6040_OUTHF_M52dB 0x1D
+
+#define TWL6040_CACHEREGNUM (TWL6040_REG_STATUS + 1)
+
+struct twl6040_jack_data {
+ struct snd_soc_jack *jack;
+ struct delayed_work work;
+ int report;
+};
/* codec private data */
struct twl6040_data {
- int audpwron;
- int naudint;
+ int plug_irq;
int codec_powered;
int pll;
- int non_lp;
+ int pll_power_mode;
+ int hs_power_mode;
+ int hs_power_mode_locked;
+ bool dl1_unmuted;
+ bool dl2_unmuted;
+ u8 dl12_cache[TWL6040_REG_HFRCTL - TWL6040_REG_HSLCTL + 1];
+ unsigned int clk_in;
unsigned int sysclk;
- struct snd_pcm_hw_constraint_list *sysclk_constraints;
- struct completion ready;
+ struct twl6040_jack_data hs_jack;
+ struct snd_soc_codec *codec;
+ struct mutex mutex;
};
-/*
- * twl6040 register cache & default register settings
- */
-static const u8 twl6040_reg[TWL6040_CACHEREGNUM] = {
- 0x00, /* not used 0x00 */
- 0x4B, /* TWL6040_ASICID (ro) 0x01 */
- 0x00, /* TWL6040_ASICREV (ro) 0x02 */
- 0x00, /* TWL6040_INTID 0x03 */
- 0x00, /* TWL6040_INTMR 0x04 */
- 0x00, /* TWL6040_NCPCTRL 0x05 */
- 0x00, /* TWL6040_LDOCTL 0x06 */
- 0x60, /* TWL6040_HPPLLCTL 0x07 */
- 0x00, /* TWL6040_LPPLLCTL 0x08 */
- 0x4A, /* TWL6040_LPPLLDIV 0x09 */
- 0x00, /* TWL6040_AMICBCTL 0x0A */
- 0x00, /* TWL6040_DMICBCTL 0x0B */
- 0x18, /* TWL6040_MICLCTL 0x0C - No input selected on Left Mic */
- 0x18, /* TWL6040_MICRCTL 0x0D - No input selected on Right Mic */
- 0x00, /* TWL6040_MICGAIN 0x0E */
- 0x1B, /* TWL6040_LINEGAIN 0x0F */
- 0x00, /* TWL6040_HSLCTL 0x10 */
- 0x00, /* TWL6040_HSRCTL 0x11 */
- 0x00, /* TWL6040_HSGAIN 0x12 */
- 0x00, /* TWL6040_EARCTL 0x13 */
- 0x00, /* TWL6040_HFLCTL 0x14 */
- 0x00, /* TWL6040_HFLGAIN 0x15 */
- 0x00, /* TWL6040_HFRCTL 0x16 */
- 0x00, /* TWL6040_HFRGAIN 0x17 */
- 0x00, /* TWL6040_VIBCTLL 0x18 */
- 0x00, /* TWL6040_VIBDATL 0x19 */
- 0x00, /* TWL6040_VIBCTLR 0x1A */
- 0x00, /* TWL6040_VIBDATR 0x1B */
- 0x00, /* TWL6040_HKCTL1 0x1C */
- 0x00, /* TWL6040_HKCTL2 0x1D */
- 0x00, /* TWL6040_GPOCTL 0x1E */
- 0x00, /* TWL6040_ALB 0x1F */
- 0x00, /* TWL6040_DLB 0x20 */
- 0x00, /* not used 0x21 */
- 0x00, /* not used 0x22 */
- 0x00, /* not used 0x23 */
- 0x00, /* not used 0x24 */
- 0x00, /* not used 0x25 */
- 0x00, /* not used 0x26 */
- 0x00, /* not used 0x27 */
- 0x00, /* TWL6040_TRIM1 0x28 */
- 0x00, /* TWL6040_TRIM2 0x29 */
- 0x00, /* TWL6040_TRIM3 0x2A */
- 0x00, /* TWL6040_HSOTRIM 0x2B */
- 0x00, /* TWL6040_HFOTRIM 0x2C */
- 0x09, /* TWL6040_ACCCTL 0x2D */
- 0x00, /* TWL6040_STATUS (ro) 0x2E */
+/* set of rates for each pll: low-power and high-performance */
+static const unsigned int lp_rates[] = {
+ 8000,
+ 11250,
+ 16000,
+ 22500,
+ 32000,
+ 44100,
+ 48000,
+ 88200,
+ 96000,
};
-/*
- * twl6040 vio/gnd registers:
- * registers under vio/gnd supply can be accessed
- * before the power-up sequence, after NRESPWRON goes high
- */
-static const int twl6040_vio_reg[TWL6040_VIOREGNUM] = {
- TWL6040_REG_ASICID,
- TWL6040_REG_ASICREV,
- TWL6040_REG_INTID,
- TWL6040_REG_INTMR,
- TWL6040_REG_NCPCTL,
- TWL6040_REG_LDOCTL,
- TWL6040_REG_AMICBCTL,
- TWL6040_REG_DMICBCTL,
- TWL6040_REG_HKCTL1,
- TWL6040_REG_HKCTL2,
- TWL6040_REG_GPOCTL,
- TWL6040_REG_TRIM1,
- TWL6040_REG_TRIM2,
- TWL6040_REG_TRIM3,
- TWL6040_REG_HSOTRIM,
- TWL6040_REG_HFOTRIM,
- TWL6040_REG_ACCCTL,
- TWL6040_REG_STATUS,
+static const unsigned int hp_rates[] = {
+ 8000,
+ 16000,
+ 32000,
+ 48000,
+ 96000,
};
-/*
- * twl6040 vdd/vss registers:
- * registers under vdd/vss supplies can only be accessed
- * after the power-up sequence
- */
-static const int twl6040_vdd_reg[TWL6040_VDDREGNUM] = {
- TWL6040_REG_HPPLLCTL,
- TWL6040_REG_LPPLLCTL,
- TWL6040_REG_LPPLLDIV,
- TWL6040_REG_MICLCTL,
- TWL6040_REG_MICRCTL,
- TWL6040_REG_MICGAIN,
- TWL6040_REG_LINEGAIN,
- TWL6040_REG_HSLCTL,
- TWL6040_REG_HSRCTL,
- TWL6040_REG_HSGAIN,
- TWL6040_REG_EARCTL,
- TWL6040_REG_HFLCTL,
- TWL6040_REG_HFLGAIN,
- TWL6040_REG_HFRCTL,
- TWL6040_REG_HFRGAIN,
- TWL6040_REG_VIBCTLL,
- TWL6040_REG_VIBDATL,
- TWL6040_REG_VIBCTLR,
- TWL6040_REG_VIBDATR,
- TWL6040_REG_ALB,
- TWL6040_REG_DLB,
+static const struct snd_pcm_hw_constraint_list sysclk_constraints[] = {
+ { .count = ARRAY_SIZE(lp_rates), .list = lp_rates, },
+ { .count = ARRAY_SIZE(hp_rates), .list = hp_rates, },
};
-/*
- * read twl6040 register cache
- */
-static inline unsigned int twl6040_read_reg_cache(struct snd_soc_codec *codec,
- unsigned int reg)
+static unsigned int twl6040_read(struct snd_soc_codec *codec, unsigned int reg)
{
- u8 *cache = codec->reg_cache;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040 *twl6040 = codec->control_data;
+ u8 value;
if (reg >= TWL6040_CACHEREGNUM)
return -EIO;
- return cache[reg];
+ switch (reg) {
+ case TWL6040_REG_HSLCTL:
+ case TWL6040_REG_HSRCTL:
+ case TWL6040_REG_EARCTL:
+ case TWL6040_REG_HFLCTL:
+ case TWL6040_REG_HFRCTL:
+ value = priv->dl12_cache[reg - TWL6040_REG_HSLCTL];
+ break;
+ default:
+ value = twl6040_reg_read(twl6040, reg);
+ break;
+ }
+
+ return value;
}
-/*
- * write twl6040 register cache
- */
-static inline void twl6040_write_reg_cache(struct snd_soc_codec *codec,
- u8 reg, u8 value)
+static bool twl6040_can_write_to_chip(struct snd_soc_codec *codec,
+ unsigned int reg)
{
- u8 *cache = codec->reg_cache;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- if (reg >= TWL6040_CACHEREGNUM)
- return;
- cache[reg] = value;
+ switch (reg) {
+ case TWL6040_REG_HSLCTL:
+ case TWL6040_REG_HSRCTL:
+ case TWL6040_REG_EARCTL:
+ /* DL1 path */
+ return priv->dl1_unmuted;
+ case TWL6040_REG_HFLCTL:
+ case TWL6040_REG_HFRCTL:
+ return priv->dl2_unmuted;
+ default:
+ return 1;
+ }
}
-/*
- * read from twl6040 hardware register
- */
-static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
- unsigned int reg)
+static inline void twl6040_update_dl12_cache(struct snd_soc_codec *codec,
+ u8 reg, u8 value)
{
- u8 value;
-
- if (reg >= TWL6040_CACHEREGNUM)
- return -EIO;
-
- twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &value, reg);
- twl6040_write_reg_cache(codec, reg, value);
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- return value;
+ switch (reg) {
+ case TWL6040_REG_HSLCTL:
+ case TWL6040_REG_HSRCTL:
+ case TWL6040_REG_EARCTL:
+ case TWL6040_REG_HFLCTL:
+ case TWL6040_REG_HFRCTL:
+ priv->dl12_cache[reg - TWL6040_REG_HSLCTL] = value;
+ break;
+ default:
+ break;
+ }
}
-/*
- * write to the twl6040 register space
- */
static int twl6040_write(struct snd_soc_codec *codec,
unsigned int reg, unsigned int value)
{
+ struct twl6040 *twl6040 = codec->control_data;
+
if (reg >= TWL6040_CACHEREGNUM)
return -EIO;
- twl6040_write_reg_cache(codec, reg, value);
- return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg);
-}
-
-static void twl6040_init_vio_regs(struct snd_soc_codec *codec)
-{
- u8 *cache = codec->reg_cache;
- int reg, i;
-
- /* allow registers to be accessed by i2c */
- twl6040_write(codec, TWL6040_REG_ACCCTL, cache[TWL6040_REG_ACCCTL]);
-
- for (i = 0; i < TWL6040_VIOREGNUM; i++) {
- reg = twl6040_vio_reg[i];
- /* skip read-only registers (ASICID, ASICREV, STATUS) */
- switch (reg) {
- case TWL6040_REG_ASICID:
- case TWL6040_REG_ASICREV:
- case TWL6040_REG_STATUS:
- continue;
- default:
- break;
- }
- twl6040_write(codec, reg, cache[reg]);
- }
+ twl6040_update_dl12_cache(codec, reg, value);
+ if (twl6040_can_write_to_chip(codec, reg))
+ return twl6040_reg_write(twl6040, reg, value);
+ else
+ return 0;
}
-static void twl6040_init_vdd_regs(struct snd_soc_codec *codec)
+static void twl6040_init_chip(struct snd_soc_codec *codec)
{
- u8 *cache = codec->reg_cache;
- int reg, i;
-
- for (i = 0; i < TWL6040_VDDREGNUM; i++) {
- reg = twl6040_vdd_reg[i];
- twl6040_write(codec, reg, cache[reg]);
- }
-}
+ twl6040_read(codec, TWL6040_REG_TRIM1);
+ twl6040_read(codec, TWL6040_REG_TRIM2);
+ twl6040_read(codec, TWL6040_REG_TRIM3);
+ twl6040_read(codec, TWL6040_REG_HSOTRIM);
+ twl6040_read(codec, TWL6040_REG_HFOTRIM);
-/* twl6040 codec manual power-up sequence */
-static void twl6040_power_up(struct snd_soc_codec *codec)
-{
- u8 ncpctl, ldoctl, lppllctl, accctl;
-
- ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL);
- ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL);
- lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
- accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL);
-
- /* enable reference system */
- ldoctl |= TWL6040_REFENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
- msleep(10);
- /* enable internal oscillator */
- ldoctl |= TWL6040_OSCENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
- udelay(10);
- /* enable high-side ldo */
- ldoctl |= TWL6040_HSLDOENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
- udelay(244);
- /* enable negative charge pump */
- ncpctl |= TWL6040_NCPENA | TWL6040_NCPOPEN;
- twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl);
- udelay(488);
- /* enable low-side ldo */
- ldoctl |= TWL6040_LSLDOENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
- udelay(244);
- /* enable low-power pll */
- lppllctl |= TWL6040_LPLLENA;
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
- /* reset state machine */
- accctl |= TWL6040_RESETSPLIT;
- twl6040_write(codec, TWL6040_REG_ACCCTL, accctl);
- mdelay(5);
- accctl &= ~TWL6040_RESETSPLIT;
- twl6040_write(codec, TWL6040_REG_ACCCTL, accctl);
- /* disable internal oscillator */
- ldoctl &= ~TWL6040_OSCENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
-}
+ /* Change chip defaults */
+ /* No imput selected for microphone amplifiers */
+ twl6040_write(codec, TWL6040_REG_MICLCTL, 0x18);
+ twl6040_write(codec, TWL6040_REG_MICRCTL, 0x18);
-/* twl6040 codec manual power-down sequence */
-static void twl6040_power_down(struct snd_soc_codec *codec)
-{
- u8 ncpctl, ldoctl, lppllctl, accctl;
-
- ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL);
- ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL);
- lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
- accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL);
-
- /* enable internal oscillator */
- ldoctl |= TWL6040_OSCENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
- udelay(10);
- /* disable low-power pll */
- lppllctl &= ~TWL6040_LPLLENA;
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
- /* disable low-side ldo */
- ldoctl &= ~TWL6040_LSLDOENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
- udelay(244);
- /* disable negative charge pump */
- ncpctl &= ~(TWL6040_NCPENA | TWL6040_NCPOPEN);
- twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl);
- udelay(488);
- /* disable high-side ldo */
- ldoctl &= ~TWL6040_HSLDOENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
- udelay(244);
- /* disable internal oscillator */
- ldoctl &= ~TWL6040_OSCENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
- /* disable reference system */
- ldoctl &= ~TWL6040_REFENA;
- twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
- msleep(10);
+ /*
+ * We need to lower the default gain values, so the ramp code
+ * can work correctly for the first playback.
+ * This reduces the pop noise heard at the first playback.
+ */
+ twl6040_write(codec, TWL6040_REG_HSGAIN, 0xff);
+ twl6040_write(codec, TWL6040_REG_EARCTL, 0x1e);
+ twl6040_write(codec, TWL6040_REG_HFLGAIN, 0x1d);
+ twl6040_write(codec, TWL6040_REG_HFRGAIN, 0x1d);
+ twl6040_write(codec, TWL6040_REG_LINEGAIN, 0);
}
/* set headset dac and driver power mode */
static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
{
int hslctl, hsrctl;
- int mask = TWL6040_HSDRVMODEL | TWL6040_HSDACMODEL;
+ int mask = TWL6040_HSDRVMODE | TWL6040_HSDACMODE;
- hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL);
- hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL);
+ hslctl = twl6040_read(codec, TWL6040_REG_HSLCTL);
+ hsrctl = twl6040_read(codec, TWL6040_REG_HSRCTL);
if (high_perf) {
hslctl &= ~mask;
@@ -362,60 +234,119 @@ static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
static int twl6040_hs_dac_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
+ struct snd_soc_codec *codec = w->codec;
+ u8 hslctl, hsrctl;
+
+ /*
+ * Workaround for Headset DC offset caused pop noise:
+ * Both HS DAC need to be turned on (before the HS driver) and off at
+ * the same time.
+ */
+ hslctl = twl6040_read(codec, TWL6040_REG_HSLCTL);
+ hsrctl = twl6040_read(codec, TWL6040_REG_HSRCTL);
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ hslctl |= TWL6040_HSDACENA;
+ hsrctl |= TWL6040_HSDACENA;
+ } else {
+ hslctl &= ~TWL6040_HSDACENA;
+ hsrctl &= ~TWL6040_HSDACENA;
+ }
+ twl6040_write(codec, TWL6040_REG_HSLCTL, hslctl);
+ twl6040_write(codec, TWL6040_REG_HSRCTL, hsrctl);
+
msleep(1);
return 0;
}
-static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w,
+static int twl6040_ep_drv_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = w->codec;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
- if (SND_SOC_DAPM_EVENT_ON(event))
- priv->non_lp++;
- else
- priv->non_lp--;
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ /* Earphone doesn't support low power mode */
+ priv->hs_power_mode_locked = 1;
+ ret = headset_power_mode(codec, 1);
+ } else {
+ priv->hs_power_mode_locked = 0;
+ ret = headset_power_mode(codec, priv->hs_power_mode);
+ }
msleep(1);
- return 0;
+ return ret;
+}
+
+static void twl6040_hs_jack_report(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack, int report)
+{
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ int status;
+
+ mutex_lock(&priv->mutex);
+
+ /* Sync status */
+ status = twl6040_read(codec, TWL6040_REG_STATUS);
+ if (status & TWL6040_PLUGCOMP)
+ snd_soc_jack_report(jack, report, report);
+ else
+ snd_soc_jack_report(jack, 0, report);
+
+ mutex_unlock(&priv->mutex);
+}
+
+void twl6040_hs_jack_detect(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack, int report)
+{
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ struct twl6040_jack_data *hs_jack = &priv->hs_jack;
+
+ hs_jack->jack = jack;
+ hs_jack->report = report;
+
+ twl6040_hs_jack_report(codec, hs_jack->jack, hs_jack->report);
+}
+EXPORT_SYMBOL_GPL(twl6040_hs_jack_detect);
+
+static void twl6040_accessory_work(struct work_struct *work)
+{
+ struct twl6040_data *priv = container_of(work,
+ struct twl6040_data, hs_jack.work.work);
+ struct snd_soc_codec *codec = priv->codec;
+ struct twl6040_jack_data *hs_jack = &priv->hs_jack;
+
+ twl6040_hs_jack_report(codec, hs_jack->jack, hs_jack->report);
}
/* audio interrupt handler */
-static irqreturn_t twl6040_naudint_handler(int irq, void *data)
+static irqreturn_t twl6040_audio_handler(int irq, void *data)
{
struct snd_soc_codec *codec = data;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- u8 intid;
- twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID);
-
- switch (intid) {
- case TWL6040_THINT:
- dev_alert(codec->dev, "die temp over-limit detection\n");
- break;
- case TWL6040_PLUGINT:
- case TWL6040_UNPLUGINT:
- case TWL6040_HOOKINT:
- break;
- case TWL6040_HFINT:
- dev_alert(codec->dev, "hf drivers over current detection\n");
- break;
- case TWL6040_VIBINT:
- dev_alert(codec->dev, "vib drivers over current detection\n");
- break;
- case TWL6040_READYINT:
- complete(&priv->ready);
- break;
- default:
- dev_err(codec->dev, "unknown audio interrupt %d\n", intid);
- break;
- }
+ queue_delayed_work(system_power_efficient_wq,
+ &priv->hs_jack.work, msecs_to_jiffies(200));
return IRQ_HANDLED;
}
+static int twl6040_soc_dapm_put_vibra_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol);
+ 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(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
@@ -429,6 +360,12 @@ static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0);
static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0);
/*
+ * AFMGAIN volume control:
+ * from -18 to 24 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(afm_amp_tlv, -1800, 600, 0);
+
+/*
* HSGAIN volume control:
* from -30 to 0 dB in 2 dB steps
*/
@@ -455,8 +392,45 @@ static const char *twl6040_amicr_texts[] =
{"Headset Mic", "Sub Mic", "Aux/FM Right", "Off"};
static const struct soc_enum twl6040_enum[] = {
- SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3, 3, twl6040_amicl_texts),
- SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, 3, twl6040_amicr_texts),
+ SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3,
+ ARRAY_SIZE(twl6040_amicl_texts), twl6040_amicl_texts),
+ SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3,
+ ARRAY_SIZE(twl6040_amicr_texts), twl6040_amicr_texts),
+};
+
+static const char *twl6040_hs_texts[] = {
+ "Off", "HS DAC", "Line-In amp"
+};
+
+static const struct soc_enum twl6040_hs_enum[] = {
+ SOC_ENUM_SINGLE(TWL6040_REG_HSLCTL, 5, ARRAY_SIZE(twl6040_hs_texts),
+ twl6040_hs_texts),
+ SOC_ENUM_SINGLE(TWL6040_REG_HSRCTL, 5, ARRAY_SIZE(twl6040_hs_texts),
+ twl6040_hs_texts),
+};
+
+static const char *twl6040_hf_texts[] = {
+ "Off", "HF DAC", "Line-In amp"
+};
+
+static const struct soc_enum twl6040_hf_enum[] = {
+ SOC_ENUM_SINGLE(TWL6040_REG_HFLCTL, 2, ARRAY_SIZE(twl6040_hf_texts),
+ twl6040_hf_texts),
+ SOC_ENUM_SINGLE(TWL6040_REG_HFRCTL, 2, ARRAY_SIZE(twl6040_hf_texts),
+ 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 =
@@ -466,21 +440,148 @@ static const struct snd_kcontrol_new amicr_control =
SOC_DAPM_ENUM("Route", twl6040_enum[1]);
/* Headset DAC playback switches */
-static const struct snd_kcontrol_new hsdacl_switch_controls =
- SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSLCTL, 5, 1, 0);
+static const struct snd_kcontrol_new hsl_mux_controls =
+ SOC_DAPM_ENUM("Route", twl6040_hs_enum[0]);
-static const struct snd_kcontrol_new hsdacr_switch_controls =
- SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSRCTL, 5, 1, 0);
+static const struct snd_kcontrol_new hsr_mux_controls =
+ SOC_DAPM_ENUM("Route", twl6040_hs_enum[1]);
/* Handsfree DAC playback switches */
-static const struct snd_kcontrol_new hfdacl_switch_controls =
- SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 2, 1, 0);
+static const struct snd_kcontrol_new hfl_mux_controls =
+ SOC_DAPM_ENUM("Route", twl6040_hf_enum[0]);
+
+static const struct snd_kcontrol_new hfr_mux_controls =
+ SOC_DAPM_ENUM("Route", twl6040_hf_enum[1]);
+
+static const struct snd_kcontrol_new ep_path_enable_control =
+ SOC_DAPM_SINGLE_VIRT("Switch", 1);
+
+static const struct snd_kcontrol_new auxl_switch_control =
+ SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 6, 1, 0);
+
+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);
-static const struct snd_kcontrol_new hfdacr_switch_controls =
- SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 2, 1, 0);
+/* Headset power mode */
+static const char *twl6040_power_mode_texts[] = {
+ "Low-Power", "High-Performance",
+};
+
+static SOC_ENUM_SINGLE_EXT_DECL(twl6040_power_mode_enum,
+ twl6040_power_mode_texts);
+
+static int twl6040_headset_power_get_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = priv->hs_power_mode;
+
+ return 0;
+}
+
+static int twl6040_headset_power_put_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ int high_perf = ucontrol->value.enumerated.item[0];
+ int ret = 0;
+
+ if (!priv->hs_power_mode_locked)
+ ret = headset_power_mode(codec, high_perf);
+
+ if (!ret)
+ priv->hs_power_mode = high_perf;
+
+ return ret;
+}
+
+static int twl6040_pll_get_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = priv->pll_power_mode;
-static const struct snd_kcontrol_new ep_driver_switch_controls =
- SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0);
+ return 0;
+}
+
+static int twl6040_pll_put_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+ priv->pll_power_mode = ucontrol->value.enumerated.item[0];
+
+ return 0;
+}
+
+int twl6040_get_dl1_gain(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ if (snd_soc_dapm_get_pin_status(dapm, "EP"))
+ return -1; /* -1dB */
+
+ if (snd_soc_dapm_get_pin_status(dapm, "HSOR") ||
+ snd_soc_dapm_get_pin_status(dapm, "HSOL")) {
+
+ u8 val = snd_soc_read(codec, TWL6040_REG_HSLCTL);
+ if (val & TWL6040_HSDACMODE)
+ /* HSDACL in LP mode */
+ return -8; /* -8dB */
+ else
+ /* HSDACL in HP mode */
+ return -1; /* -1dB */
+ }
+ return 0; /* 0dB */
+}
+EXPORT_SYMBOL_GPL(twl6040_get_dl1_gain);
+
+int twl6040_get_clk_id(struct snd_soc_codec *codec)
+{
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+ return priv->pll_power_mode;
+}
+EXPORT_SYMBOL_GPL(twl6040_get_clk_id);
+
+int twl6040_get_trim_value(struct snd_soc_codec *codec, enum twl6040_trim trim)
+{
+ if (unlikely(trim >= TWL6040_TRIM_INVAL))
+ return -EINVAL;
+
+ return twl6040_read(codec, TWL6040_REG_TRIM1 + trim);
+}
+EXPORT_SYMBOL_GPL(twl6040_get_trim_value);
+
+int twl6040_get_hs_step_size(struct snd_soc_codec *codec)
+{
+ struct twl6040 *twl6040 = codec->control_data;
+
+ if (twl6040_get_revid(twl6040) < TWL6040_REV_ES1_3)
+ /* For ES under ES_1.3 HS step is 2 mV */
+ return 2;
+ else
+ /* For ES_1.3 HS step is 1 mV */
+ return 1;
+}
+EXPORT_SYMBOL_GPL(twl6040_get_hs_step_size);
static const struct snd_kcontrol_new twl6040_snd_controls[] = {
/* Capture gains */
@@ -489,6 +590,10 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = {
SOC_DOUBLE_TLV("Capture Volume",
TWL6040_REG_MICGAIN, 0, 3, 4, 0, mic_amp_tlv),
+ /* AFM gains */
+ SOC_DOUBLE_TLV("Aux FM Volume",
+ TWL6040_REG_LINEGAIN, 0, 3, 7, 0, afm_amp_tlv),
+
/* Playback gains */
SOC_DOUBLE_TLV("Headset Playback Volume",
TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv),
@@ -496,6 +601,13 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = {
TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv),
SOC_SINGLE_TLV("Earphone Playback Volume",
TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv),
+
+ SOC_ENUM_EXT("Headset Power Mode", twl6040_power_mode_enum,
+ twl6040_headset_power_get_enum,
+ twl6040_headset_power_put_enum),
+
+ SOC_ENUM_EXT("PLL Selection", twl6040_power_mode_enum,
+ twl6040_pll_get_enum, twl6040_pll_put_enum),
};
static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
@@ -512,6 +624,10 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("HFL"),
SND_SOC_DAPM_OUTPUT("HFR"),
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",
@@ -525,77 +641,111 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
SND_SOC_DAPM_PGA("MicAmpR",
TWL6040_REG_MICRCTL, 0, 0, NULL, 0),
+ /* Auxiliary FM PGAs */
+ SND_SOC_DAPM_PGA("AFMAmpL",
+ TWL6040_REG_MICLCTL, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("AFMAmpR",
+ TWL6040_REG_MICRCTL, 1, 0, NULL, 0),
+
/* ADCs */
- SND_SOC_DAPM_ADC("ADC Left", "Left Front Capture",
- TWL6040_REG_MICLCTL, 2, 0),
- SND_SOC_DAPM_ADC("ADC Right", "Right Front Capture",
- TWL6040_REG_MICRCTL, 2, 0),
+ SND_SOC_DAPM_ADC("ADC Left", NULL, TWL6040_REG_MICLCTL, 2, 0),
+ SND_SOC_DAPM_ADC("ADC Right", NULL, TWL6040_REG_MICRCTL, 2, 0),
/* Microphone bias */
- SND_SOC_DAPM_MICBIAS("Headset Mic Bias",
- TWL6040_REG_AMICBCTL, 0, 0),
- SND_SOC_DAPM_MICBIAS("Main Mic Bias",
- TWL6040_REG_AMICBCTL, 4, 0),
- SND_SOC_DAPM_MICBIAS("Digital Mic1 Bias",
- TWL6040_REG_DMICBCTL, 0, 0),
- SND_SOC_DAPM_MICBIAS("Digital Mic2 Bias",
- TWL6040_REG_DMICBCTL, 4, 0),
+ SND_SOC_DAPM_SUPPLY("Headset Mic Bias",
+ TWL6040_REG_AMICBCTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("Main Mic Bias",
+ TWL6040_REG_AMICBCTL, 4, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("Digital Mic1 Bias",
+ TWL6040_REG_DMICBCTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("Digital Mic2 Bias",
+ TWL6040_REG_DMICBCTL, 4, 0, NULL, 0),
/* DACs */
- SND_SOC_DAPM_DAC_E("HSDAC Left", "Headset Playback",
- TWL6040_REG_HSLCTL, 0, 0,
- twl6040_hs_dac_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
- SND_SOC_DAPM_DAC_E("HSDAC Right", "Headset Playback",
- TWL6040_REG_HSRCTL, 0, 0,
- twl6040_hs_dac_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
- SND_SOC_DAPM_DAC_E("HFDAC Left", "Handsfree Playback",
- TWL6040_REG_HFLCTL, 0, 0,
- twl6040_power_mode_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
- SND_SOC_DAPM_DAC_E("HFDAC Right", "Handsfree Playback",
- TWL6040_REG_HFRCTL, 0, 0,
- twl6040_power_mode_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
-
- /* Analog playback switches */
- SND_SOC_DAPM_SWITCH("HSDAC Left Playback",
- SND_SOC_NOPM, 0, 0, &hsdacl_switch_controls),
- SND_SOC_DAPM_SWITCH("HSDAC Right Playback",
- SND_SOC_NOPM, 0, 0, &hsdacr_switch_controls),
- SND_SOC_DAPM_SWITCH("HFDAC Left Playback",
- SND_SOC_NOPM, 0, 0, &hfdacl_switch_controls),
- SND_SOC_DAPM_SWITCH("HFDAC Right Playback",
- SND_SOC_NOPM, 0, 0, &hfdacr_switch_controls),
+ SND_SOC_DAPM_DAC("HSDAC Left", NULL, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC("HSDAC Right", NULL, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC("HFDAC Left", NULL, TWL6040_REG_HFLCTL, 0, 0),
+ SND_SOC_DAPM_DAC("HFDAC Right", NULL, TWL6040_REG_HFRCTL, 0, 0),
+ /* Virtual DAC for vibra path (DL4 channel) */
+ SND_SOC_DAPM_DAC("VIBRA DAC", NULL, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_MUX("Handsfree Left Playback",
+ SND_SOC_NOPM, 0, 0, &hfl_mux_controls),
+ SND_SOC_DAPM_MUX("Handsfree Right Playback",
+ SND_SOC_NOPM, 0, 0, &hfr_mux_controls),
+ /* Analog playback Muxes */
+ SND_SOC_DAPM_MUX("Headset Left Playback",
+ SND_SOC_NOPM, 0, 0, &hsl_mux_controls),
+ 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,
+ &auxl_switch_control),
+ SND_SOC_DAPM_SWITCH("AUXR Playback", SND_SOC_NOPM, 0, 0,
+ &auxr_switch_control),
/* Analog playback drivers */
- SND_SOC_DAPM_PGA_E("Handsfree Left Driver",
- TWL6040_REG_HFLCTL, 4, 0, NULL, 0,
- twl6040_power_mode_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
- SND_SOC_DAPM_PGA_E("Handsfree Right Driver",
- TWL6040_REG_HFRCTL, 4, 0, NULL, 0,
- twl6040_power_mode_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
- SND_SOC_DAPM_PGA("Headset Left Driver",
+ SND_SOC_DAPM_OUT_DRV("HF Left Driver",
+ TWL6040_REG_HFLCTL, 4, 0, NULL, 0),
+ SND_SOC_DAPM_OUT_DRV("HF Right Driver",
+ TWL6040_REG_HFRCTL, 4, 0, NULL, 0),
+ SND_SOC_DAPM_OUT_DRV("HS Left Driver",
TWL6040_REG_HSLCTL, 2, 0, NULL, 0),
- SND_SOC_DAPM_PGA("Headset Right Driver",
+ SND_SOC_DAPM_OUT_DRV("HS Right Driver",
TWL6040_REG_HSRCTL, 2, 0, NULL, 0),
- SND_SOC_DAPM_SWITCH_E("Earphone Driver",
- SND_SOC_NOPM, 0, 0, &ep_driver_switch_controls,
- twl6040_power_mode_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_OUT_DRV_E("Earphone Driver",
+ TWL6040_REG_EARCTL, 0, 0, NULL, 0,
+ twl6040_ep_drv_event,
+ SND_SOC_DAPM_PRE_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),
+ SND_SOC_DAPM_SUPPLY_S("HSDAC Power", 1, SND_SOC_NOPM, 0, 0,
+ twl6040_hs_dac_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
/* Analog playback PGAs */
- SND_SOC_DAPM_PGA("HFDAC Left PGA",
+ SND_SOC_DAPM_PGA("HF Left PGA",
TWL6040_REG_HFLCTL, 1, 0, NULL, 0),
- SND_SOC_DAPM_PGA("HFDAC Right PGA",
+ SND_SOC_DAPM_PGA("HF Right PGA",
TWL6040_REG_HFRCTL, 1, 0, NULL, 0),
};
static const struct snd_soc_dapm_route intercon[] = {
+ /* Stream -> DAC mapping */
+ {"HSDAC Left", NULL, "Legacy Playback"},
+ {"HSDAC Left", NULL, "Headset Playback"},
+ {"HSDAC Right", NULL, "Legacy Playback"},
+ {"HSDAC Right", NULL, "Headset Playback"},
+
+ {"HFDAC Left", NULL, "Legacy Playback"},
+ {"HFDAC Left", NULL, "Handsfree Playback"},
+ {"HFDAC Right", NULL, "Legacy Playback"},
+ {"HFDAC Right", NULL, "Handsfree Playback"},
+
+ {"VIBRA DAC", NULL, "Legacy Playback"},
+ {"VIBRA DAC", NULL, "Vibra Playback"},
+
+ /* ADC -> Stream mapping */
+ {"Legacy Capture" , NULL, "ADC Left"},
+ {"Capture", NULL, "ADC Left"},
+ {"Legacy Capture", NULL, "ADC Right"},
+ {"Capture" , NULL, "ADC Right"},
+
/* Capture path */
{"Analog Left Capture Route", "Headset Mic", "HSMIC"},
{"Analog Left Capture Route", "Main Mic", "MAINMIC"},
@@ -611,76 +761,69 @@ static const struct snd_soc_dapm_route intercon[] = {
{"ADC Left", NULL, "MicAmpL"},
{"ADC Right", NULL, "MicAmpR"},
- /* Headset playback path */
- {"HSDAC Left Playback", "Switch", "HSDAC Left"},
- {"HSDAC Right Playback", "Switch", "HSDAC Right"},
+ /* AFM path */
+ {"AFMAmpL", NULL, "AFML"},
+ {"AFMAmpR", NULL, "AFMR"},
+
+ {"HSDAC Left", NULL, "HSDAC Power"},
+ {"HSDAC Right", NULL, "HSDAC Power"},
+
+ {"Headset Left Playback", "HS DAC", "HSDAC Left"},
+ {"Headset Left Playback", "Line-In amp", "AFMAmpL"},
+
+ {"Headset Right Playback", "HS DAC", "HSDAC Right"},
+ {"Headset Right Playback", "Line-In amp", "AFMAmpR"},
- {"Headset Left Driver", NULL, "HSDAC Left Playback"},
- {"Headset Right Driver", NULL, "HSDAC Right Playback"},
+ {"HS Left Driver", NULL, "Headset Left Playback"},
+ {"HS Right Driver", NULL, "Headset Right Playback"},
- {"HSOL", NULL, "Headset Left Driver"},
- {"HSOR", NULL, "Headset Right Driver"},
+ {"HSOL", NULL, "HS Left Driver"},
+ {"HSOR", NULL, "HS Right Driver"},
/* Earphone playback path */
- {"Earphone Driver", "Switch", "HSDAC Left"},
+ {"Earphone Playback", "Switch", "HSDAC Left"},
+ {"Earphone Driver", NULL, "Earphone Playback"},
{"EP", NULL, "Earphone Driver"},
- /* Handsfree playback path */
- {"HFDAC Left Playback", "Switch", "HFDAC Left"},
- {"HFDAC Right Playback", "Switch", "HFDAC Right"},
+ {"Handsfree Left Playback", "HF DAC", "HFDAC Left"},
+ {"Handsfree Left Playback", "Line-In amp", "AFMAmpL"},
- {"HFDAC Left PGA", NULL, "HFDAC Left Playback"},
- {"HFDAC Right PGA", NULL, "HFDAC Right Playback"},
+ {"Handsfree Right Playback", "HF DAC", "HFDAC Right"},
+ {"Handsfree Right Playback", "Line-In amp", "AFMAmpR"},
- {"Handsfree Left Driver", "Switch", "HFDAC Left PGA"},
- {"Handsfree Right Driver", "Switch", "HFDAC Right PGA"},
+ {"HF Left PGA", NULL, "Handsfree Left Playback"},
+ {"HF Right PGA", NULL, "Handsfree Right Playback"},
- {"HFL", NULL, "Handsfree Left Driver"},
- {"HFR", NULL, "Handsfree Right Driver"},
-};
-
-static int twl6040_add_widgets(struct snd_soc_codec *codec)
-{
- snd_soc_dapm_new_controls(codec, twl6040_dapm_widgets,
- ARRAY_SIZE(twl6040_dapm_widgets));
+ {"HF Left Driver", NULL, "HF Left PGA"},
+ {"HF Right Driver", NULL, "HF Right PGA"},
- snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+ {"HFL", NULL, "HF Left Driver"},
+ {"HFR", NULL, "HF Right Driver"},
- snd_soc_dapm_new_widgets(codec);
+ {"AUXL Playback", "Switch", "HF Left PGA"},
+ {"AUXR Playback", "Switch", "HF Right PGA"},
- return 0;
-}
+ {"AUXL", NULL, "AUXL Playback"},
+ {"AUXR", NULL, "AUXR Playback"},
-static int twl6040_power_up_completion(struct snd_soc_codec *codec,
- int naudint)
-{
- struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- int time_left;
- u8 intid;
-
- time_left = wait_for_completion_timeout(&priv->ready,
- msecs_to_jiffies(48));
-
- if (!time_left) {
- twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid,
- TWL6040_REG_INTID);
- if (!(intid & TWL6040_READYINT)) {
- dev_err(codec->dev, "timeout waiting for READYINT\n");
- return -ETIMEDOUT;
- }
- }
+ /* Vibrator paths */
+ {"Vibra Left Playback", "Audio PDM", "VIBRA DAC"},
+ {"Vibra Right Playback", "Audio PDM", "VIBRA DAC"},
- priv->codec_powered = 1;
+ {"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"},
- return 0;
-}
+ {"VIBRAL", NULL, "Vibra Left Driver"},
+ {"VIBRAR", NULL, "Vibra Right Driver"},
+};
static int twl6040_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
+ struct twl6040 *twl6040 = codec->control_data;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- int audpwron = priv->audpwron;
- int naudint = priv->naudint;
int ret;
switch (level) {
@@ -692,106 +835,38 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec,
if (priv->codec_powered)
break;
- if (gpio_is_valid(audpwron)) {
- /* use AUDPWRON line */
- gpio_set_value(audpwron, 1);
-
- /* wait for power-up completion */
- ret = twl6040_power_up_completion(codec, naudint);
- if (ret)
- return ret;
-
- /* sync registers updated during power-up sequence */
- twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL);
- twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL);
- twl6040_read_reg_volatile(codec, TWL6040_REG_LPPLLCTL);
- } else {
- /* use manual power-up sequence */
- twl6040_power_up(codec);
- priv->codec_powered = 1;
- }
+ ret = twl6040_power(twl6040, 1);
+ if (ret)
+ return ret;
- /* initialize vdd/vss registers with reg_cache */
- twl6040_init_vdd_regs(codec);
+ priv->codec_powered = 1;
+
+ /* Set external boost GPO */
+ twl6040_write(codec, TWL6040_REG_GPOCTL, 0x02);
break;
case SND_SOC_BIAS_OFF:
if (!priv->codec_powered)
break;
- if (gpio_is_valid(audpwron)) {
- /* use AUDPWRON line */
- gpio_set_value(audpwron, 0);
-
- /* power-down sequence latency */
- udelay(500);
-
- /* sync registers updated during power-down sequence */
- twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL);
- twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL);
- twl6040_write_reg_cache(codec, TWL6040_REG_LPPLLCTL,
- 0x00);
- } else {
- /* use manual power-down sequence */
- twl6040_power_down(codec);
- }
-
+ twl6040_power(twl6040, 0);
priv->codec_powered = 0;
break;
}
- codec->bias_level = level;
+ codec->dapm.bias_level = level;
return 0;
}
-/* set of rates for each pll: low-power and high-performance */
-
-static unsigned int lp_rates[] = {
- 88200,
- 96000,
-};
-
-static struct snd_pcm_hw_constraint_list lp_constraints = {
- .count = ARRAY_SIZE(lp_rates),
- .list = lp_rates,
-};
-
-static unsigned int hp_rates[] = {
- 96000,
-};
-
-static struct snd_pcm_hw_constraint_list hp_constraints = {
- .count = ARRAY_SIZE(hp_rates),
- .list = hp_rates,
-};
-
static int twl6040_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_codec *codec = dai->codec;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- if (!priv->sysclk) {
- dev_err(codec->dev,
- "no mclk configured, call set_sysclk() on init\n");
- return -EINVAL;
- }
-
- /*
- * capture is not supported at 17.64 MHz,
- * it's reserved for headset low-power playback scenario
- */
- if ((priv->sysclk == 17640000) && substream->stream) {
- dev_err(codec->dev,
- "capture mode is not supported at %dHz\n",
- priv->sysclk);
- return -EINVAL;
- }
-
snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
- priv->sysclk_constraints);
+ &sysclk_constraints[priv->pll_power_mode]);
return 0;
}
@@ -800,26 +875,29 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_codec *codec = dai->codec;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- u8 lppllctl;
int rate;
- /* nothing to do for high-perf pll, it supports only 48 kHz */
- if (priv->pll == TWL6040_HPPLL_ID)
- return 0;
-
- lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
-
rate = params_rate(params);
switch (rate) {
+ case 11250:
+ case 22500:
+ case 44100:
case 88200:
- lppllctl |= TWL6040_LPLLFIN;
+ /* These rates are not supported when HPPLL is in use */
+ if (unlikely(priv->pll == TWL6040_SYSCLK_SEL_HPPLL)) {
+ dev_err(codec->dev, "HPPLL does not support rate %d\n",
+ rate);
+ return -EINVAL;
+ }
priv->sysclk = 17640000;
break;
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
case 96000:
- lppllctl &= ~TWL6040_LPLLFIN;
priv->sysclk = 19200000;
break;
default:
@@ -827,34 +905,27 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
-
return 0;
}
-static int twl6040_trigger(struct snd_pcm_substream *substream,
- int cmd, struct snd_soc_dai *dai)
+static int twl6040_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_codec *codec = dai->codec;
+ struct twl6040 *twl6040 = codec->control_data;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ int ret;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- /*
- * low-power playback mode is restricted
- * for headset path only
- */
- if ((priv->sysclk == 17640000) && priv->non_lp) {
- dev_err(codec->dev,
- "some enabled paths aren't supported at %dHz\n",
- priv->sysclk);
- return -EPERM;
- }
- break;
- default:
- break;
+ if (!priv->sysclk) {
+ dev_err(codec->dev,
+ "no mclk configured, call set_sysclk() on init\n");
+ return -EINVAL;
+ }
+
+ ret = twl6040_set_pll(twl6040, priv->pll, priv->clk_in, priv->sysclk);
+ if (ret) {
+ dev_err(codec->dev, "Can not set PLL (%d)\n", ret);
+ return -EPERM;
}
return 0;
@@ -865,124 +936,118 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai,
{
struct snd_soc_codec *codec = codec_dai->codec;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- u8 hppllctl, lppllctl;
-
- hppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_HPPLLCTL);
- lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
switch (clk_id) {
case TWL6040_SYSCLK_SEL_LPPLL:
- switch (freq) {
- case 32768:
- /* headset dac and driver must be in low-power mode */
- headset_power_mode(codec, 0);
-
- /* clk32k input requires low-power pll */
- lppllctl |= TWL6040_LPLLENA;
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
- mdelay(5);
- lppllctl &= ~TWL6040_HPLLSEL;
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
- hppllctl &= ~TWL6040_HPLLENA;
- twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl);
- break;
- default:
- dev_err(codec->dev, "unknown mclk freq %d\n", freq);
- return -EINVAL;
- }
+ case TWL6040_SYSCLK_SEL_HPPLL:
+ priv->pll = clk_id;
+ priv->clk_in = freq;
+ break;
+ default:
+ dev_err(codec->dev, "unknown clk_id %d\n", clk_id);
+ return -EINVAL;
+ }
- /* lppll divider */
- switch (priv->sysclk) {
- case 17640000:
- lppllctl |= TWL6040_LPLLFIN;
- break;
- case 19200000:
- lppllctl &= ~TWL6040_LPLLFIN;
- break;
- default:
- /* sysclk not yet configured */
- lppllctl &= ~TWL6040_LPLLFIN;
- priv->sysclk = 19200000;
- break;
- }
+ return 0;
+}
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+static void twl6040_mute_path(struct snd_soc_codec *codec, enum twl6040_dai_id id,
+ int mute)
+{
+ struct twl6040 *twl6040 = codec->control_data;
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+ int hslctl, hsrctl, earctl;
+ int hflctl, hfrctl;
+
+ switch (id) {
+ case TWL6040_DAI_DL1:
+ hslctl = twl6040_read(codec, TWL6040_REG_HSLCTL);
+ hsrctl = twl6040_read(codec, TWL6040_REG_HSRCTL);
+ earctl = twl6040_read(codec, TWL6040_REG_EARCTL);
+
+ if (mute) {
+ /* Power down drivers and DACs */
+ earctl &= ~0x01;
+ hslctl &= ~(TWL6040_HSDRVENA | TWL6040_HSDACENA);
+ hsrctl &= ~(TWL6040_HSDRVENA | TWL6040_HSDACENA);
- priv->pll = TWL6040_LPPLL_ID;
- priv->sysclk_constraints = &lp_constraints;
- break;
- case TWL6040_SYSCLK_SEL_HPPLL:
- hppllctl &= ~TWL6040_MCLK_MSK;
-
- switch (freq) {
- case 12000000:
- /* mclk input, pll enabled */
- hppllctl |= TWL6040_MCLK_12000KHZ |
- TWL6040_HPLLSQRBP |
- TWL6040_HPLLENA;
- break;
- case 19200000:
- /* mclk input, pll disabled */
- hppllctl |= TWL6040_MCLK_19200KHZ |
- TWL6040_HPLLSQRENA |
- TWL6040_HPLLBP;
- break;
- case 26000000:
- /* mclk input, pll enabled */
- hppllctl |= TWL6040_MCLK_26000KHZ |
- TWL6040_HPLLSQRBP |
- TWL6040_HPLLENA;
- break;
- case 38400000:
- /* clk slicer, pll disabled */
- hppllctl |= TWL6040_MCLK_38400KHZ |
- TWL6040_HPLLSQRENA |
- TWL6040_HPLLBP;
- break;
- default:
- dev_err(codec->dev, "unknown mclk freq %d\n", freq);
- return -EINVAL;
}
- /* headset dac and driver must be in high-performance mode */
- headset_power_mode(codec, 1);
+ twl6040_reg_write(twl6040, TWL6040_REG_EARCTL, earctl);
+ twl6040_reg_write(twl6040, TWL6040_REG_HSLCTL, hslctl);
+ twl6040_reg_write(twl6040, TWL6040_REG_HSRCTL, hsrctl);
+ priv->dl1_unmuted = !mute;
+ break;
+ case TWL6040_DAI_DL2:
+ hflctl = twl6040_read(codec, TWL6040_REG_HFLCTL);
+ hfrctl = twl6040_read(codec, TWL6040_REG_HFRCTL);
+
+ if (mute) {
+ /* Power down drivers and DACs */
+ hflctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA |
+ TWL6040_HFDRVENA);
+ hfrctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA |
+ TWL6040_HFDRVENA);
+ }
- twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl);
- udelay(500);
- lppllctl |= TWL6040_HPLLSEL;
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
- lppllctl &= ~TWL6040_LPLLENA;
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+ twl6040_reg_write(twl6040, TWL6040_REG_HFLCTL, hflctl);
+ twl6040_reg_write(twl6040, TWL6040_REG_HFRCTL, hfrctl);
+ priv->dl2_unmuted = !mute;
+ break;
+ default:
+ break;
+ }
+}
- /* high-performance pll can provide only 19.2 MHz */
- priv->pll = TWL6040_HPPLL_ID;
- priv->sysclk = 19200000;
- priv->sysclk_constraints = &hp_constraints;
+static int twl6040_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ switch (dai->id) {
+ case TWL6040_DAI_LEGACY:
+ twl6040_mute_path(dai->codec, TWL6040_DAI_DL1, mute);
+ twl6040_mute_path(dai->codec, TWL6040_DAI_DL2, mute);
+ break;
+ case TWL6040_DAI_DL1:
+ case TWL6040_DAI_DL2:
+ twl6040_mute_path(dai->codec, dai->id, mute);
break;
default:
- dev_err(codec->dev, "unknown clk_id %d\n", clk_id);
- return -EINVAL;
+ break;
}
return 0;
}
-static struct snd_soc_dai_ops twl6040_dai_ops = {
+static const struct snd_soc_dai_ops twl6040_dai_ops = {
.startup = twl6040_startup,
.hw_params = twl6040_hw_params,
- .trigger = twl6040_trigger,
+ .prepare = twl6040_prepare,
.set_sysclk = twl6040_set_dai_sysclk,
+ .digital_mute = twl6040_digital_mute,
};
-static struct snd_soc_dai_driver twl6040_dai = {
- .name = "twl6040-hifi",
+static struct snd_soc_dai_driver twl6040_dai[] = {
+{
+ .name = "twl6040-legacy",
+ .id = TWL6040_DAI_LEGACY,
.playback = {
- .stream_name = "Playback",
+ .stream_name = "Legacy Playback",
+ .channels_min = 1,
+ .channels_max = 5,
+ .rates = TWL6040_RATES,
+ .formats = TWL6040_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Legacy Capture",
.channels_min = 1,
- .channels_max = 4,
+ .channels_max = 2,
.rates = TWL6040_RATES,
.formats = TWL6040_FORMATS,
},
+ .ops = &twl6040_dai_ops,
+},
+{
+ .name = "twl6040-ul",
+ .id = TWL6040_DAI_UL,
.capture = {
.stream_name = "Capture",
.channels_min = 1,
@@ -991,10 +1056,47 @@ static struct snd_soc_dai_driver twl6040_dai = {
.formats = TWL6040_FORMATS,
},
.ops = &twl6040_dai_ops,
+},
+{
+ .name = "twl6040-dl1",
+ .id = TWL6040_DAI_DL1,
+ .playback = {
+ .stream_name = "Headset Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TWL6040_RATES,
+ .formats = TWL6040_FORMATS,
+ },
+ .ops = &twl6040_dai_ops,
+},
+{
+ .name = "twl6040-dl2",
+ .id = TWL6040_DAI_DL2,
+ .playback = {
+ .stream_name = "Handsfree Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TWL6040_RATES,
+ .formats = TWL6040_FORMATS,
+ },
+ .ops = &twl6040_dai_ops,
+},
+{
+ .name = "twl6040-vib",
+ .id = TWL6040_DAI_VIB,
+ .playback = {
+ .stream_name = "Vibra Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .formats = TWL6040_FORMATS,
+ },
+ .ops = &twl6040_dai_ops,
+},
};
#ifdef CONFIG_PM
-static int twl6040_suspend(struct snd_soc_codec *codec, pm_message_t state)
+static int twl6040_suspend(struct snd_soc_codec *codec)
{
twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
@@ -1014,102 +1116,52 @@ static int twl6040_resume(struct snd_soc_codec *codec)
static int twl6040_probe(struct snd_soc_codec *codec)
{
- struct twl4030_codec_data *twl_codec = codec->dev->platform_data;
struct twl6040_data *priv;
- int audpwron, naudint;
+ struct twl6040 *twl6040 = dev_get_drvdata(codec->dev->parent);
+ struct platform_device *pdev = container_of(codec->dev,
+ struct platform_device, dev);
int ret = 0;
- priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL);
+ priv = devm_kzalloc(codec->dev, sizeof(*priv), GFP_KERNEL);
if (priv == NULL)
return -ENOMEM;
- snd_soc_codec_set_drvdata(codec, priv);
-
- if (twl_codec) {
- audpwron = twl_codec->audpwron_gpio;
- naudint = twl_codec->naudint_irq;
- } else {
- audpwron = -EINVAL;
- naudint = 0;
- }
-
- priv->audpwron = audpwron;
- priv->naudint = naudint;
-
- init_completion(&priv->ready);
- if (gpio_is_valid(audpwron)) {
- ret = gpio_request(audpwron, "audpwron");
- if (ret)
- goto gpio1_err;
+ snd_soc_codec_set_drvdata(codec, priv);
- ret = gpio_direction_output(audpwron, 0);
- if (ret)
- goto gpio2_err;
+ priv->codec = codec;
+ codec->control_data = twl6040;
- priv->codec_powered = 0;
+ priv->plug_irq = platform_get_irq(pdev, 0);
+ if (priv->plug_irq < 0) {
+ dev_err(codec->dev, "invalid irq\n");
+ return -EINVAL;
}
- if (naudint) {
- /* audio interrupt */
- ret = request_threaded_irq(naudint, NULL,
- twl6040_naudint_handler,
- IRQF_TRIGGER_LOW | IRQF_ONESHOT,
- "twl6040_codec", codec);
- if (ret)
- goto gpio2_err;
- } else {
- if (gpio_is_valid(audpwron)) {
- /* enable only codec ready interrupt */
- twl6040_write_reg_cache(codec, TWL6040_REG_INTMR,
- ~TWL6040_READYMSK & TWL6040_ALLINT_MSK);
- } else {
- /* no interrupts at all */
- twl6040_write_reg_cache(codec, TWL6040_REG_INTMR,
- TWL6040_ALLINT_MSK);
- }
- }
+ INIT_DELAYED_WORK(&priv->hs_jack.work, twl6040_accessory_work);
- /* init vio registers */
- twl6040_init_vio_regs(codec);
+ mutex_init(&priv->mutex);
- /* power on device */
- ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- if (ret)
- goto irq_err;
+ ret = request_threaded_irq(priv->plug_irq, NULL,
+ twl6040_audio_handler, IRQF_NO_SUSPEND,
+ "twl6040_irq_plug", codec);
+ if (ret) {
+ dev_err(codec->dev, "PLUG IRQ request failed: %d\n", ret);
+ return ret;
+ }
- snd_soc_add_controls(codec, twl6040_snd_controls,
- ARRAY_SIZE(twl6040_snd_controls));
- twl6040_add_widgets(codec);
+ twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ twl6040_init_chip(codec);
return 0;
-
-irq_err:
- if (naudint)
- free_irq(naudint, codec);
-gpio2_err:
- if (gpio_is_valid(audpwron))
- gpio_free(audpwron);
-gpio1_err:
- kfree(priv);
- return ret;
}
static int twl6040_remove(struct snd_soc_codec *codec)
{
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
- int audpwron = priv->audpwron;
- int naudint = priv->naudint;
+ free_irq(priv->plug_irq, codec);
twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
- if (gpio_is_valid(audpwron))
- gpio_free(audpwron);
-
- if (naudint)
- free_irq(naudint, codec);
-
- kfree(priv);
-
return 0;
}
@@ -1118,21 +1170,26 @@ static struct snd_soc_codec_driver soc_codec_dev_twl6040 = {
.remove = twl6040_remove,
.suspend = twl6040_suspend,
.resume = twl6040_resume,
- .read = twl6040_read_reg_cache,
+ .read = twl6040_read,
.write = twl6040_write,
.set_bias_level = twl6040_set_bias_level,
- .reg_cache_size = ARRAY_SIZE(twl6040_reg),
- .reg_word_size = sizeof(u8),
- .reg_cache_default = twl6040_reg,
+ .ignore_pmdown_time = true,
+
+ .controls = twl6040_snd_controls,
+ .num_controls = ARRAY_SIZE(twl6040_snd_controls),
+ .dapm_widgets = twl6040_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(twl6040_dapm_widgets),
+ .dapm_routes = intercon,
+ .num_dapm_routes = ARRAY_SIZE(intercon),
};
-static int __devinit twl6040_codec_probe(struct platform_device *pdev)
+static int twl6040_codec_probe(struct platform_device *pdev)
{
- return snd_soc_register_codec(&pdev->dev,
- &soc_codec_dev_twl6040, &twl6040_dai, 1);
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_twl6040,
+ twl6040_dai, ARRAY_SIZE(twl6040_dai));
}
-static int __devexit twl6040_codec_remove(struct platform_device *pdev)
+static int twl6040_codec_remove(struct platform_device *pdev)
{
snd_soc_unregister_codec(&pdev->dev);
return 0;
@@ -1144,20 +1201,10 @@ static struct platform_driver twl6040_codec_driver = {
.owner = THIS_MODULE,
},
.probe = twl6040_codec_probe,
- .remove = __devexit_p(twl6040_codec_remove),
+ .remove = twl6040_codec_remove,
};
-static int __init twl6040_codec_init(void)
-{
- return platform_driver_register(&twl6040_codec_driver);
-}
-module_init(twl6040_codec_init);
-
-static void __exit twl6040_codec_exit(void)
-{
- platform_driver_unregister(&twl6040_codec_driver);
-}
-module_exit(twl6040_codec_exit);
+module_platform_driver(twl6040_codec_driver);
MODULE_DESCRIPTION("ASoC TWL6040 codec driver");
MODULE_AUTHOR("Misael Lopez Cruz");