Skip to content

drivers/pwm/it51xxx: implement pwm driver #88471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions boards/ite/it515xx_evb/it515xx_evb.dts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
aliases {
led0 = &led0;
watchdog0 = &twd0;
pwm-0 = &pwm0;
};

chosen {
Expand Down Expand Up @@ -111,6 +112,28 @@
pinctrl-names = "default";
};

/* test pwm */
&pwm0 {
status = "okay";
prescaler-cx = <PWM_PRESCALER_C6>;
/*
* If we need pwm output in ITE chip power saving mode,
* then we should set frequency <=324Hz.
*/
pwm-output-frequency = <324>;
pinctrl-0 = <&pwm0_gpa0_default>;
pinctrl-names = "default";
};

/* test fan */
&pwm7 {
status = "okay";
prescaler-cx = <PWM_PRESCALER_C4>;
pwm-output-frequency = <30000>;
pinctrl-0 = <&pwm7_gpa7_default>;
pinctrl-names = "default";
};

/* test fan tachometer sensor */
&tach0 {
status = "okay";
Expand Down
1 change: 1 addition & 0 deletions drivers/pwm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_NRF_SW pwm_nrf_sw.c)
zephyr_library_sources_ifdef(CONFIG_PWM_NRFX pwm_nrfx.c)
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_FTM pwm_mcux_ftm.c)
zephyr_library_sources_ifdef(CONFIG_PWM_IMX pwm_imx.c)
zephyr_library_sources_ifdef(CONFIG_PWM_ITE_IT51XXX pwm_ite_it51xxx.c)
zephyr_library_sources_ifdef(CONFIG_PWM_ITE_IT8XXX2 pwm_ite_it8xxx2.c)
zephyr_library_sources_ifdef(CONFIG_PWM_ITE_IT8801 pwm_ite_it8801.c)
zephyr_library_sources_ifdef(CONFIG_PWM_LED_ESP32 pwm_led_esp32.c)
Expand Down
2 changes: 2 additions & 0 deletions drivers/pwm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ source "drivers/pwm/Kconfig.mcux_ftm"

source "drivers/pwm/Kconfig.imx"

source "drivers/pwm/Kconfig.it51xxx"

source "drivers/pwm/Kconfig.it8xxx2"

source "drivers/pwm/Kconfig.it8801"
Expand Down
12 changes: 12 additions & 0 deletions drivers/pwm/Kconfig.it51xxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright (c) 2025 ITE Corporation. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

config PWM_ITE_IT51XXX
bool "ITE IT51XXX embedded controller (EC) PWM driver"
default y
depends on DT_HAS_ITE_IT51XXX_PWM_ENABLED
select PINCTRL
help
Enable PWM driver for IT51xxx series SoC.
Supports three 16-bit prescalers each with 10-bit cycle timer, and
eight PWM channels each with 10-bit duty cycle.
313 changes: 313 additions & 0 deletions drivers/pwm/pwm_ite_it51xxx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
/*
* Copyright (c) 2025 ITE Corporation. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT ite_it51xxx_pwm

#include <errno.h>
#include <soc.h>
#include <soc_dt.h>
#include <stdlib.h>
#include <zephyr/device.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/dt-bindings/pwm/it51xxx_pwm.h>
#include <zephyr/kernel.h>

#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(pwm_ite_it51xxx, CONFIG_PWM_LOG_LEVEL);

#define PWM_CTX_MIN 100
#define PWM_FREQ IT51XXX_EC_FREQ
#define PWM_CH_SPS_MASK GENMASK(1, 0)

/* 0x00/0x10/0x20/0x30/0x40/0x50/0x60/0x70: PWM channel 0~7 duty cycle low byte */
#define REG_PWM_CH_DC_L 0x00
/* 0x01/0x11/0x21/0x31/0x41/0x51/0x61/0x71: PWM channel 0~7 duty cycle high byte */
#define REG_PWM_CH_DC_H 0x01
/* 0x04/0x14/0x24/0x34/0x44/0x54/0x64/0x74: PWM channel 0~7 control 0 */
#define REG_PWM_CH_CTRL0 0x04
#define PWM_CH_PWMODEN BIT(2)
#define PWM_CH_PCSG BIT(1)
#define PWM_CH_INVP BIT(0)
/* 0x05/0x15/0x25/0x35/0x45/0x55/0x65/0x75: PWM channel 0~7 select prescaler source */
#define REG_PWM_CH_SPS 0x05

