18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Alchemy Development Board example suspend userspace interface. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * (c) 2008 Manuel Lauss <mano@roarinelk.homelinux.net> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/init.h> 98c2ecf20Sopenharmony_ci#include <linux/kobject.h> 108c2ecf20Sopenharmony_ci#include <linux/suspend.h> 118c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 128c2ecf20Sopenharmony_ci#include <asm/mach-au1x00/au1000.h> 138c2ecf20Sopenharmony_ci#include <asm/mach-au1x00/gpio-au1000.h> 148c2ecf20Sopenharmony_ci#include <asm/mach-db1x00/bcsr.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/* 178c2ecf20Sopenharmony_ci * Generic suspend userspace interface for Alchemy development boards. 188c2ecf20Sopenharmony_ci * This code exports a few sysfs nodes under /sys/power/db1x/ which 198c2ecf20Sopenharmony_ci * can be used by userspace to en/disable all au1x-provided wakeup 208c2ecf20Sopenharmony_ci * sources and configure the timeout after which the the TOYMATCH2 irq 218c2ecf20Sopenharmony_ci * is to trigger a wakeup. 228c2ecf20Sopenharmony_ci */ 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic unsigned long db1x_pm_sleep_secs; 268c2ecf20Sopenharmony_cistatic unsigned long db1x_pm_wakemsk; 278c2ecf20Sopenharmony_cistatic unsigned long db1x_pm_last_wakesrc; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_cistatic int db1x_pm_enter(suspend_state_t state) 308c2ecf20Sopenharmony_ci{ 318c2ecf20Sopenharmony_ci unsigned short bcsrs[16]; 328c2ecf20Sopenharmony_ci int i, j, hasint; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci /* save CPLD regs */ 358c2ecf20Sopenharmony_ci hasint = bcsr_read(BCSR_WHOAMI); 368c2ecf20Sopenharmony_ci hasint = BCSR_WHOAMI_BOARD(hasint) >= BCSR_WHOAMI_DB1200; 378c2ecf20Sopenharmony_ci j = (hasint) ? BCSR_MASKSET : BCSR_SYSTEM; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci for (i = BCSR_STATUS; i <= j; i++) 408c2ecf20Sopenharmony_ci bcsrs[i] = bcsr_read(i); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci /* shut off hexleds */ 438c2ecf20Sopenharmony_ci bcsr_write(BCSR_HEXCLEAR, 3); 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci /* enable GPIO based wakeup */ 468c2ecf20Sopenharmony_ci alchemy_gpio1_input_enable(); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci /* clear and setup wake cause and source */ 498c2ecf20Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKEMSK); 508c2ecf20Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKESRC); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci alchemy_wrsys(db1x_pm_wakemsk, AU1000_SYS_WAKEMSK); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci /* setup 1Hz-timer-based wakeup: wait for reg access */ 558c2ecf20Sopenharmony_ci while (alchemy_rdsys(AU1000_SYS_CNTRCTRL) & SYS_CNTRL_M20) 568c2ecf20Sopenharmony_ci asm volatile ("nop"); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci alchemy_wrsys(alchemy_rdsys(AU1000_SYS_TOYREAD) + db1x_pm_sleep_secs, 598c2ecf20Sopenharmony_ci AU1000_SYS_TOYMATCH2); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci /* wait for value to really hit the register */ 628c2ecf20Sopenharmony_ci while (alchemy_rdsys(AU1000_SYS_CNTRCTRL) & SYS_CNTRL_M20) 638c2ecf20Sopenharmony_ci asm volatile ("nop"); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci /* ...and now the sandman can come! */ 668c2ecf20Sopenharmony_ci au_sleep(); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci /* restore CPLD regs */ 708c2ecf20Sopenharmony_ci for (i = BCSR_STATUS; i <= BCSR_SYSTEM; i++) 718c2ecf20Sopenharmony_ci bcsr_write(i, bcsrs[i]); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci /* restore CPLD int registers */ 748c2ecf20Sopenharmony_ci if (hasint) { 758c2ecf20Sopenharmony_ci bcsr_write(BCSR_INTCLR, 0xffff); 768c2ecf20Sopenharmony_ci bcsr_write(BCSR_MASKCLR, 0xffff); 778c2ecf20Sopenharmony_ci bcsr_write(BCSR_INTSTAT, 0xffff); 788c2ecf20Sopenharmony_ci bcsr_write(BCSR_INTSET, bcsrs[BCSR_INTSET]); 798c2ecf20Sopenharmony_ci bcsr_write(BCSR_MASKSET, bcsrs[BCSR_MASKSET]); 808c2ecf20Sopenharmony_ci } 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci /* light up hexleds */ 838c2ecf20Sopenharmony_ci bcsr_write(BCSR_HEXCLEAR, 0); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci return 0; 868c2ecf20Sopenharmony_ci} 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cistatic int db1x_pm_begin(suspend_state_t state) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci if (!db1x_pm_wakemsk) { 918c2ecf20Sopenharmony_ci printk(KERN_ERR "db1x: no wakeup source activated!\n"); 928c2ecf20Sopenharmony_ci return -EINVAL; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci return 0; 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic void db1x_pm_end(void) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci /* read and store wakeup source, the clear the register. To 1018c2ecf20Sopenharmony_ci * be able to clear it, WAKEMSK must be cleared first. 1028c2ecf20Sopenharmony_ci */ 1038c2ecf20Sopenharmony_ci db1x_pm_last_wakesrc = alchemy_rdsys(AU1000_SYS_WAKESRC); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKEMSK); 1068c2ecf20Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKESRC); 1078c2ecf20Sopenharmony_ci} 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic const struct platform_suspend_ops db1x_pm_ops = { 1108c2ecf20Sopenharmony_ci .valid = suspend_valid_only_mem, 1118c2ecf20Sopenharmony_ci .begin = db1x_pm_begin, 1128c2ecf20Sopenharmony_ci .enter = db1x_pm_enter, 1138c2ecf20Sopenharmony_ci .end = db1x_pm_end, 1148c2ecf20Sopenharmony_ci}; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci#define ATTRCMP(x) (0 == strcmp(attr->attr.name, #x)) 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic ssize_t db1x_pmattr_show(struct kobject *kobj, 1198c2ecf20Sopenharmony_ci struct kobj_attribute *attr, 1208c2ecf20Sopenharmony_ci char *buf) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci int idx; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if (ATTRCMP(timer_timeout)) 1258c2ecf20Sopenharmony_ci return sprintf(buf, "%lu\n", db1x_pm_sleep_secs); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci else if (ATTRCMP(timer)) 1288c2ecf20Sopenharmony_ci return sprintf(buf, "%u\n", 1298c2ecf20Sopenharmony_ci !!(db1x_pm_wakemsk & SYS_WAKEMSK_M2)); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci else if (ATTRCMP(wakesrc)) 1328c2ecf20Sopenharmony_ci return sprintf(buf, "%lu\n", db1x_pm_last_wakesrc); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci else if (ATTRCMP(gpio0) || ATTRCMP(gpio1) || ATTRCMP(gpio2) || 1358c2ecf20Sopenharmony_ci ATTRCMP(gpio3) || ATTRCMP(gpio4) || ATTRCMP(gpio5) || 1368c2ecf20Sopenharmony_ci ATTRCMP(gpio6) || ATTRCMP(gpio7)) { 1378c2ecf20Sopenharmony_ci idx = (attr->attr.name)[4] - '0'; 1388c2ecf20Sopenharmony_ci return sprintf(buf, "%d\n", 1398c2ecf20Sopenharmony_ci !!(db1x_pm_wakemsk & SYS_WAKEMSK_GPIO(idx))); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci } else if (ATTRCMP(wakemsk)) { 1428c2ecf20Sopenharmony_ci return sprintf(buf, "%08lx\n", db1x_pm_wakemsk); 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci return -ENOENT; 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic ssize_t db1x_pmattr_store(struct kobject *kobj, 1498c2ecf20Sopenharmony_ci struct kobj_attribute *attr, 1508c2ecf20Sopenharmony_ci const char *instr, 1518c2ecf20Sopenharmony_ci size_t bytes) 1528c2ecf20Sopenharmony_ci{ 1538c2ecf20Sopenharmony_ci unsigned long l; 1548c2ecf20Sopenharmony_ci int tmp; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci if (ATTRCMP(timer_timeout)) { 1578c2ecf20Sopenharmony_ci tmp = kstrtoul(instr, 0, &l); 1588c2ecf20Sopenharmony_ci if (tmp) 1598c2ecf20Sopenharmony_ci return tmp; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci db1x_pm_sleep_secs = l; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci } else if (ATTRCMP(timer)) { 1648c2ecf20Sopenharmony_ci if (instr[0] != '0') 1658c2ecf20Sopenharmony_ci db1x_pm_wakemsk |= SYS_WAKEMSK_M2; 1668c2ecf20Sopenharmony_ci else 1678c2ecf20Sopenharmony_ci db1x_pm_wakemsk &= ~SYS_WAKEMSK_M2; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci } else if (ATTRCMP(gpio0) || ATTRCMP(gpio1) || ATTRCMP(gpio2) || 1708c2ecf20Sopenharmony_ci ATTRCMP(gpio3) || ATTRCMP(gpio4) || ATTRCMP(gpio5) || 1718c2ecf20Sopenharmony_ci ATTRCMP(gpio6) || ATTRCMP(gpio7)) { 1728c2ecf20Sopenharmony_ci tmp = (attr->attr.name)[4] - '0'; 1738c2ecf20Sopenharmony_ci if (instr[0] != '0') { 1748c2ecf20Sopenharmony_ci db1x_pm_wakemsk |= SYS_WAKEMSK_GPIO(tmp); 1758c2ecf20Sopenharmony_ci } else { 1768c2ecf20Sopenharmony_ci db1x_pm_wakemsk &= ~SYS_WAKEMSK_GPIO(tmp); 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci } else if (ATTRCMP(wakemsk)) { 1808c2ecf20Sopenharmony_ci tmp = kstrtoul(instr, 0, &l); 1818c2ecf20Sopenharmony_ci if (tmp) 1828c2ecf20Sopenharmony_ci return tmp; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci db1x_pm_wakemsk = l & 0x0000003f; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci } else 1878c2ecf20Sopenharmony_ci bytes = -ENOENT; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci return bytes; 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci#define ATTR(x) \ 1938c2ecf20Sopenharmony_ci static struct kobj_attribute x##_attribute = \ 1948c2ecf20Sopenharmony_ci __ATTR(x, 0664, db1x_pmattr_show, \ 1958c2ecf20Sopenharmony_ci db1x_pmattr_store); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ciATTR(gpio0) /* GPIO-based wakeup enable */ 1988c2ecf20Sopenharmony_ciATTR(gpio1) 1998c2ecf20Sopenharmony_ciATTR(gpio2) 2008c2ecf20Sopenharmony_ciATTR(gpio3) 2018c2ecf20Sopenharmony_ciATTR(gpio4) 2028c2ecf20Sopenharmony_ciATTR(gpio5) 2038c2ecf20Sopenharmony_ciATTR(gpio6) 2048c2ecf20Sopenharmony_ciATTR(gpio7) 2058c2ecf20Sopenharmony_ciATTR(timer) /* TOYMATCH2-based wakeup enable */ 2068c2ecf20Sopenharmony_ciATTR(timer_timeout) /* timer-based wakeup timeout value, in seconds */ 2078c2ecf20Sopenharmony_ciATTR(wakesrc) /* contents of SYS_WAKESRC after last wakeup */ 2088c2ecf20Sopenharmony_ciATTR(wakemsk) /* direct access to SYS_WAKEMSK */ 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci#define ATTR_LIST(x) & x ## _attribute.attr 2118c2ecf20Sopenharmony_cistatic struct attribute *db1x_pmattrs[] = { 2128c2ecf20Sopenharmony_ci ATTR_LIST(gpio0), 2138c2ecf20Sopenharmony_ci ATTR_LIST(gpio1), 2148c2ecf20Sopenharmony_ci ATTR_LIST(gpio2), 2158c2ecf20Sopenharmony_ci ATTR_LIST(gpio3), 2168c2ecf20Sopenharmony_ci ATTR_LIST(gpio4), 2178c2ecf20Sopenharmony_ci ATTR_LIST(gpio5), 2188c2ecf20Sopenharmony_ci ATTR_LIST(gpio6), 2198c2ecf20Sopenharmony_ci ATTR_LIST(gpio7), 2208c2ecf20Sopenharmony_ci ATTR_LIST(timer), 2218c2ecf20Sopenharmony_ci ATTR_LIST(timer_timeout), 2228c2ecf20Sopenharmony_ci ATTR_LIST(wakesrc), 2238c2ecf20Sopenharmony_ci ATTR_LIST(wakemsk), 2248c2ecf20Sopenharmony_ci NULL, /* terminator */ 2258c2ecf20Sopenharmony_ci}; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cistatic struct attribute_group db1x_pmattr_group = { 2288c2ecf20Sopenharmony_ci .name = "db1x", 2298c2ecf20Sopenharmony_ci .attrs = db1x_pmattrs, 2308c2ecf20Sopenharmony_ci}; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci/* 2338c2ecf20Sopenharmony_ci * Initialize suspend interface 2348c2ecf20Sopenharmony_ci */ 2358c2ecf20Sopenharmony_cistatic int __init pm_init(void) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci /* init TOY to tick at 1Hz if not already done. No need to wait 2388c2ecf20Sopenharmony_ci * for confirmation since there's plenty of time from here to 2398c2ecf20Sopenharmony_ci * the next suspend cycle. 2408c2ecf20Sopenharmony_ci */ 2418c2ecf20Sopenharmony_ci if (alchemy_rdsys(AU1000_SYS_TOYTRIM) != 32767) 2428c2ecf20Sopenharmony_ci alchemy_wrsys(32767, AU1000_SYS_TOYTRIM); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci db1x_pm_last_wakesrc = alchemy_rdsys(AU1000_SYS_WAKESRC); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKESRC); 2478c2ecf20Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKEMSK); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci suspend_set_ops(&db1x_pm_ops); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci return sysfs_create_group(power_kobj, &db1x_pmattr_group); 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cilate_initcall(pm_init); 255