summaryrefslogtreecommitdiff
path: root/driver/ioexpander/it8801.c
diff options
context:
space:
mode:
authortim <tim2.lin@ite.corp-partner.google.com>2020-02-14 11:55:48 +0800
committerCommit Bot <commit-bot@chromium.org>2020-02-17 10:24:39 +0000
commitc2797a55a58678e3118aa0b95431c78f02b1ec3d (patch)
tree04290d466feeadef28ad620a22da9bca73f92b24 /driver/ioexpander/it8801.c
parenta3cf5101156136e6a17434f57e9e521ed624eca8 (diff)
downloadchrome-ec-c2797a55a58678e3118aa0b95431c78f02b1ec3d.tar.gz
driver/ioexpander_it8801: add I/O expander driver for GPIO
IT8801 is an I/O expander with the gpio. Add api compatible with ioexpander_drv so the main io expander framework can make use of the pins on the it8801. BUG=b/138352732, b/146996723 BRANCH=none TEST=Use gpio.inc to declare the gpio state. The console command #ioexget can get as follows: 0 O L IT8801_G0_00 1 O H IT8801_G0_03 1 O H IT8801_G0_04 0 O L IT8801_G0_06 0 O L IT8801_G0_07 1 O H ODR IT8801_G1_10 1 O H ODR IT8801_G1_11 1 O H ODR IT8801_G1_12 1 I H IT8801_G1_13 1 I H IT8801_G1_14 1 I H IT8801_G1_15 1 O H ODR IT8801_G2_20 1 O H ODR IT8801_G2_21 0 O L ODR IT8801_G2_22 0 O L ODR IT8801_G2_23 TEST=jacuzzi, juniper and kappa still compile TEST=keyboard scanning still works TEST=keyboard scanning now uses fewer i2c packets due to caching of GPIO23 Change-Id: I7ad89058ccd43b073d648e93877b86d6f187b5df Signed-off-by: tim <tim2.lin@ite.corp-partner.google.com> Signed-off-by: Alexandru M Stan <amstan@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/1712887 Reviewed-by: David Huang <David.Huang@quantatw.com> Reviewed-by: Ting Shen <phoenixshen@chromium.org> Tested-by: David Huang <David.Huang@quantatw.com>
Diffstat (limited to 'driver/ioexpander/it8801.c')
-rw-r--r--driver/ioexpander/it8801.c221
1 files changed, 205 insertions, 16 deletions
diff --git a/driver/ioexpander/it8801.c b/driver/ioexpander/it8801.c
index 00e40e12ed..1226378c9a 100644
--- a/driver/ioexpander/it8801.c
+++ b/driver/ioexpander/it8801.c
@@ -7,6 +7,7 @@
#include "console.h"
#include "gpio.h"
#include "i2c.h"
+#include "ioexpander.h"
#include "it8801.h"
#include "keyboard_raw.h"
#include "keyboard_scan.h"
@@ -16,6 +17,8 @@
#define CPRINTS(format, args...) cprints(CC_KEYSCAN, format, ## args)
+static int it8801_ioex_set_level(int ioex, int port, int mask, int value);
+
static int it8801_read(int reg, int *data)
{
return i2c_read8(I2C_PORT_IO_EXPANDER_IT8801, IT8801_I2C_ADDR,
@@ -58,7 +61,7 @@ static int it8801_check_vendor_id(void)
void keyboard_raw_init(void)
{
- int ret, val;
+ int ret;
/* Verify Vendor ID registers. */
ret = it8801_check_vendor_id();
@@ -84,8 +87,7 @@ void keyboard_raw_init(void)
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);
+ it8801_ioex_set_level(0, 2, IT8801_REG_GPIO23SOV, 1);
}
/* Keyboard scan in interrupt enable register */
@@ -113,7 +115,7 @@ BUILD_ASSERT(ARRAY_SIZE(kso_mapping) == KEYBOARD_COLS_MAX);
test_mockable void keyboard_raw_drive_column(int col)
{
- int kso_val, val;
+ int kso_val;
/* Tri-state all outputs */
if (col == KEYBOARD_COLUMN_NONE) {
@@ -122,9 +124,7 @@ test_mockable void keyboard_raw_drive_column(int col)
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);
+ it8801_ioex_set_level(0, 2, IT8801_REG_GPIO23SOV, 0);
}
}
/* Assert all outputs */
@@ -134,9 +134,7 @@ test_mockable void keyboard_raw_drive_column(int col)
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);
+ it8801_ioex_set_level(0, 2, IT8801_REG_GPIO23SOV, 1);
}
} else {
/* To check if column is valid or not. */
@@ -152,14 +150,12 @@ test_mockable void keyboard_raw_drive_column(int col)
/* 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);
+ it8801_ioex_set_level(0, 2,
+ IT8801_REG_GPIO23SOV, 1);
} else {
/* Output low(so not selected). */
- it8801_read(IT8801_REG_GPIOG2SOVR, &val);
- it8801_write(IT8801_REG_GPIOG2SOVR, val &
- ~IT8801_REG_GPIO23SOV);
+ it8801_ioex_set_level(0, 2,
+ IT8801_REG_GPIO23SOV, 0);
}
}
}
@@ -199,6 +195,199 @@ void io_expander_it8801_interrupt(enum gpio_signal signal)
task_wake(TASK_ID_KEYSCAN);
}
+static int it8801_ioex_read(int ioex, int reg, int *data)
+{
+ struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
+
+ return i2c_read8(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
+ reg, data);
+}
+
+static int it8801_ioex_write(int ioex, int reg, int data)
+{
+ struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
+
+ return i2c_write8(ioex_p->i2c_host_port, ioex_p->i2c_slave_addr,
+ reg, data);
+}
+
+/*
+ * Initialize the general purpose I/O port(GPIO)
+ */
+static int it8801_ioex_init(int ioex)
+{
+ int ret;
+
+ /* Verify Vendor ID registers. */
+ ret = it8801_check_vendor_id();
+ if (ret) {
+ CPRINTS("Failed to read IT8801 vendor id %x", ret);
+ return ret;
+ }
+
+ return EC_SUCCESS;
+}
+
+static const int it8801_valid_gpio_group[] = {
+ IT8801_VALID_GPIO_G0_MASK,
+ IT8801_VALID_GPIO_G1_MASK,
+ IT8801_VALID_GPIO_G2_MASK,
+};
+
+/* Mutexes */
+static struct mutex ioex_mutex;
+
+static uint8_t it8801_gpio_sov[ARRAY_SIZE(it8801_valid_gpio_group)];
+
+static int ioex_check_is_not_valid(int port, int mask)
+{
+ if (port >= ARRAY_SIZE(it8801_valid_gpio_group)) {
+ CPRINTS("Port%d is not support in IT8801", port);
+ return EC_ERROR_INVAL;
+ }
+
+ if (mask & ~it8801_valid_gpio_group[port]) {
+ CPRINTS("GPIO%d-%d is not support in IT8801", port,
+ __fls(mask & ~it8801_valid_gpio_group[port]));
+ return EC_ERROR_INVAL;
+ }
+
+ return EC_SUCCESS;
+}
+
+static int it8801_ioex_get_level(int ioex, int port, int mask, int *val)
+{
+ int rv;
+
+ if (ioex_check_is_not_valid(port, mask))
+ return EC_ERROR_INVAL;
+
+ rv = it8801_ioex_read(ioex, IT8801_REG_GPIO_IPSR(port), val);
+
+ *val = !!(*val & mask);
+
+ return rv;
+}
+
+static int it8801_ioex_set_level(int ioex, int port, int mask, int value)
+{
+ int rv = EC_SUCCESS;
+
+ if (ioex_check_is_not_valid(port, mask))
+ return EC_ERROR_INVAL;
+
+ mutex_lock(&ioex_mutex);
+ /*
+ * The bit of output value in SOV is different than
+ * the one we were about to set it to.
+ */
+ if (!!(it8801_gpio_sov[port] & mask) ^ value) {
+ if (value)
+ it8801_gpio_sov[port] |= mask;
+ else
+ it8801_gpio_sov[port] &= ~mask;
+
+ rv = it8801_ioex_write(ioex, IT8801_REG_GPIO_SOVR(port),
+ it8801_gpio_sov[port]);
+ }
+ mutex_unlock(&ioex_mutex);
+
+ return rv;
+}
+
+static int it8801_ioex_get_flags_by_mask(int ioex, int port,
+ int mask, int *flags)
+{
+ int rv, val;
+
+ if (ioex_check_is_not_valid(port, mask))
+ return EC_ERROR_INVAL;
+
+ rv = it8801_ioex_read(ioex, IT8801_REG_GPIO_CR(port, mask), &val);
+ if (rv)
+ return rv;
+
+ *flags = 0;
+
+ /* Get GPIO direction */
+ *flags |= (val & IT8801_GPIODIR) ? GPIO_OUTPUT : GPIO_INPUT;
+
+ /* Get GPIO type, 0:push-pull 1:open-drain */
+ if (val & IT8801_GPIOIOT)
+ *flags |= GPIO_OPEN_DRAIN;
+
+ rv = it8801_ioex_read(ioex, IT8801_REG_GPIO_IPSR(port), &val);
+ if (rv)
+ return rv;
+
+ /* Get GPIO output level */
+ *flags |= (val & mask) ? GPIO_HIGH : GPIO_LOW;
+
+ return EC_SUCCESS;
+}
+
+static int it8801_ioex_set_flags_by_mask(int ioex, int port,
+ int mask, int flags)
+{
+ int rv, val;
+
+ if (ioex_check_is_not_valid(port, mask))
+ return EC_ERROR_INVAL;
+
+ if (flags & ~IT8801_SUPPORT_GPIO_FLAGS) {
+ CPRINTS("Flag 0x%08x is not supported at port %d, mask %d",
+ flags, port, mask);
+ return EC_ERROR_INVAL;
+ }
+
+ /* GPIO alternate function switching(GPIO[00, 12:15, 20:23]). */
+ it8801_ioex_write(ioex, IT8801_REG_GPIO_CR(port, mask),
+ IT8801_REG_MASK_GPIOAFS_FUNC1);
+
+ mutex_lock(&ioex_mutex);
+ rv = it8801_ioex_read(ioex, IT8801_REG_GPIO_CR(port, mask), &val);
+ if (rv) {
+ mutex_unlock(&ioex_mutex);
+ return rv;
+ }
+ /* Select open drain 0:push-pull 1:open-drain */
+ if (flags & GPIO_OPEN_DRAIN)
+ val |= IT8801_GPIOIOT;
+ else
+ val &= ~IT8801_GPIOIOT;
+
+ /* Select GPIO direction */
+ if (flags & GPIO_OUTPUT) {
+ /* Configure the output level */
+ if (flags & GPIO_HIGH)
+ it8801_gpio_sov[port] |= mask;
+ else
+ it8801_gpio_sov[port] &= ~mask;
+
+ rv = it8801_ioex_write(ioex, IT8801_REG_GPIO_SOVR(port),
+ it8801_gpio_sov[port]);
+ if (rv) {
+ mutex_unlock(&ioex_mutex);
+ return rv;
+ }
+ val |= IT8801_GPIODIR;
+ } else
+ val &= ~IT8801_GPIODIR;
+
+ rv = it8801_ioex_write(ioex, IT8801_REG_GPIO_CR(port, mask), val);
+ mutex_unlock(&ioex_mutex);
+
+ return rv;
+}
+
+const struct ioexpander_drv it8801_ioexpander_drv = {
+ .init = &it8801_ioex_init,
+ .get_level = &it8801_ioex_get_level,
+ .set_level = &it8801_ioex_set_level,
+ .get_flags_by_mask = &it8801_ioex_get_flags_by_mask,
+ .set_flags_by_mask = &it8801_ioex_set_flags_by_mask,
+};
+
static void dump_register(int reg)
{
int rv;