|
| 1 | +/* |
| 2 | + * Copyright (c) 2024 Daikin Comfort Technologies North America, Inc. |
| 3 | + * |
| 4 | + * Heavily based on pwm_sam0_tcc.c, which is: |
| 5 | + * Copyright (c) 2020 Google LLC. |
| 6 | + * |
| 7 | + * SPDX-License-Identifier: Apache-2.0 |
| 8 | + */ |
| 9 | + |
| 10 | +/* |
| 11 | + * PWM driver using the SAM0 Timer/Counter (TC) Supports the SAMD21 and SAMD5x series, |
| 12 | + * 8 and 16 bit counter size is supported. |
| 13 | + * |
| 14 | + * The 8-bit counter operates in Normal PWM (NPWM) mode, it supports pulse width and period |
| 15 | + * values between 0 and 255. It is ideal for applications requiring moderate frequency PWM, |
| 16 | + * however, it is not suitable for high-precision or low-frequency applications. |
| 17 | + * |
| 18 | + * The 16-bit counter operates in Match PWM (MPWM) mode to generate the PWM signal. |
| 19 | + * this mode sacrifices the timer's CC0 channel in order to achieve pulse width modulation. |
| 20 | + */ |
| 21 | + |
| 22 | +#define DT_DRV_COMPAT atmel_sam0_tc_pwm |
| 23 | + |
| 24 | +#include <zephyr/kernel.h> |
| 25 | +#include <zephyr/device.h> |
| 26 | +#include <errno.h> |
| 27 | +#include <zephyr/drivers/pwm.h> |
| 28 | +#include <zephyr/drivers/pinctrl.h> |
| 29 | +#include <soc.h> |
| 30 | + |
| 31 | +/* Static configuration */ |
| 32 | +struct pwm_sam0_config { |
| 33 | + Tc *regs; |
| 34 | + const struct pinctrl_dev_config *pcfg; |
| 35 | + uint8_t channels; |
| 36 | + uint8_t counter_size; |
| 37 | + uint16_t prescaler; |
| 38 | + uint32_t freq; |
| 39 | + |
| 40 | +#ifdef MCLK |
| 41 | + volatile uint32_t *mclk; |
| 42 | + uint32_t mclk_mask; |
| 43 | + uint16_t gclk_id; |
| 44 | +#else |
| 45 | + uint32_t pm_apbcmask; |
| 46 | + uint16_t gclk_clkctrl_id; |
| 47 | +#endif |
| 48 | +}; |
| 49 | + |
| 50 | +#define COUNTER_8BITS 8U |
| 51 | + |
| 52 | +/* Wait for the peripheral to finish all commands */ |
| 53 | +static void wait_synchronization(Tc *regs, uint8_t counter_size) |
| 54 | +{ |
| 55 | + if (COUNTER_8BITS == counter_size) { |
| 56 | + while (regs->COUNT8.SYNCBUSY.reg != 0) { |
| 57 | + } |
| 58 | + } else { |
| 59 | + while (regs->COUNT16.SYNCBUSY.reg != 0) { |
| 60 | + } |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +static int pwm_sam0_get_cycles_per_sec(const struct device *dev, |
| 65 | + uint32_t channel, uint64_t *cycles) |
| 66 | +{ |
| 67 | + const struct pwm_sam0_config *const cfg = dev->config; |
| 68 | + |
| 69 | + if (channel >= cfg->channels) { |
| 70 | + return -EINVAL; |
| 71 | + } |
| 72 | + |
| 73 | + *cycles = cfg->freq; |
| 74 | + |
| 75 | + return 0; |
| 76 | +} |
| 77 | + |
| 78 | +static int pwm_sam0_set_cycles(const struct device *dev, uint32_t channel, uint32_t period_cycles, |
| 79 | + uint32_t pulse_cycles, pwm_flags_t flags) |
| 80 | +{ |
| 81 | + const struct pwm_sam0_config *const cfg = dev->config; |
| 82 | + Tc *regs = cfg->regs; |
| 83 | + uint8_t counter_size = cfg->counter_size; |
| 84 | + uint32_t top = 1 << counter_size; |
| 85 | + uint32_t invert_mask = 1 << channel; |
| 86 | + bool invert = ((flags & PWM_POLARITY_INVERTED) != 0); |
| 87 | + bool inverted; |
| 88 | + |
| 89 | + if (channel >= cfg->channels) { |
| 90 | + return -EINVAL; |
| 91 | + } |
| 92 | + if (period_cycles >= top || pulse_cycles >= top) { |
| 93 | + return -EINVAL; |
| 94 | + } |
| 95 | + |
| 96 | + /* |
| 97 | + * Update the buffered width and period. These will be automatically |
| 98 | + * loaded on the next cycle. |
| 99 | + */ |
| 100 | + if (COUNTER_8BITS == counter_size) { |
| 101 | + inverted = ((regs->COUNT8.DRVCTRL.vec.INVEN & invert_mask) != 0); |
| 102 | + regs->COUNT8.CCBUF[channel].reg = TC_COUNT8_CCBUF_CCBUF(pulse_cycles); |
| 103 | + regs->COUNT8.PERBUF.reg = TC_COUNT8_PERBUF_PERBUF(period_cycles); |
| 104 | + wait_synchronization(regs, counter_size); |
| 105 | + |
| 106 | + if (invert != inverted) { |
| 107 | + regs->COUNT8.CTRLA.bit.ENABLE = 0; |
| 108 | + wait_synchronization(regs, counter_size); |
| 109 | + |
| 110 | + regs->COUNT8.DRVCTRL.vec.INVEN ^= invert_mask; |
| 111 | + regs->COUNT8.CTRLA.bit.ENABLE = 1; |
| 112 | + wait_synchronization(regs, counter_size); |
| 113 | + } |
| 114 | + } else { |
| 115 | + inverted = ((regs->COUNT16.DRVCTRL.vec.INVEN & invert_mask) != 0); |
| 116 | + regs->COUNT16.CCBUF[0].reg = TC_COUNT16_CCBUF_CCBUF(period_cycles); |
| 117 | + regs->COUNT16.CCBUF[1].reg = TC_COUNT16_CCBUF_CCBUF(pulse_cycles); |
| 118 | + wait_synchronization(regs, counter_size); |
| 119 | + |
| 120 | + if (invert != inverted) { |
| 121 | + regs->COUNT16.CTRLA.bit.ENABLE = 0; |
| 122 | + wait_synchronization(regs, counter_size); |
| 123 | + |
| 124 | + regs->COUNT16.DRVCTRL.vec.INVEN ^= invert_mask; |
| 125 | + regs->COUNT16.CTRLA.bit.ENABLE = 1; |
| 126 | + wait_synchronization(regs, counter_size); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + return 0; |
| 131 | +} |
| 132 | + |
| 133 | +static int pwm_sam0_init(const struct device *dev) |
| 134 | +{ |
| 135 | + const struct pwm_sam0_config *const cfg = dev->config; |
| 136 | + int retval; |
| 137 | + Tc *regs = cfg->regs; |
| 138 | + uint8_t counter_size = cfg->counter_size; |
| 139 | + |
| 140 | + /* Enable the clocks */ |
| 141 | +#ifdef MCLK |
| 142 | + GCLK->PCHCTRL[cfg->gclk_id].reg = |
| 143 | + GCLK_PCHCTRL_GEN_GCLK0 | GCLK_PCHCTRL_CHEN; |
| 144 | + *cfg->mclk |= cfg->mclk_mask; |
| 145 | +#else |
| 146 | + GCLK->CLKCTRL.reg = cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 | |
| 147 | + GCLK_CLKCTRL_CLKEN; |
| 148 | + PM->APBCMASK.reg |= cfg->pm_apbcmask; |
| 149 | +#endif |
| 150 | + |
| 151 | + retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| 152 | + if (retval < 0) { |
| 153 | + return retval; |
| 154 | + } |
| 155 | + |
| 156 | + if (COUNTER_8BITS == counter_size) { |
| 157 | + regs->COUNT8.CTRLA.bit.SWRST = 1; |
| 158 | + wait_synchronization(regs, counter_size); |
| 159 | + |
| 160 | + regs->COUNT8.CTRLA.reg = cfg->prescaler | TC_CTRLA_MODE_COUNT8 | |
| 161 | + TC_CTRLA_PRESCSYNC_PRESC; |
| 162 | + regs->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_NPWM; |
| 163 | + regs->COUNT8.PER.reg = TC_COUNT8_PER_PER(1); |
| 164 | + |
| 165 | + regs->COUNT8.CTRLA.bit.ENABLE = 1; |
| 166 | + wait_synchronization(regs, counter_size); |
| 167 | + } else { |
| 168 | + regs->COUNT16.CTRLA.bit.SWRST = 1; |
| 169 | + wait_synchronization(regs, counter_size); |
| 170 | + |
| 171 | + regs->COUNT16.CTRLA.reg = cfg->prescaler | TC_CTRLA_MODE_COUNT16 | |
| 172 | + TC_CTRLA_PRESCSYNC_PRESC; |
| 173 | + regs->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MPWM; |
| 174 | + regs->COUNT16.CC[0].reg = TC_COUNT16_CC_CC(1); |
| 175 | + |
| 176 | + regs->COUNT16.CTRLA.bit.ENABLE = 1; |
| 177 | + wait_synchronization(regs, cfg->counter_size); |
| 178 | + } |
| 179 | + |
| 180 | + return 0; |
| 181 | +} |
| 182 | + |
| 183 | +static DEVICE_API(pwm, pwm_sam0_driver_api) = { |
| 184 | + .set_cycles = pwm_sam0_set_cycles, |
| 185 | + .get_cycles_per_sec = pwm_sam0_get_cycles_per_sec, |
| 186 | +}; |
| 187 | + |
| 188 | +#ifdef MCLK |
| 189 | +#define PWM_SAM0_INIT_CLOCKS(inst) \ |
| 190 | + .mclk = (volatile uint32_t *)MCLK_MASK_DT_INT_REG_ADDR(inst), \ |
| 191 | + .mclk_mask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(inst, mclk, bit)), \ |
| 192 | + .gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(inst, gclk, periph_ch) |
| 193 | +#else |
| 194 | +#define PWM_SAM0_INIT_CLOCKS(inst) \ |
| 195 | + .pm_apbcmask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(inst, pm, bit)), \ |
| 196 | + .gclk_clkctrl_id = DT_INST_CLOCKS_CELL_BY_NAME(inst, gclk, clkctrl_id) |
| 197 | +#endif |
| 198 | + |
| 199 | +#define PWM_SAM0_INIT(inst) \ |
| 200 | + PINCTRL_DT_INST_DEFINE(inst); \ |
| 201 | + static const struct pwm_sam0_config pwm_sam0_config_##inst = { \ |
| 202 | + .regs = (Tc *)DT_INST_REG_ADDR(inst), \ |
| 203 | + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ |
| 204 | + .channels = DT_INST_PROP(inst, channels), \ |
| 205 | + .counter_size = DT_INST_PROP(inst, counter_size), \ |
| 206 | + .prescaler = UTIL_CAT(TC_CTRLA_PRESCALER_DIV, DT_INST_PROP(inst, prescaler)), \ |
| 207 | + .freq = SOC_ATMEL_SAM0_GCLK0_FREQ_HZ / DT_INST_PROP(inst, prescaler), \ |
| 208 | + PWM_SAM0_INIT_CLOCKS(inst), \ |
| 209 | + }; \ |
| 210 | + \ |
| 211 | + DEVICE_DT_INST_DEFINE(inst, &pwm_sam0_init, NULL, NULL, &pwm_sam0_config_##inst, \ |
| 212 | + POST_KERNEL, CONFIG_PWM_TC_INIT_PRIORITY, &pwm_sam0_driver_api); |
| 213 | + |
| 214 | +DT_INST_FOREACH_STATUS_OKAY(PWM_SAM0_INIT) |
0 commit comments