[alsa-devel] [PATCH v5 0/3] Fix jack detection for Chromebook Pixel
Headphone/mic jack detection doesn't work on the Chromebook Pixel 2015.
This patch changes the irq implementation to support polarity flipping and fixes the configuration code so that correct GPIO pins are read from ACPI.
With this series, plugging and unplugging the headphone jack switches between headphones and speakers automatically, and headset microphones are also detected.
v5: - Fix void* parameter to devm_request_threaded_irq - Correct authorship for patch 2/3
v4: - Fix incorrect void* cast in rt5677_irq()
v3: - Update commit message for patch 1/3 to clarify why we implement our own irq_chip.
v2: - Split IRQ change into two patches: adding and fixing potential race - Change config reading code to try both DT and ACPI style names
Ben Zhang (2): ASoC: rt5677: allow multiple interrupt sources ASoC: rt5677: handle concurrent interrupts
Fletcher Woodruff (1): ASoC: rt5677: fall back to DT prop names on error
sound/soc/codecs/rt5677.c | 346 ++++++++++++++++++++++++++------------ sound/soc/codecs/rt5677.h | 14 +- 2 files changed, 256 insertions(+), 104 deletions(-)
From: Ben Zhang benzh@chromium.org
This patch allows headphone plug detect and mic present detect to be enabled at the same time. This patch implements an irq_chip with irq_domain directly instead of using regmap-irq, so that interrupt source line polarities can be flipped to support irq sharing.
This patch does not add polarity flipping support within regmap-irq because there is extra work that must be done within the irq handler to support hotword detection. On the Chromebook Pixel, the firmware will disconnect GPIO1 from the jack detection irq when a hotword is detected and trigger the interrupt handler. Inside the handler, we will need to detect this, report the hotword event, and re-connect GPIO1 to the jack detection irq.
Signed-off-by: Ben Zhang benzh@chromium.org Signed-off-by: Fletcher Woodruff fletcherw@chromium.org --- sound/soc/codecs/rt5677.c | 256 ++++++++++++++++++++++++++++---------- sound/soc/codecs/rt5677.h | 14 ++- 2 files changed, 203 insertions(+), 67 deletions(-)
diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 9b7a1833d3316c..7d2039f3f67e75 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -23,6 +23,10 @@ #include <linux/firmware.h> #include <linux/of_device.h> #include <linux/property.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/workqueue.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -4620,7 +4624,6 @@ static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) { struct rt5677_priv *rt5677 = gpiochip_get_data(chip); - struct regmap_irq_chip_data *data = rt5677->irq_data; int irq;
if ((rt5677->pdata.jd1_gpio == 1 && offset == RT5677_GPIO1) || @@ -4646,7 +4649,7 @@ static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) return -ENXIO; }
- return regmap_irq_get_virq(data, irq); + return irq_create_mapping(rt5677->domain, irq); }
static const struct gpio_chip rt5677_template_chip = { @@ -4716,37 +4719,13 @@ static int rt5677_probe(struct snd_soc_component *component)
snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF);
- regmap_write(rt5677->regmap, RT5677_DIG_MISC, 0x0020); + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, + ~RT5677_IRQ_DEBOUNCE_SEL_MASK, 0x0020); regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x0c00);
for (i = 0; i < RT5677_GPIO_NUM; i++) rt5677_gpio_config(rt5677, i, rt5677->pdata.gpio_config[i]);
- if (rt5677->irq_data) { - regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, 0x8000, - 0x8000); - regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x0018, - 0x0008); - - if (rt5677->pdata.jd1_gpio) - regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, - RT5677_SEL_GPIO_JD1_MASK, - rt5677->pdata.jd1_gpio << - RT5677_SEL_GPIO_JD1_SFT); - - if (rt5677->pdata.jd2_gpio) - regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, - RT5677_SEL_GPIO_JD2_MASK, - rt5677->pdata.jd2_gpio << - RT5677_SEL_GPIO_JD2_SFT); - - if (rt5677->pdata.jd3_gpio) - regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, - RT5677_SEL_GPIO_JD3_MASK, - rt5677->pdata.jd3_gpio << - RT5677_SEL_GPIO_JD3_SFT); - } - mutex_init(&rt5677->dsp_cmd_lock); mutex_init(&rt5677->dsp_pri_lock);
@@ -5063,65 +5042,210 @@ static void rt5677_read_device_properties(struct rt5677_priv *rt5677, &rt5677->pdata.jd3_gpio); }
-static struct regmap_irq rt5677_irqs[] = { +struct rt5677_irq_desc { + unsigned int enable_mask; + unsigned int status_mask; + unsigned int polarity_mask; +}; + +static const struct rt5677_irq_desc rt5677_irq_descs[] = { [RT5677_IRQ_JD1] = { - .reg_offset = 0, - .mask = RT5677_EN_IRQ_GPIO_JD1, + .enable_mask = RT5677_EN_IRQ_GPIO_JD1, + .status_mask = RT5677_STA_GPIO_JD1, + .polarity_mask = RT5677_INV_GPIO_JD1, }, [RT5677_IRQ_JD2] = { - .reg_offset = 0, - .mask = RT5677_EN_IRQ_GPIO_JD2, + .enable_mask = RT5677_EN_IRQ_GPIO_JD2, + .status_mask = RT5677_STA_GPIO_JD2, + .polarity_mask = RT5677_INV_GPIO_JD2, }, [RT5677_IRQ_JD3] = { - .reg_offset = 0, - .mask = RT5677_EN_IRQ_GPIO_JD3, + .enable_mask = RT5677_EN_IRQ_GPIO_JD3, + .status_mask = RT5677_STA_GPIO_JD3, + .polarity_mask = RT5677_INV_GPIO_JD3, }, };
-static struct regmap_irq_chip rt5677_irq_chip = { - .name = "rt5677", - .irqs = rt5677_irqs, - .num_irqs = ARRAY_SIZE(rt5677_irqs), +static irqreturn_t rt5677_irq(int unused, void *data) +{ + struct rt5677_priv *rt5677 = data; + int ret = 0, i, reg_irq, virq; + bool irq_fired; + + mutex_lock(&rt5677->irq_lock); + /* Read interrupt status */ + ret = regmap_read(rt5677->regmap, RT5677_IRQ_CTRL1, ®_irq); + if (ret) { + dev_err(rt5677->component->dev, + "Failed to read IRQ status: %d\n", + ret); + goto exit; + } + /* + * Clear the interrupt by flipping the polarity of the + * interrupt source lines that just fired + */ + irq_fired = false; + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if (reg_irq & rt5677_irq_descs[i].status_mask) { + reg_irq ^= rt5677_irq_descs[i].polarity_mask; + irq_fired = true; + } + } + if (!irq_fired) + goto exit;
- .num_regs = 1, - .status_base = RT5677_IRQ_CTRL1, - .mask_base = RT5677_IRQ_CTRL1, - .mask_invert = 1, -}; + ret = regmap_write(rt5677->regmap, RT5677_IRQ_CTRL1, reg_irq); + if (ret) { + dev_err(rt5677->component->dev, + "Failed to update IRQ status: %d\n", + ret); + goto exit; + } + + /* Process interrupts */ + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if ((reg_irq & rt5677_irq_descs[i].enable_mask) && + (reg_irq & rt5677_irq_descs[i].status_mask)) { + virq = irq_find_mapping(rt5677->domain, i); + if (virq) + handle_nested_irq(virq); + } + } +exit: + mutex_unlock(&rt5677->irq_lock); + return IRQ_HANDLED; +}
-static int rt5677_init_irq(struct i2c_client *i2c) +static void rt5677_irq_work(struct work_struct *work) { - int ret; - struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); + struct rt5677_priv *rt5677 = + container_of(work, struct rt5677_priv, irq_work.work);
- if (!rt5677->pdata.jd1_gpio && - !rt5677->pdata.jd2_gpio && - !rt5677->pdata.jd3_gpio) - return 0; + rt5677_irq(0, rt5677); +}
- if (!i2c->irq) { - dev_err(&i2c->dev, "No interrupt specified\n"); - return -EINVAL; - } +static void rt5677_irq_bus_lock(struct irq_data *data) +{ + struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data);
- ret = regmap_add_irq_chip(rt5677->regmap, i2c->irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, - &rt5677_irq_chip, &rt5677->irq_data); + mutex_lock(&rt5677->irq_lock); +}
- if (ret != 0) { - dev_err(&i2c->dev, "Failed to register IRQ chip: %d\n", ret); - return ret; - } +static void rt5677_irq_bus_sync_unlock(struct irq_data *data) +{ + struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data); + + regmap_update_bits(rt5677->regmap, RT5677_IRQ_CTRL1, + RT5677_EN_IRQ_GPIO_JD1 | RT5677_EN_IRQ_GPIO_JD2 | + RT5677_EN_IRQ_GPIO_JD3, rt5677->irq_en); + mutex_unlock(&rt5677->irq_lock); +}
+static void rt5677_irq_enable(struct irq_data *data) +{ + struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data); + + rt5677->irq_en |= rt5677_irq_descs[data->hwirq].enable_mask; +} + +static void rt5677_irq_disable(struct irq_data *data) +{ + struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data); + + rt5677->irq_en &= ~rt5677_irq_descs[data->hwirq].enable_mask; +} + +static struct irq_chip rt5677_irq_chip = { + .name = "rt5677_irq_chip", + .irq_bus_lock = rt5677_irq_bus_lock, + .irq_bus_sync_unlock = rt5677_irq_bus_sync_unlock, + .irq_disable = rt5677_irq_disable, + .irq_enable = rt5677_irq_enable, +}; + +static int rt5677_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct rt5677_priv *rt5677 = h->host_data; + + irq_set_chip_data(virq, rt5677); + irq_set_chip(virq, &rt5677_irq_chip); + irq_set_nested_thread(virq, 1); + irq_set_noprobe(virq); return 0; }
-static void rt5677_free_irq(struct i2c_client *i2c) + +static const struct irq_domain_ops rt5677_domain_ops = { + .map = rt5677_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static void rt5677_init_irq(struct i2c_client *i2c) +{ + struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); + int ret; + unsigned int jd_mask = 0, jd_val = 0; + + /* No irq has been assigned to the codec */ + if (!i2c->irq) + return; + + mutex_init(&rt5677->irq_lock); + INIT_DELAYED_WORK(&rt5677->irq_work, rt5677_irq_work); + + /* + * 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 */ + regmap_update_bits(rt5677->regmap, RT5677_GEN_CTRL1, 0xff, 0xff); + + /* Select and enable jack detection sources per platform data */ + if (rt5677->pdata.jd1_gpio) { + jd_mask |= RT5677_SEL_GPIO_JD1_MASK; + jd_val |= rt5677->pdata.jd1_gpio << RT5677_SEL_GPIO_JD1_SFT; + } + if (rt5677->pdata.jd2_gpio) { + jd_mask |= RT5677_SEL_GPIO_JD2_MASK; + jd_val |= rt5677->pdata.jd2_gpio << RT5677_SEL_GPIO_JD2_SFT; + } + if (rt5677->pdata.jd3_gpio) { + jd_mask |= RT5677_SEL_GPIO_JD3_MASK; + jd_val |= rt5677->pdata.jd3_gpio << RT5677_SEL_GPIO_JD3_SFT; + } + regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, jd_mask, jd_val); + + /* Set GPIO1 pin to be IRQ output */ + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, + RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); + + /* Ready to listen for interrupts */ + rt5677->domain = irq_domain_add_linear(i2c->dev.of_node, + RT5677_IRQ_NUM, &rt5677_domain_ops, rt5677); + if (!rt5677->domain) { + dev_err(&i2c->dev, "Failed to create IRQ domain\n"); + return; + } + ret = devm_request_threaded_irq(&i2c->dev, 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; + } +} + +static void rt5677_irq_exit(struct i2c_client *i2c) { struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c);
- if (rt5677->irq_data) - regmap_del_irq_chip(i2c->irq, rt5677->irq_data); + cancel_delayed_work_sync(&rt5677->irq_work); }
static int rt5677_i2c_probe(struct i2c_client *i2c) @@ -5259,7 +5383,7 @@ static int rt5677_i2c_probe(struct i2c_client *i2c)
static int rt5677_i2c_remove(struct i2c_client *i2c) { - rt5677_free_irq(i2c); + rt5677_irq_exit(i2c); rt5677_free_gpio(i2c);
return 0; diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h index 183d92b030459f..9c73bea8635508 100644 --- a/sound/soc/codecs/rt5677.h +++ b/sound/soc/codecs/rt5677.h @@ -1636,6 +1636,12 @@ #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) + /* Virtual DSP Mixer Control (0xf7 0xf8 0xf9) */ #define RT5677_DSP_IB_01_H (0x1 << 15) #define RT5677_DSP_IB_01_H_SFT 15 @@ -1713,6 +1719,7 @@ enum { RT5677_IRQ_JD1, RT5677_IRQ_JD2, RT5677_IRQ_JD3, + RT5677_IRQ_NUM, };
enum rt5677_type { @@ -1811,9 +1818,14 @@ struct rt5677_priv { struct gpio_chip gpio_chip; #endif bool dsp_vad_en; - struct regmap_irq_chip_data *irq_data; bool is_dsp_mode; bool is_vref_slow; + + /* Interrupt handling */ + struct irq_domain *domain; + struct mutex irq_lock; + unsigned int irq_en; + struct delayed_work irq_work; };
int rt5677_sel_asrc_clk_src(struct snd_soc_component *component,
On Tue, May 07, 2019 at 04:01:13PM -0600, Fletcher Woodruff wrote:
This patch does not add polarity flipping support within regmap-irq because there is extra work that must be done within the irq handler to support hotword detection. On the Chromebook Pixel, the firmware will disconnect GPIO1 from the jack detection irq when a hotword is detected and trigger the interrupt handler. Inside the handler, we will need to detect this, report the hotword event, and re-connect GPIO1 to the jack detection irq.
Please have a conversation with your firmware team about the concept of abstraction - this is clearly a problematic thing to do as it's causing the state of the system to change for devices that are mostly managed from the operating system. It's not clear to me that this shouldn't be split off somehow so that it doesn't impact other systems using this hardware.
I'm actually not entirely clear what the code that does the "reconnect GPIO1 to the jack detection IRQ" bit is, I couldn't find anything outside of the initial probe.
- if (rt5677->irq_data) {
regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, 0x8000,
0x8000);
regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x0018,
0x0008);
if (rt5677->pdata.jd1_gpio)
regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1,
RT5677_SEL_GPIO_JD1_MASK,
rt5677->pdata.jd1_gpio <<
RT5677_SEL_GPIO_JD1_SFT);
if (rt5677->pdata.jd2_gpio)
regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1,
RT5677_SEL_GPIO_JD2_MASK,
rt5677->pdata.jd2_gpio <<
RT5677_SEL_GPIO_JD2_SFT);
if (rt5677->pdata.jd3_gpio)
regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1,
RT5677_SEL_GPIO_JD3_MASK,
rt5677->pdata.jd3_gpio <<
RT5677_SEL_GPIO_JD3_SFT);
- }
There's a lot of refactoring in the patch here which makes it very hard to follow what the actual change is.
}
- }
+exit:
- mutex_unlock(&rt5677->irq_lock);
- return IRQ_HANDLED;
+}
We uncondtionally report the interrupt as handled?
+static void rt5677_irq_work(struct work_struct *work) {
- int ret;
- struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c);
- struct rt5677_priv *rt5677 =
container_of(work, struct rt5677_priv, irq_work.work);
- if (!rt5677->pdata.jd1_gpio &&
!rt5677->pdata.jd2_gpio &&
!rt5677->pdata.jd3_gpio)
return 0;
- rt5677_irq(0, rt5677);
+}
I couldn't find anything that schedules this. What is it doing, why is it here (and this is an example of a really complex to review bit of the change due to refactoring BTW, the diff is really unhelpful)?
+static void rt5677_irq_bus_sync_unlock(struct irq_data *data) +{
- struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data);
- regmap_update_bits(rt5677->regmap, RT5677_IRQ_CTRL1,
RT5677_EN_IRQ_GPIO_JD1 | RT5677_EN_IRQ_GPIO_JD2 |
RT5677_EN_IRQ_GPIO_JD3, rt5677->irq_en);
- mutex_unlock(&rt5677->irq_lock);
+}
Is this the bit that reenables the interrupt? Isn't this just a quirk to rewrite the masks frequently, that'd seem easier than doing so much open coding?
- /* Select and enable jack detection sources per platform data */
- if (rt5677->pdata.jd1_gpio) {
jd_mask |= RT5677_SEL_GPIO_JD1_MASK;
jd_val |= rt5677->pdata.jd1_gpio << RT5677_SEL_GPIO_JD1_SFT;
- }
- if (rt5677->pdata.jd2_gpio) {
jd_mask |= RT5677_SEL_GPIO_JD2_MASK;
jd_val |= rt5677->pdata.jd2_gpio << RT5677_SEL_GPIO_JD2_SFT;
- }
- if (rt5677->pdata.jd3_gpio) {
jd_mask |= RT5677_SEL_GPIO_JD3_MASK;
jd_val |= rt5677->pdata.jd3_gpio << RT5677_SEL_GPIO_JD3_SFT;
- }
- regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, jd_mask, jd_val);
- /* Set GPIO1 pin to be IRQ output */
- regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1,
RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ);
Are other GPIO outputs supported by the chip? How does this interact with the jdN_gpio settings above?
From: Mark Brown broonie@kernel.org Date: Wed, May 8, 2019 at 12:36 AM To: Fletcher Woodruff Cc: linux-kernel@vger.kernel.org, Ben Zhang, Jaroslav Kysela, Liam Girdwood, Oder Chiou, Takashi Iwai, Curtis Malainey, alsa-devel@alsa-project.org
On Tue, May 07, 2019 at 04:01:13PM -0600, Fletcher Woodruff wrote:
This patch does not add polarity flipping support within regmap-irq because there is extra work that must be done within the irq handler to support hotword detection. On the Chromebook Pixel, the firmware will disconnect GPIO1 from the jack detection irq when a hotword is detected and trigger the interrupt handler. Inside the handler, we will need to detect this, report the hotword event, and re-connect GPIO1 to the jack detection irq.
Please have a conversation with your firmware team about the concept of abstraction - this is clearly a problematic thing to do as it's causing the state of the system to change for devices that are mostly managed from the operating system. It's not clear to me that this shouldn't be split off somehow so that it doesn't impact other systems using this hardware.
Pixelbooks (Samus Chromebook) are the only devices that use this part. Realtek has confirmed this. Therefore we only have to worry about breaking ourselves. That being said I agree there is likely a better way to handle general abstraction here. We will need the explicit irq handling since I will be following these patches up with patches that enable hotwording on the codec (we will be sending the firmware to linux-firmware as well that is needed for the process.)
On Wed, May 08, 2019 at 02:39:32PM -0700, Curtis Malainey wrote:
Pixelbooks (Samus Chromebook) are the only devices that use this part. Realtek has confirmed this. Therefore we only have to worry about breaking ourselves. That being said I agree there is likely a better
And there are no other parts that are software compatible enough to share the same driver?
way to handle general abstraction here. We will need the explicit irq handling since I will be following these patches up with patches that enable hotwording on the codec (we will be sending the firmware to linux-firmware as well that is needed for the process.)
OK. Like I said it might also be clearer split into multiple patches, it was just really difficult to tell what was going on with the diff there.
From: Mark Brown broonie@kernel.org Date: Wed, May 8, 2019 at 7:33 PM To: Curtis Malainey Cc: Fletcher Woodruff, Linux Kernel Mailing List, Ben Zhang, Jaroslav Kysela, Liam Girdwood, Oder Chiou, Takashi Iwai, Curtis Malainey, alsa-devel@alsa-project.org
On Wed, May 08, 2019 at 02:39:32PM -0700, Curtis Malainey wrote:
Pixelbooks (Samus Chromebook) are the only devices that use this part. Realtek has confirmed this. Therefore we only have to worry about breaking ourselves. That being said I agree there is likely a better
And there are no other parts that are software compatible enough to share the same driver?
the rt5676 can use this driver, but from my discussions with Realtek, Samus is the only active consumer of this driver.
way to handle general abstraction here. We will need the explicit irq handling since I will be following these patches up with patches that enable hotwording on the codec (we will be sending the firmware to linux-firmware as well that is needed for the process.)
OK. Like I said it might also be clearer split into multiple patches, it was just really difficult to tell what was going on with the diff there.
From: Ben Zhang benzh@chromium.org
The rt5677 driver writes to the IRQ control register within the IRQ handler in order to flip the polarity of the interrupts that have been signalled. If an interrupt fires in the interval between the regmap_read and the regmap_write, it will not trigger a new call to rt5677_irq.
Add a bounded loop to rt5677_irq that keeps checking interrupts until none are seen, so that any interrupts that are signalled in that interval are correctly handled.
Signed-off-by: Ben Zhang benzh@chromium.org Signed-off-by: Fletcher Woodruff fletcherw@chromium.org --- sound/soc/codecs/rt5677.c | 84 +++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 34 deletions(-)
diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 7d2039f3f67e75..091ef3e78fe3d2 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5069,47 +5069,63 @@ static const struct rt5677_irq_desc rt5677_irq_descs[] = { static irqreturn_t rt5677_irq(int unused, void *data) { struct rt5677_priv *rt5677 = data; - int ret = 0, i, reg_irq, virq; + int ret = 0, loop, i, reg_irq, virq; bool irq_fired;
mutex_lock(&rt5677->irq_lock); - /* Read interrupt status */ - ret = regmap_read(rt5677->regmap, RT5677_IRQ_CTRL1, ®_irq); - if (ret) { - dev_err(rt5677->component->dev, - "Failed to read IRQ status: %d\n", - ret); - goto exit; - } /* - * Clear the interrupt by flipping the polarity of the - * interrupt source lines that just fired + * Loop to handle interrupts until the last i2c read shows no pending + * irqs. The interrupt line is shared by multiple interrupt sources. + * After the regmap_read() below, a new interrupt source line may + * become high before the regmap_write() finishes, so there isn't a + * rising edge on the shared interrupt line for the new interrupt. Thus, + * the loop is needed to avoid missing irqs. + * + * A safeguard of 20 loops is used to avoid hanging in the irq handler + * if there is something wrong with the interrupt status update. The + * interrupt sources here are audio jack plug/unplug events which + * shouldn't happen at a high frequency for a long period of time. + * Empirically, more than 3 loops have never been seen. */ - irq_fired = false; - for (i = 0; i < RT5677_IRQ_NUM; i++) { - if (reg_irq & rt5677_irq_descs[i].status_mask) { - reg_irq ^= rt5677_irq_descs[i].polarity_mask; - irq_fired = true; + for (loop = 0; loop < 20; loop++) { + /* Read interrupt status */ + ret = regmap_read(rt5677->regmap, RT5677_IRQ_CTRL1, ®_irq); + if (ret) { + dev_err(rt5677->component->dev, + "Failed to read IRQ status: %d\n", + ret); + goto exit; + } + /* + * Clear the interrupt by flipping the polarity of the + * interrupt source lines that just fired + */ + irq_fired = false; + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if (reg_irq & rt5677_irq_descs[i].status_mask) { + reg_irq ^= rt5677_irq_descs[i].polarity_mask; + irq_fired = true; + } + } + if (!irq_fired) + goto exit; + + ret = regmap_write(rt5677->regmap, RT5677_IRQ_CTRL1, reg_irq); + if (ret) { + dev_err(rt5677->component->dev, + "Failed to update IRQ status: %d\n", + ret); + goto exit; } - } - if (!irq_fired) - goto exit; - - ret = regmap_write(rt5677->regmap, RT5677_IRQ_CTRL1, reg_irq); - if (ret) { - dev_err(rt5677->component->dev, - "Failed to update IRQ status: %d\n", - ret); - goto exit; - }
- /* Process interrupts */ - for (i = 0; i < RT5677_IRQ_NUM; i++) { - if ((reg_irq & rt5677_irq_descs[i].enable_mask) && - (reg_irq & rt5677_irq_descs[i].status_mask)) { - virq = irq_find_mapping(rt5677->domain, i); - if (virq) - handle_nested_irq(virq); + /* Process interrupts */ + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if ((reg_irq & rt5677_irq_descs[i].enable_mask) && + (reg_irq & rt5677_irq_descs[i].status_mask)) { + virq = irq_find_mapping(rt5677->domain, i); + if (virq) + handle_nested_irq(virq); + } } } exit:
The rt5677 driver uses ACPI-style property names to read from the device API. However, these do not match the property names in _DSD used on the Chromebook Pixel 2015, which are closer to the Device Tree style. Unify the two functions for reading from the device API so that they try ACPI-style names first and fall back to the DT names on error.
With this patch, plugging and unplugging the headphone jack switches between headphones and speakers automatically.
Signed-off-by: Fletcher Woodruff fletcherw@chromium.org --- sound/soc/codecs/rt5677.c | 74 +++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 37 deletions(-)
diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 091ef3e78fe3d2..3a4796cabd18ea 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -4998,48 +4998,50 @@ static const struct acpi_device_id rt5677_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, rt5677_acpi_match);
-static void rt5677_read_acpi_properties(struct rt5677_priv *rt5677, +static void rt5677_read_device_properties(struct rt5677_priv *rt5677, struct device *dev) { u32 val;
- if (!device_property_read_u32(dev, "DCLK", &val)) - rt5677->pdata.dmic2_clk_pin = val; + rt5677->pdata.in1_diff = + device_property_read_bool(dev, "IN1") || + device_property_read_bool(dev, "realtek,in1-differential");
- rt5677->pdata.in1_diff = device_property_read_bool(dev, "IN1"); - rt5677->pdata.in2_diff = device_property_read_bool(dev, "IN2"); - rt5677->pdata.lout1_diff = device_property_read_bool(dev, "OUT1"); - rt5677->pdata.lout2_diff = device_property_read_bool(dev, "OUT2"); - rt5677->pdata.lout3_diff = device_property_read_bool(dev, "OUT3"); + rt5677->pdata.in2_diff = + device_property_read_bool(dev, "IN2") || + device_property_read_bool(dev, "realtek,in2-differential");
- device_property_read_u32(dev, "JD1", &rt5677->pdata.jd1_gpio); - device_property_read_u32(dev, "JD2", &rt5677->pdata.jd2_gpio); - device_property_read_u32(dev, "JD3", &rt5677->pdata.jd3_gpio); -} + rt5677->pdata.lout1_diff = + device_property_read_bool(dev, "OUT1") || + device_property_read_bool(dev, "realtek,lout1-differential");
-static void rt5677_read_device_properties(struct rt5677_priv *rt5677, - struct device *dev) -{ - rt5677->pdata.in1_diff = device_property_read_bool(dev, - "realtek,in1-differential"); - rt5677->pdata.in2_diff = device_property_read_bool(dev, - "realtek,in2-differential"); - rt5677->pdata.lout1_diff = device_property_read_bool(dev, - "realtek,lout1-differential"); - rt5677->pdata.lout2_diff = device_property_read_bool(dev, - "realtek,lout2-differential"); - rt5677->pdata.lout3_diff = device_property_read_bool(dev, - "realtek,lout3-differential"); + rt5677->pdata.lout2_diff = + device_property_read_bool(dev, "OUT2") || + device_property_read_bool(dev, "realtek,lout2-differential"); + + rt5677->pdata.lout3_diff = + device_property_read_bool(dev, "OUT3") || + device_property_read_bool(dev, "realtek,lout3-differential");
device_property_read_u8_array(dev, "realtek,gpio-config", - rt5677->pdata.gpio_config, RT5677_GPIO_NUM); - - device_property_read_u32(dev, "realtek,jd1-gpio", - &rt5677->pdata.jd1_gpio); - device_property_read_u32(dev, "realtek,jd2-gpio", - &rt5677->pdata.jd2_gpio); - device_property_read_u32(dev, "realtek,jd3-gpio", - &rt5677->pdata.jd3_gpio); + rt5677->pdata.gpio_config, + RT5677_GPIO_NUM); + + if (!device_property_read_u32(dev, "DCLK", &val) || + !device_property_read_u32(dev, "realtek,dmic2_clk_pin", &val)) + rt5677->pdata.dmic2_clk_pin = val; + + if (!device_property_read_u32(dev, "JD1", &val) || + !device_property_read_u32(dev, "realtek,jd1-gpio", &val)) + rt5677->pdata.jd1_gpio = val; + + if (!device_property_read_u32(dev, "JD2", &val) || + !device_property_read_u32(dev, "realtek,jd2-gpio", &val)) + rt5677->pdata.jd2_gpio = val; + + if (!device_property_read_u32(dev, "JD3", &val) || + !device_property_read_u32(dev, "realtek,jd3-gpio", &val)) + rt5677->pdata.jd3_gpio = val; }
struct rt5677_irq_desc { @@ -5283,20 +5285,18 @@ static int rt5677_i2c_probe(struct i2c_client *i2c) match_id = of_match_device(rt5677_of_match, &i2c->dev); if (match_id) rt5677->type = (enum rt5677_type)match_id->data; - - rt5677_read_device_properties(rt5677, &i2c->dev); } else if (ACPI_HANDLE(&i2c->dev)) { const struct acpi_device_id *acpi_id;
acpi_id = acpi_match_device(rt5677_acpi_match, &i2c->dev); if (acpi_id) rt5677->type = (enum rt5677_type)acpi_id->driver_data; - - rt5677_read_acpi_properties(rt5677, &i2c->dev); } else { return -EINVAL; }
+ rt5677_read_device_properties(rt5677, &i2c->dev); + /* pow-ldo2 and reset are optional. The codec pins may be statically * connected on the board without gpios. If the gpio device property * isn't specified, devm_gpiod_get_optional returns NULL.
On Tue, May 07, 2019 at 04:01:15PM -0600, Fletcher Woodruff wrote:
The rt5677 driver uses ACPI-style property names to read from the device API. However, these do not match the property names in _DSD used on the Chromebook Pixel 2015, which are closer to the Device Tree style. Unify the two functions for reading from the device API so that they try ACPI-style names first and fall back to the DT names on error.
This is OK and should probably be the first patch in the series since it's much simpler than the other ones but depends on them.
Headphone/mic jack detection doesn't work on the Chromebook Pixel 2015.
This patch changes the irq implementation to support polarity flipping and fixes the configuration code so that correct GPIO pins are read from ACPI.
With this series, plugging and unplugging the headphone jack switches between headphones and speakers automatically, and headset microphones are also detected.
v6: - Move refactoring into its own patch - Reorder patches so that DT property names patch is first - Clarify commit message for patch which implements irq handler - Remove unused work struct - Make IRQ function return IRQ_HANDLED only if IRQs actually fire
v5: - Fix void* parameter to devm_request_threaded_irq
v4: - Fix incorrect void* cast in rt5677_irq()
v3: - Update commit message for patch 1/3 to clarify why we implement our own irq_chip.
v2: - Split IRQ change into two patches: adding and fixing potential race - Change config reading code to try both DT and ACPI style names
Ben Zhang (2): ASoC: rt5677: clear interrupts by polarity flip ASoC: rt5677: handle concurrent interrupts
Fletcher Woodruff (2): ASoC: rt5677: fall back to DT prop names on error ASoC: rt5677: move jack-detect init to i2c probe
sound/soc/codecs/rt5677.c | 317 ++++++++++++++++++++++++++------------ sound/soc/codecs/rt5677.h | 13 +- 2 files changed, 234 insertions(+), 96 deletions(-)
The rt5677 driver uses ACPI-style property names to read from the device API. However, these do not match the property names in _DSD used on the Chromebook Pixel 2015, which are closer to the Device Tree style. Unify the two functions for reading from the device API so that they try ACPI-style names first and fall back to the DT names on error.
Signed-off-by: Fletcher Woodruff fletcherw@chromium.org --- sound/soc/codecs/rt5677.c | 74 +++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 37 deletions(-)
diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 9b7a1833d3316c..62489a8c3fed6e 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5019,48 +5019,50 @@ static const struct acpi_device_id rt5677_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, rt5677_acpi_match);
-static void rt5677_read_acpi_properties(struct rt5677_priv *rt5677, +static void rt5677_read_device_properties(struct rt5677_priv *rt5677, struct device *dev) { u32 val;
- if (!device_property_read_u32(dev, "DCLK", &val)) - rt5677->pdata.dmic2_clk_pin = val; + rt5677->pdata.in1_diff = + device_property_read_bool(dev, "IN1") || + device_property_read_bool(dev, "realtek,in1-differential");
- rt5677->pdata.in1_diff = device_property_read_bool(dev, "IN1"); - rt5677->pdata.in2_diff = device_property_read_bool(dev, "IN2"); - rt5677->pdata.lout1_diff = device_property_read_bool(dev, "OUT1"); - rt5677->pdata.lout2_diff = device_property_read_bool(dev, "OUT2"); - rt5677->pdata.lout3_diff = device_property_read_bool(dev, "OUT3"); + rt5677->pdata.in2_diff = + device_property_read_bool(dev, "IN2") || + device_property_read_bool(dev, "realtek,in2-differential");
- device_property_read_u32(dev, "JD1", &rt5677->pdata.jd1_gpio); - device_property_read_u32(dev, "JD2", &rt5677->pdata.jd2_gpio); - device_property_read_u32(dev, "JD3", &rt5677->pdata.jd3_gpio); -} + rt5677->pdata.lout1_diff = + device_property_read_bool(dev, "OUT1") || + device_property_read_bool(dev, "realtek,lout1-differential");
-static void rt5677_read_device_properties(struct rt5677_priv *rt5677, - struct device *dev) -{ - rt5677->pdata.in1_diff = device_property_read_bool(dev, - "realtek,in1-differential"); - rt5677->pdata.in2_diff = device_property_read_bool(dev, - "realtek,in2-differential"); - rt5677->pdata.lout1_diff = device_property_read_bool(dev, - "realtek,lout1-differential"); - rt5677->pdata.lout2_diff = device_property_read_bool(dev, - "realtek,lout2-differential"); - rt5677->pdata.lout3_diff = device_property_read_bool(dev, - "realtek,lout3-differential"); + rt5677->pdata.lout2_diff = + device_property_read_bool(dev, "OUT2") || + device_property_read_bool(dev, "realtek,lout2-differential"); + + rt5677->pdata.lout3_diff = + device_property_read_bool(dev, "OUT3") || + device_property_read_bool(dev, "realtek,lout3-differential");
device_property_read_u8_array(dev, "realtek,gpio-config", - rt5677->pdata.gpio_config, RT5677_GPIO_NUM); - - device_property_read_u32(dev, "realtek,jd1-gpio", - &rt5677->pdata.jd1_gpio); - device_property_read_u32(dev, "realtek,jd2-gpio", - &rt5677->pdata.jd2_gpio); - device_property_read_u32(dev, "realtek,jd3-gpio", - &rt5677->pdata.jd3_gpio); + rt5677->pdata.gpio_config, + RT5677_GPIO_NUM); + + if (!device_property_read_u32(dev, "DCLK", &val) || + !device_property_read_u32(dev, "realtek,dmic2_clk_pin", &val)) + rt5677->pdata.dmic2_clk_pin = val; + + if (!device_property_read_u32(dev, "JD1", &val) || + !device_property_read_u32(dev, "realtek,jd1-gpio", &val)) + rt5677->pdata.jd1_gpio = val; + + if (!device_property_read_u32(dev, "JD2", &val) || + !device_property_read_u32(dev, "realtek,jd2-gpio", &val)) + rt5677->pdata.jd2_gpio = val; + + if (!device_property_read_u32(dev, "JD3", &val) || + !device_property_read_u32(dev, "realtek,jd3-gpio", &val)) + rt5677->pdata.jd3_gpio = val; }
static struct regmap_irq rt5677_irqs[] = { @@ -5143,20 +5145,18 @@ static int rt5677_i2c_probe(struct i2c_client *i2c) match_id = of_match_device(rt5677_of_match, &i2c->dev); if (match_id) rt5677->type = (enum rt5677_type)match_id->data; - - rt5677_read_device_properties(rt5677, &i2c->dev); } else if (ACPI_HANDLE(&i2c->dev)) { const struct acpi_device_id *acpi_id;
acpi_id = acpi_match_device(rt5677_acpi_match, &i2c->dev); if (acpi_id) rt5677->type = (enum rt5677_type)acpi_id->driver_data; - - rt5677_read_acpi_properties(rt5677, &i2c->dev); } else { return -EINVAL; }
+ rt5677_read_device_properties(rt5677, &i2c->dev); + /* pow-ldo2 and reset are optional. The codec pins may be statically * connected on the board without gpios. If the gpio device property * isn't specified, devm_gpiod_get_optional returns NULL.
This patch moves the code to select the gpios for jack detection from rt5677_probe to rt5677_init_irq (called from rt5677_i2c_probe).
It also sets some registers to fix bugs related to jack detection, and adds some constants and comments to make it easier to understand what certain register settings are controlling.
Signed-off-by: Ben Zhang benzh@chromium.org Signed-off-by: Fletcher Woodruff fletcherw@chromium.org --- sound/soc/codecs/rt5677.c | 60 ++++++++++++++++++++++----------------- sound/soc/codecs/rt5677.h | 6 ++++ 2 files changed, 40 insertions(+), 26 deletions(-)
diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 62489a8c3fed6e..65bef50ded1151 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -4716,37 +4716,13 @@ static int rt5677_probe(struct snd_soc_component *component)
snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF);
- regmap_write(rt5677->regmap, RT5677_DIG_MISC, 0x0020); + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, + ~RT5677_IRQ_DEBOUNCE_SEL_MASK, 0x0020); regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x0c00);
for (i = 0; i < RT5677_GPIO_NUM; i++) rt5677_gpio_config(rt5677, i, rt5677->pdata.gpio_config[i]);
- if (rt5677->irq_data) { - regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, 0x8000, - 0x8000); - regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x0018, - 0x0008); - - if (rt5677->pdata.jd1_gpio) - regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, - RT5677_SEL_GPIO_JD1_MASK, - rt5677->pdata.jd1_gpio << - RT5677_SEL_GPIO_JD1_SFT); - - if (rt5677->pdata.jd2_gpio) - regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, - RT5677_SEL_GPIO_JD2_MASK, - rt5677->pdata.jd2_gpio << - RT5677_SEL_GPIO_JD2_SFT); - - if (rt5677->pdata.jd3_gpio) - regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, - RT5677_SEL_GPIO_JD3_MASK, - rt5677->pdata.jd3_gpio << - RT5677_SEL_GPIO_JD3_SFT); - } - mutex_init(&rt5677->dsp_cmd_lock); mutex_init(&rt5677->dsp_pri_lock);
@@ -5095,6 +5071,7 @@ static int rt5677_init_irq(struct i2c_client *i2c) { int ret; struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); + unsigned int jd_mask = 0, jd_val = 0;
if (!rt5677->pdata.jd1_gpio && !rt5677->pdata.jd2_gpio && @@ -5106,6 +5083,37 @@ static int rt5677_init_irq(struct i2c_client *i2c) return -EINVAL; }
+ /* + * 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 */ + regmap_update_bits(rt5677->regmap, RT5677_GEN_CTRL1, 0xff, 0xff); + + /* Select and enable jack detection sources per platform data */ + if (rt5677->pdata.jd1_gpio) { + jd_mask |= RT5677_SEL_GPIO_JD1_MASK; + jd_val |= rt5677->pdata.jd1_gpio << RT5677_SEL_GPIO_JD1_SFT; + } + if (rt5677->pdata.jd2_gpio) { + jd_mask |= RT5677_SEL_GPIO_JD2_MASK; + jd_val |= rt5677->pdata.jd2_gpio << RT5677_SEL_GPIO_JD2_SFT; + } + if (rt5677->pdata.jd3_gpio) { + jd_mask |= RT5677_SEL_GPIO_JD3_MASK; + jd_val |= rt5677->pdata.jd3_gpio << RT5677_SEL_GPIO_JD3_SFT; + } + regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, jd_mask, jd_val); + + /* Set GPIO1 pin to be IRQ output */ + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, + RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); + ret = regmap_add_irq_chip(rt5677->regmap, i2c->irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, &rt5677_irq_chip, &rt5677->irq_data); diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h index 183d92b030459f..7a89bfa95dc0bf 100644 --- a/sound/soc/codecs/rt5677.h +++ b/sound/soc/codecs/rt5677.h @@ -1636,6 +1636,12 @@ #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) + /* Virtual DSP Mixer Control (0xf7 0xf8 0xf9) */ #define RT5677_DSP_IB_01_H (0x1 << 15) #define RT5677_DSP_IB_01_H_SFT 15
From: Ben Zhang benzh@chromium.org
The rt5677 jack detection function has a requirement that the polarity of an interrupt be flipped after it fires in order to clear the interrupt.
This patch implements an irq_chip with irq_domain directly instead of using regmap-irq, so that interrupt source line polarities can be flipped in the irq handler.
The reason that this patch does not add this feature within regmap-irq is that future patches will add hotword detection support to this irq handler. Those patches will require adding additional logic that would not make sense to have in regmap-irq.
Signed-off-by: Ben Zhang benzh@chromium.org Signed-off-by: Fletcher Woodruff fletcherw@chromium.org --- sound/soc/codecs/rt5677.c | 169 ++++++++++++++++++++++++++++++-------- sound/soc/codecs/rt5677.h | 7 +- 2 files changed, 142 insertions(+), 34 deletions(-)
diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 65bef50ded1151..86555d7ec9ea8d 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -23,6 +23,10 @@ #include <linux/firmware.h> #include <linux/of_device.h> #include <linux/property.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/workqueue.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -4620,7 +4624,6 @@ static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) { struct rt5677_priv *rt5677 = gpiochip_get_data(chip); - struct regmap_irq_chip_data *data = rt5677->irq_data; int irq;
if ((rt5677->pdata.jd1_gpio == 1 && offset == RT5677_GPIO1) || @@ -4646,7 +4649,7 @@ static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) return -ENXIO; }
- return regmap_irq_get_virq(data, irq); + return irq_create_mapping(rt5677->domain, irq); }
static const struct gpio_chip rt5677_template_chip = { @@ -5041,30 +5044,129 @@ static void rt5677_read_device_properties(struct rt5677_priv *rt5677, rt5677->pdata.jd3_gpio = val; }
-static struct regmap_irq rt5677_irqs[] = { +struct rt5677_irq_desc { + unsigned int enable_mask; + unsigned int status_mask; + unsigned int polarity_mask; +}; + +static const struct rt5677_irq_desc rt5677_irq_descs[] = { [RT5677_IRQ_JD1] = { - .reg_offset = 0, - .mask = RT5677_EN_IRQ_GPIO_JD1, + .enable_mask = RT5677_EN_IRQ_GPIO_JD1, + .status_mask = RT5677_STA_GPIO_JD1, + .polarity_mask = RT5677_INV_GPIO_JD1, }, [RT5677_IRQ_JD2] = { - .reg_offset = 0, - .mask = RT5677_EN_IRQ_GPIO_JD2, + .enable_mask = RT5677_EN_IRQ_GPIO_JD2, + .status_mask = RT5677_STA_GPIO_JD2, + .polarity_mask = RT5677_INV_GPIO_JD2, }, [RT5677_IRQ_JD3] = { - .reg_offset = 0, - .mask = RT5677_EN_IRQ_GPIO_JD3, + .enable_mask = RT5677_EN_IRQ_GPIO_JD3, + .status_mask = RT5677_STA_GPIO_JD3, + .polarity_mask = RT5677_INV_GPIO_JD3, }, };
-static struct regmap_irq_chip rt5677_irq_chip = { - .name = "rt5677", - .irqs = rt5677_irqs, - .num_irqs = ARRAY_SIZE(rt5677_irqs), +static irqreturn_t rt5677_irq(int unused, void *data) +{ + struct rt5677_priv *rt5677 = data; + int ret = 0, i, reg_irq, virq; + bool irq_fired = false; + + mutex_lock(&rt5677->irq_lock); + /* Read interrupt status */ + ret = regmap_read(rt5677->regmap, RT5677_IRQ_CTRL1, ®_irq); + if (ret) { + pr_err("rt5677: failed reading IRQ status: %d\n", ret); + goto exit; + } + + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if (reg_irq & rt5677_irq_descs[i].status_mask) { + irq_fired = true; + virq = irq_find_mapping(rt5677->domain, i); + if (virq) + handle_nested_irq(virq); + + /* Clear the interrupt by flipping the polarity of the + * interrupt source line that fired + */ + reg_irq ^= rt5677_irq_descs[i].polarity_mask; + } + } + + if (!irq_fired) + goto exit; + + ret = regmap_write(rt5677->regmap, RT5677_IRQ_CTRL1, reg_irq); + if (ret) { + pr_err("rt5677: failed updating IRQ status: %d\n", ret); + goto exit; + } +exit: + mutex_unlock(&rt5677->irq_lock); + if (irq_fired) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static void rt5677_irq_bus_lock(struct irq_data *data) +{ + struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data); + + mutex_lock(&rt5677->irq_lock); +} + +static void rt5677_irq_bus_sync_unlock(struct irq_data *data) +{ + struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data); + + // Set the enable/disable bits for the jack detect IRQs. + regmap_update_bits(rt5677->regmap, RT5677_IRQ_CTRL1, + RT5677_EN_IRQ_GPIO_JD1 | RT5677_EN_IRQ_GPIO_JD2 | + RT5677_EN_IRQ_GPIO_JD3, rt5677->irq_en); + mutex_unlock(&rt5677->irq_lock); +} + +static void rt5677_irq_enable(struct irq_data *data) +{ + struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data); + + rt5677->irq_en |= rt5677_irq_descs[data->hwirq].enable_mask; +} + +static void rt5677_irq_disable(struct irq_data *data) +{ + struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data); + + rt5677->irq_en &= ~rt5677_irq_descs[data->hwirq].enable_mask; +} + +static struct irq_chip rt5677_irq_chip = { + .name = "rt5677_irq_chip", + .irq_bus_lock = rt5677_irq_bus_lock, + .irq_bus_sync_unlock = rt5677_irq_bus_sync_unlock, + .irq_disable = rt5677_irq_disable, + .irq_enable = rt5677_irq_enable, +}; + +static int rt5677_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct rt5677_priv *rt5677 = h->host_data; + + irq_set_chip_data(virq, rt5677); + irq_set_chip(virq, &rt5677_irq_chip); + irq_set_nested_thread(virq, 1); + irq_set_noprobe(virq); + return 0; +} +
- .num_regs = 1, - .status_base = RT5677_IRQ_CTRL1, - .mask_base = RT5677_IRQ_CTRL1, - .mask_invert = 1, +static const struct irq_domain_ops rt5677_domain_ops = { + .map = rt5677_irq_map, + .xlate = irq_domain_xlate_twocell, };
static int rt5677_init_irq(struct i2c_client *i2c) @@ -5083,6 +5185,8 @@ static int rt5677_init_irq(struct i2c_client *i2c) return -EINVAL; }
+ mutex_init(&rt5677->irq_lock); + /* * Select RC as the debounce clock so that GPIO works even when * MCLK is gated which happens when there is no audio stream @@ -5091,7 +5195,6 @@ static int rt5677_init_irq(struct i2c_client *i2c) 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 */ regmap_update_bits(rt5677->regmap, RT5677_GEN_CTRL1, 0xff, 0xff);
@@ -5114,26 +5217,25 @@ static int rt5677_init_irq(struct i2c_client *i2c) regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ);
- ret = regmap_add_irq_chip(rt5677->regmap, i2c->irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, - &rt5677_irq_chip, &rt5677->irq_data); + /* Ready to listen for interrupts */ + rt5677->domain = irq_domain_add_linear(i2c->dev.of_node, + RT5677_IRQ_NUM, &rt5677_domain_ops, rt5677); + if (!rt5677->domain) { + dev_err(&i2c->dev, "Failed to create IRQ domain\n"); + return -ENOMEM; + }
- if (ret != 0) { - dev_err(&i2c->dev, "Failed to register IRQ chip: %d\n", ret); + ret = devm_request_threaded_irq(&i2c->dev, 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 0; }
-static void rt5677_free_irq(struct i2c_client *i2c) -{ - struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); - - if (rt5677->irq_data) - regmap_del_irq_chip(i2c->irq, rt5677->irq_data); -} - static int rt5677_i2c_probe(struct i2c_client *i2c) { struct rt5677_priv *rt5677; @@ -5258,7 +5360,9 @@ static int rt5677_i2c_probe(struct i2c_client *i2c) RT5677_MICBIAS1_CTRL_VDD_3_3V);
rt5677_init_gpio(i2c); - rt5677_init_irq(i2c); + ret = rt5677_init_irq(i2c); + if (ret) + dev_err(&i2c->dev, "Failed to initialize irq: %d\n", ret);
return devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_rt5677, @@ -5267,7 +5371,6 @@ static int rt5677_i2c_probe(struct i2c_client *i2c)
static int rt5677_i2c_remove(struct i2c_client *i2c) { - rt5677_free_irq(i2c); rt5677_free_gpio(i2c);
return 0; diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h index 7a89bfa95dc0bf..a891a9b9eba3e6 100644 --- a/sound/soc/codecs/rt5677.h +++ b/sound/soc/codecs/rt5677.h @@ -1719,6 +1719,7 @@ enum { RT5677_IRQ_JD1, RT5677_IRQ_JD2, RT5677_IRQ_JD3, + RT5677_IRQ_NUM, };
enum rt5677_type { @@ -1817,9 +1818,13 @@ struct rt5677_priv { struct gpio_chip gpio_chip; #endif bool dsp_vad_en; - struct regmap_irq_chip_data *irq_data; bool is_dsp_mode; bool is_vref_slow; + + /* Interrupt handling */ + struct irq_domain *domain; + struct mutex irq_lock; + unsigned int irq_en; };
int rt5677_sel_asrc_clk_src(struct snd_soc_component *component,
From: Ben Zhang benzh@chromium.org
The rt5677 driver writes to the IRQ control register within the IRQ handler in order to flip the polarity of the interrupts that have been signalled. If an interrupt fires in the interval between the regmap_read and the regmap_write, it will not trigger a new call to rt5677_irq.
Add a bounded loop to rt5677_irq that keeps checking interrupts until none are seen, so that any interrupts that are signalled in that interval are correctly handled.
Signed-off-by: Ben Zhang benzh@chromium.org Signed-off-by: Fletcher Woodruff fletcherw@chromium.org --- sound/soc/codecs/rt5677.c | 67 ++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 25 deletions(-)
diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 86555d7ec9ea8d..7f7e60aceb49d3 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5071,38 +5071,55 @@ static const struct rt5677_irq_desc rt5677_irq_descs[] = { static irqreturn_t rt5677_irq(int unused, void *data) { struct rt5677_priv *rt5677 = data; - int ret = 0, i, reg_irq, virq; + int ret = 0, loop, i, reg_irq, virq; bool irq_fired = false;
mutex_lock(&rt5677->irq_lock); - /* Read interrupt status */ - ret = regmap_read(rt5677->regmap, RT5677_IRQ_CTRL1, ®_irq); - if (ret) { - pr_err("rt5677: failed reading IRQ status: %d\n", ret); - goto exit; - }
- for (i = 0; i < RT5677_IRQ_NUM; i++) { - if (reg_irq & rt5677_irq_descs[i].status_mask) { - irq_fired = true; - virq = irq_find_mapping(rt5677->domain, i); - if (virq) - handle_nested_irq(virq); - - /* Clear the interrupt by flipping the polarity of the - * interrupt source line that fired - */ - reg_irq ^= rt5677_irq_descs[i].polarity_mask; + /* + * Loop to handle interrupts until the last i2c read shows no pending + * irqs. The interrupt line is shared by multiple interrupt sources. + * After the regmap_read() below, a new interrupt source line may + * become high before the regmap_write() finishes, so there isn't a + * rising edge on the shared interrupt line for the new interrupt. Thus, + * the loop is needed to avoid missing irqs. + * + * A safeguard of 20 loops is used to avoid hanging in the irq handler + * if there is something wrong with the interrupt status update. The + * interrupt sources here are audio jack plug/unplug events which + * shouldn't happen at a high frequency for a long period of time. + * Empirically, more than 3 loops have never been seen. + */ + for (loop = 0; loop < 20; loop++) { + /* Read interrupt status */ + ret = regmap_read(rt5677->regmap, RT5677_IRQ_CTRL1, ®_irq); + if (ret) { + pr_err("rt5677: failed reading IRQ status: %d\n", ret); + goto exit; } - }
- if (!irq_fired) - goto exit; + irq_fired = false; + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if (reg_irq & rt5677_irq_descs[i].status_mask) { + irq_fired = true; + virq = irq_find_mapping(rt5677->domain, i); + if (virq) + handle_nested_irq(virq); + + /* Clear the interrupt by flipping the polarity + * of the interrupt source line that fired + */ + reg_irq ^= rt5677_irq_descs[i].polarity_mask; + } + } + if (!irq_fired) + goto exit;
- ret = regmap_write(rt5677->regmap, RT5677_IRQ_CTRL1, reg_irq); - if (ret) { - pr_err("rt5677: failed updating IRQ status: %d\n", ret); - goto exit; + ret = regmap_write(rt5677->regmap, RT5677_IRQ_CTRL1, reg_irq); + if (ret) { + pr_err("rt5677: failed updating IRQ status: %d\n", ret); + goto exit; + } } exit: mutex_unlock(&rt5677->irq_lock);
participants (3)
-
Curtis Malainey
-
Fletcher Woodruff
-
Mark Brown