1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
273969ff0SDaniel Mack /*
373969ff0SDaniel Mack  * rotary_encoder.c
473969ff0SDaniel Mack  *
573969ff0SDaniel Mack  * (c) 2009 Daniel Mack <daniel@caiaq.de>
6e70bdd41SJohan Hovold  * Copyright (C) 2011 Johan Hovold <jhovold@gmail.com>
773969ff0SDaniel Mack  *
873969ff0SDaniel Mack  * state machine code inspired by code from Tim Ruetz
973969ff0SDaniel Mack  *
1073969ff0SDaniel Mack  * A generic driver for rotary encoders connected to GPIO lines.
115fb94e9cSMauro Carvalho Chehab  * See file:Documentation/input/devices/rotary-encoder.rst for more information
1273969ff0SDaniel Mack  */
1373969ff0SDaniel Mack 
1473969ff0SDaniel Mack #include <linux/kernel.h>
1573969ff0SDaniel Mack #include <linux/module.h>
1673969ff0SDaniel Mack #include <linux/interrupt.h>
1773969ff0SDaniel Mack #include <linux/input.h>
1873969ff0SDaniel Mack #include <linux/device.h>
1973969ff0SDaniel Mack #include <linux/platform_device.h>
2077a8f0adSDmitry Torokhov #include <linux/gpio/consumer.h>
215a0e3ad6STejun Heo #include <linux/slab.h>
222e45e539SSachin Kamat #include <linux/of.h>
2347ec6e5aSSylvain Rochet #include <linux/pm.h>
24a9e340dcSDmitry Torokhov #include <linux/property.h>
2573969ff0SDaniel Mack 
2673969ff0SDaniel Mack #define DRV_NAME "rotary-encoder"
2773969ff0SDaniel Mack 
28d205a218SUwe Kleine-König enum rotary_encoder_encoding {
29d205a218SUwe Kleine-König 	ROTENC_GRAY,
30d205a218SUwe Kleine-König 	ROTENC_BINARY,
31d205a218SUwe Kleine-König };
32d205a218SUwe Kleine-König 
3373969ff0SDaniel Mack struct rotary_encoder {
3473969ff0SDaniel Mack 	struct input_dev *input;
35a9e340dcSDmitry Torokhov 
36dee520e3STimo Teräs 	struct mutex access_mutex;
37bd3ce655SH Hartley Sweeten 
38a9e340dcSDmitry Torokhov 	u32 steps;
39a9e340dcSDmitry Torokhov 	u32 axis;
40a9e340dcSDmitry Torokhov 	bool relative_axis;
41a9e340dcSDmitry Torokhov 	bool rollover;
42d205a218SUwe Kleine-König 	enum rotary_encoder_encoding encoding;
43a9e340dcSDmitry Torokhov 
44bd3ce655SH Hartley Sweeten 	unsigned int pos;
45bd3ce655SH Hartley Sweeten 
467dde4e74SUwe Kleine-König 	struct gpio_descs *gpios;
4777a8f0adSDmitry Torokhov 
487dde4e74SUwe Kleine-König 	unsigned int *irq;
49bd3ce655SH Hartley Sweeten 
50bd3ce655SH Hartley Sweeten 	bool armed;
517dde4e74SUwe Kleine-König 	signed char dir;	/* 1 - clockwise, -1 - CCW */
52e70bdd41SJohan Hovold 
53d96caf8cSClifton Barnes 	unsigned int last_stable;
5473969ff0SDaniel Mack };
5573969ff0SDaniel Mack 
rotary_encoder_get_state(struct rotary_encoder * encoder)56d96caf8cSClifton Barnes static unsigned int rotary_encoder_get_state(struct rotary_encoder *encoder)
5773969ff0SDaniel Mack {
587dde4e74SUwe Kleine-König 	int i;
59d96caf8cSClifton Barnes 	unsigned int ret = 0;
6073969ff0SDaniel Mack 
617dde4e74SUwe Kleine-König 	for (i = 0; i < encoder->gpios->ndescs; ++i) {
627dde4e74SUwe Kleine-König 		int val = gpiod_get_value_cansleep(encoder->gpios->desc[i]);
63d205a218SUwe Kleine-König 
647dde4e74SUwe Kleine-König 		/* convert from gray encoding to normal */
65d205a218SUwe Kleine-König 		if (encoder->encoding == ROTENC_GRAY && ret & 1)
667dde4e74SUwe Kleine-König 			val = !val;
677dde4e74SUwe Kleine-König 
687dde4e74SUwe Kleine-König 		ret = ret << 1 | val;
697dde4e74SUwe Kleine-König 	}
707dde4e74SUwe Kleine-König 
717dde4e74SUwe Kleine-König 	return ret & 3;
72521a8f5cSJohan Hovold }
7373969ff0SDaniel Mack 
rotary_encoder_report_event(struct rotary_encoder * encoder)74521a8f5cSJohan Hovold static void rotary_encoder_report_event(struct rotary_encoder *encoder)
75521a8f5cSJohan Hovold {
76a9e340dcSDmitry Torokhov 	if (encoder->relative_axis) {
77521a8f5cSJohan Hovold 		input_report_rel(encoder->input,
787dde4e74SUwe Kleine-König 				 encoder->axis, encoder->dir);
79bd3ce655SH Hartley Sweeten 	} else {
80bd3ce655SH Hartley Sweeten 		unsigned int pos = encoder->pos;
81bd3ce655SH Hartley Sweeten 
827dde4e74SUwe Kleine-König 		if (encoder->dir < 0) {
8373969ff0SDaniel Mack 			/* turning counter-clockwise */
84a9e340dcSDmitry Torokhov 			if (encoder->rollover)
85a9e340dcSDmitry Torokhov 				pos += encoder->steps;
86bd3ce655SH Hartley Sweeten 			if (pos)
87bd3ce655SH Hartley Sweeten 				pos--;
8873969ff0SDaniel Mack 		} else {
8973969ff0SDaniel Mack 			/* turning clockwise */
90a9e340dcSDmitry Torokhov 			if (encoder->rollover || pos < encoder->steps)
91bd3ce655SH Hartley Sweeten 				pos++;
9273969ff0SDaniel Mack 		}
93521a8f5cSJohan Hovold 
94a9e340dcSDmitry Torokhov 		if (encoder->rollover)
95a9e340dcSDmitry Torokhov 			pos %= encoder->steps;
9673969ff0SDaniel Mack 
97521a8f5cSJohan Hovold 		encoder->pos = pos;
98a9e340dcSDmitry Torokhov 		input_report_abs(encoder->input, encoder->axis, encoder->pos);
99521a8f5cSJohan Hovold 	}
100521a8f5cSJohan Hovold 
101521a8f5cSJohan Hovold 	input_sync(encoder->input);
102521a8f5cSJohan Hovold }
103521a8f5cSJohan Hovold 
rotary_encoder_irq(int irq,void * dev_id)104521a8f5cSJohan Hovold static irqreturn_t rotary_encoder_irq(int irq, void *dev_id)
105521a8f5cSJohan Hovold {
106521a8f5cSJohan Hovold 	struct rotary_encoder *encoder = dev_id;
107d96caf8cSClifton Barnes 	unsigned int state;
108521a8f5cSJohan Hovold 
109dee520e3STimo Teräs 	mutex_lock(&encoder->access_mutex);
110dee520e3STimo Teräs 
11177a8f0adSDmitry Torokhov 	state = rotary_encoder_get_state(encoder);
112521a8f5cSJohan Hovold 
113521a8f5cSJohan Hovold 	switch (state) {
114521a8f5cSJohan Hovold 	case 0x0:
115521a8f5cSJohan Hovold 		if (encoder->armed) {
116521a8f5cSJohan Hovold 			rotary_encoder_report_event(encoder);
117bd3ce655SH Hartley Sweeten 			encoder->armed = false;
118521a8f5cSJohan Hovold 		}
11973969ff0SDaniel Mack 		break;
12073969ff0SDaniel Mack 
12173969ff0SDaniel Mack 	case 0x1:
1227dde4e74SUwe Kleine-König 	case 0x3:
12373969ff0SDaniel Mack 		if (encoder->armed)
1247dde4e74SUwe Kleine-König 			encoder->dir = 2 - state;
12573969ff0SDaniel Mack 		break;
12673969ff0SDaniel Mack 
1277dde4e74SUwe Kleine-König 	case 0x2:
128bd3ce655SH Hartley Sweeten 		encoder->armed = true;
12973969ff0SDaniel Mack 		break;
13073969ff0SDaniel Mack 	}
13173969ff0SDaniel Mack 
132dee520e3STimo Teräs 	mutex_unlock(&encoder->access_mutex);
133dee520e3STimo Teräs 
13473969ff0SDaniel Mack 	return IRQ_HANDLED;
13573969ff0SDaniel Mack }
13673969ff0SDaniel Mack 
rotary_encoder_half_period_irq(int irq,void * dev_id)137e70bdd41SJohan Hovold static irqreturn_t rotary_encoder_half_period_irq(int irq, void *dev_id)
138e70bdd41SJohan Hovold {
139e70bdd41SJohan Hovold 	struct rotary_encoder *encoder = dev_id;
1407dde4e74SUwe Kleine-König 	unsigned int state;
141e70bdd41SJohan Hovold 
142dee520e3STimo Teräs 	mutex_lock(&encoder->access_mutex);
143dee520e3STimo Teräs 
14477a8f0adSDmitry Torokhov 	state = rotary_encoder_get_state(encoder);
145e70bdd41SJohan Hovold 
1467dde4e74SUwe Kleine-König 	if (state & 1) {
1477dde4e74SUwe Kleine-König 		encoder->dir = ((encoder->last_stable - state + 1) % 4) - 1;
1487dde4e74SUwe Kleine-König 	} else {
149e70bdd41SJohan Hovold 		if (state != encoder->last_stable) {
150e70bdd41SJohan Hovold 			rotary_encoder_report_event(encoder);
151e70bdd41SJohan Hovold 			encoder->last_stable = state;
152e70bdd41SJohan Hovold 		}
153e70bdd41SJohan Hovold 	}
154e70bdd41SJohan Hovold 
155dee520e3STimo Teräs 	mutex_unlock(&encoder->access_mutex);
156dee520e3STimo Teräs 
157e70bdd41SJohan Hovold 	return IRQ_HANDLED;
158e70bdd41SJohan Hovold }
159e70bdd41SJohan Hovold 
rotary_encoder_quarter_period_irq(int irq,void * dev_id)1603a341a4cSEzequiel Garcia static irqreturn_t rotary_encoder_quarter_period_irq(int irq, void *dev_id)
1613a341a4cSEzequiel Garcia {
1623a341a4cSEzequiel Garcia 	struct rotary_encoder *encoder = dev_id;
1637dde4e74SUwe Kleine-König 	unsigned int state;
1643a341a4cSEzequiel Garcia 
165dee520e3STimo Teräs 	mutex_lock(&encoder->access_mutex);
166dee520e3STimo Teräs 
16777a8f0adSDmitry Torokhov 	state = rotary_encoder_get_state(encoder);
1683a341a4cSEzequiel Garcia 
1697dde4e74SUwe Kleine-König 	if ((encoder->last_stable + 1) % 4 == state)
1707dde4e74SUwe Kleine-König 		encoder->dir = 1;
1717dde4e74SUwe Kleine-König 	else if (encoder->last_stable == (state + 1) % 4)
1727dde4e74SUwe Kleine-König 		encoder->dir = -1;
1737dde4e74SUwe Kleine-König 	else
1743a341a4cSEzequiel Garcia 		goto out;
1753a341a4cSEzequiel Garcia 
1763a341a4cSEzequiel Garcia 	rotary_encoder_report_event(encoder);
1773a341a4cSEzequiel Garcia 
1783a341a4cSEzequiel Garcia out:
1793a341a4cSEzequiel Garcia 	encoder->last_stable = state;
180dee520e3STimo Teräs 	mutex_unlock(&encoder->access_mutex);
181dee520e3STimo Teräs 
1823a341a4cSEzequiel Garcia 	return IRQ_HANDLED;
1833a341a4cSEzequiel Garcia }
1843a341a4cSEzequiel Garcia 
rotary_encoder_probe(struct platform_device * pdev)1855298cc4cSBill Pemberton static int rotary_encoder_probe(struct platform_device *pdev)
18673969ff0SDaniel Mack {
187ce919537SDmitry Torokhov 	struct device *dev = &pdev->dev;
18873969ff0SDaniel Mack 	struct rotary_encoder *encoder;
18973969ff0SDaniel Mack 	struct input_dev *input;
190e70bdd41SJohan Hovold 	irq_handler_t handler;
191a9e340dcSDmitry Torokhov 	u32 steps_per_period;
1927dde4e74SUwe Kleine-König 	unsigned int i;
19373969ff0SDaniel Mack 	int err;
19473969ff0SDaniel Mack 
195d9202af2STimo Teräs 	encoder = devm_kzalloc(dev, sizeof(struct rotary_encoder), GFP_KERNEL);
196d9202af2STimo Teräs 	if (!encoder)
197d9202af2STimo Teräs 		return -ENOMEM;
198d9202af2STimo Teräs 
19977a8f0adSDmitry Torokhov 	mutex_init(&encoder->access_mutex);
200a9e340dcSDmitry Torokhov 
201a9e340dcSDmitry Torokhov 	device_property_read_u32(dev, "rotary-encoder,steps", &encoder->steps);
202a9e340dcSDmitry Torokhov 
203a9e340dcSDmitry Torokhov 	err = device_property_read_u32(dev, "rotary-encoder,steps-per-period",
204a9e340dcSDmitry Torokhov 				       &steps_per_period);
205a9e340dcSDmitry Torokhov 	if (err) {
206a9e340dcSDmitry Torokhov 		/*
207a9e340dcSDmitry Torokhov 		 * The 'half-period' property has been deprecated, you must
208a9e340dcSDmitry Torokhov 		 * use 'steps-per-period' and set an appropriate value, but
209a9e340dcSDmitry Torokhov 		 * we still need to parse it to maintain compatibility. If
210a9e340dcSDmitry Torokhov 		 * neither property is present we fall back to the one step
211a9e340dcSDmitry Torokhov 		 * per period behavior.
212a9e340dcSDmitry Torokhov 		 */
213a9e340dcSDmitry Torokhov 		steps_per_period = device_property_read_bool(dev,
214a9e340dcSDmitry Torokhov 					"rotary-encoder,half-period") ? 2 : 1;
215a9e340dcSDmitry Torokhov 	}
216a9e340dcSDmitry Torokhov 
217a9e340dcSDmitry Torokhov 	encoder->rollover =
218a9e340dcSDmitry Torokhov 		device_property_read_bool(dev, "rotary-encoder,rollover");
219a9e340dcSDmitry Torokhov 
220d205a218SUwe Kleine-König 	if (!device_property_present(dev, "rotary-encoder,encoding") ||
221d205a218SUwe Kleine-König 	    !device_property_match_string(dev, "rotary-encoder,encoding",
222d205a218SUwe Kleine-König 					  "gray")) {
223d205a218SUwe Kleine-König 		dev_info(dev, "gray");
224d205a218SUwe Kleine-König 		encoder->encoding = ROTENC_GRAY;
225d205a218SUwe Kleine-König 	} else if (!device_property_match_string(dev, "rotary-encoder,encoding",
226d205a218SUwe Kleine-König 						 "binary")) {
227d205a218SUwe Kleine-König 		dev_info(dev, "binary");
228d205a218SUwe Kleine-König 		encoder->encoding = ROTENC_BINARY;
229d205a218SUwe Kleine-König 	} else {
230d205a218SUwe Kleine-König 		dev_err(dev, "unknown encoding setting\n");
231d205a218SUwe Kleine-König 		return -EINVAL;
232d205a218SUwe Kleine-König 	}
233d205a218SUwe Kleine-König 
234a9e340dcSDmitry Torokhov 	device_property_read_u32(dev, "linux,axis", &encoder->axis);
235a9e340dcSDmitry Torokhov 	encoder->relative_axis =
236a9e340dcSDmitry Torokhov 		device_property_read_bool(dev, "rotary-encoder,relative-axis");
23777a8f0adSDmitry Torokhov 
2387dde4e74SUwe Kleine-König 	encoder->gpios = devm_gpiod_get_array(dev, NULL, GPIOD_IN);
239*1e402a15SKrzysztof Kozlowski 	if (IS_ERR(encoder->gpios))
240*1e402a15SKrzysztof Kozlowski 		return dev_err_probe(dev, PTR_ERR(encoder->gpios), "unable to get gpios\n");
2417dde4e74SUwe Kleine-König 	if (encoder->gpios->ndescs < 2) {
2427dde4e74SUwe Kleine-König 		dev_err(dev, "not enough gpios found\n");
2437dde4e74SUwe Kleine-König 		return -EINVAL;
24477a8f0adSDmitry Torokhov 	}
24577a8f0adSDmitry Torokhov 
246d9202af2STimo Teräs 	input = devm_input_allocate_device(dev);
247d9202af2STimo Teräs 	if (!input)
248d9202af2STimo Teräs 		return -ENOMEM;
24973969ff0SDaniel Mack 
25073969ff0SDaniel Mack 	encoder->input = input;
25173969ff0SDaniel Mack 
25273969ff0SDaniel Mack 	input->name = pdev->name;
25373969ff0SDaniel Mack 	input->id.bustype = BUS_HOST;
254bd3ce655SH Hartley Sweeten 
255a9e340dcSDmitry Torokhov 	if (encoder->relative_axis)
256a9e340dcSDmitry Torokhov 		input_set_capability(input, EV_REL, encoder->axis);
2578631580fSDmitry Torokhov 	else
258a9e340dcSDmitry Torokhov 		input_set_abs_params(input,
259a9e340dcSDmitry Torokhov 				     encoder->axis, 0, encoder->steps, 0, 1);
26073969ff0SDaniel Mack 
2617dde4e74SUwe Kleine-König 	switch (steps_per_period >> (encoder->gpios->ndescs - 2)) {
2623a341a4cSEzequiel Garcia 	case 4:
2633a341a4cSEzequiel Garcia 		handler = &rotary_encoder_quarter_period_irq;
26477a8f0adSDmitry Torokhov 		encoder->last_stable = rotary_encoder_get_state(encoder);
2653a341a4cSEzequiel Garcia 		break;
2663a341a4cSEzequiel Garcia 	case 2:
267e70bdd41SJohan Hovold 		handler = &rotary_encoder_half_period_irq;
26877a8f0adSDmitry Torokhov 		encoder->last_stable = rotary_encoder_get_state(encoder);
2693a341a4cSEzequiel Garcia 		break;
2703a341a4cSEzequiel Garcia 	case 1:
271e70bdd41SJohan Hovold 		handler = &rotary_encoder_irq;
2723a341a4cSEzequiel Garcia 		break;
2733a341a4cSEzequiel Garcia 	default:
2743a341a4cSEzequiel Garcia 		dev_err(dev, "'%d' is not a valid steps-per-period value\n",
275a9e340dcSDmitry Torokhov 			steps_per_period);
276d9202af2STimo Teräs 		return -EINVAL;
277e70bdd41SJohan Hovold 	}
278e70bdd41SJohan Hovold 
2797dde4e74SUwe Kleine-König 	encoder->irq =
280a86854d0SKees Cook 		devm_kcalloc(dev,
281a86854d0SKees Cook 			     encoder->gpios->ndescs, sizeof(*encoder->irq),
2827dde4e74SUwe Kleine-König 			     GFP_KERNEL);
2837dde4e74SUwe Kleine-König 	if (!encoder->irq)
2847dde4e74SUwe Kleine-König 		return -ENOMEM;
2857dde4e74SUwe Kleine-König 
2867dde4e74SUwe Kleine-König 	for (i = 0; i < encoder->gpios->ndescs; ++i) {
2877dde4e74SUwe Kleine-König 		encoder->irq[i] = gpiod_to_irq(encoder->gpios->desc[i]);
2887dde4e74SUwe Kleine-König 
2897dde4e74SUwe Kleine-König 		err = devm_request_threaded_irq(dev, encoder->irq[i],
2907dde4e74SUwe Kleine-König 				NULL, handler,
291dee520e3STimo Teräs 				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
292dee520e3STimo Teräs 				IRQF_ONESHOT,
29373969ff0SDaniel Mack 				DRV_NAME, encoder);
29473969ff0SDaniel Mack 		if (err) {
2957dde4e74SUwe Kleine-König 			dev_err(dev, "unable to request IRQ %d (gpio#%d)\n",
2967dde4e74SUwe Kleine-König 				encoder->irq[i], i);
297d9202af2STimo Teräs 			return err;
29873969ff0SDaniel Mack 		}
29973969ff0SDaniel Mack 	}
30073969ff0SDaniel Mack 
30180c99bcdSDaniel Mack 	err = input_register_device(input);
30280c99bcdSDaniel Mack 	if (err) {
30380c99bcdSDaniel Mack 		dev_err(dev, "failed to register input device\n");
304d9202af2STimo Teräs 		return err;
30580c99bcdSDaniel Mack 	}
30680c99bcdSDaniel Mack 
307a9e340dcSDmitry Torokhov 	device_init_wakeup(dev,
308a9e340dcSDmitry Torokhov 			   device_property_read_bool(dev, "wakeup-source"));
30947ec6e5aSSylvain Rochet 
31073969ff0SDaniel Mack 	platform_set_drvdata(pdev, encoder);
31173969ff0SDaniel Mack 
31273969ff0SDaniel Mack 	return 0;
31373969ff0SDaniel Mack }
31473969ff0SDaniel Mack 
rotary_encoder_suspend(struct device * dev)3154268a06bSJonathan Cameron static int rotary_encoder_suspend(struct device *dev)
31647ec6e5aSSylvain Rochet {
31747ec6e5aSSylvain Rochet 	struct rotary_encoder *encoder = dev_get_drvdata(dev);
3187dde4e74SUwe Kleine-König 	unsigned int i;
31947ec6e5aSSylvain Rochet 
32047ec6e5aSSylvain Rochet 	if (device_may_wakeup(dev)) {
3217dde4e74SUwe Kleine-König 		for (i = 0; i < encoder->gpios->ndescs; ++i)
3227dde4e74SUwe Kleine-König 			enable_irq_wake(encoder->irq[i]);
32347ec6e5aSSylvain Rochet 	}
32447ec6e5aSSylvain Rochet 
32547ec6e5aSSylvain Rochet 	return 0;
32647ec6e5aSSylvain Rochet }
32747ec6e5aSSylvain Rochet 
rotary_encoder_resume(struct device * dev)3284268a06bSJonathan Cameron static int rotary_encoder_resume(struct device *dev)
32947ec6e5aSSylvain Rochet {
33047ec6e5aSSylvain Rochet 	struct rotary_encoder *encoder = dev_get_drvdata(dev);
3317dde4e74SUwe Kleine-König 	unsigned int i;
33247ec6e5aSSylvain Rochet 
33347ec6e5aSSylvain Rochet 	if (device_may_wakeup(dev)) {
3347dde4e74SUwe Kleine-König 		for (i = 0; i < encoder->gpios->ndescs; ++i)
3357dde4e74SUwe Kleine-König 			disable_irq_wake(encoder->irq[i]);
33647ec6e5aSSylvain Rochet 	}
33747ec6e5aSSylvain Rochet 
33847ec6e5aSSylvain Rochet 	return 0;
33947ec6e5aSSylvain Rochet }
34047ec6e5aSSylvain Rochet 
3414268a06bSJonathan Cameron static DEFINE_SIMPLE_DEV_PM_OPS(rotary_encoder_pm_ops,
34247ec6e5aSSylvain Rochet 				rotary_encoder_suspend, rotary_encoder_resume);
34347ec6e5aSSylvain Rochet 
344a9e340dcSDmitry Torokhov #ifdef CONFIG_OF
345a9e340dcSDmitry Torokhov static const struct of_device_id rotary_encoder_of_match[] = {
346a9e340dcSDmitry Torokhov 	{ .compatible = "rotary-encoder", },
347a9e340dcSDmitry Torokhov 	{ },
348a9e340dcSDmitry Torokhov };
349a9e340dcSDmitry Torokhov MODULE_DEVICE_TABLE(of, rotary_encoder_of_match);
350a9e340dcSDmitry Torokhov #endif
351a9e340dcSDmitry Torokhov 
35273969ff0SDaniel Mack static struct platform_driver rotary_encoder_driver = {
35373969ff0SDaniel Mack 	.probe		= rotary_encoder_probe,
35473969ff0SDaniel Mack 	.driver		= {
35573969ff0SDaniel Mack 		.name	= DRV_NAME,
3564268a06bSJonathan Cameron 		.pm	= pm_sleep_ptr(&rotary_encoder_pm_ops),
35780c99bcdSDaniel Mack 		.of_match_table = of_match_ptr(rotary_encoder_of_match),
35873969ff0SDaniel Mack 	}
35973969ff0SDaniel Mack };
360840a746bSJJ Ding module_platform_driver(rotary_encoder_driver);
36173969ff0SDaniel Mack 
36273969ff0SDaniel Mack MODULE_ALIAS("platform:" DRV_NAME);
36373969ff0SDaniel Mack MODULE_DESCRIPTION("GPIO rotary encoder driver");
364e70bdd41SJohan Hovold MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>, Johan Hovold");
36573969ff0SDaniel Mack MODULE_LICENSE("GPL v2");
366