|  | /* | 
|  | * Support for the OLPC DCON and OLPC EC access | 
|  | * | 
|  | * Copyright © 2006  Advanced Micro Devices, Inc. | 
|  | * Copyright © 2007-2008  Andres Salomon <dilinger@debian.org> | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of the GNU General Public License as published by | 
|  | * the Free Software Foundation; either version 2 of the License, or | 
|  | * (at your option) any later version. | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/string.h> | 
|  |  | 
|  | #include <asm/geode.h> | 
|  | #include <asm/setup.h> | 
|  | #include <asm/olpc.h> | 
|  |  | 
|  | #ifdef CONFIG_OPEN_FIRMWARE | 
|  | #include <asm/ofw.h> | 
|  | #endif | 
|  |  | 
|  | struct olpc_platform_t olpc_platform_info; | 
|  | EXPORT_SYMBOL_GPL(olpc_platform_info); | 
|  |  | 
|  | static DEFINE_SPINLOCK(ec_lock); | 
|  |  | 
|  | /* what the timeout *should* be (in ms) */ | 
|  | #define EC_BASE_TIMEOUT 20 | 
|  |  | 
|  | /* the timeout that bugs in the EC might force us to actually use */ | 
|  | static int ec_timeout = EC_BASE_TIMEOUT; | 
|  |  | 
|  | static int __init olpc_ec_timeout_set(char *str) | 
|  | { | 
|  | if (get_option(&str, &ec_timeout) != 1) { | 
|  | ec_timeout = EC_BASE_TIMEOUT; | 
|  | printk(KERN_ERR "olpc-ec:  invalid argument to " | 
|  | "'olpc_ec_timeout=', ignoring!\n"); | 
|  | } | 
|  | printk(KERN_DEBUG "olpc-ec:  using %d ms delay for EC commands.\n", | 
|  | ec_timeout); | 
|  | return 1; | 
|  | } | 
|  | __setup("olpc_ec_timeout=", olpc_ec_timeout_set); | 
|  |  | 
|  | /* | 
|  | * These {i,o}bf_status functions return whether the buffers are full or not. | 
|  | */ | 
|  |  | 
|  | static inline unsigned int ibf_status(unsigned int port) | 
|  | { | 
|  | return !!(inb(port) & 0x02); | 
|  | } | 
|  |  | 
|  | static inline unsigned int obf_status(unsigned int port) | 
|  | { | 
|  | return inb(port) & 0x01; | 
|  | } | 
|  |  | 
|  | #define wait_on_ibf(p, d) __wait_on_ibf(__LINE__, (p), (d)) | 
|  | static int __wait_on_ibf(unsigned int line, unsigned int port, int desired) | 
|  | { | 
|  | unsigned int timeo; | 
|  | int state = ibf_status(port); | 
|  |  | 
|  | for (timeo = ec_timeout; state != desired && timeo; timeo--) { | 
|  | mdelay(1); | 
|  | state = ibf_status(port); | 
|  | } | 
|  |  | 
|  | if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) && | 
|  | timeo < (ec_timeout - EC_BASE_TIMEOUT)) { | 
|  | printk(KERN_WARNING "olpc-ec:  %d: waited %u ms for IBF!\n", | 
|  | line, ec_timeout - timeo); | 
|  | } | 
|  |  | 
|  | return !(state == desired); | 
|  | } | 
|  |  | 
|  | #define wait_on_obf(p, d) __wait_on_obf(__LINE__, (p), (d)) | 
|  | static int __wait_on_obf(unsigned int line, unsigned int port, int desired) | 
|  | { | 
|  | unsigned int timeo; | 
|  | int state = obf_status(port); | 
|  |  | 
|  | for (timeo = ec_timeout; state != desired && timeo; timeo--) { | 
|  | mdelay(1); | 
|  | state = obf_status(port); | 
|  | } | 
|  |  | 
|  | if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) && | 
|  | timeo < (ec_timeout - EC_BASE_TIMEOUT)) { | 
|  | printk(KERN_WARNING "olpc-ec:  %d: waited %u ms for OBF!\n", | 
|  | line, ec_timeout - timeo); | 
|  | } | 
|  |  | 
|  | return !(state == desired); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * This allows the kernel to run Embedded Controller commands.  The EC is | 
|  | * documented at <http://wiki.laptop.org/go/Embedded_controller>, and the | 
|  | * available EC commands are here: | 
|  | * <http://wiki.laptop.org/go/Ec_specification>.  Unfortunately, while | 
|  | * OpenFirmware's source is available, the EC's is not. | 
|  | */ | 
|  | int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen, | 
|  | unsigned char *outbuf,  size_t outlen) | 
|  | { | 
|  | unsigned long flags; | 
|  | int ret = -EIO; | 
|  | int i; | 
|  |  | 
|  | spin_lock_irqsave(&ec_lock, flags); | 
|  |  | 
|  | /* Clear OBF */ | 
|  | for (i = 0; i < 10 && (obf_status(0x6c) == 1); i++) | 
|  | inb(0x68); | 
|  | if (i == 10) { | 
|  | printk(KERN_ERR "olpc-ec:  timeout while attempting to " | 
|  | "clear OBF flag!\n"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (wait_on_ibf(0x6c, 0)) { | 
|  | printk(KERN_ERR "olpc-ec:  timeout waiting for EC to " | 
|  | "quiesce!\n"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | restart: | 
|  | /* | 
|  | * Note that if we time out during any IBF checks, that's a failure; | 
|  | * we have to return.  There's no way for the kernel to clear that. | 
|  | * | 
|  | * If we time out during an OBF check, we can restart the command; | 
|  | * reissuing it will clear the OBF flag, and we should be alright. | 
|  | * The OBF flag will sometimes misbehave due to what we believe | 
|  | * is a hardware quirk.. | 
|  | */ | 
|  | printk(KERN_DEBUG "olpc-ec:  running cmd 0x%x\n", cmd); | 
|  | outb(cmd, 0x6c); | 
|  |  | 
|  | if (wait_on_ibf(0x6c, 0)) { | 
|  | printk(KERN_ERR "olpc-ec:  timeout waiting for EC to read " | 
|  | "command!\n"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (inbuf && inlen) { | 
|  | /* write data to EC */ | 
|  | for (i = 0; i < inlen; i++) { | 
|  | if (wait_on_ibf(0x6c, 0)) { | 
|  | printk(KERN_ERR "olpc-ec:  timeout waiting for" | 
|  | " EC accept data!\n"); | 
|  | goto err; | 
|  | } | 
|  | printk(KERN_DEBUG "olpc-ec:  sending cmd arg 0x%x\n", | 
|  | inbuf[i]); | 
|  | outb(inbuf[i], 0x68); | 
|  | } | 
|  | } | 
|  | if (outbuf && outlen) { | 
|  | /* read data from EC */ | 
|  | for (i = 0; i < outlen; i++) { | 
|  | if (wait_on_obf(0x6c, 1)) { | 
|  | printk(KERN_ERR "olpc-ec:  timeout waiting for" | 
|  | " EC to provide data!\n"); | 
|  | goto restart; | 
|  | } | 
|  | outbuf[i] = inb(0x68); | 
|  | printk(KERN_DEBUG "olpc-ec:  received 0x%x\n", | 
|  | outbuf[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | ret = 0; | 
|  | err: | 
|  | spin_unlock_irqrestore(&ec_lock, flags); | 
|  | return ret; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(olpc_ec_cmd); | 
|  |  | 
|  | #ifdef CONFIG_OPEN_FIRMWARE | 
|  | static void __init platform_detect(void) | 
|  | { | 
|  | size_t propsize; | 
|  | __be32 rev; | 
|  |  | 
|  | if (ofw("getprop", 4, 1, NULL, "board-revision-int", &rev, 4, | 
|  | &propsize) || propsize != 4) { | 
|  | printk(KERN_ERR "ofw: getprop call failed!\n"); | 
|  | rev = cpu_to_be32(0); | 
|  | } | 
|  | olpc_platform_info.boardrev = be32_to_cpu(rev); | 
|  | } | 
|  | #else | 
|  | static void __init platform_detect(void) | 
|  | { | 
|  | /* stopgap until OFW support is added to the kernel */ | 
|  | olpc_platform_info.boardrev = olpc_board(0xc2); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static int __init olpc_init(void) | 
|  | { | 
|  | unsigned char *romsig; | 
|  |  | 
|  | /* The ioremap check is dangerous; limit what we run it on */ | 
|  | if (!is_geode() || cs5535_has_vsa2()) | 
|  | return 0; | 
|  |  | 
|  | spin_lock_init(&ec_lock); | 
|  |  | 
|  | romsig = ioremap(0xffffffc0, 16); | 
|  | if (!romsig) | 
|  | return 0; | 
|  |  | 
|  | if (strncmp(romsig, "CL1   Q", 7)) | 
|  | goto unmap; | 
|  | if (strncmp(romsig+6, romsig+13, 3)) { | 
|  | printk(KERN_INFO "OLPC BIOS signature looks invalid.  " | 
|  | "Assuming not OLPC\n"); | 
|  | goto unmap; | 
|  | } | 
|  |  | 
|  | printk(KERN_INFO "OLPC board with OpenFirmware %.16s\n", romsig); | 
|  | olpc_platform_info.flags |= OLPC_F_PRESENT; | 
|  |  | 
|  | /* get the platform revision */ | 
|  | platform_detect(); | 
|  |  | 
|  | /* assume B1 and above models always have a DCON */ | 
|  | if (olpc_board_at_least(olpc_board(0xb1))) | 
|  | olpc_platform_info.flags |= OLPC_F_DCON; | 
|  |  | 
|  | /* get the EC revision */ | 
|  | olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, | 
|  | (unsigned char *) &olpc_platform_info.ecver, 1); | 
|  |  | 
|  | #ifdef CONFIG_PCI_OLPC | 
|  | /* If the VSA exists let it emulate PCI, if not emulate in kernel */ | 
|  | if (!cs5535_has_vsa2()) | 
|  | x86_init.pci.arch_init = pci_olpc_init; | 
|  | #endif | 
|  |  | 
|  | printk(KERN_INFO "OLPC board revision %s%X (EC=%x)\n", | 
|  | ((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", | 
|  | olpc_platform_info.boardrev >> 4, | 
|  | olpc_platform_info.ecver); | 
|  |  | 
|  | unmap: | 
|  | iounmap(romsig); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | postcore_initcall(olpc_init); |