These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / mfd / cros_ec_i2c.c
index c0c30f4..56a4664 100644 (file)
@@ -13,6 +13,7 @@
  * GNU General Public License for more details.
  */
 
+#include <linux/delay.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/i2c.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 
+/**
+ * Request format for protocol v3
+ * byte 0      0xda (EC_COMMAND_PROTOCOL_3)
+ * byte 1-8    struct ec_host_request
+ * byte 10-    response data
+ */
+struct ec_host_request_i2c {
+       /* Always 0xda to backward compatible with v2 struct */
+       uint8_t  command_protocol;
+       struct ec_host_request ec_request;
+} __packed;
+
+
+/*
+ * Response format for protocol v3
+ * byte 0      result code
+ * byte 1      packet_length
+ * byte 2-9    struct ec_host_response
+ * byte 10-    response data
+ */
+struct ec_host_response_i2c {
+       uint8_t result;
+       uint8_t packet_length;
+       struct ec_host_response ec_response;
+} __packed;
+
 static inline struct cros_ec_device *to_ec_dev(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
@@ -29,6 +56,134 @@ static inline struct cros_ec_device *to_ec_dev(struct device *dev)
        return i2c_get_clientdata(client);
 }
 
+static int cros_ec_pkt_xfer_i2c(struct cros_ec_device *ec_dev,
+                               struct cros_ec_command *msg)
+{
+       struct i2c_client *client = ec_dev->priv;
+       int ret = -ENOMEM;
+       int i;
+       int packet_len;
+       u8 *out_buf = NULL;
+       u8 *in_buf = NULL;
+       u8 sum;
+       struct i2c_msg i2c_msg[2];
+       struct ec_host_response *ec_response;
+       struct ec_host_request_i2c *ec_request_i2c;
+       struct ec_host_response_i2c *ec_response_i2c;
+       int request_header_size = sizeof(struct ec_host_request_i2c);
+       int response_header_size = sizeof(struct ec_host_response_i2c);
+
+       i2c_msg[0].addr = client->addr;
+       i2c_msg[0].flags = 0;
+       i2c_msg[1].addr = client->addr;
+       i2c_msg[1].flags = I2C_M_RD;
+
+       packet_len = msg->insize + response_header_size;
+       BUG_ON(packet_len > ec_dev->din_size);
+       in_buf = ec_dev->din;
+       i2c_msg[1].len = packet_len;
+       i2c_msg[1].buf = (char *) in_buf;
+
+       packet_len = msg->outsize + request_header_size;
+       BUG_ON(packet_len > ec_dev->dout_size);
+       out_buf = ec_dev->dout;
+       i2c_msg[0].len = packet_len;
+       i2c_msg[0].buf = (char *) out_buf;
+
+       /* create request data */
+       ec_request_i2c = (struct ec_host_request_i2c *) out_buf;
+       ec_request_i2c->command_protocol = EC_COMMAND_PROTOCOL_3;
+
+       ec_dev->dout++;
+       ret = cros_ec_prepare_tx(ec_dev, msg);
+       ec_dev->dout--;
+
+       /* send command to EC and read answer */
+       ret = i2c_transfer(client->adapter, i2c_msg, 2);
+       if (ret < 0) {
+               dev_dbg(ec_dev->dev, "i2c transfer failed: %d\n", ret);
+               goto done;
+       } else if (ret != 2) {
+               dev_err(ec_dev->dev, "failed to get response: %d\n", ret);
+               ret = -EIO;
+               goto done;
+       }
+
+       ec_response_i2c = (struct ec_host_response_i2c *) in_buf;
+       msg->result = ec_response_i2c->result;
+       ec_response = &ec_response_i2c->ec_response;
+
+       switch (msg->result) {
+       case EC_RES_SUCCESS:
+               break;
+       case EC_RES_IN_PROGRESS:
+               ret = -EAGAIN;
+               dev_dbg(ec_dev->dev, "command 0x%02x in progress\n",
+                       msg->command);
+               goto done;
+
+       default:
+               dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n",
+                       msg->command, msg->result);
+               /*
+                * When we send v3 request to v2 ec, ec won't recognize the
+                * 0xda (EC_COMMAND_PROTOCOL_3) and will return with status
+                * EC_RES_INVALID_COMMAND with zero data length.
+                *
+                * In case of invalid command for v3 protocol the data length
+                * will be at least sizeof(struct ec_host_response)
+                */
+               if (ec_response_i2c->result == EC_RES_INVALID_COMMAND &&
+                   ec_response_i2c->packet_length == 0) {
+                       ret = -EPROTONOSUPPORT;
+                       goto done;
+               }
+       }
+
+       if (ec_response_i2c->packet_length < sizeof(struct ec_host_response)) {
+               dev_err(ec_dev->dev,
+                       "response of %u bytes too short; not a full header\n",
+                       ec_response_i2c->packet_length);
+               ret = -EBADMSG;
+               goto done;
+       }
+
+       if (msg->insize < ec_response->data_len) {
+               dev_err(ec_dev->dev,
+                       "response data size is too large: expected %u, got %u\n",
+                       msg->insize,
+                       ec_response->data_len);
+               ret = -EMSGSIZE;
+               goto done;
+       }
+
+       /* copy response packet payload and compute checksum */
+       sum = 0;
+       for (i = 0; i < sizeof(struct ec_host_response); i++)
+               sum += ((u8 *)ec_response)[i];
+
+       memcpy(msg->data,
+              in_buf + response_header_size,
+              ec_response->data_len);
+       for (i = 0; i < ec_response->data_len; i++)
+               sum += msg->data[i];
+
+       /* All bytes should sum to zero */
+       if (sum) {
+               dev_err(ec_dev->dev, "bad packet checksum\n");
+               ret = -EBADMSG;
+               goto done;
+       }
+
+       ret = ec_response->data_len;
+
+done:
+       if (msg->command == EC_CMD_REBOOT_EC)
+               msleep(EC_REBOOT_DELAY_MS);
+
+       return ret;
+}
+
 static int cros_ec_cmd_xfer_i2c(struct cros_ec_device *ec_dev,
                                struct cros_ec_command *msg)
 {
@@ -76,7 +231,7 @@ static int cros_ec_cmd_xfer_i2c(struct cros_ec_device *ec_dev,
        /* copy message payload and compute checksum */
        sum = out_buf[0] + out_buf[1] + out_buf[2];
        for (i = 0; i < msg->outsize; i++) {
-               out_buf[3 + i] = msg->outdata[i];
+               out_buf[3 + i] = msg->data[i];
                sum += out_buf[3 + i];
        }
        out_buf[3 + msg->outsize] = sum;
@@ -109,7 +264,7 @@ static int cros_ec_cmd_xfer_i2c(struct cros_ec_device *ec_dev,
        /* copy response packet payload and compute checksum */
        sum = in_buf[0] + in_buf[1];
        for (i = 0; i < len; i++) {
-               msg->indata[i] = in_buf[2 + i];
+               msg->data[i] = in_buf[2 + i];
                sum += in_buf[2 + i];
        }
        dev_dbg(ec_dev->dev, "packet: %*ph, sum = %02x\n",
@@ -121,9 +276,12 @@ static int cros_ec_cmd_xfer_i2c(struct cros_ec_device *ec_dev,
        }
 
        ret = len;
- done:
+done:
        kfree(in_buf);
        kfree(out_buf);
+       if (msg->command == EC_CMD_REBOOT_EC)
+               msleep(EC_REBOOT_DELAY_MS);
+
        return ret;
 }
 
@@ -143,9 +301,11 @@ static int cros_ec_i2c_probe(struct i2c_client *client,
        ec_dev->priv = client;
        ec_dev->irq = client->irq;
        ec_dev->cmd_xfer = cros_ec_cmd_xfer_i2c;
-       ec_dev->ec_name = client->name;
+       ec_dev->pkt_xfer = cros_ec_pkt_xfer_i2c;
        ec_dev->phys_name = client->adapter->name;
-       ec_dev->parent = &client->dev;
+       ec_dev->din_size = sizeof(struct ec_host_response_i2c) +
+                          sizeof(struct ec_response_get_protocol_info);
+       ec_dev->dout_size = sizeof(struct ec_host_request_i2c);
 
        err = cros_ec_register(ec_dev);
        if (err) {
@@ -184,6 +344,12 @@ static int cros_ec_i2c_resume(struct device *dev)
 static SIMPLE_DEV_PM_OPS(cros_ec_i2c_pm_ops, cros_ec_i2c_suspend,
                          cros_ec_i2c_resume);
 
+static const struct of_device_id cros_ec_i2c_of_match[] = {
+       { .compatible = "google,cros-ec-i2c", },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, cros_ec_i2c_of_match);
+
 static const struct i2c_device_id cros_ec_i2c_id[] = {
        { "cros-ec-i2c", 0 },
        { }
@@ -193,7 +359,7 @@ MODULE_DEVICE_TABLE(i2c, cros_ec_i2c_id);
 static struct i2c_driver cros_ec_driver = {
        .driver = {
                .name   = "cros-ec-i2c",
-               .owner  = THIS_MODULE,
+               .of_match_table = of_match_ptr(cros_ec_i2c_of_match),
                .pm     = &cros_ec_i2c_pm_ops,
        },
        .probe          = cros_ec_i2c_probe,