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