diff options
-rw-r--r-- | arch/riscv/include/asm/arch-generic/gpio.h | 35 | ||||
-rw-r--r-- | arch/riscv/include/asm/gpio.h | 6 | ||||
-rw-r--r-- | drivers/gpio/Kconfig | 7 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/sifive-gpio.c | 177 |
5 files changed, 226 insertions, 0 deletions
diff --git a/arch/riscv/include/asm/arch-generic/gpio.h b/arch/riscv/include/asm/arch-generic/gpio.h new file mode 100644 index 0000000..dfcb753 --- /dev/null +++ b/arch/riscv/include/asm/arch-generic/gpio.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019 SiFive, Inc. + */ + +#ifndef _GPIO_SIFIVE_H +#define _GPIO_SIFIVE_H + +#define GPIO_INPUT_VAL 0x00 +#define GPIO_INPUT_EN 0x04 +#define GPIO_OUTPUT_EN 0x08 +#define GPIO_OUTPUT_VAL 0x0C +#define GPIO_RISE_IE 0x18 +#define GPIO_RISE_IP 0x1C +#define GPIO_FALL_IE 0x20 +#define GPIO_FALL_IP 0x24 +#define GPIO_HIGH_IE 0x28 +#define GPIO_HIGH_IP 0x2C +#define GPIO_LOW_IE 0x30 +#define GPIO_LOW_IP 0x34 +#define GPIO_OUTPUT_XOR 0x40 + +#define NR_GPIOS 16 + +enum gpio_state { + LOW, + HIGH +}; + +/* Details about a GPIO bank */ +struct sifive_gpio_platdata { + void *base; /* address of registers in physical memory */ +}; + +#endif /* _GPIO_SIFIVE_H */ diff --git a/arch/riscv/include/asm/gpio.h b/arch/riscv/include/asm/gpio.h new file mode 100644 index 0000000..008d756 --- /dev/null +++ b/arch/riscv/include/asm/gpio.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2018 SiFive, Inc. + */ + +#include <asm-generic/gpio.h> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f2dabb5..39f2c7e 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -285,6 +285,13 @@ config STM32_GPIO usable on many stm32 families like stm32f4/f7/h7 and stm32mp1. Tested on STM32F7. +config SIFIVE_GPIO + bool "SiFive GPIO driver" + depends on DM_GPIO + help + Device model driver for GPIO controller present in SiFive FU540 SoC. This + driver enables GPIO interface on HiFive Unleashed A00 board. + config MVEBU_GPIO bool "Marvell MVEBU GPIO driver" depends on DM_GPIO && ARCH_MVEBU diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 4a8aa0f..ccc49e2 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -61,3 +61,4 @@ obj-$(CONFIG_$(SPL_)PCF8575_GPIO) += pcf8575_gpio.o obj-$(CONFIG_PM8916_GPIO) += pm8916_gpio.o obj-$(CONFIG_MT7621_GPIO) += mt7621_gpio.o obj-$(CONFIG_MSCC_SGPIO) += mscc_sgpio.o +obj-$(CONFIG_SIFIVE_GPIO) += sifive-gpio.o diff --git a/drivers/gpio/sifive-gpio.c b/drivers/gpio/sifive-gpio.c new file mode 100644 index 0000000..76d5a1d3 --- /dev/null +++ b/drivers/gpio/sifive-gpio.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SiFive GPIO driver + * + * Copyright (C) 2019 SiFive, Inc. + */ + +#include <common.h> +#include <dm.h> +#include <asm/arch/gpio.h> +#include <asm/io.h> +#include <errno.h> +#include <asm/gpio.h> + +static int sifive_gpio_probe(struct udevice *dev) +{ + struct sifive_gpio_platdata *plat = dev_get_platdata(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_platdata *plat = dev_get_platdata(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_platdata *plat = dev_get_platdata(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_platdata *plat = dev_get_platdata(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_platdata *plat = dev_get_platdata(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_platdata *plat = dev_get_platdata(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_ofdata_to_platdata(struct udevice *dev) +{ + struct sifive_gpio_platdata *plat = dev_get_platdata(dev); + fdt_addr_t addr; + + addr = devfdt_get_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, + .ofdata_to_platdata = of_match_ptr(sifive_gpio_ofdata_to_platdata), + .platdata_auto_alloc_size = sizeof(struct sifive_gpio_platdata), + .ops = &sifive_gpio_ops, + .probe = sifive_gpio_probe, +}; |