blob: c7ff590f3c9eee6b8dc9c014de0eadc00862eada [file] [log] [blame]
/*
* 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);
irq_unlock(key);
if (common->col_size > 16) {
inst->KBS_KSOH2 = (kso_val >> 16) & 0xff;
}
}
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 input temporarily for gpio_pin_configure(), after
* that pinctrl_apply_state() set to alternate function
* immediately.
*/
gpio_pin_configure_dt(&config->kso16_gpios, GPIO_INPUT);
gpio_pin_configure_dt(&config->kso17_gpios, GPIO_INPUT);
}
/*
* 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) {
inst->KBS_KSOH2 = 0x00;
}
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");