Skip to content

Commit a6c2f28

Browse files
committed
input: misc: add driver for max16150
MAX16150/MAX16169 nanoPower Pushbutton On/Off Controller Signed-off-by: Marc Paolo Sosa <marcpaolo.sosa@analog.com>
1 parent 186e735 commit a6c2f28

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed

drivers/input/misc/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ config INPUT_E3X0_BUTTON
150150
To compile this driver as a module, choose M here: the
151151
module will be called e3x0_button.
152152

153+
config INPUT_MAX16150_PWRBUTTON
154+
bool "MAX16150/MAX16169 Pushbutton driver"
155+
depends on OF_GPIO
156+
help
157+
This driver supports the MAX16150 and MAX16169 pushbutton
158+
controllers, which are low-power devices with a switch
159+
debouncer and built-in latch. Device bindings are specified
160+
in the device tree.
161+
153162
config INPUT_PCSPKR
154163
tristate "PC Speaker support"
155164
depends on PCSPKR_PLATFORM

drivers/input/misc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o
4949
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
5050
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
5151
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
52+
obj-$(CONFIG_INPUT_MAX16150_PWRBUTTON) += max16150.o
5253
obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o
5354
obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o
5455
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o

drivers/input/misc/max16150.c

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Analog Devices MAX16150/MAX16169 Pushbutton On/Off Controller
4+
*
5+
* Copyright 2024 Analog Devices Inc.
6+
*/
7+
8+
#include <linux/kernel.h>
9+
#include <linux/init.h>
10+
#include <linux/interrupt.h>
11+
#include <linux/device.h>
12+
#include <linux/platform_device.h>
13+
#include <linux/gpio/consumer.h>
14+
#include <linux/module.h>
15+
#include <linux/property.h>
16+
#include <linux/hrtimer.h>
17+
#include <linux/ktime.h>
18+
#include <linux/of.h>
19+
20+
enum max161x_variant {
21+
MAX161X_A,
22+
MAX161X_B,
23+
MAX161X_C,
24+
};
25+
26+
enum max161x_type {
27+
MAX16150,
28+
MAX16169,
29+
};
30+
31+
struct max161x_data {
32+
struct device *dev;
33+
struct gpio_desc *gpio_pb_in;
34+
struct gpio_desc *gpio_out;
35+
struct gpio_desc *gpio_clr;
36+
struct gpio_desc *gpio_int;
37+
38+
struct hrtimer debounce_timer;
39+
struct hrtimer shutdown_timer;
40+
ktime_t debounce_time;
41+
ktime_t shutdown_time;
42+
43+
bool out_asserted;
44+
enum max161x_variant variant;
45+
enum max161x_type type;
46+
};
47+
48+
/* Debounce handler */
49+
static enum hrtimer_restart max161x_debounce_timer_cb(struct hrtimer *timer)
50+
{
51+
struct max161x_data *data = container_of(timer, struct max161x_data,
52+
debounce_timer);
53+
54+
if (gpiod_get_value(data->gpio_pb_in)) {
55+
dev_info(data->dev, "Debounced button press detected. Asserting OUT.");
56+
gpiod_set_value(data->gpio_clr, 1);
57+
data->out_asserted = true;
58+
59+
/* Start shutdown timer for A variant */
60+
if (data->variant == MAX161X_A)
61+
hrtimer_start(&data->shutdown_timer, data->shutdown_time,
62+
HRTIMER_MODE_REL);
63+
} else {
64+
dev_info(data->dev, "Button released before debounce time elapsed.");
65+
}
66+
67+
return HRTIMER_NORESTART;
68+
}
69+
70+
/* Shutdown handler */
71+
static enum hrtimer_restart max161x_shutdown_timer_cb(struct hrtimer *timer)
72+
{
73+
struct max161x_data *data = container_of(timer, struct max161x_data,
74+
shutdown_timer);
75+
76+
if (data->variant == MAX161X_A && data->out_asserted) {
77+
dev_info(data->dev, "Shutdown time exceeded. Deasserting OUT.");
78+
gpiod_set_value(data->gpio_clr, 0);
79+
data->out_asserted = false;
80+
}
81+
82+
return HRTIMER_NORESTART;
83+
}
84+
85+
static irqreturn_t max161x_pb_in_irq_handler(int irq, void *dev_id)
86+
{
87+
struct max161x_data *data = dev_id;
88+
89+
if (gpiod_get_value(data->gpio_pb_in)) {
90+
/* Button pressed */
91+
hrtimer_start(&data->debounce_timer, data->debounce_time,
92+
HRTIMER_MODE_REL);
93+
} else {
94+
/* Button released */
95+
if (data->variant == MAX161X_A && data->out_asserted) {
96+
dev_info(data->dev, "Cancelling shutdown timer.");
97+
hrtimer_cancel(&data->shutdown_timer);
98+
}
99+
}
100+
101+
return IRQ_HANDLED;
102+
}
103+
104+
static int max161x_probe(struct platform_device *pdev)
105+
{
106+
struct max161x_data *data;
107+
const char *variant, *type;
108+
int ret;
109+
110+
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
111+
if (!data)
112+
return -ENOMEM;
113+
114+
data->dev = &pdev->dev;
115+
116+
ret = device_property_read_string(&pdev->dev, "compatible", &type);
117+
if (ret) {
118+
dev_err(&pdev->dev, "Failed to read device type\n");
119+
return ret;
120+
}
121+
122+
if (!strcmp(type, "adi,max16150")) {
123+
data->type = MAX16150;
124+
} else if (!strcmp(type, "adi,max16169")) {
125+
data->type = MAX16169;
126+
} else {
127+
dev_err(&pdev->dev, "Unknown device type: %s\n", type);
128+
return -EINVAL;
129+
}
130+
131+
ret = device_property_read_string(&pdev->dev, "adi,variant", &variant);
132+
if (ret) {
133+
dev_err(&pdev->dev, "Failed to read device variant\n");
134+
return ret;
135+
}
136+
137+
switch (variant[0]) {
138+
case 'A':
139+
data->variant = MAX161X_A;
140+
data->debounce_time = ktime_set(0, 50 * NSEC_PER_MSEC);
141+
data->shutdown_time = (data->type == MAX16150) ? ktime_set(8,
142+
0) : ktime_set(16, 0);
143+
break;
144+
case 'B':
145+
data->variant = MAX161X_B;
146+
data->debounce_time = (data->type == MAX16150) ? ktime_set(2,
147+
0) : ktime_set(50, 0);
148+
data->shutdown_time = ktime_set(16, 0);
149+
break;
150+
case 'C':
151+
data->variant = MAX161X_C;
152+
data->debounce_time = ktime_set(0, 50 * NSEC_PER_MSEC);
153+
data->shutdown_time = ktime_set(16, 0);
154+
break;
155+
default:
156+
dev_err(&pdev->dev, "Unknown device variant: %s\n", variant);
157+
return -EINVAL;
158+
}
159+
160+
dev_info(&pdev->dev, "Detected %s variant %s\n",
161+
(data->type == MAX16150) ? "MAX16150" : "MAX16169", variant);
162+
163+
data->gpio_pb_in = devm_gpiod_get(&pdev->dev, "pb_in", GPIOD_IN);
164+
if (IS_ERR(data->gpio_pb_in))
165+
return PTR_ERR(data->gpio_pb_in);
166+
167+
data->gpio_out = devm_gpiod_get(&pdev->dev, "out", GPIOD_OUT_LOW);
168+
if (IS_ERR(data->gpio_out))
169+
return PTR_ERR(data->gpio_out);
170+
171+
data->gpio_clr = devm_gpiod_get(&pdev->dev, "clr", GPIOD_OUT_LOW);
172+
if (IS_ERR(data->gpio_clr))
173+
return PTR_ERR(data->gpio_clr);
174+
175+
data->gpio_int = devm_gpiod_get(&pdev->dev, "int", GPIOD_IN);
176+
if (IS_ERR(data->gpio_int))
177+
return PTR_ERR(data->gpio_int);
178+
179+
hrtimer_init(&data->debounce_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
180+
data->debounce_timer.function = max161x_debounce_timer_cb;
181+
182+
hrtimer_init(&data->shutdown_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
183+
data->shutdown_timer.function = max161x_shutdown_timer_cb;
184+
185+
ret = devm_request_irq(&pdev->dev, gpiod_to_irq(data->gpio_pb_in),
186+
max161x_pb_in_irq_handler,
187+
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "max161x_pb_in", data);
188+
if (ret) {
189+
dev_err(&pdev->dev, "Failed to request IRQ for PB_IN\n");
190+
return ret;
191+
}
192+
193+
platform_set_drvdata(pdev, data);
194+
dev_info(&pdev->dev, "MAX161x driver initialized\n");
195+
196+
return 0;
197+
}
198+
199+
static int max161x_remove(struct platform_device *pdev)
200+
{
201+
struct max161x_data *data = platform_get_drvdata(pdev);
202+
203+
hrtimer_cancel(&data->debounce_timer);
204+
hrtimer_cancel(&data->shutdown_timer);
205+
return 0;
206+
}
207+
208+
static const struct of_device_id max161x_dt_ids[] = {
209+
{ .compatible = "adi,max16150" },
210+
{ .compatible = "adi,max16169" },
211+
{ }
212+
};
213+
MODULE_DEVICE_TABLE(of, max161x_dt_ids);
214+
215+
static struct platform_driver max161x_driver = {
216+
.driver = {
217+
.name = "max161x",
218+
.of_match_table = max161x_dt_ids,
219+
},
220+
.probe = max161x_probe,
221+
.remove = max161x_remove,
222+
};
223+
module_platform_driver(max161x_driver);
224+
225+
MODULE_AUTHOR("Marc Paolo Sosa <marcpaolo.sosa@analog.com>");
226+
MODULE_DESCRIPTION("MAX16150/MAX16169 Pushbutton Controller Driver");
227+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)