pinctrl: single: Add support for wake-up interrupts

The pin control registers can have interrupts for example
for device wake-up. These interrupts can be treated as a
chained interrupt controller as suggested earlier by
Linus Walleij <linus.walleij@linaro.org>.

This patch adds support for interrupts in a way that
should be pretty generic, and works for the omaps that
support wake-up interrupts. On omaps, there's an
interrupt enable and interrupt status bit for each pin.
The two pinctrl domains on omaps share a single interrupt
from the PRM chained interrupt handler. Support for
other similar hardware should be easy to add.

Note that this patch does not attempt to handle the
wake-up interrupts automatically unlike the earlier
patches. This patch allows the device drivers to do
a request_irq() on the wake-up pins as needed. I'll
try to do also a separate generic patch for handling
the wake-up events automatically.

Also note that as this patch makes the pinctrl-single
an irq controller, the current bindings need some
extra trickery to use interrupts from two different
interrupt controllers for the same driver. So it
might be worth waiting a little on the patches
enabling the wake-up interrupts from drivers as there
should be a generic way to handle it coming. And also
there's been discussion of interrupts-extended binding
for using interrupts from multiple interrupt controllers.

In any case, this patch should be ready to go allowing
handling the wake-up interrupts in a generic way, or
separately from the device drivers.

Cc: Peter Ujfalusi <peter.ujfalusi@ti.com>
Cc: Grygorii Strashko <grygorii.strashko@ti.com>
Cc: Prakash Manjunathappa <prakash.pm@ti.com>
Cc: Roger Quadros <rogerq@ti.com>
Cc: linux-kernel@vger.kernel.org
Cc: Benoît Cousson <bcousson@baylibre.com>
Cc: devicetree@vger.kernel.org
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Haojian Zhuang <haojian.zhuang@gmail.com>
Reviewed-by: Kevin Hilman <khilman@linaro.org>
Tested-by: Kevin Hilman <khilman@linaro.org>
Signed-off-by: Tony Lindgren <tony@atomide.com>
This commit is contained in:
Tony Lindgren 2013-10-02 21:39:40 -07:00
parent 02e483f66d
commit 3e6cee1786
2 changed files with 333 additions and 0 deletions

View File

@ -72,6 +72,13 @@ Optional properties:
/* pin base, nr pins & gpio function */ /* pin base, nr pins & gpio function */
pinctrl-single,gpio-range = <&range 0 3 0 &range 3 9 1>; pinctrl-single,gpio-range = <&range 0 3 0 &range 3 9 1>;
- interrupt-controller : standard interrupt controller binding if using
interrupts for wake-up events for example. In this case pinctrl-single
is set up as a chained interrupt controller and the wake-up interrupts
can be requested by the drivers using request_irq().
- #interrupt-cells : standard interrupt binding if using interrupts
This driver assumes that there is only one register for each pin (unless the This driver assumes that there is only one register for each pin (unless the
pinctrl-single,bit-per-mux is set), and uses the common pinctrl bindings as pinctrl-single,bit-per-mux is set), and uses the common pinctrl bindings as
specified in the pinctrl-bindings.txt document in this directory. specified in the pinctrl-bindings.txt document in this directory.
@ -121,6 +128,8 @@ pmx_core: pinmux@4a100040 {
reg = <0x4a100040 0x0196>; reg = <0x4a100040 0x0196>;
#address-cells = <1>; #address-cells = <1>;
#size-cells = <0>; #size-cells = <0>;
#interrupt-cells = <1>;
interrupt-controller;
pinctrl-single,register-width = <16>; pinctrl-single,register-width = <16>;
pinctrl-single,function-mask = <0xffff>; pinctrl-single,function-mask = <0xffff>;
}; };
@ -131,6 +140,8 @@ pmx_wkup: pinmux@4a31e040 {
reg = <0x4a31e040 0x0038>; reg = <0x4a31e040 0x0038>;
#address-cells = <1>; #address-cells = <1>;
#size-cells = <0>; #size-cells = <0>;
#interrupt-cells = <1>;
interrupt-controller;
pinctrl-single,register-width = <16>; pinctrl-single,register-width = <16>;
pinctrl-single,function-mask = <0xffff>; pinctrl-single,function-mask = <0xffff>;
}; };

View File

