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