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

Commit d751cdee authored by Lina Iyer's avatar Lina Iyer Committed by Maulik Shah
Browse files

drivers: pinctrl: msm: setup GPIO irqchip hierarchy



To allow GPIOs to wakeup the system from suspend or deep idle, the
wakeup capable GPIOs are setup in hierarchy with interrupts from the
wakeup-parent irqchip.

In older SoC's, the TLMM will handover detection to the parent irqchip
and in newer SoC's, the parent irqchip may also be active as well as the
TLMM and therefore the GPIOs need to be masked at TLMM to avoid
duplicate interrupts. To enable both these configurations to exist,
allow the parent irqchip to dictate the TLMM irqchip's behavior when
masking/unmasking the interrupt.

Change-Id: I0d9368454ff1289d5fb1cac84e3f7333e8dbb8fe
Co-developed-by: default avatarStephen Boyd <swboyd@chromium.org>
Patch-mainline: https://lore.kernel.org/patchwork/patch/1026604/


Signed-off-by: default avatarLina Iyer <ilina@codeaurora.org>
parent 231ab5f2
Loading
Loading
Loading
Loading
+132 −15
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinctrl.h>
@@ -28,6 +29,7 @@
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/syscore_ops.h>
#include <linux/soc/qcom/irq.h>
#include <linux/reboot.h>
#include <linux/pm.h>
#include <linux/log2.h>
@@ -71,6 +73,7 @@ struct msm_pinctrl {

	DECLARE_BITMAP(dual_edge_irqs, MAX_NR_GPIO);
	DECLARE_BITMAP(enabled_irqs, MAX_NR_GPIO);
	DECLARE_BITMAP(wakeup_masked_irqs, MAX_NR_GPIO);

	const struct msm_pinctrl_soc_data *soc;
	void __iomem *regs;
@@ -640,6 +643,13 @@ static void msm_gpio_irq_mask(struct irq_data *d)

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

	if (d->parent_data)
		irq_chip_mask_parent(d);

	/* Monitored by parent wakeup controller? */
	if (test_bit(d->hwirq, pctrl->wakeup_masked_irqs))
		return;

	raw_spin_lock_irqsave(&pctrl->lock, flags);

	val = readl(pctrl->regs + g->intr_cfg_reg);
@@ -684,6 +694,13 @@ static void msm_gpio_irq_unmask(struct irq_data *d)

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

	if (d->parent_data)
		irq_chip_unmask_parent(d);

	/* Monitored by parent wakeup controller? Keep masked */
	if (test_bit(d->hwirq, pctrl->wakeup_masked_irqs))
		return;

	raw_spin_lock_irqsave(&pctrl->lock, flags);

	val = readl(pctrl->regs + g->intr_cfg_reg);
@@ -704,6 +721,10 @@ static void msm_gpio_irq_ack(struct irq_data *d)
	unsigned long flags;
	u32 val;

	/* Handled by parent wakeup controller? Do nothing */
	if (test_bit(d->hwirq, pctrl->wakeup_masked_irqs))
		return;

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

	raw_spin_lock_irqsave(&pctrl->lock, flags);
@@ -731,6 +752,13 @@ static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int type)

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

	if (d->parent_data)
		irq_chip_set_type_parent(d, type);

	/* Monitored by parent wakeup controller? Keep masked */
	if (test_bit(d->hwirq, pctrl->wakeup_masked_irqs))
		return 0;

	raw_spin_lock_irqsave(&pctrl->lock, flags);

	/*
@@ -821,6 +849,9 @@ static int msm_gpio_irq_set_wake(struct irq_data *d, unsigned int on)
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	unsigned long flags;

	if (d->parent_data)
		return irq_chip_set_wake_parent(d, on);

	raw_spin_lock_irqsave(&pctrl->lock, flags);

	irq_set_irq_wake(pctrl->irq, on);
@@ -904,11 +935,90 @@ static bool msm_gpio_needs_valid_mask(struct msm_pinctrl *pctrl)
	return device_property_read_u16_array(pctrl->dev, "gpios", NULL, 0) > 0;
}

static int msm_gpio_domain_translate(struct irq_domain *d,
				     struct irq_fwspec *fwspec,
				     unsigned long *hwirq, unsigned int *type)
{
	if (is_of_node(fwspec->fwnode)) {
		if (fwspec->param_count < 2)
			return -EINVAL;
		*hwirq = fwspec->param[0];
		*type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
		return 0;
	}

	return 0;
}

static int msm_gpio_domain_alloc(struct irq_domain *domain, unsigned int virq,
				 unsigned int nr_irqs, void *arg)
{
	int ret;
	irq_hw_number_t hwirq;
	struct gpio_chip *gc = domain->host_data;
	struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
	struct irq_fwspec *fwspec = arg;
	struct qcom_irq_fwspec parent = { };
	unsigned int type;

	ret = msm_gpio_domain_translate(domain, fwspec, &hwirq, &type);
	if (ret)
		return ret;

	ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
					    &pctrl->irq_chip, gc);
	if (ret < 0)
		return ret;

	if (!domain->parent)
		return 0;

	parent.fwspec.param_count = 2;
	parent.fwspec.param[0] = GPIO_NO_WAKE_IRQ;
	parent.fwspec.param[1] = type;
	ret = of_irq_domain_map(fwspec, &parent.fwspec);
	if (ret == -ENOMEM)
		return ret;

	parent.fwspec.fwnode = domain->parent->fwnode;

	ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent);
	if (ret)
		return ret;

	if (parent.mask)
		set_bit(hwirq, pctrl->wakeup_masked_irqs);

	return 0;
}

/*
 * TODO: Get rid of this and push it into gpiochip_to_irq()
 */
