Skip to content

Add support for MAX16150/MAX16169 #2672

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions Documentation/devicetree/bindings/input/adi,max16150.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/input/adi,max16150.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: Analog Devices MAX16150/MAX16169 nanoPower Pushbutton On/Off Controller

maintainers:
- Marc Paolo Sosa <[email protected]>

description:
The MAX16150/MAX16169 is a low-power pushbutton on/off controller with a
switch debouncer and built-in latch. It accepts a noisy input from a
mechanical switch and produces a clean latched output, as well as a one-shot
interrupt output.

properties:
compatible:
description:
Specifies the supported device variants. The MAX16150 and MAX16169 are supported.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think the text needs to be formatted... This means you can drop '|'

enum:
- adi,max16150a
- adi,max16150b
- adi,max16169a
- adi,max16169b

interrupt-gpio:
maxItems: 1

clr-gpios:
description:
Clear Input. Pulling CLR low deasserts the latched OUT signal. If OUT is
already deasserted when CLR is pulled low, the state of OUT is unchanged.
maxItems: 1

linux,code:
default: KEY_POWER

required:
- compatible
- interrupt-gpios
- clr-gpios

additionalProperties: false

examples:
- |
#include <dt-bindings/input/linux-event-codes.h>
#include <dt-bindings/gpio/gpio.h>

power-button {
compatible = "adi,max16150a";
interrupt-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>;
clr-gpios = <&gpio 4 GPIO_ACTIVE_LOW>;
linux,code = <KEY_POWER>;
};
1 change: 1 addition & 0 deletions drivers/input/Kconfig.adi
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ config INPUT_ALL_ADI_DRIVERS
imply INPUT_AD714X
imply INPUT_AD714X_I2C
imply INPUT_AD714X_SPI
imply INPUT_MAX16150_PWRBUTTON
9 changes: 9 additions & 0 deletions drivers/input/misc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ config INPUT_E3X0_BUTTON
To compile this driver as a module, choose M here: the
module will be called e3x0_button.

config INPUT_MAX16150_PWRBUTTON
tristate "MAX16150/MAX16169 Pushbutton driver"
help
Say Y here if you want to enable power key reporting via
MAX16150/MAX16169 nanoPower Pushbutton On/Off Controller.

To compile this driver as a module, choose M here. The module will
be called max16150.

config INPUT_PCSPKR
tristate "PC Speaker support"
depends on PCSPKR_PLATFORM
Expand Down
1 change: 1 addition & 0 deletions drivers/input/misc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
obj-$(CONFIG_INPUT_MAX16150_PWRBUTTON) += max16150.o
obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o
obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
Expand Down
161 changes: 161 additions & 0 deletions drivers/input/misc/max16150.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Analog Devices MAX16150/MAX16169 Pushbutton Driver
*
* Copyright 2025 Analog Devices Inc.
*/

#include <linux/delay.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not in alphabetical order and you still have of.h

#include <linux/platform_device.h>
#include <linux/property.h>

#define MAX16150_LONG_INTERRUPT 120000000

struct max16150_chip_info {
bool has_clr_gpio;
};

struct max16150_device {
struct input_dev *input;
struct gpio_desc *gpiod;
struct gpio_desc *clr_gpiod;
const struct max16150_chip_info *chip_info;
u64 low, high, duration;
unsigned int keycode;
};