/* 0x84/0x88/0x8C: PWM prescaler 4/6/7 clock low byte */
#define REG_PWM_PXC_L(prs_sel) (0x04 * (prs_sel))
/* 0x85/0x89/0x8D: PWM prescaler 4/6/7 clock high byte */
#define REG_PWM_PXC_H(prs_sel) (0x04 * (prs_sel) + 0x01)
/* 0x86/0x8A/0x8E: PWM prescaler 4/6/7 clock source select low byte */
#define REG_PWM_PXCSS_L(prs_sel) (0x04 * (prs_sel) + 0x02)
#define PWM_PCFS_EC BIT(0)
/* 0xA4/0xA8/0xAC: PWM cycle timer 1/2/3 low byte */
#define REG_PWM_CTX_L(prs_sel) (0x20 + 0x04 * (prs_sel))
/* 0xA5/0xA9/0xAD: PWM cycle timer 1/2/3 high byte */
#define REG_PWM_CTX_H(prs_sel) (0x20 + 0x04 * (prs_sel) + 0x01)
/* 0xF0: PWM global control */
#define REG_PWM_GCTRL 0x70
#define PWM_PCCE BIT(1)

struct pwm_it51xxx_cfg {
/* PWM channel register base address */
uintptr_t base_ch;
/* PWM prescaler register base address */
uintptr_t base_prs;
/* Select PWM prescaler that output to PWM channel */
int prs_sel;
/* PWM alternate configuration */
const struct pinctrl_dev_config *pcfg;
};

struct pwm_it51xxx_data {
uint32_t ctx;
uint32_t pxc;
uint32_t target_freq_prev;
};

static void pwm_enable(const struct device *dev, int enabled)
{
const struct pwm_it51xxx_cfg *config = dev->config;
const uintptr_t base_ch = config->base_ch;
uint8_t reg_val;

if (enabled) {
/* PWM channel clock source not gating */
reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0);
sys_write8(reg_val & ~PWM_CH_PCSG, base_ch + REG_PWM_CH_CTRL0);
} else {
/* PWM channel clock source gating */
reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0);
sys_write8(reg_val | PWM_CH_PCSG, base_ch + REG_PWM_CH_CTRL0);
}
}

static int pwm_it51xxx_get_cycles_per_sec(const struct device *dev, uint32_t channel,
uint64_t *cycles)
{
ARG_UNUSED(channel);

/*
* Path of pwm_it51xxx_set_cycles() called from pwm api:
* 1) pwm_set() -> pwm_set_cycles_cycles() -> pwm_it51xxx_set_cycles()
* target_freq = pwm_clk_src / period_cycles
* = cycles / (period * cycles / NSEC_PER_SEC)
* = NSEC_PER_SEC / period
* 2) pwm_set_cycles() -> pwm_it51xxx_set_cycles()
* target_freq = pwm_clk_src / period_cycles
* = cycles / period
*
* When pwm needs output in EC power saving mode, we switch the prescaler
* clock source (cycles) from 9.2MHz to 32.768kHz. Whether in power saving
* mode or not, we need to get the same target_freq on above two cases,
* so here always return PWM_FREQ.
*/
*cycles = (uint64_t)PWM_FREQ;

return 0;
}

static int pwm_it51xxx_set_cycles(const struct device *dev, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles, pwm_flags_t flags)
{
const struct pwm_it51xxx_cfg *config = dev->config;
const uintptr_t base_ch = config->base_ch;
const uintptr_t base_prs = config->base_prs;
struct pwm_it51xxx_data *data = dev->data;
int prs_sel = config->prs_sel;
uint32_t actual_freq = 0xffffffff, target_freq, deviation, dc_val;
uint64_t pwm_clk_src;
uint8_t reg_val;

/* Select PWM inverted polarity (ex. active-low pulse) */
if (flags & PWM_POLARITY_INVERTED) {
reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0);
sys_write8(reg_val | PWM_CH_INVP, base_ch + REG_PWM_CH_CTRL0);
} else {
reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0);
sys_write8(reg_val & ~PWM_CH_INVP, base_ch + REG_PWM_CH_CTRL0);
}

