/* * 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 #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_SOC_SERIES_MEC172X #include #include #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");