diff options
| -rw-r--r-- | drivers/pinctrl/pinctrl-samsung.c | 148 | ||||
| -rw-r--r-- | drivers/pinctrl/pinctrl-samsung.h | 5 | 
2 files changed, 153 insertions, 0 deletions
| diff --git a/drivers/pinctrl/pinctrl-samsung.c b/drivers/pinctrl/pinctrl-samsung.c index 055d0162098b..15db2580c145 100644 --- a/drivers/pinctrl/pinctrl-samsung.c +++ b/drivers/pinctrl/pinctrl-samsung.c @@ -28,6 +28,7 @@  #include <linux/gpio.h>  #include <linux/irqdomain.h>  #include <linux/spinlock.h> +#include <linux/syscore_ops.h>  #include "core.h"  #include "pinctrl-samsung.h" @@ -48,6 +49,9 @@ static struct pin_config {  	{ "samsung,pin-pud-pdn", PINCFG_TYPE_PUD_PDN },  }; +/* Global list of devices (struct samsung_pinctrl_drv_data) */ +LIST_HEAD(drvdata_list); +  static unsigned int pin_base;  static inline struct samsung_pin_bank *gc_to_pin_bank(struct gpio_chip *gc) @@ -956,9 +960,145 @@ static int samsung_pinctrl_probe(struct platform_device *pdev)  		ctrl->eint_wkup_init(drvdata);  	platform_set_drvdata(pdev, drvdata); + +	/* Add to the global list */ +	list_add_tail(&drvdata->node, &drvdata_list); +  	return 0;  } +#ifdef CONFIG_PM + +/** + * samsung_pinctrl_suspend_dev - save pinctrl state for suspend for a device + * + * Save data for all banks handled by this device. + */ +static void samsung_pinctrl_suspend_dev( +	struct samsung_pinctrl_drv_data *drvdata) +{ +	struct samsung_pin_ctrl *ctrl = drvdata->ctrl; +	void __iomem *virt_base = drvdata->virt_base; +	int i; + +	for (i = 0; i < ctrl->nr_banks; i++) { +		struct samsung_pin_bank *bank = &ctrl->pin_banks[i]; +		void __iomem *reg = virt_base + bank->pctl_offset; + +		u8 *offs = bank->type->reg_offset; +		u8 *widths = bank->type->fld_width; +		enum pincfg_type type; + +		/* Registers without a powerdown config aren't lost */ +		if (!widths[PINCFG_TYPE_CON_PDN]) +			continue; + +		for (type = 0; type < PINCFG_TYPE_NUM; type++) +			if (widths[type]) +				bank->pm_save[type] = readl(reg + offs[type]); + +		if (widths[PINCFG_TYPE_FUNC] * bank->nr_pins > 32) { +			/* Some banks have two config registers */ +			bank->pm_save[PINCFG_TYPE_NUM] = +				readl(reg + offs[PINCFG_TYPE_FUNC] + 4); +			pr_debug("Save %s @ %p (con %#010x %08x)\n", +				 bank->name, reg, +				 bank->pm_save[PINCFG_TYPE_FUNC], +				 bank->pm_save[PINCFG_TYPE_NUM]); +		} else { +			pr_debug("Save %s @ %p (con %#010x)\n", bank->name, +				 reg, bank->pm_save[PINCFG_TYPE_FUNC]); +		} +	} +} + +/** + * samsung_pinctrl_resume_dev - restore pinctrl state from suspend for a device + * + * Restore one of the banks that was saved during suspend. + * + * We don't bother doing anything complicated to avoid glitching lines since + * we're called before pad retention is turned off. + */ +static void samsung_pinctrl_resume_dev(struct samsung_pinctrl_drv_data *drvdata) +{ +	struct samsung_pin_ctrl *ctrl = drvdata->ctrl; +	void __iomem *virt_base = drvdata->virt_base; +	int i; + +	for (i = 0; i < ctrl->nr_banks; i++) { +		struct samsung_pin_bank *bank = &ctrl->pin_banks[i]; +		void __iomem *reg = virt_base + bank->pctl_offset; + +		u8 *offs = bank->type->reg_offset; +		u8 *widths = bank->type->fld_width; +		enum pincfg_type type; + +		/* Registers without a powerdown config aren't lost */ +		if (!widths[PINCFG_TYPE_CON_PDN]) +			continue; + +		if (widths[PINCFG_TYPE_FUNC] * bank->nr_pins > 32) { +			/* Some banks have two config registers */ +			pr_debug("%s @ %p (con %#010x %08x => %#010x %08x)\n", +				 bank->name, reg, +				 readl(reg + offs[PINCFG_TYPE_FUNC]), +				 readl(reg + offs[PINCFG_TYPE_FUNC] + 4), +				 bank->pm_save[PINCFG_TYPE_FUNC], +				 bank->pm_save[PINCFG_TYPE_NUM]); +			writel(bank->pm_save[PINCFG_TYPE_NUM], +			       reg + offs[PINCFG_TYPE_FUNC] + 4); +		} else { +			pr_debug("%s @ %p (con %#010x => %#010x)\n", bank->name, +				 reg, readl(reg + offs[PINCFG_TYPE_FUNC]), +				 bank->pm_save[PINCFG_TYPE_FUNC]); +		} +		for (type = 0; type < PINCFG_TYPE_NUM; type++) +			if (widths[type]) +				writel(bank->pm_save[type], reg + offs[type]); +	} +} + +/** + * samsung_pinctrl_suspend - save pinctrl state for suspend + * + * Save data for all banks across all devices. + */ +static int samsung_pinctrl_suspend(void) +{ +	struct samsung_pinctrl_drv_data *drvdata; + +	list_for_each_entry(drvdata, &drvdata_list, node) { +		samsung_pinctrl_suspend_dev(drvdata); +	} + +	return 0; +} + +/** + * samsung_pinctrl_resume - restore pinctrl state for suspend + * + * Restore data for all banks across all devices. + */ +static void samsung_pinctrl_resume(void) +{ +	struct samsung_pinctrl_drv_data *drvdata; + +	list_for_each_entry_reverse(drvdata, &drvdata_list, node) { +		samsung_pinctrl_resume_dev(drvdata); +	} +} + +#else +#define samsung_pinctrl_suspend		NULL +#define samsung_pinctrl_resume		NULL +#endif + +static struct syscore_ops samsung_pinctrl_syscore_ops = { +	.suspend	= samsung_pinctrl_suspend, +	.resume		= samsung_pinctrl_resume, +}; +  static const struct of_device_id samsung_pinctrl_dt_match[] = {  #ifdef CONFIG_PINCTRL_EXYNOS  	{ .compatible = "samsung,exynos4210-pinctrl", @@ -987,6 +1127,14 @@ static struct platform_driver samsung_pinctrl_driver = {  static int __init samsung_pinctrl_drv_register(void)  { +	/* +	 * Register syscore ops for save/restore of registers across suspend. +	 * It's important to ensure that this driver is running at an earlier +	 * initcall level than any arch-specific init calls that install syscore +	 * ops that turn off pad retention (like exynos_pm_resume). +	 */ +	register_syscore_ops(&samsung_pinctrl_syscore_ops); +  	return platform_driver_register(&samsung_pinctrl_driver);  }  postcore_initcall(samsung_pinctrl_drv_register); diff --git a/drivers/pinctrl/pinctrl-samsung.h b/drivers/pinctrl/pinctrl-samsung.h index 7c7f9ebcd05b..9f5cc811b8cf 100644 --- a/drivers/pinctrl/pinctrl-samsung.h +++ b/drivers/pinctrl/pinctrl-samsung.h @@ -127,6 +127,7 @@ struct samsung_pin_bank_type {   * @gpio_chip: GPIO chip of the bank.   * @grange: linux gpio pin range supported by this bank.   * @slock: spinlock protecting bank registers + * @pm_save: saved register values during suspend   */  struct samsung_pin_bank {  	struct samsung_pin_bank_type *type; @@ -144,6 +145,8 @@ struct samsung_pin_bank {  	struct gpio_chip gpio_chip;  	struct pinctrl_gpio_range grange;  	spinlock_t slock; + +	u32 pm_save[PINCFG_TYPE_NUM + 1]; /* +1 to handle double CON registers*/  };  /** @@ -189,6 +192,7 @@ struct samsung_pin_ctrl {  /**   * struct samsung_pinctrl_drv_data: wrapper for holding driver data together. + * @node: global list node   * @virt_base: register base address of the controller.   * @dev: device instance representing the controller.   * @irq: interrpt number used by the controller to notify gpio interrupts. @@ -201,6 +205,7 @@ struct samsung_pin_ctrl {   * @nr_function: number of such pin functions.   */  struct samsung_pinctrl_drv_data { +	struct list_head		node;  	void __iomem			*virt_base;  	struct device			*dev;  	int				irq; |