CHROMIUM: mfd: add chromeos_ec driver to kernel 3.4
authorLuigi Semenzato <semenzato@chromium.org>
Thu, 17 May 2012 19:02:26 +0000 (12:02 -0700)
committerOlof Johansson <olofj@chromium.org>
Wed, 20 Jun 2012 19:07:51 +0000 (12:07 -0700)
This adds the chromeos-ec driver.

BUG=chrome-os-partner:8917
TEST=none (not configured yet)

Change-Id: I999b1d4cb89ce62900659eb0ad7ebbb883f09b50
Signed-off-by: Luigi Semenzato <semenzato@chromium.org>
Reviewed-on: https://gerrit-int.chromium.org/17751
Commit-Ready: Luigi Semenzato <semenzato@google.com>
Reviewed-by: Luigi Semenzato <semenzato@google.com>
Tested-by: Luigi Semenzato <semenzato@google.com>
Reviewed-by: Olof Johansson <olofj@google.com>
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/chromeos_ec.c [new file with mode: 0644]
include/linux/mfd/chromeos_ec.h [new file with mode: 0644]

index f6cb543..44986b9 100644 (file)
@@ -20,6 +20,15 @@ config MFD_88PM860X
          select individual components like voltage regulators, RTC and
          battery-charger under the corresponding menus.
 
+config MFD_CHROMEOS_EC
+       tristate "ChromeOS Embedded Controller"
+       select MFD_CORE
+       depends on I2C
+       help
+         If you say yes here you get support for the ChromeOS Embedded
+         Controller providing keyboard, battery and power services
+         over I2C bus.
+
 config MFD_SM501
        tristate "Support for Silicon Motion SM501"
         ---help---
index 19ecca9..852df3e 100644 (file)
@@ -6,6 +6,7 @@
 obj-$(CONFIG_MFD_88PM860X)     += 88pm860x.o
 obj-$(CONFIG_MFD_SM501)                += sm501.o
 obj-$(CONFIG_MFD_ASIC3)                += asic3.o tmio_core.o
+obj-$(CONFIG_MFD_CHROMEOS_EC)  += chromeos_ec.o
 
 obj-$(CONFIG_HTC_EGPIO)                += htc-egpio.o
 obj-$(CONFIG_HTC_PASIC3)       += htc-pasic3.o
