1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Loongson GPIO Support
4 *
5 * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
6 */
7
8#include <linux/kernel.h>
9#include <linux/init.h>
10#include <linux/module.h>
11#include <linux/spinlock.h>
12#include <linux/err.h>
13#include <linux/gpio/driver.h>
14#include <linux/platform_device.h>
15#include <linux/bitops.h>
16#include <asm/types.h>
17
18enum loongson_gpio_mode {
19	BIT_CTRL_MODE,
20	BYTE_CTRL_MODE,
21};
22
23struct loongson_gpio_chip_data {
24	const char		*label;
25	enum loongson_gpio_mode	mode;
26	unsigned int		conf_offset;
27	unsigned int		out_offset;
28	unsigned int		in_offset;
29};
30
31struct loongson_gpio_chip {
32	struct gpio_chip	chip;
33	struct fwnode_handle	*fwnode;
34	spinlock_t		lock;
35	void __iomem		*reg_base;
36	const struct loongson_gpio_chip_data *chip_data;
37};
38
39static inline struct loongson_gpio_chip *to_loongson_gpio_chip(struct gpio_chip *chip)
40{
41	return container_of(chip, struct loongson_gpio_chip, chip);
42}
43
44static inline void loongson_commit_direction(struct loongson_gpio_chip *lgpio, unsigned int pin,
45					     int input)
46{
47	u8 bval = input ? 1 : 0;
48
49	writeb(bval, lgpio->reg_base + lgpio->chip_data->conf_offset + pin);
50}
51
52static void loongson_commit_level(struct loongson_gpio_chip *lgpio, unsigned int pin, int high)
53{
54	u8 bval = high ? 1 : 0;
55
56	writeb(bval, lgpio->reg_base + lgpio->chip_data->out_offset + pin);
57}
58
59static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned int pin)
60{
61	unsigned long flags;
62	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
63
64	spin_lock_irqsave(&lgpio->lock, flags);
65	loongson_commit_direction(lgpio, pin, 1);
66	spin_unlock_irqrestore(&lgpio->lock, flags);
67
68	return 0;
69}
70
71static int loongson_gpio_direction_output(struct gpio_chip *chip, unsigned int pin, int value)
72{
73	unsigned long flags;
74	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
75
76	spin_lock_irqsave(&lgpio->lock, flags);
77	loongson_commit_level(lgpio, pin, value);
78	loongson_commit_direction(lgpio, pin, 0);
79	spin_unlock_irqrestore(&lgpio->lock, flags);
80
81	return 0;
82}
83
84static int loongson_gpio_get(struct gpio_chip *chip, unsigned int pin)
85{
86	u8  bval;
87	int val;
88	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
89
90	bval = readb(lgpio->reg_base + lgpio->chip_data->in_offset + pin);
91	val = bval & 1;
92
93	return val;
94}
95
96static int loongson_gpio_get_direction(struct gpio_chip *chip, unsigned int pin)
97{
98	u8  bval;
99	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
100
101	bval = readb(lgpio->reg_base + lgpio->chip_data->conf_offset + pin);
102	if (bval & 1)
103		return GPIO_LINE_DIRECTION_IN;
104
105	return GPIO_LINE_DIRECTION_OUT;
106}
107
108static void loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
109{
110	unsigned long flags;
111	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
112
113	spin_lock_irqsave(&lgpio->lock, flags);
114	loongson_commit_level(lgpio, pin, value);
115	spin_unlock_irqrestore(&lgpio->lock, flags);
116}
117
118static int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
119{
120	struct platform_device *pdev = to_platform_device(chip->parent);
121
122	return platform_get_irq(pdev, offset);
123}
124
125static int loongson_gpio_init(struct device *dev, struct loongson_gpio_chip *lgpio,
126			      struct device_node *np, void __iomem *reg_base)
127{
128	int ret;
129	u32 ngpios;
130
131	lgpio->reg_base = reg_base;
132
133	if (lgpio->chip_data->mode == BIT_CTRL_MODE) {
134		ret = bgpio_init(&lgpio->chip, dev, 8,
135				lgpio->reg_base + lgpio->chip_data->in_offset,
136				lgpio->reg_base + lgpio->chip_data->out_offset,
137				NULL, NULL,
138				lgpio->reg_base + lgpio->chip_data->conf_offset,
139				0);
140		if (ret) {
141			dev_err(dev, "unable to init generic GPIO\n");
142			return ret;
143		}
144	} else {
145		lgpio->chip.direction_input = loongson_gpio_direction_input;
146		lgpio->chip.get = loongson_gpio_get;
147		lgpio->chip.get_direction = loongson_gpio_get_direction;
148		lgpio->chip.direction_output = loongson_gpio_direction_output;
149		lgpio->chip.set = loongson_gpio_set;
150		lgpio->chip.parent = dev;
151		spin_lock_init(&lgpio->lock);
152	}
153
154	device_property_read_u32(dev, "ngpios", &ngpios);
155
156	lgpio->chip.can_sleep = 0;
157	lgpio->chip.ngpio = ngpios;
158	lgpio->chip.label = lgpio->chip_data->label;
159	lgpio->chip.to_irq = loongson_gpio_to_irq;
160
161	return devm_gpiochip_add_data(dev, &lgpio->chip, lgpio);
162}
163
164static int loongson_gpio_probe(struct platform_device *pdev)
165{
166	void __iomem *reg_base;
167	struct loongson_gpio_chip *lgpio;
168	struct device_node *np = pdev->dev.of_node;
169	struct device *dev = &pdev->dev;
170
171	lgpio = devm_kzalloc(dev, sizeof(*lgpio), GFP_KERNEL);
172	if (!lgpio)
173		return -ENOMEM;
174
175	lgpio->chip_data = device_get_match_data(dev);
176
177	reg_base = devm_platform_ioremap_resource(pdev, 0);
178	if (IS_ERR(reg_base))
179		return PTR_ERR(reg_base);
180
181	return loongson_gpio_init(dev, lgpio, np, reg_base);
182}
183
184static const struct loongson_gpio_chip_data loongson_gpio_ls2k_data = {
185	.label = "ls2k_gpio",
186	.mode = BIT_CTRL_MODE,
187	.conf_offset = 0x0,
188	.in_offset = 0x20,
189	.out_offset = 0x10,
190};
191
192static const struct loongson_gpio_chip_data loongson_gpio_ls7a_data = {
193	.label = "ls7a_gpio",
194	.mode = BYTE_CTRL_MODE,
195	.conf_offset = 0x800,
196	.in_offset = 0xa00,
197	.out_offset = 0x900,
198};
199
200static const struct of_device_id loongson_gpio_of_match[] = {
201	{
202		.compatible = "loongson,ls2k-gpio",
203		.data = &loongson_gpio_ls2k_data,
204	},
205	{
206		.compatible = "loongson,ls7a-gpio",
207		.data = &loongson_gpio_ls7a_data,
208	},
209	{}
210};
211MODULE_DEVICE_TABLE(of, loongson_gpio_of_match);
212
213static const struct acpi_device_id loongson_gpio_acpi_match[] = {
214	{
215		.id = "LOON0002",
216		.driver_data = (kernel_ulong_t)&loongson_gpio_ls7a_data,
217	},
218	{}
219};
220MODULE_DEVICE_TABLE(acpi, loongson_gpio_acpi_match);
221
222static struct platform_driver loongson_gpio_driver = {
223	.driver = {
224		.name = "loongson-gpio",
225		.of_match_table = loongson_gpio_of_match,
226		.acpi_match_table = loongson_gpio_acpi_match,
227	},
228	.probe = loongson_gpio_probe,
229};
230
231static int __init loongson_gpio_setup(void)
232{
233	return platform_driver_register(&loongson_gpio_driver);
234}
235postcore_initcall(loongson_gpio_setup);
236
237MODULE_DESCRIPTION("Loongson gpio driver");
238MODULE_LICENSE("GPL");
239