162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Support for the OLPC DCON and OLPC EC access
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright © 2006  Advanced Micro Devices, Inc.
662306a36Sopenharmony_ci * Copyright © 2007-2008  Andres Salomon <dilinger@debian.org>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/export.h>
1262306a36Sopenharmony_ci#include <linux/delay.h>
1362306a36Sopenharmony_ci#include <linux/io.h>
1462306a36Sopenharmony_ci#include <linux/string.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/of.h>
1762306a36Sopenharmony_ci#include <linux/syscore_ops.h>
1862306a36Sopenharmony_ci#include <linux/mutex.h>
1962306a36Sopenharmony_ci#include <linux/olpc-ec.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <asm/geode.h>
2262306a36Sopenharmony_ci#include <asm/setup.h>
2362306a36Sopenharmony_ci#include <asm/olpc.h>
2462306a36Sopenharmony_ci#include <asm/olpc_ofw.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct olpc_platform_t olpc_platform_info;
2762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(olpc_platform_info);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/* what the timeout *should* be (in ms) */
3062306a36Sopenharmony_ci#define EC_BASE_TIMEOUT 20
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/* the timeout that bugs in the EC might force us to actually use */
3362306a36Sopenharmony_cistatic int ec_timeout = EC_BASE_TIMEOUT;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic int __init olpc_ec_timeout_set(char *str)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	if (get_option(&str, &ec_timeout) != 1) {
3862306a36Sopenharmony_ci		ec_timeout = EC_BASE_TIMEOUT;
3962306a36Sopenharmony_ci		printk(KERN_ERR "olpc-ec:  invalid argument to "
4062306a36Sopenharmony_ci				"'olpc_ec_timeout=', ignoring!\n");
4162306a36Sopenharmony_ci	}
4262306a36Sopenharmony_ci	printk(KERN_DEBUG "olpc-ec:  using %d ms delay for EC commands.\n",
4362306a36Sopenharmony_ci			ec_timeout);
4462306a36Sopenharmony_ci	return 1;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci__setup("olpc_ec_timeout=", olpc_ec_timeout_set);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/*
4962306a36Sopenharmony_ci * These {i,o}bf_status functions return whether the buffers are full or not.
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic inline unsigned int ibf_status(unsigned int port)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	return !!(inb(port) & 0x02);
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic inline unsigned int obf_status(unsigned int port)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	return inb(port) & 0x01;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci#define wait_on_ibf(p, d) __wait_on_ibf(__LINE__, (p), (d))
6362306a36Sopenharmony_cistatic int __wait_on_ibf(unsigned int line, unsigned int port, int desired)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	unsigned int timeo;
6662306a36Sopenharmony_ci	int state = ibf_status(port);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	for (timeo = ec_timeout; state != desired && timeo; timeo--) {
6962306a36Sopenharmony_ci		mdelay(1);
7062306a36Sopenharmony_ci		state = ibf_status(port);
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) &&
7462306a36Sopenharmony_ci			timeo < (ec_timeout - EC_BASE_TIMEOUT)) {
7562306a36Sopenharmony_ci		printk(KERN_WARNING "olpc-ec:  %d: waited %u ms for IBF!\n",
7662306a36Sopenharmony_ci				line, ec_timeout - timeo);
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	return !(state == desired);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci#define wait_on_obf(p, d) __wait_on_obf(__LINE__, (p), (d))
8362306a36Sopenharmony_cistatic int __wait_on_obf(unsigned int line, unsigned int port, int desired)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	unsigned int timeo;
8662306a36Sopenharmony_ci	int state = obf_status(port);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	for (timeo = ec_timeout; state != desired && timeo; timeo--) {
8962306a36Sopenharmony_ci		mdelay(1);
9062306a36Sopenharmony_ci		state = obf_status(port);
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) &&
9462306a36Sopenharmony_ci			timeo < (ec_timeout - EC_BASE_TIMEOUT)) {
9562306a36Sopenharmony_ci		printk(KERN_WARNING "olpc-ec:  %d: waited %u ms for OBF!\n",
9662306a36Sopenharmony_ci				line, ec_timeout - timeo);
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	return !(state == desired);
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci/*
10362306a36Sopenharmony_ci * This allows the kernel to run Embedded Controller commands.  The EC is
10462306a36Sopenharmony_ci * documented at <http://wiki.laptop.org/go/Embedded_controller>, and the
10562306a36Sopenharmony_ci * available EC commands are here:
10662306a36Sopenharmony_ci * <http://wiki.laptop.org/go/Ec_specification>.  Unfortunately, while
10762306a36Sopenharmony_ci * OpenFirmware's source is available, the EC's is not.
10862306a36Sopenharmony_ci */
10962306a36Sopenharmony_cistatic int olpc_xo1_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf,
11062306a36Sopenharmony_ci		size_t outlen, void *arg)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	int ret = -EIO;
11362306a36Sopenharmony_ci	int i;
11462306a36Sopenharmony_ci	int restarts = 0;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	/* Clear OBF */
11762306a36Sopenharmony_ci	for (i = 0; i < 10 && (obf_status(0x6c) == 1); i++)
11862306a36Sopenharmony_ci		inb(0x68);
11962306a36Sopenharmony_ci	if (i == 10) {
12062306a36Sopenharmony_ci		printk(KERN_ERR "olpc-ec:  timeout while attempting to "
12162306a36Sopenharmony_ci				"clear OBF flag!\n");
12262306a36Sopenharmony_ci		goto err;
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	if (wait_on_ibf(0x6c, 0)) {
12662306a36Sopenharmony_ci		printk(KERN_ERR "olpc-ec:  timeout waiting for EC to "
12762306a36Sopenharmony_ci				"quiesce!\n");
12862306a36Sopenharmony_ci		goto err;
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cirestart:
13262306a36Sopenharmony_ci	/*
13362306a36Sopenharmony_ci	 * Note that if we time out during any IBF checks, that's a failure;
13462306a36Sopenharmony_ci	 * we have to return.  There's no way for the kernel to clear that.
13562306a36Sopenharmony_ci	 *
13662306a36Sopenharmony_ci	 * If we time out during an OBF check, we can restart the command;
13762306a36Sopenharmony_ci	 * reissuing it will clear the OBF flag, and we should be alright.
13862306a36Sopenharmony_ci	 * The OBF flag will sometimes misbehave due to what we believe
13962306a36Sopenharmony_ci	 * is a hardware quirk..
14062306a36Sopenharmony_ci	 */
14162306a36Sopenharmony_ci	pr_devel("olpc-ec:  running cmd 0x%x\n", cmd);
14262306a36Sopenharmony_ci	outb(cmd, 0x6c);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	if (wait_on_ibf(0x6c, 0)) {
14562306a36Sopenharmony_ci		printk(KERN_ERR "olpc-ec:  timeout waiting for EC to read "
14662306a36Sopenharmony_ci				"command!\n");
14762306a36Sopenharmony_ci		goto err;
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	if (inbuf && inlen) {
15162306a36Sopenharmony_ci		/* write data to EC */
15262306a36Sopenharmony_ci		for (i = 0; i < inlen; i++) {
15362306a36Sopenharmony_ci			pr_devel("olpc-ec:  sending cmd arg 0x%x\n", inbuf[i]);
15462306a36Sopenharmony_ci			outb(inbuf[i], 0x68);
15562306a36Sopenharmony_ci			if (wait_on_ibf(0x6c, 0)) {
15662306a36Sopenharmony_ci				printk(KERN_ERR "olpc-ec:  timeout waiting for"
15762306a36Sopenharmony_ci						" EC accept data!\n");
15862306a36Sopenharmony_ci				goto err;
15962306a36Sopenharmony_ci			}
16062306a36Sopenharmony_ci		}
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci	if (outbuf && outlen) {
16362306a36Sopenharmony_ci		/* read data from EC */
16462306a36Sopenharmony_ci		for (i = 0; i < outlen; i++) {
16562306a36Sopenharmony_ci			if (wait_on_obf(0x6c, 1)) {
16662306a36Sopenharmony_ci				printk(KERN_ERR "olpc-ec:  timeout waiting for"
16762306a36Sopenharmony_ci						" EC to provide data!\n");
16862306a36Sopenharmony_ci				if (restarts++ < 10)
16962306a36Sopenharmony_ci					goto restart;
17062306a36Sopenharmony_ci				goto err;
17162306a36Sopenharmony_ci			}
17262306a36Sopenharmony_ci			outbuf[i] = inb(0x68);
17362306a36Sopenharmony_ci			pr_devel("olpc-ec:  received 0x%x\n", outbuf[i]);
17462306a36Sopenharmony_ci		}
17562306a36Sopenharmony_ci	}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	ret = 0;
17862306a36Sopenharmony_cierr:
17962306a36Sopenharmony_ci	return ret;
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic bool __init check_ofw_architecture(struct device_node *root)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	const char *olpc_arch;
18562306a36Sopenharmony_ci	int propsize;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	olpc_arch = of_get_property(root, "architecture", &propsize);
18862306a36Sopenharmony_ci	return propsize == 5 && strncmp("OLPC", olpc_arch, 5) == 0;
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistatic u32 __init get_board_revision(struct device_node *root)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	int propsize;
19462306a36Sopenharmony_ci	const __be32 *rev;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	rev = of_get_property(root, "board-revision-int", &propsize);
19762306a36Sopenharmony_ci	if (propsize != 4)
19862306a36Sopenharmony_ci		return 0;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	return be32_to_cpu(*rev);
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic bool __init platform_detect(void)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	struct device_node *root = of_find_node_by_path("/");
20662306a36Sopenharmony_ci	bool success;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (!root)
20962306a36Sopenharmony_ci		return false;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	success = check_ofw_architecture(root);
21262306a36Sopenharmony_ci	if (success) {
21362306a36Sopenharmony_ci		olpc_platform_info.boardrev = get_board_revision(root);
21462306a36Sopenharmony_ci		olpc_platform_info.flags |= OLPC_F_PRESENT;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci		pr_info("OLPC board revision %s%X\n",
21762306a36Sopenharmony_ci			((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "",
21862306a36Sopenharmony_ci			olpc_platform_info.boardrev >> 4);
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	of_node_put(root);
22262306a36Sopenharmony_ci	return success;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic int __init add_xo1_platform_devices(void)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	struct platform_device *pdev;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	pdev = platform_device_register_simple("xo1-rfkill", -1, NULL, 0);
23062306a36Sopenharmony_ci	if (IS_ERR(pdev))
23162306a36Sopenharmony_ci		return PTR_ERR(pdev);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	pdev = platform_device_register_simple("olpc-xo1", -1, NULL, 0);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(pdev);
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic int olpc_xo1_ec_suspend(struct platform_device *pdev)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	/*
24162306a36Sopenharmony_ci	 * Squelch SCIs while suspended.  This is a fix for
24262306a36Sopenharmony_ci	 * <http://dev.laptop.org/ticket/1835>.
24362306a36Sopenharmony_ci	 */
24462306a36Sopenharmony_ci	return olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0);
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_cistatic int olpc_xo1_ec_resume(struct platform_device *pdev)
24862306a36Sopenharmony_ci{
24962306a36Sopenharmony_ci	/* Tell the EC to stop inhibiting SCIs */
25062306a36Sopenharmony_ci	olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, 0, NULL, 0);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	/*
25362306a36Sopenharmony_ci	 * Tell the wireless module to restart USB communication.
25462306a36Sopenharmony_ci	 * Must be done twice.
25562306a36Sopenharmony_ci	 */
25662306a36Sopenharmony_ci	olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
25762306a36Sopenharmony_ci	olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	return 0;
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_cistatic struct olpc_ec_driver ec_xo1_driver = {
26362306a36Sopenharmony_ci	.suspend = olpc_xo1_ec_suspend,
26462306a36Sopenharmony_ci	.resume = olpc_xo1_ec_resume,
26562306a36Sopenharmony_ci	.ec_cmd = olpc_xo1_ec_cmd,
26662306a36Sopenharmony_ci#ifdef CONFIG_OLPC_XO1_SCI
26762306a36Sopenharmony_ci	/*
26862306a36Sopenharmony_ci	 * XO-1 EC wakeups are available when olpc-xo1-sci driver is
26962306a36Sopenharmony_ci	 * compiled in
27062306a36Sopenharmony_ci	 */
27162306a36Sopenharmony_ci	.wakeup_available = true,
27262306a36Sopenharmony_ci#endif
27362306a36Sopenharmony_ci};
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_cistatic struct olpc_ec_driver ec_xo1_5_driver = {
27662306a36Sopenharmony_ci	.ec_cmd = olpc_xo1_ec_cmd,
27762306a36Sopenharmony_ci#ifdef CONFIG_OLPC_XO15_SCI
27862306a36Sopenharmony_ci	/*
27962306a36Sopenharmony_ci	 * XO-1.5 EC wakeups are available when olpc-xo15-sci driver is
28062306a36Sopenharmony_ci	 * compiled in
28162306a36Sopenharmony_ci	 */
28262306a36Sopenharmony_ci	.wakeup_available = true,
28362306a36Sopenharmony_ci#endif
28462306a36Sopenharmony_ci};
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_cistatic int __init olpc_init(void)
28762306a36Sopenharmony_ci{
28862306a36Sopenharmony_ci	int r = 0;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	if (!olpc_ofw_present() || !platform_detect())
29162306a36Sopenharmony_ci		return 0;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	/* register the XO-1 and 1.5-specific EC handler */
29462306a36Sopenharmony_ci	if (olpc_platform_info.boardrev < olpc_board_pre(0xd0))	/* XO-1 */
29562306a36Sopenharmony_ci		olpc_ec_driver_register(&ec_xo1_driver, NULL);
29662306a36Sopenharmony_ci	else
29762306a36Sopenharmony_ci		olpc_ec_driver_register(&ec_xo1_5_driver, NULL);
29862306a36Sopenharmony_ci	platform_device_register_simple("olpc-ec", -1, NULL, 0);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	/* assume B1 and above models always have a DCON */
30162306a36Sopenharmony_ci	if (olpc_board_at_least(olpc_board(0xb1)))
30262306a36Sopenharmony_ci		olpc_platform_info.flags |= OLPC_F_DCON;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci#ifdef CONFIG_PCI_OLPC
30562306a36Sopenharmony_ci	/* If the VSA exists let it emulate PCI, if not emulate in kernel.
30662306a36Sopenharmony_ci	 * XO-1 only. */
30762306a36Sopenharmony_ci	if (olpc_platform_info.boardrev < olpc_board_pre(0xd0) &&
30862306a36Sopenharmony_ci			!cs5535_has_vsa2())
30962306a36Sopenharmony_ci		x86_init.pci.arch_init = pci_olpc_init;
31062306a36Sopenharmony_ci#endif
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) { /* XO-1 */
31362306a36Sopenharmony_ci		r = add_xo1_platform_devices();
31462306a36Sopenharmony_ci		if (r)
31562306a36Sopenharmony_ci			return r;
31662306a36Sopenharmony_ci	}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	return 0;
31962306a36Sopenharmony_ci}
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_cipostcore_initcall(olpc_init);
322