linux-hwmon.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Mario Kicherer <dev@kicherer.org>
To: Guenter Roeck <linux@roeck-us.net>
Cc: Armin Wolf <W_Armin@gmx.de>,
	linux-hwmon@vger.kernel.org, jdelvare@suse.com
Subject: Re: [PATCH] hwmon: add initial NXP MC34VR500 PMIC monitoring support
Date: Tue, 17 Jan 2023 16:55:50 +0100	[thread overview]
Message-ID: <4fe880f4fded8f55d26851b2935825a3@kicherer.org> (raw)
In-Reply-To: <f96d7856-a244-a1a8-4ff3-1392d483aab9@roeck-us.net>

Sorry, I did not know that there is separate submission guide for HWMON. 
I only
checked the code without the --strict parameter.

I made all changes and will send a v2 in a few seconds.

Thank you both for the review!

Best regards,
Mario

On 2023-01-16 17:48, Guenter Roeck wrote:
> On 1/16/23 07:55, Armin Wolf wrote:
>> Am 16.01.23 um 14:42 schrieb Mario Kicherer:
>> 
>>> This patch adds initial monitoring support for the MC34VR500 PMIC. In 
>>> its
>>> current form, input voltage and temperature events are reported to 
>>> hwmon.
>>> Other interrupts only generate a generic entry in the kernel log so 
>>> far.
>>> 
> 
> This is unacceptable. The driver should neither enable nor log any 
> interrupt
> bits it doesn't support.
> 
>>> The header file is copied from U-Boot and placed next to the C file 
>>> as the
>>> chip is usually only configured by the bootloader and it is unlikely 
>>> that
>>> it will be used by another Linux subsystem. If I should remove unused 
>>> parts
>>> or move the file to another path, let me know.
>>> 
>>> Datasheet:
>>>   - https://www.nxp.com/docs/en/data-sheet/MC34VR500.pdf
>>> 
>>> Signed-off-by: Mario Kicherer <dev@kicherer.org>
>>> ---
>>>   drivers/hwmon/Kconfig          |   7 +
>>>   drivers/hwmon/Makefile         |   1 +
>>>   drivers/hwmon/mc34vr500.c      | 382 
>>> +++++++++++++++++++++++++++++++++
>>>   drivers/hwmon/mc34vr500_pmic.h | 172 +++++++++++++++
> 
> Documentation/hwmon/mc34vr500 is missing.
> 
> Devicetree property documentation is missing (though that doesn't
> really matter since the defined property is unacceptable).
> 
>>>   4 files changed, 562 insertions(+)
>>>   create mode 100644 drivers/hwmon/mc34vr500.c
>>>   create mode 100644 drivers/hwmon/mc34vr500_pmic.h
>>> 
>>> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
>>> index 3176c33af6c6..b917c2528b62 100644
>>> --- a/drivers/hwmon/Kconfig
>>> +++ b/drivers/hwmon/Kconfig
>>> @@ -2350,6 +2350,13 @@ config SENSORS_INTEL_M10_BMC_HWMON
>>>         sensors monitor various telemetry data of different 
>>> components on the
>>>         card, e.g. board temperature, FPGA core 
>>> temperature/voltage/current.
>>> 
>>> +config SENSORS_MC34VR500
>>> +    tristate "NXP MC34VR500 hardware monitoring driver"
>>> +    depends on I2C
>>> +    help
>>> +      If you say yes here you get support for the temperature and 
>>> input
>>> +      voltage sensors of the NXP MC34VR500.
>>> +
>>>   if ACPI
>>> 
>>>   comment "ACPI drivers"
>>> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
>>> index e2e4e87b282f..99e8cd8275c5 100644
>>> --- a/drivers/hwmon/Makefile
>>> +++ b/drivers/hwmon/Makefile
>>> @@ -216,6 +216,7 @@ obj-$(CONFIG_SENSORS_W83L786NG)    += w83l786ng.o
>>>   obj-$(CONFIG_SENSORS_WM831X)    += wm831x-hwmon.o
>>>   obj-$(CONFIG_SENSORS_WM8350)    += wm8350-hwmon.o
>>>   obj-$(CONFIG_SENSORS_XGENE)    += xgene-hwmon.o
>>> +obj-$(CONFIG_SENSORS_MC34VR500)    += mc34vr500.o
>>> 
>>>   obj-$(CONFIG_SENSORS_OCC)    += occ/
>>>   obj-$(CONFIG_SENSORS_PECI)    += peci/
>>> diff --git a/drivers/hwmon/mc34vr500.c b/drivers/hwmon/mc34vr500.c
>>> new file mode 100644
>>> index 000000000000..bddf108d05ae
>>> --- /dev/null
>>> +++ b/drivers/hwmon/mc34vr500.c
>>> @@ -0,0 +1,382 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +/*
>>> + * An hwmon driver for the NXP MC34VR500
>>> + *
>>> + * Copyright 2022 Mario Kicherer <dev@kicherer.org>
>>> + */
>>> +
>>> +#include <linux/hwmon.h>
>>> +#include <linux/hwmon-sysfs.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/regmap.h>
>>> +#include "mc34vr500_pmic.h"
>>> +
>>> +#define MC34VR500_DEVICEID_VALUE    0x14
>>> +
>>> +// INTSENSE0
> 
> Please no C++ comments.
> 
>>> +#define ENS_BIT        (1<<0)
>>> +#define LOWVINS_BIT    (1<<1)
>>> +#define THERM110S_BIT    (1<<2)
>>> +#define THERM120S_BIT    (1<<3)
>>> +#define THERM125S_BIT    (1<<4)
>>> +#define THERM130S_BIT    (1<<5)
>>> +
>>> +// INTSENSE1
>>> +#define SW1FAULTS1_BIT    (1<<0)
>>> +#define SW1FAULTS2_BIT    (1<<1)
>>> +#define SW1FAULTS3_BIT    (1<<2)
>>> +#define SW2FAULTS_BIT    (1<<3)
>>> +#define SW3FAULTS1_BIT    (1<<4)
>>> +#define SW3FAULTS2_BIT    (1<<5)
>>> +#define SW4FAULTS_BIT    (1<<6)
>>> +
>>> +// INTSENSE4
>>> +#define LDO1FAULTS_BIT    (1<<1)
>>> +#define LDO2FAULTS_BIT    (1<<2)
>>> +#define LDO3FAULTS_BIT    (1<<3)
>>> +#define LDO4FAULTS_BIT    (1<<4)
>>> +#define LDO5FAULTS_BIT    (1<<5)
>> 
>> Maybe you could use BIT() here?
>> 
> 
> Documentation/hwmon/submitting-patches.rst says:
> 
> * Please run your patch through 'checkpatch --strict'. There should be 
> no
>   errors, no warnings, and few if any check messages. If there are any
>   messages, please be prepared to explain.
> 
> With
> 
> total: 0 errors, 2 warnings, 43 checks, 574 lines checked
> 
> the author will have a lot of explaining to do.
> 
> I don't think the author read that document, though, because various
> other requirements in that document are not followed either.
> Some of the more obvious ones:
> 
> * Limit the number of kernel log messages. In general, your driver 
> should not
>   generate an error message just because a runtime operation failed. 
> Report
>   errors to user space instead, using an appropriate error code. Keep 
> in mind
>   that kernel error log messages not only fill up the kernel log, but 
> also are
>   printed synchronously, most likely with interrupt disabled, often to 
> a serial
>   console. Excessive logging can seriously affect system performance.
> 
> * Use devm_hwmon_device_register_with_info() or, if your driver needs a 
> remove
>   function, hwmon_device_register_with_info() to register your driver 
> with the
>   hwmon subsystem. Try using devm_add_action() instead of a remove 
> function if
>   possible. Do not use hwmon_device_register().
> 
> * Document the driver in Documentation/hwmon/<driver_name>.rst.
> 
> * Add the driver to Kconfig and Makefile in alphabetical order.
> 
> That really makes me wonder if I should create a form letter advising 
> people
> to follow the document, and that I'll only review the driver if there 
> are no
> violations.
> 
>>> +
>>> +struct mc34vr500_data {
>>> +    struct i2c_client    *client;
>>> +    struct regmap *regmap;
>>> +};
>>> +
>>> +static ssize_t mc34vr500_bool_show(struct device *dev,
>>> +                   struct device_attribute *da, char *buf)
>>> +{
>>> +    struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
>>> +    struct mc34vr500_data *data = (struct mc34vr500_data *) 
>>> dev_get_drvdata(dev);
>>> +    unsigned int reg;
>>> +    int ret;
>>> +
>>> +    ret = regmap_read(data->regmap, MC34VR500_INTSENSE0 + 
>>> (attr->index >> 8), &reg);
>>> +    if (ret < 0)
>>> +        return ret;
>>> +
>>> +    reg &= (attr->index & 0xff);
>>> +
>>> +    return sysfs_emit(buf, "%d\n", !!reg);
>>> +}
>>> +
>>> +// INTSENSE0
>>> +static SENSOR_DEVICE_ATTR_RO(in1_alarm, mc34vr500_bool, 0x00 | 
>>> LOWVINS_BIT);
> 
> This should really be either _min_alarm or _lcrit_alarm.
> 
>>> +static SENSOR_DEVICE_ATTR_RO(temp110_alarm, mc34vr500_bool, 0x00 | 
>>> THERM110S_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(temp120_alarm, mc34vr500_bool, 0x00 | 
>>> THERM120S_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(temp125_alarm, mc34vr500_bool, 0x00 | 
>>> THERM125S_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(temp130_alarm, mc34vr500_bool, 0x00 | 
>>> THERM130S_BIT);
>> 
>> Does the chip support up to 130 temperature channels? If no, then 
>> maybe you could
>> name them "temp1_*", ..., "temp5_*" and use tempX_label for the 
>> labeling.
>> 
> 
> No, I checked the datasheet. This reflects temperatures and is an abuse 
> of the hwmon ABI.
> There is only a single temperature. The author will have to decide 
> which of the alarm
> attributes (_max_alarm, _crit_alarm, _emergency_alarm) to use for the 
> four limits.
> 
>>> +
>>> +// INTSENSE1
>>> +static SENSOR_DEVICE_ATTR_RO(curr1_alarm, mc34vr500_bool, 0x300 | 
>>> SW1FAULTS1_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr2_alarm, mc34vr500_bool, 0x300 | 
>>> SW1FAULTS2_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr3_alarm, mc34vr500_bool, 0x300 | 
>>> SW1FAULTS3_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr4_alarm, mc34vr500_bool, 0x300 | 
>>> SW2FAULTS_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr5_alarm, mc34vr500_bool, 0x300 | 
>>> SW3FAULTS1_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr6_alarm, mc34vr500_bool, 0x300 | 
>>> SW3FAULTS2_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr7_alarm, mc34vr500_bool, 0x300 | 
>>> SW4FAULTS_BIT);
>>> +
>>> +// INTSENSE4
>>> +static SENSOR_DEVICE_ATTR_RO(curr8_alarm, mc34vr500_bool, 0x600 | 
>>> LDO1FAULTS_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr9_alarm, mc34vr500_bool, 0x600 | 
>>> LDO2FAULTS_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr10_alarm, mc34vr500_bool, 0x600 | 
>>> LDO3FAULTS_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr11_alarm, mc34vr500_bool, 0x600 | 
>>> LDO4FAULTS_BIT);
>>> +static SENSOR_DEVICE_ATTR_RO(curr12_alarm, mc34vr500_bool, 0x600 | 
>>> LDO5FAULTS_BIT);
>>> +
>>> +static struct attribute *mc34vr500_attrs[] = {
>>> +    &sensor_dev_attr_in1_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_temp110_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_temp120_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_temp125_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_temp130_alarm.dev_attr.attr,
>>> +
>>> +    &sensor_dev_attr_curr1_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr2_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr3_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr4_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr5_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr6_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr7_alarm.dev_attr.attr,
>>> +
>>> +    &sensor_dev_attr_curr8_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr9_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr10_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr11_alarm.dev_attr.attr,
>>> +    &sensor_dev_attr_curr12_alarm.dev_attr.attr,
> 
> There are only 9 current channels (SW1..SW4 and LDO1..LDO5). Some of 
> them
> consume more than one interrupt status bit, but the documentation 
> doesn't
> say what the two bits exactly refer to. Maybe to multiple phases ?
> Either that (but that would not explain why only two of the four SWx
> channels have two status bits) or this is another abuse of the hwmon 
> ABI.
> 
>>> +    NULL
>>> +};
>>> +ATTRIBUTE_GROUPS(mc34vr500);
>> 
>> You should use the new hwmon_device_register_with_info() API, which 
>> does
>> the sysfs handling for you.
>> 
> 
> should -> must. I won't accept new drivers if they don't use the new 
> API.
> 
>>> +
>>> +static irqreturn_t mc34vr500_process_interrupt(int irq, void 
>>> *userdata)
>>> +{
>>> +    struct mc34vr500_data *data = (struct mc34vr500_data *) 
>>> userdata;
>>> +    struct i2c_client *client = data->client;
>>> +    unsigned int reg;
>>> +    int ret;
>>> +
>>> +    pr_debug("mc34vr500: received interrupt\n");
>>> +
>>> +    ret = regmap_read(data->regmap, MC34VR500_INTSTAT0, &reg);
>>> +    if (ret < 0) {
>>> +        dev_err(&client->dev, "unable to read mc34vr500 intsense0 
>>> register\n");
>> 
>> Maybe use some sort of ratelimiting here? Otherwise it could 
>> potentially fill up
>> the kernel log with redundant messages. Or maybe just omit this and 
>> all the other
>> error messages?
>> 
>>> +        return ret;
>>> +    }
>>> +
>>> +    if (reg) {
>>> +        pr_warn("mc34vr500: interrupt intstat0 %u\n", reg);
>> 
>> Same as above, it could fill up the kernel log is the alarm interrupt 
>> is triggered fast enough.
>> 
> 
> This isn't acceptable anyway. It logs a message with every interrupts.
> All those pr_{warn, notice} messages need to be dropped from the 
> driver.
> 
>>> +
>>> +        if (reg & LOWVINS_BIT) {
>>> +            ret = hwmon_notify_event(&client->dev, hwmon_in,
>>> +                         hwmon_in_alarm, 1);
> 
> Notifications need to be on the hwmon device.
> 
>>> +            if (ret)
>>> +                dev_err(&client->dev,
>>> +                    "mc34vr500: error, hwmon_notify_event() failed: 
>>> %d\n",
>>> +                    ret);
>>> +        }
>>> +
>>> +        if (reg & THERM110S_BIT) {
>>> +            ret = hwmon_notify_event(&client->dev, hwmon_temp,
>>> +                         hwmon_temp_alarm, 110);
>>> +            if (ret)
>>> +                dev_err(&client->dev,
>>> +                    "mc34vr500: error, hwmon_notify_event() failed: 
>>> %d\n",
>>> +                    ret);
>>> +        }
> 
> hwmon_notify_event() only fails if bad data is passed to it, ie if the 
> driver
> code is bad. Checking and logging it is only necessary if the code was 
> never
> tested.
> 
>>> +
>>> +        reg = 0;
>>> +        ret = regmap_write(data->regmap, MC34VR500_INTSTAT0, reg);
>>> +        if (ret) {
>>> +            dev_err(&client->dev, "unable to enable intmask0 
>>> interrupts\n");
>>> +            return ret;
>>> +        }
>>> +    }
>>> +
>>> +    ret = regmap_read(data->regmap, MC34VR500_INTSTAT1, &reg);
>>> +    if (ret < 0) {
>>> +        dev_err(&client->dev, "unable to read mc34vr500 intsense1 
>>> register\n");
>>> +        return ret;
>>> +    }
>>> +
>>> +    if (reg) {
>>> +        pr_warn("mc34vr500: interrupt intstat1 %u\n", reg);
>>> +
>>> +        reg = 0;
>>> +        ret = regmap_write(data->regmap, MC34VR500_INTSTAT1, reg);
>>> +        if (ret) {
>>> +            dev_err(&client->dev, "unable to enable intmask1 
>>> interrupts\n");
>>> +            return ret;
>>> +        }
>>> +    }
>>> +
>>> +    ret = regmap_read(data->regmap, MC34VR500_INTSTAT4, &reg);
>>> +    if (ret < 0) {
>>> +        dev_err(&client->dev, "unable to read mc34vr500 intsense4 
>>> register\n");
>>> +        return ret;
>>> +    }
>>> +
>>> +    if (reg) {
>>> +        pr_warn("mc34vr500: interrupt intstat4 %u\n", reg);
>>> +
>>> +        reg = 0;
>>> +        ret = regmap_write(data->regmap, MC34VR500_INTSTAT4, reg);
>>> +        if (ret) {
>>> +            dev_err(&client->dev, "unable to enable intmask4 
>>> interrupts\n");
>>> +            return ret;
>>> +        }
>>> +    }
>>> +
>>> +    return IRQ_HANDLED;
>>> +}
>>> +
>>> +static const struct regmap_config mc34vr500_regmap_config = {
>>> +    .reg_bits = 8,
>>> +    .val_bits = 8,
>>> +    .max_register = MC34VR500_PWRGD_EN,
>>> +};
>>> +
>>> +static int mc34vr500_probe(struct i2c_client *client)
>>> +{
>>> +    struct device *dev = &client->dev;
>>> +    struct mc34vr500_data *data;
>>> +    struct device *hwmon_dev;
>>> +    int ret;
>>> +    unsigned int reg, revid, fabid;
>>> +    struct regmap *regmap;
>>> +    u32 interrupt_mask;
>>> +
>>> +    regmap = devm_regmap_init_i2c(client, &mc34vr500_regmap_config);
>>> +    if (IS_ERR(regmap)) {
>>> +        dev_err(dev, "failed to allocate register map\n");
>>> +        return PTR_ERR(regmap);
>>> +    }
>>> +
>>> +    data = devm_kzalloc(dev, sizeof(struct mc34vr500_data), 
>>> GFP_KERNEL);
>>> +    if (!data)
>>> +        return -ENOMEM;
>>> +
>>> +    data->regmap = regmap;
>>> +    data->client = client;
>>> +
>>> +    ret = regmap_read(regmap, MC34VR500_DEVICEID, &reg);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "unable to read config register\n");
>>> +
>>> +        return ret;
>>> +    }
>>> +
>>> +    if (reg != MC34VR500_DEVICEID_VALUE) {
>>> +        dev_err(dev, "invalid config register value 0x%x\n", reg);
>>> +
>>> +        return -ENODEV;
>>> +    }
> 
> This simply reflects that the chip isn't there and should result in 
> -ENODEV
> without logging noise.
> 
>>> +
>>> +    ret = regmap_read(regmap, MC34VR500_SILICONREVID, &revid);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "unable to read mc34vr500 revid register\n");
>>> +        return ret;
>>> +    }
>>> +
>>> +    ret = regmap_read(regmap, MC34VR500_FABID, &fabid);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "unable to read mc34vr500 fabid register\n");
>>> +        return ret;
>>> +    }
>>> +
>>> +    pr_notice("mc34vr500: revid 0x%x fabid 0x%x\n", revid, fabid);
>>> +
>>> +
>>> +    ret = of_property_read_u32(dev->of_node,
>>> +                   "interrupt-mask",
>>> +                   &interrupt_mask);
>>> +    if (ret == -EINVAL) {
>>> +        interrupt_mask = 0;
>>> +    } else if (ret) {
>>> +        dev_err(dev, "Error reading interrupt_mask\n");
>>> +        return ret;
>>> +    }
>>> +
> 
> This is inappropriate. The driver needs to enable the interrupt bits it 
> needs,
> and nothing else.
> 
> If there is a desire to enable individual channels, that should be done
> with a per-channel deviretree file and with the {in, current, 
> temp}X_enable
> sysfs attribute.
> 
>>> +    ret = regmap_read(regmap, MC34VR500_INTSTAT0, &reg);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "unable to read mc34vr500 intstat0 
>>> register\n");
>>> +        return ret;
>>> +    }
>>> +    reg = reg & (~interrupt_mask);
>>> +
>>> +    if (reg)
>>> +        pr_notice("mc34vr500: intstat0: 0x%x\n", reg);
>>> +
>>> +    if (reg & LOWVINS_BIT)
>>> +        pr_notice("mc34vr500: low input voltage detected\n");
>>> +
>>> +    if (reg & THERM130S_BIT)
>>> +        pr_notice("mc34vr500: temperature >= 130°c\n");
>>> +    else if (reg & THERM125S_BIT)
>>> +        pr_notice("mc34vr500: temperature >= 125°c\n");
>>> +    else if (reg & THERM120S_BIT)
>>> +        pr_notice("mc34vr500: temperature >= 120°c\n");
>>> +    else if (reg & THERM110S_BIT)
>>> +        pr_notice("mc34vr500: temperature >= 110°c\n");
>>> +
>>> +    ret = regmap_read(regmap, MC34VR500_INTSTAT1, &reg);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "unable to read mc34vr500 intstat1 
>>> register\n");
>>> +        return ret;
>>> +    }
>>> +    reg = reg & ((~interrupt_mask) >> 8);
>>> +
>>> +    if (reg)
>>> +        pr_notice("mc34vr500: intstat1: 0x%x\n", reg);
>>> +
>>> +    ret = regmap_read(regmap, MC34VR500_INTSTAT4, &reg);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "unable to read mc34vr500 intstat4 
>>> register\n");
>>> +        return ret;
>>> +    }
>>> +    reg = reg & ((~interrupt_mask) >> 16);
>>> +
>>> +    if (reg)
>>> +        pr_notice("mc34vr500: intstat4: 0x%x\n", reg);
>>> +
> 
> Most of the above does not belong in the probe function and needs to be 
> dropped.
> It is not the responsibility of the probe function to log interrupt 
> status
> values.
> 
>>> +    hwmon_dev = devm_hwmon_device_register_with_groups(dev, 
>>> client->name,
>>> +                               data,
>>> +                               mc34vr500_groups);
>>> +
>>> +    if (client->irq) {
>>> +        pr_notice("mc34vr500: enabling IRQ...\n");
>>> +
> 
> Example for driver noise. I could try to mark them all but gave up 
> after
> realizing how noisy this driver is. This all needs to go.
> 
>>> +        ret = devm_request_threaded_irq(dev, client->irq, NULL,
>>> +                        mc34vr500_process_interrupt,
>>> +                        IRQF_TRIGGER_RISING |
>>> +                            IRQF_ONESHOT |
>>> +                            IRQF_SHARED,
>>> +                        dev_name(dev),
>>> +                        data);
>>> +        if (ret) {
>>> +            dev_err(dev, "Error requesting irq\n");
>>> +            return ret;
>>> +        }
>>> +
>>> +        /* clear interrupts */
>>> +        reg = 0;
>>> +        ret = regmap_write(regmap, MC34VR500_INTSTAT0, reg);
>>> +        if (ret) {
>>> +            dev_err(dev, "unable to write intstat0 register\n");
>>> +            return ret;
>>> +        }
>>> +
>>> +        /* enable interrupts */
>>> +        ret = regmap_write(regmap, MC34VR500_INTMASK0, 
>>> interrupt_mask & 0xff);
>>> +        if (ret) {
>>> +            dev_err(dev, "unable to enable intmask0 interrupts\n");
>>> +            return ret;
>>> +        }
>>> +
>>> +        reg = 0;
>>> +        ret = regmap_write(regmap, MC34VR500_INTSTAT1, reg);
>>> +        if (ret) {
>>> +            dev_err(dev, "unable to write intstat1 register\n");
>>> +            return ret;
>>> +        }
>>> +
>>> +        ret = regmap_write(regmap, MC34VR500_INTMASK1, 
>>> (interrupt_mask >> 8) & 0xff);
>>> +        if (ret) {
>>> +            dev_err(dev, "unable to enable intmask1 interrupts\n");
>>> +            return ret;
>>> +        }
>>> +
>>> +        reg = 0;
>>> +        ret = regmap_write(regmap, MC34VR500_INTSTAT4, reg);
>>> +        if (ret) {
>>> +            dev_err(dev, "unable to write intstat1 register\n");
>>> +            return ret;
>>> +        }
>>> +
>>> +        ret = regmap_write(regmap, MC34VR500_INTMASK4, 
>>> (interrupt_mask >> 16) & 0xff);
>>> +        if (ret) {
>>> +            dev_err(dev, "unable to enable intmask1 interrupts\n");
>>> +            return ret;
>>> +        }
>>> +    }
>>> +
>>> +    return PTR_ERR_OR_ZERO(hwmon_dev);
>>> +}
>>> +
>>> +static const struct i2c_device_id mc34vr500_id[] = {
>>> +    { "mc34vr500", 0 },
>>> +    {}
>>> +};
>>> +MODULE_DEVICE_TABLE(i2c, mc34vr500_id);
>>> +
>>> +static struct i2c_driver mc34vr500_driver = {
>>> +    .driver = {
>>> +        .name    = "mc34vr500",
>>> +    },
>>> +    .probe_new = mc34vr500_probe,
>>> +    .id_table = mc34vr500_id,
>>> +};
>>> +
>>> +module_i2c_driver(mc34vr500_driver);
>>> +
>>> +MODULE_AUTHOR("Mario Kicherer <dev@kicherer.org>");
>>> +
>>> +MODULE_DESCRIPTION("MC34VR500 driver");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/hwmon/mc34vr500_pmic.h 
>>> b/drivers/hwmon/mc34vr500_pmic.h
>>> new file mode 100644
>>> index 000000000000..1543ac692d72
>>> --- /dev/null
>>> +++ b/drivers/hwmon/mc34vr500_pmic.h
>>> @@ -0,0 +1,172 @@
>>> +/* SPDX-License-Identifier: GPL-2.0+ */
>>> +/*
>>> + * Copyright 2016 Freescale Semiconductor, Inc.
>>> + * Hou Zhiqiang <Zhiqiang.Hou@freescale.com>
>>> + */
>>> +
>>> +#ifndef __MC34VR500_H_
>>> +#define __MC34VR500_H_
>>> +
>>> +#define MC34VR500_I2C_ADDR    0x08
>>> +
>>> +/* Drivers name */
>>> +#define MC34VR500_REGULATOR_DRIVER    "mc34vr500_regulator"
>>> +
>>> +/* Register map */
>>> +enum {
>>> +    MC34VR500_DEVICEID        = 0x00,
>>> +
>>> +    MC34VR500_SILICONREVID        = 0x03,
>>> +    MC34VR500_FABID,
>>> +    MC34VR500_INTSTAT0,
>>> +    MC34VR500_INTMASK0,
>>> +    MC34VR500_INTSENSE0,
>>> +    MC34VR500_INTSTAT1,
>>> +    MC34VR500_INTMASK1,
>>> +    MC34VR500_INTSENSE1,
>>> +
>>> +    MC34VR500_INTSTAT4        = 0x11,
>>> +    MC34VR500_INTMASK4,
>>> +    MC34VR500_INTSENSE4,
>>> +
>>> +    MC34VR500_PWRCTL        = 0x1B,
>>> +
>>> +    MC34VR500_SW1VOLT        = 0x2E,
>>> +    MC34VR500_SW1STBY,
>>> +    MC34VR500_SW1OFF,
>>> +    MC34VR500_SW1MODE,
>>> +    MC34VR500_SW1CONF,
>>> +    MC34VR500_SW2VOLT,
>>> +    MC34VR500_SW2STBY,
>>> +    MC34VR500_SW2OFF,
>>> +    MC34VR500_SW2MODE,
>>> +    MC34VR500_SW2CONF,
>>> +
>>> +    MC34VR500_SW3VOLT        = 0x3C,
>>> +    MC34VR500_SW3STBY,
>>> +    MC34VR500_SW3OFF,
>>> +    MC34VR500_SW3MODE,
>>> +    MC34VR500_SW3CONF,
>>> +
>>> +    MC34VR500_SW4VOLT        = 0x4A,
>>> +    MC34VR500_SW4STBY,
>>> +    MC34VR500_SW4OFF,
>>> +    MC34VR500_SW4MODE,
>>> +    MC34VR500_SW4CONF,
>>> +
>>> +    MC34VR500_REFOUTCRTRL        = 0x6A,
>>> +
>>> +    MC34VR500_LDO1CTL        = 0x6D,
>>> +    MC34VR500_LDO2CTL,
>>> +    MC34VR500_LDO3CTL,
>>> +    MC34VR500_LDO4CTL,
>>> +    MC34VR500_LDO5CTL,
>>> +
>>> +    MC34VR500_PAGE_REGISTER        = 0x7F,
>>> +
>>> +    /* Internal RAM */
>>> +    MC34VR500_SW1_VOLT        = 0xA8,
>>> +    MC34VR500_SW1_SEQ,
>>> +    MC34VR500_SW1_CONFIG,
>>> +
>>> +    MC34VR500_SW2_VOLT        = 0xAC,
>>> +    MC34VR500_SW2_SEQ,
>>> +    MC34VR500_SW2_CONFIG,
>>> +
>>> +    MC34VR500_SW3_VOLT        = 0xB0,
>>> +    MC34VR500_SW3_SEQ,
>>> +    MC34VR500_SW3_CONFIG,
>>> +
>>> +    MC34VR500_SW4_VOLT        = 0xB8,
>>> +    MC34VR500_SW4_SEQ,
>>> +    MC34VR500_SW4_CONFIG,
>>> +
>>> +    MC34VR500_REFOUT_SEQ        = 0xC4,
>>> +
>>> +    MC34VR500_LDO1_VOLT        = 0xCC,
>>> +    MC34VR500_LDO1_SEQ,
>>> +
>>> +    MC34VR500_LDO2_VOLT        = 0xD0,
>>> +    MC34VR500_LDO2_SEQ,
>>> +
>>> +    MC34VR500_LDO3_VOLT        = 0xD4,
>>> +    MC34VR500_LDO3_SEQ,
>>> +
>>> +    MC34VR500_LDO4_VOLT        = 0xD8,
>>> +    MC34VR500_LDO4_SEQ,
>>> +
>>> +    MC34VR500_LDO5_VOLT        = 0xDC,
>>> +    MC34VR500_LDO5_SEQ,
>>> +
>>> +    MC34VR500_PU_CONFIG1        = 0xE0,
>>> +
>>> +    MC34VR500_TBB_POR        = 0xE4,
>>> +
>>> +    MC34VR500_PWRGD_EN        = 0xE8,
>>> +
>>> +    MC34VR500_NUM_OF_REGS,
>>> +};
>>> +
>>> +/* Registor offset based on SWxVOLT register */
>>> +#define MC34VR500_VOLT_OFFSET    0
>>> +#define MC34VR500_STBY_OFFSET    1
>>> +#define MC34VR500_OFF_OFFSET    2
>>> +#define MC34VR500_MODE_OFFSET    3
>>> +#define MC34VR500_CONF_OFFSET    4
>>> +
>>> +#define SW_MODE_MASK    0xf
>>> +#define SW_MODE_SHIFT    0
>>> +
>>> +#define LDO_VOL_MASK    0xf
>>> +#define LDO_EN        (1 << 4)
>>> +#define LDO_MODE_SHIFT    4
>>> +#define LDO_MODE_MASK    (1 << 4)
>>> +#define LDO_MODE_OFF    0
>>> +#define LDO_MODE_ON    1
>>> +
>>> +#define REFOUTEN    (1 << 4)
>>> +
>>> +/*
>>> + * Regulator Mode Control
>>> + *
>>> + * OFF: The regulator is switched off and the output voltage is 
>>> discharged.
>>> + * PFM: In this mode, the regulator is always in PFM mode, which is 
>>> useful
>>> + *      at light loads for optimized efficiency.
>>> + * PWM: In this mode, the regulator is always in PWM mode operation
>>> + *    regardless of load conditions.
>>> + * APS: In this mode, the regulator moves automatically between 
>>> pulse
>>> + *    skipping mode and PWM mode depending on load conditions.
>>> + *
>>> + * SWxMODE[3:0]
>>> + * Normal Mode  |  Standby Mode    |      value
>>> + *   OFF        OFF        0x0
>>> + *   PWM        OFF        0x1
>>> + *   PFM        OFF        0x3
>>> + *   APS        OFF        0x4
>>> + *   PWM        PWM        0x5
>>> + *   PWM        APS        0x6
>>> + *   APS        APS        0x8
>>> + *   APS        PFM        0xc
>>> + *   PWM        PFM        0xd
>>> + */
>>> +#define OFF_OFF        0x0
>>> +#define PWM_OFF        0x1
>>> +#define PFM_OFF        0x3
>>> +#define APS_OFF        0x4
>>> +#define PWM_PWM        0x5
>>> +#define PWM_APS        0x6
>>> +#define APS_APS        0x8
>>> +#define APS_PFM        0xc
>>> +#define PWM_PFM        0xd
>>> +
>>> +enum swx {
>>> +    SW1 = 0,
>>> +    SW2,
>>> +    SW3,
>>> +    SW4,
>>> +};
>>> +
>>> +int mc34vr500_get_sw_volt(uint8_t sw);
>>> +int mc34vr500_set_sw_volt(uint8_t sw, int sw_volt);
>>> +int power_mc34vr500_init(unsigned char bus);
>>> +#endif /* __MC34VR500_PMIC_H_ */

      reply	other threads:[~2023-01-17 15:56 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-01-16 13:42 [PATCH] hwmon: add initial NXP MC34VR500 PMIC monitoring support Mario Kicherer
2023-01-16 15:55 ` Armin Wolf
2023-01-16 16:48   ` Guenter Roeck
2023-01-17 15:55     ` Mario Kicherer [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4fe880f4fded8f55d26851b2935825a3@kicherer.org \
    --to=dev@kicherer.org \
    --cc=W_Armin@gmx.de \
    --cc=jdelvare@suse.com \
    --cc=linux-hwmon@vger.kernel.org \
    --cc=linux@roeck-us.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).