/* 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. * * Megachips DisplayPort to HDMI protocol converter / level shifter driver. */ #include "config.h" #include "console.h" #include "common.h" #include "ec_commands.h" #include "mcdp28x0.h" #include "queue.h" #include "queue_policies.h" #include "timer.h" #include "usart-stm32f0.h" #include "util.h" #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) static uint8_t mcdp_inbuf[MCDP_INBUF_MAX]; #undef MCDP_DEBUG #ifdef MCDP_DEBUG static inline void print_buffer(uint8_t *buf, int cnt) { int i; CPRINTF("buf:"); for (i = 0; i < cnt; i++) { if (i && !(i % 4)) CPRINTF("\n "); CPRINTF("[%02d]0x%02x ", i, buf[i]); } CPRINTF("\n"); } #else static inline void print_buffer(uint8_t *buf, int cnt) {} #endif static struct usart_config const usart_mcdp; struct queue const usart_mcdp_rx_queue = QUEUE_DIRECT(MCDP_INBUF_MAX, uint8_t, usart_mcdp.producer, null_consumer); struct queue const usart_mcdp_tx_queue = QUEUE_DIRECT(MCDP_OUTBUF_MAX, uint8_t, null_producer, usart_mcdp.consumer); static struct usart_config const usart_mcdp = USART_CONFIG(CONFIG_MCDP28X0, usart_rx_interrupt, usart_tx_interrupt, 115200, 0, usart_mcdp_rx_queue, usart_mcdp_tx_queue); /** * Compute checksum. * * @seed initial value of checksum. * @msg message bytes to compute checksum on. * @cnt count of message bytes. * @return partial checksum. */ static uint8_t compute_checksum(uint8_t seed, const uint8_t *msg, int cnt) { int i; uint8_t chksum = seed; for (i = 0; i < cnt; i++) chksum += msg[i]; return ~chksum + 1; } /** * transmit message over serial * * Packet consists of: * msg[0] == length of entire packet * msg[1] == 1st message byte (typically command) * msg[cnt+1] == last message byte * msg[cnt+2] == checksum * * @msg message bytes * @cnt count of message bytes * @return zero if success, error code otherwise */ static int tx_serial(const uint8_t *msg, int cnt) { uint8_t out = cnt + 2; /* 1st byte (not in msg) is always cnt + 2, so seed chksum with that */ uint8_t chksum = compute_checksum(cnt + 2, msg, cnt); if (queue_add_unit(&usart_mcdp_tx_queue, &out) != 1) return MCDP_ERROR_TX_CNT; if (queue_add_units(&usart_mcdp_tx_queue, msg, cnt) != cnt) return MCDP_ERROR_TX_BODY; print_buffer((uint8_t *)msg, cnt); if (queue_add_unit(&usart_mcdp_tx_queue, &chksum) != 1) return MCDP_ERROR_TX_CHKSUM; return MCDP_SUCCESS; } /** * receive message over serial * * While definitive documentation is lacking its believed the following receive * packet is always true. * * Packet consists of: * msg[0] == length of entire packet * msg[1] == 1st message byte (typically command) * msg[cnt+2] == last message byte * msg[cnt+3] == checksum * * @msg pointer to buffer to read message into * @cnt count of message bytes * @return zero if success, error code otherwise */ static int rx_serial(uint8_t *msg, int cnt) { size_t read; int retry = 2; read = queue_remove_units(&usart_mcdp_rx_queue, msg, cnt); while ((read < cnt) && retry) { usleep(100*MSEC); read += queue_remove_units(&usart_mcdp_rx_queue, msg + read, cnt - read); retry--; } print_buffer(msg, cnt); /* Some response sizes are dynamic so shrink cnt accordingly. */ if (cnt > msg[0]) cnt = msg[0]; if (msg[cnt-1] != compute_checksum(0, msg, cnt-1)) return MCDP_ERROR_CHKSUM; if (read != cnt) { CPRINTF("rx_serial: read bytes %d != %d cnt\n", read, cnt); return MCDP_ERROR_RX_BYTES; } return MCDP_SUCCESS; } static int rx_serial_ack(void) { int rv = rx_serial(mcdp_inbuf, 3); if (rv) return rv; if (mcdp_inbuf[1] != MCDP_CMD_ACK) return MCDP_ERROR_RX_ACK; return rv; } void mcdp_enable(void) { usart_init(&usart_mcdp); } void mcdp_disable(void) { usart_shutdown(&usart_mcdp); } int mcdp_get_info(struct mcdp_info *info) { const uint8_t msg[2] = {MCDP_CMD_APPSTEST, 0x28}; int rv = tx_serial(msg, sizeof(msg)); if (rv) return rv; rv = rx_serial_ack(); if (rv) return rv; /* chksum is unreliable ... don't check */ rx_serial(mcdp_inbuf, MCDP_RSP_LEN(MCDP_LEN_GETINFO)); memcpy(info, &mcdp_inbuf[2], MCDP_LEN_GETINFO); return rv; } #ifdef CONFIG_CMD_MCDP static int mcdp_get_dev_id(char *dev, uint8_t dev_id, int dev_cnt) { uint8_t msg[2]; int rv; msg[0] = MCDP_CMD_GETDEVID; msg[1] = dev_id; rv = tx_serial(msg, sizeof(msg)); if (rv) return rv; rv = rx_serial(mcdp_inbuf, sizeof(mcdp_inbuf)); if (rv) return rv; memcpy(dev, &mcdp_inbuf[2], mcdp_inbuf[0] - 3); dev[mcdp_inbuf[0] - 3] = '\0'; return EC_SUCCESS; } static int mcdp_appstest(uint8_t cmd, int paramc, char **paramv) { uint8_t msg[6]; char *e; int i; int rv = MCDP_SUCCESS; /* setup any appstest params */ msg[0] = MCDP_CMD_APPSTESTPARAM; for (i = 0; i < paramc; i++) { uint32_t param = strtoi(paramv[i], &e, 10); if (*e) return EC_ERROR_PARAM1; msg[1] = i + 1; msg[2] = (param >> 24) & 0xff; msg[3] = (param >> 16) & 0xff; msg[4] = (param >> 8) & 0xff; msg[5] = (param >> 0) & 0xff; rv = tx_serial(msg, sizeof(msg)); if (rv) return rv; rv = rx_serial_ack(); if (rv) return rv; } msg[0] = MCDP_CMD_APPSTEST; msg[1] = cmd; rv = tx_serial(msg, 2); if (rv) return rv; rv = rx_serial_ack(); if (rv) return rv; /* magic */ rx_serial(mcdp_inbuf, sizeof(mcdp_inbuf)); rx_serial(mcdp_inbuf, sizeof(mcdp_inbuf)); return EC_SUCCESS; } int command_mcdp(int argc, char **argv) { int rv = EC_SUCCESS; char *e; if (argc < 2) return EC_ERROR_PARAM_COUNT; mcdp_enable(); if (!strncasecmp(argv[1], "info", 4)) { struct mcdp_info info; rv = mcdp_get_info(&info); if (!rv) ccprintf("family:%04x chipid:%04x irom:%d.%d.%d " "fw:%d.%d.%d\n", MCDP_FAMILY(info.family), MCDP_CHIPID(info.chipid), info.irom.major, info.irom.minor, info.irom.build, info.fw.major, info.fw.minor, info.fw.build); } else if (!strncasecmp(argv[1], "devid", 4)) { uint8_t dev_id = strtoi(argv[2], &e, 10); char dev[32]; if (*e) return EC_ERROR_PARAM2; else rv = mcdp_get_dev_id(dev, dev_id, 32); if (!rv) ccprintf("devid[%d] = %s\n", dev_id, dev); } else if (!strncasecmp(argv[1], "appstest", 4)) { uint8_t cmd = strtoi(argv[2], &e, 10); if (*e) return EC_ERROR_PARAM2; else rv = mcdp_appstest(cmd, argc - 3, &argv[3]); if (!rv) ccprintf("appstest[%d] completed\n", cmd); } else { return EC_ERROR_PARAM1; } mcdp_disable(); if (rv) ccprintf("mcdp_error:%d\n", rv); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(mcdp, command_mcdp, "info|devid |appstest []", "USB PD"); #endif /* CONFIG_CMD_MCDP */