1 /*
2 * Copyright (c) 2006-2023, RT-Thread Development Team
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 * Change Logs:
7 * Date Author Notes
8 * 2022-08-04 Emuzit first version
9 */
10 #include <rthw.h>
11 #include <drivers/dev_pwm.h>
12 #include <drivers/dev_pin.h>
13 #include "ch56x_pwm.h"
14 #include "ch56x_sys.h"
15
16 #define PWM_CYCLE_MAX 255 // must be 255 for 0%~100% duty cycle
17
18 struct pwm_device
19 {
20 struct rt_device_pwm parent;
21 volatile struct pwm_registers *reg_base;
22 uint32_t period;
23 };
24 static struct pwm_device pwmx_device;
25
26 static const uint8_t pwmx_pin[] = {PWM0_PIN, PWM1_PIN, PWM2_PIN, PWM3_PIN};
27
28 /**
29 * @brief Enable or disable PWM channel output.
30 * Make sure PWM clock is ON for writing registers.
31 *
32 * @param device is pointer to the rt_device_pwm device.
33 *
34 * @param channel is the PWM channel (0~3) to operate on.
35 *
36 * @param enable is to enable PWM when RT_TRUE, or disable when RT_FALSE.
37 *
38 * @return None.
39 */
pwm_channel_enable(struct rt_device_pwm * device,uint32_t channel,rt_bool_t enable)40 static void pwm_channel_enable(struct rt_device_pwm *device,
41 uint32_t channel, rt_bool_t enable)
42 {
43 struct pwm_device *pwm_device = (struct pwm_device *)device;
44 volatile struct pwm_registers *pxreg = pwm_device->reg_base;
45
46 uint8_t ctrl_mod, polar;
47
48 if (enable)
49 {
50 /* set pwm_out_en to allow pwm output */
51 ctrl_mod = pxreg->CTRL_MOD.reg;
52 pxreg->CTRL_MOD.reg = ctrl_mod | (RB_PWM0_OUT_EN << channel);
53 }
54 else
55 {
56 /* ch56x has no disable bit, set pin out to quiesce */
57 ctrl_mod = pxreg->CTRL_MOD.reg;
58 polar = ctrl_mod & (RB_PWM0_POLAR << channel);
59 rt_pin_write(pwmx_pin[channel], polar ? PIN_HIGH : PIN_LOW);
60 ctrl_mod &= ~(RB_PWM0_OUT_EN << channel);
61 pxreg->CTRL_MOD.reg = ctrl_mod;
62 }
63 }
64
65 /**
66 * @brief Set period of the PWM channel.
67 * Make sure PWM clock is ON for writing registers.
68 *
69 * @param device is pointer to the rt_device_pwm device.
70 *
71 * @param channel is the PWM channel (0~3) to operate on.
72 *
73 * @param period is PWM period in nanoseconds.
74 *
75 * @return RT_EOK if successful.
76 */
pwm_channel_period(struct rt_device_pwm * device,uint32_t channel,uint32_t period)77 static rt_err_t pwm_channel_period(struct rt_device_pwm *device,
78 uint32_t channel, uint32_t period)
79 {
80 struct pwm_device *pwm_device = (struct pwm_device *)device;
81
82 uint32_t clock_div;
83
84 /* All ch56x PWMX channels share the same period, channel ignored.
85 *
86 * Max allowed period is when Fsys@2MHz and CLOCK_DIV is 0 (256) :
87 * (1 / 2MHz) * 256 * PWM_CYCLE_MAX => 32640000 ns
88 * Note that `period * F_MHz` won't overflow in calculation below.
89 */
90 if (period > (256 * PWM_CYCLE_MAX * 1000 / 2))
91 return -RT_EINVAL;
92
93 if (period != pwm_device->period)
94 {
95 uint32_t Fsys = sys_hclk_get();
96 uint32_t F_MHz = Fsys / 1000000;
97 uint32_t F_mod = Fsys % 1000000;
98
99 /* period = (clock_div / Fsys) * 10^9 * PWM_CYCLE_MAX */
100 clock_div = period * F_MHz + (1000 * PWM_CYCLE_MAX / 2);
101 /* Fsys is mostly in integer MHz, likely to be skipped */
102 if (F_mod != 0)
103 {
104 uint64_t u64v = ((uint64_t)period * F_mod) / 1000000;
105 clock_div += (uint32_t)u64v;
106 }
107 clock_div = clock_div / (1000 * PWM_CYCLE_MAX);
108 if (clock_div > 256)
109 return -RT_EINVAL;
110 /* CLOCK_DIV will be 0 if `clock_div` is 256 */
111 pwm_device->reg_base->CLOCK_DIV = (uint8_t)clock_div;
112 /* cycle_sel set to PWM_CYCLE_SEL_255 for 0%~100% duty cycle */
113 pwmx_device.reg_base->CTRL_CFG.cycle_sel = PWM_CYCLE_SEL_255;
114 pwm_device->period = period;
115 }
116
117 return RT_EOK;
118 }
119
120 /**
121 * @brief Set pulse duration of the PWM channel.
122 * Make sure PWM clock is ON for writing registers.
123 *
124 * @param device is pointer to the rt_device_pwm device.
125 *
126 * @param channel is the PWM channel (0~3) to operate on.
127 *
128 * @param pulse is PWM pulse duration in nanoseconds.
129 *
130 * @return RT_EOK if successful.
131 */
pwm_channel_pulse(struct rt_device_pwm * device,uint32_t channel,uint32_t pulse)132 static rt_err_t pwm_channel_pulse(struct rt_device_pwm *device,
133 uint32_t channel, uint32_t pulse)
134 {
135 struct pwm_device *pwm_device = (struct pwm_device *)device;
136
137 uint32_t pdata, period;
138
139 /* duty cycle is calculated with "raw" period setting */
140 period = pwm_device->period;
141 if (!period || pulse > period)
142 return -RT_EINVAL;
143
144 pdata = (pulse * PWM_CYCLE_MAX + (period >> 1)) / period;
145 pwm_device->reg_base->PWM_DATA[channel] = pdata;
146
147 return RT_EOK;
148 }
149
150 /**
151 * @brief Set period & pulse of the PWM channel, remain disabled.
152 * Make sure PWM clock is ON for writing registers.
153 *
154 * @param device is pointer to the rt_device_pwm device.
155 *
156 * @param configuration is the channel/period/pulse specification.
157 * ch56x PWM has no complementary pin, complementary ignored.
158 * FIXME: can we specify PWM output polarity somehow ?
159 *
160 * @return RT_EOK if successful.
161 */
pwm_device_set(struct rt_device_pwm * device,struct rt_pwm_configuration * configuration)162 static rt_err_t pwm_device_set(struct rt_device_pwm *device,
163 struct rt_pwm_configuration *configuration)
164 {
165 struct pwm_device *pwm_device = (struct pwm_device *)device;
166
167 uint32_t channel = configuration->channel;
168
169 rt_err_t res;
170
171 res = pwm_channel_period(device, channel, configuration->period);
172 if (res == RT_EOK)
173 {
174 res = pwm_channel_pulse(device, channel, configuration->pulse);
175 if (res == RT_EOK)
176 {
177 rt_pin_mode(pwmx_pin[channel], PIN_MODE_OUTPUT);
178 /* seems to be kept disabled according to sample code */
179 pwm_channel_enable(device, channel, RT_FALSE);
180 }
181 }
182
183 return res;
184 }
185
186 /**
187 * @brief Get period & pulse of the PWM channel.
188 * The returned information is calculated with h/w setting.
189 *
190 * @param device is pointer to the rt_device_pwm device.
191 *
192 * @param configuration->channel specify the PWM channel (0~3).
193 * configuration->period & pulse return the calculated result.
194 *
195 * @return RT_EOK if successful.
196 */
pwm_device_get(struct rt_device_pwm * device,struct rt_pwm_configuration * configuration)197 static rt_err_t pwm_device_get(struct rt_device_pwm *device,
198 struct rt_pwm_configuration *configuration)
199 {
200 struct pwm_device *pwm_device = (struct pwm_device *)device;
201 volatile struct pwm_registers *pxreg = pwm_device->reg_base;
202
203 uint32_t channel = configuration->channel;
204
205 uint32_t Fsys = sys_hclk_get();
206
207 uint32_t clock_div;
208 uint32_t pdata;
209 uint64_t u64v;
210
211 /* clock_div is actually 256 when CLOCK_DIV is 0 */
212 clock_div = pxreg->CLOCK_DIV;
213 if (clock_div == 0)
214 clock_div = 256;
215
216 u64v = clock_div;
217 u64v = (u64v * 1000*1000*1000 * PWM_CYCLE_MAX + (Fsys >> 1)) / Fsys;
218 configuration->period = (uint32_t)u64v;
219
220 /* `pdata` <= PWM_CYCLE_MAX, calculated pulse won't exceed period */
221 pdata = pxreg->PWM_DATA[channel];
222 u64v = clock_div;
223 u64v = (u64v * 1000*1000*1000 * pdata + (Fsys >> 1)) / Fsys;
224 configuration->pulse = (uint32_t)u64v;
225
226 return RT_EOK;
227 }
228
pwm_control(struct rt_device_pwm * device,int cmd,void * arg)229 static rt_err_t pwm_control(struct rt_device_pwm *device, int cmd, void *arg)
230 {
231 struct pwm_device *pwm_device = (struct pwm_device *)device;
232
233 struct rt_pwm_configuration *configuration = arg;
234 uint32_t channel = configuration->channel;
235
236 rt_err_t res = RT_EOK;
237
238 RT_ASSERT(device != RT_NULL);
239
240 if (channel >= PWM_CHANNELS)
241 return -RT_EINVAL;
242
243 /* PWM clock needs to be ON to write PWM registers */
244 sys_slp_clk_off0(RB_SLP_CLK_PWMX, SYS_SLP_CLK_ON);
245
246 switch (cmd)
247 {
248 case PWM_CMD_ENABLE:
249 pwm_channel_enable(device, channel, RT_TRUE);
250 break;
251 case PWM_CMD_DISABLE:
252 pwm_channel_enable(device, channel, RT_FALSE);
253 break;
254 case PWM_CMD_SET:
255 return pwm_device_set(device, configuration);
256 case PWM_CMD_GET:
257 return pwm_device_get(device, configuration);
258 case PWM_CMD_SET_PERIOD:
259 return pwm_channel_period(device, channel, configuration->period);
260 case PWM_CMD_SET_PULSE:
261 return pwm_channel_pulse(device, channel, configuration->pulse);
262 default:
263 res = -RT_EINVAL;
264 }
265
266 /* disable PWMX clocking, if all channels are disabled */
267 if ((pwm_device->reg_base->CTRL_MOD.reg & PWM_OUT_EN_MASK) == 0)
268 sys_slp_clk_off0(RB_SLP_CLK_PWMX, SYS_SLP_CLK_OFF);
269
270 return res;
271 }
272
273 static struct rt_pwm_ops pwm_ops =
274 {
275 .control = pwm_control
276 };
277
rt_hw_pwm_init(void)278 static int rt_hw_pwm_init(void)
279 {
280 /* init pwmx_device with code to save some flash space */
281 pwmx_device.reg_base = (struct pwm_registers *)PWMX_REG_BASE;
282 /* Note: PWM clock OFF here => PWM registers not writable */
283
284 return rt_device_pwm_register(
285 &pwmx_device.parent, PWM_DEVICE_NAME, &pwm_ops, RT_NULL);
286 }
287 INIT_DEVICE_EXPORT(rt_hw_pwm_init);
288