| Manuel Lauss | 61f9c58 | 2008-12-21 09:26:27 +0100 | [diff] [blame] | 1 | /* | 
|  | 2 | * Alchemy Development Board example suspend userspace interface. | 
|  | 3 | * | 
|  | 4 | * (c) 2008 Manuel Lauss <mano@roarinelk.homelinux.net> | 
|  | 5 | */ | 
|  | 6 |  | 
|  | 7 | #include <linux/init.h> | 
|  | 8 | #include <linux/kobject.h> | 
|  | 9 | #include <linux/suspend.h> | 
|  | 10 | #include <linux/sysfs.h> | 
|  | 11 | #include <asm/mach-au1x00/au1000.h> | 
| Manuel Lauss | ce65cc8 | 2009-06-06 14:09:58 +0200 | [diff] [blame] | 12 | #include <asm/mach-au1x00/gpio.h> | 
| Manuel Lauss | e275ed5 | 2010-02-23 18:57:43 +0100 | [diff] [blame] | 13 | #include <asm/mach-db1x00/bcsr.h> | 
| Manuel Lauss | 61f9c58 | 2008-12-21 09:26:27 +0100 | [diff] [blame] | 14 |  | 
|  | 15 | /* | 
|  | 16 | * Generic suspend userspace interface for Alchemy development boards. | 
|  | 17 | * This code exports a few sysfs nodes under /sys/power/db1x/ which | 
|  | 18 | * can be used by userspace to en/disable all au1x-provided wakeup | 
|  | 19 | * sources and configure the timeout after which the the TOYMATCH2 irq | 
|  | 20 | * is to trigger a wakeup. | 
|  | 21 | */ | 
|  | 22 |  | 
|  | 23 |  | 
|  | 24 | static unsigned long db1x_pm_sleep_secs; | 
|  | 25 | static unsigned long db1x_pm_wakemsk; | 
|  | 26 | static unsigned long db1x_pm_last_wakesrc; | 
|  | 27 |  | 
|  | 28 | static int db1x_pm_enter(suspend_state_t state) | 
|  | 29 | { | 
| Manuel Lauss | e275ed5 | 2010-02-23 18:57:43 +0100 | [diff] [blame] | 30 | unsigned short bcsrs[16]; | 
|  | 31 | int i, j, hasint; | 
|  | 32 |  | 
|  | 33 | /* save CPLD regs */ | 
|  | 34 | hasint = bcsr_read(BCSR_WHOAMI); | 
|  | 35 | hasint = BCSR_WHOAMI_BOARD(hasint) >= BCSR_WHOAMI_DB1200; | 
|  | 36 | j = (hasint) ? BCSR_MASKSET : BCSR_SYSTEM; | 
|  | 37 |  | 
|  | 38 | for (i = BCSR_STATUS; i <= j; i++) | 
|  | 39 | bcsrs[i] = bcsr_read(i); | 
|  | 40 |  | 
|  | 41 | /* shut off hexleds */ | 
|  | 42 | bcsr_write(BCSR_HEXCLEAR, 3); | 
|  | 43 |  | 
| Manuel Lauss | 61f9c58 | 2008-12-21 09:26:27 +0100 | [diff] [blame] | 44 | /* enable GPIO based wakeup */ | 
| Manuel Lauss | ce65cc8 | 2009-06-06 14:09:58 +0200 | [diff] [blame] | 45 | alchemy_gpio1_input_enable(); | 
| Manuel Lauss | 61f9c58 | 2008-12-21 09:26:27 +0100 | [diff] [blame] | 46 |  | 
|  | 47 | /* clear and setup wake cause and source */ | 
|  | 48 | au_writel(0, SYS_WAKEMSK); | 
|  | 49 | au_sync(); | 
|  | 50 | au_writel(0, SYS_WAKESRC); | 
|  | 51 | au_sync(); | 
|  | 52 |  | 
|  | 53 | au_writel(db1x_pm_wakemsk, SYS_WAKEMSK); | 
|  | 54 | au_sync(); | 
|  | 55 |  | 
|  | 56 | /* setup 1Hz-timer-based wakeup: wait for reg access */ | 
|  | 57 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20) | 
|  | 58 | asm volatile ("nop"); | 
|  | 59 |  | 
|  | 60 | au_writel(au_readl(SYS_TOYREAD) + db1x_pm_sleep_secs, SYS_TOYMATCH2); | 
|  | 61 | au_sync(); | 
|  | 62 |  | 
|  | 63 | /* wait for value to really hit the register */ | 
|  | 64 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20) | 
|  | 65 | asm volatile ("nop"); | 
|  | 66 |  | 
|  | 67 | /* ...and now the sandman can come! */ | 
|  | 68 | au_sleep(); | 
|  | 69 |  | 
| Manuel Lauss | e275ed5 | 2010-02-23 18:57:43 +0100 | [diff] [blame] | 70 |  | 
|  | 71 | /* restore CPLD regs */ | 
|  | 72 | for (i = BCSR_STATUS; i <= BCSR_SYSTEM; i++) | 
|  | 73 | bcsr_write(i, bcsrs[i]); | 
|  | 74 |  | 
|  | 75 | /* restore CPLD int registers */ | 
|  | 76 | if (hasint) { | 
|  | 77 | bcsr_write(BCSR_INTCLR, 0xffff); | 
|  | 78 | bcsr_write(BCSR_MASKCLR, 0xffff); | 
|  | 79 | bcsr_write(BCSR_INTSTAT, 0xffff); | 
|  | 80 | bcsr_write(BCSR_INTSET, bcsrs[BCSR_INTSET]); | 
|  | 81 | bcsr_write(BCSR_MASKSET, bcsrs[BCSR_MASKSET]); | 
|  | 82 | } | 
|  | 83 |  | 
|  | 84 | /* light up hexleds */ | 
|  | 85 | bcsr_write(BCSR_HEXCLEAR, 0); | 
|  | 86 |  | 
| Manuel Lauss | 61f9c58 | 2008-12-21 09:26:27 +0100 | [diff] [blame] | 87 | return 0; | 
|  | 88 | } | 
|  | 89 |  | 
|  | 90 | static int db1x_pm_begin(suspend_state_t state) | 
|  | 91 | { | 
|  | 92 | if (!db1x_pm_wakemsk) { | 
|  | 93 | printk(KERN_ERR "db1x: no wakeup source activated!\n"); | 
|  | 94 | return -EINVAL; | 
|  | 95 | } | 
|  | 96 |  | 
|  | 97 | return 0; | 
|  | 98 | } | 
|  | 99 |  | 
|  | 100 | static void db1x_pm_end(void) | 
|  | 101 | { | 
|  | 102 | /* read and store wakeup source, the clear the register. To | 
|  | 103 | * be able to clear it, WAKEMSK must be cleared first. | 
|  | 104 | */ | 
|  | 105 | db1x_pm_last_wakesrc = au_readl(SYS_WAKESRC); | 
|  | 106 |  | 
|  | 107 | au_writel(0, SYS_WAKEMSK); | 
|  | 108 | au_writel(0, SYS_WAKESRC); | 
|  | 109 | au_sync(); | 
|  | 110 |  | 
|  | 111 | } | 
|  | 112 |  | 
| Lionel Debroux | 2f55ac0 | 2010-11-16 14:14:02 +0100 | [diff] [blame] | 113 | static const struct platform_suspend_ops db1x_pm_ops = { | 
| Manuel Lauss | 61f9c58 | 2008-12-21 09:26:27 +0100 | [diff] [blame] | 114 | .valid		= suspend_valid_only_mem, | 
|  | 115 | .begin		= db1x_pm_begin, | 
|  | 116 | .enter		= db1x_pm_enter, | 
|  | 117 | .end		= db1x_pm_end, | 
|  | 118 | }; | 
|  | 119 |  | 
|  | 120 | #define ATTRCMP(x) (0 == strcmp(attr->attr.name, #x)) | 
|  | 121 |  | 
|  | 122 | static ssize_t db1x_pmattr_show(struct kobject *kobj, | 
|  | 123 | struct kobj_attribute *attr, | 
|  | 124 | char *buf) | 
|  | 125 | { | 
|  | 126 | int idx; | 
|  | 127 |  | 
|  | 128 | if (ATTRCMP(timer_timeout)) | 
|  | 129 | return sprintf(buf, "%lu\n", db1x_pm_sleep_secs); | 
|  | 130 |  | 
|  | 131 | else if (ATTRCMP(timer)) | 
|  | 132 | return sprintf(buf, "%u\n", | 
|  | 133 | !!(db1x_pm_wakemsk & SYS_WAKEMSK_M2)); | 
|  | 134 |  | 
|  | 135 | else if (ATTRCMP(wakesrc)) | 
|  | 136 | return sprintf(buf, "%lu\n", db1x_pm_last_wakesrc); | 
|  | 137 |  | 
|  | 138 | else if (ATTRCMP(gpio0) || ATTRCMP(gpio1) || ATTRCMP(gpio2) || | 
|  | 139 | ATTRCMP(gpio3) || ATTRCMP(gpio4) || ATTRCMP(gpio5) || | 
|  | 140 | ATTRCMP(gpio6) || ATTRCMP(gpio7)) { | 
|  | 141 | idx = (attr->attr.name)[4] - '0'; | 
|  | 142 | return sprintf(buf, "%d\n", | 
|  | 143 | !!(db1x_pm_wakemsk & SYS_WAKEMSK_GPIO(idx))); | 
|  | 144 |  | 
|  | 145 | } else if (ATTRCMP(wakemsk)) { | 
|  | 146 | return sprintf(buf, "%08lx\n", db1x_pm_wakemsk); | 
|  | 147 | } | 
|  | 148 |  | 
|  | 149 | return -ENOENT; | 
|  | 150 | } | 
|  | 151 |  | 
|  | 152 | static ssize_t db1x_pmattr_store(struct kobject *kobj, | 
|  | 153 | struct kobj_attribute *attr, | 
|  | 154 | const char *instr, | 
|  | 155 | size_t bytes) | 
|  | 156 | { | 
|  | 157 | unsigned long l; | 
|  | 158 | int tmp; | 
|  | 159 |  | 
|  | 160 | if (ATTRCMP(timer_timeout)) { | 
|  | 161 | tmp = strict_strtoul(instr, 0, &l); | 
|  | 162 | if (tmp) | 
|  | 163 | return tmp; | 
|  | 164 |  | 
|  | 165 | db1x_pm_sleep_secs = l; | 
|  | 166 |  | 
|  | 167 | } else if (ATTRCMP(timer)) { | 
|  | 168 | if (instr[0] != '0') | 
|  | 169 | db1x_pm_wakemsk |= SYS_WAKEMSK_M2; | 
|  | 170 | else | 
|  | 171 | db1x_pm_wakemsk &= ~SYS_WAKEMSK_M2; | 
|  | 172 |  | 
|  | 173 | } else if (ATTRCMP(gpio0) || ATTRCMP(gpio1) || ATTRCMP(gpio2) || | 
|  | 174 | ATTRCMP(gpio3) || ATTRCMP(gpio4) || ATTRCMP(gpio5) || | 
|  | 175 | ATTRCMP(gpio6) || ATTRCMP(gpio7)) { | 
|  | 176 | tmp = (attr->attr.name)[4] - '0'; | 
|  | 177 | if (instr[0] != '0') { | 
|  | 178 | db1x_pm_wakemsk |= SYS_WAKEMSK_GPIO(tmp); | 
|  | 179 | } else { | 
|  | 180 | db1x_pm_wakemsk &= ~SYS_WAKEMSK_GPIO(tmp); | 
|  | 181 | } | 
|  | 182 |  | 
|  | 183 | } else if (ATTRCMP(wakemsk)) { | 
|  | 184 | tmp = strict_strtoul(instr, 0, &l); | 
|  | 185 | if (tmp) | 
|  | 186 | return tmp; | 
|  | 187 |  | 
|  | 188 | db1x_pm_wakemsk = l & 0x0000003f; | 
|  | 189 |  | 
|  | 190 | } else | 
|  | 191 | bytes = -ENOENT; | 
|  | 192 |  | 
|  | 193 | return bytes; | 
|  | 194 | } | 
|  | 195 |  | 
|  | 196 | #define ATTR(x)							\ | 
|  | 197 | static struct kobj_attribute x##_attribute = 		\ | 
|  | 198 | __ATTR(x, 0664, db1x_pmattr_show,		\ | 
|  | 199 | db1x_pmattr_store); | 
|  | 200 |  | 
|  | 201 | ATTR(gpio0)		/* GPIO-based wakeup enable */ | 
|  | 202 | ATTR(gpio1) | 
|  | 203 | ATTR(gpio2) | 
|  | 204 | ATTR(gpio3) | 
|  | 205 | ATTR(gpio4) | 
|  | 206 | ATTR(gpio5) | 
|  | 207 | ATTR(gpio6) | 
|  | 208 | ATTR(gpio7) | 
|  | 209 | ATTR(timer)		/* TOYMATCH2-based wakeup enable */ | 
|  | 210 | ATTR(timer_timeout)	/* timer-based wakeup timeout value, in seconds */ | 
|  | 211 | ATTR(wakesrc)		/* contents of SYS_WAKESRC after last wakeup */ | 
|  | 212 | ATTR(wakemsk)		/* direct access to SYS_WAKEMSK */ | 
|  | 213 |  | 
|  | 214 | #define ATTR_LIST(x)	& x ## _attribute.attr | 
|  | 215 | static struct attribute *db1x_pmattrs[] = { | 
|  | 216 | ATTR_LIST(gpio0), | 
|  | 217 | ATTR_LIST(gpio1), | 
|  | 218 | ATTR_LIST(gpio2), | 
|  | 219 | ATTR_LIST(gpio3), | 
|  | 220 | ATTR_LIST(gpio4), | 
|  | 221 | ATTR_LIST(gpio5), | 
|  | 222 | ATTR_LIST(gpio6), | 
|  | 223 | ATTR_LIST(gpio7), | 
|  | 224 | ATTR_LIST(timer), | 
|  | 225 | ATTR_LIST(timer_timeout), | 
|  | 226 | ATTR_LIST(wakesrc), | 
|  | 227 | ATTR_LIST(wakemsk), | 
|  | 228 | NULL,		/* terminator */ | 
|  | 229 | }; | 
|  | 230 |  | 
|  | 231 | static struct attribute_group db1x_pmattr_group = { | 
|  | 232 | .name	= "db1x", | 
|  | 233 | .attrs	= db1x_pmattrs, | 
|  | 234 | }; | 
|  | 235 |  | 
|  | 236 | /* | 
|  | 237 | * Initialize suspend interface | 
|  | 238 | */ | 
|  | 239 | static int __init pm_init(void) | 
|  | 240 | { | 
|  | 241 | /* init TOY to tick at 1Hz if not already done. No need to wait | 
|  | 242 | * for confirmation since there's plenty of time from here to | 
|  | 243 | * the next suspend cycle. | 
|  | 244 | */ | 
|  | 245 | if (au_readl(SYS_TOYTRIM) != 32767) { | 
|  | 246 | au_writel(32767, SYS_TOYTRIM); | 
|  | 247 | au_sync(); | 
|  | 248 | } | 
|  | 249 |  | 
|  | 250 | db1x_pm_last_wakesrc = au_readl(SYS_WAKESRC); | 
|  | 251 |  | 
|  | 252 | au_writel(0, SYS_WAKESRC); | 
|  | 253 | au_sync(); | 
|  | 254 | au_writel(0, SYS_WAKEMSK); | 
|  | 255 | au_sync(); | 
|  | 256 |  | 
|  | 257 | suspend_set_ops(&db1x_pm_ops); | 
|  | 258 |  | 
|  | 259 | return sysfs_create_group(power_kobj, &db1x_pmattr_group); | 
|  | 260 | } | 
|  | 261 |  | 
|  | 262 | late_initcall(pm_init); |