Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 //
0003 // Copyright 2008 Openmoko, Inc.
0004 // Copyright 2008 Simtec Electronics
0005 //  Ben Dooks <ben@simtec.co.uk>
0006 //  http://armlinux.simtec.co.uk/
0007 //
0008 // S3C series GPIO PM code
0009 
0010 #include <linux/kernel.h>
0011 #include <linux/device.h>
0012 #include <linux/init.h>
0013 #include <linux/io.h>
0014 #include <linux/gpio.h>
0015 
0016 #include "gpio-samsung.h"
0017 
0018 #include "gpio-core.h"
0019 #include "pm.h"
0020 
0021 /* PM GPIO helpers */
0022 
0023 #define OFFS_CON    (0x00)
0024 #define OFFS_DAT    (0x04)
0025 #define OFFS_UP     (0x08)
0026 
0027 static void samsung_gpio_pm_1bit_save(struct samsung_gpio_chip *chip)
0028 {
0029     chip->pm_save[0] = __raw_readl(chip->base + OFFS_CON);
0030     chip->pm_save[1] = __raw_readl(chip->base + OFFS_DAT);
0031 }
0032 
0033 static void samsung_gpio_pm_1bit_resume(struct samsung_gpio_chip *chip)
0034 {
0035     void __iomem *base = chip->base;
0036     u32 old_gpcon = __raw_readl(base + OFFS_CON);
0037     u32 old_gpdat = __raw_readl(base + OFFS_DAT);
0038     u32 gps_gpcon = chip->pm_save[0];
0039     u32 gps_gpdat = chip->pm_save[1];
0040     u32 gpcon;
0041 
0042     /* GPACON only has one bit per control / data and no PULLUPs.
0043      * GPACON[x] = 0 => Output, 1 => SFN */
0044 
0045     /* first set all SFN bits to SFN */
0046 
0047     gpcon = old_gpcon | gps_gpcon;
0048     __raw_writel(gpcon, base + OFFS_CON);
0049 
0050     /* now set all the other bits */
0051 
0052     __raw_writel(gps_gpdat, base + OFFS_DAT);
0053     __raw_writel(gps_gpcon, base + OFFS_CON);
0054 
0055     S3C_PMDBG("%s: CON %08x => %08x, DAT %08x => %08x\n",
0056           chip->chip.label, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat);
0057 }
0058 
0059 struct samsung_gpio_pm samsung_gpio_pm_1bit = {
0060     .save   = samsung_gpio_pm_1bit_save,
0061     .resume = samsung_gpio_pm_1bit_resume,
0062 };
0063 
0064 static void samsung_gpio_pm_2bit_save(struct samsung_gpio_chip *chip)
0065 {
0066     chip->pm_save[0] = __raw_readl(chip->base + OFFS_CON);
0067     chip->pm_save[1] = __raw_readl(chip->base + OFFS_DAT);
0068     chip->pm_save[2] = __raw_readl(chip->base + OFFS_UP);
0069 }
0070 
0071 /* Test whether the given masked+shifted bits of an GPIO configuration
0072  * are one of the SFN (special function) modes. */
0073 
0074 static inline int is_sfn(unsigned long con)
0075 {
0076     return con >= 2;
0077 }
0078 
0079 /* Test if the given masked+shifted GPIO configuration is an input */
0080 
0081 static inline int is_in(unsigned long con)
0082 {
0083     return con == 0;
0084 }
0085 
0086 /* Test if the given masked+shifted GPIO configuration is an output */
0087 
0088 static inline int is_out(unsigned long con)
0089 {
0090     return con == 1;
0091 }
0092 
0093 /**
0094  * samsung_gpio_pm_2bit_resume() - restore the given GPIO bank
0095  * @chip: The chip information to resume.
0096  *
0097  * Restore one of the GPIO banks that was saved during suspend. This is
0098  * not as simple as once thought, due to the possibility of glitches
0099  * from the order that the CON and DAT registers are set in.
0100  *
0101  * The three states the pin can be are {IN,OUT,SFN} which gives us 9
0102  * combinations of changes to check. Three of these, if the pin stays
0103  * in the same configuration can be discounted. This leaves us with
0104  * the following:
0105  *
0106  * { IN => OUT }  Change DAT first
0107  * { IN => SFN }  Change CON first
0108  * { OUT => SFN } Change CON first, so new data will not glitch
0109  * { OUT => IN }  Change CON first, so new data will not glitch
0110  * { SFN => IN }  Change CON first
0111  * { SFN => OUT } Change DAT first, so new data will not glitch [1]
0112  *
0113  * We do not currently deal with the UP registers as these control
0114  * weak resistors, so a small delay in change should not need to bring
0115  * these into the calculations.
0116  *
0117  * [1] this assumes that writing to a pin DAT whilst in SFN will set the
0118  *     state for when it is next output.
0119  */
0120 static void samsung_gpio_pm_2bit_resume(struct samsung_gpio_chip *chip)
0121 {
0122     void __iomem *base = chip->base;
0123     u32 old_gpcon = __raw_readl(base + OFFS_CON);
0124     u32 old_gpdat = __raw_readl(base + OFFS_DAT);
0125     u32 gps_gpcon = chip->pm_save[0];
0126     u32 gps_gpdat = chip->pm_save[1];
0127     u32 gpcon, old, new, mask;
0128     u32 change_mask = 0x0;
0129     int nr;
0130 
0131     /* restore GPIO pull-up settings */
0132     __raw_writel(chip->pm_save[2], base + OFFS_UP);
0133 
0134     /* Create a change_mask of all the items that need to have
0135      * their CON value changed before their DAT value, so that
0136      * we minimise the work between the two settings.
0137      */
0138 
0139     for (nr = 0, mask = 0x03; nr < 32; nr += 2, mask <<= 2) {
0140         old = (old_gpcon & mask) >> nr;
0141         new = (gps_gpcon & mask) >> nr;
0142 
0143         /* If there is no change, then skip */
0144 
0145         if (old == new)
0146             continue;
0147 
0148         /* If both are special function, then skip */
0149 
0150         if (is_sfn(old) && is_sfn(new))
0151             continue;
0152 
0153         /* Change is IN => OUT, do not change now */
0154 
0155         if (is_in(old) && is_out(new))
0156             continue;
0157 
0158         /* Change is SFN => OUT, do not change now */
0159 
0160         if (is_sfn(old) && is_out(new))
0161             continue;
0162 
0163         /* We should now be at the case of IN=>SFN,
0164          * OUT=>SFN, OUT=>IN, SFN=>IN. */
0165 
0166         change_mask |= mask;
0167     }
0168 
0169 
0170     /* Write the new CON settings */
0171 
0172     gpcon = old_gpcon & ~change_mask;
0173     gpcon |= gps_gpcon & change_mask;
0174 
0175     __raw_writel(gpcon, base + OFFS_CON);
0176 
0177     /* Now change any items that require DAT,CON */
0178 
0179     __raw_writel(gps_gpdat, base + OFFS_DAT);
0180     __raw_writel(gps_gpcon, base + OFFS_CON);
0181 
0182     S3C_PMDBG("%s: CON %08x => %08x, DAT %08x => %08x\n",
0183           chip->chip.label, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat);
0184 }
0185 
0186 struct samsung_gpio_pm samsung_gpio_pm_2bit = {
0187     .save   = samsung_gpio_pm_2bit_save,
0188     .resume = samsung_gpio_pm_2bit_resume,
0189 };
0190 
0191 #if defined(CONFIG_ARCH_S3C64XX)
0192 static void samsung_gpio_pm_4bit_save(struct samsung_gpio_chip *chip)
0193 {
0194     chip->pm_save[1] = __raw_readl(chip->base + OFFS_CON);
0195     chip->pm_save[2] = __raw_readl(chip->base + OFFS_DAT);
0196     chip->pm_save[3] = __raw_readl(chip->base + OFFS_UP);
0197 
0198     if (chip->chip.ngpio > 8)
0199         chip->pm_save[0] = __raw_readl(chip->base - 4);
0200 }
0201 
0202 static u32 samsung_gpio_pm_4bit_mask(u32 old_gpcon, u32 gps_gpcon)
0203 {
0204     u32 old, new, mask;
0205     u32 change_mask = 0x0;
0206     int nr;
0207 
0208     for (nr = 0, mask = 0x0f; nr < 16; nr += 4, mask <<= 4) {
0209         old = (old_gpcon & mask) >> nr;
0210         new = (gps_gpcon & mask) >> nr;
0211 
0212         /* If there is no change, then skip */
0213 
0214         if (old == new)
0215             continue;
0216 
0217         /* If both are special function, then skip */
0218 
0219         if (is_sfn(old) && is_sfn(new))
0220             continue;
0221 
0222         /* Change is IN => OUT, do not change now */
0223 
0224         if (is_in(old) && is_out(new))
0225             continue;
0226 
0227         /* Change is SFN => OUT, do not change now */
0228 
0229         if (is_sfn(old) && is_out(new))
0230             continue;
0231 
0232         /* We should now be at the case of IN=>SFN,
0233          * OUT=>SFN, OUT=>IN, SFN=>IN. */
0234 
0235         change_mask |= mask;
0236     }
0237 
0238     return change_mask;
0239 }
0240 
0241 static void samsung_gpio_pm_4bit_con(struct samsung_gpio_chip *chip, int index)
0242 {
0243     void __iomem *con = chip->base + (index * 4);
0244     u32 old_gpcon = __raw_readl(con);
0245     u32 gps_gpcon = chip->pm_save[index + 1];
0246     u32 gpcon, mask;
0247 
0248     mask = samsung_gpio_pm_4bit_mask(old_gpcon, gps_gpcon);
0249 
0250     gpcon = old_gpcon & ~mask;
0251     gpcon |= gps_gpcon & mask;
0252 
0253     __raw_writel(gpcon, con);
0254 }
0255 
0256 static void samsung_gpio_pm_4bit_resume(struct samsung_gpio_chip *chip)
0257 {
0258     void __iomem *base = chip->base;
0259     u32 old_gpcon[2];
0260     u32 old_gpdat = __raw_readl(base + OFFS_DAT);
0261     u32 gps_gpdat = chip->pm_save[2];
0262 
0263     /* First, modify the CON settings */
0264 
0265     old_gpcon[0] = 0;
0266     old_gpcon[1] = __raw_readl(base + OFFS_CON);
0267 
0268     samsung_gpio_pm_4bit_con(chip, 0);
0269     if (chip->chip.ngpio > 8) {
0270         old_gpcon[0] = __raw_readl(base - 4);
0271         samsung_gpio_pm_4bit_con(chip, -1);
0272     }
0273 
0274     /* Now change the configurations that require DAT,CON */
0275 
0276     __raw_writel(chip->pm_save[2], base + OFFS_DAT);
0277     __raw_writel(chip->pm_save[1], base + OFFS_CON);
0278     if (chip->chip.ngpio > 8)
0279         __raw_writel(chip->pm_save[0], base - 4);
0280 
0281     __raw_writel(chip->pm_save[2], base + OFFS_DAT);
0282     __raw_writel(chip->pm_save[3], base + OFFS_UP);
0283 
0284     if (chip->chip.ngpio > 8) {
0285         S3C_PMDBG("%s: CON4 %08x,%08x => %08x,%08x, DAT %08x => %08x\n",
0286               chip->chip.label, old_gpcon[0], old_gpcon[1],
0287               __raw_readl(base - 4),
0288               __raw_readl(base + OFFS_CON),
0289               old_gpdat, gps_gpdat);
0290     } else
0291         S3C_PMDBG("%s: CON4 %08x => %08x, DAT %08x => %08x\n",
0292               chip->chip.label, old_gpcon[1],
0293               __raw_readl(base + OFFS_CON),
0294               old_gpdat, gps_gpdat);
0295 }
0296 
0297 struct samsung_gpio_pm samsung_gpio_pm_4bit = {
0298     .save   = samsung_gpio_pm_4bit_save,
0299     .resume = samsung_gpio_pm_4bit_resume,
0300 };
0301 #endif /* CONFIG_ARCH_S3C64XX */
0302 
0303 /**
0304  * samsung_pm_save_gpio() - save gpio chip data for suspend
0305  * @ourchip: The chip for suspend.
0306  */
0307 static void samsung_pm_save_gpio(struct samsung_gpio_chip *ourchip)
0308 {
0309     struct samsung_gpio_pm *pm = ourchip->pm;
0310 
0311     if (pm == NULL || pm->save == NULL)
0312         S3C_PMDBG("%s: no pm for %s\n", __func__, ourchip->chip.label);
0313     else
0314         pm->save(ourchip);
0315 }
0316 
0317 /**
0318  * samsung_pm_save_gpios() - Save the state of the GPIO banks.
0319  *
0320  * For all the GPIO banks, save the state of each one ready for going
0321  * into a suspend mode.
0322  */
0323 void samsung_pm_save_gpios(void)
0324 {
0325     struct samsung_gpio_chip *ourchip;
0326     unsigned int gpio_nr;
0327 
0328     for (gpio_nr = 0; gpio_nr < S3C_GPIO_END;) {
0329         ourchip = samsung_gpiolib_getchip(gpio_nr);
0330         if (!ourchip) {
0331             gpio_nr++;
0332             continue;
0333         }
0334 
0335         samsung_pm_save_gpio(ourchip);
0336 
0337         S3C_PMDBG("%s: save %08x,%08x,%08x,%08x\n",
0338               ourchip->chip.label,
0339               ourchip->pm_save[0],
0340               ourchip->pm_save[1],
0341               ourchip->pm_save[2],
0342               ourchip->pm_save[3]);
0343 
0344         gpio_nr += ourchip->chip.ngpio;
0345         gpio_nr += CONFIG_S3C_GPIO_SPACE;
0346     }
0347 }
0348 
0349 /**
0350  * samsung_pm_resume_gpio() - restore gpio chip data after suspend
0351  * @ourchip: The suspended chip.
0352  */
0353 static void samsung_pm_resume_gpio(struct samsung_gpio_chip *ourchip)
0354 {
0355     struct samsung_gpio_pm *pm = ourchip->pm;
0356 
0357     if (pm == NULL || pm->resume == NULL)
0358         S3C_PMDBG("%s: no pm for %s\n", __func__, ourchip->chip.label);
0359     else
0360         pm->resume(ourchip);
0361 }
0362 
0363 void samsung_pm_restore_gpios(void)
0364 {
0365     struct samsung_gpio_chip *ourchip;
0366     unsigned int gpio_nr;
0367 
0368     for (gpio_nr = 0; gpio_nr < S3C_GPIO_END;) {
0369         ourchip = samsung_gpiolib_getchip(gpio_nr);
0370         if (!ourchip) {
0371             gpio_nr++;
0372             continue;
0373         }
0374 
0375         samsung_pm_resume_gpio(ourchip);
0376 
0377         gpio_nr += ourchip->chip.ngpio;
0378         gpio_nr += CONFIG_S3C_GPIO_SPACE;
0379     }
0380 }