summaryrefslogtreecommitdiffstats
path: root/drivers/platform/olpc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/platform/olpc/Kconfig29
-rw-r--r--drivers/platform/olpc/Makefile6
-rw-r--r--drivers/platform/olpc/olpc-ec.c495
-rw-r--r--drivers/platform/olpc/olpc-xo175-ec.c757
4 files changed, 1287 insertions, 0 deletions
diff --git a/drivers/platform/olpc/Kconfig b/drivers/platform/olpc/Kconfig
new file mode 100644
index 000000000..919b489e2
--- /dev/null
+++ b/drivers/platform/olpc/Kconfig
@@ -0,0 +1,29 @@
+config OLPC_EC
+ select REGULATOR
+ bool
+
+menuconfig OLPC_XO175
+ bool "Platform support for OLPC XO 1.75 hardware"
+ depends on ARCH_MMP || COMPILE_TEST
+ help
+ Say Y here to get to see options for the ARM-based OLPC platform.
+ This option alone does not add any kernel code.
+
+ Unless you have an OLPC XO laptop, you will want to say N.
+
+if OLPC_XO175
+
+config OLPC_XO175_EC
+ tristate "OLPC XO 1.75 Embedded Controller"
+ depends on SPI_SLAVE
+ depends on INPUT
+ depends on POWER_SUPPLY
+ select OLPC_EC
+ help
+ Include support for the OLPC XO Embedded Controller (EC). The EC
+ provides various platform services, including support for the power,
+ button, restart, shutdown and battery charging status.
+
+ Unless you have an OLPC XO laptop, you will want to say N.
+
+endif # OLPC_XO175
diff --git a/drivers/platform/olpc/Makefile b/drivers/platform/olpc/Makefile
new file mode 100644
index 000000000..e9b67000c
--- /dev/null
+++ b/drivers/platform/olpc/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# OLPC XO platform-specific drivers
+#
+obj-$(CONFIG_OLPC_EC) += olpc-ec.o
+obj-$(CONFIG_OLPC_XO175_EC) += olpc-xo175-ec.o
diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c
new file mode 100644
index 000000000..921520475
--- /dev/null
+++ b/drivers/platform/olpc/olpc-ec.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Generic driver for the OLPC Embedded Controller.
+ *
+ * Author: Andres Salomon <dilinger@queued.net>
+ *
+ * Copyright (C) 2011-2012 One Laptop per Child Foundation.
+ */
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/regulator/driver.h>
+#include <linux/olpc-ec.h>
+
+struct ec_cmd_desc {
+ u8 cmd;
+ u8 *inbuf, *outbuf;
+ size_t inlen, outlen;
+
+ int err;
+ struct completion finished;
+ struct list_head node;
+
+ void *priv;
+};
+
+struct olpc_ec_priv {
+ struct olpc_ec_driver *drv;
+ u8 version;
+ struct work_struct worker;
+ struct mutex cmd_lock;
+
+ /* DCON regulator */
+ bool dcon_enabled;
+
+ /* Pending EC commands */
+ struct list_head cmd_q;
+ spinlock_t cmd_q_lock;
+
+ struct dentry *dbgfs_dir;
+
+ /*
+ * EC event mask to be applied during suspend (defining wakeup
+ * sources).
+ */
+ u16 ec_wakeup_mask;
+
+ /*
+ * Running an EC command while suspending means we don't always finish
+ * the command before the machine suspends. This means that the EC
+ * is expecting the command protocol to finish, but we after a period
+ * of time (while the OS is asleep) the EC times out and restarts its
+ * idle loop. Meanwhile, the OS wakes up, thinks it's still in the
+ * middle of the command protocol, starts throwing random things at
+ * the EC... and everyone's uphappy.
+ */
+ bool suspended;
+};
+
+static struct olpc_ec_driver *ec_driver;
+static struct olpc_ec_priv *ec_priv;
+static void *ec_cb_arg;
+
+void olpc_ec_driver_register(struct olpc_ec_driver *drv, void *arg)
+{
+ ec_driver = drv;
+ ec_cb_arg = arg;
+}
+EXPORT_SYMBOL_GPL(olpc_ec_driver_register);
+
+static void olpc_ec_worker(struct work_struct *w)
+{
+ struct olpc_ec_priv *ec = container_of(w, struct olpc_ec_priv, worker);
+ struct ec_cmd_desc *desc = NULL;
+ unsigned long flags;
+
+ /* Grab the first pending command from the queue */
+ spin_lock_irqsave(&ec->cmd_q_lock, flags);
+ if (!list_empty(&ec->cmd_q)) {
+ desc = list_first_entry(&ec->cmd_q, struct ec_cmd_desc, node);
+ list_del(&desc->node);
+ }
+ spin_unlock_irqrestore(&ec->cmd_q_lock, flags);
+
+ /* Do we actually have anything to do? */
+ if (!desc)
+ return;
+
+ /* Protect the EC hw with a mutex; only run one cmd at a time */
+ mutex_lock(&ec->cmd_lock);
+ desc->err = ec_driver->ec_cmd(desc->cmd, desc->inbuf, desc->inlen,
+ desc->outbuf, desc->outlen, ec_cb_arg);
+ mutex_unlock(&ec->cmd_lock);
+
+ /* Finished, wake up olpc_ec_cmd() */
+ complete(&desc->finished);
+
+ /* Run the worker thread again in case there are more cmds pending */
+ schedule_work(&ec->worker);
+}
+
+/*
+ * Throw a cmd descripter onto the list. We now have SMP OLPC machines, so
+ * locking is pretty critical.
+ */
+static void queue_ec_descriptor(struct ec_cmd_desc *desc,
+ struct olpc_ec_priv *ec)
+{
+ unsigned long flags;
+
+ INIT_LIST_HEAD(&desc->node);
+
+ spin_lock_irqsave(&ec->cmd_q_lock, flags);
+ list_add_tail(&desc->node, &ec->cmd_q);
+ spin_unlock_irqrestore(&ec->cmd_q_lock, flags);
+
+ schedule_work(&ec->worker);
+}
+
+int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
+{
+ struct olpc_ec_priv *ec = ec_priv;
+ struct ec_cmd_desc desc;
+
+ /* Driver not yet registered. */
+ if (!ec_driver)
+ return -EPROBE_DEFER;
+
+ if (WARN_ON(!ec_driver->ec_cmd))
+ return -ENODEV;
+
+ if (!ec)
+ return -ENOMEM;
+
+ /* Suspending in the middle of a command hoses things really badly */
+ if (WARN_ON(ec->suspended))
+ return -EBUSY;
+
+ might_sleep();
+
+ desc.cmd = cmd;
+ desc.inbuf = inbuf;
+ desc.outbuf = outbuf;
+ desc.inlen = inlen;
+ desc.outlen = outlen;
+ desc.err = 0;
+ init_completion(&desc.finished);
+
+ queue_ec_descriptor(&desc, ec);
+
+ /* Timeouts must be handled in the platform-specific EC hook */
+ wait_for_completion(&desc.finished);
+
+ /* The worker thread dequeues the cmd; no need to do anything here */
+ return desc.err;
+}
+EXPORT_SYMBOL_GPL(olpc_ec_cmd);
+
+void olpc_ec_wakeup_set(u16 value)
+{
+ struct olpc_ec_priv *ec = ec_priv;
+
+ if (WARN_ON(!ec))
+ return;
+
+ ec->ec_wakeup_mask |= value;
+}
+EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set);
+
+void olpc_ec_wakeup_clear(u16 value)
+{
+ struct olpc_ec_priv *ec = ec_priv;
+
+ if (WARN_ON(!ec))
+ return;
+
+ ec->ec_wakeup_mask &= ~value;
+}
+EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear);
+
+int olpc_ec_mask_write(u16 bits)
+{
+ struct olpc_ec_priv *ec = ec_priv;
+
+ if (WARN_ON(!ec))
+ return -ENODEV;
+
+ /* EC version 0x5f adds support for wide SCI mask */
+ if (ec->version >= 0x5f) {
+ __be16 ec_word = cpu_to_be16(bits);
+
+ return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *)&ec_word, 2, NULL, 0);
+ } else {
+ u8 ec_byte = bits & 0xff;
+
+ return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0);
+ }
+}
+EXPORT_SYMBOL_GPL(olpc_ec_mask_write);
+
+/*
+ * Returns true if the compile and runtime configurations allow for EC events
+ * to wake the system.
+ */
+bool olpc_ec_wakeup_available(void)
+{
+ if (WARN_ON(!ec_driver))
+ return false;
+
+ return ec_driver->wakeup_available;
+}
+EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available);
+
+int olpc_ec_sci_query(u16 *sci_value)
+{
+ struct olpc_ec_priv *ec = ec_priv;
+ int ret;
+
+ if (WARN_ON(!ec))
+ return -ENODEV;
+
+ /* EC version 0x5f adds support for wide SCI mask */
+ if (ec->version >= 0x5f) {
+ __be16 ec_word;
+
+ ret = olpc_ec_cmd(EC_EXT_SCI_QUERY, NULL, 0, (void *)&ec_word, 2);
+ if (ret == 0)
+ *sci_value = be16_to_cpu(ec_word);
+ } else {
+ u8 ec_byte;
+
+ ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1);
+ if (ret == 0)
+ *sci_value = ec_byte;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(olpc_ec_sci_query);
+
+#ifdef CONFIG_DEBUG_FS
+
+/*
+ * debugfs support for "generic commands", to allow sending
+ * arbitrary EC commands from userspace.
+ */
+
+#define EC_MAX_CMD_ARGS (5 + 1) /* cmd byte + 5 args */
+#define EC_MAX_CMD_REPLY (8)
+
+static DEFINE_MUTEX(ec_dbgfs_lock);
+static unsigned char ec_dbgfs_resp[EC_MAX_CMD_REPLY];
+static unsigned int ec_dbgfs_resp_bytes;
+
+static ssize_t ec_dbgfs_cmd_write(struct file *file, const char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ int i, m;
+ unsigned char ec_cmd[EC_MAX_CMD_ARGS];
+ unsigned int ec_cmd_int[EC_MAX_CMD_ARGS];
+ char cmdbuf[64] = "";
+ int ec_cmd_bytes;
+
+ mutex_lock(&ec_dbgfs_lock);
+
+ size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf), ppos, buf, size);
+
+ m = sscanf(cmdbuf, "%x:%u %x %x %x %x %x", &ec_cmd_int[0],
+ &ec_dbgfs_resp_bytes, &ec_cmd_int[1], &ec_cmd_int[2],
+ &ec_cmd_int[3], &ec_cmd_int[4], &ec_cmd_int[5]);
+ if (m < 2 || ec_dbgfs_resp_bytes > EC_MAX_CMD_REPLY) {
+ /* reset to prevent overflow on read */
+ ec_dbgfs_resp_bytes = 0;
+
+ pr_debug("olpc-ec: bad ec cmd: cmd:response-count [arg1 [arg2 ...]]\n");
+ size = -EINVAL;
+ goto out;
+ }
+
+ /* convert scanf'd ints to char */
+ ec_cmd_bytes = m - 2;
+ for (i = 0; i <= ec_cmd_bytes; i++)
+ ec_cmd[i] = ec_cmd_int[i];
+
+ pr_debug("olpc-ec: debugfs cmd 0x%02x with %d args %5ph, want %d returns\n",
+ ec_cmd[0], ec_cmd_bytes, ec_cmd + 1,
+ ec_dbgfs_resp_bytes);
+
+ olpc_ec_cmd(ec_cmd[0], (ec_cmd_bytes == 0) ? NULL : &ec_cmd[1],
+ ec_cmd_bytes, ec_dbgfs_resp, ec_dbgfs_resp_bytes);
+
+ pr_debug("olpc-ec: response %8ph (%d bytes expected)\n",
+ ec_dbgfs_resp, ec_dbgfs_resp_bytes);
+
+out:
+ mutex_unlock(&ec_dbgfs_lock);
+ return size;
+}
+
+static ssize_t ec_dbgfs_cmd_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ unsigned int i, r;
+ char *rp;
+ char respbuf[64];
+
+ mutex_lock(&ec_dbgfs_lock);
+ rp = respbuf;
+ rp += sprintf(rp, "%02x", ec_dbgfs_resp[0]);
+ for (i = 1; i < ec_dbgfs_resp_bytes; i++)
+ rp += sprintf(rp, ", %02x", ec_dbgfs_resp[i]);
+ mutex_unlock(&ec_dbgfs_lock);
+ rp += sprintf(rp, "\n");
+
+ r = rp - respbuf;
+ return simple_read_from_buffer(buf, size, ppos, respbuf, r);
+}
+
+static const struct file_operations ec_dbgfs_ops = {
+ .write = ec_dbgfs_cmd_write,
+ .read = ec_dbgfs_cmd_read,
+};
+
+static struct dentry *olpc_ec_setup_debugfs(void)
+{
+ struct dentry *dbgfs_dir;
+
+ dbgfs_dir = debugfs_create_dir("olpc-ec", NULL);
+ if (IS_ERR_OR_NULL(dbgfs_dir))
+ return NULL;
+
+ debugfs_create_file("cmd", 0600, dbgfs_dir, NULL, &ec_dbgfs_ops);
+
+ return dbgfs_dir;
+}
+
+#else
+
+static struct dentry *olpc_ec_setup_debugfs(void)
+{
+ return NULL;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+static int olpc_ec_set_dcon_power(struct olpc_ec_priv *ec, bool state)
+{
+ unsigned char ec_byte = state;
+ int ret;
+
+ if (ec->dcon_enabled == state)
+ return 0;
+
+ ret = olpc_ec_cmd(EC_DCON_POWER_MODE, &ec_byte, 1, NULL, 0);
+ if (ret)
+ return ret;
+
+ ec->dcon_enabled = state;
+ return 0;
+}
+
+static int dcon_regulator_enable(struct regulator_dev *rdev)
+{
+ struct olpc_ec_priv *ec = rdev_get_drvdata(rdev);
+
+ return olpc_ec_set_dcon_power(ec, true);
+}
+
+static int dcon_regulator_disable(struct regulator_dev *rdev)
+{
+ struct olpc_ec_priv *ec = rdev_get_drvdata(rdev);
+
+ return olpc_ec_set_dcon_power(ec, false);
+}
+
+static int dcon_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct olpc_ec_priv *ec = rdev_get_drvdata(rdev);
+
+ return ec->dcon_enabled ? 1 : 0;
+}
+
+static const struct regulator_ops dcon_regulator_ops = {
+ .enable = dcon_regulator_enable,
+ .disable = dcon_regulator_disable,
+ .is_enabled = dcon_regulator_is_enabled,
+};
+
+static const struct regulator_desc dcon_desc = {
+ .name = "dcon",
+ .id = 0,
+ .ops = &dcon_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .enable_time = 25000,
+};
+
+static int olpc_ec_probe(struct platform_device *pdev)
+{
+ struct olpc_ec_priv *ec;
+ struct regulator_config config = { };
+ struct regulator_dev *regulator;
+ int err;
+
+ if (!ec_driver)
+ return -ENODEV;
+
+ ec = kzalloc(sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ ec->drv = ec_driver;
+ INIT_WORK(&ec->worker, olpc_ec_worker);
+ mutex_init(&ec->cmd_lock);
+
+ INIT_LIST_HEAD(&ec->cmd_q);
+ spin_lock_init(&ec->cmd_q_lock);
+
+ ec_priv = ec;
+ platform_set_drvdata(pdev, ec);
+
+ /* get the EC revision */
+ err = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ec->version, 1);
+ if (err)
+ goto error;
+
+ config.dev = pdev->dev.parent;
+ config.driver_data = ec;
+ ec->dcon_enabled = true;
+ regulator = devm_regulator_register(&pdev->dev, &dcon_desc, &config);
+ if (IS_ERR(regulator)) {
+ dev_err(&pdev->dev, "failed to register DCON regulator\n");
+ err = PTR_ERR(regulator);
+ goto error;
+ }
+
+ ec->dbgfs_dir = olpc_ec_setup_debugfs();
+
+ return 0;
+
+error:
+ ec_priv = NULL;
+ kfree(ec);
+ return err;
+}
+
+static int olpc_ec_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct olpc_ec_priv *ec = platform_get_drvdata(pdev);
+ int err = 0;
+
+ olpc_ec_mask_write(ec->ec_wakeup_mask);
+
+ if (ec_driver->suspend)
+ err = ec_driver->suspend(pdev);
+ if (!err)
+ ec->suspended = true;
+
+ return err;
+}
+
+static int olpc_ec_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct olpc_ec_priv *ec = platform_get_drvdata(pdev);
+
+ ec->suspended = false;
+ return ec_driver->resume ? ec_driver->resume(pdev) : 0;
+}
+
+static const struct dev_pm_ops olpc_ec_pm_ops = {
+ .suspend_late = olpc_ec_suspend,
+ .resume_early = olpc_ec_resume,
+};
+
+static struct platform_driver olpc_ec_plat_driver = {
+ .probe = olpc_ec_probe,
+ .driver = {
+ .name = "olpc-ec",
+ .pm = &olpc_ec_pm_ops,
+ },
+};
+
+static int __init olpc_ec_init_module(void)
+{
+ return platform_driver_register(&olpc_ec_plat_driver);
+}
+arch_initcall(olpc_ec_init_module);
diff --git a/drivers/platform/olpc/olpc-xo175-ec.c b/drivers/platform/olpc/olpc-xo175-ec.c
new file mode 100644
index 000000000..4823bd281
--- /dev/null
+++ b/drivers/platform/olpc/olpc-xo175-ec.c
@@ -0,0 +1,757 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the OLPC XO-1.75 Embedded Controller.
+ *
+ * The EC protocol is documented at:
+ * http://wiki.laptop.org/go/XO_1.75_HOST_to_EC_Protocol
+ *
+ * Copyright (C) 2010 One Laptop per Child Foundation.
+ * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk>
+ */
+
+#include <linux/completion.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/kfifo.h>
+#include <linux/module.h>
+#include <linux/olpc-ec.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/spi/spi.h>
+
+struct ec_cmd_t {
+ u8 cmd;
+ u8 bytes_returned;
+};
+
+enum ec_chan_t {
+ CHAN_NONE = 0,
+ CHAN_SWITCH,
+ CHAN_CMD_RESP,
+ CHAN_KEYBOARD,
+ CHAN_TOUCHPAD,
+ CHAN_EVENT,
+ CHAN_DEBUG,
+ CHAN_CMD_ERROR,
+};
+
+/*
+ * EC events
+ */
+#define EVENT_AC_CHANGE 1 /* AC plugged/unplugged */
+#define EVENT_BATTERY_STATUS 2 /* Battery low/full/error/gone */
+#define EVENT_BATTERY_CRITICAL 3 /* Battery critical voltage */
+#define EVENT_BATTERY_SOC_CHANGE 4 /* 1% SOC Change */
+#define EVENT_BATTERY_ERROR 5 /* Abnormal error, query for cause */
+#define EVENT_POWER_PRESSED 6 /* Power button was pressed */
+#define EVENT_POWER_PRESS_WAKE 7 /* Woken up with a power button */
+#define EVENT_TIMED_HOST_WAKE 8 /* Host wake timer */
+#define EVENT_OLS_HIGH_LIMIT 9 /* OLS crossed dark threshold */
+#define EVENT_OLS_LOW_LIMIT 10 /* OLS crossed light threshold */
+
+/*
+ * EC commands
+ * (from http://dev.laptop.org/git/users/rsmith/ec-1.75/tree/ec_cmd.h)
+ */
+#define CMD_GET_API_VERSION 0x08 /* out: u8 */
+#define CMD_READ_VOLTAGE 0x10 /* out: u16, *9.76/32, mV */
+#define CMD_READ_CURRENT 0x11 /* out: s16, *15.625/120, mA */
+#define CMD_READ_ACR 0x12 /* out: s16, *6250/15, uAh */
+#define CMD_READ_BATT_TEMPERATURE 0x13 /* out: u16, *100/256, deg C */
+#define CMD_READ_AMBIENT_TEMPERATURE 0x14 /* unimplemented, no hardware */
+#define CMD_READ_BATTERY_STATUS 0x15 /* out: u8, bitmask */
+#define CMD_READ_SOC 0x16 /* out: u8, percentage */
+#define CMD_READ_GAUGE_ID 0x17 /* out: u8 * 8 */
+#define CMD_READ_GAUGE_DATA 0x18 /* in: u8 addr, out: u8 data */
+#define CMD_READ_BOARD_ID 0x19 /* out: u16 (platform id) */
+#define CMD_READ_BATT_ERR_CODE 0x1f /* out: u8, error bitmask */
+#define CMD_SET_DCON_POWER 0x26 /* in: u8 */
+#define CMD_RESET_EC 0x28 /* none */
+#define CMD_READ_BATTERY_TYPE 0x2c /* out: u8 */
+#define CMD_SET_AUTOWAK 0x33 /* out: u8 */
+#define CMD_SET_EC_WAKEUP_TIMER 0x36 /* in: u32, out: ? */
+#define CMD_READ_EXT_SCI_MASK 0x37 /* ? */
+#define CMD_WRITE_EXT_SCI_MASK 0x38 /* ? */
+#define CMD_CLEAR_EC_WAKEUP_TIMER 0x39 /* none */
+#define CMD_ENABLE_RUNIN_DISCHARGE 0x3B /* none */
+#define CMD_DISABLE_RUNIN_DISCHARGE 0x3C /* none */
+#define CMD_READ_MPPT_ACTIVE 0x3d /* out: u8 */
+#define CMD_READ_MPPT_LIMIT 0x3e /* out: u8 */
+#define CMD_SET_MPPT_LIMIT 0x3f /* in: u8 */
+#define CMD_DISABLE_MPPT 0x40 /* none */
+#define CMD_ENABLE_MPPT 0x41 /* none */
+#define CMD_READ_VIN 0x42 /* out: u16 */
+#define CMD_EXT_SCI_QUERY 0x43 /* ? */
+#define RSP_KEYBOARD_DATA 0x48 /* ? */
+#define RSP_TOUCHPAD_DATA 0x49 /* ? */
+#define CMD_GET_FW_VERSION 0x4a /* out: u8 * 16 */
+#define CMD_POWER_CYCLE 0x4b /* none */
+#define CMD_POWER_OFF 0x4c /* none */
+#define CMD_RESET_EC_SOFT 0x4d /* none */
+#define CMD_READ_GAUGE_U16 0x4e /* ? */
+#define CMD_ENABLE_MOUSE 0x4f /* ? */
+#define CMD_ECHO 0x52 /* in: u8 * 5, out: u8 * 5 */
+#define CMD_GET_FW_DATE 0x53 /* out: u8 * 16 */
+#define CMD_GET_FW_USER 0x54 /* out: u8 * 16 */
+#define CMD_TURN_OFF_POWER 0x55 /* none (same as 0x4c) */
+#define CMD_READ_OLS 0x56 /* out: u16 */
+#define CMD_OLS_SMT_LEDON 0x57 /* none */
+#define CMD_OLS_SMT_LEDOFF 0x58 /* none */
+#define CMD_START_OLS_ASSY 0x59 /* none */
+#define CMD_STOP_OLS_ASSY 0x5a /* none */
+#define CMD_OLS_SMTTEST_STOP 0x5b /* none */
+#define CMD_READ_VIN_SCALED 0x5c /* out: u16 */
+#define CMD_READ_BAT_MIN_W 0x5d /* out: u16 */
+#define CMD_READ_BAR_MAX_W 0x5e /* out: u16 */
+#define CMD_RESET_BAT_MINMAX_W 0x5f /* none */
+#define CMD_READ_LOCATION 0x60 /* in: u16 addr, out: u8 data */
+#define CMD_WRITE_LOCATION 0x61 /* in: u16 addr, u8 data */
+#define CMD_KEYBOARD_CMD 0x62 /* in: u8, out: ? */
+#define CMD_TOUCHPAD_CMD 0x63 /* in: u8, out: ? */
+#define CMD_GET_FW_HASH 0x64 /* out: u8 * 16 */
+#define CMD_SUSPEND_HINT 0x65 /* in: u8 */
+#define CMD_ENABLE_WAKE_TIMER 0x66 /* in: u8 */
+#define CMD_SET_WAKE_TIMER 0x67 /* in: 32 */
+#define CMD_ENABLE_WAKE_AUTORESET 0x68 /* in: u8 */
+#define CMD_OLS_SET_LIMITS 0x69 /* in: u16, u16 */
+#define CMD_OLS_GET_LIMITS 0x6a /* out: u16, u16 */
+#define CMD_OLS_SET_CEILING 0x6b /* in: u16 */
+#define CMD_OLS_GET_CEILING 0x6c /* out: u16 */
+
+/*
+ * Accepted EC commands, and how many bytes they return. There are plenty
+ * of EC commands that are no longer implemented, or are implemented only on
+ * certain older boards.
+ */
+static const struct ec_cmd_t olpc_xo175_ec_cmds[] = {
+ { CMD_GET_API_VERSION, 1 },
+ { CMD_READ_VOLTAGE, 2 },
+ { CMD_READ_CURRENT, 2 },
+ { CMD_READ_ACR, 2 },
+ { CMD_READ_BATT_TEMPERATURE, 2 },
+ { CMD_READ_BATTERY_STATUS, 1 },
+ { CMD_READ_SOC, 1 },
+ { CMD_READ_GAUGE_ID, 8 },
+ { CMD_READ_GAUGE_DATA, 1 },
+ { CMD_READ_BOARD_ID, 2 },
+ { CMD_READ_BATT_ERR_CODE, 1 },
+ { CMD_SET_DCON_POWER, 0 },
+ { CMD_RESET_EC, 0 },
+ { CMD_READ_BATTERY_TYPE, 1 },
+ { CMD_ENABLE_RUNIN_DISCHARGE, 0 },
+ { CMD_DISABLE_RUNIN_DISCHARGE, 0 },
+ { CMD_READ_MPPT_ACTIVE, 1 },
+ { CMD_READ_MPPT_LIMIT, 1 },
+ { CMD_SET_MPPT_LIMIT, 0 },
+ { CMD_DISABLE_MPPT, 0 },
+ { CMD_ENABLE_MPPT, 0 },
+ { CMD_READ_VIN, 2 },
+ { CMD_GET_FW_VERSION, 16 },
+ { CMD_POWER_CYCLE, 0 },
+ { CMD_POWER_OFF, 0 },
+ { CMD_RESET_EC_SOFT, 0 },
+ { CMD_ECHO, 5 },
+ { CMD_GET_FW_DATE, 16 },
+ { CMD_GET_FW_USER, 16 },
+ { CMD_TURN_OFF_POWER, 0 },
+ { CMD_READ_OLS, 2 },
+ { CMD_OLS_SMT_LEDON, 0 },
+ { CMD_OLS_SMT_LEDOFF, 0 },
+ { CMD_START_OLS_ASSY, 0 },
+ { CMD_STOP_OLS_ASSY, 0 },
+ { CMD_OLS_SMTTEST_STOP, 0 },
+ { CMD_READ_VIN_SCALED, 2 },
+ { CMD_READ_BAT_MIN_W, 2 },
+ { CMD_READ_BAR_MAX_W, 2 },
+ { CMD_RESET_BAT_MINMAX_W, 0 },
+ { CMD_READ_LOCATION, 1 },
+ { CMD_WRITE_LOCATION, 0 },
+ { CMD_GET_FW_HASH, 16 },
+ { CMD_SUSPEND_HINT, 0 },
+ { CMD_ENABLE_WAKE_TIMER, 0 },
+ { CMD_SET_WAKE_TIMER, 0 },
+ { CMD_ENABLE_WAKE_AUTORESET, 0 },
+ { CMD_OLS_SET_LIMITS, 0 },
+ { CMD_OLS_GET_LIMITS, 4 },
+ { CMD_OLS_SET_CEILING, 0 },
+ { CMD_OLS_GET_CEILING, 2 },
+ { CMD_READ_EXT_SCI_MASK, 2 },
+ { CMD_WRITE_EXT_SCI_MASK, 0 },
+
+ { }
+};
+
+#define EC_MAX_CMD_DATA_LEN 5
+#define EC_MAX_RESP_LEN 16
+
+#define LOG_BUF_SIZE 128
+
+#define PM_WAKEUP_TIME 1000
+
+#define EC_ALL_EVENTS GENMASK(15, 0)
+
+enum ec_state_t {
+ CMD_STATE_IDLE = 0,
+ CMD_STATE_WAITING_FOR_SWITCH,
+ CMD_STATE_CMD_IN_TX_FIFO,
+ CMD_STATE_CMD_SENT,
+ CMD_STATE_RESP_RECEIVED,
+ CMD_STATE_ERROR_RECEIVED,
+};
+
+struct olpc_xo175_ec_cmd {
+ u8 command;
+ u8 nr_args;
+ u8 data_len;
+ u8 args[EC_MAX_CMD_DATA_LEN];
+};
+
+struct olpc_xo175_ec_resp {
+ u8 channel;
+ u8 byte;
+};
+
+struct olpc_xo175_ec {
+ bool suspended;
+
+ /* SPI related stuff. */
+ struct spi_device *spi;
+ struct spi_transfer xfer;
+ struct spi_message msg;
+ union {
+ struct olpc_xo175_ec_cmd cmd;
+ struct olpc_xo175_ec_resp resp;
+ } tx_buf, rx_buf;
+
+ /* GPIO for the CMD signals. */
+ struct gpio_desc *gpio_cmd;
+
+ /* Command handling related state. */
+ spinlock_t cmd_state_lock;
+ int cmd_state;
+ bool cmd_running;
+ struct completion cmd_done;
+ struct olpc_xo175_ec_cmd cmd;
+ u8 resp_data[EC_MAX_RESP_LEN];
+ int expected_resp_len;
+ int resp_len;
+
+ /* Power button. */
+ struct input_dev *pwrbtn;
+
+ /* Debug handling. */
+ char logbuf[LOG_BUF_SIZE];
+ int logbuf_len;
+};
+
+static struct platform_device *olpc_ec;
+
+static int olpc_xo175_ec_resp_len(u8 cmd)
+{
+ const struct ec_cmd_t *p;
+
+ for (p = olpc_xo175_ec_cmds; p->cmd; p++) {
+ if (p->cmd == cmd)
+ return p->bytes_returned;
+ }
+
+ return -EINVAL;
+}
+
+static void olpc_xo175_ec_flush_logbuf(struct olpc_xo175_ec *priv)
+{
+ dev_dbg(&priv->spi->dev, "got debug string [%*pE]\n",
+ priv->logbuf_len, priv->logbuf);
+ priv->logbuf_len = 0;
+}
+
+static void olpc_xo175_ec_complete(void *arg);
+
+static void olpc_xo175_ec_send_command(struct olpc_xo175_ec *priv, void *cmd,
+ size_t cmdlen)
+{
+ int ret;
+
+ memcpy(&priv->tx_buf, cmd, cmdlen);
+ priv->xfer.len = cmdlen;
+
+ spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1);
+
+ priv->msg.complete = olpc_xo175_ec_complete;
+ priv->msg.context = priv;
+
+ ret = spi_async(priv->spi, &priv->msg);
+ if (ret)
+ dev_err(&priv->spi->dev, "spi_async() failed %d\n", ret);
+}
+
+static void olpc_xo175_ec_read_packet(struct olpc_xo175_ec *priv)
+{
+ u8 nonce[] = {0xA5, 0x5A};
+
+ olpc_xo175_ec_send_command(priv, nonce, sizeof(nonce));
+}
+
+static void olpc_xo175_ec_complete(void *arg)
+{
+ struct olpc_xo175_ec *priv = arg;
+ struct device *dev = &priv->spi->dev;
+ struct power_supply *psy;
+ unsigned long flags;
+ u8 channel;
+ u8 byte;
+ int ret;
+
+ ret = priv->msg.status;
+ if (ret) {
+ dev_err(dev, "SPI transfer failed: %d\n", ret);
+
+ spin_lock_irqsave(&priv->cmd_state_lock, flags);
+ if (priv->cmd_running) {
+ priv->resp_len = 0;
+ priv->cmd_state = CMD_STATE_ERROR_RECEIVED;
+ complete(&priv->cmd_done);
+ }
+ spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
+
+ if (ret != -EINTR)
+ olpc_xo175_ec_read_packet(priv);
+
+ return;
+ }
+
+ channel = priv->rx_buf.resp.channel;
+ byte = priv->rx_buf.resp.byte;
+
+ switch (channel) {
+ case CHAN_NONE:
+ spin_lock_irqsave(&priv->cmd_state_lock, flags);
+
+ if (!priv->cmd_running) {
+ /* We can safely ignore these */
+ dev_err(dev, "spurious FIFO read packet\n");
+ spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
+ return;
+ }
+
+ priv->cmd_state = CMD_STATE_CMD_SENT;
+ if (!priv->expected_resp_len)
+ complete(&priv->cmd_done);
+ olpc_xo175_ec_read_packet(priv);
+
+ spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
+ return;
+
+ case CHAN_SWITCH:
+ spin_lock_irqsave(&priv->cmd_state_lock, flags);
+
+ if (!priv->cmd_running) {
+ /* Just go with the flow */
+ dev_err(dev, "spurious SWITCH packet\n");
+ memset(&priv->cmd, 0, sizeof(priv->cmd));
+ priv->cmd.command = CMD_ECHO;
+ }
+
+ priv->cmd_state = CMD_STATE_CMD_IN_TX_FIFO;
+
+ /* Throw command into TxFIFO */
+ gpiod_set_value_cansleep(priv->gpio_cmd, 0);
+ olpc_xo175_ec_send_command(priv, &priv->cmd, sizeof(priv->cmd));
+
+ spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
+ return;
+
+ case CHAN_CMD_RESP:
+ spin_lock_irqsave(&priv->cmd_state_lock, flags);
+
+ if (!priv->cmd_running) {
+ dev_err(dev, "spurious response packet\n");
+ } else if (priv->resp_len >= priv->expected_resp_len) {
+ dev_err(dev, "too many response packets\n");
+ } else {
+ priv->resp_data[priv->resp_len++] = byte;
+ if (priv->resp_len == priv->expected_resp_len) {
+ priv->cmd_state = CMD_STATE_RESP_RECEIVED;
+ complete(&priv->cmd_done);
+ }
+ }
+
+ spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
+ break;
+
+ case CHAN_CMD_ERROR:
+ spin_lock_irqsave(&priv->cmd_state_lock, flags);
+
+ if (!priv->cmd_running) {
+ dev_err(dev, "spurious cmd error packet\n");
+ } else {
+ priv->resp_data[0] = byte;
+ priv->resp_len = 1;
+ priv->cmd_state = CMD_STATE_ERROR_RECEIVED;
+ complete(&priv->cmd_done);
+ }
+ spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
+ break;
+
+ case CHAN_KEYBOARD:
+ dev_warn(dev, "keyboard is not supported\n");
+ break;
+
+ case CHAN_TOUCHPAD:
+ dev_warn(dev, "touchpad is not supported\n");
+ break;
+
+ case CHAN_EVENT:
+ dev_dbg(dev, "got event %.2x\n", byte);
+ switch (byte) {
+ case EVENT_AC_CHANGE:
+ psy = power_supply_get_by_name("olpc_ac");
+ if (psy) {
+ power_supply_changed(psy);
+ power_supply_put(psy);
+ }
+ break;
+ case EVENT_BATTERY_STATUS:
+ case EVENT_BATTERY_CRITICAL:
+ case EVENT_BATTERY_SOC_CHANGE:
+ case EVENT_BATTERY_ERROR:
+ psy = power_supply_get_by_name("olpc_battery");
+ if (psy) {
+ power_supply_changed(psy);
+ power_supply_put(psy);
+ }
+ break;
+ case EVENT_POWER_PRESSED:
+ input_report_key(priv->pwrbtn, KEY_POWER, 1);
+ input_sync(priv->pwrbtn);
+ input_report_key(priv->pwrbtn, KEY_POWER, 0);
+ input_sync(priv->pwrbtn);
+ fallthrough;
+ case EVENT_POWER_PRESS_WAKE:
+ case EVENT_TIMED_HOST_WAKE:
+ pm_wakeup_event(priv->pwrbtn->dev.parent,
+ PM_WAKEUP_TIME);
+ break;
+ default:
+ dev_dbg(dev, "ignored unknown event %.2x\n", byte);
+ break;
+ }
+ break;
+
+ case CHAN_DEBUG:
+ if (byte == '\n') {
+ olpc_xo175_ec_flush_logbuf(priv);
+ } else if (isprint(byte)) {
+ priv->logbuf[priv->logbuf_len++] = byte;
+ if (priv->logbuf_len == LOG_BUF_SIZE)
+ olpc_xo175_ec_flush_logbuf(priv);
+ }
+ break;
+
+ default:
+ dev_warn(dev, "unknown channel: %d, %.2x\n", channel, byte);
+ break;
+ }
+
+ /* Most non-command packets get the TxFIFO refilled and an ACK. */
+ olpc_xo175_ec_read_packet(priv);
+}
+
+/*
+ * This function is protected with a mutex. We can safely assume that
+ * there will be only one instance of this function running at a time.
+ * One of the ways in which we enforce this is by waiting until we get
+ * all response bytes back from the EC, rather than just the number that
+ * the caller requests (otherwise, we might start a new command while an
+ * old command's response bytes are still incoming).
+ */
+static int olpc_xo175_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *resp,
+ size_t resp_len, void *ec_cb_arg)
+{
+ struct olpc_xo175_ec *priv = ec_cb_arg;
+ struct device *dev = &priv->spi->dev;
+ unsigned long flags;
+ size_t nr_bytes;
+ int ret = 0;
+
+ dev_dbg(dev, "CMD %x, %zd bytes expected\n", cmd, resp_len);
+
+ if (inlen > 5) {
+ dev_err(dev, "command len %zd too big!\n", resp_len);
+ return -EOVERFLOW;
+ }
+
+ /* Suspending in the middle of an EC command hoses things badly! */
+ if (WARN_ON(priv->suspended))
+ return -EBUSY;
+
+ /* Ensure a valid command and return bytes */
+ ret = olpc_xo175_ec_resp_len(cmd);
+ if (ret < 0) {
+ dev_err_ratelimited(dev, "unknown command 0x%x\n", cmd);
+
+ /*
+ * Assume the best in our callers, and allow unknown commands
+ * through. I'm not the charitable type, but it was beaten
+ * into me. Just maintain a minimum standard of sanity.
+ */
+ if (resp_len > sizeof(priv->resp_data)) {
+ dev_err(dev, "response too big: %zd!\n", resp_len);
+ return -EOVERFLOW;
+ }
+ nr_bytes = resp_len;
+ } else {
+ nr_bytes = (size_t)ret;
+ ret = 0;
+ }
+ resp_len = min(resp_len, nr_bytes);
+
+ spin_lock_irqsave(&priv->cmd_state_lock, flags);
+
+ /* Initialize the state machine */
+ init_completion(&priv->cmd_done);
+ priv->cmd_running = true;
+ priv->cmd_state = CMD_STATE_WAITING_FOR_SWITCH;
+ memset(&priv->cmd, 0, sizeof(priv->cmd));
+ priv->cmd.command = cmd;
+ priv->cmd.nr_args = inlen;
+ priv->cmd.data_len = 0;
+ memcpy(priv->cmd.args, inbuf, inlen);
+ priv->expected_resp_len = nr_bytes;
+ priv->resp_len = 0;
+
+ /* Tickle the cmd gpio to get things started */
+ gpiod_set_value_cansleep(priv->gpio_cmd, 1);
+
+ spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
+
+ /* The irq handler should do the rest */
+ if (!wait_for_completion_timeout(&priv->cmd_done,
+ msecs_to_jiffies(4000))) {
+ dev_err(dev, "EC cmd error: timeout in STATE %d\n",
+ priv->cmd_state);
+ gpiod_set_value_cansleep(priv->gpio_cmd, 0);
+ spi_slave_abort(priv->spi);
+ olpc_xo175_ec_read_packet(priv);
+ return -ETIMEDOUT;
+ }
+
+ spin_lock_irqsave(&priv->cmd_state_lock, flags);
+
+ /* Deal with the results. */
+ if (priv->cmd_state == CMD_STATE_ERROR_RECEIVED) {
+ /* EC-provided error is in the single response byte */
+ dev_err(dev, "command 0x%x returned error 0x%x\n",
+ cmd, priv->resp_data[0]);
+ ret = -EREMOTEIO;
+ } else if (priv->resp_len != nr_bytes) {
+ dev_err(dev, "command 0x%x returned %d bytes, expected %zd bytes\n",
+ cmd, priv->resp_len, nr_bytes);
+ ret = -EREMOTEIO;
+ } else {
+ /*
+ * We may have 8 bytes in priv->resp, but we only care about
+ * what we've been asked for. If the caller asked for only 2
+ * bytes, give them that. We've guaranteed that
+ * resp_len <= priv->resp_len and priv->resp_len == nr_bytes.
+ */
+ memcpy(resp, priv->resp_data, resp_len);
+ }
+
+ /* This should already be low, but just in case. */
+ gpiod_set_value_cansleep(priv->gpio_cmd, 0);
+ priv->cmd_running = false;
+
+ spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
+
+ return ret;
+}
+
+static int olpc_xo175_ec_set_event_mask(unsigned int mask)
+{
+ u8 args[2];
+
+ args[0] = mask >> 0;
+ args[1] = mask >> 8;
+ return olpc_ec_cmd(CMD_WRITE_EXT_SCI_MASK, args, 2, NULL, 0);
+}
+
+static void olpc_xo175_ec_power_off(void)
+{
+ while (1) {
+ olpc_ec_cmd(CMD_POWER_OFF, NULL, 0, NULL, 0);
+ mdelay(1000);
+ }
+}
+
+static int __maybe_unused olpc_xo175_ec_suspend(struct device *dev)
+{
+ struct olpc_xo175_ec *priv = dev_get_drvdata(dev);
+ static struct {
+ u8 suspend;
+ u32 suspend_count;
+ } __packed hintargs;
+ static unsigned int suspend_count;
+
+ /*
+ * SOC_SLEEP is not wired to the EC on B3 and earlier boards.
+ * This command lets the EC know instead. The suspend count doesn't seem
+ * to be used anywhere but in the EC debug output.
+ */
+ hintargs.suspend = 1;
+ hintargs.suspend_count = suspend_count++;
+ olpc_ec_cmd(CMD_SUSPEND_HINT, (void *)&hintargs, sizeof(hintargs),
+ NULL, 0);
+
+ /*
+ * After we've sent the suspend hint, don't allow further EC commands
+ * to be run until we've resumed. Userspace tasks should be frozen,
+ * but kernel threads and interrupts could still schedule EC commands.
+ */
+ priv->suspended = true;
+
+ return 0;
+}
+
+static int __maybe_unused olpc_xo175_ec_resume_noirq(struct device *dev)
+{
+ struct olpc_xo175_ec *priv = dev_get_drvdata(dev);
+
+ priv->suspended = false;
+
+ return 0;
+}
+
+static int __maybe_unused olpc_xo175_ec_resume(struct device *dev)
+{
+ u8 x = 0;
+
+ /*
+ * The resume hint is only needed if no other commands are
+ * being sent during resume. all it does is tell the EC
+ * the SoC is definitely awake.
+ */
+ olpc_ec_cmd(CMD_SUSPEND_HINT, &x, 1, NULL, 0);
+
+ /* Enable all EC events while we're awake */
+ olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS);
+
+ return 0;
+}
+
+static struct olpc_ec_driver olpc_xo175_ec_driver = {
+ .ec_cmd = olpc_xo175_ec_cmd,
+};
+
+static void olpc_xo175_ec_remove(struct spi_device *spi)
+{
+ if (pm_power_off == olpc_xo175_ec_power_off)
+ pm_power_off = NULL;
+
+ spi_slave_abort(spi);
+
+ platform_device_unregister(olpc_ec);
+ olpc_ec = NULL;
+}
+
+static int olpc_xo175_ec_probe(struct spi_device *spi)
+{
+ struct olpc_xo175_ec *priv;
+ int ret;
+
+ if (olpc_ec) {
+ dev_err(&spi->dev, "OLPC EC already registered.\n");
+ return -EBUSY;
+ }
+
+ priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->gpio_cmd = devm_gpiod_get(&spi->dev, "cmd", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->gpio_cmd)) {
+ dev_err(&spi->dev, "failed to get cmd gpio: %ld\n",
+ PTR_ERR(priv->gpio_cmd));
+ return PTR_ERR(priv->gpio_cmd);
+ }
+
+ priv->spi = spi;
+
+ spin_lock_init(&priv->cmd_state_lock);
+ priv->cmd_state = CMD_STATE_IDLE;
+ init_completion(&priv->cmd_done);
+
+ priv->logbuf_len = 0;
+
+ /* Set up power button input device */
+ priv->pwrbtn = devm_input_allocate_device(&spi->dev);
+ if (!priv->pwrbtn)
+ return -ENOMEM;
+ priv->pwrbtn->name = "Power Button";
+ priv->pwrbtn->dev.parent = &spi->dev;
+ input_set_capability(priv->pwrbtn, EV_KEY, KEY_POWER);
+ ret = input_register_device(priv->pwrbtn);
+ if (ret) {
+ dev_err(&spi->dev, "error registering input device: %d\n", ret);
+ return ret;
+ }
+
+ spi_set_drvdata(spi, priv);
+
+ priv->xfer.rx_buf = &priv->rx_buf;
+ priv->xfer.tx_buf = &priv->tx_buf;
+
+ olpc_xo175_ec_read_packet(priv);
+
+ olpc_ec_driver_register(&olpc_xo175_ec_driver, priv);
+ olpc_ec = platform_device_register_resndata(&spi->dev, "olpc-ec", -1,
+ NULL, 0, NULL, 0);
+
+ /* Enable all EC events while we're awake */
+ olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS);
+
+ if (pm_power_off == NULL)
+ pm_power_off = olpc_xo175_ec_power_off;
+
+ dev_info(&spi->dev, "OLPC XO-1.75 Embedded Controller driver\n");
+
+ return 0;
+}
+
+static const struct dev_pm_ops olpc_xo175_ec_pm_ops = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, olpc_xo175_ec_resume_noirq)
+ SET_RUNTIME_PM_OPS(olpc_xo175_ec_suspend, olpc_xo175_ec_resume, NULL)
+};
+
+static const struct of_device_id olpc_xo175_ec_of_match[] = {
+ { .compatible = "olpc,xo1.75-ec" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, olpc_xo175_ec_of_match);
+
+static const struct spi_device_id olpc_xo175_ec_id_table[] = {
+ { "xo1.75-ec", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, olpc_xo175_ec_id_table);
+
+static struct spi_driver olpc_xo175_ec_spi_driver = {
+ .driver = {
+ .name = "olpc-xo175-ec",
+ .of_match_table = olpc_xo175_ec_of_match,
+ .pm = &olpc_xo175_ec_pm_ops,
+ },
+ .probe = olpc_xo175_ec_probe,
+ .remove = olpc_xo175_ec_remove,
+};
+module_spi_driver(olpc_xo175_ec_spi_driver);
+
+MODULE_DESCRIPTION("OLPC XO-1.75 Embedded Controller driver");
+MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>"); /* Functionality */
+MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); /* Bugs */
+MODULE_LICENSE("GPL");