Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2fedc6a3 authored by Maulik Shah's avatar Maulik Shah
Browse files

pinctrl: qcom: Use TLMM and PDC for dual edge wakeup interrupts



Although PDC has the capability to route falling edge interrupt to
GIC by inverting the input signal, falling edge of the interrupts marked
as dual edge type is routed as is. This causes GIC to ignore to falling
edge of these interrupts when the system is active. During APSS system
sleep once PDC starts monitoring these interrupts, PDC always replays a
rising edge irrespective of which edge woke the APSS from sleep.  So,
the issue does not happen during the system sleep.

To address the issue when system is active, route the falling_edge
of these interrupts through one of the direct connect interrupts
available for APSS. The original GIC interrupt is configured as dual
edge at PDC, rising edge at GIC (same as earlier, this does not change).

This solution requires additional programming of 2 registers:

TLMM_<ZONE>_DIR_CONN_INTRn_CFG_HMSS - program GPIO_SEL with the gpio
number and set POLAIRTY to 0 for inverting the falling edge

TLMM_GPIO_INTR_CFGn - set DIR_CONN_EN bit to 1 for enabling direct
connect for this gpio.

Change-Id: Iaa9dfe391ef4942cd8b12fd1e7d8f9c238947af2
Signed-off-by: default avatarArchana Sathyakumar <asathyak@codeaurora.org>
Signed-off-by: default avatarMaulik Shah <mkshah@codeaurora.org>
parent 757f1d14
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@
		.intr_cfg_reg = REG_BASE + 0x8 + REG_SIZE * id,		\
		.intr_status_reg = REG_BASE + 0xc + REG_SIZE * id,	\
		.intr_target_reg = REG_BASE + 0x8 + REG_SIZE * id,	\
		.dir_conn_reg = REG_BASE + 0xBF000,\
		.mux_bit = 2,			\
		.pull_bit = 0,			\
		.drv_bit = 6,			\
@@ -58,6 +59,7 @@
		.intr_polarity_bit = 1,		\
		.intr_detection_bit = 2,	\
		.intr_detection_width = 2,	\
		.dir_conn_en_bit = 8,		\
		.wake_reg = REG_BASE + wake_off,	\
		.wake_bit = bit,		\
	}
@@ -1618,6 +1620,11 @@ static const int lagoon_reserved_gpios[] = {
	13, 14, 15, 16, 45, 46, 56, 57, -1
};

static struct msm_dir_conn lagoon_dir_conn[] = {
	{-1, 0}, {-1, 0}, {-1, 0}, {-1, 0}, {-1, 0},
	{-1, 0}, {-1, 0}, {-1, 0}, {-1, 0}
};

