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