Skip to content

Add driver for MAX42500 #2659

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 6 commits into
base: main
Choose a base branch
from

Conversation

kentlibe
Copy link

@kentlibe kentlibe commented Dec 1, 2024

PR Description

  • This adds new driver for the MAX42500 Industrial Power System Monitor Family, along with corresponding device tree bindings.
  • Datasheet MAX42500
  • Tested in RPI4 with MAX42500_EVKIT_P1.

PR Type

  • Bug fix (a change that fixes an issue)
  • New feature (a change that adds new functionality)
  • Breaking change (a change that affects other repos or cause CIs to fail)

PR Checklist

  • I have conducted a self-review of my own code changes
  • I have tested the changes on the relevant hardware
  • I have updated the documentation outside this repo accordingly (if there is the case)

@nunojsa
Copy link
Collaborator

nunojsa commented Dec 2, 2024

Is this still WIP? As it stands it's not really ready for reviewing... Please make sure to address CI complains and to re-organize your commits. Merge commits don't really make sense (rebase your dev branch) and please run git lon --oneline <DIR_YOU'RE_CHANGING> so you can the git log style for that subsystem. Then, please follow the same style. Also note that a commit as Cleanup MAX42500 Linux driver. is not fine. You need to explain what you're actually cleaning and why...

@kentlibe kentlibe force-pushed the dev/max42500 branch 3 times, most recently from a3dad5b to a6008ba Compare December 10, 2024 07:49
@kentlibe kentlibe changed the title Add driver support for MAX42500 Add driver for MAX42500 Dec 10, 2024
@kentlibe kentlibe force-pushed the dev/max42500 branch 4 times, most recently from 32dc5ba to 5920e35 Compare December 11, 2024 02:02
@kentlibe
Copy link
Author

v2

  • ready for review
  • address CI complaints
  • address style issues
  • re-organize commits
  • rebase dev branch

Copy link
Contributor

@kister-jimenez kister-jimenez left a comment

Choose a reason for hiding this comment

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

First review attempt.

I think you need to do some adjustments in you formatting especially the tabs to make the code more readable. I have commented some of them, but I see a lot needs to be adjusted.

@kentlibe kentlibe force-pushed the dev/max42500 branch 8 times, most recently from 40e89e5 to d468dbd Compare January 31, 2025 08:09
@kentlibe kentlibe force-pushed the dev/max42500 branch 4 times, most recently from 870d915 to 144fac7 Compare March 3, 2025 08:38
@kentlibe
Copy link
Author

kentlibe commented Mar 3, 2025

v4

  • split into 3 drivers: mfd, hwmon and watchdog
  • implement the mfd as the main device with the i2c driver
  • rework the hwmon driver to follow latest ABI for sysfs platform
  • rework the watchdog driver to follow watchdog sysfs platform
  • add debugfs to handle all non-standard attr in hwmon and watchdog
  • implement the two gpios control in power management format
  • renamed the 3 driver files according to their usage and purpose.
  • add documenatation for the hwmon and watchdog sysfs atttr usage

@nunojsa kindly review.

Copy link
Collaborator

@nunojsa nunojsa left a comment

Choose a reason for hiding this comment

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

Looks better to me... If you want we can still do another internal review. If not, take my latest inputs and send it upstream. Up to you :)


#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typically the above header is not really needed and not encouraged because it includes a lot of things one usually do not need. It seems you're missing some headers but you're not realizing it because they are being included by kernel.h. Always prefer to explicitly include the needed headers...

if (st->fpsr[MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE])
gpiod_set_value(st->sleep_gpio, GPIOD_OUT_HIGH);
else
gpiod_set_value(st->sleep_gpio, GPIOD_OUT_LOW);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not just something like?

gpiod_set_value(st->sleep_gpio, !!st->fpsr[MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE])

if (st->fpsr[MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE])
gpiod_set_value(st->sleep_gpio, GPIOD_OUT_LOW);
else
gpiod_set_value(st->sleep_gpio, GPIOD_OUT_HIGH);
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

