Add ITE UART wrapper to enable serial driver of ns16550. Signed-off-by: Tim Lin <tim2.lin@ite.corp-partner.google.com>
164 lines
5.6 KiB
C
164 lines
5.6 KiB
C
/*
|
|
* Copyright (c) 2025 ITE Corporation. All Rights Reserved.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT ite_it51xxx_uart
|
|
|
|
#include <soc.h>
|
|
#include <soc_dt.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/interrupt_controller/wuc_ite_it51xxx.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <zephyr/drivers/uart.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/pm/device.h>
|
|
#include <zephyr/pm/policy.h>
|
|
|
|
LOG_MODULE_REGISTER(uart_ite_it51xxx, CONFIG_UART_LOG_LEVEL);
|
|
|
|
struct it51xxx_uart_wuc_map_cfg {
|
|
/* WUC control device structure */
|
|
const struct device *wucs;
|
|
/* WUC pin mask */
|
|
uint8_t mask;
|
|
};
|
|
|
|
struct uart_it51xxx_config {
|
|
/* UART wake-up input source configuration list */
|
|
const struct it51xxx_uart_wuc_map_cfg *wuc_map_list;
|
|
const struct device *clk_dev;
|
|
/* clock configuration */
|
|
struct ite_clk_cfg clk_cfg;
|
|
/* UART interrupt */
|
|
uint8_t irq;
|
|
};
|
|
|
|
struct uart_it51xxx_data {
|
|
#ifdef CONFIG_UART_CONSOLE_INPUT_EXPIRED
|
|
struct k_work_delayable rx_refresh_timeout_work;
|
|
#endif
|
|
};
|
|
|
|
static void it51xxx_uart_wui_isr(const void *arg)
|
|
{
|
|
const struct device *dev = arg;
|
|
const struct uart_it51xxx_config *const config = dev->config;
|
|
|
|
/* Disable interrupts on UART RX pin to avoid repeated interrupts. */
|
|
irq_disable(config->irq);
|
|
/* W/C wakeup interrupt status of UART pin */
|
|
it51xxx_wuc_clear_status(config->wuc_map_list[0].wucs, config->wuc_map_list[0].mask);
|
|
|
|
/* Refresh console expired time if got UART Rx wake-up event */
|
|
#ifdef CONFIG_UART_CONSOLE_INPUT_EXPIRED
|
|
struct uart_it51xxx_data *data = dev->data;
|
|
k_timeout_t delay = K_MSEC(CONFIG_UART_CONSOLE_INPUT_EXPIRED_TIMEOUT);
|
|
|
|
/*
|
|
* The pm state of it51xxx chip only supports standby, so here we
|
|
* can directly set the constraint for standby.
|
|
*/
|
|
pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
|
|
k_work_reschedule(&data->rx_refresh_timeout_work, delay);
|
|
#endif
|
|
}
|
|
|
|
static inline int uart_it51xxx_pm_action(const struct device *dev, enum pm_device_action action)
|
|
{
|
|
const struct uart_it51xxx_config *const config = dev->config;
|
|
|
|
switch (action) {
|
|
/* Next device power state is in active. */
|
|
case PM_DEVICE_ACTION_RESUME:
|
|
/* Nothing to do. */
|
|
break;
|
|
/* Next device power state is deep doze mode */
|
|
case PM_DEVICE_ACTION_SUSPEND:
|
|
/* W/C wake-up interrupt status of UART pin */
|
|
it51xxx_wuc_clear_status(config->wuc_map_list[0].wucs,
|
|
config->wuc_map_list[0].mask);
|
|
/* W/C interrupt status of UART pin */
|
|
ite_intc_isr_clear(config->irq);
|
|
/* Enable UART interrupt */
|
|
irq_enable(config->irq);
|
|
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_UART_CONSOLE_INPUT_EXPIRED
|
|
static void uart_it51xxx_rx_refresh_timeout(struct k_work *work)
|
|
{
|
|
ARG_UNUSED(work);
|
|
|
|
pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
|
|
}
|
|
#endif
|
|
|
|
static int uart_it51xxx_init(const struct device *dev)
|
|
{
|
|
const struct uart_it51xxx_config *const config = dev->config;
|
|
int ret;
|
|
|
|
/* Enable clock to specified peripheral */
|
|
ret = clock_control_on(config->clk_dev, (clock_control_subsys_t *)&config->clk_cfg);
|
|
if (ret < 0) {
|
|
LOG_ERR("Turn on clock fail %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Select wakeup interrupt falling-edge triggered of UART pin */
|
|
it51xxx_wuc_set_polarity(config->wuc_map_list[0].wucs, config->wuc_map_list[0].mask,
|
|
WUC_TYPE_EDGE_FALLING);
|
|
/* W/C wakeup interrupt status of UART pin */
|
|
it51xxx_wuc_clear_status(config->wuc_map_list[0].wucs, config->wuc_map_list[0].mask);
|
|
/* Enable wakeup interrupt of UART pin */
|
|
it51xxx_wuc_enable(config->wuc_map_list[0].wucs, config->wuc_map_list[0].mask);
|
|
|
|
/*
|
|
* We need to configure UART Rx interrupt as wakeup source and initialize
|
|
* a delayable work for console expired time.
|
|
*/
|
|
#ifdef CONFIG_UART_CONSOLE_INPUT_EXPIRED
|
|
struct uart_it51xxx_data *data = dev->data;
|
|
|
|
k_work_init_delayable(&data->rx_refresh_timeout_work, uart_it51xxx_rx_refresh_timeout);
|
|
#endif
|
|
/*
|
|
* When the system enters deep doze, all clocks are gated only the
|
|
* 32.768k clock is active. We need to wakeup EC by configuring
|
|
* UART Rx interrupt as a wakeup source. When the interrupt of UART
|
|
* Rx falling, EC will be woken.
|
|
*/
|
|
irq_connect_dynamic(config->irq, 0, it51xxx_uart_wui_isr, dev, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define UART_ITE_IT51XXX_INIT(inst) \
|
|
static const struct it51xxx_uart_wuc_map_cfg \
|
|
it51xxx_uart_wuc_##inst[IT8XXX2_DT_INST_WUCCTRL_LEN(inst)] = \
|
|
IT8XXX2_DT_WUC_ITEMS_LIST(inst); \
|
|
static struct uart_it51xxx_data uart_it51xxx_data_##inst; \
|
|
static const struct uart_it51xxx_config uart_it51xxx_cfg_##inst = { \
|
|
.wuc_map_list = it51xxx_uart_wuc_##inst, \
|
|
.clk_dev = DEVICE_DT_GET(DT_INST_PHANDLE(inst, clocks)), \
|
|
.clk_cfg = {.ctrl = DT_INST_CLOCKS_CELL(inst, ctrl), \
|
|
.bits = DT_INST_CLOCKS_CELL(inst, bits)}, \
|
|
.irq = DT_INST_IRQN(inst), \
|
|
}; \
|
|
PM_DEVICE_DT_INST_DEFINE(inst, uart_it51xxx_pm_action); \
|
|
DEVICE_DT_INST_DEFINE(inst, uart_it51xxx_init, PM_DEVICE_DT_INST_GET(inst), \
|
|
&uart_it51xxx_data_##inst, &uart_it51xxx_cfg_##inst, PRE_KERNEL_1, \
|
|
CONFIG_SERIAL_INIT_PRIORITY, NULL);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(UART_ITE_IT51XXX_INIT)
|