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