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