driver: sensor: npcx: add tachometer sensor support.

In NPCX7 series, it contains two tachometer (TACH) modules that contains
two Independent timers (counter 1 and 2). They are used to capture a
counter value when an event is detected via the external pads (TA or
TB).

The CL also includes:
— Add npcx tachometer device tree declarations.
— Zephyr sensor api implementation for tachometer.
— Enable "tach1" device in npcx7m6fb.dts for testing.

Signed-off-by: Mulin Chao <mlchao@nuvoton.com>
This commit is contained in:
Mulin Chao 2021-01-12 01:45:06 -08:00 committed by Anas Nashif
parent 377456c5af
commit 7c9d3f44f0
13 changed files with 573 additions and 0 deletions

View file

@ -79,3 +79,11 @@
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
};
&tach1 {
status = "okay";
pinctrl-0 = <&alt3_ta1_sl1>; /* Use TA1_SL1 (PIN40) as input pin */
port = <NPCX_TACH_PORT_A>; /* port-A is selected */
sample_clk = <NPCX_TACH_FREQ_LFCLK>; /* Use LFCLK as sampling clock */
pulses_per_round = <1>; /* number of pulses per round of encoder */
};

View file

@ -79,6 +79,7 @@ add_subdirectory_ifdef(CONFIG_TEMP_KINETIS nxp_kinetis_temp)
add_subdirectory_ifdef(CONFIG_TACH_XEC mchp_tach_xec)
add_subdirectory_ifdef(CONFIG_ITDS wsen_itds)
add_subdirectory_ifdef(CONFIG_MCUX_ACMP mcux_acmp)
add_subdirectory_ifdef(CONFIG_TACH_NPCX nuvoton_tach_npcx)
zephyr_sources_ifdef(CONFIG_USERSPACE sensor_handlers.c)
zephyr_sources_ifdef(CONFIG_SENSOR_SHELL sensor_shell.c)

View file

@ -196,4 +196,6 @@ source "drivers/sensor/wsen_itds/Kconfig"
source "drivers/sensor/mcux_acmp/Kconfig"
source "drivers/sensor/nuvoton_tach_npcx/Kconfig"
endif # SENSOR

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_TACH_NPCX tach_nuvoton_npcx.c)

View file

@ -0,0 +1,10 @@
# NPCX tachometer sensor configuration options
# Copyright (c) 2021 Nuvoton Technology Corporation.
# SPDX-License-Identifier: Apache-2.0
config TACH_NPCX
bool "Nuvoton NPCX Tachometer sensor"
depends on SOC_FAMILY_NPCX
help
Enable the "Nuvoton NPCX tachometer sensor.

View file

@ -0,0 +1,390 @@
/*
* 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, \
device_pm_control_nop, \
&tach_data_##inst, \
&tach_cfg_##inst, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&tach_npcx_driver_api);
DT_INST_FOREACH_STATUS_OKAY(NPCX_TACH_INIT)

View file

@ -11,6 +11,7 @@
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/i2c/i2c.h>
#include <dt-bindings/pwm/pwm.h>
#include <dt-bindings/sensor/npcx_tach.h>
/* NPCX7 series pinmux mapping table */
#include "npcx/npcx7-alts-map.dtsi"
@ -700,6 +701,22 @@
label = "I2C_7_PORT_0";
status = "disabled";
};
tach1: tach@400e1000 {
compatible = "nuvoton,npcx-tach";
reg = <0x400e1000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_LFCLK NPCX_PWDWN_CTL1 5>;
label = "TACH_1";
status = "disabled";
};
tach2: tach@400e3000 {
compatible = "nuvoton,npcx-tach";
reg = <0x400e3000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_LFCLK NPCX_PWDWN_CTL1 6>;
label = "TACH_2";
status = "disabled";
};
};
};

View file

@ -0,0 +1,32 @@
# Copyright (c) 2021 Nuvoton Technology Corporation.
# SPDX-License-Identifier: Apache-2.0
description: Nuvoton, NPCX-Tachometer node
compatible: "nuvoton,npcx-tach"
include: tach.yaml
properties:
reg:
required: true
clocks:
required: true
pinctrl-0:
type: phandles
required: false
description: configurations of pinmux controllers in tachometers
sample_clk:
type: int
required: false
description: |
sampling clock frequency of tachometer. Please notice that it must be
fixed to 32768 if bus in clocks property is NPCX_CLOCK_BUS_LFCLK.
port:
type: int
required: false
description: selected port of tachometer (port-A is 0 and port-B is 1)
pulses_per_round:
type: int
required: false
description: number of pulses (holes) per round of tachometer's input (encoder)

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2021 Nuvoton Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_SENSOR_NPCX_TACH_H_
#define ZEPHYR_INCLUDE_DT_BINDINGS_SENSOR_NPCX_TACH_H_
/* NPCX tachometer port type */
#define NPCX_TACH_PORT_A 0
#define NPCX_TACH_PORT_B 1
/* NPCX tachometer specific operate frequency */
#define NPCX_TACH_FREQ_LFCLK 32768
#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_SENSOR_NPCX_TACH_H_ */

View file

