Commit | Line | Data |
---|---|---|
7944d3b7 YZ |
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 | ||
18 | enum loongson_gpio_mode { | |
19 | BIT_CTRL_MODE, | |
20 | BYTE_CTRL_MODE, | |
21 | }; | |
22 | ||
23 | struct 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 | ||
31 | struct 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 | ||
39 | static 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 | ||
44 | static 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 | ||
52 | static 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 | ||
59 | static 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 | ||
71 | static 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 | ||
84 | static 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 | ||
96 | static 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 | ||
108 | static 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 | ||
118 | static 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 | ||
125 | static 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 | ||
164 | static 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 | ||
184 | static 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 | ||
192 | static 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 | ||
200 | static 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 | }; | |
211 | MODULE_DEVICE_TABLE(of, loongson_gpio_of_match); | |
212 | ||
213 | static 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 | }; | |
220 | MODULE_DEVICE_TABLE(acpi, loongson_gpio_acpi_match); | |
221 | ||
222 | static struct platform_driver loongson_gpio_driver = { | |
223 | .driver = { | |
224 | .name = "loongson-gpio", | |
7944d3b7 YZ |
225 | .of_match_table = loongson_gpio_of_match, |
226 | .acpi_match_table = loongson_gpio_acpi_match, | |
227 | }, | |
228 | .probe = loongson_gpio_probe, | |
229 | }; | |
230 | ||
231 | static int __init loongson_gpio_setup(void) | |
232 | { | |
233 | return platform_driver_register(&loongson_gpio_driver); | |
234 | } | |
235 | postcore_initcall(loongson_gpio_setup); | |
236 | ||
237 | MODULE_DESCRIPTION("Loongson gpio driver"); | |
238 | MODULE_LICENSE("GPL"); |