mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-26 15:28:50 +00:00

Some Qualcomm boards feature reserved ranges of pins which are protected by firmware. Attempting to read or write any registers associated with these pins results the board resetting. Add support for parsing these ranges from devicetree and ensure that the pinctrl and GPIO drivers don't try to interact with these pins. Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org> Reviewed-by: Sumit Garg <sumit.garg@oss.qualcomm.com> Link: https://lore.kernel.org/r/20250410-topic-sm8x50-pinctrl-reserved-ranges-v2-1-654488392b9a@linaro.org Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
293 lines
7.3 KiB
C
293 lines
7.3 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Qualcomm GPIO driver
|
|
*
|
|
* (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com>
|
|
*/
|
|
|
|
#include <dm.h>
|
|
#include <errno.h>
|
|
#include <asm/global_data.h>
|
|
#include <asm/gpio.h>
|
|
#include <asm/io.h>
|
|
#include <mach/gpio.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
/* OE */
|
|
#define GPIO_OE_DISABLE (0x0 << 9)
|
|
#define GPIO_OE_ENABLE (0x1 << 9)
|
|
#define GPIO_OE_MASK (0x1 << 9)
|
|
|
|
/* GPIO_IN_OUT register shifts. */
|
|
#define GPIO_IN 0
|
|
#define GPIO_OUT 1
|
|
|
|
struct msm_gpio_bank {
|
|
phys_addr_t base;
|
|
const struct msm_pin_data *pin_data;
|
|
};
|
|
|
|
#define GPIO_CONFIG_REG(dev, x) \
|
|
(qcom_pin_offset(((struct msm_gpio_bank *)dev_get_priv(dev))->pin_data->pin_offsets, x))
|
|
|
|
#define GPIO_IN_OUT_REG(dev, x) \
|
|
(GPIO_CONFIG_REG(dev, x) + 0x4)
|
|
|
|
static void msm_gpio_direction_input_special(struct msm_gpio_bank *priv,
|
|
unsigned int gpio)
|
|
{
|
|
unsigned int offset = gpio - priv->pin_data->special_pins_start;
|
|
const struct msm_special_pin_data *data;
|
|
|
|
if (!priv->pin_data->special_pins_data)
|
|
return;
|
|
|
|
data = &priv->pin_data->special_pins_data[offset];
|
|
|
|
if (!data->ctl_reg || data->oe_bit >= 31)
|
|
return;
|
|
|
|
/* switch direction */
|
|
clrsetbits_le32(priv->base + data->ctl_reg,
|
|
BIT(data->oe_bit), 0);
|
|
}
|
|
|
|
static void msm_gpio_direction_input(struct udevice *dev, unsigned int gpio)
|
|
{
|
|
struct msm_gpio_bank *priv = dev_get_priv(dev);
|
|
|
|
if (qcom_is_special_pin(priv->pin_data, gpio))
|
|
msm_gpio_direction_input_special(priv, gpio);
|
|
|
|
/* Disable OE bit */
|
|
clrsetbits_le32(priv->base + GPIO_CONFIG_REG(dev, gpio),
|
|
GPIO_OE_MASK, GPIO_OE_DISABLE);
|
|
|
|
return;
|
|
}
|
|
|
|
static int msm_gpio_set_value_special(struct msm_gpio_bank *priv,
|
|
unsigned int gpio, int value)
|
|
{
|
|
unsigned int offset = gpio - priv->pin_data->special_pins_start;
|
|
const struct msm_special_pin_data *data;
|
|
|
|
if (!priv->pin_data->special_pins_data)
|
|
return 0;
|
|
|
|
data = &priv->pin_data->special_pins_data[offset];
|
|
|
|
if (!data->io_reg || data->out_bit >= 31)
|
|
return 0;
|
|
|
|
value = !!value;
|
|
/* set value */
|
|
writel(value << data->out_bit, priv->base + data->io_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_gpio_set_value(struct udevice *dev, unsigned int gpio, int value)
|
|
{
|
|
struct msm_gpio_bank *priv = dev_get_priv(dev);
|
|
|
|
if (qcom_is_special_pin(priv->pin_data, gpio))
|
|
return msm_gpio_set_value_special(priv, gpio, value);
|
|
|
|
value = !!value;
|
|
/* set value */
|
|
writel(value << GPIO_OUT, priv->base + GPIO_IN_OUT_REG(dev, gpio));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_gpio_direction_output_special(struct msm_gpio_bank *priv,
|
|
unsigned int gpio,
|
|
int value)
|
|
{
|
|
unsigned int offset = gpio - priv->pin_data->special_pins_start;
|
|
const struct msm_special_pin_data *data;
|
|
|
|
if (!priv->pin_data->special_pins_data)
|
|
return 0;
|
|
|
|
data = &priv->pin_data->special_pins_data[offset];
|
|
|
|
if (!data->io_reg || data->out_bit >= 31)
|
|
return 0;
|
|
|
|
value = !!value;
|
|
/* set value */
|
|
writel(value << data->out_bit, priv->base + data->io_reg);
|
|
|
|
if (!data->ctl_reg || data->oe_bit >= 31)
|
|
return 0;
|
|
|
|
/* switch direction */
|
|
clrsetbits_le32(priv->base + data->ctl_reg,
|
|
BIT(data->oe_bit), BIT(data->oe_bit));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_gpio_direction_output(struct udevice *dev, unsigned int gpio,
|
|
int value)
|
|
{
|
|
struct msm_gpio_bank *priv = dev_get_priv(dev);
|
|
|
|
if (qcom_is_special_pin(priv->pin_data, gpio))
|
|
return msm_gpio_direction_output_special(priv, gpio, value);
|
|
|
|
value = !!value;
|
|
/* set value */
|
|
writel(value << GPIO_OUT, priv->base + GPIO_IN_OUT_REG(dev, gpio));
|
|
/* switch direction */
|
|
clrsetbits_le32(priv->base + GPIO_CONFIG_REG(dev, gpio),
|
|
GPIO_OE_MASK, GPIO_OE_ENABLE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_gpio_set_flags(struct udevice *dev, unsigned int gpio, ulong flags)
|
|
{
|
|
if (msm_pinctrl_is_reserved(dev_get_parent(dev), gpio))
|
|
return -EPERM;
|
|
|
|
if (flags & GPIOD_IS_OUT_ACTIVE) {
|
|
return msm_gpio_direction_output(dev, gpio, 1);
|
|
} else if (flags & GPIOD_IS_OUT) {
|
|
return msm_gpio_direction_output(dev, gpio, 0);
|
|
} else if (flags & GPIOD_IS_IN) {
|
|
msm_gpio_direction_input(dev, gpio);
|
|
if (flags & GPIOD_PULL_UP)
|
|
return msm_gpio_set_value(dev, gpio, 1);
|
|
else if (flags & GPIOD_PULL_DOWN)
|
|
return msm_gpio_set_value(dev, gpio, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_gpio_get_value_special(struct msm_gpio_bank *priv, unsigned int gpio)
|
|
{
|
|
unsigned int offset = gpio - priv->pin_data->special_pins_start;
|
|
const struct msm_special_pin_data *data;
|
|
|
|
if (!priv->pin_data->special_pins_data)
|
|
return -EINVAL;
|
|
|
|
data = &priv->pin_data->special_pins_data[offset];
|
|
|
|
if (!data->io_reg)
|
|
return -EINVAL;
|
|
|
|
if (data->in_bit >= 31) {
|
|
if (data->out_bit >= 31)
|
|
return -EINVAL;
|
|
|
|
return !!(readl(priv->base + data->io_reg) >> data->out_bit);
|
|
}
|
|
|
|
return !!(readl(priv->base + data->io_reg) >> data->in_bit);
|
|
}
|
|
|
|
static int msm_gpio_get_value(struct udevice *dev, unsigned int gpio)
|
|
{
|
|
struct msm_gpio_bank *priv = dev_get_priv(dev);
|
|
|
|
if (msm_pinctrl_is_reserved(dev_get_parent(dev), gpio))
|
|
return -EPERM;
|
|
|
|
if (qcom_is_special_pin(priv->pin_data, gpio))
|
|
return msm_gpio_get_value_special(priv, gpio);
|
|
|
|
return !!(readl(priv->base + GPIO_IN_OUT_REG(dev, gpio)) >> GPIO_IN);
|
|
}
|
|
|
|
static int msm_gpio_get_function_special(struct msm_gpio_bank *priv,
|
|
unsigned int gpio)
|
|
{
|
|
unsigned int offset = gpio - priv->pin_data->special_pins_start;
|
|
const struct msm_special_pin_data *data;
|
|
|
|
if (!priv->pin_data->special_pins_data)
|
|
return GPIOF_UNKNOWN;
|
|
|
|
data = &priv->pin_data->special_pins_data[offset];
|
|
|
|
/* No I/O fields, cannot control/read the I/O value */
|
|
if (!data->io_reg || (data->out_bit >= 31 && data->in_bit >= 31))
|
|
return GPIOF_FUNC;
|
|
|
|
/* No Output-Enable register, cannot control I/O direction */
|
|
if (!data->ctl_reg || data->oe_bit >= 31) {
|
|
if (data->out_bit >= 31)
|
|
return GPIOF_INPUT;
|
|
else
|
|
return GPIOF_OUTPUT;
|
|
}
|
|
|
|
if (readl(priv->base + data->ctl_reg) & BIT(data->oe_bit))
|
|
return GPIOF_OUTPUT;
|
|
|
|
return GPIOF_INPUT;
|
|
}
|
|
|
|
static int msm_gpio_get_function(struct udevice *dev, unsigned int gpio)
|
|
{
|
|
struct msm_gpio_bank *priv = dev_get_priv(dev);
|
|
|
|
if (msm_pinctrl_is_reserved(dev_get_parent(dev), gpio))
|
|
return GPIOF_UNKNOWN;
|
|
|
|
/* Always NOP for special pins, assume they're in the correct state */
|
|
if (qcom_is_special_pin(priv->pin_data, gpio))
|
|
return msm_gpio_get_function_special(priv, gpio);
|
|
|
|
if (readl(priv->base + GPIO_CONFIG_REG(dev, gpio)) & GPIO_OE_ENABLE)
|
|
return GPIOF_OUTPUT;
|
|
|
|
return GPIOF_INPUT;
|
|
}
|
|
|
|
static const struct dm_gpio_ops gpio_msm_ops = {
|
|
.set_flags = msm_gpio_set_flags,
|
|
.get_value = msm_gpio_get_value,
|
|
.get_function = msm_gpio_get_function,
|
|
};
|
|
|
|
static int msm_gpio_probe(struct udevice *dev)
|
|
{
|
|
struct msm_gpio_bank *priv = dev_get_priv(dev);
|
|
|
|
priv->base = dev_read_addr(dev);
|
|
priv->pin_data = (struct msm_pin_data *)dev_get_driver_data(dev);
|
|
|
|
return priv->base == FDT_ADDR_T_NONE ? -EINVAL : 0;
|
|
}
|
|
|
|
static int msm_gpio_of_to_plat(struct udevice *dev)
|
|
{
|
|
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
|
|
const struct msm_pin_data *pin_data = (struct msm_pin_data *)dev_get_driver_data(dev);
|
|
|
|
/* Get the pin count from the pinctrl driver */
|
|
uc_priv->gpio_count = pin_data->pin_count;
|
|
uc_priv->bank_name = fdt_getprop(gd->fdt_blob, dev_of_offset(dev),
|
|
"gpio-bank-name", NULL);
|
|
if (uc_priv->bank_name == NULL)
|
|
uc_priv->bank_name = "soc";
|
|
|
|
return 0;
|
|
}
|
|
|
|
U_BOOT_DRIVER(gpio_msm) = {
|
|
.name = "gpio_msm",
|
|
.id = UCLASS_GPIO,
|
|
.of_to_plat = msm_gpio_of_to_plat,
|
|
.probe = msm_gpio_probe,
|
|
.ops = &gpio_msm_ops,
|
|
.flags = DM_UC_FLAG_SEQ_ALIAS,
|
|
.priv_auto = sizeof(struct msm_gpio_bank),
|
|
};
|