Browse Source
Implement pwm driver for ITE it51xxx series chip. Signed-off-by: Ruibin Chang <Ruibin.Chang@ite.com.tw>pull/89586/head
8 changed files with 481 additions and 0 deletions
@ -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. |
@ -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) |
@ -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 |
@ -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…
Reference in new issue