@ -15,10 +15,14 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/of_address.h> #include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/pinctrl/pinctrl.h> #include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h> #include <linux/pinctrl/pinmux.h>
@ -152,9 +156,15 @@ struct pcs_name {
/** /**
* struct pcs_soc_data - SoC specific settings * struct pcs_soc_data - SoC specific settings
* @flags: initial SoC specific PCS_FEAT_xxx values * @flags: initial SoC specific PCS_FEAT_xxx values
* @irq: optional interrupt for the controller
* @irq_enable_mask: optional SoC specific interrupt enable mask
* @irq_status_mask: optional SoC specific interrupt status mask
*/ */
struct pcs_soc_data { struct pcs_soc_data {
unsigned flags; unsigned flags;
int irq;
unsigned irq_enable_mask;
unsigned irq_status_mask;
}; };
/** /**
@ -165,6 +175,7 @@ struct pcs_soc_data {
* @dev: device entry * @dev: device entry
* @pctl: pin controller device * @pctl: pin controller device
* @flags: mask of PCS_FEAT_xxx values * @flags: mask of PCS_FEAT_xxx values
* @lock: spinlock for register access
* @mutex: mutex protecting the lists * @mutex: mutex protecting the lists
* @width: bits per mux register * @width: bits per mux register
* @fmask: function register mask * @fmask: function register mask
@ -179,6 +190,9 @@ struct pcs_soc_data {
* @pingroups: list of pingroups * @pingroups: list of pingroups
* @functions: list of functions * @functions: list of functions
* @gpiofuncs: list of gpio functions * @gpiofuncs: list of gpio functions
* @irqs: list of interrupt registers
* @chip: chip container for this instance
* @domain: IRQ domain for this instance
* @ngroups: number of pingroups * @ngroups: number of pingroups
* @nfuncs: number of functions * @nfuncs: number of functions
* @desc: pin controller descriptor * @desc: pin controller descriptor
@ -192,7 +206,11 @@ struct pcs_device {
struct device *dev; struct device *dev;
struct pinctrl_dev *pctl; struct pinctrl_dev *pctl;
unsigned flags; unsigned flags;
#define PCS_QUIRK_SHARED_IRQ (1 << 2)
#define PCS_FEAT_IRQ (1 << 1)
#define PCS_FEAT_PINCONF (1 << 0) #define PCS_FEAT_PINCONF (1 << 0)
struct pcs_soc_data socdata;
raw_spinlock_t lock;
struct mutex mutex; struct mutex mutex;
unsigned width; unsigned width;
unsigned fmask; unsigned fmask;
@ -208,6 +226,9 @@ struct pcs_device {
struct list_head pingroups; struct list_head pingroups;
struct list_head functions; struct list_head functions;
struct list_head gpiofuncs; struct list_head gpiofuncs;
struct list_head irqs;
struct irq_chip chip;
struct irq_domain *domain;
unsigned ngroups; unsigned ngroups;
unsigned nfuncs; unsigned nfuncs;
struct pinctrl_desc desc; struct pinctrl_desc desc;
@ -215,6 +236,8 @@ struct pcs_device {
void (*write)(unsigned val, void __iomem *reg); void (*write)(unsigned val, void __iomem *reg);
}; };
#define PCS_QUIRK_HAS_SHARED_IRQ (pcs->flags & PCS_QUIRK_SHARED_IRQ)
#define PCS_HAS_IRQ (pcs->flags & PCS_FEAT_IRQ)
#define PCS_HAS_PINCONF (pcs->flags & PCS_FEAT_PINCONF) #define PCS_HAS_PINCONF (pcs->flags & PCS_FEAT_PINCONF)
static int pcs_pinconf_get(struct pinctrl_dev *pctldev, unsigned pin, static int pcs_pinconf_get(struct pinctrl_dev *pctldev, unsigned pin,
@ -440,9 +463,11 @@ static int pcs_enable(struct pinctrl_dev *pctldev, unsigned fselector,
for (i = 0; i < func->nvals; i++) { for (i = 0; i < func->nvals; i++) {
struct pcs_func_vals *vals; struct pcs_func_vals *vals;
unsigned long flags;
unsigned val, mask; unsigned val, mask;
vals = &func->vals[i]; vals = &func->vals[i];
raw_spin_lock_irqsave(&pcs->lock, flags);
val = pcs->read(vals->reg); val = pcs->read(vals->reg);
if (pcs->bits_per_mux) if (pcs->bits_per_mux)
@ -453,6 +478,7 @@ static int pcs_enable(struct pinctrl_dev *pctldev, unsigned fselector,
val &= ~mask; val &= ~mask;
val |= (vals->val & mask); val |= (vals->val & mask);
pcs->write(val, vals->reg); pcs->write(val, vals->reg);
raw_spin_unlock_irqrestore(&pcs->lock, flags);
} }
return 0; return 0;
@ -494,13 +520,16 @@ static void pcs_disable(struct pinctrl_dev *pctldev, unsigned fselector,
for (i = 0; i < func->nvals; i++) { for (i = 0; i < func->nvals; i++) {
struct pcs_func_vals *vals; struct pcs_func_vals *vals;
unsigned long flags;
unsigned val; unsigned val;
vals = &func->vals[i]; vals = &func->vals[i];
raw_spin_lock_irqsave(&pcs->lock, flags);
val = pcs->read(vals->reg); val = pcs->read(vals->reg);
val &= ~pcs->fmask; val &= ~pcs->fmask;
val |= pcs->foff << pcs->fshift; val |= pcs->foff << pcs->fshift;
pcs->write(val, vals->reg); pcs->write(val, vals->reg);
raw_spin_unlock_irqrestore(&pcs->lock, flags);
} }
} }
@ -1450,12 +1479,34 @@ static void pcs_free_pingroups(struct pcs_device *pcs)
mutex_unlock(&pcs->mutex); mutex_unlock(&pcs->mutex);
} }
/**
* pcs_irq_free() - free interrupt
* @pcs: pcs driver instance
*/
static void pcs_irq_free(struct pcs_device *pcs)
{
struct pcs_soc_data *pcs_soc = &pcs->socdata;
if (pcs_soc->irq < 0)
return;
if (pcs->domain)
irq_domain_remove(pcs->domain);
if (PCS_QUIRK_HAS_SHARED_IRQ)
free_irq(pcs_soc->irq, pcs_soc);
else
irq_set_chained_handler(pcs_soc->irq, NULL);
}
/** /**
* pcs_free_resources() - free memory used by this driver * pcs_free_resources() - free memory used by this driver
* @pcs: pcs driver instance * @pcs: pcs driver instance
*/ */
static void pcs_free_resources(struct pcs_device *pcs) static void pcs_free_resources(struct pcs_device *pcs)
{ {
pcs_irq_free(pcs);
if (pcs->pctl) if (pcs->pctl)
pinctrl_unregister(pcs->pctl); pinctrl_unregister(pcs->pctl);
@ -1504,6 +1555,256 @@ static int pcs_add_gpio_func(struct device_node *node, struct pcs_device *pcs)
} }
return ret; return ret;
} }
/**
* @reg: virtual address of interrupt register
* @hwirq: hardware irq number
* @irq: virtual irq number
* @node: list node
*/
struct pcs_interrupt {
void __iomem *reg;
irq_hw_number_t hwirq;
unsigned int irq;
struct list_head node;
};
/**
* pcs_irq_set() - enables or disables an interrupt
*
* Note that this currently assumes one interrupt per pinctrl
* register that is typically used for wake-up events.
*/
static inline void pcs_irq_set(struct pcs_soc_data *pcs_soc,
int irq, const bool enable)
{
struct pcs_device *pcs;
struct list_head *pos;
unsigned mask;
pcs = container_of(pcs_soc, struct pcs_device, socdata);
list_for_each(pos, &pcs->irqs) {
struct pcs_interrupt *pcswi;
unsigned soc_mask;
pcswi = list_entry(pos, struct pcs_interrupt, node);
if (irq != pcswi->irq)
continue;
soc_mask = pcs_soc->irq_enable_mask;
raw_spin_lock(&pcs->lock);
mask = pcs->read(pcswi->reg);
if (enable)
mask |= soc_mask;
else
mask &= ~soc_mask;
pcs->write(mask, pcswi->reg);
raw_spin_unlock(&pcs->lock);
}
}
/**
* pcs_irq_mask() - mask pinctrl interrupt
* @d: interrupt data
*/
static void pcs_irq_mask(struct irq_data *d)
{
struct pcs_soc_data *pcs_soc = irq_data_get_irq_chip_data(d);
pcs_irq_set(pcs_soc, d->irq, false);
}
/**
* pcs_irq_unmask() - unmask pinctrl interrupt
* @d: interrupt data
*/
static void pcs_irq_unmask(struct irq_data *d)
{
struct pcs_soc_data *pcs_soc = irq_data_get_irq_chip_data(d);
pcs_irq_set(pcs_soc, d->irq, true);
}
/**
* pcs_irq_set_wake() - toggle the suspend and resume wake up
* @d: interrupt data
* @state: wake-up state
*
* Note that this should be called only for suspend and resume.
* For runtime PM, the wake-up events should be enabled by default.
*/
static int pcs_irq_set_wake(struct irq_data *d, unsigned int state)
{
if (state)
pcs_irq_unmask(d);
else
pcs_irq_mask(d);
return 0;
}
/**
* pcs_irq_handle() - common interrupt handler
* @pcs_irq: interrupt data
*
* Note that this currently assumes we have one interrupt bit per
* mux register. This interrupt is typically used for wake-up events.
* For more complex interrupts different handlers can be specified.
*/
static int pcs_irq_handle(struct pcs_soc_data *pcs_soc)
{
struct pcs_device *pcs;
struct list_head *pos;
int count = 0;
pcs = container_of(pcs_soc, struct pcs_device, socdata);
list_for_each(pos, &pcs->irqs) {
struct pcs_interrupt *pcswi;
unsigned mask;
pcswi = list_entry(pos, struct pcs_interrupt, node);
raw_spin_lock(&pcs->lock);
mask = pcs->read(pcswi->reg);
raw_spin_unlock(&pcs->lock);
if (mask & pcs_soc->irq_status_mask) {
generic_handle_irq(irq_find_mapping(pcs->domain,
pcswi->hwirq));
count++;
}
}
return count;
}
/**
* pcs_irq_handler() - handler for the shared interrupt case
* @irq: interrupt
* @d: data
*
* Use this for cases where multiple instances of
* pinctrl-single share a single interrupt like on omaps.
*/
static irqreturn_t pcs_irq_handler(int irq, void *d)
{
struct pcs_soc_data *pcs_soc = d;
return pcs_irq_handle(pcs_soc) ? IRQ_HANDLED : IRQ_NONE;
}
/**
* pcs_irq_handle() - handler for the dedicated chained interrupt case
* @irq: interrupt
* @desc: interrupt descriptor
*
* Use this if you have a separate interrupt for each
* pinctrl-single instance.
*/
static void pcs_irq_chain_handler(unsigned int irq, struct irq_desc *desc)
{
struct pcs_soc_data *pcs_soc = irq_desc_get_handler_data(desc);
struct irq_chip *chip;
int res;
chip = irq_get_chip(irq);
chained_irq_enter(chip, desc);
res = pcs_irq_handle(pcs_soc);
/* REVISIT: export and add handle_bad_irq(irq, desc)? */
chained_irq_exit(chip, desc);
return;
}
static int pcs_irqdomain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
struct pcs_soc_data *pcs_soc = d->host_data;
struct pcs_device *pcs;
struct pcs_interrupt *pcswi;
pcs = container_of(pcs_soc, struct pcs_device, socdata);
pcswi = devm_kzalloc(pcs->dev, sizeof(*pcswi), GFP_KERNEL);
if (!pcswi)
return -ENOMEM;
pcswi->reg = pcs->base + hwirq;
pcswi->hwirq = hwirq;
pcswi->irq = irq;
mutex_lock(&pcs->mutex);
list_add_tail(&pcswi->node, &pcs->irqs);
mutex_unlock(&pcs->mutex);
irq_set_chip_data(irq, pcs_soc);
irq_set_chip_and_handler(irq, &pcs->chip,
handle_level_irq);
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
return 0;
}
static struct irq_domain_ops pcs_irqdomain_ops = {
.map = pcs_irqdomain_map,
.xlate = irq_domain_xlate_onecell,
};
/**
* pcs_irq_init_chained_handler() - set up a chained interrupt handler
* @pcs: pcs driver instance
* @np: device node pointer
*/
static int pcs_irq_init_chained_handler(struct pcs_device *pcs,
struct device_node *np)
{
struct pcs_soc_data *pcs_soc = &pcs->socdata;
const char *name = "pinctrl";
int num_irqs;
if (!pcs_soc->irq_enable_mask ||
!pcs_soc->irq_status_mask) {
pcs_soc->irq = -1;
return -EINVAL;
}
INIT_LIST_HEAD(&pcs->irqs);
pcs->chip.name = name;
pcs->chip.irq_ack = pcs_irq_mask;
pcs->chip.irq_mask = pcs_irq_mask;
pcs->chip.irq_unmask = pcs_irq_unmask;
pcs->chip.irq_set_wake = pcs_irq_set_wake;
if (PCS_QUIRK_HAS_SHARED_IRQ) {
int res;
res = request_irq(pcs_soc->irq, pcs_irq_handler,
IRQF_SHARED | IRQF_NO_SUSPEND,
name, pcs_soc);
if (res) {
pcs_soc->irq = -1;
return res;
}
} else {
irq_set_handler_data(pcs_soc->irq, pcs_soc);
irq_set_chained_handler(pcs_soc->irq,
pcs_irq_chain_handler);
}
/*
* We can use the register offset as the hardirq
* number as irq_domain_add_simple maps them lazily.
* This way we can easily support more than one
* interrupt per function if needed.
*/
num_irqs = pcs->size;
pcs->domain = irq_domain_add_simple(np, num_irqs, 0,
&pcs_irqdomain_ops,
pcs_soc);
if (!pcs->domain) {
irq_set_chained_handler(pcs_soc->irq, NULL);
return -EINVAL;
}
return 0;
}
#ifdef CONFIG_PM #ifdef CONFIG_PM
static int pinctrl_single_suspend(struct platform_device *pdev, static int pinctrl_single_suspend(struct platform_device *pdev,
@ -1549,12 +1850,14 @@ static int pcs_probe(struct platform_device *pdev)
return -ENOMEM; return -ENOMEM;
} }
pcs->dev = &pdev->dev; pcs->dev = &pdev->dev;
raw_spin_lock_init(&pcs->lock);
mutex_init(&pcs->mutex); mutex_init(&pcs->mutex);
INIT_LIST_HEAD(&pcs->pingroups); INIT_LIST_HEAD(&pcs->pingroups);
INIT_LIST_HEAD(&pcs->functions); INIT_LIST_HEAD(&pcs->functions);
INIT_LIST_HEAD(&pcs->gpiofuncs); INIT_LIST_HEAD(&pcs->gpiofuncs);
soc = match->data; soc = match->data;
pcs->flags = soc->flags; pcs->flags = soc->flags;
memcpy(&pcs->socdata, soc, sizeof(*soc));
PCS_GET_PROP_U32("pinctrl-single,register-width", &pcs->width, PCS_GET_PROP_U32("pinctrl-single,register-width", &pcs->width,
"register width not specified\n"); "register width not specified\n");
@ -1642,6 +1945,16 @@ static int pcs_probe(struct platform_device *pdev)
if (ret < 0) if (ret < 0)
goto free; goto free;
pcs->socdata.irq = irq_of_parse_and_map(np, 0);
if (pcs->socdata.irq)
pcs->flags |= PCS_FEAT_IRQ;
if (PCS_HAS_IRQ) {
ret = pcs_irq_init_chained_handler(pcs, np);
if (ret < 0)
dev_warn(pcs->dev, "initialized with no interrupts\n");
}
dev_info(pcs->dev, "%i pins at pa %p size %u\n", dev_info(pcs->dev, "%i pins at pa %p size %u\n",
pcs->desc.npins, pcs->base, pcs->size); pcs->desc.npins, pcs->base, pcs->size);
@ -1665,6 +1978,12 @@ static int pcs_remove(struct platform_device *pdev)
return 0; return 0;
} }
static const struct pcs_soc_data pinctrl_single_omap_wkup = {
.flags = PCS_QUIRK_SHARED_IRQ,
.irq_enable_mask = (1 << 14), /* OMAP_WAKEUP_EN */
.irq_status_mask = (1 << 15), /* OMAP_WAKEUP_EVENT */
};
static const struct pcs_soc_data pinctrl_single = { static const struct pcs_soc_data pinctrl_single = {
}; };
@ -1673,6 +1992,9 @@ static const struct pcs_soc_data pinconf_single = {
}; };
static struct of_device_id pcs_of_match[] = { static struct of_device_id pcs_of_match[] = {
{ .compatible = "ti,omap3-padconf", .data = &pinctrl_single_omap_wkup },
{ .compatible = "ti,omap4-padconf", .data = &pinctrl_single_omap_wkup },
{ .compatible = "ti,omap5-padconf", .data = &pinctrl_single_omap_wkup },
{ .compatible = "pinctrl-single", .data = &pinctrl_single }, { .compatible = "pinctrl-single", .data = &pinctrl_single },
{ .compatible = "pinconf-single", .data = &pinconf_single }, { .compatible = "pinconf-single", .data = &pinconf_single },
{ }, { },