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

Commit 7ce28f58 authored by Archana Sathyakumar's avatar Archana Sathyakumar Committed by Gerrit - the friendly Code Review server
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 avatarMahesh Sivasubramanian <msivasub@codeaurora.org>
Signed-off-by: default avatarLina Iyer <ilina@codeaurora.org>
[minor fix]
parent 797c103e
Loading
Loading
Loading
Loading
+209 −6
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
@@ -775,11 +776,46 @@ static struct irq_chip msm_gpio_irq_chip = {
	.irq_set_wake   = msm_gpio_irq_set_wake,
};

static bool is_gpio_dual_edge(struct irq_data *d, irq_hw_number_t *dir_conn_irq)
{
	struct irq_desc *desc = irq_data_to_desc(d);
	struct irq_data *parent_data = irq_get_irq_data(desc->parent_irq);
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	int i;

	if (!parent_data)
		return false;

	for (i = 0; i < pctrl->soc->n_dir_conns; i++) {
		const struct msm_dir_conn *dir_conn = &pctrl->soc->dir_conn[i];

		if (dir_conn->gpio == d->hwirq && (dir_conn->hwirq + 32)
				!= parent_data->hwirq) {
			*dir_conn_irq = dir_conn->hwirq + 32;
			return true;
		}
	}
	return false;
}

static void msm_dirconn_irq_mask(struct irq_data *d)
{
	struct irq_desc *desc = irq_data_to_desc(d);
	struct irq_data *parent_data = irq_get_irq_data(desc->parent_irq);
	irq_hw_number_t dir_conn_irq = 0;

	if (!parent_data)
		return;

	if (is_gpio_dual_edge(d, &dir_conn_irq)) {
		struct irq_data *dir_conn_data =
			irq_get_irq_data(irq_find_mapping(parent_data->domain,
						dir_conn_irq));

		if (dir_conn_data && dir_conn_data->chip->irq_mask)
			dir_conn_data->chip->irq_mask(dir_conn_data);
	}
	if (parent_data->chip->irq_mask)
		parent_data->chip->irq_mask(parent_data);
}
@@ -788,7 +824,19 @@ static void msm_dirconn_irq_unmask(struct irq_data *d)
{
	struct irq_desc *desc = irq_data_to_desc(d);
	struct irq_data *parent_data = irq_get_irq_data(desc->parent_irq);
	irq_hw_number_t dir_conn_irq = 0;

	if (!parent_data)
		return;

	if (is_gpio_dual_edge(d, &dir_conn_irq)) {
		struct irq_data *dir_conn_data =
			irq_get_irq_data(irq_find_mapping(parent_data->domain,
						dir_conn_irq));

		if (dir_conn_data && dir_conn_data->chip->irq_unmask)
			dir_conn_data->chip->irq_unmask(dir_conn_data);
	}
	if (parent_data->chip->irq_unmask)
		parent_data->chip->irq_unmask(parent_data);
}
@@ -817,6 +865,9 @@ static int msm_dirconn_irq_set_affinity(struct irq_data *d,
	struct irq_desc *desc = irq_data_to_desc(d);
	struct irq_data *parent_data = irq_get_irq_data(desc->parent_irq);

	if (!parent_data)
		return 0;

	if (parent_data->chip->irq_set_affinity)
		return parent_data->chip->irq_set_affinity(parent_data,
				maskval, force);
@@ -835,10 +886,157 @@ static int msm_dirconn_irq_set_vcpu_affinity(struct irq_data *d,
	return 0;
}

static void msm_dirconn_cfg_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];

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

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

	//write the dir_conn_en bit
	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)
{
	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(BIT(8), pctrl->regs + g->dir_conn_reg + (offset * 4));
	raw_spin_unlock_irqrestore(&pctrl->lock, flags);
}

