blob: 28886a0b052a00825f32469c1fc0e7c36c59be86 [file] [log] [blame]
/*
* Copyright (c) 2018, Diego Sueiro <diego.sueiro@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/kernel.h>
#include <soc.h>
#include <device_imx.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(pwm_imx, CONFIG_PWM_LOG_LEVEL);
#define PWM_PWMSR_FIFOAV_4WORDS 0x4
#define PWM_PWMCR_SWR(x) (((uint32_t)(((uint32_t)(x)) \
<<PWM_PWMCR_SWR_SHIFT))&PWM_PWMCR_SWR_MASK)
struct imx_pwm_config {
PWM_Type *base;
uint16_t prescaler;
const struct pinctrl_dev_config *pincfg;
};
struct imx_pwm_data {
uint32_t period_cycles;
};
static bool imx_pwm_is_enabled(PWM_Type *base)
{
return PWM_PWMCR_REG(base) & PWM_PWMCR_EN_MASK;
}
static int imx_pwm_get_cycles_per_sec(const struct device *dev, uint32_t pwm,
uint64_t *cycles)
{
const struct imx_pwm_config *config = dev->config;
*cycles = get_pwm_clock_freq(config->base) >> config->prescaler;
return 0;
}
static int imx_pwm_set_cycles(const struct device *dev, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles,
pwm_flags_t flags)
{
const struct imx_pwm_config *config = dev->config;
struct imx_pwm_data *data = dev->data;
unsigned int period_ms;
bool enabled = imx_pwm_is_enabled(config->base);
int wait_count = 0, fifoav;
uint32_t cr, sr;
if (period_cycles == 0U) {
LOG_ERR("Channel can not be set to inactive level");
return -ENOTSUP;
}
if (flags) {
/* PWM polarity not supported (yet?) */
return -ENOTSUP;
}
LOG_DBG("enabled=%d, pulse_cycles=%d, period_cycles=%d,"
" duty_cycle=%d\n", enabled, pulse_cycles, period_cycles,
(pulse_cycles * 100U / period_cycles));
/*
* i.MX PWMv2 has a 4-word sample FIFO.
* In order to avoid FIFO overflow issue, we do software reset
* to clear all sample FIFO if the controller is disabled or
* wait for a full PWM cycle to get a relinquished FIFO slot
* when the controller is enabled and the FIFO is fully loaded.
*/
if (enabled) {
sr = PWM_PWMSR_REG(config->base);
fifoav = PWM_PWMSR_FIFOAV(sr);
if (fifoav == PWM_PWMSR_FIFOAV_4WORDS) {
period_ms = (get_pwm_clock_freq(config->base) >>
config->prescaler) * MSEC_PER_SEC;
k_sleep(K_MSEC(period_ms));
sr = PWM_PWMSR_REG(config->base);
if (fifoav == PWM_PWMSR_FIFOAV(sr)) {
LOG_WRN("there is no free FIFO slot\n");
}
}
} else {
PWM_PWMCR_REG(config->base) = PWM_PWMCR_SWR(1);
do {
k_sleep(K_MSEC(1));
cr = PWM_PWMCR_REG(config->base);
} while ((PWM_PWMCR_SWR(cr)) &&
(++wait_count < CONFIG_PWM_PWMSWR_LOOP));
if (PWM_PWMCR_SWR(cr)) {
LOG_WRN("software reset timeout\n");
}
}
/*
* according to imx pwm RM, the real period value should be
* PERIOD value in PWMPR plus 2.
*/
if (period_cycles > 2) {
period_cycles -= 2U;
} else {
return -EINVAL;
}
PWM_PWMSAR_REG(config->base) = pulse_cycles;
if (data->period_cycles != period_cycles) {
LOG_WRN("Changing period cycles from %d to %d in %s",
data->period_cycles, period_cycles,
dev->name);
data->period_cycles = period_cycles;
PWM_PWMPR_REG(config->base) = period_cycles;
}
cr = PWM_PWMCR_EN_MASK | PWM_PWMCR_PRESCALER(config->prescaler) |
PWM_PWMCR_DOZEN_MASK | PWM_PWMCR_WAITEN_MASK |
PWM_PWMCR_DBGEN_MASK | PWM_PWMCR_CLKSRC(2);
PWM_PWMCR_REG(config->base) = cr;
return 0;
}
static int imx_pwm_init(const struct device *dev)
{
const struct imx_pwm_config *config = dev->config;
struct imx_pwm_data *data = dev->data;
int err;
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
if (err) {
return err;
}
PWM_PWMPR_REG(config->base) = data->period_cycles;
return 0;
}
static const struct pwm_driver_api imx_pwm_driver_api = {
.set_cycles = imx_pwm_set_cycles,
.get_cycles_per_sec = imx_pwm_get_cycles_per_sec,
};
#define PWM_IMX_INIT(n) \
PINCTRL_DT_INST_DEFINE(n); \
static const struct imx_pwm_config imx_pwm_config_##n = { \
.base = (PWM_Type *)DT_INST_REG_ADDR(n), \
.prescaler = DT_INST_PROP(n, prescaler), \
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
}; \
\
static struct imx_pwm_data imx_pwm_data_##n; \
\
DEVICE_DT_INST_DEFINE(n, &imx_pwm_init, NULL, \
&imx_pwm_data_##n, \
&imx_pwm_config_##n, POST_KERNEL, \
CONFIG_PWM_INIT_PRIORITY, \
&imx_pwm_driver_api);
#if DT_HAS_COMPAT_STATUS_OKAY(fsl_imx27_pwm)
#define DT_DRV_COMPAT fsl_imx27_pwm
DT_INST_FOREACH_STATUS_OKAY(PWM_IMX_INIT)
#endif