summaryrefslogtreecommitdiff
path: root/driver/ioexpander
diff options
context:
space:
mode:
authorVijay Hiremath <vijay.p.hiremath@intel.com>2019-11-19 10:15:35 -0800
committerCommit Bot <commit-bot@chromium.org>2019-11-22 02:02:15 +0000
commitb6e36785982074af5638c456f305871f1465d460 (patch)
tree1d8537c283419139d881b7321b28be5927c61b06 /driver/ioexpander
parent2748f2cbe6850d9ec7a307f53b3025924e25bf05 (diff)
downloadchrome-ec-b6e36785982074af5638c456f305871f1465d460.tar.gz
cleanup: Segregate ioexpander related drivers in ioexpander folder
BUG=none BRANCH=none TEST=make buildall -j Change-Id: I7fe9ab23254dbd8515936d10ad6782305e76236c Signed-off-by: Vijay Hiremath <vijay.p.hiremath@intel.com> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/1925173 Reviewed-by: Jett Rink <jettrink@chromium.org>
Diffstat (limited to 'driver/ioexpander')
-rw-r--r--driver/ioexpander/ioexpander_nct38xx.c470
-rw-r--r--driver/ioexpander/ioexpander_nct38xx.h33
-rw-r--r--driver/ioexpander/it8300.h106
-rw-r--r--driver/ioexpander/it8801.c227
-rw-r--r--driver/ioexpander/it8801.h46
-rw-r--r--driver/ioexpander/pca9534.c52
-rw-r--r--driver/ioexpander/pca9534.h59
-rw-r--r--driver/ioexpander/pca9555.h45
8 files changed, 1038 insertions, 0 deletions
diff --git a/driver/ioexpander/ioexpander_nct38xx.c b/driver/ioexpander/ioexpander_nct38xx.c
new file mode 100644
index 0000000000..e7fe3d9453
--- /dev/null
+++ b/driver/ioexpander/ioexpander_nct38xx.c
@@ -0,0 +1,470 @@
+/*
+ * Copyright 2019 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.
+ */
+
+/* GPIO expander for Nuvoton NCT38XX. */
+
+#include "console.h"
+#include "gpio.h"
+#include "i2c.h"
+#include "ioexpander.h"
+#include "ioexpander_nct38xx.h"
+#include "tcpci.h"
+
+#define CPRINTF(format, args...) cprintf(CC_GPIO, format, ## args)
+#define CPRINTS(format, args...) cprints(CC_GPIO, format, ## args)
+
+/*
+ * 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_data[ioex].chip_id == NCT38XX_VARIANT_3808) {
+ if (port == 1) {
+ CPRINTF("Port 1 is not support in NCT3808\n");
+ return EC_ERROR_INVAL;
+ }
+ if (mask & ~NCT38XXX_3808_VALID_GPIO_MASK) {
+
+ CPRINTF("GPIO%02d is not support in NCT3808\n",
+ __fls(mask));
+ return EC_ERROR_INVAL;
+ }
+ }
+
+ return EC_SUCCESS;
+}
+
+static int nct38xx_ioex_init(int ioex)
+{
+ int rv, val;
+ struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
+
+ /*
+ * Check the NCT38xx part number in the register DEVICE_ID[4:2]:
+ * 000: NCT3807
+ * 010: NCT3808
+ */
+ rv = i2c_read8(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
+ TCPC_REG_BCD_DEV, &val);
+
+ if (rv != EC_SUCCESS) {
+ CPRINTF("Failed to read NCT38XX DEV ID for IOexpander %d\n",
+ ioex);
+ 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;
+
+ rv = nct38xx_ioex_check_is_valid(ioex, port, mask);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ 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 EC_SUCCESS;
+}
+
+static int nct38xx_ioex_set_level(int ioex, int port, int mask, int value)
+{
+ int rv, reg, val;
+
+ rv = nct38xx_ioex_check_is_valid(ioex, port, mask);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ 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;
+
+ return i2c_write8(ioex_config[ioex].i2c_host_port,
+ ioex_config[ioex].i2c_slave_addr, reg, val);
+}
+
+static int nct38xx_ioex_get_flags(int ioex, int port, int mask, int *flags)
+{
+ int rv, reg, val, i2c_port, i2c_addr;
+ struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
+
+ i2c_port = ioex_p->i2c_host_port;
+ i2c_addr = ioex_p->i2c_slave_addr;
+
+ rv = nct38xx_ioex_check_is_valid(ioex, port, mask);
+ 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;
+
+ if (val & mask)
+ *flags |= GPIO_OUTPUT;
+ else
+ *flags |= GPIO_INPUT;
+
+ 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 = 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 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,
+ int flags)
+{
+ int rv, reg, val, i2c_port, i2c_addr;
+ struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
+
+ i2c_port = ioex_p->i2c_host_port;
+ i2c_addr = ioex_p->i2c_slave_addr;
+
+ rv = nct38xx_ioex_check_is_valid(ioex, port, mask);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ /*
+ * GPIO port 0 muxs with alternative function. Disable the alternative
+ * function before setting flags.
+ */
+ if (port == 0) {
+ /* GPIO03 in NCT3807 is not muxed with other function. */
+ if (!(chip_data[ioex].chip_id ==
+ NCT38XX_VARIANT_3807 && mask & 0x08)) {
+ 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);
+ if (rv != EC_SUCCESS)
+ return rv;
+ }
+ }
+
+ val = flags & ~NCT38XX_SUPPORT_GPIO_FLAGS;
+ if (val) {
+ CPRINTF("Flag 0x%08x is not supported\n", val);
+ return EC_ERROR_INVAL;
+ }
+
+ /* Select open drain 0:push-pull 1:open-drain */
+ 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);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ nct38xx_ioex_sel_int_type(i2c_port, i2c_addr, port, mask, flags);
+
+ /* Configure the output level */
+ 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);
+ 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;
+
+ if (flags & GPIO_OUTPUT)
+ val |= mask;
+ else
+ val &= ~mask;
+
+ 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 = {
+ .init = &nct38xx_ioex_init,
+ .get_level = &nct38xx_ioex_get_level,
+ .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/ioexpander_nct38xx.h b/driver/ioexpander/ioexpander_nct38xx.h
new file mode 100644
index 0000000000..56dfe76e04
--- /dev/null
+++ b/driver/ioexpander/ioexpander_nct38xx.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#ifndef __CROS_EC_IOEXPANDER_NCT38XX_H
+#define __CROS_EC_IOEXPANDER_NCT38XX_H
+/*
+ * NCT38XX registers are defined in the driver/tcpm/nct38xx.h.
+ * No matter they are used by TCPC or IO Expander driver.
+ */
+#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/ioexpander/it8300.h b/driver/ioexpander/it8300.h
new file mode 100644
index 0000000000..2b47e7f3e1
--- /dev/null
+++ b/driver/ioexpander/it8300.h
@@ -0,0 +1,106 @@
+/* Copyright 2018 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.
+ *
+ * ITE IT8300 I/O Port expander driver header
+ */
+
+#ifndef __CROS_EC_IOEXPANDER_IT8300_H
+#define __CROS_EC_IOEXPANDER_IT8300_H
+
+#include "i2c.h"
+
+/* Gather Interrupt Status Register */
+#define IT8300_GISR 0x0
+
+/* Interrupt Status Registers */
+#define IT8300_ISR_A 0x6
+#define IT8300_ISR_B 0x7
+#define IT8300_ISR_C 0x28
+#define IT8300_ISR_D 0x2E
+#define IT8300_ISR_E 0x2F
+
+/* Port Data Register Groups */
+#define IT8300_PDGR_A 0x1
+#define IT8300_PDGR_B 0x2
+#define IT8300_PDGR_C 0x3
+#define IT8300_PDGR_D 0x4
+#define IT8300_PDGR_E 0x5
+
+/* GPIO Port Control n Registers */
+#define IT8300_GPCR_A0 0x10
+#define IT8300_GPCR_A1 0x11
+#define IT8300_GPCR_A2 0x12
+#define IT8300_GPCR_A3 0x13
+#define IT8300_GPCR_A4 0x14
+#define IT8300_GPCR_A5 0x15
+#define IT8300_GPCR_A6 0x16
+#define IT8300_GPCR_A7 0x17
+
+#define IT8300_GPCR_B0 0x18
+#define IT8300_GPCR_B1 0x19
+#define IT8300_GPCR_B2 0x1A
+#define IT8300_GPCR_B3 0x1B
+#define IT8300_GPCR_B4 0x1C
+#define IT8300_GPCR_B5 0x1D
+#define IT8300_GPCR_B6 0x1E
+
+#define IT8300_GPCR_C0 0x20
+#define IT8300_GPCR_C1 0x21
+#define IT8300_GPCR_C2 0x22
+#define IT8300_GPCR_C3 0x23
+#define IT8300_GPCR_C4 0x24
+#define IT8300_GPCR_C5 0x25
+#define IT8300_GPCR_C6 0x26
+
+#define IT8300_GPCR_D0 0x08
+#define IT8300_GPCR_D1 0x09
+#define IT8300_GPCR_D2 0x0A
+#define IT8300_GPCR_D3 0x0B
+#define IT8300_GPCR_D4 0x0C
+#define IT8300_GPCR_D5 0x0D
+
+#define IT8300_GPCR_E0 0x32
+
+#define IT8300_GPCR_E2 0x34
+#define IT8300_GPCR_E3 0x35
+#define IT8300_GPCR_E4 0x36
+#define IT8300_GPCR_E5 0x37
+#define IT8300_GPCR_E6 0x38
+
+#define IT8300_GPCR_GPI_MODE BIT(7)
+#define IT8300_GPCR_GP0_MODE BIT(6)
+#define IT8300_GPCR_PULL_UP_EN BIT(2)
+#define IT8300_GPCR_PULL_DN_EN BIT(1)
+
+/* EXGPIO Clear Alert */
+#define IT8300_ECA 0x30
+
+/* EXGPIO Alert Enable */
+#define IT8300_EAE 0x31
+
+/* Port Data Mirror Registers */
+#define IT8300_PDMRA_A 0x29
+#define IT8300_PDMRA_B 0x2A
+#define IT8300_PDMRA_C 0x2B
+#define IT8300_PDMRA_D 0x2C
+#define IT8300_PDMRA_E 0x2D
+
+/* Output Open-Drain Enable Registers */
+#define IT8300_OODER_A 0x39
+#define IT8300_OODER_B 0x3A
+#define IT8300_OODER_C 0x3B
+#define IT8300_OODER_D 0x3C
+#define IT8300_OODER_E 0x3D
+
+/* IT83200 Port GPIOs */
+#define IT8300_GPX_0 BIT(0)
+#define IT8300_GPX_1 BIT(1)
+#define IT8300_GPX_2 BIT(2)
+#define IT8300_GPX_3 BIT(3)
+#define IT8300_GPX_4 BIT(4)
+#define IT8300_GPX_5 BIT(5)
+#define IT8300_GPX_6 BIT(6)
+#define IT8300_GPX_7 BIT(7)
+
+#endif /* __CROS_EC_IOEXPANDER_IT8300_H */
diff --git a/driver/ioexpander/it8801.c b/driver/ioexpander/it8801.c
new file mode 100644
index 0000000000..6b1184c740
--- /dev/null
+++ b/driver/ioexpander/it8801.c
@@ -0,0 +1,227 @@
+/* Copyright 2019 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.
+ */
+
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "i2c.h"
+#include "it8801.h"
+#include "keyboard_raw.h"
+#include "keyboard_scan.h"
+#include "registers.h"
+#include "task.h"
+
+#define CPRINTS(format, args...) cprints(CC_KEYSCAN, format, ## args)
+
+static int it8801_read(int reg, int *data)
+{
+ return i2c_read8(I2C_PORT_IO_EXPANDER_IT8801, IT8801_I2C_ADDR,
+ reg, data);
+}
+
+static int it8801_write(int reg, int data)
+{
+ return i2c_write8(I2C_PORT_IO_EXPANDER_IT8801, IT8801_I2C_ADDR,
+ reg, data);
+}
+
+struct it8801_vendor_id_t {
+ uint8_t chip_id;
+ uint8_t reg;
+};
+
+static const struct it8801_vendor_id_t it8801_vendor_id_verify[] = {
+ { 0x12, IT8801_REG_HBVIDR},
+ { 0x83, IT8801_REG_LBVIDR},
+};
+
+static int it8801_check_vendor_id(void)
+{
+ int i, ret, val;
+
+ /* Verify vendor ID registers(16-bits). */
+ for (i = 0; i < ARRAY_SIZE(it8801_vendor_id_verify); i++) {
+ ret = it8801_read(it8801_vendor_id_verify[i].reg, &val);
+
+ if (ret != EC_SUCCESS)
+ return ret;
+
+ if (val != it8801_vendor_id_verify[i].chip_id)
+ return EC_ERROR_UNKNOWN;
+ }
+
+ return EC_SUCCESS;
+}
+
+void keyboard_raw_init(void)
+{
+ int ret, val;
+
+ /* Verify Vendor ID registers. */
+ ret = it8801_check_vendor_id();
+ if (ret) {
+ CPRINTS("Failed to read IT8801 vendor id %x", ret);
+ return;
+ }
+
+ /* KSO alternate function switching(KSO[21:20, 18]). */
+ it8801_write(IT8801_REG_GPIO01_KSO18, IT8801_REG_MASK_GPIOAFS_FUNC2);
+ it8801_write(IT8801_REG_GPIO22_KSO21, IT8801_REG_MASK_GPIOAFS_FUNC2);
+ it8801_write(IT8801_REG_GPIO23_KSO20, IT8801_REG_MASK_GPIOAFS_FUNC2);
+
+ /* Start with KEYBOARD_COLUMN_ALL, KSO[22:11, 6:0] output low. */
+ it8801_write(IT8801_REG_KSOMCR, IT8801_REG_MASK_AKSOSC);
+
+ if (IS_ENABLED(CONFIG_KEYBOARD_COL2_INVERTED)) {
+ /*
+ * Since most of the KSO pins can't drive up, we'll must use
+ * a pin capable of being a GPIO instead and use the GPIO
+ * feature to do the required inverted push pull.
+ */
+ it8801_write(IT8801_REG_GPIO23_KSO20, IT8801_REG_MASK_GPIODIR);
+
+ /* Start with KEYBOARD_COLUMN_ALL, output high(so selected). */
+ it8801_read(IT8801_REG_GPIOG2SOVR, &val);
+ it8801_write(IT8801_REG_GPIOG2SOVR, val | IT8801_REG_GPIO23SOV);
+ }
+
+ /* Keyboard scan in interrupt enable register */
+ it8801_write(IT8801_REG_KSIIER, 0xff);
+ /* Gather KSI interrupt enable */
+ it8801_write(IT8801_REG_GIECR, IT8801_REG_MASK_GKSIIE);
+ /* Alert response enable */
+ it8801_write(IT8801_REG_SMBCR, IT8801_REG_MASK_ARE);
+
+ keyboard_raw_enable_interrupt(0);
+}
+
+void keyboard_raw_task_start(void)
+{
+ keyboard_raw_enable_interrupt(1);
+}
+
+static const uint8_t kso_mapping[] = {
+ 0, 1, 20, 3, 4, 5, 6, 17, 18, 16, 15, 11, 12,
+#ifdef CONFIG_KEYBOARD_KEYPAD
+ 13, 14
+#endif
+};
+BUILD_ASSERT(ARRAY_SIZE(kso_mapping) == KEYBOARD_COLS_MAX);
+
+test_mockable void keyboard_raw_drive_column(int col)
+{
+ int kso_val, val;
+
+ /* Tri-state all outputs */
+ if (col == KEYBOARD_COLUMN_NONE) {
+ /* KSO[22:11, 6:0] output high */
+ kso_val = IT8801_REG_MASK_KSOSDIC | IT8801_REG_MASK_AKSOSC;
+
+ if (IS_ENABLED(CONFIG_KEYBOARD_COL2_INVERTED)) {
+ /* Output low(so not selected). */
+ it8801_read(IT8801_REG_GPIOG2SOVR, &val);
+ it8801_write(IT8801_REG_GPIOG2SOVR, val &
+ ~IT8801_REG_GPIO23SOV);
+ }
+ }
+ /* Assert all outputs */
+ else if (col == KEYBOARD_COLUMN_ALL) {
+ /* KSO[22:11, 6:0] output low */
+ kso_val = IT8801_REG_MASK_AKSOSC;
+
+ if (IS_ENABLED(CONFIG_KEYBOARD_COL2_INVERTED)) {
+ /* Output high(so selected). */
+ it8801_read(IT8801_REG_GPIOG2SOVR, &val);
+ it8801_write(IT8801_REG_GPIOG2SOVR, val |
+ IT8801_REG_GPIO23SOV);
+ }
+ } else {
+ /* To check if column is valid or not. */
+ if (col >= KEYBOARD_COLS_MAX)
+ return;
+ /*
+ * Selected KSO[20, 18:11, 6:3, 1:0] output low,
+ * all others KSO output high.
+ */
+ kso_val = kso_mapping[col];
+
+ if (IS_ENABLED(CONFIG_KEYBOARD_COL2_INVERTED)) {
+ /* GPIO23 is inverted. */
+ if (col == IT8801_REG_MASK_SELKSO2) {
+ /* Output high(so selected). */
+ it8801_read(IT8801_REG_GPIOG2SOVR, &val);
+ it8801_write(IT8801_REG_GPIOG2SOVR, val |
+ IT8801_REG_GPIO23SOV);
+ } else {
+ /* Output low(so not selected). */
+ it8801_read(IT8801_REG_GPIOG2SOVR, &val);
+ it8801_write(IT8801_REG_GPIOG2SOVR, val &
+ ~IT8801_REG_GPIO23SOV);
+ }
+ }
+ }
+
+ it8801_write(IT8801_REG_KSOMCR, kso_val);
+}
+
+test_mockable int keyboard_raw_read_rows(void)
+{
+ int data = 0;
+ int ksieer = 0;
+
+ it8801_read(IT8801_REG_KSIDR, &data);
+
+ /* This register needs to write clear after reading data */
+ it8801_read(IT8801_REG_KSIEER, &ksieer);
+ it8801_write(IT8801_REG_KSIEER, ksieer);
+
+ /* Bits are active-low, so invert returned levels */
+ return (~data) & 0xff;
+}
+
+void keyboard_raw_enable_interrupt(int enable)
+{
+ if (enable) {
+ it8801_write(IT8801_REG_KSIEER, 0xff);
+ gpio_clear_pending_interrupt(GPIO_IT8801_SMB_INT);
+ gpio_enable_interrupt(GPIO_IT8801_SMB_INT);
+ } else {
+ gpio_disable_interrupt(GPIO_IT8801_SMB_INT);
+ }
+}
+
+void io_expander_it8801_interrupt(enum gpio_signal signal)
+{
+ /* Wake the scan task */
+ task_wake(TASK_ID_KEYSCAN);
+}
+
+static void dump_register(int reg)
+{
+ int rv;
+ int data;
+
+ ccprintf("[%Xh] = ", reg);
+
+ rv = it8801_read(reg, &data);
+
+ if (!rv)
+ ccprintf("0x%02x\n", data);
+ else
+ ccprintf("ERR (%d)\n", rv);
+}
+
+static int it8801_dump(int argc, char **argv)
+{
+ dump_register(IT8801_REG_KSIIER);
+ dump_register(IT8801_REG_KSIEER);
+ dump_register(IT8801_REG_KSIDR);
+ dump_register(IT8801_REG_KSOMCR);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(it8801_dump, it8801_dump, "NULL",
+ "Dumps IT8801 registers");
diff --git a/driver/ioexpander/it8801.h b/driver/ioexpander/it8801.h
new file mode 100644
index 0000000000..9b84adf764
--- /dev/null
+++ b/driver/ioexpander/it8801.h
@@ -0,0 +1,46 @@
+/* Copyright 2019 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.
+ *
+ * IT8801 is an I/O expander with the keyboard matrix controller.
+ *
+ */
+
+#ifndef __CROS_EC_IO_EXPANDER_IT8801_H
+#define __CROS_EC_IO_EXPANDER_IT8801_H
+
+/* I2C slave address(7-bit without R/W) */
+#define IT8801_I2C_ADDR 0x38
+
+/* Keyboard Matrix Scan control (KBS) */
+#define IT8801_REG_KSOMCR 0x40
+#define IT8801_REG_MASK_KSOSDIC BIT(7)
+#define IT8801_REG_MASK_KSE BIT(6)
+#define IT8801_REG_MASK_AKSOSC BIT(5)
+#define IT8801_REG_KSIDR 0x41
+#define IT8801_REG_KSIEER 0x42
+#define IT8801_REG_KSIIER 0x43
+#define IT8801_REG_SMBCR 0xfa
+#define IT8801_REG_MASK_ARE BIT(4)
+#define IT8801_REG_GIECR 0xfb
+#define IT8801_REG_MASK_GKSIIE BIT(3)
+#define IT8801_REG_GPIO10 0x12
+#define IT8801_REG_GPIO00_KSO19 0x0a
+#define IT8801_REG_GPIO01_KSO18 0x0b
+#define IT8801_REG_GPIO22_KSO21 0x1c
+#define IT8801_REG_GPIO23_KSO20 0x1d
+#define IT8801_REG_MASK_GPIOAFS_PULLUP BIT(7)
+#define IT8801_REG_MASK_GPIOAFS_FUNC2 BIT(6)
+#define IT8801_REG_MASK_GPIODIR BIT(5)
+#define IT8801_REG_MASK_GPIOPUE BIT(0)
+#define IT8801_REG_GPIOG2SOVR 0x07
+#define IT8801_REG_GPIO23SOV BIT(3)
+#define IT8801_REG_MASK_SELKSO2 0x02
+#define IT8801_REG_LBVIDR 0xFE
+#define IT8801_REG_HBVIDR 0xFF
+#define IT8801_KSO_COUNT 18
+
+/* ISR for IT8801's SMB_INT# */
+void io_expander_it8801_interrupt(enum gpio_signal signal);
+
+#endif /* __CROS_EC_KBEXPANDER_IT8801_H */
diff --git a/driver/ioexpander/pca9534.c b/driver/ioexpander/pca9534.c
new file mode 100644
index 0000000000..d56eb864cb
--- /dev/null
+++ b/driver/ioexpander/pca9534.c
@@ -0,0 +1,52 @@
+/* Copyright 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.
+ *
+ * NXP PCA9534 I/O expander
+ */
+
+#include "i2c.h"
+#include "pca9534.h"
+
+static int pca9534_pin_read(const int port, const uint16_t addr_flags,
+ int reg, int pin, int *val)
+{
+ int ret;
+ ret = i2c_read8(port, addr_flags, reg, val);
+ *val = (*val & BIT(pin)) ? 1 : 0;
+ return ret;
+}
+
+static int pca9534_pin_write(const int port, const uint16_t addr_flags,
+ int reg, int pin, int val)
+{
+ int ret, v;
+ ret = i2c_read8(port, addr_flags, reg, &v);
+ if (ret != EC_SUCCESS)
+ return ret;
+ v &= ~BIT(pin);
+ if (val)
+ v |= 1 << pin;
+ return i2c_write8(port, addr_flags, reg, v);
+}
+
+int pca9534_get_level(const int port, const uint16_t addr_flags,
+ int pin, int *level)
+{
+ return pca9534_pin_read(port, addr_flags,
+ PCA9534_REG_INPUT, pin, level);
+}
+
+int pca9534_set_level(const int port, const uint16_t addr_flags,
+ int pin, int level)
+{
+ return pca9534_pin_write(port, addr_flags,
+ PCA9534_REG_OUTPUT, pin, level);
+}
+
+int pca9534_config_pin(const int port, const uint16_t addr_flags,
+ int pin, int is_input)
+{
+ return pca9534_pin_write(port, addr_flags,
+ PCA9534_REG_CONFIG, pin, is_input);
+}
diff --git a/driver/ioexpander/pca9534.h b/driver/ioexpander/pca9534.h
new file mode 100644
index 0000000000..0fec577576
--- /dev/null
+++ b/driver/ioexpander/pca9534.h
@@ -0,0 +1,59 @@
+/* Copyright 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.
+ *
+ * NXP PCA9534 I/O expander
+ */
+
+#ifndef __CROS_EC_IOEXPANDER_PCA9534_H
+#define __CROS_EC_IOEXPANDER_PCA9534_H
+
+#define PCA9534_REG_INPUT 0x0
+#define PCA9534_REG_OUTPUT 0x1
+#define PCA9534_REG_CONFIG 0x3
+
+#define PCA9534_OUTPUT 0
+#define PCA9534_INPUT 1
+
+/*
+ * Get input level. Note that this reflects the actual level on the
+ * pin, even if the pin is configured as output.
+ *
+ * @param port The I2C port of PCA9534.
+ * @param addr The address of PCA9534.
+ * @param pin The index of the pin to read.
+ * @param level The pointer to where the read level is stored.
+ *
+ * @return EC_SUCCESS, or EC_ERROR_* on error.
+ */
+int pca9534_get_level(const int port, const uint16_t addr_flags,
+ int pin, int *level);
+
+/*
+ * Set output level. This function has no effect if the pin is
+ * configured as input.
+ *
+ * @param port The I2C port of PCA9534.
+ * @param addr The address of PCA9534.
+ * @param pin The index of the pin to set.
+ * @param level The level to set.
+ *
+ * @return EC_SUCCESS, or EC_ERROR_* on error.
+ */
+int pca9534_set_level(const int port, const uint16_t addr_flags,
+ int pin, int level);
+
+/*
+ * Config a pin as input or output.
+ *
+ * @param port The I2C port of PCA9534.
+ * @param addr The address of PCA9534.
+ * @param pin The index of the pin to set.
+ * @param is_input PCA9534_INPUT or PCA9534_OUTPUT.
+ *
+ * @return EC_SUCCESS, or EC_ERROR_* on error.
+ */
+int pca9534_config_pin(const int port, const uint16_t addr_flags,
+ int pin, int is_input);
+
+#endif /* __CROS_EC_IOEXPANDER_PCA9534_H */
diff --git a/driver/ioexpander/pca9555.h b/driver/ioexpander/pca9555.h
new file mode 100644
index 0000000000..273f898821
--- /dev/null
+++ b/driver/ioexpander/pca9555.h
@@ -0,0 +1,45 @@
+/* Copyright 2017 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.
+ *
+ * NXP PCA9555 I/O Port expander driver header
+ */
+
+#ifndef __CROS_EC_IOEXPANDER_PCA9555_H
+#define __CROS_EC_IOEXPANDER_PCA9555_H
+
+#include "i2c.h"
+
+#define PCA9555_CMD_INPUT_PORT_0 0
+#define PCA9555_CMD_INPUT_PORT_1 1
+#define PCA9555_CMD_OUTPUT_PORT_0 2
+#define PCA9555_CMD_OUTPUT_PORT_1 3
+#define PCA9555_CMD_POLARITY_INVERSION_PORT_0 4
+#define PCA9555_CMD_POLARITY_INVERSION_PORT_1 5
+#define PCA9555_CMD_CONFIGURATION_PORT_0 6
+#define PCA9555_CMD_CONFIGURATION_PORT_1 7
+
+#define PCA9555_IO_0 BIT(0)
+#define PCA9555_IO_1 BIT(1)
+#define PCA9555_IO_2 BIT(2)
+#define PCA9555_IO_3 BIT(3)
+#define PCA9555_IO_4 BIT(4)
+#define PCA9555_IO_5 BIT(5)
+#define PCA9555_IO_6 BIT(6)
+#define PCA9555_IO_7 BIT(7)
+
+static inline int pca9555_read(const int port,
+ const uint16_t i2c_addr_flags,
+ int reg, int *data_ptr)
+{
+ return i2c_read8(port, i2c_addr_flags, reg, data_ptr);
+}
+
+static inline int pca9555_write(const int port,
+ const uint16_t i2c_addr_flags,
+ int reg, int data)
+{
+ return i2c_write8(port, i2c_addr_flags, reg, data);
+}
+
+#endif /* __CROS_EC_IOEXPANDER_PCA9555_H */