drivers: pwm: nrf_sw: support generating PWM based on RTC

So far nRF's TIMER was used for generating PWM signal. Add support for
generating PWM based on RTC, which is sourced by 32KHz low frequency
crystal. This allows to use low frequency PWM with much lower power
consumption, because high frequency clock path can be disabled.

Don't support RTC clock prescaler, because maximum 512s period covers
most use cases. This allows to adjust pulse and period cycles to the
fact that CLEAR task event is generated always one LFCLK cycle after
period COMPARE value is reached.

Also update hal_nordic revision, as it contains updated check for PPI
channels conflict when RTC is used to generate PWM.

Signed-off-by: Marcin Niestroj <m.niestroj@grinn-global.com>
This commit is contained in:
Marcin Niestroj 2020-10-09 22:15:34 +02:00 committed by Carles Cufí
commit bc5a6164c9
4 changed files with 179 additions and 59 deletions

View file

@ -21,13 +21,27 @@ BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, timer_instance) !=
"Please define either the timer-instance or generator property, but not both");
#if DT_INST_NODE_HAS_PROP(0, timer_instance)
#define USE_RTC 0
#define GENERATOR_ADDR _CONCAT(NRF_TIMER, DT_INST_PROP(0, timer_instance))
#define GENERATOR_CC_NUM \
_CONCAT(_CONCAT(TIMER, DT_INST_PROP(0, timer_instance)), _CC_NUM)
#else /* DT_INST_NODE_HAS_PROP(0, timer_instance) */
#define GENERATOR_NODE DT_PHANDLE(DT_DRV_INST(0), generator)
#define GENERATOR_ADDR ((NRF_TIMER_Type *) DT_REG_ADDR(GENERATOR_NODE))
#define GENERATOR_CC_NUM DT_PROP(GENERATOR_NODE, cc_num)
#if DT_NODE_HAS_COMPAT(GENERATOR_NODE, nordic_nrf_rtc)
#define USE_RTC 1
#define GENERATOR_ADDR ((NRF_RTC_Type *) DT_REG_ADDR(GENERATOR_NODE))
BUILD_ASSERT(DT_INST_PROP(0, clock_prescaler) == 0,
"Only clock-prescaler = <0> is supported when used with RTC");
#else
#define USE_RTC 0
#define GENERATOR_ADDR ((NRF_TIMER_Type *) DT_REG_ADDR(GENERATOR_NODE))
#endif
#endif /* DT_INST_NODE_HAS_PROP(0, timer_instance) */
/* One compare channel is needed to set the PWM period, hence +1. */
@ -37,7 +51,10 @@ BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, timer_instance) !=
#define PWM_0_MAP_SIZE DT_INST_PROP(0, channel_count)
struct pwm_config {
NRF_TIMER_Type *timer;
union {
NRF_RTC_Type *rtc;
NRF_TIMER_Type *timer;
};
uint8_t gpiote_base;
uint8_t ppi_base;
uint8_t map_size;
@ -54,6 +71,24 @@ struct pwm_data {
struct chan_map map[PWM_0_MAP_SIZE];
};
static inline NRF_RTC_Type *pwm_config_rtc(const struct pwm_config *config)
{
#if USE_RTC
return config->rtc;
#else
return NULL;
#endif
}
static inline NRF_TIMER_Type *pwm_config_timer(const struct pwm_config *config)
{
#if !USE_RTC
return config->timer;
#else
return NULL;
#endif
}
static uint32_t pwm_period_check(struct pwm_data *data, uint8_t map_size,
uint32_t pwm, uint32_t period_cycles,
uint32_t pulse_cycles)
@ -104,17 +139,15 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
uint32_t period_cycles, uint32_t pulse_cycles,
pwm_flags_t flags)
{
const struct pwm_config *config;
NRF_TIMER_Type *timer;
struct pwm_data *data;
const struct pwm_config *config = dev->config;
NRF_TIMER_Type *timer = pwm_config_timer(config);
NRF_RTC_Type *rtc = pwm_config_rtc(config);
struct pwm_data *data = dev->data;
uint8_t ppi_index;
uint32_t ppi_mask;
uint8_t channel;
uint32_t ret;
config = (const struct pwm_config *)dev->config;
timer = config->timer;
data = dev->data;
if (flags) {
/* PWM polarity not supported (yet?) */
return -ENOTSUP;
@ -130,13 +163,21 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
return ret;
}
/* TODO: if the assigned NRF_TIMER supports higher bit resolution,
* use that info in config struct.
*/
if (period_cycles > BIT_MASK(16)) {
LOG_ERR("Too long period (%u), adjust pwm prescaler!",
period_cycles);
return -EINVAL;
if (USE_RTC) {
/* pulse_cycles - 1 is written to 24-bit CC */
if (period_cycles > BIT_MASK(24) + 1) {
LOG_ERR("Too long period (%u)!", period_cycles);
return -EINVAL;
}
} else {
/* TODO: if the assigned NRF_TIMER supports higher bit
* resolution, use that info in config struct.
*/
if (period_cycles > UINT16_MAX) {
LOG_ERR("Too long period (%u), adjust pwm prescaler!",
period_cycles);
return -EINVAL;
}
}
/* map pwm pin to GPIOTE config/channel */
@ -153,8 +194,15 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
NRF_GPIOTE->CONFIG[config->gpiote_base + channel] = 0;
/* clear PPI used */
ppi_index = config->ppi_base + (channel << 1);
NRF_PPI->CHENCLR = BIT(ppi_index) | BIT(ppi_index + 1);
if (USE_RTC) {
ppi_index = config->ppi_base + (channel * 3);
ppi_mask = BIT(ppi_index) | BIT(ppi_index + 1) |
BIT(ppi_index + 2);
} else {
ppi_index = config->ppi_base + (channel * 2);
ppi_mask = BIT(ppi_index) | BIT(ppi_index + 1);
}
NRF_PPI->CHENCLR = ppi_mask;
/* configure GPIO pin as output */
nrf_gpio_cfg_output(pwm);
@ -173,32 +221,64 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
nrf_gpio_pin_clear(pwm);
}
/* configure TIMER */
timer->EVENTS_COMPARE[channel] = 0;
timer->EVENTS_COMPARE[config->map_size] = 0;
/* configure RTC / TIMER */
if (USE_RTC) {
rtc->EVENTS_COMPARE[channel] = 0;
rtc->EVENTS_COMPARE[config->map_size] = 0;
timer->CC[channel] = pulse_cycles;
timer->CC[config->map_size] = period_cycles;
timer->TASKS_CLEAR = 1;
/*
* '- 1' adjusts pulse and period cycles to the fact that CLEAR
* task event is generated always one LFCLK cycle after period
* COMPARE value is reached.
*/
rtc->CC[channel] = pulse_cycles - 1;
rtc->CC[config->map_size] = period_cycles - 1;
rtc->TASKS_CLEAR = 1;
} else {
timer->EVENTS_COMPARE[channel] = 0;
timer->EVENTS_COMPARE[config->map_size] = 0;
timer->CC[channel] = pulse_cycles;
timer->CC[config->map_size] = period_cycles;
timer->TASKS_CLEAR = 1;
}
/* configure GPIOTE, toggle with initialise output high */
NRF_GPIOTE->CONFIG[config->gpiote_base + channel] = 0x00130003 |
(pwm << 8);
/* setup PPI */
NRF_PPI->CH[ppi_index].EEP = (uint32_t)
&(timer->EVENTS_COMPARE[channel]);
NRF_PPI->CH[ppi_index].TEP = (uint32_t)
&(NRF_GPIOTE->TASKS_OUT[channel]);
NRF_PPI->CH[ppi_index + 1].EEP = (uint32_t)
&(timer->EVENTS_COMPARE[
config->map_size]);
NRF_PPI->CH[ppi_index + 1].TEP = (uint32_t)
&(NRF_GPIOTE->TASKS_OUT[channel]);
NRF_PPI->CHENSET = BIT(ppi_index) | BIT(ppi_index + 1);
if (USE_RTC) {
NRF_PPI->CH[ppi_index].EEP =
(uint32_t) &(rtc->EVENTS_COMPARE[channel]);
NRF_PPI->CH[ppi_index].TEP =
(uint32_t) &(NRF_GPIOTE->TASKS_OUT[channel]);
NRF_PPI->CH[ppi_index + 1].EEP =
(uint32_t) &(rtc->EVENTS_COMPARE[config->map_size]);
NRF_PPI->CH[ppi_index + 1].TEP =
(uint32_t) &(NRF_GPIOTE->TASKS_OUT[channel]);
NRF_PPI->CH[ppi_index + 2].EEP =
(uint32_t) &(rtc->EVENTS_COMPARE[config->map_size]);
NRF_PPI->CH[ppi_index + 2].TEP =
(uint32_t) &(rtc->TASKS_CLEAR);
} else {
NRF_PPI->CH[ppi_index].EEP =
(uint32_t) &(timer->EVENTS_COMPARE[channel]);
NRF_PPI->CH[ppi_index].TEP =
(uint32_t) &(NRF_GPIOTE->TASKS_OUT[channel]);
NRF_PPI->CH[ppi_index + 1].EEP =
(uint32_t) &(timer->EVENTS_COMPARE[config->map_size]);
NRF_PPI->CH[ppi_index + 1].TEP =
(uint32_t) &(NRF_GPIOTE->TASKS_OUT[channel]);
}
NRF_PPI->CHENSET = ppi_mask;
/* start timer, hence PWM */
timer->TASKS_START = 1;
if (USE_RTC) {
rtc->TASKS_START = 1;
} else {
timer->TASKS_START = 1;
}
/* store the pwm/pin and its param */
data->period_cycles = period_cycles;
@ -221,7 +301,11 @@ pin_set_pwm_off:
if (!pwm_active) {
/* No active PWM, stop timer */
timer->TASKS_STOP = 1;
if (USE_RTC) {
rtc->TASKS_STOP = 1;
} else {
timer->TASKS_STOP = 1;
}
}
return 0;
@ -231,12 +315,21 @@ static int pwm_nrf5_sw_get_cycles_per_sec(const struct device *dev,
uint32_t pwm,
uint64_t *cycles)
{
const struct pwm_config *config;
const struct pwm_config *config = dev->config;
config = (const struct pwm_config *)dev->config;
/* HF timer frequency is derived from 16MHz source with a prescaler */
*cycles = 16000000UL / BIT(config->prescaler);
if (USE_RTC) {
/*
* RTC frequency is derived from 32768Hz source without any
* prescaler
*/
*cycles = 32768UL;
} else {
/*
* HF timer frequency is derived from 16MHz source with a
* prescaler
*/
*cycles = 16000000UL / BIT(config->prescaler);
}
return 0;
}
@ -248,27 +341,41 @@ static const struct pwm_driver_api pwm_nrf5_sw_drv_api_funcs = {
static int pwm_nrf5_sw_init(const struct device *dev)
{
const struct pwm_config *config;
NRF_TIMER_Type *timer;
const struct pwm_config *config = dev->config;
NRF_TIMER_Type *timer = pwm_config_timer(config);
NRF_RTC_Type *rtc = pwm_config_rtc(config);
config = (const struct pwm_config *)dev->config;
timer = config->timer;
if (USE_RTC) {
/* setup RTC */
rtc->PRESCALER = 0;
/* setup HF timer */
timer->MODE = TIMER_MODE_MODE_Timer;
timer->PRESCALER = config->prescaler;
timer->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
/*
* TODO: set EVTEN to map_size if not 3, i.e. if RTC supports
* less than 4 compares, then less channels can be supported.
*/
rtc->EVTENSET = (RTC_EVTENSET_COMPARE0_Msk |
RTC_EVTENSET_COMPARE1_Msk |
RTC_EVTENSET_COMPARE2_Msk |
RTC_EVTENSET_COMPARE3_Msk);
} else {
/* setup HF timer */
timer->MODE = TIMER_MODE_MODE_Timer;
timer->PRESCALER = config->prescaler;
timer->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
/* TODO: set shorts according to map_size if not 3, i.e. if NRF_TIMER
* supports more than 4 compares, then more channels can be supported.
*/
timer->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk;
/*
* TODO: set shorts according to map_size if not 3, i.e. if
* NRF_TIMER supports more than 4 compares, then more channels
* can be supported.
*/
timer->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk;
}
return 0;
}
static const struct pwm_config pwm_nrf5_sw_0_config = {
.timer = GENERATOR_ADDR,
COND_CODE_1(USE_RTC, (.rtc), (.timer)) = GENERATOR_ADDR,
.ppi_base = DT_INST_PROP(0, ppi_base),
.gpiote_base = DT_INST_PROP(0, gpiote_base),
.map_size = PWM_0_MAP_SIZE,