xref: /linux/drivers/input/misc/max7360-rotary.c (revision 07fdad3a93756b872da7b53647715c48d0f4a2d0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright 2025 Bootlin
4  *
5  * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
6  */
7 
8 #include <linux/bitfield.h>
9 #include <linux/device/devres.h>
10 #include <linux/dev_printk.h>
11 #include <linux/init.h>
12 #include <linux/input.h>
13 #include <linux/interrupt.h>
14 #include <linux/mfd/max7360.h>
15 #include <linux/property.h>
16 #include <linux/platform_device.h>
17 #include <linux/pm_wakeirq.h>
18 #include <linux/regmap.h>
19 #include <linux/types.h>
20 
21 #define MAX7360_ROTARY_DEFAULT_STEPS 24
22 
23 struct max7360_rotary {
24 	struct input_dev *input;
25 	struct regmap *regmap;
26 	unsigned int debounce_ms;
27 
28 	unsigned int pos;
29 
30 	u32 steps;
31 	u32 axis;
32 	bool relative_axis;
33 	bool rollover;
34 };
35 
36 static void max7360_rotary_report_event(struct max7360_rotary *max7360_rotary, int steps)
37 {
38 	if (max7360_rotary->relative_axis) {
39 		input_report_rel(max7360_rotary->input, max7360_rotary->axis, steps);
40 	} else {
41 		int pos = max7360_rotary->pos;
42 		int maxval = max7360_rotary->steps;
43 
44 		/*
45 		 * Add steps to the position.
46 		 * Make sure added steps are always in ]-maxval; maxval[
47 		 * interval, so (pos + maxval) is always >= 0.
48 		 * Then set back pos to the [0; maxval[ interval.
49 		 */
50 		pos += steps % maxval;
51 		if (max7360_rotary->rollover)
52 			pos = (pos + maxval) % maxval;
53 		else
54 			pos = clamp(pos, 0, maxval - 1);
55 
56 		max7360_rotary->pos = pos;
57 		input_report_abs(max7360_rotary->input, max7360_rotary->axis, max7360_rotary->pos);
58 	}
59 
60 	input_sync(max7360_rotary->input);
61 }
62 
63 static irqreturn_t max7360_rotary_irq(int irq, void *data)
64 {
65 	struct max7360_rotary *max7360_rotary = data;
66 	struct device *dev = max7360_rotary->input->dev.parent;
67 	unsigned int val;
68 	int error;
69 
70 	error = regmap_read(max7360_rotary->regmap, MAX7360_REG_RTR_CNT, &val);
71 	if (error < 0) {
72 		dev_err(dev, "Failed to read rotary counter\n");
73 		return IRQ_NONE;
74 	}
75 
76 	if (val == 0)
77 		return IRQ_NONE;
78 
79 	max7360_rotary_report_event(max7360_rotary, sign_extend32(val, 7));
80 
81 	return IRQ_HANDLED;
82 }
83 
84 static int max7360_rotary_hw_init(struct max7360_rotary *max7360_rotary)
85 {
86 	struct device *dev = max7360_rotary->input->dev.parent;
87 	int val;
88 	int error;
89 
90 	val = FIELD_PREP(MAX7360_ROT_DEBOUNCE, max7360_rotary->debounce_ms) |
91 	      FIELD_PREP(MAX7360_ROT_INTCNT, 1) | MAX7360_ROT_INTCNT_DLY;
92 	error = regmap_write(max7360_rotary->regmap, MAX7360_REG_RTRCFG, val);
93 	if (error)
94 		dev_err(dev, "Failed to set max7360 rotary encoder configuration\n");
95 
96 	return error;
97 }
98 
99 static int max7360_rotary_probe(struct platform_device *pdev)
100 {
101 	struct max7360_rotary *max7360_rotary;
102 	struct device *dev = &pdev->dev;
103 	struct input_dev *input;
104 	struct regmap *regmap;
105 	int irq;
106 	int error;
107 
108 	regmap = dev_get_regmap(dev->parent, NULL);
109 	if (!regmap)
110 		return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
111 
112 	irq = fwnode_irq_get_byname(dev_fwnode(dev->parent), "inti");
113 	if (irq < 0)
114 		return dev_err_probe(dev, irq, "Failed to get IRQ\n");
115 
116 	max7360_rotary = devm_kzalloc(dev, sizeof(*max7360_rotary), GFP_KERNEL);
117 	if (!max7360_rotary)
118 		return -ENOMEM;
119 
120 	max7360_rotary->regmap = regmap;
121 
122 	device_property_read_u32(dev->parent, "linux,axis", &max7360_rotary->axis);
123 	max7360_rotary->rollover = device_property_read_bool(dev->parent,
124 							     "rotary-encoder,rollover");
125 	max7360_rotary->relative_axis =
126 		device_property_read_bool(dev->parent, "rotary-encoder,relative-axis");
127 
128 	error = device_property_read_u32(dev->parent, "rotary-encoder,steps",
129 					 &max7360_rotary->steps);
130 	if (error)
131 		max7360_rotary->steps = MAX7360_ROTARY_DEFAULT_STEPS;
132 
133 	device_property_read_u32(dev->parent, "rotary-debounce-delay-ms",
134 				 &max7360_rotary->debounce_ms);
135 	if (max7360_rotary->debounce_ms > MAX7360_ROT_DEBOUNCE_MAX)
136 		return dev_err_probe(dev, -EINVAL, "Invalid debounce timing: %u\n",
137 				     max7360_rotary->debounce_ms);
138 
139 	input = devm_input_allocate_device(dev);
140 	if (!input)
141 		return -ENOMEM;
142 
143 	max7360_rotary->input = input;
144 
145 	input->id.bustype = BUS_I2C;
146 	input->name = pdev->name;
147 
148 	if (max7360_rotary->relative_axis)
149 		input_set_capability(input, EV_REL, max7360_rotary->axis);
150 	else
151 		input_set_abs_params(input, max7360_rotary->axis, 0, max7360_rotary->steps, 0, 1);
152 
153 	error = devm_request_threaded_irq(dev, irq, NULL, max7360_rotary_irq,
154 					  IRQF_ONESHOT | IRQF_SHARED,
155 					  "max7360-rotary", max7360_rotary);
156 	if (error)
157 		return dev_err_probe(dev, error, "Failed to register interrupt\n");
158 
159 	error = input_register_device(input);
160 	if (error)
161 		return dev_err_probe(dev, error, "Could not register input device\n");
162 
163 	error = max7360_rotary_hw_init(max7360_rotary);
164 	if (error)
165 		return dev_err_probe(dev, error, "Failed to initialize max7360 rotary\n");
166 
167 	device_init_wakeup(dev, true);
168 	error = dev_pm_set_wake_irq(dev, irq);
169 	if (error)
170 		dev_warn(dev, "Failed to set up wakeup irq: %d\n", error);
171 
172 	return 0;
173 }
174 
175 static void max7360_rotary_remove(struct platform_device *pdev)
176 {
177 	dev_pm_clear_wake_irq(&pdev->dev);
178 	device_init_wakeup(&pdev->dev, false);
179 }
180 
181 static struct platform_driver max7360_rotary_driver = {
182 	.driver = {
183 		.name	= "max7360-rotary",
184 	},
185 	.probe		= max7360_rotary_probe,
186 	.remove		= max7360_rotary_remove,
187 };
188 module_platform_driver(max7360_rotary_driver);
189 
190 MODULE_DESCRIPTION("MAX7360 Rotary driver");
191 MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
192 MODULE_LICENSE("GPL");
193