From: David Brownell

The regulator framework needs to better distinguish actual hardware
state reported by regulator_ops.get_mode() from whatever Linux last
requested. Some examples:

- Overcurrent events can turn regulators off, and even if
there's a notification, Linux may not have received it.

- TWL4030 regulator state is effectively the result of a vote
among three parties; the Linux vote can be overridden.

This patch:

- Updates the sysfs documentation to match the regulator_get_mode()
description, clarifing that the "opmode" attribute exposes what
the regulator driver reports.

- Adds a new "requested_opmode" attribute matching the previous
documented semantics for "opmode": report what Linux last
requested. (Those semantics still seem dubious to me ...)

- Adds REGULATOR_MODE_OFF which can be reported by hardware,
but not is not used in regulator_ops mode setting methods.
(The disable methods are still exclusively used for that.)

Signed-off-by: David Brownell
---
Difference from V1: add new sysfs attribute and the state
it tracks; update syfs docs; refocus patch description on
the distinction betwen Linux' view and the hardware state.

Documentation/ABI/testing/sysfs-class-regulator | 42 +++++++++++++++++++---
drivers/regulator/core.c | 40 +++++++++++++++++---
include/linux/regulator/consumer.h | 7 +++
3 files changed, 77 insertions(+), 12 deletions(-)

--- a/Documentation/ABI/testing/sysfs-class-regulator
+++ b/Documentation/ABI/testing/sysfs-class-regulator
@@ -23,7 +23,9 @@ Description:
the reported state is invalid.

NOTE: this field can be used in conjunction with microvolts
- and microamps to determine regulator output levels.
+ and microamps to determine regulator output levels requested
+ by Linux. Use "opmode" to determine actual regulator state,
+ which can reflect actions not controlled by Linux.


What: /sys/class/regulator/.../type
@@ -95,13 +97,20 @@ Description:
'normal'
'idle'
'standby'
- 'unknown'
+ 'off'
+ 'unknown' (reported state is invalid)

The modes are described in include/linux/regulator/consumer.h

- NOTE: This value should not be used to determine the regulator
- output operating mode as this value is the same regardless of
- whether the regulator is enabled or disabled.
+ NOTE: this is the actual hardware state reported by the
+ regulator driver. In contrast to "requested_opmode"
+ (see below), this can reflect actions not controlled by
+ Linux software.
+
+ For example, another system component may have asked the
+ regulator to enter "normal" mode although Linux-controlled
+ devices can suffice with "standby" (or even "off") levels.
+ Or an overcurrent event might have shut the regulator down.


What: /sys/class/regulator/.../min_microvolts
@@ -187,6 +196,29 @@ Description:
have called regulator_enable() on this regulator.


+What: /sys/class/regulator/.../requested_opmode
+Date: November 2008
+KernelVersion: 2.6.28
+Description:
+ Some regulator directories will contain a field called
+ requested_opmode. This reports the last requested regulator
+ operating mode, for regulators which can change that mode.
+
+ The opmode value can be one of the following strings:
+
+ 'fast'
+ 'normal'
+ 'idle'
+ 'standby'
+
+ The modes are described in include/linux/regulator/consumer.h
+
+ NOTE: This value should not be used to determine the regulator
+ output operating mode as this value is the same regardless of
+ whether the regulator is enabled or disabled, and regardless
+ of what non-Linux components may have done.
+
+
What: /sys/class/regulator/.../requested_microamps
Date: April 2008
KernelVersion: 2.6.26
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -37,6 +37,7 @@ static LIST_HEAD(regulator_map_list);
struct regulator_dev {
struct regulator_desc *desc;
int use_count;
+ int requested_mode;

/* lists we belong to */
struct list_head list; /* list of all regulators */
@@ -270,6 +271,8 @@ static ssize_t regulator_print_opmode(ch
return sprintf(buf, "idle\n");
case REGULATOR_MODE_STANDBY:
return sprintf(buf, "standby\n");
+ case REGULATOR_MODE_OFF:
+ return sprintf(buf, "off\n");
}
return sprintf(buf, "unknown\n");
}
@@ -283,6 +286,15 @@ static ssize_t regulator_opmode_show(str
}
static DEVICE_ATTR(opmode, 0444, regulator_opmode_show, NULL);

