zephyr/drivers/counter/counter_npcx_lct.c
Etienne Carriere aa3187f793 drivers: Include include/zephyr/sys/clock.h
Header file include/zephyr/sys_clock.h is deprecated and will be removed
someday. Update the whole file tree to include zephyr/sys/clock.h
straight instead of zephyr/sys_clock.h.

This change was made running the sed shell command below:
$ sed -i 's/zephyr\/sys_clock\.h/zephyr\/sys\/clock\.h/' \
      `grep -rsl "zephyr/sys_clock\.h" drivers/`

Signed-off-by: Etienne Carriere <etienne.carriere@st.com>
2026-07-30 07:45:05 -05:00

393 lines
10 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2025 Nuvoton Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/counter.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/clock.h>
#include "soc_miwu.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(counter_npcx_lct, CONFIG_COUNTER_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(nuvoton_npcx_lct_v1)
#define DT_DRV_COMPAT nuvoton_npcx_lct_v1
#elif DT_HAS_COMPAT_STATUS_OKAY(nuvoton_npcx_lct_v2)
#define DT_DRV_COMPAT nuvoton_npcx_lct_v2
#elif DT_HAS_COMPAT_STATUS_OKAY(nuvoton_npck_lct)
#define DT_DRV_COMPAT nuvoton_npck_lct
#endif
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1, "Invalid number of NPCX LCT peripherals");
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(nuvoton_npcx_lct_v1) +
DT_NUM_INST_STATUS_OKAY(nuvoton_npcx_lct_v2) +
DT_NUM_INST_STATUS_OKAY(nuvoton_npck_lct) <= 1,
"Multiple npcx LCT instances in DT");
#define COUNTER_NPCX_LCT_MAX_SECOND DT_INST_PROP(0, maximum_cnt_in_sec)
#define COUNTER_NPCX_LCT_CHECK_TIMEOUT_US 200
#define DAY_PER_WEEK 7U
#define SEC_PER_WEEK ((DAY_PER_WEEK) * (SEC_PER_DAY))
#define NPCX_LCT_PWR_VCC1 0
#define NPCX_LCT_PWR_VSBY 1
#define NPCX_LCT_STAT_EV_MASK 0x01
struct counter_npcx_lct_config {
struct counter_config_info info;
struct lct_reg *reg_base;
bool pwr_by_vsby;
#if defined(CONFIG_COUNTER_NPCX_NPCXN_V1) || defined(CONFIG_COUNTER_NPCX_NPCXN_V2)
const struct npcx_wui lct_wui;
#endif
#if defined(CONFIG_COUNTER_NPCX_NPCKN)
int irq;
#endif
};
struct counter_npcx_lct_data {
struct k_sem lock;
#if defined(CONFIG_COUNTER_NPCX_NPCXN_V1) || defined(CONFIG_COUNTER_NPCX_NPCXN_V2)
struct miwu_callback lct_wui_cb;
#endif
counter_alarm_callback_t alarm_cb;
void *user_data;
};
static void npcx_lct_handle_isr(const struct device *dev)
{
const struct counter_npcx_lct_config *const config = dev->config;
struct counter_npcx_lct_data *data = dev->data;
struct lct_reg *const reg_base = config->reg_base;
counter_alarm_callback_t alarm_cb = data->alarm_cb;
/* The counter needs some time to stop. Wait for the LCT enable bit to clear, or the event
* status wont clear properly.
*/
if (WAIT_FOR(!IS_BIT_SET(reg_base->LCTCONT, NPCX_LCTCONT_LCTEN),
COUNTER_NPCX_LCT_CHECK_TIMEOUT_US, NULL) == false) {
LOG_ERR("The LCT function is still working");
}
reg_base->LCTCONT &= ~BIT(NPCX_LCTCONT_LCTEVEN);
reg_base->LCTSTAT |= BIT(NPCX_LCTSTAT_LCTEVST);
if (data->alarm_cb) {
data->alarm_cb = NULL;
alarm_cb(dev, 0, 0, data->user_data);
}
}
#if defined(CONFIG_COUNTER_NPCX_NPCKN)
static void counter_npcx_lct_isr(const struct device *dev)
{
npcx_lct_handle_isr(dev);
}
#endif
#if defined(CONFIG_COUNTER_NPCX_NPCXN_V1) || defined(CONFIG_COUNTER_NPCX_NPCXN_V2)
static void counter_npcx_lct_isr(const struct device *dev, struct npcx_wui *wui)
{
npcx_lct_handle_isr(dev);
}
#endif
#if defined(CONFIG_COUNTER_NPCX_NPCXN_V2)
#define LCT_REG_WEEKM(base) (base->LCTWEEKM)
#else
#define LCT_REG_WEEKM(base) 0U
#endif
static int npcx_lct_enable(struct lct_reg *const reg_base, bool enable)
{
if (enable) {
reg_base->LCTCONT |= BIT(NPCX_LCTCONT_LCTEN);
} else {
reg_base->LCTCONT &= ~BIT(NPCX_LCTCONT_LCTEN);
}
/* The counter takes time to start and stop. Wait until the LCT enable bit is in the correct
* state, otherwise the hardware behavior may be incorrect.
*/
if (WAIT_FOR(IS_BIT_SET(reg_base->LCTCONT, NPCX_LCTCONT_LCTEN) == enable,
COUNTER_NPCX_LCT_CHECK_TIMEOUT_US, NULL) == false) {
LOG_ERR("LCT enable/disable timeout");
return -ETIMEDOUT;
}
return 0;
}
static void npcx_lct_set_alarm_time(struct lct_reg *const reg_base, int32_t seconds)
{
uint32_t weeks;
weeks = seconds / SEC_PER_WEEK;
#if defined(CONFIG_COUNTER_NPCX_NPCXN_V2)
reg_base->LCTWEEKM = (weeks >> 8) & 0xf;
#endif
reg_base->LCTWEEK = weeks & 0xff;
seconds %= SEC_PER_WEEK;
reg_base->LCTDAY = seconds / SEC_PER_DAY;
seconds %= SEC_PER_DAY;
reg_base->LCTHOUR = seconds / SEC_PER_HOUR;
seconds %= SEC_PER_HOUR;
reg_base->LCTMINUTE = seconds / SEC_PER_MIN;
reg_base->LCTSECOND = seconds % SEC_PER_MIN;
}
static int counter_npcx_lct_cancel_alarm(const struct device *dev, uint8_t chan_id)
{
const struct counter_npcx_lct_config *const config = dev->config;
struct counter_npcx_lct_data *data = dev->data;
struct lct_reg *const reg_base = config->reg_base;
int ret = 0;
if (chan_id != 0) {
LOG_ERR("Invalid channel id %u", chan_id);
return -ENOTSUP;
}
k_sem_take(&data->lock, K_FOREVER);
ret = npcx_lct_enable(reg_base, false);
if (ret != 0) {
LOG_ERR("disable LCT failed");
ret = -EBUSY;
goto return_exit;
}
reg_base->LCTCONT &= ~BIT(NPCX_LCTCONT_LCTEVEN);
#if defined(CONFIG_COUNTER_NPCX_NPCXN_V2)
reg_base->LCTWEEKM = 0;
#endif
reg_base->LCTWEEK = 0;
reg_base->LCTDAY = 0;
reg_base->LCTHOUR = 0;
reg_base->LCTMINUTE = 0;
reg_base->LCTSECOND = 0;
data->alarm_cb = NULL;
data->user_data = NULL;
return_exit:
k_sem_give(&data->lock);
return ret;
}
static int counter_npcx_lct_start(const struct device *dev)
{
const struct counter_npcx_lct_config *const config = dev->config;
struct counter_npcx_lct_data *data = dev->data;
struct lct_reg *const reg_base = config->reg_base;
int ret = 0;
k_sem_take(&data->lock, K_FOREVER);
if (IS_BIT_SET(reg_base->LCTCONT, NPCX_LCTCONT_LCTEN)) {
ret = -EALREADY;
goto return_exit;
}
ret = npcx_lct_enable(reg_base, true);
if (ret != 0) {
LOG_ERR("enable LCT failed");
ret = -EBUSY;
goto return_exit;
}
return_exit:
k_sem_give(&data->lock);
return ret;
}
static int counter_npcx_lct_stop(const struct device *dev)
{
const struct counter_npcx_lct_config *const config = dev->config;
struct counter_npcx_lct_data *data = dev->data;
struct lct_reg *const reg_base = config->reg_base;
int ret = 0;
k_sem_take(&data->lock, K_FOREVER);
if (!IS_BIT_SET(reg_base->LCTCONT, NPCX_LCTCONT_LCTEN)) {
/* Already stop, nothing to do. */
goto return_exit;
}
ret = npcx_lct_enable(reg_base, false);
if (ret != 0) {
LOG_ERR("disable LCT failed");
ret = -EBUSY;
goto return_exit;
}
return_exit:
k_sem_give(&data->lock);
return ret;
}
static int counter_npcx_lct_get_value(const struct device *dev, uint32_t *ticks)
{
const struct counter_npcx_lct_config *const config = dev->config;
struct lct_reg *const reg_base = config->reg_base;
struct counter_npcx_lct_data *data = dev->data;
uint32_t second;
uint8_t week, day, hour, minute;
uint16_t weekm;
k_sem_take(&data->lock, K_FOREVER);
do {
weekm = LCT_REG_WEEKM(reg_base);
week = reg_base->LCTWEEK;
day = reg_base->LCTDAY;
hour = reg_base->LCTHOUR;
minute = reg_base->LCTMINUTE;
second = reg_base->LCTSECOND;
} while (weekm != LCT_REG_WEEKM(reg_base) || week != reg_base->LCTWEEK ||
day != reg_base->LCTDAY || hour != reg_base->LCTHOUR ||
minute != reg_base->LCTMINUTE || second != reg_base->LCTSECOND);
weekm = (weekm << 8) + week;
*ticks = weekm * SEC_PER_WEEK + day * SEC_PER_DAY + hour * SEC_PER_HOUR +
minute * SEC_PER_MIN + second;
k_sem_give(&data->lock);
return 0;
}
static int counter_npcx_lct_set_alarm(const struct device *dev, uint8_t chan_id,
const struct counter_alarm_cfg *alarm_cfg)
{
const struct counter_npcx_lct_config *const config = dev->config;
struct counter_npcx_lct_data *data = dev->data;
struct lct_reg *const reg_base = config->reg_base;
int ret;
if (chan_id != 0) {
LOG_ERR("Invalid channel id %u", chan_id);
ret = -ENOTSUP;
goto return_exit;
}
/*
* Interrupts are only triggered when the counter reaches 0.
* So only relative alarms are supported.
*/
if (alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) {
LOG_ERR("Invalid flags %x", alarm_cfg->flags);
ret = -ENOTSUP;
goto return_exit;
}
if (data->alarm_cb != NULL) {
ret = -EBUSY;
goto return_exit;
}
if (alarm_cfg->ticks > COUNTER_NPCX_LCT_MAX_SECOND) {
ret = -EINVAL;
goto return_exit;
}
k_sem_take(&data->lock, K_FOREVER);
data->alarm_cb = alarm_cfg->callback;
data->user_data = alarm_cfg->user_data;
npcx_lct_set_alarm_time(reg_base, alarm_cfg->ticks);
/* Clear pending LCT event */
reg_base->LCTSTAT |= BIT(NPCX_LCTSTAT_LCTEVST);
reg_base->LCTCONT |= BIT(NPCX_LCTCONT_LCTEVEN);
if (config->pwr_by_vsby == true) {
reg_base->LCTCONT |= BIT(NPCX_LCTCONT_LCTPSLEN);
}
ret = npcx_lct_enable(reg_base, true);
return_exit:
k_sem_give(&data->lock);
return ret;
}
static uint32_t counter_npcx_get_pending_int(const struct device *dev)
{
const struct counter_npcx_lct_config *const config = dev->config;
struct lct_reg *const reg_base = config->reg_base;
return reg_base->LCTSTAT & NPCX_LCT_STAT_EV_MASK;
}
static DEVICE_API(counter, counter_npcx_lct_api) = {
.start = counter_npcx_lct_start,
.stop = counter_npcx_lct_stop,
.get_value = counter_npcx_lct_get_value,
.set_alarm = counter_npcx_lct_set_alarm,
.cancel_alarm = counter_npcx_lct_cancel_alarm,
.get_pending_int = counter_npcx_get_pending_int,
};
static int counter_npcx_lct_init(const struct device *dev)
{
const struct counter_npcx_lct_config *const config = dev->config;
struct counter_npcx_lct_data *const data = dev->data;
struct lct_reg *const reg_base = config->reg_base;
#if defined(CONFIG_COUNTER_NPCX_NPCXN_V1) || defined(CONFIG_COUNTER_NPCX_NPCXN_V2)
npcx_miwu_init_dev_callback(&data->lct_wui_cb, &config->lct_wui, counter_npcx_lct_isr, dev);
npcx_miwu_manage_callback(&data->lct_wui_cb, true);
npcx_miwu_interrupt_configure(&config->lct_wui, NPCX_MIWU_MODE_EDGE, NPCX_MIWU_TRIG_HIGH);
/* Enable irq of t0-out expired event */
npcx_miwu_irq_enable(&config->lct_wui);
reg_base->LCTCONT |= BIT(NPCX_LCTCONT_LCT_CLK_EN);
#endif
#if defined(CONFIG_COUNTER_NPCX_NPCKN)
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), counter_npcx_lct_isr,
DEVICE_DT_INST_GET(0), 0);
irq_enable(DT_INST_IRQN(0));
#endif
k_sem_init(&data->lock, 1, 1);
if (config->pwr_by_vsby == true) {
reg_base->LCTCONT |= BIT(NPCX_LCTCONT_LCT_VSBY_PWR);
} else {
reg_base->LCTCONT &= ~BIT(NPCX_LCTCONT_LCT_VSBY_PWR);
}
return 0;
}
static const struct counter_npcx_lct_config npcx_lct_cfg = {
.info = {
.channels = 1,
},
.reg_base = (struct lct_reg *)DT_INST_REG_ADDR(0),
.pwr_by_vsby = DT_INST_PROP(0, pwr_by_vsby),
#if defined(CONFIG_COUNTER_NPCX_NPCKN)
.irq = DT_INST_IRQN(0),
#endif
#if defined(CONFIG_COUNTER_NPCX_NPCXN_V1) || defined(CONFIG_COUNTER_NPCX_NPCXN_V2)
.lct_wui = NPCX_DT_WUI_ITEM_BY_NAME(0, lct_wui),
#endif
};
static struct counter_npcx_lct_data npcx_lct_data;
DEVICE_DT_INST_DEFINE(0, counter_npcx_lct_init, NULL, &npcx_lct_data, &npcx_lct_cfg, POST_KERNEL,
CONFIG_COUNTER_INIT_PRIORITY, &counter_npcx_lct_api);