From patchwork Fri Oct 3 21:37:26 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ben Zhang X-Patchwork-Id: 5025231 Return-Path: X-Original-To: patchwork-alsa-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 5C478C11AC for ; Fri, 3 Oct 2014 21:40:28 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 274572022A for ; Fri, 3 Oct 2014 21:40:27 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.kernel.org (Postfix) with ESMTP id 79CAF20219 for ; Fri, 3 Oct 2014 21:40:25 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 79083260515; Fri, 3 Oct 2014 23:40:24 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from alsa0.perex.cz (localhost [IPv6:::1]) by alsa0.perex.cz (Postfix) with ESMTP id 420412604ED; Fri, 3 Oct 2014 23:39:20 +0200 (CEST) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 827022604ED; Fri, 3 Oct 2014 23:39:19 +0200 (CEST) Received: from mail-qg0-f74.google.com (mail-qg0-f74.google.com [209.85.192.74]) by alsa0.perex.cz (Postfix) with ESMTP id E87E3260520 for ; Fri, 3 Oct 2014 23:38:44 +0200 (CEST) Received: by mail-qg0-f74.google.com with SMTP id q108so81162qgd.3 for ; Fri, 03 Oct 2014 14:38:44 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=p4iaC+1f0EZU9FIyjdiVihCMWia2ljacMa+7jUdNdKk=; b=J21kbW8BUtMSSjX2Lp38cobpaTc7kAyOAozkVXOjz7PF2AFOAM67tmy1uyPB+IdxiE zpiiw9FqXCWdsUSRn6KFf5aHBUw9rHVOBdxKGJYo94TfLchJYanKAM4+8RzovL517d2h c4qAEzSBQG9m2P/E22soALQIuvwubiOVfhKEMyEivc1zbxA5WLyV4A+Lpx9iPCKGbClM ZVmOHCahJpbvpnCZTBpNChH61YBIMvjB6aviV2enqSyglR4szmKydxiEqKhXZRNZ4Awa vPQ7mCsr2kXzh9Fo/8n+B3ZodzUNpSyNXy6L25+7FzXFGDUWuPMB8c3/eY1DK7U0b37k XrRA== X-Gm-Message-State: ALoCoQnAAGRN2tpqqY4FMWteKUvYOJay/tqUycCyDlk9RD5yrls5DLB7WNwdNYyoypYJFjKp7qwqZEwz443zW+MVX7C4YKuU7zDNlYatVMgD/yslHgVKxFFXaDt+sWvXwssHqCfsno75O8KlagK3MlsBj58/DZ+cVKDX1OBPm9lHhxlhQqzNkWY= X-Received: by 10.236.207.164 with SMTP id n24mr874734yho.5.1412372323792; Fri, 03 Oct 2014 14:38:43 -0700 (PDT) Received: from corpmail-nozzle1-2.hot.corp.google.com ([100.108.1.103]) by gmr-mx.google.com with ESMTPS id f102si394612yhp.7.2014.10.03.14.38.43 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 03 Oct 2014 14:38:43 -0700 (PDT) Received: from benzh.mtv.corp.google.com ([172.22.65.67]) by corpmail-nozzle1-2.hot.corp.google.com with ESMTP id vYWIXUpu.1; Fri, 03 Oct 2014 14:38:43 -0700 Received: by benzh.mtv.corp.google.com (Postfix, from userid 215530) id ED8E7E0A4B; Fri, 3 Oct 2014 14:38:42 -0700 (PDT) From: Ben Zhang To: alsa-devel@alsa-project.org Date: Fri, 3 Oct 2014 14:37:26 -0700 Message-Id: <1412372246-10489-3-git-send-email-benzh@chromium.org> X-Mailer: git-send-email 2.1.0.rc2.206.gedb03e5 In-Reply-To: <1412372246-10489-1-git-send-email-benzh@chromium.org> References: <1412372246-10489-1-git-send-email-benzh@chromium.org> Cc: Oder Chiou , Bard Liao , Mark Brown , linux-kernel@vger.kernel.org, Ben Zhang Subject: [alsa-devel] [PATCH 3/3] ASoC: rt5677: Add jack detection support X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP This patch adds support for jack detection using GPIOs on the codec. Plug detect signal and mic present signal can be routed from the physical audio jack to GPIOs on the codec. The codec is configured so that upon signal change, an IRQ(GPIO1) is fired. The codec interrupt handler reads related status registers, figures out what has been plugged/unplugged from the audio jack, and report jack states via snd_soc_jack_report(). ASoC machine driver should register audio jacks for detection with the codec driver using rt5677_register_jack_detect(). Board setup code should assign GPIOs receiving plug detect/mic present signal to the codec device itself, via Device Tree, ACPI or platform data. The corresponding GPIO indexes are: RT5677_GPIO_PLUG_DET - 0 RT5677_GPIO_MIC_PRESENT_L - 1 Signed-off-by: Ben Zhang --- sound/soc/codecs/rt5677.c | 179 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/rt5677.h | 92 ++++++++++++++++++++++++ 2 files changed, 271 insertions(+) diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 19dbb8a..99f4d1a 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "rl6231.h" #include "rt5677.h" @@ -3341,6 +3342,25 @@ static void rt5677_free_gpio(struct i2c_client *i2c) gpiochip_remove(&rt5677->gpio_chip); } + +static void rt5677_request_codec_gpios(struct rt5677_priv *rt5677, + struct i2c_client *i2c) +{ + struct gpio_desc *desc; + + desc = devm_gpiod_get_index(&i2c->dev, "RT5677_GPIO", + RT5677_GPIO_PLUG_DET); + if (!IS_ERR(desc) && !gpiod_direction_input(desc)) + rt5677->gpio_plug_det = desc_to_gpio(desc) + - rt5677->gpio_chip.base + 1; + + desc = devm_gpiod_get_index(&i2c->dev, "RT5677_GPIO", + RT5677_GPIO_MIC_PRESENT_L); + if (!IS_ERR(desc) && !gpiod_direction_input(desc)) + rt5677->gpio_mic_present_l = desc_to_gpio(desc) + - rt5677->gpio_chip.base + 1; +} + #else static void rt5677_init_gpio(struct i2c_client *i2c) { @@ -3349,8 +3369,155 @@ static void rt5677_init_gpio(struct i2c_client *i2c) static void rt5677_free_gpio(struct i2c_client *i2c) { } +static void rt5677_request_codec_gpios(struct rt5677_priv *rt5677, + struct i2c_client *i2c) +{ +} #endif +static void rt5677_report_jack_status(struct rt5677_priv *rt5677, int reg_irq) +{ + /* reg_irq: IRQ Control register MX-BDh */ + int polarity; + if (reg_irq & RT5677_JD2_STATUS_MASK) { + polarity = reg_irq & RT5677_JD2_POLARITY_MASK; + if (rt5677->headphone_jack) { + snd_soc_jack_report(rt5677->headphone_jack, + polarity ? SND_JACK_HEADPHONE : 0, + SND_JACK_HEADPHONE); + } + } + + if (reg_irq & RT5677_JD3_STATUS_MASK) { + polarity = reg_irq & RT5677_JD3_POLARITY_MASK; + if (rt5677->mic_jack) { + snd_soc_jack_report(rt5677->mic_jack, + polarity ? 0 : SND_JACK_MICROPHONE, + SND_JACK_MICROPHONE); + } + } +} + +static irqreturn_t rt5677_irq(int unused, void *data) +{ + struct rt5677_priv *rt5677 = data; + int ret = 0, i; + bool irq_fired; + int reg_irq; + + /* + * Loop to handle interrupts until the last i2c read shows no pending + * irqs with a safeguard of 20 loops + */ + for (i = 0; i < 20; i++) { + /* Read interrupt status */ + ret = regmap_read(rt5677->regmap, RT5677_IRQ_CTRL1, ®_irq); + if (ret) + break; + + /* Flip polarity for interrupts that just fired */ + irq_fired = false; + if (reg_irq & RT5677_JD2_STATUS_MASK) { + reg_irq ^= RT5677_JD2_POLARITY_MASK; + irq_fired = true; + } + + if (reg_irq & RT5677_JD3_STATUS_MASK) { + reg_irq ^= RT5677_JD3_POLARITY_MASK; + irq_fired = true; + } + + if (!irq_fired) + break; + + /* Clear interrupts */ + ret = regmap_write(rt5677->regmap, RT5677_IRQ_CTRL1, reg_irq); + if (ret) + break; + + /* Process interrupts */ + rt5677_report_jack_status(rt5677, reg_irq); + } + return IRQ_HANDLED; +} + +int rt5677_register_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *headphone_jack, struct snd_soc_jack *mic_jack) +{ + struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + + rt5677->headphone_jack = headphone_jack; + rt5677->mic_jack = mic_jack; + + /* Report initial jack status */ + rt5677_irq(0, rt5677); + return 0; +} +EXPORT_SYMBOL_GPL(rt5677_register_jack_detect); + +static void rt5677_setup_jack_detect(struct snd_soc_codec *codec, + struct rt5677_priv *rt5677) +{ + unsigned int val; + + /* Skip jack detect setup if there are no assigned GPIOs */ + if (!rt5677->gpio_plug_det && !rt5677->gpio_mic_present_l) + return; + /* GPIO range check */ + if (rt5677->gpio_plug_det < 4 || rt5677->gpio_plug_det > 6) { + dev_err(codec->dev, "PLUG_DET can only use GPIO 4~6, given %d\n", + rt5677->gpio_plug_det); + } + if (rt5677->gpio_mic_present_l < 4 || rt5677->gpio_mic_present_l > 6) { + dev_err(codec->dev, "MIC_PRESENT_L can only use GPIO 4~6, given %d\n", + rt5677->gpio_mic_present_l); + } + + /* + * Select RC as the debounce clock so that GPIO works even when + * MCLK is gated which happens when there is no audio stream + * (SND_SOC_BIAS_OFF). + */ + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, + RT5677_IRQ_DEBOUNCE_SEL_MASK, + RT5677_IRQ_DEBOUNCE_SEL_RC); + /* Enable auto power on RC when GPIO states are changed */ + val = RT5677_AUTO_RC_ON_GPIO_CHANGE_MASK; + if (rt5677->gpio_plug_det) + val |= 1 << (rt5677->gpio_plug_det - 1); + if (rt5677->gpio_mic_present_l) + val |= 1 << (rt5677->gpio_mic_present_l - 1); + regmap_update_bits(rt5677->regmap, RT5677_GEN_CTRL1, val, val); + + /* + * Select jack detection source + * JD2: PLUG_DET + * JD3: !MIC_PRESENT_L + */ + val = 0; + if (rt5677->gpio_plug_det) + val |= (rt5677->gpio_plug_det - 3) + << RT5677_JD2_SRC_SEL_SFT; + if (rt5677->gpio_mic_present_l) + val |= (rt5677->gpio_mic_present_l - 3) + << RT5677_JD3_SRC_SEL_SFT; + regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, + RT5677_JD2_SRC_SEL_MASK | RT5677_JD3_SRC_SEL_MASK, + val); + + /* Enable JD2 and/or JD3 as IRQ source */ + val = 0; + if (rt5677->gpio_plug_det) + val |= RT5677_JD2_IRQ_EN_MASK; + if (rt5677->gpio_mic_present_l) + val |= RT5677_JD3_IRQ_EN_MASK | RT5677_JD3_POLARITY_INV; + regmap_update_bits(rt5677->regmap, RT5677_IRQ_CTRL1, val, val); + + /* Set GPIO1 to be IRQ */ + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, + RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); +} + static int rt5677_probe(struct snd_soc_codec *codec) { struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); @@ -3372,6 +3539,7 @@ static int rt5677_probe(struct snd_soc_codec *codec) regmap_write(rt5677->regmap, RT5677_DIG_MISC, 0x0020); regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x0c00); + rt5677_setup_jack_detect(codec, rt5677); return 0; } @@ -3668,6 +3836,17 @@ static int rt5677_i2c_probe(struct i2c_client *i2c, } rt5677_init_gpio(i2c); + rt5677_request_codec_gpios(rt5677, i2c); + + if (i2c->irq) { + ret = request_threaded_irq(i2c->irq, NULL, rt5677_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "rt5677", rt5677); + if (ret) { + dev_err(&i2c->dev, "Failed to request IRQ: %d\n", ret); + return ret; + } + } return snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5677, rt5677_dai, ARRAY_SIZE(rt5677_dai)); diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h index 99fd023..2ec0844 100644 --- a/sound/soc/codecs/rt5677.h +++ b/sound/soc/codecs/rt5677.h @@ -1368,6 +1368,47 @@ #define RT5677_SEL_SRC_IB01 (0x1 << 0) #define RT5677_SEL_SRC_IB01_SFT 0 +/* Jack Detection Control 1 (0xb5) */ +#define RT5677_JD1_SRC_SEL_MASK (0x3 << 14) +#define RT5677_JD1_SRC_GPIO1 (0x1 << 14) +#define RT5677_JD1_SRC_GPIO2 (0x2 << 14) +#define RT5677_JD1_SRC_GPIO3 (0x3 << 14) +#define RT5677_JD2_SRC_SEL_MASK (0x3 << 12) +#define RT5677_JD2_SRC_SEL_SFT 12 +#define RT5677_JD2_SRC_GPIO4 (0x1 << 12) +#define RT5677_JD2_SRC_GPIO5 (0x2 << 12) +#define RT5677_JD2_SRC_GPIO6 (0x3 << 12) +#define RT5677_JD3_SRC_SEL_MASK (0x3 << 10) +#define RT5677_JD3_SRC_SEL_SFT 10 +#define RT5677_JD3_SRC_GPIO4 (0x1 << 10) +#define RT5677_JD3_SRC_GPIO5 (0x2 << 10) +#define RT5677_JD3_SRC_GPIO6 (0x3 << 10) + +/* IRQ Control 1 (0xbd) */ +#define RT5677_JD1_STATUS_MASK (0x1 << 15) +#define RT5677_JD1_STATUS_SFT 15 +#define RT5677_JD1_IRQ_EN_MASK (0x1 << 14) +#define RT5677_JD1_IRQ_EN_SFT 14 +#define RT5677_JD1_POLARITY_MASK (0x1 << 12) +#define RT5677_JD1_POLARITY_NOR (0x0 << 12) +#define RT5677_JD1_POLARITY_INV (0x1 << 12) + +#define RT5677_JD2_STATUS_MASK (0x1 << 11) +#define RT5677_JD2_STATUS_SFT 11 +#define RT5677_JD2_IRQ_EN_MASK (0x1 << 10) +#define RT5677_JD2_IRQ_EN_SFT 10 +#define RT5677_JD2_POLARITY_MASK (0x1 << 8) +#define RT5677_JD2_POLARITY_NOR (0x0 << 8) +#define RT5677_JD2_POLARITY_INV (0x1 << 8) + +#define RT5677_JD3_STATUS_MASK (0x1 << 3) +#define RT5677_JD3_STATUS_SFT 3 +#define RT5677_JD3_IRQ_EN_MASK (0x1 << 2) +#define RT5677_JD3_IRQ_EN_SFT 2 +#define RT5677_JD3_POLARITY_MASK (0x1 << 0) +#define RT5677_JD3_POLARITY_NOR (0x0 << 0) +#define RT5677_JD3_POLARITY_INV (0x1 << 0) + /* GPIO status (0xbf) */ #define RT5677_GPIO6_STATUS_MASK (0x1 << 5) #define RT5677_GPIO6_STATUS_SFT 5 @@ -1472,6 +1513,28 @@ #define RT5677_GPIO6_P_NOR (0x0 << 0) #define RT5677_GPIO6_P_INV (0x1 << 0) +/* General Control (0xfa) */ +#define RT5677_IRQ_DEBOUNCE_SEL_MASK (0x3 << 3) +#define RT5677_IRQ_DEBOUNCE_SEL_MCLK (0x0 << 3) +#define RT5677_IRQ_DEBOUNCE_SEL_RC (0x1 << 3) +#define RT5677_IRQ_DEBOUNCE_SEL_SLIM (0x2 << 3) + +/* General Control (0xfb) */ +#define RT5677_AUTO_RC_ON_GPIO_CHANGE_MASK (0x1 << 7) +#define RT5677_AUTO_RC_ON_GPIO_CHANGE_SFT 7 +#define RT5677_AUTO_RC_ON_GPIO6_MASK (0x1 << 5) +#define RT5677_AUTO_RC_ON_GPIO6_SFT 5 +#define RT5677_AUTO_RC_ON_GPIO5_MASK (0x1 << 4) +#define RT5677_AUTO_RC_ON_GPIO5_SFT 4 +#define RT5677_AUTO_RC_ON_GPIO4_MASK (0x1 << 3) +#define RT5677_AUTO_RC_ON_GPIO4_SFT 3 +#define RT5677_AUTO_RC_ON_GPIO3_MASK (0x1 << 2) +#define RT5677_AUTO_RC_ON_GPIO3_SFT 2 +#define RT5677_AUTO_RC_ON_GPIO2_MASK (0x1 << 1) +#define RT5677_AUTO_RC_ON_GPIO2_SFT 1 +#define RT5677_AUTO_RC_ON_GPIO1_MASK (0x1 << 0) +#define RT5677_AUTO_RC_ON_GPIO1_SFT 0 + /* Virtual DSP Mixer Control (0xf7 0xf8 0xf9) */ #define RT5677_DSP_IB_01_H (0x1 << 15) #define RT5677_DSP_IB_01_H_SFT 15 @@ -1542,10 +1605,20 @@ enum { RT5677_GPIO_NUM, }; +enum { + RT5677_GPIO_PLUG_DET, + RT5677_GPIO_MIC_PRESENT_L, + RT5677_GPIO_HOTWORD_DET_L, + RT5677_GPIO_DSP_INT, + RT5677_GPIO_HP_AMP_SHDN_L, +}; + struct rt5677_priv { struct snd_soc_codec *codec; struct rt5677_platform_data pdata; struct regmap *regmap; + struct snd_soc_jack *headphone_jack; + struct snd_soc_jack *mic_jack; int sysclk; int sysclk_src; @@ -1559,6 +1632,25 @@ struct rt5677_priv { #ifdef CONFIG_GPIOLIB struct gpio_chip gpio_chip; #endif + /* + * Predefined common codec GPIO tasks. Codec GPIO number is + * 1-based, 0 means no GPIO is assigned to the corresponding task. + * + * gpio_plug_det: A codec GPIO used for headphone jack detect + * Low - headphone is not present + * High - headphone is plugged in + * Supported GPIO: 4,5,6 + * + * gpio_mic_present_l: A codec GPIO used for mic jack detect + * Low - mic is plugged in + * High - mic is not present + * Supported GPIO: 4,5,6 + */ + int gpio_plug_det; + int gpio_mic_present_l; }; +int rt5677_register_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *headphone_jack, struct snd_soc_jack *mic_jack); + #endif /* __RT5677_H__ */