diff options
author | Vijay Hiremath <vijay.p.hiremath@intel.com> | 2019-11-19 10:15:35 -0800 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2019-11-22 02:02:15 +0000 |
commit | b6e36785982074af5638c456f305871f1465d460 (patch) | |
tree | 1d8537c283419139d881b7321b28be5927c61b06 /driver/ioexpander | |
parent | 2748f2cbe6850d9ec7a307f53b3025924e25bf05 (diff) | |
download | chrome-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.c | 470 | ||||
-rw-r--r-- | driver/ioexpander/ioexpander_nct38xx.h | 33 | ||||
-rw-r--r-- | driver/ioexpander/it8300.h | 106 | ||||
-rw-r--r-- | driver/ioexpander/it8801.c | 227 | ||||
-rw-r--r-- | driver/ioexpander/it8801.h | 46 | ||||
-rw-r--r-- | driver/ioexpander/pca9534.c | 52 | ||||
-rw-r--r-- | driver/ioexpander/pca9534.h | 59 | ||||
-rw-r--r-- | driver/ioexpander/pca9555.h | 45 |
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 */ |