Commit 8843797d authored by Geert Uytterhoeven's avatar Geert Uytterhoeven

pinctrl: sh-pfc: Save/restore registers for PSCI system suspend

During PSCI system suspend, R-Car Gen3 SoCs are powered down, and their
pinctrl register state is lost.  Note that as the boot loader skips most
initialization after system resume, pinctrl register state differs from
the state encountered during normal system boot, too.

To fix this, save all GPIO and peripheral function select, module
select, drive strength control, bias, and other I/O control registers
during system suspend, and restore them during system resume.

Note that to avoid overhead on platforms not needing it, the
suspend/resume code has a build time dependency on sleep and PSCI
support, and a runtime dependency on PSCI.

Inspired by a patch in the BSP by Hien Dang.
Signed-off-by: default avatarGeert Uytterhoeven <geert+renesas@glider.be>
parent 3870a6f6
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/pinctrl/machine.h> #include <linux/pinctrl/machine.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/psci.h>
#include <linux/slab.h> #include <linux/slab.h>
#include "core.h" #include "core.h"
...@@ -572,6 +573,97 @@ static const struct of_device_id sh_pfc_of_table[] = { ...@@ -572,6 +573,97 @@ static const struct of_device_id sh_pfc_of_table[] = {
}; };
#endif #endif
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM_PSCI_FW)
static void sh_pfc_nop_reg(struct sh_pfc *pfc, u32 reg, unsigned int idx)
{
}
static void sh_pfc_save_reg(struct sh_pfc *pfc, u32 reg, unsigned int idx)
{
pfc->saved_regs[idx] = sh_pfc_read(pfc, reg);
}
static void sh_pfc_restore_reg(struct sh_pfc *pfc, u32 reg, unsigned int idx)
{
sh_pfc_write(pfc, reg, pfc->saved_regs[idx]);
}
static unsigned int sh_pfc_walk_regs(struct sh_pfc *pfc,
void (*do_reg)(struct sh_pfc *pfc, u32 reg, unsigned int idx))
{
unsigned int i, n = 0;
if (pfc->info->cfg_regs)
for (i = 0; pfc->info->cfg_regs[i].reg; i++)
do_reg(pfc, pfc->info->cfg_regs[i].reg, n++);
if (pfc->info->drive_regs)
for (i = 0; pfc->info->drive_regs[i].reg; i++)
do_reg(pfc, pfc->info->drive_regs[i].reg, n++);
if (pfc->info->bias_regs)
for (i = 0; pfc->info->bias_regs[i].puen; i++) {
do_reg(pfc, pfc->info->bias_regs[i].puen, n++);
if (pfc->info->bias_regs[i].pud)
do_reg(pfc, pfc->info->bias_regs[i].pud, n++);
}
if (pfc->info->ioctrl_regs)
for (i = 0; pfc->info->ioctrl_regs[i].reg; i++)
do_reg(pfc, pfc->info->ioctrl_regs[i].reg, n++);
return n;
}
static int sh_pfc_suspend_init(struct sh_pfc *pfc)
{
unsigned int n;
/* This is the best we can do to check for the presence of PSCI */
if (!psci_ops.cpu_suspend)
return 0;
n = sh_pfc_walk_regs(pfc, sh_pfc_nop_reg);
if (!n)
return 0;
pfc->saved_regs = devm_kmalloc_array(pfc->dev, n,
sizeof(*pfc->saved_regs),
GFP_KERNEL);
if (!pfc->saved_regs)
return -ENOMEM;
dev_dbg(pfc->dev, "Allocated space to save %u regs\n", n);
return 0;
}
static int sh_pfc_suspend_noirq(struct device *dev)
{
struct sh_pfc *pfc = dev_get_drvdata(dev);
if (pfc->saved_regs)
sh_pfc_walk_regs(pfc, sh_pfc_save_reg);
return 0;
}
static int sh_pfc_resume_noirq(struct device *dev)
{
struct sh_pfc *pfc = dev_get_drvdata(dev);
if (pfc->saved_regs)
sh_pfc_walk_regs(pfc, sh_pfc_restore_reg);
return 0;
}
static const struct dev_pm_ops sh_pfc_pm = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(sh_pfc_suspend_noirq, sh_pfc_resume_noirq)
};
#define DEV_PM_OPS &sh_pfc_pm
#else
static int sh_pfc_suspend_init(struct sh_pfc *pfc) { return 0; }
#define DEV_PM_OPS NULL
#endif /* CONFIG_PM_SLEEP && CONFIG_ARM_PSCI_FW */
static int sh_pfc_probe(struct platform_device *pdev) static int sh_pfc_probe(struct platform_device *pdev)
{ {
#ifdef CONFIG_OF #ifdef CONFIG_OF
...@@ -610,6 +702,10 @@ static int sh_pfc_probe(struct platform_device *pdev) ...@@ -610,6 +702,10 @@ static int sh_pfc_probe(struct platform_device *pdev)
info = pfc->info; info = pfc->info;
} }
ret = sh_pfc_suspend_init(pfc);
if (ret)
return ret;
/* Enable dummy states for those platforms without pinctrl support */ /* Enable dummy states for those platforms without pinctrl support */
if (!of_have_populated_dt()) if (!of_have_populated_dt())
pinctrl_provide_dummies(); pinctrl_provide_dummies();
...@@ -693,6 +789,7 @@ static struct platform_driver sh_pfc_driver = { ...@@ -693,6 +789,7 @@ static struct platform_driver sh_pfc_driver = {
.driver = { .driver = {
.name = DRV_NAME, .name = DRV_NAME,
.of_match_table = of_match_ptr(sh_pfc_of_table), .of_match_table = of_match_ptr(sh_pfc_of_table),
.pm = DEV_PM_OPS,
}, },
}; };
......
...@@ -222,6 +222,7 @@ struct sh_pfc { ...@@ -222,6 +222,7 @@ struct sh_pfc {
unsigned int nr_gpio_pins; unsigned int nr_gpio_pins;
struct sh_pfc_chip *gpio; struct sh_pfc_chip *gpio;
u32 *saved_regs;
}; };
struct sh_pfc_soc_operations { struct sh_pfc_soc_operations {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment