blob: 818023eb337a2fb1d8ae8ebfdd979f93fb93c80e [file]
/*
* Copyright (c) 2019 Intel Corporation
* Copyright (c) 2022 Microchip Technology Inc.
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT microchip_xec_kbd
#include <cmsis_core.h>
#include <errno.h>
#include <soc.h>
#include <zephyr/device.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/dt-bindings/interrupt-controller/mchp-xec-ecia.h>
#include <zephyr/input/input.h>
#include <zephyr/input/input_kbd_matrix.h>
#include <zephyr/irq.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/policy.h>
#include <zephyr/sys/sys_io.h>
LOG_MODULE_REGISTER(input_xec_kbd, CONFIG_INPUT_LOG_LEVEL);
struct xec_kbd_config {
struct input_kbd_matrix_common_config common;
mm_reg_t base;
const struct pinctrl_dev_config *pcfg;
uint8_t enc_pcr;
uint8_t girq;
uint8_t girq_pos;
bool wakeup_source;
};
struct xec_kbd_data {
struct input_kbd_matrix_common_data common;
bool pm_lock_taken;
};
static void xec_kbd_drive_column(const struct device *dev, int data)
{
struct xec_kbd_config const *cfg = dev->config;
mm_reg_t base = cfg->base;
if (data == INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL) {
/* KSO output controlled by the KSO_SELECT field */
sys_write32(MCHP_KSCAN_KSO_ALL, base + XEC_KBD_KSO_SEL_OFS);
} else if (data == INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE) {
/* Keyboard scan disabled. All KSO output buffers disabled */
sys_write32(MCHP_KSCAN_KSO_EN, base + XEC_KBD_KSO_SEL_OFS);
} else {
/* Assume, ALL was previously set */
sys_write32(data, base + XEC_KBD_KSO_SEL_OFS);
}
}
static kbd_row_t xec_kbd_read_row(const struct device *dev)
{
struct xec_kbd_config const *cfg = dev->config;
mm_reg_t base = cfg->base;
/* In this implementation a 1 means key pressed */
return ~(sys_read32(base + XEC_KBD_KSI_IN_OFS) & 0xff);
}
static void xec_kbd_isr(const struct device *dev)
{
struct xec_kbd_config const *cfg = dev->config;
soc_ecia_girq_status_clear(cfg->girq, cfg->girq_pos);
irq_disable(DT_INST_IRQN(0));
input_kbd_matrix_poll_start(dev);
}
static void xec_kbd_set_detect_mode(const struct device *dev, bool enabled)
{
struct xec_kbd_config const *cfg = dev->config;
struct xec_kbd_data *data = dev->data;
mm_reg_t base = cfg->base;
if (enabled) {
if (data->pm_lock_taken) {
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE,
PM_ALL_SUBSTATES);
}
sys_write32(MCHP_KSCAN_KSO_SEL_REG_MASK, base + XEC_KBD_KSI_STS_OFS);
soc_ecia_girq_status_clear(cfg->girq, cfg->girq_pos);
NVIC_ClearPendingIRQ(DT_INST_IRQN(0));
irq_enable(DT_INST_IRQN(0));
} else {
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE,
PM_ALL_SUBSTATES);
data->pm_lock_taken = true;
}
}
#ifdef CONFIG_PM_DEVICE
static int xec_kbd_pm_action(const struct device *dev, enum pm_device_action action)
{
struct xec_kbd_config const *cfg = dev->config;
mm_reg_t base = cfg->base;
int ret;
ret = input_kbd_matrix_pm_action(dev, action);
if (ret < 0) {
return ret;
}
if (cfg->wakeup_source) {
return 0;
}
switch (action) {
case PM_DEVICE_ACTION_RESUME:
ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
if (ret != 0) {
LOG_ERR("XEC KSCAN pinctrl init failed (%d)", ret);
return ret;
}
sys_clear_bits(base + XEC_KBD_KSO_SEL_OFS, BIT(MCHP_KSCAN_KSO_EN_POS));
/* Clear status register */
sys_write32(MCHP_KSCAN_KSO_SEL_REG_MASK, base + XEC_KBD_KSI_STS_OFS);
sys_write32(MCHP_KSCAN_KSI_IEN_REG_MASK, base + XEC_KBD_KSI_IEN_OFS);
break;
case PM_DEVICE_ACTION_SUSPEND:
sys_set_bits(base + XEC_KBD_KSO_SEL_OFS, BIT(MCHP_KSCAN_KSO_EN_POS));
sys_write32(~MCHP_KSCAN_KSI_IEN_REG_MASK, base + XEC_KBD_KSI_IEN_OFS);
ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_SLEEP);
if (ret != -ENOENT) {
/* pinctrl-1 does not exist */
return ret;
}
break;
default:
return -ENOTSUP;
}
return 0;
}
#endif /* CONFIG_PM_DEVICE */
static int xec_kbd_init(const struct device *dev)
{
struct xec_kbd_config const *cfg = dev->config;
mm_reg_t base = cfg->base;
int ret;
ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
if (ret != 0) {
LOG_ERR("XEC KSCAN pinctrl init failed (%d)", ret);
return ret;
}
soc_xec_pcr_sleep_en_clear(cfg->enc_pcr);
/* Enable predrive */
sys_set_bits(base + XEC_KBD_KSO_SEL_OFS, BIT(MCHP_KSCAN_KSO_EN_POS));
sys_write32(MCHP_KSCAN_EXT_CTRL_PREDRV_EN, base + XEC_KBD_EXT_CTRL_OFS);
sys_clear_bits(base + XEC_KBD_KSO_SEL_OFS, BIT(MCHP_KSCAN_KSO_EN_POS));
sys_write32(MCHP_KSCAN_KSI_IEN_REG_MASK, base + XEC_KBD_KSI_IEN_OFS);
/* Interrupts are enabled in the thread function */
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority),
xec_kbd_isr, DEVICE_DT_INST_GET(0), 0);
soc_ecia_girq_status_clear(cfg->girq, cfg->girq_pos);
soc_ecia_girq_ctrl(cfg->girq, cfg->girq_pos, 1u);
return input_kbd_matrix_common_init(dev);
}
PINCTRL_DT_INST_DEFINE(0);
PM_DEVICE_DT_INST_DEFINE(0, xec_kbd_pm_action);
INPUT_KBD_MATRIX_DT_INST_DEFINE(0);
static const struct input_kbd_matrix_api xec_kbd_api = {
.drive_column = xec_kbd_drive_column,
.read_row = xec_kbd_read_row,
.set_detect_mode = xec_kbd_set_detect_mode,
};
#define DEV_CFG_GIRQ(inst) MCHP_XEC_ECIA_GIRQ(DT_INST_PROP_BY_IDX(inst, girqs, 0))
#define DEV_CFG_GIRQ_POS(inst) MCHP_XEC_ECIA_GIRQ_POS(DT_INST_PROP_BY_IDX(inst, girqs, 0))
/* To enable wakeup, set the "wakeup-source" on the keyboard scanning device
* node.
*/
static struct xec_kbd_config xec_kbd_cfg_0 = {
.common = INPUT_KBD_MATRIX_DT_INST_COMMON_CONFIG_INIT(0, &xec_kbd_api),
.base = DT_INST_REG_ADDR(0),
.girq = DEV_CFG_GIRQ(0),
.girq_pos = DEV_CFG_GIRQ_POS(0),
.enc_pcr = DT_INST_PROP(0, pcrs),
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
.wakeup_source = DT_INST_PROP(0, wakeup_source)};
static struct xec_kbd_data kbd_data_0;
DEVICE_DT_INST_DEFINE(0, xec_kbd_init,
PM_DEVICE_DT_INST_GET(0), &kbd_data_0, &xec_kbd_cfg_0,
POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL);
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1,
"only one microchip,xec-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");