// SPDX-License-Identifier: GPL-2.0+ /* * SiFive GPIO driver * * Copyright (C) 2019 SiFive, Inc. */ #include #include #include #include #include #include #include static int sifive_gpio_probe(struct udevice *dev) { struct sifive_gpio_plat *plat = dev_get_plat(dev); struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); char name[18], *str; sprintf(name, "gpio@%4lx_", (uintptr_t)plat->base); str = strdup(name); if (!str) return -ENOMEM; uc_priv->bank_name = str; /* * Use the gpio count mentioned in device tree, * if not specified in dt, set NR_GPIOS as default */ uc_priv->gpio_count = dev_read_u32_default(dev, "ngpios", NR_GPIOS); return 0; } static void sifive_update_gpio_reg(void *bptr, u32 offset, bool value) { void __iomem *ptr = (void __iomem *)bptr; u32 bit = BIT(offset); u32 old = readl(ptr); if (value) writel(old | bit, ptr); else writel(old & ~bit, ptr); } static int sifive_gpio_direction_input(struct udevice *dev, u32 offset) { struct sifive_gpio_plat *plat = dev_get_plat(dev); struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); if (offset > uc_priv->gpio_count) return -EINVAL; /* Configure gpio direction as input */ sifive_update_gpio_reg(plat->base + GPIO_INPUT_EN, offset, true); sifive_update_gpio_reg(plat->base + GPIO_OUTPUT_EN, offset, false); return 0; } static int sifive_gpio_direction_output(struct udevice *dev, u32 offset, int value) { struct sifive_gpio_plat *plat = dev_get_plat(dev); struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); if (offset > uc_priv->gpio_count) return -EINVAL; /* Configure gpio direction as output */ sifive_update_gpio_reg(plat->base + GPIO_OUTPUT_EN, offset, true); sifive_update_gpio_reg(plat->base + GPIO_INPUT_EN, offset, false); /* Set the output state of the pin */ sifive_update_gpio_reg(plat->base + GPIO_OUTPUT_VAL, offset, value); return 0; } static int sifive_gpio_get_value(struct udevice *dev, u32 offset) { struct sifive_gpio_plat *plat = dev_get_plat(dev); struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); int val; int dir; if (offset > uc_priv->gpio_count) return -EINVAL; /* Get direction of the pin */ dir = !(readl(plat->base + GPIO_OUTPUT_EN) & BIT(offset)); if (dir) val = readl(plat->base + GPIO_INPUT_VAL) & BIT(offset); else val = readl(plat->base + GPIO_OUTPUT_VAL) & BIT(offset); return val ? HIGH : LOW; } static int sifive_gpio_set_value(struct udevice *dev, u32 offset, int value) { struct sifive_gpio_plat *plat = dev_get_plat(dev); struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); if (offset > uc_priv->gpio_count) return -EINVAL; sifive_update_gpio_reg(plat->base + GPIO_OUTPUT_VAL, offset, value); return 0; } static int sifive_gpio_get_function(struct udevice *dev, unsigned int offset) { struct sifive_gpio_plat *plat = dev_get_plat(dev); u32 outdir, indir, val; struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); if (offset > uc_priv->gpio_count) return -1; /* Get direction of the pin */ outdir = readl(plat->base + GPIO_OUTPUT_EN) & BIT(offset); indir = readl(plat->base + GPIO_INPUT_EN) & BIT(offset); if (outdir) /* Pin at specified offset is configured as output */ val = GPIOF_OUTPUT; else if (indir) /* Pin at specified offset is configured as input */ val = GPIOF_INPUT; else /*The requested GPIO is not set as input or output */ val = GPIOF_UNUSED; return val; } static const struct udevice_id sifive_gpio_match[] = { { .compatible = "sifive,gpio0" }, { } }; static const struct dm_gpio_ops sifive_gpio_ops = { .direction_input = sifive_gpio_direction_input, .direction_output = sifive_gpio_direction_output, .get_value = sifive_gpio_get_value, .set_value = sifive_gpio_set_value, .get_function = sifive_gpio_get_function, }; static int sifive_gpio_of_to_plat(struct udevice *dev) { struct sifive_gpio_plat *plat = dev_get_plat(dev); fdt_addr_t addr; addr = dev_read_addr(dev); if (addr == FDT_ADDR_T_NONE) return -EINVAL; plat->base = (void *)addr; return 0; } U_BOOT_DRIVER(gpio_sifive) = { .name = "gpio_sifive", .id = UCLASS_GPIO, .of_match = sifive_gpio_match, .of_to_plat = of_match_ptr(sifive_gpio_of_to_plat), .plat_auto = sizeof(struct sifive_gpio_plat), .ops = &sifive_gpio_ops, .probe = sifive_gpio_probe, };