Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / mfd / cros_ec.c
diff --git a/kernel/drivers/mfd/cros_ec.c b/kernel/drivers/mfd/cros_ec.c
new file mode 100644 (file)
index 0000000..c4aecc6
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * 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/module.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/cros_ec.h>
+#include <linux/mfd/cros_ec_commands.h>
+#include <linux/delay.h>
+
+#define EC_COMMAND_RETRIES     50
+
+int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
+                      struct cros_ec_command *msg)
+{
+       uint8_t *out;
+       int csum, i;
+
+       BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE);
+       out = ec_dev->dout;
+       out[0] = EC_CMD_VERSION0 + msg->version;
+       out[1] = msg->command;
+       out[2] = msg->outsize;
+       csum = out[0] + out[1] + out[2];
+       for (i = 0; i < msg->outsize; i++)
+               csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->outdata[i];
+       out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff);
+
+       return EC_MSG_TX_PROTO_BYTES + msg->outsize;
+}
+EXPORT_SYMBOL(cros_ec_prepare_tx);
+
+int cros_ec_check_result(struct cros_ec_device *ec_dev,
+                        struct cros_ec_command *msg)
+{
+       switch (msg->result) {
+       case EC_RES_SUCCESS:
+               return 0;
+       case EC_RES_IN_PROGRESS:
+               dev_dbg(ec_dev->dev, "command 0x%02x in progress\n",
+                       msg->command);
+               return -EAGAIN;
+       default:
+               dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n",
+                       msg->command, msg->result);
+               return 0;
+       }
+}
+EXPORT_SYMBOL(cros_ec_check_result);
+
+int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
+                    struct cros_ec_command *msg)
+{
+       int ret;
+
+       mutex_lock(&ec_dev->lock);
+       ret = ec_dev->cmd_xfer(ec_dev, msg);
+       if (msg->result == EC_RES_IN_PROGRESS) {
+               int i;
+               struct cros_ec_command status_msg = { };
+               struct ec_response_get_comms_status *status;
+
+               status_msg.command = EC_CMD_GET_COMMS_STATUS;
+               status_msg.insize = sizeof(*status);
+
+               /*
+                * Query the EC's status until it's no longer busy or
+                * we encounter an error.
+                */
+               for (i = 0; i < EC_COMMAND_RETRIES; i++) {
+                       usleep_range(10000, 11000);
+
+                       ret = ec_dev->cmd_xfer(ec_dev, &status_msg);
+                       if (ret < 0)
+                               break;
+
+                       msg->result = status_msg.result;
+                       if (status_msg.result != EC_RES_SUCCESS)
+                               break;
+
+                       status = (struct ec_response_get_comms_status *)
+                                status_msg.indata;
+                       if (!(status->flags & EC_COMMS_STATUS_PROCESSING))
+                               break;
+               }
+       }
+       mutex_unlock(&ec_dev->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL(cros_ec_cmd_xfer);
+
+static const struct mfd_cell cros_devs[] = {
+       {
+               .name = "cros-ec-keyb",
+               .id = 1,
+               .of_compatible = "google,cros-ec-keyb",
+       },
+       {
+               .name = "cros-ec-i2c-tunnel",
+               .id = 2,
+               .of_compatible = "google,cros-ec-i2c-tunnel",
+       },
+       {
+               .name = "cros-ec-ctl",
+               .id = 3,
+       },
+};
+
+int cros_ec_register(struct cros_ec_device *ec_dev)
+{
+       struct device *dev = ec_dev->dev;
+       int err = 0;
+
+       if (ec_dev->din_size) {
+               ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL);
+               if (!ec_dev->din)
+                       return -ENOMEM;
+       }
+       if (ec_dev->dout_size) {
+               ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL);
+               if (!ec_dev->dout)
+                       return -ENOMEM;
+       }
+
+       mutex_init(&ec_dev->lock);
+
+       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");
+               return err;
+       }
+
+       dev_info(dev, "Chrome EC device registered\n");
+
+       return 0;
+}
+EXPORT_SYMBOL(cros_ec_register);
+
+int cros_ec_remove(struct cros_ec_device *ec_dev)
+{
+       mfd_remove_devices(ec_dev->dev);
+
+       return 0;
+}
+EXPORT_SYMBOL(cros_ec_remove);
+
+#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;
+}
+EXPORT_SYMBOL(cros_ec_suspend);
+
+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;
+}
+EXPORT_SYMBOL(cros_ec_resume);
+
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ChromeOS EC core driver");