391 lines
12 KiB
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)
|