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