162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Alchemy Development Board example suspend userspace interface. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (c) 2008 Manuel Lauss <mano@roarinelk.homelinux.net> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/init.h> 962306a36Sopenharmony_ci#include <linux/kobject.h> 1062306a36Sopenharmony_ci#include <linux/suspend.h> 1162306a36Sopenharmony_ci#include <linux/sysfs.h> 1262306a36Sopenharmony_ci#include <asm/mach-au1x00/au1000.h> 1362306a36Sopenharmony_ci#include <asm/mach-au1x00/gpio-au1000.h> 1462306a36Sopenharmony_ci#include <asm/mach-db1x00/bcsr.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci/* 1762306a36Sopenharmony_ci * Generic suspend userspace interface for Alchemy development boards. 1862306a36Sopenharmony_ci * This code exports a few sysfs nodes under /sys/power/db1x/ which 1962306a36Sopenharmony_ci * can be used by userspace to en/disable all au1x-provided wakeup 2062306a36Sopenharmony_ci * sources and configure the timeout after which the TOYMATCH2 irq 2162306a36Sopenharmony_ci * is to trigger a wakeup. 2262306a36Sopenharmony_ci */ 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic unsigned long db1x_pm_sleep_secs; 2662306a36Sopenharmony_cistatic unsigned long db1x_pm_wakemsk; 2762306a36Sopenharmony_cistatic unsigned long db1x_pm_last_wakesrc; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic int db1x_pm_enter(suspend_state_t state) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci unsigned short bcsrs[16]; 3262306a36Sopenharmony_ci int i, j, hasint; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci /* save CPLD regs */ 3562306a36Sopenharmony_ci hasint = bcsr_read(BCSR_WHOAMI); 3662306a36Sopenharmony_ci hasint = BCSR_WHOAMI_BOARD(hasint) >= BCSR_WHOAMI_DB1200; 3762306a36Sopenharmony_ci j = (hasint) ? BCSR_MASKSET : BCSR_SYSTEM; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci for (i = BCSR_STATUS; i <= j; i++) 4062306a36Sopenharmony_ci bcsrs[i] = bcsr_read(i); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci /* shut off hexleds */ 4362306a36Sopenharmony_ci bcsr_write(BCSR_HEXCLEAR, 3); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* enable GPIO based wakeup */ 4662306a36Sopenharmony_ci alchemy_gpio1_input_enable(); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci /* clear and setup wake cause and source */ 4962306a36Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKEMSK); 5062306a36Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKESRC); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci alchemy_wrsys(db1x_pm_wakemsk, AU1000_SYS_WAKEMSK); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci /* setup 1Hz-timer-based wakeup: wait for reg access */ 5562306a36Sopenharmony_ci while (alchemy_rdsys(AU1000_SYS_CNTRCTRL) & SYS_CNTRL_M20) 5662306a36Sopenharmony_ci asm volatile ("nop"); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci alchemy_wrsys(alchemy_rdsys(AU1000_SYS_TOYREAD) + db1x_pm_sleep_secs, 5962306a36Sopenharmony_ci AU1000_SYS_TOYMATCH2); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci /* wait for value to really hit the register */ 6262306a36Sopenharmony_ci while (alchemy_rdsys(AU1000_SYS_CNTRCTRL) & SYS_CNTRL_M20) 6362306a36Sopenharmony_ci asm volatile ("nop"); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci /* ...and now the sandman can come! */ 6662306a36Sopenharmony_ci au_sleep(); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci /* restore CPLD regs */ 7062306a36Sopenharmony_ci for (i = BCSR_STATUS; i <= BCSR_SYSTEM; i++) 7162306a36Sopenharmony_ci bcsr_write(i, bcsrs[i]); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci /* restore CPLD int registers */ 7462306a36Sopenharmony_ci if (hasint) { 7562306a36Sopenharmony_ci bcsr_write(BCSR_INTCLR, 0xffff); 7662306a36Sopenharmony_ci bcsr_write(BCSR_MASKCLR, 0xffff); 7762306a36Sopenharmony_ci bcsr_write(BCSR_INTSTAT, 0xffff); 7862306a36Sopenharmony_ci bcsr_write(BCSR_INTSET, bcsrs[BCSR_INTSET]); 7962306a36Sopenharmony_ci bcsr_write(BCSR_MASKSET, bcsrs[BCSR_MASKSET]); 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci /* light up hexleds */ 8362306a36Sopenharmony_ci bcsr_write(BCSR_HEXCLEAR, 0); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int db1x_pm_begin(suspend_state_t state) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci if (!db1x_pm_wakemsk) { 9162306a36Sopenharmony_ci printk(KERN_ERR "db1x: no wakeup source activated!\n"); 9262306a36Sopenharmony_ci return -EINVAL; 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return 0; 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic void db1x_pm_end(void) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci /* read and store wakeup source, the clear the register. To 10162306a36Sopenharmony_ci * be able to clear it, WAKEMSK must be cleared first. 10262306a36Sopenharmony_ci */ 10362306a36Sopenharmony_ci db1x_pm_last_wakesrc = alchemy_rdsys(AU1000_SYS_WAKESRC); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKEMSK); 10662306a36Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKESRC); 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic const struct platform_suspend_ops db1x_pm_ops = { 11062306a36Sopenharmony_ci .valid = suspend_valid_only_mem, 11162306a36Sopenharmony_ci .begin = db1x_pm_begin, 11262306a36Sopenharmony_ci .enter = db1x_pm_enter, 11362306a36Sopenharmony_ci .end = db1x_pm_end, 11462306a36Sopenharmony_ci}; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci#define ATTRCMP(x) (0 == strcmp(attr->attr.name, #x)) 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic ssize_t db1x_pmattr_show(struct kobject *kobj, 11962306a36Sopenharmony_ci struct kobj_attribute *attr, 12062306a36Sopenharmony_ci char *buf) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci int idx; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci if (ATTRCMP(timer_timeout)) 12562306a36Sopenharmony_ci return sprintf(buf, "%lu\n", db1x_pm_sleep_secs); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci else if (ATTRCMP(timer)) 12862306a36Sopenharmony_ci return sprintf(buf, "%u\n", 12962306a36Sopenharmony_ci !!(db1x_pm_wakemsk & SYS_WAKEMSK_M2)); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci else if (ATTRCMP(wakesrc)) 13262306a36Sopenharmony_ci return sprintf(buf, "%lu\n", db1x_pm_last_wakesrc); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci else if (ATTRCMP(gpio0) || ATTRCMP(gpio1) || ATTRCMP(gpio2) || 13562306a36Sopenharmony_ci ATTRCMP(gpio3) || ATTRCMP(gpio4) || ATTRCMP(gpio5) || 13662306a36Sopenharmony_ci ATTRCMP(gpio6) || ATTRCMP(gpio7)) { 13762306a36Sopenharmony_ci idx = (attr->attr.name)[4] - '0'; 13862306a36Sopenharmony_ci return sprintf(buf, "%d\n", 13962306a36Sopenharmony_ci !!(db1x_pm_wakemsk & SYS_WAKEMSK_GPIO(idx))); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci } else if (ATTRCMP(wakemsk)) { 14262306a36Sopenharmony_ci return sprintf(buf, "%08lx\n", db1x_pm_wakemsk); 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci return -ENOENT; 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic ssize_t db1x_pmattr_store(struct kobject *kobj, 14962306a36Sopenharmony_ci struct kobj_attribute *attr, 15062306a36Sopenharmony_ci const char *instr, 15162306a36Sopenharmony_ci size_t bytes) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci unsigned long l; 15462306a36Sopenharmony_ci int tmp; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (ATTRCMP(timer_timeout)) { 15762306a36Sopenharmony_ci tmp = kstrtoul(instr, 0, &l); 15862306a36Sopenharmony_ci if (tmp) 15962306a36Sopenharmony_ci return tmp; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci db1x_pm_sleep_secs = l; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci } else if (ATTRCMP(timer)) { 16462306a36Sopenharmony_ci if (instr[0] != '0') 16562306a36Sopenharmony_ci db1x_pm_wakemsk |= SYS_WAKEMSK_M2; 16662306a36Sopenharmony_ci else 16762306a36Sopenharmony_ci db1x_pm_wakemsk &= ~SYS_WAKEMSK_M2; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci } else if (ATTRCMP(gpio0) || ATTRCMP(gpio1) || ATTRCMP(gpio2) || 17062306a36Sopenharmony_ci ATTRCMP(gpio3) || ATTRCMP(gpio4) || ATTRCMP(gpio5) || 17162306a36Sopenharmony_ci ATTRCMP(gpio6) || ATTRCMP(gpio7)) { 17262306a36Sopenharmony_ci tmp = (attr->attr.name)[4] - '0'; 17362306a36Sopenharmony_ci if (instr[0] != '0') { 17462306a36Sopenharmony_ci db1x_pm_wakemsk |= SYS_WAKEMSK_GPIO(tmp); 17562306a36Sopenharmony_ci } else { 17662306a36Sopenharmony_ci db1x_pm_wakemsk &= ~SYS_WAKEMSK_GPIO(tmp); 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci } else if (ATTRCMP(wakemsk)) { 18062306a36Sopenharmony_ci tmp = kstrtoul(instr, 0, &l); 18162306a36Sopenharmony_ci if (tmp) 18262306a36Sopenharmony_ci return tmp; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci db1x_pm_wakemsk = l & 0x0000003f; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci } else 18762306a36Sopenharmony_ci bytes = -ENOENT; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return bytes; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci#define ATTR(x) \ 19362306a36Sopenharmony_ci static struct kobj_attribute x##_attribute = \ 19462306a36Sopenharmony_ci __ATTR(x, 0664, db1x_pmattr_show, \ 19562306a36Sopenharmony_ci db1x_pmattr_store); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ciATTR(gpio0) /* GPIO-based wakeup enable */ 19862306a36Sopenharmony_ciATTR(gpio1) 19962306a36Sopenharmony_ciATTR(gpio2) 20062306a36Sopenharmony_ciATTR(gpio3) 20162306a36Sopenharmony_ciATTR(gpio4) 20262306a36Sopenharmony_ciATTR(gpio5) 20362306a36Sopenharmony_ciATTR(gpio6) 20462306a36Sopenharmony_ciATTR(gpio7) 20562306a36Sopenharmony_ciATTR(timer) /* TOYMATCH2-based wakeup enable */ 20662306a36Sopenharmony_ciATTR(timer_timeout) /* timer-based wakeup timeout value, in seconds */ 20762306a36Sopenharmony_ciATTR(wakesrc) /* contents of SYS_WAKESRC after last wakeup */ 20862306a36Sopenharmony_ciATTR(wakemsk) /* direct access to SYS_WAKEMSK */ 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci#define ATTR_LIST(x) & x ## _attribute.attr 21162306a36Sopenharmony_cistatic struct attribute *db1x_pmattrs[] = { 21262306a36Sopenharmony_ci ATTR_LIST(gpio0), 21362306a36Sopenharmony_ci ATTR_LIST(gpio1), 21462306a36Sopenharmony_ci ATTR_LIST(gpio2), 21562306a36Sopenharmony_ci ATTR_LIST(gpio3), 21662306a36Sopenharmony_ci ATTR_LIST(gpio4), 21762306a36Sopenharmony_ci ATTR_LIST(gpio5), 21862306a36Sopenharmony_ci ATTR_LIST(gpio6), 21962306a36Sopenharmony_ci ATTR_LIST(gpio7), 22062306a36Sopenharmony_ci ATTR_LIST(timer), 22162306a36Sopenharmony_ci ATTR_LIST(timer_timeout), 22262306a36Sopenharmony_ci ATTR_LIST(wakesrc), 22362306a36Sopenharmony_ci ATTR_LIST(wakemsk), 22462306a36Sopenharmony_ci NULL, /* terminator */ 22562306a36Sopenharmony_ci}; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic struct attribute_group db1x_pmattr_group = { 22862306a36Sopenharmony_ci .name = "db1x", 22962306a36Sopenharmony_ci .attrs = db1x_pmattrs, 23062306a36Sopenharmony_ci}; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci/* 23362306a36Sopenharmony_ci * Initialize suspend interface 23462306a36Sopenharmony_ci */ 23562306a36Sopenharmony_cistatic int __init pm_init(void) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci /* init TOY to tick at 1Hz if not already done. No need to wait 23862306a36Sopenharmony_ci * for confirmation since there's plenty of time from here to 23962306a36Sopenharmony_ci * the next suspend cycle. 24062306a36Sopenharmony_ci */ 24162306a36Sopenharmony_ci if (alchemy_rdsys(AU1000_SYS_TOYTRIM) != 32767) 24262306a36Sopenharmony_ci alchemy_wrsys(32767, AU1000_SYS_TOYTRIM); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci db1x_pm_last_wakesrc = alchemy_rdsys(AU1000_SYS_WAKESRC); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKESRC); 24762306a36Sopenharmony_ci alchemy_wrsys(0, AU1000_SYS_WAKEMSK); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci suspend_set_ops(&db1x_pm_ops); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci return sysfs_create_group(power_kobj, &db1x_pmattr_group); 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cilate_initcall(pm_init); 255