-
Notifications
You must be signed in to change notification settings - Fork 868
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. | ||
enum: | ||
- adi,max16150a | ||
- adi,max16150b | ||
- adi,max16169a | ||
- adi,max16169b | ||
|
||
interrupt-gpio: | ||
mrcsosa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
mrcsosa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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>; | ||
}; |
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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 I would also expect to see a call to 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; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
If we have a long INT pulse, then
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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with this. The datasheet also mentions this: There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for a function for this. Call platform_get_irq() directly There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The above is wrong... If
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); There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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:
Makes sense? |
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no need for the remove callback... There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"); |
There was a problem hiding this comment.
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 '|'