[PATCH v3] pinctrl: add support for group and pinmux states

Linus Walleij linus.walleij at stericsson.com
Sun Jan 15 19:16:55 EST 2012


From: Linus Walleij <linus.walleij at linaro.org>

This makes it possible for pin groups and pin muxes to go into
different states, which is especially useful for power management,
both runtime and across suspend/resume.

ChangeLog v2->v3:
- Let the pin controller enumerate the available states for a
  group with string names just like it enumerates the groups
  themselves.
- Do not mandate any states (these are full-custom) but supply
  a bunch of clever presets in <linux/pinctrl/pinctrl.h> that
  can be used when applicable.
- Consequental changes to the pinmux interface - muxes use the
  named group states.
ChangeLog v1->v2:
- Use a pin controller name instead of the device itself as
  parameter to the state set function, as indicated by Stephens
  similar patch to config this is more helpful.

Signed-off-by: Linus Walleij <linus.walleij at linaro.org>
---
 Documentation/pinctrl.txt       |  114 +++++++++++++++++++++++++++++++++++++++
 drivers/pinctrl/core.c          |   86 +++++++++++++++++++++++++++++
 drivers/pinctrl/core.h          |    3 +
 drivers/pinctrl/pinmux.c        |   32 +++++++++++
 include/linux/pinctrl/pinctrl.h |   44 +++++++++++++++
 include/linux/pinctrl/pinmux.h  |    7 +++
 6 files changed, 286 insertions(+), 0 deletions(-)

diff --git a/Documentation/pinctrl.txt b/Documentation/pinctrl.txt
index 1851f1d..bf50192 100644
--- a/Documentation/pinctrl.txt
+++ b/Documentation/pinctrl.txt
@@ -194,6 +194,96 @@ just a simple example - in practice you may need more entries in your group
 structure, for example specific register ranges associated with each group
 and so on.
 
