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

Commit 9d25ab83 authored by qctecmdr Service's avatar qctecmdr Service Committed by Gerrit - the friendly Code Review server
Browse files

Merge "pinctrl: qcom: Support using tlmm direct connect GPIOs"

parents 34125a10 ee483a9e
Loading
Loading
Loading
Loading
+234 −45
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ struct msm_pinctrl {

	const struct msm_pinctrl_soc_data *soc;
	void __iomem *regs;
	void __iomem *pdc_regs;
#ifdef CONFIG_FRAGMENTED_GPIO_ADDRESS_SPACE
	/* For holding per tile virtual address */
	void __iomem *per_tile_regs[4];
@@ -975,6 +976,158 @@ static const struct irq_domain_ops msm_gpio_domain_ops = {
	.free		= irq_domain_free_irqs_top,
};

static struct irq_chip msm_dirconn_irq_chip;

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);

	chained_irq_enter(chip, desc);
	generic_handle_irq(irqd->irq);
	chained_irq_exit(chip, desc);
}

static void setup_pdc_gpio(struct irq_domain *domain,
			unsigned int parent_irq, unsigned int gpio)
{
	int irq;

	if (gpio != 0) {
		irq = irq_create_mapping(domain, gpio);
		irq_set_parent(irq, parent_irq);
		irq_set_chip(irq, &msm_dirconn_irq_chip);
		irq_set_handler_data(parent_irq, irq_get_irq_data(irq));
	}

	__irq_set_handler(parent_irq, msm_gpio_dirconn_handler, false, NULL);
}

static void request_dc_interrupt(struct irq_domain *domain,
			struct irq_domain *parent, irq_hw_number_t hwirq,
			unsigned int gpio)
{
	struct irq_fwspec fwspec;
	unsigned int parent_irq;

	fwspec.fwnode = parent->fwnode;
	fwspec.param[0] = 0; /* SPI */
	fwspec.param[1] = hwirq;
	fwspec.param[2] = IRQ_TYPE_NONE;
	fwspec.param_count = 3;

	parent_irq = irq_create_fwspec_mapping(&fwspec);

	setup_pdc_gpio(domain, parent_irq, gpio);
}

/**
 * gpio_muxed_to_pdc: Mux the GPIO to a PDC IRQ
 *
 * @pdc_domain: the PDC's domain
 * @d: the GPIO's IRQ data
 *
 * Find a free PDC port for the GPIO and map the GPIO's mux information to the
 * PDC registers; so the GPIO can be used a wakeup source.
 */
static void gpio_muxed_to_pdc(struct irq_domain *pdc_domain, struct irq_data *d)
{
	int i, j;
	unsigned int mux;
	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);
	unsigned int gpio = d->hwirq;
	struct msm_pinctrl *pctrl;
	unsigned int irq;

	if (!gc || !parent_data)
		return;

	pctrl = gpiochip_get_data(gc);

	for (i = 0; i < pctrl->soc->n_gpio_mux_in; i++) {
		if (gpio != pctrl->soc->gpio_mux_in[i].gpio)
			continue;
		mux = pctrl->soc->gpio_mux_in[i].mux;
		for (j = 0; j < pctrl->soc->n_pdc_mux_out; j++) {
			struct msm_pdc_mux_output *pdc_out =
						&pctrl->soc->pdc_mux_out[j];

			if (pdc_out->mux == mux)
				break;
			if (pdc_out->mux)
				continue;
			pdc_out->mux = gpio;
			irq = irq_find_mapping(pdc_domain, pdc_out->hwirq + 32);
			/* setup the IRQ parent for the GPIO */
			setup_pdc_gpio(pctrl->chip.irqdomain, irq, gpio);
			/* program pdc select grp register */
			writel_relaxed((mux & 0x3F), pctrl->pdc_regs +
				(0x14 * j));
			break;
		}
		/* We have no more PDC port available */
		WARN_ON(j == pctrl->soc->n_pdc_mux_out);
	}
}

