uboot/drivers/gpio/sifive-gpio.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * SiFive GPIO driver
   4 *
   5 * Copyright (C) 2019 SiFive, Inc.
   6 */
   7
   8#include <common.h>
   9#include <dm.h>
  10#include <asm/arch/gpio.h>
  11#include <asm/io.h>
  12#include <errno.h>
  13#include <asm/gpio.h>
  14#include <linux/bitops.h>
  15
  16static int sifive_gpio_probe(struct udevice *dev)
  17{
  18        struct sifive_gpio_plat *plat = dev_get_plat(dev);
  19        struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
  20        char name[18], *str;
  21
  22        sprintf(name, "gpio@%4lx_", (uintptr_t)plat->base);
  23        str = strdup(name);
  24        if (!str)
  25                return -ENOMEM;
  26        uc_priv->bank_name = str;
  27
  28        /*
  29         * Use the gpio count mentioned in device tree,
  30         * if not specified in dt, set NR_GPIOS as default
  31         */
  32        uc_priv->gpio_count = dev_read_u32_default(dev, "ngpios", NR_GPIOS);
  33
  34        return 0;
  35}
  36
  37static void sifive_update_gpio_reg(void *bptr, u32 offset, bool value)
  38{
  39        void __iomem *ptr = (void __iomem *)bptr;
  40
  41        u32 bit = BIT(offset);
  42        u32 old = readl(ptr);
  43
  44        if (value)
  45                writel(old | bit, ptr);
  46        else
  47                writel(old & ~bit, ptr);
  48}
  49
  50static int sifive_gpio_direction_input(struct udevice *dev, u32 offset)
  51{
  52        struct sifive_gpio_plat *plat = dev_get_plat(dev);
  53        struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
  54
  55        if (offset > uc_priv->gpio_count)
  56                return -EINVAL;
  57
  58        /* Configure gpio direction as input */
  59        sifive_update_gpio_reg(plat->base + GPIO_INPUT_EN,  offset, true);
  60        sifive_update_gpio_reg(plat->base + GPIO_OUTPUT_EN, offset, false);
  61
  62        return 0;
  63}
  64
  65static int sifive_gpio_direction_output(struct udevice *dev, u32 offset,
  66                                        int value)
  67{
  68        struct sifive_gpio_plat *plat = dev_get_plat(dev);
  69        struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
  70
  71        if (offset > uc_priv->gpio_count)
  72                return -EINVAL;
  73
  74        /* Configure gpio direction as output */
  75        sifive_update_gpio_reg(plat->base + GPIO_OUTPUT_EN, offset, true);
  76        sifive_update_gpio_reg(plat->base + GPIO_INPUT_EN,  offset, false);
  77
  78        /* Set the output state of the pin */
  79        sifive_update_gpio_reg(plat->base + GPIO_OUTPUT_VAL, offset, value);
  80
  81        return 0;
  82}
  83
  84static int sifive_gpio_get_value(struct udevice *dev, u32 offset)
  85{
  86        struct sifive_gpio_plat *plat = dev_get_plat(dev);
  87        struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
  88        int val;
  89        int dir;
  90
  91        if (offset > uc_priv->gpio_count)
  92                return -EINVAL;
  93
  94        /* Get direction of the pin */
  95        dir = !(readl(plat->base + GPIO_OUTPUT_EN) & BIT(offset));
  96
  97        if (dir)
  98                val = readl(plat->base + GPIO_INPUT_VAL) & BIT(offset);
  99        else
 100                val = readl(plat->base + GPIO_OUTPUT_VAL) & BIT(offset);
 101
 102        return val ? HIGH : LOW;
 103}
 104
 105static int sifive_gpio_set_value(struct udevice *dev, u32 offset, int value)
 106{
 107        struct sifive_gpio_plat *plat = dev_get_plat(dev);
 108        struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
 109
 110        if (offset > uc_priv->gpio_count)
 111                return -EINVAL;
 112
 113        sifive_update_gpio_reg(plat->base + GPIO_OUTPUT_VAL, offset, value);
 114
 115        return 0;
 116}
 117
 118static int sifive_gpio_get_function(struct udevice *dev, unsigned int offset)
 119{
 120        struct sifive_gpio_plat *plat = dev_get_plat(dev);
 121        u32     outdir, indir, val;
 122        struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
 123
 124        if (offset > uc_priv->gpio_count)
 125                return -1;
 126
 127        /* Get direction of the pin */
 128        outdir = readl(plat->base + GPIO_OUTPUT_EN) & BIT(offset);
 129        indir  = readl(plat->base + GPIO_INPUT_EN) & BIT(offset);
 130
 131        if (outdir)
 132                /* Pin at specified offset is configured as output */
 133                val = GPIOF_OUTPUT;
 134        else if (indir)
 135                /* Pin at specified offset is configured as input */
 136                val = GPIOF_INPUT;
 137        else
 138                /*The requested GPIO is not set as input or output */
 139                val = GPIOF_UNUSED;
 140
 141        return val;
 142}
 143
 144static const struct udevice_id sifive_gpio_match[] = {
 145        { .compatible = "sifive,gpio0" },
 146        { }
 147};
 148
 149static const struct dm_gpio_ops sifive_gpio_ops = {
 150        .direction_input        = sifive_gpio_direction_input,
 151        .direction_output       = sifive_gpio_direction_output,
 152        .get_value              = sifive_gpio_get_value,
 153        .set_value              = sifive_gpio_set_value,
 154        .get_function           = sifive_gpio_get_function,
 155};
 156
 157static int sifive_gpio_of_to_plat(struct udevice *dev)
 158{
 159        struct sifive_gpio_plat *plat = dev_get_plat(dev);
 160        fdt_addr_t addr;
 161
 162        addr = dev_read_addr(dev);
 163        if (addr == FDT_ADDR_T_NONE)
 164                return -EINVAL;
 165
 166        plat->base = (void *)addr;
 167        return 0;
 168}
 169
 170U_BOOT_DRIVER(gpio_sifive) = {
 171        .name   = "gpio_sifive",
 172        .id     = UCLASS_GPIO,
 173        .of_match = sifive_gpio_match,
 174        .of_to_plat = of_match_ptr(sifive_gpio_of_to_plat),
 175        .plat_auto      = sizeof(struct sifive_gpio_plat),
 176        .ops    = &sifive_gpio_ops,
 177        .probe  = sifive_gpio_probe,
 178};
 179