static int select_dir_conn_mux(struct irq_data *d, irq_hw_number_t *irq)
{
	struct msm_dir_conn *dc = NULL;
	struct irq_desc *desc = irq_data_to_desc(d);
	struct irq_data *parent_data = irq_get_irq_data(desc->parent_irq);
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	int i;

	if (!parent_data)
		return -EINVAL;

	for (i = 0; i < pctrl->soc->n_dir_conns; i++) {
		struct msm_dir_conn *dir_conn =
			(struct msm_dir_conn *)&pctrl->soc->dir_conn[i];

		/* Check if there is already mux assigned for this gpio */
		if (dir_conn->gpio == d->hwirq && (dir_conn->hwirq + 32) !=
				parent_data->hwirq) {
			*irq = dir_conn->hwirq + 32;
			return pctrl->soc->dir_conn_irq_base - dir_conn->hwirq;
		}

		if (dir_conn->gpio)
			continue;

		/* Use the first unused direct connect available */
		dc = dir_conn;
		break;
	}

	if (dc) {
		*irq = dc->hwirq + 32;
		dc->gpio = (u32)d->hwirq;
		return pctrl->soc->dir_conn_irq_base - (u32)dc->hwirq;
	}

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

static void add_dirconn_tlmm(struct irq_data *d, irq_hw_number_t irq)
{
	struct irq_desc *desc = irq_data_to_desc(d);
	struct irq_data *parent_data = irq_get_irq_data(desc->parent_irq);
	struct irq_data *dir_conn_data = NULL;
	int offset = 0;
	unsigned int virt = 0;

	offset = select_dir_conn_mux(d, &irq);
	if (offset < 0 || !parent_data)
		return;

	virt = irq_find_mapping(parent_data->domain, irq);
	msm_dirconn_cfg_reg(d, offset);
	irq_set_handler_data(virt, d);
	desc = irq_to_desc(virt);
	if (!desc)
		return;

	dir_conn_data = &(desc->irq_data);

	if (dir_conn_data) {
		if (dir_conn_data->chip && dir_conn_data->chip->irq_set_type)
			dir_conn_data->chip->irq_set_type(dir_conn_data,
					IRQ_TYPE_EDGE_RISING);
		if (dir_conn_data->chip && dir_conn_data->chip->irq_unmask)
			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_desc *desc = irq_data_to_desc(d);
	struct irq_data *parent_data = irq_get_irq_data(desc->parent_irq);
	struct irq_data *dir_conn_data = NULL;
	int offset = 0;
	unsigned int virt = 0;

	virt = irq_find_mapping(parent_data->domain, irq);
	msm_dirconn_uncfg_reg(d, offset);
	irq_set_handler_data(virt, NULL);
	desc = irq_to_desc(virt);
	if (!desc)
		return;

	dir_conn_data = &(desc->irq_data);

	if (dir_conn_data) {
		if (dir_conn_data->chip && dir_conn_data->chip->irq_mask)
			dir_conn_data->chip->irq_mask(dir_conn_data);
	}
}

static int msm_dirconn_irq_set_type(struct irq_data *d, unsigned int type)
{
	struct irq_desc *desc = irq_data_to_desc(d);
	struct irq_data *parent_data = irq_get_irq_data(desc->parent_irq);
	irq_hw_number_t irq = 0;

	if (!parent_data)
		return 0;

	if (type == IRQ_TYPE_EDGE_BOTH) {
		add_dirconn_tlmm(d, irq);
	} else {
		if (is_gpio_dual_edge(d, &irq))
			remove_dirconn_tlmm(d, irq);
	}

	if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
		irq_set_handler_locked(d, handle_level_irq);
	else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
		irq_set_handler_locked(d, handle_edge_irq);

	if (parent_data->chip->irq_set_type)
		return parent_data->chip->irq_set_type(parent_data, type);
@@ -932,14 +1130,19 @@ static void msm_gpio_setup_dir_connects(struct msm_pinctrl *pctrl)
		fwspec.param_count = 3;
		parent_irq = irq_create_fwspec_mapping(&fwspec);

		irq = irq_find_mapping(pctrl->chip.irqdomain, dirconn->gpio);
		if (dirconn->gpio != 0) {
			irq = irq_find_mapping(pctrl->chip.irqdomain,
					dirconn->gpio);

			irq_set_parent(irq, parent_irq);
			irq_set_chip(irq, &msm_dirconn_irq_chip);
		irq_set_chip_data(irq, irq_get_irq_data(parent_irq));
			__irq_set_handler(parent_irq, msm_gpio_dirconn_handler,
				false, NULL);
			irq_set_handler_data(parent_irq, irq_get_irq_data(irq));
		} else {
			__irq_set_handler(parent_irq, msm_gpio_dirconn_handler,
				false, NULL);
		}
	}
}

+6 −0
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ 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 specifying direct connect
 *                        setup of this group.
 * @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.
@@ -75,6 +77,7 @@ struct msm_pingroup {
	u32 intr_cfg_reg;
	u32 intr_status_reg;
	u32 intr_target_reg;
	u32 dir_conn_reg;

	unsigned mux_bit:5;

@@ -95,6 +98,7 @@ struct msm_pingroup {
	unsigned intr_polarity_bit:5;
	unsigned intr_detection_bit:5;
	unsigned intr_detection_width:5;
	unsigned dir_conn_en_bit:8;
}

/**
@@ -119,6 +123,7 @@ struct msm_dir_conn {
 * @pull_no_keeper: The SoC does not support keeper bias.
 * @dir_conn:   An array describing all the pins directly connected to GIC.
 * @ndirconns:  The number of pins directly connected to GIC
 * @dir_conn_irq_base:  Direct connect interrupt base register for kpss.
 */
struct msm_pinctrl_soc_data {
	const struct pinctrl_pin_desc *pins;
@@ -131,6 +136,7 @@ struct msm_pinctrl_soc_data {
	bool pull_no_keeper;
	const struct msm_dir_conn *dir_conn;
	unsigned int n_dir_conns;
	unsigned int dir_conn_irq_base;
};

int msm_pinctrl_probe(struct platform_device *pdev,