diff options
author | Stephen Barber <smbarber@chromium.org> | 2015-06-09 13:04:46 +0200 |
---|---|---|
committer | Lee Jones <lee.jones@linaro.org> | 2015-06-15 13:18:22 +0100 |
commit | d365407079d33106f76bd486a863de05eb5ae95d (patch) | |
tree | 46cc169453a614fee690d5ce9df51ed992222c4c /drivers/mfd | |
parent | 2c7589af3c4dee844e6a4174f2aa8996cf837604 (diff) |
mfd: cros_ec: add bus-specific proto v3 code
Add proto v3 support to the SPI, I2C, and LPC.
Signed-off-by: Stephen Barber <smbarber@chromium.org>
Signed-off-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
Tested-by: Heiko Stuebner <heiko@sntech.de>
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
Tested-by: Gwendal Grignou <gwendal@chromium.org>
Acked-by: Lee Jones <lee.jones@linaro.org>
Acked-by: Olof Johansson <olof@lixom.net>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/cros_ec_i2c.c | 166 | ||||
-rw-r--r-- | drivers/mfd/cros_ec_spi.c | 382 |
2 files changed, 491 insertions, 57 deletions
diff --git a/drivers/mfd/cros_ec_i2c.c b/drivers/mfd/cros_ec_i2c.c index b400bfa2772a..22e8a4ae1711 100644 --- a/drivers/mfd/cros_ec_i2c.c +++ b/drivers/mfd/cros_ec_i2c.c @@ -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> @@ -22,6 +23,32 @@ #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) { @@ -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,12 +301,12 @@ 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->pkt_xfer = NULL; + ec_dev->pkt_xfer = cros_ec_pkt_xfer_i2c; ec_dev->ec_name = client->name; ec_dev->phys_name = client->adapter->name; - ec_dev->din_size = sizeof(struct ec_host_response) + + 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); + ec_dev->dout_size = sizeof(struct ec_host_request_i2c); err = cros_ec_register(ec_dev); if (err) { diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c index 04da2f288ef8..4e6f2f6b1095 100644 --- a/drivers/mfd/cros_ec_spi.c +++ b/drivers/mfd/cros_ec_spi.c @@ -65,12 +65,6 @@ */ #define EC_SPI_RECOVERY_TIME_NS (200 * 1000) -/* - * The EC is unresponsive for a time after a reboot command. Add a - * simple delay to make sure that the bus stays locked. - */ -#define EC_REBOOT_DELAY_MS 50 - /** * struct cros_ec_spi - information about a SPI-connected EC * @@ -87,7 +81,7 @@ struct cros_ec_spi { }; static void debug_packet(struct device *dev, const char *name, u8 *ptr, - int len) + int len) { #ifdef DEBUG int i; @@ -100,6 +94,172 @@ static void debug_packet(struct device *dev, const char *name, u8 *ptr, #endif } +static int terminate_request(struct cros_ec_device *ec_dev) +{ + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_message msg; + struct spi_transfer trans; + int ret; + + /* + * Turn off CS, possibly adding a delay to ensure the rising edge + * doesn't come too soon after the end of the data. + */ + spi_message_init(&msg); + memset(&trans, 0, sizeof(trans)); + trans.delay_usecs = ec_spi->end_of_msg_delay; + spi_message_add_tail(&trans, &msg); + + ret = spi_sync(ec_spi->spi, &msg); + + /* Reset end-of-response timer */ + ec_spi->last_transfer_ns = ktime_get_ns(); + if (ret < 0) { + dev_err(ec_dev->dev, + "cs-deassert spi transfer failed: %d\n", + ret); + } + + return ret; +} + +/** + * receive_n_bytes - receive n bytes from the EC. + * + * Assumes buf is a pointer into the ec_dev->din buffer + */ +static int receive_n_bytes(struct cros_ec_device *ec_dev, u8 *buf, int n) +{ + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_transfer trans; + struct spi_message msg; + int ret; + + BUG_ON(buf - ec_dev->din + n > ec_dev->din_size); + + memset(&trans, 0, sizeof(trans)); + trans.cs_change = 1; + trans.rx_buf = buf; + trans.len = n; + + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + ret = spi_sync(ec_spi->spi, &msg); + if (ret < 0) + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + + return ret; +} + +/** + * cros_ec_spi_receive_packet - Receive a packet from the EC. + * + * This function has two phases: reading the preamble bytes (since if we read + * data from the EC before it is ready to send, we just get preamble) and + * reading the actual message. + * + * The received data is placed into ec_dev->din. + * + * @ec_dev: ChromeOS EC device + * @need_len: Number of message bytes we need to read + */ +static int cros_ec_spi_receive_packet(struct cros_ec_device *ec_dev, + int need_len) +{ + struct ec_host_response *response; + u8 *ptr, *end; + int ret; + unsigned long deadline; + int todo; + + BUG_ON(EC_MSG_PREAMBLE_COUNT > ec_dev->din_size); + + /* Receive data until we see the header byte */ + deadline = jiffies + msecs_to_jiffies(EC_MSG_DEADLINE_MS); + while (true) { + unsigned long start_jiffies = jiffies; + + ret = receive_n_bytes(ec_dev, + ec_dev->din, + EC_MSG_PREAMBLE_COUNT); + if (ret < 0) + return ret; + + ptr = ec_dev->din; + for (end = ptr + EC_MSG_PREAMBLE_COUNT; ptr != end; ptr++) { + if (*ptr == EC_SPI_FRAME_START) { + dev_dbg(ec_dev->dev, "msg found at %zd\n", + ptr - ec_dev->din); + break; + } + } + if (ptr != end) + break; + + /* + * Use the time at the start of the loop as a timeout. This + * gives us one last shot at getting the transfer and is useful + * in case we got context switched out for a while. + */ + if (time_after(start_jiffies, deadline)) { + dev_warn(ec_dev->dev, "EC failed to respond in time\n"); + return -ETIMEDOUT; + } + } + + /* + * ptr now points to the header byte. Copy any valid data to the + * start of our buffer + */ + todo = end - ++ptr; + BUG_ON(todo < 0 || todo > ec_dev->din_size); + todo = min(todo, need_len); + memmove(ec_dev->din, ptr, todo); + ptr = ec_dev->din + todo; + dev_dbg(ec_dev->dev, "need %d, got %d bytes from preamble\n", + need_len, todo); + need_len -= todo; + + /* If the entire response struct wasn't read, get the rest of it. */ + if (todo < sizeof(*response)) { + ret = receive_n_bytes(ec_dev, ptr, sizeof(*response) - todo); + if (ret < 0) + return -EBADMSG; + ptr += (sizeof(*response) - todo); + todo = sizeof(*response); + } + + response = (struct ec_host_response *)ec_dev->din; + + /* Abort if data_len is too large. */ + if (response->data_len > ec_dev->din_size) + return -EMSGSIZE; + + /* Receive data until we have it all */ + while (need_len > 0) { + /* + * We can't support transfers larger than the SPI FIFO size + * unless we have DMA. We don't have DMA on the ISP SPI ports + * for Exynos. We need a way of asking SPI driver for + * maximum-supported transfer size. + */ + todo = min(need_len, 256); + dev_dbg(ec_dev->dev, "loop, todo=%d, need_len=%d, ptr=%zd\n", + todo, need_len, ptr - ec_dev->din); + + ret = receive_n_bytes(ec_dev, ptr, todo); + if (ret < 0) + return ret; + + ptr += todo; + need_len -= todo; + } + + dev_dbg(ec_dev->dev, "loop done, ptr=%zd\n", ptr - ec_dev->din); + + return 0; +} + /** * cros_ec_spi_receive_response - Receive a response from the EC. * @@ -115,34 +275,27 @@ static void debug_packet(struct device *dev, const char *name, u8 *ptr, static int cros_ec_spi_receive_response(struct cros_ec_device *ec_dev, int need_len) { - struct cros_ec_spi *ec_spi = ec_dev->priv; - struct spi_transfer trans; - struct spi_message msg; u8 *ptr, *end; int ret; unsigned long deadline; int todo; + BUG_ON(EC_MSG_PREAMBLE_COUNT > ec_dev->din_size); + /* Receive data until we see the header byte */ deadline = jiffies + msecs_to_jiffies(EC_MSG_DEADLINE_MS); while (true) { unsigned long start_jiffies = jiffies; - memset(&trans, 0, sizeof(trans)); - trans.cs_change = 1; - trans.rx_buf = ptr = ec_dev->din; - trans.len = EC_MSG_PREAMBLE_COUNT; - - spi_message_init(&msg); - spi_message_add_tail(&trans, &msg); - ret = spi_sync(ec_spi->spi, &msg); - if (ret < 0) { - dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + ret = receive_n_bytes(ec_dev, + ec_dev->din, + EC_MSG_PREAMBLE_COUNT); + if (ret < 0) return ret; - } + ptr = ec_dev->din; for (end = ptr + EC_MSG_PREAMBLE_COUNT; ptr != end; ptr++) { - if (*ptr == EC_MSG_HEADER) { + if (*ptr == EC_SPI_FRAME_START) { dev_dbg(ec_dev->dev, "msg found at %zd\n", ptr - ec_dev->din); break; @@ -187,21 +340,9 @@ static int cros_ec_spi_receive_response(struct cros_ec_device *ec_dev, dev_dbg(ec_dev->dev, "loop, todo=%d, need_len=%d, ptr=%zd\n", todo, need_len, ptr - ec_dev->din); - memset(&trans, 0, sizeof(trans)); - trans.cs_change = 1; - trans.rx_buf = ptr; - trans.len = todo; - spi_message_init(&msg); - spi_message_add_tail(&trans, &msg); - - /* send command to EC and read answer */ - BUG_ON((u8 *)trans.rx_buf - ec_dev->din + todo > - ec_dev->din_size); - ret = spi_sync(ec_spi->spi, &msg); - if (ret < 0) { - dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + ret = receive_n_bytes(ec_dev, ptr, todo); + if (ret < 0) return ret; - } debug_packet(ec_dev->dev, "interim", ptr, todo); ptr += todo; @@ -214,6 +355,128 @@ static int cros_ec_spi_receive_response(struct cros_ec_device *ec_dev, } /** + * cros_ec_pkt_xfer_spi - Transfer a packet over SPI and receive the reply + * + * @ec_dev: ChromeOS EC device + * @ec_msg: Message to transfer + */ +static int cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg) +{ + struct ec_host_request *request; + struct ec_host_response *response; + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_transfer trans; + struct spi_message msg; + int i, len; + u8 *ptr; + u8 *rx_buf; + u8 sum; + int ret = 0, final_ret; + + len = cros_ec_prepare_tx(ec_dev, ec_msg); + request = (struct ec_host_request *)ec_dev->dout; + dev_dbg(ec_dev->dev, "prepared, len=%d\n", len); + + /* If it's too soon to do another transaction, wait */ + if (ec_spi->last_transfer_ns) { + unsigned long delay; /* The delay completed so far */ + + delay = ktime_get_ns() - ec_spi->last_transfer_ns; + if (delay < EC_SPI_RECOVERY_TIME_NS) + ndelay(EC_SPI_RECOVERY_TIME_NS - delay); + } + + rx_buf = kzalloc(len, GFP_KERNEL); + if (!rx_buf) { + ret = -ENOMEM; + goto exit; + } + + /* Transmit phase - send our message */ + memset(&trans, 0, sizeof(trans)); + trans.tx_buf = ec_dev->dout; + trans.rx_buf = rx_buf; + trans.len = len; + trans.cs_change = 1; + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + ret = spi_sync(ec_spi->spi, &msg); + + /* Get the response */ + if (!ret) { + /* Verify that EC can process command */ + for (i = 0; i < len; i++) { + switch (rx_buf[i]) { + case EC_SPI_PAST_END: + case EC_SPI_RX_BAD_DATA: + case EC_SPI_NOT_READY: + ret = -EAGAIN; + ec_msg->result = EC_RES_IN_PROGRESS; + default: + break; + } + if (ret) + break; + } + if (!ret) + ret = cros_ec_spi_receive_packet(ec_dev, + ec_msg->insize + sizeof(*response)); + } else { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + } + + final_ret = terminate_request(ec_dev); + if (!ret) + ret = final_ret; + if (ret < 0) + goto exit; + + ptr = ec_dev->din; + + /* check response error code */ + response = (struct ec_host_response *)ptr; + ec_msg->result = response->result; + + ret = cros_ec_check_result(ec_dev, ec_msg); + if (ret) + goto exit; + + len = response->data_len; + sum = 0; + if (len > ec_msg->insize) { + dev_err(ec_dev->dev, "packet too long (%d bytes, expected %d)", + len, ec_msg->insize); + ret = -EMSGSIZE; + goto exit; + } + + for (i = 0; i < sizeof(*response); i++) + sum += ptr[i]; + + /* copy response packet payload and compute checksum */ + memcpy(ec_msg->data, ptr + sizeof(*response), len); + for (i = 0; i < len; i++) + sum += ec_msg->data[i]; + + if (sum) { + dev_err(ec_dev->dev, + "bad packet checksum, calculated %x\n", + sum); + ret = -EBADMSG; + goto exit; + } + + ret = len; +exit: + kfree(rx_buf); + if (ec_msg->command == EC_CMD_REBOOT_EC) + msleep(EC_REBOOT_DELAY_MS); + + return ret; +} + +/** * cros_ec_cmd_xfer_spi - Transfer a message over SPI and receive the reply * * @ec_dev: ChromeOS EC device @@ -227,6 +490,7 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, struct spi_message msg; int i, len; u8 *ptr; + u8 *rx_buf; int sum; int ret = 0, final_ret; @@ -242,10 +506,17 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, ndelay(EC_SPI_RECOVERY_TIME_NS - delay); } + rx_buf = kzalloc(len, GFP_KERNEL); + if (!rx_buf) { + ret = -ENOMEM; + goto exit; + } + /* Transmit phase - send our message */ debug_packet(ec_dev->dev, "out", ec_dev->dout, len); memset(&trans, 0, sizeof(trans)); trans.tx_buf = ec_dev->dout; + trans.rx_buf = rx_buf; trans.len = len; trans.cs_change = 1; spi_message_init(&msg); @@ -254,29 +525,32 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, /* Get the response */ if (!ret) { - ret = cros_ec_spi_receive_response(ec_dev, - ec_msg->insize + EC_MSG_TX_PROTO_BYTES); + /* Verify that EC can process command */ + for (i = 0; i < len; i++) { + switch (rx_buf[i]) { + case EC_SPI_PAST_END: + case EC_SPI_RX_BAD_DATA: + case EC_SPI_NOT_READY: + ret = -EAGAIN; + ec_msg->result = EC_RES_IN_PROGRESS; + default: + break; + } + if (ret) + break; + } + if (!ret) + ret = cros_ec_spi_receive_response(ec_dev, + ec_msg->insize + EC_MSG_TX_PROTO_BYTES); } else { dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); } - /* - * Turn off CS, possibly adding a delay to ensure the rising edge - * doesn't come too soon after the end of the data. - */ - spi_message_init(&msg); - memset(&trans, 0, sizeof(trans)); - trans.delay_usecs = ec_spi->end_of_msg_delay; - spi_message_add_tail(&trans, &msg); - - final_ret = spi_sync(ec_spi->spi, &msg); - ec_spi->last_transfer_ns = ktime_get_ns(); + final_ret = terminate_request(ec_dev); if (!ret) ret = final_ret; - if (ret < 0) { - dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + if (ret < 0) goto exit; - } ptr = ec_dev->din; @@ -315,6 +589,7 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, ret = len; exit: + kfree(rx_buf); if (ec_msg->command == EC_CMD_REBOOT_EC) msleep(EC_REBOOT_DELAY_MS); @@ -361,7 +636,7 @@ static int cros_ec_spi_probe(struct spi_device *spi) ec_dev->priv = ec_spi; ec_dev->irq = spi->irq; ec_dev->cmd_xfer = cros_ec_cmd_xfer_spi; - ec_dev->pkt_xfer = NULL; + ec_dev->pkt_xfer = cros_ec_pkt_xfer_spi; ec_dev->ec_name = ec_spi->spi->modalias; ec_dev->phys_name = dev_name(&ec_spi->spi->dev); ec_dev->din_size = EC_MSG_PREAMBLE_COUNT + @@ -369,6 +644,7 @@ static int cros_ec_spi_probe(struct spi_device *spi) sizeof(struct ec_response_get_protocol_info); ec_dev->dout_size = sizeof(struct ec_host_request); + err = cros_ec_register(ec_dev); if (err) { dev_err(dev, "cannot register EC\n"); |