summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCHLin <CHLIN56@nuvoton.com>2019-08-05 16:23:26 +0800
committerCommit Bot <commit-bot@chromium.org>2019-08-21 04:58:23 +0000
commitac780ae08f9cc880b8606937e03e4d8193e1022a (patch)
treec14d70ba0fabb070f8dd50f1496d6a308d770fcc
parent2fb1836646a1f4edcea1f22408535872b833d14e (diff)
downloadchrome-ec-ac780ae08f9cc880b8606937e03e4d8193e1022a.tar.gz
driver: IO expander: nct38xx: add the interrupt support
The IO pins of Nuvoton TCPC NCT38XX chips have the ability to support the interrupt function. This commit adds the driver to support it. However, please note that if the system needs to use an IO on NCT38XX to support the interrupt, the following two consideration should be taken into account. 1. Interrupt latency: Because it requires to access the registers of NCT38XX via I2C transaction to know the interrupt event, there is some added latency for the interrupt handling. If the interrupt requires short latency, we do not recommend to connect such a signal to the NCT38XX. 2. Shared ALERT pin: Because the ALERT pin is shared also with the TCPC ALERT, we do not recommend to connect any signal that may generate a high rate of interrupts so it will not interfere with the normal work of the TCPC. BRANCH=none BUG=none TEST=No error for "make buildall" TEST=Apply this and related CLs, manually test each IO pins; make sure each pin's interrupt handler is correctly executed. Change-Id: I72d835557913d87097b2e0d82165e40fe132ca77 Signed-off-by: CHLin <CHLIN56@nuvoton.com> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/1734948 Reviewed-by: Edward Hill <ecgh@chromium.org> Commit-Queue: CH Lin <chlin56@nuvoton.com> Tested-by: CH Lin <chlin56@nuvoton.com>
-rw-r--r--baseboard/zork/baseboard.c10
-rw-r--r--driver/ioexpander_nct38xx.c344
-rw-r--r--driver/ioexpander_nct38xx.h16
-rw-r--r--driver/tcpm/nct38xx.c39
-rw-r--r--driver/tcpm/nct38xx.h39
-rw-r--r--driver/tcpm/tcpci.h1
-rw-r--r--include/ioexpander.h5
7 files changed, 389 insertions, 65 deletions
diff --git a/baseboard/zork/baseboard.c b/baseboard/zork/baseboard.c
index e160c7b38d..1505405071 100644
--- a/baseboard/zork/baseboard.c
+++ b/baseboard/zork/baseboard.c
@@ -315,7 +315,7 @@ const struct tcpc_config_t tcpc_config[] = {
.bus_type = EC_BUS_TYPE_I2C,
.i2c_info = {
.port = I2C_PORT_TCPC0,
- .addr_flags = NCT38xx_I2C_ADDR1_1_FLAGS,
+ .addr_flags = NCT38XX_I2C_ADDR1_1_FLAGS,
},
.drv = &nct38xx_tcpm_drv,
},
@@ -323,7 +323,7 @@ const struct tcpc_config_t tcpc_config[] = {
.bus_type = EC_BUS_TYPE_I2C,
.i2c_info = {
.port = I2C_PORT_TCPC1,
- .addr_flags = NCT38xx_I2C_ADDR1_1_FLAGS,
+ .addr_flags = NCT38XX_I2C_ADDR1_1_FLAGS,
},
.drv = &nct38xx_tcpm_drv,
},
@@ -453,14 +453,12 @@ BUILD_ASSERT(ARRAY_SIZE(usb_muxes) == USBC_PORT_COUNT);
struct ioexpander_config_t ioex_config[] = {
[USBC_PORT_C0] = {
.i2c_host_port = I2C_PORT_TCPC0,
- .i2c_slave_addr = NCT38xx_I2C_ADDR1_1_FLAGS,
- .chip_info = -1,
+ .i2c_slave_addr = NCT38XX_I2C_ADDR1_1_FLAGS,
.drv = &nct38xx_ioexpander_drv,
},
[USBC_PORT_C1] = {
.i2c_host_port = I2C_PORT_TCPC1,
- .i2c_slave_addr = NCT38xx_I2C_ADDR1_1_FLAGS,
- .chip_info = -1,
+ .i2c_slave_addr = NCT38XX_I2C_ADDR1_1_FLAGS,
.drv = &nct38xx_ioexpander_drv,
},
};
diff --git a/driver/ioexpander_nct38xx.c b/driver/ioexpander_nct38xx.c
index 3baca77ddf..e7fe3d9453 100644
--- a/driver/ioexpander_nct38xx.c
+++ b/driver/ioexpander_nct38xx.c
@@ -16,9 +16,22 @@
#define CPRINTF(format, args...) cprintf(CC_GPIO, format, ## args)
#define CPRINTS(format, args...) cprints(CC_GPIO, format, ## args)
-static int nct38xx_ioex_check_is_valid(int chip_info, int port, int mask)
+/*
+ * Store the GPIO_ALERT_MASK_0/1 and chip ID registers locally. In this way,
+ * we don't have to read it via I2C transaction everytime.
+ */
+struct nct38xx_chip_data {
+ uint8_t int_mask[2];
+ int chip_id;
+};
+
+static struct nct38xx_chip_data chip_data[CONFIG_IO_EXPANDER_PORT_COUNT] = {
+ [0 ... (CONFIG_IO_EXPANDER_PORT_COUNT - 1)] = { {0, 0}, -1 }
+};
+
+static int nct38xx_ioex_check_is_valid(int ioex, int port, int mask)
{
- if (chip_info == NCT38XX_VARIANT_3808) {
+ if (chip_data[ioex].chip_id == NCT38XX_VARIANT_3808) {
if (port == 1) {
CPRINTF("Port 1 is not support in NCT3808\n");
return EC_ERROR_INVAL;
@@ -32,7 +45,6 @@ static int nct38xx_ioex_check_is_valid(int chip_info, int port, int mask)
}
return EC_SUCCESS;
-
}
static int nct38xx_ioex_init(int ioex)
@@ -48,54 +60,73 @@ static int nct38xx_ioex_init(int ioex)
rv = i2c_read8(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
TCPC_REG_BCD_DEV, &val);
- if (rv)
+ if (rv != EC_SUCCESS) {
CPRINTF("Failed to read NCT38XX DEV ID for IOexpander %d\n",
ioex);
- else
- ioex_p->chip_info =
- ((uint8_t)val & NCT38XX_VARIANT_MASK) >> 2;
+ return rv;
+ }
- return rv;
+ chip_data[ioex].chip_id = ((uint8_t)val & NCT38XX_VARIANT_MASK) >> 2;
+
+ /*
+ * NCT38XX uses the Vendor Define bit in the ALERT event to indicate
+ * that an IOEX IO's interrupt is triggered.
+ * Normally, The ALERT MASK for Vendor Define event should be set by
+ * the NCT38XX TCPCI driver's init function.
+ * However, it should be also set here if we want to test the interrupt
+ * function of IOEX when the NCT38XX TCPCI driver is not included.
+ */
+ if (!IS_ENABLED(CONFIG_USB_PD_TCPM_NCT38XX)) {
+ rv = i2c_write16(ioex_p->i2c_host_port,
+ ioex_p->i2c_slave_addr, TCPC_REG_ALERT_MASK,
+ TCPC_REG_ALERT_VENDOR_DEF);
+ if (rv != EC_SUCCESS)
+ return rv;
+ }
+ return EC_SUCCESS;
}
static int nct38xx_ioex_get_level(int ioex, int port, int mask, int *val)
{
int rv, reg;
- struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
- rv = nct38xx_ioex_check_is_valid(ioex_p->chip_info, port, mask);
+ rv = nct38xx_ioex_check_is_valid(ioex, port, mask);
if (rv != EC_SUCCESS)
return rv;
- reg = NCT38XXX_REG_GPIO_DATA_IN(port);
+ reg = NCT38XX_REG_GPIO_DATA_IN(port);
rv = i2c_read8(ioex_config[ioex].i2c_host_port,
ioex_config[ioex].i2c_slave_addr, reg, val);
+ if (rv != EC_SUCCESS)
+ return rv;
*val = !!(*val & mask);
- return rv;
+
+ return EC_SUCCESS;
}
static int nct38xx_ioex_set_level(int ioex, int port, int mask, int value)
{
int rv, reg, val;
- struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
- rv = nct38xx_ioex_check_is_valid(ioex_p->chip_info, port, mask);
+ rv = nct38xx_ioex_check_is_valid(ioex, port, mask);
if (rv != EC_SUCCESS)
return rv;
- reg = NCT38XXX_REG_GPIO_DATA_OUT(port);
+ reg = NCT38XX_REG_GPIO_DATA_OUT(port);
rv = i2c_read8(ioex_config[ioex].i2c_host_port,
ioex_config[ioex].i2c_slave_addr, reg, &val);
+ if (rv != EC_SUCCESS)
+ return rv;
if (value)
val |= mask;
else
val &= ~mask;
- rv |= i2c_write8(ioex_config[ioex].i2c_host_port,
+
+ return i2c_write8(ioex_config[ioex].i2c_host_port,
ioex_config[ioex].i2c_slave_addr, reg, val);
- return rv;
}
static int nct38xx_ioex_get_flags(int ioex, int port, int mask, int *flags)
@@ -106,30 +137,104 @@ static int nct38xx_ioex_get_flags(int ioex, int port, int mask, int *flags)
i2c_port = ioex_p->i2c_host_port;
i2c_addr = ioex_p->i2c_slave_addr;
- rv = nct38xx_ioex_check_is_valid(ioex_p->chip_info, port, mask);
+ rv = nct38xx_ioex_check_is_valid(ioex, port, mask);
if (rv != EC_SUCCESS)
return rv;
- reg = NCT38XXX_REG_GPIO_DIR(port);
+ reg = NCT38XX_REG_GPIO_DIR(port);
rv = i2c_read8(i2c_port, i2c_addr, reg, &val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
if (val & mask)
*flags |= GPIO_OUTPUT;
else
*flags |= GPIO_INPUT;
- reg = NCT38XXX_REG_GPIO_DATA_IN(port);
- rv |= i2c_read8(i2c_port, i2c_addr, reg, &val);
+ reg = NCT38XX_REG_GPIO_DATA_IN(port);
+ rv = i2c_read8(i2c_port, i2c_addr, reg, &val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
if (val & mask)
*flags |= GPIO_HIGH;
else
*flags |= GPIO_LOW;
- reg = NCT38XXX_REG_GPIO_OD_SEL(port);
- rv |= i2c_read8(i2c_port, i2c_addr, reg, &val);
+ reg = NCT38XX_REG_GPIO_OD_SEL(port);
+ rv = i2c_read8(i2c_port, i2c_addr, reg, &val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
if (val & mask)
*flags |= GPIO_OPEN_DRAIN;
- return rv;
+ return EC_SUCCESS;
+}
+
+static int nct38xx_ioex_sel_int_type(int i2c_port, int i2c_addr, int port,
+ int mask, int flags)
+{
+ int rv;
+ int reg_rising, reg_falling;
+ int rising, falling;
+
+ reg_rising = NCT38XX_REG_GPIO_ALERT_RISE(port);
+ rv = i2c_read8(i2c_port, i2c_addr, reg_rising, &rising);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ reg_falling = NCT38XX_REG_GPIO_ALERT_FALL(port);
+ rv = i2c_read8(i2c_port, i2c_addr, reg_falling, &falling);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ /* Handle interrupt for level trigger */
+ if ((flags & GPIO_INT_F_HIGH) || (flags & GPIO_INT_F_LOW)) {
+ int reg_level, level;
+
+ reg_level = NCT38XX_REG_GPIO_ALERT_LEVEL(port);
+ rv = i2c_read8(i2c_port, i2c_addr, reg_level, &level);
+ if (rv != EC_SUCCESS)
+ return rv;
+ /*
+ * For "level" triggered interrupt, the related bit in
+ * ALERT_RISE and ALERT_FALL registers must be 0
+ */
+ rising &= ~mask;
+ falling &= ~mask;
+ if (flags & GPIO_INT_F_HIGH)
+ level |= mask;
+ else
+ level &= ~mask;
+
+ rv = i2c_write8(i2c_port, i2c_addr, reg_rising, rising);
+ if (rv != EC_SUCCESS)
+ return rv;
+ rv = i2c_write8(i2c_port, i2c_addr, reg_falling, falling);
+ if (rv != EC_SUCCESS)
+ return rv;
+ rv = i2c_write8(i2c_port, i2c_addr, reg_level, level);
+ if (rv != EC_SUCCESS)
+ return rv;
+ } else if ((flags & GPIO_INT_F_RISING) ||
+ (flags & GPIO_INT_F_FALLING)) {
+ if (flags & GPIO_INT_F_RISING)
+ rising |= mask;
+ else
+ rising &= ~mask;
+ if (flags & GPIO_INT_F_FALLING)
+ falling |= mask;
+ else
+ falling &= ~mask;
+ rv = i2c_write8(i2c_port, i2c_addr, reg_rising, rising);
+ if (rv != EC_SUCCESS)
+ return rv;
+ rv = i2c_write8(i2c_port, i2c_addr, reg_falling, falling);
+ if (rv != EC_SUCCESS)
+ return rv;
+ }
+ return EC_SUCCESS;
}
static int nct38xx_ioex_set_flags_by_mask(int ioex, int port, int mask,
@@ -141,7 +246,7 @@ static int nct38xx_ioex_set_flags_by_mask(int ioex, int port, int mask,
i2c_port = ioex_p->i2c_host_port;
i2c_addr = ioex_p->i2c_slave_addr;
- rv = nct38xx_ioex_check_is_valid(ioex_p->chip_info, port, mask);
+ rv = nct38xx_ioex_check_is_valid(ioex, port, mask);
if (rv != EC_SUCCESS)
return rv;
@@ -151,12 +256,17 @@ static int nct38xx_ioex_set_flags_by_mask(int ioex, int port, int mask,
*/
if (port == 0) {
/* GPIO03 in NCT3807 is not muxed with other function. */
- if (!(ioex_p->chip_info ==
+ if (!(chip_data[ioex].chip_id ==
NCT38XX_VARIANT_3807 && mask & 0x08)) {
- reg = NCT38XXX_REG_MUX_CONTROL;
- rv |= i2c_read8(i2c_port, i2c_addr, reg, &val);
+ reg = NCT38XX_REG_MUX_CONTROL;
+ rv = i2c_read8(i2c_port, i2c_addr, reg, &val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
val = (val | mask);
- rv |= i2c_write8(i2c_port, i2c_addr, reg, val);
+ rv = i2c_write8(i2c_port, i2c_addr, reg, val);
+ if (rv != EC_SUCCESS)
+ return rv;
}
}
@@ -167,32 +277,187 @@ static int nct38xx_ioex_set_flags_by_mask(int ioex, int port, int mask,
}
/* Select open drain 0:push-pull 1:open-drain */
- reg = NCT38XXX_REG_GPIO_OD_SEL(port);
- rv |= i2c_read8(i2c_port, i2c_addr, reg, &val);
+ reg = NCT38XX_REG_GPIO_OD_SEL(port);
+ rv = i2c_read8(i2c_port, i2c_addr, reg, &val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
if (flags & GPIO_OPEN_DRAIN)
val |= mask;
else
val &= ~mask;
- rv |= i2c_write8(i2c_port, i2c_addr, reg, val);
+ rv = i2c_write8(i2c_port, i2c_addr, reg, val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ nct38xx_ioex_sel_int_type(i2c_port, i2c_addr, port, mask, flags);
/* Configure the output level */
- reg = NCT38XXX_REG_GPIO_DATA_OUT(port);
- rv |= i2c_read8(i2c_port, i2c_addr, reg, &val);
+ reg = NCT38XX_REG_GPIO_DATA_OUT(port);
+ rv = i2c_read8(i2c_port, i2c_addr, reg, &val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
if (flags & GPIO_HIGH)
val |= mask;
else if (flags & GPIO_LOW)
val &= ~mask;
- rv |= i2c_write8(i2c_port, i2c_addr, reg, val);
+ rv = i2c_write8(i2c_port, i2c_addr, reg, val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ reg = NCT38XX_REG_GPIO_DIR(port);
+ rv = i2c_read8(i2c_port, i2c_addr, reg, &val);
+ if (rv != EC_SUCCESS)
+ return rv;
- reg = NCT38XXX_REG_GPIO_DIR(port);
- rv |= i2c_read8(i2c_port, i2c_addr, reg, &val);
if (flags & GPIO_OUTPUT)
val |= mask;
else
val &= ~mask;
- rv |= i2c_write8(i2c_port, i2c_addr, reg, val);
- return rv;
+ return i2c_write8(i2c_port, i2c_addr, reg, val);
+}
+
+/*
+ * The following functions are used for IO's interrupt support.
+ *
+ * please note that if the system needs to use an IO on NCT38XX to support
+ * the interrupt, the following two consideration should be taken into account.
+ * 1. Interrupt latency:
+ * Because it requires to access the registers of NCT38XX via I2C
+ * transaction to know the interrupt event, there is some added latency
+ * for the interrupt handling. If the interrupt requires short latency,
+ * we do not recommend to connect such a signal to the NCT38XX.
+ *
+ * 2. Shared ALERT pin:
+ * Because the ALERT pin is shared also with the TCPC ALERT, we do not
+ * recommend to connect any signal that may generate a high rate of
+ * interrupts so it will not interfere with the normal work of the
+ * TCPC.
+ */
+static int nct38xx_ioex_enable_interrupt(int ioex, int port, int mask,
+ int enable)
+{
+ int rv, reg, val;
+ struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
+
+ rv = nct38xx_ioex_check_is_valid(ioex, port, mask);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ /* Clear the pending bit */
+ reg = NCT38XX_REG_GPIO_ALERT_STAT(port);
+ rv = i2c_read8(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
+ reg, &val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ val |= mask;
+ rv = i2c_write8(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
+ reg, val);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ reg = NCT38XX_REG_GPIO_ALERT_MASK(port);
+ if (enable) {
+ /* Enable the alert mask */
+ chip_data[ioex].int_mask[port] |= mask;
+ val = chip_data[ioex].int_mask[port];
+ } else {
+ /* Disable the alert mask */
+ chip_data[ioex].int_mask[port] &= ~mask;
+ val = chip_data[ioex].int_mask[port];
+ }
+
+ return i2c_write8(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
+ reg, val);
+}
+
+int nct38xx_ioex_event_handler(int ioex)
+{
+ int reg, int_status, int_mask;
+ int i, j, total_port;
+ const struct ioex_info *g;
+ struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
+ int rv = 0;
+
+ int_mask = chip_data[ioex].int_mask[0] | (
+ chip_data[ioex].int_mask[1] << 8);
+ reg = NCT38XX_REG_GPIO_ALERT_STAT(0);
+ /*
+ * Read ALERT_STAT_0 and ALERT_STAT_1 register in a single I2C
+ * transaction to increase efficiency
+ */
+ rv = i2c_read16(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
+ reg, &int_status);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ int_status = int_status & int_mask;
+ /*
+ * Clear the changed status bits in ALERT_STAT_0 and ALERT_STAT_1
+ * register in a single I2C transaction to increase efficiency
+ */
+ rv = i2c_write16(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
+ reg, int_status);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ /* For NCT3808, only check one port */
+ total_port = (chip_data[ioex].chip_id == NCT38XX_VARIANT_3808) ?
+ NCT38XX_NCT3808_MAX_IO_PORT :
+ NCT38XX_NCT3807_MAX_IO_PORT;
+ for (i = 0; i < total_port; i++) {
+ uint8_t pending;
+
+ pending = int_status >> (i * 8);
+
+ if (!pending)
+ continue;
+
+ for (j = 0, g = ioex_list; j < ioex_ih_count; j++, g++) {
+
+ if (ioex == g->ioex && i == g->port &&
+ (pending & g->mask)) {
+ ioex_irq_handlers[j](j);
+ pending &= ~g->mask;
+ if (!pending)
+ break;
+ }
+
+ }
+ }
+
+ return EC_SUCCESS;
+}
+
+/*
+ * Normally, the ALERT MASK for Vendor Define event should be checked by
+ * the NCT38XX TCPCI driver's tcpc_alert function.
+ * However, it should be checked here if we want to test the interrupt
+ * function of IOEX when the NCT38XX TCPCI driver is not included.
+ */
+void nct38xx_ioex_handle_alert(int ioex)
+{
+ int rv, status;
+ struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
+
+ rv = i2c_read16(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
+ TCPC_REG_ALERT, &status);
+ if (rv != EC_SUCCESS)
+ CPRINTF("fail to read ALERT register\n");
+
+ if (status & TCPC_REG_ALERT_VENDOR_DEF) {
+ rv = i2c_write16(ioex_p->i2c_host_port,
+ ioex_p->i2c_slave_addr, TCPC_REG_ALERT,
+ TCPC_REG_ALERT_VENDOR_DEF);
+ if (rv != EC_SUCCESS) {
+ CPRINTF("Fail to clear Vendor Define mask\n");
+ return;
+ }
+ nct38xx_ioex_event_handler(ioex);
+ }
}
const struct ioexpander_drv nct38xx_ioexpander_drv = {
@@ -201,4 +466,5 @@ const struct ioexpander_drv nct38xx_ioexpander_drv = {
.set_level = &nct38xx_ioex_set_level,
.get_flags_by_mask = &nct38xx_ioex_get_flags,
.set_flags_by_mask = &nct38xx_ioex_set_flags_by_mask,
+ .enable_interrupt = &nct38xx_ioex_enable_interrupt,
};
diff --git a/driver/ioexpander_nct38xx.h b/driver/ioexpander_nct38xx.h
index bbd5d89ac0..56dfe76e04 100644
--- a/driver/ioexpander_nct38xx.h
+++ b/driver/ioexpander_nct38xx.h
@@ -12,6 +12,22 @@
*/
#include "nct38xx.h"
+/*
+ * The interrupt handler to handle Vendor Define ALERT event from IOEX chip.
+ *
+ * Normally, the Vendor Define event should be checked by the NCT38XX TCPCI
+ * driver's tcpc_alert function.
+ * This function is only included when NCT38XX TCPC driver is not included.
+ * (i.e. CONFIG_USB_PD_TCPM_NCT38XX is not defined)
+ */
+void nct38xx_ioex_handle_alert(int ioex);
+
+/*
+ * Check which IO's interrupt event is triggered. If any, call its
+ * registered interrupt handler.
+ */
+int nct38xx_ioex_event_handler(int ioex);
+
extern const struct ioexpander_drv nct38xx_ioexpander_drv;
#endif /* defined(__CROS_EC_IOEXPANDER_NCT38XX_H) */
diff --git a/driver/tcpm/nct38xx.c b/driver/tcpm/nct38xx.c
index ee8aeb58e6..8061f7190c 100644
--- a/driver/tcpm/nct38xx.c
+++ b/driver/tcpm/nct38xx.c
@@ -8,6 +8,7 @@
#include "common.h"
#include "console.h"
+#include "ioexpander_nct38xx.h"
#include "nct38xx.h"
#include "tcpci.h"
@@ -75,6 +76,18 @@ static int nct38xx_tcpm_init(int port)
/* Start VBus monitor */
rv = tcpc_write(port, TCPC_REG_COMMAND,
TCPC_REG_COMMAND_ENABLE_VBUS_DETECT);
+
+ /*
+ * Enable the Vendor Define alert event only when the IO expander
+ * feature is defined
+ */
+ if (IS_ENABLED(CONFIG_IO_EXPANDER_NCT38XX)) {
+ int mask;
+
+ rv |= tcpc_read16(port, TCPC_REG_ALERT_MASK, &mask);
+ mask |= TCPC_REG_ALERT_VENDOR_DEF;
+ rv |= tcpc_write16(port, TCPC_REG_ALERT_MASK, mask);
+ }
return rv;
}
@@ -297,6 +310,30 @@ clear:
return rv;
}
+static void nct38xx_tcpc_alert(int port)
+{
+ int alert, rv;
+
+ /*
+ * If IO expander feature is defined, read the ALERT register first to
+ * keep the status of Vendor Define bit. Otherwise, the status of ALERT
+ * register will be cleared after tcpci_tcpc_alert() is executed.
+ */
+ if (IS_ENABLED(CONFIG_IO_EXPANDER_NCT38XX))
+ rv = tcpc_read16(port, TCPC_REG_ALERT, &alert);
+
+ /* Process normal TCPC ALERT event and clear status */
+ tcpci_tcpc_alert(port);
+
+ /*
+ * If IO expander feature is defined, check the Vendor Define bit to
+ * handle the IOEX IO's interrupt event
+ */
+ if (IS_ENABLED(CONFIG_IO_EXPANDER_NCT38XX))
+ if (!rv && (alert & TCPC_REG_ALERT_VENDOR_DEF))
+ nct38xx_ioex_event_handler(port);
+
+}
const struct tcpm_drv nct38xx_tcpm_drv = {
.init = &nct38xx_tcpm_init,
.release = &tcpci_tcpm_release,
@@ -312,7 +349,7 @@ const struct tcpm_drv nct38xx_tcpm_drv = {
.set_rx_enable = &tcpci_tcpm_set_rx_enable,
.get_message_raw = &tcpci_nct38xx_get_message_raw,
.transmit = &tcpci_nct38xx_transmit,
- .tcpc_alert = &tcpci_tcpc_alert,
+ .tcpc_alert = &nct38xx_tcpc_alert,
#ifdef CONFIG_USB_PD_DISCHARGE_TCPC
.tcpc_discharge_vbus = &tcpci_tcpc_discharge_vbus,
#endif
diff --git a/driver/tcpm/nct38xx.h b/driver/tcpm/nct38xx.h
index 5d80606018..970c1c8c85 100644
--- a/driver/tcpm/nct38xx.h
+++ b/driver/tcpm/nct38xx.h
@@ -14,19 +14,25 @@
#define NCT38XX_VARIANT_3807 0x0
#define NCT38XX_VARIANT_3808 0x2
+/* There are two IO ports in NCT3807 */
+#define NCT38XX_NCT3807_MAX_IO_PORT 2
+/* There is only one IO port in NCT3808 */
+#define NCT38XX_NCT3808_MAX_IO_PORT 1
+
#define NCT38XX_SUPPORT_GPIO_FLAGS (GPIO_OPEN_DRAIN | GPIO_INPUT | \
- GPIO_OUTPUT | GPIO_LOW | GPIO_HIGH)
+ GPIO_OUTPUT | GPIO_LOW | GPIO_HIGH | GPIO_INT_F_RISING | \
+ GPIO_INT_F_FALLING | GPIO_INT_F_HIGH | GPIO_INT_F_LOW)
/* I2C interface */
-#define NCT38xx_I2C_ADDR1_1_FLAGS 0x70
-#define NCT38xx_I2C_ADDR1_2_FLAGS 0x71
-#define NCT38xx_I2C_ADDR1_3_FLAGS 0x72
-#define NCT38xx_I2C_ADDR1_4_FLAGS 0x73
+#define NCT38XX_I2C_ADDR1_1_FLAGS 0x70
+#define NCT38XX_I2C_ADDR1_2_FLAGS 0x71
+#define NCT38XX_I2C_ADDR1_3_FLAGS 0x72
+#define NCT38XX_I2C_ADDR1_4_FLAGS 0x73
-#define NCT38xx_I2C_ADDR2_1_FLAGS 0x74
-#define NCT38xx_I2C_ADDR2_2_FLAGS 0x75
-#define NCT38xx_I2C_ADDR2_3_FLAGS 0x76
-#define NCT38xx_I2C_ADDR2_4_FLAGS 0x77
+#define NCT38XX_I2C_ADDR2_1_FLAGS 0x74
+#define NCT38XX_I2C_ADDR2_2_FLAGS 0x75
+#define NCT38XX_I2C_ADDR2_3_FLAGS 0x76
+#define NCT38XX_I2C_ADDR2_4_FLAGS 0x77
#define NCT38XX_REG_VENDOR_ID_L 0x00
#define NCT38XX_REG_VENDOR_ID_H 0x01
@@ -34,11 +40,16 @@
#define NCT38XX_PRODUCT_ID 0xC301
-#define NCT38XXX_REG_GPIO_DATA_IN(n) (0xC0 + ((n) * 8))
-#define NCT38XXX_REG_GPIO_DATA_OUT(n) (0xC1 + ((n) * 8))
-#define NCT38XXX_REG_GPIO_DIR(n) (0xC2 + ((n) * 8))
-#define NCT38XXX_REG_GPIO_OD_SEL(n) (0xC3 + ((n) * 8))
-#define NCT38XXX_REG_MUX_CONTROL 0xD0
+#define NCT38XX_REG_GPIO_DATA_IN(n) (0xC0 + ((n) * 8))
+#define NCT38XX_REG_GPIO_DATA_OUT(n) (0xC1 + ((n) * 8))
+#define NCT38XX_REG_GPIO_DIR(n) (0xC2 + ((n) * 8))
+#define NCT38XX_REG_GPIO_OD_SEL(n) (0xC3 + ((n) * 8))
+#define NCT38XX_REG_GPIO_ALERT_RISE(n) (0xC4 + ((n) * 8))
+#define NCT38XX_REG_GPIO_ALERT_FALL(n) (0xC5 + ((n) * 8))
+#define NCT38XX_REG_GPIO_ALERT_LEVEL(n) (0xC6 + ((n) * 8))
+#define NCT38XX_REG_GPIO_ALERT_MASK(n) (0xC7 + ((n) * 8))
+#define NCT38XX_REG_MUX_CONTROL 0xD0
+#define NCT38XX_REG_GPIO_ALERT_STAT(n) (0xD4 + (n))
/* NCT3808 only supports GPIO 2/3/4/6/7 */
#define NCT38XXX_3808_VALID_GPIO_MASK 0xDC
diff --git a/driver/tcpm/tcpci.h b/driver/tcpm/tcpci.h
index 6db71e4161..d2950b7b1a 100644
--- a/driver/tcpm/tcpci.h
+++ b/driver/tcpm/tcpci.h
@@ -21,6 +21,7 @@
#define TCPC_REG_ALERT 0x10
#define TCPC_REG_ALERT_MASK_ALL 0xfff
+#define TCPC_REG_ALERT_VENDOR_DEF (1<<15)
#define TCPC_REG_ALERT_VBUS_DISCNCT (1<<11)
#define TCPC_REG_ALERT_RX_BUF_OVF (1<<10)
#define TCPC_REG_ALERT_FAULT (1<<9)
diff --git a/include/ioexpander.h b/include/ioexpander.h
index e8f76d9fbc..150db65750 100644
--- a/include/ioexpander.h
+++ b/include/ioexpander.h
@@ -51,11 +51,6 @@ struct ioexpander_config_t {
/* I2C slave address */
int i2c_slave_addr;
/*
- * The extra variable used to store information which may be required
- * by the IO expander chip.
- */
- int chip_info;
- /*
* Pointer to the specific IO expander chip's ops defined in
* the struct ioexpander_drv.
*/