summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Glass <sjg@chromium.org>2012-08-15 14:33:13 -0700
committerGerrit <chrome-bot@google.com>2012-08-16 18:45:20 -0700
commit76619f904df8ed519fac0d46d9c6c3a1956a88c2 (patch)
tree008138b09eb57cd3f4d8ec76914c58e69952b0f6
parentea56741fb3391989525aae2cc6dec112bb093f6d (diff)
downloadchrome-ec-76619f904df8ed519fac0d46d9c6c3a1956a88c2.tar.gz
stm32: i2c: Implement in-progress commands
When a command is marked as in-progress, provide an interim EC_RES_IN_PROGRESS response and then stash the real response (when available) ready for a EC_CMD_RESEND_RESPONSE message. Track whether the host_command processor is busy internally within this driver. Provide this information through an EC_CMD_GET_STATUS message. BUG=chrome-os-partner:12685 BRANCH=snow,link TEST=manual build and boot to kernel on snow Change-Id: I5acece074ad8408c978ca36b73d1330fa51575ae Signed-off-by: Simon Glass <sjg@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/30470 Reviewed-by: David Hendricks <dhendrix@chromium.org>
-rw-r--r--chip/stm32/i2c.c103
-rw-r--r--include/ec_commands.h30
2 files changed, 132 insertions, 1 deletions
diff --git a/chip/stm32/i2c.c b/chip/stm32/i2c.c
index bc3aac9eac..8ac6823cea 100644
--- a/chip/stm32/i2c.c
+++ b/chip/stm32/i2c.c
@@ -23,6 +23,12 @@
#define CPUTS(outstr) cputs(CC_I2C, outstr)
#define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args)
+#ifdef DEBUG
+#define debug(f, a...) CPRINTF(f, ##a)
+#else
+#define debug(f, a...)
+#endif
+
/* 8-bit I2C slave address */
#define I2C_ADDRESS 0x3c
@@ -65,6 +71,12 @@ static struct host_cmd_handler_args host_cmd_args;
/* Flag indicating if a command is currently in the buffer */
static uint8_t rx_pending;
+/* Indicates that a command is in progress */
+static uint8_t command_pending;
+
+/* The result of the last 'slow' operation */
+static uint8_t saved_result = EC_RES_UNAVAILABLE;
+
static inline void disable_i2c_interrupt(int port)
{
STM32_I2C_CR2(port) &= ~(3 << 8);
@@ -143,8 +155,45 @@ static void i2c_send_response(struct host_cmd_handler_args *args)
const uint8_t *data = args->response;
int size = args->response_size;
uint8_t *out = host_buffer;
+ int watch_command_pending = !in_interrupt_context();
int sum = 0, i;
+ /*
+ * TODO(sjg@chromium.org):
+ * The logic here is a little painful since we are avoiding changing
+ * host_command. If we got an 'in progress' previously, then this
+ * must be the completion of that command, so stash the result
+ * code. We can't send it back to the host now since we already sent
+ * the in-progress response and the host is on to other things now.
+ *
+ * Of course, if we are in interrupt context, then we are just
+ * handling a get_status response. We can't check that in
+ * args->command of course because the original command value has
+ * now been overwritten. This would be much easier to do in
+ * host_command since it actually knows what is going on.
+ *
+ * When a EC_CMD_RESEND_RESPONSE arrives we will supply this response
+ * to that command.
+ *
+ * We don't support stashing response data, so mark the response as
+ * unavailable in that case.
+ *
+ * TODO(sjg@chromium): It would all be easier if drivers used a
+ * stack variable for args and host_command was responsible for
+ * saving the command before execution. Perhaps fast commands could
+ * be executed in interrupt context anyway?
+ */
+ if (command_pending && watch_command_pending) {
+ debug("pending complete, size=%d, result=%d\n",
+ args->response_size, args->result);
+ if (args->response_size != 0)
+ saved_result = EC_RES_UNAVAILABLE;
+ else
+ saved_result = args->result;
+ command_pending = 0;
+ return;
+ }
+
*out++ = args->result;
if (!args->i2c_old_response) {
*out++ = size;
@@ -159,6 +208,12 @@ static void i2c_send_response(struct host_cmd_handler_args *args)
/* send the answer to the AP */
i2c_write_raw_slave(I2C2, host_buffer, out - host_buffer);
+
+ if (watch_command_pending) {
+ command_pending = (args->result == EC_RES_IN_PROGRESS);
+ if (command_pending)
+ debug("Command pending\n");
+ }
}
/* Process the command in the i2c host buffer */
@@ -200,7 +255,21 @@ static void i2c_process_command(void)
args->response = host_buffer + 2;
args->response_max = EC_HOST_PARAM_SIZE;
args->response_size = 0;
- host_command_received(args);
+
+ /*
+ * Special handling for GET_STATUS, which happens entirely outside
+ * host_command.
+ */
+ if (args->command == EC_CMD_GET_COMMS_STATUS) {
+ /*
+ * Could do this directly, but then we get no logging
+ * args->result = host_command_get_comms_status(args);
+ */
+ args->result = host_command_process(args);
+ args->send_response(args);
+ } else {
+ host_command_received(args);
+ }
}
static void i2c_event_handler(int port)
@@ -351,6 +420,38 @@ static int i2c_init(void)
DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT);
+/* Returns current command status (busy or not) */
+static int host_command_get_comms_status(struct host_cmd_handler_args *args)
+{
+ struct ec_response_get_comms_status *r = args->response;
+
+ r->flags = command_pending ? EC_COMMS_STATUS_PROCESSING : 0;
+ args->response_size = sizeof(*r);
+
+ return EC_SUCCESS;
+}
+
+DECLARE_HOST_COMMAND(EC_CMD_GET_COMMS_STATUS,
+ host_command_get_comms_status,
+ EC_VER_MASK(0));
+
+/* Resend the last saved response */
+static int host_command_resend_response(struct host_cmd_handler_args *args)
+{
+ /* Handle resending response */
+ args->result = saved_result;
+ args->response_size = 0;
+
+ saved_result = EC_RES_UNAVAILABLE;
+
+ return EC_SUCCESS;
+}
+
+DECLARE_HOST_COMMAND(EC_CMD_RESEND_RESPONSE,
+ host_command_resend_response,
+ EC_VER_MASK(0));
+
+
/*****************************************************************************/
/* STM32 Host I2C */
diff --git a/include/ec_commands.h b/include/ec_commands.h
index 66c5a1d04f..cecf44e000 100644
--- a/include/ec_commands.h
+++ b/include/ec_commands.h
@@ -187,6 +187,8 @@ enum ec_status {
EC_RES_INVALID_VERSION = 6,
EC_RES_INVALID_CHECKSUM = 7,
EC_RES_IN_PROGRESS = 8, /* Accepted, command in progress */
+ EC_RES_UNAVAILABLE = 9, /* No response available */
+ EC_RES_TIMEOUT = 10, /* We got a timeout */
};
/*
@@ -389,6 +391,25 @@ struct ec_response_get_cmd_versions {
uint32_t version_mask;
} __packed;
+/*
+ * Check EC communcations status (busy). This is needed on i2c/spi but not
+ * on lpc since it has its own out-of-band busy indicator.
+ *
+ * lpc must read the status from the command register. Attempting this on
+ * lpc will overwrite the args/parameter space and corrupt its data.
+ */
+#define EC_CMD_GET_COMMS_STATUS 0x09
+
+/* Avoid using ec_status which is for return values */
+enum ec_comms_status {
+ EC_COMMS_STATUS_PROCESSING = 1 << 0, /* Processing cmd */
+};
+
+struct ec_response_get_comms_status {
+ uint32_t flags; /* Mask of enum ec_comms_status */
+} __packed;
+
+
/*****************************************************************************/
/* Flash commands */
@@ -1054,6 +1075,15 @@ struct ec_params_reboot_ec {
#define EC_CMD_REBOOT 0xd1 /* Think "die" */
/*
+ * Resend last response (not supported on LPC).
+ *
+ * Returns EC_RES_UNAVAILABLE if there is no response available - for example,
+ * there was no previous command, or the previous command's response was too
+ * big to save.
+ */
+#define EC_CMD_RESEND_RESPONSE 0xdb
+
+/*
* This header byte on a command indicate version 0. Any header byte less
* than this means that we are talking to an old EC which doesn't support
* versioning. In that case, we assume version 0.