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 
sifive_gpio_probe(struct udevice * dev)16 static 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 
sifive_update_gpio_reg(void * bptr,u32 offset,bool value)37 static 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 
sifive_gpio_direction_input(struct udevice * dev,u32 offset)50 static 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 
sifive_gpio_direction_output(struct udevice * dev,u32 offset,int value)65 static 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 
sifive_gpio_get_value(struct udevice * dev,u32 offset)84 static 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 
sifive_gpio_set_value(struct udevice * dev,u32 offset,int value)105 static 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 
sifive_gpio_get_function(struct udevice * dev,unsigned int offset)118 static 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 
144 static const struct udevice_id sifive_gpio_match[] = {
145 	{ .compatible = "sifive,gpio0" },
146 	{ }
147 };
148 
149 static 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 
sifive_gpio_of_to_plat(struct udevice * dev)157 static 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 
170 U_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