summaryrefslogtreecommitdiff
path: root/drivers/pinctrl/intel/pinctrl-intel.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pinctrl/intel/pinctrl-intel.c')
-rw-r--r--drivers/pinctrl/intel/pinctrl-intel.c163
1 files changed, 91 insertions, 72 deletions
diff --git a/drivers/pinctrl/intel/pinctrl-intel.c b/drivers/pinctrl/intel/pinctrl-intel.c
index 54848b8de..26f6b6ffe 100644
--- a/drivers/pinctrl/intel/pinctrl-intel.c
+++ b/drivers/pinctrl/intel/pinctrl-intel.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
#include <linux/acpi.h>
#include <linux/gpio.h>
#include <linux/gpio/driver.h>
@@ -24,9 +25,6 @@
#include "pinctrl-intel.h"
-/* Maximum number of pads in each group */
-#define NPADS_IN_GPP 24
-
/* Offset from regs */
#define PADBAR 0x00c
#define GPI_IS 0x100
@@ -36,6 +34,7 @@
#define PADOWN_BITS 4
#define PADOWN_SHIFT(p) ((p) % 8 * PADOWN_BITS)
#define PADOWN_MASK(p) (0xf << PADOWN_SHIFT(p))
+#define PADOWN_GPP(p) ((p) / 8)
/* Offset from pad_regs */
#define PADCFG0 0x000
@@ -141,7 +140,7 @@ static void __iomem *intel_get_padcfg(struct intel_pinctrl *pctrl, unsigned pin,
static bool intel_pad_owned_by_host(struct intel_pinctrl *pctrl, unsigned pin)
{
const struct intel_community *community;
- unsigned padno, gpp, gpp_offset, offset;
+ unsigned padno, gpp, offset, group;
void __iomem *padown;
community = intel_get_community(pctrl, pin);
@@ -151,16 +150,15 @@ static bool intel_pad_owned_by_host(struct intel_pinctrl *pctrl, unsigned pin)
return true;
padno = pin_to_padno(community, pin);
- gpp = padno / NPADS_IN_GPP;
- gpp_offset = padno % NPADS_IN_GPP;
- offset = community->padown_offset + gpp * 16 + (gpp_offset / 8) * 4;
+ group = padno / community->gpp_size;
+ gpp = PADOWN_GPP(padno % community->gpp_size);
+ offset = community->padown_offset + 0x10 * group + gpp * 4;
padown = community->regs + offset;
return !(readl(padown) & PADOWN_MASK(padno));
}
-static bool intel_pad_reserved_for_acpi(struct intel_pinctrl *pctrl,
- unsigned pin)
+static bool intel_pad_acpi_mode(struct intel_pinctrl *pctrl, unsigned pin)
{
const struct intel_community *community;
unsigned padno, gpp, offset;
@@ -173,11 +171,11 @@ static bool intel_pad_reserved_for_acpi(struct intel_pinctrl *pctrl,
return false;
padno = pin_to_padno(community, pin);
- gpp = padno / NPADS_IN_GPP;
+ gpp = padno / community->gpp_size;
offset = community->hostown_offset + gpp * 4;
hostown = community->regs + offset;
- return !(readl(hostown) & BIT(padno % NPADS_IN_GPP));
+ return !(readl(hostown) & BIT(padno % community->gpp_size));
}
static bool intel_pad_locked(struct intel_pinctrl *pctrl, unsigned pin)
@@ -193,7 +191,7 @@ static bool intel_pad_locked(struct intel_pinctrl *pctrl, unsigned pin)
return false;
padno = pin_to_padno(community, pin);
- gpp = padno / NPADS_IN_GPP;
+ gpp = padno / community->gpp_size;
/*
* If PADCFGLOCK and PADCFGLOCKTX bits are both clear for this pad,
@@ -202,12 +200,12 @@ static bool intel_pad_locked(struct intel_pinctrl *pctrl, unsigned pin)
*/
offset = community->padcfglock_offset + gpp * 8;
value = readl(community->regs + offset);
- if (value & BIT(pin % NPADS_IN_GPP))
+ if (value & BIT(pin % community->gpp_size))
return true;
offset = community->padcfglock_offset + 4 + gpp * 8;
value = readl(community->regs + offset);
- if (value & BIT(pin % NPADS_IN_GPP))
+ if (value & BIT(pin % community->gpp_size))
return true;
return false;
@@ -216,7 +214,6 @@ static bool intel_pad_locked(struct intel_pinctrl *pctrl, unsigned pin)
static bool intel_pad_usable(struct intel_pinctrl *pctrl, unsigned pin)
{
return intel_pad_owned_by_host(pctrl, pin) &&
- !intel_pad_reserved_for_acpi(pctrl, pin) &&
!intel_pad_locked(pctrl, pin);
}
@@ -269,7 +266,7 @@ static void intel_pin_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s,
seq_printf(s, "0x%08x 0x%08x", cfg0, cfg1);
locked = intel_pad_locked(pctrl, pin);
- acpi = intel_pad_reserved_for_acpi(pctrl, pin);
+ acpi = intel_pad_acpi_mode(pctrl, pin);
if (locked || acpi) {
seq_puts(s, " [");
@@ -597,16 +594,6 @@ static const struct pinctrl_desc intel_pinctrl_desc = {
.owner = THIS_MODULE,
};
-static int intel_gpio_request(struct gpio_chip *chip, unsigned offset)
-{
- return pinctrl_request_gpio(chip->base + offset);
-}
-
-static void intel_gpio_free(struct gpio_chip *chip, unsigned offset)
-{
- pinctrl_free_gpio(chip->base + offset);
-}
-
static int intel_gpio_get(struct gpio_chip *chip, unsigned offset)
{
struct intel_pinctrl *pctrl = gpiochip_to_pinctrl(chip);
@@ -654,8 +641,8 @@ static int intel_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
static const struct gpio_chip intel_gpio_chip = {
.owner = THIS_MODULE,
- .request = intel_gpio_request,
- .free = intel_gpio_free,
+ .request = gpiochip_generic_request,
+ .free = gpiochip_generic_free,
.direction_input = intel_gpio_direction_input,
.direction_output = intel_gpio_direction_output,
.get = intel_gpio_get,
@@ -674,8 +661,8 @@ static void intel_gpio_irq_ack(struct irq_data *d)
community = intel_get_community(pctrl, pin);
if (community) {
unsigned padno = pin_to_padno(community, pin);
- unsigned gpp_offset = padno % NPADS_IN_GPP;
- unsigned gpp = padno / NPADS_IN_GPP;
+ unsigned gpp_offset = padno % community->gpp_size;
+ unsigned gpp = padno / community->gpp_size;
writel(BIT(gpp_offset), community->regs + GPI_IS + gpp * 4);
}
@@ -696,8 +683,8 @@ static void intel_gpio_irq_mask_unmask(struct irq_data *d, bool mask)
community = intel_get_community(pctrl, pin);
if (community) {
unsigned padno = pin_to_padno(community, pin);
- unsigned gpp_offset = padno % NPADS_IN_GPP;
- unsigned gpp = padno / NPADS_IN_GPP;
+ unsigned gpp_offset = padno % community->gpp_size;
+ unsigned gpp = padno / community->gpp_size;
void __iomem *reg;
u32 value;
@@ -736,6 +723,16 @@ static int intel_gpio_irq_type(struct irq_data *d, unsigned type)
if (!reg)
return -EINVAL;
+ /*
+ * If the pin is in ACPI mode it is still usable as a GPIO but it
+ * cannot be used as IRQ because GPI_IS status bit will not be
+ * updated by the host controller hardware.
+ */
+ if (intel_pad_acpi_mode(pctrl, pin)) {
+ dev_warn(pctrl->dev, "pin %u cannot be used as IRQ\n", pin);
+ return -EPERM;
+ }
+
spin_lock_irqsave(&pctrl->lock, flags);
value = readl(reg);
@@ -781,8 +778,8 @@ static int intel_gpio_irq_wake(struct irq_data *d, unsigned int on)
return -EINVAL;
padno = pin_to_padno(community, pin);
- gpp = padno / NPADS_IN_GPP;
- gpp_offset = padno % NPADS_IN_GPP;
+ gpp = padno / community->gpp_size;
+ gpp_offset = padno % community->gpp_size;
/* Clear the existing wake status */
writel(BIT(gpp_offset), community->regs + GPI_GPE_STS + gpp * 4);
@@ -803,9 +800,11 @@ static int intel_gpio_irq_wake(struct irq_data *d, unsigned int on)
return 0;
}
-static void intel_gpio_community_irq_handler(struct gpio_chip *gc,
+static irqreturn_t intel_gpio_community_irq_handler(struct intel_pinctrl *pctrl,
const struct intel_community *community)
{
+ struct gpio_chip *gc = &pctrl->chip;
+ irqreturn_t ret = IRQ_NONE;
int gpp;
for (gpp = 0; gpp < community->ngpps; gpp++) {
@@ -818,38 +817,42 @@ static void intel_gpio_community_irq_handler(struct gpio_chip *gc,
/* Only interrupts that are enabled */
pending &= enabled;
- for_each_set_bit(gpp_offset, &pending, NPADS_IN_GPP) {
+ for_each_set_bit(gpp_offset, &pending, community->gpp_size) {
unsigned padno, irq;
/*
* The last group in community can have less pins
* than NPADS_IN_GPP.
*/
- padno = gpp_offset + gpp * NPADS_IN_GPP;
+ padno = gpp_offset + gpp * community->gpp_size;
if (padno >= community->npins)
break;
irq = irq_find_mapping(gc->irqdomain,
community->pin_base + padno);
generic_handle_irq(irq);
+
+ ret |= IRQ_HANDLED;
}
}
+
+ return ret;
}
-static void intel_gpio_irq_handler(struct irq_desc *desc)
+static irqreturn_t intel_gpio_irq(int irq, void *data)
{
- struct gpio_chip *gc = irq_desc_get_handler_data(desc);
- struct intel_pinctrl *pctrl = gpiochip_to_pinctrl(gc);
- struct irq_chip *chip = irq_desc_get_chip(desc);
+ const struct intel_community *community;
+ struct intel_pinctrl *pctrl = data;
+ irqreturn_t ret = IRQ_NONE;
int i;
- chained_irq_enter(chip, desc);
-
/* Need to check all communities for pending interrupts */
- for (i = 0; i < pctrl->ncommunities; i++)
- intel_gpio_community_irq_handler(gc, &pctrl->communities[i]);
+ for (i = 0; i < pctrl->ncommunities; i++) {
+ community = &pctrl->communities[i];
+ ret |= intel_gpio_community_irq_handler(pctrl, community);
+ }
- chained_irq_exit(chip, desc);
+ return ret;
}
static struct irq_chip intel_gpio_irqchip = {
@@ -861,26 +864,6 @@ static struct irq_chip intel_gpio_irqchip = {
.irq_set_wake = intel_gpio_irq_wake,
};
-static void intel_gpio_irq_init(struct intel_pinctrl *pctrl)
-{
- size_t i;
-
- for (i = 0; i < pctrl->ncommunities; i++) {
- const struct intel_community *community;
- void __iomem *base;
- unsigned gpp;
-
- community = &pctrl->communities[i];
- base = community->regs;
-
- for (gpp = 0; gpp < community->ngpps; gpp++) {
- /* Mask and clear all interrupts */
- writel(0, base + community->ie_offset + gpp * 4);
- writel(0xffff, base + GPI_IS + gpp * 4);
- }
- }
-}
-
static int intel_gpio_probe(struct intel_pinctrl *pctrl, int irq)
{
int ret;
@@ -902,21 +885,36 @@ static int intel_gpio_probe(struct intel_pinctrl *pctrl, int irq)
0, 0, pctrl->soc->npins);
if (ret) {
dev_err(pctrl->dev, "failed to add GPIO pin range\n");
- gpiochip_remove(&pctrl->chip);
- return ret;
+ goto fail;
+ }
+
+ /*
+ * We need to request the interrupt here (instead of providing chip
+ * to the irq directly) because on some platforms several GPIO
+ * controllers share the same interrupt line.
+ */
+ ret = devm_request_irq(pctrl->dev, irq, intel_gpio_irq, IRQF_SHARED,
+ dev_name(pctrl->dev), pctrl);
+ if (ret) {
+ dev_err(pctrl->dev, "failed to request interrupt\n");
+ goto fail;
}
ret = gpiochip_irqchip_add(&pctrl->chip, &intel_gpio_irqchip, 0,
handle_simple_irq, IRQ_TYPE_NONE);
if (ret) {
dev_err(pctrl->dev, "failed to add irqchip\n");
- gpiochip_remove(&pctrl->chip);
- return ret;
+ goto fail;
}
gpiochip_set_chained_irqchip(&pctrl->chip, &intel_gpio_irqchip, irq,
- intel_gpio_irq_handler);
+ NULL);
return 0;
+
+fail:
+ gpiochip_remove(&pctrl->chip);
+
+ return ret;
}
static int intel_pinctrl_pm_init(struct intel_pinctrl *pctrl)
@@ -1002,7 +1000,8 @@ int intel_pinctrl_probe(struct platform_device *pdev,
community->regs = regs;
community->pad_regs = regs + padbar;
- community->ngpps = DIV_ROUND_UP(community->npins, NPADS_IN_GPP);
+ community->ngpps = DIV_ROUND_UP(community->npins,
+ community->gpp_size);
}
irq = platform_get_irq(pdev, 0);
@@ -1087,6 +1086,26 @@ int intel_pinctrl_suspend(struct device *dev)
}
EXPORT_SYMBOL_GPL(intel_pinctrl_suspend);
+static void intel_gpio_irq_init(struct intel_pinctrl *pctrl)
+{
+ size_t i;
+
+ for (i = 0; i < pctrl->ncommunities; i++) {
+ const struct intel_community *community;
+ void __iomem *base;
+ unsigned gpp;
+
+ community = &pctrl->communities[i];
+ base = community->regs;
+
+ for (gpp = 0; gpp < community->ngpps; gpp++) {
+ /* Mask and clear all interrupts */
+ writel(0, base + community->ie_offset + gpp * 4);
+ writel(0xffff, base + GPI_IS + gpp * 4);
+ }
+ }
+}
+
int intel_pinctrl_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);