@ -1173,4 +1173,78 @@ struct itim64_reg {
#define NPCX_ITCTSXX_CKSEL 4
#define NPCX_ITCTSXX_ITEN 7
/*
* Tachometer (TACH) Sensor device registers
*/
struct tach_reg {
/* 0x000: Timer/Counter 1 */
volatile uint16_t TCNT1;
/* 0x002: Reload/Capture A */
volatile uint16_t TCRA;
/* 0x004: Reload/Capture B */
volatile uint16_t TCRB;
/* 0x006: Timer/Counter 2 */
volatile uint16_t TCNT2;
/* 0x008: Clock Prescaler */
volatile uint8_t TPRSC;
volatile uint8_t reserved1;
/* 0x00A: Clock Unit Control */
volatile uint8_t TCKC;
volatile uint8_t reserved2;
/* 0x00C: Timer Mode Control */
volatile uint8_t TMCTRL;
volatile uint8_t reserved3;
/* 0x00E: Timer Event Control */
volatile uint8_t TECTRL;
volatile uint8_t reserved4;
/* 0x010: Timer Event Clear */
volatile uint8_t TECLR;
volatile uint8_t reserved5;
/* 0x012: Timer Interrupt Enable */
volatile uint8_t TIEN;
volatile uint8_t reserved6;
/* 0x014: Compare A */
volatile uint16_t TCPA;
/* 0x016: Compare B */
volatile uint16_t TCPB;
/* 0x018: Compare Configuration */
volatile uint8_t TCPCFG;
volatile uint8_t reserved7;
/* 0x01A: Timer Wake-Up Enablen */
volatile uint8_t TWUEN;
volatile uint8_t reserved8;
/* 0x01C: Timer Configuration */
volatile uint8_t TCFG;
volatile uint8_t reserved9;
};
/* TACH register fields */
#define NPCX_TCKC_LOW_PWR 7
#define NPCX_TCKC_PLS_ACC_CLK 6
#define NPCX_TCKC_C1CSEL_FIELD FIELD(0, 3)
#define NPCX_TCKC_C2CSEL_FIELD FIELD(3, 3)
#define NPCX_TMCTRL_MDSEL_FIELD FIELD(0, 3)
#define NPCX_TMCTRL_TAEN 5
#define NPCX_TMCTRL_TBEN 6
#define NPCX_TMCTRL_TAEDG 3
#define NPCX_TMCTRL_TBEDG 4
#define NPCX_TCFG_TADBEN 6
#define NPCX_TCFG_TBDBEN 7
#define NPCX_TECTRL_TAPND 0
#define NPCX_TECTRL_TBPND 1
#define NPCX_TECTRL_TCPND 2
#define NPCX_TECTRL_TDPND 3
#define NPCX_TECLR_TACLR 0
#define NPCX_TECLR_TBCLR 1
#define NPCX_TECLR_TCCLR 2
#define NPCX_TECLR_TDCLR 3
#define NPCX_TIEN_TAIEN 0
#define NPCX_TIEN_TBIEN 1
#define NPCX_TIEN_TCIEN 2
#define NPCX_TIEN_TDIEN 3
#define NPCX_TWUEN_TAWEN 0
#define NPCX_TWUEN_TBWEN 1
#define NPCX_TWUEN_TCWEN 2
#define NPCX_TWUEN_TDWEN 3
#endif /* _NUVOTON_NPCX_REG_DEF_H */

View file

@ -88,6 +88,15 @@ config I2C_NPCX
both Intel SMBus and Philips I2C physical layer. There are 8 SMBus
modules and 10 buses in NPCX7 series.
config TACH_NPCX
default y
depends on SENSOR
help
Enable support for NPCX tachometer sensor driver. The NPCX7 series
contains two tachometer (TACH) modules that contains two counters
(counter A and B). They are used to capture/compare a counter value
when an event is generated on comparison of signals match.
if SOC_POWER_MANAGEMENT
config PM

View file

@ -149,3 +149,11 @@ NPCX_REG_OFFSET_CHECK(itim64_reg, ITPRE64, 0x001);
NPCX_REG_OFFSET_CHECK(itim64_reg, ITCTS64, 0x004);
NPCX_REG_OFFSET_CHECK(itim64_reg, ITCNT64L, 0x008);
NPCX_REG_OFFSET_CHECK(itim64_reg, ITCNT64H, 0x00c);
/* TACH register structure check */
NPCX_REG_SIZE_CHECK(tach_reg, 0x01e);
NPCX_REG_OFFSET_CHECK(tach_reg, TPRSC, 0x008);
NPCX_REG_OFFSET_CHECK(tach_reg, TECLR, 0x010);
NPCX_REG_OFFSET_CHECK(tach_reg, TCPA, 0x014);
NPCX_REG_OFFSET_CHECK(tach_reg, TCPCFG, 0x018);
NPCX_REG_OFFSET_CHECK(tach_reg, TCFG, 0x01c);

View file

@ -37,6 +37,7 @@ CONFIG_SI7055=y
CONFIG_SI7060=y
CONFIG_SM351LT=y
CONFIG_SX9500=y
CONFIG_TACH_NPCX=y
CONFIG_TEMP_NRF5=y
CONFIG_TH02=y
CONFIG_TI_HDC=y