From dd99fbebe6c1e4222bdc81f15ff3ec94db4999f5 Mon Sep 17 00:00:00 2001 From: Mulin Chao Date: Mon, 17 Aug 2020 10:28:04 +0800 Subject: [PATCH] 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 --- CODEOWNERS | 1 + boards/arm/npcx7m6fb_evb/npcx7m6fb_evb.dts | 19 ++ .../arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig | 3 + drivers/pwm/CMakeLists.txt | 1 + drivers/pwm/Kconfig | 2 + drivers/pwm/Kconfig.npcx | 12 + drivers/pwm/pwm_npcx.c | 213 ++++++++++++++++++ dts/arm/nuvoton/npcx7m6fb.dtsi | 81 +++++++ dts/bindings/pwm/nuvoton,npcx-pwm.yaml | 24 ++ soc/arm/nuvoton_npcx/common/reg/reg_def.h | 50 ++++ .../npcx7/Kconfig.defconfig.series | 6 + soc/arm/nuvoton_npcx/npcx7/soc.c | 6 + 12 files changed, 418 insertions(+) create mode 100644 drivers/pwm/Kconfig.npcx create mode 100644 drivers/pwm/pwm_npcx.c create mode 100644 dts/bindings/pwm/nuvoton,npcx-pwm.yaml diff --git a/CODEOWNERS b/CODEOWNERS index d5452be970..72206800a5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb.dts b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb.dts index aaca7d252d..2901340748 100644 --- a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb.dts +++ b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb.dts @@ -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"; }; diff --git a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig index f78449f20d..cb39af6bdf 100644 --- a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig +++ b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig @@ -34,6 +34,9 @@ CONFIG_UART_INTERRUPT_DRIVEN=y # GPIO Driver CONFIG_GPIO=y +# PWM Driver +CONFIG_PWM=y + # ESPI Driver CONFIG_ESPI=y diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index e1c78cc4fc..e19512a13d 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -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) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c2edabf602..f193c9ac8f 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -53,4 +53,6 @@ source "drivers/pwm/Kconfig.mcux_tpm" source "drivers/pwm/Kconfig.sam0" +source "drivers/pwm/Kconfig.npcx" + endif # PWM diff --git a/drivers/pwm/Kconfig.npcx b/drivers/pwm/Kconfig.npcx new file mode 100644 index 0000000000..1d883806af --- /dev/null +++ b/drivers/pwm/Kconfig.npcx @@ -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. diff --git a/drivers/pwm/pwm_npcx.c b/drivers/pwm/pwm_npcx.c new file mode 100644 index 0000000000..cc44715053 --- /dev/null +++ b/drivers/pwm/pwm_npcx.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2020 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nuvoton_npcx_pwm + +#include +#include +#include +#include +#include +#include + +#include +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) diff --git a/dts/arm/nuvoton/npcx7m6fb.dtsi b/dts/arm/nuvoton/npcx7m6fb.dtsi index 53ac2616d3..aa63d36bfb 100644 --- a/dts/arm/nuvoton/npcx7m6fb.dtsi +++ b/dts/arm/nuvoton/npcx7m6fb.dtsi @@ -9,6 +9,7 @@ #include #include #include +#include /* 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>; diff --git a/dts/bindings/pwm/nuvoton,npcx-pwm.yaml b/dts/bindings/pwm/nuvoton,npcx-pwm.yaml new file mode 100644 index 0000000000..8fd375dd5e --- /dev/null +++ b/dts/bindings/pwm/nuvoton,npcx-pwm.yaml @@ -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 diff --git a/soc/arm/nuvoton_npcx/common/reg/reg_def.h b/soc/arm/nuvoton_npcx/common/reg/reg_def.h index 0154cf05df..e06f68dfb0 100644 --- a/soc/arm/nuvoton_npcx/common/reg/reg_def.h +++ b/soc/arm/nuvoton_npcx/common/reg/reg_def.h @@ -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; diff --git a/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series b/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series index 51b6f683e7..66dc5c4fdd 100644 --- a/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series +++ b/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series @@ -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 diff --git a/soc/arm/nuvoton_npcx/npcx7/soc.c b/soc/arm/nuvoton_npcx/npcx7/soc.c index 1e24e2fe4c..bdbac5f7f4 100644 --- a/soc/arm/nuvoton_npcx/npcx7/soc.c +++ b/soc/arm/nuvoton_npcx/npcx7/soc.c @@ -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);