static const struct msm_pinctrl_soc_data lagoon_pinctrl = {
	.pins = lagoon_pins,
	.npins = ARRAY_SIZE(lagoon_pins),
@@ -1627,6 +1634,7 @@ static const struct msm_pinctrl_soc_data lagoon_pinctrl = {
	.ngroups = ARRAY_SIZE(lagoon_groups),
	.reserved_gpios = lagoon_reserved_gpios,
	.ngpios = 156,
	.dir_conn = lagoon_dir_conn,
};

static int lagoon_pinctrl_probe(struct platform_device *pdev)
+215 −6
Original line number Diff line number Diff line
/*
 * Copyright (c) 2013, Sony Mobile Communications AB.
 * Copyright (c) 2013-2019, The Linux Foundation. All rights reserved.
 * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -68,6 +68,7 @@ struct msm_pinctrl {

	struct irq_chip irq_chip;
	int irq;
	int n_dir_conns;

	raw_spinlock_t lock;

@@ -633,6 +634,25 @@ static void msm_gpio_update_dual_edge_pos(struct msm_pinctrl *pctrl,
		val, val2);
}

static bool is_gpio_dual_edge(struct irq_data *d, irq_hw_number_t *irq)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	struct msm_dir_conn *dc;
	int i;

	for (i = pctrl->n_dir_conns; i > 0; i--) {
		dc = &pctrl->soc->dir_conn[i];

		if (dc->gpio == d->hwirq) {
			*irq = dc->irq;
			return true;
		}
	}

	return false;
}

static void _msm_gpio_irq_mask(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
@@ -714,9 +734,19 @@ static void msm_gpio_irq_mask(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	struct irq_data *dir_conn_data;
	irq_hw_number_t dir_conn_irq = 0;

	if (d->parent_data)
	if (d->parent_data) {
		if (is_gpio_dual_edge(d, &dir_conn_irq)) {
			dir_conn_data = irq_get_irq_data(dir_conn_irq);
			if (!dir_conn_data)
				return;

			dir_conn_data->chip->irq_mask(dir_conn_data);
		}
		irq_chip_mask_parent(d);
	}

	if (test_bit(d->hwirq, pctrl->wakeup_masked_irqs))
		return;
@@ -728,9 +758,19 @@ static void msm_gpio_irq_unmask(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	struct irq_data *dir_conn_data;
	irq_hw_number_t dir_conn_irq = 0;

	if (d->parent_data)
	if (d->parent_data) {
		if (is_gpio_dual_edge(d, &dir_conn_irq)) {
			dir_conn_data = irq_get_irq_data(dir_conn_irq);
			if (!dir_conn_data)
				return;

			dir_conn_data->chip->irq_unmask(dir_conn_data);
		}
		irq_chip_unmask_parent(d);
	}

	if (test_bit(d->hwirq, pctrl->wakeup_masked_irqs))
		return;
@@ -742,9 +782,22 @@ static void msm_gpio_irq_disable(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	struct irq_data *dir_conn_data;
	irq_hw_number_t dir_conn_irq = 0;

	if (d->parent_data)
	if (d->parent_data) {
		if (is_gpio_dual_edge(d, &dir_conn_irq)) {
			dir_conn_data = irq_get_irq_data(dir_conn_irq);
			if (!dir_conn_data)
				return;

			if (dir_conn_data->chip->irq_disable)
				dir_conn_data->chip->irq_disable(dir_conn_data);
			else
				dir_conn_data->chip->irq_mask(dir_conn_data);
		}
		irq_chip_disable_parent(d);
	}

	if (test_bit(d->hwirq, pctrl->wakeup_masked_irqs))
		return;
@@ -756,6 +809,8 @@ static void msm_gpio_irq_enable(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	struct irq_data *dir_conn_data;
	irq_hw_number_t dir_conn_irq = 0;

	if (d->parent_data) {
		/*
@@ -767,6 +822,18 @@ static void msm_gpio_irq_enable(struct irq_data *d)
		 * the interrupt from being latched at the GIC. The state at
		 * GIC needs to be cleared before enabling.
		 */
		if (is_gpio_dual_edge(d, &dir_conn_irq)) {
			dir_conn_data = irq_get_irq_data(dir_conn_irq);
			if (!dir_conn_data)
				return;

			irq_set_irqchip_state(dir_conn_irq,
					IRQCHIP_STATE_PENDING, 0);
			if (dir_conn_data->chip->irq_enable)
				dir_conn_data->chip->irq_enable(dir_conn_data);
			else
				dir_conn_data->chip->irq_unmask(dir_conn_data);
		}
		irq_chip_set_parent_state(d, IRQCHIP_STATE_PENDING, 0);
		irq_chip_enable_parent(d);
	}
@@ -806,18 +873,148 @@ static void msm_gpio_irq_ack(struct irq_data *d)
	raw_spin_unlock_irqrestore(&pctrl->lock, flags);
}

static void msm_dirconn_cfg_reg(struct irq_data *d, u32 offset)
{
	u32 val;
	const struct msm_pingroup *g;
	unsigned long flags;
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);

	raw_spin_lock_irqsave(&pctrl->lock, flags);
	g = &pctrl->soc->groups[d->hwirq];

	val = (d->hwirq) & 0xFF;

	writel_relaxed(val, pctrl->regs + g->dir_conn_reg + (offset * 4));

	val = readl_relaxed(pctrl->regs + g->intr_cfg_reg);
	val |= BIT(g->dir_conn_en_bit);

	writel_relaxed(val, pctrl->regs + g->intr_cfg_reg);
	raw_spin_unlock_irqrestore(&pctrl->lock, flags);
}

