[PATCH v1 1/2] pinctrl: core: Allow drivers to keep "init" pinctrl state after probe

"Michał Kardaś" <[email protected]>
Newsgroups gmane.linux.kernel,gmane.linux.kernel.gpio,gmane.linux.serial
Message-ID <[email protected]>
During device probe, pinctrl_bind_pins() binds pins to their "init" state
if specified in Device Tree. When probe finishes, pinctrl_init_done()
automatically transitions the pins from "init" to "default" state.

While this auto-transition works well for devices that are immediately
active upon driver binding, certain peripherals (such as power-sequenced
devices connected over UART, SPI, or other buses) remain unpowered until
userspace explicitly opens the device node or attaches a protocol driver.

On board designs where the connected peripheral is kept unpowered during
boot, auto-selecting "default" or "sleep" pin states (where signals such
as TXD or RTS may be driven high or pulled up) can cause parasitic
back-powering into the unpowered peripheral through its ESD protection
diodes.

Allow drivers to explicitly opt out of the automatic "init" -> "default"
transition by calling pinctrl_keep_init_state(dev) during probe. When this
helper is called, pinctrl_init_done() leaves the pins in their "init"
state upon probe completion. The driver can then transition to the
"default" state when the device is first opened by calling
pinctrl_pm_select_default_state(dev).

Suggested-by: Douglas Anderson <[email protected]>
Signed-off-by: Michał Kardaś <[email protected]>
---
 Documentation/driver-api/pin-control.rst | 10 ++++++----
 drivers/pinctrl/core.c                   | 20 ++++++++++++++++++++
 include/linux/pinctrl/consumer.h         |  6 ++++++
 include/linux/pinctrl/devinfo.h          |  2 ++
 4 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/Documentation/driver-api/pin-control.rst b/Documentation/driver-api/pin-control.rst
index 1f585ecca63c..8d52bf74da6c 100644
--- a/Documentation/driver-api/pin-control.rst
+++ b/Documentation/driver-api/pin-control.rst
@@ -1172,7 +1172,8 @@ Possible standard state names are: "default", "init", "sleep" and "idle".
 
 - if ``init`` and ``default`` are defined in the device tree, the "init"
   state is selected before the driver probe and the "default" state is
-  selected after the driver probe.
+  selected after the driver probe, unless the driver explicitly opts out
+  by calling ``pinctrl_keep_init_state()`` during probe.
 
 - the ``sleep`` and ``idle`` states are for power management and can only
   be selected with the PM API bellow.
@@ -1233,9 +1234,10 @@ operation and going to sleep, moving from the ``PINCTRL_STATE_DEFAULT`` to
 current in sleep mode.
 
 Another case is when the pinctrl needs to switch to a certain mode during
-probe and then revert to the default state at the end of probe. For example
-a PINMUX may need to be configured as a GPIO during probe. In this case, use
-``PINCTRL_STATE_INIT`` to switch state before probe, then move to
+probe and then revert to the default state at the end of probe (or remain
+in the init state until activated if ``pinctrl_keep_init_state()`` is called).
+For example a PINMUX may need to be configured as a GPIO during probe. In this
+case, use ``PINCTRL_STATE_INIT`` to switch state before probe, then move to
 ``PINCTRL_STATE_DEFAULT`` at the end of probe for normal operation.
 
 A driver may request a certain control state to be activated, usually just the
diff --git a/drivers/pinctrl/core.c b/drivers/pinctrl/core.c
index 1675dd36bd5c..fd0c91610338 100644
--- a/drivers/pinctrl/core.c
+++ b/drivers/pinctrl/core.c
@@ -1640,6 +1640,9 @@ int pinctrl_init_done(struct device *dev)
 	if (pins->p->state != pins->init_state)
 		return 0; /* Not at init anyway */
 
+	if (pins->keep_init)
+		return 0; /* Driver explicitly requested to stay in init state */
+
 	if (IS_ERR(pins->default_state))
 		return 0; /* No default state */
 
@@ -1678,6 +1681,23 @@ int pinctrl_select_default_state(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(pinctrl_select_default_state);
 
+/**
+ * pinctrl_keep_init_state() - mark pinctrl handle to stay in init state after probe
+ * @dev: device to keep init state for
+ *
+ * Return: true if the device has a valid init state and keep_init flag was set,
+ *         false otherwise.
+ */
+bool pinctrl_keep_init_state(struct device *dev)
+{
+	if (!dev->pins || IS_ERR(dev->pins->init_state))
+		return false;
+
+	dev->pins->keep_init = true;
+	return true;
+}
+EXPORT_SYMBOL_GPL(pinctrl_keep_init_state);
+
 #ifdef CONFIG_PM
 
 /**
diff --git a/include/linux/pinctrl/consumer.h b/include/linux/pinctrl/consumer.h
index 11b8f0b8da0c..4312a098fb4c 100644
--- a/include/linux/pinctrl/consumer.h
+++ b/include/linux/pinctrl/consumer.h
@@ -47,6 +47,7 @@ int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);
 struct pinctrl * __must_check devm_pinctrl_get(struct device *dev);
 void devm_pinctrl_put(struct pinctrl *p);
 int pinctrl_select_default_state(struct device *dev);
+bool pinctrl_keep_init_state(struct device *dev);
 
 #ifdef CONFIG_PM
 int pinctrl_pm_select_default_state(struct device *dev);
@@ -152,6 +153,11 @@ static inline int pinctrl_select_default_state(struct device *dev)
 	return 0;
 }
 
+static inline bool pinctrl_keep_init_state(struct device *dev)
+{
+	return false;
+}
+
 static inline int pinctrl_pm_select_default_state(struct device *dev)
 {
 	return 0;
diff --git a/include/linux/pinctrl/devinfo.h b/include/linux/pinctrl/devinfo.h
index de4228eea90a..13dac33f2df1 100644
--- a/include/linux/pinctrl/devinfo.h
+++ b/include/linux/pinctrl/devinfo.h
@@ -32,6 +32,7 @@ struct pinctrl;
  * @init_state: the state at probe time, if found
  * @sleep_state: the state at suspend time, if found
  * @idle_state: the state at idle (runtime suspend) time, if found
+ * @keep_init: flag indicating if init state should persist after probe
  */
 struct dev_pin_info {
 	struct pinctrl *p;
@@ -41,6 +42,7 @@ struct dev_pin_info {
 	struct pinctrl_state *sleep_state;
 	struct pinctrl_state *idle_state;
 #endif
+	bool keep_init:1;
 };
 
 extern int pinctrl_init_done(struct device *dev);
-- 
2.55.0.654.g21b8a5bc05-goog
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.