static bool is_gpio_tlmm_dc(struct irq_data *d, u32 type)
{
	const struct msm_pingroup *g;
	unsigned long flags;
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct msm_pinctrl *pctrl;
	bool ret = false;
	unsigned int polarity = 0, offset, val;
	int i;
	void __iomem *base;

	if (!gc)
		return false;

	pctrl = gpiochip_get_data(gc);

	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];

		if (dir_conn->gpio == d->hwirq && dir_conn->tlmm_dc) {
			ret = true;
			offset = pctrl->soc->dir_conn_irq_base -
				dir_conn->hwirq;
			break;
		}
	}

	if (!ret)
		return ret;

	if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW))
		return ret;

	/*
	 * Since the default polarity is set to 0, change it to 1 for
	 * Rising edge and active high interrupt type such that the line
	 * is not inverted.
	 */
	polarity = 1;

	raw_spin_lock_irqsave(&pctrl->lock, flags);

	g = &pctrl->soc->groups[d->hwirq];
	base = reassign_pctrl_reg(pctrl->soc, d->hwirq);

	val = readl_relaxed(base + g->dir_conn_reg + (offset * 4));
	val |= polarity << 8;

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

	raw_spin_unlock_irqrestore(&pctrl->lock, flags);

	return ret;
}

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);
@@ -995,6 +1148,17 @@ static bool is_gpio_dual_edge(struct irq_data *d, irq_hw_number_t *dir_conn_irq)
			return true;
		}
	}

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

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

@@ -1012,9 +1176,13 @@ static void msm_dirconn_irq_mask(struct irq_data *d)
			irq_get_irq_data(irq_find_mapping(parent_data->domain,
						dir_conn_irq));

		if (dir_conn_data && dir_conn_data->chip->irq_mask)
		if (!dir_conn_data)
			return;

		if (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);
}
@@ -1065,7 +1233,10 @@ static void msm_dirconn_irq_unmask(struct irq_data *d)
			irq_get_irq_data(irq_find_mapping(parent_data->domain,
						dir_conn_irq));

		if (dir_conn_data && dir_conn_data->chip->irq_unmask)
		if (!dir_conn_data)
			return;

		if (dir_conn_data->chip->irq_unmask)
			dir_conn_data->chip->irq_unmask(dir_conn_data);
	}
	if (parent_data->chip->irq_unmask)
@@ -1264,12 +1435,12 @@ static int msm_dirconn_irq_set_type(struct irq_data *d, unsigned int type)
	if (!parent_data)
		return 0;

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

	if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
		irq_set_handler_locked(d, handle_level_irq);
@@ -1333,57 +1504,72 @@ static void msm_gpio_irq_handler(struct irq_desc *desc)
	chained_irq_exit(chip, desc);
}

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);

	chained_irq_enter(chip, desc);
	generic_handle_irq(irqd->irq);
	chained_irq_exit(chip, desc);
}

static void msm_gpio_setup_dir_connects(struct msm_pinctrl *pctrl)
{
	struct device_node *parent_node;
	struct irq_domain *parent_domain;
	struct irq_fwspec fwspec;
	struct irq_domain *pdc_domain;
	unsigned int i;

	parent_node = of_irq_find_parent(pctrl->dev->of_node);

	if (!parent_node)
		return;

	parent_domain = irq_find_host(parent_node);
	if (!parent_domain)
	pdc_domain = irq_find_host(parent_node);
	if (!pdc_domain)
		return;

	fwspec.fwnode = parent_domain->fwnode;
	for (i = 0; i < pctrl->soc->n_dir_conns; i++) {
		const struct msm_dir_conn *dirconn = &pctrl->soc->dir_conn[i];
		unsigned int parent_irq;
		int irq;
		struct irq_data *d;

		fwspec.param[0] = 0; /* SPI */
		fwspec.param[1] = dirconn->hwirq;
		fwspec.param[2] = IRQ_TYPE_NONE;
		fwspec.param_count = 3;
		parent_irq = irq_create_fwspec_mapping(&fwspec);
		request_dc_interrupt(pctrl->chip.irqdomain, pdc_domain,
					dirconn->hwirq, dirconn->gpio);

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

			irq_set_parent(irq, parent_irq);
			irq_set_chip(irq, &msm_dirconn_irq_chip);
			__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);
		if (!dirconn->tlmm_dc)
			continue;

		/*
		 * If the gpio is routed through TLMM direct connect interrupts,
		 * program the TLMM registers for this setup.
		 */
		d = irq_get_irq_data(irq_find_mapping(pctrl->chip.irqdomain,
					dirconn->gpio));
		if (!d)
			continue;

		msm_dirconn_cfg_reg(d, pctrl->soc->dir_conn_irq_base
					- (u32)dirconn->hwirq);
	}

	for (i = 0; i < pctrl->soc->n_pdc_mux_out; i++) {
		struct msm_pdc_mux_output *pdc_out =
					&pctrl->soc->pdc_mux_out[i];

		request_dc_interrupt(pctrl->chip.irqdomain, pdc_domain,
					pdc_out->hwirq, 0);
	}

	/*
	 * Statically choose the GPIOs for mapping to PDC. Dynamic mux mapping
	 * is very difficult.
	 */
	for (i = 0; i < pctrl->soc->n_gpio_mux_in; i++) {
		unsigned int irq;
		struct irq_data *d;
		struct msm_gpio_mux_input *gpio_in =
					&pctrl->soc->gpio_mux_in[i];
		if (!gpio_in->init)
			continue;

		irq = irq_find_mapping(pctrl->chip.irqdomain, gpio_in->gpio);
		d = irq_get_irq_data(irq);
		if (!d)
			continue;

		gpio_muxed_to_pdc(pdc_domain, d);
	}
}

