diff --git a/components/esp_hw_support/esp_clock_output.c b/components/esp_hw_support/esp_clock_output.c index 89397f59fb..0ccf012323 100644 --- a/components/esp_hw_support/esp_clock_output.c +++ b/components/esp_hw_support/esp_clock_output.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include "sdkconfig.h" #include "driver/gpio.h" @@ -16,139 +17,194 @@ #include "soc/soc_caps.h" #include "soc/io_mux_reg.h" -typedef struct gpio_clock_out_ctx { +typedef struct clkout_channel_handle { bool is_mapped; - uint8_t ref_cnt; - clock_out_channel_t clk_out; - gpio_num_t mapped_io; soc_clkout_sig_id_t mapped_clock; -} gpio_clock_out_ctx_t; + uint8_t channel_id; + uint8_t ref_cnt; + uint64_t mapped_io_bmap; + portMUX_TYPE clkout_channel_lock; +} clkout_channel_handle_t; -static const char *TAG = "gpio_output_clk"; +typedef struct esp_clock_output_mapping { + gpio_num_t mapped_io; + clkout_channel_handle_t* clkout_channel_hdl; + uint8_t ref_cnt; + portMUX_TYPE clkout_mapping_lock; + SLIST_ENTRY(esp_clock_output_mapping) next; +} esp_clock_output_mapping_t; -static portMUX_TYPE s_clkout_channel_lock = portMUX_INITIALIZER_UNLOCKED; +static const char *TAG = "esp_clock_output"; -static gpio_clock_out_ctx_t s_pin_ctrl_clk_out[CLKOUT_CHANNEL_MAX] = { +static SLIST_HEAD(esp_clock_output_mapping_head, esp_clock_output_mapping) s_mapping_list = SLIST_HEAD_INITIALIZER(s_mapping_list_head); +static portMUX_TYPE s_mapping_list_lock = portMUX_INITIALIZER_UNLOCKED; +static portMUX_TYPE s_clkout_lock = portMUX_INITIALIZER_UNLOCKED; + +static clkout_channel_handle_t s_clkout_handle[CLKOUT_CHANNEL_MAX] = { [0 ... CLKOUT_CHANNEL_MAX - 1] = { .is_mapped = false, .ref_cnt = 0, + .mapped_io_bmap = 0, + .clkout_channel_lock = portMUX_INITIALIZER_UNLOCKED, } }; -static gpio_clock_out_ctx_t* io_mux_pin_ctrl_clk_out_alloc(soc_clkout_sig_id_t clk_sig, gpio_num_t gpio_num) +static clkout_channel_handle_t* clkout_channel_alloc(soc_clkout_sig_id_t clk_sig, gpio_num_t gpio_num) { - gpio_clock_out_ctx_t *allocated_clk_out = NULL; + clkout_channel_handle_t *allocated_channel = NULL; #if SOC_GPIO_CLOCKOUT_BY_IO_MUX - portENTER_CRITICAL(&s_clkout_channel_lock); - if (!s_pin_ctrl_clk_out[IONUM_TO_CLKOUT(gpio_num)].is_mapped) { - s_pin_ctrl_clk_out[IONUM_TO_CLKOUT(gpio_num)].is_mapped = true; - s_pin_ctrl_clk_out[IONUM_TO_CLKOUT(gpio_num)].clk_out = IONUM_TO_CLKOUT(gpio_num); - allocated_clk_out = &s_pin_ctrl_clk_out[IONUM_TO_CLKOUT(gpio_num)]; - } else if ((s_pin_ctrl_clk_out[IONUM_TO_CLKOUT(gpio_num)].mapped_io == gpio_num) && - (s_pin_ctrl_clk_out[IONUM_TO_CLKOUT(gpio_num)].mapped_clock == clk_sig)) { - allocated_clk_out = &s_pin_ctrl_clk_out[IONUM_TO_CLKOUT(gpio_num)]; + portENTER_CRITICAL(&s_clkout_handle[IONUM_TO_CLKOUT_CHANNEL(gpio_num)].clkout_channel_lock); + if (!s_clkout_handle[IONUM_TO_CLKOUT_CHANNEL(gpio_num)].is_mapped) { + s_clkout_handle[IONUM_TO_CLKOUT_CHANNEL(gpio_num)].is_mapped = true; + allocated_channel = &s_clkout_handle[IONUM_TO_CLKOUT_CHANNEL(gpio_num)]; + } else if ((s_clkout_handle[IONUM_TO_CLKOUT_CHANNEL(gpio_num)].mapped_io_bmap & BIT(gpio_num)) && + (s_clkout_handle[IONUM_TO_CLKOUT_CHANNEL(gpio_num)].mapped_clock == clk_sig)) { + allocated_channel = &s_clkout_handle[IONUM_TO_CLKOUT_CHANNEL(gpio_num)]; } - portEXIT_CRITICAL(&s_clkout_channel_lock); + allocated_channel->channel_id = (clock_out_channel_t)IONUM_TO_CLKOUT_CHANNEL(gpio_num); + portEXIT_CRITICAL(&s_clkout_handle[IONUM_TO_CLKOUT_CHANNEL(gpio_num)].clkout_channel_lock); #elif SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX - for(uint32_t i = 0; i < CLKOUT_CHANNEL_MAX; i++) { - portENTER_CRITICAL(&s_clkout_channel_lock); - if (!s_pin_ctrl_clk_out[i].is_mapped) { - s_pin_ctrl_clk_out[i].is_mapped = true; - s_pin_ctrl_clk_out[i].clk_out = (clock_out_channel_t)i; - allocated_clk_out = &s_pin_ctrl_clk_out[i]; - portEXIT_CRITICAL(&s_clkout_channel_lock); + for(uint32_t channel = 0; channel < CLKOUT_CHANNEL_MAX; channel++) { + portENTER_CRITICAL(&s_clkout_handle[channel].clkout_channel_lock); + if (!s_clkout_handle[channel].is_mapped) { + s_clkout_handle[channel].is_mapped = true; + allocated_channel = &s_clkout_handle[channel]; + allocated_channel->channel_id = (clock_out_channel_t)channel; + portEXIT_CRITICAL(&s_clkout_handle[channel].clkout_channel_lock); break; - } else if ((s_pin_ctrl_clk_out[i].mapped_io == gpio_num) && - (s_pin_ctrl_clk_out[i].mapped_clock == clk_sig)) { - allocated_clk_out = &s_pin_ctrl_clk_out[i]; - portEXIT_CRITICAL(&s_clkout_channel_lock); + } else if (s_clkout_handle[channel].mapped_clock == clk_sig) { + allocated_channel = &s_clkout_handle[channel]; + portEXIT_CRITICAL(&s_clkout_handle[channel].clkout_channel_lock); break; } - portEXIT_CRITICAL(&s_clkout_channel_lock); + portEXIT_CRITICAL(&s_clkout_handle[channel].clkout_channel_lock); } #endif - if (allocated_clk_out != NULL) { - portENTER_CRITICAL(&s_clkout_channel_lock); - allocated_clk_out->mapped_io = gpio_num; - allocated_clk_out->mapped_clock = clk_sig; - allocated_clk_out->ref_cnt++; - portEXIT_CRITICAL(&s_clkout_channel_lock); + if (allocated_channel != NULL) { + portENTER_CRITICAL(&allocated_channel->clkout_channel_lock); + allocated_channel->mapped_io_bmap |= BIT(gpio_num); + allocated_channel->mapped_clock = clk_sig; + allocated_channel->ref_cnt++; + + if (allocated_channel->ref_cnt == 1) { + portENTER_CRITICAL(&s_clkout_lock); + gpio_ll_set_pin_ctrl(clk_sig, CLKOUT_CHANNEL_MASK(allocated_channel->channel_id), CLKOUT_CHANNEL_SHIFT(allocated_channel->channel_id)); + portEXIT_CRITICAL(&s_clkout_lock); + } + portEXIT_CRITICAL(&allocated_channel->clkout_channel_lock); } - return allocated_clk_out; + return allocated_channel; } -static bool io_mux_pin_ctrl_clk_out_sig_try_free(gpio_clock_out_handle_t clk_out_hdl) +static esp_clock_output_mapping_t* clkout_mapping_alloc(clkout_channel_handle_t* channel_hdl, gpio_num_t gpio_num) { - bool do_free = false; - portENTER_CRITICAL(&s_clkout_channel_lock); - if (--clk_out_hdl->ref_cnt == 0) { - do_free = true; - clk_out_hdl->is_mapped = false; + esp_clock_output_mapping_t *allocated_mapping = NULL; + + portENTER_CRITICAL(&s_mapping_list_lock); + esp_clock_output_mapping_t *hdl; + SLIST_FOREACH(hdl, &s_mapping_list, next) { + if ((hdl->clkout_channel_hdl == channel_hdl) && (hdl->mapped_io == gpio_num)) { + allocated_mapping = hdl; + } } - portEXIT_CRITICAL(&s_clkout_channel_lock); - return do_free; -} + portEXIT_CRITICAL(&s_mapping_list_lock); -esp_err_t esp_clock_output_start(soc_clkout_sig_id_t clk_sig, gpio_num_t gpio_num, gpio_clock_out_handle_t *clk_out_hdl) -{ - ESP_RETURN_ON_FALSE((clk_out_hdl != NULL), ESP_ERR_INVALID_ARG, TAG, "Clock out handle passed in is invalid"); - - ESP_RETURN_ON_FALSE(IS_VALID_CLKOUT_IO(gpio_num), ESP_ERR_INVALID_ARG, TAG, "%s", "Output GPIO number error"); - - gpio_clock_out_ctx_t* new_hdl= io_mux_pin_ctrl_clk_out_alloc(clk_sig, gpio_num); + if (allocated_mapping == NULL) { + allocated_mapping = (esp_clock_output_mapping_t *)malloc(sizeof(esp_clock_output_mapping_t)); + allocated_mapping->mapped_io = gpio_num; + allocated_mapping->clkout_channel_hdl = channel_hdl; + allocated_mapping->ref_cnt = 0; + portMUX_INITIALIZE(&allocated_mapping->clkout_mapping_lock); + portENTER_CRITICAL(&s_mapping_list_lock); + SLIST_INSERT_HEAD(&s_mapping_list, allocated_mapping, next); + portEXIT_CRITICAL(&s_mapping_list_lock); + } + portENTER_CRITICAL(&allocated_mapping->clkout_mapping_lock); + allocated_mapping->ref_cnt++; + if (allocated_mapping->ref_cnt == 1) { #if SOC_GPIO_CLOCKOUT_BY_IO_MUX - ESP_RETURN_ON_FALSE((new_hdl != NULL), ESP_ERR_INVALID_ARG, TAG, "Selected clock out IO is already mapped to other internal clock source"); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[gpio_num], CLKOUT_CHANNEL_TO_IOMUX_FUNC(allocated_mapping->clkout_channel_hdl->channel_id)); #elif SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX - ESP_RETURN_ON_FALSE((new_hdl != NULL), ESP_FAIL, TAG, "Maximum support for %d output clock signals, no available clock_out channel for assignment", CLKOUT_CHANNEL_MAX); -#endif - - if (new_hdl->ref_cnt == 1) { - uint32_t clk_out_mask = (new_hdl->clk_out == CLKOUT_CHANNEL_1) ? CLK_OUT1 : - (new_hdl->clk_out == CLKOUT_CHANNEL_2) ? CLK_OUT2 : - (new_hdl->clk_out == CLKOUT_CHANNEL_3) ? CLK_OUT3 : 0; - uint32_t clk_out_shift = (new_hdl->clk_out == CLKOUT_CHANNEL_1) ? CLK_OUT1_S : - (new_hdl->clk_out == CLKOUT_CHANNEL_2) ? CLK_OUT2_S : - (new_hdl->clk_out == CLKOUT_CHANNEL_3) ? CLK_OUT3_S : 0; - - portENTER_CRITICAL(&s_clkout_channel_lock); - gpio_ll_set_pin_ctrl(clk_sig, clk_out_mask, clk_out_shift); - portEXIT_CRITICAL(&s_clkout_channel_lock); - -#if SOC_GPIO_CLOCKOUT_BY_IO_MUX - uint32_t clk_out_func = (new_hdl->clk_out == CLKOUT_CHANNEL_1) ? FUNC_CLK_OUT1 : - (new_hdl->clk_out == CLKOUT_CHANNEL_2) ? FUNC_CLK_OUT2 : - (new_hdl->clk_out == CLKOUT_CHANNEL_3) ? FUNC_CLK_OUT3 : 0; - - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[gpio_num], clk_out_func); -#elif SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX - uint32_t gpio_clk_out_sig_idx = (new_hdl->clk_out == CLKOUT_CHANNEL_1) ? CLK_OUT_OUT1_IDX : - (new_hdl->clk_out == CLKOUT_CHANNEL_2) ? CLK_OUT_OUT2_IDX : - (new_hdl->clk_out == CLKOUT_CHANNEL_3) ? CLK_OUT_OUT3_IDX : SIG_GPIO_OUT_IDX; - gpio_set_pull_mode(gpio_num, GPIO_FLOATING); gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[gpio_num], PIN_FUNC_GPIO); gpio_set_direction(gpio_num, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(gpio_num, gpio_clk_out_sig_idx, false, false); + esp_rom_gpio_connect_out_signal(gpio_num, CLKOUT_CHANNEL_TO_GPIO_SIG_ID(allocated_mapping->clkout_channel_hdl->channel_id), false, false); #endif } + portEXIT_CRITICAL(&allocated_mapping->clkout_mapping_lock); + return allocated_mapping; +} + +static void clkout_channel_free(clkout_channel_handle_t *channel_hdl) +{ + portENTER_CRITICAL(&channel_hdl->clkout_channel_lock); + if (--channel_hdl->ref_cnt == 0) { + channel_hdl->mapped_clock = CLKOUT_SIG_INVALID; + portENTER_CRITICAL(&s_clkout_lock); + gpio_ll_set_pin_ctrl(0, CLKOUT_CHANNEL_MASK(channel_hdl->channel_id), CLKOUT_CHANNEL_SHIFT(channel_hdl->channel_id)); + portEXIT_CRITICAL(&s_clkout_lock); + channel_hdl->is_mapped = false; + } + portEXIT_CRITICAL(&channel_hdl->clkout_channel_lock); +} + +static void clkout_mapping_free(esp_clock_output_mapping_t *mapping_hdl) +{ + portENTER_CRITICAL(&mapping_hdl->clkout_mapping_lock); + clkout_channel_free(mapping_hdl->clkout_channel_hdl); + bool do_free_mapping_hdl = false; + if (--mapping_hdl->ref_cnt == 0) { + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[mapping_hdl->mapped_io], PIN_FUNC_GPIO); + esp_rom_gpio_connect_out_signal(mapping_hdl->mapped_io, SIG_GPIO_OUT_IDX, false, false); + gpio_set_direction(mapping_hdl->mapped_io, GPIO_MODE_DISABLE); + + portENTER_CRITICAL(&mapping_hdl->clkout_channel_hdl->clkout_channel_lock); + mapping_hdl->clkout_channel_hdl->mapped_io_bmap &= ~BIT(mapping_hdl->mapped_io); + portEXIT_CRITICAL(&mapping_hdl->clkout_channel_hdl->clkout_channel_lock); + + portENTER_CRITICAL(&s_mapping_list_lock); + SLIST_REMOVE(&s_mapping_list, mapping_hdl, esp_clock_output_mapping, next); + portEXIT_CRITICAL(&s_mapping_list_lock); + do_free_mapping_hdl = true; + } + portEXIT_CRITICAL(&mapping_hdl->clkout_mapping_lock); + + if (do_free_mapping_hdl) { + free(mapping_hdl); + } +} + +esp_err_t esp_clock_output_start(soc_clkout_sig_id_t clk_sig, gpio_num_t gpio_num, esp_clock_output_mapping_handle_t *clkout_mapping_ret_hdl) +{ + ESP_RETURN_ON_FALSE((clkout_mapping_ret_hdl != NULL), ESP_ERR_INVALID_ARG, TAG, "Clock out mapping handle passed in is invalid"); + ESP_RETURN_ON_FALSE(IS_VALID_CLKOUT_IO(gpio_num), ESP_ERR_INVALID_ARG, TAG, "%s", "Output GPIO number error"); + + esp_clock_output_mapping_t *hdl; + SLIST_FOREACH(hdl, &s_mapping_list, next) { + ESP_RETURN_ON_FALSE(!((hdl->mapped_io == gpio_num) && (hdl->clkout_channel_hdl->mapped_clock != clk_sig)), ESP_ERR_INVALID_ARG, TAG, "Selected io is already mapped by another signal"); + } + + clkout_channel_handle_t *channel_hdl = clkout_channel_alloc(clk_sig, gpio_num); +#if SOC_GPIO_CLOCKOUT_BY_IO_MUX + ESP_RETURN_ON_FALSE((channel_hdl != NULL), ESP_ERR_INVALID_ARG, TAG, "Selected clock out IO is already mapped to other internal clock source"); +#elif SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX + ESP_RETURN_ON_FALSE((channel_hdl != NULL), ESP_FAIL, TAG, "Maximum support for %d output clock signals, no available clock_out channel for assignment", CLKOUT_CHANNEL_MAX); +#endif + + *clkout_mapping_ret_hdl = clkout_mapping_alloc(channel_hdl, gpio_num); - *clk_out_hdl = new_hdl; return ESP_OK; } -esp_err_t esp_clock_output_stop(gpio_clock_out_handle_t clk_out_hdl) +esp_err_t esp_clock_output_stop(esp_clock_output_mapping_handle_t clkout_mapping_hdl) { - assert(clk_out_hdl != NULL); - ESP_RETURN_ON_FALSE(clk_out_hdl->is_mapped, ESP_ERR_INVALID_STATE, TAG, "%s", "Clock outputting is already disabled"); - if (io_mux_pin_ctrl_clk_out_sig_try_free(clk_out_hdl)) { - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[clk_out_hdl->mapped_io], PIN_FUNC_GPIO); - esp_rom_gpio_connect_out_signal(clk_out_hdl->mapped_io, SIG_GPIO_OUT_IDX, false, false); - gpio_set_direction(clk_out_hdl->mapped_io, GPIO_MODE_DISABLE); - } + ESP_RETURN_ON_FALSE((clkout_mapping_hdl != NULL), ESP_ERR_INVALID_ARG, TAG, "Clock out mapping handle passed in is invalid"); + ESP_RETURN_ON_FALSE(clkout_mapping_hdl->ref_cnt > 0, ESP_ERR_INVALID_STATE, TAG, "%s", "Clock outputting is already disabled"); + clkout_mapping_free(clkout_mapping_hdl); return ESP_OK; } diff --git a/components/esp_hw_support/include/esp_clock_output.h b/components/esp_hw_support/include/esp_clock_output.h index 1ccf58a6a6..33c87f9cec 100644 --- a/components/esp_hw_support/include/esp_clock_output.h +++ b/components/esp_hw_support/include/esp_clock_output.h @@ -21,31 +21,31 @@ extern "C" { #endif #if SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX || SOC_GPIO_CLOCKOUT_BY_IO_MUX -typedef struct gpio_clock_out_ctx* gpio_clock_out_handle_t; +typedef struct esp_clock_output_mapping *esp_clock_output_mapping_handle_t; /** * @brief Start output specified clock signal to specified GPIO, will also - * initialize the clk_out_hdl. + * initialize the clkout_mapping_ret_hdl. * * @param[in] clk_src The clock signal source to be mapped to GPIOs * @param[in] gpio_num GPIO number to be mapped soc_root_clk signal source - * @param[out] clk_out_hdl Clock output controll handler + * @param[out] clkout_mapping_ret_hdl Clock output controll handler * @return * - ESP_OK: Output specified clock signal to specified GPIO successfully * - ESP_ERR_INVALID_ARG: Specified GPIO not supported to output internal clock * or specified GPIO is already mapped to other internal clock source. * - ESP_FAIL: There are no clock out signals that can be allocated. */ -esp_err_t esp_clock_output_start(soc_clkout_sig_id_t clk_sig, gpio_num_t gpio_num, gpio_clock_out_handle_t *clk_out_hdl); +esp_err_t esp_clock_output_start(soc_clkout_sig_id_t clk_sig, gpio_num_t gpio_num, esp_clock_output_mapping_handle_t *clkout_mapping_ret_hdl); /** * @brief Stop clock signal to GPIO outputting - * @param[in] clk_out_hdl Clock output controll handle + * @param[in] clkout_mapping_hdl Clock output mapping controll handle * @return * - ESP_OK: Disable the clock output on GPIO successfully * - ESP_ERR_INVALID_STATE The clock in handle is already in the disabled state */ -esp_err_t esp_clock_output_stop(gpio_clock_out_handle_t clk_out_hdl); +esp_err_t esp_clock_output_stop(esp_clock_output_mapping_handle_t clkout_mapping_hdl); #endif #ifdef __cplusplus diff --git a/components/esp_hw_support/include/esp_private/clkout_channel.h b/components/esp_hw_support/include/esp_private/clkout_channel.h index ff6e580273..2daa0d447f 100644 --- a/components/esp_hw_support/include/esp_private/clkout_channel.h +++ b/components/esp_hw_support/include/esp_private/clkout_channel.h @@ -34,14 +34,27 @@ typedef enum clock_out_channel { #define FUNC_CLK_OUT2 FUNC_GPIO19_CLK_OUT2 #define FUNC_CLK_OUT3 FUNC_DAC_2_CLK_OUT3 #endif +#define IONUM_TO_CLKOUT_CHANNEL(gpio_num) ((gpio_num == CLKOUT_CHANNEL1_GPIO) ? CLKOUT_CHANNEL_1 : \ + (gpio_num == CLKOUT_CHANNEL2_GPIO) ? CLKOUT_CHANNEL_2 : \ + (gpio_num == CLKOUT_CHANNEL3_GPIO) ? CLKOUT_CHANNEL_3 : 0) +#define CLKOUT_CHANNEL_TO_IOMUX_FUNC(channel) ((channel == CLKOUT_CHANNEL_1) ? FUNC_CLK_OUT1 : \ + (channel == CLKOUT_CHANNEL_2) ? FUNC_CLK_OUT2 : \ + (channel == CLKOUT_CHANNEL_3) ? FUNC_CLK_OUT3 : 0) #define IS_VALID_CLKOUT_IO(gpio_num) ((gpio_num == CLKOUT_CHANNEL1_GPIO) || (gpio_num == CLKOUT_CHANNEL2_GPIO) || (gpio_num == CLKOUT_CHANNEL3_GPIO)) #elif SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX +#define CLKOUT_CHANNEL_TO_GPIO_SIG_ID(channel) ((channel == CLKOUT_CHANNEL_1) ? CLK_OUT_OUT1_IDX : \ + (channel == CLKOUT_CHANNEL_2) ? CLK_OUT_OUT2_IDX : \ + (channel == CLKOUT_CHANNEL_3) ? CLK_OUT_OUT3_IDX : SIG_GPIO_OUT_IDX) #define IS_VALID_CLKOUT_IO(gpio_num) GPIO_IS_VALID_GPIO(gpio_num) #endif -#define IONUM_TO_CLKOUT(gpio_num) ((gpio_num == CLKOUT_CHANNEL1_GPIO) ? CLKOUT_CHANNEL_1 : \ - (gpio_num == CLKOUT_CHANNEL2_GPIO) ? CLKOUT_CHANNEL_2 : \ - (gpio_num == CLKOUT_CHANNEL3_GPIO) ? CLKOUT_CHANNEL_3 : 0) +#define CLKOUT_CHANNEL_MASK(channel) ((channel == CLKOUT_CHANNEL_1) ? CLK_OUT1 : \ + (channel == CLKOUT_CHANNEL_2) ? CLK_OUT2 : \ + (channel == CLKOUT_CHANNEL_3) ? CLK_OUT3 : 0) + +#define CLKOUT_CHANNEL_SHIFT(channel) ((channel == CLKOUT_CHANNEL_1) ? CLK_OUT1_S : \ + (channel == CLKOUT_CHANNEL_2) ? CLK_OUT2_S : \ + (channel == CLKOUT_CHANNEL_3) ? CLK_OUT3_S : 0) #ifdef __cplusplus }