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:
parent
e7a075f460
commit
c23a449bfb
10 changed files with 68 additions and 72 deletions
|
@ -31,11 +31,12 @@ BUILD_ASSERT(DT_INST_PROP(0, clock_prescaler) == 0,
|
||||||
#define GENERATOR_ADDR ((NRF_TIMER_Type *) DT_REG_ADDR(GENERATOR_NODE))
|
#define GENERATOR_ADDR ((NRF_TIMER_Type *) DT_REG_ADDR(GENERATOR_NODE))
|
||||||
#endif
|
#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. */
|
/* 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."
|
#error "Invalid number of PWM channels configured."
|
||||||
#endif
|
#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
|
/* 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
|
* 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_RTC_Type *rtc;
|
||||||
NRF_TIMER_Type *timer;
|
NRF_TIMER_Type *timer;
|
||||||
};
|
};
|
||||||
|
uint8_t psel_ch[PWM_0_MAP_SIZE];
|
||||||
uint8_t map_size;
|
uint8_t map_size;
|
||||||
uint8_t prescaler;
|
uint8_t prescaler;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct chan_map {
|
|
||||||
uint32_t pwm;
|
|
||||||
uint32_t pulse_cycles;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct pwm_data {
|
struct pwm_data {
|
||||||
uint32_t period_cycles;
|
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 ppi_ch[PWM_0_MAP_SIZE][PPI_PER_CH];
|
||||||
uint8_t gpiote_ch[PWM_0_MAP_SIZE];
|
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)
|
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,
|
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)
|
uint32_t pulse_cycles)
|
||||||
{
|
{
|
||||||
uint8_t i;
|
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 */
|
/* fail if requested period does not match already running period */
|
||||||
for (i = 0U; i < map_size; i++) {
|
for (i = 0U; i < map_size; i++) {
|
||||||
if ((data->map[i].pwm != pwm) &&
|
if ((i != channel) &&
|
||||||
(data->map[i].pulse_cycles != 0U) &&
|
(data->pulse_cycles[i] != 0U) &&
|
||||||
(period_cycles != data->period_cycles)) {
|
(period_cycles != data->period_cycles)) {
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
@ -109,29 +106,6 @@ static uint32_t pwm_period_check(struct pwm_data *data, uint8_t map_size,
|
||||||
return 0;
|
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,
|
static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
|
||||||
uint32_t period_cycles, uint32_t pulse_cycles,
|
uint32_t period_cycles, uint32_t pulse_cycles,
|
||||||
pwm_flags_t flags)
|
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);
|
NRF_RTC_Type *rtc = pwm_config_rtc(config);
|
||||||
struct pwm_data *data = dev->data;
|
struct pwm_data *data = dev->data;
|
||||||
uint32_t ppi_mask;
|
uint32_t ppi_mask;
|
||||||
uint8_t channel;
|
uint8_t channel = pwm;
|
||||||
|
uint8_t psel_ch;
|
||||||
uint8_t gpiote_ch;
|
uint8_t gpiote_ch;
|
||||||
const uint8_t *ppi_chs;
|
const uint8_t *ppi_chs;
|
||||||
uint32_t ret;
|
uint32_t ret;
|
||||||
|
@ -151,10 +126,15 @@ static int pwm_nrf5_sw_pin_set(const struct device *dev, uint32_t pwm,
|
||||||
return -ENOTSUP;
|
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
|
/* check if requested period is allowed while other channels are
|
||||||
* active.
|
* 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);
|
pulse_cycles);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
LOG_ERR("Incompatible period");
|
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.
|
* resolution, use that info in config struct.
|
||||||
*/
|
*/
|
||||||
if (period_cycles > UINT16_MAX) {
|
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);
|
period_cycles);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* map pwm pin to GPIOTE config/channel */
|
psel_ch = config->psel_ch[channel];
|
||||||
channel = pwm_channel_map(data, config->map_size, pwm);
|
|
||||||
gpiote_ch = data->gpiote_ch[channel];
|
gpiote_ch = data->gpiote_ch[channel];
|
||||||
ppi_chs = data->ppi_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,
|
LOG_DBG("channel %u, period %u, pulse %u",
|
||||||
period_cycles, pulse_cycles);
|
channel, period_cycles, pulse_cycles);
|
||||||
|
|
||||||
/* clear GPIOTE config */
|
/* clear GPIOTE config */
|
||||||
NRF_GPIOTE->CONFIG[gpiote_ch] = 0;
|
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;
|
NRF_PPI->CHENCLR = ppi_mask;
|
||||||
|
|
||||||
/* configure GPIO pin as output */
|
/* configure GPIO pin as output */
|
||||||
nrf_gpio_cfg_output(pwm);
|
nrf_gpio_cfg_output(psel_ch);
|
||||||
if (pulse_cycles == 0U) {
|
if (pulse_cycles == 0U) {
|
||||||
/* 0% duty cycle, keep pin low */
|
/* 0% duty cycle, keep pin low */
|
||||||
nrf_gpio_pin_clear(pwm);
|
nrf_gpio_pin_clear(psel_ch);
|
||||||
|
|
||||||
goto pin_set_pwm_off;
|
goto pin_set_pwm_off;
|
||||||
} else if (pulse_cycles == period_cycles) {
|
} else if (pulse_cycles == period_cycles) {
|
||||||
/* 100% duty cycle, keep pin high */
|
/* 100% duty cycle, keep pin high */
|
||||||
nrf_gpio_pin_set(pwm);
|
nrf_gpio_pin_set(psel_ch);
|
||||||
|
|
||||||
goto pin_set_pwm_off;
|
goto pin_set_pwm_off;
|
||||||
} else {
|
} else {
|
||||||
/* x% duty cycle, start PWM with pin low */
|
/* x% duty cycle, start PWM with pin low */
|
||||||
nrf_gpio_pin_clear(pwm);
|
nrf_gpio_pin_clear(psel_ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* configure RTC / TIMER */
|
/* 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 */
|
/* 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 */
|
/* setup PPI */
|
||||||
if (USE_RTC) {
|
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;
|
timer->TASKS_START = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* store the pwm/pin and its param */
|
/* store the period and pulse cycles */
|
||||||
data->period_cycles = period_cycles;
|
data->period_cycles = period_cycles;
|
||||||
data->map[channel].pwm = pwm;
|
data->pulse_cycles[channel] = pulse_cycles;
|
||||||
data->map[channel].pulse_cycles = pulse_cycles;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
pin_set_pwm_off:
|
pin_set_pwm_off:
|
||||||
data->map[channel].pulse_cycles = 0U;
|
data->pulse_cycles[channel] = 0U;
|
||||||
bool pwm_active = false;
|
bool pwm_active = false;
|
||||||
|
|
||||||
/* stop timer if all channels are inactive */
|
/* stop timer if all channels are inactive */
|
||||||
for (channel = 0U; channel < config->map_size; channel++) {
|
for (channel = 0U; channel < config->map_size; channel++) {
|
||||||
if (data->map[channel].pulse_cycles) {
|
if (data->pulse_cycles[channel]) {
|
||||||
pwm_active = true;
|
pwm_active = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -389,8 +363,14 @@ static int pwm_nrf5_sw_init(const struct device *dev)
|
||||||
return 0;
|
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 = {
|
static const struct pwm_config pwm_nrf5_sw_0_config = {
|
||||||
COND_CODE_1(USE_RTC, (.rtc), (.timer)) = GENERATOR_ADDR,
|
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,
|
.map_size = PWM_0_MAP_SIZE,
|
||||||
.prescaler = DT_INST_PROP(0, clock_prescaler),
|
.prescaler = DT_INST_PROP(0, clock_prescaler),
|
||||||
};
|
};
|
||||||
|
|
|
@ -323,10 +323,9 @@
|
||||||
|
|
||||||
sw_pwm: sw-pwm {
|
sw_pwm: sw-pwm {
|
||||||
compatible = "nordic,nrf-sw-pwm";
|
compatible = "nordic,nrf-sw-pwm";
|
||||||
status = "okay";
|
status = "disabled";
|
||||||
label = "SW_PWM";
|
label = "SW_PWM";
|
||||||
generator = <&timer1>;
|
generator = <&timer1>;
|
||||||
channel-count = <3>;
|
|
||||||
clock-prescaler = <0>;
|
clock-prescaler = <0>;
|
||||||
#pwm-cells = <1>;
|
#pwm-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -314,7 +314,6 @@
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
label = "SW_PWM";
|
label = "SW_PWM";
|
||||||
generator = <&timer2>;
|
generator = <&timer2>;
|
||||||
channel-count = <3>;
|
|
||||||
clock-prescaler = <0>;
|
clock-prescaler = <0>;
|
||||||
#pwm-cells = <1>;
|
#pwm-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -335,7 +335,6 @@
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
label = "SW_PWM";
|
label = "SW_PWM";
|
||||||
generator = <&timer2>;
|
generator = <&timer2>;
|
||||||
channel-count = <3>;
|
|
||||||
clock-prescaler = <0>;
|
clock-prescaler = <0>;
|
||||||
#pwm-cells = <1>;
|
#pwm-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -364,7 +364,6 @@
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
label = "SW_PWM";
|
label = "SW_PWM";
|
||||||
generator = <&timer2>;
|
generator = <&timer2>;
|
||||||
channel-count = <3>;
|
|
||||||
clock-prescaler = <0>;
|
clock-prescaler = <0>;
|
||||||
#pwm-cells = <1>;
|
#pwm-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -377,7 +377,6 @@
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
label = "SW_PWM";
|
label = "SW_PWM";
|
||||||
generator = <&timer2>;
|
generator = <&timer2>;
|
||||||
channel-count = <3>;
|
|
||||||
clock-prescaler = <0>;
|
clock-prescaler = <0>;
|
||||||
#pwm-cells = <1>;
|
#pwm-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -465,7 +465,6 @@
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
label = "SW_PWM";
|
label = "SW_PWM";
|
||||||
generator = <&timer2>;
|
generator = <&timer2>;
|
||||||
channel-count = <3>;
|
|
||||||
clock-prescaler = <0>;
|
clock-prescaler = <0>;
|
||||||
#pwm-cells = <1>;
|
#pwm-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -527,7 +527,6 @@
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
label = "SW_PWM";
|
label = "SW_PWM";
|
||||||
generator = <&timer2>;
|
generator = <&timer2>;
|
||||||
channel-count = <3>;
|
|
||||||
clock-prescaler = <0>;
|
clock-prescaler = <0>;
|
||||||
#pwm-cells = <1>;
|
#pwm-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -548,7 +548,6 @@
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
label = "SW_PWM";
|
label = "SW_PWM";
|
||||||
generator = <&timer2>;
|
generator = <&timer2>;
|
||||||
channel-count = <3>;
|
|
||||||
clock-prescaler = <0>;
|
clock-prescaler = <0>;
|
||||||
#pwm-cells = <1>;
|
#pwm-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,19 +7,13 @@ include: [pwm-controller.yaml, base.yaml]
|
||||||
properties:
|
properties:
|
||||||
generator:
|
generator:
|
||||||
type: phandle
|
type: phandle
|
||||||
|
required: true
|
||||||
description: |
|
description: |
|
||||||
Reference to TIMER or RTC instance for generating PWM output signals
|
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:
|
clock-prescaler:
|
||||||
type: int
|
type: int
|
||||||
|
required: true
|
||||||
description: |
|
description: |
|
||||||
Clock prescaler for RTC or TIMER used for generating PWM output signals.
|
Clock prescaler for RTC or TIMER used for generating PWM output signals.
|
||||||
|
|
||||||
|
@ -27,7 +21,37 @@ properties:
|
||||||
generation.
|
generation.
|
||||||
|
|
||||||
TIMER: 16 MHz / 2^prescaler base clock is used for PWM generation.
|
TIMER: 16 MHz / 2^prescaler base clock is used for PWM generation.
|
||||||
|
|
||||||
|
channel-gpios:
|
||||||
|
type: phandle-array
|
||||||
required: true
|
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":
|
"#pwm-cells":
|
||||||
const: 1
|
const: 1
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue