|  | /* | 
|  | * Copyright (c) 2021 ITE Corporation. All Rights Reserved. | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT ite_it8xxx2_pwm | 
|  |  | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/drivers/pwm.h> | 
|  | #include <zephyr/drivers/pinctrl.h> | 
|  | #include <zephyr/dt-bindings/pwm/it8xxx2_pwm.h> | 
|  | #include <errno.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <soc.h> | 
|  | #include <soc_dt.h> | 
|  | #include <stdlib.h> | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(pwm_ite_it8xxx2, CONFIG_PWM_LOG_LEVEL); | 
|  |  | 
|  | #define PWM_CTRX_MIN	100 | 
|  | #define PWM_FREQ	EC_FREQ | 
|  | #define PCSSG_MASK	0x3 | 
|  |  | 
|  | struct pwm_it8xxx2_cfg { | 
|  | /* PWM channel duty cycle register */ | 
|  | uintptr_t reg_dcr; | 
|  | /* PWM channel clock source selection register */ | 
|  | uintptr_t reg_pcssg; | 
|  | /* PWM channel clock source gating register */ | 
|  | uintptr_t reg_pcsgr; | 
|  | /* PWM channel output polarity register */ | 
|  | uintptr_t reg_pwmpol; | 
|  | /* PWM channel */ | 
|  | int channel; | 
|  | /* PWM prescaler control register base */ | 
|  | struct pwm_it8xxx2_regs *base; | 
|  | /* Select PWM prescaler that output to PWM channel */ | 
|  | int prs_sel; | 
|  | /* PWM alternate configuration */ | 
|  | const struct pinctrl_dev_config *pcfg; | 
|  | }; | 
|  |  | 
|  | struct pwm_it8xxx2_data { | 
|  | uint32_t ctr; | 
|  | uint32_t cxcprs; | 
|  | uint32_t target_freq_prev; | 
|  | }; | 
|  |  | 
|  | static void pwm_enable(const struct device *dev, int enabled) | 
|  | { | 
|  | const struct pwm_it8xxx2_cfg *config = dev->config; | 
|  | volatile uint8_t *reg_pcsgr = (uint8_t *)config->reg_pcsgr; | 
|  | int ch = config->channel; | 
|  |  | 
|  | if (enabled) { | 
|  | /* PWM channel clock source not gating */ | 
|  | *reg_pcsgr &= ~BIT(ch); | 
|  | } else { | 
|  | /* PWM channel clock source gating */ | 
|  | *reg_pcsgr |= BIT(ch); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int pwm_it8xxx2_get_cycles_per_sec(const struct device *dev, | 
|  | uint32_t channel, uint64_t *cycles) | 
|  | { | 
|  | ARG_UNUSED(channel); | 
|  |  | 
|  | /* | 
|  | * There are three ways to call pwm_it8xxx2_set_cycles() from pwm api: | 
|  | * 1) pwm_set_cycles_usec() -> pwm_set_cycles_cycles() -> pwm_it8xxx2_set_cycles() | 
|  | *    target_freq = pwm_clk_src / period_cycles | 
|  | *                = cycles / (period * cycles / USEC_PER_SEC) | 
|  | *                = USEC_PER_SEC / period | 
|  | * 2) pwm_set_cycles_nsec() -> pwm_set_cycles_cycles() -> pwm_it8xxx2_set_cycles() | 
|  | *    target_freq = pwm_clk_src / period_cycles | 
|  | *                = cycles / (period * cycles / NSEC_PER_SEC) | 
|  | *                = NSEC_PER_SEC / period | 
|  | * 3) pwm_set_cycles_cycles() -> pwm_it8xxx2_set_cycles() | 
|  | *    target_freq = pwm_clk_src / period_cycles | 
|  | *                = cycles / period | 
|  | * | 
|  | * If we need to pwm output in EC power saving mode, then we will switch | 
|  | * the prescaler clock source (cycles) from 8MHz to 32.768kHz. In order | 
|  | * to get the same target_freq in the 3) case, we always return PWM_FREQ. | 
|  | */ | 
|  | *cycles = (uint64_t) PWM_FREQ; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int pwm_it8xxx2_set_cycles(const struct device *dev, | 
|  | uint32_t channel, uint32_t period_cycles, | 
|  | uint32_t pulse_cycles, pwm_flags_t flags) | 
|  | { | 
|  | const struct pwm_it8xxx2_cfg *config = dev->config; | 
|  | struct pwm_it8xxx2_regs *const inst = config->base; | 
|  | struct pwm_it8xxx2_data *data = dev->data; | 
|  | volatile uint8_t *reg_dcr = (uint8_t *)config->reg_dcr; | 
|  | volatile uint8_t *reg_pwmpol = (uint8_t *)config->reg_pwmpol; | 
|  | int ch = config->channel; | 
|  | int prs_sel = config->prs_sel; | 
|  | uint32_t actual_freq = 0xffffffff, target_freq, deviation; | 
|  | uint64_t pwm_clk_src; | 
|  |  | 
|  | /* Select PWM inverted polarity (ex. active-low pulse) */ | 
|  | if (flags & PWM_POLARITY_INVERTED) { | 
|  | *reg_pwmpol |= BIT(ch); | 
|  | } else { | 
|  | *reg_pwmpol &= ~BIT(ch); | 
|  | } | 
|  |  | 
|  | /* Enable PWM output open-drain */ | 
|  | if (flags & PWM_IT8XXX2_OPEN_DRAIN) { | 
|  | inst->PWMODENR |= BIT(ch); | 
|  | } | 
|  |  | 
|  | /* If pulse cycles is 0, set duty cycle 0 and enable pwm channel */ | 
|  | if (pulse_cycles == 0) { | 
|  | *reg_dcr = 0; | 
|  | pwm_enable(dev, 1); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | pwm_it8xxx2_get_cycles_per_sec(dev, channel, &pwm_clk_src); | 
|  | target_freq = ((uint32_t) pwm_clk_src) / period_cycles; | 
|  |  | 
|  | /* | 
|  | * Support PWM output frequency: | 
|  | * 1) 8MHz clock source: 1Hz <= target_freq <= 79207Hz | 
|  | * 2) 32.768KHz clock source: 1Hz <= target_freq <= 324Hz | 
|  | * NOTE: PWM output signal maximum supported frequency comes from | 
|  | *       [8MHz or 32.768KHz] / 1 / (PWM_CTRX_MIN + 1). | 
|  | *       PWM output signal minimum supported frequency comes from | 
|  | *       [8MHz or 32.768KHz] / 65536 / 256, the minimum integer is 1. | 
|  | */ | 
|  | if (target_freq < 1) { | 
|  | LOG_ERR("PWM output frequency is < 1 !"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | deviation = (target_freq / 100) + 1; | 
|  |  | 
|  | /* | 
|  | * Default clock source setting is 8MHz, when ITE chip is in power | 
|  | * saving mode, clock source 8MHz 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 8MHz to | 
|  | * 32.768KHz to support pwm output in mode. | 
|  | */ | 
|  | if (target_freq <= 324) { | 
|  | if (inst->PCFSR & BIT(prs_sel)) { | 
|  | inst->PCFSR &= ~BIT(prs_sel); | 
|  | } | 
|  |  | 
|  | pwm_clk_src = (uint64_t) 32768; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * PWM output signal frequency is | 
|  | * pwm_clk_src / ((CxCPRS[15:0] + 1) * (CTRx[7:0] + 1)) | 
|  | * NOTE: 1) define CTR minimum is 100 for more precisely when | 
|  | *          calculate DCR | 
|  | *       2) CxCPRS[15:0] value 0001h results in a divisor 2 | 
|  | *          CxCPRS[15:0] value FFFFh results in a divisor 65536 | 
|  | *          CTRx[7:0] value 00h results in a divisor 1 | 
|  | *          CTRx[7:0] value FFh results in a divisor 256 | 
|  | */ | 
|  | if (target_freq != data->target_freq_prev) { | 
|  | uint32_t ctr, cxcprs; | 
|  |  | 
|  | for (ctr = 0xFF; ctr >= PWM_CTRX_MIN; ctr--) { | 
|  | cxcprs = (((uint32_t) pwm_clk_src) / (ctr + 1) / target_freq); | 
|  | /* | 
|  | * Make sure cxcprs isn't zero, or we will have | 
|  | * divide-by-zero on calculating actual_freq. | 
|  | */ | 
|  | if (cxcprs != 0) { | 
|  | actual_freq = ((uint32_t) pwm_clk_src) / (ctr + 1) / cxcprs; | 
|  | if (abs(actual_freq - target_freq) < deviation) { | 
|  | /* CxCPRS[15:0] = cxcprs - 1 */ | 
|  | cxcprs--; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (cxcprs > UINT16_MAX) { | 
|  | LOG_ERR("PWM prescaler CxCPRS only support 2 bytes !"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Store ctr and cxcprs with successful frequency change */ | 
|  | data->ctr = ctr; | 
|  | data->cxcprs = cxcprs; | 
|  | } | 
|  |  | 
|  | /* Set PWM prescaler clock divide and cycle time register */ | 
|  | if (prs_sel == PWM_PRESCALER_C4) { | 
|  | inst->C4CPRS = data->cxcprs & 0xFF; | 
|  | inst->C4MCPRS = (data->cxcprs >> 8) & 0xFF; | 
|  | inst->CTR1 = data->ctr; | 
|  | } else if (prs_sel == PWM_PRESCALER_C6) { | 
|  | inst->C6CPRS = data->cxcprs & 0xFF; | 
|  | inst->C6MCPRS = (data->cxcprs >> 8) & 0xFF; | 
|  | inst->CTR2 = data->ctr; | 
|  | } else if (prs_sel == PWM_PRESCALER_C7) { | 
|  | inst->C7CPRS = data->cxcprs & 0xFF; | 
|  | inst->C7MCPRS = (data->cxcprs >> 8) & 0xFF; | 
|  | inst->CTR3 = data->ctr; | 
|  | } | 
|  |  | 
|  | /* Set PWM channel duty cycle register */ | 
|  | *reg_dcr = (data->ctr * pulse_cycles) / period_cycles; | 
|  |  | 
|  | /* 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_it8xxx2_init(const struct device *dev) | 
|  | { | 
|  | const struct pwm_it8xxx2_cfg *config = dev->config; | 
|  | struct pwm_it8xxx2_regs *const inst = config->base; | 
|  | volatile uint8_t *reg_pcssg = (uint8_t *)config->reg_pcssg; | 
|  | int ch = config->channel; | 
|  | int prs_sel = config->prs_sel; | 
|  | int pcssg_shift; | 
|  | int pcssg_mask; | 
|  | int status; | 
|  |  | 
|  | /* PWM channel clock source gating before configuring */ | 
|  | pwm_enable(dev, 0); | 
|  |  | 
|  | /* Select clock source 8MHz for prescaler */ | 
|  | inst->PCFSR |= BIT(prs_sel); | 
|  |  | 
|  | /* Bit shift and mask of prescaler clock source select group register */ | 
|  | pcssg_shift = (ch % 4) * 2; | 
|  | pcssg_mask = (prs_sel & PCSSG_MASK) << pcssg_shift; | 
|  |  | 
|  | /* Select which prescaler output to PWM channel */ | 
|  | *reg_pcssg &= ~(PCSSG_MASK << pcssg_shift); | 
|  | *reg_pcssg |= pcssg_mask; | 
|  |  | 
|  | /* | 
|  | * The cycle timer1 of it8320 later series was enhanced from | 
|  | * 8bits to 10bits resolution, and others are still 8bit resolution. | 
|  | * Because the cycle timer1 high byte default value is not zero, | 
|  | * we clear cycle timer1 high byte at init and use it as 8-bit | 
|  | * resolution like others. | 
|  | */ | 
|  | inst->CTR1M = 0; | 
|  |  | 
|  | /* Enable PWMs clock counter */ | 
|  | inst->ZTIER |= IT8XXX2_PWM_PCCE; | 
|  |  | 
|  | /* 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_it8xxx2_api) = { | 
|  | .set_cycles = pwm_it8xxx2_set_cycles, | 
|  | .get_cycles_per_sec = pwm_it8xxx2_get_cycles_per_sec, | 
|  | }; | 
|  |  | 
|  | /* Device Instance */ | 
|  | #define PWM_IT8XXX2_INIT(inst)								\ | 
|  | PINCTRL_DT_INST_DEFINE(inst);							\ | 
|  | \ | 
|  | static const struct pwm_it8xxx2_cfg pwm_it8xxx2_cfg_##inst = {			\ | 
|  | .reg_dcr = DT_INST_REG_ADDR_BY_IDX(inst, 0),				\ | 
|  | .reg_pcssg = DT_INST_REG_ADDR_BY_IDX(inst, 1),				\ | 
|  | .reg_pcsgr = DT_INST_REG_ADDR_BY_IDX(inst, 2),				\ | 
|  | .reg_pwmpol = DT_INST_REG_ADDR_BY_IDX(inst, 3),				\ | 
|  | .channel = DT_PROP(DT_INST(inst, ite_it8xxx2_pwm), channel),		\ | 
|  | .base = (struct pwm_it8xxx2_regs *) DT_REG_ADDR(DT_NODELABEL(prs)),	\ | 
|  | .prs_sel = DT_PROP(DT_INST(inst, ite_it8xxx2_pwm), prescaler_cx),	\ | 
|  | .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),				\ | 
|  | };										\ | 
|  | \ | 
|  | static struct pwm_it8xxx2_data pwm_it8xxx2_data_##inst;                         \ | 
|  | \ | 
|  | DEVICE_DT_INST_DEFINE(inst,							\ | 
|  | &pwm_it8xxx2_init,					\ | 
|  | NULL,							\ | 
|  | &pwm_it8xxx2_data_##inst,					\ | 
|  | &pwm_it8xxx2_cfg_##inst,					\ | 
|  | PRE_KERNEL_1,						\ | 
|  | CONFIG_PWM_INIT_PRIORITY,					\ | 
|  | &pwm_it8xxx2_api); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(PWM_IT8XXX2_INIT) |