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