From 31369a69dbf34cff8d9135f5d503efa717e3da35 Mon Sep 17 00:00:00 2001 From: Alec Berg Date: Wed, 21 May 2014 07:32:59 -0700 Subject: samus: Add EC <-> PD i2c interface using host commands Initial support for EC to PD communication using host command interface over i2c. BUG=chrome-os-partner:28351, chrome-os-partner:28352 BRANCH=none TEST=on EC console send hello host command: > pdcmd 0x01 0 0xa0 0xb0 0xc0 0xd0 Host command 0x01, returned 4 a4 b3 c2 d1 Change-Id: I0969808f455574ee456d6db8a60ce9b1204a0739 Signed-off-by: Alec Berg Reviewed-on: https://chromium-review.googlesource.com/200786 Reviewed-by: Randall Spangler --- board/samus/board.h | 2 + board/samus_pd/board.h | 3 +- board/samus_pd/ec.tasklist | 1 + chip/stm32/i2c-stm32f0.c | 194 +++++++++++++++++++++++++++++++++++++++++++ chip/stm32/registers.h | 19 +++++ common/build.mk | 1 + common/host_command.c | 10 +++ common/host_command_master.c | 185 +++++++++++++++++++++++++++++++++++++++++ include/config.h | 6 ++ include/host_command.h | 14 ++++ 10 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 common/host_command_master.c diff --git a/board/samus/board.h b/board/samus/board.h index 2ec8fc899a..0b72482f9e 100644 --- a/board/samus/board.h +++ b/board/samus/board.h @@ -43,6 +43,7 @@ #define CONFIG_CHARGER_INPUT_CURRENT 2000 #define CONFIG_CHARGER_DISCHARGE_ON_AC #define CONFIG_FANS 2 +#define CONFIG_HOST_CMD_MASTER #define CONFIG_PECI_TJMAX 100 #define CONFIG_PWM #define CONFIG_PWM_KBLIGHT @@ -64,6 +65,7 @@ #define I2C_PORT_BACKLIGHT 0 #define I2C_PORT_BATTERY 0 #define I2C_PORT_CHARGER 0 +#define I2C_PORT_PD_MCU 0 #define I2C_PORT_ALS 1 #define I2C_PORT_ACCEL 1 #define I2C_PORT_LIGHTBAR 1 diff --git a/board/samus_pd/board.h b/board/samus_pd/board.h index 265f3e18a0..65218b5b57 100644 --- a/board/samus_pd/board.h +++ b/board/samus_pd/board.h @@ -31,9 +31,8 @@ /* I2C ports configuration */ #define I2C_PORT_MASTER 1 -#define I2C_PORT_BATTERY I2C_PORT_MASTER -#define I2C_PORT_CHARGER I2C_PORT_MASTER #define I2C_PORT_SLAVE 0 +#define I2C_PORT_EC I2C_PORT_SLAVE /* * Allow dangerous commands all the time, since we don't have a write protect diff --git a/board/samus_pd/ec.tasklist b/board/samus_pd/ec.tasklist index a8250d57c1..1dbac0ba02 100644 --- a/board/samus_pd/ec.tasklist +++ b/board/samus_pd/ec.tasklist @@ -18,5 +18,6 @@ */ #define CONFIG_TASK_LIST \ TASK_ALWAYS(HOOKS, hook_task, NULL, TASK_STACK_SIZE) \ + TASK_NOTEST(HOSTCMD, host_command_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(CONSOLE, console_task, NULL, LARGER_TASK_STACK_SIZE) \ TASK_ALWAYS(PD, pd_task, NULL, TASK_STACK_SIZE) diff --git a/chip/stm32/i2c-stm32f0.c b/chip/stm32/i2c-stm32f0.c index cd93965503..ab41b65b71 100644 --- a/chip/stm32/i2c-stm32f0.c +++ b/chip/stm32/i2c-stm32f0.c @@ -9,6 +9,7 @@ #include "console.h" #include "gpio.h" #include "hooks.h" +#include "host_command.h" #include "i2c.h" #include "registers.h" #include "task.h" @@ -25,6 +26,12 @@ /* Transmit timeout in microseconds */ #define I2C_TX_TIMEOUT_MASTER (10 * MSEC) +/* + * Max data size for a version 3 request/response packet. This is + * big enough for EC_CMD_GET_VERSION plus header info. + */ +#define I2C_MAX_HOST_PACKET_SIZE 128 + /** * Wait for ISR register to contain the specified mask. * @@ -92,6 +99,10 @@ static void i2c_init_port(const struct i2c_port_t *p) if (!(STM32_RCC_APB1ENR & (1 << (21 + port)))) STM32_RCC_APB1ENR |= 1 << (21 + port); + /* Default clock to i2c port 0 is HSI (8MHz). Change to SYSCLK. */ + if (port == 0) + STM32_RCC_CFGR3 |= 0x10; + /* Configure GPIOs */ gpio_config_module(MODULE_I2C, 1); @@ -99,6 +110,160 @@ static void i2c_init_port(const struct i2c_port_t *p) i2c_set_freq_port(p); } +/*****************************************************************************/ +#ifdef HAS_TASK_HOSTCMD +/* Host command slave */ +/* Buffer for host commands (including version, error code and checksum) */ +static uint8_t host_buffer[I2C_MAX_HOST_PACKET_SIZE]; +static uint8_t params_copy[I2C_MAX_HOST_PACKET_SIZE] __aligned(4); +static int host_i2c_resp_port; +static int tx_pending; +static struct host_packet i2c_packet; + +static void i2c_send_response_packet(struct host_packet *pkt) +{ + int size = pkt->response_size; + uint8_t *out = host_buffer; + int i = 0; + + /* Ignore host command in-progress */ + if (pkt->driver_result == EC_RES_IN_PROGRESS) + return; + + /* Write result and size to first two bytes. */ + *out++ = pkt->driver_result; + *out++ = size; + + /* Transmit data when I2C tx buffer is empty until finished. */ + while ((i < size + 2) && tx_pending) { + if (STM32_I2C_ISR(host_i2c_resp_port) & STM32_I2C_CR1_TXIE) + STM32_I2C_TXDR(host_i2c_resp_port) = host_buffer[i++]; + + /* I2C is slow, so let other things run while we wait */ + usleep(50); + } +} + +/* Process the command in the i2c host buffer */ +static void i2c_process_command(void) +{ + char *buff = host_buffer; + + /* + * TODO(crosbug.com/p/29241): Combine this functionality with the + * i2c_process_command function in chip/stm32/i2c-stm32f.c to make one + * host command i2c process function which handles all protocol + * versions. + */ + if (*buff >= EC_COMMAND_PROTOCOL_3) { + i2c_packet.send_response = i2c_send_response_packet; + + i2c_packet.request = (const void *)(&buff[1]); + i2c_packet.request_temp = params_copy; + i2c_packet.request_max = sizeof(params_copy); + /* Don't know the request size so pass in the entire buffer */ + i2c_packet.request_size = I2C_MAX_HOST_PACKET_SIZE; + + /* + * Stuff response at buff[2] to leave the first two bytes of + * buffer available for the result and size to send over i2c. + */ + i2c_packet.response = (void *)(&buff[2]); + i2c_packet.response_max = I2C_MAX_HOST_PACKET_SIZE; + i2c_packet.response_size = 0; + + i2c_packet.driver_result = EC_RES_SUCCESS; + } else { + /* Only host command protocol 3 is supported. */ + i2c_packet.driver_result = EC_RES_INVALID_HEADER; + } + host_packet_receive(&i2c_packet); +} + +static void i2c_event_handler(int port) +{ + int i2c_isr; + static int rx_pending, buf_idx; + + i2c_isr = STM32_I2C_ISR(port); + + /* + * Check for error conditions. Note, arbitration loss and bus error + * are the only two errors we can get as a slave allowing clock + * stretching and in non-SMBus mode. + */ + if (i2c_isr & (STM32_I2C_ISR_ARLO | STM32_I2C_ISR_BERR)) { + rx_pending = 0; + tx_pending = 0; + + /* Make sure TXIS interrupt is disabled */ + STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_TXIE; + + /* Clear error status bits */ + STM32_I2C_ICR(port) |= STM32_I2C_ICR_BERRCF | + STM32_I2C_ICR_ARLOCF; + } + + /* Transfer matched our slave address */ + if (i2c_isr & STM32_I2C_ISR_ADDR) { + if (i2c_isr & STM32_I2C_ISR_DIR) { + /* Transmitter slave */ + /* Clear transmit buffer */ + STM32_I2C_ISR(port) |= STM32_I2C_ISR_TXE; + + /* Enable txis interrupt to start response */ + STM32_I2C_CR1(port) |= STM32_I2C_CR1_TXIE; + } else { + /* Receiver slave */ + buf_idx = 0; + rx_pending = 1; + } + + /* Clear ADDR bit by writing to ADDRCF bit */ + STM32_I2C_ICR(port) |= STM32_I2C_ICR_ADDRCF; + } + + /* Stop condition on bus */ + if (i2c_isr & STM32_I2C_ISR_STOP) { + rx_pending = 0; + tx_pending = 0; + + /* Make sure TXIS interrupt is disabled */ + STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_TXIE; + + /* Clear STOPF bit by writing to STOPCF bit */ + STM32_I2C_ICR(port) |= STM32_I2C_ICR_STOPCF; + } + + /* Receiver full event */ + if (i2c_isr & STM32_I2C_ISR_RXNE) + host_buffer[buf_idx++] = STM32_I2C_RXDR(port); + + /* Transmitter empty event */ + if (i2c_isr & STM32_I2C_ISR_TXIS) { + if (port == I2C_PORT_EC) { /* host is waiting for PD response */ + if (rx_pending) { + host_i2c_resp_port = port; + /* + * Disable TXIS interrupt, transmission will + * be done by host command task. + */ + STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_TXIE; + + i2c_process_command(); + /* Reset host buffer after end of transfer */ + rx_pending = 0; + tx_pending = 1; + } else { + STM32_I2C_TXDR(port) = 0xec; + } + } + } +} +void i2c2_event_interrupt(void) { i2c_event_handler(I2C_PORT_EC); } +DECLARE_IRQ(STM32_IRQ_I2C1, i2c2_event_interrupt, 2); +#endif + /*****************************************************************************/ /* Interface */ @@ -255,5 +420,34 @@ static void i2c_init(void) for (i = 0; i < i2c_ports_used; i++, p++) i2c_init_port(p); + +#ifdef HAS_TASK_HOSTCMD + STM32_I2C_CR1(I2C_PORT_EC) |= STM32_I2C_CR1_RXIE | STM32_I2C_CR1_ERRIE + | STM32_I2C_CR1_ADDRIE | STM32_I2C_CR1_STOPIE; + STM32_I2C_OAR1(I2C_PORT_EC) = 0x8000 | CONFIG_USB_PD_I2C_SLAVE_ADDR; + task_enable_irq(STM32_IRQ_I2C1); +#endif } DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT); + +/** + * Get protocol information + */ +static int i2c_get_protocol_info(struct host_cmd_handler_args *args) +{ + struct ec_response_get_protocol_info *r = args->response; + + memset(r, 0, sizeof(*r)); + r->protocol_versions = (1 << 3); + r->max_request_packet_size = I2C_MAX_HOST_PACKET_SIZE; + r->max_response_packet_size = I2C_MAX_HOST_PACKET_SIZE; + r->flags = 0; + + args->response_size = sizeof(*r); + + return EC_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO, + i2c_get_protocol_info, + EC_VER_MASK(0)); + diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h index c3e0f19e49..4e9dbd1419 100644 --- a/chip/stm32/registers.h +++ b/chip/stm32/registers.h @@ -407,6 +407,12 @@ typedef volatile struct timer_ctlr timer_ctlr_t; #ifdef CHIP_FAMILY_STM32F0 #define STM32_I2C_CR1(n) REG32(stm32_i2c_reg(n, 0x00)) #define STM32_I2C_CR1_PE (1 << 0) +#define STM32_I2C_CR1_TXIE (1 << 1) +#define STM32_I2C_CR1_RXIE (1 << 2) +#define STM32_I2C_CR1_ADDRIE (1 << 3) +#define STM32_I2C_CR1_NACKIE (1 << 4) +#define STM32_I2C_CR1_STOPIE (1 << 5) +#define STM32_I2C_CR1_ERRIE (1 << 7) #define STM32_I2C_CR2(n) REG32(stm32_i2c_reg(n, 0x04)) #define STM32_I2C_CR2_RD_WRN (1 << 10) #define STM32_I2C_CR2_START (1 << 13) @@ -419,15 +425,28 @@ typedef volatile struct timer_ctlr timer_ctlr_t; #define STM32_I2C_TIMINGR(n) REG32(stm32_i2c_reg(n, 0x10)) #define STM32_I2C_TIMEOUTR(n) REG32(stm32_i2c_reg(n, 0x14)) #define STM32_I2C_ISR(n) REG32(stm32_i2c_reg(n, 0x18)) +#define STM32_I2C_ISR_TXE (1 << 0) #define STM32_I2C_ISR_TXIS (1 << 1) #define STM32_I2C_ISR_RXNE (1 << 2) +#define STM32_I2C_ISR_ADDR (1 << 3) #define STM32_I2C_ISR_NACK (1 << 4) #define STM32_I2C_ISR_STOP (1 << 5) #define STM32_I2C_ISR_TC (1 << 6) #define STM32_I2C_ISR_BERR (1 << 8) #define STM32_I2C_ISR_ARLO (1 << 9) +#define STM32_I2C_ISR_OVR (1 << 10) +#define STM32_I2C_ISR_PECERR (1 << 11) +#define STM32_I2C_ISR_TIMEOUT (1 << 12) +#define STM32_I2C_ISR_ALERT (1 << 13) #define STM32_I2C_ISR_BUSY (1 << 15) +#define STM32_I2C_ISR_DIR (1 << 16) #define STM32_I2C_ICR(n) REG32(stm32_i2c_reg(n, 0x1C)) +#define STM32_I2C_ICR_ADDRCF (1 << 3) +#define STM32_I2C_ICR_STOPCF (1 << 5) +#define STM32_I2C_ICR_BERRCF (1 << 8) +#define STM32_I2C_ICR_ARLOCF (1 << 9) +#define STM32_I2C_ICR_OVRCF (1 << 10) +#define STM32_I2C_ICR_TIMEOUTCF (1 << 12) #define STM32_I2C_PECR(n) REG32(stm32_i2c_reg(n, 0x20)) #define STM32_I2C_RXDR(n) REG32(stm32_i2c_reg(n, 0x24)) #define STM32_I2C_TXDR(n) REG32(stm32_i2c_reg(n, 0x28)) diff --git a/common/build.mk b/common/build.mk index e8a5900695..18c1c47579 100644 --- a/common/build.mk +++ b/common/build.mk @@ -43,6 +43,7 @@ common-$(CONFIG_EXTPOWER_SPRING)+=extpower_spring.o common-$(CONFIG_FANS)+=fan.o common-$(CONFIG_FLASH)+=flash.o common-$(CONFIG_FMAP)+=fmap.o +common-$(CONFIG_HOST_CMD_MASTER)+=host_command_master.o common-$(CONFIG_I2C)+=i2c.o common-$(CONFIG_I2C_ARBITRATION)+=i2c_arbitration.o common-$(CONFIG_KEYBOARD_PROTOCOL_8042)+=keyboard_8042.o diff --git a/common/host_command.c b/common/host_command.c index 36344ac7ec..9f72fe7f43 100644 --- a/common/host_command.c +++ b/common/host_command.c @@ -166,6 +166,11 @@ void host_command_received(struct host_cmd_handler_args *args) return; } + /* + * TODO (crosbug.com/p/29315): This is typically running in interrupt + * context, so it woud be better not to send the response here, and to + * let the host command task send the response. + */ /* Send the response now */ host_send_response(args); } @@ -331,6 +336,11 @@ void host_packet_receive(struct host_packet *pkt) return; host_packet_bad: + /* + * TODO (crosbug.com/p/29315): This is typically running in interrupt + * context, so it woud be better not to send the response here, and to + * let the host command task send the response. + */ /* Improperly formed packet from host, so send an error response */ host_packet_respond(&args0); } diff --git a/common/host_command_master.c b/common/host_command_master.c new file mode 100644 index 0000000000..55e3befbee --- /dev/null +++ b/common/host_command_master.c @@ -0,0 +1,185 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Host command master module for Chrome EC */ + +#include "common.h" +#include "console.h" +#include "host_command.h" +#include "i2c.h" +#include "util.h" + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_HOSTCMD, outstr) +#define CPRINTF(format, args...) cprintf(CC_HOSTCMD, format, ## args) + +/* + * Sends a command to the PD (protocol v3). + * + * Returns >= 0 for success, or negative if error. + */ +int pd_host_command(int command, int version, + const void *outdata, int outsize, + void *indata, int insize) +{ + int ret, i; + int resp_len; + struct ec_host_request rq; + struct ec_host_response rs; + static uint8_t req_buf[EC_LPC_HOST_PACKET_SIZE]; + static uint8_t resp_buf[EC_LPC_HOST_PACKET_SIZE]; + uint8_t sum = 0; + const uint8_t *c; + uint8_t *d; + + /* Fail if output size is too big */ + if (outsize + sizeof(rq) > EC_LPC_HOST_PACKET_SIZE) + return -EC_RES_REQUEST_TRUNCATED; + + /* Fill in request packet */ + rq.struct_version = EC_HOST_REQUEST_VERSION; + rq.checksum = 0; + rq.command = command; + rq.command_version = version; + rq.reserved = 0; + rq.data_len = outsize; + + /* Copy data and start checksum */ + for (i = 0, c = (const uint8_t *)outdata; i < outsize; i++, c++) { + req_buf[sizeof(rq) + 1 + i] = *c; + sum += *c; + } + + /* Finish checksum */ + for (i = 0, c = (const uint8_t *)&rq; i < sizeof(rq); i++, c++) + sum += *c; + + /* Write checksum field so the entire packet sums to 0 */ + rq.checksum = (uint8_t)(-sum); + + /* Copy header */ + for (i = 0, c = (const uint8_t *)&rq; i < sizeof(rq); i++, c++) + req_buf[1 + i] = *c; + + /* Set command to use protocol v3 */ + req_buf[0] = EC_COMMAND_PROTOCOL_3; + + /* + * Transmit all data and receive 2 bytes for return value and response + * length. + */ + i2c_lock(I2C_PORT_PD_MCU, 1); + ret = i2c_xfer(I2C_PORT_PD_MCU, CONFIG_USB_PD_I2C_SLAVE_ADDR, + &req_buf[0], outsize + sizeof(rq) + 1, &resp_buf[0], + 2, I2C_XFER_START); + i2c_lock(I2C_PORT_PD_MCU, 0); + if (ret) { + CPRINTF("[%T i2c transaction 1 failed: %d]\n", ret); + return -ret; + } + + ret = resp_buf[0]; + resp_len = resp_buf[1]; + + if (ret) + CPRINTF("[%T command 0x%02x returned error %d]\n", command, + ret); + + if (resp_len > (insize + sizeof(rs))) { + CPRINTF("[%T response size is too large %d > %d]\n", + resp_len, insize + sizeof(rs)); + return -EC_RES_RESPONSE_TOO_BIG; + } + + /* Receive remaining data */ + i2c_lock(I2C_PORT_PD_MCU, 1); + ret = i2c_xfer(I2C_PORT_PD_MCU, CONFIG_USB_PD_I2C_SLAVE_ADDR, 0, 0, + &resp_buf[2], resp_len, I2C_XFER_STOP); + i2c_lock(I2C_PORT_PD_MCU, 0); + if (ret) { + CPRINTF("[%T i2c transaction 2 failed: %d]\n", ret); + return -ret; + } + + /* Read back response header and start checksum */ + sum = 0; + for (i = 0, d = (uint8_t *)&rs; i < sizeof(rs); i++, d++) { + *d = resp_buf[i + 2]; + sum += *d; + } + + if (rs.struct_version != EC_HOST_RESPONSE_VERSION) { + CPRINTF("[%T PD response version mismatch]\n"); + return -EC_RES_INVALID_RESPONSE; + } + + if (rs.reserved) { + CPRINTF("[%T PD response reserved != 0]\n"); + return -EC_RES_INVALID_RESPONSE; + } + + if (rs.data_len > insize) { + CPRINTF("[%T PD returned too much data]\n"); + return -EC_RES_RESPONSE_TOO_BIG; + } + + /* Read back data and update checksum */ + resp_len -= sizeof(rs); + for (i = 0, d = (uint8_t *)indata; i < resp_len; i++, d++) { + *d = resp_buf[sizeof(rs) + i + 2]; + sum += *d; + } + + + if ((uint8_t)sum) { + CPRINTF("[%T command 0x%02x bad checksum returned: " + "%d]\n", command, sum); + return -EC_RES_ERROR; + } + + /* Return output buffer size */ + return resp_len; +} + +static int command_pd_mcu(int argc, char **argv) +{ + char *e; + static char outbuf[128]; + static char inbuf[128]; + int command, version; + int i, ret, tmp; + + if (argc < 3) + return EC_ERROR_PARAM_COUNT; + + command = strtoi(argv[1], &e, 0); + if (*e) + return EC_ERROR_PARAM1; + + version = strtoi(argv[2], &e, 0); + if (*e) + return EC_ERROR_PARAM2; + + for (i = 3; i < argc; i++) { + tmp = strtoi(argv[i], &e, 0); + if (*e) + return EC_ERROR_PARAM3; + outbuf[i-3] = tmp; + } + + ret = pd_host_command(command, version, &outbuf, argc - 3, &inbuf, + sizeof(inbuf)); + + ccprintf("Host command 0x%02x, returned %d\n", command, ret); + for (i = 0; i < ret; i++) + ccprintf("0x%02x\n", inbuf[i]); + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(pdcmd, command_pd_mcu, + "cmd ver [params]", + "Send PD host command", + NULL); + diff --git a/include/config.h b/include/config.h index 4700e00dbb..86b43d2509 100644 --- a/include/config.h +++ b/include/config.h @@ -496,6 +496,9 @@ /*****************************************************************************/ +/* Support EC acting as host master for other MCUs. */ +#undef CONFIG_HOST_CMD_MASTER + /* * Support the host asking the EC about the status of the most recent host * command. @@ -860,6 +863,9 @@ /* USB PD transmit uses SPI master */ #undef CONFIG_USB_PD_TX_USES_SPI_MASTER +/* USB PD MCU slave address for host commands */ +#define CONFIG_USB_PD_I2C_SLAVE_ADDR 0x3c + /* Support simple control of power to the device's USB ports */ #undef CONFIG_USB_PORT_POWER_DUMB diff --git a/include/host_command.h b/include/host_command.h index 158367a80b..1fbf0b6554 100644 --- a/include/host_command.h +++ b/include/host_command.h @@ -207,4 +207,18 @@ void host_packet_receive(struct host_packet *pkt); */ void host_throttle_cpu(int throttle); +/** + * Send host command to PD MCU. + * + * @param command Host command number + * @param version Version of host command + * @param outdata Pointer to buffer of out data + * @param outsize Size of buffer to out data + * @param indata Pointer to buffer to store response + * @param insize Size of buffer to store response + */ +int pd_host_command(int command, int version, + const void *outdata, int outsize, + void *indata, int insize); + #endif /* __CROS_EC_HOST_COMMAND_H */ -- cgit v1.2.1