+static ssize_t regulator_requested_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct regulator_dev *rdev = dev_get_drvdata(dev);
+
+ return regulator_print_opmode(buf, rdev->requested_mode);
+}
+static DEVICE_ATTR(requested_opmode, 0444, regulator_requested_mode_show, NULL);
+
static ssize_t regulator_print_state(char *buf, int state)
{
if (state > 0)
@@ -542,8 +554,10 @@ static void drms_uA_update(struct regula

/* check the new mode is allowed */
err = regulator_check_mode(rdev, mode);
- if (err == 0)
- rdev->desc->ops->set_mode(rdev, mode);
+ if (err == 0) {
+ if (rdev->desc->ops->set_mode(rdev, mode) > 0)
+ rdev->requested_mode = mode;
+ }
}

static int suspend_set_state(struct regulator_dev *rdev,
@@ -559,7 +573,7 @@ static int suspend_set_state(struct regu
return -EINVAL;
}

- if (rstate->enabled)
+ if (rstate->enabled && rstate->mode != REGULATOR_MODE_OFF)
ret = rdev->desc->ops->set_suspend_enable(rdev);
else
ret = rdev->desc->ops->set_suspend_disable(rdev);
@@ -638,7 +652,9 @@ static void print_constraints(struct reg
if (constraints->valid_modes_mask & REGULATOR_MODE_IDLE)
count += sprintf(buf + count, "idle ");
if (constraints->valid_modes_mask & REGULATOR_MODE_STANDBY)
- count += sprintf(buf + count, "standby");
+ count += sprintf(buf + count, "standby ");
+ if (constraints->valid_modes_mask & REGULATOR_MODE_OFF)
+ count += sprintf(buf + count, "off ");

printk(KERN_INFO "regulator: %s: %s\n", rdev->desc->name, buf);
}
@@ -1332,7 +1348,8 @@ EXPORT_SYMBOL_GPL(regulator_get_current_
* @mode: operating mode - one of the REGULATOR_MODE constants
*
* Set regulator operating mode to increase regulator efficiency or improve
- * regulation performance.
+ * regulation performance. You may not pass REGULATOR_MODE_OFF; to achieve
+ * that effect, call regulator_disable().
*
* NOTE: Regulator system constraints must be set for this regulator before
* calling this function otherwise this call will fail.
@@ -1342,6 +1359,9 @@ int regulator_set_mode(struct regulator
struct regulator_dev *rdev = regulator->rdev;
int ret;

+ if (mode == REGULATOR_MODE_OFF)
+ return -EINVAL;
+
mutex_lock(&rdev->mutex);

/* sanity check */
@@ -1356,6 +1376,8 @@ int regulator_set_mode(struct regulator
goto out;

ret = rdev->desc->ops->set_mode(rdev, mode);
+ if (ret > 0)
+ rdev->requested_mode = mode;
out:
mutex_unlock(&rdev->mutex);
return ret;
@@ -1475,7 +1497,8 @@ int regulator_set_optimum_mode(struct re
printk(KERN_ERR "%s: failed to set optimum mode %x for %s\n",
__func__, mode, rdev->desc->name);
goto out;
- }
+ } else
+ rdev->requested_mode = mode;
ret = mode;
out:
mutex_unlock(&rdev->mutex);
@@ -1709,6 +1732,11 @@ static int add_regulator_attributes(stru
if (status < 0)
return status;
}
+ if (ops->set_mode) {
+ status = device_create_file(dev, &dev_attr_requested_opmode);
+ if (status < 0)
+ return status;
+ }
if (ops->is_enabled) {
status = device_create_file(dev, &dev_attr_state);
if (status < 0)
--- a/include/linux/regulator/consumer.h
+++ b/include/linux/regulator/consumer.h
@@ -68,16 +68,21 @@
* the most noisy and may not be able to handle fast load
* switching.
*
+ * OFF Regulator is disabled. This mode may be observed from
+ * some hardware after invoking disable() primitives, or
+ * after events not controlled by Linux.
+ *
* NOTE: Most regulators will only support a subset of these modes. Some
* will only just support NORMAL.
*
- * These modes can be OR'ed together to make up a mask of valid register modes.
+ * These modes can be OR'ed together to make up a mask of valid modes.
*/

#define REGULATOR_MODE_FAST 0x1
#define REGULATOR_MODE_NORMAL 0x2
#define REGULATOR_MODE_IDLE 0x4
#define REGULATOR_MODE_STANDBY 0x8
+#define REGULATOR_MODE_OFF 0x10

/*
* Regulator notifier events.
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/