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