static irqreturn_t max16150_irq_handler(int irq, void *_max16150)
{
struct max16150_device *max16150 = _max16150;
int value;

value = gpiod_get_value(max16150->gpiod);

if (!value) {
max16150->low = ktime_get_ns();
return IRQ_HANDLED;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the support for the required gpio on the chip that do not automatically de-asserts the output?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the CLR GPIO just use the gpio-poweroff? So that whenever a power off command or event is triggered, the CLR pin is pulled low.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for an example, I tried using a separate script where a gpio is set to normally high, the same script captures the events send by the driver code, when long press is detected, it will toggle the gpio pin to low and deassert the clr pin

max16150->high = ktime_get_ns();
if (max16150->low) {
max16150->duration = max16150->high - max16150->low;

if (max16150->duration > MAX16150_LONG_INTERRUPT) {
gpiod_set_value(max16150->clr_gpiod, 1);
input_report_key(max16150->input, max16150->keycode, 1);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be making some confusion but does this not mean power off? I guess it should be input_report_key(max16150->input, max16150->keycode, 0);?

I would also expect to see a call to input_report_key(max16150->input, max16150->keycode, 1); to signal power up?

So long press != short press in terms of the key value right?

input_sync(max16150->input);
input_report_key(max16150->input, max16150->keycode, 0);
input_sync(max16150->input);
}

max16150->low = 0;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things are better (I was reviewing the older version when you sent this one) but still look wrong to me. We just have one event: KEY_POWER. IIUC, if the INT pulse is short then we have:

/* OUT is asserted so the value for KEY_POWER should 1 */
input_report_key(button, KEY_POWER, 1);`

If we have a long INT pulse, then

/* OUT is deasserted so the value for KEY_POWER should 0 */
input_report_key(button, KEY_POWER, 0);
/* For the chips where OUT is not automatically de-asserted, we should make this gpio mandatory */
gpio_value_set(gpiod_clr, 1);

AFAICT, we only have one even and depending on the duration of the INT pulse we either value 1 or 0. Also, while KEY_POWER is likely the default event, I guess there's nothing mandating that. Consider doing something like:

https://elixir.bootlin.com/linux/v6.12.6/source/drivers/input/misc/pm8941-pwrkey.c#L321

But use device properties and not OF

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this. The datasheet also mentions this:
to signal the system to perform a specific function, or to initiate a shutdown process for the longer int. It also make sense to have the shorter int to have an option for a different event to be triggered. So, having 2 device properties for these 2, should be considered.

Copy link
Collaborator

@nunojsa nunojsa Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it needs two events... I think the datasheet wording of "initiate a shutdown process" is just to state that the OUT pin will be deasserted. If it is a shutdown, ultimately depends on your platform and where you connect that ouptut. I think we just have one event and assert/deassert states for it.


return IRQ_HANDLED;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a function for this. Call platform_get_irq() directly

Copy link
Collaborator

@nunojsa nunojsa Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this might be a good idea there's one thing that is still not perfect... If for some reason, the HW designers remember to invert the IRQ signal (because... why not :)), this whole logic will fail... Did you tried my suggestion with

https://elixir.bootlin.com/linux/v6.6.65/source/include/linux/interrupt.h#L504

??


static const struct max16150_chip_info max16150_variant_a = {
.has_clr_gpio = true,
};

static const struct max16150_chip_info max16150_variant_b = {
.has_clr_gpio = false,
};

static int max16150_probe(struct platform_device *pdev)
{
const struct max16150_chip_info *chip_info;
struct max16150_device *max16150;
struct device *dev = &pdev->dev;
int err, irq, ret;
u32 keycode;

chip_info = device_get_match_data(dev);
if (!chip_info)
return -EINVAL;

max16150 = devm_kzalloc(dev, sizeof(*max16150), GFP_KERNEL);
if (!max16150)
return -ENOMEM;

max16150->chip_info = chip_info;

max16150->input = devm_input_allocate_device(dev);
if (!max16150->input)
return -ENOMEM;

max16150->input->name = "MAX16150 Pushbutton";
max16150->input->phys = "max16150/input0";
max16150->input->id.bustype = BUS_HOST;

keycode = KEY_POWER;
ret = device_property_read_u32(dev, "linux,code", &keycode);
if (ret)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line seems long. Typically the flags should be given in DT and we should not overwrite them here. But in this particular case, we do need these specific flags so not sure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both flags are needed here. That is, if we need to measure the falling to rising duration. Unless there's another way for this.

return dev_err_probe(dev, ret, "Failed to get keycode\n");

max16150->keycode = keycode;

input_set_capability(max16150->input, EV_KEY, max16150->keycode);

max16150->gpiod = devm_gpiod_get(dev, "interrupt", GPIOD_IN);
if (IS_ERR(max16150->gpiod))
return dev_err_probe(dev, PTR_ERR(max16150->gpiod),
"Failed to get interrupt GPIO\n");

if (chip_info->has_clr_gpio) {
max16150->clr_gpiod = devm_gpiod_get(dev, "clr", GPIOD_OUT_HIGH);
if (IS_ERR(max16150->clr_gpiod))
return dev_err_probe(dev, PTR_ERR(max16150->clr_gpiod),
"Failed to get clr GPIO\n");

if (!max16150->clr_gpiod)
return dev_err_probe(dev, -ENODEV,
"clr GPIO is mandatory\n");

if (max16150->clr_gpiod) {
fsleep(1000);
gpiod_set_value(max16150->clr_gpiod, 0);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above is wrong... If has_clr_gpio is true then the gpio is mandatory. This means the above should be:

max16150->clr_gpiod = devm_gpiod_get(&pdev->dev, "clr", GPIOD_OUT_LOW);
if (IS_ERR(max16150->clr_gpiod))
    return dev_err_probe(...);

Also note the usage of GPIOD_OUT_LOW which get the pin in the deasserted state. Therefore, no need for gpiod_set_value(max16150->clr_gpiod, 0);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using the clr gpio to reset the device, this is what we have implemented. I think the gpio should be high on boot since it will only go low if we want to reset or deassert the output.

		max16150->clr_gpiod = devm_gpiod_get(dev, "clr", GPIOD_OUT_HIGH);
		if (IS_ERR(max16150->clr_gpiod))
			return dev_err_probe(dev, PTR_ERR(max16150->clr_gpiod),
					     "Failed to get clr GPIO\n");

		if (max16150->clr_gpiod) {
			fsleep(1000);
			gpiod_set_value(max16150->clr_gpiod, 0);
		}
	}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using the clr gpio to reset the device, this is what we have implemented. I think the gpio should be high on boot since it will only go low if we want to reset or deassert the output.

Ahh, I get it know... But now that I thin about this, we should do this in another way because as we have it know, we will never use this pin for chips where has_clr_gpio = false. Sure, for those devices, it's not a mandatory pin but we should still be able to use it if we want too right? So, I would suggest something like:

max16150->clr_gpiod = devm_gpiod_get_optional(dev, "clr", GPIOD_OUT_HIGH);
if (IS_ERR(max16150->clr_gpiod))
    return dev_err_probe(dev, PTR_ERR(max16150->clr_gpiod),
           "Failed to get clr GPIO\n");
if (!max16150->clr_gpiod && chip_info->needs_clr_gpio)
    return dev_err_probe(dev, ...); //clr gpio is mandatory for latching out the output 
if (max16150->clr_gpiod) {
    ...
}

Makes sense?

}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already mentioned this... do not use OF but device properties. Please ask to me or someone else if you do not understand what i'm suggesting


irq = gpiod_to_irq(max16150->gpiod);
if (irq < 0)
return dev_err_probe(dev, irq,
"MAX16150: Failed to map GPIO to IRQ");

err = devm_request_irq(dev, irq, max16150_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"max16150_irq", max16150);
if (err)
return err;

return input_register_device(max16150->input);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need for the remove callback... input_unregister_device() is already called by the input subsystem... See devm_input_allocate_device()

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not just ignore comments... this .remove() callback is still here


static const struct of_device_id max16150_of_match[] = {
{ .compatible = "adi,max16150a", .data = &max16150_variant_a },
{ .compatible = "adi,max16150b", .data = &max16150_variant_b },
{ .compatible = "adi,max16169a", .data = &max16150_variant_a },
{ .compatible = "adi,max16169b", .data = &max16150_variant_b },
{ }
};
MODULE_DEVICE_TABLE(of, max16150_of_match);

static struct platform_driver max16150_driver = {
.probe = max16150_probe,
.driver = {
.name = "max16150",
.of_match_table = max16150_of_match,
},
};
module_platform_driver(max16150_driver);

MODULE_AUTHOR("Marc Paolo Sosa <[email protected]>");
MODULE_DESCRIPTION("MAX16150/MAX16169 Pushbutton Driver");
MODULE_LICENSE("GPL");