diff --git a/drivers/mfd/chromeos_ec.c b/drivers/mfd/chromeos_ec.c
new file mode 100644 (file)
index 0000000..db15dd5
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ *  Copyright (C) 2012 Google, Inc
+ *
+ *  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.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *
+ * The ChromeOS EC multi function device is used to mux all the requests
+ * to the EC device for its multiple features : keyboard controller,
+ * battery charging and regulator control, firmware update.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/chromeos_ec.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+
+#define MKBP_MAX_TRIES 3
+
+/* Send a one-byte command to the keyboard and receive a response of length
+ * BUF_LEN.  Return BUF_LEN, or a negative error code.
+ */
+static int mkbp_command_noretry(struct chromeos_ec_device *ec_dev,
+                               char cmd, uint8_t *buf, int buf_len)
+{
+       int ret;
+       int i;
+       int packet_len;
+       uint8_t *packet;
+       uint8_t sum;
+
+       /* allocate larger packet (one extra byte for checksum) */
+       packet_len = buf_len + MKBP_MSG_PROTO_BYTES;
+       packet = kzalloc(packet_len, GFP_KERNEL);
+       if (!packet)
+               return -ENOMEM;
+
+       /* send command to EC */
+       ret = i2c_master_send(ec_dev->client, &cmd, 1);
+       if (ret < 0) {
+               dev_err(ec_dev->dev, "i2c send failed: %d\n", ret);
+               goto done;
+       }
+       /* receive response */
+       ret = i2c_master_recv(ec_dev->client, packet, packet_len);
+       if (ret < 0) {
+               dev_err(ec_dev->dev, "i2c receive failed: %d\n", ret);
+               goto done;
+       } else if (ret != packet_len) {
+               dev_err(ec_dev->dev, "expected %d bytes, got %d\n",
+                       packet_len, ret);
+               ret = -EIO;
+               goto done;
+       }
+       /* copy response packet payload and compute checksum */
+       for (i = 0, sum = 0; i < buf_len; i++) {
+               buf[i] = packet[i];
+               sum += buf[i];
+       }
+#ifdef DEBUG
+       dev_dbg(ec_dev->dev, "packet: ");
+       for (i = 0; i < packet_len; i++) {
+               printk(" %02x", packet[i]);
+       }
+       printk(", sum = %02x\n", sum);
+#endif
+       if (sum != packet[packet_len - 1]) {
+               dev_err(ec_dev->dev, "bad keyboard packet checksum\n");
+               ret = -EIO;
+               goto done;
+       }
+       ret = buf_len;
+ done:
+       kfree(packet);
+       return ret;
+}
+
+static int mkbp_command(struct chromeos_ec_device *ec_dev,
+                       char cmd, uint8_t *buf, int buf_len)
+{
+       int try;
+       int ret;
+       /*
+        * Try the command a few times in case there are transmission errors.
+        * It is possible that this is overkill, but we don't completely trust
+        * i2c.
+        */
+       for (try = 0; try < MKBP_MAX_TRIES; try++) {
+               ret = mkbp_command_noretry(ec_dev, cmd, buf, buf_len);
+               if (ret >= 0)
+                       return ret;
+       }
+       dev_err(ec_dev->dev, "mkbp_command failed with %d (%d tries)\n",
+               ret, try);
+       return ret;
+}
+
+static irqreturn_t mkbp_isr(int irq, void *data)
+{
+       struct chromeos_ec_device *ec = data;
+
+       atomic_notifier_call_chain(&ec->event_notifier, 1, ec);
+
+       return IRQ_HANDLED;
+}
+
+static int __devinit mkbp_check_protocol_version(struct chromeos_ec_device *ec)
+{
+       int ret;
+       char buf[4];
+       static char expected_version[4] = {1, 0, 0, 0};
+       int i;
+
+       ret = mkbp_command(ec, MKBP_CMDC_PROTO_VER, buf, sizeof(buf));
+       if (ret < 0)
+               return ret;
+       for (i = 0; i < sizeof(expected_version); i++) {
+               if (buf[i] != expected_version[i])
+                       return -EPROTONOSUPPORT;
+       }
+       return 0;
+}
+
+static struct mfd_cell cros_devs[] = {
+       {
+               .name = "mkbp",
+               .id = 1,
+       },
+};
+
+static int __devinit cros_ec_probe(struct i2c_client *client,
+                                  const struct i2c_device_id *dev_id)
+{
+       struct device *dev = &client->dev;
+       struct chromeos_ec_device *ec_dev = NULL;
+       int err;
+
+       dev_dbg(dev, "probing\n");
+
+       ec_dev = kzalloc(sizeof(*ec_dev), GFP_KERNEL);
+       if (ec_dev == NULL) {
+               err = -ENOMEM;
+               dev_err(dev, "cannot allocate\n");
+               goto fail;
+       }
+
+       ec_dev->client = client;
+       ec_dev->dev = dev;
+       i2c_set_clientdata(client, ec_dev);
+       ec_dev->irq = client->irq;
+       ec_dev->send_command = mkbp_command;
+
+       ATOMIC_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier);
+
+       err = request_irq(ec_dev->irq, mkbp_isr,
+                         IRQF_TRIGGER_FALLING, "chromeos-ec", ec_dev);
+       if (err) {
+               dev_err(dev, "request irq %d: error %d\n", ec_dev->irq, err);
+               goto fail;
+       }
+
+       err = mkbp_check_protocol_version(ec_dev);
+       if (err < 0) {
+               dev_err(dev, "protocol version check failed: %d\n", err);
+               goto fail_irq;
+       }
+
+       err = mfd_add_devices(dev, 0, cros_devs, ARRAY_SIZE(cros_devs),
+                             NULL, ec_dev->irq);
+       if (err)
+               goto fail_irq;
+
+       return 0;
+fail_irq:
+       free_irq(ec_dev->irq, ec_dev);
+fail:
+       kfree(ec_dev);
+       return err;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int cros_ec_suspend(struct device *dev)
+{
+       return 0;
+}
+
+static int cros_ec_resume(struct device *dev)
+{
+       return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(cros_ec_pm_ops, cros_ec_suspend, cros_ec_resume);
+
+static const struct i2c_device_id cros_ec_i2c_id[] = {
+       { "chromeos-ec", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, mkbp_i2c_id);
+
+static struct i2c_driver cros_ec_driver = {
+       .driver = {
+               .name   = "chromeos-ec",
+               .owner  = THIS_MODULE,
+               .pm     = &cros_ec_pm_ops,
+       },
+       .probe          = cros_ec_probe,
+       .id_table       = cros_ec_i2c_id,
+};
+
+module_i2c_driver(cros_ec_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ChromeOS EC multi function device");
diff --git a/include/linux/mfd/chromeos_ec.h b/include/linux/mfd/chromeos_ec.h
new file mode 100644 (file)
index 0000000..efea4e4
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (C) 2012 Google, Inc
+ *
+ *  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.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *
+ * ChromeOS EC multi function device
+ * and MKBP (matrix keyboard protocol) message-based protocol definitions.
+ */
+
+#ifndef __LINUX_MFD_CHROMEOS_EC_H
+#define __LINUX_MFD_CHROMEOS_EC_H
+
+enum {
+       /* Mask to convert a command byte into a command */
+       MKBP_MSG_TRAILER_BYTES  = 1,
+       MKBP_MSG_PROTO_BYTES    = MKBP_MSG_TRAILER_BYTES,
+};
+
+/* The EC command codes */
+
+enum message_cmd_t {
+       /* EC control/status messages */
+       MKBP_CMDC_PROTO_VER =   0x00,   /* Read protocol version */
+       MKBP_CMDC_NOP =         0x01,   /* No operation / ping */
+       MKBP_CMDC_ID =          0x02,   /* Read EC ID */
+
+       /* Functional messages */
+       MKBP_CMDC_KEY_STATE =   0x20,   /* Read key state */
+};
+
+
+struct chromeos_ec_device {
+       struct device *dev;
+       struct i2c_client *client;
+       int irq;
+       struct atomic_notifier_head event_notifier;
+       int (*send_command)(struct chromeos_ec_device *ec,
+                       char cmd, uint8_t *buf, int buf_len);
+};
+
+#endif /* __LINUX_MFD_CHROMEOS_EC_H */