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 27 static 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 33 static 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 59 struct samsung_gpio_pm samsung_gpio_pm_1bit = { 60 .save = samsung_gpio_pm_1bit_save, 61 .resume = samsung_gpio_pm_1bit_resume, 62 }; 63 64 static 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 74 static 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 81 static 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 88 static 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 */ 120 static 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 186 struct 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) 192 static 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 202 static 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 241 static 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 256 static 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 297 struct 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 */ 307 static 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 */ 323 void 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 */ 353 static 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 363 void 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 } 381