1 /*
2 * Copyright (c) 2020 Nuvoton Technology Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT nuvoton_npcx_pwm
8
9 #include <zephyr/drivers/pinctrl.h>
10 #include <zephyr/drivers/pwm.h>
11 #include <zephyr/dt-bindings/clock/npcx_clock.h>
12 #include <zephyr/drivers/clock_control.h>
13 #include <zephyr/kernel.h>
14 #include <soc.h>
15
16 #include <zephyr/logging/log.h>
17
18 LOG_MODULE_REGISTER(pwm_npcx, LOG_LEVEL_ERR);
19
20 /* 16-bit period cycles/prescaler in NPCX PWM modules */
21 #define NPCX_PWM_MAX_PRESCALER (1UL << (16))
22 #define NPCX_PWM_MAX_PERIOD_CYCLES (1UL << (16))
23
24 /* PWM clock sources */
25 #define NPCX_PWM_CLOCK_APB2_LFCLK 0
26 #define NPCX_PWM_CLOCK_FX 1
27 #define NPCX_PWM_CLOCK_FR 2
28 #define NPCX_PWM_CLOCK_RESERVED 3
29
30 /* PWM heart-beat mode selection */
31 #define NPCX_PWM_HBM_NORMAL 0
32 #define NPCX_PWM_HBM_25 1
33 #define NPCX_PWM_HBM_50 2
34 #define NPCX_PWM_HBM_100 3
35
36 /* Device config */
37 struct pwm_npcx_config {
38 /* pwm controller base address */
39 struct pwm_reg *base;
40 /* clock configuration */
41 struct npcx_clk_cfg clk_cfg;
42 /* pinmux configuration */
43 const struct pinctrl_dev_config *pcfg;
44 };
45
46 /* Driver data */
47 struct pwm_npcx_data {
48 /* PWM cycles per second */
49 uint32_t cycles_per_sec;
50 };
51
52 /* PWM local functions */
pwm_npcx_configure(const struct device * dev,int clk_bus)53 static void pwm_npcx_configure(const struct device *dev, int clk_bus)
54 {
55 const struct pwm_npcx_config *config = dev->config;
56 struct pwm_reg *inst = config->base;
57
58 /* Disable PWM for module configuration first */
59 inst->PWMCTL &= ~BIT(NPCX_PWMCTL_PWR);
60
61 /* Set default PWM polarity to normal */
62 inst->PWMCTL &= ~BIT(NPCX_PWMCTL_INVP);
63
64 /* Turn off PWM heart-beat mode */
65 SET_FIELD(inst->PWMCTL, NPCX_PWMCTL_HB_DC_CTL_FIELD,
66 NPCX_PWM_HBM_NORMAL);
67
68 /* Select APB CLK/LFCLK clock sources to PWM module by default */
69 SET_FIELD(inst->PWMCTLEX, NPCX_PWMCTLEX_FCK_SEL_FIELD,
70 NPCX_PWM_CLOCK_APB2_LFCLK);
71
72 /* Select clock source to LFCLK by flag, otherwise APB clock source */
73 if (clk_bus == NPCX_CLOCK_BUS_LFCLK)
74 inst->PWMCTL |= BIT(NPCX_PWMCTL_CKSEL);
75 else
76 inst->PWMCTL &= ~BIT(NPCX_PWMCTL_CKSEL);
77 }
78
79 /* PWM api functions */
pwm_npcx_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)80 static int pwm_npcx_set_cycles(const struct device *dev, uint32_t channel,
81 uint32_t period_cycles, uint32_t pulse_cycles,
82 pwm_flags_t flags)
83 {
84 /* Single channel for each pwm device */
85 ARG_UNUSED(channel);
86 const struct pwm_npcx_config *config = dev->config;
87 struct pwm_npcx_data *const data = dev->data;
88 struct pwm_reg *inst = config->base;
89 int prescaler;
90 uint32_t ctl;
91 uint32_t ctr;
92 uint32_t dcr;
93 uint32_t prsc;
94
95 ctl = inst->PWMCTL | BIT(NPCX_PWMCTL_PWR);
96
97 /* Select PWM inverted polarity (ie. active-low pulse). */
98 if (flags & PWM_POLARITY_INVERTED) {
99 ctl |= BIT(NPCX_PWMCTL_INVP);
100 } else {
101 ctl &= ~BIT(NPCX_PWMCTL_INVP);
102 }
103
104 /* If pulse_cycles is 0, switch PWM off and return. */
105 if (pulse_cycles == 0) {
106 ctl &= ~BIT(NPCX_PWMCTL_PWR);
107 inst->PWMCTL = ctl;
108 return 0;
109 }
110
111 /*
112 * Calculate PWM prescaler that let period_cycles map to
113 * maximum pwm period cycles and won't exceed it.
114 * Then prescaler = ceil (period_cycles / pwm_max_period_cycles)
115 */
116 prescaler = DIV_ROUND_UP(period_cycles, NPCX_PWM_MAX_PERIOD_CYCLES);
117 if (prescaler > NPCX_PWM_MAX_PRESCALER) {
118 return -EINVAL;
119 }
120
121 /* Set PWM prescaler. */
122 prsc = prescaler - 1;
123
124 /* Set PWM period cycles. */
125 ctr = (period_cycles / prescaler) - 1;
126
127 /* Set PWM pulse cycles. */
128 dcr = (pulse_cycles / prescaler) - 1;
129
130 LOG_DBG("freq %d, pre %d, period %d, pulse %d",
131 data->cycles_per_sec / period_cycles, prsc, ctr, dcr);
132
133 /* Reconfigure only if necessary. */
134 if (inst->PWMCTL != ctl || inst->PRSC != prsc || inst->CTR != ctr) {
135 /* Disable PWM before configuring. */
136 inst->PWMCTL &= ~BIT(NPCX_PWMCTL_PWR);
137
138 inst->PRSC = prsc;
139 inst->CTR = ctr;
140 inst->DCR = dcr;
141
142 /* Enable PWM now. */
143 inst->PWMCTL = ctl;
144
145 return 0;
146 }
147
148 inst->DCR = dcr;
149
150 return 0;
151 }
152
pwm_npcx_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)153 static int pwm_npcx_get_cycles_per_sec(const struct device *dev,
154 uint32_t channel, uint64_t *cycles)
155 {
156 /* Single channel for each pwm device */
157 ARG_UNUSED(channel);
158 struct pwm_npcx_data *const data = dev->data;
159
160 *cycles = data->cycles_per_sec;
161 return 0;
162 }
163
164 /* PWM driver registration */
165 static const struct pwm_driver_api pwm_npcx_driver_api = {
166 .set_cycles = pwm_npcx_set_cycles,
167 .get_cycles_per_sec = pwm_npcx_get_cycles_per_sec
168 };
169
pwm_npcx_init(const struct device * dev)170 static int pwm_npcx_init(const struct device *dev)
171 {
172 const struct pwm_npcx_config *const config = dev->config;
173 struct pwm_npcx_data *const data = dev->data;
174 struct pwm_reg *const inst = config->base;
175 const struct device *const clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE);
176 int ret;
177
178 /*
179 * NPCX PWM module mixes byte and word registers together. Make sure
180 * word reg access via structure won't break into two byte reg accesses
181 * unexpectedly by toolchains options or attributes. If so, stall here.
182 */
183 NPCX_REG_WORD_ACCESS_CHECK(inst->PRSC, 0xA55A);
184
185
186 if (!device_is_ready(clk_dev)) {
187 LOG_ERR("clock control device not ready");
188 return -ENODEV;
189 }
190
191 /* Turn on device clock first and get source clock freq. */
192 ret = clock_control_on(clk_dev, (clock_control_subsys_t)
193 &config->clk_cfg);
194 if (ret < 0) {
195 LOG_ERR("Turn on PWM clock fail %d", ret);
196 return ret;
197 }
198
199 ret = clock_control_get_rate(clk_dev, (clock_control_subsys_t)
200 &config->clk_cfg, &data->cycles_per_sec);
201 if (ret < 0) {
202 LOG_ERR("Get PWM clock rate error %d", ret);
203 return ret;
204 }
205
206 /* Configure PWM device initially */
207 pwm_npcx_configure(dev, config->clk_cfg.bus);
208
209 /* Configure pin-mux for PWM device */
210 ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
211 if (ret < 0) {
212 LOG_ERR("PWM pinctrl setup failed (%d)", ret);
213 return ret;
214 }
215
216 return 0;
217 }
218
219 #define NPCX_PWM_INIT(inst) \
220 PINCTRL_DT_INST_DEFINE(inst); \
221 \
222 static const struct pwm_npcx_config pwm_npcx_cfg_##inst = { \
223 .base = (struct pwm_reg *)DT_INST_REG_ADDR(inst), \
224 .clk_cfg = NPCX_DT_CLK_CFG_ITEM(inst), \
225 .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
226 }; \
227 \
228 static struct pwm_npcx_data pwm_npcx_data_##inst; \
229 \
230 DEVICE_DT_INST_DEFINE(inst, \
231 &pwm_npcx_init, NULL, \
232 &pwm_npcx_data_##inst, &pwm_npcx_cfg_##inst, \
233 PRE_KERNEL_1, CONFIG_PWM_INIT_PRIORITY, \
234 &pwm_npcx_driver_api);
235
236 DT_INST_FOREACH_STATUS_OKAY(NPCX_PWM_INIT)
237