255 lines
6.5 KiB
C
255 lines
6.5 KiB
C
/*
|
|
* 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/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>
|
|
#ifdef CONFIG_SOC_SERIES_MEC172X
|
|
#include <zephyr/drivers/clock_control/mchp_xec_clock_control.h>
|
|
#include <zephyr/drivers/interrupt_controller/intc_mchp_xec_ecia.h>
|
|
#endif
|
|
|
|
LOG_MODULE_REGISTER(input_xec_kbd, CONFIG_INPUT_LOG_LEVEL);
|
|
|
|
struct xec_kbd_config {
|
|
struct input_kbd_matrix_common_config common;
|
|
|
|
struct kscan_regs *regs;
|
|
const struct pinctrl_dev_config *pcfg;
|
|
uint8_t girq;
|
|
uint8_t girq_pos;
|
|
#ifdef CONFIG_SOC_SERIES_MEC172X
|
|
uint8_t pcr_idx;
|
|
uint8_t pcr_pos;
|
|
#endif
|
|
bool wakeup_source;
|
|
};
|
|
|
|
struct xec_kbd_data {
|
|
struct input_kbd_matrix_common_data common;
|
|
bool pm_lock_taken;
|
|
};
|
|
|
|
static void xec_kbd_clear_girq_status(const struct device *dev)
|
|
{
|
|
struct xec_kbd_config const *cfg = dev->config;
|
|
|
|
#ifdef CONFIG_SOC_SERIES_MEC172X
|
|
mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos);
|
|
#else
|
|
MCHP_GIRQ_SRC(cfg->girq) = BIT(cfg->girq_pos);
|
|
#endif
|
|
}
|
|
|
|
static void xec_kbd_configure_girq(const struct device *dev)
|
|
{
|
|
struct xec_kbd_config const *cfg = dev->config;
|
|
|
|
#ifdef CONFIG_SOC_SERIES_MEC172X
|
|
mchp_xec_ecia_enable(cfg->girq, cfg->girq_pos);
|
|
#else
|
|
MCHP_GIRQ_ENSET(cfg->girq) = BIT(cfg->girq_pos);
|
|
#endif
|
|
}
|
|
|
|
static void xec_kbd_clr_slp_en(const struct device *dev)
|
|
{
|
|
#ifdef CONFIG_SOC_SERIES_MEC172X
|
|
struct xec_kbd_config const *cfg = dev->config;
|
|
|
|
z_mchp_xec_pcr_periph_sleep(cfg->pcr_idx, cfg->pcr_pos, 0);
|
|
#else
|
|
ARG_UNUSED(dev);
|
|
mchp_pcr_periph_slp_ctrl(PCR_KEYSCAN, 0);
|
|
#endif
|
|
}
|
|
|
|
static void xec_kbd_drive_column(const struct device *dev, int data)
|
|
{
|
|
struct xec_kbd_config const *cfg = dev->config;
|
|
struct kscan_regs *regs = cfg->regs;
|
|
|
|
if (data == INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL) {
|
|
/* KSO output controlled by the KSO_SELECT field */
|
|
regs->KSO_SEL = MCHP_KSCAN_KSO_ALL;
|
|
} else if (data == INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE) {
|
|
/* Keyboard scan disabled. All KSO output buffers disabled */
|
|
regs->KSO_SEL = MCHP_KSCAN_KSO_EN;
|
|
} else {
|
|
/* Assume, ALL was previously set */
|
|
regs->KSO_SEL = data;
|
|
}
|
|
}
|
|
|
|
static kbd_row_t xec_kbd_read_row(const struct device *dev)
|
|
{
|
|
struct xec_kbd_config const *cfg = dev->config;
|
|
struct kscan_regs *regs = cfg->regs;
|
|
|
|
/* In this implementation a 1 means key pressed */
|
|
return ~(regs->KSI_IN & 0xff);
|
|
}
|
|
|
|
static void xec_kbd_isr(const struct device *dev)
|
|
{
|
|
xec_kbd_clear_girq_status(dev);
|
|
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;
|
|
struct kscan_regs *regs = cfg->regs;
|
|
|
|
if (enabled) {
|
|
if (data->pm_lock_taken) {
|
|
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE,
|
|
PM_ALL_SUBSTATES);
|
|
}
|
|
|
|
regs->KSI_STS = MCHP_KSCAN_KSO_SEL_REG_MASK;
|
|
|
|
xec_kbd_clear_girq_status(dev);
|
|
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;
|
|
struct kscan_regs *regs = cfg->regs;
|
|
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;
|
|
}
|
|
|
|
regs->KSO_SEL &= ~BIT(MCHP_KSCAN_KSO_EN_POS);
|
|
/* Clear status register */
|
|
regs->KSI_STS = MCHP_KSCAN_KSO_SEL_REG_MASK;
|
|
regs->KSI_IEN = MCHP_KSCAN_KSI_IEN_REG_MASK;
|
|
break;
|
|
|
|
case PM_DEVICE_ACTION_SUSPEND:
|
|
regs->KSO_SEL |= BIT(MCHP_KSCAN_KSO_EN_POS);
|
|
regs->KSI_IEN = (~MCHP_KSCAN_KSI_IEN_REG_MASK);
|
|
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;
|
|
struct kscan_regs *regs = cfg->regs;
|
|
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;
|
|
}
|
|
|
|
xec_kbd_clr_slp_en(dev);
|
|
|
|
/* Enable predrive */
|
|
regs->KSO_SEL |= BIT(MCHP_KSCAN_KSO_EN_POS);
|
|
regs->EXT_CTRL = MCHP_KSCAN_EXT_CTRL_PREDRV_EN;
|
|
regs->KSO_SEL &= ~BIT(MCHP_KSCAN_KSO_EN_POS);
|
|
regs->KSI_IEN = MCHP_KSCAN_KSI_IEN_REG_MASK;
|
|
|
|
/* 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);
|
|
|
|
xec_kbd_clear_girq_status(dev);
|
|
xec_kbd_configure_girq(dev);
|
|
|
|
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,
|
|
};
|
|
|
|
/* 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),
|
|
.regs = (struct kscan_regs *)(DT_INST_REG_ADDR(0)),
|
|
.girq = DT_INST_PROP_BY_IDX(0, girqs, 0),
|
|
.girq_pos = DT_INST_PROP_BY_IDX(0, girqs, 1),
|
|
#ifdef CONFIG_SOC_SERIES_MEC172X
|
|
.pcr_idx = DT_INST_PROP_BY_IDX(0, pcrs, 0),
|
|
.pcr_pos = DT_INST_PROP_BY_IDX(0, pcrs, 1),
|
|
#endif
|
|
.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");
|