/* Enable PWM output open-drain */
if (flags & PWM_IT51XXX_OPEN_DRAIN) {
reg_val = sys_read8(base_ch + REG_PWM_CH_CTRL0);
sys_write8(reg_val | PWM_CH_PWMODEN, base_ch + REG_PWM_CH_CTRL0);
}

/* If pulse cycles is 0, set duty cycle 0 and enable pwm channel */
if (pulse_cycles == 0) {
/* DC_H will be valid when the next time write DC_L */
sys_write8(0, base_ch + REG_PWM_CH_DC_H);
sys_write8(0, base_ch + REG_PWM_CH_DC_L);
pwm_enable(dev, 1);
return 0;
}

pwm_it51xxx_get_cycles_per_sec(dev, channel, &pwm_clk_src);
target_freq = ((uint32_t)pwm_clk_src) / period_cycles;

/*
* Support PWM output frequency:
* 1) 9.2MHz clock source: 1Hz <= target_freq <= 91089Hz
* 2) 32.768KHz clock source: 1Hz <= target_freq <= 324Hz
* NOTE: PWM output signal maximum supported frequency comes from
* [9.2MHz or 32.768KHz] / 1 / (PWM_CTX_MIN + 1).
* PWM output signal minimum supported frequency comes from
* [9.2MHz or 32.768KHz] / 65536 / 1024, the minimum integer is 1.
*/
if (target_freq < 1) {
LOG_ERR("PWM output frequency is < 1 !");
return -EINVAL;
}

deviation = (target_freq / 100) + 1;

reg_val = sys_read8(base_prs + REG_PWM_PXCSS_L(prs_sel));
if (target_freq <= 324) {
/*
* Default clock source setting is 9.2MHz. When ITE chip is in power
* saving mode, 9.2MHz clock source will be gated (32.768KHz won't).
* So if we still need pwm output in mode, then we should set frequency
* <=324Hz in board dts. Now change prescaler clock source from 9.2MHz to
* 32.768KHz to support pwm output in power saving mode.
*/
if (reg_val & PWM_PCFS_EC) {
sys_write8(reg_val & ~PWM_PCFS_EC, base_prs + REG_PWM_PXCSS_L(prs_sel));
}
pwm_clk_src = (uint64_t)32768;
} else {
if ((reg_val & PWM_PCFS_EC) == 0) {
sys_write8(reg_val | PWM_PCFS_EC, base_prs + REG_PWM_PXCSS_L(prs_sel));
}
}

/*
* PWM output signal frequency is
* pwm_clk_src / ((PxC[15:0] + 1) * (CTx[9:0] + 1))
* NOTE: 1) define CT minimum is 100 for more precisely when
* calculate DCR
* 2) PxC[15:0] value 0001h results in a divisor 2
* PxC[15:0] value FFFFh results in a divisor 65536
* CTx[9:0] value 00h results in a divisor 1
* CTx[9:0] value FFh results in a divisor 256
*/
if (target_freq != data->target_freq_prev) {
uint32_t ctx, pxc;

for (ctx = 0x3FF; ctx >= PWM_CTX_MIN; ctx--) {
pxc = (((uint32_t)pwm_clk_src) / (ctx + 1) / target_freq);
/*
* Make sure pxc isn't zero, or we will have
* divide-by-zero on calculating actual_freq.
*/
if (pxc != 0) {
actual_freq = ((uint32_t)pwm_clk_src) / (ctx + 1) / pxc;
if (abs(actual_freq - target_freq) < deviation) {
/* PxC[15:0] = pxc - 1 */
pxc--;
break;
}
}
}

if (pxc > UINT16_MAX) {
LOG_ERR("PWM prescaler PxC only support 2 bytes !");
return -EINVAL;
}

/* Store ctx and pxc with successful frequency change */
data->ctx = ctx;
data->pxc = pxc;
}

/* Set PWM prescaler clock divide register */
sys_write8(data->pxc & 0xFF, base_prs + REG_PWM_PXC_L(prs_sel));
sys_write8((data->pxc >> 8) & 0xFF, base_prs + REG_PWM_PXC_H(prs_sel));

/*
* Set PWM prescaler cycle time register.
* CTx must be written high bytes first.
*/
sys_write8((data->ctx >> 8) & 0xFF, base_prs + REG_PWM_CTX_H(prs_sel));
sys_write8(data->ctx & 0xFF, base_prs + REG_PWM_CTX_L(prs_sel));

/*
* Set PWM channel duty cycle register.
* DC_H will be valid when the next time write DC_L.
*/
dc_val = (data->ctx * pulse_cycles) / period_cycles;
sys_write8((dc_val >> 8) & 0xFF, base_ch + REG_PWM_CH_DC_H);
sys_write8(dc_val & 0xFF, base_ch + REG_PWM_CH_DC_L);

/* PWM channel clock source not gating */
pwm_enable(dev, 1);

/* Store the frequency to be compared */
data->target_freq_prev = target_freq;

LOG_DBG("clock source freq %d, target freq %d", (uint32_t)pwm_clk_src, target_freq);

return 0;
}

static int pwm_it51xxx_init(const struct device *dev)
{
const struct pwm_it51xxx_cfg *config = dev->config;
const uintptr_t base_ch = config->base_ch;
const uintptr_t base_prs = config->base_prs;
int prs_sel = config->prs_sel;
int status;
uint8_t reg_val;

/* PWM channel clock source gating before configuring */
pwm_enable(dev, 0);

/* Select clock source from EC 9.2MHz to prescaler */
reg_val = sys_read8(base_prs + REG_PWM_PXCSS_L(prs_sel));
sys_write8(reg_val | PWM_PCFS_EC, base_prs + REG_PWM_PXCSS_L(prs_sel));

/* Clear default value and select prescaler output to PWM channel */
reg_val = sys_read8(base_ch + REG_PWM_CH_SPS);
reg_val &= ~PWM_CH_SPS_MASK;
sys_write8(reg_val | prs_sel, base_ch + REG_PWM_CH_SPS);

/* Enable PWMs clock counter */
reg_val = sys_read8(base_prs + REG_PWM_GCTRL);
sys_write8(reg_val | PWM_PCCE, base_prs + REG_PWM_GCTRL);

/* Set alternate mode of PWM pin */
status = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
if (status < 0) {
LOG_ERR("Failed to configure PWM pins");
return status;
}

return 0;
}

static DEVICE_API(pwm, pwm_it51xxx_api) = {
.set_cycles = pwm_it51xxx_set_cycles,
.get_cycles_per_sec = pwm_it51xxx_get_cycles_per_sec,
};

/* Device Instance */
#define PWM_IT51XXX_INIT(inst) \
PINCTRL_DT_INST_DEFINE(inst); \
\
static const struct pwm_it51xxx_cfg pwm_it51xxx_cfg_##inst = { \
.base_ch = DT_INST_REG_ADDR_BY_IDX(inst, 0), \
.base_prs = DT_INST_REG_ADDR_BY_IDX(inst, 1), \
.prs_sel = DT_PROP(DT_INST(inst, ite_it51xxx_pwm), prescaler_cx), \
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
}; \
\
static struct pwm_it51xxx_data pwm_it51xxx_data_##inst; \
\
DEVICE_DT_INST_DEFINE(inst, &pwm_it51xxx_init, NULL, &pwm_it51xxx_data_##inst, \
&pwm_it51xxx_cfg_##inst, PRE_KERNEL_1, CONFIG_PWM_INIT_PRIORITY, \
&pwm_it51xxx_api);

DT_INST_FOREACH_STATUS_OKAY(PWM_IT51XXX_INIT)
Loading