[alsa-devel] [PATCH 0/3] add support for jack detection in mid-x86
From: Vinod Koul vinod.koul@intel.com
This patch set adds support for jack detection in mid-x6 machine and TI sn95031 codec drivers. This also adds support to read mic bias values thru ADC
The comments from previous submission are addressed and patches split
Vinod Koul (3): ASoC: sn95031: Add jack support in the codec ASoC: mfld_machine: Add support for jack detection ASoC: sn95031: Add support for reading mic bias
sound/soc/codecs/sn95031.c | 184 ++++++++++++++++++++++++++++++++- sound/soc/codecs/sn95031.h | 35 ++++++ sound/soc/mid-x86/mfld_machine.c | 211 +++++++++++++++++++++++++++++++++----- 3 files changed, 399 insertions(+), 31 deletions(-)
From: Vinod Koul vinod.koul@intel.com
This patch adds support for jack detection and reporting in the codec It however is not fully functional as it doesn't measure adc to figure out what got inserted which will be added later
Signed-off-by: Vinod Koul vinod.koul@intel.com Signed-off-by: Harsha Priya priya.harsha@intel.com --- sound/soc/codecs/sn95031.c | 57 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/sn95031.h | 10 +++++++ 2 files changed, 67 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/sn95031.c b/sound/soc/codecs/sn95031.c index 40e285d..3cd812d 100644 --- a/sound/soc/codecs/sn95031.c +++ b/sound/soc/codecs/sn95031.c @@ -34,6 +34,7 @@ #include <sound/soc-dapm.h> #include <sound/initval.h> #include <sound/tlv.h> +#include <sound/jack.h> #include "sn95031.h"
#define SN95031_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100) @@ -649,6 +650,62 @@ struct snd_soc_dai_driver sn95031_dais[] = { }, };
+void disable_jack_btn(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, SN95031_BTNCTRL2, 0x00); +} +EXPORT_SYMBOL_GPL(disable_jack_btn); + +static inline void enable_jack_btn(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, SN95031_BTNCTRL1, 0x77); + snd_soc_write(codec, SN95031_BTNCTRL2, 0x01); +} + +static int sn95031_get_headset_state(struct snd_soc_jack *mfld_jack) +{ + /* Defaulting to HEADSET for now. + * will change after adding soc-jack detection apis */ + int jack_type = SND_JACK_HEADSET; + + pr_debug("jack type detected = %d\n", jack_type); + if (jack_type == SND_JACK_HEADSET) + enable_jack_btn(mfld_jack->codec); + return jack_type; +} + +void sn95031_jack_detection(struct mfld_jack_data *jack_data) +{ + unsigned int status; + unsigned int mask = SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_HEADSET; + + pr_debug("interrupt id read in sram = 0x%x\n", jack_data->intr_id); + if (jack_data->intr_id & 0x1) { + pr_debug("short_push detected\n"); + status = SND_JACK_HEADSET | SND_JACK_BTN_0; + } else if (jack_data->intr_id & 0x2) { + pr_debug("long_push detected\n"); + status = SND_JACK_HEADSET | SND_JACK_BTN_1; + } else if (jack_data->intr_id & 0x4) { + pr_debug("headset or headphones inserted\n"); + status = sn95031_get_headset_state(jack_data->mfld_jack); + } else if (jack_data->intr_id & 0x8) { + pr_debug("headset or headphones removed\n"); + status = 0; + disable_jack_btn(jack_data->mfld_jack->codec); + } else { + pr_err("unidentified interrupt\n"); + return; + } + + snd_soc_jack_report(jack_data->mfld_jack, status, mask); + /*button pressed and released so we send explicit button release */ + if ((status & SND_JACK_BTN_0) | (status & SND_JACK_BTN_1)) + snd_soc_jack_report(jack_data->mfld_jack, + SND_JACK_HEADSET, mask); +} +EXPORT_SYMBOL_GPL(sn95031_jack_detection); + /* codec registration */ static int sn95031_codec_probe(struct snd_soc_codec *codec) { diff --git a/sound/soc/codecs/sn95031.h b/sound/soc/codecs/sn95031.h index e2b17d9..3d6f9bb 100644 --- a/sound/soc/codecs/sn95031.h +++ b/sound/soc/codecs/sn95031.h @@ -96,4 +96,14 @@ #define SN95031_SSR5 0x384 #define SN95031_SSR6 0x385
+#define SN95031_AUDIO_GPIO_CTRL 0x070 +struct mfld_jack_data { + int intr_id; + int micbias_vol; + struct snd_soc_jack *mfld_jack; +}; + +extern void sn95031_jack_detection(struct mfld_jack_data *jack_data); + +extern void disable_jack_btn(struct snd_soc_codec *codec); #endif
On Wed, Feb 09, 2011 at 02:42:25PM +0530, Koul, Vinod wrote:
+void disable_jack_btn(struct snd_soc_codec *codec) +{
- snd_soc_write(codec, SN95031_BTNCTRL2, 0x00);
+} +EXPORT_SYMBOL_GPL(disable_jack_btn);
This needs to be namespaced. It's also surprising that the matching enable function isn't exposed...
The code itself looks OK.
-----Original Message----- From: Mark Brown [mailto:broonie@opensource.wolfsonmicro.com] Sent: Wednesday, February 09, 2011 5:42 PM To: Koul, Vinod Cc: alsa-devel@alsa-project.org; lrg@slimlogic.co.uk; Harsha, Priya Subject: Re: [PATCH 1/3] ASoC: sn95031: Add jack support in the codec
On Wed, Feb 09, 2011 at 02:42:25PM +0530, Koul, Vinod wrote:
+void disable_jack_btn(struct snd_soc_codec *codec) +{
- snd_soc_write(codec, SN95031_BTNCTRL2, 0x00);
+} +EXPORT_SYMBOL_GPL(disable_jack_btn);
This needs to be namespaced. It's also surprising that the matching enable function isn't exposed...
Yes, that's right, this should be namespaced :)
This buttons are disabled at init, but I added code to check jack status at init, If it finds a avalid check it will enabled jack or disable otherwise.
I don't strictly need this. I will remove this as exported and fix namespace...
The code itself looks OK.
In that case would it be okay if you accept this and I will send follow patch right away fixing this...Or I will resend all three?
~Vinod
On Wed, Feb 09, 2011 at 06:36:44PM +0530, Koul, Vinod wrote:
The code itself looks OK.
In that case would it be okay if you accept this and I will send follow patch right away fixing this...Or I will resend all three?
Please wait for the others, I've not reviewed them yet.
From: Vinod Koul vinod.koul@intel.com
This patch adds support for registering jack interupt and registering jack with core
Signed-off-by: Vinod Koul vinod.koul@intel.com Signed-off-by: Harsha Priya priya.harsha@intel.com --- sound/soc/mid-x86/mfld_machine.c | 211 +++++++++++++++++++++++++++++++++----- 1 files changed, 184 insertions(+), 27 deletions(-)
diff --git a/sound/soc/mid-x86/mfld_machine.c b/sound/soc/mid-x86/mfld_machine.c index 7925851..a8d5ad8 100644 --- a/sound/soc/mid-x86/mfld_machine.c +++ b/sound/soc/mid-x86/mfld_machine.c @@ -27,18 +27,53 @@ #include <linux/init.h> #include <linux/device.h> #include <linux/slab.h> +#include <linux/io.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/jack.h> #include "../codecs/sn95031.h"
#define MID_MONO 1 #define MID_STEREO 2 #define MID_MAX_CAP 5 +#define MFLD_JACK_INSERT 0x04 + +enum soc_mic_bias_zones { + MFLD_MV_START = 0, + /* mic bias volutage range for Headphones*/ + MFLD_MV_HP = 400, + /* mic bias volutage range for American Headset*/ + MFLD_MV_AM_HS = 650, + /* mic bias volutage range for Headset*/ + MFLD_MV_HS = 2000, + MFLD_MV_UNDEFINED, +};
static unsigned int hs_switch; static unsigned int lo_dac;
+struct mfld_mc_private { + struct platform_device *socdev; + void __iomem *int_base; + struct snd_soc_codec *codec; + u8 interrupt_status; +}; + +struct snd_soc_jack mfld_jack; + +/*Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin mfld_jack_pins[] = { + { + .pin = "Headphones", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "AMIC1", + .mask = SND_JACK_MICROPHONE, + }, +}; + /* sound card controls */ static const char *headset_switch_text[] = {"Earpiece", "Headset"};
@@ -67,13 +102,11 @@ static int headset_set_switch(struct snd_kcontrol *kcontrol,
if (ucontrol->value.integer.value[0]) { pr_debug("hs_set HS path\n"); - snd_soc_dapm_enable_pin(&codec->dapm, "HPOUTL"); - snd_soc_dapm_enable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_enable_pin(&codec->dapm, "Headphones"); snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT"); } else { pr_debug("hs_set EP path\n"); - snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTL"); - snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_disable_pin(&codec->dapm, "Headphones"); snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT"); } snd_soc_dapm_sync(&codec->dapm); @@ -91,12 +124,10 @@ static void lo_enable_out_pins(struct snd_soc_codec *codec) snd_soc_dapm_enable_pin(&codec->dapm, "VIB1OUT"); snd_soc_dapm_enable_pin(&codec->dapm, "VIB2OUT"); if (hs_switch) { - snd_soc_dapm_enable_pin(&codec->dapm, "HPOUTL"); - snd_soc_dapm_enable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_enable_pin(&codec->dapm, "Headphones"); snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT"); } else { - snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTL"); - snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_disable_pin(&codec->dapm, "Headphones"); snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT"); } } @@ -130,8 +161,7 @@ static int lo_set_switch(struct snd_kcontrol *kcontrol,
case 1: pr_debug("set hs path\n"); - snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTL"); - snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_disable_pin(&codec->dapm, "Headphones"); snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT"); snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x22); break; @@ -162,12 +192,52 @@ static const struct snd_kcontrol_new mfld_snd_controls[] = { lo_get_switch, lo_set_switch), };
+static const struct snd_soc_dapm_widget mfld_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route mfld_map[] = { + {"Headphones", NULL, "HPOUTR"}, + {"Headphones", NULL, "HPOUTL"}, + {"Mic", NULL, "AMIC1"}, +}; + +static void mfld_jack_check(unsigned int intr_status) +{ + struct mfld_jack_data jack_data; + + jack_data.mfld_jack = &mfld_jack; + jack_data.intr_id = intr_status; + + sn95031_jack_detection(&jack_data); + /* If american headest, program the gpio control registers */ + if (jack_data.micbias_vol >= MFLD_MV_HP && + jack_data.micbias_vol < MFLD_MV_HS) + snd_soc_update_bits(mfld_jack.codec, SN95031_AUDIO_GPIO_CTRL, + BIT(1), BIT(1)); + else + snd_soc_update_bits(mfld_jack.codec, SN95031_AUDIO_GPIO_CTRL, + BIT(1), BIT(0)); +} + static int mfld_init(struct snd_soc_pcm_runtime *runtime) { struct snd_soc_codec *codec = runtime->codec; struct snd_soc_dapm_context *dapm = &codec->dapm; int ret_val;
+ /* Add jack sense widgets */ + snd_soc_dapm_new_controls(dapm, mfld_widgets, ARRAY_SIZE(mfld_widgets)); + + /* Set up the map */ + snd_soc_dapm_add_routes(dapm, mfld_map, ARRAY_SIZE(mfld_map)); + + /* always connected */ + snd_soc_dapm_enable_pin(dapm, "Headphones"); + snd_soc_dapm_enable_pin(dapm, "Mic"); + snd_soc_dapm_sync(dapm); + ret_val = snd_soc_add_controls(codec, mfld_snd_controls, ARRAY_SIZE(mfld_snd_controls)); if (ret_val) { @@ -175,8 +245,7 @@ static int mfld_init(struct snd_soc_pcm_runtime *runtime) return ret_val; } /* default is earpiece pin, userspace sets it explcitly */ - snd_soc_dapm_disable_pin(dapm, "HPOUTL"); - snd_soc_dapm_disable_pin(dapm, "HPOUTR"); + snd_soc_dapm_disable_pin(dapm, "Headphones"); /* default is lineout NC, userspace sets it explcitly */ snd_soc_dapm_disable_pin(dapm, "LINEOUTL"); snd_soc_dapm_disable_pin(dapm, "LINEOUTR"); @@ -185,7 +254,30 @@ static int mfld_init(struct snd_soc_pcm_runtime *runtime) /* we dont use linein in this so set to NC */ snd_soc_dapm_disable_pin(dapm, "LINEINL"); snd_soc_dapm_disable_pin(dapm, "LINEINR"); - return snd_soc_dapm_sync(dapm); + snd_soc_dapm_sync(dapm); + + /* Headset and button jack detection */ + ret_val = snd_soc_jack_new(codec, "Intel(R) MID Audio Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1, &mfld_jack); + if (ret_val) { + pr_err("jack creation failed\n"); + return ret_val; + } + + ret_val = snd_soc_jack_add_pins(&mfld_jack, + ARRAY_SIZE(mfld_jack_pins), mfld_jack_pins); + if (ret_val) { + pr_err("adding jack pins failed\n"); + return ret_val; + } + disable_jack_btn(codec); + + /* we want to check if anything is inserted at boot, + * so send a fake event to codec and it will read adc + * to find if anything is there or not */ + mfld_jack_check(MFLD_JACK_INSERT); + return ret_val; }
struct snd_soc_dai_link mfld_msic_dailink[] = { @@ -234,37 +326,102 @@ static struct snd_soc_card snd_soc_card_mfld = { .num_links = ARRAY_SIZE(mfld_msic_dailink), };
+static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev) +{ + struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev; + + memcpy_fromio(&mc_private->interrupt_status, + ((void *)(mc_private->int_base)), + sizeof(u8)); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t snd_mfld_jack_detection(int irq, void *data) +{ + struct mfld_mc_private *mc_drv_ctx = (struct mfld_mc_private *) data; + + if (mfld_jack.codec == NULL) + return IRQ_HANDLED; + mfld_jack_check(mc_drv_ctx->interrupt_status); + + return IRQ_HANDLED; +} + static int __devinit snd_mfld_mc_probe(struct platform_device *pdev) { - struct platform_device *socdev; - int ret_val = 0; + int ret_val = 0, irq; + struct mfld_mc_private *mc_drv_ctx; + struct resource *irq_mem;
pr_debug("snd_mfld_mc_probe called\n");
- socdev = platform_device_alloc("soc-audio", -1); - if (!socdev) { - pr_err("soc-audio device allocation failed\n"); + /* retrive the irq number */ + irq = platform_get_irq(pdev, 0); + + /* audio interrupt base of SRAM location where + * interrupts are stored by System FW */ + mc_drv_ctx = kzalloc(sizeof(*mc_drv_ctx), GFP_ATOMIC); + if (!mc_drv_ctx) { + pr_err("allocation failed\n"); return -ENOMEM; } - platform_set_drvdata(socdev, &snd_soc_card_mfld); - ret_val = platform_device_add(socdev); + + irq_mem = platform_get_resource_byname( + pdev, IORESOURCE_MEM, "IRQ_BASE"); + if (!irq_mem) { + pr_err("no mem resource given\n"); + ret_val = -ENODEV; + goto unalloc; + } + mc_drv_ctx->int_base = ioremap_nocache(irq_mem->start, + resource_size(irq_mem)); + if (!mc_drv_ctx->int_base) { + pr_err("Mapping of cache failed\n"); + ret_val = -ENOMEM; + goto unalloc; + } + /* register for interrupt */ + ret_val = request_threaded_irq(irq, snd_mfld_jack_intr_handler, + snd_mfld_jack_detection, + IRQF_SHARED, pdev->dev.driver->name, mc_drv_ctx); + if (ret_val) { + pr_err("cannot register IRQ\n"); + goto unalloc; + } + /* create soc device */ + mc_drv_ctx->socdev = platform_device_alloc("soc-audio", -1); + if (!mc_drv_ctx->socdev) { + pr_err("soc-audio device allocation failed\n"); + ret_val = -ENOMEM; + goto freeirq; + } + platform_set_drvdata(mc_drv_ctx->socdev, &snd_soc_card_mfld); + ret_val = platform_device_add(mc_drv_ctx->socdev); if (ret_val) { pr_err("Unable to add soc-audio device, err %d\n", ret_val); - platform_device_put(socdev); + goto unregister; } - - platform_set_drvdata(pdev, socdev); - + platform_set_drvdata(pdev, mc_drv_ctx); pr_debug("successfully exited probe\n"); return ret_val; + +unregister: + platform_device_put(mc_drv_ctx->socdev); +freeirq: + free_irq(irq, mc_drv_ctx); +unalloc: + kfree(mc_drv_ctx); + return ret_val; }
static int __devexit snd_mfld_mc_remove(struct platform_device *pdev) { - struct platform_device *socdev = platform_get_drvdata(pdev); - pr_debug("snd_mfld_mc_remove called\n"); + struct mfld_mc_private *mc_drv_ctx = platform_get_drvdata(pdev);
- platform_device_unregister(socdev); + pr_debug("snd_mfld_mc_remove called\n"); + free_irq(platform_get_irq(pdev, 0), mc_drv_ctx); + platform_device_unregister(mc_drv_ctx->socdev); + kfree(mc_drv_ctx); platform_set_drvdata(pdev, NULL); return 0; }
On Wed, Feb 09, 2011 at 02:42:26PM +0530, Koul, Vinod wrote:
- sn95031_jack_detection(&jack_data);
- /* If american headest, program the gpio control registers */
- if (jack_data.micbias_vol >= MFLD_MV_HP &&
jack_data.micbias_vol < MFLD_MV_HS)
snd_soc_update_bits(mfld_jack.codec, SN95031_AUDIO_GPIO_CTRL,
BIT(1), BIT(1));
- else
snd_soc_update_bits(mfld_jack.codec, SN95031_AUDIO_GPIO_CTRL,
BIT(1), BIT(0));
This looks like the CODEC driver should be exporting gpiolib support - that'd make it much easier for others to reuse the GPIOs in their designs (eg, powering external amplifiers).
+static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev) +{
- struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev;
- memcpy_fromio(&mc_private->interrupt_status,
((void *)(mc_private->int_base)),
sizeof(u8));
- return IRQ_WAKE_THREAD;
+}
Seems like it'd be as easy to do the copy in the main interrupt handler rather than bothering with having a primary handler but it doesn't really matter.
- sn95031_jack_detection(&jack_data);
- /* If american headest, program the gpio control registers */
- if (jack_data.micbias_vol >= MFLD_MV_HP &&
jack_data.micbias_vol < MFLD_MV_HS)
snd_soc_update_bits(mfld_jack.codec, SN95031_AUDIO_GPIO_CTRL,
BIT(1), BIT(1));
- else
snd_soc_update_bits(mfld_jack.codec, SN95031_AUDIO_GPIO_CTRL,
BIT(1), BIT(0));
This looks like the CODEC driver should be exporting gpiolib support - that'd make it much easier for others to reuse the GPIOs in their designs (eg, powering external amplifiers).
The GPIOs in this chip can be programmed using a codec register read/write. Anyone can use the soc_read/write or scu_ipc_read/writes to program the GPIO. Would putting one more wrapper over these APIs make it look better?
~Vinod
On Wed, Feb 09, 2011 at 08:08:39PM +0530, Koul, Vinod wrote:
The GPIOs in this chip can be programmed using a codec register read/write. Anyone can use the soc_read/write or scu_ipc_read/writes to program the GPIO.
This is the same as any other CODEC with GPIOs.
Would putting one more wrapper over these APIs make it look better?
Yes, it means that users can just use standard Linux APIs to control the GPIOs instead of having to learn about the CODEC register map.
The GPIOs in this chip can be programmed using a codec register read/write. Anyone can use the soc_read/write or scu_ipc_read/writes to program the GPIO.
This is the same as any other CODEC with GPIOs.
Would putting one more wrapper over these APIs make it look better?
Yes, it means that users can just use standard Linux APIs to control the GPIOs instead of having to learn about the CODEC register map.
Okay, agreed. I will add the gpiolib support here (would add that as separate patch)
~Vinod
From: Vinod Koul vinod.koul@intel.com
This patch adds support to read the mic bias voltage when a jack is inserted. It uses ADC to measure.
Signed-off-by: Vinod Koul vinod.koul@intel.com Signed-off-by: Harsha Priya priya.harsha@intel.com --- sound/soc/codecs/sn95031.c | 127 ++++++++++++++++++++++++++++++++++++++++++-- sound/soc/codecs/sn95031.h | 25 +++++++++ 2 files changed, 148 insertions(+), 4 deletions(-)
diff --git a/sound/soc/codecs/sn95031.c b/sound/soc/codecs/sn95031.c index 3cd812d..a0808dd 100644 --- a/sound/soc/codecs/sn95031.c +++ b/sound/soc/codecs/sn95031.c @@ -40,12 +40,129 @@ #define SN95031_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100) #define SN95031_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
+/* adc helper functions */ + +/* enables mic bias voltage */ +static void sn95031_enable_mic_bias(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, SN95031_VAUD, BIT(2)|BIT(1)|BIT(0)); + snd_soc_update_bits(codec, SN95031_MICBIAS, BIT(2), BIT(2)); +} + +/* Enable/Disable the ADC depending on the argument */ +static void configure_adc(struct snd_soc_codec *sn95031_codec, int val) +{ + int value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1); + + if (val) { + /* Enable and start the ADC */ + value |= (SN95031_ADC_ENBL | SN95031_ADC_START); + value &= (~SN95031_ADC_NO_LOOP); + } else { + /* Just stop the ADC */ + value &= (~SN95031_ADC_START); + } + snd_soc_write(sn95031_codec, SN95031_ADC1CNTL1, value); +} + /* - * todo: - * capture paths - * jack detection - * PM functions + * finds an empty channel for conversion + * If the ADC is not enabled then start using 0th channel + * itself. Otherwise find an empty channel by looking for a + * channel in which the stopbit is set to 1. returns the index + * of the first free channel if succeeds or an error code. + * + * Context: can sleep + * */ +static int find_free_channel(struct snd_soc_codec *sn95031_codec) +{ + int ret = 0, i, value; + + /* check whether ADC is enabled */ + value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1); + + if ((value & SN95031_ADC_ENBL) == 0) + return 0; + + /* ADC is already enabled; Looking for an empty channel */ + for (i = 0; i < SN95031_ADC_CHANLS_MAX; i++) { + value = snd_soc_read(sn95031_codec, + SN95031_ADC_CHNL_START_ADDR + i); + if (value & SN95031_STOPBIT_MASK) { + ret = i; + break; + } + } + return (ret > SN95031_ADC_LOOP_MAX) ? (-EINVAL) : ret; +} + +/* Initialize the ADC for reading micbias values. Can sleep. */ +static int sn95031_initialize_adc(struct snd_soc_codec *sn95031_codec) +{ + int base_addr, chnl_addr; + int value; + static int channel_index; + + /* Index of the first channel in which the stop bit is set */ + channel_index = find_free_channel(sn95031_codec); + if (channel_index < 0) { + pr_err("No free ADC channels"); + return channel_index; + } + + base_addr = SN95031_ADC_CHNL_START_ADDR + channel_index; + + if (!(channel_index == 0 || channel_index == SN95031_ADC_LOOP_MAX)) { + /* Reset stop bit for channels other than 0 and 12 */ + value = snd_soc_read(sn95031_codec, base_addr); + /* Set the stop bit to zero */ + snd_soc_write(sn95031_codec, base_addr, value & 0xEF); + /* Index of the first free channel */ + base_addr++; + channel_index++; + } + + /* Since this is the last channel, set the stop bit + to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ + snd_soc_write(sn95031_codec, base_addr, + SN95031_AUDIO_DETECT_CODE | 0x10); + + chnl_addr = SN95031_ADC_DATA_START_ADDR + 2 * channel_index; + pr_debug("mid_initialize : %x", chnl_addr); + configure_adc(sn95031_codec, 1); + return chnl_addr; +} + + +/* reads the ADC registers and gets the mic bias value in mV. */ +static unsigned int sn95031_get_mic_bias(struct snd_soc_codec *codec) +{ + u16 adc_adr = sn95031_initialize_adc(codec); + u16 adc_val1, adc_val2; + unsigned int mic_bias; + + sn95031_enable_mic_bias(codec); + + /* Enable the sound card for conversion before reading */ + snd_soc_write(codec, SN95031_ADC1CNTL3, 0x05); + /* Re-toggle the RRDATARD bit */ + snd_soc_write(codec, SN95031_ADC1CNTL3, 0x04); + + /* Read the higher bits of data */ + msleep(1000); + adc_val1 = snd_soc_read(codec, adc_adr); + adc_adr++; + adc_val2 = snd_soc_read(codec, adc_adr); + + /* Adding lower two bits to the higher bits */ + mic_bias = (adc_val1 << 2) + (adc_val2 & 3); + mic_bias = (mic_bias * SN95031_ADC_ONE_LSB_MULTIPLIER) / 1000; + pr_debug("mic bias = %dmV\n", mic_bias); + return mic_bias; +} +EXPORT_SYMBOL_GPL(sn95031_get_mic_bias); +/*end - adc helper functions */
static inline unsigned int sn95031_read(struct snd_soc_codec *codec, unsigned int reg) @@ -664,6 +781,8 @@ static inline void enable_jack_btn(struct snd_soc_codec *codec)
static int sn95031_get_headset_state(struct snd_soc_jack *mfld_jack) { + int micbias = sn95031_get_mic_bias(mfld_jack->codec); + /* Defaulting to HEADSET for now. * will change after adding soc-jack detection apis */ int jack_type = SND_JACK_HEADSET; diff --git a/sound/soc/codecs/sn95031.h b/sound/soc/codecs/sn95031.h index 3d6f9bb..47bd6af 100644 --- a/sound/soc/codecs/sn95031.h +++ b/sound/soc/codecs/sn95031.h @@ -96,7 +96,31 @@ #define SN95031_SSR5 0x384 #define SN95031_SSR6 0x385
+/* ADC registers */ + +#define SN95031_ADC1CNTL1 0x1C0 +#define SN95031_ADC_ENBL 0x10 +#define SN95031_ADC_START 0x08 +#define SN95031_ADC1CNTL3 0x1C2 +#define SN95031_ADCTHERM_ENBL 0x04 +#define SN95031_ADCRRDATA_ENBL 0x05 +#define SN95031_STOPBIT_MASK 16 +#define SN95031_ADCTHERM_MASK 4 +#define SN95031_ADC_CHANLS_MAX 15 /* Number of ADC channels */ +#define SN95031_ADC_LOOP_MAX (SN95031_ADC_CHANLS_MAX - 1) +#define SN95031_ADC_NO_LOOP 0x07 #define SN95031_AUDIO_GPIO_CTRL 0x070 + +/* ADC channel code values */ +#define SN95031_AUDIO_DETECT_CODE 0x06 + +/* ADC base addresses */ +#define SN95031_ADC_CHNL_START_ADDR 0x1C5 /* increments by 1 */ +#define SN95031_ADC_DATA_START_ADDR 0x1D4 /* increments by 2 */ +/* multipier to convert to mV */ +#define SN95031_ADC_ONE_LSB_MULTIPLIER 2346 + + struct mfld_jack_data { int intr_id; int micbias_vol; @@ -106,4 +130,5 @@ struct mfld_jack_data { extern void sn95031_jack_detection(struct mfld_jack_data *jack_data);
extern void disable_jack_btn(struct snd_soc_codec *codec); + #endif
On Wed, Feb 09, 2011 at 02:42:27PM +0530, Koul, Vinod wrote:
From: Vinod Koul vinod.koul@intel.com
This patch adds support to read the mic bias voltage when a jack is inserted. It uses ADC to measure.
This looks OK.
This patch adds support to read the mic bias voltage when a jack is inserted. It uses ADC to measure.
This looks OK.
Thanks for the review. I will fix patch 1, and remove the gpios stuff from patch 2. For gpiolib I will send a seprate patch
~Vinod
participants (2)
-
Koul, Vinod
-
Mark Brown