summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFugang Duan <fugang.duan@nxp.com>2017-12-19 15:47:08 +0800
committerFugang Duan <fugang.duan@nxp.com>2018-01-08 14:12:02 +0800
commit6927b042dfc9ab99d5bbd4374aa2ac299f9f1a14 (patch)
treee0d2288b969a08be47dfd27783053418de5d924b
parent21253ef7cd0b79c249ddbcf144bd74dac210ece1 (diff)
MLK-17290-04 gpio: mxc: add runtime pm support
Add runtime pm support to automatically enable the ipg clock and power domain if present. To save power, suggest all sub-devices of the gpiochip/irq domain should dynamically manage gpio/irq resouces like: gpio: gpiod_request() ... //set gpio direction gpiod_free() irq: devm_request_irq() //=> module active devm_free_irq() //=>module is non-active or runtime idle Since the driver support irqchip and gpiochip, any irq/gpio resouce requested by other modules the gpio controller clock and power domain should be enabled. And the irqchip's parent's clock and power also should be enabled if irq resouce requested. Signed-off-by: Fugang Duan <fugang.duan@nxp.com> Tested-by: Guoniu.Zhou <guoniu.zhou@nxp.com> Reviewed-by: Frank Li <Frank.Li@nxp.com> (cherry picked from commit: 89d5032d5bb34c343147be1d88a0bd2c5901fe10)
-rw-r--r--drivers/gpio/gpio-mxc.c145
1 files changed, 138 insertions, 7 deletions
diff --git a/drivers/gpio/gpio-mxc.c b/drivers/gpio/gpio-mxc.c
index c1a1e00b8cb0..8c7f2ad897d6 100644
--- a/drivers/gpio/gpio-mxc.c
+++ b/drivers/gpio/gpio-mxc.c
@@ -20,6 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
@@ -28,6 +29,7 @@
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/gpio/driver.h>
/* FIXME: for gpio_get_value() replace this with direct register read */
@@ -61,12 +63,14 @@ struct mxc_gpio_hwdata {
struct mxc_gpio_port {
struct list_head node;
+ struct clk *clk;
void __iomem *base;
int irq;
int irq_high;
struct irq_domain *domain;
struct gpio_chip gc;
u32 both_edges;
+ bool gpio_ranges;
};
static struct mxc_gpio_hwdata imx1_imx21_gpio_hwdata = {
@@ -340,7 +344,32 @@ static int gpio_set_wake_irq(struct irq_data *d, u32 enable)
return 0;
}
-static int mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base)
+static int mxc_gpio_irq_reqres(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mxc_gpio_port *port = gc->private;
+
+ if (gpiochip_lock_as_irq(&port->gc, d->hwirq)) {
+ dev_err(port->gc.parent,
+ "unable to lock HW IRQ %lu for IRQ\n",
+ d->hwirq);
+ return -EINVAL;
+ }
+
+ return irq_chip_pm_get(d);
+}
+
+static void mxc_gpio_irq_relres(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mxc_gpio_port *port = gc->private;
+
+ gpiochip_unlock_as_irq(&port->gc, d->hwirq);
+ irq_chip_pm_put(d);
+}
+
+static int mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base,
+ struct device *dev)
{
struct irq_chip_generic *gc;
struct irq_chip_type *ct;
@@ -352,11 +381,14 @@ static int mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base)
gc->private = port;
ct = gc->chip_types;
+ ct->chip.parent_device = dev;
ct->chip.irq_ack = irq_gc_ack_set_bit;
ct->chip.irq_mask = irq_gc_mask_clr_bit;
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.irq_request_resources = mxc_gpio_irq_reqres;
+ ct->chip.irq_release_resources = mxc_gpio_irq_relres,
ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND;
ct->regs.ack = GPIO_ISR;
ct->regs.mask = GPIO_IMR;
@@ -404,12 +436,36 @@ static int mxc_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
return irq_find_mapping(port->domain, offset);
}
+static int mxc_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct mxc_gpio_port *port = gpiochip_get_data(chip);
+ int ret;
+
+ if (port->gpio_ranges) {
+ ret = gpiochip_generic_request(chip, offset);
+ if (ret)
+ return ret;
+ }
+
+ ret = pm_runtime_get_sync(chip->parent);
+ return ret < 0 ? ret : 0;
+}
+
+static void mxc_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct mxc_gpio_port *port = gpiochip_get_data(chip);
+
+ if (port->gpio_ranges)
+ gpiochip_generic_free(chip, offset);
+ pm_runtime_put(chip->parent);
+}
+
static int mxc_gpio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct mxc_gpio_port *port;
struct resource *iores;
- int irq_base;
+ int irq_base = 0;
int err;
mxc_gpio_get_hw(pdev);
@@ -428,6 +484,23 @@ static int mxc_gpio_probe(struct platform_device *pdev)
if (port->irq < 0)
return port->irq;
+ /* the controller clock is optional */
+ port->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(port->clk))
+ port->clk = NULL;
+
+ err = clk_prepare_enable(port->clk);
+ if (err) {
+ dev_err(&pdev->dev, "Unable to enable clock.\n");
+ return err;
+ }
+
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ err = pm_runtime_get_sync(&pdev->dev);
+ if (err < 0)
+ goto out_pm_dis;
+
/* disable the interrupt and clear the status */
writel(0, port->base + GPIO_IMR);
writel(~0, port->base + GPIO_ISR);
@@ -458,11 +531,14 @@ static int mxc_gpio_probe(struct platform_device *pdev)
if (err)
goto out_bgio;
- if (of_property_read_bool(np, "gpio-ranges")) {
- port->gc.request = gpiochip_generic_request;
- port->gc.free = gpiochip_generic_free;
- }
+ if (of_property_read_bool(np, "gpio_ranges"))
+ port->gpio_ranges = true;
+ else
+ port->gpio_ranges = false;
+ port->gc.request = mxc_gpio_request;
+ port->gc.free = mxc_gpio_free;
+ port->gc.parent = &pdev->dev;
port->gc.to_irq = mxc_gpio_to_irq;
port->gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
pdev->id * 32;
@@ -485,14 +561,20 @@ static int mxc_gpio_probe(struct platform_device *pdev)
}
/* gpio-mxc can be a generic irq chip */
- err = mxc_gpio_init_gc(port, irq_base);
+ err = mxc_gpio_init_gc(port, irq_base, &pdev->dev);
if (err < 0)
goto out_irqdomain_remove;
list_add_tail(&port->node, &mxc_gpio_ports);
+ platform_set_drvdata(pdev, port);
+ pm_runtime_put(&pdev->dev);
+
return 0;
+out_pm_dis:
+ pm_runtime_disable(&pdev->dev);
+ clk_disable_unprepare(port->clk);
out_irqdomain_remove:
irq_domain_remove(port->domain);
out_irqdesc_free:
@@ -502,9 +584,58 @@ out_bgio:
return err;
}
+static int __maybe_unused mxc_gpio_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxc_gpio_port *port = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(port->clk);
+
+ return 0;
+}
+
+static int __maybe_unused mxc_gpio_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxc_gpio_port *port = platform_get_drvdata(pdev);
+
+ return clk_prepare_enable(port->clk);
+}
+
+static int __maybe_unused mxc_gpio_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ int irq = platform_get_irq(pdev, 0);
+ struct irq_data *data = irq_get_irq_data(irq);
+
+ if (!irqd_is_wakeup_set(data))
+ return pm_runtime_force_suspend(dev);
+
+ return 0;
+}
+
+static int __maybe_unused mxc_gpio_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ int irq = platform_get_irq(pdev, 0);
+ struct irq_data *data = irq_get_irq_data(irq);
+
+ if (!irqd_is_wakeup_set(data))
+ return pm_runtime_force_resume(dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops mxc_gpio_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(mxc_gpio_suspend, mxc_gpio_resume)
+ SET_RUNTIME_PM_OPS(mxc_gpio_runtime_suspend,
+ mxc_gpio_runtime_resume, NULL)
+};
+
static struct platform_driver mxc_gpio_driver = {
.driver = {
.name = "gpio-mxc",
+ .pm = &mxc_gpio_dev_pm_ops,
.of_match_table = mxc_gpio_dt_ids,
},
.probe = mxc_gpio_probe,