diff options
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 6 | ||||
-rw-r--r-- | drivers/mfd/axp20x.c | 100 | ||||
-rw-r--r-- | drivers/mfd/cros_ec.c | 173 | ||||
-rw-r--r-- | drivers/mfd/cros_ec_i2c.c | 170 | ||||
-rw-r--r-- | drivers/mfd/cros_ec_spi.c | 408 |
5 files changed, 685 insertions, 172 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d5ad04dad081..cf3b86d441f7 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -94,6 +94,8 @@ config MFD_AXP20X config MFD_CROS_EC tristate "ChromeOS Embedded Controller" select MFD_CORE + select CHROME_PLATFORMS + select CROS_EC_PROTO help If you say Y here you get support for the ChromeOS Embedded Controller (EC) providing keyboard, battery and power services. @@ -102,7 +104,7 @@ config MFD_CROS_EC config MFD_CROS_EC_I2C tristate "ChromeOS Embedded Controller (I2C)" - depends on MFD_CROS_EC && I2C + depends on MFD_CROS_EC && CROS_EC_PROTO && I2C help If you say Y here, you get support for talking to the ChromeOS @@ -112,7 +114,7 @@ config MFD_CROS_EC_I2C config MFD_CROS_EC_SPI tristate "ChromeOS Embedded Controller (SPI)" - depends on MFD_CROS_EC && SPI && OF + depends on MFD_CROS_EC && CROS_EC_PROTO && SPI && OF ---help--- If you say Y here, you get support for talking to the ChromeOS EC diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index d18029be6a78..6df91556faf3 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -32,6 +32,7 @@ static const char * const axp20x_model_names[] = { "AXP202", "AXP209", + "AXP221", "AXP288", }; @@ -54,6 +55,25 @@ static const struct regmap_access_table axp20x_volatile_table = { .n_yes_ranges = ARRAY_SIZE(axp20x_volatile_ranges), }; +static const struct regmap_range axp22x_writeable_ranges[] = { + regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE), + regmap_reg_range(AXP20X_DCDC_MODE, AXP22X_BATLOW_THRES1), +}; + +static const struct regmap_range axp22x_volatile_ranges[] = { + regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IRQ5_STATE), +}; + +static const struct regmap_access_table axp22x_writeable_table = { + .yes_ranges = axp22x_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(axp22x_writeable_ranges), +}; + +static const struct regmap_access_table axp22x_volatile_table = { + .yes_ranges = axp22x_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(axp22x_volatile_ranges), +}; + static const struct regmap_range axp288_writeable_ranges[] = { regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ6_STATE), regmap_reg_range(AXP20X_DCDC_MODE, AXP288_FG_TUNE5), @@ -87,6 +107,20 @@ static struct resource axp20x_pek_resources[] = { }, }; +static struct resource axp22x_pek_resources[] = { + { + .name = "PEK_DBR", + .start = AXP22X_IRQ_PEK_RIS_EDGE, + .end = AXP22X_IRQ_PEK_RIS_EDGE, + .flags = IORESOURCE_IRQ, + }, { + .name = "PEK_DBF", + .start = AXP22X_IRQ_PEK_FAL_EDGE, + .end = AXP22X_IRQ_PEK_FAL_EDGE, + .flags = IORESOURCE_IRQ, + }, +}; + static struct resource axp288_fuel_gauge_resources[] = { { .start = AXP288_IRQ_QWBTU, @@ -129,6 +163,15 @@ static const struct regmap_config axp20x_regmap_config = { .cache_type = REGCACHE_RBTREE, }; +static const struct regmap_config axp22x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .wr_table = &axp22x_writeable_table, + .volatile_table = &axp22x_volatile_table, + .max_register = AXP22X_BATLOW_THRES1, + .cache_type = REGCACHE_RBTREE, +}; + static const struct regmap_config axp288_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -181,6 +224,34 @@ static const struct regmap_irq axp20x_regmap_irqs[] = { INIT_REGMAP_IRQ(AXP20X, GPIO0_INPUT, 4, 0), }; +static const struct regmap_irq axp22x_regmap_irqs[] = { + INIT_REGMAP_IRQ(AXP22X, ACIN_OVER_V, 0, 7), + INIT_REGMAP_IRQ(AXP22X, ACIN_PLUGIN, 0, 6), + INIT_REGMAP_IRQ(AXP22X, ACIN_REMOVAL, 0, 5), + INIT_REGMAP_IRQ(AXP22X, VBUS_OVER_V, 0, 4), + INIT_REGMAP_IRQ(AXP22X, VBUS_PLUGIN, 0, 3), + INIT_REGMAP_IRQ(AXP22X, VBUS_REMOVAL, 0, 2), + INIT_REGMAP_IRQ(AXP22X, VBUS_V_LOW, 0, 1), + INIT_REGMAP_IRQ(AXP22X, BATT_PLUGIN, 1, 7), + INIT_REGMAP_IRQ(AXP22X, BATT_REMOVAL, 1, 6), + INIT_REGMAP_IRQ(AXP22X, BATT_ENT_ACT_MODE, 1, 5), + INIT_REGMAP_IRQ(AXP22X, BATT_EXIT_ACT_MODE, 1, 4), + INIT_REGMAP_IRQ(AXP22X, CHARG, 1, 3), + INIT_REGMAP_IRQ(AXP22X, CHARG_DONE, 1, 2), + INIT_REGMAP_IRQ(AXP22X, BATT_TEMP_HIGH, 1, 1), + INIT_REGMAP_IRQ(AXP22X, BATT_TEMP_LOW, 1, 0), + INIT_REGMAP_IRQ(AXP22X, DIE_TEMP_HIGH, 2, 7), + INIT_REGMAP_IRQ(AXP22X, PEK_SHORT, 2, 1), + INIT_REGMAP_IRQ(AXP22X, PEK_LONG, 2, 0), + INIT_REGMAP_IRQ(AXP22X, LOW_PWR_LVL1, 3, 1), + INIT_REGMAP_IRQ(AXP22X, LOW_PWR_LVL2, 3, 0), + INIT_REGMAP_IRQ(AXP22X, TIMER, 4, 7), + INIT_REGMAP_IRQ(AXP22X, PEK_RIS_EDGE, 4, 6), + INIT_REGMAP_IRQ(AXP22X, PEK_FAL_EDGE, 4, 5), + INIT_REGMAP_IRQ(AXP22X, GPIO1_INPUT, 4, 1), + INIT_REGMAP_IRQ(AXP22X, GPIO0_INPUT, 4, 0), +}; + /* some IRQs are compatible with axp20x models */ static const struct regmap_irq axp288_regmap_irqs[] = { INIT_REGMAP_IRQ(AXP288, VBUS_FALL, 0, 2), @@ -224,6 +295,7 @@ static const struct regmap_irq axp288_regmap_irqs[] = { static const struct of_device_id axp20x_of_match[] = { { .compatible = "x-powers,axp202", .data = (void *) AXP202_ID }, { .compatible = "x-powers,axp209", .data = (void *) AXP209_ID }, + { .compatible = "x-powers,axp221", .data = (void *) AXP221_ID }, { }, }; MODULE_DEVICE_TABLE(of, axp20x_of_match); @@ -258,6 +330,18 @@ static const struct regmap_irq_chip axp20x_regmap_irq_chip = { }; +static const struct regmap_irq_chip axp22x_regmap_irq_chip = { + .name = "axp22x_irq_chip", + .status_base = AXP20X_IRQ1_STATE, + .ack_base = AXP20X_IRQ1_STATE, + .mask_base = AXP20X_IRQ1_EN, + .mask_invert = true, + .init_ack_masked = true, + .irqs = axp22x_regmap_irqs, + .num_irqs = ARRAY_SIZE(axp22x_regmap_irqs), + .num_regs = 5, +}; + static const struct regmap_irq_chip axp288_regmap_irq_chip = { .name = "axp288_irq_chip", .status_base = AXP20X_IRQ1_STATE, @@ -281,6 +365,16 @@ static struct mfd_cell axp20x_cells[] = { }, }; +static struct mfd_cell axp22x_cells[] = { + { + .name = "axp20x-pek", + .num_resources = ARRAY_SIZE(axp22x_pek_resources), + .resources = axp22x_pek_resources, + }, { + .name = "axp20x-regulator", + }, +}; + static struct resource axp288_adc_resources[] = { { .name = "GPADC", @@ -426,6 +520,12 @@ static int axp20x_match_device(struct axp20x_dev *axp20x, struct device *dev) axp20x->regmap_cfg = &axp20x_regmap_config; axp20x->regmap_irq_chip = &axp20x_regmap_irq_chip; break; + case AXP221_ID: + axp20x->nr_cells = ARRAY_SIZE(axp22x_cells); + axp20x->cells = axp22x_cells; + axp20x->regmap_cfg = &axp22x_regmap_config; + axp20x->regmap_irq_chip = &axp22x_regmap_irq_chip; + break; case AXP288_ID: axp20x->cells = axp288_cells; axp20x->nr_cells = ARRAY_SIZE(axp288_cells); diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index c4aecc6f8373..0eee63542038 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -17,111 +17,36 @@ * battery charging and regulator control, firmware update. */ +#include <linux/of_platform.h> #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 +#define CROS_EC_DEV_EC_INDEX 0 +#define CROS_EC_DEV_PD_INDEX 1 -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; +static struct cros_ec_platform ec_p = { + .ec_name = CROS_EC_DEV_NAME, + .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_EC_INDEX), +}; - msg->result = status_msg.result; - if (status_msg.result != EC_RES_SUCCESS) - break; +static struct cros_ec_platform pd_p = { + .ec_name = CROS_EC_DEV_PD_NAME, + .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX), +}; - status = (struct ec_response_get_comms_status *) - status_msg.indata; - if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) - break; - } - } - mutex_unlock(&ec_dev->lock); +static const struct mfd_cell ec_cell = { + .name = "cros-ec-ctl", + .platform_data = &ec_p, + .pdata_size = sizeof(ec_p), +}; - 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, - }, +static const struct mfd_cell ec_pd_cell = { + .name = "cros-ec-ctl", + .platform_data = &pd_p, + .pdata_size = sizeof(pd_p), }; int cros_ec_register(struct cros_ec_device *ec_dev) @@ -129,27 +54,59 @@ 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; - } + ec_dev->max_request = sizeof(struct ec_params_hello); + ec_dev->max_response = sizeof(struct ec_response_get_protocol_info); + ec_dev->max_passthru = 0; + + ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); + if (!ec_dev->din) + return -ENOMEM; + + 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), + cros_ec_query_all(ec_dev); + + err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO, &ec_cell, 1, NULL, ec_dev->irq, NULL); if (err) { - dev_err(dev, "failed to add mfd devices\n"); + dev_err(dev, + "Failed to register Embedded Controller subdevice %d\n", + err); return err; } + if (ec_dev->max_passthru) { + /* + * Register a PD device as well on top of this device. + * We make the following assumptions: + * - behind an EC, we have a pd + * - only one device added. + * - the EC is responsive at init time (it is not true for a + * sensor hub. + */ + err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO, + &ec_pd_cell, 1, NULL, ec_dev->irq, NULL); + if (err) { + dev_err(dev, + "Failed to register Power Delivery subdevice %d\n", + err); + return err; + } + } + + if (IS_ENABLED(CONFIG_OF) && dev->of_node) { + err = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (err) { + mfd_remove_devices(dev); + dev_err(dev, "Failed to register sub-devices\n"); + return err; + } + } + dev_info(dev, "Chrome EC device registered\n"); return 0; diff --git a/drivers/mfd/cros_ec_i2c.c b/drivers/mfd/cros_ec_i2c.c index c0c30f4f946f..b9a0963ca5c3 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) { @@ -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) { diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c index bf6e08e8013e..16f228dc243f 100644 --- a/drivers/mfd/cros_ec_spi.c +++ b/drivers/mfd/cros_ec_spi.c @@ -65,29 +65,26 @@ */ #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 * * @spi: SPI device we are connected to * @last_transfer_ns: time that we last finished a transfer, or 0 if there * if no record + * @start_of_msg_delay: used to set the delay_usecs on the spi_transfer that + * is sent when we want to turn on CS at the start of a transaction. * @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that * is sent when we want to turn off CS at the end of a transaction. */ struct cros_ec_spi { struct spi_device *spi; s64 last_transfer_ns; + unsigned int start_of_msg_delay; unsigned int end_of_msg_delay; }; static void debug_packet(struct device *dev, const char *name, u8 *ptr, - int len) + int len) { #ifdef DEBUG int i; @@ -100,6 +97,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 +278,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 +343,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 +358,138 @@ 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, trans_delay; + 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; + } + + /* + * Leave a gap between CS assertion and clocking of data to allow the + * EC time to wakeup. + */ + spi_message_init(&msg); + if (ec_spi->start_of_msg_delay) { + memset(&trans_delay, 0, sizeof(trans_delay)); + trans_delay.delay_usecs = ec_spi->start_of_msg_delay; + spi_message_add_tail(&trans_delay, &msg); + } + + /* 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_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 +503,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 +519,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 +538,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; @@ -299,7 +586,7 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, for (i = 0; i < len; i++) { sum += ptr[i + 2]; if (ec_msg->insize) - ec_msg->indata[i] = ptr[i + 2]; + ec_msg->data[i] = ptr[i + 2]; } sum &= 0xff; @@ -315,6 +602,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); @@ -327,6 +615,10 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev) u32 val; int ret; + ret = of_property_read_u32(np, "google,cros-ec-spi-pre-delay", &val); + if (!ret) + ec_spi->start_of_msg_delay = val; + ret = of_property_read_u32(np, "google,cros-ec-spi-msg-delay", &val); if (!ret) ec_spi->end_of_msg_delay = val; @@ -361,11 +653,13 @@ 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->ec_name = ec_spi->spi->modalias; + ec_dev->pkt_xfer = cros_ec_pkt_xfer_spi; ec_dev->phys_name = dev_name(&ec_spi->spi->dev); - ec_dev->parent = &ec_spi->spi->dev; - ec_dev->din_size = EC_MSG_BYTES + EC_MSG_PREAMBLE_COUNT; - ec_dev->dout_size = EC_MSG_BYTES; + ec_dev->din_size = EC_MSG_PREAMBLE_COUNT + + sizeof(struct ec_host_response) + + sizeof(struct ec_response_get_protocol_info); + ec_dev->dout_size = sizeof(struct ec_host_request); + err = cros_ec_register(ec_dev); if (err) { |