| /* |
| * Copyright (c) 2020 Nuvoton Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nuvoton_npcx_pwm |
| |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/pwm.h> |
| #include <zephyr/dt-bindings/clock/npcx_clock.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/kernel.h> |
| #include <soc.h> |
| |
| #include <zephyr/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 */ |
| struct pwm_reg *base; |
| /* clock configuration */ |
| struct npcx_clk_cfg clk_cfg; |
| /* pinmux configuration */ |
| const struct pinctrl_dev_config *pcfg; |
| }; |
| |
| /* Driver data */ |
| struct pwm_npcx_data { |
| /* PWM cycles per second */ |
| uint32_t cycles_per_sec; |
| }; |
| |
| /* PWM local functions */ |
| static void pwm_npcx_configure(const struct device *dev, int clk_bus) |
| { |
| const struct pwm_npcx_config *config = dev->config; |
| struct pwm_reg *inst = config->base; |
| |
| /* 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_set_cycles(const struct device *dev, uint32_t channel, |
| uint32_t period_cycles, uint32_t pulse_cycles, |
| pwm_flags_t flags) |
| { |
| /* Single channel for each pwm device */ |
| ARG_UNUSED(channel); |
| const struct pwm_npcx_config *config = dev->config; |
| struct pwm_npcx_data *const data = dev->data; |
| struct pwm_reg *inst = config->base; |
| int prescaler; |
| uint32_t ctl; |
| uint32_t ctr; |
| uint32_t dcr; |
| uint32_t prsc; |
| |
| ctl = inst->PWMCTL | BIT(NPCX_PWMCTL_PWR); |
| |
| /* Select PWM inverted polarity (ie. active-low pulse). */ |
| if (flags & PWM_POLARITY_INVERTED) { |
| ctl |= BIT(NPCX_PWMCTL_INVP); |
| } else { |
| ctl &= ~BIT(NPCX_PWMCTL_INVP); |
| } |
| |
| /* If pulse_cycles is 0, switch PWM off and return. */ |
| if (pulse_cycles == 0) { |
| ctl &= ~BIT(NPCX_PWMCTL_PWR); |
| inst->PWMCTL = ctl; |
| return 0; |
| } |
| |
| /* |
| * 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 = DIV_ROUND_UP(period_cycles, NPCX_PWM_MAX_PERIOD_CYCLES); |
| if (prescaler > NPCX_PWM_MAX_PRESCALER) { |
| return -EINVAL; |
| } |
| |
| /* Set PWM prescaler. */ |
| prsc = prescaler - 1; |
| |
| /* Set PWM period cycles. */ |
| ctr = (period_cycles / prescaler) - 1; |
| |
| /* Set PWM pulse cycles. */ |
| dcr = (pulse_cycles / prescaler) - 1; |
| |
| LOG_DBG("freq %d, pre %d, period %d, pulse %d", |
| data->cycles_per_sec / period_cycles, prsc, ctr, dcr); |
| |
| /* Reconfigure only if necessary. */ |
| if (inst->PWMCTL != ctl || inst->PRSC != prsc || inst->CTR != ctr) { |
| /* Disable PWM before configuring. */ |
| inst->PWMCTL &= ~BIT(NPCX_PWMCTL_PWR); |
| |
| inst->PRSC = prsc; |
| inst->CTR = ctr; |
| inst->DCR = dcr; |
| |
| /* Enable PWM now. */ |
| inst->PWMCTL = ctl; |
| |
| return 0; |
| } |
| |
| inst->DCR = dcr; |
| |
| return 0; |
| } |
| |
| static int pwm_npcx_get_cycles_per_sec(const struct device *dev, |
| uint32_t channel, uint64_t *cycles) |
| { |
| /* Single channel for each pwm device */ |
| ARG_UNUSED(channel); |
| struct pwm_npcx_data *const data = dev->data; |
| |
| *cycles = data->cycles_per_sec; |
| return 0; |
| } |
| |
| /* PWM driver registration */ |
| static const struct pwm_driver_api pwm_npcx_driver_api = { |
| .set_cycles = pwm_npcx_set_cycles, |
| .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 = dev->config; |
| struct pwm_npcx_data *const data = dev->data; |
| struct pwm_reg *const inst = config->base; |
| const struct device *const clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE); |
| int ret; |
| |
| /* |
| * NPCX PWM module 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); |
| |
| |
| if (!device_is_ready(clk_dev)) { |
| LOG_ERR("clock control device not ready"); |
| return -ENODEV; |
| } |
| |
| /* 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 PWM clock fail %d", ret); |
| return ret; |
| } |
| |
| ret = clock_control_get_rate(clk_dev, (clock_control_subsys_t) |
| &config->clk_cfg, &data->cycles_per_sec); |
| if (ret < 0) { |
| LOG_ERR("Get PWM clock rate error %d", ret); |
| return ret; |
| } |
| |
| /* Configure PWM device initially */ |
| pwm_npcx_configure(dev, config->clk_cfg.bus); |
| |
| /* Configure pin-mux for PWM device */ |
| ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret < 0) { |
| LOG_ERR("PWM pinctrl setup failed (%d)", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| #define NPCX_PWM_INIT(inst) \ |
| PINCTRL_DT_INST_DEFINE(inst); \ |
| \ |
| static const struct pwm_npcx_config pwm_npcx_cfg_##inst = { \ |
| .base = (struct pwm_reg *)DT_INST_REG_ADDR(inst), \ |
| .clk_cfg = NPCX_DT_CLK_CFG_ITEM(inst), \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ |
| }; \ |
| \ |
| static struct pwm_npcx_data pwm_npcx_data_##inst; \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, \ |
| &pwm_npcx_init, NULL, \ |
| &pwm_npcx_data_##inst, &pwm_npcx_cfg_##inst, \ |
| PRE_KERNEL_1, CONFIG_PWM_INIT_PRIORITY, \ |
| &pwm_npcx_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(NPCX_PWM_INIT) |