+Pin group states
+================
+
+To simplify handling of specific group states, such as when a group of
+pins need to be deactivated or put in a specific sleep state, the pin controller
+may implement callbacks to enumerate a number of states and select one of
+them. This will for example be called automatically to set a certain pin group
+in active state when the groups are selected for use in a certain pin
+multiplexing configuration (see below). This is a good opportunity to set up
+any default pin configuration (see below) for the pins in a group.
+
+While the driver will have to maintain states for its pin groups internally
+or in the hardware registers, as well as for setting up the pins on boot,
+it will know through these calls if some special action is needed on behalf
+of the users.
+
+For example, a board file may issue:
+
+pinctrl_set_group_state("foo-dev", "i2c7-group", "disabled");
+
+In this case it could be that I2C7 is not used at all in this board, and
+whereas the driver would normally set the I2C pins to pull-up (this happens to
+be required for I2C), in this case since they are not even used on this board,
+it may just as well ground the pins instead and save some power.
+
+The driver will know what to do through this set of callbacks, for example
+if group 3 supports two states "active" and "idle" like this:
+
+static const char *activestate = "active";
+static const char *idlestate = "idle";
+
+static int foo_list_group_states(struct pinctrl_dev *pctldev,
+				unsigned selector,
+				unsigned state)
+{
+	/* Only group three has states */
+	if (selector != 3)
+		return -EINVAL;
+	/* Only two states, 0 and 1 */
+	if (state > 1)
+		return -EINVAL;
+}
+
+static const char foo_get_group_state_name(struct pinctrl_dev *pctldev,
+				unsigned selector,
+				unsigned state)
+{
+
+	if (selector != 3)
+		return -EINVAL;
+	if (state == 0)
+		return activestate;
+	if (state == 1)
+		return idlestate;
+	return -EINVAL;
+}
+
+static int foo_set_group_state(struct pinctrl_dev *pctldev,
+				unsigned selector,
+				unsigned state)
+{
+	if (selector != 3)
+		return -EINVAL;
+	switch(state) {
+	case 0:
+		...
+	case 1:
+		...
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct pinctrl_ops foo_pctrl_ops = {
+	...
+	.list_group_states = foo_list_group_states,
+	.get_group_state_name = foo_get_group_state_name,
+	.set_group_state = foo_set_group_state,
+};
+
+
+static struct pinctrl_desc foo_desc = {
+	...
+	.pctlops = &foo_pctrl_ops,
+};
+
+So the core will call .list_group_states() iteratively to get the available
+state names for a group, get names for comparison from .get_group_state_name()
+and then finally set a state using the .set_group_state().
 
 Pin configuration
 =================
@@ -1066,3 +1156,27 @@ foo_switch()
 }
 
 The above has to be done from process context.
+
+
+Pinmux states
+=============
+
+Pinmuxes can have states just like groups, and in fact setting the state of
+a certain pinmux to a certain named state will in practice loop over the pin
+groups used by the pinmux and set the state for each of these groups. Any calls
+to set the pinmux state will be silently ignored if the pin controller does not
+implement handling of group states.
+
+For example a driver, or centralized power management code for the platform
+(wherever the struct pinmux *pmx pointer is handled) can do power saving calls
+like this:
+
+pinmux_set_state(pmx, "active");
+pinmux_set_state(pmx, "idle");
+pinmux_set_state(pmx, "sleep");
+
+Each call may or may not result in the corresponding pin control driver setting
+pins in special low-power modes, disabling pull-ups or anything else (likely
+pin configuration related). It may also just ignore the call, which is useful
+when the same driver is used with different pin controllers, some of which can
+do something meaningful with this information, some which can't.
diff --git a/drivers/pinctrl/core.c b/drivers/pinctrl/core.c
index 569bdb3..132bbce 100644
--- a/drivers/pinctrl/core.c
+++ b/drivers/pinctrl/core.c
@@ -345,6 +345,92 @@ int pinctrl_get_group_selector(struct pinctrl_dev *pctldev,
 	return -EINVAL;
 }
 
+/**
+ * pinctrl_get_group_state_selector() - locate a certain group state selector
+ * @pctldev: name of the pin controller to find the group state for
+ * @groupsel: the previously located group selector
+ * @state: the name of the state to locate
+ */
+static int pinctrl_get_group_state_selector(struct pinctrl_dev *pctldev,
+					    unsigned groupsel,
+					    const char *state)
+{
+	const struct pinctrl_ops *pctlops = pctldev->desc->pctlops;
+	unsigned statesel = 0;
+
+	while (pctlops->list_group_states(pctldev, groupsel, statesel) >= 0) {
+		const char *sname = pctlops->get_group_state_name(pctldev,
+								  groupsel,
+								  statesel);
+		if (!strcmp(sname, state)) {
+			dev_dbg(pctldev->dev,
+				"found group state selector %u for %s\n",
+				statesel,
+				state);
+			return statesel;
+		}
+
+		statesel++;
+	}
+
+	dev_err(pctldev->dev, "does not have state %s\n", state);
+
+	return -EINVAL;
+}
+
+/**
+ * pinctrl_set_group_state_for_selector() - knowing a groups selector, set its
+ * state
+ * @pctldev: name of the pin controller to find the group state for
+ * @groupsel: the previously located group selector
+ * @state: the name of the state to set
+ */
+int pinctrl_set_group_state_for_selector(struct pinctrl_dev *pctldev,
+					 unsigned groupsel,
+					 const char *state)
+{
+	const struct pinctrl_ops *ops;
+	int statesel;
+
+	ops = pctldev->desc->pctlops;
+	statesel = pinctrl_get_group_state_selector(pctldev, groupsel, state);
+	if (statesel < 0)
+		return statesel;
+
+	return ops->set_group_state(pctldev, groupsel, statesel);
+}
+
+/**
+ * pinctrl_set_group_state() - set pin group state
+ * @dev_name: name of the pin controller holding the group to change state for
+ * @name: the name of the pin group to change the state of
+ * @state: the new state for the pin group
+ */
+int pinctrl_set_group_state(const char *dev_name,
+			    const char *name,
+			    const char *state)
+{
+	struct pinctrl_dev *pctldev;
+	const struct pinctrl_ops *ops;
+	int groupsel;
+
+	pctldev = get_pinctrl_dev_from_dev(NULL, dev_name);
+	if (!pctldev)
+		return -EINVAL;
+	ops = pctldev->desc->pctlops;
+
+	if (!ops->list_group_states || !ops->get_group_state_name ||
+	    !ops->set_group_state)
+		return 0;
+
+	groupsel = pinctrl_get_group_selector(pctldev, name);
+	if (groupsel < 0)
+		return groupsel;
+
+	return pinctrl_set_group_state_for_selector(pctldev, groupsel, state);
+}
+EXPORT_SYMBOL_GPL(pinctrl_set_group_state);
+
 #ifdef CONFIG_DEBUG_FS
 
 static int pinctrl_pins_show(struct seq_file *s, void *what)
diff --git a/drivers/pinctrl/core.h b/drivers/pinctrl/core.h
index 177a331..cbdccb8 100644
--- a/drivers/pinctrl/core.h
+++ b/drivers/pinctrl/core.h
@@ -78,3 +78,6 @@ int pinctrl_get_device_gpio_range(unsigned gpio,
 				  struct pinctrl_gpio_range **outrange);
 int pinctrl_get_group_selector(struct pinctrl_dev *pctldev,
 			       const char *pin_group);
+int pinctrl_set_group_state_for_selector(struct pinctrl_dev *pctldev,
+					 unsigned groupsel,
+					 const char *state);
diff --git a/drivers/pinctrl/pinmux.c b/drivers/pinctrl/pinmux.c
index a76a348..31c9e1d 100644
--- a/drivers/pinctrl/pinmux.c
+++ b/drivers/pinctrl/pinmux.c
@@ -904,6 +904,38 @@ void pinmux_disable(struct pinmux *pmx)
 }
 EXPORT_SYMBOL_GPL(pinmux_disable);
 
+int pinmux_set_state(struct pinmux *pmx, const char *state)
+{
+	struct pinctrl_dev *pctldev = pmx->pctldev;
+	const struct pinctrl_ops *pctlops = pctldev->desc->pctlops;
+	struct pinmux_group *grp;
+	int ret;
+
+	/* Does not implement group states, bail out */
+	if (!pctlops->set_group_state)
+		return 0;
+
+	/*
+	 * Loop over the currently used groups for this pinmux and set the
+	 * state for each and every one of them.
+	 */
+	list_for_each_entry(grp, &pmx->groups, node) {
+		ret = pinctrl_set_group_state_for_selector(pctldev,
+							   grp->group_selector,
+							   state);
+		if (ret) {
+			dev_err(pctldev->dev, "unable to set state %s for "
+				"group %u on %s\n",
+				state, grp->group_selector,
+				pinctrl_dev_get_name(pctldev));
+			return ret;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pinmux_set_state);
+
 int pinmux_check_ops(const struct pinmux_ops *ops)
 {
 	/* Check that we implement required operations */
diff --git a/include/linux/pinctrl/pinctrl.h b/include/linux/pinctrl/pinctrl.h
index 8bd22ee..ca33b84 100644
--- a/include/linux/pinctrl/pinctrl.h
+++ b/include/linux/pinctrl/pinctrl.h
@@ -12,6 +12,23 @@
 #ifndef __LINUX_PINCTRL_PINCTRL_H
 #define __LINUX_PINCTRL_PINCTRL_H
 
+/**
+ * Standard pin group states. States are defined by the driver, and names can
+ * be all-custom. However, to avoid duplication of common pin group states,
+ * they are provided here.
+ * @PIN_GROUP_STATE_DISABLED: disabled state
+ * @PIN_GROUP_STATE_ACTIVE: active state, pins are in active use for their
+ *	purpose, this should be the default state when pins are muxed in for
+ *	example
+ * @PIN_GROUP_STATE_IDLE: the pin group is not actively used now
+ * @PIN_GROUP_STATE_SLEEPING: the pin group is in low-power mode for a longer
+ *	period of time
+ */
+#define PIN_GROUP_STATE_DISABLED	"disabled"
+#define PIN_GROUP_STATE_ACTIVE		"active"
+#define PIN_GROUP_STATE_IDLE		"idle"
+#define PIN_GROUP_STATE_SLEEP		"sleeping"
+
 #ifdef CONFIG_PINCTRL
 
 #include <linux/radix-tree.h>
@@ -69,6 +86,14 @@ struct pinctrl_gpio_range {
  * @get_group_name: return the group name of the pin group
  * @get_group_pins: return an array of pins corresponding to a certain
  *	group selector @pins, and the size of the array in @num_pins
+ * @list_group_states: optional callback to list the number of selectable
+ *	named group states available for a certain group, the core will begin
+ *	on 0 and call this repeatedly as long as it returns >= 0 to enumerate
+ *	the states
+ * @get_group_state_name: optional callback to return the name of the pin
+ *	group state indicated
+ * @set_group_state: optional callback to set the state of a certain pin
+ *	group. Not defining this will make all calls to set state succeed.
  * @pin_dbg_show: optional debugfs display hook that will provide per-device
  *	info for a certain pin in debugfs
  */
@@ -80,6 +105,15 @@ struct pinctrl_ops {
 			       unsigned selector,
 			       const unsigned **pins,
 			       unsigned *num_pins);
+	int (*list_group_states) (struct pinctrl_dev *pctldev,
+				  unsigned selector,
+				  unsigned state);
+	const char *(*get_group_state_name) (struct pinctrl_dev *pctldev,
+					     unsigned selector,
+					     unsigned state);
+	int (*set_group_state) (struct pinctrl_dev *pctldev,
+				unsigned selector,
+				unsigned state);
 	void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,
 			  unsigned offset);
 };
@@ -120,6 +154,9 @@ extern void pinctrl_remove_gpio_range(struct pinctrl_dev *pctldev,
 				struct pinctrl_gpio_range *range);
 extern const char *pinctrl_dev_get_name(struct pinctrl_dev *pctldev);
 extern void *pinctrl_dev_get_drvdata(struct pinctrl_dev *pctldev);
+extern int pinctrl_set_group_state(const char *dev_name,
+				   const char *name,
+				   const char *state);
 #else
 
 struct pinctrl_dev;
@@ -130,6 +167,13 @@ static inline bool pin_is_valid(struct pinctrl_dev *pctldev, int pin)
 	return pin >= 0;
 }
 
+static inline int pinctrl_set_group_state(const char *dev_name,
+					  const char *name,
+					  const char *state)
+{
+	return 0;
+}
+
 #endif /* !CONFIG_PINCTRL */
 
 #endif /* __LINUX_PINCTRL_PINCTRL_H */
diff --git a/include/linux/pinctrl/pinmux.h b/include/linux/pinctrl/pinmux.h
index 937b3e2..a87be46 100644
--- a/include/linux/pinctrl/pinmux.h
+++ b/include/linux/pinctrl/pinmux.h
@@ -97,6 +97,7 @@ extern struct pinmux * __must_check pinmux_get(struct device *dev, const char *n
 extern void pinmux_put(struct pinmux *pmx);
 extern int pinmux_enable(struct pinmux *pmx);
 extern void pinmux_disable(struct pinmux *pmx);
+extern int pinmux_set_state(struct pinmux *pmx, const char *state);
 
 #else /* !CONFIG_PINMUX */
 
@@ -137,6 +138,12 @@ static inline void pinmux_disable(struct pinmux *pmx)
 {
 }
 
+static inline int pinmux_set_state(struct pinmux *pmx,
+				   const char *state)
+{
+	return 0;
+}
+
 #endif /* CONFIG_PINMUX */
 
 #endif /* __LINUX_PINCTRL_PINMUX_H */
-- 
1.7.8




More information about the linux-arm-kernel mailing list