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 @@ | |||
28 | #include <linux/gpio.h> | 28 | #include <linux/gpio.h> |
29 | #include <linux/irqdomain.h> | 29 | #include <linux/irqdomain.h> |
30 | #include <linux/spinlock.h> | 30 | #include <linux/spinlock.h> |
31 | #include <linux/syscore_ops.h> | ||
31 | 32 | ||
32 | #include "core.h" | 33 | #include "core.h" |
33 | #include "pinctrl-samsung.h" | 34 | #include "pinctrl-samsung.h" |
@@ -48,6 +49,9 @@ static struct pin_config { | |||
48 | { "samsung,pin-pud-pdn", PINCFG_TYPE_PUD_PDN }, | 49 | { "samsung,pin-pud-pdn", PINCFG_TYPE_PUD_PDN }, |
49 | }; | 50 | }; |
50 | 51 | ||
52 | /* Global list of devices (struct samsung_pinctrl_drv_data) */ | ||
53 | LIST_HEAD(drvdata_list); | ||
54 | |||
51 | static unsigned int pin_base; | 55 | static unsigned int pin_base; |
52 | 56 | ||
53 | static inline struct samsung_pin_bank *gc_to_pin_bank(struct gpio_chip *gc) | 57 | 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) | |||
956 | ctrl->eint_wkup_init(drvdata); | 960 | ctrl->eint_wkup_init(drvdata); |
957 | 961 | ||
958 | platform_set_drvdata(pdev, drvdata); | 962 | platform_set_drvdata(pdev, drvdata); |
963 | |||
964 | /* Add to the global list */ | ||
965 | list_add_tail(&drvdata->node, &drvdata_list); | ||
966 | |||
959 | return 0; | 967 | return 0; |
960 | } | 968 | } |
961 | 969 | ||
970 | #ifdef CONFIG_PM | ||
971 | |||
972 | /** | ||
973 | * samsung_pinctrl_suspend_dev - save pinctrl state for suspend for a device | ||
974 | * | ||
975 | * Save data for all banks handled by this device. | ||
976 | */ | ||
977 | static void samsung_pinctrl_suspend_dev( | ||
978 | struct samsung_pinctrl_drv_data *drvdata) | ||
979 | { | ||
980 | struct samsung_pin_ctrl *ctrl = drvdata->ctrl; | ||
981 | void __iomem *virt_base = drvdata->virt_base; | ||
982 | int i; | ||
983 | |||
984 | for (i = 0; i < ctrl->nr_banks; i++) { | ||
985 | struct samsung_pin_bank *bank = &ctrl->pin_banks[i]; | ||
986 | void __iomem *reg = virt_base + bank->pctl_offset; | ||
987 | |||
988 | u8 *offs = bank->type->reg_offset; | ||
989 | u8 *widths = bank->type->fld_width; | ||
990 | enum pincfg_type type; | ||
991 | |||
992 | /* Registers without a powerdown config aren't lost */ | ||
993 | if (!widths[PINCFG_TYPE_CON_PDN]) | ||
994 | continue; | ||
995 | |||
996 | for (type = 0; type < PINCFG_TYPE_NUM; type++) | ||
997 | if (widths[type]) | ||
998 | bank->pm_save[type] = readl(reg + offs[type]); | ||
999 | |||
1000 | if (widths[PINCFG_TYPE_FUNC] * bank->nr_pins > 32) { | ||
1001 | /* Some banks have two config registers */ | ||
1002 | bank->pm_save[PINCFG_TYPE_NUM] = | ||
1003 | readl(reg + offs[PINCFG_TYPE_FUNC] + 4); | ||
1004 | pr_debug("Save %s @ %p (con %#010x %08x)\n", | ||
1005 | bank->name, reg, | ||
1006 | bank->pm_save[PINCFG_TYPE_FUNC], | ||
1007 | bank->pm_save[PINCFG_TYPE_NUM]); | ||
1008 | } else { | ||
1009 | pr_debug("Save %s @ %p (con %#010x)\n", bank->name, | ||
1010 | reg, bank->pm_save[PINCFG_TYPE_FUNC]); | ||
1011 | } | ||
1012 | } | ||
1013 | } | ||
1014 | |||
1015 | /** | ||
1016 | * samsung_pinctrl_resume_dev - restore pinctrl state from suspend for a device | ||
1017 | * | ||
1018 | * Restore one of the banks that was saved during suspend. | ||
1019 | * | ||
1020 | * We don't bother doing anything complicated to avoid glitching lines since | ||
1021 | * we're called before pad retention is turned off. | ||
1022 | */ | ||
1023 | static void samsung_pinctrl_resume_dev(struct samsung_pinctrl_drv_data *drvdata) | ||
1024 | { | ||
1025 | struct samsung_pin_ctrl *ctrl = drvdata->ctrl; | ||
1026 | void __iomem *virt_base = drvdata->virt_base; | ||
1027 | int i; | ||
1028 | |||
1029 | for (i = 0; i < ctrl->nr_banks; i++) { | ||
1030 | struct samsung_pin_bank *bank = &ctrl->pin_banks[i]; | ||
1031 | void __iomem *reg = virt_base + bank->pctl_offset; | ||
1032 | |||
1033 | u8 *offs = bank->type->reg_offset; | ||
1034 | u8 *widths = bank->type->fld_width; | ||
1035 | enum pincfg_type type; | ||
1036 | |||
1037 | /* Registers without a powerdown config aren't lost */ | ||
1038 | if (!widths[PINCFG_TYPE_CON_PDN]) | ||
1039 | continue; | ||
1040 | |||
1041 | if (widths[PINCFG_TYPE_FUNC] * bank->nr_pins > 32) { | ||
1042 | /* Some banks have two config registers */ | ||
1043 | pr_debug("%s @ %p (con %#010x %08x => %#010x %08x)\n", | ||
1044 | bank->name, reg, | ||
1045 | readl(reg + offs[PINCFG_TYPE_FUNC]), | ||
1046 | readl(reg + offs[PINCFG_TYPE_FUNC] + 4), | ||
1047 | bank->pm_save[PINCFG_TYPE_FUNC], | ||
1048 | bank->pm_save[PINCFG_TYPE_NUM]); | ||
1049 | writel(bank->pm_save[PINCFG_TYPE_NUM], | ||
1050 | reg + offs[PINCFG_TYPE_FUNC] + 4); | ||
1051 | } else { | ||
1052 | pr_debug("%s @ %p (con %#010x => %#010x)\n", bank->name, | ||
1053 | reg, readl(reg + offs[PINCFG_TYPE_FUNC]), | ||
1054 | bank->pm_save[PINCFG_TYPE_FUNC]); | ||
1055 | } | ||
1056 | for (type = 0; type < PINCFG_TYPE_NUM; type++) | ||
1057 | if (widths[type]) | ||
1058 | writel(bank->pm_save[type], reg + offs[type]); | ||
1059 | } | ||
1060 | } | ||
1061 | |||
1062 | /** | ||
1063 | * samsung_pinctrl_suspend - save pinctrl state for suspend | ||
1064 | * | ||
1065 | * Save data for all banks across all devices. | ||
1066 | */ | ||
1067 | static int samsung_pinctrl_suspend(void) | ||
1068 | { | ||
1069 | struct samsung_pinctrl_drv_data *drvdata; | ||
1070 | |||
1071 | list_for_each_entry(drvdata, &drvdata_list, node) { | ||
1072 | samsung_pinctrl_suspend_dev(drvdata); | ||
1073 | } | ||
1074 | |||
1075 | return 0; | ||
1076 | } | ||
1077 | |||
1078 | /** | ||
1079 | * samsung_pinctrl_resume - restore pinctrl state for suspend | ||
1080 | * | ||
1081 | * Restore data for all banks across all devices. | ||
1082 | */ | ||
1083 | static void samsung_pinctrl_resume(void) | ||
1084 | { | ||
1085 | struct samsung_pinctrl_drv_data *drvdata; | ||
1086 | |||
1087 | list_for_each_entry_reverse(drvdata, &drvdata_list, node) { | ||
1088 | samsung_pinctrl_resume_dev(drvdata); | ||
1089 | } | ||
1090 | } | ||
1091 | |||
1092 | #else | ||
1093 | #define samsung_pinctrl_suspend NULL | ||
1094 | #define samsung_pinctrl_resume NULL | ||
1095 | #endif | ||
1096 | |||
1097 | static struct syscore_ops samsung_pinctrl_syscore_ops = { | ||
1098 | .suspend = samsung_pinctrl_suspend, | ||
1099 | .resume = samsung_pinctrl_resume, | ||
1100 | }; | ||
1101 | |||
962 | static const struct of_device_id samsung_pinctrl_dt_match[] = { | 1102 | static const struct of_device_id samsung_pinctrl_dt_match[] = { |
963 | #ifdef CONFIG_PINCTRL_EXYNOS | 1103 | #ifdef CONFIG_PINCTRL_EXYNOS |
964 | { .compatible = "samsung,exynos4210-pinctrl", | 1104 | { .compatible = "samsung,exynos4210-pinctrl", |
@@ -987,6 +1127,14 @@ static struct platform_driver samsung_pinctrl_driver = { | |||
987 | 1127 | ||
988 | static int __init samsung_pinctrl_drv_register(void) | 1128 | static int __init samsung_pinctrl_drv_register(void) |
989 | { | 1129 | { |
1130 | /* | ||
1131 | * Register syscore ops for save/restore of registers across suspend. | ||
1132 | * It's important to ensure that this driver is running at an earlier | ||
1133 | * initcall level than any arch-specific init calls that install syscore | ||
1134 | * ops that turn off pad retention (like exynos_pm_resume). | ||
1135 | */ | ||
1136 | register_syscore_ops(&samsung_pinctrl_syscore_ops); | ||
1137 | |||
990 | return platform_driver_register(&samsung_pinctrl_driver); | 1138 | return platform_driver_register(&samsung_pinctrl_driver); |
991 | } | 1139 | } |
992 | postcore_initcall(samsung_pinctrl_drv_register); | 1140 | 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 { | |||
127 | * @gpio_chip: GPIO chip of the bank. | 127 | * @gpio_chip: GPIO chip of the bank. |
128 | * @grange: linux gpio pin range supported by this bank. | 128 | * @grange: linux gpio pin range supported by this bank. |
129 | * @slock: spinlock protecting bank registers | 129 | * @slock: spinlock protecting bank registers |
130 | * @pm_save: saved register values during suspend | ||
130 | */ | 131 | */ |
131 | struct samsung_pin_bank { | 132 | struct samsung_pin_bank { |
132 | struct samsung_pin_bank_type *type; | 133 | struct samsung_pin_bank_type *type; |
@@ -144,6 +145,8 @@ struct samsung_pin_bank { | |||
144 | struct gpio_chip gpio_chip; | 145 | struct gpio_chip gpio_chip; |
145 | struct pinctrl_gpio_range grange; | 146 | struct pinctrl_gpio_range grange; |
146 | spinlock_t slock; | 147 | spinlock_t slock; |
148 | |||
149 | u32 pm_save[PINCFG_TYPE_NUM + 1]; /* +1 to handle double CON registers*/ | ||
147 | }; | 150 | }; |
148 | 151 | ||
149 | /** | 152 | /** |
@@ -189,6 +192,7 @@ struct samsung_pin_ctrl { | |||
189 | 192 | ||
190 | /** | 193 | /** |
191 | * struct samsung_pinctrl_drv_data: wrapper for holding driver data together. | 194 | * struct samsung_pinctrl_drv_data: wrapper for holding driver data together. |
195 | * @node: global list node | ||
192 | * @virt_base: register base address of the controller. | 196 | * @virt_base: register base address of the controller. |
193 | * @dev: device instance representing the controller. | 197 | * @dev: device instance representing the controller. |
194 | * @irq: interrpt number used by the controller to notify gpio interrupts. | 198 | * @irq: interrpt number used by the controller to notify gpio interrupts. |
@@ -201,6 +205,7 @@ struct samsung_pin_ctrl { | |||
201 | * @nr_function: number of such pin functions. | 205 | * @nr_function: number of such pin functions. |
202 | */ | 206 | */ |
203 | struct samsung_pinctrl_drv_data { | 207 | struct samsung_pinctrl_drv_data { |
208 | struct list_head node; | ||
204 | void __iomem *virt_base; | 209 | void __iomem *virt_base; |
205 | struct device *dev; | 210 | struct device *dev; |
206 | int irq; | 211 | int irq; |