zephyr/drivers/sensor/nuvoton_tach_npcx/tach_nuvoton_npcx.c

391 lines
12 KiB
C

/*
* Copyright (c) 2021 Nuvoton Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nuvoton_npcx_tach
/**
* @file
* @brief Nuvoton NPCX tachometer sensor module driver
*
* This file contains a driver for the tachometer sensor module which contains
* two independent timers (counter 1 and 2). They are used to capture a counter
* value when the signals via external pins match the condition. The following
* is block diagram of this module when it set to mode 5.
*
* | Capture A
* | | +-----------+ TA Pin
* +-----------+ | +-----+-----+ | _ _ | |
* APB_CLK-->| Prescaler |--->|---+--->| Counter 1 |<--| _| |_| |_ |<--+
* +-----------+ | | +-----------+ +-----------+
* | CLK_SEL Edge Detection
* | Capture B
* LFCLK--------------------->| | +-----------+ TB Pin
* | +-----+-----+ | _ _ | |
* |---+--->| Counter 2 |<--| _| |_| |_ |<--+
* | | +-----------+ +-----------+
* | CLK_SEL Edge Detection
* |
* | TACH_CLK
* +----------
* (NPCX Tachometer Mode 5, Dual-Independent Input Capture)
*
* This mode is used to measure either the frequency of two external clocks
* (via TA or TB pins) that are slower than TACH_CLK. A transition event (rising
* or falling edge) received on TAn/TBn pin causes a transfer of timer 1/2
* contents to Capture register and reload counter. Based on this value, we can
* compute the current RPM of external signal from encoders.
*/
#include <errno.h>
#include <device.h>
#include <drivers/clock_control.h>
#include <drivers/sensor.h>
#include <dt-bindings/sensor/npcx_tach.h>
#include <soc.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(tach_npcx, CONFIG_SENSOR_LOG_LEVEL);
/* Device config */
struct tach_npcx_config {
/* tachometer controller base address */
uintptr_t base;
/* clock configuration */
struct npcx_clk_cfg clk_cfg;
/* pinmux configuration */
const uint8_t alts_size;
const struct npcx_alt *alts_list;
/* sampling clock frequency of tachometer */
uint32_t sample_clk;
/* selected port of tachometer */
int port;
/* number of pulses (holes) per round of tachometer's input (encoder) */
int pulses_per_round;
};
/* Driver data */
struct tach_npcx_data {
/* Input clock for tachometers */
uint32_t input_clk;
/* Captured counts of tachometer */
uint32_t capture;
};
/* Driver convenience defines */
#define DRV_CONFIG(dev) ((const struct tach_npcx_config *)(dev)->config)
#define DRV_DATA(dev) ((struct tach_npcx_data *)(dev)->data)
#define HAL_INSTANCE(dev) (struct tach_reg *)(DRV_CONFIG(dev)->base)
/* Maximum count of prescaler */
#define NPCX_TACHO_PRSC_MAX 0xff
/* Maximum count of counter */
#define NPCX_TACHO_CNT_MAX 0xffff
/* Operation mode used for tachometer */
#define NPCX_TACH_MDSEL 4
/* Clock selection for tachometer */
#define NPCX_CLKSEL_APBCLK 1
#define NPCX_CLKSEL_LFCLK 4
/* TACH inline local functions */
static inline void tach_npcx_start_port_a(const struct device *dev)
{
struct tach_npcx_data *const data = DRV_DATA(dev);
struct tach_reg *const inst = HAL_INSTANCE(dev);
/* Set the default value of counter and capture register of timer 1. */
inst->TCNT1 = NPCX_TACHO_CNT_MAX;
inst->TCRA = NPCX_TACHO_CNT_MAX;
/*
* Set the edge detection polarity of port A to falling (high-to-low
* transition) and enable the functionality to capture TCNT1 into TCRA
* and preset TCNT1 when event is triggered.
*/
inst->TMCTRL |= BIT(NPCX_TMCTRL_TAEN);
/* Enable input debounce logic into TA pin. */
inst->TCFG |= BIT(NPCX_TCFG_TADBEN);
/* Select clock source of timer 1 from no clock and start to count. */
SET_FIELD(inst->TCKC, NPCX_TCKC_C1CSEL_FIELD, data->input_clk == LFCLK
? NPCX_CLKSEL_LFCLK : NPCX_CLKSEL_APBCLK);
}
static inline void tach_npcx_start_port_b(const struct device *dev)
{
struct tach_reg *const inst = HAL_INSTANCE(dev);
struct tach_npcx_data *const data = DRV_DATA(dev);
/* Set the default value of counter and capture register of timer 2. */
inst->TCNT2 = NPCX_TACHO_CNT_MAX;
inst->TCRB = NPCX_TACHO_CNT_MAX;
/*
* Set the edge detection polarity of port B to falling (high-to-low
* transition) and enable the functionality to capture TCNT2 into TCRB
* and preset TCNT2 when event is triggered.
*/
inst->TMCTRL |= BIT(NPCX_TMCTRL_TBEN);
/* Enable input debounce logic into TB pin. */
inst->TCFG |= BIT(NPCX_TCFG_TBDBEN);
/* Select clock source of timer 2 from no clock and start to count. */
SET_FIELD(inst->TCKC, NPCX_TCKC_C2CSEL_FIELD, data->input_clk == LFCLK
? NPCX_CLKSEL_LFCLK : NPCX_CLKSEL_APBCLK);
}
static inline bool tach_npcx_is_underflow(const struct device *dev)
{
const struct tach_npcx_config *const config = DRV_CONFIG(dev);
struct tach_reg *const inst = HAL_INSTANCE(dev);
LOG_DBG("port A is underflow %d, port b is underflow %d",
IS_BIT_SET(inst->TECTRL, NPCX_TECTRL_TCPND),
IS_BIT_SET(inst->TECTRL, NPCX_TECTRL_TDPND));
/*
* In mode 5, the flag TCPND or TDPND indicates the TCNT1 or TCNT2
* is under underflow situation. (No edges are detected.)
*/
if (config->port == NPCX_TACH_PORT_A) {
return IS_BIT_SET(inst->TECTRL, NPCX_TECTRL_TCPND);
} else {
return IS_BIT_SET(inst->TECTRL, NPCX_TECTRL_TDPND);
}
}
static inline void tach_npcx_clear_underflow_flag(const struct device *dev)
{
const struct tach_npcx_config *const config = DRV_CONFIG(dev);
struct tach_reg *const inst = HAL_INSTANCE(dev);
if (config->port == NPCX_TACH_PORT_A) {
inst->TECLR = BIT(NPCX_TECLR_TCCLR);
} else {
inst->TECLR = BIT(NPCX_TECLR_TDCLR);
}
}
static inline bool tach_npcx_is_captured(const struct device *dev)
{
const struct tach_npcx_config *const config = DRV_CONFIG(dev);
struct tach_reg *const inst = HAL_INSTANCE(dev);
LOG_DBG("port A is captured %d, port b is captured %d",
IS_BIT_SET(inst->TECTRL, NPCX_TECTRL_TAPND),
IS_BIT_SET(inst->TECTRL, NPCX_TECTRL_TAPND));
/*
* In mode 5, the flag TAPND or TBPND indicates a input captured on
* TAn or TBn transition.
*/
if (config->port == NPCX_TACH_PORT_A) {
return IS_BIT_SET(inst->TECTRL, NPCX_TECTRL_TAPND);
} else {
return IS_BIT_SET(inst->TECTRL, NPCX_TECTRL_TBPND);
}
}
static inline void tach_npcx_clear_captured_flag(const struct device *dev)
{
const struct tach_npcx_config *const config = DRV_CONFIG(dev);
struct tach_reg *const inst = HAL_INSTANCE(dev);
if (config->port == NPCX_TACH_PORT_A) {
inst->TECLR = BIT(NPCX_TECLR_TACLR);
} else {
inst->TECLR = BIT(NPCX_TECLR_TBCLR);
}
}
static inline uint16_t tach_npcx_get_captured_count(const struct device *dev)
{
const struct tach_npcx_config *const config = DRV_CONFIG(dev);
struct tach_reg *const inst = HAL_INSTANCE(dev);
if (config->port == NPCX_TACH_PORT_A) {
return inst->TCRA;
} else {
return inst->TCRB;
}
}
/* TACH local functions */
static int tach_npcx_configure(const struct device *dev)
{
const struct tach_npcx_config *const config = DRV_CONFIG(dev);
struct tach_npcx_data *const data = DRV_DATA(dev);
struct tach_reg *const inst = HAL_INSTANCE(dev);
/* Set mode 5 to tachometer module */
SET_FIELD(inst->TMCTRL, NPCX_TMCTRL_MDSEL_FIELD, NPCX_TACH_MDSEL);
/* Configure clock module and its frequency of tachometer */
if (config->sample_clk == 0) {
return -EINVAL;
} else if (data->input_clk == LFCLK) {
/* Enable low power mode */
inst->TCKC |= BIT(NPCX_TCKC_LOW_PWR);
if (config->sample_clk != data->input_clk) {
LOG_ERR("%s operate freq is %d not fixed to 32kHz",
dev->name, data->input_clk);
return -EINVAL;
}
} else {
/* Configure sampling freq by setting prescaler of APB1 */
uint16_t prescaler = data->input_clk / config->sample_clk;
if (data->input_clk > config->sample_clk) {
LOG_ERR("%s operate freq exceeds APB1 clock",
dev->name);
return -EINVAL;
}
inst->TPRSC = MIN(NPCX_TACHO_PRSC_MAX, MAX(prescaler, 1));
}
return 0;
}
/* TACH api functions */
int tach_npcx_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
ARG_UNUSED(chan);
struct tach_npcx_data *const data = DRV_DATA(dev);
/* Check whether underflow flag of tachometer is occurred */
if (tach_npcx_is_underflow(dev)) {
/* Clear pending flags */
tach_npcx_clear_underflow_flag(dev);
LOG_INF("%s is underflow!", dev->name);
data->capture = 0;
return 0;
}
/* Check whether capture flag of tachometer is set */
if (tach_npcx_is_captured(dev)) {
/* Clear pending flags */
tach_npcx_clear_captured_flag(dev);
/* Save captured count */
data->capture = NPCX_TACHO_CNT_MAX -
tach_npcx_get_captured_count(dev);
}
return 0;
}
static int tach_npcx_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
const struct tach_npcx_config *const config = DRV_CONFIG(dev);
struct tach_npcx_data *const data = DRV_DATA(dev);
if (chan != SENSOR_CHAN_RPM) {
return -ENOTSUP;
}
if (data->capture > 0) {
/*
* RPM = (f * 60) / (n * TACH)
* n = Pulses per round
* f = Tachometer operation freqency (Hz)
* TACH = Captured counts of tachometer
*/
val->val1 = (config->sample_clk * 60) /
(config->pulses_per_round * data->capture);
} else {
val->val1 = 0U;
}
val->val2 = 0U;
return 0;
}
/* TACH driver registration */
static int tach_npcx_init(const struct device *dev)
{
const struct tach_npcx_config *const config = DRV_CONFIG(dev);
struct tach_npcx_data *const data = DRV_DATA(dev);
const struct device *const clk_dev =
device_get_binding(NPCX_CLK_CTRL_NAME);
int ret;
/* Turn on device clock first and get source clock freq. */
ret = clock_control_on(clk_dev, (clock_control_subsys_t *)
&config->clk_cfg);
if (ret < 0) {
LOG_ERR("Turn on tachometer clock fail %d", ret);
return ret;
}
ret = clock_control_get_rate(clk_dev, (clock_control_subsys_t *)
&config->clk_cfg, &data->input_clk);
if (ret < 0) {
LOG_ERR("Get tachometer clock rate error %d", ret);
return ret;
}
/* Configure pin-mux for tachometer device */
npcx_pinctrl_mux_configure(config->alts_list, config->alts_size, 1);
/* Configure tachometer and its operate freq. */
ret = tach_npcx_configure(dev);
if (ret < 0) {
LOG_ERR("Config tachometer port %d failed", config->port);
return ret;
}
/* Start tachometer sensor */
if (config->port == NPCX_TACH_PORT_A) {
tach_npcx_start_port_a(dev);
} else if (config->port == NPCX_TACH_PORT_B) {
tach_npcx_start_port_b(dev);
} else {
return -EINVAL;
}
return 0;
}
static const struct sensor_driver_api tach_npcx_driver_api = {
.sample_fetch = tach_npcx_sample_fetch,
.channel_get = tach_npcx_channel_get,
};
#define NPCX_TACH_INIT(inst) \
static const struct npcx_alt tach_alts##inst[] = \
NPCX_DT_ALT_ITEMS_LIST(inst); \
\
static const struct tach_npcx_config tach_cfg_##inst = { \
.base = DT_INST_REG_ADDR(inst), \
.clk_cfg = NPCX_DT_CLK_CFG_ITEM(inst), \
.alts_size = ARRAY_SIZE(tach_alts##inst), \
.alts_list = tach_alts##inst, \
.sample_clk = DT_INST_PROP(inst, sample_clk), \
.port = DT_INST_PROP(inst, port), \
.pulses_per_round = DT_INST_PROP(inst, pulses_per_round), \
}; \
\
static struct tach_npcx_data tach_data_##inst; \
\
DEVICE_DT_INST_DEFINE(inst, \
tach_npcx_init, \
NULL, \
&tach_data_##inst, \
&tach_cfg_##inst, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&tach_npcx_driver_api);
DT_INST_FOREACH_STATUS_OKAY(NPCX_TACH_INIT)