diff --git a/drivers/pwm/pwm_nrf5_sw.c b/drivers/pwm/pwm_nrf5_sw.c index a3c394b9956..0d3b79055b7 100644 --- a/drivers/pwm/pwm_nrf5_sw.c +++ b/drivers/pwm/pwm_nrf5_sw.c @@ -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), }; diff --git a/dts/arm/nordic/nrf51822.dtsi b/dts/arm/nordic/nrf51822.dtsi index 625e74e0608..85325bca701 100644 --- a/dts/arm/nordic/nrf51822.dtsi +++ b/dts/arm/nordic/nrf51822.dtsi @@ -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>; }; diff --git a/dts/arm/nordic/nrf52805.dtsi b/dts/arm/nordic/nrf52805.dtsi index a5c132a7827..3c130a4e4f1 100644 --- a/dts/arm/nordic/nrf52805.dtsi +++ b/dts/arm/nordic/nrf52805.dtsi @@ -314,7 +314,6 @@ status = "disabled"; label = "SW_PWM"; generator = <&timer2>; - channel-count = <3>; clock-prescaler = <0>; #pwm-cells = <1>; }; diff --git a/dts/arm/nordic/nrf52810.dtsi b/dts/arm/nordic/nrf52810.dtsi index e51bf365d82..b07522a037a 100644 --- a/dts/arm/nordic/nrf52810.dtsi +++ b/dts/arm/nordic/nrf52810.dtsi @@ -335,7 +335,6 @@ status = "disabled"; label = "SW_PWM"; generator = <&timer2>; - channel-count = <3>; clock-prescaler = <0>; #pwm-cells = <1>; }; diff --git a/dts/arm/nordic/nrf52811.dtsi b/dts/arm/nordic/nrf52811.dtsi index 4a4b7bd213e..790e0bb390c 100644 --- a/dts/arm/nordic/nrf52811.dtsi +++ b/dts/arm/nordic/nrf52811.dtsi @@ -364,7 +364,6 @@ status = "disabled"; label = "SW_PWM"; generator = <&timer2>; - channel-count = <3>; clock-prescaler = <0>; #pwm-cells = <1>; }; diff --git a/dts/arm/nordic/nrf52820.dtsi b/dts/arm/nordic/nrf52820.dtsi index 4ac690a6a3a..9944b503d65 100644 --- a/dts/arm/nordic/nrf52820.dtsi +++ b/dts/arm/nordic/nrf52820.dtsi @@ -377,7 +377,6 @@ status = "disabled"; label = "SW_PWM"; generator = <&timer2>; - channel-count = <3>; clock-prescaler = <0>; #pwm-cells = <1>; }; diff --git a/dts/arm/nordic/nrf52832.dtsi b/dts/arm/nordic/nrf52832.dtsi index 3c05f607424..df4f7785329 100644 --- a/dts/arm/nordic/nrf52832.dtsi +++ b/dts/arm/nordic/nrf52832.dtsi @@ -465,7 +465,6 @@ status = "disabled"; label = "SW_PWM"; generator = <&timer2>; - channel-count = <3>; clock-prescaler = <0>; #pwm-cells = <1>; }; diff --git a/dts/arm/nordic/nrf52833.dtsi b/dts/arm/nordic/nrf52833.dtsi index 261442a41e7..a84bd661c13 100644 --- a/dts/arm/nordic/nrf52833.dtsi +++ b/dts/arm/nordic/nrf52833.dtsi @@ -527,7 +527,6 @@ status = "disabled"; label = "SW_PWM"; generator = <&timer2>; - channel-count = <3>; clock-prescaler = <0>; #pwm-cells = <1>; }; diff --git a/dts/arm/nordic/nrf52840.dtsi b/dts/arm/nordic/nrf52840.dtsi index 057df631e43..3259fec3572 100644 --- a/dts/arm/nordic/nrf52840.dtsi +++ b/dts/arm/nordic/nrf52840.dtsi @@ -548,7 +548,6 @@ status = "disabled"; label = "SW_PWM"; generator = <&timer2>; - channel-count = <3>; clock-prescaler = <0>; #pwm-cells = <1>; }; diff --git a/dts/bindings/pwm/nordic,nrf-sw-pwm.yaml b/dts/bindings/pwm/nordic,nrf-sw-pwm.yaml index 2eb15f86d4c..b2b3f4ca37c 100644 --- a/dts/bindings/pwm/nordic,nrf-sw-pwm.yaml +++ b/dts/bindings/pwm/nordic,nrf-sw-pwm.yaml @@ -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