1
0
Fork 0

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 resources 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 resource
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 resoruce 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>
[Arul: Fix merge conflicts]
Signed-off-by: Arulpandiyan Vadivel <arulpandiyan_vadivel@mentor.com>
5.4-rM2-2.2.x-imx-squashed
Fugang Duan 2020-04-02 16:10:57 +08:00
parent 8ee250e84f
commit f0e3012be8
1 changed files with 107 additions and 3 deletions

View File

@ -16,6 +16,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/syscore_ops.h>
#include <linux/gpio/driver.h>
@ -102,6 +103,7 @@ struct mxc_gpio_port {
u32 both_edges;
struct mxc_gpio_reg_saved gpio_saved_reg;
bool power_off;
bool gpio_ranges;
#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP
u32 pad_wakeup_num;
struct mxc_gpio_pad_wakeup pad_wakeup[32];
@ -463,7 +465,32 @@ static int gpio_set_wake_irq(struct irq_data *d, u32 enable)
return ret;
}
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;
@ -476,11 +503,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;
@ -529,12 +559,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;
int irq_count;
int irq_base;
int irq_base = 0;
int err;
#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP
int i;
@ -609,6 +663,12 @@ static int mxc_gpio_probe(struct platform_device *pdev)
if (of_device_is_compatible(np, "fsl,imx7d-gpio"))
port->power_off = true;
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);
@ -644,6 +704,14 @@ static int mxc_gpio_probe(struct platform_device *pdev)
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;
@ -666,16 +734,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_bgio:
@ -710,6 +782,32 @@ static void mxc_gpio_restore_regs(struct mxc_gpio_port *port)
writel(port->gpio_saved_reg.dr, port->base + GPIO_DR);
}
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);
mxc_gpio_save_regs(port);
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);
int ret;
ret = clk_prepare_enable(port->clk);
if (ret)
return ret;
mxc_gpio_restore_regs(port);
return 0;
}
static int __maybe_unused mxc_gpio_noirq_suspend(struct device *dev)
{
#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP
@ -738,14 +836,19 @@ static int __maybe_unused mxc_gpio_noirq_resume(struct device *dev)
static const struct dev_pm_ops mxc_gpio_dev_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(mxc_gpio_noirq_suspend, mxc_gpio_noirq_resume)
SET_RUNTIME_PM_OPS(mxc_gpio_runtime_suspend, mxc_gpio_runtime_resume, NULL)
};
static int mxc_gpio_syscore_suspend(void)
{
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);
}
@ -766,6 +869,7 @@ static void mxc_gpio_syscore_resume(void)
return;
}
mxc_gpio_restore_regs(port);
clk_disable_unprepare(port->clk);
}
}