static int msm_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
{
	struct irq_fwspec fwspec;

	fwspec.fwnode = of_node_to_fwnode(chip->of_node);
	fwspec.param[0] = offset;
	fwspec.param[1] = IRQ_TYPE_NONE;
	fwspec.param_count = 2;

	return irq_create_fwspec_mapping(&fwspec);
}

static const struct irq_domain_ops msm_gpio_domain_ops = {
	.translate = msm_gpio_domain_translate,
	.alloc     = msm_gpio_domain_alloc,
	.free      = irq_domain_free_irqs_top,
};

static int msm_gpio_init(struct msm_pinctrl *pctrl)
{
	struct gpio_chip *chip;
	int ret;
	unsigned ngpio = pctrl->soc->ngpios;
	struct device_node *dn;

	if (WARN_ON(ngpio > MAX_NR_GPIO))
		return -EINVAL;
@@ -923,12 +1033,29 @@ static int msm_gpio_init(struct msm_pinctrl *pctrl)
	chip->need_valid_mask = msm_gpio_needs_valid_mask(pctrl);

	pctrl->irq_chip.name = "msmgpio";
	pctrl->irq_chip.irq_eoi	= irq_chip_eoi_parent;
	pctrl->irq_chip.irq_mask = msm_gpio_irq_mask;
	pctrl->irq_chip.irq_unmask = msm_gpio_irq_unmask;
	pctrl->irq_chip.irq_ack = msm_gpio_irq_ack;
	pctrl->irq_chip.irq_set_type = msm_gpio_irq_set_type;
	pctrl->irq_chip.irq_set_wake = msm_gpio_irq_set_wake;

	chip->irq.chip = &pctrl->irq_chip;
	chip->irq.handler = handle_edge_irq;
	chip->irq.default_type = IRQ_TYPE_NONE;

	dn = of_parse_phandle(pctrl->dev->of_node, "wakeup-parent", 0);
	if (dn) {
		chip->irq.parent_domain = irq_find_matching_host(dn,
						 DOMAIN_BUS_WAKEUP);
		of_node_put(dn);
		if (!chip->irq.parent_domain)
			return -EPROBE_DEFER;

		chip->to_irq = msm_gpio_to_irq;
		chip->irq.domain_ops = &msm_gpio_domain_ops;
	}

	ret = gpiochip_add_data(&pctrl->chip, pctrl);
	if (ret) {
		dev_err(pctrl->dev, "Failed register gpiochip\n");
@@ -938,8 +1065,7 @@ static int msm_gpio_init(struct msm_pinctrl *pctrl)
	ret = msm_gpio_init_valid_mask(chip, pctrl);
	if (ret) {
		dev_err(pctrl->dev, "Failed to setup irq valid bits\n");
		gpiochip_remove(&pctrl->chip);
		return ret;
		goto fail;
	}

	/*
@@ -957,26 +1083,17 @@ static int msm_gpio_init(struct msm_pinctrl *pctrl)
			dev_name(pctrl->dev), 0, 0, chip->ngpio);
		if (ret) {
			dev_err(pctrl->dev, "Failed to add pin range\n");
			gpiochip_remove(&pctrl->chip);
			return ret;
			goto fail;
		}
	}

	ret = gpiochip_irqchip_add(chip,
				   &pctrl->irq_chip,
				   0,
				   handle_edge_irq,
				   IRQ_TYPE_NONE);
	if (ret) {
		dev_err(pctrl->dev, "Failed to add irqchip to gpiochip\n");
		gpiochip_remove(&pctrl->chip);
		return -ENOSYS;
	}

	gpiochip_set_chained_irqchip(chip, &pctrl->irq_chip, pctrl->irq,
				     msm_gpio_irq_handler);

	return 0;
fail:
	gpiochip_remove(&pctrl->chip);
	return ret;
}

static int msm_ps_hold_restart(struct notifier_block *nb, unsigned long action,