drivers: pwm_nrf5_sw: Treat pwm parameter as PWM channel, not SoC pin

Align with other PWM drivers and treat the `pwm` parameter (described
ambiguously as "PWM pin") of the `pwm_pin_set_cycles` function as a PWM
channel, not an SoC pin. This will also make the driver consistent with
the `pwm-cells` property definition in the "nordic,nrf-sw-pwm" binding
and with related `DT_PWMS_*` macros.
The change described above requires also providing a way to specify
SoC pins that are to be assigned to the PWM channels. Hence, the commit
introduces in the "nordic,nrf-sw-pwm" binding the `channel-gpios`
property that replaces the `channel-count` one.

Signed-off-by: Andrzej Głąbek <andrzej.glabek@nordicsemi.no>
This commit is contained in:
Andrzej Głąbek 2022-04-13 11:44:24 +02:00 committed by Carles Cufí
commit c23a449bfb
10 changed files with 68 additions and 72 deletions

View file

@ -31,11 +31,12 @@ BUILD_ASSERT(DT_INST_PROP(0, clock_prescaler) == 0,
#define GENERATOR_ADDR ((NRF_TIMER_Type *) DT_REG_ADDR(GENERATOR_NODE))
#endif
#define PWM_0_MAP_SIZE DT_INST_PROP_LEN(0, channel_gpios)
/* One compare channel is needed to set the PWM period, hence +1. */
#if ((DT_INST_PROP(0, channel_count) + 1) > GENERATOR_CC_NUM)
#if ((PWM_0_MAP_SIZE + 1) > GENERATOR_CC_NUM)
#error "Invalid number of PWM channels configured."
#endif
#define PWM_0_MAP_SIZE DT_INST_PROP(0, channel_count)
/* When RTC is used, one more PPI task endpoint is required for clearing
* the counter, so when FORK feature is not available, one more PPI channel
@ -52,20 +53,16 @@ struct pwm_config {
NRF_RTC_Type *rtc;
NRF_TIMER_Type *timer;
};
uint8_t psel_ch[PWM_0_MAP_SIZE];
uint8_t map_size;
uint8_t prescaler;
};
struct chan_map {
uint32_t pwm;
uint32_t pulse_cycles;
};
struct pwm_data {
uint32_t period_cycles;
uint32_t pulse_cycles[PWM_0_MAP_SIZE];
uint8_t ppi_ch[PWM_0_MAP_SIZE][PPI_PER_CH];
uint8_t gpiote_ch[PWM_0_MAP_SIZE];
struct chan_map map[PWM_0_MAP_SIZE];
};
static inline NRF_RTC_Type *pwm_config_rtc(const struct pwm_config *config)
@ -87,7 +84,7 @@ static inline NRF_TIMER_Type *pwm_config_timer(const struct pwm_config *config)
}
static uint32_t pwm_period_check(struct pwm_data *data, uint8_t map_size,
uint32_t pwm, uint32_t period_cycles,
uint32_t channel, uint32_t period_cycles,
uint32_t pulse_cycles)
{
uint8_t i;
@ -99,8 +96,8 @@ static uint32_t pwm_period_check(struct pwm_data *data, uint8_t map_size,
/* fail if requested period does not match already running period */
for (i = 0U; i < map_size; i++) {
if ((data->map[i].pwm != pwm) &&
(data->map[i].pulse_cycles != 0U) &&
if ((i != channel) &&
(data->pulse_cycles[i] != 0U) &&
(period_cycles != data->period_cycles)) {
return -EINVAL;
}
@ -109,29 +106,6 @@ static uint32_t pwm_period_check(struct pwm_data *data, uint8_t map_size,
return 0;
}
static uint8_t pwm_channel_map(struct pwm_data *data, uint8_t map_size,
uint32_t pwm)
{
uint8_t i;
/* find pin, if already present */
for (i = 0U; i < map_size; i++) {
if (pwm == data->map[i].pwm) {
return i;
}
}
/* find a free entry */
i = map_size;
while (i--) {
if (data->map[i].pulse_cycles == 0U) {
break;
}
}
return i;
}
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)
@ -141,7 +115,8 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
NRF_RTC_Type *rtc = pwm_config_rtc(config);
struct pwm_data *data = dev->data;
uint32_t ppi_mask;
uint8_t channel;
uint8_t channel = pwm;
uint8_t psel_ch;
uint8_t gpiote_ch;
const uint8_t *ppi_chs;
uint32_t ret;
@ -151,10 +126,15 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
return -ENOTSUP;
}
if (channel >= config->map_size) {
LOG_ERR("Invalid channel: %u.", channel);
return -EINVAL;
}
/* check if requested period is allowed while other channels are
* active.
*/
ret = pwm_period_check(data, config->map_size, pwm, period_cycles,
ret = pwm_period_check(data, config->map_size, channel, period_cycles,
pulse_cycles);
if (ret) {
LOG_ERR("Incompatible period");
@ -172,23 +152,18 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
* resolution, use that info in config struct.
*/
if (period_cycles > UINT16_MAX) {
LOG_ERR("Too long period (%u), adjust pwm prescaler!",
LOG_ERR("Too long period (%u), adjust PWM prescaler!",
period_cycles);
return -EINVAL;
}
}
/* map pwm pin to GPIOTE config/channel */
channel = pwm_channel_map(data, config->map_size, pwm);
psel_ch = config->psel_ch[channel];
gpiote_ch = data->gpiote_ch[channel];
ppi_chs = data->ppi_ch[channel];
if (channel >= config->map_size) {
LOG_ERR("No more channels available");
return -ENOMEM;
}
LOG_DBG("PWM %d, period %u, pulse %u", pwm,
period_cycles, pulse_cycles);
LOG_DBG("channel %u, period %u, pulse %u",
channel, period_cycles, pulse_cycles);
/* clear GPIOTE config */
NRF_GPIOTE->CONFIG[gpiote_ch] = 0;
@ -199,20 +174,20 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
NRF_PPI->CHENCLR = ppi_mask;
/* configure GPIO pin as output */
nrf_gpio_cfg_output(pwm);
nrf_gpio_cfg_output(psel_ch);
if (pulse_cycles == 0U) {
/* 0% duty cycle, keep pin low */
nrf_gpio_pin_clear(pwm);
nrf_gpio_pin_clear(psel_ch);
goto pin_set_pwm_off;
} else if (pulse_cycles == period_cycles) {
/* 100% duty cycle, keep pin high */
nrf_gpio_pin_set(pwm);
nrf_gpio_pin_set(psel_ch);
goto pin_set_pwm_off;
} else {
/* x% duty cycle, start PWM with pin low */
nrf_gpio_pin_clear(pwm);
nrf_gpio_pin_clear(psel_ch);
}
/* configure RTC / TIMER */
@ -238,7 +213,7 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
}
/* configure GPIOTE, toggle with initialise output high */
NRF_GPIOTE->CONFIG[gpiote_ch] = 0x00130003 | (pwm << 8);
NRF_GPIOTE->CONFIG[gpiote_ch] = 0x00130003 | (psel_ch << 8);
/* setup PPI */
if (USE_RTC) {
@ -278,20 +253,19 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
timer->TASKS_START = 1;
}
/* store the pwm/pin and its param */
/* store the period and pulse cycles */
data->period_cycles = period_cycles;
data->map[channel].pwm = pwm;
data->map[channel].pulse_cycles = pulse_cycles;
data->pulse_cycles[channel] = pulse_cycles;
return 0;
pin_set_pwm_off:
data->map[channel].pulse_cycles = 0U;
data->pulse_cycles[channel] = 0U;
bool pwm_active = false;
/* stop timer if all channels are inactive */
for (channel = 0U; channel < config->map_size; channel++) {
if (data->map[channel].pulse_cycles) {
if (data->pulse_cycles[channel]) {
pwm_active = true;
break;
}
@ -389,8 +363,14 @@ static int pwm_nrf5_sw_init(const struct device *dev)
return 0;
}
#define PSEL_AND_COMMA(_node_id, _prop, _idx) \
NRF_DT_GPIOS_TO_PSEL_BY_IDX(_node_id, _prop, _idx),
static const struct pwm_config pwm_nrf5_sw_0_config = {
COND_CODE_1(USE_RTC, (.rtc), (.timer)) = GENERATOR_ADDR,
.psel_ch = {
DT_INST_FOREACH_PROP_ELEM(0, channel_gpios, PSEL_AND_COMMA)
},
.map_size = PWM_0_MAP_SIZE,
.prescaler = DT_INST_PROP(0, clock_prescaler),
};