Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | /*
* 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;
/* Output buffer - open drain */
const bool is_od;
/* 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)
{
const struct pwm_npcx_config *const config = DRV_CONFIG(dev);
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);
/* Select output buffer type of io pad */
if (config->is_od)
inst->PWMCTLEX |= BIT(NPCX_PWMCTLEX_OD_OUT);
else
inst->PWMCTLEX &= ~BIT(NPCX_PWMCTLEX_OD_OUT);
}
/* 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);
/* 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);
/* If pulse_cycles is 0, return directly since PWM is already off */
if (pulse_cycles == 0)
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 = 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_DT_GET(NPCX_CLK_CTRL_NODE);
int ret;
/*
* 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. */
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 */
npcx_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[] = \
NPCX_DT_ALT_ITEMS_LIST(inst); \
\
static const struct pwm_npcx_config pwm_npcx_cfg_##inst = { \
.base = DT_INST_REG_ADDR(inst), \
.clk_cfg = NPCX_DT_CLK_CFG_ITEM(inst), \
.is_od = DT_INST_PROP(inst, drive_open_drain), \
.alts_size = ARRAY_SIZE(pwm_alts##inst), \
.alts_list = pwm_alts##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_KERNEL_INIT_PRIORITY_DEVICE, \
&pwm_npcx_driver_api);
DT_INST_FOREACH_STATUS_OKAY(NPCX_PWM_INIT)
|