xref: /openbmc/linux/drivers/usb/misc/brcmstb-usb-pinmap.c (revision 762f99f4f3cb41a775b5157dd761217beba65873)
1517c4c44SAl Cooper // SPDX-License-Identifier: GPL-2.0
2517c4c44SAl Cooper /* Copyright (c) 2020, Broadcom */
3517c4c44SAl Cooper 
4517c4c44SAl Cooper #include <linux/init.h>
5517c4c44SAl Cooper #include <linux/types.h>
6517c4c44SAl Cooper #include <linux/module.h>
7517c4c44SAl Cooper #include <linux/platform_device.h>
8517c4c44SAl Cooper #include <linux/interrupt.h>
9517c4c44SAl Cooper #include <linux/io.h>
10517c4c44SAl Cooper #include <linux/device.h>
11517c4c44SAl Cooper #include <linux/of.h>
12517c4c44SAl Cooper #include <linux/kernel.h>
13517c4c44SAl Cooper #include <linux/kdebug.h>
14517c4c44SAl Cooper #include <linux/gpio/consumer.h>
15517c4c44SAl Cooper 
16517c4c44SAl Cooper struct out_pin {
17517c4c44SAl Cooper 	u32 enable_mask;
18517c4c44SAl Cooper 	u32 value_mask;
19517c4c44SAl Cooper 	u32 changed_mask;
20517c4c44SAl Cooper 	u32 clr_changed_mask;
21517c4c44SAl Cooper 	struct gpio_desc *gpiod;
22517c4c44SAl Cooper 	const char *name;
23517c4c44SAl Cooper };
24517c4c44SAl Cooper 
25517c4c44SAl Cooper struct in_pin {
26517c4c44SAl Cooper 	u32 enable_mask;
27517c4c44SAl Cooper 	u32 value_mask;
28517c4c44SAl Cooper 	struct gpio_desc *gpiod;
29517c4c44SAl Cooper 	const char *name;
30517c4c44SAl Cooper 	struct brcmstb_usb_pinmap_data *pdata;
31517c4c44SAl Cooper };
32517c4c44SAl Cooper 
33517c4c44SAl Cooper struct brcmstb_usb_pinmap_data {
34517c4c44SAl Cooper 	void __iomem *regs;
35517c4c44SAl Cooper 	int in_count;
36517c4c44SAl Cooper 	struct in_pin *in_pins;
37517c4c44SAl Cooper 	int out_count;
38517c4c44SAl Cooper 	struct out_pin *out_pins;
39517c4c44SAl Cooper };
40517c4c44SAl Cooper 
41517c4c44SAl Cooper 
pinmap_set(void __iomem * reg,u32 mask)42517c4c44SAl Cooper static void pinmap_set(void __iomem *reg, u32 mask)
43517c4c44SAl Cooper {
44517c4c44SAl Cooper 	u32 val;
45517c4c44SAl Cooper 
46517c4c44SAl Cooper 	val = readl(reg);
47517c4c44SAl Cooper 	val |= mask;
48517c4c44SAl Cooper 	writel(val, reg);
49517c4c44SAl Cooper }
50517c4c44SAl Cooper 
pinmap_unset(void __iomem * reg,u32 mask)51517c4c44SAl Cooper static void pinmap_unset(void __iomem *reg, u32 mask)
52517c4c44SAl Cooper {
53517c4c44SAl Cooper 	u32 val;
54517c4c44SAl Cooper 
55517c4c44SAl Cooper 	val = readl(reg);
56517c4c44SAl Cooper 	val &= ~mask;
57517c4c44SAl Cooper 	writel(val, reg);
58517c4c44SAl Cooper }
59517c4c44SAl Cooper 
sync_in_pin(struct in_pin * pin)60517c4c44SAl Cooper static void sync_in_pin(struct in_pin *pin)
61517c4c44SAl Cooper {
62517c4c44SAl Cooper 	u32 val;
63517c4c44SAl Cooper 
64517c4c44SAl Cooper 	val = gpiod_get_value(pin->gpiod);
65517c4c44SAl Cooper 	if (val)
66517c4c44SAl Cooper 		pinmap_set(pin->pdata->regs, pin->value_mask);
67517c4c44SAl Cooper 	else
68517c4c44SAl Cooper 		pinmap_unset(pin->pdata->regs, pin->value_mask);
69517c4c44SAl Cooper }
70517c4c44SAl Cooper 
71517c4c44SAl Cooper /*
72517c4c44SAl Cooper  * Interrupt from override register, propagate from override bit
73517c4c44SAl Cooper  * to GPIO.
74517c4c44SAl Cooper  */
brcmstb_usb_pinmap_ovr_isr(int irq,void * dev_id)75517c4c44SAl Cooper static irqreturn_t brcmstb_usb_pinmap_ovr_isr(int irq, void *dev_id)
76517c4c44SAl Cooper {
77517c4c44SAl Cooper 	struct brcmstb_usb_pinmap_data *pdata = dev_id;
78517c4c44SAl Cooper 	struct out_pin *pout;
79517c4c44SAl Cooper 	u32 val;
80517c4c44SAl Cooper 	u32 bit;
81517c4c44SAl Cooper 	int x;
82517c4c44SAl Cooper 
83517c4c44SAl Cooper 	pr_debug("%s: reg: 0x%x\n", __func__, readl(pdata->regs));
84517c4c44SAl Cooper 	pout = pdata->out_pins;
85517c4c44SAl Cooper 	for (x = 0; x < pdata->out_count; x++) {
86517c4c44SAl Cooper 		val = readl(pdata->regs);
87517c4c44SAl Cooper 		if (val & pout->changed_mask) {
88517c4c44SAl Cooper 			pinmap_set(pdata->regs, pout->clr_changed_mask);
89517c4c44SAl Cooper 			pinmap_unset(pdata->regs, pout->clr_changed_mask);
90517c4c44SAl Cooper 			bit = val & pout->value_mask;
91517c4c44SAl Cooper 			gpiod_set_value(pout->gpiod, bit ? 1 : 0);
92517c4c44SAl Cooper 			pr_debug("%s: %s bit changed state to %d\n",
93517c4c44SAl Cooper 				 __func__, pout->name, bit ? 1 : 0);
94517c4c44SAl Cooper 		}
95517c4c44SAl Cooper 	}
96517c4c44SAl Cooper 	return IRQ_HANDLED;
97517c4c44SAl Cooper }
98517c4c44SAl Cooper 
99517c4c44SAl Cooper /*
100517c4c44SAl Cooper  * Interrupt from GPIO, propagate from GPIO to override bit.
101517c4c44SAl Cooper  */
brcmstb_usb_pinmap_gpio_isr(int irq,void * dev_id)102517c4c44SAl Cooper static irqreturn_t brcmstb_usb_pinmap_gpio_isr(int irq, void *dev_id)
103517c4c44SAl Cooper {
104517c4c44SAl Cooper 	struct in_pin *pin = dev_id;
105517c4c44SAl Cooper 
106517c4c44SAl Cooper 	pr_debug("%s: %s pin changed state\n", __func__, pin->name);
107517c4c44SAl Cooper 	sync_in_pin(pin);
108517c4c44SAl Cooper 	return IRQ_HANDLED;
109517c4c44SAl Cooper }
110517c4c44SAl Cooper 
111517c4c44SAl Cooper 
get_pin_counts(struct device_node * dn,int * in_count,int * out_count)112517c4c44SAl Cooper static void get_pin_counts(struct device_node *dn, int *in_count,
113517c4c44SAl Cooper 			   int *out_count)
114517c4c44SAl Cooper {
115517c4c44SAl Cooper 	int in;
116517c4c44SAl Cooper 	int out;
117517c4c44SAl Cooper 
118517c4c44SAl Cooper 	*in_count = 0;
119517c4c44SAl Cooper 	*out_count = 0;
120517c4c44SAl Cooper 	in = of_property_count_strings(dn, "brcm,in-functions");
121517c4c44SAl Cooper 	if (in < 0)
122517c4c44SAl Cooper 		return;
123517c4c44SAl Cooper 	out = of_property_count_strings(dn, "brcm,out-functions");
124517c4c44SAl Cooper 	if (out < 0)
125517c4c44SAl Cooper 		return;
126517c4c44SAl Cooper 	*in_count = in;
127517c4c44SAl Cooper 	*out_count = out;
128517c4c44SAl Cooper }
129517c4c44SAl Cooper 
parse_pins(struct device * dev,struct device_node * dn,struct brcmstb_usb_pinmap_data * pdata)130517c4c44SAl Cooper static int parse_pins(struct device *dev, struct device_node *dn,
131517c4c44SAl Cooper 		      struct brcmstb_usb_pinmap_data *pdata)
132517c4c44SAl Cooper {
133517c4c44SAl Cooper 	struct out_pin *pout;
134517c4c44SAl Cooper 	struct in_pin *pin;
135517c4c44SAl Cooper 	int index;
136517c4c44SAl Cooper 	int res;
137517c4c44SAl Cooper 	int x;
138517c4c44SAl Cooper 
139517c4c44SAl Cooper 	pin = pdata->in_pins;
140517c4c44SAl Cooper 	for (x = 0, index = 0; x < pdata->in_count; x++) {
141517c4c44SAl Cooper 		pin->gpiod = devm_gpiod_get_index(dev, "in", x, GPIOD_IN);
142517c4c44SAl Cooper 		if (IS_ERR(pin->gpiod)) {
143517c4c44SAl Cooper 			dev_err(dev, "Error getting gpio %s\n", pin->name);
144517c4c44SAl Cooper 			return PTR_ERR(pin->gpiod);
145517c4c44SAl Cooper 
146517c4c44SAl Cooper 		}
147517c4c44SAl Cooper 		res = of_property_read_string_index(dn, "brcm,in-functions", x,
148517c4c44SAl Cooper 						    &pin->name);
149517c4c44SAl Cooper 		if (res < 0) {
150517c4c44SAl Cooper 			dev_err(dev, "Error getting brcm,in-functions for %s\n",
151517c4c44SAl Cooper 				pin->name);
152517c4c44SAl Cooper 			return res;
153517c4c44SAl Cooper 		}
154517c4c44SAl Cooper 		res = of_property_read_u32_index(dn, "brcm,in-masks", index++,
155517c4c44SAl Cooper 						 &pin->enable_mask);
156517c4c44SAl Cooper 		if (res < 0) {
157517c4c44SAl Cooper 			dev_err(dev, "Error getting 1st brcm,in-masks for %s\n",
158517c4c44SAl Cooper 				pin->name);
159517c4c44SAl Cooper 			return res;
160517c4c44SAl Cooper 		}
161517c4c44SAl Cooper 		res = of_property_read_u32_index(dn, "brcm,in-masks", index++,
162517c4c44SAl Cooper 						 &pin->value_mask);
163517c4c44SAl Cooper 		if (res < 0) {
164517c4c44SAl Cooper 			dev_err(dev, "Error getting 2nd brcm,in-masks for %s\n",
165517c4c44SAl Cooper 				pin->name);
166517c4c44SAl Cooper 			return res;
167517c4c44SAl Cooper 		}
168517c4c44SAl Cooper 		pin->pdata = pdata;
169517c4c44SAl Cooper 		pin++;
170517c4c44SAl Cooper 	}
171517c4c44SAl Cooper 	pout = pdata->out_pins;
172517c4c44SAl Cooper 	for (x = 0, index = 0; x < pdata->out_count; x++) {
173517c4c44SAl Cooper 		pout->gpiod = devm_gpiod_get_index(dev, "out", x,
174517c4c44SAl Cooper 						   GPIOD_OUT_HIGH);
175517c4c44SAl Cooper 		if (IS_ERR(pout->gpiod)) {
176517c4c44SAl Cooper 			dev_err(dev, "Error getting gpio %s\n", pin->name);
177517c4c44SAl Cooper 			return PTR_ERR(pout->gpiod);
178517c4c44SAl Cooper 		}
179517c4c44SAl Cooper 		res = of_property_read_string_index(dn, "brcm,out-functions", x,
180517c4c44SAl Cooper 						    &pout->name);
181517c4c44SAl Cooper 		if (res < 0) {
182517c4c44SAl Cooper 			dev_err(dev, "Error getting brcm,out-functions for %s\n",
183517c4c44SAl Cooper 				pout->name);
184517c4c44SAl Cooper 			return res;
185517c4c44SAl Cooper 		}
186517c4c44SAl Cooper 		res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
187517c4c44SAl Cooper 						 &pout->enable_mask);
188517c4c44SAl Cooper 		if (res < 0) {
189517c4c44SAl Cooper 			dev_err(dev, "Error getting 1st brcm,out-masks for %s\n",
190517c4c44SAl Cooper 				pout->name);
191517c4c44SAl Cooper 			return res;
192517c4c44SAl Cooper 		}
193517c4c44SAl Cooper 		res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
194517c4c44SAl Cooper 						 &pout->value_mask);
195517c4c44SAl Cooper 		if (res < 0) {
196517c4c44SAl Cooper 			dev_err(dev, "Error getting 2nd brcm,out-masks for %s\n",
197517c4c44SAl Cooper 				pout->name);
198517c4c44SAl Cooper 			return res;
199517c4c44SAl Cooper 		}
200517c4c44SAl Cooper 		res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
201517c4c44SAl Cooper 						 &pout->changed_mask);
202517c4c44SAl Cooper 		if (res < 0) {
203517c4c44SAl Cooper 			dev_err(dev, "Error getting 3rd brcm,out-masks for %s\n",
204517c4c44SAl Cooper 				pout->name);
205517c4c44SAl Cooper 			return res;
206517c4c44SAl Cooper 		}
207517c4c44SAl Cooper 		res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
208517c4c44SAl Cooper 						 &pout->clr_changed_mask);
209517c4c44SAl Cooper 		if (res < 0) {
210517c4c44SAl Cooper 			dev_err(dev, "Error getting 4th out-masks for %s\n",
211517c4c44SAl Cooper 				pout->name);
212517c4c44SAl Cooper 			return res;
213517c4c44SAl Cooper 		}
214517c4c44SAl Cooper 		pout++;
215517c4c44SAl Cooper 	}
216517c4c44SAl Cooper 	return 0;
217517c4c44SAl Cooper }
218517c4c44SAl Cooper 
sync_all_pins(struct brcmstb_usb_pinmap_data * pdata)2199e39aef3SZou Wei static void sync_all_pins(struct brcmstb_usb_pinmap_data *pdata)
220517c4c44SAl Cooper {
221517c4c44SAl Cooper 	struct out_pin *pout;
222517c4c44SAl Cooper 	struct in_pin *pin;
223517c4c44SAl Cooper 	int val;
224517c4c44SAl Cooper 	int x;
225517c4c44SAl Cooper 
226517c4c44SAl Cooper 	/*
227517c4c44SAl Cooper 	 * Enable the override, clear any changed condition and
228517c4c44SAl Cooper 	 * propagate the state to the GPIO for all out pins.
229517c4c44SAl Cooper 	 */
230517c4c44SAl Cooper 	pout = pdata->out_pins;
231517c4c44SAl Cooper 	for (x = 0; x < pdata->out_count; x++) {
232517c4c44SAl Cooper 		pinmap_set(pdata->regs, pout->enable_mask);
233517c4c44SAl Cooper 		pinmap_set(pdata->regs, pout->clr_changed_mask);
234517c4c44SAl Cooper 		pinmap_unset(pdata->regs, pout->clr_changed_mask);
235517c4c44SAl Cooper 		val = readl(pdata->regs) & pout->value_mask;
236517c4c44SAl Cooper 		gpiod_set_value(pout->gpiod, val ? 1 : 0);
237517c4c44SAl Cooper 		pout++;
238517c4c44SAl Cooper 	}
239517c4c44SAl Cooper 
240517c4c44SAl Cooper 	/* sync and enable all in pins. */
241517c4c44SAl Cooper 	pin = pdata->in_pins;
242517c4c44SAl Cooper 	for (x = 0; x < pdata->in_count; x++) {
243517c4c44SAl Cooper 		sync_in_pin(pin);
244517c4c44SAl Cooper 		pinmap_set(pdata->regs, pin->enable_mask);
245517c4c44SAl Cooper 		pin++;
246517c4c44SAl Cooper 	}
247517c4c44SAl Cooper }
248517c4c44SAl Cooper 
brcmstb_usb_pinmap_probe(struct platform_device * pdev)249517c4c44SAl Cooper static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev)
250517c4c44SAl Cooper {
251517c4c44SAl Cooper 	struct device_node *dn = pdev->dev.of_node;
252517c4c44SAl Cooper 	struct brcmstb_usb_pinmap_data *pdata;
253517c4c44SAl Cooper 	struct in_pin *pin;
254517c4c44SAl Cooper 	struct resource *r;
255517c4c44SAl Cooper 	int out_count;
256517c4c44SAl Cooper 	int in_count;
257517c4c44SAl Cooper 	int err;
258517c4c44SAl Cooper 	int irq;
259517c4c44SAl Cooper 	int x;
260517c4c44SAl Cooper 
261517c4c44SAl Cooper 	get_pin_counts(dn, &in_count, &out_count);
262517c4c44SAl Cooper 	if ((in_count + out_count) == 0)
263517c4c44SAl Cooper 		return -EINVAL;
264517c4c44SAl Cooper 
265517c4c44SAl Cooper 	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
266fbf649cdSYang Yingliang 	if (!r)
267fbf649cdSYang Yingliang 		return -EINVAL;
268517c4c44SAl Cooper 
269517c4c44SAl Cooper 	pdata = devm_kzalloc(&pdev->dev,
270517c4c44SAl Cooper 			     sizeof(*pdata) +
271517c4c44SAl Cooper 			     (sizeof(struct in_pin) * in_count) +
272517c4c44SAl Cooper 			     (sizeof(struct out_pin) * out_count), GFP_KERNEL);
273517c4c44SAl Cooper 	if (!pdata)
274517c4c44SAl Cooper 		return -ENOMEM;
275517c4c44SAl Cooper 
276517c4c44SAl Cooper 	pdata->in_count = in_count;
277517c4c44SAl Cooper 	pdata->out_count = out_count;
278517c4c44SAl Cooper 	pdata->in_pins = (struct in_pin *)(pdata + 1);
279517c4c44SAl Cooper 	pdata->out_pins = (struct out_pin *)(pdata->in_pins + in_count);
280517c4c44SAl Cooper 
281517c4c44SAl Cooper 	pdata->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
28235ad0d90SDan Carpenter 	if (!pdata->regs)
28335ad0d90SDan Carpenter 		return -ENOMEM;
284517c4c44SAl Cooper 	platform_set_drvdata(pdev, pdata);
285517c4c44SAl Cooper 
286517c4c44SAl Cooper 	err = parse_pins(&pdev->dev, dn, pdata);
287517c4c44SAl Cooper 	if (err)
288517c4c44SAl Cooper 		return err;
289517c4c44SAl Cooper 
290517c4c44SAl Cooper 	sync_all_pins(pdata);
291517c4c44SAl Cooper 
292517c4c44SAl Cooper 	if (out_count) {
293517c4c44SAl Cooper 
294517c4c44SAl Cooper 		/* Enable interrupt for out pins */
295517c4c44SAl Cooper 		irq = platform_get_irq(pdev, 0);
296*711087f3SSergey Shtylyov 		if (irq < 0)
297*711087f3SSergey Shtylyov 			return irq;
298517c4c44SAl Cooper 		err = devm_request_irq(&pdev->dev, irq,
299517c4c44SAl Cooper 				       brcmstb_usb_pinmap_ovr_isr,
300517c4c44SAl Cooper 				       IRQF_TRIGGER_RISING,
301517c4c44SAl Cooper 				       pdev->name, pdata);
302517c4c44SAl Cooper 		if (err < 0) {
303517c4c44SAl Cooper 			dev_err(&pdev->dev, "Error requesting IRQ\n");
304517c4c44SAl Cooper 			return err;
305517c4c44SAl Cooper 		}
306517c4c44SAl Cooper 	}
307517c4c44SAl Cooper 
308517c4c44SAl Cooper 	for (x = 0, pin = pdata->in_pins; x < pdata->in_count; x++, pin++) {
309517c4c44SAl Cooper 		irq = gpiod_to_irq(pin->gpiod);
310517c4c44SAl Cooper 		if (irq < 0) {
311517c4c44SAl Cooper 			dev_err(&pdev->dev, "Error getting IRQ for %s pin\n",
312517c4c44SAl Cooper 				pin->name);
313517c4c44SAl Cooper 			return irq;
314517c4c44SAl Cooper 		}
315517c4c44SAl Cooper 		err = devm_request_irq(&pdev->dev, irq,
316517c4c44SAl Cooper 				       brcmstb_usb_pinmap_gpio_isr,
317517c4c44SAl Cooper 				       IRQF_SHARED | IRQF_TRIGGER_RISING |
318517c4c44SAl Cooper 				       IRQF_TRIGGER_FALLING,
319517c4c44SAl Cooper 				       pdev->name, pin);
320517c4c44SAl Cooper 		if (err < 0) {
321517c4c44SAl Cooper 			dev_err(&pdev->dev, "Error requesting IRQ for %s pin\n",
322517c4c44SAl Cooper 				pin->name);
323517c4c44SAl Cooper 			return err;
324517c4c44SAl Cooper 		}
325517c4c44SAl Cooper 	}
326517c4c44SAl Cooper 
327517c4c44SAl Cooper 	dev_dbg(&pdev->dev, "Driver probe succeeded\n");
328517c4c44SAl Cooper 	dev_dbg(&pdev->dev, "In pin count: %d, out pin count: %d\n",
329517c4c44SAl Cooper 		pdata->in_count, pdata->out_count);
330517c4c44SAl Cooper 	return 0;
331517c4c44SAl Cooper }
332517c4c44SAl Cooper 
333517c4c44SAl Cooper 
334517c4c44SAl Cooper static const struct of_device_id brcmstb_usb_pinmap_of_match[] = {
335517c4c44SAl Cooper 	{ .compatible = "brcm,usb-pinmap" },
336517c4c44SAl Cooper 	{ },
337517c4c44SAl Cooper };
338517c4c44SAl Cooper 
339517c4c44SAl Cooper static struct platform_driver brcmstb_usb_pinmap_driver = {
340517c4c44SAl Cooper 	.driver = {
341517c4c44SAl Cooper 		.name	= "brcm-usb-pinmap",
342517c4c44SAl Cooper 		.of_match_table = brcmstb_usb_pinmap_of_match,
343517c4c44SAl Cooper 	},
344517c4c44SAl Cooper };
345517c4c44SAl Cooper 
brcmstb_usb_pinmap_init(void)346517c4c44SAl Cooper static int __init brcmstb_usb_pinmap_init(void)
347517c4c44SAl Cooper {
348517c4c44SAl Cooper 	return platform_driver_probe(&brcmstb_usb_pinmap_driver,
349517c4c44SAl Cooper 				     brcmstb_usb_pinmap_probe);
350517c4c44SAl Cooper }
351517c4c44SAl Cooper 
352517c4c44SAl Cooper module_init(brcmstb_usb_pinmap_init);
353517c4c44SAl Cooper MODULE_AUTHOR("Al Cooper <alcooperx@gmail.com>");
354517c4c44SAl Cooper MODULE_DESCRIPTION("Broadcom USB Pinmap Driver");
355517c4c44SAl Cooper MODULE_LICENSE("GPL");
356