173969ff0SDaniel Mack /*
273969ff0SDaniel Mack  * rotary_encoder.c
373969ff0SDaniel Mack  *
473969ff0SDaniel Mack  * (c) 2009 Daniel Mack <daniel@caiaq.de>
5e70bdd41SJohan Hovold  * Copyright (C) 2011 Johan Hovold <jhovold@gmail.com>
673969ff0SDaniel Mack  *
773969ff0SDaniel Mack  * state machine code inspired by code from Tim Ruetz
873969ff0SDaniel Mack  *
973969ff0SDaniel Mack  * A generic driver for rotary encoders connected to GPIO lines.
10395cf969SPaul Bolle  * See file:Documentation/input/rotary-encoder.txt for more information
1173969ff0SDaniel Mack  *
1273969ff0SDaniel Mack  * This program is free software; you can redistribute it and/or modify
1373969ff0SDaniel Mack  * it under the terms of the GNU General Public License version 2 as
1473969ff0SDaniel Mack  * published by the Free Software Foundation.
1573969ff0SDaniel Mack  */
1673969ff0SDaniel Mack 
1773969ff0SDaniel Mack #include <linux/kernel.h>
1873969ff0SDaniel Mack #include <linux/module.h>
1973969ff0SDaniel Mack #include <linux/interrupt.h>
2073969ff0SDaniel Mack #include <linux/input.h>
2173969ff0SDaniel Mack #include <linux/device.h>
2273969ff0SDaniel Mack #include <linux/platform_device.h>
2377a8f0adSDmitry Torokhov #include <linux/gpio/consumer.h>
245a0e3ad6STejun Heo #include <linux/slab.h>
252e45e539SSachin Kamat #include <linux/of.h>
2647ec6e5aSSylvain Rochet #include <linux/pm.h>
27a9e340dcSDmitry Torokhov #include <linux/property.h>
2873969ff0SDaniel Mack 
2973969ff0SDaniel Mack #define DRV_NAME "rotary-encoder"
3073969ff0SDaniel Mack 
31d205a218SUwe Kleine-König enum rotary_encoder_encoding {
32d205a218SUwe Kleine-König 	ROTENC_GRAY,
33d205a218SUwe Kleine-König 	ROTENC_BINARY,
34d205a218SUwe Kleine-König };
35d205a218SUwe Kleine-König 
3673969ff0SDaniel Mack struct rotary_encoder {
3773969ff0SDaniel Mack 	struct input_dev *input;
38a9e340dcSDmitry Torokhov 
39dee520e3STimo Teräs 	struct mutex access_mutex;
40bd3ce655SH Hartley Sweeten 
41a9e340dcSDmitry Torokhov 	u32 steps;
42a9e340dcSDmitry Torokhov 	u32 axis;
43a9e340dcSDmitry Torokhov 	bool relative_axis;
44a9e340dcSDmitry Torokhov 	bool rollover;
45d205a218SUwe Kleine-König 	enum rotary_encoder_encoding encoding;
46a9e340dcSDmitry Torokhov 
47bd3ce655SH Hartley Sweeten 	unsigned int pos;
48bd3ce655SH Hartley Sweeten 
497dde4e74SUwe Kleine-König 	struct gpio_descs *gpios;
5077a8f0adSDmitry Torokhov 
517dde4e74SUwe Kleine-König 	unsigned int *irq;
52bd3ce655SH Hartley Sweeten 
53bd3ce655SH Hartley Sweeten 	bool armed;
547dde4e74SUwe Kleine-König 	signed char dir;	/* 1 - clockwise, -1 - CCW */
55e70bdd41SJohan Hovold 
56d96caf8cSClifton Barnes 	unsigned int last_stable;
5773969ff0SDaniel Mack };
5873969ff0SDaniel Mack 
59d96caf8cSClifton Barnes static unsigned int rotary_encoder_get_state(struct rotary_encoder *encoder)
6073969ff0SDaniel Mack {
617dde4e74SUwe Kleine-König 	int i;
62d96caf8cSClifton Barnes 	unsigned int ret = 0;
6373969ff0SDaniel Mack 
647dde4e74SUwe Kleine-König 	for (i = 0; i < encoder->gpios->ndescs; ++i) {
657dde4e74SUwe Kleine-König 		int val = gpiod_get_value_cansleep(encoder->gpios->desc[i]);
66d205a218SUwe Kleine-König 
677dde4e74SUwe Kleine-König 		/* convert from gray encoding to normal */
68d205a218SUwe Kleine-König 		if (encoder->encoding == ROTENC_GRAY && ret & 1)
697dde4e74SUwe Kleine-König 			val = !val;
707dde4e74SUwe Kleine-König 
717dde4e74SUwe Kleine-König 		ret = ret << 1 | val;
727dde4e74SUwe Kleine-König 	}
737dde4e74SUwe Kleine-König 
747dde4e74SUwe Kleine-König 	return ret & 3;
75521a8f5cSJohan Hovold }
7673969ff0SDaniel Mack 
77521a8f5cSJohan Hovold static void rotary_encoder_report_event(struct rotary_encoder *encoder)
78521a8f5cSJohan Hovold {
79a9e340dcSDmitry Torokhov 	if (encoder->relative_axis) {
80521a8f5cSJohan Hovold 		input_report_rel(encoder->input,
817dde4e74SUwe Kleine-König 				 encoder->axis, encoder->dir);
82bd3ce655SH Hartley Sweeten 	} else {
83bd3ce655SH Hartley Sweeten 		unsigned int pos = encoder->pos;
84bd3ce655SH Hartley Sweeten 
857dde4e74SUwe Kleine-König 		if (encoder->dir < 0) {
8673969ff0SDaniel Mack 			/* turning counter-clockwise */
87a9e340dcSDmitry Torokhov 			if (encoder->rollover)
88a9e340dcSDmitry Torokhov 				pos += encoder->steps;
89bd3ce655SH Hartley Sweeten 			if (pos)
90bd3ce655SH Hartley Sweeten 				pos--;
9173969ff0SDaniel Mack 		} else {
9273969ff0SDaniel Mack 			/* turning clockwise */
93a9e340dcSDmitry Torokhov 			if (encoder->rollover || pos < encoder->steps)
94bd3ce655SH Hartley Sweeten 				pos++;
9573969ff0SDaniel Mack 		}
96521a8f5cSJohan Hovold 
97a9e340dcSDmitry Torokhov 		if (encoder->rollover)
98a9e340dcSDmitry Torokhov 			pos %= encoder->steps;
9973969ff0SDaniel Mack 
100521a8f5cSJohan Hovold 		encoder->pos = pos;
101a9e340dcSDmitry Torokhov 		input_report_abs(encoder->input, encoder->axis, encoder->pos);
102521a8f5cSJohan Hovold 	}
103521a8f5cSJohan Hovold 
104521a8f5cSJohan Hovold 	input_sync(encoder->input);
105521a8f5cSJohan Hovold }
106521a8f5cSJohan Hovold 
107521a8f5cSJohan Hovold static irqreturn_t rotary_encoder_irq(int irq, void *dev_id)
108521a8f5cSJohan Hovold {
109521a8f5cSJohan Hovold 	struct rotary_encoder *encoder = dev_id;
110d96caf8cSClifton Barnes 	unsigned int state;
111521a8f5cSJohan Hovold 
112dee520e3STimo Teräs 	mutex_lock(&encoder->access_mutex);
113dee520e3STimo Teräs 
11477a8f0adSDmitry Torokhov 	state = rotary_encoder_get_state(encoder);
115521a8f5cSJohan Hovold 
116521a8f5cSJohan Hovold 	switch (state) {
117521a8f5cSJohan Hovold 	case 0x0:
118521a8f5cSJohan Hovold 		if (encoder->armed) {
119521a8f5cSJohan Hovold 			rotary_encoder_report_event(encoder);
120bd3ce655SH Hartley Sweeten 			encoder->armed = false;
121521a8f5cSJohan Hovold 		}
12273969ff0SDaniel Mack 		break;
12373969ff0SDaniel Mack 
12473969ff0SDaniel Mack 	case 0x1:
1257dde4e74SUwe Kleine-König 	case 0x3:
12673969ff0SDaniel Mack 		if (encoder->armed)
1277dde4e74SUwe Kleine-König 			encoder->dir = 2 - state;
12873969ff0SDaniel Mack 		break;
12973969ff0SDaniel Mack 
1307dde4e74SUwe Kleine-König 	case 0x2:
131bd3ce655SH Hartley Sweeten 		encoder->armed = true;
13273969ff0SDaniel Mack 		break;
13373969ff0SDaniel Mack 	}
13473969ff0SDaniel Mack 
135dee520e3STimo Teräs 	mutex_unlock(&encoder->access_mutex);
136dee520e3STimo Teräs 
13773969ff0SDaniel Mack 	return IRQ_HANDLED;
13873969ff0SDaniel Mack }
13973969ff0SDaniel Mack 
140e70bdd41SJohan Hovold static irqreturn_t rotary_encoder_half_period_irq(int irq, void *dev_id)
141e70bdd41SJohan Hovold {
142e70bdd41SJohan Hovold 	struct rotary_encoder *encoder = dev_id;
1437dde4e74SUwe Kleine-König 	unsigned int state;
144e70bdd41SJohan Hovold 
145dee520e3STimo Teräs 	mutex_lock(&encoder->access_mutex);
146dee520e3STimo Teräs 
14777a8f0adSDmitry Torokhov 	state = rotary_encoder_get_state(encoder);
148e70bdd41SJohan Hovold 
1497dde4e74SUwe Kleine-König 	if (state & 1) {
1507dde4e74SUwe Kleine-König 		encoder->dir = ((encoder->last_stable - state + 1) % 4) - 1;
1517dde4e74SUwe Kleine-König 	} else {
152e70bdd41SJohan Hovold 		if (state != encoder->last_stable) {
153e70bdd41SJohan Hovold 			rotary_encoder_report_event(encoder);
154e70bdd41SJohan Hovold 			encoder->last_stable = state;
155e70bdd41SJohan Hovold 		}
156e70bdd41SJohan Hovold 	}
157e70bdd41SJohan Hovold 
158dee520e3STimo Teräs 	mutex_unlock(&encoder->access_mutex);
159dee520e3STimo Teräs 
160e70bdd41SJohan Hovold 	return IRQ_HANDLED;
161e70bdd41SJohan Hovold }
162e70bdd41SJohan Hovold 
1633a341a4cSEzequiel Garcia static irqreturn_t rotary_encoder_quarter_period_irq(int irq, void *dev_id)
1643a341a4cSEzequiel Garcia {
1653a341a4cSEzequiel Garcia 	struct rotary_encoder *encoder = dev_id;
1667dde4e74SUwe Kleine-König 	unsigned int state;
1673a341a4cSEzequiel Garcia 
168dee520e3STimo Teräs 	mutex_lock(&encoder->access_mutex);
169dee520e3STimo Teräs 
17077a8f0adSDmitry Torokhov 	state = rotary_encoder_get_state(encoder);
1713a341a4cSEzequiel Garcia 
1727dde4e74SUwe Kleine-König 	if ((encoder->last_stable + 1) % 4 == state)
1737dde4e74SUwe Kleine-König 		encoder->dir = 1;
1747dde4e74SUwe Kleine-König 	else if (encoder->last_stable == (state + 1) % 4)
1757dde4e74SUwe Kleine-König 		encoder->dir = -1;
1767dde4e74SUwe Kleine-König 	else
1773a341a4cSEzequiel Garcia 		goto out;
1783a341a4cSEzequiel Garcia 
1793a341a4cSEzequiel Garcia 	rotary_encoder_report_event(encoder);
1803a341a4cSEzequiel Garcia 
1813a341a4cSEzequiel Garcia out:
1823a341a4cSEzequiel Garcia 	encoder->last_stable = state;
183dee520e3STimo Teräs 	mutex_unlock(&encoder->access_mutex);
184dee520e3STimo Teräs 
1853a341a4cSEzequiel Garcia 	return IRQ_HANDLED;
1863a341a4cSEzequiel Garcia }
1873a341a4cSEzequiel Garcia 
1885298cc4cSBill Pemberton static int rotary_encoder_probe(struct platform_device *pdev)
18973969ff0SDaniel Mack {
190ce919537SDmitry Torokhov 	struct device *dev = &pdev->dev;
19173969ff0SDaniel Mack 	struct rotary_encoder *encoder;
19273969ff0SDaniel Mack 	struct input_dev *input;
193e70bdd41SJohan Hovold 	irq_handler_t handler;
194a9e340dcSDmitry Torokhov 	u32 steps_per_period;
1957dde4e74SUwe Kleine-König 	unsigned int i;
19673969ff0SDaniel Mack 	int err;
19773969ff0SDaniel Mack 
198d9202af2STimo Teräs 	encoder = devm_kzalloc(dev, sizeof(struct rotary_encoder), GFP_KERNEL);
199d9202af2STimo Teräs 	if (!encoder)
200d9202af2STimo Teräs 		return -ENOMEM;
201d9202af2STimo Teräs 
20277a8f0adSDmitry Torokhov 	mutex_init(&encoder->access_mutex);
203a9e340dcSDmitry Torokhov 
204a9e340dcSDmitry Torokhov 	device_property_read_u32(dev, "rotary-encoder,steps", &encoder->steps);
205a9e340dcSDmitry Torokhov 
206a9e340dcSDmitry Torokhov 	err = device_property_read_u32(dev, "rotary-encoder,steps-per-period",
207a9e340dcSDmitry Torokhov 				       &steps_per_period);
208a9e340dcSDmitry Torokhov 	if (err) {
209a9e340dcSDmitry Torokhov 		/*
210a9e340dcSDmitry Torokhov 		 * The 'half-period' property has been deprecated, you must
211a9e340dcSDmitry Torokhov 		 * use 'steps-per-period' and set an appropriate value, but
212a9e340dcSDmitry Torokhov 		 * we still need to parse it to maintain compatibility. If
213a9e340dcSDmitry Torokhov 		 * neither property is present we fall back to the one step
214a9e340dcSDmitry Torokhov 		 * per period behavior.
215a9e340dcSDmitry Torokhov 		 */
216a9e340dcSDmitry Torokhov 		steps_per_period = device_property_read_bool(dev,
217a9e340dcSDmitry Torokhov 					"rotary-encoder,half-period") ? 2 : 1;
218a9e340dcSDmitry Torokhov 	}
219a9e340dcSDmitry Torokhov 
220a9e340dcSDmitry Torokhov 	encoder->rollover =
221a9e340dcSDmitry Torokhov 		device_property_read_bool(dev, "rotary-encoder,rollover");
222a9e340dcSDmitry Torokhov 
223d205a218SUwe Kleine-König 	if (!device_property_present(dev, "rotary-encoder,encoding") ||
224d205a218SUwe Kleine-König 	    !device_property_match_string(dev, "rotary-encoder,encoding",
225d205a218SUwe Kleine-König 					  "gray")) {
226d205a218SUwe Kleine-König 		dev_info(dev, "gray");
227d205a218SUwe Kleine-König 		encoder->encoding = ROTENC_GRAY;
228d205a218SUwe Kleine-König 	} else if (!device_property_match_string(dev, "rotary-encoder,encoding",
229d205a218SUwe Kleine-König 						 "binary")) {
230d205a218SUwe Kleine-König 		dev_info(dev, "binary");
231d205a218SUwe Kleine-König 		encoder->encoding = ROTENC_BINARY;
232d205a218SUwe Kleine-König 	} else {
233d205a218SUwe Kleine-König 		dev_err(dev, "unknown encoding setting\n");
234d205a218SUwe Kleine-König 		return -EINVAL;
235d205a218SUwe Kleine-König 	}
236d205a218SUwe Kleine-König 
237a9e340dcSDmitry Torokhov 	device_property_read_u32(dev, "linux,axis", &encoder->axis);
238a9e340dcSDmitry Torokhov 	encoder->relative_axis =
239a9e340dcSDmitry Torokhov 		device_property_read_bool(dev, "rotary-encoder,relative-axis");
24077a8f0adSDmitry Torokhov 
2417dde4e74SUwe Kleine-König 	encoder->gpios = devm_gpiod_get_array(dev, NULL, GPIOD_IN);
2427dde4e74SUwe Kleine-König 	if (IS_ERR(encoder->gpios)) {
2437dde4e74SUwe Kleine-König 		dev_err(dev, "unable to get gpios\n");
2447dde4e74SUwe Kleine-König 		return PTR_ERR(encoder->gpios);
24577a8f0adSDmitry Torokhov 	}
2467dde4e74SUwe Kleine-König 	if (encoder->gpios->ndescs < 2) {
2477dde4e74SUwe Kleine-König 		dev_err(dev, "not enough gpios found\n");
2487dde4e74SUwe Kleine-König 		return -EINVAL;
24977a8f0adSDmitry Torokhov 	}
25077a8f0adSDmitry Torokhov 
251d9202af2STimo Teräs 	input = devm_input_allocate_device(dev);
252d9202af2STimo Teräs 	if (!input)
253d9202af2STimo Teräs 		return -ENOMEM;
25473969ff0SDaniel Mack 
25573969ff0SDaniel Mack 	encoder->input = input;
25673969ff0SDaniel Mack 
25773969ff0SDaniel Mack 	input->name = pdev->name;
25873969ff0SDaniel Mack 	input->id.bustype = BUS_HOST;
25980c99bcdSDaniel Mack 	input->dev.parent = dev;
260bd3ce655SH Hartley Sweeten 
261a9e340dcSDmitry Torokhov 	if (encoder->relative_axis)
262a9e340dcSDmitry Torokhov 		input_set_capability(input, EV_REL, encoder->axis);
2638631580fSDmitry Torokhov 	else
264a9e340dcSDmitry Torokhov 		input_set_abs_params(input,
265a9e340dcSDmitry Torokhov 				     encoder->axis, 0, encoder->steps, 0, 1);
26673969ff0SDaniel Mack 
2677dde4e74SUwe Kleine-König 	switch (steps_per_period >> (encoder->gpios->ndescs - 2)) {
2683a341a4cSEzequiel Garcia 	case 4:
2693a341a4cSEzequiel Garcia 		handler = &rotary_encoder_quarter_period_irq;
27077a8f0adSDmitry Torokhov 		encoder->last_stable = rotary_encoder_get_state(encoder);
2713a341a4cSEzequiel Garcia 		break;
2723a341a4cSEzequiel Garcia 	case 2:
273e70bdd41SJohan Hovold 		handler = &rotary_encoder_half_period_irq;
27477a8f0adSDmitry Torokhov 		encoder->last_stable = rotary_encoder_get_state(encoder);
2753a341a4cSEzequiel Garcia 		break;
2763a341a4cSEzequiel Garcia 	case 1:
277e70bdd41SJohan Hovold 		handler = &rotary_encoder_irq;
2783a341a4cSEzequiel Garcia 		break;
2793a341a4cSEzequiel Garcia 	default:
2803a341a4cSEzequiel Garcia 		dev_err(dev, "'%d' is not a valid steps-per-period value\n",
281a9e340dcSDmitry Torokhov 			steps_per_period);
282d9202af2STimo Teräs 		return -EINVAL;
283e70bdd41SJohan Hovold 	}
284e70bdd41SJohan Hovold 
2857dde4e74SUwe Kleine-König 	encoder->irq =
286a86854d0SKees Cook 		devm_kcalloc(dev,
287a86854d0SKees Cook 			     encoder->gpios->ndescs, sizeof(*encoder->irq),
2887dde4e74SUwe Kleine-König 			     GFP_KERNEL);
2897dde4e74SUwe Kleine-König 	if (!encoder->irq)
2907dde4e74SUwe Kleine-König 		return -ENOMEM;
2917dde4e74SUwe Kleine-König 
2927dde4e74SUwe Kleine-König 	for (i = 0; i < encoder->gpios->ndescs; ++i) {
2937dde4e74SUwe Kleine-König 		encoder->irq[i] = gpiod_to_irq(encoder->gpios->desc[i]);
2947dde4e74SUwe Kleine-König 
2957dde4e74SUwe Kleine-König 		err = devm_request_threaded_irq(dev, encoder->irq[i],
2967dde4e74SUwe Kleine-König 				NULL, handler,
297dee520e3STimo Teräs 				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
298dee520e3STimo Teräs 				IRQF_ONESHOT,
29973969ff0SDaniel Mack 				DRV_NAME, encoder);
30073969ff0SDaniel Mack 		if (err) {
3017dde4e74SUwe Kleine-König 			dev_err(dev, "unable to request IRQ %d (gpio#%d)\n",
3027dde4e74SUwe Kleine-König 				encoder->irq[i], i);
303d9202af2STimo Teräs 			return err;
30473969ff0SDaniel Mack 		}
30573969ff0SDaniel Mack 	}
30673969ff0SDaniel Mack 
30780c99bcdSDaniel Mack 	err = input_register_device(input);
30880c99bcdSDaniel Mack 	if (err) {
30980c99bcdSDaniel Mack 		dev_err(dev, "failed to register input device\n");
310d9202af2STimo Teräs 		return err;
31180c99bcdSDaniel Mack 	}
31280c99bcdSDaniel Mack 
313a9e340dcSDmitry Torokhov 	device_init_wakeup(dev,
314a9e340dcSDmitry Torokhov 			   device_property_read_bool(dev, "wakeup-source"));
31547ec6e5aSSylvain Rochet 
31673969ff0SDaniel Mack 	platform_set_drvdata(pdev, encoder);
31773969ff0SDaniel Mack 
31873969ff0SDaniel Mack 	return 0;
31973969ff0SDaniel Mack }
32073969ff0SDaniel Mack 
3216a6f70b3SDmitry Torokhov static int __maybe_unused rotary_encoder_suspend(struct device *dev)
32247ec6e5aSSylvain Rochet {
32347ec6e5aSSylvain Rochet 	struct rotary_encoder *encoder = dev_get_drvdata(dev);
3247dde4e74SUwe Kleine-König 	unsigned int i;
32547ec6e5aSSylvain Rochet 
32647ec6e5aSSylvain Rochet 	if (device_may_wakeup(dev)) {
3277dde4e74SUwe Kleine-König 		for (i = 0; i < encoder->gpios->ndescs; ++i)
3287dde4e74SUwe Kleine-König 			enable_irq_wake(encoder->irq[i]);
32947ec6e5aSSylvain Rochet 	}
33047ec6e5aSSylvain Rochet 
33147ec6e5aSSylvain Rochet 	return 0;
33247ec6e5aSSylvain Rochet }
33347ec6e5aSSylvain Rochet 
3346a6f70b3SDmitry Torokhov static int __maybe_unused rotary_encoder_resume(struct device *dev)
33547ec6e5aSSylvain Rochet {
33647ec6e5aSSylvain Rochet 	struct rotary_encoder *encoder = dev_get_drvdata(dev);
3377dde4e74SUwe Kleine-König 	unsigned int i;
33847ec6e5aSSylvain Rochet 
33947ec6e5aSSylvain Rochet 	if (device_may_wakeup(dev)) {
3407dde4e74SUwe Kleine-König 		for (i = 0; i < encoder->gpios->ndescs; ++i)
3417dde4e74SUwe Kleine-König 			disable_irq_wake(encoder->irq[i]);
34247ec6e5aSSylvain Rochet 	}
34347ec6e5aSSylvain Rochet 
34447ec6e5aSSylvain Rochet 	return 0;
34547ec6e5aSSylvain Rochet }
34647ec6e5aSSylvain Rochet 
34747ec6e5aSSylvain Rochet static SIMPLE_DEV_PM_OPS(rotary_encoder_pm_ops,
34847ec6e5aSSylvain Rochet 			 rotary_encoder_suspend, rotary_encoder_resume);
34947ec6e5aSSylvain Rochet 
350a9e340dcSDmitry Torokhov #ifdef CONFIG_OF
351a9e340dcSDmitry Torokhov static const struct of_device_id rotary_encoder_of_match[] = {
352a9e340dcSDmitry Torokhov 	{ .compatible = "rotary-encoder", },
353a9e340dcSDmitry Torokhov 	{ },
354a9e340dcSDmitry Torokhov };
355a9e340dcSDmitry Torokhov MODULE_DEVICE_TABLE(of, rotary_encoder_of_match);
356a9e340dcSDmitry Torokhov #endif
357a9e340dcSDmitry Torokhov 
35873969ff0SDaniel Mack static struct platform_driver rotary_encoder_driver = {
35973969ff0SDaniel Mack 	.probe		= rotary_encoder_probe,
36073969ff0SDaniel Mack 	.driver		= {
36173969ff0SDaniel Mack 		.name	= DRV_NAME,
36247ec6e5aSSylvain Rochet 		.pm	= &rotary_encoder_pm_ops,
36380c99bcdSDaniel Mack 		.of_match_table = of_match_ptr(rotary_encoder_of_match),
36473969ff0SDaniel Mack 	}
36573969ff0SDaniel Mack };
366840a746bSJJ Ding module_platform_driver(rotary_encoder_driver);
36773969ff0SDaniel Mack 
36873969ff0SDaniel Mack MODULE_ALIAS("platform:" DRV_NAME);
36973969ff0SDaniel Mack MODULE_DESCRIPTION("GPIO rotary encoder driver");
370e70bdd41SJohan Hovold MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>, Johan Hovold");
37173969ff0SDaniel Mack MODULE_LICENSE("GPL v2");
372