| /* |
| * Xilinx Processor System MIO / EMIO GPIO controller driver |
| * GPIO bank module |
| * |
| * Copyright (c) 2022, Weidmueller Interface GmbH & Co. KG |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/devicetree.h> |
| |
| #include <zephyr/drivers/gpio.h> |
| #include "gpio_utils.h" |
| #include "gpio_xlnx_ps_bank.h" |
| |
| #define LOG_MODULE_NAME gpio_xlnx_ps_bank |
| #define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(LOG_MODULE_NAME); |
| |
| #define DT_DRV_COMPAT xlnx_ps_gpio_bank |
| |
| /** |
| * @brief GPIO bank pin configuration function |
| * |
| * Configures an individual pin within a MIO / EMIO GPIO pin bank. |
| * The following flags specified by the GPIO subsystem are NOT |
| * supported by the PS GPIO controller: |
| * |
| * - Pull up |
| * - Pull down |
| * - Open drain |
| * - Open source. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * @param pin Index of the pin within the bank to be configured |
| * (decimal index of the pin). |
| * @param flags Flags specifying the pin's configuration. |
| * |
| * @retval 0 if the device initialization completed successfully, |
| * -EINVAL if the specified pin index is out of range, |
| * -ENOTSUP if the pin configuration data contains a flag |
| * that is not supported by the controller. |
| */ |
| static int gpio_xlnx_ps_pin_configure(const struct device *dev, |
| gpio_pin_t pin, |
| gpio_flags_t flags) |
| { |
| const struct gpio_xlnx_ps_bank_dev_cfg *dev_conf = dev->config; |
| uint32_t pin_mask = BIT(pin); |
| uint32_t bank_data; |
| uint32_t dirm_data; |
| uint32_t oen_data; |
| |
| /* Validity of the specified pin index is checked in drivers/gpio.h */ |
| |
| /* Check for config flags not supported by the controller */ |
| if (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN | GPIO_SINGLE_ENDED)) { |
| return -ENOTSUP; |
| } |
| |
| /* Read the data direction & output enable registers */ |
| dirm_data = sys_read32(GPIO_XLNX_PS_BANK_DIRM_REG); |
| oen_data = sys_read32(GPIO_XLNX_PS_BANK_OEN_REG); |
| |
| if (flags & GPIO_OUTPUT) { |
| dirm_data |= pin_mask; |
| oen_data |= pin_mask; |
| |
| /* |
| * Setting of an initial value (see below) requires the |
| * direction register to be written *BEFORE* the data |
| * register, otherwise, the value will not be applied! |
| * The output enable bit can be set after the initial |
| * value has been written. |
| */ |
| sys_write32(dirm_data, GPIO_XLNX_PS_BANK_DIRM_REG); |
| |
| /* |
| * If the current pin is to be configured as an output |
| * pin, it is up to the caller to specify whether the |
| * output's initial value shall be high or low. |
| * -> Write the initial output value into the data register. |
| */ |
| bank_data = sys_read32(GPIO_XLNX_PS_BANK_DATA_REG); |
| if (flags & GPIO_OUTPUT_INIT_HIGH) { |
| bank_data |= pin_mask; |
| } else if (flags & GPIO_OUTPUT_INIT_LOW) { |
| bank_data &= ~pin_mask; |
| } |
| sys_write32(bank_data, GPIO_XLNX_PS_BANK_DATA_REG); |
| |
| /* Set the pin's output enable bit */ |
| sys_write32(oen_data, GPIO_XLNX_PS_BANK_OEN_REG); |
| } else { |
| dirm_data &= ~pin_mask; |
| oen_data &= ~pin_mask; |
| |
| /* |
| * Disable the output first in case of an O -> I |
| * transition, then change the pin's direction. |
| */ |
| sys_write32(oen_data, GPIO_XLNX_PS_BANK_OEN_REG); |
| sys_write32(dirm_data, GPIO_XLNX_PS_BANK_DIRM_REG); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Reads the current bit mask of the entire GPIO pin bank. |
| * |
| * Reads the current bit mask of the entire bank from the |
| * read-only data register. This includes the current values |
| * of not just all input pins, but both the input and output |
| * pins within the bank. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * @param value Pointer to a variable of type gpio_port_value_t |
| * to which the current bit mask read from the bank's |
| * RO data register will be written to. |
| * |
| * @retval 0 if the read operation completed successfully, |
| * -EINVAL if the pointer to the output variable is NULL. |
| */ |
| static int gpio_xlnx_ps_bank_get(const struct device *dev, |
| gpio_port_value_t *value) |
| { |
| const struct gpio_xlnx_ps_bank_dev_cfg *dev_conf = dev->config; |
| |
| *value = sys_read32(GPIO_XLNX_PS_BANK_DATA_REG); |
| return 0; |
| } |
| |
| /** |
| * @brief Masked write of a bit mask for the entire GPIO pin bank. |
| * |
| * Performs a masked write operation on the data register of |
| * the current GPIO pin bank. The mask is applied twice: |
| * first, it is applied to the current contents of the bank's |
| * RO data register, clearing any bits that are zeroes in the |
| * mask (will not have any effect on input pins). Second, it |
| * is applied to the data word to be written into the current |
| * bank's data register. The masked data word read from the |
| * RO data register and the masked data word provided by the |
| * caller ar then OR'ed and written to the bank's data register. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * @param mask Mask to be applied to both the current contents |
| * of the data register and the data word provided |
| * by the caller. |
| * @param value Value to be written to the current bank's data |
| * register. |
| * |
| * @retval Always 0. |
| */ |
| static int gpio_xlnx_ps_bank_set_masked(const struct device *dev, |
| gpio_port_pins_t mask, |
| gpio_port_value_t value) |
| { |
| const struct gpio_xlnx_ps_bank_dev_cfg *dev_conf = dev->config; |
| uint32_t bank_data; |
| |
| bank_data = sys_read32(GPIO_XLNX_PS_BANK_DATA_REG); |
| bank_data = (bank_data & ~mask) | (value & mask); |
| sys_write32(bank_data, GPIO_XLNX_PS_BANK_DATA_REG); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Sets bits in the data register of the GPIO pin bank. |
| * |
| * Sets bits in the data register of the current GPIO pin bank |
| * as a read-modify-write operation. All bits set in the bit |
| * mask provided by the caller are OR'ed into the current data |
| * word of the bank. This operation has no effect on the values |
| * associated with pins configured as inputs. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * @param pins Bit mask specifying which bits shall be set in |
| * the data word of the current GPIO pin bank. |
| * |
| * @retval Always 0. |
| */ |
| static int gpio_xlnx_ps_bank_set_bits(const struct device *dev, |
| gpio_port_pins_t pins) |
| { |
| const struct gpio_xlnx_ps_bank_dev_cfg *dev_conf = dev->config; |
| uint32_t bank_data; |
| |
| bank_data = sys_read32(GPIO_XLNX_PS_BANK_DATA_REG); |
| bank_data |= pins; |
| sys_write32(bank_data, GPIO_XLNX_PS_BANK_DATA_REG); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Clears bits in the data register of the GPIO pin bank. |
| * |
| * Clears bits in the data register of the current GPIO pin bank |
| * as a read-modify-write operation. All bits set in the bit |
| * mask provided by the caller are NAND'ed into the current data |
| * word of the bank. This operation has no effect on the values |
| * associated with pins configured as inputs. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * @param pins Bit mask specifying which bits shall be cleared |
| * in the data word of the current GPIO pin bank. |
| * |
| * @retval Always 0. |
| */ |
| static int gpio_xlnx_ps_bank_clear_bits(const struct device *dev, |
| gpio_port_pins_t pins) |
| { |
| const struct gpio_xlnx_ps_bank_dev_cfg *dev_conf = dev->config; |
| uint32_t bank_data; |
| |
| bank_data = sys_read32(GPIO_XLNX_PS_BANK_DATA_REG); |
| bank_data &= ~pins; |
| sys_write32(bank_data, GPIO_XLNX_PS_BANK_DATA_REG); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Toggles bits in the data register of the GPIO pin bank. |
| * |
| * Toggles bits in the data register of the current GPIO pin bank |
| * as a read-modify-write operation. All bits set in the bit |
| * mask provided by the caller are XOR'ed into the current data |
| * word of the bank. This operation has no effect on the values |
| * associated with pins configured as inputs. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * @param pins Bit mask specifying which bits shall be toggled |
| * in the data word of the current GPIO pin bank. |
| * |
| * @retval Always 0. |
| */ |
| static int gpio_xlnx_ps_bank_toggle_bits(const struct device *dev, |
| gpio_port_pins_t pins) |
| { |
| const struct gpio_xlnx_ps_bank_dev_cfg *dev_conf = dev->config; |
| uint32_t bank_data; |
| |
| bank_data = sys_read32(GPIO_XLNX_PS_BANK_DATA_REG); |
| bank_data ^= pins; |
| sys_write32(bank_data, GPIO_XLNX_PS_BANK_DATA_REG); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Configures the interrupt behaviour of a pin within the |
| * current GPIO bank. |
| * |
| * Configures the interrupt behaviour of a pin within the current |
| * GPIO bank. If a pin is to be configured to trigger an interrupt, |
| * the following modes are supported: |
| * |
| * - edge or level triggered, |
| * - rising edge / high level or falling edge / low level, |
| * - in edge mode only: trigger on both rising and falling edge. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * @param pin Index of the pin within the bank to be configured |
| * (decimal index of the pin). |
| * @param mode Mode configuration: edge, level or interrupt disabled. |
| * @param trig Trigger condition configuration: high/low level or |
| * rising/falling/both edge(s). |
| * |
| * @retval 0 if the interrupt configuration completed successfully, |
| * -EINVAL if the specified pin index is out of range, |
| * -ENOTSUP if the interrupt configuration data contains an |
| * invalid combination of configuration flags. |
| */ |
| static int gpio_xlnx_ps_bank_pin_irq_configure(const struct device *dev, |
| gpio_pin_t pin, |
| enum gpio_int_mode mode, |
| enum gpio_int_trig trig) |
| { |
| const struct gpio_xlnx_ps_bank_dev_cfg *dev_conf = dev->config; |
| uint32_t pin_mask = BIT(pin); |
| uint32_t int_type_data; |
| uint32_t int_polarity_data; |
| uint32_t int_any_data; |
| |
| /* Validity of the specified pin index is checked in drivers/gpio.h */ |
| |
| /* Disable the specified pin's interrupt before (re-)configuring it */ |
| sys_write32(pin_mask, GPIO_XLNX_PS_BANK_INT_DIS_REG); |
| |
| int_type_data = sys_read32(GPIO_XLNX_PS_BANK_INT_TYPE_REG); |
| int_polarity_data = sys_read32(GPIO_XLNX_PS_BANK_INT_POLARITY_REG); |
| int_any_data = sys_read32(GPIO_XLNX_PS_BANK_INT_ANY_REG); |
| |
| if (mode != GPIO_INT_MODE_DISABLED) { |
| |
| if (mode == GPIO_INT_MODE_LEVEL) { |
| int_type_data &= ~pin_mask; |
| } else if (mode == GPIO_INT_MODE_EDGE) { |
| int_type_data |= pin_mask; |
| } else { |
| return -EINVAL; |
| } |
| |
| if (trig == GPIO_INT_TRIG_LOW) { |
| int_any_data &= ~pin_mask; |
| int_polarity_data &= ~pin_mask; |
| } else if (trig == GPIO_INT_TRIG_HIGH) { |
| int_any_data &= ~pin_mask; |
| int_polarity_data |= pin_mask; |
| } else if (trig == GPIO_INT_TRIG_BOTH) { |
| if (mode == GPIO_INT_MODE_LEVEL) { |
| return -EINVAL; |
| } |
| int_any_data |= pin_mask; |
| } |
| |
| } else { /* mode == GPIO_INT_MODE_DISABLED */ |
| int_any_data &= ~pin_mask; |
| int_polarity_data &= ~pin_mask; |
| int_type_data &= ~pin_mask; |
| } |
| |
| sys_write32(int_any_data, GPIO_XLNX_PS_BANK_INT_ANY_REG); |
| sys_write32(int_polarity_data, GPIO_XLNX_PS_BANK_INT_POLARITY_REG); |
| sys_write32(int_type_data, GPIO_XLNX_PS_BANK_INT_TYPE_REG); |
| |
| if (mode != GPIO_INT_MODE_DISABLED) { |
| /* Clear potential stale pending bit before enabling interrupt */ |
| sys_write32(pin_mask, GPIO_XLNX_PS_BANK_INT_STAT_REG); |
| sys_write32(pin_mask, GPIO_XLNX_PS_BANK_INT_EN_REG); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Returns the interrupt status of the current GPIO bank. |
| * |
| * Returns the interrupt status of the current GPIO bank, in the |
| * form of a bit mask where each pin with a pending interrupt is |
| * indicated. This information can either be used by the PM sub- |
| * system or the parent controller device driver, which manages |
| * the interrupt line of the entire PS GPIO controller, regardless |
| * of how many bank sub-devices exist. As the current status is |
| * read, it is automatically cleared. Callback triggering is handled |
| * by the parent controller device. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * |
| * @retval A bit mask indicating for which pins within the bank |
| * an interrupt is pending. |
| */ |
| static uint32_t gpio_xlnx_ps_bank_get_int_status(const struct device *dev) |
| { |
| const struct gpio_xlnx_ps_bank_dev_cfg *dev_conf = dev->config; |
| uint32_t int_status; |
| |
| int_status = sys_read32(GPIO_XLNX_PS_BANK_INT_STAT_REG); |
| if (int_status != 0) { |
| sys_write32(int_status, GPIO_XLNX_PS_BANK_INT_STAT_REG); |
| } |
| |
| return int_status; |
| } |
| |
| /** |
| * @brief Callback management re-direction function. |
| * |
| * Re-directs any callback management calls relating to the current |
| * GPIO bank to the GPIO sub-system. Comp. documentation of the |
| * underlying sub-system's #gpio_manage_callback function. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * @param callback Pointer to a GPIO callback struct. |
| * @param set Callback set flag. |
| * |
| * @retval A bit mask indicating for which pins within the bank |
| * an interrupt is pending. |
| */ |
| static int gpio_xlnx_ps_bank_manage_callback(const struct device *dev, |
| struct gpio_callback *callback, |
| bool set) |
| { |
| struct gpio_xlnx_ps_bank_dev_data *dev_data = dev->data; |
| |
| return gpio_manage_callback(&dev_data->callbacks, callback, set); |
| } |
| |
| /* GPIO bank device driver API */ |
| static const struct gpio_driver_api gpio_xlnx_ps_bank_apis = { |
| .pin_configure = gpio_xlnx_ps_pin_configure, |
| .port_get_raw = gpio_xlnx_ps_bank_get, |
| .port_set_masked_raw = gpio_xlnx_ps_bank_set_masked, |
| .port_set_bits_raw = gpio_xlnx_ps_bank_set_bits, |
| .port_clear_bits_raw = gpio_xlnx_ps_bank_clear_bits, |
| .port_toggle_bits = gpio_xlnx_ps_bank_toggle_bits, |
| .pin_interrupt_configure = gpio_xlnx_ps_bank_pin_irq_configure, |
| .manage_callback = gpio_xlnx_ps_bank_manage_callback, |
| .get_pending_int = gpio_xlnx_ps_bank_get_int_status |
| }; |
| |
| /** |
| * @brief Initialize a MIO / EMIO GPIO bank sub-device |
| * |
| * Initialize a MIO / EMIO GPIO bank sub-device, which is a child |
| * of the parent Xilinx PS GPIO controller device driver. This ini- |
| * tialization function sets up a defined initial state for each |
| * GPIO bank. |
| * |
| * @param dev Pointer to the GPIO bank's device. |
| * |
| * @retval Always 0. |
| */ |
| static int gpio_xlnx_ps_bank_init(const struct device *dev) |
| { |
| const struct gpio_xlnx_ps_bank_dev_cfg *dev_conf = dev->config; |
| |
| sys_write32(~0x0, GPIO_XLNX_PS_BANK_INT_DIS_REG); /* Disable all interrupts */ |
| sys_write32(~0x0, GPIO_XLNX_PS_BANK_INT_STAT_REG); /* Clear all interrupts */ |
| sys_write32(0x0, GPIO_XLNX_PS_BANK_OEN_REG); /* All outputs disabled */ |
| sys_write32(0x0, GPIO_XLNX_PS_BANK_DIRM_REG); /* All pins input */ |
| sys_write32(0x0, GPIO_XLNX_PS_BANK_DATA_REG); /* Zero data register */ |
| |
| return 0; |
| } |
| |
| /* MIO / EMIO bank device definition macros */ |
| #define GPIO_XLNX_PS_BANK_INIT(idx)\ |
| static const struct gpio_xlnx_ps_bank_dev_cfg gpio_xlnx_ps_bank##idx##_cfg = {\ |
| .common = {\ |
| .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(idx),\ |
| },\ |
| .base_addr = DT_REG_ADDR(DT_PARENT(DT_INST(idx, DT_DRV_COMPAT))),\ |
| .bank_index = idx,\ |
| };\ |
| static struct gpio_xlnx_ps_bank_dev_data gpio_xlnx_ps_bank##idx##_data;\ |
| DEVICE_DT_INST_DEFINE(idx, gpio_xlnx_ps_bank_init, NULL,\ |
| &gpio_xlnx_ps_bank##idx##_data, &gpio_xlnx_ps_bank##idx##_cfg,\ |
| PRE_KERNEL_1, CONFIG_GPIO_INIT_PRIORITY, &gpio_xlnx_ps_bank_apis); |
| |
| /* Register & initialize all MIO / EMIO GPIO banks specified in the device tree. */ |
| DT_INST_FOREACH_STATUS_OKAY(GPIO_XLNX_PS_BANK_INIT); |