zephyr/drivers/gpio/gpio_xlnx_ps_bank.c
Robert Hancock 83488e4354 drivers: gpio: xlnx_ps: Do not clear GPIO states on initialization
This driver was setting all GPIO lines to input and the data register to
zero on initialization. This does not appear to be common practice among
other GPIO drivers, and in fact caused a serious problem on the ZynqMP
platform, where between 1 and 4 of the top-most GPIO lines are
frequently used by platform firmware and Vivado as reset lines for the
programmable logic. Since these resets are active low, and their
input/output state is ignored due to how they are connected to the EMIO
GPIO outputs from the PS, this caused the PL reset to be asserted when
the GPIO driver initialized, preventing any logic using that reset from
functioning properly.

There may also be other cases where GPIO line states have already been
set by the boot loader or firmware and clearing them may result in
improper behavior or glitches on the lines during initialization.

Update the driver to disable GPIO interrupts but leave the pin
modes/states unchanged until/unless they are explicitly reconfigured.

Signed-off-by: Robert Hancock <robert.hancock@calian.com>
2025-05-07 00:01:57 +02:00

464 lines
15 KiB
C

/*
* 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/arch/cpu.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/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
#define DEV_CFG(_dev) ((const struct gpio_xlnx_ps_bank_dev_cfg *)(_dev)->config)
#define DEV_DATA(_dev) ((struct gpio_xlnx_ps_bank_dev_data *const)(_dev)->data)
/**
* @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_CFG(dev);
struct gpio_xlnx_ps_bank_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct gpio_xlnx_ps_bank_dev_data *dev_data = DEV_DATA(dev);
*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_CFG(dev);
struct gpio_xlnx_ps_bank_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct gpio_xlnx_ps_bank_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct gpio_xlnx_ps_bank_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct gpio_xlnx_ps_bank_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct gpio_xlnx_ps_bank_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct gpio_xlnx_ps_bank_dev_data *dev_data = DEV_DATA(dev);
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(dev);
return gpio_manage_callback(&dev_data->callbacks, callback, set);
}
/* GPIO bank device driver API */
static DEVICE_API(gpio, 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_CFG(dev);
struct gpio_xlnx_ps_bank_dev_data *dev_data = DEV_DATA(dev);
__ASSERT(dev_data->base != 0, "%s mapped base address missing", dev->name);
if (dev_data->base == 0) {
LOG_ERR("%s mapped base address missing", dev->name);
return -EIO;
}
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 */
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),\
},\
.bank_index = DT_INST_REG_ADDR(idx),\
};\
static struct gpio_xlnx_ps_bank_dev_data gpio_xlnx_ps_bank##idx##_data = {\
.base = 0,\
};\
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);