static void msm_dirconn_uncfg_reg(struct irq_data *d, u32 offset)
{
	u32 val = 0;
	const struct msm_pingroup *g;
	unsigned long flags;
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);

	raw_spin_lock_irqsave(&pctrl->lock, flags);
	g = &pctrl->soc->groups[d->hwirq];

	writel_relaxed(val, pctrl->regs + g->dir_conn_reg + (offset * 4));
	val = readl_relaxed(pctrl->regs + g->intr_cfg_reg);
	val &= ~BIT(g->dir_conn_en_bit);
	writel_relaxed(val, pctrl->regs + g->intr_cfg_reg);
	raw_spin_unlock_irqrestore(&pctrl->lock, flags);
}

static int select_dir_conn_mux(struct irq_data *d, irq_hw_number_t *irq,
			       bool add)
{
	struct msm_dir_conn *dc = NULL;
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	int i, n_dir_conns = pctrl->n_dir_conns;

	for (i = n_dir_conns; i > 0; i--) {
		dc = &pctrl->soc->dir_conn[i];
		if (dc->gpio == d->hwirq && !add) {
			*irq = dc->irq;
			dc->gpio = -1;
			return n_dir_conns - i;
		}

		if (dc->gpio == -1 && add) {
			dc->gpio = (int)d->hwirq;
			*irq = dc->irq;
			return n_dir_conns - i;
		}
	}

	pr_err("%s: No direct connects selected for interrupt %lu\n",
				__func__, d->hwirq);
	return -EBUSY;
}

static void msm_gpio_dirconn_handler(struct irq_desc *desc)
{
	struct irq_data *irqd = irq_desc_get_handler_data(desc);
	struct irq_chip *chip = irq_desc_get_chip(desc);

	if (!irqd)
		return;

	chained_irq_enter(chip, desc);
	generic_handle_irq(irqd->irq);
	chained_irq_exit(chip, desc);
	irq_set_irqchip_state(irq_desc_get_irq_data(desc)->irq,
			      IRQCHIP_STATE_ACTIVE, 0);
}

static void add_dirconn_tlmm(struct irq_data *d, struct msm_pinctrl *pctrl)
{
	struct irq_data *dir_conn_data = NULL;
	int offset = 0;
	irq_hw_number_t irq = 0;

	offset = select_dir_conn_mux(d, &irq, true);
	if (offset < 0)
		return;

	msm_dirconn_cfg_reg(d, offset);
	irq_set_handler_data(irq, d);
	dir_conn_data = irq_get_irq_data(irq);

	if (!dir_conn_data)
		return;

	dir_conn_data->chip->irq_unmask(dir_conn_data);
}

static void remove_dirconn_tlmm(struct irq_data *d, irq_hw_number_t irq)
{
	struct irq_data *dir_conn_data = NULL;
	int offset = 0;

	offset = select_dir_conn_mux(d, &irq, false);
	if (offset < 0)
		return;

	msm_dirconn_uncfg_reg(d, offset);
	irq_set_handler_data(irq, NULL);
	dir_conn_data = irq_get_irq_data(irq);

	if (!dir_conn_data)
		return;

	dir_conn_data->chip->irq_mask(dir_conn_data);
}

static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int type)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	const struct msm_pingroup *g;
	irq_hw_number_t irq = 0;
	unsigned long flags;
	u32 val;

	g = &pctrl->soc->groups[d->hwirq];

	if (d->parent_data)
	if (d->parent_data) {
		if (pctrl->n_dir_conns > 0) {
			if (type == IRQ_TYPE_EDGE_BOTH)
				add_dirconn_tlmm(d, pctrl);
			else if (is_gpio_dual_edge(d, &irq))
				remove_dirconn_tlmm(d, irq);
		}
		irq_chip_set_type_parent(d, type);
	}

	/* Monitored by parent wakeup controller? Keep masked */
	if (test_bit(d->hwirq, pctrl->wakeup_masked_irqs))
