You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
5.7 KiB
234 lines
5.7 KiB
/* |
|
* Copyright (c) 2018 SiFive Inc. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT sifive_pwm0 |
|
|
|
#include <zephyr/arch/cpu.h> |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/sys/sys_io.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/drivers/pinctrl.h> |
|
#include <zephyr/drivers/pwm.h> |
|
#include <soc.h> |
|
|
|
LOG_MODULE_REGISTER(pwm_sifive, CONFIG_PWM_LOG_LEVEL); |
|
|
|
/* Macros */ |
|
|
|
#define PWM_REG(z_config, _offset) ((mem_addr_t) ((z_config)->base + _offset)) |
|
|
|
/* Register Offsets */ |
|
#define REG_PWMCFG 0x00 |
|
#define REG_PWMCOUNT 0x08 |
|
#define REG_PWMS 0x10 |
|
#define REG_PWMCMP0 0x20 |
|
#define REG_PWMCMP(_channel) (REG_PWMCMP0 + ((_channel) * 0x4)) |
|
|
|
/* Number of PWM Channels */ |
|
#define SF_NUMCHANNELS 4 |
|
|
|
/* pwmcfg Bit Offsets */ |
|
#define SF_PWMSTICKY 8 |
|
#define SF_PWMZEROCMP 9 |
|
#define SF_PWMDEGLITCH 10 |
|
#define SF_PWMENALWAYS 12 |
|
#define SF_PWMENONESHOT 13 |
|
#define SF_PWMCMPCENTER(_channel) (16 + (_channel)) |
|
#define SF_PWMCMPGANG(_channel) (24 + (_channel)) |
|
#define SF_PWMCMPIP(_channel) (28 + (_channel)) |
|
|
|
/* pwmcount scale factor */ |
|
#define SF_PWMSCALEMASK 0xF |
|
#define SF_PWMSCALE(_val) (SF_PWMSCALEMASK & (_val)) |
|
|
|
#define SF_PWMCOUNT_MIN_WIDTH 15 |
|
|
|
/* Structure Declarations */ |
|
|
|
struct pwm_sifive_data {}; |
|
|
|
struct pwm_sifive_cfg { |
|
uint32_t base; |
|
uint32_t f_sys; |
|
uint32_t cmpwidth; |
|
const struct pinctrl_dev_config *pcfg; |
|
}; |
|
|
|
/* Helper Functions */ |
|
|
|
static inline void sys_set_mask(mem_addr_t addr, uint32_t mask, uint32_t value) |
|
{ |
|
uint32_t temp = sys_read32(addr); |
|
|
|
temp &= ~(mask); |
|
temp |= value; |
|
|
|
sys_write32(temp, addr); |
|
} |
|
|
|
/* API Functions */ |
|
|
|
static int pwm_sifive_init(const struct device *dev) |
|
{ |
|
const struct pwm_sifive_cfg *config = dev->config; |
|
#ifdef CONFIG_PINCTRL |
|
int ret; |
|
|
|
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
#endif |
|
|
|
/* When pwms == pwmcmp0, reset the counter */ |
|
sys_set_bit(PWM_REG(config, REG_PWMCFG), SF_PWMZEROCMP); |
|
|
|
/* Enable continuous operation */ |
|
sys_set_bit(PWM_REG(config, REG_PWMCFG), SF_PWMENALWAYS); |
|
|
|
/* Clear IP config bits */ |
|
sys_clear_bit(PWM_REG(config, REG_PWMCFG), SF_PWMSTICKY); |
|
sys_clear_bit(PWM_REG(config, REG_PWMCFG), SF_PWMDEGLITCH); |
|
|
|
/* Clear all channels */ |
|
for (int i = 0; i < SF_NUMCHANNELS; i++) { |
|
/* Clear the channel comparator */ |
|
sys_write32(0, PWM_REG(config, REG_PWMCMP(i))); |
|
|
|
/* Clear the compare center and compare gang bits */ |
|
sys_clear_bit(PWM_REG(config, REG_PWMCFG), SF_PWMCMPCENTER(i)); |
|
sys_clear_bit(PWM_REG(config, REG_PWMCFG), SF_PWMCMPGANG(i)); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int pwm_sifive_set_cycles(const struct device *dev, uint32_t channel, |
|
uint32_t period_cycles, uint32_t pulse_cycles, |
|
pwm_flags_t flags) |
|
{ |
|
const struct pwm_sifive_cfg *config = dev->config; |
|
uint32_t count_max = 0U; |
|
uint32_t max_cmp_val = 0U; |
|
uint32_t pwmscale = 0U; |
|
|
|
if (flags) { |
|
/* PWM polarity not supported (yet?) */ |
|
return -ENOTSUP; |
|
} |
|
|
|
if (channel >= SF_NUMCHANNELS) { |
|
LOG_ERR("The requested PWM channel %d is invalid\n", channel); |
|
return -EINVAL; |
|
} |
|
|
|
/* Channel 0 sets the period, we can't output PWM with it */ |
|
if (channel == 0U) { |
|
LOG_ERR("PWM channel 0 cannot be configured\n"); |
|
return -ENOTSUP; |
|
} |
|
|
|
/* We can't support periods greater than we can store in pwmcount */ |
|
count_max = (1 << (config->cmpwidth + SF_PWMCOUNT_MIN_WIDTH)) - 1; |
|
|
|
if (period_cycles > count_max) { |
|
LOG_ERR("Requested period is %d but maximum is %d\n", |
|
period_cycles, count_max); |
|
return -EIO; |
|
} |
|
|
|
/* Calculate the maximum value that pwmcmpX can be set to */ |
|
max_cmp_val = ((1 << config->cmpwidth) - 1); |
|
|
|
/* |
|
* Find the minimum value of pwmscale that will allow us to set the |
|
* requested period |
|
*/ |
|
while ((period_cycles >> pwmscale) > max_cmp_val) { |
|
pwmscale++; |
|
} |
|
|
|
/* Make sure that we can scale that much */ |
|
if (pwmscale > SF_PWMSCALEMASK) { |
|
LOG_ERR("Requested period is %d but maximum is %d\n", |
|
period_cycles, max_cmp_val << pwmscale); |
|
return -EIO; |
|
} |
|
|
|
/* Set the pwmscale field */ |
|
sys_set_mask(PWM_REG(config, REG_PWMCFG), |
|
SF_PWMSCALEMASK, |
|
SF_PWMSCALE(pwmscale)); |
|
|
|
/* Set the period by setting pwmcmp0 */ |
|
sys_write32((period_cycles >> pwmscale), PWM_REG(config, REG_PWMCMP0)); |
|
|
|
/* Set the duty cycle by setting pwmcmpX */ |
|
sys_write32((pulse_cycles >> pwmscale), |
|
PWM_REG(config, REG_PWMCMP(channel))); |
|
|
|
LOG_DBG("channel: %d, pwmscale: %d, pwmcmp0: %d, pwmcmp%d: %d", |
|
channel, |
|
pwmscale, |
|
(period_cycles >> pwmscale), |
|
channel, |
|
(pulse_cycles >> pwmscale)); |
|
|
|
return 0; |
|
} |
|
|
|
static int pwm_sifive_get_cycles_per_sec(const struct device *dev, |
|
uint32_t channel, uint64_t *cycles) |
|
{ |
|
const struct pwm_sifive_cfg *config; |
|
|
|
if (dev == NULL) { |
|
LOG_ERR("The device instance pointer was NULL\n"); |
|
return -EFAULT; |
|
} |
|
|
|
config = dev->config; |
|
if (config == NULL) { |
|
LOG_ERR("The device configuration is NULL\n"); |
|
return -EFAULT; |
|
} |
|
|
|
/* Fail if we don't have that channel */ |
|
if (channel >= SF_NUMCHANNELS) { |
|
return -EINVAL; |
|
} |
|
|
|
*cycles = config->f_sys; |
|
|
|
return 0; |
|
} |
|
|
|
/* Device Instantiation */ |
|
|
|
static DEVICE_API(pwm, pwm_sifive_api) = { |
|
.set_cycles = pwm_sifive_set_cycles, |
|
.get_cycles_per_sec = pwm_sifive_get_cycles_per_sec, |
|
}; |
|
|
|
#define PWM_SIFIVE_INIT(n) \ |
|
PINCTRL_DT_INST_DEFINE(n); \ |
|
static struct pwm_sifive_data pwm_sifive_data_##n; \ |
|
static const struct pwm_sifive_cfg pwm_sifive_cfg_##n = { \ |
|
.base = DT_INST_REG_ADDR(n), \ |
|
.f_sys = SIFIVE_PERIPHERAL_CLOCK_FREQUENCY, \ |
|
.cmpwidth = DT_INST_PROP(n, sifive_compare_width), \ |
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
|
}; \ |
|
DEVICE_DT_INST_DEFINE(n, \ |
|
pwm_sifive_init, \ |
|
NULL, \ |
|
&pwm_sifive_data_##n, \ |
|
&pwm_sifive_cfg_##n, \ |
|
POST_KERNEL, \ |
|
CONFIG_PWM_INIT_PRIORITY, \ |
|
&pwm_sifive_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(PWM_SIFIVE_INIT)
|
|
|