| /* |
| * Copyright (c) 2018 SiFive Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT sifive_pwm0 |
| |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_REGISTER(pwm_sifive, CONFIG_PWM_LOG_LEVEL); |
| |
| #include <zephyr/sys/sys_io.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/pwm.h> |
| #include <soc.h> |
| |
| /* 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 const struct pwm_driver_api 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_SIFIVE_INIT_PRIORITY, \ |
| &pwm_sifive_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(PWM_SIFIVE_INIT) |