diff options
author | Jan Dabros <jsd@semihalf.com> | 2020-11-27 18:35:00 +0100 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-12-08 19:23:53 +0000 |
commit | b64904e81c66071aa39adb1bd6e9e001f876ef4d (patch) | |
tree | 3a91c0013f2e7f35cd69ebff91098901baa0ec00 /driver | |
parent | 22c30a4e561254d3e63303646cfb3d597abc4d0d (diff) | |
download | chrome-ec-b64904e81c66071aa39adb1bd6e9e001f876ef4d.tar.gz |
gl3590: Add initial support for USB HUB I2C interface
GL3590 is a USB hub with support for out-of-band transactions via I2C.
Set of additional registers allow to disable/enable ports, query port
status (including power-related information), handle over-current events
and others.
This initial driver implements method for reading and writing from/to
GL3590 registers as well as event handler to parse asynchronous messages
from hub.
BUG:b:150323106,b:169929627
BRANCH:master
TEST:buildall. Functionality can be tested on servo_v4p1 with a
consecutive commit applied.
Signed-off-by: Jan Dabros <jsd@semihalf.com>
Change-Id: I385ff4790ddc367d07b7e588f4e10aeb83e9f1c0
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2563686
Reviewed-by: Brian Nemec <bnemec@chromium.org>
Signed-off-by: Abe Levkoy <alevkoy@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2580261
Diffstat (limited to 'driver')
-rw-r--r-- | driver/build.mk | 3 | ||||
-rw-r--r-- | driver/gl3590.c | 144 | ||||
-rw-r--r-- | driver/gl3590.h | 19 |
3 files changed, 166 insertions, 0 deletions
diff --git a/driver/build.mk b/driver/build.mk index 33c61a4d7d..6a752ce72a 100644 --- a/driver/build.mk +++ b/driver/build.mk @@ -175,6 +175,9 @@ driver-$(CONFIG_USB_MUX_PS8742)+=usb_mux/ps8740.o driver-$(CONFIG_USB_MUX_PS8743)+=usb_mux/ps8743.o driver-$(CONFIG_USB_MUX_VIRTUAL)+=usb_mux/virtual.o +# USB Hub with I2C interface +driver-$(CONFIG_USB_HUB_GL3590)+=gl3590.o + # Type-C Power Path Controllers (PPC) driver-$(CONFIG_USBC_PPC_AOZ1380)+=ppc/aoz1380.o driver-$(CONFIG_USBC_PPC_SN5S330)+=ppc/sn5s330.o diff --git a/driver/gl3590.c b/driver/gl3590.c new file mode 100644 index 0000000000..bde2971553 --- /dev/null +++ b/driver/gl3590.c @@ -0,0 +1,144 @@ +#include <console.h> +#include <i2c.h> +#include <system.h> +#include <util.h> + +#include <gl3590.h> + +/* GL3590 is unique in terms of i2c_read, since it doesn't support repeated + * start sequence. One need to issue two separate transactions - first is write + * with a register offset, then after a delay second transaction is actual read. + */ +int gl3590_read(int hub, uint8_t reg, uint8_t *data, int count) +{ + int rv; + struct uhub_i2c_iface_t *uhub_p = &uhub_config[hub]; + + i2c_lock(uhub_p->i2c_host_port, 1); + rv = i2c_xfer_unlocked(uhub_p->i2c_host_port, + uhub_p->i2c_addr, + ®, 1, + NULL, 0, + I2C_XFER_SINGLE); + i2c_lock(uhub_p->i2c_host_port, 0); + + if (rv) + return rv; + + /* GL3590 requires at least 300us before data is ready */ + udelay(400); + + i2c_lock(uhub_p->i2c_host_port, 1); + rv = i2c_xfer_unlocked(uhub_p->i2c_host_port, + uhub_p->i2c_addr, + NULL, 0, + data, count, + I2C_XFER_SINGLE); + i2c_lock(uhub_p->i2c_host_port, 0); + + return rv; +}; + +int gl3590_write(int hub, uint8_t reg, uint8_t *data, int count) +{ + int rv; + uint8_t buf[5]; + struct uhub_i2c_iface_t *uhub_p = &uhub_config[hub]; + + /* GL3590 registers accept 4 bytes at max */ + if (count > (sizeof(buf) - 1)) { + ccprintf("Too many bytes to write"); + return EC_ERROR_INVAL; + } + + buf[0] = reg; + memcpy(&buf[1], data, count); + + i2c_lock(uhub_p->i2c_host_port, 1); + rv = i2c_xfer_unlocked(uhub_p->i2c_host_port, + uhub_p->i2c_addr, + buf, count + 1, + NULL, 0, + I2C_XFER_SINGLE); + i2c_lock(uhub_p->i2c_host_port, 0); + + return rv; +} + +void gl3590_irq_handler(int hub) +{ + uint8_t buf = 0; + uint8_t res_reg[2]; + + /* Verify that irq is pending */ + if (gl3590_read(hub, GL3590_INT_REG, &buf, sizeof(buf))) { + ccprintf("Cannot read from the host hub i2c\n"); + goto exit; + } + + if ((buf & GL3590_INT_PENDING) == 0) { + ccprintf("Invalid hub event\n"); + goto exit; + } + + /* Get the hub event reason */ + if (gl3590_read(hub, GL3590_RESPONSE_REG, res_reg, sizeof(res_reg))) { + ccprintf("Cannot read from the host hub i2c\n"); + goto exit; + } + + if ((res_reg[0] & GL3590_RESPONSE_REG_SYNC_MASK) == 0) + ccprintf("Host hub response: "); + else + ccprintf("Host hub event! "); + + switch(res_reg[0]) { + case 0x0: + ccprintf("No response"); + break; + case 0x1: + ccprintf("Successful"); + break; + case 0x2: + ccprintf("Invalid command"); + break; + case 0x3: + ccprintf("Invalid arguments"); + break; + case 0x4: + ccprintf("Invalid port: %d", res_reg[1]); + break; + case 0x5: + ccprintf("Command not completed"); + break; + case 0x80: + ccprintf("Reset complete"); + break; + case 0x81: + ccprintf("Power operation mode change"); + break; + case 0x82: + ccprintf("Connect change"); + break; + case 0x83: + ccprintf("Error on the specific port"); + break; + case 0x84: + ccprintf("Hub state change"); + break; + case 0x85: + ccprintf("SetFeature PORT_POWER failure"); + break; + default: + ccprintf("Unknown value: 0x%0x", res_reg[0]); + } + ccprintf("\n"); + + if (res_reg[1]) + ccprintf("Affected port %d\n", res_reg[1]); + +exit: + /* Try to clear interrupt */ + buf = GL3590_INT_CLEAR; + gl3590_write(hub, GL3590_INT_REG, &buf, sizeof(buf)); +} diff --git a/driver/gl3590.h b/driver/gl3590.h new file mode 100644 index 0000000000..ef018d0685 --- /dev/null +++ b/driver/gl3590.h @@ -0,0 +1,19 @@ +/* Registers definitions */ +#define GL3590_INT_REG 0x1 +#define GL3590_INT_PENDING 0x1 +#define GL3590_INT_CLEAR 0x1 +#define GL3590_RESPONSE_REG 0x2 +#define GL3590_RESPONSE_REG_SYNC_MASK 0x80 + +#define GL3590_I2C_ADDR0 0x50 + +int gl3590_read(int hub, uint8_t reg, uint8_t *data, int count); +int gl3590_write(int hub, uint8_t reg, uint8_t *data, int count); +void gl3590_irq_handler(int hub); + +/* Generic USB HUB I2C interface */ +struct uhub_i2c_iface_t { + int i2c_host_port; + int i2c_addr; +}; +extern struct uhub_i2c_iface_t uhub_config[]; |