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),
};

View file

@ -323,10 +323,9 @@
sw_pwm: sw-pwm {
compatible = "nordic,nrf-sw-pwm";
status = "okay";
status = "disabled";
label = "SW_PWM";
generator = <&timer1>;
channel-count = <3>;
clock-prescaler = <0>;
#pwm-cells = <1>;
};

View file

@ -314,7 +314,6 @@
status = "disabled";
label = "SW_PWM";
generator = <&timer2>;
channel-count = <3>;
clock-prescaler = <0>;
#pwm-cells = <1>;
};

View file

@ -335,7 +335,6 @@
status = "disabled";
label = "SW_PWM";
generator = <&timer2>;
channel-count = <3>;
clock-prescaler = <0>;
#pwm-cells = <1>;
};

View file

@ -364,7 +364,6 @@
status = "disabled";
label = "SW_PWM";
generator = <&timer2>;
channel-count = <3>;
clock-prescaler = <0>;
#pwm-cells = <1>;
};

View file

@ -377,7 +377,6 @@
status = "disabled";
label = "SW_PWM";
generator = <&timer2>;
channel-count = <3>;
clock-prescaler = <0>;
#pwm-cells = <1>;
};

View file

@ -465,7 +465,6 @@
status = "disabled";
label = "SW_PWM";
generator = <&timer2>;
channel-count = <3>;
clock-prescaler = <0>;
#pwm-cells = <1>;
};

View file

@ -527,7 +527,6 @@
status = "disabled";
label = "SW_PWM";
generator = <&timer2>;
channel-count = <3>;
clock-prescaler = <0>;
#pwm-cells = <1>;
};

View file

@ -548,7 +548,6 @@
status = "disabled";
label = "SW_PWM";
generator = <&timer2>;
channel-count = <3>;
clock-prescaler = <0>;
#pwm-cells = <1>;
};

View file

@ -7,19 +7,13 @@ include: [pwm-controller.yaml, base.yaml]
properties:
generator:
type: phandle
required: true
description: |
Reference to TIMER or RTC instance for generating PWM output signals
required: true
channel-count:
type: int
description: |
Number of PWM channels. Limited by RTC/TIMER instance compare registers
minus 1.
required: true
clock-prescaler:
type: int
required: true
description: |
Clock prescaler for RTC or TIMER used for generating PWM output signals.
@ -27,7 +21,37 @@ properties:
generation.
TIMER: 16 MHz / 2^prescaler base clock is used for PWM generation.
channel-gpios:
type: phandle-array
required: true
description: |
An array of GPIOs assigned as outputs for the PWM channels. The number
of items in this array determines the number of channels that this PWM
will provide. This value is limited by the number of compare registers
in the used RTC/TIMER instance minus 1.
Example:
sw_pwm: sw-pwm {
compatible = "nordic,nrf-sw-pwm";
...
channel-gpios = <&gpio0 20 GPIO_ACTIVE_HIGH>,
<&gpio1 12 GPIO_ACTIVE_HIGH>;
...
};
The above will assign P0.20 as the output for channel 0 and P1.12 as
the output for channel 1. Both outputs will be initially configured as
active high.
Please note that in the flags cell (the last component of each item
of the array) only the GPIO flags that specify the active level are
taken into account (any others are ignored), and they are used only
when the initial (inactive) state of the outputs is set.
After any PWM signal generation is requested for a given channel,
the polarity of its output is determined by the flag specified
in the request, i.e. PWM_POLARITY_INVERTED or PWM_POLARITY_NORMAL.
"#pwm-cells":
const: 1