pm-gpio.c (9726B)
1// SPDX-License-Identifier: GPL-2.0+ 2// 3// Copyright 2008 Openmoko, Inc. 4// Copyright 2008 Simtec Electronics 5// Ben Dooks <ben@simtec.co.uk> 6// http://armlinux.simtec.co.uk/ 7// 8// S3C series GPIO PM code 9 10#include <linux/kernel.h> 11#include <linux/device.h> 12#include <linux/init.h> 13#include <linux/io.h> 14#include <linux/gpio.h> 15 16#include "gpio-samsung.h" 17 18#include "gpio-core.h" 19#include "pm.h" 20 21/* PM GPIO helpers */ 22 23#define OFFS_CON (0x00) 24#define OFFS_DAT (0x04) 25#define OFFS_UP (0x08) 26 27static void samsung_gpio_pm_1bit_save(struct samsung_gpio_chip *chip) 28{ 29 chip->pm_save[0] = __raw_readl(chip->base + OFFS_CON); 30 chip->pm_save[1] = __raw_readl(chip->base + OFFS_DAT); 31} 32 33static void samsung_gpio_pm_1bit_resume(struct samsung_gpio_chip *chip) 34{ 35 void __iomem *base = chip->base; 36 u32 old_gpcon = __raw_readl(base + OFFS_CON); 37 u32 old_gpdat = __raw_readl(base + OFFS_DAT); 38 u32 gps_gpcon = chip->pm_save[0]; 39 u32 gps_gpdat = chip->pm_save[1]; 40 u32 gpcon; 41 42 /* GPACON only has one bit per control / data and no PULLUPs. 43 * GPACON[x] = 0 => Output, 1 => SFN */ 44 45 /* first set all SFN bits to SFN */ 46 47 gpcon = old_gpcon | gps_gpcon; 48 __raw_writel(gpcon, base + OFFS_CON); 49 50 /* now set all the other bits */ 51 52 __raw_writel(gps_gpdat, base + OFFS_DAT); 53 __raw_writel(gps_gpcon, base + OFFS_CON); 54 55 S3C_PMDBG("%s: CON %08x => %08x, DAT %08x => %08x\n", 56 chip->chip.label, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat); 57} 58 59struct samsung_gpio_pm samsung_gpio_pm_1bit = { 60 .save = samsung_gpio_pm_1bit_save, 61 .resume = samsung_gpio_pm_1bit_resume, 62}; 63 64static void samsung_gpio_pm_2bit_save(struct samsung_gpio_chip *chip) 65{ 66 chip->pm_save[0] = __raw_readl(chip->base + OFFS_CON); 67 chip->pm_save[1] = __raw_readl(chip->base + OFFS_DAT); 68 chip->pm_save[2] = __raw_readl(chip->base + OFFS_UP); 69} 70 71/* Test whether the given masked+shifted bits of an GPIO configuration 72 * are one of the SFN (special function) modes. */ 73 74static inline int is_sfn(unsigned long con) 75{ 76 return con >= 2; 77} 78 79/* Test if the given masked+shifted GPIO configuration is an input */ 80 81static inline int is_in(unsigned long con) 82{ 83 return con == 0; 84} 85 86/* Test if the given masked+shifted GPIO configuration is an output */ 87 88static inline int is_out(unsigned long con) 89{ 90 return con == 1; 91} 92 93/** 94 * samsung_gpio_pm_2bit_resume() - restore the given GPIO bank 95 * @chip: The chip information to resume. 96 * 97 * Restore one of the GPIO banks that was saved during suspend. This is 98 * not as simple as once thought, due to the possibility of glitches 99 * from the order that the CON and DAT registers are set in. 100 * 101 * The three states the pin can be are {IN,OUT,SFN} which gives us 9 102 * combinations of changes to check. Three of these, if the pin stays 103 * in the same configuration can be discounted. This leaves us with 104 * the following: 105 * 106 * { IN => OUT } Change DAT first 107 * { IN => SFN } Change CON first 108 * { OUT => SFN } Change CON first, so new data will not glitch 109 * { OUT => IN } Change CON first, so new data will not glitch 110 * { SFN => IN } Change CON first 111 * { SFN => OUT } Change DAT first, so new data will not glitch [1] 112 * 113 * We do not currently deal with the UP registers as these control 114 * weak resistors, so a small delay in change should not need to bring 115 * these into the calculations. 116 * 117 * [1] this assumes that writing to a pin DAT whilst in SFN will set the 118 * state for when it is next output. 119 */ 120static void samsung_gpio_pm_2bit_resume(struct samsung_gpio_chip *chip) 121{ 122 void __iomem *base = chip->base; 123 u32 old_gpcon = __raw_readl(base + OFFS_CON); 124 u32 old_gpdat = __raw_readl(base + OFFS_DAT); 125 u32 gps_gpcon = chip->pm_save[0]; 126 u32 gps_gpdat = chip->pm_save[1]; 127 u32 gpcon, old, new, mask; 128 u32 change_mask = 0x0; 129 int nr; 130 131 /* restore GPIO pull-up settings */ 132 __raw_writel(chip->pm_save[2], base + OFFS_UP); 133 134 /* Create a change_mask of all the items that need to have 135 * their CON value changed before their DAT value, so that 136 * we minimise the work between the two settings. 137 */ 138 139 for (nr = 0, mask = 0x03; nr < 32; nr += 2, mask <<= 2) { 140 old = (old_gpcon & mask) >> nr; 141 new = (gps_gpcon & mask) >> nr; 142 143 /* If there is no change, then skip */ 144 145 if (old == new) 146 continue; 147 148 /* If both are special function, then skip */ 149 150 if (is_sfn(old) && is_sfn(new)) 151 continue; 152 153 /* Change is IN => OUT, do not change now */ 154 155 if (is_in(old) && is_out(new)) 156 continue; 157 158 /* Change is SFN => OUT, do not change now */ 159 160 if (is_sfn(old) && is_out(new)) 161 continue; 162 163 /* We should now be at the case of IN=>SFN, 164 * OUT=>SFN, OUT=>IN, SFN=>IN. */ 165 166 change_mask |= mask; 167 } 168 169 170 /* Write the new CON settings */ 171 172 gpcon = old_gpcon & ~change_mask; 173 gpcon |= gps_gpcon & change_mask; 174 175 __raw_writel(gpcon, base + OFFS_CON); 176 177 /* Now change any items that require DAT,CON */ 178 179 __raw_writel(gps_gpdat, base + OFFS_DAT); 180 __raw_writel(gps_gpcon, base + OFFS_CON); 181 182 S3C_PMDBG("%s: CON %08x => %08x, DAT %08x => %08x\n", 183 chip->chip.label, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat); 184} 185 186struct samsung_gpio_pm samsung_gpio_pm_2bit = { 187 .save = samsung_gpio_pm_2bit_save, 188 .resume = samsung_gpio_pm_2bit_resume, 189}; 190 191#if defined(CONFIG_ARCH_S3C64XX) 192static void samsung_gpio_pm_4bit_save(struct samsung_gpio_chip *chip) 193{ 194 chip->pm_save[1] = __raw_readl(chip->base + OFFS_CON); 195 chip->pm_save[2] = __raw_readl(chip->base + OFFS_DAT); 196 chip->pm_save[3] = __raw_readl(chip->base + OFFS_UP); 197 198 if (chip->chip.ngpio > 8) 199 chip->pm_save[0] = __raw_readl(chip->base - 4); 200} 201 202static u32 samsung_gpio_pm_4bit_mask(u32 old_gpcon, u32 gps_gpcon) 203{ 204 u32 old, new, mask; 205 u32 change_mask = 0x0; 206 int nr; 207 208 for (nr = 0, mask = 0x0f; nr < 16; nr += 4, mask <<= 4) { 209 old = (old_gpcon & mask) >> nr; 210 new = (gps_gpcon & mask) >> nr; 211 212 /* If there is no change, then skip */ 213 214 if (old == new) 215 continue; 216 217 /* If both are special function, then skip */ 218 219 if (is_sfn(old) && is_sfn(new)) 220 continue; 221 222 /* Change is IN => OUT, do not change now */ 223 224 if (is_in(old) && is_out(new)) 225 continue; 226 227 /* Change is SFN => OUT, do not change now */ 228 229 if (is_sfn(old) && is_out(new)) 230 continue; 231 232 /* We should now be at the case of IN=>SFN, 233 * OUT=>SFN, OUT=>IN, SFN=>IN. */ 234 235 change_mask |= mask; 236 } 237 238 return change_mask; 239} 240 241static void samsung_gpio_pm_4bit_con(struct samsung_gpio_chip *chip, int index) 242{ 243 void __iomem *con = chip->base + (index * 4); 244 u32 old_gpcon = __raw_readl(con); 245 u32 gps_gpcon = chip->pm_save[index + 1]; 246 u32 gpcon, mask; 247 248 mask = samsung_gpio_pm_4bit_mask(old_gpcon, gps_gpcon); 249 250 gpcon = old_gpcon & ~mask; 251 gpcon |= gps_gpcon & mask; 252 253 __raw_writel(gpcon, con); 254} 255 256static void samsung_gpio_pm_4bit_resume(struct samsung_gpio_chip *chip) 257{ 258 void __iomem *base = chip->base; 259 u32 old_gpcon[2]; 260 u32 old_gpdat = __raw_readl(base + OFFS_DAT); 261 u32 gps_gpdat = chip->pm_save[2]; 262 263 /* First, modify the CON settings */ 264 265 old_gpcon[0] = 0; 266 old_gpcon[1] = __raw_readl(base + OFFS_CON); 267 268 samsung_gpio_pm_4bit_con(chip, 0); 269 if (chip->chip.ngpio > 8) { 270 old_gpcon[0] = __raw_readl(base - 4); 271 samsung_gpio_pm_4bit_con(chip, -1); 272 } 273 274 /* Now change the configurations that require DAT,CON */ 275 276 __raw_writel(chip->pm_save[2], base + OFFS_DAT); 277 __raw_writel(chip->pm_save[1], base + OFFS_CON); 278 if (chip->chip.ngpio > 8) 279 __raw_writel(chip->pm_save[0], base - 4); 280 281 __raw_writel(chip->pm_save[2], base + OFFS_DAT); 282 __raw_writel(chip->pm_save[3], base + OFFS_UP); 283 284 if (chip->chip.ngpio > 8) { 285 S3C_PMDBG("%s: CON4 %08x,%08x => %08x,%08x, DAT %08x => %08x\n", 286 chip->chip.label, old_gpcon[0], old_gpcon[1], 287 __raw_readl(base - 4), 288 __raw_readl(base + OFFS_CON), 289 old_gpdat, gps_gpdat); 290 } else 291 S3C_PMDBG("%s: CON4 %08x => %08x, DAT %08x => %08x\n", 292 chip->chip.label, old_gpcon[1], 293 __raw_readl(base + OFFS_CON), 294 old_gpdat, gps_gpdat); 295} 296 297struct samsung_gpio_pm samsung_gpio_pm_4bit = { 298 .save = samsung_gpio_pm_4bit_save, 299 .resume = samsung_gpio_pm_4bit_resume, 300}; 301#endif /* CONFIG_ARCH_S3C64XX */ 302 303/** 304 * samsung_pm_save_gpio() - save gpio chip data for suspend 305 * @ourchip: The chip for suspend. 306 */ 307static void samsung_pm_save_gpio(struct samsung_gpio_chip *ourchip) 308{ 309 struct samsung_gpio_pm *pm = ourchip->pm; 310 311 if (pm == NULL || pm->save == NULL) 312 S3C_PMDBG("%s: no pm for %s\n", __func__, ourchip->chip.label); 313 else 314 pm->save(ourchip); 315} 316 317/** 318 * samsung_pm_save_gpios() - Save the state of the GPIO banks. 319 * 320 * For all the GPIO banks, save the state of each one ready for going 321 * into a suspend mode. 322 */ 323void samsung_pm_save_gpios(void) 324{ 325 struct samsung_gpio_chip *ourchip; 326 unsigned int gpio_nr; 327 328 for (gpio_nr = 0; gpio_nr < S3C_GPIO_END;) { 329 ourchip = samsung_gpiolib_getchip(gpio_nr); 330 if (!ourchip) { 331 gpio_nr++; 332 continue; 333 } 334 335 samsung_pm_save_gpio(ourchip); 336 337 S3C_PMDBG("%s: save %08x,%08x,%08x,%08x\n", 338 ourchip->chip.label, 339 ourchip->pm_save[0], 340 ourchip->pm_save[1], 341 ourchip->pm_save[2], 342 ourchip->pm_save[3]); 343 344 gpio_nr += ourchip->chip.ngpio; 345 gpio_nr += CONFIG_S3C_GPIO_SPACE; 346 } 347} 348 349/** 350 * samsung_pm_resume_gpio() - restore gpio chip data after suspend 351 * @ourchip: The suspended chip. 352 */ 353static void samsung_pm_resume_gpio(struct samsung_gpio_chip *ourchip) 354{ 355 struct samsung_gpio_pm *pm = ourchip->pm; 356 357 if (pm == NULL || pm->resume == NULL) 358 S3C_PMDBG("%s: no pm for %s\n", __func__, ourchip->chip.label); 359 else 360 pm->resume(ourchip); 361} 362 363void samsung_pm_restore_gpios(void) 364{ 365 struct samsung_gpio_chip *ourchip; 366 unsigned int gpio_nr; 367 368 for (gpio_nr = 0; gpio_nr < S3C_GPIO_END;) { 369 ourchip = samsung_gpiolib_getchip(gpio_nr); 370 if (!ourchip) { 371 gpio_nr++; 372 continue; 373 } 374 375 samsung_pm_resume_gpio(ourchip); 376 377 gpio_nr += ourchip->chip.ngpio; 378 gpio_nr += CONFIG_S3C_GPIO_SPACE; 379 } 380}