summaryrefslogtreecommitdiff
path: root/zephyr/shim/src/ioex_drv.c
diff options
context:
space:
mode:
Diffstat (limited to 'zephyr/shim/src/ioex_drv.c')
-rw-r--r--zephyr/shim/src/ioex_drv.c381
1 files changed, 381 insertions, 0 deletions
diff --git a/zephyr/shim/src/ioex_drv.c b/zephyr/shim/src/ioex_drv.c
new file mode 100644
index 0000000000..a6075de59a
--- /dev/null
+++ b/zephyr/shim/src/ioex_drv.c
@@ -0,0 +1,381 @@
+/* Copyright 2021 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.
+ */
+
+#define DT_DRV_COMPAT cros_ioex_port
+#define DT_DRV_COMPAT_CHIP cros_ioex_chip
+
+#include <device.h>
+#include <drivers/gpio.h>
+#include <drivers/i2c.h>
+#include <errno.h>
+#include <gpio/gpio_utils.h>
+#include <init.h>
+#include <kernel.h>
+#include <logging/log.h>
+#include <sys/byteorder.h>
+#include <sys/util.h>
+#include "common.h"
+#include "config.h"
+#include "i2c.h"
+#include "ioexpander.h"
+
+/* Include drivers if enabled */
+#ifdef CONFIG_PLATFORM_EC_IOEX_CCGXXF
+#include "driver/tcpm/ccgxxf.h"
+#endif
+#ifdef CONFIG_PLATFORM_EC_IOEX_IT8801
+#include "driver/ioexpander/it8801.h"
+#endif
+#ifdef CONFIG_PLATFORM_EC_IOEX_NCT38XX
+#include "driver/tcpm/nct38xx.h"
+#endif
+#ifdef CONFIG_PLATFORM_EC_IOEX_PCA9675
+#include "driver/ioexpander/pca9675.h"
+#endif
+#ifdef CONFIG_PLATFORM_EC_IOEX_PCAL6408
+#include "driver/ioexpander/pcal6408.h"
+#endif
+#ifdef CONFIG_PLATFORM_EC_IOEX_TCA64XXA
+#include "driver/ioexpander/tca64xxa.h"
+#endif
+
+LOG_MODULE_REGISTER(cros_ioex_port, CONFIG_GPIO_LOG_LEVEL);
+
+struct ioex_drv_data {
+ const struct device *dev;
+ int ioex;
+ int port;
+
+ sys_slist_t callbacks;
+ struct k_work worker;
+
+ const struct device *int_gpio_dev;
+ gpio_pin_t int_gpio_pin;
+ gpio_flags_t int_gpio_flags;
+ struct gpio_callback int_gpio_callback;
+ gpio_port_value_t cached_values;
+ gpio_port_value_t pin_trig_edge_rising;
+ gpio_port_value_t pin_trig_edge_falling;
+ gpio_port_value_t pin_trig_level_zero;
+ gpio_port_value_t pin_trig_level_one;
+};
+
+static int shim_ioex_pin_configure(const struct device *dev, gpio_pin_t pin,
+ gpio_flags_t flags)
+{
+ const struct ioexpander_config_t *cfg = dev->config;
+ struct ioex_drv_data *drv_data = dev->data;
+ int res;
+
+ res = cfg->drv->set_flags_by_mask(drv_data->ioex, drv_data->port,
+ BIT(pin),
+ convert_from_zephyr_flags(flags));
+ if (res)
+ return -EIO;
+
+ return 0;
+}
+
+static int shim_ioex_port_get_raw(const struct device *dev,
+ gpio_port_value_t *value)
+{
+ const struct ioexpander_config_t *cfg = dev->config;
+ struct ioex_drv_data *drv_data = dev->data;
+ int res;
+
+ res = cfg->drv->get_port(drv_data->ioex, drv_data->port, value);
+ if (res)
+ return -EIO;
+
+ return 0;
+}
+
+static int shim_ioex_port_set_masked_raw(const struct device *dev,
+ gpio_port_pins_t mask,
+ gpio_port_value_t value)
+{
+ const struct ioexpander_config_t *cfg = dev->config;
+ struct ioex_drv_data *drv_data = dev->data;
+ int res;
+
+ res = cfg->drv->set_level(drv_data->ioex, drv_data->port, mask, value);
+ if (res)
+ return -EIO;
+
+ return 0;
+}
+
+static int shim_ioex_port_set_bits_raw(const struct device *dev,
+ gpio_port_pins_t pins)
+{
+ const struct ioexpander_config_t *cfg = dev->config;
+ struct ioex_drv_data *drv_data = dev->data;
+ int res;
+
+ res = cfg->drv->set_level(drv_data->ioex, drv_data->port, pins, 1);
+ if (res)
+ return -EIO;
+
+ return 0;
+}
+
+static int shim_ioex_port_clear_bits_raw(const struct device *dev,
+ gpio_port_pins_t pins)
+{
+ const struct ioexpander_config_t *cfg = dev->config;
+ struct ioex_drv_data *drv_data = dev->data;
+ int res;
+
+ res = cfg->drv->set_level(drv_data->ioex, drv_data->port, pins, 0);
+ if (res)
+ return -EIO;
+
+ return 0;
+}
+
+static int shim_ioex_port_toggle_bits(const struct device *dev,
+ gpio_port_pins_t pins)
+{
+ const struct ioexpander_config_t *cfg = dev->config;
+ struct ioex_drv_data *drv_data = dev->data;
+ int to_set;
+ int to_clr;
+ int res;
+ int val;
+
+ res = cfg->drv->get_port(drv_data->ioex, drv_data->port, &val);
+ if (res)
+ return -EIO;
+
+ to_set = (~val) & pins;
+ to_clr = val & pins;
+
+ res = cfg->drv->set_level(drv_data->ioex, drv_data->port, to_set, 1);
+ if (res)
+ return -EIO;
+
+ res = cfg->drv->set_level(drv_data->ioex, drv_data->port, to_clr, 0);
+ if (res)
+ return -EIO;
+
+ return 0;
+}
+
+static int shim_ioex_pin_interrupt_configure(const struct device *dev,
+ gpio_pin_t pin,
+ enum gpio_int_mode mode,
+ enum gpio_int_trig trig)
+{
+ const struct ioexpander_config_t *cfg = dev->config;
+ struct ioex_drv_data *drv_data = dev->data;
+ int flags;
+ int res;
+
+ if (!drv_data->int_gpio_dev) {
+ LOG_ERR("Trying to enable interrupt on ioex %d without "
+ "defined IO expander interrupt pin",
+ drv_data->ioex);
+ return -EIO;
+ }
+
+ res = cfg->drv->get_flags_by_mask(drv_data->ioex, drv_data->port,
+ BIT(pin), &flags);
+ if (res)
+ return -EIO;
+
+ flags |= convert_from_zephyr_flags(mode | trig);
+
+ res = cfg->drv->set_flags_by_mask(drv_data->ioex, drv_data->port,
+ BIT(pin), flags);
+ if (res)
+ return -EIO;
+
+ if (!cfg->drv->enable_interrupt) {
+ LOG_ERR("Trying to enable interrupt on ioex %d which doesn't "
+ "support interrupts",
+ drv_data->ioex);
+ return -EIO;
+ }
+
+ res = cfg->drv->enable_interrupt(drv_data->ioex, drv_data->port,
+ BIT(pin), (mode & GPIO_INT_ENABLE));
+ if (res)
+ return -EIO;
+
+ if (mode == GPIO_INT_MODE_DISABLED) {
+ drv_data->pin_trig_edge_rising &= ~BIT(pin);
+ drv_data->pin_trig_edge_falling &= ~BIT(pin);
+ drv_data->pin_trig_level_zero &= ~BIT(pin);
+ drv_data->pin_trig_level_one &= ~BIT(pin);
+ } else if (mode == GPIO_INT_MODE_EDGE) {
+ if (trig & GPIO_INT_LOW_0)
+ drv_data->pin_trig_edge_falling |= BIT(pin);
+ if (trig & GPIO_INT_HIGH_1)
+ drv_data->pin_trig_edge_rising |= BIT(pin);
+ } else {
+ if (trig & GPIO_INT_LOW_0)
+ drv_data->pin_trig_level_zero |= BIT(pin);
+ if (trig & GPIO_INT_HIGH_1)
+ drv_data->pin_trig_level_one |= BIT(pin);
+ }
+
+ return 0;
+}
+
+static void shim_ioex_isr(const struct device *dev,
+ struct gpio_callback *callback, gpio_port_pins_t pins)
+{
+ struct ioex_drv_data *drv_data =
+ CONTAINER_OF(callback, struct ioex_drv_data, int_gpio_callback);
+
+ k_work_submit(&drv_data->worker);
+}
+
+static void shim_ioex_worker(struct k_work *worker)
+{
+ struct ioex_drv_data *drv_data =
+ CONTAINER_OF(worker, struct ioex_drv_data, worker);
+ const struct ioexpander_drv *drv = ioex_config[drv_data->ioex].drv;
+ int interrupted_pins_level = 0;
+ int interrupted_pins_edge = 0;
+ int interrupted_pins = 0;
+ int current_values;
+ int changed_pins;
+
+ if (drv_data->ioex < 0) {
+ LOG_ERR("Invalid int IOEX");
+ return;
+ }
+
+ if (!drv->get_port) {
+ LOG_ERR("IO expander doesn't support get_port function");
+ return;
+ }
+
+ if (drv->get_port(drv_data->ioex, drv_data->port, &current_values)) {
+ LOG_ERR("Couldn't get int ioex values");
+ return;
+ }
+
+ changed_pins = current_values ^ drv_data->cached_values;
+
+ /* Edge rising */
+ interrupted_pins_edge |= (changed_pins & current_values) &
+ drv_data->pin_trig_edge_rising;
+ /* Edge falling */
+ interrupted_pins_edge |= (changed_pins & (~current_values)) &
+ drv_data->pin_trig_edge_falling;
+ /* Level 1 */
+ interrupted_pins_level |=
+ (current_values & drv_data->pin_trig_level_one);
+ /* Level 0 */
+ interrupted_pins_level |=
+ ((~current_values) & drv_data->pin_trig_level_zero);
+
+ interrupted_pins = (interrupted_pins_edge | interrupted_pins_level);
+ gpio_fire_callbacks(&drv_data->callbacks, drv_data->dev,
+ interrupted_pins);
+
+ drv_data->cached_values = current_values;
+
+ /* Recalling this function will simulate interrupts triggered by
+ * logic level instead of level change (edge).
+ * Function will be called repeatedly until the level change to value
+ * not triggering the interrupt.
+ */
+ if (interrupted_pins_level)
+ k_work_submit(worker);
+}
+
+static int shim_ioex_init(const struct device *dev)
+{
+ struct ioex_drv_data *drv_data = dev->data;
+
+ drv_data->dev = dev;
+
+ /* IO expander may have specified GPIO pin that should trigger
+ * interrupt handling routines for signals on this IO expander.
+ * If this GPIO is specified, it should be configured as interrupt
+ * pin and should have callback assigned to it.
+ */
+ if (drv_data->int_gpio_dev) {
+ int res;
+
+ res = gpio_pin_configure(drv_data->int_gpio_dev,
+ drv_data->int_gpio_pin,
+ drv_data->int_gpio_flags | GPIO_INPUT);
+ if (res)
+ return -EIO;
+
+ gpio_init_callback(&drv_data->int_gpio_callback, shim_ioex_isr,
+ BIT(drv_data->int_gpio_pin));
+
+ res = gpio_add_callback(drv_data->int_gpio_dev,
+ &drv_data->int_gpio_callback);
+ if (res)
+ return -EIO;
+
+ k_work_init(&drv_data->worker, shim_ioex_worker);
+ }
+
+ return 0;
+}
+
+static int shim_ioex_manage_callback(const struct device *dev,
+ struct gpio_callback *callback,
+ bool enable)
+{
+ struct ioex_drv_data *drv_data = dev->data;
+
+ return gpio_manage_callback(&drv_data->callbacks, callback, enable);
+}
+
+static const struct gpio_driver_api api_table = {
+ .pin_configure = shim_ioex_pin_configure,
+ .port_get_raw = shim_ioex_port_get_raw,
+ .port_set_masked_raw = shim_ioex_port_set_masked_raw,
+ .port_set_bits_raw = shim_ioex_port_set_bits_raw,
+ .port_clear_bits_raw = shim_ioex_port_clear_bits_raw,
+ .port_toggle_bits = shim_ioex_port_toggle_bits,
+ .pin_interrupt_configure = shim_ioex_pin_interrupt_configure,
+ .manage_callback = shim_ioex_manage_callback,
+};
+
+#define IOEX_INIT_CONFIG_ELEM(id) \
+ { \
+ .i2c_host_port = I2C_PORT(DT_PHANDLE(id, i2c_port)), \
+ .i2c_addr_flags = DT_PROP(id, i2c_addr), \
+ .drv = &DT_STRING_TOKEN(id, drv), \
+ .flags = DT_PROP(id, flags), \
+ },
+
+#define IOEX_INIT_DATA(idx) \
+ { \
+ .ioex = IOEXPANDER_ID(DT_PARENT(DT_DRV_INST(idx))), \
+ .port = DT_REG_ADDR(DT_DRV_INST(idx)), \
+ COND_CODE_1( \
+ DT_NODE_HAS_PROP(DT_PARENT(DT_DRV_INST(idx)), \
+ int_gpios), \
+ (.int_gpio_dev = DEVICE_DT_GET(DT_PHANDLE( \
+ DT_PARENT(DT_DRV_INST(idx)), int_gpios)), \
+ .int_gpio_pin = DT_GPIO_PIN( \
+ DT_PARENT(DT_DRV_INST(idx)), int_gpios), \
+ .int_gpio_flags = DT_GPIO_FLAGS( \
+ DT_PARENT(DT_DRV_INST(idx)), int_gpios), ), \
+ ()) \
+ }
+
+struct ioexpander_config_t ioex_config[] = { DT_FOREACH_STATUS_OKAY(
+ DT_DRV_COMPAT_CHIP, IOEX_INIT_CONFIG_ELEM) };
+
+#define GPIO_PORT_INIT(idx) \
+ static struct ioex_drv_data ioex_##idx##_data = IOEX_INIT_DATA(idx); \
+ DEVICE_DT_INST_DEFINE( \
+ idx, shim_ioex_init, NULL, &ioex_##idx##_data, \
+ &ioex_config[IOEXPANDER_ID(DT_PARENT(DT_DRV_INST(idx)))], \
+ POST_KERNEL, CONFIG_PLATFORM_EC_IOEX_INIT_PRIORITY, \
+ &api_table);
+
+DT_INST_FOREACH_STATUS_OKAY(GPIO_PORT_INIT)