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