diff --git a/boards/common/esp32/include/periph_conf_common.h b/boards/common/esp32/include/periph_conf_common.h index 437ba58f49..31c4894ca2 100644 --- a/boards/common/esp32/include/periph_conf_common.h +++ b/boards/common/esp32/include/periph_conf_common.h @@ -138,33 +138,92 @@ static const i2c_conf_t i2c_config[] = { */ /** - * @brief Static array of GPIOs that can be used as channels of PWM0 + * @brief GPIOs used as channels for the according PWM device */ #ifdef PWM0_GPIOS -static const gpio_t pwm0_channels[] = PWM0_GPIOS; +static const gpio_t pwm0_gpios[] = PWM0_GPIOS; #endif + /** - * @brief Static array of GPIOs that can be used as channels of PWM0 + * @brief GPIOs used as channels for the according PWM device */ #ifdef PWM1_GPIOS -static const gpio_t pwm1_channels[] = PWM1_GPIOS; +static const gpio_t pwm1_gpios[] = PWM1_GPIOS; #endif +/** + * @brief GPIOs used as channels for the according PWM device + */ +#ifdef PWM2_GPIOS +static const gpio_t pwm2_gpios[] = PWM2_GPIOS; +#endif + +/** + * @brief GPIOs used as channels for the according PWM device + */ +#ifdef PWM3_GPIOS +static const gpio_t pwm3_gpios[] = PWM3_GPIOS; +#endif + +/** + * @brief PWM device configuration based on defined PWM channel GPIOs + */ +static const pwm_config_t pwm_config[] = +{ +#ifdef PWM0_GPIOS + { + .module = PERIPH_LEDC_MODULE, + .group = LEDC_LOW_SPEED_MODE, + .timer = LEDC_TIMER_0, + .ch_numof = ARRAY_SIZE(pwm0_gpios), + .gpios = pwm0_gpios, + }, +#endif +#ifdef PWM1_GPIOS + { + .module = PERIPH_LEDC_MODULE, +#ifdef SOC_LEDC_SUPPORT_HS_MODE + .group = LEDC_HIGH_SPEED_MODE, +#else + .group = LEDC_LOW_SPEED_MODE, +#endif + .timer = LEDC_TIMER_1, + .ch_numof = ARRAY_SIZE(pwm1_gpios), + .gpios = pwm1_gpios, + }, +#endif +#ifdef PWM2_GPIOS + { + .module = PERIPH_LEDC_MODULE, + .group = LEDC_LOW_SPEED_MODE, + .timer = LEDC_TIMER_2, + .ch_numof = ARRAY_SIZE(pwm2_gpios), + .gpios = pwm2_gpios, + }, +#endif +#ifdef PWM3_GPIOS + { + .module = PERIPH_LEDC_MODULE, +#ifdef SOC_LEDC_SUPPORT_HS_MODE + .group = LEDC_HIGH_SPEED_MODE, +#else + .group = LEDC_LOW_SPEED_MODE, +#endif + .timer = LEDC_TIMER_3, + .ch_numof = ARRAY_SIZE(pwm3_gpios), + .gpios = pwm3_gpios, + }, +#endif +}; + /** * @brief Number of PWM devices * - * The number of PWM devices is determined from the PWM0_GPIOS and PWM1_GPIOS - * definitions. + * The number of PWM devices is determined from the PWM device configuration. * * @note PWM_NUMOF definition must not be changed. */ -#if defined(PWM0_GPIOS) && defined(PWM1_GPIOS) -#define PWM_NUMOF (2) -#elif defined(PWM0_GPIOS) || defined(PWM1_GPIOS) -#define PWM_NUMOF (1) -#else -#define PWM_NUMOF (0) -#endif +#define PWM_NUMOF ARRAY_SIZE(pwm_config) /** @} */ diff --git a/boards/esp32-ethernet-kit-v1_0/include/periph_conf.h b/boards/esp32-ethernet-kit-v1_0/include/periph_conf.h index d8f7682a9a..5c5663fef5 100644 --- a/boards/esp32-ethernet-kit-v1_0/include/periph_conf.h +++ b/boards/esp32-ethernet-kit-v1_0/include/periph_conf.h @@ -88,10 +88,6 @@ #endif #endif /* PWM0_GPIOS */ -/** PWM_DEV(1) is not used */ -#ifndef PWM1_GPIOS -#define PWM1_GPIOS { } -#endif /** @} */ /** diff --git a/boards/esp32-mh-et-live-minikit/include/periph_conf.h b/boards/esp32-mh-et-live-minikit/include/periph_conf.h index 719eb04904..15fe2760ae 100644 --- a/boards/esp32-mh-et-live-minikit/include/periph_conf.h +++ b/boards/esp32-mh-et-live-minikit/include/periph_conf.h @@ -101,10 +101,6 @@ #define PWM0_GPIOS { GPIO2, GPIO0, GPIO4, GPIO15 } #endif -/** PWM_DEV(1) is not used */ -#ifndef PWM1_GPIOS -#define PWM1_GPIOS { } -#endif /** @} */ /** diff --git a/boards/esp32-olimex-evb/include/periph_conf.h b/boards/esp32-olimex-evb/include/periph_conf.h index 6ab9fd4e6e..3f3cfe610d 100644 --- a/boards/esp32-olimex-evb/include/periph_conf.h +++ b/boards/esp32-olimex-evb/include/periph_conf.h @@ -134,14 +134,9 @@ extern "C" { #else #error Configuration problem: Flash mode qio or qout is used, \ GPIO9 and GPIO10 cannot be used as PWM channels as configured -#define PWM0_GPIOS { } #endif #endif -/** PWM_DEV(1) is not used */ -#ifndef PWM1_GPIOS -#define PWM1_GPIOS { } -#endif /** @} */ /** diff --git a/boards/esp32-wemos-lolin-d32-pro/include/periph_conf.h b/boards/esp32-wemos-lolin-d32-pro/include/periph_conf.h index 5c79d3ba13..9b6ec7001d 100644 --- a/boards/esp32-wemos-lolin-d32-pro/include/periph_conf.h +++ b/boards/esp32-wemos-lolin-d32-pro/include/periph_conf.h @@ -119,10 +119,6 @@ #define PWM0_GPIOS { GPIO0, GPIO2 } #endif -/** PWM_DEV(1) is not used */ -#ifndef PWM1_GPIOS -#define PWM1_GPIOS { } -#endif /** @} */ /** diff --git a/boards/esp32-wrover-kit/include/periph_conf.h b/boards/esp32-wrover-kit/include/periph_conf.h index acccc5a673..0b1010cdd9 100644 --- a/boards/esp32-wrover-kit/include/periph_conf.h +++ b/boards/esp32-wrover-kit/include/periph_conf.h @@ -127,10 +127,6 @@ #endif #endif -/** PWM_DEV(1) is not used */ -#ifndef PWM1_GPIOS -#define PWM1_GPIOS { } -#endif /** @} */ /** diff --git a/cpu/esp32/doc.txt b/cpu/esp32/doc.txt index 1d589ecf23..4ccc18f10f 100644 --- a/cpu/esp32/doc.txt +++ b/cpu/esp32/doc.txt @@ -99,6 +99,8 @@ Parameter | Short Description [I2C1_SDA](#esp32_i2c_interfaces) | GPIO used as SCL for I2C_DEV(1) | o [PWM0_GPIOS](#esp32_pwm_channels) | GPIOs that can be used at channels of PWM_DEV(0) | o [PWM1_GPIOS](#esp32_pwm_channels) | GPIOs that can be used at channels of PWM_DEV(1) | o +[PWM3_GPIOS](#esp32_pwm_channels) | GPIOs that can be used at channels of PWM_DEV(2) | o +[PWM4_GPIOS](#esp32_pwm_channels) | GPIOs that can be used at channels of PWM_DEV(3) | o [SPI0_CTRL](#esp32_spi_interfaces) | SPI Controller used for SPI_DEV(0), can be `VSPI` `HSPI` | o [SPI0_SCK](#esp32_spi_interfaces) | GPIO used as SCK for SPI_DEV(0) | o [SPI0_MOSI](#esp32_spi_interfaces) | GPIO used as MOSI for SPI_DEV(0) | o @@ -184,8 +186,8 @@ The key features of ESP32 are: | Ethernet | MAC interface with dedicated DMA and IEEE 1588 support | yes | | CAN | version 2.0 | yes | | IR | up to 8 channels TX/RX | no | -| Motor PWM | 2 devices x 6 channels | yes | -| LED PWM | 16 channels | no | +| Motor PWM | 2 devices x 6 channels | no | +| LED PWM | 16 channels | yes | | Crypto | Hardware acceleration of AES, SHA-2, RSA, ECC, RNG | no | | Vcc | 2.5 - 3.6 V | | | Documents | [Datasheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf)
[Technical Reference](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf) | | @@ -810,6 +812,62 @@ waiting. ESP supports two types of PWM generators +The implementation of the PWM peripheral driver uses the LED PWM Controller +(LEDC) module of the ESP32x SoC. This LEDC module has one or two channel +groups with 6 or 8 channels each. The channels of each channel group can +use one of 4 timers as clock source. Thus, it is possible to define at +4 or 8 virtual PWM devices in RIOT with different frequencies and +resolutions. Regardless of whether the LEDC module of the ESP32x SoC has +one or two channel groups, the PWM driver implementation allows to organize +the available channels into up to 4 virtual PWM devices. + +The assignment of the available channels to the virtual PWM devices is +done in the board-specific peripheral configuration by defining the +macros `PWM0_GPIOS`, `PWM1_GPIOS`, `PWM2_GPIOS` and `PWM3_GPIOS` These +macros specify the GPIOs that are used as channels for the available +virtual PWM devices PWM_DEV(0) ... PWM_DEV(3) in RIOT. The mapping of +these channels to the available channel groups and channel group timers +is done by the driver automatically as follows: + +
+Macro | 1 Channel Group | 2 Channel Groups | Timer +-------------|-----------------------|------------------------|--------------- +`PWM0_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_LOW_SPEED_MODE` | `LEDC_TIMER_0` +`PWM1_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_HIGH_SPEED_MODE` | `LEDC_TIMER_1` +`PWM2_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_LOW_SPEED_MODE` | `LEDC_TIMER_2` +`PWM3_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_HIGH_SPEED_MODE` | `LEDC_TIMER_3` + +
+For example, if the LEDC module of the ESP32x SoC has two channel groups, +two virtual PWM devices with 2 x 6/8 channels could be used by defining +'PWM0_GPIOS' and 'PWM1_GPIOS' with up to 6 or 8 GPIOs each. + +Example: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} +#define PWM0_GPIOS { GPIO0, GPIO2, GPIO4, GPIO16, GPIO17 } +#define PWM1_GPIOS { GPIO27, GPIO32, GPIO33 } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This configuration can be changed by +[application specific configurations](#esp32_application_specific_configurations). + +@note +- The total number of channels defined for a channel group must not exceed + #PWM_CH_NUMOF_MAX. +- The definition of `PWM0_GPIOS`, `PWM1_GPIOS`, `PWM2_GPIOS` and + `PWM3_GPIOS` can be omitted. In this case the existing macros should + be defined in ascending order, as the first defined macro is assigned + to PWM_DEV(0), the second defined macro is assigned to PWM_DEV(1) + and so on. So the minimal configuration would define all channels by + `PWM0_GPIOS` as PWM_DEV(0). +- #PWM_NUMOF is determined automatically. +- The order of the GPIOs in these macros determines the mapping between + RIOT's PWM channels and the GPIOs. +- As long as the GPIOs listed in `PWM0_GPIOS`, `PWM1_GPIOS`, + `PWM2_GPIOS` and `PWM3_GPIOS` are not initialized as PWM channels with + the #pwm_init function, they can be used for other purposes. + + - one LED PWM controller (LEDPWM) with 16 channels, and - two high-speed Motor Control PWM controllers (MCPWM) with 6 channels each. diff --git a/cpu/esp32/esp-idf-api/ledc.c b/cpu/esp32/esp-idf-api/ledc.c new file mode 100644 index 0000000000..74a3dcb140 --- /dev/null +++ b/cpu/esp32/esp-idf-api/ledc.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp32_esp_idf_api + * @{ + * + * @file + * @brief Interface for the ESP-IDF LEDC HAL API + * + * @author Gunar Schorcht + * @} + */ + +#include + +#include "driver/ledc.h" + +int esp_ledc_channel_config(const ledc_channel_config_t* ledc_conf) +{ + return ledc_channel_config(ledc_conf); +} + +int esp_ledc_timer_config(const ledc_timer_config_t* timer_conf) +{ + return ledc_timer_config(timer_conf); +} + +int esp_ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel) +{ + return ledc_update_duty(speed_mode, channel); +} + +int esp_ledc_set_duty_with_hpoint(ledc_mode_t speed_mode, + ledc_channel_t channel, + uint32_t duty, uint32_t hpoint) +{ + return ledc_set_duty_with_hpoint(speed_mode, channel, duty, hpoint); +} diff --git a/cpu/esp32/include/esp_idf_api/ledc.h b/cpu/esp32/include/esp_idf_api/ledc.h new file mode 100644 index 0000000000..442360ff55 --- /dev/null +++ b/cpu/esp32/include/esp_idf_api/ledc.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp32_esp_idf_api + * @{ + * + * @file + * @brief Interface for the ESP-IDF LEDC HAL API + * + * @author Gunar Schorcht + * @} + */ + +#ifndef ESP_IDF_API_LEDC_H +#define ESP_IDF_API_LEDC_H + +#include "hal/ledc_types.h" + +#ifndef DOXYGEN /* Hide implementation details from doxygen */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name ESP-IDF interface wrapper functions + * @{ + */ +int esp_ledc_channel_config(const ledc_channel_config_t* ledc_conf); +int esp_ledc_timer_config(const ledc_timer_config_t* timer_conf); +int esp_ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel); +int esp_ledc_set_duty_with_hpoint(ledc_mode_t speed_mode, + ledc_channel_t channel, + uint32_t duty, uint32_t hpoint); +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* DOXYGEN */ +#endif /* ESP_IDF_API_LEDC_H */ diff --git a/cpu/esp32/include/periph_cpu.h b/cpu/esp32/include/periph_cpu.h index ddd5d60b8a..a2312b316e 100644 --- a/cpu/esp32/include/periph_cpu.h +++ b/cpu/esp32/include/periph_cpu.h @@ -21,6 +21,10 @@ #include #include "sdkconfig.h" +#include "hal/ledc_types.h" +#include "soc/ledc_struct.h" +#include "soc/periph_defs.h" +#include "soc/soc_caps.h" #ifdef __cplusplus extern "C" { @@ -363,39 +367,86 @@ typedef struct { /** * @name PWM configuration * - * PWM implementation uses ESP32's high-speed MCPWM modules. ESP32 has 2 such - * modules, each with up to 6 channels (PWM_CHANNEL_NUM_DEV_MAX). Thus, the - * maximum number of PWM devices is 2 and the maximum total number of PWM - * channels is 12. + * The implementation of the PWM peripheral driver uses the LED PWM Controller + * (LEDC) module of the ESP32x SoC. This LEDC module has one or two channel + * groups with 6 or 8 channels each. The channels of each channel group can + * use one of 4 timers as clock source. Thus, it is possible to define at + * 4 or 8 virtual PWM devices in RIOT with different frequencies and + * resolutions. Regardless of whether the LEDC module of the ESP32x SoC has + * one or two channel groups, the PWM driver implementation allows to organize + * the available channels into up to 4 virtual PWM devices. * - * PWM0_GPIOS and PWM1_GPIOS in the board-specific peripheral configuration - * each define a list of GPIOs that can be used with the respective PWM - * devices as PWM channels. The order of the listed GPIOs determines the - * association between the RIOT PWM channels and the GPIOs. + * The assignment of the available channels to the virtual PWM devices is + * done in the board-specific peripheral configuration by defining the + * macros `PWM0_GPIOS`, `PWM1_GPIOS`, `PWM2_GPIOS` and `PWM3_GPIOS` These + * macros specify the GPIOs that are used as channels for the available + * virtual PWM devices PWM_DEV(0) ... PWM_DEV(3) in RIOT. The mapping of + * these channels to the available channel groups and channel group timers + * is done by the driver automatically as follows. * - * @note The definition of PWM0_GPIOS and PWM1_GPIOS can be omitted or - * empty. In the latter case, they must at least contain the curly braces. - * The corresponding PWM device can not be used in this case. + *
+ * Macro | 1 Channel Group | 2 Channel Groups | Timer + * -------------|-----------------------|------------------------|--------------- + * `PWM0_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_LOW_SPEED_MODE` | `LEDC_TIMER_0` + * `PWM1_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_HIGH_SPEED_MODE` | `LEDC_TIMER_1` + * `PWM2_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_LOW_SPEED_MODE` | `LEDC_TIMER_2` + * `PWM3_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_HIGH_SPEED_MODE` | `LEDC_TIMER_3` * - * PWM_NUMOF is determined automatically from the PWM0_GPIOS and PWM1_GPIOS - * definitions. + *
+ * For example, if the LEDC module of the ESP32x SoC has two channel groups, + * two virtual PWM devices with 2 x 6/8 channels could be used by defining + * 'PWM0_GPIOS' and 'PWM1_GPIOS' with 6/8 GPIOs each. * - * @note As long as the GPIOs listed in PWM0_GPIOS and PMW1_GPIOS are not - * initialized as PWM channels with the *pwm_init* function, they can be used - * other purposes. + * @note + * - The total number of channels defined for a channel group must not exceed + * #PWM_CH_NUMOF_MAX. + * - The definition of `PWM0_GPIOS`, `PWM1_GPIOS`, `PWM2_GPIOS` and + * `PWM3_GPIOS` can be omitted. In this case the existing macros should + * be defined in ascending order, as the first defined macro is assigned + * to PWM_DEV(0), the second defined macro is assigned to PWM_DEV(1) + * and so on. So the minimal configuration would define all channels by + * `PWM0_GPIOS` as PWM_DEV(0). + * - #PWM_NUMOF is determined automatically. + * - The order of the GPIOs in these macros determines the mapping between + * RIOT's PWM channels and the GPIOs. + * - As long as the GPIOs listed in `PWM0_GPIOS`, `PWM1_GPIOS`, + * `PWM2_GPIOS` and `PWM3_GPIOS` are not initialized as PWM channels with + * the #pwm_init function, they can be used for other purposes. * * @{ */ +/** + * @brief PWM configuration structure type + * + * The implementation of the PWM peripheral driver uses the LED PWM Controller + * (LEDC) module of the ESP32x SoC. The LEDC module has up to 2 channel groups + * with 6 or 8 channels each, which can use one of 4 timers. + * + * Based on these maximum 2 channel groups with 6 or 8 channels each and 4 + * timers, up to 4 PWM devices can be configured in RIOT. The configuration + * structure defines static parameters for each virtual PWM device, i.e. + * the channel group used, the timer used, the number of channels used and + * the GPIOs assigned to the channels. The number of channels used by a PWM + * device corresponds to the number of GPIOs assigned to this PWM device. + */ +typedef struct { + uint8_t module; /**< LEDC module identifier */ + ledc_mode_t group; /**< LEDC channel group used (low/high speed) */ + ledc_timer_t timer; /**< LEDC timer used by this device */ + uint8_t ch_numof; /**< Number of channels used by this device */ + const gpio_t *gpios; /**< GPIOs used as channels of this device */ +} pwm_config_t; + /** * @brief Maximum number of PWM devices */ -#define PWM_NUMOF_MAX (2) +#define PWM_NUMOF_MAX (4) /** * @brief Maximum number of channels per PWM device. */ -#define PWM_CHANNEL_NUM_DEV_MAX (6) +#define PWM_CH_NUMOF_MAX (SOC_LEDC_CHANNEL_NUM) /** @} */ diff --git a/cpu/esp32/periph/pwm.c b/cpu/esp32/periph/pwm.c index 4e4f6b9af1..e1ad2afbed 100644 --- a/cpu/esp32/periph/pwm.c +++ b/cpu/esp32/periph/pwm.c @@ -18,410 +18,352 @@ * @} */ -#define ENABLE_DEBUG 0 -#include "debug.h" - +#include "bitarithm.h" #include "board.h" #include "cpu.h" +#include "gpio_arch.h" +#include "kernel_defines.h" #include "log.h" #include "irq_arch.h" #include "periph/pwm.h" #include "periph/gpio.h" #include "esp_common.h" -#include "gpio_arch.h" +#include "esp_rom_gpio.h" +#include "hal/ledc_hal.h" +#include "soc/ledc_struct.h" +#include "soc/rtc.h" -#include "driver/periph_ctrl.h" -#include "soc/gpio_struct.h" -#include "soc/gpio_sig_map.h" -#include "soc/mcpwm_reg.h" -#include "soc/mcpwm_struct.h" +#include "esp_idf_api/periph_ctrl.h" -#if defined(PWM0_GPIOS) || defined(PWM1_GPIOS) +#define ENABLE_DEBUG 0 +#include "debug.h" -#define PWM_CLK (160000000UL) /* base clock of PWM devices */ -#define PWM_CPS_MAX (10000000UL) /* maximum cycles per second supported */ -#define PWM_CPS_MIN (2500UL) /* minimum cycles per second supported */ +#if defined(PWM0_GPIOS) || defined(PWM1_GPIOS) || defined(PWM2_GPIOS) || defined(PWM3_GPIOS) -#define PWM_TIMER_MOD_FREEZE 0 /* timer is disabled */ -#define PWM_TIMER_MOD_UP 1 /* timer counts up */ -#define PWM_TIMER_MOD_DOWN 2 /* timer counts down */ -#define PWM_TIMER_MOD_UP_DOWN 3 /* timer counts up and then down */ +#define SOC_LEDC_CLK_DIV_BIT_NUM 18 +#define SOC_LEDC_CLK_DIV_INT_BIT_NUM 10 /* integral part of CLK divider */ +#define SOC_LEDC_CLK_DIV_FRAC_BIT_NUM 8 /* fractional part of CLK divider */ -#define PWM_TIMER_STOPS_AT_TEZ 0 /* PWM starts, then stops at next TEZ */ -#define PWM_TIMER_STOPS_AT_TEP 1 /* PWM starts, then stops at next TEP */ -#define PWM_TIMER_RUNS_ON 2 /* PWM runs on */ -#define PWM_TIMER_STARTS_STOPS_AT_TEZ 3 /* PWM starts and stops at next TEZ */ -#define PWM_TIMER_STARTS_STOPS_AT_TEP 4 /* PWM starts and stops at next TEP */ +#define PWM_HW_RES_MAX ((uint32_t)1 << SOC_LEDC_TIMER_BIT_WIDE_NUM) +#define PWM_HW_RES_MIN ((uint32_t)1 << 1) -#define PWM_TIMER_UPDATE_IMMIDIATE 0 /* update period immediately */ -#define PWM_TIMER_UPDATE_AT_TEZ 1 /* update period at TEZ */ -#define PWM_TIMER_UPDATE_AT_SYNC 2 /* update period at sync */ -#define PWM_TIMER_UPDATE_AT_TEZ_SYNC 3 /* update period at TEZ and sync */ +#define _DEV _pwm_dev[pwm] /* shortcut for PWM device descriptor */ +#define _CFG pwm_config[pwm] /* shortcut for PWM device configuration */ -#define PWM_OP_ACTION_NO_CHANGE 0 /* do not change output */ -#define PWM_OP_ACTION_LOW 1 /* set the output to high */ -#define PWM_OP_ACTION_HIGH 2 /* set the output to low */ -#define PWM_OP_ACTION_TOGGLE 3 /* toggle the output */ - -#define PWM_OP_CHANNEL_A 0 /* operator channel A */ -#define PWM_OP_CHANNEL_B 0 /* operator channel B */ - -/* forward declaration of internal functions */ -static void _pwm_start(pwm_t pwm); -static void _pwm_stop(pwm_t pwm); -static bool _pwm_configuration(void); - -/* data structure for static configuration of PWM devices */ -struct _pwm_hw_t { - mcpwm_dev_t* regs; /* PWM's registers set address */ - uint8_t mod; /* PWM's hardware module */ - uint8_t int_src; /* PWM's peripheral interrupt source */ - uint32_t signal_group; /* PWM's base peripheral signal index */ - uint8_t gpio_num; /* number of GPIOs used as channels outputs */ - const gpio_t* gpios; /* GPIOs used as channel outputs */ -}; - -#ifdef PWM0_GPIOS -static const gpio_t _pwm_channel_gpios_0[] = PWM0_GPIOS; -#endif - -#ifdef PWM1_GPIOS -static const gpio_t _pwm_channel_gpios_1[] = PWM1_GPIOS; -#endif - -/* static configuration of PWM devices */ -static const struct _pwm_hw_t _pwm_hw[] = -{ -#ifdef PWM0_GPIOS - { - .regs = &MCPWM0, - .mod = PERIPH_PWM0_MODULE, - .int_src = ETS_PWM0_INTR_SOURCE, - .signal_group = PWM0_OUT0A_IDX, - .gpio_num = ARRAY_SIZE(_pwm_channel_gpios_0), - .gpios = _pwm_channel_gpios_0, - }, -#endif -#ifdef PWM1_GPIOS - { - .regs = &MCPWM1, - .mod = PERIPH_PWM1_MODULE, - .int_src = ETS_PWM1_INTR_SOURCE, - .signal_group = PWM1_OUT0A_IDX, - .gpio_num = ARRAY_SIZE(_pwm_channel_gpios_1), - .gpios = _pwm_channel_gpios_1, - }, -#endif -}; - -/* data structure dynamic channel configuration */ +/* data structure for dynamic channel parameters */ typedef struct { - bool used; - uint32_t duty; -} _pwm_chn_t; + uint32_t duty; /* actual duty value */ + uint32_t hpoint; /* actual hpoing value */ + bool used; /* true if the channel is set by pwm_set */ + uint8_t ch; /* actual channel index within used channel group */ +} _pwm_ch_t; -/* data structure for dynamic configuration of PWM devices */ -struct _pwm_dev_t { - uint16_t res; - uint32_t freq; - pwm_mode_t mode; - uint8_t chn_num; - _pwm_chn_t chn[PWM_CHANNEL_NUM_DEV_MAX]; -}; +/* data structure for device handling at runtime */ +typedef struct { + uint32_t freq; /* specified frequency parameter */ + uint32_t res; /* specified resolution parameter */ + uint32_t hw_freq; /* used hardware frequency */ + uint32_t hw_res; /* used hardware resolution */ + uint32_t hw_clk_div; /* used hardware clock divider */ + _pwm_ch_t ch[PWM_CH_NUMOF_MAX]; /* dynamic channel parameters at runtime */ + ledc_hal_context_t hw; /* used hardware device context */ + pwm_mode_t mode; /* specified mode */ + ledc_timer_bit_t hw_res_bit; /* used hardware resolution in bit */ + bool enabled; /* true if the device is used (powered on) */ +} _pwm_dev_t; -/* dynamic configuration of PWM devices */ -static struct _pwm_dev_t _pwm_dev[PWM_NUMOF_MAX] = {}; +static _pwm_dev_t _pwm_dev[PWM_NUMOF] = { }; -/* if pwm_init is called first time, it checks the overall pwm configuration */ -static bool _pwm_init_first_time = true; +/* if pwm_init is called first time, it checks the pwm configuration */ +static bool _pwm_initialized = false; + +/* static configuration checks and initialization on first pwm_init */ +static bool _pwm_initialize(void); /* Initialize PWM device */ uint32_t pwm_init(pwm_t pwm, pwm_mode_t mode, uint32_t freq, uint16_t res) { - DEBUG ("%s pwm=%u mode=%u freq=%u, res=%u\n", __func__, pwm, mode, freq, res); + _Static_assert(PWM_NUMOF <= PWM_NUMOF_MAX, "Too many PWM devices defined"); - CHECK_PARAM_RET (pwm < PWM_NUMOF, 0); - CHECK_PARAM_RET (freq > 0, 0); - - if (_pwm_init_first_time) { - if (!_pwm_configuration()) + if (!_pwm_initialized) { + if (!_pwm_initialize()) { return 0; + } + _pwm_initialized = true; } - if (_pwm_hw[pwm].gpio_num == 0) { - LOG_TAG_ERROR("pwm", "PWM device %d has no assigned pins\n", pwm); + assert(pwm < PWM_NUMOF); + assert(freq > 0); + + _DEV.enabled = true; + _DEV.res = res; + _DEV.freq = freq; + _DEV.mode = mode; + + if ((res < PWM_HW_RES_MIN) || (_DEV.res > PWM_HW_RES_MAX)) { + LOG_TAG_ERROR("pwm", "Resolution of PWM device %u to be in " + "range [%"PRIu32", %"PRIu32"]\n", + pwm, PWM_HW_RES_MIN, PWM_HW_RES_MAX); return 0; } - /* reset by disabling and enable the PWM module */ - periph_module_disable(_pwm_hw[pwm].mod); - periph_module_enable(_pwm_hw[pwm].mod); + /* + * The hardware resolution must be a power of two, so we determine the + * next power of two, which covers the desired resolution + */ + ledc_timer_bit_t hw_res_bit = bitarithm_msb(res - 1); + if (hw_res_bit < SOC_LEDC_TIMER_BIT_WIDE_NUM) { + hw_res_bit++; + } - _pwm_dev[pwm].res = res; - _pwm_dev[pwm].freq = freq; - _pwm_dev[pwm].mode = mode; - _pwm_dev[pwm].chn_num = _pwm_hw[pwm].gpio_num; + uint32_t hw_res = 1 << hw_res_bit; - for (int i = 0; i < _pwm_dev[pwm].chn_num; i++) { + uint32_t hw_ticks_max = rtc_clk_apb_freq_get(); + uint32_t hw_ticks_min = hw_ticks_max / (1 << SOC_LEDC_CLK_DIV_INT_BIT_NUM); + uint32_t hw_freq_min = hw_ticks_min / (1 << SOC_LEDC_TIMER_BIT_WIDE_NUM) + 1; + + if (freq < hw_freq_min) { + LOG_TAG_ERROR("pwm", "Frequency of %"PRIu32" Hz is too less, minimum " + "frequency is %"PRIu32" Hz\n", freq, hw_freq_min); + return 0; + } + + /* number of hardware ticks required, at maximum it can be APB clock */ + uint32_t hw_ticks = MIN(freq * hw_res, rtc_clk_apb_freq_get()); + + /* + * if the number of required ticks is less than minimum ticks supported by + * the hardware supports, we have to increase the resolution. + */ + while (hw_ticks < hw_ticks_min) { + hw_res_bit++; + hw_res = 1 << hw_res_bit; + hw_ticks = freq * hw_res; + } + + /* LEDC_CLK_DIV is given in Q10.8 format */ + uint32_t hw_clk_div = + ((uint64_t)rtc_clk_apb_freq_get() << SOC_LEDC_CLK_DIV_FRAC_BIT_NUM) / hw_ticks; + + _DEV.freq = freq; + _DEV.res = res; + _DEV.hw_freq = hw_ticks / hw_res; + _DEV.hw_res = hw_res; + _DEV.hw_res_bit = hw_res_bit; + _DEV.hw_clk_div = hw_clk_div; + + DEBUG("%s hw_freq=%"PRIu32" hw_res=%"PRIu32" hw_ticks=%"PRIu32 + " hw_clk_div=%"PRIu32"\n", __func__, + _DEV.hw_freq, _DEV.hw_res, hw_ticks, _DEV.hw_clk_div); + + for (int i = 0; i < _CFG.ch_numof; i++) { /* initialize channel data */ - _pwm_dev[pwm].chn[i].used = false; - _pwm_dev[pwm].chn[i].duty = 0; + _DEV.ch[i].used = false; + _DEV.ch[i].duty = 0; /* reset GPIO usage type if the pins were used already for PWM before to make it possible to reinitialize PWM with new parameters */ - if (gpio_get_pin_usage(_pwm_hw[pwm].gpios[i]) == _PWM) { - gpio_set_pin_usage(_pwm_hw[pwm].gpios[i], _GPIO); + if (gpio_get_pin_usage(_CFG.gpios[i]) == _PWM) { + gpio_set_pin_usage(_CFG.gpios[i], _GPIO); } - if (gpio_get_pin_usage(_pwm_hw[pwm].gpios[i]) != _GPIO) { - LOG_TAG_ERROR("pwm", "GPIO%d is used for %s and cannot be used as PWM output\n", i, - gpio_get_pin_usage_str(_pwm_hw[pwm].gpios[i])); + if (gpio_get_pin_usage(_CFG.gpios[i]) != _GPIO) { + LOG_TAG_ERROR("pwm", "GPIO%d is used for %s and cannot be used as " + "PWM output\n", + i, gpio_get_pin_usage_str(_CFG.gpios[i])); return 0; } - if (gpio_init(_pwm_hw[pwm].gpios[i], GPIO_OUT) < 0) { + /* initialize the GPIOs and route the PWM signal output to the GPIO */ + if (gpio_init(_CFG.gpios[i], GPIO_OUT) < 0) { return 0; } - /* initialize the GPIO and route the PWM signal output to the GPIO */ - gpio_set_pin_usage(_pwm_hw[pwm].gpios[i], _GPIO); - gpio_clear (_pwm_hw[pwm].gpios[i]); - GPIO.func_out_sel_cfg[_pwm_hw[pwm].gpios[i]].func_sel = _pwm_hw[pwm].signal_group + i; + gpio_set_pin_usage(_CFG.gpios[i], _PWM); + gpio_clear(_CFG.gpios[i]); + + esp_rom_gpio_connect_out_signal( + _CFG.gpios[i], + ledc_periph_signal[_CFG.group].sig_out0_idx + _DEV.ch[i].ch, 0, 0); + } - /* start the PWM device */ - _pwm_start(pwm); + pwm_poweron(pwm); - return freq; + return _DEV.hw_freq; } uint8_t pwm_channels(pwm_t pwm) { - CHECK_PARAM_RET (pwm < PWM_NUMOF, 0); - - return _pwm_hw[pwm].gpio_num; + assert(pwm < PWM_NUMOF); + return _CFG.ch_numof; } void pwm_set(pwm_t pwm, uint8_t channel, uint16_t value) { DEBUG("%s pwm=%u channel=%u value=%u\n", __func__, pwm, channel, value); - CHECK_PARAM (pwm < PWM_NUMOF); - CHECK_PARAM (channel < _pwm_dev[pwm].chn_num); - CHECK_PARAM (value <= _pwm_dev[pwm].res); + assert(pwm < PWM_NUMOF); + assert(channel < _CFG.ch_numof); - uint32_t state = irq_disable(); + value = MIN(value, _DEV.res); - _pwm_dev[pwm].chn[channel].duty = value; - _pwm_dev[pwm].chn[channel].used = true; + _DEV.ch[channel].used = true; + _DEV.ch[channel].duty = value * _DEV.hw_res / _DEV.res; + _DEV.ch[channel].hpoint = 0; - /* determine used operator and operator output */ - uint8_t op_idx = channel >> 1; - uint8_t op_out = channel & 0x01; - - /* compute and set shadow register (compare) )value of according channel */ - uint16_t cmp = 0; - switch (_pwm_dev[pwm].mode) { - case PWM_LEFT: cmp = value; - break; - case PWM_RIGHT: cmp = value - 1; - break; - case PWM_CENTER: cmp = _pwm_hw[pwm].regs->timer[0].timer_cfg0.timer_period - value; - break; - } - _pwm_hw[pwm].regs->operators[op_idx].timestamp[op_out].gen = cmp; - - /* set actions for timing events (reset all first) */ - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].val = 0; - - if (op_out == 0) - { - /* channel/output A is used -> set actions for channel A */ - switch (_pwm_dev[pwm].mode) - { - case PWM_LEFT: - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_utez = PWM_OP_ACTION_HIGH; - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_utea = PWM_OP_ACTION_LOW; - break; - - case PWM_RIGHT: - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dtea = PWM_OP_ACTION_HIGH; - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dtep = PWM_OP_ACTION_LOW; - break; - - case PWM_CENTER: - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_utea = PWM_OP_ACTION_HIGH; - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dtea = PWM_OP_ACTION_LOW; - break; - } - } - else { - /* channel/output B is used -> set actions for channel B */ - switch (_pwm_dev[pwm].mode) - { - case PWM_LEFT: - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_utez = PWM_OP_ACTION_HIGH; - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_uteb = PWM_OP_ACTION_LOW; - break; - - case PWM_RIGHT: - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dteb = PWM_OP_ACTION_HIGH; - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dtep = PWM_OP_ACTION_LOW; - break; - - case PWM_CENTER: - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_uteb = PWM_OP_ACTION_HIGH; - _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dteb = PWM_OP_ACTION_LOW; - break; - } + switch (_DEV.mode) { + case PWM_LEFT: + _DEV.ch[channel].hpoint = 0; + break; + case PWM_RIGHT: + _DEV.ch[channel].hpoint = _DEV.hw_res - 1 - _DEV.ch[channel].duty; + break; + case PWM_CENTER: + _DEV.ch[channel].hpoint = (_DEV.hw_res - _DEV.ch[channel].duty) >> 1; + break; } - irq_restore(state); + DEBUG("%s pwm=%u duty=%"PRIu32" hpoint=%"PRIu32"\n", + __func__, pwm, _DEV.ch[channel].duty, _DEV.ch[channel].hpoint); + + unsigned ch = _DEV.ch[channel].ch; /* internal channel mapping */ + + critical_enter(); + ledc_hal_set_duty_int_part(&_DEV.hw, ch, _DEV.ch[channel].duty); + ledc_hal_set_hpoint(&_DEV.hw, ch, _DEV.ch[channel].hpoint); + ledc_hal_set_sig_out_en(&_DEV.hw, ch, true); + ledc_hal_ls_channel_update(&_DEV.hw, ch); + ledc_hal_set_duty_start(&_DEV.hw, ch, true); + critical_exit(); } void pwm_poweron(pwm_t pwm) { - CHECK_PARAM (pwm < PWM_NUMOF); - periph_module_enable(_pwm_hw[pwm].mod); - _pwm_start(pwm); + DEBUG("%s pwm=%u\n", __func__, pwm); + + /* enable and init the module and select the right clock source */ + esp_idf_periph_module_enable(_CFG.module); + ledc_hal_init(&_DEV.hw, _CFG.group); + ledc_hal_set_slow_clk_sel(&_DEV.hw, LEDC_SLOW_CLK_APB); + ledc_hal_set_clock_source(&_DEV.hw, _CFG.timer, LEDC_APB_CLK); + + /* update the timer according to determined parameters */ + ledc_hal_set_clock_divider(&_DEV.hw, _CFG.timer, _DEV.hw_clk_div); + ledc_hal_set_duty_resolution(&_DEV.hw, _CFG.timer, _DEV.hw_res_bit); + ledc_hal_ls_timer_update(&_DEV.hw, _CFG.timer); + ledc_hal_timer_rst(&_DEV.hw, _CFG.timer); + + critical_enter(); + for (unsigned i = 0; i < _CFG.ch_numof; i++) { + /* static configuration of the channel, no fading */ + ledc_hal_set_duty_direction(&_DEV.hw, _DEV.ch[i].ch, 1); + ledc_hal_set_duty_num(&_DEV.hw, _DEV.ch[i].ch, 1); + ledc_hal_set_duty_cycle(&_DEV.hw, _DEV.ch[i].ch, 1); + ledc_hal_set_duty_scale(&_DEV.hw, _DEV.ch[i].ch, 0); + ledc_hal_set_fade_end_intr(&_DEV.hw, _DEV.ch[i].ch, 0); + + /* bind the channel to the timer and disable the output for now */ + ledc_hal_bind_channel_timer(&_DEV.hw, _DEV.ch[i].ch, _CFG.timer); + + /* restore used parameters */ + ledc_hal_set_duty_int_part(&_DEV.hw, _DEV.ch[i].ch, _DEV.ch[i].duty); + ledc_hal_set_hpoint(&_DEV.hw, _DEV.ch[i].ch, _DEV.ch[i].hpoint); + ledc_hal_set_sig_out_en(&_DEV.hw, _DEV.ch[i].ch, _DEV.ch[i].used); + ledc_hal_ls_channel_update(&_DEV.hw, _DEV.ch[i].ch); + ledc_hal_set_duty_start(&_DEV.hw, _DEV.ch[i].ch, _DEV.ch[i].used); + } + _DEV.enabled = true; + critical_exit(); } void pwm_poweroff(pwm_t pwm) { - CHECK_PARAM (pwm < PWM_NUMOF); - _pwm_stop (pwm); - periph_module_disable(_pwm_hw[pwm].mod); -} + DEBUG("%s pwm=%u\n", __func__, pwm); -static void _pwm_start(pwm_t pwm) -{ - pwm_mode_t mode = _pwm_dev[pwm].mode; - uint16_t res = _pwm_dev[pwm].res; - uint32_t freq = _pwm_dev[pwm].freq; - uint32_t period = 0; + if (!_pwm_dev[pwm].enabled) { + return; + } - /* set timer mode */ - switch (mode) { - case PWM_LEFT: - period = res; - _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_UP; - break; - case PWM_RIGHT: - period = res; - _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_DOWN; - break; - case PWM_CENTER: - period = res * 2; - _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_UP_DOWN; + unsigned i; + + /* disable the signal of all channels */ + critical_enter(); + for (i = 0; i < _CFG.ch_numof; i++) { + ledc_hal_set_idle_level(&_DEV.hw, _DEV.ch[i].ch, 0); + ledc_hal_set_sig_out_en(&_DEV.hw, _DEV.ch[i].ch, false); + ledc_hal_set_duty_start(&_DEV.hw, _DEV.ch[i].ch, false); + ledc_hal_ls_channel_update(&_DEV.hw, _DEV.ch[i].ch); + } + _DEV.enabled = false; + critical_exit(); + + /* check whether all devices of the same hardware module are disabled */ + for (i = 0; i < PWM_NUMOF; i++) { + if ((_CFG.module == pwm_config[i].module) && _pwm_dev[i].enabled) { break; + } } - uint32_t cps = period * freq; - /* maximum number of timer clock cycles per second (freq*period) must not - be greater than PWM_CPS_MAX, reduce the freq if necessary and keep - the resolution */ - if (cps > PWM_CPS_MAX) { - freq = PWM_CPS_MAX / period; - _pwm_dev[pwm].freq = freq; - DEBUG("%s freq*res was to high, freq was reduced to %d Hz\n", - __func__, freq); - } - /* minimum number of timer clock cycles per second (freq*period) must not - be less than PWM_CPS_MIN, increase the freq if necessary and keep - the resolution */ - else if (cps < PWM_CPS_MIN) { - freq = PWM_CPS_MIN / period; - _pwm_dev[pwm].freq = freq; - DEBUG("%s freq*res was to low, freq was increased to %d Hz\n", - __func__, freq); - } - - /* determine a suitable pwm clock prescale */ - uint32_t prescale; - if (cps > 1000000) { - /* pwm clock is not scaled, - 8 bit timer prescaler can scale down timer clock to 625 kHz */ - prescale = 1; - } - else if (cps > 100000) { - /* pwm clock is scaled down to 10 MHz, - 8 bit timer prescaler can scale down timer clock to 39,0625 kHz */ - prescale = 16; - } - else if (cps > 10000) { - /* pwm clock is scaled down to 1 MHz - 8 bit timer prescaler can scale down timer clock to 3,90625 kHz */ - prescale = 160; - } - else { - /* pwm clock is scaled down to 640 kHz - 8 bit timer prescaler can scale down timer clock to 2,5 kHz */ - prescale = 250; - } - _pwm_hw[pwm].regs->clk_cfg.clk_prescale = prescale - 1; - - /* set timing parameters (only timer0 is used) */ - _pwm_hw[pwm].regs->timer[0].timer_cfg0.timer_prescale = (PWM_CLK / prescale / cps) - 1; - _pwm_hw[pwm].regs->timer[0].timer_cfg0.timer_period = (mode == PWM_CENTER) ? res : res - 1; - _pwm_hw[pwm].regs->timer[0].timer_cfg0.timer_period_upmethod = PWM_TIMER_UPDATE_IMMIDIATE; - - /* start the timer */ - _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_start = PWM_TIMER_RUNS_ON; - - /* set timer sync phase and enable timer sync input */ - _pwm_hw[pwm].regs->timer[0].timer_sync.timer_phase = 0; - _pwm_hw[pwm].regs->timer[0].timer_sync.timer_synci_en = 1; - - /* set the duty for all channels to start them */ - for (int i = 0; i < _pwm_dev[pwm].chn_num; i++) { - if (_pwm_dev[pwm].chn[i].used) - pwm_set(pwm, i, _pwm_dev[pwm].chn[i].duty); - } - - /* sync all timers */ - for (unsigned i = 0; i < PWM_NUMOF; i++) { - _pwm_hw[i].regs->timer[0].timer_sync.timer_sync_sw = - ~_pwm_hw[i].regs->timer[0].timer_sync.timer_sync_sw; + /* if all devices of the same hardware module are disable, it is powered off */ + if (i == PWM_NUMOF) { + esp_idf_periph_module_disable(_CFG.module); } } -static void _pwm_stop(pwm_t pwm) +void pwm_print_config(void) { - /* disable the timer */ - _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_FREEZE; + for (unsigned pwm = 0; pwm < PWM_NUMOF; pwm++) { + printf("\tPWM_DEV(%d)\tchannels=[ ", pwm); + for (int i = 0; i < _CFG.ch_numof; i++) { + printf("%d ", _CFG.gpios[i]); + } + printf("]\n"); + } } -/* do some static initialization and configuration checks */ -static bool _pwm_configuration(void) +/* do static configuration checks */ +static bool _pwm_initialize(void) { - if (PWM_NUMOF > PWM_NUMOF_MAX) { - LOG_TAG_ERROR("pwm", "%d PWM devices were defined, only %d PWM are " - "supported\n", PWM_NUMOF, PWM_NUMOF_MAX); - return false; - } + unsigned ch_numof[2] = {}; - for (unsigned i = 0; i < PWM_NUMOF; i++) { - if (_pwm_hw[i].gpio_num > PWM_CHANNEL_NUM_DEV_MAX) { + for (unsigned pwm = 0; pwm < PWM_NUMOF; pwm++) { + /* compute the channel indices */ + for (unsigned i = 0; i < _CFG.ch_numof; i++) { + _pwm_dev[pwm].ch[i].ch = ch_numof[_CFG.group] + i; + } + ch_numof[_CFG.group] += _CFG.ch_numof; + if (_CFG.ch_numof > PWM_CH_NUMOF_MAX) { LOG_TAG_ERROR("pwm", "Number of PWM channels of device %d is %d, " - "at maximum only %d channels per PWM device are " - "supported\n", - i, _pwm_hw[i].gpio_num, PWM_CHANNEL_NUM_DEV_MAX); + "only %d channels per PWM device are supported\n", + pwm, _CFG.ch_numof, PWM_CH_NUMOF_MAX); return false; } } + + unsigned total_ch_numof = ch_numof[0] + ch_numof[1]; + + if (total_ch_numof > (SOC_LEDC_CHANNEL_NUM * ARRAY_SIZE(ledc_periph_signal))) { + LOG_TAG_ERROR("pwm", "Total number of PWM channels is %d, only " + "%d channels are supported at maximum\n", total_ch_numof, + PWM_CH_NUMOF_MAX * ARRAY_SIZE(ledc_periph_signal)); + return false; + } + bool multiple_used = false; - for (unsigned i = 0; i < PWM_NUMOF; i++) { - for (unsigned j = 0; j < PWM_NUMOF; j++) { - if (i != j) { - for (unsigned k = 0; k < _pwm_hw[i].gpio_num >> 2; k++) { - for (unsigned l = 0; l < _pwm_hw[j].gpio_num >> 2; l++) { - if (_pwm_hw[i].gpios[k] == _pwm_hw[j].gpios[l]) { - LOG_TAG_ERROR("pwm", "GPIO%d is used multiple times in " - "PWM devices %d and %d\n", - _pwm_hw[i].gpios[k], i, j); - multiple_used = true; - } + for (unsigned pi = 0; pi < PWM_NUMOF; pi++) { + for (unsigned ci = 0; ci < pwm_config[pi].ch_numof; ci++) { + for (unsigned pj = 0; pj < PWM_NUMOF; pj++) { + if (pi == pj) { + continue; + } + for (unsigned cj = 0; cj < pwm_config[pj].ch_numof; cj++) { + if (pwm_config[pi].gpios[ci] == pwm_config[pj].gpios[cj]) { + LOG_TAG_ERROR("pwm", "GPIO%d is used multiple times in " + "PWM devices %d and %d\n", + pwm_config[pi].gpios[ci], pi, pj); + multiple_used = true; } } } @@ -434,22 +376,11 @@ static bool _pwm_configuration(void) return true; } -void pwm_print_config(void) -{ - for (unsigned pwm = 0; pwm < PWM_NUMOF; pwm++) { - printf("\tPWM_DEV(%d)\tchannels=[ ", pwm); - for (int i = 0; i < _pwm_hw[pwm].gpio_num; i++) { - printf("%d ", _pwm_hw[pwm].gpios[i]); - } - printf("]\n"); - } -} - -#else /* defined(PWM0_GPIOS) || defined(PWM1_GPIOS) */ +#else void pwm_print_config(void) { LOG_TAG_INFO("pwm", "no PWM devices\n"); } -#endif /* defined(PWM0_GPIOS) || defined(PWM1_GPIOS) */ +#endif