drivers: pwm: add pwm driver support in NPCX7 series

In npcx7 series, there're 8 Pulse Width Modulator (PWM) modules and each
one support generating a single 16-bit PWM output. A 16-bit clock
prescaler (PRSCn) and a 16-bit counter (CTRn) determine the cycle time,
the minimal possible pulse width, and the duty-cycle steps.

Beside introducing pwm driver for Nuvoton NPCX series, this CL also
includes:

1. Add PWM device tree declarations.
2. Zephyr PWM api implementation.
3. Add aliases in npcx7m6fb_evb board device tree file for supporting
   samples/basic/blinky_pwm application and pwm test suites

Signed-off-by: Mulin Chao <MLChao@nuvoton.com>
This commit is contained in:
Mulin Chao 2020-08-17 10:28:04 +08:00 committed by Maureen Helm
parent 6b22c764f1
commit dd99fbebe6
12 changed files with 418 additions and 0 deletions

View file

@ -226,6 +226,7 @@
/drivers/pinmux/*npcx* @MulinChao
/drivers/ps2/ @albertofloyd @franciscomunoz @scottwcpg
/drivers/pwm/*litex* @mateusz-holenko @kgugala @pgielda
/drivers/pwm/*npcx* @MulinChao
/drivers/pwm/*sam0* @nzmichaelh
/drivers/pwm/*stm32* @gmarull
/drivers/sensor/ @MaureenHelm

View file

@ -16,6 +16,21 @@
zephyr,console = &uart1;
zephyr,flash = &flash0;
};
aliases {
/* For samples/basic/blinky_pwm */
pwm-led0 = &pwm_led0_green;
/* For pwm test suites */
pwm-0 = &pwm6;
};
pwmleds {
compatible = "pwm-leds";
pwm_led0_green: pwm_led_0 {
pwms = <&pwm6 0 PWM_POLARITY_INVERTED>;
label = "User D7 green";
};
};
};
/* Overwirte default device properties with overlays in board dt file here. */
@ -25,6 +40,10 @@
pinctrl = <&altc_uart1_sl2>; /* Use UART1_SL2 ie. PIN64.65 */
};
&pwm6 {
status = "okay";
};
&espi0 {
status = "okay";
};

View file

@ -34,6 +34,9 @@ CONFIG_UART_INTERRUPT_DRIVEN=y
# GPIO Driver
CONFIG_GPIO=y
# PWM Driver
CONFIG_PWM=y
# ESPI Driver
CONFIG_ESPI=y

View file

