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