From c84b0326d5e4fe08d493f6fff245da2ad473f4ae Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 21 Feb 2019 16:25:53 +0100 Subject: [PATCH 1/7] reset: add acquired/released state for exclusive reset controls There are cases where a driver needs explicit control over a reset line that is exclusively conneted to its device, but this control has to be temporarily handed over to the power domain controller to handle reset requirements during power transitions. Allow multiple exclusive reset controls to be requested in 'released' state for the same physical reset line, only one of which can be acquired at the same time. Signed-off-by: Philipp Zabel Signed-off-by: Thierry Reding --- drivers/reset/core.c | 139 ++++++++++++++++++++++++++++++++++++++---- include/linux/reset.h | 93 ++++++++++++++++++++++------ 2 files changed, 200 insertions(+), 32 deletions(-) diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 9582efb70025..1e8a42b16f23 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -34,6 +34,7 @@ static LIST_HEAD(reset_lookup_list); * @id: ID of the reset controller in the reset * controller device * @refcnt: Number of gets of this reset_control + * @acquired: Only one reset_control may be acquired for a given rcdev and id. * @shared: Is this a shared (1), or an exclusive (0) reset_control? * @deassert_cnt: Number of times this reset line has been deasserted * @triggered_count: Number of times this reset line has been reset. Currently @@ -45,6 +46,7 @@ struct reset_control { struct list_head list; unsigned int id; struct kref refcnt; + bool acquired; bool shared; bool array; atomic_t deassert_count; @@ -63,6 +65,17 @@ struct reset_control_array { struct reset_control *rstc[]; }; +static const char *rcdev_name(struct reset_controller_dev *rcdev) +{ + if (rcdev->dev) + return dev_name(rcdev->dev); + + if (rcdev->of_node) + return rcdev->of_node->full_name; + + return NULL; +} + /** * of_reset_simple_xlate - translate reset_spec to the reset line number * @rcdev: a pointer to the reset controller device @@ -272,6 +285,9 @@ int reset_control_reset(struct reset_control *rstc) if (atomic_inc_return(&rstc->triggered_count) != 1) return 0; + } else { + if (!rstc->acquired) + return -EPERM; } ret = rstc->rcdev->ops->reset(rstc->rcdev, rstc->id); @@ -334,6 +350,12 @@ int reset_control_assert(struct reset_control *rstc) */ if (!rstc->rcdev->ops->assert) return -ENOTSUPP; + + if (!rstc->acquired) { + WARN(1, "reset %s (ID: %u) is not acquired\n", + rcdev_name(rstc->rcdev), rstc->id); + return -EPERM; + } } return rstc->rcdev->ops->assert(rstc->rcdev, rstc->id); @@ -369,6 +391,12 @@ int reset_control_deassert(struct reset_control *rstc) if (atomic_inc_return(&rstc->deassert_count) != 1) return 0; + } else { + if (!rstc->acquired) { + WARN(1, "reset %s (ID: %u) is not acquired\n", + rcdev_name(rstc->rcdev), rstc->id); + return -EPERM; + } } /* @@ -406,9 +434,81 @@ int reset_control_status(struct reset_control *rstc) } EXPORT_SYMBOL_GPL(reset_control_status); +/** + * reset_control_acquire() - acquires a reset control for exclusive use + * @rstc: reset control + * + * This is used to explicitly acquire a reset control for exclusive use. Note + * that exclusive resets are requested as acquired by default. In order for a + * second consumer to be able to control the reset, the first consumer has to + * release it first. Typically the easiest way to achieve this is to call the + * reset_control_get_exclusive_released() to obtain an instance of the reset + * control. Such reset controls are not acquired by default. + * + * Consumers implementing shared access to an exclusive reset need to follow + * a specific protocol in order to work together. Before consumers can change + * a reset they must acquire exclusive access using reset_control_acquire(). + * After they are done operating the reset, they must release exclusive access + * with a call to reset_control_release(). Consumers are not granted exclusive + * access to the reset as long as another consumer hasn't released a reset. + * + * See also: reset_control_release() + */ +int reset_control_acquire(struct reset_control *rstc) +{ + struct reset_control *rc; + + if (!rstc) + return 0; + + if (WARN_ON(IS_ERR(rstc))) + return -EINVAL; + + mutex_lock(&reset_list_mutex); + + if (rstc->acquired) { + mutex_unlock(&reset_list_mutex); + return 0; + } + + list_for_each_entry(rc, &rstc->rcdev->reset_control_head, list) { + if (rstc != rc && rstc->id == rc->id) { + if (rc->acquired) { + mutex_unlock(&reset_list_mutex); + return -EBUSY; + } + } + } + + rstc->acquired = true; + + mutex_unlock(&reset_list_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(reset_control_acquire); + +/** + * reset_control_release() - releases exclusive access to a reset control + * @rstc: reset control + * + * Releases exclusive access right to a reset control previously obtained by a + * call to reset_control_acquire(). Until a consumer calls this function, no + * other consumers will be granted exclusive access. + * + * See also: reset_control_acquire() + */ +void reset_control_release(struct reset_control *rstc) +{ + if (!rstc || WARN_ON(IS_ERR(rstc))) + return; + + rstc->acquired = false; +} +EXPORT_SYMBOL_GPL(reset_control_release); + static struct reset_control *__reset_control_get_internal( struct reset_controller_dev *rcdev, - unsigned int index, bool shared) + unsigned int index, bool shared, bool acquired) { struct reset_control *rstc; @@ -416,6 +516,14 @@ static struct reset_control *__reset_control_get_internal( list_for_each_entry(rstc, &rcdev->reset_control_head, list) { if (rstc->id == index) { + /* + * Allow creating a secondary exclusive reset_control + * that is initially not acquired for an already + * controlled reset line. + */ + if (!rstc->shared && !shared && !acquired) + break; + if (WARN_ON(!rstc->shared || !shared)) return ERR_PTR(-EBUSY); @@ -434,6 +542,7 @@ static struct reset_control *__reset_control_get_internal( list_add(&rstc->list, &rcdev->reset_control_head); rstc->id = index; kref_init(&rstc->refcnt); + rstc->acquired = acquired; rstc->shared = shared; return rstc; @@ -461,7 +570,7 @@ static void __reset_control_put_internal(struct reset_control *rstc) struct reset_control *__of_reset_control_get(struct device_node *node, const char *id, int index, bool shared, - bool optional) + bool optional, bool acquired) { struct reset_control *rstc; struct reset_controller_dev *r, *rcdev; @@ -514,7 +623,7 @@ struct reset_control *__of_reset_control_get(struct device_node *node, } /* reset_list_mutex also protects the rcdev's reset_control list */ - rstc = __reset_control_get_internal(rcdev, rstc_id, shared); + rstc = __reset_control_get_internal(rcdev, rstc_id, shared, acquired); out: mutex_unlock(&reset_list_mutex); @@ -544,7 +653,7 @@ __reset_controller_by_name(const char *name) static struct reset_control * __reset_control_get_from_lookup(struct device *dev, const char *con_id, - bool shared, bool optional) + bool shared, bool optional, bool acquired) { const struct reset_control_lookup *lookup; struct reset_controller_dev *rcdev; @@ -574,7 +683,7 @@ __reset_control_get_from_lookup(struct device *dev, const char *con_id, rstc = __reset_control_get_internal(rcdev, lookup->index, - shared); + shared, acquired); mutex_unlock(&reset_list_mutex); break; } @@ -589,13 +698,18 @@ __reset_control_get_from_lookup(struct device *dev, const char *con_id, } struct reset_control *__reset_control_get(struct device *dev, const char *id, - int index, bool shared, bool optional) + int index, bool shared, bool optional, + bool acquired) { + if (WARN_ON(shared && acquired)) + return ERR_PTR(-EINVAL); + if (dev->of_node) return __of_reset_control_get(dev->of_node, id, index, shared, - optional); + optional, acquired); - return __reset_control_get_from_lookup(dev, id, shared, optional); + return __reset_control_get_from_lookup(dev, id, shared, optional, + acquired); } EXPORT_SYMBOL_GPL(__reset_control_get); @@ -636,7 +750,7 @@ static void devm_reset_control_release(struct device *dev, void *res) struct reset_control *__devm_reset_control_get(struct device *dev, const char *id, int index, bool shared, - bool optional) + bool optional, bool acquired) { struct reset_control **ptr, *rstc; @@ -645,7 +759,7 @@ struct reset_control *__devm_reset_control_get(struct device *dev, if (!ptr) return ERR_PTR(-ENOMEM); - rstc = __reset_control_get(dev, id, index, shared, optional); + rstc = __reset_control_get(dev, id, index, shared, optional, acquired); if (!IS_ERR(rstc)) { *ptr = rstc; devres_add(dev, ptr); @@ -672,7 +786,7 @@ int __device_reset(struct device *dev, bool optional) struct reset_control *rstc; int ret; - rstc = __reset_control_get(dev, NULL, 0, 0, optional); + rstc = __reset_control_get(dev, NULL, 0, 0, optional, true); if (IS_ERR(rstc)) return PTR_ERR(rstc); @@ -736,7 +850,8 @@ of_reset_control_array_get(struct device_node *np, bool shared, bool optional) return ERR_PTR(-ENOMEM); for (i = 0; i < num; i++) { - rstc = __of_reset_control_get(np, NULL, i, shared, optional); + rstc = __of_reset_control_get(np, NULL, i, shared, optional, + true); if (IS_ERR(rstc)) goto err_rst; resets->rstc[i] = rstc; diff --git a/include/linux/reset.h b/include/linux/reset.h index c1901b61ca30..ea9a8a1ce4b1 100644 --- a/include/linux/reset.h +++ b/include/linux/reset.h @@ -14,18 +14,20 @@ int reset_control_reset(struct reset_control *rstc); int reset_control_assert(struct reset_control *rstc); int reset_control_deassert(struct reset_control *rstc); int reset_control_status(struct reset_control *rstc); +int reset_control_acquire(struct reset_control *rstc); +void reset_control_release(struct reset_control *rstc); struct reset_control *__of_reset_control_get(struct device_node *node, const char *id, int index, bool shared, - bool optional); + bool optional, bool acquired); struct reset_control *__reset_control_get(struct device *dev, const char *id, int index, bool shared, - bool optional); + bool optional, bool acquired); void reset_control_put(struct reset_control *rstc); int __device_reset(struct device *dev, bool optional); struct reset_control *__devm_reset_control_get(struct device *dev, const char *id, int index, bool shared, - bool optional); + bool optional, bool acquired); struct reset_control *devm_reset_control_array_get(struct device *dev, bool shared, bool optional); @@ -56,6 +58,15 @@ static inline int reset_control_status(struct reset_control *rstc) return 0; } +static inline int reset_control_acquire(struct reset_control *rstc) +{ + return 0; +} + +static inline void reset_control_release(struct reset_control *rstc) +{ +} + static inline void reset_control_put(struct reset_control *rstc) { } @@ -68,21 +79,23 @@ static inline int __device_reset(struct device *dev, bool optional) static inline struct reset_control *__of_reset_control_get( struct device_node *node, const char *id, int index, bool shared, - bool optional) + bool optional, bool acquired) { return optional ? NULL : ERR_PTR(-ENOTSUPP); } static inline struct reset_control *__reset_control_get( struct device *dev, const char *id, - int index, bool shared, bool optional) + int index, bool shared, bool optional, + bool acquired) { return optional ? NULL : ERR_PTR(-ENOTSUPP); } static inline struct reset_control *__devm_reset_control_get( struct device *dev, const char *id, - int index, bool shared, bool optional) + int index, bool shared, bool optional, + bool acquired) { return optional ? NULL : ERR_PTR(-ENOTSUPP); } @@ -134,7 +147,28 @@ static inline int device_reset_optional(struct device *dev) static inline struct reset_control * __must_check reset_control_get_exclusive(struct device *dev, const char *id) { - return __reset_control_get(dev, id, 0, false, false); + return __reset_control_get(dev, id, 0, false, false, true); +} + +/** + * reset_control_get_exclusive_released - Lookup and obtain a temoprarily + * exclusive reference to a reset + * controller. + * @dev: device to be reset by the controller + * @id: reset line name + * + * Returns a struct reset_control or IS_ERR() condition containing errno. + * reset-controls returned by this function must be acquired via + * reset_control_acquire() before they can be used and should be released + * via reset_control_release() afterwards. + * + * Use of id names is optional. + */ +static inline struct reset_control * +__must_check reset_control_get_exclusive_released(struct device *dev, + const char *id) +{ + return __reset_control_get(dev, id, 0, false, false, false); } /** @@ -162,19 +196,19 @@ __must_check reset_control_get_exclusive(struct device *dev, const char *id) static inline struct reset_control *reset_control_get_shared( struct device *dev, const char *id) { - return __reset_control_get(dev, id, 0, true, false); + return __reset_control_get(dev, id, 0, true, false, false); } static inline struct reset_control *reset_control_get_optional_exclusive( struct device *dev, const char *id) { - return __reset_control_get(dev, id, 0, false, true); + return __reset_control_get(dev, id, 0, false, true, true); } static inline struct reset_control *reset_control_get_optional_shared( struct device *dev, const char *id) { - return __reset_control_get(dev, id, 0, true, true); + return __reset_control_get(dev, id, 0, true, true, false); } /** @@ -190,7 +224,7 @@ static inline struct reset_control *reset_control_get_optional_shared( static inline struct reset_control *of_reset_control_get_exclusive( struct device_node *node, const char *id) { - return __of_reset_control_get(node, id, 0, false, false); + return __of_reset_control_get(node, id, 0, false, false, true); } /** @@ -215,7 +249,7 @@ static inline struct reset_control *of_reset_control_get_exclusive( static inline struct reset_control *of_reset_control_get_shared( struct device_node *node, const char *id) { - return __of_reset_control_get(node, id, 0, true, false); + return __of_reset_control_get(node, id, 0, true, false, false); } /** @@ -232,7 +266,7 @@ static inline struct reset_control *of_reset_control_get_shared( static inline struct reset_control *of_reset_control_get_exclusive_by_index( struct device_node *node, int index) { - return __of_reset_control_get(node, NULL, index, false, false); + return __of_reset_control_get(node, NULL, index, false, false, true); } /** @@ -260,7 +294,7 @@ static inline struct reset_control *of_reset_control_get_exclusive_by_index( static inline struct reset_control *of_reset_control_get_shared_by_index( struct device_node *node, int index) { - return __of_reset_control_get(node, NULL, index, true, false); + return __of_reset_control_get(node, NULL, index, true, false, false); } /** @@ -279,7 +313,26 @@ static inline struct reset_control * __must_check devm_reset_control_get_exclusive(struct device *dev, const char *id) { - return __devm_reset_control_get(dev, id, 0, false, false); + return __devm_reset_control_get(dev, id, 0, false, false, true); +} + +/** + * devm_reset_control_get_exclusive_released - resource managed + * reset_control_get_exclusive_released() + * @dev: device to be reset by the controller + * @id: reset line name + * + * Managed reset_control_get_exclusive_released(). For reset controllers + * returned from this function, reset_control_put() is called automatically on + * driver detach. + * + * See reset_control_get_exclusive_released() for more information. + */ +static inline struct reset_control * +__must_check devm_reset_control_get_exclusive_released(struct device *dev, + const char *id) +{ + return __devm_reset_control_get(dev, id, 0, false, false, false); } /** @@ -294,19 +347,19 @@ __must_check devm_reset_control_get_exclusive(struct device *dev, static inline struct reset_control *devm_reset_control_get_shared( struct device *dev, const char *id) { - return __devm_reset_control_get(dev, id, 0, true, false); + return __devm_reset_control_get(dev, id, 0, true, false, false); } static inline struct reset_control *devm_reset_control_get_optional_exclusive( struct device *dev, const char *id) { - return __devm_reset_control_get(dev, id, 0, false, true); + return __devm_reset_control_get(dev, id, 0, false, true, true); } static inline struct reset_control *devm_reset_control_get_optional_shared( struct device *dev, const char *id) { - return __devm_reset_control_get(dev, id, 0, true, true); + return __devm_reset_control_get(dev, id, 0, true, true, false); } /** @@ -324,7 +377,7 @@ static inline struct reset_control *devm_reset_control_get_optional_shared( static inline struct reset_control * devm_reset_control_get_exclusive_by_index(struct device *dev, int index) { - return __devm_reset_control_get(dev, NULL, index, false, false); + return __devm_reset_control_get(dev, NULL, index, false, false, true); } /** @@ -340,7 +393,7 @@ devm_reset_control_get_exclusive_by_index(struct device *dev, int index) static inline struct reset_control * devm_reset_control_get_shared_by_index(struct device *dev, int index) { - return __devm_reset_control_get(dev, NULL, index, true, false); + return __devm_reset_control_get(dev, NULL, index, true, false, false); } /* From f31d5c24fb2ea6fcfa4d300886eb87b662fbc0da Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 21 Feb 2019 16:25:54 +0100 Subject: [PATCH 2/7] reset: Add acquired flag to of_reset_control_array_get() In order to be able to request an array of reset controls in acquired or released mode, add the acquired flag to of_reset_control_array_get() and pass the flag to subsequent calls of __of_reset_control_get(). Signed-off-by: Thierry Reding Acked-by: Felipe Balbi Signed-off-by: Philipp Zabel --- drivers/reset/core.c | 9 ++++++--- drivers/usb/dwc3/dwc3-of-simple.c | 3 ++- include/linux/reset.h | 14 ++++++++------ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 1e8a42b16f23..f94da91c22af 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -830,12 +830,15 @@ static int of_reset_control_get_count(struct device_node *node) * @np: device node for the device that requests the reset controls array * @shared: whether reset controls are shared or not * @optional: whether it is optional to get the reset controls + * @acquired: only one reset control may be acquired for a given controller + * and ID * * Returns pointer to allocated reset_control_array on success or * error on failure */ struct reset_control * -of_reset_control_array_get(struct device_node *np, bool shared, bool optional) +of_reset_control_array_get(struct device_node *np, bool shared, bool optional, + bool acquired) { struct reset_control_array *resets; struct reset_control *rstc; @@ -851,7 +854,7 @@ of_reset_control_array_get(struct device_node *np, bool shared, bool optional) for (i = 0; i < num; i++) { rstc = __of_reset_control_get(np, NULL, i, shared, optional, - true); + acquired); if (IS_ERR(rstc)) goto err_rst; resets->rstc[i] = rstc; @@ -898,7 +901,7 @@ devm_reset_control_array_get(struct device *dev, bool shared, bool optional) if (!devres) return ERR_PTR(-ENOMEM); - rstc = of_reset_control_array_get(dev->of_node, shared, optional); + rstc = of_reset_control_array_get(dev->of_node, shared, optional, true); if (IS_ERR(rstc)) { devres_free(devres); return rstc; diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index 4c2771c5e727..67ce2037472d 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -107,7 +107,8 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) simple->pulse_resets = true; } - simple->resets = of_reset_control_array_get(np, shared_resets, true); + simple->resets = of_reset_control_array_get(np, shared_resets, true, + true); if (IS_ERR(simple->resets)) { ret = PTR_ERR(simple->resets); dev_err(dev, "failed to get device resets, err=%d\n", ret); diff --git a/include/linux/reset.h b/include/linux/reset.h index ea9a8a1ce4b1..a01b32bf51d4 100644 --- a/include/linux/reset.h +++ b/include/linux/reset.h @@ -32,7 +32,8 @@ struct reset_control *__devm_reset_control_get(struct device *dev, struct reset_control *devm_reset_control_array_get(struct device *dev, bool shared, bool optional); struct reset_control *of_reset_control_array_get(struct device_node *np, - bool shared, bool optional); + bool shared, bool optional, + bool acquired); int reset_control_get_count(struct device *dev); @@ -107,7 +108,8 @@ devm_reset_control_array_get(struct device *dev, bool shared, bool optional) } static inline struct reset_control * -of_reset_control_array_get(struct device_node *np, bool shared, bool optional) +of_reset_control_array_get(struct device_node *np, bool shared, bool optional, + bool acquired) { return optional ? NULL : ERR_PTR(-ENOTSUPP); } @@ -465,24 +467,24 @@ devm_reset_control_array_get_optional_shared(struct device *dev) static inline struct reset_control * of_reset_control_array_get_exclusive(struct device_node *node) { - return of_reset_control_array_get(node, false, false); + return of_reset_control_array_get(node, false, false, true); } static inline struct reset_control * of_reset_control_array_get_shared(struct device_node *node) { - return of_reset_control_array_get(node, true, false); + return of_reset_control_array_get(node, true, false, true); } static inline struct reset_control * of_reset_control_array_get_optional_exclusive(struct device_node *node) { - return of_reset_control_array_get(node, false, true); + return of_reset_control_array_get(node, false, true, true); } static inline struct reset_control * of_reset_control_array_get_optional_shared(struct device_node *node) { - return of_reset_control_array_get(node, true, true); + return of_reset_control_array_get(node, true, true, true); } #endif From 22815f1825e4c50314e7084ca375f7368704fdd4 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 21 Feb 2019 16:25:55 +0100 Subject: [PATCH 3/7] reset: Add acquire/release support for arrays Add implementations that apply acquire and release operations to all reset controls part of a reset control array. Signed-off-by: Thierry Reding Reviewed-by: Philipp Zabel Signed-off-by: Philipp Zabel --- drivers/reset/core.c | 36 +++++++++++++++++++++++++++++++++++- include/linux/reset.h | 6 ++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/drivers/reset/core.c b/drivers/reset/core.c index f94da91c22af..81ea77cba123 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -245,6 +245,34 @@ static int reset_control_array_deassert(struct reset_control_array *resets) return ret; } +static int reset_control_array_acquire(struct reset_control_array *resets) +{ + unsigned int i; + int err; + + for (i = 0; i < resets->num_rstcs; i++) { + err = reset_control_acquire(resets->rstc[i]); + if (err < 0) + goto release; + } + + return 0; + +release: + while (i--) + reset_control_release(resets->rstc[i]); + + return err; +} + +static void reset_control_array_release(struct reset_control_array *resets) +{ + unsigned int i; + + for (i = 0; i < resets->num_rstcs; i++) + reset_control_release(resets->rstc[i]); +} + static inline bool reset_control_is_array(struct reset_control *rstc) { return rstc->array; @@ -464,6 +492,9 @@ int reset_control_acquire(struct reset_control *rstc) if (WARN_ON(IS_ERR(rstc))) return -EINVAL; + if (reset_control_is_array(rstc)) + return reset_control_array_acquire(rstc_to_array(rstc)); + mutex_lock(&reset_list_mutex); if (rstc->acquired) { @@ -502,7 +533,10 @@ void reset_control_release(struct reset_control *rstc) if (!rstc || WARN_ON(IS_ERR(rstc))) return; - rstc->acquired = false; + if (reset_control_is_array(rstc)) + reset_control_array_release(rstc_to_array(rstc)); + else + rstc->acquired = false; } EXPORT_SYMBOL_GPL(reset_control_release); diff --git a/include/linux/reset.h b/include/linux/reset.h index a01b32bf51d4..95d555c2130a 100644 --- a/include/linux/reset.h +++ b/include/linux/reset.h @@ -470,6 +470,12 @@ of_reset_control_array_get_exclusive(struct device_node *node) return of_reset_control_array_get(node, false, false, true); } +static inline struct reset_control * +of_reset_control_array_get_exclusive_released(struct device_node *node) +{ + return of_reset_control_array_get(node, false, false, false); +} + static inline struct reset_control * of_reset_control_array_get_shared(struct device_node *node) { From 7fe5719b4364fe9b673c3763007915877f3922c0 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 18 Feb 2019 17:36:43 +0100 Subject: [PATCH 4/7] soc/tegra: pmc: Implement acquire/release for resets By implementing the acquire/release protocol, the resets can be shared with other drivers that also adhere to this protocol. This will be used for example by the SOR driver to put hardware into a known good state, irrespective of whether or not the power domain can be reset. Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 43 +++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 0df258518693..0c5f79528e5f 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -656,10 +656,15 @@ static int tegra_genpd_power_on(struct generic_pm_domain *domain) int err; err = tegra_powergate_power_up(pg, true); - if (err) + if (err) { dev_err(dev, "failed to turn on PM domain %s: %d\n", pg->genpd.name, err); + goto out; + } + reset_control_release(pg->reset); + +out: return err; } @@ -669,10 +674,18 @@ static int tegra_genpd_power_off(struct generic_pm_domain *domain) struct device *dev = pg->pmc->dev; int err; + err = reset_control_acquire(pg->reset); + if (err < 0) { + pr_err("failed to acquire resets: %d\n", err); + return err; + } + err = tegra_powergate_power_down(pg); - if (err) + if (err) { dev_err(dev, "failed to turn off PM domain %s: %d\n", pg->genpd.name, err); + reset_control_release(pg->reset); + } return err; } @@ -937,20 +950,34 @@ static int tegra_powergate_of_get_resets(struct tegra_powergate *pg, struct device *dev = pg->pmc->dev; int err; - pg->reset = of_reset_control_array_get_exclusive(np); + pg->reset = of_reset_control_array_get_exclusive_released(np); if (IS_ERR(pg->reset)) { err = PTR_ERR(pg->reset); dev_err(dev, "failed to get device resets: %d\n", err); return err; } - if (off) - err = reset_control_assert(pg->reset); - else - err = reset_control_deassert(pg->reset); + err = reset_control_acquire(pg->reset); + if (err < 0) { + pr_err("failed to acquire resets: %d\n", err); + goto out; + } - if (err) + if (off) { + err = reset_control_assert(pg->reset); + } else { + err = reset_control_deassert(pg->reset); + if (err < 0) + goto out; + + reset_control_release(pg->reset); + } + +out: + if (err) { + reset_control_release(pg->reset); reset_control_put(pg->reset); + } return err; } From 00cdaa1b811f36afa52a5956350c263ded4944a6 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Tue, 16 Apr 2019 17:48:06 +0100 Subject: [PATCH 5/7] soc/tegra: pmc: Fix reset sources and levels Commit 5f84bb1a4099 ("soc/tegra: pmc: Add sysfs entries for reset info") added support for reading the Tegra reset source and level from sysfs. However, there are a few issues with this commit which are ... 1. The number of reset sources for Tegra210 is defined as 5 but it should be 6. 2. The number of reset sources for Tegra186 is defined as 13 but it should be 15. 3. The SoC data variables num_reset_sources and num_reset_levels are defined but never used. Fix the above by ... 1. Removing the reset source 'AOTAG' from the tegra30_reset_sources because this is only applicable for Tegra210. 2. Adding a new tegra210_reset_sources structure for Tegra210 reset sources. 3. Correct the number of reset sources for Tegra210 and Tegra186 by using the ARRAY_SIZE macro. 4. Updating the functions reset_reason_show() and reset_level_show() to check whether the value read is valid. While we are at it clean-up these functions to remove an unnecessary u32 variable. Fixes: 5f84bb1a4099 ("soc/tegra: pmc: Add sysfs entries for reset info") Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 44 +++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 0c5f79528e5f..7953aa7b11df 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -268,6 +268,14 @@ static const char * const tegra186_reset_levels[] = { }; static const char * const tegra30_reset_sources[] = { + "POWER_ON_RESET", + "WATCHDOG", + "SENSOR", + "SW_MAIN", + "LP0" +}; + +static const char * const tegra210_reset_sources[] = { "POWER_ON_RESET", "WATCHDOG", "SENSOR", @@ -1736,13 +1744,16 @@ static int tegra_pmc_pinctrl_init(struct tegra_pmc *pmc) static ssize_t reset_reason_show(struct device *dev, struct device_attribute *attr, char *buf) { - u32 value, rst_src; + u32 value; value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status); - rst_src = (value & pmc->soc->regs->rst_source_mask) >> - pmc->soc->regs->rst_source_shift; + value &= pmc->soc->regs->rst_source_mask; + value >>= pmc->soc->regs->rst_source_shift; - return sprintf(buf, "%s\n", pmc->soc->reset_sources[rst_src]); + if (WARN_ON(value >= pmc->soc->num_reset_sources)) + return sprintf(buf, "%s\n", "UNKNOWN"); + + return sprintf(buf, "%s\n", pmc->soc->reset_sources[value]); } static DEVICE_ATTR_RO(reset_reason); @@ -1750,13 +1761,16 @@ static DEVICE_ATTR_RO(reset_reason); static ssize_t reset_level_show(struct device *dev, struct device_attribute *attr, char *buf) { - u32 value, rst_lvl; + u32 value; value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status); - rst_lvl = (value & pmc->soc->regs->rst_level_mask) >> - pmc->soc->regs->rst_level_shift; + value &= pmc->soc->regs->rst_level_mask; + value >>= pmc->soc->regs->rst_level_shift; - return sprintf(buf, "%s\n", pmc->soc->reset_levels[rst_lvl]); + if (WARN_ON(value >= pmc->soc->num_reset_levels)) + return sprintf(buf, "%s\n", "UNKNOWN"); + + return sprintf(buf, "%s\n", pmc->soc->reset_levels[value]); } static DEVICE_ATTR_RO(reset_level); @@ -2212,7 +2226,7 @@ static const struct tegra_pmc_soc tegra30_pmc_soc = { .init = tegra20_pmc_init, .setup_irq_polarity = tegra20_pmc_setup_irq_polarity, .reset_sources = tegra30_reset_sources, - .num_reset_sources = 5, + .num_reset_sources = ARRAY_SIZE(tegra30_reset_sources), .reset_levels = NULL, .num_reset_levels = 0, }; @@ -2263,7 +2277,7 @@ static const struct tegra_pmc_soc tegra114_pmc_soc = { .init = tegra20_pmc_init, .setup_irq_polarity = tegra20_pmc_setup_irq_polarity, .reset_sources = tegra30_reset_sources, - .num_reset_sources = 5, + .num_reset_sources = ARRAY_SIZE(tegra30_reset_sources), .reset_levels = NULL, .num_reset_levels = 0, }; @@ -2374,7 +2388,7 @@ static const struct tegra_pmc_soc tegra124_pmc_soc = { .init = tegra20_pmc_init, .setup_irq_polarity = tegra20_pmc_setup_irq_polarity, .reset_sources = tegra30_reset_sources, - .num_reset_sources = 5, + .num_reset_sources = ARRAY_SIZE(tegra30_reset_sources), .reset_levels = NULL, .num_reset_levels = 0, }; @@ -2479,8 +2493,8 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = { .regs = &tegra20_pmc_regs, .init = tegra20_pmc_init, .setup_irq_polarity = tegra20_pmc_setup_irq_polarity, - .reset_sources = tegra30_reset_sources, - .num_reset_sources = 5, + .reset_sources = tegra210_reset_sources, + .num_reset_sources = ARRAY_SIZE(tegra210_reset_sources), .reset_levels = NULL, .num_reset_levels = 0, }; @@ -2605,9 +2619,9 @@ static const struct tegra_pmc_soc tegra186_pmc_soc = { .init = NULL, .setup_irq_polarity = tegra186_pmc_setup_irq_polarity, .reset_sources = tegra186_reset_sources, - .num_reset_sources = 14, + .num_reset_sources = ARRAY_SIZE(tegra186_reset_sources), .reset_levels = tegra186_reset_levels, - .num_reset_levels = 3, + .num_reset_levels = ARRAY_SIZE(tegra186_reset_levels), .num_wake_events = ARRAY_SIZE(tegra186_wake_events), .wake_events = tegra186_wake_events, }; From a46b51cd2a57d52d5047e1d48240536243eeab34 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Tue, 16 Apr 2019 17:48:07 +0100 Subject: [PATCH 6/7] soc/tegra: pmc: Remove reset sysfs entries on error Commit 5f84bb1a4099 ("soc/tegra: pmc: Add sysfs entries for reset info") added sysfs entries for Tegra reset source and level. However, these sysfs are not removed on error and so if the registering of PMC device is probe deferred, then the next time we attempt to probe the PMC device warnings such as the following will be displayed on boot ... sysfs: cannot create duplicate filename '/devices/platform/7000e400.pmc/reset_reason' Fix this by calling device_remove_file() for each sysfs entry added on failure. Note that we call device_remove_file() unconditionally without checking if the sysfs entry was created in the first place, but this should be OK because kernfs_remove_by_name_ns() will fail silently. Fixes: 5f84bb1a4099 ("soc/tegra: pmc: Add sysfs entries for reset info") Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 7953aa7b11df..741afb141887 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -2040,7 +2040,7 @@ static int tegra_pmc_probe(struct platform_device *pdev) if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_powergate_debugfs_init(); if (err < 0) - return err; + goto cleanup_sysfs; } err = register_restart_handler(&tegra_pmc_restart_handler); @@ -2071,6 +2071,9 @@ static int tegra_pmc_probe(struct platform_device *pdev) unregister_restart_handler(&tegra_pmc_restart_handler); cleanup_debugfs: debugfs_remove(pmc->debugfs); +cleanup_sysfs: + device_remove_file(&pdev->dev, &dev_attr_reset_reason); + device_remove_file(&pdev->dev, &dev_attr_reset_level); return err; } From 6ac2a01de1700c1b6d889f02f61c4c9602573a8d Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Tue, 16 Apr 2019 17:48:08 +0100 Subject: [PATCH 7/7] soc/tegra: pmc: Move powergate initialisation to probe Commit 8df127456f29 ("soc/tegra: pmc: Enable XUSB partitions on boot") was added as a workaround to ensure that the XUSB powergates or domains were turned on early during boot because as this time the Tegra XHCI driver did not handle the power domains at all. Now that the Tegra XHCI driver has been updated to properly managed the power domains, the workaround to enable the XUSB power domain early has been removed. This also means that we can now move the initialisation of the powergates into the PMC driver probe. Therefore, move the powergate initialisation into the PMC driver probe and return any errors detected. To handle any errors, functions to cleanup and remove any power-domains registered with the generic power-domain framework have been added. Finally the initialisation of the 'powergates_available' bitmask is kept in the PMC early init function to allow the legacy PMC powergate APIs to be called during early boot for enabling secondary CPUs on 32-bit Tegra devices. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 83 +++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 741afb141887..5648e5c09ef5 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -990,20 +990,21 @@ static int tegra_powergate_of_get_resets(struct tegra_powergate *pg, return err; } -static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) +static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) { struct device *dev = pmc->dev; struct tegra_powergate *pg; - int id, err; + int id, err = 0; bool off; pg = kzalloc(sizeof(*pg), GFP_KERNEL); if (!pg) - return; + return -ENOMEM; id = tegra_powergate_lookup(pmc, np->name); if (id < 0) { dev_err(dev, "powergate lookup failed for %pOFn: %d\n", np, id); + err = -ENODEV; goto free_mem; } @@ -1056,7 +1057,7 @@ static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) dev_dbg(dev, "added PM domain %s\n", pg->genpd.name); - return; + return 0; remove_genpd: pm_genpd_remove(&pg->genpd); @@ -1075,25 +1076,67 @@ static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) free_mem: kfree(pg); + + return err; } -static void tegra_powergate_init(struct tegra_pmc *pmc, - struct device_node *parent) +static int tegra_powergate_init(struct tegra_pmc *pmc, + struct device_node *parent) { struct device_node *np, *child; - unsigned int i; + int err = 0; - /* Create a bitmap of the available and valid partitions */ - for (i = 0; i < pmc->soc->num_powergates; i++) - if (pmc->soc->powergates[i]) - set_bit(i, pmc->powergates_available); + np = of_get_child_by_name(parent, "powergates"); + if (!np) + return 0; + + for_each_child_of_node(np, child) { + err = tegra_powergate_add(pmc, child); + if (err < 0) { + of_node_put(child); + break; + } + } + + of_node_put(np); + + return err; +} + +static void tegra_powergate_remove(struct generic_pm_domain *genpd) +{ + struct tegra_powergate *pg = to_powergate(genpd); + + reset_control_put(pg->reset); + + while (pg->num_clks--) + clk_put(pg->clks[pg->num_clks]); + + kfree(pg->clks); + + set_bit(pg->id, pmc->powergates_available); + + kfree(pg); +} + +static void tegra_powergate_remove_all(struct device_node *parent) +{ + struct generic_pm_domain *genpd; + struct device_node *np, *child; np = of_get_child_by_name(parent, "powergates"); if (!np) return; - for_each_child_of_node(np, child) - tegra_powergate_add(pmc, child); + for_each_child_of_node(np, child) { + of_genpd_del_provider(child); + + genpd = of_genpd_remove_last(child); + if (IS_ERR(genpd)) + continue; + + tegra_powergate_remove(genpd); + } of_node_put(np); } @@ -2054,9 +2097,13 @@ static int tegra_pmc_probe(struct platform_device *pdev) if (err) goto cleanup_restart_handler; + err = tegra_powergate_init(pmc, pdev->dev.of_node); + if (err < 0) + goto cleanup_powergates; + err = tegra_pmc_irq_init(pmc); if (err < 0) - goto cleanup_restart_handler; + goto cleanup_powergates; mutex_lock(&pmc->powergates_lock); iounmap(pmc->base); @@ -2067,6 +2114,8 @@ static int tegra_pmc_probe(struct platform_device *pdev) return 0; +cleanup_powergates: + tegra_powergate_remove_all(pdev->dev.of_node); cleanup_restart_handler: unregister_restart_handler(&tegra_pmc_restart_handler); cleanup_debugfs: @@ -2763,6 +2812,7 @@ static int __init tegra_pmc_early_init(void) const struct of_device_id *match; struct device_node *np; struct resource regs; + unsigned int i; bool invert; mutex_init(&pmc->powergates_lock); @@ -2819,7 +2869,10 @@ static int __init tegra_pmc_early_init(void) if (pmc->soc->maybe_tz_only) pmc->tz_only = tegra_pmc_detect_tz_only(pmc); - tegra_powergate_init(pmc, np); + /* Create a bitmap of the available and valid partitions */ + for (i = 0; i < pmc->soc->num_powergates; i++) + if (pmc->soc->powergates[i]) + set_bit(i, pmc->powergates_available); /* * Invert the interrupt polarity if a PMC device tree node