Browse Source

drivers/pwm/it51xxx: implement pwm driver

Implement pwm driver for ITE it51xxx series chip.

Signed-off-by: Ruibin Chang <Ruibin.Chang@ite.com.tw>
pull/89586/head
Ruibin Chang 3 months ago committed by Fabio Baltieri
parent
commit
265a0b991a
  1. 21
      boards/ite/it515xx_evb/it515xx_evb.dts
  2. 1
      drivers/pwm/CMakeLists.txt
  3. 2
      drivers/pwm/Kconfig
  4. 12
      drivers/pwm/Kconfig.it51xxx
  5. 313
      drivers/pwm/pwm_ite_it51xxx.c
  6. 32
      dts/bindings/pwm/ite,it51xxx-pwm.yaml
  7. 66
      dts/riscv/ite/it51xxx.dtsi
  8. 34
      include/zephyr/dt-bindings/pwm/it51xxx_pwm.h

21
boards/ite/it515xx_evb/it515xx_evb.dts

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
i2c-0 = &i2c0;
led0 = &led0;
watchdog0 = &twd0;
pwm-0 = &pwm0;
};
chosen {
@ -120,6 +121,26 @@ @@ -120,6 +121,26 @@
pinctrl-names = "default";
};
/*
* test pwm:
* If we need pwm output in ITE chip power saving mode,
* then we should set pwm output frequency <=324Hz.
*/
&pwm0 {
status = "okay";
prescaler-cx = <PWM_PRESCALER_C6>;
pinctrl-0 = <&pwm0_gpa0_default>;
pinctrl-names = "default";
};
/* test fan */
&pwm7 {
status = "okay";
prescaler-cx = <PWM_PRESCALER_C4>;
pinctrl-0 = <&pwm7_gpa7_default>;
pinctrl-names = "default";
};
/* test fan tachometer sensor */
&tach0 {
status = "okay";

1
drivers/pwm/CMakeLists.txt

@ -12,6 +12,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_NRF_SW pwm_nrf_sw.c) @@ -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)

2
drivers/pwm/Kconfig

@ -48,6 +48,8 @@ source "drivers/pwm/Kconfig.mcux_ftm" @@ -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"

12
drivers/pwm/Kconfig.it51xxx

@ -0,0 +1,12 @@ @@ -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
drivers/pwm/pwm_ite_it51xxx.c

@ -0,0 +1,313 @@ @@ -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)

32
dts/bindings/pwm/ite,it51xxx-pwm.yaml

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
# Copyright (c) 2025 ITE Corporation. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
description: ITE, it51xxx Pulse Width Modulator (PWM) node
compatible: "ite,it51xxx-pwm"
include: [pwm-controller.yaml, base.yaml, pinctrl-device.yaml]
properties:
reg:
required: true
prescaler-cx:
type: int
required: true
enum:
- 1
- 2
- 3
description: 1 = PWM_PRESCALER_C4, 2 = PWM_PRESCALER_C6, 3 = PWM_PRESCALER_C7
pinctrl-0:
required: true
pinctrl-names:
required: true
pwm-cells:
- channel
- period
- flags

66
dts/riscv/ite/it51xxx.dtsi

@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
#include <zephyr/dt-bindings/interrupt-controller/ite-it51xxx-intc.h>
#include <zephyr/dt-bindings/interrupt-controller/ite-it51xxx-wuc.h>
#include <zephyr/dt-bindings/pinctrl/it8xxx2-pinctrl.h>
#include <zephyr/dt-bindings/pwm/it51xxx_pwm.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <zephyr/dt-bindings/sensor/it51xxx_tach.h>
/ {
@ -1186,6 +1188,70 @@ @@ -1186,6 +1188,70 @@
interrupt-parent = <&intc>;
};
pwm0: pwm@f04600 {
compatible = "ite,it51xxx-pwm";
reg = <0x00f04600 0xf
0x00f04680 0x80>;
status = "disabled";
#pwm-cells = <3>;
};
pwm1: pwm@f04610 {
compatible = "ite,it51xxx-pwm";
reg = <0x00f04610 0xf
0x00f04680 0x80>;
status = "disabled";
#pwm-cells = <3>;
};
pwm2: pwm@f04620 {
compatible = "ite,it51xxx-pwm";
reg = <0x00f04620 0xf
0x00f04680 0x80>;
status = "disabled";
#pwm-cells = <3>;
};
pwm3: pwm@f04630 {
compatible = "ite,it51xxx-pwm";
reg = <0x00f04630 0xf
0x00f04680 0x80>;
status = "disabled";
#pwm-cells = <3>;
};
pwm4: pwm@f04640 {
compatible = "ite,it51xxx-pwm";
reg = <0x00f04640 0xf
0x00f04680 0x80>;
status = "disabled";
#pwm-cells = <3>;
};
pwm5: pwm@f04650 {
compatible = "ite,it51xxx-pwm";
reg = <0x00f04650 0xf
0x00f04680 0x80>;
status = "disabled";
#pwm-cells = <3>;
};
pwm6: pwm@f04660 {
compatible = "ite,it51xxx-pwm";
reg = <0x00f04660 0xf
0x00f04680 0x80>;
status = "disabled";
#pwm-cells = <3>;
};
pwm7: pwm@f04670 {
compatible = "ite,it51xxx-pwm";
reg = <0x00f04670 0xf
0x00f04680 0x80>;
status = "disabled";
#pwm-cells = <3>;
};
tach0: tach@f046c0 {
compatible = "ite,it51xxx-tach";
reg = <0x00f046c0 0xf>;

34
include/zephyr/dt-bindings/pwm/it51xxx_pwm.h

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright (c) 2025 ITE Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_PWM_IT51XXX_H_
#define ZEPHYR_INCLUDE_DT_BINDINGS_PWM_IT51XXX_H_
#include <zephyr/dt-bindings/dt-util.h>
/* PWM prescaler references */
#define PWM_PRESCALER_C4 1
#define PWM_PRESCALER_C6 2
#define PWM_PRESCALER_C7 3
/* PWM channel references */
#define PWM_CHANNEL_0 0
#define PWM_CHANNEL_1 1
#define PWM_CHANNEL_2 2
#define PWM_CHANNEL_3 3
#define PWM_CHANNEL_4 4
#define PWM_CHANNEL_5 5
#define PWM_CHANNEL_6 6
#define PWM_CHANNEL_7 7
/*
* Provides a type to hold PWM configuration flags.
*
* The upper 8 bits are reserved for SoC specific flags.
* Output open-drain flag [ 8 ]
*/
#define PWM_IT51XXX_OPEN_DRAIN BIT(8)
#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_PWM_IT51XXX_H_ */
Loading…
Cancel
Save