@@ -1361,7 +1558,7 @@ int msm_pinctrl_probe(struct platform_device *pdev,
{
	struct msm_pinctrl *pctrl;
	struct resource *res;
	int ret;
	int ret, i, num_irq, irq;

	msm_pinctrl_data = pctrl = devm_kzalloc(&pdev->dev,
				sizeof(*pctrl), GFP_KERNEL);
@@ -1406,6 +1603,18 @@ int msm_pinctrl_probe(struct platform_device *pdev,
	if (ret)
		return ret;

	num_irq = platform_irq_count(pdev);

	for (i = 1; i < num_irq; i++) {
		struct msm_dir_conn *dc = &soc_data->dir_conn[i];

		irq = platform_get_irq(pdev, i);
		dc->irq = irq;
		__irq_set_handler(irq, msm_gpio_dirconn_handler, false, NULL);
		irq_set_irq_type(irq, IRQ_TYPE_EDGE_RISING);
		pctrl->n_dir_conns++;
	}

	platform_set_drvdata(pdev, pctrl);

	register_syscore_ops(&msm_pinctrl_pm_ops);
+18 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ struct msm_function {
 * @intr_status_reg:      Offset of the register holding the status bits for this group.
 * @intr_target_reg:      Offset of the register specifying routing of the interrupts
 *                        from this group.
 * @dir_conn_reg:         Offset of the register hmss setup in tile.
 * @mux_bit:              Offset in @ctl_reg for the pinmux function selection.
 * @pull_bit:             Offset in @ctl_reg for the bias configuration.
 * @drv_bit:              Offset in @ctl_reg for the drive strength configuration.
@@ -63,6 +64,7 @@ struct msm_function {
 * @intr_detection_width: Number of bits used for specifying interrupt type,
 *                        Should be 2 for SoCs that can detect both edges in hardware,
 *                        otherwise 1.
 * @dir_conn_en_bit:      Offset in @intr_cfg_reg for direct connect enable bit
 * @wake_reg:             Offset of the WAKEUP_INT_EN register from base tile
 * @wake_bit:             Bit number for the corresponding gpio
 */
@@ -79,6 +81,7 @@ struct msm_pingroup {
	u32 intr_cfg_reg;
	u32 intr_status_reg;
	u32 intr_target_reg;
	u32 dir_conn_reg;

	unsigned mux_bit:5;

@@ -101,11 +104,22 @@ struct msm_pingroup {
	unsigned intr_polarity_bit:5;
	unsigned intr_detection_bit:5;
	unsigned intr_detection_width:5;
	unsigned dir_conn_en_bit:8;

	u32 wake_reg;
	unsigned int wake_bit;
};

/**
 * struct msm_dir_conn - TLMM Direct GPIO connect configuration
 * @gpio:	GPIO pin number
 * @irq:	The GIC interrupt that the pin is connected to
 */
struct msm_dir_conn {
	int gpio;
	int irq;
};

/*
 * struct pinctrl_qup - Qup mode configuration
 * @mode:	Qup i3c mode
@@ -126,6 +140,9 @@ struct pinctrl_qup {
 * @ngroups:	    The numbmer of entries in @groups.
 * @ngpio:	    The number of pingroups the driver should expose as GPIOs.
 * @pull_no_keeper: The SoC does not support keeper bias.
 * @wakeirq_map:    The map of wakeup capable GPIOs and the pin at PDC/MPM
 * @nwakeirq_map:   The number of entries in @hierarchy_map
 * @dir_conn:       An array describing all the pins directly connected to GIC.
 */
struct msm_pinctrl_soc_data {
	const struct pinctrl_pin_desc *pins;
@@ -139,6 +156,7 @@ struct msm_pinctrl_soc_data {
	struct pinctrl_qup *qup_regs;
	unsigned int nqup_regs;
	const int *reserved_gpios;
	struct msm_dir_conn *dir_conn;
};

int msm_pinctrl_probe(struct platform_device *pdev,