xref: /linux/drivers/gpio/gpio-aggregator.c (revision cdd5b5a9761fd66d17586e4f4ba6588c70e640ea)
1828546e2SGeert Uytterhoeven // SPDX-License-Identifier: GPL-2.0-only
2828546e2SGeert Uytterhoeven //
3828546e2SGeert Uytterhoeven // GPIO Aggregator
4828546e2SGeert Uytterhoeven //
5828546e2SGeert Uytterhoeven // Copyright (C) 2019-2020 Glider bv
6828546e2SGeert Uytterhoeven 
7828546e2SGeert Uytterhoeven #define DRV_NAME       "gpio-aggregator"
8828546e2SGeert Uytterhoeven #define pr_fmt(fmt)	DRV_NAME ": " fmt
9828546e2SGeert Uytterhoeven 
10828546e2SGeert Uytterhoeven #include <linux/bitmap.h>
11828546e2SGeert Uytterhoeven #include <linux/bitops.h>
12828546e2SGeert Uytterhoeven #include <linux/ctype.h>
13b466622cSAndy Shevchenko #include <linux/delay.h>
14828546e2SGeert Uytterhoeven #include <linux/idr.h>
15828546e2SGeert Uytterhoeven #include <linux/kernel.h>
16b89a9e98SAndy Shevchenko #include <linux/mod_devicetable.h>
17828546e2SGeert Uytterhoeven #include <linux/module.h>
18828546e2SGeert Uytterhoeven #include <linux/mutex.h>
19828546e2SGeert Uytterhoeven #include <linux/overflow.h>
20828546e2SGeert Uytterhoeven #include <linux/platform_device.h>
21*81674beaSAndy Shevchenko #include <linux/property.h>
2239ebbd52SAndy Shevchenko #include <linux/slab.h>
23828546e2SGeert Uytterhoeven #include <linux/spinlock.h>
24828546e2SGeert Uytterhoeven #include <linux/string.h>
25828546e2SGeert Uytterhoeven 
2639ebbd52SAndy Shevchenko #include <linux/gpio/consumer.h>
2739ebbd52SAndy Shevchenko #include <linux/gpio/driver.h>
2839ebbd52SAndy Shevchenko #include <linux/gpio/machine.h>
2939ebbd52SAndy Shevchenko 
3095b39792SChristophe Leroy #define AGGREGATOR_MAX_GPIOS 512
31828546e2SGeert Uytterhoeven 
32828546e2SGeert Uytterhoeven /*
33828546e2SGeert Uytterhoeven  * GPIO Aggregator sysfs interface
34828546e2SGeert Uytterhoeven  */
35828546e2SGeert Uytterhoeven 
36828546e2SGeert Uytterhoeven struct gpio_aggregator {
37828546e2SGeert Uytterhoeven 	struct gpiod_lookup_table *lookups;
38828546e2SGeert Uytterhoeven 	struct platform_device *pdev;
39828546e2SGeert Uytterhoeven 	char args[];
40828546e2SGeert Uytterhoeven };
41828546e2SGeert Uytterhoeven 
42828546e2SGeert Uytterhoeven static DEFINE_MUTEX(gpio_aggregator_lock);	/* protects idr */
43828546e2SGeert Uytterhoeven static DEFINE_IDR(gpio_aggregator_idr);
44828546e2SGeert Uytterhoeven 
aggr_add_gpio(struct gpio_aggregator * aggr,const char * key,int hwnum,unsigned int * n)45828546e2SGeert Uytterhoeven static int aggr_add_gpio(struct gpio_aggregator *aggr, const char *key,
46828546e2SGeert Uytterhoeven 			 int hwnum, unsigned int *n)
47828546e2SGeert Uytterhoeven {
48828546e2SGeert Uytterhoeven 	struct gpiod_lookup_table *lookups;
49828546e2SGeert Uytterhoeven 
50828546e2SGeert Uytterhoeven 	lookups = krealloc(aggr->lookups, struct_size(lookups, table, *n + 2),
51828546e2SGeert Uytterhoeven 			   GFP_KERNEL);
52828546e2SGeert Uytterhoeven 	if (!lookups)
53828546e2SGeert Uytterhoeven 		return -ENOMEM;
54828546e2SGeert Uytterhoeven 
55b2498cb8SAndy Shevchenko 	lookups->table[*n] = GPIO_LOOKUP_IDX(key, hwnum, NULL, *n, 0);
56828546e2SGeert Uytterhoeven 
57828546e2SGeert Uytterhoeven 	(*n)++;
58828546e2SGeert Uytterhoeven 	memset(&lookups->table[*n], 0, sizeof(lookups->table[*n]));
59828546e2SGeert Uytterhoeven 
60828546e2SGeert Uytterhoeven 	aggr->lookups = lookups;
61828546e2SGeert Uytterhoeven 	return 0;
62828546e2SGeert Uytterhoeven }
63828546e2SGeert Uytterhoeven 
aggr_parse(struct gpio_aggregator * aggr)64828546e2SGeert Uytterhoeven static int aggr_parse(struct gpio_aggregator *aggr)
65828546e2SGeert Uytterhoeven {
66ac505b6fSAndy Shevchenko 	char *args = skip_spaces(aggr->args);
67deb631c4SAndy Shevchenko 	char *name, *offsets, *p;
68ec75039dSGeert Uytterhoeven 	unsigned long *bitmap;
69ec75039dSGeert Uytterhoeven 	unsigned int i, n = 0;
70ec75039dSGeert Uytterhoeven 	int error = 0;
71ec75039dSGeert Uytterhoeven 
7295b39792SChristophe Leroy 	bitmap = bitmap_alloc(AGGREGATOR_MAX_GPIOS, GFP_KERNEL);
73ec75039dSGeert Uytterhoeven 	if (!bitmap)
74ec75039dSGeert Uytterhoeven 		return -ENOMEM;
75828546e2SGeert Uytterhoeven 
76ac505b6fSAndy Shevchenko 	args = next_arg(args, &name, &p);
77ac505b6fSAndy Shevchenko 	while (*args) {
78ac505b6fSAndy Shevchenko 		args = next_arg(args, &offsets, &p);
79828546e2SGeert Uytterhoeven 
80deb631c4SAndy Shevchenko 		p = get_options(offsets, 0, &error);
81deb631c4SAndy Shevchenko 		if (error == 0 || *p) {
82828546e2SGeert Uytterhoeven 			/* Named GPIO line */
83828546e2SGeert Uytterhoeven 			error = aggr_add_gpio(aggr, name, U16_MAX, &n);
84828546e2SGeert Uytterhoeven 			if (error)
85ec75039dSGeert Uytterhoeven 				goto free_bitmap;
86828546e2SGeert Uytterhoeven 
87828546e2SGeert Uytterhoeven 			name = offsets;
88828546e2SGeert Uytterhoeven 			continue;
89828546e2SGeert Uytterhoeven 		}
90828546e2SGeert Uytterhoeven 
91828546e2SGeert Uytterhoeven 		/* GPIO chip + offset(s) */
9295b39792SChristophe Leroy 		error = bitmap_parselist(offsets, bitmap, AGGREGATOR_MAX_GPIOS);
93ec75039dSGeert Uytterhoeven 		if (error) {
94ec75039dSGeert Uytterhoeven 			pr_err("Cannot parse %s: %d\n", offsets, error);
95ec75039dSGeert Uytterhoeven 			goto free_bitmap;
96828546e2SGeert Uytterhoeven 		}
97828546e2SGeert Uytterhoeven 
9895b39792SChristophe Leroy 		for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) {
99828546e2SGeert Uytterhoeven 			error = aggr_add_gpio(aggr, name, i, &n);
100828546e2SGeert Uytterhoeven 			if (error)
101ec75039dSGeert Uytterhoeven 				goto free_bitmap;
102828546e2SGeert Uytterhoeven 		}
103828546e2SGeert Uytterhoeven 
104ac505b6fSAndy Shevchenko 		args = next_arg(args, &name, &p);
105828546e2SGeert Uytterhoeven 	}
106828546e2SGeert Uytterhoeven 
107828546e2SGeert Uytterhoeven 	if (!n) {
108828546e2SGeert Uytterhoeven 		pr_err("No GPIOs specified\n");
109ec75039dSGeert Uytterhoeven 		error = -EINVAL;
110828546e2SGeert Uytterhoeven 	}
111828546e2SGeert Uytterhoeven 
112ec75039dSGeert Uytterhoeven free_bitmap:
113ec75039dSGeert Uytterhoeven 	bitmap_free(bitmap);
114ec75039dSGeert Uytterhoeven 	return error;
115828546e2SGeert Uytterhoeven }
116828546e2SGeert Uytterhoeven 
new_device_store(struct device_driver * driver,const char * buf,size_t count)117828546e2SGeert Uytterhoeven static ssize_t new_device_store(struct device_driver *driver, const char *buf,
118828546e2SGeert Uytterhoeven 				size_t count)
119828546e2SGeert Uytterhoeven {
120828546e2SGeert Uytterhoeven 	struct gpio_aggregator *aggr;
121828546e2SGeert Uytterhoeven 	struct platform_device *pdev;
122828546e2SGeert Uytterhoeven 	int res, id;
123828546e2SGeert Uytterhoeven 
124828546e2SGeert Uytterhoeven 	/* kernfs guarantees string termination, so count + 1 is safe */
125828546e2SGeert Uytterhoeven 	aggr = kzalloc(sizeof(*aggr) + count + 1, GFP_KERNEL);
126828546e2SGeert Uytterhoeven 	if (!aggr)
127828546e2SGeert Uytterhoeven 		return -ENOMEM;
128828546e2SGeert Uytterhoeven 
129828546e2SGeert Uytterhoeven 	memcpy(aggr->args, buf, count + 1);
130828546e2SGeert Uytterhoeven 
131828546e2SGeert Uytterhoeven 	aggr->lookups = kzalloc(struct_size(aggr->lookups, table, 1),
132828546e2SGeert Uytterhoeven 				GFP_KERNEL);
133828546e2SGeert Uytterhoeven 	if (!aggr->lookups) {
134828546e2SGeert Uytterhoeven 		res = -ENOMEM;
135828546e2SGeert Uytterhoeven 		goto free_ga;
136828546e2SGeert Uytterhoeven 	}
137828546e2SGeert Uytterhoeven 
138828546e2SGeert Uytterhoeven 	mutex_lock(&gpio_aggregator_lock);
139828546e2SGeert Uytterhoeven 	id = idr_alloc(&gpio_aggregator_idr, aggr, 0, 0, GFP_KERNEL);
140828546e2SGeert Uytterhoeven 	mutex_unlock(&gpio_aggregator_lock);
141828546e2SGeert Uytterhoeven 
142828546e2SGeert Uytterhoeven 	if (id < 0) {
143828546e2SGeert Uytterhoeven 		res = id;
144828546e2SGeert Uytterhoeven 		goto free_table;
145828546e2SGeert Uytterhoeven 	}
146828546e2SGeert Uytterhoeven 
147828546e2SGeert Uytterhoeven 	aggr->lookups->dev_id = kasprintf(GFP_KERNEL, "%s.%d", DRV_NAME, id);
148828546e2SGeert Uytterhoeven 	if (!aggr->lookups->dev_id) {
149828546e2SGeert Uytterhoeven 		res = -ENOMEM;
150828546e2SGeert Uytterhoeven 		goto remove_idr;
151828546e2SGeert Uytterhoeven 	}
152828546e2SGeert Uytterhoeven 
153828546e2SGeert Uytterhoeven 	res = aggr_parse(aggr);
154828546e2SGeert Uytterhoeven 	if (res)
155828546e2SGeert Uytterhoeven 		goto free_dev_id;
156828546e2SGeert Uytterhoeven 
157828546e2SGeert Uytterhoeven 	gpiod_add_lookup_table(aggr->lookups);
158828546e2SGeert Uytterhoeven 
159828546e2SGeert Uytterhoeven 	pdev = platform_device_register_simple(DRV_NAME, id, NULL, 0);
160828546e2SGeert Uytterhoeven 	if (IS_ERR(pdev)) {
161828546e2SGeert Uytterhoeven 		res = PTR_ERR(pdev);
162828546e2SGeert Uytterhoeven 		goto remove_table;
163828546e2SGeert Uytterhoeven 	}
164828546e2SGeert Uytterhoeven 
165828546e2SGeert Uytterhoeven 	aggr->pdev = pdev;
166828546e2SGeert Uytterhoeven 	return count;
167828546e2SGeert Uytterhoeven 
168828546e2SGeert Uytterhoeven remove_table:
169828546e2SGeert Uytterhoeven 	gpiod_remove_lookup_table(aggr->lookups);
170828546e2SGeert Uytterhoeven free_dev_id:
171828546e2SGeert Uytterhoeven 	kfree(aggr->lookups->dev_id);
172828546e2SGeert Uytterhoeven remove_idr:
173828546e2SGeert Uytterhoeven 	mutex_lock(&gpio_aggregator_lock);
174828546e2SGeert Uytterhoeven 	idr_remove(&gpio_aggregator_idr, id);
175828546e2SGeert Uytterhoeven 	mutex_unlock(&gpio_aggregator_lock);
176828546e2SGeert Uytterhoeven free_table:
177828546e2SGeert Uytterhoeven 	kfree(aggr->lookups);
178828546e2SGeert Uytterhoeven free_ga:
179828546e2SGeert Uytterhoeven 	kfree(aggr);
180828546e2SGeert Uytterhoeven 	return res;
181828546e2SGeert Uytterhoeven }
182828546e2SGeert Uytterhoeven 
183828546e2SGeert Uytterhoeven static DRIVER_ATTR_WO(new_device);
184828546e2SGeert Uytterhoeven 
gpio_aggregator_free(struct gpio_aggregator * aggr)185828546e2SGeert Uytterhoeven static void gpio_aggregator_free(struct gpio_aggregator *aggr)
186828546e2SGeert Uytterhoeven {
187828546e2SGeert Uytterhoeven 	platform_device_unregister(aggr->pdev);
188828546e2SGeert Uytterhoeven 	gpiod_remove_lookup_table(aggr->lookups);
189828546e2SGeert Uytterhoeven 	kfree(aggr->lookups->dev_id);
190828546e2SGeert Uytterhoeven 	kfree(aggr->lookups);
191828546e2SGeert Uytterhoeven 	kfree(aggr);
192828546e2SGeert Uytterhoeven }
193828546e2SGeert Uytterhoeven 
delete_device_store(struct device_driver * driver,const char * buf,size_t count)194828546e2SGeert Uytterhoeven static ssize_t delete_device_store(struct device_driver *driver,
195828546e2SGeert Uytterhoeven 				   const char *buf, size_t count)
196828546e2SGeert Uytterhoeven {
197828546e2SGeert Uytterhoeven 	struct gpio_aggregator *aggr;
198828546e2SGeert Uytterhoeven 	unsigned int id;
199828546e2SGeert Uytterhoeven 	int error;
200828546e2SGeert Uytterhoeven 
201828546e2SGeert Uytterhoeven 	if (!str_has_prefix(buf, DRV_NAME "."))
202828546e2SGeert Uytterhoeven 		return -EINVAL;
203828546e2SGeert Uytterhoeven 
204828546e2SGeert Uytterhoeven 	error = kstrtouint(buf + strlen(DRV_NAME "."), 10, &id);
205828546e2SGeert Uytterhoeven 	if (error)
206828546e2SGeert Uytterhoeven 		return error;
207828546e2SGeert Uytterhoeven 
208828546e2SGeert Uytterhoeven 	mutex_lock(&gpio_aggregator_lock);
209828546e2SGeert Uytterhoeven 	aggr = idr_remove(&gpio_aggregator_idr, id);
210828546e2SGeert Uytterhoeven 	mutex_unlock(&gpio_aggregator_lock);
211828546e2SGeert Uytterhoeven 	if (!aggr)
212828546e2SGeert Uytterhoeven 		return -ENOENT;
213828546e2SGeert Uytterhoeven 
214828546e2SGeert Uytterhoeven 	gpio_aggregator_free(aggr);
215828546e2SGeert Uytterhoeven 	return count;
216828546e2SGeert Uytterhoeven }
217828546e2SGeert Uytterhoeven static DRIVER_ATTR_WO(delete_device);
218828546e2SGeert Uytterhoeven 
219828546e2SGeert Uytterhoeven static struct attribute *gpio_aggregator_attrs[] = {
220828546e2SGeert Uytterhoeven 	&driver_attr_new_device.attr,
221828546e2SGeert Uytterhoeven 	&driver_attr_delete_device.attr,
2226e004a98SAndy Shevchenko 	NULL
223828546e2SGeert Uytterhoeven };
224828546e2SGeert Uytterhoeven ATTRIBUTE_GROUPS(gpio_aggregator);
225828546e2SGeert Uytterhoeven 
gpio_aggregator_idr_remove(int id,void * p,void * data)226828546e2SGeert Uytterhoeven static int __exit gpio_aggregator_idr_remove(int id, void *p, void *data)
227828546e2SGeert Uytterhoeven {
228828546e2SGeert Uytterhoeven 	gpio_aggregator_free(p);
229828546e2SGeert Uytterhoeven 	return 0;
230828546e2SGeert Uytterhoeven }
231828546e2SGeert Uytterhoeven 
gpio_aggregator_remove_all(void)232828546e2SGeert Uytterhoeven static void __exit gpio_aggregator_remove_all(void)
233828546e2SGeert Uytterhoeven {
234828546e2SGeert Uytterhoeven 	mutex_lock(&gpio_aggregator_lock);
235828546e2SGeert Uytterhoeven 	idr_for_each(&gpio_aggregator_idr, gpio_aggregator_idr_remove, NULL);
236828546e2SGeert Uytterhoeven 	idr_destroy(&gpio_aggregator_idr);
237828546e2SGeert Uytterhoeven 	mutex_unlock(&gpio_aggregator_lock);
238828546e2SGeert Uytterhoeven }
239828546e2SGeert Uytterhoeven 
240828546e2SGeert Uytterhoeven 
241828546e2SGeert Uytterhoeven /*
242828546e2SGeert Uytterhoeven  *  GPIO Forwarder
243828546e2SGeert Uytterhoeven  */
244828546e2SGeert Uytterhoeven 
245b466622cSAndy Shevchenko struct gpiochip_fwd_timing {
246b466622cSAndy Shevchenko 	u32 ramp_up_us;
247b466622cSAndy Shevchenko 	u32 ramp_down_us;
248b466622cSAndy Shevchenko };
249b466622cSAndy Shevchenko 
250828546e2SGeert Uytterhoeven struct gpiochip_fwd {
251828546e2SGeert Uytterhoeven 	struct gpio_chip chip;
252828546e2SGeert Uytterhoeven 	struct gpio_desc **descs;
253828546e2SGeert Uytterhoeven 	union {
254828546e2SGeert Uytterhoeven 		struct mutex mlock;	/* protects tmp[] if can_sleep */
255828546e2SGeert Uytterhoeven 		spinlock_t slock;	/* protects tmp[] if !can_sleep */
256828546e2SGeert Uytterhoeven 	};
257b466622cSAndy Shevchenko 	struct gpiochip_fwd_timing *delay_timings;
258828546e2SGeert Uytterhoeven 	unsigned long tmp[];		/* values and descs for multiple ops */
259828546e2SGeert Uytterhoeven };
260828546e2SGeert Uytterhoeven 
261aa4858ebSGeert Uytterhoeven #define fwd_tmp_values(fwd)	&(fwd)->tmp[0]
262aa4858ebSGeert Uytterhoeven #define fwd_tmp_descs(fwd)	(void *)&(fwd)->tmp[BITS_TO_LONGS((fwd)->chip.ngpio)]
263aa4858ebSGeert Uytterhoeven 
264aa4858ebSGeert Uytterhoeven #define fwd_tmp_size(ngpios)	(BITS_TO_LONGS((ngpios)) + (ngpios))
265aa4858ebSGeert Uytterhoeven 
gpio_fwd_get_direction(struct gpio_chip * chip,unsigned int offset)266828546e2SGeert Uytterhoeven static int gpio_fwd_get_direction(struct gpio_chip *chip, unsigned int offset)
267828546e2SGeert Uytterhoeven {
268828546e2SGeert Uytterhoeven 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
269828546e2SGeert Uytterhoeven 
270828546e2SGeert Uytterhoeven 	return gpiod_get_direction(fwd->descs[offset]);
271828546e2SGeert Uytterhoeven }
272828546e2SGeert Uytterhoeven 
gpio_fwd_direction_input(struct gpio_chip * chip,unsigned int offset)273828546e2SGeert Uytterhoeven static int gpio_fwd_direction_input(struct gpio_chip *chip, unsigned int offset)
274828546e2SGeert Uytterhoeven {
275828546e2SGeert Uytterhoeven 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
276828546e2SGeert Uytterhoeven 
277828546e2SGeert Uytterhoeven 	return gpiod_direction_input(fwd->descs[offset]);
278828546e2SGeert Uytterhoeven }
279828546e2SGeert Uytterhoeven 
gpio_fwd_direction_output(struct gpio_chip * chip,unsigned int offset,int value)280828546e2SGeert Uytterhoeven static int gpio_fwd_direction_output(struct gpio_chip *chip,
281828546e2SGeert Uytterhoeven 				     unsigned int offset, int value)
282828546e2SGeert Uytterhoeven {
283828546e2SGeert Uytterhoeven 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
284828546e2SGeert Uytterhoeven 
285828546e2SGeert Uytterhoeven 	return gpiod_direction_output(fwd->descs[offset], value);
286828546e2SGeert Uytterhoeven }
287828546e2SGeert Uytterhoeven 
gpio_fwd_get(struct gpio_chip * chip,unsigned int offset)288828546e2SGeert Uytterhoeven static int gpio_fwd_get(struct gpio_chip *chip, unsigned int offset)
289828546e2SGeert Uytterhoeven {
290828546e2SGeert Uytterhoeven 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
291828546e2SGeert Uytterhoeven 
2922cba0545SGeert Uytterhoeven 	return chip->can_sleep ? gpiod_get_value_cansleep(fwd->descs[offset])
2932cba0545SGeert Uytterhoeven 			       : gpiod_get_value(fwd->descs[offset]);
294828546e2SGeert Uytterhoeven }
295828546e2SGeert Uytterhoeven 
gpio_fwd_get_multiple(struct gpiochip_fwd * fwd,unsigned long * mask,unsigned long * bits)29601e8d85bSAndy Shevchenko static int gpio_fwd_get_multiple(struct gpiochip_fwd *fwd, unsigned long *mask,
297828546e2SGeert Uytterhoeven 				 unsigned long *bits)
298828546e2SGeert Uytterhoeven {
299aa4858ebSGeert Uytterhoeven 	struct gpio_desc **descs = fwd_tmp_descs(fwd);
300aa4858ebSGeert Uytterhoeven 	unsigned long *values = fwd_tmp_values(fwd);
301828546e2SGeert Uytterhoeven 	unsigned int i, j = 0;
302828546e2SGeert Uytterhoeven 	int error;
303828546e2SGeert Uytterhoeven 
304828546e2SGeert Uytterhoeven 	bitmap_clear(values, 0, fwd->chip.ngpio);
305828546e2SGeert Uytterhoeven 	for_each_set_bit(i, mask, fwd->chip.ngpio)
306828546e2SGeert Uytterhoeven 		descs[j++] = fwd->descs[i];
307828546e2SGeert Uytterhoeven 
3082cba0545SGeert Uytterhoeven 	if (fwd->chip.can_sleep)
3092cba0545SGeert Uytterhoeven 		error = gpiod_get_array_value_cansleep(j, descs, NULL, values);
3102cba0545SGeert Uytterhoeven 	else
311828546e2SGeert Uytterhoeven 		error = gpiod_get_array_value(j, descs, NULL, values);
31201e8d85bSAndy Shevchenko 	if (error)
31301e8d85bSAndy Shevchenko 		return error;
31401e8d85bSAndy Shevchenko 
315828546e2SGeert Uytterhoeven 	j = 0;
316828546e2SGeert Uytterhoeven 	for_each_set_bit(i, mask, fwd->chip.ngpio)
317828546e2SGeert Uytterhoeven 		__assign_bit(i, bits, test_bit(j++, values));
31801e8d85bSAndy Shevchenko 
31901e8d85bSAndy Shevchenko 	return 0;
320828546e2SGeert Uytterhoeven }
321828546e2SGeert Uytterhoeven 
gpio_fwd_get_multiple_locked(struct gpio_chip * chip,unsigned long * mask,unsigned long * bits)32201e8d85bSAndy Shevchenko static int gpio_fwd_get_multiple_locked(struct gpio_chip *chip,
32301e8d85bSAndy Shevchenko 					unsigned long *mask, unsigned long *bits)
32401e8d85bSAndy Shevchenko {
32501e8d85bSAndy Shevchenko 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
32601e8d85bSAndy Shevchenko 	unsigned long flags;
32701e8d85bSAndy Shevchenko 	int error;
32801e8d85bSAndy Shevchenko 
32901e8d85bSAndy Shevchenko 	if (chip->can_sleep) {
33001e8d85bSAndy Shevchenko 		mutex_lock(&fwd->mlock);
33101e8d85bSAndy Shevchenko 		error = gpio_fwd_get_multiple(fwd, mask, bits);
332828546e2SGeert Uytterhoeven 		mutex_unlock(&fwd->mlock);
33301e8d85bSAndy Shevchenko 	} else {
33401e8d85bSAndy Shevchenko 		spin_lock_irqsave(&fwd->slock, flags);
33501e8d85bSAndy Shevchenko 		error = gpio_fwd_get_multiple(fwd, mask, bits);
336828546e2SGeert Uytterhoeven 		spin_unlock_irqrestore(&fwd->slock, flags);
33701e8d85bSAndy Shevchenko 	}
338828546e2SGeert Uytterhoeven 
339828546e2SGeert Uytterhoeven 	return error;
340828546e2SGeert Uytterhoeven }
341828546e2SGeert Uytterhoeven 
gpio_fwd_delay(struct gpio_chip * chip,unsigned int offset,int value)342b466622cSAndy Shevchenko static void gpio_fwd_delay(struct gpio_chip *chip, unsigned int offset, int value)
343b466622cSAndy Shevchenko {
344b466622cSAndy Shevchenko 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
345b466622cSAndy Shevchenko 	const struct gpiochip_fwd_timing *delay_timings;
346b466622cSAndy Shevchenko 	bool is_active_low = gpiod_is_active_low(fwd->descs[offset]);
347b466622cSAndy Shevchenko 	u32 delay_us;
348b466622cSAndy Shevchenko 
349b466622cSAndy Shevchenko 	delay_timings = &fwd->delay_timings[offset];
350b466622cSAndy Shevchenko 	if ((!is_active_low && value) || (is_active_low && !value))
351b466622cSAndy Shevchenko 		delay_us = delay_timings->ramp_up_us;
352b466622cSAndy Shevchenko 	else
353b466622cSAndy Shevchenko 		delay_us = delay_timings->ramp_down_us;
354b466622cSAndy Shevchenko 	if (!delay_us)
355b466622cSAndy Shevchenko 		return;
356b466622cSAndy Shevchenko 
357b466622cSAndy Shevchenko 	if (chip->can_sleep)
358b466622cSAndy Shevchenko 		fsleep(delay_us);
359b466622cSAndy Shevchenko 	else
360b466622cSAndy Shevchenko 		udelay(delay_us);
361b466622cSAndy Shevchenko }
362b466622cSAndy Shevchenko 
gpio_fwd_set(struct gpio_chip * chip,unsigned int offset,int value)363828546e2SGeert Uytterhoeven static void gpio_fwd_set(struct gpio_chip *chip, unsigned int offset, int value)
364828546e2SGeert Uytterhoeven {
365828546e2SGeert Uytterhoeven 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
366828546e2SGeert Uytterhoeven 
3672cba0545SGeert Uytterhoeven 	if (chip->can_sleep)
3682cba0545SGeert Uytterhoeven 		gpiod_set_value_cansleep(fwd->descs[offset], value);
3692cba0545SGeert Uytterhoeven 	else
370828546e2SGeert Uytterhoeven 		gpiod_set_value(fwd->descs[offset], value);
371b466622cSAndy Shevchenko 
372b466622cSAndy Shevchenko 	if (fwd->delay_timings)
373b466622cSAndy Shevchenko 		gpio_fwd_delay(chip, offset, value);
374828546e2SGeert Uytterhoeven }
375828546e2SGeert Uytterhoeven 
gpio_fwd_set_multiple(struct gpiochip_fwd * fwd,unsigned long * mask,unsigned long * bits)37601e8d85bSAndy Shevchenko static void gpio_fwd_set_multiple(struct gpiochip_fwd *fwd, unsigned long *mask,
377828546e2SGeert Uytterhoeven 				  unsigned long *bits)
378828546e2SGeert Uytterhoeven {
379aa4858ebSGeert Uytterhoeven 	struct gpio_desc **descs = fwd_tmp_descs(fwd);
380aa4858ebSGeert Uytterhoeven 	unsigned long *values = fwd_tmp_values(fwd);
381828546e2SGeert Uytterhoeven 	unsigned int i, j = 0;
382828546e2SGeert Uytterhoeven 
383828546e2SGeert Uytterhoeven 	for_each_set_bit(i, mask, fwd->chip.ngpio) {
384828546e2SGeert Uytterhoeven 		__assign_bit(j, values, test_bit(i, bits));
385828546e2SGeert Uytterhoeven 		descs[j++] = fwd->descs[i];
386828546e2SGeert Uytterhoeven 	}
387828546e2SGeert Uytterhoeven 
3882cba0545SGeert Uytterhoeven 	if (fwd->chip.can_sleep)
3892cba0545SGeert Uytterhoeven 		gpiod_set_array_value_cansleep(j, descs, NULL, values);
3902cba0545SGeert Uytterhoeven 	else
391828546e2SGeert Uytterhoeven 		gpiod_set_array_value(j, descs, NULL, values);
39201e8d85bSAndy Shevchenko }
393828546e2SGeert Uytterhoeven 
gpio_fwd_set_multiple_locked(struct gpio_chip * chip,unsigned long * mask,unsigned long * bits)39401e8d85bSAndy Shevchenko static void gpio_fwd_set_multiple_locked(struct gpio_chip *chip,
39501e8d85bSAndy Shevchenko 					 unsigned long *mask, unsigned long *bits)
39601e8d85bSAndy Shevchenko {
39701e8d85bSAndy Shevchenko 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
39801e8d85bSAndy Shevchenko 	unsigned long flags;
39901e8d85bSAndy Shevchenko 
40001e8d85bSAndy Shevchenko 	if (chip->can_sleep) {
40101e8d85bSAndy Shevchenko 		mutex_lock(&fwd->mlock);
40201e8d85bSAndy Shevchenko 		gpio_fwd_set_multiple(fwd, mask, bits);
403828546e2SGeert Uytterhoeven 		mutex_unlock(&fwd->mlock);
40401e8d85bSAndy Shevchenko 	} else {
40501e8d85bSAndy Shevchenko 		spin_lock_irqsave(&fwd->slock, flags);
40601e8d85bSAndy Shevchenko 		gpio_fwd_set_multiple(fwd, mask, bits);
407828546e2SGeert Uytterhoeven 		spin_unlock_irqrestore(&fwd->slock, flags);
408828546e2SGeert Uytterhoeven 	}
40901e8d85bSAndy Shevchenko }
410828546e2SGeert Uytterhoeven 
gpio_fwd_set_config(struct gpio_chip * chip,unsigned int offset,unsigned long config)411828546e2SGeert Uytterhoeven static int gpio_fwd_set_config(struct gpio_chip *chip, unsigned int offset,
412828546e2SGeert Uytterhoeven 			       unsigned long config)
413828546e2SGeert Uytterhoeven {
414828546e2SGeert Uytterhoeven 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
415828546e2SGeert Uytterhoeven 
416828546e2SGeert Uytterhoeven 	return gpiod_set_config(fwd->descs[offset], config);
417828546e2SGeert Uytterhoeven }
418828546e2SGeert Uytterhoeven 
gpio_fwd_to_irq(struct gpio_chip * chip,unsigned int offset)419a00128dfSGeert Uytterhoeven static int gpio_fwd_to_irq(struct gpio_chip *chip, unsigned int offset)
420a00128dfSGeert Uytterhoeven {
421a00128dfSGeert Uytterhoeven 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
422a00128dfSGeert Uytterhoeven 
423a00128dfSGeert Uytterhoeven 	return gpiod_to_irq(fwd->descs[offset]);
424a00128dfSGeert Uytterhoeven }
425a00128dfSGeert Uytterhoeven 
426*81674beaSAndy Shevchenko /*
427*81674beaSAndy Shevchenko  * The GPIO delay provides a way to configure platform specific delays
428*81674beaSAndy Shevchenko  * for the GPIO ramp-up or ramp-down delays. This can serve the following
429*81674beaSAndy Shevchenko  * purposes:
430*81674beaSAndy Shevchenko  *   - Open-drain output using an RC filter
431*81674beaSAndy Shevchenko  */
432*81674beaSAndy Shevchenko #define FWD_FEATURE_DELAY		BIT(0)
433*81674beaSAndy Shevchenko 
434*81674beaSAndy Shevchenko #ifdef CONFIG_OF_GPIO
gpiochip_fwd_delay_of_xlate(struct gpio_chip * chip,const struct of_phandle_args * gpiospec,u32 * flags)435*81674beaSAndy Shevchenko static int gpiochip_fwd_delay_of_xlate(struct gpio_chip *chip,
436*81674beaSAndy Shevchenko 				       const struct of_phandle_args *gpiospec,
437*81674beaSAndy Shevchenko 				       u32 *flags)
438*81674beaSAndy Shevchenko {
439*81674beaSAndy Shevchenko 	struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
440*81674beaSAndy Shevchenko 	struct gpiochip_fwd_timing *timings;
441*81674beaSAndy Shevchenko 	u32 line;
442*81674beaSAndy Shevchenko 
443*81674beaSAndy Shevchenko 	if (gpiospec->args_count != chip->of_gpio_n_cells)
444*81674beaSAndy Shevchenko 		return -EINVAL;
445*81674beaSAndy Shevchenko 
446*81674beaSAndy Shevchenko 	line = gpiospec->args[0];
447*81674beaSAndy Shevchenko 	if (line >= chip->ngpio)
448*81674beaSAndy Shevchenko 		return -EINVAL;
449*81674beaSAndy Shevchenko 
450*81674beaSAndy Shevchenko 	timings = &fwd->delay_timings[line];
451*81674beaSAndy Shevchenko 	timings->ramp_up_us = gpiospec->args[1];
452*81674beaSAndy Shevchenko 	timings->ramp_down_us = gpiospec->args[2];
453*81674beaSAndy Shevchenko 
454*81674beaSAndy Shevchenko 	return line;
455*81674beaSAndy Shevchenko }
456*81674beaSAndy Shevchenko 
gpiochip_fwd_setup_delay_line(struct device * dev,struct gpio_chip * chip,struct gpiochip_fwd * fwd)457*81674beaSAndy Shevchenko static int gpiochip_fwd_setup_delay_line(struct device *dev, struct gpio_chip *chip,
458*81674beaSAndy Shevchenko 					 struct gpiochip_fwd *fwd)
459*81674beaSAndy Shevchenko {
460*81674beaSAndy Shevchenko 	fwd->delay_timings = devm_kcalloc(dev, chip->ngpio,
461*81674beaSAndy Shevchenko 					  sizeof(*fwd->delay_timings),
462*81674beaSAndy Shevchenko 					  GFP_KERNEL);
463*81674beaSAndy Shevchenko 	if (!fwd->delay_timings)
464*81674beaSAndy Shevchenko 		return -ENOMEM;
465*81674beaSAndy Shevchenko 
466*81674beaSAndy Shevchenko 	chip->of_xlate = gpiochip_fwd_delay_of_xlate;
467*81674beaSAndy Shevchenko 	chip->of_gpio_n_cells = 3;
468*81674beaSAndy Shevchenko 
469*81674beaSAndy Shevchenko 	return 0;
470*81674beaSAndy Shevchenko }
471*81674beaSAndy Shevchenko #else
gpiochip_fwd_setup_delay_line(struct device * dev,struct gpio_chip * chip,struct gpiochip_fwd * fwd)472*81674beaSAndy Shevchenko static int gpiochip_fwd_setup_delay_line(struct device *dev, struct gpio_chip *chip,
473*81674beaSAndy Shevchenko 					 struct gpiochip_fwd *fwd)
474*81674beaSAndy Shevchenko {
475*81674beaSAndy Shevchenko 	return 0;
476*81674beaSAndy Shevchenko }
477*81674beaSAndy Shevchenko #endif	/* !CONFIG_OF_GPIO */
478*81674beaSAndy Shevchenko 
479828546e2SGeert Uytterhoeven /**
480828546e2SGeert Uytterhoeven  * gpiochip_fwd_create() - Create a new GPIO forwarder
481828546e2SGeert Uytterhoeven  * @dev: Parent device pointer
482828546e2SGeert Uytterhoeven  * @ngpios: Number of GPIOs in the forwarder.
483828546e2SGeert Uytterhoeven  * @descs: Array containing the GPIO descriptors to forward to.
484828546e2SGeert Uytterhoeven  *         This array must contain @ngpios entries, and must not be deallocated
485828546e2SGeert Uytterhoeven  *         before the forwarder has been destroyed again.
486*81674beaSAndy Shevchenko  * @features: Bitwise ORed features as defined with FWD_FEATURE_*.
487828546e2SGeert Uytterhoeven  *
488828546e2SGeert Uytterhoeven  * This function creates a new gpiochip, which forwards all GPIO operations to
489828546e2SGeert Uytterhoeven  * the passed GPIO descriptors.
490828546e2SGeert Uytterhoeven  *
491828546e2SGeert Uytterhoeven  * Return: An opaque object pointer, or an ERR_PTR()-encoded negative error
492828546e2SGeert Uytterhoeven  *         code on failure.
493828546e2SGeert Uytterhoeven  */
gpiochip_fwd_create(struct device * dev,unsigned int ngpios,struct gpio_desc * descs[],unsigned long features)494828546e2SGeert Uytterhoeven static struct gpiochip_fwd *gpiochip_fwd_create(struct device *dev,
495828546e2SGeert Uytterhoeven 						unsigned int ngpios,
496*81674beaSAndy Shevchenko 						struct gpio_desc *descs[],
497*81674beaSAndy Shevchenko 						unsigned long features)
498828546e2SGeert Uytterhoeven {
499828546e2SGeert Uytterhoeven 	const char *label = dev_name(dev);
500828546e2SGeert Uytterhoeven 	struct gpiochip_fwd *fwd;
501828546e2SGeert Uytterhoeven 	struct gpio_chip *chip;
502828546e2SGeert Uytterhoeven 	unsigned int i;
503828546e2SGeert Uytterhoeven 	int error;
504828546e2SGeert Uytterhoeven 
505aa4858ebSGeert Uytterhoeven 	fwd = devm_kzalloc(dev, struct_size(fwd, tmp, fwd_tmp_size(ngpios)),
506aa4858ebSGeert Uytterhoeven 			   GFP_KERNEL);
507828546e2SGeert Uytterhoeven 	if (!fwd)
508828546e2SGeert Uytterhoeven 		return ERR_PTR(-ENOMEM);
509828546e2SGeert Uytterhoeven 
510828546e2SGeert Uytterhoeven 	chip = &fwd->chip;
511828546e2SGeert Uytterhoeven 
512828546e2SGeert Uytterhoeven 	/*
513828546e2SGeert Uytterhoeven 	 * If any of the GPIO lines are sleeping, then the entire forwarder
514828546e2SGeert Uytterhoeven 	 * will be sleeping.
515828546e2SGeert Uytterhoeven 	 * If any of the chips support .set_config(), then the forwarder will
516828546e2SGeert Uytterhoeven 	 * support setting configs.
517828546e2SGeert Uytterhoeven 	 */
518828546e2SGeert Uytterhoeven 	for (i = 0; i < ngpios; i++) {
519828546e2SGeert Uytterhoeven 		struct gpio_chip *parent = gpiod_to_chip(descs[i]);
520828546e2SGeert Uytterhoeven 
521a00128dfSGeert Uytterhoeven 		dev_dbg(dev, "%u => gpio %d irq %d\n", i,
522a00128dfSGeert Uytterhoeven 			desc_to_gpio(descs[i]), gpiod_to_irq(descs[i]));
523828546e2SGeert Uytterhoeven 
524828546e2SGeert Uytterhoeven 		if (gpiod_cansleep(descs[i]))
525828546e2SGeert Uytterhoeven 			chip->can_sleep = true;
526828546e2SGeert Uytterhoeven 		if (parent && parent->set_config)
527828546e2SGeert Uytterhoeven 			chip->set_config = gpio_fwd_set_config;
528828546e2SGeert Uytterhoeven 	}
529828546e2SGeert Uytterhoeven 
530828546e2SGeert Uytterhoeven 	chip->label = label;
531828546e2SGeert Uytterhoeven 	chip->parent = dev;
532828546e2SGeert Uytterhoeven 	chip->owner = THIS_MODULE;
533828546e2SGeert Uytterhoeven 	chip->get_direction = gpio_fwd_get_direction;
534828546e2SGeert Uytterhoeven 	chip->direction_input = gpio_fwd_direction_input;
535828546e2SGeert Uytterhoeven 	chip->direction_output = gpio_fwd_direction_output;
536828546e2SGeert Uytterhoeven 	chip->get = gpio_fwd_get;
53701e8d85bSAndy Shevchenko 	chip->get_multiple = gpio_fwd_get_multiple_locked;
538828546e2SGeert Uytterhoeven 	chip->set = gpio_fwd_set;
53901e8d85bSAndy Shevchenko 	chip->set_multiple = gpio_fwd_set_multiple_locked;
540a00128dfSGeert Uytterhoeven 	chip->to_irq = gpio_fwd_to_irq;
541828546e2SGeert Uytterhoeven 	chip->base = -1;
542828546e2SGeert Uytterhoeven 	chip->ngpio = ngpios;
543828546e2SGeert Uytterhoeven 	fwd->descs = descs;
544828546e2SGeert Uytterhoeven 
545828546e2SGeert Uytterhoeven 	if (chip->can_sleep)
546828546e2SGeert Uytterhoeven 		mutex_init(&fwd->mlock);
547828546e2SGeert Uytterhoeven 	else
548828546e2SGeert Uytterhoeven 		spin_lock_init(&fwd->slock);
549828546e2SGeert Uytterhoeven 
550*81674beaSAndy Shevchenko 	if (features & FWD_FEATURE_DELAY) {
551*81674beaSAndy Shevchenko 		error = gpiochip_fwd_setup_delay_line(dev, chip, fwd);
552*81674beaSAndy Shevchenko 		if (error)
553*81674beaSAndy Shevchenko 			return ERR_PTR(error);
554*81674beaSAndy Shevchenko 	}
555*81674beaSAndy Shevchenko 
556828546e2SGeert Uytterhoeven 	error = devm_gpiochip_add_data(dev, chip, fwd);
557828546e2SGeert Uytterhoeven 	if (error)
558828546e2SGeert Uytterhoeven 		return ERR_PTR(error);
559828546e2SGeert Uytterhoeven 
560828546e2SGeert Uytterhoeven 	return fwd;
561828546e2SGeert Uytterhoeven }
562828546e2SGeert Uytterhoeven 
563828546e2SGeert Uytterhoeven 
564828546e2SGeert Uytterhoeven /*
565828546e2SGeert Uytterhoeven  *  GPIO Aggregator platform device
566828546e2SGeert Uytterhoeven  */
567828546e2SGeert Uytterhoeven 
gpio_aggregator_probe(struct platform_device * pdev)568828546e2SGeert Uytterhoeven static int gpio_aggregator_probe(struct platform_device *pdev)
569828546e2SGeert Uytterhoeven {
570828546e2SGeert Uytterhoeven 	struct device *dev = &pdev->dev;
571828546e2SGeert Uytterhoeven 	struct gpio_desc **descs;
572828546e2SGeert Uytterhoeven 	struct gpiochip_fwd *fwd;
573*81674beaSAndy Shevchenko 	unsigned long features;
574828546e2SGeert Uytterhoeven 	int i, n;
575828546e2SGeert Uytterhoeven 
576828546e2SGeert Uytterhoeven 	n = gpiod_count(dev, NULL);
577828546e2SGeert Uytterhoeven 	if (n < 0)
578828546e2SGeert Uytterhoeven 		return n;
579828546e2SGeert Uytterhoeven 
580828546e2SGeert Uytterhoeven 	descs = devm_kmalloc_array(dev, n, sizeof(*descs), GFP_KERNEL);
581828546e2SGeert Uytterhoeven 	if (!descs)
582828546e2SGeert Uytterhoeven 		return -ENOMEM;
583828546e2SGeert Uytterhoeven 
584828546e2SGeert Uytterhoeven 	for (i = 0; i < n; i++) {
585828546e2SGeert Uytterhoeven 		descs[i] = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS);
586828546e2SGeert Uytterhoeven 		if (IS_ERR(descs[i]))
587828546e2SGeert Uytterhoeven 			return PTR_ERR(descs[i]);
588828546e2SGeert Uytterhoeven 	}
589828546e2SGeert Uytterhoeven 
590*81674beaSAndy Shevchenko 	features = (uintptr_t)device_get_match_data(dev);
591*81674beaSAndy Shevchenko 	fwd = gpiochip_fwd_create(dev, n, descs, features);
592828546e2SGeert Uytterhoeven 	if (IS_ERR(fwd))
593828546e2SGeert Uytterhoeven 		return PTR_ERR(fwd);
594828546e2SGeert Uytterhoeven 
595828546e2SGeert Uytterhoeven 	platform_set_drvdata(pdev, fwd);
596828546e2SGeert Uytterhoeven 	return 0;
597828546e2SGeert Uytterhoeven }
598828546e2SGeert Uytterhoeven 
599828546e2SGeert Uytterhoeven static const struct of_device_id gpio_aggregator_dt_ids[] = {
600*81674beaSAndy Shevchenko 	{
601*81674beaSAndy Shevchenko 		.compatible = "gpio-delay",
602*81674beaSAndy Shevchenko 		.data = (void *)FWD_FEATURE_DELAY,
603*81674beaSAndy Shevchenko 	},
604828546e2SGeert Uytterhoeven 	/*
605828546e2SGeert Uytterhoeven 	 * Add GPIO-operated devices controlled from userspace below,
606b89a9e98SAndy Shevchenko 	 * or use "driver_override" in sysfs.
607828546e2SGeert Uytterhoeven 	 */
6086e004a98SAndy Shevchenko 	{}
609828546e2SGeert Uytterhoeven };
610828546e2SGeert Uytterhoeven MODULE_DEVICE_TABLE(of, gpio_aggregator_dt_ids);
611828546e2SGeert Uytterhoeven 
612828546e2SGeert Uytterhoeven static struct platform_driver gpio_aggregator_driver = {
613828546e2SGeert Uytterhoeven 	.probe = gpio_aggregator_probe,
614828546e2SGeert Uytterhoeven 	.driver = {
615828546e2SGeert Uytterhoeven 		.name = DRV_NAME,
616828546e2SGeert Uytterhoeven 		.groups = gpio_aggregator_groups,
617b89a9e98SAndy Shevchenko 		.of_match_table = gpio_aggregator_dt_ids,
618828546e2SGeert Uytterhoeven 	},
619828546e2SGeert Uytterhoeven };
620828546e2SGeert Uytterhoeven 
gpio_aggregator_init(void)621828546e2SGeert Uytterhoeven static int __init gpio_aggregator_init(void)
622828546e2SGeert Uytterhoeven {
623828546e2SGeert Uytterhoeven 	return platform_driver_register(&gpio_aggregator_driver);
624828546e2SGeert Uytterhoeven }
625828546e2SGeert Uytterhoeven module_init(gpio_aggregator_init);
626828546e2SGeert Uytterhoeven 
gpio_aggregator_exit(void)627828546e2SGeert Uytterhoeven static void __exit gpio_aggregator_exit(void)
628828546e2SGeert Uytterhoeven {
629828546e2SGeert Uytterhoeven 	gpio_aggregator_remove_all();
630828546e2SGeert Uytterhoeven 	platform_driver_unregister(&gpio_aggregator_driver);
631828546e2SGeert Uytterhoeven }
632828546e2SGeert Uytterhoeven module_exit(gpio_aggregator_exit);
633828546e2SGeert Uytterhoeven 
634828546e2SGeert Uytterhoeven MODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>");
635828546e2SGeert Uytterhoeven MODULE_DESCRIPTION("GPIO Aggregator");
636828546e2SGeert Uytterhoeven MODULE_LICENSE("GPL v2");
637