]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
hwmon: (pmbus/core) improve handling of write protected regulators
authorJerome Brunet <jbrunet@baylibre.com>
Mon, 2 Dec 2024 10:28:00 +0000 (11:28 +0100)
committerGuenter Roeck <linux@roeck-us.net>
Sat, 14 Dec 2024 16:00:44 +0000 (08:00 -0800)
Writing PMBus protected registers does succeed from the smbus perspective,
even if the write is ignored by the device and a communication fault is
raised. This fault will silently be caught and cleared by pmbus irq if one
has been registered.

This means that the regulator call may return succeed although the
operation was ignored.

With this change, the operation which are not supported will be properly
flagged as such and the regulator framework won't even try to execute them.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
[groeck: Adjust to EXPORT_SYMBOL_NS_GPL API change]
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/pmbus-core.rst
drivers/hwmon/pmbus/pmbus.h
drivers/hwmon/pmbus/pmbus_core.c
include/linux/pmbus.h

index 686a00265bf71231c684afad6df41d6266303919..0a251960f8910ffb121d82b45e729d06f98424ef 100644 (file)
@@ -312,6 +312,10 @@ currently provides a flags field with four bits used::
 
        #define PMBUS_USE_COEFFICIENTS_CMD              BIT(5)
 
+       #define PMBUS_OP_PROTECTED                      BIT(6)
+
+       #define PMBUS_VOUT_PROTECTED                    BIT(7)
+
        struct pmbus_platform_data {
                u32 flags;              /* Device specific flags */
 
@@ -373,3 +377,13 @@ PMBUS_USE_COEFFICIENTS_CMD
 
 When this flag is set the PMBus core driver will use the COEFFICIENTS
 register to initialize the coefficients for the direct mode format.
+
+PMBUS_OP_PROTECTED
+
+Set if the chip OPERATION command is protected and protection is not
+determined by the standard WRITE_PROTECT command.
+
+PMBUS_VOUT_PROTECTED
+
+Set if the chip VOUT_COMMAND command is protected and protection is not
+determined by the standard WRITE_PROTECT command.
index d605412a3173b95041524285ad1fde52fb64ce5a..ddb19c9726d62416244f83603b92d81d82e64891 100644 (file)
@@ -487,6 +487,8 @@ struct pmbus_driver_info {
 /* Regulator ops */
 
 extern const struct regulator_ops pmbus_regulator_ops;
+int pmbus_regulator_init_cb(struct regulator_dev *rdev,
+                           struct regulator_config *config);
 
 /* Macros for filling in array of struct regulator_desc */
 #define PMBUS_REGULATOR_STEP(_name, _id, _voltages, _step, _min_uV)  \
@@ -501,6 +503,7 @@ extern const struct regulator_ops pmbus_regulator_ops;
                .n_voltages = _voltages,                        \
                .uV_step = _step,                               \
                .min_uV = _min_uV,                              \
+               .init_cb = pmbus_regulator_init_cb,             \
        }
 
 #define PMBUS_REGULATOR(_name, _id)   PMBUS_REGULATOR_STEP(_name, _id, 0, 0, 0)
@@ -516,6 +519,7 @@ extern const struct regulator_ops pmbus_regulator_ops;
                .n_voltages = _voltages,                        \
                .uV_step = _step,                               \
                .min_uV = _min_uV,                              \
+               .init_cb = pmbus_regulator_init_cb,             \
        }
 
 #define PMBUS_REGULATOR_ONE(_name)   PMBUS_REGULATOR_STEP_ONE(_name, 0, 0, 0)
index a1375cb6b648c15835c1dde611af2df6b31e774b..5976dd819b3c6f80ed8ddd141a6dab35c7897628 100644 (file)
@@ -2665,6 +2665,30 @@ static void pmbus_remove_pec(void *dev)
        device_remove_file(dev, &dev_attr_pec);
 }
 
+static void pmbus_init_wp(struct i2c_client *client, struct pmbus_data *data)
+{
+       int ret;
+
+       ret = _pmbus_read_byte_data(client, -1, PMBUS_WRITE_PROTECT);
+       if (ret < 0)
+               return;
+
+       switch (ret & PB_WP_ANY) {
+       case PB_WP_ALL:
+               data->flags |= PMBUS_OP_PROTECTED;
+               fallthrough;
+       case PB_WP_OP:
+               data->flags |= PMBUS_VOUT_PROTECTED;
+               fallthrough;
+       case PB_WP_VOUT:
+               data->flags |= PMBUS_WRITE_PROTECTED | PMBUS_SKIP_STATUS_CHECK;
+               break;
+
+       default:
+               break;
+       }
+}
+
 static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
                             struct pmbus_driver_info *info)
 {
@@ -2718,12 +2742,8 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
         * faults, and we should not try it. Also, in that case, writes into
         * limit registers need to be disabled.
         */
-       if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) {
-               ret = _pmbus_read_byte_data(client, -1, PMBUS_WRITE_PROTECT);
-
-               if (ret > 0 && (ret & PB_WP_ANY))
-                       data->flags |= PMBUS_WRITE_PROTECTED | PMBUS_SKIP_STATUS_CHECK;
-       }
+       if (!(data->flags & PMBUS_NO_WRITE_PROTECT))
+               pmbus_init_wp(client, data);
 
        ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION);
        if (ret >= 0)
@@ -3183,8 +3203,12 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
 {
        struct device *dev = rdev_get_dev(rdev);
        struct i2c_client *client = to_i2c_client(dev->parent);
+       struct pmbus_data *data = i2c_get_clientdata(client);
        int val, low, high;
 
+       if (data->flags & PMBUS_VOUT_PROTECTED)
+               return 0;
+
        if (selector >= rdev->desc->n_voltages ||
            selector < rdev->desc->linear_min_sel)
                return -EINVAL;
@@ -3219,6 +3243,22 @@ const struct regulator_ops pmbus_regulator_ops = {
 };
 EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, "PMBUS");
 
+int pmbus_regulator_init_cb(struct regulator_dev *rdev,
+                           struct regulator_config *config)
+{
+       struct pmbus_data *data = config->driver_data;
+       struct regulation_constraints *constraints = rdev->constraints;
+
+       if (data->flags & PMBUS_OP_PROTECTED)
+               constraints->valid_ops_mask &= ~REGULATOR_CHANGE_STATUS;
+
+       if (data->flags & PMBUS_VOUT_PROTECTED)
+               constraints->valid_ops_mask &= ~REGULATOR_CHANGE_VOLTAGE;
+
+       return 0;
+}
+EXPORT_SYMBOL_NS_GPL(pmbus_regulator_init_cb, "PMBUS");
+
 static int pmbus_regulator_register(struct pmbus_data *data)
 {
        struct device *dev = data->dev;
index fa9f08164c365a541ee1c6480bafd8c3a8f98138..884040e1383bf41d2eb3b6de72c40e2650178dc6 100644 (file)
  */
 #define PMBUS_USE_COEFFICIENTS_CMD             BIT(5)
 
+/*
+ * PMBUS_OP_PROTECTED
+ * Set if the chip OPERATION command is protected and protection is not
+ * determined by the standard WRITE_PROTECT command.
+ */
+#define PMBUS_OP_PROTECTED                     BIT(6)
+
+/*
+ * PMBUS_VOUT_PROTECTED
+ * Set if the chip VOUT_COMMAND command is protected and protection is not
+ * determined by the standard WRITE_PROTECT command.
+ */
+#define PMBUS_VOUT_PROTECTED                   BIT(7)
+
 struct pmbus_platform_data {
        u32 flags;              /* Device specific flags */