summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpio-mxc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpio/gpio-mxc.c')
-rw-r--r--drivers/gpio/gpio-mxc.c616
1 files changed, 409 insertions, 207 deletions
diff --git a/drivers/gpio/gpio-mxc.c b/drivers/gpio/gpio-mxc.c
index 7176743915d3..d7666fe9dbf8 100644
--- a/drivers/gpio/gpio-mxc.c
+++ b/drivers/gpio/gpio-mxc.c
@@ -1,45 +1,37 @@
-/*
- * MXC GPIO support. (c) 2008 Daniel Mack <daniel@caiaq.de>
- * Copyright 2008 Juergen Beisert, kernel@pengutronix.de
- *
- * Based on code from Freescale,
- * Copyright (C) 2004-2010 Freescale Semiconductor, Inc. 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
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
+// SPDX-License-Identifier: GPL-2.0+
+//
+// MXC GPIO support. (c) 2008 Daniel Mack <daniel@caiaq.de>
+// Copyright 2008 Juergen Beisert, kernel@pengutronix.de
+//
+// Based on code from Freescale Semiconductor,
+// Authors: Daniel Mack, Juergen Beisert.
+// Copyright (C) 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
-#include <linux/gpio.h>
+#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
-#include <linux/basic_mmio_gpio.h>
+#include <linux/spinlock.h>
+#include <linux/syscore_ops.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/generic.h>
#include <linux/of.h>
-#include <linux/of_device.h>
-#include <linux/module.h>
-#include <asm-generic/bug.h>
+#include <linux/bug.h>
-enum mxc_gpio_hwtype {
- IMX1_GPIO, /* runs on i.mx1 */
- IMX21_GPIO, /* runs on i.mx21 and i.mx27 */
- IMX31_GPIO, /* runs on i.mx31 */
- IMX35_GPIO, /* runs on all other i.mx */
-};
+#define IMX_SCU_WAKEUP_OFF 0
+#define IMX_SCU_WAKEUP_LOW_LVL 4
+#define IMX_SCU_WAKEUP_FALL_EDGE 5
+#define IMX_SCU_WAKEUP_RISE_EDGE 6
+#define IMX_SCU_WAKEUP_HIGH_LVL 7
/* device type dependent stuff */
struct mxc_gpio_hwdata {
@@ -57,14 +49,32 @@ struct mxc_gpio_hwdata {
unsigned fall_edge;
};
+struct mxc_gpio_reg_saved {
+ u32 icr1;
+ u32 icr2;
+ u32 imr;
+ u32 gdir;
+ u32 edge_sel;
+ u32 dr;
+};
+
struct mxc_gpio_port {
struct list_head node;
void __iomem *base;
+ struct clk *clk;
int irq;
int irq_high;
+ void (*mx_irq_handler)(struct irq_desc *desc);
struct irq_domain *domain;
- struct bgpio_chip bgc;
+ struct gpio_generic_chip gen_gc;
+ struct device *dev;
u32 both_edges;
+ struct mxc_gpio_reg_saved gpio_saved_reg;
+ bool power_off;
+ u32 wakeup_pads;
+ bool is_pad_wakeup;
+ u32 pad_type[32];
+ const struct mxc_gpio_hwdata *hwdata;
};
static struct mxc_gpio_hwdata imx1_imx21_gpio_hwdata = {
@@ -112,49 +122,33 @@ static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
.fall_edge = 0x03,
};
-static enum mxc_gpio_hwtype mxc_gpio_hwtype;
-static struct mxc_gpio_hwdata *mxc_gpio_hwdata;
-
-#define GPIO_DR (mxc_gpio_hwdata->dr_reg)
-#define GPIO_GDIR (mxc_gpio_hwdata->gdir_reg)
-#define GPIO_PSR (mxc_gpio_hwdata->psr_reg)
-#define GPIO_ICR1 (mxc_gpio_hwdata->icr1_reg)
-#define GPIO_ICR2 (mxc_gpio_hwdata->icr2_reg)
-#define GPIO_IMR (mxc_gpio_hwdata->imr_reg)
-#define GPIO_ISR (mxc_gpio_hwdata->isr_reg)
-#define GPIO_EDGE_SEL (mxc_gpio_hwdata->edge_sel_reg)
-
-#define GPIO_INT_LOW_LEV (mxc_gpio_hwdata->low_level)
-#define GPIO_INT_HIGH_LEV (mxc_gpio_hwdata->high_level)
-#define GPIO_INT_RISE_EDGE (mxc_gpio_hwdata->rise_edge)
-#define GPIO_INT_FALL_EDGE (mxc_gpio_hwdata->fall_edge)
+#define GPIO_DR (port->hwdata->dr_reg)
+#define GPIO_GDIR (port->hwdata->gdir_reg)
+#define GPIO_PSR (port->hwdata->psr_reg)
+#define GPIO_ICR1 (port->hwdata->icr1_reg)
+#define GPIO_ICR2 (port->hwdata->icr2_reg)
+#define GPIO_IMR (port->hwdata->imr_reg)
+#define GPIO_ISR (port->hwdata->isr_reg)
+#define GPIO_EDGE_SEL (port->hwdata->edge_sel_reg)
+
+#define GPIO_INT_LOW_LEV (port->hwdata->low_level)
+#define GPIO_INT_HIGH_LEV (port->hwdata->high_level)
+#define GPIO_INT_RISE_EDGE (port->hwdata->rise_edge)
+#define GPIO_INT_FALL_EDGE (port->hwdata->fall_edge)
#define GPIO_INT_BOTH_EDGES 0x4
-static struct platform_device_id mxc_gpio_devtype[] = {
- {
- .name = "imx1-gpio",
- .driver_data = IMX1_GPIO,
- }, {
- .name = "imx21-gpio",
- .driver_data = IMX21_GPIO,
- }, {
- .name = "imx31-gpio",
- .driver_data = IMX31_GPIO,
- }, {
- .name = "imx35-gpio",
- .driver_data = IMX35_GPIO,
- }, {
- /* sentinel */
- }
-};
-
static const struct of_device_id mxc_gpio_dt_ids[] = {
- { .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
- { .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
- { .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
- { .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
+ { .compatible = "fsl,imx1-gpio", .data = &imx1_imx21_gpio_hwdata },
+ { .compatible = "fsl,imx21-gpio", .data = &imx1_imx21_gpio_hwdata },
+ { .compatible = "fsl,imx31-gpio", .data = &imx31_gpio_hwdata },
+ { .compatible = "fsl,imx35-gpio", .data = &imx35_gpio_hwdata },
+ { .compatible = "fsl,imx7d-gpio", .data = &imx35_gpio_hwdata },
+ { .compatible = "fsl,imx8dxl-gpio", .data = &imx35_gpio_hwdata },
+ { .compatible = "fsl,imx8qm-gpio", .data = &imx35_gpio_hwdata },
+ { .compatible = "fsl,imx8qxp-gpio", .data = &imx35_gpio_hwdata },
{ /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, mxc_gpio_dt_ids);
/*
* MX2 has one interrupt *for all* gpio ports. The list is used
@@ -171,7 +165,6 @@ static int gpio_set_irq_type(struct irq_data *d, u32 type)
struct mxc_gpio_port *port = gc->private;
u32 bit, val;
u32 gpio_idx = d->hwirq;
- u32 gpio = port->bgc.gc.base + gpio_idx;
int edge;
void __iomem *reg = port->base;
@@ -187,13 +180,13 @@ static int gpio_set_irq_type(struct irq_data *d, u32 type)
if (GPIO_EDGE_SEL >= 0) {
edge = GPIO_INT_BOTH_EDGES;
} else {
- val = gpio_get_value(gpio);
+ val = port->gen_gc.gc.get(&port->gen_gc.gc, gpio_idx);
if (val) {
edge = GPIO_INT_LOW_LEV;
- pr_debug("mxc: set GPIO %d to low trigger\n", gpio);
+ pr_debug("mxc: set GPIO %d to low trigger\n", gpio_idx);
} else {
edge = GPIO_INT_HIGH_LEV;
- pr_debug("mxc: set GPIO %d to high trigger\n", gpio);
+ pr_debug("mxc: set GPIO %d to high trigger\n", gpio_idx);
}
port->both_edges |= 1 << gpio_idx;
}
@@ -208,26 +201,29 @@ static int gpio_set_irq_type(struct irq_data *d, u32 type)
return -EINVAL;
}
- if (GPIO_EDGE_SEL >= 0) {
- val = readl(port->base + GPIO_EDGE_SEL);
- if (edge == GPIO_INT_BOTH_EDGES)
- writel(val | (1 << gpio_idx),
- port->base + GPIO_EDGE_SEL);
- else
- writel(val & ~(1 << gpio_idx),
- port->base + GPIO_EDGE_SEL);
- }
+ scoped_guard(gpio_generic_lock_irqsave, &port->gen_gc) {
+ if (GPIO_EDGE_SEL >= 0) {
+ val = readl(port->base + GPIO_EDGE_SEL);
+ if (edge == GPIO_INT_BOTH_EDGES)
+ writel(val | (1 << gpio_idx),
+ port->base + GPIO_EDGE_SEL);
+ else
+ writel(val & ~(1 << gpio_idx),
+ port->base + GPIO_EDGE_SEL);
+ }
- if (edge != GPIO_INT_BOTH_EDGES) {
- reg += GPIO_ICR1 + ((gpio_idx & 0x10) >> 2); /* lower or upper register */
- bit = gpio_idx & 0xf;
- val = readl(reg) & ~(0x3 << (bit << 1));
- writel(val | (edge << (bit << 1)), reg);
- }
+ if (edge != GPIO_INT_BOTH_EDGES) {
+ reg += GPIO_ICR1 + ((gpio_idx & 0x10) >> 2); /* lower or upper register */
+ bit = gpio_idx & 0xf;
+ val = readl(reg) & ~(0x3 << (bit << 1));
+ writel(val | (edge << (bit << 1)), reg);
+ }
- writel(1 << gpio_idx, port->base + GPIO_ISR);
+ writel(1 << gpio_idx, port->base + GPIO_ISR);
+ port->pad_type[gpio_idx] = type;
+ }
- return 0;
+ return port->gen_gc.gc.direction_input(&port->gen_gc.gc, gpio_idx);
}
static void mxc_flip_edge(struct mxc_gpio_port *port, u32 gpio)
@@ -236,6 +232,8 @@ static void mxc_flip_edge(struct mxc_gpio_port *port, u32 gpio)
u32 bit, val;
int edge;
+ guard(gpio_generic_lock_irqsave)(&port->gen_gc);
+
reg += GPIO_ICR1 + ((gpio & 0x10) >> 2); /* lower or upper register */
bit = gpio & 0xf;
val = readl(reg);
@@ -264,18 +262,21 @@ static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat)
if (port->both_edges & (1 << irqoffset))
mxc_flip_edge(port, irqoffset);
- generic_handle_irq(irq_find_mapping(port->domain, irqoffset));
+ generic_handle_domain_irq(port->domain, irqoffset);
irq_stat &= ~(1 << irqoffset);
}
}
/* MX1 and MX3 has one interrupt *per* gpio port */
-static void mx3_gpio_irq_handler(u32 irq, struct irq_desc *desc)
+static void mx3_gpio_irq_handler(struct irq_desc *desc)
{
u32 irq_stat;
- struct mxc_gpio_port *port = irq_get_handler_data(irq);
- struct irq_chip *chip = irq_get_chip(irq);
+ struct mxc_gpio_port *port = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+
+ if (port->is_pad_wakeup)
+ return;
chained_irq_enter(chip, desc);
@@ -287,10 +288,13 @@ static void mx3_gpio_irq_handler(u32 irq, struct irq_desc *desc)
}
/* MX2 has one interrupt *for all* gpio ports */
-static void mx2_gpio_irq_handler(u32 irq, struct irq_desc *desc)
+static void mx2_gpio_irq_handler(struct irq_desc *desc)
{
u32 irq_msk, irq_stat;
struct mxc_gpio_port *port;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+
+ chained_irq_enter(chip, desc);
/* walk through all interrupt status registers */
list_for_each_entry(port, &mxc_gpio_ports, node) {
@@ -302,6 +306,7 @@ static void mx2_gpio_irq_handler(u32 irq, struct irq_desc *desc)
if (irq_stat)
mxc_gpio_irq_handler(port, irq_stat);
}
+ chained_irq_exit(chip, desc);
}
/*
@@ -318,29 +323,35 @@ static int gpio_set_wake_irq(struct irq_data *d, u32 enable)
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
struct mxc_gpio_port *port = gc->private;
u32 gpio_idx = d->hwirq;
+ int ret;
if (enable) {
if (port->irq_high && (gpio_idx >= 16))
- enable_irq_wake(port->irq_high);
+ ret = enable_irq_wake(port->irq_high);
else
- enable_irq_wake(port->irq);
+ ret = enable_irq_wake(port->irq);
+ port->wakeup_pads |= (1 << gpio_idx);
} else {
if (port->irq_high && (gpio_idx >= 16))
- disable_irq_wake(port->irq_high);
+ ret = disable_irq_wake(port->irq_high);
else
- disable_irq_wake(port->irq);
+ ret = disable_irq_wake(port->irq);
+ port->wakeup_pads &= ~(1 << gpio_idx);
}
- return 0;
+ return ret;
}
-static void __init mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base)
+static int mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base)
{
struct irq_chip_generic *gc;
struct irq_chip_type *ct;
+ int rv;
- gc = irq_alloc_generic_chip("gpio-mxc", 1, irq_base,
- port->base, handle_level_irq);
+ gc = devm_irq_alloc_generic_chip(port->dev, "gpio-mxc", 1, irq_base,
+ port->base, handle_level_irq);
+ if (!gc)
+ return -ENOMEM;
gc->private = port;
ct = gc->chip_types;
@@ -349,183 +360,374 @@ static void __init mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base)
ct->chip.irq_unmask = irq_gc_mask_set_bit;
ct->chip.irq_set_type = gpio_set_irq_type;
ct->chip.irq_set_wake = gpio_set_wake_irq;
+ ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND;
ct->regs.ack = GPIO_ISR;
ct->regs.mask = GPIO_IMR;
- irq_setup_generic_chip(gc, IRQ_MSK(32), IRQ_GC_INIT_NESTED_LOCK,
- IRQ_NOREQUEST, 0);
+ rv = devm_irq_setup_generic_chip(port->dev, gc, IRQ_MSK(32),
+ IRQ_GC_INIT_NESTED_LOCK,
+ IRQ_NOREQUEST, 0);
+
+ return rv;
}
-static void mxc_gpio_get_hw(struct platform_device *pdev)
+static int mxc_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
{
- const struct of_device_id *of_id =
- of_match_device(mxc_gpio_dt_ids, &pdev->dev);
- enum mxc_gpio_hwtype hwtype;
+ struct mxc_gpio_port *port = gpiochip_get_data(gc);
- if (of_id)
- pdev->id_entry = of_id->data;
- hwtype = pdev->id_entry->driver_data;
+ return irq_find_mapping(port->domain, offset);
+}
- if (mxc_gpio_hwtype) {
- /*
- * The driver works with a reasonable presupposition,
- * that is all gpio ports must be the same type when
- * running on one soc.
- */
- BUG_ON(mxc_gpio_hwtype != hwtype);
- return;
- }
+static int mxc_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret;
- if (hwtype == IMX35_GPIO)
- mxc_gpio_hwdata = &imx35_gpio_hwdata;
- else if (hwtype == IMX31_GPIO)
- mxc_gpio_hwdata = &imx31_gpio_hwdata;
- else
- mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata;
+ ret = gpiochip_generic_request(chip, offset);
+ if (ret)
+ return ret;
- mxc_gpio_hwtype = hwtype;
+ return pm_runtime_resume_and_get(chip->parent);
}
-static int mxc_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+static void mxc_gpio_free(struct gpio_chip *chip, unsigned int offset)
{
- struct bgpio_chip *bgc = to_bgpio_chip(gc);
- struct mxc_gpio_port *port =
- container_of(bgc, struct mxc_gpio_port, bgc);
+ gpiochip_generic_free(chip, offset);
+ pm_runtime_put(chip->parent);
+}
- return irq_find_mapping(port->domain, offset);
+static void mxc_update_irq_chained_handler(struct mxc_gpio_port *port, bool enable)
+{
+ if (enable)
+ irq_set_chained_handler_and_data(port->irq, port->mx_irq_handler, port);
+ else
+ irq_set_chained_handler_and_data(port->irq, NULL, NULL);
+
+ /* setup handler for GPIO 16 to 31 */
+ if (port->irq_high > 0) {
+ if (enable)
+ irq_set_chained_handler_and_data(port->irq_high,
+ port->mx_irq_handler,
+ port);
+ else
+ irq_set_chained_handler_and_data(port->irq_high, NULL, NULL);
+ }
}
static int mxc_gpio_probe(struct platform_device *pdev)
{
+ struct gpio_generic_chip_config config = { };
struct device_node *np = pdev->dev.of_node;
struct mxc_gpio_port *port;
- struct resource *iores;
+ int irq_count;
int irq_base;
int err;
- mxc_gpio_get_hw(pdev);
-
- port = kzalloc(sizeof(struct mxc_gpio_port), GFP_KERNEL);
+ port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;
- iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!iores) {
- err = -ENODEV;
- goto out_kfree;
- }
+ port->dev = &pdev->dev;
+ port->hwdata = device_get_match_data(&pdev->dev);
- if (!request_mem_region(iores->start, resource_size(iores),
- pdev->name)) {
- err = -EBUSY;
- goto out_kfree;
- }
+ port->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(port->base))
+ return PTR_ERR(port->base);
+
+ irq_count = platform_irq_count(pdev);
+ if (irq_count < 0)
+ return irq_count;
- port->base = ioremap(iores->start, resource_size(iores));
- if (!port->base) {
- err = -ENOMEM;
- goto out_release_mem;
+ if (irq_count > 1) {
+ port->irq_high = platform_get_irq(pdev, 1);
+ if (port->irq_high < 0)
+ port->irq_high = 0;
}
- port->irq_high = platform_get_irq(pdev, 1);
port->irq = platform_get_irq(pdev, 0);
- if (port->irq < 0) {
- err = -EINVAL;
- goto out_iounmap;
- }
+ if (port->irq < 0)
+ return port->irq;
+
+ /* the controller clock is optional */
+ port->clk = devm_clk_get_optional_enabled(&pdev->dev, NULL);
+ if (IS_ERR(port->clk))
+ return PTR_ERR(port->clk);
+
+ if (of_device_is_compatible(np, "fsl,imx7d-gpio"))
+ port->power_off = true;
+
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
/* disable the interrupt and clear the status */
writel(0, port->base + GPIO_IMR);
writel(~0, port->base + GPIO_ISR);
- if (mxc_gpio_hwtype == IMX21_GPIO) {
+ if (of_device_is_compatible(np, "fsl,imx21-gpio")) {
/*
* Setup one handler for all GPIO interrupts. Actually setting
* the handler is needed only once, but doing it for every port
* is more robust and easier.
*/
- irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
- } else {
- /* setup one handler for each entry */
- irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
- irq_set_handler_data(port->irq, port);
- if (port->irq_high > 0) {
- /* setup handler for GPIO 16 to 31 */
- irq_set_chained_handler(port->irq_high,
- mx3_gpio_irq_handler);
- irq_set_handler_data(port->irq_high, port);
- }
- }
+ port->irq_high = -1;
+ port->mx_irq_handler = mx2_gpio_irq_handler;
+ } else
+ port->mx_irq_handler = mx3_gpio_irq_handler;
- err = bgpio_init(&port->bgc, &pdev->dev, 4,
- port->base + GPIO_PSR,
- port->base + GPIO_DR, NULL,
- port->base + GPIO_GDIR, NULL, 0);
- if (err)
- goto out_iounmap;
+ mxc_update_irq_chained_handler(port, true);
- port->bgc.gc.to_irq = mxc_gpio_to_irq;
- port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
- pdev->id * 32;
+ config.dev = &pdev->dev;
+ config.sz = 4;
+ config.dat = port->base + GPIO_PSR;
+ config.set = port->base + GPIO_DR;
+ config.dirout = port->base + GPIO_GDIR;
+ config.flags = GPIO_GENERIC_READ_OUTPUT_REG_SET;
- err = gpiochip_add(&port->bgc.gc);
+ err = gpio_generic_chip_init(&port->gen_gc, &config);
if (err)
- goto out_bgpio_remove;
+ goto out_bgio;
+
+ port->gen_gc.gc.request = mxc_gpio_request;
+ port->gen_gc.gc.free = mxc_gpio_free;
+ port->gen_gc.gc.to_irq = mxc_gpio_to_irq;
+ /*
+ * Driver is DT-only, so a fixed base needs only be maintained for legacy
+ * userspace with sysfs interface.
+ */
+ if (IS_ENABLED(CONFIG_GPIO_SYSFS))
+ port->gen_gc.gc.base = of_alias_get_id(np, "gpio") * 32;
+ else /* silence boot time warning */
+ port->gen_gc.gc.base = -1;
+
+ err = devm_gpiochip_add_data(&pdev->dev, &port->gen_gc.gc, port);
+ if (err)
+ goto out_bgio;
- irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
+ irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 0, 32, numa_node_id());
if (irq_base < 0) {
err = irq_base;
- goto out_gpiochip_remove;
+ goto out_bgio;
}
- port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
- &irq_domain_simple_ops, NULL);
+ port->domain = irq_domain_create_legacy(dev_fwnode(&pdev->dev), 32, irq_base, 0,
+ &irq_domain_simple_ops, NULL);
if (!port->domain) {
err = -ENODEV;
- goto out_irqdesc_free;
+ goto out_bgio;
}
+ irq_domain_set_pm_device(port->domain, &pdev->dev);
+
/* gpio-mxc can be a generic irq chip */
- mxc_gpio_init_gc(port, irq_base);
+ err = mxc_gpio_init_gc(port, irq_base);
+ if (err < 0)
+ goto out_irqdomain_remove;
list_add_tail(&port->node, &mxc_gpio_ports);
+ platform_set_drvdata(pdev, port);
+ pm_runtime_put_autosuspend(&pdev->dev);
+
return 0;
-out_irqdesc_free:
- irq_free_descs(irq_base, 32);
-out_gpiochip_remove:
- WARN_ON(gpiochip_remove(&port->bgc.gc) < 0);
-out_bgpio_remove:
- bgpio_remove(&port->bgc);
-out_iounmap:
- iounmap(port->base);
-out_release_mem:
- release_mem_region(iores->start, resource_size(iores));
-out_kfree:
- kfree(port);
+out_irqdomain_remove:
+ irq_domain_remove(port->domain);
+out_bgio:
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err);
return err;
}
+static void mxc_gpio_save_regs(struct mxc_gpio_port *port)
+{
+ if (!port->power_off)
+ return;
+
+ port->gpio_saved_reg.icr1 = readl(port->base + GPIO_ICR1);
+ port->gpio_saved_reg.icr2 = readl(port->base + GPIO_ICR2);
+ port->gpio_saved_reg.imr = readl(port->base + GPIO_IMR);
+ port->gpio_saved_reg.gdir = readl(port->base + GPIO_GDIR);
+ port->gpio_saved_reg.edge_sel = readl(port->base + GPIO_EDGE_SEL);
+ port->gpio_saved_reg.dr = readl(port->base + GPIO_DR);
+}
+
+static void mxc_gpio_restore_regs(struct mxc_gpio_port *port)
+{
+ if (!port->power_off)
+ return;
+
+ writel(port->gpio_saved_reg.icr1, port->base + GPIO_ICR1);
+ writel(port->gpio_saved_reg.icr2, port->base + GPIO_ICR2);
+ writel(port->gpio_saved_reg.imr, port->base + GPIO_IMR);
+ writel(port->gpio_saved_reg.gdir, port->base + GPIO_GDIR);
+ writel(port->gpio_saved_reg.edge_sel, port->base + GPIO_EDGE_SEL);
+ writel(port->gpio_saved_reg.dr, port->base + GPIO_DR);
+}
+
+static bool mxc_gpio_generic_config(struct mxc_gpio_port *port,
+ unsigned int offset, unsigned long conf)
+{
+ struct device_node *np = port->dev->of_node;
+
+ if (of_device_is_compatible(np, "fsl,imx8dxl-gpio") ||
+ of_device_is_compatible(np, "fsl,imx8qxp-gpio") ||
+ of_device_is_compatible(np, "fsl,imx8qm-gpio"))
+ return (gpiochip_generic_config(&port->gen_gc.gc,
+ offset, conf) == 0);
+
+ return false;
+}
+
+static bool mxc_gpio_set_pad_wakeup(struct mxc_gpio_port *port, bool enable)
+{
+ unsigned long config;
+ bool ret = false;
+ int i, type;
+
+ static const u32 pad_type_map[] = {
+ IMX_SCU_WAKEUP_OFF, /* 0 */
+ IMX_SCU_WAKEUP_RISE_EDGE, /* IRQ_TYPE_EDGE_RISING */
+ IMX_SCU_WAKEUP_FALL_EDGE, /* IRQ_TYPE_EDGE_FALLING */
+ IMX_SCU_WAKEUP_FALL_EDGE, /* IRQ_TYPE_EDGE_BOTH */
+ IMX_SCU_WAKEUP_HIGH_LVL, /* IRQ_TYPE_LEVEL_HIGH */
+ IMX_SCU_WAKEUP_OFF, /* 5 */
+ IMX_SCU_WAKEUP_OFF, /* 6 */
+ IMX_SCU_WAKEUP_OFF, /* 7 */
+ IMX_SCU_WAKEUP_LOW_LVL, /* IRQ_TYPE_LEVEL_LOW */
+ };
+
+ for (i = 0; i < 32; i++) {
+ if ((port->wakeup_pads & (1 << i))) {
+ type = port->pad_type[i];
+ if (enable)
+ config = pad_type_map[type];
+ else
+ config = IMX_SCU_WAKEUP_OFF;
+ ret |= mxc_gpio_generic_config(port, i, config);
+ }
+ }
+
+ return ret;
+}
+
+static int mxc_gpio_runtime_suspend(struct device *dev)
+{
+ struct mxc_gpio_port *port = dev_get_drvdata(dev);
+
+ mxc_gpio_save_regs(port);
+ clk_disable_unprepare(port->clk);
+ mxc_update_irq_chained_handler(port, false);
+
+ return 0;
+}
+
+static int mxc_gpio_runtime_resume(struct device *dev)
+{
+ struct mxc_gpio_port *port = dev_get_drvdata(dev);
+ int ret;
+
+ mxc_update_irq_chained_handler(port, true);
+ ret = clk_prepare_enable(port->clk);
+ if (ret) {
+ mxc_update_irq_chained_handler(port, false);
+ return ret;
+ }
+
+ mxc_gpio_restore_regs(port);
+
+ return 0;
+}
+
+static int mxc_gpio_noirq_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxc_gpio_port *port = platform_get_drvdata(pdev);
+
+ if (port->wakeup_pads > 0)
+ port->is_pad_wakeup = mxc_gpio_set_pad_wakeup(port, true);
+
+ return 0;
+}
+
+static int mxc_gpio_noirq_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxc_gpio_port *port = platform_get_drvdata(pdev);
+
+ if (port->wakeup_pads > 0)
+ mxc_gpio_set_pad_wakeup(port, false);
+ port->is_pad_wakeup = false;
+
+ return 0;
+}
+
+static const struct dev_pm_ops mxc_gpio_dev_pm_ops = {
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(mxc_gpio_noirq_suspend, mxc_gpio_noirq_resume)
+ RUNTIME_PM_OPS(mxc_gpio_runtime_suspend, mxc_gpio_runtime_resume, NULL)
+};
+
+static int mxc_gpio_syscore_suspend(void *data)
+{
+ struct mxc_gpio_port *port;
+ int ret;
+
+ /* walk through all ports */
+ list_for_each_entry(port, &mxc_gpio_ports, node) {
+ ret = clk_prepare_enable(port->clk);
+ if (ret)
+ return ret;
+ mxc_gpio_save_regs(port);
+ clk_disable_unprepare(port->clk);
+ }
+
+ return 0;
+}
+
+static void mxc_gpio_syscore_resume(void *data)
+{
+ struct mxc_gpio_port *port;
+ int ret;
+
+ /* walk through all ports */
+ list_for_each_entry(port, &mxc_gpio_ports, node) {
+ ret = clk_prepare_enable(port->clk);
+ if (ret) {
+ pr_err("mxc: failed to enable gpio clock %d\n", ret);
+ return;
+ }
+ mxc_gpio_restore_regs(port);
+ clk_disable_unprepare(port->clk);
+ }
+}
+
+static const struct syscore_ops mxc_gpio_syscore_ops = {
+ .suspend = mxc_gpio_syscore_suspend,
+ .resume = mxc_gpio_syscore_resume,
+};
+
+static struct syscore mxc_gpio_syscore = {
+ .ops = &mxc_gpio_syscore_ops,
+};
+
static struct platform_driver mxc_gpio_driver = {
.driver = {
.name = "gpio-mxc",
- .owner = THIS_MODULE,
.of_match_table = mxc_gpio_dt_ids,
+ .suppress_bind_attrs = true,
+ .pm = pm_ptr(&mxc_gpio_dev_pm_ops),
},
.probe = mxc_gpio_probe,
- .id_table = mxc_gpio_devtype,
};
static int __init gpio_mxc_init(void)
{
+ register_syscore(&mxc_gpio_syscore);
+
return platform_driver_register(&mxc_gpio_driver);
}
-postcore_initcall(gpio_mxc_init);
+subsys_initcall(gpio_mxc_init);
-MODULE_AUTHOR("Freescale Semiconductor, "
- "Daniel Mack <danielncaiaq.de>, "
- "Juergen Beisert <kernel@pengutronix.de>");
-MODULE_DESCRIPTION("Freescale MXC GPIO");
+MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
+MODULE_DESCRIPTION("i.MX GPIO Driver");
MODULE_LICENSE("GPL");