drivers/pwm: Driver for SiFive PWM peripheral
The PWM driver can only control channels 1-3 of the PWM peripheral, not channel 0. This is an artifact of the peripheral's design. Signed-off-by: Nathaniel Graff <nathaniel.graff@sifive.com>
This commit is contained in:
parent
a49f9ebb2e
commit
1dc3cc7fc6
5 changed files with 292 additions and 0 deletions
|
@ -4,6 +4,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_PCA9685 pwm_pca9685.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_PWM_DW pwm_dw.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_PWM_QMSI pwm_qmsi.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_PWM_STM32 pwm_stm32.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_PWM_SIFIVE pwm_sifive.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_PWM_NRF5_SW pwm_nrf5_sw.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_PWM_NRFX pwm_nrfx.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_FTM pwm_mcux_ftm.c)
|
||||
|
|
|
@ -40,6 +40,8 @@ source "drivers/pwm/Kconfig.dw"
|
|||
|
||||
source "drivers/pwm/Kconfig.stm32"
|
||||
|
||||
source "drivers/pwm/Kconfig.sifive"
|
||||
|
||||
source "drivers/pwm/Kconfig.nrf5_sw"
|
||||
|
||||
source "drivers/pwm/Kconfig.nrfx"
|
||||
|
|
25
drivers/pwm/Kconfig.sifive
Normal file
25
drivers/pwm/Kconfig.sifive
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Kconfig.sifive - SiFive Freedom PWM configuration options
|
||||
#
|
||||
#
|
||||
# Copyright (c) 2018 SiFive Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
menuconfig PWM_SIFIVE
|
||||
bool "SiFive Freedom PWM driver"
|
||||
depends on PWM
|
||||
depends on SOC_SERIES_RISCV32_SIFIVE_FREEDOM
|
||||
depends on HAS_DTS
|
||||
help
|
||||
Enable the PWM driver for the SiFive Freedom platform
|
||||
|
||||
if PWM_SIFIVE
|
||||
|
||||
config PWM_SIFIVE_INIT_PRIORITY
|
||||
int "Init Priority"
|
||||
default KERNEL_INIT_PRIORITY_DEVICE
|
||||
help
|
||||
SiFive PWM Driver Initialization Priority
|
||||
|
||||
endif # PWM_SIFIVE
|
252
drivers/pwm/pwm_sifive.c
Normal file
252
drivers/pwm/pwm_sifive.c
Normal file
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* Copyright (c) 2018 SiFive Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define LOG_LEVEL CONFIG_LOG_PWM_LEVEL
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_REGISTER(pwm_sifive);
|
||||
|
||||
#include <sys_io.h>
|
||||
#include <device.h>
|
||||
#include <pwm.h>
|
||||
|
||||
/* Macros */
|
||||
|
||||
#define PWM_REG(_config, _offset) ((mem_addr_t) ((_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 {
|
||||
u32_t base;
|
||||
u32_t f_sys;
|
||||
u32_t cmpwidth;
|
||||
};
|
||||
|
||||
/* Helper Functions */
|
||||
|
||||
static inline void sys_set_mask(mem_addr_t addr, u32_t mask, u32_t value)
|
||||
{
|
||||
u32_t temp = sys_read32(addr);
|
||||
|
||||
temp &= ~(mask);
|
||||
temp |= value;
|
||||
|
||||
sys_write32(temp, addr);
|
||||
}
|
||||
|
||||
/* API Functions */
|
||||
|
||||
static int pwm_sifive_init(struct device *dev)
|
||||
{
|
||||
const struct pwm_sifive_cfg *config = dev->config->config_info;
|
||||
|
||||
/* 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_pin_set(struct device *dev,
|
||||
u32_t pwm,
|
||||
u32_t period_cycles,
|
||||
u32_t pulse_cycles)
|
||||
{
|
||||
const struct pwm_sifive_cfg *config = NULL;
|
||||
u32_t count_max = 0;
|
||||
u32_t max_cmp_val = 0;
|
||||
u32_t pwmscale = 0;
|
||||
|
||||
if (dev == NULL) {
|
||||
LOG_ERR("The device instance pointer was NULL\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
if (dev->config == NULL) {
|
||||
LOG_ERR("The device config pointer was NULL\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
config = dev->config->config_info;
|
||||
if (config == NULL) {
|
||||
LOG_ERR("The device configuration is NULL\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (pwm >= SF_NUMCHANNELS) {
|
||||
LOG_ERR("The requested PWM channel %d is invalid\n", pwm);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Channel 0 sets the period, we can't output PWM with it */
|
||||
if ((pwm == 0)) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (pulse_cycles > period_cycles) {
|
||||
LOG_ERR("Requested pulse %d is longer than period %d\n",
|
||||
pulse_cycles, period_cycles);
|
||||
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(pwm)));
|
||||
|
||||
LOG_DBG("channel: %d, pwmscale: %d, pwmcmp0: %d, pwmcmp%d: %d",
|
||||
pwm,
|
||||
pwmscale,
|
||||
(period_cycles >> pwmscale),
|
||||
pwm,
|
||||
(pulse_cycles >> pwmscale));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_sifive_get_cycles_per_sec(struct device *dev,
|
||||
u32_t pwm,
|
||||
u64_t *cycles)
|
||||
{
|
||||
const struct pwm_sifive_cfg *config;
|
||||
|
||||
if (dev == NULL) {
|
||||
LOG_ERR("The device instance pointer was NULL\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
if (dev->config == NULL) {
|
||||
LOG_ERR("The device config pointer was NULL\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
config = dev->config->config_info;
|
||||
if (config == NULL) {
|
||||
LOG_ERR("The device configuration is NULL\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* Fail if we don't have that channel */
|
||||
if (pwm >= SF_NUMCHANNELS) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*cycles = config->f_sys;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Device Instantiation */
|
||||
|
||||
static const struct pwm_driver_api pwm_sifive_api = {
|
||||
.pin_set = pwm_sifive_pin_set,
|
||||
.get_cycles_per_sec = pwm_sifive_get_cycles_per_sec,
|
||||
};
|
||||
|
||||
#define PWM_SIFIVE_INIT(n) \
|
||||
static struct pwm_sifive_data pwm_sifive_data_##n; \
|
||||
static const struct pwm_sifive_cfg pwm_sifive_cfg_##n = { \
|
||||
.base = DT_SIFIVE_PWM0_##n##_BASE_ADDRESS, \
|
||||
.f_sys = DT_SIFIVE_PWM0_##n##_CLOCK_FREQUENCY, \
|
||||
.cmpwidth = DT_SIFIVE_PWM0_##n##_SIFIVE_COMPARE_WIDTH, \
|
||||
}; \
|
||||
DEVICE_AND_API_INIT(pwm_##n, \
|
||||
DT_SIFIVE_PWM0_##n##_LABEL, \
|
||||
pwm_sifive_init, \
|
||||
&pwm_sifive_data_##n, \
|
||||
&pwm_sifive_cfg_##n, \
|
||||
POST_KERNEL, \
|
||||
CONFIG_PWM_SIFIVE_INIT_PRIORITY, \
|
||||
&pwm_sifive_api)
|
||||
|
||||
#ifdef DT_SIFIVE_PWM0_0_LABEL
|
||||
PWM_SIFIVE_INIT(0);
|
||||
#endif /* DT_SIFIVE_PWM0_0_LABEL */
|
||||
|
||||
#ifdef DT_SIFIVE_PWM0_1_LABEL
|
||||
PWM_SIFIVE_INIT(1);
|
||||
#endif /* DT_SIFIVE_PWM0_1_LABEL */
|
||||
|
||||
#ifdef DT_SIFIVE_PWM0_2_LABEL
|
||||
PWM_SIFIVE_INIT(2);
|
||||
#endif /* DT_SIFIVE_PWM0_2_LABEL */
|
||||
|
|
@ -21,6 +21,12 @@ properties:
|
|||
constraint: "sifive,pwm0"
|
||||
generation: define
|
||||
|
||||
clock-frequency:
|
||||
type: int
|
||||
category: optional
|
||||
description: Clock frequency information for PWM operation
|
||||
generation: define
|
||||
|
||||
reg:
|
||||
type: array
|
||||
description: mmio register space
|
||||
|
@ -33,6 +39,12 @@ properties:
|
|||
description: required interrupts
|
||||
generation: define
|
||||
|
||||
sifive,compare-width:
|
||||
type: int
|
||||
category: required
|
||||
description: Width of the PWM comparator in bits
|
||||
generation: define
|
||||
|
||||
"#cells":
|
||||
- channel
|
||||
# period in terms of nanoseconds
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue