1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2020, Broadcom */ |
3 | |
4 | #include <linux/init.h> |
5 | #include <linux/types.h> |
6 | #include <linux/module.h> |
7 | #include <linux/platform_device.h> |
8 | #include <linux/interrupt.h> |
9 | #include <linux/io.h> |
10 | #include <linux/device.h> |
11 | #include <linux/of.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/kdebug.h> |
14 | #include <linux/gpio/consumer.h> |
15 | |
16 | struct out_pin { |
17 | u32 enable_mask; |
18 | u32 value_mask; |
19 | u32 changed_mask; |
20 | u32 clr_changed_mask; |
21 | struct gpio_desc *gpiod; |
22 | const char *name; |
23 | }; |
24 | |
25 | struct in_pin { |
26 | u32 enable_mask; |
27 | u32 value_mask; |
28 | struct gpio_desc *gpiod; |
29 | const char *name; |
30 | struct brcmstb_usb_pinmap_data *pdata; |
31 | }; |
32 | |
33 | struct brcmstb_usb_pinmap_data { |
34 | void __iomem *regs; |
35 | int in_count; |
36 | struct in_pin *in_pins; |
37 | int out_count; |
38 | struct out_pin *out_pins; |
39 | }; |
40 | |
41 | |
42 | static void pinmap_set(void __iomem *reg, u32 mask) |
43 | { |
44 | u32 val; |
45 | |
46 | val = readl(addr: reg); |
47 | val |= mask; |
48 | writel(val, addr: reg); |
49 | } |
50 | |
51 | static void pinmap_unset(void __iomem *reg, u32 mask) |
52 | { |
53 | u32 val; |
54 | |
55 | val = readl(addr: reg); |
56 | val &= ~mask; |
57 | writel(val, addr: reg); |
58 | } |
59 | |
60 | static void sync_in_pin(struct in_pin *pin) |
61 | { |
62 | u32 val; |
63 | |
64 | val = gpiod_get_value(desc: pin->gpiod); |
65 | if (val) |
66 | pinmap_set(reg: pin->pdata->regs, mask: pin->value_mask); |
67 | else |
68 | pinmap_unset(reg: pin->pdata->regs, mask: pin->value_mask); |
69 | } |
70 | |
71 | /* |
72 | * Interrupt from override register, propagate from override bit |
73 | * to GPIO. |
74 | */ |
75 | static irqreturn_t brcmstb_usb_pinmap_ovr_isr(int irq, void *dev_id) |
76 | { |
77 | struct brcmstb_usb_pinmap_data *pdata = dev_id; |
78 | struct out_pin *pout; |
79 | u32 val; |
80 | u32 bit; |
81 | int x; |
82 | |
83 | pr_debug("%s: reg: 0x%x\n" , __func__, readl(pdata->regs)); |
84 | pout = pdata->out_pins; |
85 | for (x = 0; x < pdata->out_count; x++) { |
86 | val = readl(addr: pdata->regs); |
87 | if (val & pout->changed_mask) { |
88 | pinmap_set(reg: pdata->regs, mask: pout->clr_changed_mask); |
89 | pinmap_unset(reg: pdata->regs, mask: pout->clr_changed_mask); |
90 | bit = val & pout->value_mask; |
91 | gpiod_set_value(desc: pout->gpiod, value: bit ? 1 : 0); |
92 | pr_debug("%s: %s bit changed state to %d\n" , |
93 | __func__, pout->name, bit ? 1 : 0); |
94 | } |
95 | } |
96 | return IRQ_HANDLED; |
97 | } |
98 | |
99 | /* |
100 | * Interrupt from GPIO, propagate from GPIO to override bit. |
101 | */ |
102 | static irqreturn_t brcmstb_usb_pinmap_gpio_isr(int irq, void *dev_id) |
103 | { |
104 | struct in_pin *pin = dev_id; |
105 | |
106 | pr_debug("%s: %s pin changed state\n" , __func__, pin->name); |
107 | sync_in_pin(pin); |
108 | return IRQ_HANDLED; |
109 | } |
110 | |
111 | |
112 | static void get_pin_counts(struct device_node *dn, int *in_count, |
113 | int *out_count) |
114 | { |
115 | int in; |
116 | int out; |
117 | |
118 | *in_count = 0; |
119 | *out_count = 0; |
120 | in = of_property_count_strings(np: dn, propname: "brcm,in-functions" ); |
121 | if (in < 0) |
122 | return; |
123 | out = of_property_count_strings(np: dn, propname: "brcm,out-functions" ); |
124 | if (out < 0) |
125 | return; |
126 | *in_count = in; |
127 | *out_count = out; |
128 | } |
129 | |
130 | static int parse_pins(struct device *dev, struct device_node *dn, |
131 | struct brcmstb_usb_pinmap_data *pdata) |
132 | { |
133 | struct out_pin *pout; |
134 | struct in_pin *pin; |
135 | int index; |
136 | int res; |
137 | int x; |
138 | |
139 | pin = pdata->in_pins; |
140 | for (x = 0, index = 0; x < pdata->in_count; x++) { |
141 | pin->gpiod = devm_gpiod_get_index(dev, con_id: "in" , idx: x, flags: GPIOD_IN); |
142 | if (IS_ERR(ptr: pin->gpiod)) { |
143 | dev_err(dev, "Error getting gpio %s\n" , pin->name); |
144 | return PTR_ERR(ptr: pin->gpiod); |
145 | |
146 | } |
147 | res = of_property_read_string_index(np: dn, propname: "brcm,in-functions" , index: x, |
148 | output: &pin->name); |
149 | if (res < 0) { |
150 | dev_err(dev, "Error getting brcm,in-functions for %s\n" , |
151 | pin->name); |
152 | return res; |
153 | } |
154 | res = of_property_read_u32_index(np: dn, propname: "brcm,in-masks" , index: index++, |
155 | out_value: &pin->enable_mask); |
156 | if (res < 0) { |
157 | dev_err(dev, "Error getting 1st brcm,in-masks for %s\n" , |
158 | pin->name); |
159 | return res; |
160 | } |
161 | res = of_property_read_u32_index(np: dn, propname: "brcm,in-masks" , index: index++, |
162 | out_value: &pin->value_mask); |
163 | if (res < 0) { |
164 | dev_err(dev, "Error getting 2nd brcm,in-masks for %s\n" , |
165 | pin->name); |
166 | return res; |
167 | } |
168 | pin->pdata = pdata; |
169 | pin++; |
170 | } |
171 | pout = pdata->out_pins; |
172 | for (x = 0, index = 0; x < pdata->out_count; x++) { |
173 | pout->gpiod = devm_gpiod_get_index(dev, con_id: "out" , idx: x, |
174 | flags: GPIOD_OUT_HIGH); |
175 | if (IS_ERR(ptr: pout->gpiod)) { |
176 | dev_err(dev, "Error getting gpio %s\n" , pin->name); |
177 | return PTR_ERR(ptr: pout->gpiod); |
178 | } |
179 | res = of_property_read_string_index(np: dn, propname: "brcm,out-functions" , index: x, |
180 | output: &pout->name); |
181 | if (res < 0) { |
182 | dev_err(dev, "Error getting brcm,out-functions for %s\n" , |
183 | pout->name); |
184 | return res; |
185 | } |
186 | res = of_property_read_u32_index(np: dn, propname: "brcm,out-masks" , index: index++, |
187 | out_value: &pout->enable_mask); |
188 | if (res < 0) { |
189 | dev_err(dev, "Error getting 1st brcm,out-masks for %s\n" , |
190 | pout->name); |
191 | return res; |
192 | } |
193 | res = of_property_read_u32_index(np: dn, propname: "brcm,out-masks" , index: index++, |
194 | out_value: &pout->value_mask); |
195 | if (res < 0) { |
196 | dev_err(dev, "Error getting 2nd brcm,out-masks for %s\n" , |
197 | pout->name); |
198 | return res; |
199 | } |
200 | res = of_property_read_u32_index(np: dn, propname: "brcm,out-masks" , index: index++, |
201 | out_value: &pout->changed_mask); |
202 | if (res < 0) { |
203 | dev_err(dev, "Error getting 3rd brcm,out-masks for %s\n" , |
204 | pout->name); |
205 | return res; |
206 | } |
207 | res = of_property_read_u32_index(np: dn, propname: "brcm,out-masks" , index: index++, |
208 | out_value: &pout->clr_changed_mask); |
209 | if (res < 0) { |
210 | dev_err(dev, "Error getting 4th out-masks for %s\n" , |
211 | pout->name); |
212 | return res; |
213 | } |
214 | pout++; |
215 | } |
216 | return 0; |
217 | } |
218 | |
219 | static void sync_all_pins(struct brcmstb_usb_pinmap_data *pdata) |
220 | { |
221 | struct out_pin *pout; |
222 | struct in_pin *pin; |
223 | int val; |
224 | int x; |
225 | |
226 | /* |
227 | * Enable the override, clear any changed condition and |
228 | * propagate the state to the GPIO for all out pins. |
229 | */ |
230 | pout = pdata->out_pins; |
231 | for (x = 0; x < pdata->out_count; x++) { |
232 | pinmap_set(reg: pdata->regs, mask: pout->enable_mask); |
233 | pinmap_set(reg: pdata->regs, mask: pout->clr_changed_mask); |
234 | pinmap_unset(reg: pdata->regs, mask: pout->clr_changed_mask); |
235 | val = readl(addr: pdata->regs) & pout->value_mask; |
236 | gpiod_set_value(desc: pout->gpiod, value: val ? 1 : 0); |
237 | pout++; |
238 | } |
239 | |
240 | /* sync and enable all in pins. */ |
241 | pin = pdata->in_pins; |
242 | for (x = 0; x < pdata->in_count; x++) { |
243 | sync_in_pin(pin); |
244 | pinmap_set(reg: pdata->regs, mask: pin->enable_mask); |
245 | pin++; |
246 | } |
247 | } |
248 | |
249 | static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev) |
250 | { |
251 | struct device_node *dn = pdev->dev.of_node; |
252 | struct brcmstb_usb_pinmap_data *pdata; |
253 | struct in_pin *pin; |
254 | struct resource *r; |
255 | int out_count; |
256 | int in_count; |
257 | int err; |
258 | int irq; |
259 | int x; |
260 | |
261 | get_pin_counts(dn, in_count: &in_count, out_count: &out_count); |
262 | if ((in_count + out_count) == 0) |
263 | return -EINVAL; |
264 | |
265 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
266 | if (!r) |
267 | return -EINVAL; |
268 | |
269 | pdata = devm_kzalloc(dev: &pdev->dev, |
270 | size: sizeof(*pdata) + |
271 | (sizeof(struct in_pin) * in_count) + |
272 | (sizeof(struct out_pin) * out_count), GFP_KERNEL); |
273 | if (!pdata) |
274 | return -ENOMEM; |
275 | |
276 | pdata->in_count = in_count; |
277 | pdata->out_count = out_count; |
278 | pdata->in_pins = (struct in_pin *)(pdata + 1); |
279 | pdata->out_pins = (struct out_pin *)(pdata->in_pins + in_count); |
280 | |
281 | pdata->regs = devm_ioremap(dev: &pdev->dev, offset: r->start, size: resource_size(res: r)); |
282 | if (!pdata->regs) |
283 | return -ENOMEM; |
284 | platform_set_drvdata(pdev, data: pdata); |
285 | |
286 | err = parse_pins(dev: &pdev->dev, dn, pdata); |
287 | if (err) |
288 | return err; |
289 | |
290 | sync_all_pins(pdata); |
291 | |
292 | if (out_count) { |
293 | |
294 | /* Enable interrupt for out pins */ |
295 | irq = platform_get_irq(pdev, 0); |
296 | if (irq < 0) |
297 | return irq; |
298 | err = devm_request_irq(dev: &pdev->dev, irq, |
299 | handler: brcmstb_usb_pinmap_ovr_isr, |
300 | IRQF_TRIGGER_RISING, |
301 | devname: pdev->name, dev_id: pdata); |
302 | if (err < 0) { |
303 | dev_err(&pdev->dev, "Error requesting IRQ\n" ); |
304 | return err; |
305 | } |
306 | } |
307 | |
308 | for (x = 0, pin = pdata->in_pins; x < pdata->in_count; x++, pin++) { |
309 | irq = gpiod_to_irq(desc: pin->gpiod); |
310 | if (irq < 0) { |
311 | dev_err(&pdev->dev, "Error getting IRQ for %s pin\n" , |
312 | pin->name); |
313 | return irq; |
314 | } |
315 | err = devm_request_irq(dev: &pdev->dev, irq, |
316 | handler: brcmstb_usb_pinmap_gpio_isr, |
317 | IRQF_SHARED | IRQF_TRIGGER_RISING | |
318 | IRQF_TRIGGER_FALLING, |
319 | devname: pdev->name, dev_id: pin); |
320 | if (err < 0) { |
321 | dev_err(&pdev->dev, "Error requesting IRQ for %s pin\n" , |
322 | pin->name); |
323 | return err; |
324 | } |
325 | } |
326 | |
327 | dev_dbg(&pdev->dev, "Driver probe succeeded\n" ); |
328 | dev_dbg(&pdev->dev, "In pin count: %d, out pin count: %d\n" , |
329 | pdata->in_count, pdata->out_count); |
330 | return 0; |
331 | } |
332 | |
333 | |
334 | static const struct of_device_id brcmstb_usb_pinmap_of_match[] = { |
335 | { .compatible = "brcm,usb-pinmap" }, |
336 | { }, |
337 | }; |
338 | |
339 | static struct platform_driver brcmstb_usb_pinmap_driver = { |
340 | .driver = { |
341 | .name = "brcm-usb-pinmap" , |
342 | .of_match_table = brcmstb_usb_pinmap_of_match, |
343 | }, |
344 | }; |
345 | |
346 | static int __init brcmstb_usb_pinmap_init(void) |
347 | { |
348 | return platform_driver_probe(&brcmstb_usb_pinmap_driver, |
349 | brcmstb_usb_pinmap_probe); |
350 | } |
351 | |
352 | module_init(brcmstb_usb_pinmap_init); |
353 | MODULE_AUTHOR("Al Cooper <alcooperx@gmail.com>" ); |
354 | MODULE_DESCRIPTION("Broadcom USB Pinmap Driver" ); |
355 | MODULE_LICENSE("GPL" ); |
356 | |