|
@@ -24,11 +24,10 @@
|
|
|
#include <linux/init.h>
|
|
#include <linux/init.h>
|
|
|
#include <linux/delay.h>
|
|
#include <linux/delay.h>
|
|
|
#include <linux/pm.h>
|
|
#include <linux/pm.h>
|
|
|
-#include <linux/i2c.h>
|
|
|
|
|
-#include <linux/gpio.h>
|
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/platform_device.h>
|
|
|
#include <linux/slab.h>
|
|
#include <linux/slab.h>
|
|
|
#include <linux/i2c/twl.h>
|
|
#include <linux/i2c/twl.h>
|
|
|
|
|
+#include <linux/mfd/twl6040.h>
|
|
|
|
|
|
|
|
#include <sound/core.h>
|
|
#include <sound/core.h>
|
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm.h>
|
|
@@ -77,14 +76,19 @@ struct twl6040_jack_data {
|
|
|
|
|
|
|
|
/* codec private data */
|
|
/* codec private data */
|
|
|
struct twl6040_data {
|
|
struct twl6040_data {
|
|
|
- int audpwron;
|
|
|
|
|
- int naudint;
|
|
|
|
|
|
|
+ int plug_irq;
|
|
|
int codec_powered;
|
|
int codec_powered;
|
|
|
int pll;
|
|
int pll;
|
|
|
int non_lp;
|
|
int non_lp;
|
|
|
|
|
+ int pll_power_mode;
|
|
|
|
|
+ int hs_power_mode;
|
|
|
|
|
+ int hs_power_mode_locked;
|
|
|
|
|
+ unsigned int clk_in;
|
|
|
unsigned int sysclk;
|
|
unsigned int sysclk;
|
|
|
- struct snd_pcm_hw_constraint_list *sysclk_constraints;
|
|
|
|
|
- struct completion ready;
|
|
|
|
|
|
|
+ u16 hs_left_step;
|
|
|
|
|
+ u16 hs_right_step;
|
|
|
|
|
+ u16 hf_left_step;
|
|
|
|
|
+ u16 hf_right_step;
|
|
|
struct twl6040_jack_data hs_jack;
|
|
struct twl6040_jack_data hs_jack;
|
|
|
struct snd_soc_codec *codec;
|
|
struct snd_soc_codec *codec;
|
|
|
struct workqueue_struct *workqueue;
|
|
struct workqueue_struct *workqueue;
|
|
@@ -206,6 +210,32 @@ static const int twl6040_vdd_reg[TWL6040_VDDREGNUM] = {
|
|
|
TWL6040_REG_DLB,
|
|
TWL6040_REG_DLB,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/* set of rates for each pll: low-power and high-performance */
|
|
|
|
|
+static unsigned int lp_rates[] = {
|
|
|
|
|
+ 8000,
|
|
|
|
|
+ 11250,
|
|
|
|
|
+ 16000,
|
|
|
|
|
+ 22500,
|
|
|
|
|
+ 32000,
|
|
|
|
|
+ 44100,
|
|
|
|
|
+ 48000,
|
|
|
|
|
+ 88200,
|
|
|
|
|
+ 96000,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static unsigned int hp_rates[] = {
|
|
|
|
|
+ 8000,
|
|
|
|
|
+ 16000,
|
|
|
|
|
+ 32000,
|
|
|
|
|
+ 48000,
|
|
|
|
|
+ 96000,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static 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
|
|
* read twl6040 register cache
|
|
|
*/
|
|
*/
|
|
@@ -239,12 +269,13 @@ static inline void twl6040_write_reg_cache(struct snd_soc_codec *codec,
|
|
|
static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
|
|
static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
|
|
|
unsigned int reg)
|
|
unsigned int reg)
|
|
|
{
|
|
{
|
|
|
|
|
+ struct twl6040 *twl6040 = codec->control_data;
|
|
|
u8 value;
|
|
u8 value;
|
|
|
|
|
|
|
|
if (reg >= TWL6040_CACHEREGNUM)
|
|
if (reg >= TWL6040_CACHEREGNUM)
|
|
|
return -EIO;
|
|
return -EIO;
|
|
|
|
|
|
|
|
- twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &value, reg);
|
|
|
|
|
|
|
+ value = twl6040_reg_read(twl6040, reg);
|
|
|
twl6040_write_reg_cache(codec, reg, value);
|
|
twl6040_write_reg_cache(codec, reg, value);
|
|
|
|
|
|
|
|
return value;
|
|
return value;
|
|
@@ -256,11 +287,13 @@ static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
|
|
|
static int twl6040_write(struct snd_soc_codec *codec,
|
|
static int twl6040_write(struct snd_soc_codec *codec,
|
|
|
unsigned int reg, unsigned int value)
|
|
unsigned int reg, unsigned int value)
|
|
|
{
|
|
{
|
|
|
|
|
+ struct twl6040 *twl6040 = codec->control_data;
|
|
|
|
|
+
|
|
|
if (reg >= TWL6040_CACHEREGNUM)
|
|
if (reg >= TWL6040_CACHEREGNUM)
|
|
|
return -EIO;
|
|
return -EIO;
|
|
|
|
|
|
|
|
twl6040_write_reg_cache(codec, reg, value);
|
|
twl6040_write_reg_cache(codec, reg, value);
|
|
|
- return twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, value, reg);
|
|
|
|
|
|
|
+ return twl6040_reg_write(twl6040, reg, value);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void twl6040_init_vio_regs(struct snd_soc_codec *codec)
|
|
static void twl6040_init_vio_regs(struct snd_soc_codec *codec)
|
|
@@ -268,15 +301,21 @@ static void twl6040_init_vio_regs(struct snd_soc_codec *codec)
|
|
|
u8 *cache = codec->reg_cache;
|
|
u8 *cache = codec->reg_cache;
|
|
|
int reg, i;
|
|
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++) {
|
|
for (i = 0; i < TWL6040_VIOREGNUM; i++) {
|
|
|
reg = twl6040_vio_reg[i];
|
|
reg = twl6040_vio_reg[i];
|
|
|
- /* skip read-only registers (ASICID, ASICREV, STATUS) */
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * skip read-only registers (ASICID, ASICREV, STATUS)
|
|
|
|
|
+ * and registers shared among MFD children
|
|
|
|
|
+ */
|
|
|
switch (reg) {
|
|
switch (reg) {
|
|
|
case TWL6040_REG_ASICID:
|
|
case TWL6040_REG_ASICID:
|
|
|
case TWL6040_REG_ASICREV:
|
|
case TWL6040_REG_ASICREV:
|
|
|
|
|
+ case TWL6040_REG_INTID:
|
|
|
|
|
+ case TWL6040_REG_INTMR:
|
|
|
|
|
+ case TWL6040_REG_NCPCTL:
|
|
|
|
|
+ case TWL6040_REG_LDOCTL:
|
|
|
|
|
+ case TWL6040_REG_GPOCTL:
|
|
|
|
|
+ case TWL6040_REG_ACCCTL:
|
|
|
case TWL6040_REG_STATUS:
|
|
case TWL6040_REG_STATUS:
|
|
|
continue;
|
|
continue;
|
|
|
default:
|
|
default:
|
|
@@ -293,6 +332,20 @@ static void twl6040_init_vdd_regs(struct snd_soc_codec *codec)
|
|
|
|
|
|
|
|
for (i = 0; i < TWL6040_VDDREGNUM; i++) {
|
|
for (i = 0; i < TWL6040_VDDREGNUM; i++) {
|
|
|
reg = twl6040_vdd_reg[i];
|
|
reg = twl6040_vdd_reg[i];
|
|
|
|
|
+ /* skip vibra and PLL registers */
|
|
|
|
|
+ switch (reg) {
|
|
|
|
|
+ case TWL6040_REG_VIBCTLL:
|
|
|
|
|
+ case TWL6040_REG_VIBDATL:
|
|
|
|
|
+ case TWL6040_REG_VIBCTLR:
|
|
|
|
|
+ case TWL6040_REG_VIBDATR:
|
|
|
|
|
+ case TWL6040_REG_HPPLLCTL:
|
|
|
|
|
+ case TWL6040_REG_LPPLLCTL:
|
|
|
|
|
+ case TWL6040_REG_LPPLLDIV:
|
|
|
|
|
+ continue;
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
twl6040_write(codec, reg, cache[reg]);
|
|
twl6040_write(codec, reg, cache[reg]);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -317,7 +370,11 @@ static inline int twl6040_hs_ramp_step(struct snd_soc_codec *codec,
|
|
|
if (headset->ramp == TWL6040_RAMP_UP) {
|
|
if (headset->ramp == TWL6040_RAMP_UP) {
|
|
|
/* ramp step up */
|
|
/* ramp step up */
|
|
|
if (val < headset->left_vol) {
|
|
if (val < headset->left_vol) {
|
|
|
- val += left_step;
|
|
|
|
|
|
|
+ if (val + left_step > headset->left_vol)
|
|
|
|
|
+ val = headset->left_vol;
|
|
|
|
|
+ else
|
|
|
|
|
+ val += left_step;
|
|
|
|
|
+
|
|
|
reg &= ~TWL6040_HSL_VOL_MASK;
|
|
reg &= ~TWL6040_HSL_VOL_MASK;
|
|
|
twl6040_write(codec, TWL6040_REG_HSGAIN,
|
|
twl6040_write(codec, TWL6040_REG_HSGAIN,
|
|
|
(reg | (~val & TWL6040_HSL_VOL_MASK)));
|
|
(reg | (~val & TWL6040_HSL_VOL_MASK)));
|
|
@@ -327,7 +384,11 @@ static inline int twl6040_hs_ramp_step(struct snd_soc_codec *codec,
|
|
|
} else if (headset->ramp == TWL6040_RAMP_DOWN) {
|
|
} else if (headset->ramp == TWL6040_RAMP_DOWN) {
|
|
|
/* ramp step down */
|
|
/* ramp step down */
|
|
|
if (val > 0x0) {
|
|
if (val > 0x0) {
|
|
|
- val -= left_step;
|
|
|
|
|
|
|
+ if ((int)val - (int)left_step < 0)
|
|
|
|
|
+ val = 0;
|
|
|
|
|
+ else
|
|
|
|
|
+ val -= left_step;
|
|
|
|
|
+
|
|
|
reg &= ~TWL6040_HSL_VOL_MASK;
|
|
reg &= ~TWL6040_HSL_VOL_MASK;
|
|
|
twl6040_write(codec, TWL6040_REG_HSGAIN, reg |
|
|
twl6040_write(codec, TWL6040_REG_HSGAIN, reg |
|
|
|
(~val & TWL6040_HSL_VOL_MASK));
|
|
(~val & TWL6040_HSL_VOL_MASK));
|
|
@@ -344,7 +405,11 @@ static inline int twl6040_hs_ramp_step(struct snd_soc_codec *codec,
|
|
|
if (headset->ramp == TWL6040_RAMP_UP) {
|
|
if (headset->ramp == TWL6040_RAMP_UP) {
|
|
|
/* ramp step up */
|
|
/* ramp step up */
|
|
|
if (val < headset->right_vol) {
|
|
if (val < headset->right_vol) {
|
|
|
- val += right_step;
|
|
|
|
|
|
|
+ if (val + right_step > headset->right_vol)
|
|
|
|
|
+ val = headset->right_vol;
|
|
|
|
|
+ else
|
|
|
|
|
+ val += right_step;
|
|
|
|
|
+
|
|
|
reg &= ~TWL6040_HSR_VOL_MASK;
|
|
reg &= ~TWL6040_HSR_VOL_MASK;
|
|
|
twl6040_write(codec, TWL6040_REG_HSGAIN,
|
|
twl6040_write(codec, TWL6040_REG_HSGAIN,
|
|
|
(reg | (~val << TWL6040_HSR_VOL_SHIFT)));
|
|
(reg | (~val << TWL6040_HSR_VOL_SHIFT)));
|
|
@@ -354,7 +419,11 @@ static inline int twl6040_hs_ramp_step(struct snd_soc_codec *codec,
|
|
|
} else if (headset->ramp == TWL6040_RAMP_DOWN) {
|
|
} else if (headset->ramp == TWL6040_RAMP_DOWN) {
|
|
|
/* ramp step down */
|
|
/* ramp step down */
|
|
|
if (val > 0x0) {
|
|
if (val > 0x0) {
|
|
|
- val -= right_step;
|
|
|
|
|
|
|
+ if ((int)val - (int)right_step < 0)
|
|
|
|
|
+ val = 0;
|
|
|
|
|
+ else
|
|
|
|
|
+ val -= right_step;
|
|
|
|
|
+
|
|
|
reg &= ~TWL6040_HSR_VOL_MASK;
|
|
reg &= ~TWL6040_HSR_VOL_MASK;
|
|
|
twl6040_write(codec, TWL6040_REG_HSGAIN,
|
|
twl6040_write(codec, TWL6040_REG_HSGAIN,
|
|
|
reg | (~val << TWL6040_HSR_VOL_SHIFT));
|
|
reg | (~val << TWL6040_HSR_VOL_SHIFT));
|
|
@@ -385,7 +454,11 @@ static inline int twl6040_hf_ramp_step(struct snd_soc_codec *codec,
|
|
|
if (handsfree->ramp == TWL6040_RAMP_UP) {
|
|
if (handsfree->ramp == TWL6040_RAMP_UP) {
|
|
|
/* ramp step up */
|
|
/* ramp step up */
|
|
|
if (val < handsfree->left_vol) {
|
|
if (val < handsfree->left_vol) {
|
|
|
- val += left_step;
|
|
|
|
|
|
|
+ if (val + left_step > handsfree->left_vol)
|
|
|
|
|
+ val = handsfree->left_vol;
|
|
|
|
|
+ else
|
|
|
|
|
+ val += left_step;
|
|
|
|
|
+
|
|
|
reg &= ~TWL6040_HF_VOL_MASK;
|
|
reg &= ~TWL6040_HF_VOL_MASK;
|
|
|
twl6040_write(codec, TWL6040_REG_HFLGAIN,
|
|
twl6040_write(codec, TWL6040_REG_HFLGAIN,
|
|
|
reg | (0x1D - val));
|
|
reg | (0x1D - val));
|
|
@@ -395,7 +468,11 @@ static inline int twl6040_hf_ramp_step(struct snd_soc_codec *codec,
|
|
|
} else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
|
|
} else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
|
|
|
/* ramp step down */
|
|
/* ramp step down */
|
|
|
if (val > 0) {
|
|
if (val > 0) {
|
|
|
- val -= left_step;
|
|
|
|
|
|
|
+ if ((int)val - (int)left_step < 0)
|
|
|
|
|
+ val = 0;
|
|
|
|
|
+ else
|
|
|
|
|
+ val -= left_step;
|
|
|
|
|
+
|
|
|
reg &= ~TWL6040_HF_VOL_MASK;
|
|
reg &= ~TWL6040_HF_VOL_MASK;
|
|
|
twl6040_write(codec, TWL6040_REG_HFLGAIN,
|
|
twl6040_write(codec, TWL6040_REG_HFLGAIN,
|
|
|
reg | (0x1D - val));
|
|
reg | (0x1D - val));
|
|
@@ -412,7 +489,11 @@ static inline int twl6040_hf_ramp_step(struct snd_soc_codec *codec,
|
|
|
if (handsfree->ramp == TWL6040_RAMP_UP) {
|
|
if (handsfree->ramp == TWL6040_RAMP_UP) {
|
|
|
/* ramp step up */
|
|
/* ramp step up */
|
|
|
if (val < handsfree->right_vol) {
|
|
if (val < handsfree->right_vol) {
|
|
|
- val += right_step;
|
|
|
|
|
|
|
+ if (val + right_step > handsfree->right_vol)
|
|
|
|
|
+ val = handsfree->right_vol;
|
|
|
|
|
+ else
|
|
|
|
|
+ val += right_step;
|
|
|
|
|
+
|
|
|
reg &= ~TWL6040_HF_VOL_MASK;
|
|
reg &= ~TWL6040_HF_VOL_MASK;
|
|
|
twl6040_write(codec, TWL6040_REG_HFRGAIN,
|
|
twl6040_write(codec, TWL6040_REG_HFRGAIN,
|
|
|
reg | (0x1D - val));
|
|
reg | (0x1D - val));
|
|
@@ -422,7 +503,11 @@ static inline int twl6040_hf_ramp_step(struct snd_soc_codec *codec,
|
|
|
} else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
|
|
} else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
|
|
|
/* ramp step down */
|
|
/* ramp step down */
|
|
|
if (val > 0) {
|
|
if (val > 0) {
|
|
|
- val -= right_step;
|
|
|
|
|
|
|
+ if ((int)val - (int)right_step < 0)
|
|
|
|
|
+ val = 0;
|
|
|
|
|
+ else
|
|
|
|
|
+ val -= right_step;
|
|
|
|
|
+
|
|
|
reg &= ~TWL6040_HF_VOL_MASK;
|
|
reg &= ~TWL6040_HF_VOL_MASK;
|
|
|
twl6040_write(codec, TWL6040_REG_HFRGAIN,
|
|
twl6040_write(codec, TWL6040_REG_HFRGAIN,
|
|
|
reg | (0x1D - val));
|
|
reg | (0x1D - val));
|
|
@@ -451,11 +536,9 @@ static void twl6040_pga_hs_work(struct work_struct *work)
|
|
|
|
|
|
|
|
/* HS PGA volumes have 4 bits of resolution to ramp */
|
|
/* HS PGA volumes have 4 bits of resolution to ramp */
|
|
|
for (i = 0; i <= 16; i++) {
|
|
for (i = 0; i <= 16; i++) {
|
|
|
- headset_complete = 1;
|
|
|
|
|
- if (headset->ramp != TWL6040_RAMP_NONE)
|
|
|
|
|
- headset_complete = twl6040_hs_ramp_step(codec,
|
|
|
|
|
- headset->left_step,
|
|
|
|
|
- headset->right_step);
|
|
|
|
|
|
|
+ headset_complete = twl6040_hs_ramp_step(codec,
|
|
|
|
|
+ headset->left_step,
|
|
|
|
|
+ headset->right_step);
|
|
|
|
|
|
|
|
/* ramp finished ? */
|
|
/* ramp finished ? */
|
|
|
if (headset_complete)
|
|
if (headset_complete)
|
|
@@ -496,11 +579,9 @@ static void twl6040_pga_hf_work(struct work_struct *work)
|
|
|
|
|
|
|
|
/* HF PGA volumes have 5 bits of resolution to ramp */
|
|
/* HF PGA volumes have 5 bits of resolution to ramp */
|
|
|
for (i = 0; i <= 32; i++) {
|
|
for (i = 0; i <= 32; i++) {
|
|
|
- handsfree_complete = 1;
|
|
|
|
|
- if (handsfree->ramp != TWL6040_RAMP_NONE)
|
|
|
|
|
- handsfree_complete = twl6040_hf_ramp_step(codec,
|
|
|
|
|
- handsfree->left_step,
|
|
|
|
|
- handsfree->right_step);
|
|
|
|
|
|
|
+ handsfree_complete = twl6040_hf_ramp_step(codec,
|
|
|
|
|
+ handsfree->left_step,
|
|
|
|
|
+ handsfree->right_step);
|
|
|
|
|
|
|
|
/* ramp finished ? */
|
|
/* ramp finished ? */
|
|
|
if (handsfree_complete)
|
|
if (handsfree_complete)
|
|
@@ -541,12 +622,16 @@ static int pga_event(struct snd_soc_dapm_widget *w,
|
|
|
out = &priv->headset;
|
|
out = &priv->headset;
|
|
|
work = &priv->hs_delayed_work;
|
|
work = &priv->hs_delayed_work;
|
|
|
queue = priv->hs_workqueue;
|
|
queue = priv->hs_workqueue;
|
|
|
|
|
+ out->left_step = priv->hs_left_step;
|
|
|
|
|
+ out->right_step = priv->hs_right_step;
|
|
|
out->step_delay = 5; /* 5 ms between volume ramp steps */
|
|
out->step_delay = 5; /* 5 ms between volume ramp steps */
|
|
|
break;
|
|
break;
|
|
|
case 4:
|
|
case 4:
|
|
|
out = &priv->handsfree;
|
|
out = &priv->handsfree;
|
|
|
work = &priv->hf_delayed_work;
|
|
work = &priv->hf_delayed_work;
|
|
|
queue = priv->hf_workqueue;
|
|
queue = priv->hf_workqueue;
|
|
|
|
|
+ out->left_step = priv->hf_left_step;
|
|
|
|
|
+ out->right_step = priv->hf_right_step;
|
|
|
out->step_delay = 5; /* 5 ms between volume ramp steps */
|
|
out->step_delay = 5; /* 5 ms between volume ramp steps */
|
|
|
if (SND_SOC_DAPM_EVENT_ON(event))
|
|
if (SND_SOC_DAPM_EVENT_ON(event))
|
|
|
priv->non_lp++;
|
|
priv->non_lp++;
|
|
@@ -579,8 +664,6 @@ static int pga_event(struct snd_soc_dapm_widget *w,
|
|
|
|
|
|
|
|
if (!delayed_work_pending(work)) {
|
|
if (!delayed_work_pending(work)) {
|
|
|
/* use volume ramp for power-down */
|
|
/* use volume ramp for power-down */
|
|
|
- out->left_step = 1;
|
|
|
|
|
- out->right_step = 1;
|
|
|
|
|
out->ramp = TWL6040_RAMP_DOWN;
|
|
out->ramp = TWL6040_RAMP_DOWN;
|
|
|
INIT_COMPLETION(out->ramp_done);
|
|
INIT_COMPLETION(out->ramp_done);
|
|
|
|
|
|
|
@@ -596,88 +679,6 @@ static int pga_event(struct snd_soc_dapm_widget *w,
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 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);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/* 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);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
/* set headset dac and driver power mode */
|
|
/* set headset dac and driver power mode */
|
|
|
static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
|
|
static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
|
|
|
{
|
|
{
|
|
@@ -713,15 +714,26 @@ static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w,
|
|
|
{
|
|
{
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
+ int ret = 0;
|
|
|
|
|
|
|
|
- if (SND_SOC_DAPM_EVENT_ON(event))
|
|
|
|
|
|
|
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
|
|
|
priv->non_lp++;
|
|
priv->non_lp++;
|
|
|
- else
|
|
|
|
|
|
|
+ if (!strcmp(w->name, "Earphone Driver")) {
|
|
|
|
|
+ /* Earphone doesn't support low power mode */
|
|
|
|
|
+ priv->hs_power_mode_locked = 1;
|
|
|
|
|
+ ret = headset_power_mode(codec, 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
priv->non_lp--;
|
|
priv->non_lp--;
|
|
|
|
|
+ if (!strcmp(w->name, "Earphone Driver")) {
|
|
|
|
|
+ priv->hs_power_mode_locked = 0;
|
|
|
|
|
+ ret = headset_power_mode(codec, priv->hs_power_mode);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
msleep(1);
|
|
msleep(1);
|
|
|
|
|
|
|
|
- return 0;
|
|
|
|
|
|
|
+ return ret;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void twl6040_hs_jack_report(struct snd_soc_codec *codec,
|
|
static void twl6040_hs_jack_report(struct snd_soc_codec *codec,
|
|
@@ -766,33 +778,19 @@ static void twl6040_accessory_work(struct work_struct *work)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* audio interrupt handler */
|
|
/* 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 snd_soc_codec *codec = data;
|
|
|
|
|
+ struct twl6040 *twl6040 = codec->control_data;
|
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
|
u8 intid;
|
|
u8 intid;
|
|
|
|
|
|
|
|
- twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID);
|
|
|
|
|
-
|
|
|
|
|
- if (intid & TWL6040_THINT)
|
|
|
|
|
- dev_alert(codec->dev, "die temp over-limit detection\n");
|
|
|
|
|
|
|
+ intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
|
|
|
|
|
|
|
|
if ((intid & TWL6040_PLUGINT) || (intid & TWL6040_UNPLUGINT))
|
|
if ((intid & TWL6040_PLUGINT) || (intid & TWL6040_UNPLUGINT))
|
|
|
queue_delayed_work(priv->workqueue, &priv->delayed_work,
|
|
queue_delayed_work(priv->workqueue, &priv->delayed_work,
|
|
|
msecs_to_jiffies(200));
|
|
msecs_to_jiffies(200));
|
|
|
|
|
|
|
|
- if (intid & TWL6040_HOOKINT)
|
|
|
|
|
- dev_info(codec->dev, "hook detection\n");
|
|
|
|
|
-
|
|
|
|
|
- if (intid & TWL6040_HFINT)
|
|
|
|
|
- dev_alert(codec->dev, "hf drivers over current detection\n");
|
|
|
|
|
-
|
|
|
|
|
- if (intid & TWL6040_VIBINT)
|
|
|
|
|
- dev_alert(codec->dev, "vib drivers over current detection\n");
|
|
|
|
|
-
|
|
|
|
|
- if (intid & TWL6040_READYINT)
|
|
|
|
|
- complete(&priv->ready);
|
|
|
|
|
-
|
|
|
|
|
return IRQ_HANDLED;
|
|
return IRQ_HANDLED;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1040,6 +1038,73 @@ static const struct snd_kcontrol_new hfr_mux_controls =
|
|
|
static const struct snd_kcontrol_new ep_driver_switch_controls =
|
|
static const struct snd_kcontrol_new ep_driver_switch_controls =
|
|
|
SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0);
|
|
SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0);
|
|
|
|
|
|
|
|
|
|
+/* Headset power mode */
|
|
|
|
|
+static const char *twl6040_power_mode_texts[] = {
|
|
|
|
|
+ "Low-Power", "High-Perfomance",
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static const struct soc_enum twl6040_power_mode_enum =
|
|
|
|
|
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(twl6040_power_mode_texts),
|
|
|
|
|
+ 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_kcontrol_chip(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_kcontrol_chip(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_kcontrol_chip(kcontrol);
|
|
|
|
|
+ struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
+
|
|
|
|
|
+ ucontrol->value.enumerated.item[0] = priv->pll_power_mode;
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int twl6040_pll_put_enum(struct snd_kcontrol *kcontrol,
|
|
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
|
|
+{
|
|
|
|
|
+ struct snd_soc_codec *codec = snd_kcontrol_chip(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_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);
|
|
|
|
|
+
|
|
|
static const struct snd_kcontrol_new twl6040_snd_controls[] = {
|
|
static const struct snd_kcontrol_new twl6040_snd_controls[] = {
|
|
|
/* Capture gains */
|
|
/* Capture gains */
|
|
|
SOC_DOUBLE_TLV("Capture Preamplifier Volume",
|
|
SOC_DOUBLE_TLV("Capture Preamplifier Volume",
|
|
@@ -1058,6 +1123,13 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = {
|
|
|
TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv),
|
|
TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv),
|
|
|
SOC_SINGLE_TLV("Earphone Playback Volume",
|
|
SOC_SINGLE_TLV("Earphone Playback Volume",
|
|
|
TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv),
|
|
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[] = {
|
|
static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
|
|
@@ -1231,36 +1303,11 @@ static int twl6040_add_widgets(struct snd_soc_codec *codec)
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-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(144));
|
|
|
|
|
-
|
|
|
|
|
- if (!time_left) {
|
|
|
|
|
- twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid,
|
|
|
|
|
- TWL6040_REG_INTID);
|
|
|
|
|
- if (!(intid & TWL6040_READYINT)) {
|
|
|
|
|
- dev_err(codec->dev, "timeout waiting for READYINT\n");
|
|
|
|
|
- return -ETIMEDOUT;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- priv->codec_powered = 1;
|
|
|
|
|
-
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
static int twl6040_set_bias_level(struct snd_soc_codec *codec,
|
|
static int twl6040_set_bias_level(struct snd_soc_codec *codec,
|
|
|
enum snd_soc_bias_level level)
|
|
enum snd_soc_bias_level level)
|
|
|
{
|
|
{
|
|
|
|
|
+ struct twl6040 *twl6040 = codec->control_data;
|
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
|
- int audpwron = priv->audpwron;
|
|
|
|
|
- int naudint = priv->naudint;
|
|
|
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
|
|
switch (level) {
|
|
switch (level) {
|
|
@@ -1272,58 +1319,23 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec,
|
|
|
if (priv->codec_powered)
|
|
if (priv->codec_powered)
|
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
- if (gpio_is_valid(audpwron)) {
|
|
|
|
|
- /* use AUDPWRON line */
|
|
|
|
|
- gpio_set_value(audpwron, 1);
|
|
|
|
|
|
|
+ ret = twl6040_power(twl6040, 1);
|
|
|
|
|
+ if (ret)
|
|
|
|
|
+ return ret;
|
|
|
|
|
|
|
|
- /* 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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ priv->codec_powered = 1;
|
|
|
|
|
|
|
|
/* initialize vdd/vss registers with reg_cache */
|
|
/* initialize vdd/vss registers with reg_cache */
|
|
|
twl6040_init_vdd_regs(codec);
|
|
twl6040_init_vdd_regs(codec);
|
|
|
|
|
|
|
|
/* Set external boost GPO */
|
|
/* Set external boost GPO */
|
|
|
twl6040_write(codec, TWL6040_REG_GPOCTL, 0x02);
|
|
twl6040_write(codec, TWL6040_REG_GPOCTL, 0x02);
|
|
|
-
|
|
|
|
|
- /* Set initial minimal gain values */
|
|
|
|
|
- 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);
|
|
|
|
|
break;
|
|
break;
|
|
|
case SND_SOC_BIAS_OFF:
|
|
case SND_SOC_BIAS_OFF:
|
|
|
if (!priv->codec_powered)
|
|
if (!priv->codec_powered)
|
|
|
break;
|
|
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;
|
|
priv->codec_powered = 0;
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
@@ -1333,27 +1345,6 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec,
|
|
|
return 0;
|
|
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,
|
|
static int twl6040_startup(struct snd_pcm_substream *substream,
|
|
|
struct snd_soc_dai *dai)
|
|
struct snd_soc_dai *dai)
|
|
|
{
|
|
{
|
|
@@ -1363,7 +1354,7 @@ static int twl6040_startup(struct snd_pcm_substream *substream,
|
|
|
|
|
|
|
|
snd_pcm_hw_constraint_list(substream->runtime, 0,
|
|
snd_pcm_hw_constraint_list(substream->runtime, 0,
|
|
|
SNDRV_PCM_HW_PARAM_RATE,
|
|
SNDRV_PCM_HW_PARAM_RATE,
|
|
|
- priv->sysclk_constraints);
|
|
|
|
|
|
|
+ &sysclk_constraints[priv->pll_power_mode]);
|
|
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
@@ -1375,22 +1366,27 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream,
|
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
|
struct snd_soc_codec *codec = rtd->codec;
|
|
struct snd_soc_codec *codec = rtd->codec;
|
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
|
- u8 lppllctl;
|
|
|
|
|
int rate;
|
|
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);
|
|
rate = params_rate(params);
|
|
|
switch (rate) {
|
|
switch (rate) {
|
|
|
case 11250:
|
|
case 11250:
|
|
|
case 22500:
|
|
case 22500:
|
|
|
case 44100:
|
|
case 44100:
|
|
|
case 88200:
|
|
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;
|
|
|
|
|
+ }
|
|
|
|
|
+ /* Capture is not supported with 17.64MHz sysclk */
|
|
|
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
|
|
|
+ dev_err(codec->dev,
|
|
|
|
|
+ "capture mode is not supported at %dHz\n",
|
|
|
|
|
+ rate);
|
|
|
|
|
+ return -EINVAL;
|
|
|
|
|
+ }
|
|
|
priv->sysclk = 17640000;
|
|
priv->sysclk = 17640000;
|
|
|
break;
|
|
break;
|
|
|
case 8000:
|
|
case 8000:
|
|
@@ -1398,7 +1394,6 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream,
|
|
|
case 32000:
|
|
case 32000:
|
|
|
case 48000:
|
|
case 48000:
|
|
|
case 96000:
|
|
case 96000:
|
|
|
- lppllctl &= ~TWL6040_LPLLFIN;
|
|
|
|
|
priv->sysclk = 19200000;
|
|
priv->sysclk = 19200000;
|
|
|
break;
|
|
break;
|
|
|
default:
|
|
default:
|
|
@@ -1406,8 +1401,6 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream,
|
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
|
|
|
|
|
-
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1416,7 +1409,9 @@ static int twl6040_prepare(struct snd_pcm_substream *substream,
|
|
|
{
|
|
{
|
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
|
struct snd_soc_codec *codec = rtd->codec;
|
|
struct snd_soc_codec *codec = rtd->codec;
|
|
|
|
|
+ struct twl6040 *twl6040 = codec->control_data;
|
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
+ int ret;
|
|
|
|
|
|
|
|
if (!priv->sysclk) {
|
|
if (!priv->sysclk) {
|
|
|
dev_err(codec->dev,
|
|
dev_err(codec->dev,
|
|
@@ -1424,24 +1419,19 @@ static int twl6040_prepare(struct snd_pcm_substream *substream,
|
|
|
return -EINVAL;
|
|
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 == SNDRV_PCM_STREAM_CAPTURE) {
|
|
|
|
|
- dev_err(codec->dev,
|
|
|
|
|
- "capture mode is not supported at %dHz\n",
|
|
|
|
|
- priv->sysclk);
|
|
|
|
|
- return -EINVAL;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
if ((priv->sysclk == 17640000) && priv->non_lp) {
|
|
if ((priv->sysclk == 17640000) && priv->non_lp) {
|
|
|
dev_err(codec->dev,
|
|
dev_err(codec->dev,
|
|
|
"some enabled paths aren't supported at %dHz\n",
|
|
"some enabled paths aren't supported at %dHz\n",
|
|
|
priv->sysclk);
|
|
priv->sysclk);
|
|
|
return -EPERM;
|
|
return -EPERM;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ 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;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1450,99 +1440,12 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai,
|
|
|
{
|
|
{
|
|
|
struct snd_soc_codec *codec = codec_dai->codec;
|
|
struct snd_soc_codec *codec = codec_dai->codec;
|
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(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) {
|
|
switch (clk_id) {
|
|
|
case TWL6040_SYSCLK_SEL_LPPLL:
|
|
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;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /* 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;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
|
|
|
|
|
-
|
|
|
|
|
- priv->pll = TWL6040_LPPLL_ID;
|
|
|
|
|
- priv->sysclk_constraints = &lp_constraints;
|
|
|
|
|
- break;
|
|
|
|
|
case TWL6040_SYSCLK_SEL_HPPLL:
|
|
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_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);
|
|
|
|
|
-
|
|
|
|
|
- /* high-performance pll can provide only 19.2 MHz */
|
|
|
|
|
- priv->pll = TWL6040_HPPLL_ID;
|
|
|
|
|
- priv->sysclk = 19200000;
|
|
|
|
|
- priv->sysclk_constraints = &hp_constraints;
|
|
|
|
|
|
|
+ priv->pll = clk_id;
|
|
|
|
|
+ priv->clk_in = freq;
|
|
|
break;
|
|
break;
|
|
|
default:
|
|
default:
|
|
|
dev_err(codec->dev, "unknown clk_id %d\n", clk_id);
|
|
dev_err(codec->dev, "unknown clk_id %d\n", clk_id);
|
|
@@ -1559,15 +1462,27 @@ static struct snd_soc_dai_ops twl6040_dai_ops = {
|
|
|
.set_sysclk = twl6040_set_dai_sysclk,
|
|
.set_sysclk = twl6040_set_dai_sysclk,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-static struct snd_soc_dai_driver twl6040_dai = {
|
|
|
|
|
|
|
+static struct snd_soc_dai_driver twl6040_dai[] = {
|
|
|
|
|
+{
|
|
|
.name = "twl6040-hifi",
|
|
.name = "twl6040-hifi",
|
|
|
.playback = {
|
|
.playback = {
|
|
|
.stream_name = "Playback",
|
|
.stream_name = "Playback",
|
|
|
.channels_min = 1,
|
|
.channels_min = 1,
|
|
|
- .channels_max = 4,
|
|
|
|
|
|
|
+ .channels_max = 2,
|
|
|
|
|
+ .rates = TWL6040_RATES,
|
|
|
|
|
+ .formats = TWL6040_FORMATS,
|
|
|
|
|
+ },
|
|
|
|
|
+ .capture = {
|
|
|
|
|
+ .stream_name = "Capture",
|
|
|
|
|
+ .channels_min = 1,
|
|
|
|
|
+ .channels_max = 2,
|
|
|
.rates = TWL6040_RATES,
|
|
.rates = TWL6040_RATES,
|
|
|
.formats = TWL6040_FORMATS,
|
|
.formats = TWL6040_FORMATS,
|
|
|
},
|
|
},
|
|
|
|
|
+ .ops = &twl6040_dai_ops,
|
|
|
|
|
+},
|
|
|
|
|
+{
|
|
|
|
|
+ .name = "twl6040-ul",
|
|
|
.capture = {
|
|
.capture = {
|
|
|
.stream_name = "Capture",
|
|
.stream_name = "Capture",
|
|
|
.channels_min = 1,
|
|
.channels_min = 1,
|
|
@@ -1576,6 +1491,40 @@ static struct snd_soc_dai_driver twl6040_dai = {
|
|
|
.formats = TWL6040_FORMATS,
|
|
.formats = TWL6040_FORMATS,
|
|
|
},
|
|
},
|
|
|
.ops = &twl6040_dai_ops,
|
|
.ops = &twl6040_dai_ops,
|
|
|
|
|
+},
|
|
|
|
|
+{
|
|
|
|
|
+ .name = "twl6040-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",
|
|
|
|
|
+ .playback = {
|
|
|
|
|
+ .stream_name = "Handsfree Playback",
|
|
|
|
|
+ .channels_min = 1,
|
|
|
|
|
+ .channels_max = 2,
|
|
|
|
|
+ .rates = TWL6040_RATES,
|
|
|
|
|
+ .formats = TWL6040_FORMATS,
|
|
|
|
|
+ },
|
|
|
|
|
+ .ops = &twl6040_dai_ops,
|
|
|
|
|
+},
|
|
|
|
|
+{
|
|
|
|
|
+ .name = "twl6040-vib",
|
|
|
|
|
+ .playback = {
|
|
|
|
|
+ .stream_name = "Vibra Playback",
|
|
|
|
|
+ .channels_min = 2,
|
|
|
|
|
+ .channels_max = 2,
|
|
|
|
|
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
|
|
|
|
|
+ .formats = TWL6040_FORMATS,
|
|
|
|
|
+ },
|
|
|
|
|
+ .ops = &twl6040_dai_ops,
|
|
|
|
|
+},
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
#ifdef CONFIG_PM
|
|
@@ -1600,11 +1549,11 @@ static int twl6040_resume(struct snd_soc_codec *codec)
|
|
|
|
|
|
|
|
static int twl6040_probe(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;
|
|
struct twl6040_data *priv;
|
|
|
- int audpwron, naudint;
|
|
|
|
|
|
|
+ struct twl4030_codec_data *pdata = dev_get_platdata(codec->dev);
|
|
|
|
|
+ struct platform_device *pdev = container_of(codec->dev,
|
|
|
|
|
+ struct platform_device, dev);
|
|
|
int ret = 0;
|
|
int ret = 0;
|
|
|
- u8 icrev, intmr = TWL6040_ALLINT_MSK;
|
|
|
|
|
|
|
|
|
|
priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL);
|
|
priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL);
|
|
|
if (priv == NULL)
|
|
if (priv == NULL)
|
|
@@ -1612,23 +1561,32 @@ static int twl6040_probe(struct snd_soc_codec *codec)
|
|
|
snd_soc_codec_set_drvdata(codec, priv);
|
|
snd_soc_codec_set_drvdata(codec, priv);
|
|
|
|
|
|
|
|
priv->codec = codec;
|
|
priv->codec = codec;
|
|
|
|
|
+ codec->control_data = dev_get_drvdata(codec->dev->parent);
|
|
|
|
|
|
|
|
- twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &icrev, TWL6040_REG_ASICREV);
|
|
|
|
|
|
|
+ if (pdata && pdata->hs_left_step && pdata->hs_right_step) {
|
|
|
|
|
+ priv->hs_left_step = pdata->hs_left_step;
|
|
|
|
|
+ priv->hs_right_step = pdata->hs_right_step;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ priv->hs_left_step = 1;
|
|
|
|
|
+ priv->hs_right_step = 1;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (twl_codec && (icrev > 0))
|
|
|
|
|
- audpwron = twl_codec->audpwron_gpio;
|
|
|
|
|
- else
|
|
|
|
|
- audpwron = -EINVAL;
|
|
|
|
|
|
|
+ if (pdata && pdata->hf_left_step && pdata->hf_right_step) {
|
|
|
|
|
+ priv->hf_left_step = pdata->hf_left_step;
|
|
|
|
|
+ priv->hf_right_step = pdata->hf_right_step;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ priv->hf_left_step = 1;
|
|
|
|
|
+ priv->hf_right_step = 1;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (twl_codec)
|
|
|
|
|
- naudint = twl_codec->naudint_irq;
|
|
|
|
|
- else
|
|
|
|
|
- naudint = 0;
|
|
|
|
|
|
|
+ priv->plug_irq = platform_get_irq(pdev, 0);
|
|
|
|
|
+ if (priv->plug_irq < 0) {
|
|
|
|
|
+ dev_err(codec->dev, "invalid irq\n");
|
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
|
+ goto work_err;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- priv->audpwron = audpwron;
|
|
|
|
|
- priv->naudint = naudint;
|
|
|
|
|
priv->workqueue = create_singlethread_workqueue("twl6040-codec");
|
|
priv->workqueue = create_singlethread_workqueue("twl6040-codec");
|
|
|
-
|
|
|
|
|
if (!priv->workqueue) {
|
|
if (!priv->workqueue) {
|
|
|
ret = -ENOMEM;
|
|
ret = -ENOMEM;
|
|
|
goto work_err;
|
|
goto work_err;
|
|
@@ -1638,56 +1596,33 @@ static int twl6040_probe(struct snd_soc_codec *codec)
|
|
|
|
|
|
|
|
mutex_init(&priv->mutex);
|
|
mutex_init(&priv->mutex);
|
|
|
|
|
|
|
|
- init_completion(&priv->ready);
|
|
|
|
|
init_completion(&priv->headset.ramp_done);
|
|
init_completion(&priv->headset.ramp_done);
|
|
|
init_completion(&priv->handsfree.ramp_done);
|
|
init_completion(&priv->handsfree.ramp_done);
|
|
|
|
|
|
|
|
- if (gpio_is_valid(audpwron)) {
|
|
|
|
|
- ret = gpio_request(audpwron, "audpwron");
|
|
|
|
|
- if (ret)
|
|
|
|
|
- goto gpio1_err;
|
|
|
|
|
-
|
|
|
|
|
- ret = gpio_direction_output(audpwron, 0);
|
|
|
|
|
- if (ret)
|
|
|
|
|
- goto gpio2_err;
|
|
|
|
|
-
|
|
|
|
|
- priv->codec_powered = 0;
|
|
|
|
|
-
|
|
|
|
|
- /* enable only codec ready interrupt */
|
|
|
|
|
- intmr &= ~(TWL6040_READYMSK | TWL6040_PLUGMSK);
|
|
|
|
|
-
|
|
|
|
|
- /* reset interrupt status to allow correct power up sequence */
|
|
|
|
|
- twl6040_read_reg_volatile(codec, TWL6040_REG_INTID);
|
|
|
|
|
- }
|
|
|
|
|
- twl6040_write(codec, TWL6040_REG_INTMR, intmr);
|
|
|
|
|
-
|
|
|
|
|
- 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;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /* init vio registers */
|
|
|
|
|
- twl6040_init_vio_regs(codec);
|
|
|
|
|
-
|
|
|
|
|
priv->hf_workqueue = create_singlethread_workqueue("twl6040-hf");
|
|
priv->hf_workqueue = create_singlethread_workqueue("twl6040-hf");
|
|
|
if (priv->hf_workqueue == NULL) {
|
|
if (priv->hf_workqueue == NULL) {
|
|
|
ret = -ENOMEM;
|
|
ret = -ENOMEM;
|
|
|
- goto irq_err;
|
|
|
|
|
|
|
+ goto hfwq_err;
|
|
|
}
|
|
}
|
|
|
priv->hs_workqueue = create_singlethread_workqueue("twl6040-hs");
|
|
priv->hs_workqueue = create_singlethread_workqueue("twl6040-hs");
|
|
|
if (priv->hs_workqueue == NULL) {
|
|
if (priv->hs_workqueue == NULL) {
|
|
|
ret = -ENOMEM;
|
|
ret = -ENOMEM;
|
|
|
- goto wq_err;
|
|
|
|
|
|
|
+ goto hswq_err;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
INIT_DELAYED_WORK(&priv->hs_delayed_work, twl6040_pga_hs_work);
|
|
INIT_DELAYED_WORK(&priv->hs_delayed_work, twl6040_pga_hs_work);
|
|
|
INIT_DELAYED_WORK(&priv->hf_delayed_work, twl6040_pga_hf_work);
|
|
INIT_DELAYED_WORK(&priv->hf_delayed_work, twl6040_pga_hf_work);
|
|
|
|
|
|
|
|
|
|
+ ret = request_threaded_irq(priv->plug_irq, NULL, twl6040_audio_handler,
|
|
|
|
|
+ 0, "twl6040_irq_plug", codec);
|
|
|
|
|
+ if (ret) {
|
|
|
|
|
+ dev_err(codec->dev, "PLUG IRQ request failed: %d\n", ret);
|
|
|
|
|
+ goto plugirq_err;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* init vio registers */
|
|
|
|
|
+ twl6040_init_vio_regs(codec);
|
|
|
|
|
+
|
|
|
/* power on device */
|
|
/* power on device */
|
|
|
ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
|
ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
|
|
if (ret)
|
|
if (ret)
|
|
@@ -1700,16 +1635,12 @@ static int twl6040_probe(struct snd_soc_codec *codec)
|
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
|
|
bias_err:
|
|
bias_err:
|
|
|
|
|
+ free_irq(priv->plug_irq, codec);
|
|
|
|
|
+plugirq_err:
|
|
|
destroy_workqueue(priv->hs_workqueue);
|
|
destroy_workqueue(priv->hs_workqueue);
|
|
|
-wq_err:
|
|
|
|
|
|
|
+hswq_err:
|
|
|
destroy_workqueue(priv->hf_workqueue);
|
|
destroy_workqueue(priv->hf_workqueue);
|
|
|
-irq_err:
|
|
|
|
|
- if (naudint)
|
|
|
|
|
- free_irq(naudint, codec);
|
|
|
|
|
-gpio2_err:
|
|
|
|
|
- if (gpio_is_valid(audpwron))
|
|
|
|
|
- gpio_free(audpwron);
|
|
|
|
|
-gpio1_err:
|
|
|
|
|
|
|
+hfwq_err:
|
|
|
destroy_workqueue(priv->workqueue);
|
|
destroy_workqueue(priv->workqueue);
|
|
|
work_err:
|
|
work_err:
|
|
|
kfree(priv);
|
|
kfree(priv);
|
|
@@ -1719,17 +1650,9 @@ static int twl6040_probe(struct snd_soc_codec *codec)
|
|
|
static int twl6040_remove(struct snd_soc_codec *codec)
|
|
static int twl6040_remove(struct snd_soc_codec *codec)
|
|
|
{
|
|
{
|
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
|
|
|
- int audpwron = priv->audpwron;
|
|
|
|
|
- int naudint = priv->naudint;
|
|
|
|
|
|
|
|
|
|
twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
|
twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
|
|
-
|
|
|
|
|
- if (gpio_is_valid(audpwron))
|
|
|
|
|
- gpio_free(audpwron);
|
|
|
|
|
-
|
|
|
|
|
- if (naudint)
|
|
|
|
|
- free_irq(naudint, codec);
|
|
|
|
|
-
|
|
|
|
|
|
|
+ free_irq(priv->plug_irq, codec);
|
|
|
destroy_workqueue(priv->workqueue);
|
|
destroy_workqueue(priv->workqueue);
|
|
|
destroy_workqueue(priv->hf_workqueue);
|
|
destroy_workqueue(priv->hf_workqueue);
|
|
|
destroy_workqueue(priv->hs_workqueue);
|
|
destroy_workqueue(priv->hs_workqueue);
|
|
@@ -1753,8 +1676,8 @@ static struct snd_soc_codec_driver soc_codec_dev_twl6040 = {
|
|
|
|
|
|
|
|
static int __devinit twl6040_codec_probe(struct platform_device *pdev)
|
|
static int __devinit 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 __devexit twl6040_codec_remove(struct platform_device *pdev)
|