| /* |
| * Copyright (c) 2019 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT microchip_xec_ps2 |
| |
| #include <errno.h> |
| #include <device.h> |
| #include <drivers/ps2.h> |
| #include <soc.h> |
| #include <logging/log.h> |
| |
| #define LOG_LEVEL CONFIG_PS2_LOG_LEVEL |
| LOG_MODULE_REGISTER(ps2_mchp_xec); |
| |
| /* in 50us units */ |
| #define PS2_TIMEOUT 10000 |
| |
| struct ps2_xec_config { |
| PS2_Type *base; |
| uint8_t girq_id; |
| uint8_t girq_bit; |
| uint8_t isr_nvic; |
| }; |
| |
| struct ps2_xec_data { |
| ps2_callback_t callback_isr; |
| struct k_sem tx_lock; |
| }; |
| |
| static int ps2_xec_configure(const struct device *dev, |
| ps2_callback_t callback_isr) |
| { |
| const struct ps2_xec_config *config = dev->config; |
| struct ps2_xec_data *data = dev->data; |
| PS2_Type *base = config->base; |
| |
| uint8_t __attribute__((unused)) dummy; |
| |
| if (!callback_isr) { |
| return -EINVAL; |
| } |
| |
| data->callback_isr = callback_isr; |
| |
| /* In case the self test for a PS2 device already finished and |
| * set the SOURCE bit to 1 we clear it before enabling the |
| * interrupts. Instances must be allocated before the BAT or |
| * the host may time out. |
| */ |
| MCHP_GIRQ_SRC(config->girq_id) = BIT(config->girq_bit); |
| dummy = base->TRX_BUFF; |
| base->STATUS = MCHP_PS2_STATUS_RW1C_MASK; |
| |
| /* Enable FSM and init instance in rx mode*/ |
| base->CTRL = MCHP_PS2_CTRL_EN_POS; |
| |
| /* We enable the interrupts in the EC aggregator so that the |
| * result can be forwarded to the ARM NVIC |
| */ |
| MCHP_GIRQ_ENSET(config->girq_id) = BIT(config->girq_bit); |
| |
| k_sem_give(&data->tx_lock); |
| |
| return 0; |
| } |
| |
| |
| static int ps2_xec_write(const struct device *dev, uint8_t value) |
| { |
| const struct ps2_xec_config *config = dev->config; |
| struct ps2_xec_data *data = dev->data; |
| PS2_Type *base = config->base; |
| int i = 0; |
| |
| uint8_t __attribute__((unused)) dummy; |
| |
| if (k_sem_take(&data->tx_lock, K_NO_WAIT)) { |
| return -EACCES; |
| } |
| /* Allow the PS2 controller to complete a RX transaction. This |
| * is because the channel may be actively receiving data. |
| * In addition, it is necessary to wait for a previous TX |
| * transaction to complete. The PS2 block has a single |
| * FSM. |
| */ |
| while (((base->STATUS & |
| (MCHP_PS2_STATUS_RX_BUSY | MCHP_PS2_STATUS_TX_IDLE)) |
| != MCHP_PS2_STATUS_TX_IDLE) && (i < PS2_TIMEOUT)) { |
| k_busy_wait(50); |
| i++; |
| } |
| |
| if (unlikely(i == PS2_TIMEOUT)) { |
| LOG_DBG("PS2 write timed out"); |
| return -ETIMEDOUT; |
| } |
| |
| /* Inhibit ps2 controller and clear status register */ |
| base->CTRL = 0x00; |
| |
| /* Read to clear data ready bit in the status register*/ |
| dummy = base->TRX_BUFF; |
| k_sleep(K_MSEC(1)); |
| base->STATUS = MCHP_PS2_STATUS_RW1C_MASK; |
| |
| /* Switch the interface to TX mode and enable state machine */ |
| base->CTRL = MCHP_PS2_CTRL_TR_TX | MCHP_PS2_CTRL_EN; |
| |
| /* Write value to TX/RX register */ |
| base->TRX_BUFF = value; |
| |
| k_sem_give(&data->tx_lock); |
| |
| return 0; |
| } |
| |
| static int ps2_xec_inhibit_interface(const struct device *dev) |
| { |
| const struct ps2_xec_config *config = dev->config; |
| struct ps2_xec_data *data = dev->data; |
| PS2_Type *base = config->base; |
| |
| if (k_sem_take(&data->tx_lock, K_MSEC(10)) != 0) { |
| return -EACCES; |
| } |
| |
| base->CTRL = 0x00; |
| MCHP_GIRQ_SRC(config->girq_id) = BIT(config->girq_bit); |
| NVIC_ClearPendingIRQ(config->isr_nvic); |
| |
| k_sem_give(&data->tx_lock); |
| |
| return 0; |
| } |
| |
| static int ps2_xec_enable_interface(const struct device *dev) |
| { |
| const struct ps2_xec_config *config = dev->config; |
| struct ps2_xec_data *data = dev->data; |
| PS2_Type *base = config->base; |
| |
| MCHP_GIRQ_SRC(config->girq_id) = BIT(config->girq_bit); |
| base->CTRL = MCHP_PS2_CTRL_EN; |
| |
| k_sem_give(&data->tx_lock); |
| |
| return 0; |
| } |
| static void ps2_xec_isr(const struct device *dev) |
| { |
| const struct ps2_xec_config *config = dev->config; |
| struct ps2_xec_data *data = dev->data; |
| PS2_Type *base = config->base; |
| uint32_t status; |
| |
| MCHP_GIRQ_SRC(config->girq_id) = BIT(config->girq_bit); |
| |
| /* Read and clear status */ |
| status = base->STATUS; |
| |
| if (status & MCHP_PS2_STATUS_RXD_RDY) { |
| base->CTRL = 0x00; |
| if (data->callback_isr) { |
| data->callback_isr(dev, base->TRX_BUFF); |
| } |
| } else if (status & |
| (MCHP_PS2_STATUS_TX_TMOUT | MCHP_PS2_STATUS_TX_ST_TMOUT)) { |
| /* Clear sticky bits and go to read mode */ |
| base->STATUS = MCHP_PS2_STATUS_RW1C_MASK; |
| LOG_ERR("TX time out: %0x", status); |
| } |
| |
| /* The control register reverts to RX automatically after |
| * transmitting the data |
| */ |
| base->CTRL = MCHP_PS2_CTRL_EN; |
| } |
| |
| static const struct ps2_driver_api ps2_xec_driver_api = { |
| .config = ps2_xec_configure, |
| .read = NULL, |
| .write = ps2_xec_write, |
| .disable_callback = ps2_xec_inhibit_interface, |
| .enable_callback = ps2_xec_enable_interface, |
| }; |
| |
| #ifdef CONFIG_PS2_XEC_0 |
| static int ps2_xec_init_0(const struct device *dev); |
| |
| static const struct ps2_xec_config ps2_xec_config_0 = { |
| .base = (PS2_Type *) DT_INST_REG_ADDR(0), |
| .girq_id = DT_INST_PROP(0, girq), |
| .girq_bit = DT_INST_PROP(0, girq_bit), |
| .isr_nvic = DT_INST_IRQN(0), |
| }; |
| |
| static struct ps2_xec_data ps2_xec_port_data_0; |
| |
| DEVICE_DT_INST_DEFINE(0, |
| &ps2_xec_init_0, |
| NULL, |
| &ps2_xec_port_data_0, &ps2_xec_config_0, |
| POST_KERNEL, CONFIG_PS2_INIT_PRIORITY, |
| &ps2_xec_driver_api); |
| |
| |
| static int ps2_xec_init_0(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| struct ps2_xec_data *data = dev->data; |
| |
| k_sem_init(&data->tx_lock, 0, 1); |
| |
| IRQ_CONNECT(DT_INST_IRQN(0), |
| DT_INST_IRQ(0, priority), |
| ps2_xec_isr, DEVICE_DT_INST_GET(0), 0); |
| |
| irq_enable(DT_INST_IRQN(0)); |
| |
| return 0; |
| } |
| #endif /* CONFIG_PS2_XEC_0 */ |
| |
| #ifdef CONFIG_PS2_XEC_1 |
| static int ps2_xec_init_1(const struct device *dev); |
| |
| static const struct ps2_xec_config ps2_xec_config_1 = { |
| .base = (PS2_Type *) DT_INST_REG_ADDR(1), |
| .girq_id = DT_INST_PROP(1, girq), |
| .girq_bit = DT_INST_PROP(1, girq_bit), |
| .isr_nvic = DT_INST_IRQN(1), |
| |
| }; |
| |
| static struct ps2_xec_data ps2_xec_port_data_1; |
| |
| DEVICE_DT_INST_DEFINE(1, |
| &ps2_xec_init_1, |
| NULL, |
| &ps2_xec_port_data_1, &ps2_xec_config_1, |
| POST_KERNEL, CONFIG_PS2_INIT_PRIORITY, |
| &ps2_xec_driver_api); |
| |
| static int ps2_xec_init_1(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| struct ps2_xec_data *data = dev->data; |
| |
| k_sem_init(&data->tx_lock, 0, 1); |
| |
| IRQ_CONNECT(DT_INST_IRQN(1), |
| DT_INST_IRQ(1, priority), |
| ps2_xec_isr, DEVICE_DT_INST_GET(1), 0); |
| |
| irq_enable(DT_INST_IRQN(1)); |
| |
| return 0; |
| } |
| #endif /* CONFIG_PS2_XEC_1 */ |