@@ -1554,6 +1740,9 @@ int msm_pinctrl_probe(struct platform_device *pdev,
	}
#endif

	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	pctrl->pdc_regs = devm_ioremap_resource(&pdev->dev, res);

	msm_pinctrl_setup_pm_reset(pctrl);

	pctrl->irq = platform_get_irq(pdev, 0);
+37 −2
Original line number Diff line number Diff line
/*
 * Copyright (c) 2013, Sony Mobile Communications AB.
 * Copyright (c) 2018, 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
@@ -104,16 +105,40 @@ struct msm_pingroup {
	unsigned intr_detection_bit:5;
	unsigned intr_detection_width:5;
	unsigned dir_conn_en_bit:8;
}
};

/**
 * struct msm_gpio_mux_input - Map GPIO to Mux pin
 * @mux::	The mux pin to which the GPIO is connected to
 * @gpio:	GPIO pin number
 * @init:	Setup PDC connection at probe
 */
struct msm_gpio_mux_input {
	unsigned int mux;
	unsigned int gpio;
	bool init;
};

/**
 * struct msm_pdc_mux_output - GPIO mux pin to PDC port
 * @mux:	GPIO mux pin number
 * @hwirq:	The PDC port (hwirq) that GPIO is connected to
 */
struct msm_pdc_mux_output {
	unsigned int mux;
	irq_hw_number_t hwirq;
};

/**
 * struct msm_dir_conn - Direct GPIO connect configuration
 * @gpio:	GPIO pin number
 * @hwirq:	The GIC interrupt that the pin is connected to
 */;
 * @tlmm_dc:	indicates if the GPIO is routed to GIC directly
 */
struct msm_dir_conn {
	unsigned int gpio;
	irq_hw_number_t hwirq;
	bool tlmm_dc;
};

/**
@@ -129,6 +154,11 @@ struct msm_dir_conn {
 * @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.
 * @gpio_mux_in:	Map of GPIO pin to the hwirq.
 * @n_gpioc_mux_in:	The number of entries in @pdc_mux_in.
 * @pdc_mux_out:	Map of GPIO mux to PDC port.
 * @n_pdc_mux_out:	The number of entries in @pdc_mux_out.
 * @n_pdc_offset:	The offset for the PDC mux pins
 */
struct msm_pinctrl_soc_data {
	const struct pinctrl_pin_desc *pins;
@@ -142,6 +172,11 @@ struct msm_pinctrl_soc_data {
	const struct msm_dir_conn *dir_conn;
	unsigned int n_dir_conns;
	unsigned int dir_conn_irq_base;
	struct msm_pdc_mux_output *pdc_mux_out;
	unsigned int n_pdc_mux_out;
	struct msm_gpio_mux_input *gpio_mux_in;
	unsigned int n_gpio_mux_in;
	unsigned int n_pdc_mux_offset;
#ifdef CONFIG_FRAGMENTED_GPIO_ADDRESS_SPACE
	const u32 *tile_start;
	const u32 *tile_offsets;