@ -18,6 +18,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_LITEX pwm_litex.c)
zephyr_library_sources_ifdef(CONFIG_PWM_RV32M1_TPM pwm_rv32m1_tpm.c)
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_TPM pwm_mcux_tpm.c)
zephyr_library_sources_ifdef(CONFIG_PWM_SAM0_TCC pwm_sam0_tcc.c)
zephyr_library_sources_ifdef(CONFIG_PWM_NPCX pwm_npcx.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
zephyr_library_sources_ifdef(CONFIG_PWM_SHELL pwm_shell.c)

View file

@ -53,4 +53,6 @@ source "drivers/pwm/Kconfig.mcux_tpm"
source "drivers/pwm/Kconfig.sam0"
source "drivers/pwm/Kconfig.npcx"
endif # PWM

12
drivers/pwm/Kconfig.npcx Normal file
View file

@ -0,0 +1,12 @@
# NPCX PWM driver configuration options
# Copyright (c) 2020 Nuvoton Technology Corporation.
# SPDX-License-Identifier: Apache-2.0
config PWM_NPCX
bool "Nuvoton NPCX embedd controller (EC) PWM driver"
depends on SOC_FAMILY_NPCX
help
This option enables the PWM driver for NPCX family of
processors.
Say y if you wish to use pwm ports on NPCX MCU.

213
drivers/pwm/pwm_npcx.c Normal file
View file

@ -0,0 +1,213 @@
/*
* Copyright (c) 2020 Nuvoton Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nuvoton_npcx_pwm
#include <assert.h>
#include <drivers/pwm.h>
#include <dt-bindings/clock/npcx_clock.h>
#include <drivers/clock_control.h>
#include <kernel.h>
#include <soc.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(pwm_npcx, LOG_LEVEL_ERR);
/* 16-bit period cycles/prescaler in NPCX PWM modules */
#define NPCX_PWM_MAX_PRESCALER (1UL << (16))
#define NPCX_PWM_MAX_PERIOD_CYCLES (1UL << (16))
/* PWM clock sources */
#define NPCX_PWM_CLOCK_APB2_LFCLK 0
#define NPCX_PWM_CLOCK_FX 1
#define NPCX_PWM_CLOCK_FR 2
#define NPCX_PWM_CLOCK_RESERVED 3
/* PWM heart-beat mode selection */
#define NPCX_PWM_HBM_NORMAL 0
#define NPCX_PWM_HBM_25 1
#define NPCX_PWM_HBM_50 2
#define NPCX_PWM_HBM_100 3
/* Device config */
struct pwm_npcx_config {
/* pwm 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;
};
/* Driver data */
struct pwm_npcx_data {
/* PWM cycles per second */
uint32_t cycles_per_sec;
};
/* Driver convenience defines */
#define DRV_CONFIG(dev) ((const struct pwm_npcx_config *)(dev)->config)
#define DRV_DATA(dev) ((struct pwm_npcx_data *)(dev)->data)
#define HAL_INSTANCE(dev) (struct pwm_reg *)(DRV_CONFIG(dev)->base)
/* PWM local functions */
static void pwm_npcx_configure(const struct device *dev, int clk_bus)
{
struct pwm_reg *const inst = HAL_INSTANCE(dev);
/* Disable PWM for module configuration first */
inst->PWMCTL &= ~BIT(NPCX_PWMCTL_PWR);
/* Set default PWM polarity to normal */
inst->PWMCTL &= ~BIT(NPCX_PWMCTL_INVP);
/* Turn off PWM heart-beat mode */
SET_FIELD(inst->PWMCTL, NPCX_PWMCTL_HB_DC_CTL_FIELD,
NPCX_PWM_HBM_NORMAL);
/* Select APB CLK/LFCLK clock sources to PWM module by default */
SET_FIELD(inst->PWMCTLEX, NPCX_PWMCTLEX_FCK_SEL_FIELD,
NPCX_PWM_CLOCK_APB2_LFCLK);
/* Select clock source to LFCLK by flag, otherwise APB clock source */
if (clk_bus == NPCX_CLOCK_BUS_LFCLK)
inst->PWMCTL |= BIT(NPCX_PWMCTL_CKSEL);
else
inst->PWMCTL &= ~BIT(NPCX_PWMCTL_CKSEL);
}
/* PWM api functions */
static int pwm_npcx_pin_set(const struct device *dev, uint32_t pwm,
uint32_t period_cycles, uint32_t pulse_cycles,
pwm_flags_t flags)
{
/* Single channel for each pwm device */
ARG_UNUSED(pwm);
struct pwm_npcx_data *const data = DRV_DATA(dev);
struct pwm_reg *const inst = HAL_INSTANCE(dev);
int prescaler;
if (pulse_cycles > period_cycles)
return -EINVAL;
/* Disable PWM before configuring */
inst->PWMCTL &= ~BIT(NPCX_PWMCTL_PWR);
/* If pulse_cycles is 0, return directly since PWM is already off */
if (pulse_cycles == 0)
return 0;
/* Select PWM inverted polarity (ie. active-low pulse). */
if (flags & PWM_POLARITY_INVERTED)
inst->PWMCTL |= BIT(NPCX_PWMCTL_INVP);
else
inst->PWMCTL &= ~BIT(NPCX_PWMCTL_INVP);
/*
* Calculate PWM prescaler that let period_cycles map to
* maximum pwm period cycles and won't exceed it.
* Then prescaler = ceil (period_cycles / pwm_max_period_cycles)
*/
prescaler = ceiling_fraction(period_cycles, NPCX_PWM_MAX_PERIOD_CYCLES);
if (prescaler > NPCX_PWM_MAX_PRESCALER)
return -EINVAL;
/* Set PWM prescaler.*/
inst->PRSC = prescaler - 1;
/* Set PWM period cycles */
inst->CTR = (period_cycles / prescaler) - 1;
/* Set PWM pulse cycles */
inst->DCR = (pulse_cycles / prescaler) - 1;
LOG_DBG("freq %d, pre %d, period %d, pulse %d",
data->cycles_per_sec / period_cycles,
inst->PRSC + 1, inst->CTR + 1, inst->DCR + 1);
/* Enable PWM now */
inst->PWMCTL |= BIT(NPCX_PWMCTL_PWR);
return 0;
}
static int pwm_npcx_get_cycles_per_sec(const struct device *dev, uint32_t pwm,
uint64_t *cycles)
{
/* Single channel for each pwm device */
ARG_UNUSED(pwm);
struct pwm_npcx_data *const data = DRV_DATA(dev);
*cycles = data->cycles_per_sec;
return 0;
}
/* PWM driver registration */
static const struct pwm_driver_api pwm_npcx_driver_api = {
.pin_set = pwm_npcx_pin_set,
.get_cycles_per_sec = pwm_npcx_get_cycles_per_sec
};
static int pwm_npcx_init(const struct device *dev)
{
const struct pwm_npcx_config *const config = DRV_CONFIG(dev);
struct pwm_npcx_data *const data = DRV_DATA(dev);
struct pwm_reg *const inst = HAL_INSTANCE(dev);
const struct device *const clk_dev =
device_get_binding(NPCX_CLK_CTRL_NAME);
/*
* NPCX PWM modulee mixes byte and word registers together. Make sure
* word reg access via structure won't break into two byte reg accesses
* unexpectedly by toolchains options or attributes. If so, stall here.
*/
NPCX_REG_WORD_ACCESS_CHECK(inst->PRSC, 0xA55A);
/* Turn on device clock first and get source clock freq of it. */
if (clock_control_on(clk_dev,
(clock_control_subsys_t *)&config->clk_cfg) != 0) {
LOG_ERR("Turn on PWM clock fail.");
return -EIO;
}
if (clock_control_get_rate(clk_dev, (clock_control_subsys_t *)
&config->clk_cfg, &data->cycles_per_sec) != 0) {
LOG_ERR("Get PWM clock rate error.");
return -EIO;
}
/* Configure PWM device initially */
pwm_npcx_configure(dev, config->clk_cfg.bus);
/* Configure pin-mux for PWM device */
soc_pinctrl_mux_configure(config->alts_list, config->alts_size, 1);
return 0;
}
#define NPCX_PWM_INIT(inst) \
static const struct npcx_alt pwm_alts##inst[] = \
DT_NPCX_ALT_ITEMS_LIST(inst); \
\
static const struct pwm_npcx_config pwm_npcx_cfg_##inst = { \
.base = DT_INST_REG_ADDR(inst), \
.clk_cfg = DT_NPCX_CLK_CFG_ITEM(inst), \
.alts_size = ARRAY_SIZE(pwm_alts##inst), \
.alts_list = pwm_alts##inst, \
}; \
\
static struct pwm_npcx_data pwm_npcx_data_##inst; \
\
DEVICE_AND_API_INIT(pwm_npcx_##inst, DT_INST_LABEL(inst), \
&pwm_npcx_init, \
&pwm_npcx_data_##inst, &pwm_npcx_cfg_##inst, \
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&pwm_npcx_driver_api);
DT_INST_FOREACH_STATUS_OKAY(NPCX_PWM_INIT)

View file

@ -9,6 +9,7 @@
#include <dt-bindings/clock/npcx_clock.h>
#include <dt-bindings/espi/npcx_espi.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pwm/pwm.h>
/* NPCX7 series pinmux mapping table */
#include "npcx/npcx7-alts-map.dtsi"
@ -332,6 +333,86 @@
label="GPIO_F";
};
pwm0: pwm@40080000 {
compatible = "nuvoton,npcx-pwm";
reg = <0x40080000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_APB2 NPCX_PWDWN_CTL2 0>;
pinctrl = <&alt4_pwm0_sl>; /* PINC3 */
#pwm-cells = <2>;
status = "disabled";
label = "PWM_0";
};
pwm1: pwm@40082000 {
compatible = "nuvoton,npcx-pwm";
reg = <0x40082000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_APB2 NPCX_PWDWN_CTL2 1>;
pinctrl = <&alt4_pwm1_sl>; /* PINC2 */
#pwm-cells = <2>;
status = "disabled";
label = "PWM_1";
};
pwm2: pwm@40084000 {
compatible = "nuvoton,npcx-pwm";
reg = <0x40084000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_APB2 NPCX_PWDWN_CTL2 2>;
pinctrl = <&alt4_pwm2_sl>; /* PINC4 */
#pwm-cells = <2>;
status = "disabled";
label = "PWM_2";
};
pwm3: pwm@40086000 {
compatible = "nuvoton,npcx-pwm";
reg = <0x40086000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_APB2 NPCX_PWDWN_CTL2 3>;
pinctrl = <&alt4_pwm3_sl>; /* PIN80 */
#pwm-cells = <2>;
status = "disabled";
label = "PWM_3";
};
pwm4: pwm@40088000 {
compatible = "nuvoton,npcx-pwm";
reg = <0x40088000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_APB2 NPCX_PWDWN_CTL2 4>;
pinctrl = <&alt4_pwm4_sl>; /* PINB6 */
#pwm-cells = <2>;
status = "disabled";
label = "PWM_4";
};
pwm5: pwm@4008a000 {
compatible = "nuvoton,npcx-pwm";
reg = <0x4008a000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_APB2 NPCX_PWDWN_CTL2 5>;
pinctrl = <&alt4_pwm5_sl>; /* PINB7 */
#pwm-cells = <2>;
status = "disabled";
label = "PWM_5";
};
pwm6: pwm@4008c000 {
compatible = "nuvoton,npcx-pwm";
reg = <0x4008c000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_APB2 NPCX_PWDWN_CTL2 6>;
pinctrl = <&alt4_pwm6_sl>; /* PINC0 */
#pwm-cells = <2>;
status = "disabled";
label = "PWM_6";
};
pwm7: pwm@4008e000 {
compatible = "nuvoton,npcx-pwm";
reg = <0x4008e000 0x2000>;
clocks = <&pcc NPCX_CLOCK_BUS_APB2 NPCX_PWDWN_CTL2 7>;
pinctrl = <&alt4_pwm7_sl>; /* PIN60 */
#pwm-cells = <2>;
status = "disabled";
label = "PWM_7";
};
espi0: espi@4000a000 {
compatible = "nuvoton,npcx-espi";
reg = <0x4000a000 0x2000>;

View file

@ -0,0 +1,24 @@
# Copyright (c) 2020 Nuvoton Technology Corporation.
# SPDX-License-Identifier: Apache-2.0
description: Nuvoton, NPCX Pulse Width Modulator (PWM) node
compatible: "nuvoton,npcx-pwm"
include: [pwm-controller.yaml, base.yaml]
properties:
reg:
required: true
clocks:
required: true
label:
required: true
pinctrl:
type: phandles
required: true
description: configurations of pinmux controllers
pwm-cells:
- channel
- flags

View file

@ -21,6 +21,28 @@
BUILD_ASSERT(offsetof(struct reg_def, member) == offset, \
"Failed in offset check of register structure member!")
/*
* NPCX register access checking via structure macro function to mitigate the
* risk of unexpected compiling results if module contains different length
* registers. For example, a word register access might break into two byte
* register accesses by adding 'packed' attribute.
*
* For example, add this macro for word register 'PRSC' of PWM module in its
* device init function for checking violation. Once it occurred, core will be
* stalled forever and easy to find out what happens.
*/
#define NPCX_REG_WORD_ACCESS_CHECK(reg, val) { \
uint16_t placeholder = reg; \
reg = val; \
__ASSERT(reg == val, "16-bit reg access failed!"); \
reg = placeholder; \
}
#define NPCX_REG_DWORD_ACCESS_CHECK(reg, val) { \
uint32_t placeholder = reg; \
reg = val; \
__ASSERT(reg == val, "32-bit reg access failed!"); \
reg = placeholder; \
}
/*
* Core Domain Clock Generator (CDCG) device registers
*/
@ -308,6 +330,33 @@ struct gpio_reg {
volatile uint8_t PLOCK_CTL;
};
/*
* Pulse Width Modulator (PWM) device registers
*/
struct pwm_reg {
/* 0x000: Clock Prescaler */
volatile uint16_t PRSC;
/* 0x002: Cycle Time */
volatile uint16_t CTR;
/* 0x004: PWM Control */
volatile uint8_t PWMCTL;
volatile uint8_t reserved1;
/* 0x006: Duty Cycle */
volatile uint16_t DCR;
volatile uint8_t reserved2[4];
/* 0x00C: PWM Control Extended */
volatile uint8_t PWMCTLEX;
volatile uint8_t reserved3;
};
/* PWM register fields */
#define NPCX_PWMCTL_INVP 0
#define NPCX_PWMCTL_CKSEL 1
#define NPCX_PWMCTL_HB_DC_CTL_FIELD FIELD(2, 2)
#define NPCX_PWMCTL_PWR 7
#define NPCX_PWMCTLEX_FCK_SEL_FIELD FIELD(4, 2)
#define NPCX_PWMCTLEX_OD_OUT 7
/*
* Enhanced Serial Peripheral Interface (eSPI) device registers
*/
@ -680,6 +729,7 @@ struct kbc_reg {
/*
* Power Management Channel (PMCH) device registers
*/
struct pmch_reg {
/* 0x000: Host Interface PM Status */
volatile uint8_t HIPMST;

View file

@ -35,6 +35,12 @@ config GPIO_NPCX
help
Enable support for NPCX GPIO driver.
config PWM_NPCX
default y
depends on PWM
help
Enable support for NPCX PWM driver.
config ESPI_NPCX
default y
depends on ESPI

View file

@ -47,6 +47,12 @@ NPCX_REG_OFFSET_CHECK(uart_reg, UFRCTL, 0x026);
NPCX_REG_SIZE_CHECK(gpio_reg, 0x008);
NPCX_REG_OFFSET_CHECK(gpio_reg, PLOCK_CTL, 0x007);
/* PWM register structure check */
NPCX_REG_SIZE_CHECK(pwm_reg, 0x00e);
NPCX_REG_OFFSET_CHECK(pwm_reg, PWMCTL, 0x004);
NPCX_REG_OFFSET_CHECK(pwm_reg, DCR, 0x006);
NPCX_REG_OFFSET_CHECK(pwm_reg, PWMCTLEX, 0x00c);
/* ESPI register structure check */
NPCX_REG_SIZE_CHECK(espi_reg, 0x500);
NPCX_REG_OFFSET_CHECK(espi_reg, FLASHCFG, 0x034);