if (IS_ERR(st->sleep_gpio))
return PTR_ERR(st->sleep_gpio);

devm_mutex_init(dev, &st->lock);
Copy link
Collaborator

Choose a reason for hiding this comment

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

error handling

st->client = client;
st->regmap = regmap;

devm_mutex_init(dev, &st->lock);
Copy link
Collaborator

Choose a reason for hiding this comment

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

error handling...

{
struct device *parent_dev = pdev->dev.parent;
struct regmap *regmap = dev_get_drvdata(parent_dev);
struct i2c_client *client = to_i2c_client(parent_dev);
Copy link
Collaborator

Choose a reason for hiding this comment

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

do you need the i2c_client?

Copy link
Author

Choose a reason for hiding this comment

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

will be removed

const u8 clk_div = MAX42500_WDOG_WD_CLOCK_DIVIDER;
const u8 win_mode = MAX42500_WDOG_WD_SIMPLE_MODE_ENABLE;
const u8 opn_win = MAX42500_WDOG_WD_OPEN_WINDOW_INTERVAL;
const u8 clo_win = MAX42500_WDOG_WD_CLOSE_WINDOW_INTERVAL;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would drop the const qualifier. It does not give you much in here and the compiler should be more than capable of realizing it anyways without the qualifier


ret = regmap_write(st->regmap, MAX42500_REG_WDKEY, reg_val);
if (ret)
return ret;
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

Copy link
Author

Choose a reason for hiding this comment

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

For simple mode, yes can update this. But for challenge-response mode, we need to read the current key first, apply the LFSR algorithm and write back the new key. Maybe we could leave this here or go with the change?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, now that I look better into this, simple mode looks odd. We read the key and then write the same key back. Is this expected?

Copy link
Author

Choose a reason for hiding this comment

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

For simple mode, any key value would do (whether same or not) as long as the WDKEY register is updated before the watchdog resets. In the datasheet it states:
The watchdog can operate in challenge/response mode (in which a specific key value must be written to WDKEY) or in simple mode (in which any write to WDKEY will update the watchdog).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see. Then I would say/ask. why not just writing 0 in the simple mode case? I mean, there's no real need for a register read right? And do the RMW only for the more complex case?

Copy link
Author

Choose a reason for hiding this comment

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

yes, I see that would be simpler and optimal. So, code would now look like:

	guard(mutex)(&st->lock);

	switch (mode) {
	case MAX42500_WD_MODE_CH_RESP:
		ret = regmap_read(st->regmap, MAX42500_REG_WDKEY, &key);
		if (ret)
			return ret;
		
		/* Linear-Feedback Shift Register (LFSR) Polynomial Equation */
		lfsr = ((key >> 7) ^ (key >> 5) ^ (key >> 4) ^ (key >> 3)) & 1;
		reg_val = (key << 1) | lfsr;

		return regmap_write(st->regmap, MAX42500_REG_WDKEY, reg_val);
	case MAX42500_WD_MODE_SIMPLE:
		return regmap_write(st->regmap, MAX42500_REG_WDKEY, 0);
	default:
		return -EINVAL;
	}

@kentlibe kentlibe force-pushed the dev/max42500 branch 2 times, most recently from 576aa49 to e305f27 Compare March 4, 2025 10:12
@kentlibe
Copy link
Author

kentlibe commented Mar 4, 2025

@nunojsa could you take one final review before we upstream?

@kentlibe
Copy link
Author

kentlibe commented Mar 5, 2025

@kister-jimenez is the dt-binding check error caused by a CI script error?

@nunojsa
Copy link
Collaborator

nunojsa commented Mar 5, 2025

@kister-jimenez is the dt-binding check error caused by a CI script error?

Yes, our CI needs to be fixed for the bindings check

properties:
compatible:
enum:
- adi,max42500-mfd
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typically, you do not need the -mfd for the real device (meaning for the top device). Keep it only for -hwmon and -wdt

gpiod_set_value(st->power_gpio, GPIOD_OUT_LOW);

gpiod_set_value(st->sleep_gpio,
st->fpsr[MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I would do !!st->fpsr[MAX42500_FPSR_FPS_EN1_START_TIMER_ENABLE] just to be sure to pass 0 or 1 into gpiod_set_value()


static int max42500_wdt_store_wd_rst2_cnt_enable_log(void *arg, u64 val)
{
return max42500_wdt_wdog_write(arg, (long)val,
Copy link
Collaborator

Choose a reason for hiding this comment

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

this explicit cast should not be needed


static int max42500_wdt_show_wd_rst2_cnt_enable_log(void *arg, u64 *val)
{
return max42500_wdt_wdog_read(arg, (long *)val,
Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure this is correct. This will go wrong in big endian 32bit archs... I would use a dedicated variable. This:

long __val;
int ret;

ret = max42500_wdt_wdog_read(arg, &__val, ...);
if (ret)
   return ret;

*val = __val; 

Same for the other places where this applies. Note this is mostly a problem fro getters and not setters since getters use pointers which this cast to potentially smaller types could be problematic.


ret = regmap_write(st->regmap, MAX42500_REG_WDKEY, reg_val);
if (ret)
return ret;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, now that I look better into this, simple mode looks odd. We read the key and then write the same key back. Is this expected?

return ret;

/* Update after successful register write */
st->wdog[MAX42500_WDOG_WD_KEY] = FIELD_GET(GENMASK(7, 0), reg_val);
Copy link
Author

Choose a reason for hiding this comment

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

And I think I would also remove this line.

Copy link
Collaborator

@nunojsa nunojsa Mar 5, 2025

Choose a reason for hiding this comment

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

yeah, no need to cache all values you write even more when you can read them from the device

/* Update after successful register write */
st->wdog[MAX42500_WDOG_WD_KEY] = FIELD_GET(GENMASK(7, 0), reg_val);

return 0;
Copy link
Author

Choose a reason for hiding this comment

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

and this as well

@kentlibe kentlibe force-pushed the dev/max42500 branch 3 times, most recently from 93c684e to 6560334 Compare March 7, 2025 07:40
Copy link
Collaborator

@nunojsa nunojsa left a comment

Choose a reason for hiding this comment

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

Feel free to send this upstream. As another advice, I would improve the commit messages for the bindings patch. Saying "adding bindings file" or something like that is only stating the obvious, You should add a short description of the device being added (similar to the driver patch)

#address-cells = <1>;
#size-cells = <0>;

max42500@28 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do not think this will be accepted... Needs to be more generic but honestly i'm not sure what can we put in here. I know I was the one to mention this but maybe just send this upstream as you had it before. Meaning, "hwmon" since the primary function of the device is monitoring.

Copy link
Author

Choose a reason for hiding this comment

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

ok will do. Previously I wrote "mfd" then "mfdev", but I thought it was too generic since all other mfd drivers are also too.

Copy link
Author

Choose a reason for hiding this comment

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

Saw something similar in richtek and/or ricoh mfd dt-bindings and was not sure if same for max42500. But I guess not.

max42500_subdev, ARRAY_SIZE(max42500_subdev),
NULL, 0, NULL);

return ret;
Copy link
Collaborator

Choose a reason for hiding this comment

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

you can just return devm_mfd_add_devices(...)

#include <linux/mutex.h>
#include <linux/mfd/max42500.h>
#include <linux/platform_device.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.

alphabetical order

static struct platform_driver max42500_hwmon_driver = {
.driver = {
.name = "max42500-hwmon",
.pm = &max42500_pm_ops,
Copy link
Collaborator

Choose a reason for hiding this comment

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

use pm_sleep_ptr()

return 0;
}

EXPORT_SIMPLE_DEV_PM_OPS(max42500_pm_ops, max42500_suspend, max42500_resume);
Copy link
Collaborator

Choose a reason for hiding this comment

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

why exporting this? Do you need this function anywhere else? I guess not...

Copy link
Author

Choose a reason for hiding this comment

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

will remove this

int ret;

ret = max42500_wdt_wdog_read(watchdog_get_drvdata(wdd), &wdog_val,
MAX42500_WDOG_WD_STATUS);
Copy link
Collaborator

Choose a reason for hiding this comment

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

alignment seems a bit off here

guard(mutex)(&st->lock);

switch (mode) {
case MAX42500_WD_MODE_CH_RESP:
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: the mutex is only needed in this case

Copy link
Author

Choose a reason for hiding this comment

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

when the mutex is moved in the case, there is build error:

In file included from drivers/watchdog/max42500-wdt.c:8:
drivers/watchdog/max42500-wdt.c: In function ‘max42500_wdt_set_watchdog_key’:
./include/linux/cleanup.h:86:2: error: a label can only be part of a statement and a declaration is not a statement
   86 |  class_##_name##_t var __cleanup(class_##_name##_destructor) = \
      |  ^~~~~~
./include/linux/cleanup.h:131:2: note: in expansion of macro ‘CLASS’
  131 |  CLASS(_name, __UNIQUE_ID(guard))
      |  ^~~~~
drivers/watchdog/max42500-wdt.c:94:3: note: in expansion of macro ‘guard’
   94 |   guard(mutex)(&st->lock);
      |   ^~~~~
make[4]: *** [scripts/Makefile.build:243: drivers/watchdog/max42500-wdt.o] Error 1
make[3]: *** [scripts/Makefile.build:480: drivers/watchdog] Error 2
make[3]: *** Waiting for unfinished jobs....

Copy link
Collaborator

Choose a reason for hiding this comment

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

You need the scoped version...

reg_val = (key << 1) | lfsr;

return regmap_write(st->regmap, MAX42500_REG_WDKEY, reg_val);
case MAX42500_WD_MODE_SIMPLE:
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I would be tempted to put a comment stating that we just need to do a random write in this case (just to avoid potential questions) - not sure if the intent is already obvious.

Copy link
Author

Choose a reason for hiding this comment

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

should I add the mutex in the case here too?

Copy link
Collaborator

Choose a reason for hiding this comment

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

no, regmap already has an internal lock unless you disable it in the config (which you're not doing)

The MAX42500 component structure of the nodes and properties of the serial
interfaces and the I/O ports to access, control and configure the voltage
monitors, and to program the power sequence timestamp recordings and the
windowed watchdog timing.

Signed-off-by: Kent Libetario <Kent.Libetario@analog.com>
kentlibe added 2 commits March 9, 2025 02:00
The MAX42500 is a multiple function device for power system monitoring,
power sequence timestamp recordings and windowed watchdog combined all-
together in one SoC package. These features can be used independently or
at the same time in a wide range of applications.

Signed-off-by: Kent Libetario <Kent.Libetario@analog.com>
The MAX42500 supports standard power management, hwmon and watchdog ABI
and sysfs attribute entries to expose the sensor data of the voltage
monitors, power sequence timestamp recordings and windowed watchdog to
the userspace.

Signed-off-by: Kent Libetario <Kent.Libetario@analog.com>
The MAX42500 is a SoC power-system monitor with up to seven voltage
monitor inputs that has programmable OV/UV thresholds and support DVS
by I2C interface. Also, it has a programmable flexible power sequence
recorder that stores the power-up and power-down timestamps separately.

Signed-off-by: Kent Libetario <Kent.Libetario@analog.com>
@kentlibe kentlibe force-pushed the dev/max42500 branch 2 times, most recently from 5baf2de to 8772b88 Compare March 11, 2025 09:09
The MAX42500 contains a programmable windowed watchdog and can be used
either as simple mode windowed watchdog or challenge-and-response mode
watchdog, which is accessible through the I2C interface, along with a
configurable RESET output.

Signed-off-by: Kent Libetario <Kent.Libetario@analog.com>
Add entry for the MAX42500 driver

Signed-off-by: Kent Libetario <Kent.Libetario@analog.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants