mfd: Add ChromeOS EC implementation
authorSimon Glass <sjg@chromium.org>
Mon, 25 Feb 2013 22:08:37 +0000 (14:08 -0800)
committerSamuel Ortiz <sameo@linux.intel.com>
Fri, 5 Apr 2013 09:20:13 +0000 (11:20 +0200)
This is the base EC implementation, which provides a high level
interface to the EC for use by the rest of the kernel. The actual
communcations is dealt with by a separate protocol driver which
registers itself with this interface.

Interrupts are passed on through a notifier.

A simple message structure is used to pass messages to the
protocol driver.
Signed-off-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Che-Liang Chiou <clchiou@chromium.org>
Signed-off-by: Jonathan Kliegman <kliegs@chromium.org>
Signed-off-by: Luigi Semenzato <semenzato@chromium.org>
Signed-off-by: Olof Johansson <olofj@chromium.org>
Signed-off-by: Vincent Palatin <vpalatin@chromium.org>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Documentation/devicetree/bindings/mfd/cros-ec.txt [new file with mode: 0644]
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/cros_ec.c [new file with mode: 0644]
include/linux/mfd/cros_ec.h [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/mfd/cros-ec.txt b/Documentation/devicetree/bindings/mfd/cros-ec.txt
new file mode 100644 (file)
index 0000000..e0e59c5
--- /dev/null
@@ -0,0 +1,56 @@
+ChromeOS Embedded Controller
+
+Google's ChromeOS EC is a Cortex-M device which talks to the AP and
+implements various function such as keyboard and battery charging.
+
+The EC can be connect through various means (I2C, SPI, LPC) and the
+compatible string used depends on the inteface. Each connection method has
+its own driver which connects to the top level interface-agnostic EC driver.
+Other Linux driver (such as cros-ec-keyb for the matrix keyboard) connect to
+the top-level driver.
+
+Required properties (I2C):
+- compatible: "google,cros-ec-i2c"
+- reg: I2C slave address
+
+Required properties (SPI):
+- compatible: "google,cros-ec-spi"
+- reg: SPI chip select
+
+Required properties (LPC):
+- compatible: "google,cros-ec-lpc"
+- reg: List of (IO address, size) pairs defining the interface uses
+
+
+Example for I2C:
+
+i2c@12CA0000 {
+       cros-ec@1e {
+               reg = <0x1e>;
+               compatible = "google,cros-ec-i2c";
+               interrupts = <14 0>;
+               interrupt-parent = <&wakeup_eint>;
+               wakeup-source;
+       };
+
+
+Example for SPI:
+
+spi@131b0000 {
+       ec@0 {
+               compatible = "google,cros-ec-spi";
+               reg = <0x0>;
+               interrupts = <14 0>;
+               interrupt-parent = <&wakeup_eint>;
+               wakeup-source;
+               spi-max-frequency = <5000000>;
+               controller-data {
+               cs-gpio = <&gpf0 3 4 3 0>;
+               samsung,spi-cs;
+               samsung,spi-feedback-delay = <2>;
+               };
+       };
+};
+
+
+Example for LPC is not supplied as it is not yet implemented.
index c346941a251593bbe42bd8f3d3171acc6a93c2cf..a8bafb560196ae3e26c91a6044ff969a07b99317 100644 (file)
@@ -21,6 +21,14 @@ config MFD_88PM860X
          select individual components like voltage regulators, RTC and
          battery-charger under the corresponding menus.
 
+config MFD_CROS_EC
+       tristate "Support ChromeOS Embedded Controller"
+       help
+         If you say Y here you get support for the ChromeOS Embedded
+         Controller (EC) providing keyboard, battery and power services.
+         You also ned to enable the driver for the bus you are using. The
+         protocol for talking to the EC is defined by the bus driver.
+
 config MFD_88PM800
        tristate "Support Marvell 88PM800"
        depends on I2C=y && GENERIC_HARDIRQS
index b90409c236645ea3769e963a142b263e7c3eb35f..967767d3d7594de8ae833439612b3b2c16385cc7 100644 (file)
@@ -8,6 +8,7 @@ obj-$(CONFIG_MFD_88PM800)       += 88pm800.o 88pm80x.o
 obj-$(CONFIG_MFD_88PM805)      += 88pm805.o 88pm80x.o
 obj-$(CONFIG_MFD_SM501)                += sm501.o
 obj-$(CONFIG_MFD_ASIC3)                += asic3.o tmio_core.o
+obj-$(CONFIG_MFD_CROS_EC)      += cros_ec.o
 
 rtsx_pci-objs                  := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o
 obj-$(CONFIG_MFD_RTSX_PCI)     += rtsx_pci.o
diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c
new file mode 100644 (file)
index 0000000..ac824cc
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * ChromeOS EC multi-function device
+ *
+ * Copyright (C) 2012 Google, Inc
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ * 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/interrupt.h>
+#include <linux/slab.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/cros_ec.h>
+#include <linux/mfd/cros_ec_commands.h>
+
+int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
+                      struct cros_ec_msg *msg)
+{
+       uint8_t *out;
+       int csum, i;
+
+       BUG_ON(msg->out_len > EC_HOST_PARAM_SIZE);
+       out = ec_dev->dout;
+       out[0] = EC_CMD_VERSION0 + msg->version;
+       out[1] = msg->cmd;
+       out[2] = msg->out_len;
+       csum = out[0] + out[1] + out[2];
+       for (i = 0; i < msg->out_len; i++)
+               csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->out_buf[i];
+       out[EC_MSG_TX_HEADER_BYTES + msg->out_len] = (uint8_t)(csum & 0xff);
+
+       return EC_MSG_TX_PROTO_BYTES + msg->out_len;
+}
+
+static int cros_ec_command_sendrecv(struct cros_ec_device *ec_dev,
+               uint16_t cmd, void *out_buf, int out_len,
+               void *in_buf, int in_len)
+{
+       struct cros_ec_msg msg;
+
+       msg.version = cmd >> 8;
+       msg.cmd = cmd & 0xff;
+       msg.out_buf = out_buf;
+       msg.out_len = out_len;
+       msg.in_buf = in_buf;
+       msg.in_len = in_len;
+
+       return ec_dev->command_xfer(ec_dev, &msg);
+}
+
+static int cros_ec_command_recv(struct cros_ec_device *ec_dev,
+               uint16_t cmd, void *buf, int buf_len)
+{
+       return cros_ec_command_sendrecv(ec_dev, cmd, NULL, 0, buf, buf_len);
+}
+
+static int cros_ec_command_send(struct cros_ec_device *ec_dev,
+               uint16_t cmd, void *buf, int buf_len)
+{
+       return cros_ec_command_sendrecv(ec_dev, cmd, buf, buf_len, NULL, 0);
+}
+
+static irqreturn_t ec_irq_thread(int irq, void *data)
+{
+       struct cros_ec_device *ec_dev = data;
+
+       if (device_may_wakeup(ec_dev->dev))
+               pm_wakeup_event(ec_dev->dev, 0);
+
+       blocking_notifier_call_chain(&ec_dev->event_notifier, 1, ec_dev);
+
+       return IRQ_HANDLED;
+}
+
+static struct mfd_cell cros_devs[] = {
+       {
+               .name = "cros-ec-keyb",
+               .id = 1,
+               .of_compatible = "google,cros-ec-keyb",
+       },
+};
+
+int cros_ec_register(struct cros_ec_device *ec_dev)
+{
+       struct device *dev = ec_dev->dev;
+       int err = 0;
+
+       BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier);
+
+       ec_dev->command_send = cros_ec_command_send;
+       ec_dev->command_recv = cros_ec_command_recv;
+       ec_dev->command_sendrecv = cros_ec_command_sendrecv;
+
+       if (ec_dev->din_size) {
+               ec_dev->din = kmalloc(ec_dev->din_size, GFP_KERNEL);
+               if (!ec_dev->din) {
+                       err = -ENOMEM;
+                       goto fail_din;
+               }
+       }
+       if (ec_dev->dout_size) {
+               ec_dev->dout = kmalloc(ec_dev->dout_size, GFP_KERNEL);
+               if (!ec_dev->dout) {
+                       err = -ENOMEM;
+                       goto fail_dout;
+               }
+       }
+
+       if (!ec_dev->irq) {
+               dev_dbg(dev, "no valid IRQ: %d\n", ec_dev->irq);
+               goto fail_irq;
+       }
+
+       err = request_threaded_irq(ec_dev->irq, NULL, ec_irq_thread,
+                                  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+                                  "chromeos-ec", ec_dev);
+       if (err) {
+               dev_err(dev, "request irq %d: error %d\n", ec_dev->irq, err);
+               goto fail_irq;
+       }
+
+       err = mfd_add_devices(dev, 0, cros_devs,
+                             ARRAY_SIZE(cros_devs),
+                             NULL, ec_dev->irq, NULL);
+       if (err) {
+               dev_err(dev, "failed to add mfd devices\n");
+               goto fail_mfd;
+       }
+
+       dev_info(dev, "Chrome EC (%s)\n", ec_dev->name);
+
+       return 0;
+
+fail_mfd:
+       free_irq(ec_dev->irq, ec_dev);
+fail_irq:
+       kfree(ec_dev->dout);
+fail_dout:
+       kfree(ec_dev->din);
+fail_din:
+       return err;
+}
+
+int cros_ec_remove(struct cros_ec_device *ec_dev)
+{
+       mfd_remove_devices(ec_dev->dev);
+       free_irq(ec_dev->irq, ec_dev);
+       kfree(ec_dev->dout);
+       kfree(ec_dev->din);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+int cros_ec_suspend(struct cros_ec_device *ec_dev)
+{
+       struct device *dev = ec_dev->dev;
+
+       if (device_may_wakeup(dev))
+               ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq);
+
+       disable_irq(ec_dev->irq);
+       ec_dev->was_wake_device = ec_dev->wake_enabled;
+
+       return 0;
+}
+
+int cros_ec_resume(struct cros_ec_device *ec_dev)
+{
+       enable_irq(ec_dev->irq);
+
+       if (ec_dev->wake_enabled) {
+               disable_irq_wake(ec_dev->irq);
+               ec_dev->wake_enabled = 0;
+       }
+
+       return 0;
+}
+#endif
diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h
new file mode 100644 (file)
index 0000000..032af7f
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * ChromeOS EC multi-function device
+ *
+ * Copyright (C) 2012 Google, Inc
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_MFD_CROS_EC_H
+#define __LINUX_MFD_CROS_EC_H
+
+#include <linux/mfd/cros_ec_commands.h>
+
+/*
+ * Command interface between EC and AP, for LPC, I2C and SPI interfaces.
+ */
+enum {
+       EC_MSG_TX_HEADER_BYTES  = 3,
+       EC_MSG_TX_TRAILER_BYTES = 1,
+       EC_MSG_TX_PROTO_BYTES   = EC_MSG_TX_HEADER_BYTES +
+                                       EC_MSG_TX_TRAILER_BYTES,
+       EC_MSG_RX_PROTO_BYTES   = 3,
+
+       /* Max length of messages */
+       EC_MSG_BYTES            = EC_HOST_PARAM_SIZE + EC_MSG_TX_PROTO_BYTES,
+
+};
+
+/**
+ * struct cros_ec_msg - A message sent to the EC, and its reply
+ *
+ * @version: Command version number (often 0)
+ * @cmd: Command to send (EC_CMD_...)
+ * @out_buf: Outgoing payload (to EC)
+ * @outlen: Outgoing length
+ * @in_buf: Incoming payload (from EC)
+ * @in_len: Incoming length
+ */
+struct cros_ec_msg {
+       u8 version;
+       u8 cmd;
+       uint8_t *out_buf;
+       int out_len;
+       uint8_t *in_buf;
+       int in_len;
+};
+
+/**
+ * struct cros_ec_device - Information about a ChromeOS EC device
+ *
+ * @name: Name of this EC interface
+ * @priv: Private data
+ * @irq: Interrupt to use
+ * @din: input buffer (from EC)
+ * @dout: output buffer (to EC)
+ * \note
+ * These two buffers will always be dword-aligned and include enough
+ * space for up to 7 word-alignment bytes also, so we can ensure that
+ * the body of the message is always dword-aligned (64-bit).
+ *
+ * We use this alignment to keep ARM and x86 happy. Probably word
+ * alignment would be OK, there might be a small performance advantage
+ * to using dword.
+ * @din_size: size of din buffer
+ * @dout_size: size of dout buffer
+ * @command_send: send a command
+ * @command_recv: receive a command
+ * @ec_name: name of EC device (e.g. 'chromeos-ec')
+ * @phys_name: name of physical comms layer (e.g. 'i2c-4')
+ * @parent: pointer to parent device (e.g. i2c or spi device)
+ * @dev: Device pointer
+ * dev_lock: Lock to prevent concurrent access
+ * @wake_enabled: true if this device can wake the system from sleep
+ * @was_wake_device: true if this device was set to wake the system from
+ * sleep at the last suspend
+ * @event_notifier: interrupt event notifier for transport devices
+ */
+struct cros_ec_device {
+       const char *name;
+       void *priv;
+       int irq;
+       uint8_t *din;
+       uint8_t *dout;
+       int din_size;
+       int dout_size;
+       int (*command_send)(struct cros_ec_device *ec,
+                       uint16_t cmd, void *out_buf, int out_len);
+       int (*command_recv)(struct cros_ec_device *ec,
+                       uint16_t cmd, void *in_buf, int in_len);
+       int (*command_sendrecv)(struct cros_ec_device *ec,
+                       uint16_t cmd, void *out_buf, int out_len,
+                       void *in_buf, int in_len);
+       int (*command_xfer)(struct cros_ec_device *ec,
+                       struct cros_ec_msg *msg);
+
+       const char *ec_name;
+       const char *phys_name;
+       struct device *parent;
+
+       /* These are --private-- fields - do not assign */
+       struct device *dev;
+       struct mutex dev_lock;
+       bool wake_enabled;
+       bool was_wake_device;
+       struct blocking_notifier_head event_notifier;
+};
+
+/**
+ * cros_ec_suspend - Handle a suspend operation for the ChromeOS EC device
+ *
+ * This can be called by drivers to handle a suspend event.
+ *
+ * ec_dev: Device to suspend
+ * @return 0 if ok, -ve on error
+ */
+int cros_ec_suspend(struct cros_ec_device *ec_dev);
+
+/**
+ * cros_ec_resume - Handle a resume operation for the ChromeOS EC device
+ *
+ * This can be called by drivers to handle a resume event.
+ *
+ * @ec_dev: Device to resume
+ * @return 0 if ok, -ve on error
+ */
+int cros_ec_resume(struct cros_ec_device *ec_dev);
+
+/**
+ * cros_ec_prepare_tx - Prepare an outgoing message in the output buffer
+ *
+ * This is intended to be used by all ChromeOS EC drivers, but at present
+ * only SPI uses it. Once LPC uses the same protocol it can start using it.
+ * I2C could use it now, with a refactor of the existing code.
+ *
+ * @ec_dev: Device to register
+ * @msg: Message to write
+ */
+int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
+                      struct cros_ec_msg *msg);
+
+/**
+ * cros_ec_remove - Remove a ChromeOS EC
+ *
+ * Call this to deregister a ChromeOS EC. After this you should call
+ * cros_ec_free().
+ *
+ * @ec_dev: Device to register
+ * @return 0 if ok, -ve on error
+ */
+int cros_ec_remove(struct cros_ec_device *ec_dev);
+
+/**
+ * cros_ec_register - Register a new ChromeOS EC, using the provided info
+ *
+ * Before calling this, allocate a pointer to a new device and then fill
+ * in all the fields up to the --private-- marker.
+ *
+ * @ec_dev: Device to register
+ * @return 0 if ok, -ve on error
+ */
+int cros_ec_register(struct cros_ec_device *ec_dev);
+
+#endif /* __LINUX_MFD_CROS_EC_H */