diff options
Diffstat (limited to 'drivers/reset')
-rw-r--r-- | drivers/reset/core.c | 180 |
1 files changed, 166 insertions, 14 deletions
diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 9582efb70025..81ea77cba123 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 @@ -232,6 +245,34 @@ err: 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; @@ -272,6 +313,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 +378,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 +419,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 +462,87 @@ 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; + + if (reset_control_is_array(rstc)) + return reset_control_array_acquire(rstc_to_array(rstc)); + + 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; + + if (reset_control_is_array(rstc)) + reset_control_array_release(rstc_to_array(rstc)); + else + 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 +550,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 +576,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 +604,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 +657,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 +687,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 +717,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 +732,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 +784,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 +793,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 +820,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); @@ -716,12 +864,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; @@ -736,7 +887,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, + acquired); if (IS_ERR(rstc)) goto err_rst; resets->rstc[i] = rstc; @@ -783,7 +935,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; |