-
Notifications
You must be signed in to change notification settings - Fork 50
core: pinmux: improve dynamic pinmux configuration at runtime #415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| /* | ||
| * Copyright (c) 2022 Dhruva Gole | ||
| * | ||
|
|
@@ -6,20 +6,152 @@ | |
|
|
||
| #include <Arduino.h> | ||
| #include "zephyrInternal.h" | ||
| #include <zephyr/drivers/pinctrl.h> | ||
|
|
||
| #include <zephyr/spinlock.h> | ||
|
|
||
| // create an array of arduino_pins with functions to reinitialize pins if needed | ||
| static const struct device *pinmux_array[DT_PROP_LEN(DT_PATH(zephyr_user), digital_pin_gpios)] = { | ||
| nullptr}; | ||
| #if defined(ARDUINO) | ||
| /* | ||
| * The global ARDUINO macro is numeric (e.g. 10607) in Arduino builds. | ||
| * Temporarily hide it so pinctrl token concatenation can use the literal | ||
| * custom state name "ARDUINO" from devicetree pinctrl-names. | ||
| * Otherwise, the generated pinctrl state identifiers would be like PINCTRL_STATE_10607 instead of | ||
| * PINCTRL_STATE_ARDUINO. | ||
| */ | ||
| #pragma push_macro("ARDUINO") | ||
| #undef ARDUINO | ||
| #endif | ||
|
|
||
| /* | ||
| * Pinctrl configuration structures for dynamic pin switching. | ||
| * | ||
| * Map deferred-init peripherals with their pinctrl configuration from devicetree. | ||
| */ | ||
| #define PINCTRL_NODE_DEFERRED(node_id) DT_PROP(node_id, zephyr_deferred_init) | ||
|
|
||
| #define PINCTRL_DECLARE_IF_DEFERRED(node_id) \ | ||
| COND_CODE_1(PINCTRL_NODE_DEFERRED(node_id), (PINCTRL_DT_DEV_CONFIG_DECLARE(node_id);), ()) | ||
|
|
||
| DT_FOREACH_STATUS_OKAY_NODE(PINCTRL_DECLARE_IF_DEFERRED) | ||
|
|
||
| #if DT_HAS_ALIAS(arduino_log_uart) | ||
| PINCTRL_DT_DEV_CONFIG_DECLARE(DT_ALIAS(arduino_log_uart)); | ||
| #endif | ||
|
|
||
| struct pinctrl_map_entry { | ||
| const struct device *dev; | ||
| const struct pinctrl_dev_config *pcfg; | ||
| }; | ||
|
|
||
| #define PINCTRL_MAP_ENTRY(node_id) {DEVICE_DT_GET(node_id), PINCTRL_DT_DEV_CONFIG_GET(node_id)}, | ||
| #define PINCTRL_MAP_ENTRY_IF_PRESENT(node_id) \ | ||
| COND_CODE_1(PINCTRL_NODE_DEFERRED(node_id), (PINCTRL_MAP_ENTRY(node_id)), ()) | ||
|
|
||
| /* Add the pinctrl_dev_config for the log-uart nodes, that are not deferred init */ | ||
| #if DT_HAS_ALIAS(arduino_log_uart) | ||
| #define PINCTRL_MAP_LOG_UART_ENTRY PINCTRL_MAP_ENTRY(DT_ALIAS(arduino_log_uart)) | ||
| #else | ||
| #define PINCTRL_MAP_LOG_UART_ENTRY | ||
| #endif | ||
|
|
||
| static const struct pinctrl_map_entry pinctrl_map[] = {DT_FOREACH_STATUS_OKAY_NODE( | ||
| PINCTRL_MAP_ENTRY_IF_PRESENT) PINCTRL_MAP_LOG_UART_ENTRY{nullptr, nullptr}}; | ||
|
|
||
| #if defined(ARDUINO) | ||
| #pragma pop_macro("ARDUINO") | ||
| #endif | ||
|
|
||
| /* Get pinctrl_dev_config for a device from the generated map. */ | ||
| static const struct pinctrl_dev_config *get_known_pcfg(const struct device *dev) { | ||
| for (size_t i = 0; i < ARRAY_SIZE(pinctrl_map); i++) { | ||
| if (pinctrl_map[i].dev == dev) { | ||
| return pinctrl_map[i].pcfg; | ||
| } | ||
| } | ||
|
|
||
| return nullptr; | ||
| } | ||
|
|
||
| /** | ||
| * @brief Initialize the peripheral and acquire a single pin to ARDUINO state. | ||
| * | ||
| * Switches peripheral pins back to ARDUINO pinctrl state (alternate function), | ||
| * typically after a temporary transition to GPIO mode. | ||
| * | ||
| * @param dev Pointer to the peripheral device | ||
| * @param state_pin_idx Index of the pin within the device's ARDUINO pinctrl state | ||
| * @return 0 on success, negative on error | ||
| */ | ||
| int init_dev_apply_channel_pinctrl(const struct device *dev, size_t state_pin_idx) { | ||
|
|
||
| if (dev == nullptr) { | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| if (!device_is_ready(dev)) { | ||
| // init device for first usage, if not ready | ||
| int err = device_init(dev); | ||
| if (err < 0) { | ||
| return err; | ||
| } | ||
| } | ||
|
|
||
| const struct pinctrl_state *state; | ||
| const struct pinctrl_dev_config *pcfg = get_known_pcfg(dev); | ||
|
|
||
| void _reinit_peripheral_if_needed(pin_size_t pin, const struct device *dev) { | ||
| if (pinmux_array[pin] != dev) { | ||
| pinmux_array[pin] = dev; | ||
| if (dev != NULL) { | ||
| dev->ops.init(dev); | ||
| if (pcfg == nullptr) { | ||
| /* Device not in DT mapping - add to pinctrl_map if needed */ | ||
| return -ENOTSUP; | ||
| } | ||
|
|
||
| int err = pinctrl_lookup_state(pcfg, PINCTRL_STATE_ARDUINO, &state); | ||
| if (err < 0) { | ||
| return err; /* Fails if the state is not defined in pinctrl-names */ | ||
| } | ||
|
|
||
| /* bounds check */ | ||
| if (state_pin_idx >= state->pin_cnt) { | ||
| return -ERANGE; | ||
| } | ||
|
|
||
| /* | ||
| * On platforms without CONFIG_PINCTRL_STORE_REG (e.g. STM32) the pcfg->reg is not present but | ||
| * the argument is ignored by their pinctrl driver, so passing PINCTRL_REG_NONE is safe. | ||
| */ | ||
| #ifdef CONFIG_PINCTRL_STORE_REG | ||
| uintptr_t reg = pcfg->reg; | ||
| #else | ||
| uintptr_t reg = PINCTRL_REG_NONE; | ||
| #endif | ||
|
|
||
| return pinctrl_configure_pins(&state->pins[state_pin_idx], 1, reg); | ||
| } | ||
|
|
||
| /** | ||
| * @brief Optimize peripheral transitions applying pinctrl state PINCTRL_STATE_DEFAULT. | ||
| * | ||
| * @param dev Target peripheral device to acquire pin for | ||
| */ | ||
| int init_dev_apply_pinctrl(const struct device *dev) { | ||
|
|
||
| if (dev == nullptr) { | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| if (!device_is_ready(dev)) { | ||
| int ret = device_init(dev); | ||
| if (ret != 0 && ret != -EALREADY) { | ||
| return ret; | ||
| } | ||
| } | ||
|
|
||
| const struct pinctrl_dev_config *pcfg = get_known_pcfg(dev); | ||
| if (pcfg == nullptr) { | ||
| /* Device not in DT mapping - add to pinctrl_map if needed */ | ||
| return -ENOTSUP; | ||
| } | ||
|
|
||
| return pinctrl_apply_state(pcfg, PINCTRL_STATE_DEFAULT); | ||
| } | ||
|
|
||
| static const struct gpio_dt_spec arduino_pins[] = { | ||
|
|
@@ -143,6 +275,24 @@ | |
| } | ||
| } | ||
|
|
||
| /* | ||
| * Resolve pin index in a device ARDUINO pinctrl state from a DT spec array. | ||
| * The resulting index is the per-device ordinal at spec_idx. | ||
| */ | ||
| template <typename DT_SPEC, size_t N> | ||
| static size_t state_pin_index_from_spec_index(const DT_SPEC (&specs)[N], size_t spec_idx) { | ||
| const struct device *dev = specs[spec_idx].dev; | ||
| size_t state_pin_idx = 0; | ||
|
|
||
| for (size_t i = 0; i < spec_idx; i++) { | ||
| if (specs[i].dev == dev) { | ||
| state_pin_idx++; | ||
| } | ||
| } | ||
|
|
||
| return state_pin_idx; | ||
| } | ||
|
Comment on lines
+282
to
+294
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, the goal here is not to find a peripheral channel number; it is to find the correct pinctrl entry for the selected Arduino pin: for example on GIGA, A0 maps to adc1_inp4_pc4, and D3 maps to tim2_ch3_pa2. There isn't a Zephyr generic API to map an arbitrary peripheral channel back to the specific pinctrl entry required by pinctrl_configure_pins(), so we derive the pinctrl entry from the board DT ordering. |
||
|
|
||
| #ifdef CONFIG_PWM | ||
|
|
||
| #define PWM_DT_SPEC(n, p, i) PWM_DT_SPEC_GET_BY_IDX(n, i), | ||
|
|
@@ -216,7 +366,6 @@ | |
| static const struct dac_channel_cfg dac_ch_cfg[] = { | ||
| DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), dac_channels, DAC_CHANNEL_DEFINE)}; | ||
|
|
||
| static bool dac_channel_initialized[NUM_OF_DACS]; | ||
| #endif | ||
|
|
||
| #endif | ||
|
|
@@ -251,7 +400,6 @@ | |
| void pinMode(pin_size_t pinNumber, PinMode pinMode) { | ||
| RETURN_ON_INVALID_PIN(pinNumber); | ||
|
|
||
| _reinit_peripheral_if_needed(pinNumber, NULL); | ||
| if (pinMode == INPUT) { // input mode | ||
| gpio_pin_configure_dt(&arduino_pins[pinNumber], GPIO_INPUT | GPIO_ACTIVE_HIGH); | ||
| } else if (pinMode == INPUT_PULLUP) { // input with internal pull-up | ||
|
|
@@ -486,13 +634,15 @@ | |
| return; | ||
| } | ||
|
|
||
| (void)init_dev_apply_channel_pinctrl(arduino_pwm[idx].dev, | ||
| state_pin_index_from_spec_index(arduino_pwm, idx)); | ||
|
|
||
| if (!pwm_is_ready_dt(&arduino_pwm[idx])) { | ||
| pinMode(pinNumber, OUTPUT); | ||
| digitalWrite(pinNumber, value > 127 ? HIGH : LOW); | ||
| return; | ||
| } | ||
|
|
||
| _reinit_peripheral_if_needed(pinNumber, arduino_pwm[idx].dev); | ||
| value = CLAMP(value, 0, maxInput); | ||
|
|
||
| const uint32_t pulse = map64(value, 0, maxInput, 0, arduino_pwm[idx].period); | ||
|
|
@@ -516,19 +666,13 @@ | |
| return; | ||
| } | ||
|
|
||
| if (!dac_channel_initialized[dacName]) { | ||
| if (!device_is_ready(dac_dev)) { | ||
| return; | ||
| } | ||
| // TODO: add reverse map to find pin name from DAC* define | ||
| // In the meantime, consider A0 == DAC0 | ||
| (void)init_dev_apply_pinctrl(dac_dev); | ||
|
|
||
| // TODO: add reverse map to find pin name from DAC* define | ||
| // In the meantime, consider A0 == DAC0 | ||
| _reinit_peripheral_if_needed((pin_size_t)(dacName + A0), dac_dev); | ||
| ret = dac_channel_setup(dac_dev, &dac_ch_cfg[dacName]); | ||
| if (ret != 0) { | ||
| return; | ||
| } | ||
| dac_channel_initialized[dacName] = true; | ||
| ret = dac_channel_setup(dac_dev, &dac_ch_cfg[dacName]); | ||
| if (ret != 0) { | ||
| return; | ||
| } | ||
|
|
||
| value = CLAMP(value, 0, maxInput); | ||
|
|
@@ -586,7 +730,13 @@ | |
| return -ENOTSUP; | ||
| } | ||
|
|
||
| _reinit_peripheral_if_needed(pinNumber, arduino_adc[idx].dev); | ||
| /* | ||
| * Init the ADC device for the first acquisition and restore only the required pin to analog mode when | ||
| * transitioning from GPIO. The pin is selected from the ADC device "arduino" pinctrl state. Not | ||
| * checking the return value because the device might not have pinctrl (e.g. nRF SAADC). | ||
| */ | ||
| (void)init_dev_apply_channel_pinctrl(arduino_adc[idx].dev, | ||
| state_pin_index_from_spec_index(arduino_adc, idx)); | ||
|
|
||
| err = adc_channel_setup(arduino_adc[idx].dev, &arduino_adc[idx].channel_cfg); | ||
| if (err < 0) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly defining all entries here, or those that have a
PINCTRL_STATE_ARDUINO, is better because it allows other devices (e.g serial, DAC) to be used in the same way.