|
| 1 | +/* |
| 2 | + * Copyright (c) 2025 ITE Corporation. All Rights Reserved. |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#define DT_DRV_COMPAT ite_it51xxx_pwm |
| 8 | + |
| 9 | +#include <zephyr/device.h> |
| 10 | +#include <zephyr/drivers/pwm.h> |
| 11 | +#include <zephyr/drivers/pinctrl.h> |
| 12 | +#include <zephyr/dt-bindings/pwm/it8xxx2_pwm.h> |
| 13 | +#include <errno.h> |
| 14 | +#include <zephyr/kernel.h> |
| 15 | +#include <soc.h> |
| 16 | +#include <soc_dt.h> |
| 17 | +#include <stdlib.h> |
| 18 | + |
| 19 | +#include <zephyr/logging/log.h> |
| 20 | + |
| 21 | +LOG_MODULE_REGISTER(pwm_ite_it51xxx, CONFIG_PWM_LOG_LEVEL); |
| 22 | + |
| 23 | +#define PWM_CTX_MIN 100 |
| 24 | +#define PWM_FREQ IT51XXX_EC_FREQ |
| 25 | +#define PWM_CH_SPS_MASK 0x3 |
| 26 | + |
| 27 | +/* 0x00/0x10/0x20/0x30/0x40/0x50/0x60/0x70: PWM channel 0~7 duty cycle low byte */ |
| 28 | +#define REG_PWM_CH_DC_L 0x00 |
| 29 | +/* 0x01/0x11/0x21/0x31/0x41/0x51/0x61/0x71: PWM channel 0~7 duty cycle high byte */ |
| 30 | +#define REG_PWM_CH_DC_H 0x01 |
| 31 | +/* 0x04/0x14/0x24/0x34/0x44/0x54/0x64/0x74: PWM channel 0~7 control 0 */ |
| 32 | +#define REG_PWM_CH_CTRL0 0x04 |
| 33 | +#define PWM_CH_PWMODEN BIT(2) |
| 34 | +#define PWM_CH_PCSG BIT(1) |
| 35 | +#define PWM_CH_INVP BIT(0) |
| 36 | +/* 0x05/0x15/0x25/0x35/0x45/0x55/0x65/0x75: PWM channel 0~7 select prescaler source */ |
| 37 | +#define REG_PWM_CH_SPS 0x05 |
| 38 | + |
| 39 | +/* 0x84/0x88/0x8C: PWM prescaler 4/6/7 clock low byte */ |
| 40 | +#define REG_PWM_PXC_L(prs_sel) (0x04 * (prs_sel)) |
| 41 | +/* 0x85/0x89/0x8D: PWM prescaler 4/6/7 clock high byte */ |
| 42 | +#define REG_PWM_PXC_H(prs_sel) (0x04 * (prs_sel) + 0x01) |
| 43 | +/* 0x86/0x8A/0x8E: PWM prescaler 4/6/7 clock source select low byte */ |
| 44 | +#define REG_PWM_PXCSS_L(prs_sel) (0x04 * (prs_sel) + 0x02) |
| 45 | +#define PWM_PCFS_EC BIT(0) |
| 46 | +/* 0xA4/0xA8/0xAC: PWM cycle timer 1/2/3 low byte */ |
| 47 | +#define REG_PWM_CTX_L(prs_sel) (0x20 + 0x04 * (prs_sel)) |
| 48 | +/* 0xA5/0xA9/0xAD: PWM cycle timer 1/2/3 high byte */ |
| 49 | +#define REG_PWM_CTX_H(prs_sel) (0x20 + 0x04 * (prs_sel) + 0x01) |
| 50 | +/* 0xF0: PWM global control */ |
| 51 | +#define REG_PWM_GCTRL 0x70 |
| 52 | +#define PWM_PCCE BIT(1) |
| 53 | + |
| 54 | +struct pwm_it51xxx_cfg { |
| 55 | + /* PWM channel register base address */ |
| 56 | + uintptr_t base_ch; |
| 57 | + /* PWM prescaler register base address */ |
| 58 | + uintptr_t base_prs; |
| 59 | + /* Select PWM prescaler that output to PWM channel */ |
| 60 | + int prs_sel; |
| 61 | + /* PWM alternate configuration */ |
| 62 | + const struct pinctrl_dev_config *pcfg; |
| 63 | +}; |
| 64 | + |
| 65 | +struct pwm_it51xxx_data { |
| 66 | + uint32_t ctx; |
| 67 | + uint32_t pxc; |
| 68 | + uint32_t target_freq_prev; |
| 69 | +}; |
| 70 | + |
| 71 | +static void pwm_enable(const struct device *dev, int enabled) |
| 72 | +{ |
| 73 | + const struct pwm_it51xxx_cfg *config = dev->config; |
| 74 | + const uintptr_t base_ch = config->base_ch; |
| 75 | + uint8_t reg_val; |
| 76 | + |
| 77 | + if (enabled) { |
| 78 | + /* PWM channel clock source not gating */ |
| 79 | + reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0); |
| 80 | + sys_write8(reg_val & ~PWM_CH_PCSG, base_ch + REG_PWM_CH_CTRL0); |
| 81 | + } else { |
| 82 | + /* PWM channel clock source gating */ |
| 83 | + reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0); |
| 84 | + sys_write8(reg_val | PWM_CH_PCSG, base_ch + REG_PWM_CH_CTRL0); |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +static int pwm_it51xxx_get_cycles_per_sec(const struct device *dev, uint32_t channel, |
| 89 | + uint64_t *cycles) |
| 90 | +{ |
| 91 | + ARG_UNUSED(channel); |
| 92 | + |
| 93 | + /* |
| 94 | + * Path of pwm_it51xxx_set_cycles() called from pwm api: |
| 95 | + * 1) pwm_set() -> pwm_set_cycles_cycles() -> pwm_it51xxx_set_cycles() |
| 96 | + * target_freq = pwm_clk_src / period_cycles |
| 97 | + * = cycles / (period * cycles / NSEC_PER_SEC) |
| 98 | + * = NSEC_PER_SEC / period |
| 99 | + * 2) pwm_set_cycles() -> pwm_it51xxx_set_cycles() |
| 100 | + * target_freq = pwm_clk_src / period_cycles |
| 101 | + * = cycles / period |
| 102 | + * |
| 103 | + * When pwm needs output in EC power saving mode, we switch the prescaler |
| 104 | + * clock source (cycles) from 9.2MHz to 32.768kHz. Whether in power saving |
| 105 | + * mode or not, we need to get the same target_freq on above two cases, |
| 106 | + * so here always return PWM_FREQ. |
| 107 | + */ |
| 108 | + *cycles = (uint64_t)PWM_FREQ; |
| 109 | + |
| 110 | + return 0; |
| 111 | +} |
| 112 | + |
| 113 | +static int pwm_it51xxx_set_cycles(const struct device *dev, uint32_t channel, |
| 114 | + uint32_t period_cycles, uint32_t pulse_cycles, pwm_flags_t flags) |
| 115 | +{ |
| 116 | + const struct pwm_it51xxx_cfg *config = dev->config; |
| 117 | + const uintptr_t base_ch = config->base_ch; |
| 118 | + const uintptr_t base_prs = config->base_prs; |
| 119 | + struct pwm_it51xxx_data *data = dev->data; |
| 120 | + int prs_sel = config->prs_sel; |
| 121 | + uint32_t actual_freq = 0xffffffff, target_freq, deviation, dc_val; |
| 122 | + uint64_t pwm_clk_src; |
| 123 | + uint8_t reg_val; |
| 124 | + |
| 125 | + /* Select PWM inverted polarity (ex. active-low pulse) */ |
| 126 | + if (flags & PWM_POLARITY_INVERTED) { |
| 127 | + reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0); |
| 128 | + sys_write8(reg_val | PWM_CH_INVP, base_ch + REG_PWM_CH_CTRL0); |
| 129 | + } else { |
| 130 | + reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0); |
| 131 | + sys_write8(reg_val & ~PWM_CH_INVP, base_ch + REG_PWM_CH_CTRL0); |
| 132 | + } |
| 133 | + |
| 134 | + /* Enable PWM output open-drain */ |
| 135 | + if (flags & PWM_IT8XXX2_OPEN_DRAIN) { |
| 136 | + reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0); |
| 137 | + sys_write8(reg_val | PWM_CH_PWMODEN, base_ch + REG_PWM_CH_CTRL0); |
| 138 | + } |
| 139 | + |
| 140 | + /* If pulse cycles is 0, set duty cycle 0 and enable pwm channel */ |
| 141 | + if (pulse_cycles == 0) { |
| 142 | + /* DC_H will be valid when the next time write DC_L */ |
| 143 | + sys_write8(0, base_ch + REG_PWM_CH_DC_H); |
| 144 | + sys_write8(0, base_ch + REG_PWM_CH_DC_L); |
| 145 | + pwm_enable(dev, 1); |
| 146 | + return 0; |
| 147 | + } |
| 148 | + |
| 149 | + pwm_it51xxx_get_cycles_per_sec(dev, channel, &pwm_clk_src); |
| 150 | + target_freq = ((uint32_t)pwm_clk_src) / period_cycles; |
| 151 | + |
| 152 | + /* |
| 153 | + * Support PWM output frequency: |
| 154 | + * 1) 9.2MHz clock source: 1Hz <= target_freq <= 91089Hz |
| 155 | + * 2) 32.768KHz clock source: 1Hz <= target_freq <= 324Hz |
| 156 | + * NOTE: PWM output signal maximum supported frequency comes from |
| 157 | + * [9.2MHz or 32.768KHz] / 1 / (PWM_CTX_MIN + 1). |
| 158 | + * PWM output signal minimum supported frequency comes from |
| 159 | + * [9.2MHz or 32.768KHz] / 65536 / 1024, the minimum integer is 1. |
| 160 | + */ |
| 161 | + if (target_freq < 1) { |
| 162 | + LOG_ERR("PWM output frequency is < 1 !"); |
| 163 | + return -EINVAL; |
| 164 | + } |
| 165 | + |
| 166 | + deviation = (target_freq / 100) + 1; |
| 167 | + |
| 168 | + reg_val = sys_read8(base_prs + REG_PWM_PXCSS_L(prs_sel)); |
| 169 | + if (target_freq <= 324) { |
| 170 | + /* |
| 171 | + * Default clock source setting is 9.2MHz. When ITE chip is in power |
| 172 | + * saving mode, 9.2MHz clock source will be gated (32.768KHz won't). |
| 173 | + * So if we still need pwm output in mode, then we should set frequency |
| 174 | + * <=324Hz in board dts. Now change prescaler clock source from 9.2MHz to |
| 175 | + * 32.768KHz to support pwm output in power saving mode. |
| 176 | + */ |
| 177 | + if (reg_val & PWM_PCFS_EC) { |
| 178 | + sys_write8(reg_val & ~PWM_PCFS_EC, base_prs + REG_PWM_PXCSS_L(prs_sel)); |
| 179 | + } |
| 180 | + pwm_clk_src = (uint64_t)32768; |
| 181 | + } else { |
| 182 | + if ((reg_val & PWM_PCFS_EC) == 0) { |
| 183 | + sys_write8(reg_val | PWM_PCFS_EC, base_prs + REG_PWM_PXCSS_L(prs_sel)); |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | + /* |
| 188 | + * PWM output signal frequency is |
| 189 | + * pwm_clk_src / ((PxC[15:0] + 1) * (CTx[9:0] + 1)) |
| 190 | + * NOTE: 1) define CT minimum is 100 for more precisely when |
| 191 | + * calculate DCR |
| 192 | + * 2) PxC[15:0] value 0001h results in a divisor 2 |
| 193 | + * PxC[15:0] value FFFFh results in a divisor 65536 |
| 194 | + * CTx[9:0] value 00h results in a divisor 1 |
| 195 | + * CTx[9:0] value FFh results in a divisor 256 |
| 196 | + */ |
| 197 | + if (target_freq != data->target_freq_prev) { |
| 198 | + uint32_t ctx, pxc; |
| 199 | + |
| 200 | + for (ctx = 0x3FF; ctx >= PWM_CTX_MIN; ctx--) { |
| 201 | + pxc = (((uint32_t)pwm_clk_src) / (ctx + 1) / target_freq); |
| 202 | + /* |
| 203 | + * Make sure pxc isn't zero, or we will have |
| 204 | + * divide-by-zero on calculating actual_freq. |
| 205 | + */ |
| 206 | + if (pxc != 0) { |
| 207 | + actual_freq = ((uint32_t)pwm_clk_src) / (ctx + 1) / pxc; |
| 208 | + if (abs(actual_freq - target_freq) < deviation) { |
| 209 | + /* PxC[15:0] = pxc - 1 */ |
| 210 | + pxc--; |
| 211 | + break; |
| 212 | + } |
| 213 | + } |
| 214 | + } |
| 215 | + |
| 216 | + if (pxc > UINT16_MAX) { |
| 217 | + LOG_ERR("PWM prescaler PxC only support 2 bytes !"); |
| 218 | + return -EINVAL; |
| 219 | + } |
| 220 | + |
| 221 | + /* Store ctx and pxc with successful frequency change */ |
| 222 | + data->ctx = ctx; |
| 223 | + data->pxc = pxc; |
| 224 | + } |
| 225 | + |
| 226 | + /* Set PWM prescaler clock divide register */ |
| 227 | + sys_write8(data->pxc & 0xFF, base_prs + REG_PWM_PXC_L(prs_sel)); |
| 228 | + sys_write8((data->pxc >> 8) & 0xFF, base_prs + REG_PWM_PXC_H(prs_sel)); |
| 229 | + |
| 230 | + /* |
| 231 | + * Set PWM prescaler cycle time register. |
| 232 | + * CTx must be written high bytes first. |
| 233 | + */ |
| 234 | + sys_write8((data->ctx >> 8) & 0xFF, base_prs + REG_PWM_CTX_H(prs_sel)); |
| 235 | + sys_write8(data->ctx & 0xFF, base_prs + REG_PWM_CTX_L(prs_sel)); |
| 236 | + |
| 237 | + /* |
| 238 | + * Set PWM channel duty cycle register. |
| 239 | + * DC_H will be valid when the next time write DC_L. |
| 240 | + */ |
| 241 | + dc_val = (data->ctx * pulse_cycles) / period_cycles; |
| 242 | + sys_write8((dc_val >> 8) & 0xFF, base_ch + REG_PWM_CH_DC_H); |
| 243 | + sys_write8(dc_val & 0xFF, base_ch + REG_PWM_CH_DC_L); |
| 244 | + |
| 245 | + /* PWM channel clock source not gating */ |
| 246 | + pwm_enable(dev, 1); |
| 247 | + |
| 248 | + /* Store the frequency to be compared */ |
| 249 | + data->target_freq_prev = target_freq; |
| 250 | + |
| 251 | + LOG_DBG("clock source freq %d, target freq %d", (uint32_t)pwm_clk_src, target_freq); |
| 252 | + |
| 253 | + return 0; |
| 254 | +} |
| 255 | + |
| 256 | +static int pwm_it51xxx_init(const struct device *dev) |
| 257 | +{ |
| 258 | + const struct pwm_it51xxx_cfg *config = dev->config; |
| 259 | + const uintptr_t base_ch = config->base_ch; |
| 260 | + const uintptr_t base_prs = config->base_prs; |
| 261 | + int prs_sel = config->prs_sel; |
| 262 | + int status; |
| 263 | + uint8_t reg_val; |
| 264 | + |
| 265 | + /* PWM channel clock source gating before configuring */ |
| 266 | + pwm_enable(dev, 0); |
| 267 | + |
| 268 | + /* Select clock source from EC 9.2MHz to prescaler */ |
| 269 | + reg_val = sys_read8(base_prs + REG_PWM_PXCSS_L(prs_sel)); |
| 270 | + sys_write8(reg_val | PWM_PCFS_EC, base_prs + REG_PWM_PXCSS_L(prs_sel)); |
| 271 | + |
| 272 | + /* Clear default value and select prescaler output to PWM channel */ |
| 273 | + reg_val = sys_read8(base_ch + REG_PWM_CH_SPS); |
| 274 | + reg_val &= ~PWM_CH_SPS_MASK; |
| 275 | + sys_write8(reg_val | prs_sel, base_ch + REG_PWM_CH_SPS); |
| 276 | + |
| 277 | + /* Enable PWMs clock counter */ |
| 278 | + reg_val = sys_read8(base_prs + REG_PWM_GCTRL); |
| 279 | + sys_write8(reg_val | PWM_PCCE, base_prs + REG_PWM_GCTRL); |
| 280 | + |
| 281 | + /* Set alternate mode of PWM pin */ |
| 282 | + status = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| 283 | + if (status < 0) { |
| 284 | + LOG_ERR("Failed to configure PWM pins"); |
| 285 | + return status; |
| 286 | + } |
| 287 | + |
| 288 | + return 0; |
| 289 | +} |
| 290 | + |
| 291 | +static DEVICE_API(pwm, pwm_it51xxx_api) = { |
| 292 | + .set_cycles = pwm_it51xxx_set_cycles, |
| 293 | + .get_cycles_per_sec = pwm_it51xxx_get_cycles_per_sec, |
| 294 | +}; |
| 295 | + |
| 296 | +/* Device Instance */ |
| 297 | +#define PWM_IT51XXX_INIT(inst) \ |
| 298 | + PINCTRL_DT_INST_DEFINE(inst); \ |
| 299 | + \ |
| 300 | + static const struct pwm_it51xxx_cfg pwm_it51xxx_cfg_##inst = { \ |
| 301 | + .base_ch = DT_INST_REG_ADDR(inst), \ |
| 302 | + .base_prs = DT_REG_ADDR(DT_NODELABEL(prs)), \ |
| 303 | + .prs_sel = DT_PROP(DT_INST(inst, ite_it51xxx_pwm), prescaler_cx), \ |
| 304 | + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ |
| 305 | + }; \ |
| 306 | + \ |
| 307 | + static struct pwm_it51xxx_data pwm_it51xxx_data_##inst; \ |
| 308 | + \ |
| 309 | + DEVICE_DT_INST_DEFINE(inst, &pwm_it51xxx_init, NULL, &pwm_it51xxx_data_##inst, \ |
| 310 | + &pwm_it51xxx_cfg_##inst, PRE_KERNEL_1, CONFIG_PWM_INIT_PRIORITY, \ |
| 311 | + &pwm_it51xxx_api); |
| 312 | + |
| 313 | +DT_INST_FOREACH_STATUS_OKAY(PWM_IT51XXX_INIT) |
0 commit comments