Skip to content

Commit 26a8d3d

Browse files
committed
drivers/pwm/it51xxx: implement pwm driver
Implement pwm driver for ITE it51xxx series chip. Signed-off-by: Ruibin Chang <[email protected]>
1 parent bf104d4 commit 26a8d3d

File tree

8 files changed

+475
-0
lines changed

8 files changed

+475
-0
lines changed

boards/ite/it515xx_evb/it515xx_evb.dts

+23
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
aliases {
1818
led0 = &led0;
1919
watchdog0 = &twd0;
20+
pwm-0 = &pwm0;
2021
};
2122

2223
chosen {
@@ -111,6 +112,28 @@
111112
pinctrl-names = "default";
112113
};
113114

115+
/* test pwm */
116+
&pwm0 {
117+
status = "okay";
118+
prescaler-cx = <PWM_PRESCALER_C6>;
119+
/*
120+
* If we need pwm output in ITE chip power saving mode,
121+
* then we should set frequency <=324Hz.
122+
*/
123+
pwm-output-frequency = <324>;
124+
pinctrl-0 = <&pwm0_gpa0_default>;
125+
pinctrl-names = "default";
126+
};
127+
128+
/* test fan */
129+
&pwm7 {
130+
status = "okay";
131+
prescaler-cx = <PWM_PRESCALER_C4>;
132+
pwm-output-frequency = <30000>;
133+
pinctrl-0 = <&pwm7_gpa7_default>;
134+
pinctrl-names = "default";
135+
};
136+
114137
/* test fan tachometer sensor */
115138
&tach0 {
116139
status = "okay";

drivers/pwm/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_NRF_SW pwm_nrf_sw.c)
1212
zephyr_library_sources_ifdef(CONFIG_PWM_NRFX pwm_nrfx.c)
1313
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_FTM pwm_mcux_ftm.c)
1414
zephyr_library_sources_ifdef(CONFIG_PWM_IMX pwm_imx.c)
15+
zephyr_library_sources_ifdef(CONFIG_PWM_ITE_IT51XXX pwm_ite_it51xxx.c)
1516
zephyr_library_sources_ifdef(CONFIG_PWM_ITE_IT8XXX2 pwm_ite_it8xxx2.c)
1617
zephyr_library_sources_ifdef(CONFIG_PWM_ITE_IT8801 pwm_ite_it8801.c)
1718
zephyr_library_sources_ifdef(CONFIG_PWM_LED_ESP32 pwm_led_esp32.c)

drivers/pwm/Kconfig

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ source "drivers/pwm/Kconfig.mcux_ftm"
4848

4949
source "drivers/pwm/Kconfig.imx"
5050

51+
source "drivers/pwm/Kconfig.it51xxx"
52+
5153
source "drivers/pwm/Kconfig.it8xxx2"
5254

5355
source "drivers/pwm/Kconfig.it8801"

drivers/pwm/Kconfig.it51xxx

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) 2025 ITE Corporation. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config PWM_ITE_IT51XXX
5+
bool "ITE IT51XXX embedded controller (EC) PWM driver"
6+
default y
7+
depends on DT_HAS_ITE_IT51XXX_PWM_ENABLED
8+
select PINCTRL
9+
help
10+
Enable PWM driver for IT51xxx series SoC.
11+
Supports three 16-bit prescalers each with 10-bit cycle timer, and
12+
eight PWM channels each with 10-bit duty cycle.

drivers/pwm/pwm_ite_it51xxx.c

+313
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
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

Comments
 (0)