Skip to content

Commit 229c15e

Browse files
mbriandlag-linaro
authored andcommitted
input: misc: Add support for MAX7360 rotary
Add driver for Maxim Integrated MAX7360 rotary encoder controller, supporting a single rotary switch. Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com> Link: https://lore.kernel.org/r/20250824-mdb-max7360-support-v14-9-435cfda2b1ea@bootlin.com Signed-off-by: Lee Jones <lee@kernel.org>
1 parent fa6a23f commit 229c15e

3 files changed

Lines changed: 203 additions & 0 deletions

File tree

drivers/input/misc/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,16 @@ config INPUT_M68K_BEEP
230230
tristate "M68k Beeper support"
231231
depends on M68K
232232

233+
config INPUT_MAX7360_ROTARY
234+
tristate "Maxim MAX7360 Rotary Encoder"
235+
depends on MFD_MAX7360
236+
help
237+
If you say yes here you get support for the rotary encoder on the
238+
Maxim MAX7360 I/O Expander.
239+
240+
To compile this driver as a module, choose M here: the module will be
241+
called max7360_rotary.
242+
233243
config INPUT_MAX77650_ONKEY
234244
tristate "Maxim MAX77650 ONKEY support"
235245
depends on MFD_MAX77650

drivers/input/misc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o
5151
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
5252
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
5353
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
54+
obj-$(CONFIG_INPUT_MAX7360_ROTARY) += max7360-rotary.o
5455
obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o
5556
obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o
5657
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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");

0 commit comments

Comments
 (0)