/* * Copyright (c) 2021 Nuvoton Technology Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nuvoton_npcx_ps2_ctrl /** * @file * @brief Nuvoton NPCX PS/2 module (controller) driver * * This file contains the driver of PS/2 module (controller) which provides a * hardware accelerator mechanism to handle both incoming and outgoing data. * The hardware accelerator mechanism is shared by four PS/2 channels. */ #include #include #include #include #include #include LOG_MODULE_REGISTER(ps2_npcx_ctrl, CONFIG_PS2_LOG_LEVEL); #define NPCX_PS2_CH_COUNT 4 /* * Set WDAT3-0 and clear CLK3-0 in the PSOSIG register to * reset the shift mechanism. */ #define NPCX_PS2_SHIFT_MECH_RESET (uint8_t)~NPCX_PSOSIG_CLK_MASK_ALL /* in 50us units */ #define PS2_RETRY_COUNT 10000 /* * The max duration of a PS/2 clock is about 100 micro-seconds. * A PS/2 transaction needs 11 clock cycles. It will take about 1.1 ms for a * complete transaction. */ #define PS2_TRANSACTION_TIMEOUT K_MSEC(2) /* Device config */ struct ps2_npcx_ctrl_config { uintptr_t base; struct npcx_clk_cfg clk_cfg; }; /* Driver data */ struct ps2_npcx_ctrl_data { /* * Bit mask to record the enabled PS/2 channels. * Only bit[7] and bit[5:3] are used * (i.e. the bit position of CLK3-0 in the PS2_PSOSIG register) */ uint8_t channel_enabled_mask; /* The mutex of the PS/2 controller */ struct k_sem lock; /* The semaphore to synchronize the Tx transaction */ struct k_sem tx_sync_sem; /* * The callback function to handle the data received from PS/2 device */ ps2_callback_t callback_isr[NPCX_PS2_CH_COUNT]; }; /* Driver convenience defines */ #define HAL_PS2_INSTANCE(dev) \ ((struct ps2_reg *)((const struct ps2_npcx_ctrl_config *)(dev)->config)->base) static uint8_t ps2_npcx_ctrl_get_ch_clk_mask(uint8_t channel_id) { return BIT(NPCX_PSOSIG_CLK(channel_id)); } int ps2_npcx_ctrl_configure(const struct device *dev, uint8_t channel_id, ps2_callback_t callback_isr) { struct ps2_npcx_ctrl_data *const data = dev->data; if (channel_id >= NPCX_PS2_CH_COUNT) { LOG_ERR("unexpected channel ID: %d", channel_id); return -EINVAL; } if (callback_isr == NULL) { return -EINVAL; } k_sem_take(&data->lock, K_FOREVER); data->callback_isr[channel_id] = callback_isr; k_sem_give(&data->lock); return 0; } int ps2_npcx_ctrl_enable_interface(const struct device *dev, uint8_t channel_id, bool enable) { struct ps2_npcx_ctrl_data *const data = dev->data; struct ps2_reg *const inst = HAL_PS2_INSTANCE(dev); uint8_t ch_clk_mask; k_sem_take(&data->lock, K_FOREVER); /* * Disable the interrupt during changing the enabled channel mask to * prevent from preemption. */ irq_disable(DT_INST_IRQN(0)); if (channel_id >= NPCX_PS2_CH_COUNT) { LOG_ERR("unexpected channel ID: %d", channel_id); irq_enable(DT_INST_IRQN(0)); k_sem_give(&data->lock); return -EINVAL; } ch_clk_mask = ps2_npcx_ctrl_get_ch_clk_mask(channel_id); if (enable) { data->channel_enabled_mask |= ch_clk_mask; /* Enable the relevant channel clock */ inst->PSOSIG |= ch_clk_mask; } else { data->channel_enabled_mask &= ~ch_clk_mask; /* Disable the relevant channel clock */ inst->PSOSIG &= ~ch_clk_mask; } irq_enable(DT_INST_IRQN(0)); k_sem_give(&data->lock); return 0; } static int ps2_npcx_ctrl_bus_busy(const struct device *dev) { struct ps2_reg *const inst = HAL_PS2_INSTANCE(dev); /* * The driver pulls the CLK for non-active channels to low when Start * bit is detected and pull the CLK of the active channel low after * Stop bit detected. The EOT bit is set when Stop bit is detected, * but both SOT and EOT are cleared when all CLKs are pull low * (due to Shift Mechanism is reset) */ return (IS_BIT_SET(inst->PSTAT, NPCX_PSTAT_SOT) || IS_BIT_SET(inst->PSTAT, NPCX_PSTAT_EOT)) ? -EBUSY : 0; } int ps2_npcx_ctrl_write(const struct device *dev, uint8_t channel_id, uint8_t value) { struct ps2_npcx_ctrl_data *const data = dev->data; struct ps2_reg *const inst = HAL_PS2_INSTANCE(dev); int i = 0; if (channel_id >= NPCX_PS2_CH_COUNT) { LOG_ERR("unexpected channel ID: %d", channel_id); return -EINVAL; } if (!(ps2_npcx_ctrl_get_ch_clk_mask(channel_id) & data->channel_enabled_mask)) { LOG_ERR("channel %d is not enabled", channel_id); return -EINVAL; } k_sem_take(&data->lock, K_FOREVER); while (ps2_npcx_ctrl_bus_busy(dev) && (i < PS2_RETRY_COUNT)) { k_busy_wait(50); i++; } if (unlikely(i == PS2_RETRY_COUNT)) { LOG_ERR("PS2 write attempt timed out"); goto timeout_invalid; } /* Set PS/2 in transmit mode */ inst->PSCON |= BIT(NPCX_PSCON_XMT); /* Enable Start Of Transaction interrupt */ inst->PSIEN |= BIT(NPCX_PSIEN_SOTIE); /* Reset the shift mechanism */ inst->PSOSIG = NPCX_PS2_SHIFT_MECH_RESET; /* Inhibit communication should last at least 100 micro-seconds */ k_busy_wait(100); /* Write the data to be transmitted */ inst->PSDAT = value; /* Apply the Request-to-send */ inst->PSOSIG &= ~BIT(NPCX_PSOSIG_WDAT(channel_id)); inst->PSOSIG |= ps2_npcx_ctrl_get_ch_clk_mask(channel_id); if (k_sem_take(&data->tx_sync_sem, PS2_TRANSACTION_TIMEOUT) != 0) { irq_disable(DT_INST_IRQN(0)); LOG_ERR("PS/2 Tx timeout"); /* Reset the shift mechanism */ inst->PSOSIG = NPCX_PS2_SHIFT_MECH_RESET; /* Change the PS/2 module to receive mode */ inst->PSCON &= ~BIT(NPCX_PSCON_XMT); /* * Restore the channel back to enable according to * channel_enabled_mask. */ inst->PSOSIG |= data->channel_enabled_mask; irq_enable(DT_INST_IRQN(0)); goto timeout_invalid; } k_sem_give(&data->lock); return 0; timeout_invalid: k_sem_give(&data->lock); return -ETIMEDOUT; } static int ps2_npcx_ctrl_is_rx_error(const struct device *dev) { struct ps2_reg *const inst = HAL_PS2_INSTANCE(dev); uint8_t status; status = inst->PSTAT & (BIT(NPCX_PSTAT_PERR) | BIT(NPCX_PSTAT_RFERR)); if (status) { if (status & BIT(NPCX_PSTAT_PERR)) { LOG_ERR("RX parity error"); } if (status & BIT(NPCX_PSTAT_RFERR)) { LOG_ERR("RX Frame error"); } return -EIO; } return 0; } static void ps2_npcx_ctrl_isr(const struct device *dev) { uint8_t active_ch, mask; struct ps2_reg *const inst = HAL_PS2_INSTANCE(dev); struct ps2_npcx_ctrl_data *const data = dev->data; /* * ACH = 1 : Channel 0 * ACH = 2 : Channel 1 * ACH = 4 : Channel 2 * ACH = 5 : Channel 3 */ active_ch = GET_FIELD(inst->PSTAT, NPCX_PSTAT_ACH); active_ch = active_ch > 2 ? (active_ch - 2) : (active_ch - 1); LOG_DBG("ACH: %d\n", active_ch); /* * Inhibit PS/2 transaction of the other non-active channels by * pulling down the clock signal */ mask = ~NPCX_PSOSIG_CLK_MASK_ALL | BIT(NPCX_PSOSIG_CLK(active_ch)); inst->PSOSIG &= mask; /* PS/2 Start of Transaction */ if (IS_BIT_SET(inst->PSTAT, NPCX_PSTAT_SOT) && IS_BIT_SET(inst->PSIEN, NPCX_PSIEN_SOTIE)) { /* * Once set, SOT is not cleared until the shift mechanism * is reset. Therefore, SOTIE should be cleared on the * first occurrence of an SOT interrupt. */ inst->PSIEN &= ~BIT(NPCX_PSIEN_SOTIE); LOG_DBG("SOT"); /* PS/2 End of Transaction */ } else if (IS_BIT_SET(inst->PSTAT, NPCX_PSTAT_EOT)) { inst->PSIEN &= ~BIT(NPCX_PSIEN_EOTIE); /* * Clear the CLK of active channel to reset * the shift mechanism */ inst->PSOSIG &= ~BIT(NPCX_PSOSIG_CLK(active_ch)); /* Tx is done */ if (IS_BIT_SET(inst->PSCON, NPCX_PSCON_XMT)) { /* Change the PS/2 module to receive mode */ inst->PSCON &= ~BIT(NPCX_PSCON_XMT); k_sem_give(&data->tx_sync_sem); } else { if (ps2_npcx_ctrl_is_rx_error(dev) == 0) { ps2_callback_t callback; uint8_t data_in = inst->PSDAT; LOG_DBG("Recv:0x%02x", data_in); callback = data->callback_isr[active_ch]; if (callback != NULL) { callback(dev, data_in); } } } /* Restore the enabled channel */ inst->PSOSIG |= data->channel_enabled_mask; /* * Re-enable the Start Of Transaction interrupt when * the shift mechanism is reset */ inst->PSIEN |= BIT(NPCX_PSIEN_SOTIE); inst->PSIEN |= BIT(NPCX_PSIEN_EOTIE); LOG_DBG("EOT"); } } /* PS/2 driver registration */ static int ps2_npcx_ctrl_init(const struct device *dev); static struct ps2_npcx_ctrl_data ps2_npcx_ctrl_data_0; static const struct ps2_npcx_ctrl_config ps2_npcx_ctrl_config_0 = { .base = DT_INST_REG_ADDR(0), .clk_cfg = NPCX_DT_CLK_CFG_ITEM(0), }; DEVICE_DT_INST_DEFINE(0, &ps2_npcx_ctrl_init, NULL, &ps2_npcx_ctrl_data_0, &ps2_npcx_ctrl_config_0, POST_KERNEL, CONFIG_PS2_INIT_PRIORITY, NULL); static int ps2_npcx_ctrl_init(const struct device *dev) { const struct ps2_npcx_ctrl_config *const config = dev->config; struct ps2_npcx_ctrl_data *const data = dev->data; struct ps2_reg *const inst = HAL_PS2_INSTANCE(dev); const struct device *const clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE); int ret; if (!device_is_ready(clk_dev)) { LOG_ERR("%s device not ready", clk_dev->name); return -ENODEV; } /* Turn on PS/2 controller device clock */ ret = clock_control_on(clk_dev, (clock_control_subsys_t)&config->clk_cfg); if (ret < 0) { LOG_ERR("Turn on PS/2 clock fail %d", ret); return ret; } /* Disable shift mechanism and configure PS/2 to received mode. */ inst->PSCON = 0x0; /* Set WDAT3-0 and clear CLK3-0 before enabling shift mechanism */ inst->PSOSIG = NPCX_PS2_SHIFT_MECH_RESET; /* * PS/2 interrupt enable register * [0] - : SOTIE = 1: Start Of Transaction Interrupt Enable * [1] - : EOTIE = 1: End Of Transaction Interrupt Enable * [4] - : WUE = 1: Wake-Up Enable * [7] - : CLK_SEL = 1: Select Free-Run clock as the basic clock * 0: Select APB1 clock as the basic clock */ inst->PSIEN = BIT(NPCX_PSIEN_SOTIE) | BIT(NPCX_PSIEN_EOTIE) | BIT(NPCX_PSIEN_PS2_WUE); if (config->clk_cfg.bus == NPCX_CLOCK_BUS_FREERUN) { inst->PSIEN |= BIT(NPCX_PSIEN_PS2_CLK_SEL); } /* Enable weak internal pull-up */ inst->PSCON |= BIT(NPCX_PSCON_WPUED); /* Enable shift mechanism */ inst->PSCON |= BIT(NPCX_PSCON_EN); k_sem_init(&data->lock, 1, 1); k_sem_init(&data->tx_sync_sem, 0, 1); IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), ps2_npcx_ctrl_isr, DEVICE_DT_INST_GET(0), 0); irq_enable(DT_INST_IRQN(0)); return 0; }