Files
zephyr/drivers/input/input_ite_it8xxx2_kbd.c
Ruibin Chang 3fc27e71d2 drivers: input: it8xxx2: resolve runtime assertion failure
When the kso[17:16] lines are enabled and `CONFIG_ASSERT` is
configured, the following error occurs: "Input cannot be
enabled for 'Open Drain', 'Open Source' modes without Output."
To address this, this commit configures kso16 and kso17 as
gpio output before applying the pinctrl settings.

Tested with: no abnormal keyscan occurs during
initialization

Signed-off-by: Ruibin Chang <Ruibin.Chang@ite.com.tw>
Signed-off-by: Ren Chen <Ren.Chen@ite.com.tw>
2025-08-11 12:50:00 +03:00

277 lines
8.9 KiB
C

/*
* Copyright (c) 2021 ITE Corporation. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ite_it8xxx2_kbd
#include <errno.h>
#include <soc.h>
#include <soc_dt.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/interrupt_controller/wuc_ite_it8xxx2.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/dt-bindings/interrupt-controller/it8xxx2-wuc.h>
#include <zephyr/input/input.h>
#include <zephyr/input/input_kbd_matrix.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(input_ite_it8xxx2_kbd);
#define KEYBOARD_KSI_PIN_COUNT IT8XXX2_DT_INST_WUCCTRL_LEN(0)
struct it8xxx2_kbd_wuc_map_cfg {
/* WUC control device structure */
const struct device *wucs;
/* WUC pin mask */
uint8_t mask;
};
struct it8xxx2_kbd_config {
struct input_kbd_matrix_common_config common;
/* Keyboard scan controller base address */
struct kscan_it8xxx2_regs *base;
/* Keyboard scan input (KSI) wake-up irq */
int irq;
/* KSI[7:0] wake-up input source configuration list */
const struct it8xxx2_kbd_wuc_map_cfg *wuc_map_list;
/* KSI[7:0]/KSO[17:0] keyboard scan alternate configuration */
const struct pinctrl_dev_config *pcfg;
/* KSO16 GPIO cells */
struct gpio_dt_spec kso16_gpios;
/* KSO17 GPIO cells */
struct gpio_dt_spec kso17_gpios;
/* Mask of signals to ignore */
uint32_t kso_ignore_mask;
};
struct it8xxx2_kbd_data {
struct input_kbd_matrix_common_data common;
/* KSI[7:0] wake-up interrupt status mask */
uint8_t ksi_pin_mask;
};
INPUT_KBD_STRUCT_CHECK(struct it8xxx2_kbd_config, struct it8xxx2_kbd_data);
static void it8xxx2_kbd_drive_column(const struct device *dev, int col)
{
const struct it8xxx2_kbd_config *const config = dev->config;
const struct input_kbd_matrix_common_config *common = &config->common;
struct kscan_it8xxx2_regs *const inst = config->base;
const uint32_t kso_mask = BIT_MASK(common->col_size) & ~config->kso_ignore_mask;
const uint8_t ksol_mask = kso_mask & 0xff;
const uint8_t ksoh1_mask = (kso_mask >> 8) & 0xff;
uint32_t kso_val;
unsigned int key;
/* Tri-state all outputs */
if (col == INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE) {
kso_val = kso_mask;
/* Assert all outputs */
} else if (col == INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL) {
kso_val = 0;
/* Assert a single output */
} else {
kso_val = kso_mask ^ BIT(col);
}
/* Set KSO[17:0] output data, disable global interrupts for critical section.
* The KBS_KSO* registers contains both keyboard and GPIO output settings.
* Not all bits are for the keyboard will be driven, so a critical section
* is needed to avoid race conditions.
*/
key = irq_lock();
inst->KBS_KSOL = (inst->KBS_KSOL & ~ksol_mask) | (kso_val & ksol_mask);
inst->KBS_KSOH1 = (inst->KBS_KSOH1 & ~ksoh1_mask) | ((kso_val >> 8) & ksoh1_mask);
if (common->col_size > 16) {
const uint8_t ksoh2_mask = (kso_mask >> 16) & 0xff;
inst->KBS_KSOH2 = (inst->KBS_KSOH2 & ~ksoh2_mask) |
((kso_val >> 16) & ksoh2_mask);
}
irq_unlock(key);
}
static kbd_row_t it8xxx2_kbd_read_row(const struct device *dev)
{
const struct it8xxx2_kbd_config *const config = dev->config;
struct kscan_it8xxx2_regs *const inst = config->base;
/* Bits are active-low, so toggle it (return 1 means key pressed) */
return (inst->KBS_KSI ^ 0xff);
}
static void it8xxx2_kbd_isr(const struct device *dev)
{
const struct it8xxx2_kbd_config *const config = dev->config;
struct it8xxx2_kbd_data *data = dev->data;
/*
* W/C wakeup interrupt status of KSI[7:0] pins
*
* NOTE: We want to clear the status as soon as possible,
* so clear KSI[7:0] pins at a time.
*/
it8xxx2_wuc_clear_status(config->wuc_map_list[0].wucs,
data->ksi_pin_mask);
/* W/C interrupt status of KSI[7:0] pins */
ite_intc_isr_clear(config->irq);
input_kbd_matrix_poll_start(dev);
}
static void it8xxx2_kbd_set_detect_mode(const struct device *dev, bool enable)
{
const struct it8xxx2_kbd_config *const config = dev->config;
struct it8xxx2_kbd_data *data = dev->data;
if (enable) {
/*
* W/C wakeup interrupt status of KSI[7:0] pins
*
* NOTE: We want to clear the status as soon as possible,
* so clear KSI[7:0] pins at a time.
*/
it8xxx2_wuc_clear_status(config->wuc_map_list[0].wucs,
data->ksi_pin_mask);
/* W/C interrupt status of KSI[7:0] pins */
ite_intc_isr_clear(config->irq);
irq_enable(config->irq);
} else {
irq_disable(config->irq);
}
}
static int it8xxx2_kbd_init(const struct device *dev)
{
const struct it8xxx2_kbd_config *const config = dev->config;
const struct input_kbd_matrix_common_config *common = &config->common;
struct it8xxx2_kbd_data *data = dev->data;
struct kscan_it8xxx2_regs *const inst = config->base;
const uint32_t kso_mask = BIT_MASK(common->col_size) & ~config->kso_ignore_mask;
const uint8_t ksol_mask = kso_mask & 0xff;
const uint8_t ksoh1_mask = (kso_mask >> 8) & 0xff;
int status;
/* Disable wakeup and interrupt of KSI pins before configuring */
it8xxx2_kbd_set_detect_mode(dev, false);
if (common->col_size > 16) {
/*
* For KSO[16] and KSO[17]:
* 1.GPOTRC:
* Bit[x] = 1b: Enable the open-drain mode of KSO pin
* 2.GPCRCx:
* Bit[7:6] = 00b: Select alternate KSO function
* Bit[2] = 1b: Enable the internal pull-up of KSO pin
*
* NOTE: Set (output | dt_flag) temporarily for kso16 and
* kso17 pins, after that pinctrl_apply_state() set to
* alternate function immediately.
*/
if (!(config->kso_ignore_mask & BIT(16))) {
gpio_pin_configure_dt(&config->kso16_gpios, GPIO_OUTPUT);
}
if (common->col_size > 17 && !(config->kso_ignore_mask & BIT(17))) {
gpio_pin_configure_dt(&config->kso17_gpios, GPIO_OUTPUT);
}
}
/*
* Enable the internal pull-up and kbs mode of the KSI[7:0] pins.
* Enable the internal pull-up and kbs mode of the KSO[15:0] pins.
* Enable the open-drain mode of the KSO[17:0] pins.
*/
status = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
if (status < 0) {
LOG_ERR("Failed to configure KSI[7:0] and KSO[17:0] pins");
return status;
}
/* KSO[17:0] pins output low */
inst->KBS_KSOL = inst->KBS_KSOL & ~ksol_mask;
inst->KBS_KSOH1 = inst->KBS_KSOH1 & ~ksoh1_mask;
if (common->col_size > 16) {
const uint8_t ksoh2_mask = (kso_mask >> 16) & 0xff;
inst->KBS_KSOH2 = inst->KBS_KSOH2 & ~ksoh2_mask;
}
for (int i = 0; i < KEYBOARD_KSI_PIN_COUNT; i++) {
/* Select wakeup interrupt falling-edge triggered of KSI[7:0] pins */
it8xxx2_wuc_set_polarity(config->wuc_map_list[i].wucs,
config->wuc_map_list[i].mask,
WUC_TYPE_EDGE_FALLING);
/* W/C wakeup interrupt status of KSI[7:0] pins */
it8xxx2_wuc_clear_status(config->wuc_map_list[i].wucs,
config->wuc_map_list[i].mask);
/* Enable wakeup interrupt of KSI[7:0] pins */
it8xxx2_wuc_enable(config->wuc_map_list[i].wucs,
config->wuc_map_list[i].mask);
/*
* We want to clear KSI[7:0] pins status at a time when wakeup
* interrupt fire, so gather the KSI[7:0] pin mask value here.
*/
if (config->wuc_map_list[i].wucs != config->wuc_map_list[0].wucs) {
LOG_ERR("KSI%d pin isn't in the same wuc node!", i);
}
data->ksi_pin_mask |= config->wuc_map_list[i].mask;
}
/* W/C interrupt status of KSI[7:0] pins */
ite_intc_isr_clear(config->irq);
irq_connect_dynamic(DT_INST_IRQN(0), 0,
(void (*)(const void *))it8xxx2_kbd_isr,
(const void *)dev, 0);
return input_kbd_matrix_common_init(dev);
}
static const struct it8xxx2_kbd_wuc_map_cfg
it8xxx2_kbd_wuc[IT8XXX2_DT_INST_WUCCTRL_LEN(0)] = IT8XXX2_DT_WUC_ITEMS_LIST(0);
PINCTRL_DT_INST_DEFINE(0);
INPUT_KBD_MATRIX_DT_INST_DEFINE(0);
static const struct input_kbd_matrix_api it8xxx2_kbd_api = {
.drive_column = it8xxx2_kbd_drive_column,
.read_row = it8xxx2_kbd_read_row,
.set_detect_mode = it8xxx2_kbd_set_detect_mode,
};
static const struct it8xxx2_kbd_config it8xxx2_kbd_cfg_0 = {
.common = INPUT_KBD_MATRIX_DT_INST_COMMON_CONFIG_INIT(0, &it8xxx2_kbd_api),
.base = (struct kscan_it8xxx2_regs *)DT_INST_REG_ADDR_BY_IDX(0, 0),
.irq = DT_INST_IRQN(0),
.wuc_map_list = it8xxx2_kbd_wuc,
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
.kso16_gpios = GPIO_DT_SPEC_INST_GET(0, kso16_gpios),
.kso17_gpios = GPIO_DT_SPEC_INST_GET(0, kso17_gpios),
.kso_ignore_mask = DT_INST_PROP(0, kso_ignore_mask),
};
static struct it8xxx2_kbd_data it8xxx2_kbd_data_0;
PM_DEVICE_DT_INST_DEFINE(0, input_kbd_matrix_pm_action);
DEVICE_DT_INST_DEFINE(0, &it8xxx2_kbd_init, PM_DEVICE_DT_INST_GET(0),
&it8xxx2_kbd_data_0, &it8xxx2_kbd_cfg_0,
POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL);
BUILD_ASSERT(!IS_ENABLED(CONFIG_PM_DEVICE_SYSTEM_MANAGED) ||
IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME),
"CONFIG_PM_DEVICE_RUNTIME must be enabled when using CONFIG_PM_DEVICE_SYSTEM_MANAGED");
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1,
"only one ite,it8xxx2-kbd compatible node can be supported");
BUILD_ASSERT(IN_RANGE(DT_INST_PROP(0, row_size), 1, 8), "invalid row-size");
BUILD_ASSERT(IN_RANGE(DT_INST_PROP(0, col_size), 1, 18), "invalid col-size");