From patchwork Tue Feb 23 17:46:26 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 8395151 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id B2F29C0553 for ; Tue, 23 Feb 2016 17:49:52 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 56AD4202FF for ; Tue, 23 Feb 2016 17:49:51 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 625CA202C8 for ; Tue, 23 Feb 2016 17:49:46 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1aYH4E-00078g-2U; Tue, 23 Feb 2016 17:48:10 +0000 Received: from mx1.redhat.com ([209.132.183.28]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1aYH3C-0006IA-7d for linux-arm-kernel@lists.infradead.org; Tue, 23 Feb 2016 17:47:08 +0000 Received: from int-mx11.intmail.prod.int.phx2.redhat.com (int-mx11.intmail.prod.int.phx2.redhat.com [10.5.11.24]) by mx1.redhat.com (Postfix) with ESMTPS id 38077311E; Tue, 23 Feb 2016 17:46:47 +0000 (UTC) Received: from localhost.localdomain.com (vpn1-7-183.ams2.redhat.com [10.36.7.183]) by int-mx11.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u1NHkVCs002802; Tue, 23 Feb 2016 12:46:44 -0500 From: Hans de Goede To: Philipp Zabel Subject: [PATCH v4 3/3] reset: Add support for shared reset controls Date: Tue, 23 Feb 2016 18:46:26 +0100 Message-Id: <1456249586-12101-4-git-send-email-hdegoede@redhat.com> In-Reply-To: <1456249586-12101-1-git-send-email-hdegoede@redhat.com> References: <1456249586-12101-1-git-send-email-hdegoede@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.24 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20160223_094706_404586_01839771 X-CRM114-Status: GOOD ( 21.72 ) X-Spam-Score: -6.9 (------) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Stephen Warren , Josh Triplett , Hans de Goede , Rashika Kheria , Maxime Ripard , linux-arm-kernel@lists.infradead.org MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP In some SoCs some hw-blocks share a reset control. Add support for this setup by adding new: reset_control_get_shared() devm_reset_control_get_shared() devm_reset_control_get_shared_by_index() methods to get a reset_control. Note that this patch omits adding of_ variants, if these are needed later they can be easily added. This patch also changes the behavior of the existing exclusive reset_control_get() variants, if these are now called more then once for the same reset_control they will return -EBUSY. To catch existing drivers triggering this error (there should not be any) a WARN_ON(1) is added in this path. When a reset_control is shared, the behavior of reset_control_assert / deassert is changed, for shared reset_controls these will work like the clock-enable/disable and regulator-on/off functions. They will keep a deassert_count, and only (re-)assert the reset after reset_control_assert has been called as many times as reset_control_deassert was called. Calling reset_control_assert without first calling reset_control_deassert is not allowed on a shared reset control. Calling reset_control_reset is also not allowed on a shared reset control. Signed-off-by: Hans de Goede --- drivers/reset/core.c | 59 +++++++++++++++++++++++++------ include/linux/reset.h | 96 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 129 insertions(+), 26 deletions(-) diff --git a/drivers/reset/core.c b/drivers/reset/core.c index e439ae2..400e42f 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -8,6 +8,7 @@ * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ +#include #include #include #include @@ -29,12 +30,16 @@ static LIST_HEAD(reset_controller_list); * @id: ID of the reset controller in the reset * controller device * @refcnt: Number of gets of this reset_control + * @shared: Is this a shared (1), or an exclusive (0) reset_control? + * @deassert_cnt: Number of times this reset line has been deasserted */ struct reset_control { struct reset_controller_dev *rcdev; struct list_head list; unsigned int id; unsigned int refcnt; + int shared; + atomic_t deassert_count; }; /** @@ -94,9 +99,14 @@ EXPORT_SYMBOL_GPL(reset_controller_unregister); /** * reset_control_reset - reset the controlled device * @rstc: reset controller + * + * Calling this on a shared reset controller is an error. */ int reset_control_reset(struct reset_control *rstc) { + if (WARN_ON(rstc->shared)) + return -EINVAL; + if (rstc->rcdev->ops->reset) return rstc->rcdev->ops->reset(rstc->rcdev, rstc->id); @@ -107,26 +117,48 @@ EXPORT_SYMBOL_GPL(reset_control_reset); /** * reset_control_assert - asserts the reset line * @rstc: reset controller + * + * Calling this on an exclusive reset controller guarantees that the reset + * will be asserted. When called on a shared reset controller the line may + * still be deasserted, as long as other users keep it so. + * + * For shared reset controls a driver cannot expect the hw's registers and + * internal state to be reset, but must be prepared for this to happen. */ int reset_control_assert(struct reset_control *rstc) { - if (rstc->rcdev->ops->assert) - return rstc->rcdev->ops->assert(rstc->rcdev, rstc->id); + if (!rstc->rcdev->ops->assert) + return -ENOTSUPP; - return -ENOTSUPP; + if (rstc->shared) { + if (WARN_ON(atomic_read(&rstc->deassert_count) == 0)) + return -EINVAL; + + if (atomic_dec_return(&rstc->deassert_count) != 0) + return 0; + } + + return rstc->rcdev->ops->assert(rstc->rcdev, rstc->id); } EXPORT_SYMBOL_GPL(reset_control_assert); /** * reset_control_deassert - deasserts the reset line * @rstc: reset controller + * + * After calling this function, the reset is guaranteed to be deasserted. */ int reset_control_deassert(struct reset_control *rstc) { - if (rstc->rcdev->ops->deassert) - return rstc->rcdev->ops->deassert(rstc->rcdev, rstc->id); + if (!rstc->rcdev->ops->deassert) + return -ENOTSUPP; - return -ENOTSUPP; + if (rstc->shared) { + if (atomic_inc_return(&rstc->deassert_count) != 1) + return 0; + } + + return rstc->rcdev->ops->deassert(rstc->rcdev, rstc->id); } EXPORT_SYMBOL_GPL(reset_control_deassert); @@ -147,7 +179,7 @@ EXPORT_SYMBOL_GPL(reset_control_status); static struct reset_control *__reset_control_get( struct reset_controller_dev *rcdev, - unsigned int index) + unsigned int index, int shared) { struct reset_control *rstc; @@ -155,6 +187,9 @@ static struct reset_control *__reset_control_get( list_for_each_entry(rstc, &rcdev->reset_control_head, list) { if (rstc->id == index) { + if (WARN_ON(!rstc->shared || !shared)) + return ERR_PTR(-EBUSY); + rstc->refcnt++; return rstc; } @@ -170,6 +205,7 @@ static struct reset_control *__reset_control_get( list_add(&rstc->list, &rcdev->reset_control_head); rstc->id = index; rstc->refcnt = 1; + rstc->shared = shared; return rstc; } @@ -188,7 +224,7 @@ static void __reset_control_put(struct reset_control *rstc) } struct reset_control *__of_reset_control_get(struct device_node *node, - const char *id, int index) + const char *id, int index, int shared) { struct reset_control *rstc = ERR_PTR(-EPROBE_DEFER); struct reset_controller_dev *r, *rcdev; @@ -233,7 +269,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(rcdev, rstc_id); + rstc = __reset_control_get(rcdev, rstc_id, shared); mutex_unlock(&reset_list_mutex); @@ -263,7 +299,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) + const char *id, int index, int shared) { struct reset_control **ptr, *rstc; @@ -272,7 +308,8 @@ struct reset_control *__devm_reset_control_get(struct device *dev, if (!ptr) return ERR_PTR(-ENOMEM); - rstc = __of_reset_control_get(dev ? dev->of_node : NULL, id, index); + rstc = __of_reset_control_get(dev ? dev->of_node : NULL, + id, index, shared); if (!IS_ERR(rstc)) { *ptr = rstc; devres_add(dev, ptr); diff --git a/include/linux/reset.h b/include/linux/reset.h index 1bb69a2..a552134 100644 --- a/include/linux/reset.h +++ b/include/linux/reset.h @@ -13,10 +13,10 @@ int reset_control_deassert(struct reset_control *rstc); int reset_control_status(struct reset_control *rstc); struct reset_control *__of_reset_control_get(struct device_node *node, - const char *id, int index); + const char *id, int index, int shared); void reset_control_put(struct reset_control *rstc); struct reset_control *__devm_reset_control_get(struct device *dev, - const char *id, int index); + const char *id, int index, int shared); int __must_check device_reset(struct device *dev); @@ -63,14 +63,14 @@ static inline int device_reset_optional(struct device *dev) static inline struct reset_control *__of_reset_control_get( struct device_node *node, - const char *id, int index) + const char *id, int index, int shared) { return ERR_PTR(-EINVAL); } static inline struct reset_control *__devm_reset_control_get( struct device *dev, - const char *id, int index) + const char *id, int index, int shared) { return ERR_PTR(-EINVAL); } @@ -78,11 +78,17 @@ static inline struct reset_control *__devm_reset_control_get( #endif /* CONFIG_RESET_CONTROLLER */ /** - * reset_control_get - Lookup and obtain a reference to a reset controller. + * reset_control_get - Lookup and obtain an 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. + * If this function is called more then once for the same reset_control it will + * return -EBUSY. + * + * See reset_control_get_shared for details on shared references to + * reset-controls. * * Use of id names is optional. */ @@ -92,17 +98,46 @@ static inline struct reset_control *__must_check reset_control_get( #ifndef CONFIG_RESET_CONTROLLER WARN_ON(1); #endif - return __of_reset_control_get(dev ? dev->of_node : NULL, id, 0); + return __of_reset_control_get(dev ? dev->of_node : NULL, id, 0, 0); } static inline struct reset_control *reset_control_get_optional( struct device *dev, const char *id) { - return __of_reset_control_get(dev ? dev->of_node : NULL, id, 0); + return __of_reset_control_get(dev ? dev->of_node : NULL, id, 0, 0); } /** - * of_reset_control_get - Lookup and obtain a reference to a reset controller. + * reset_control_get_shared - Lookup and obtain a shared 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. + * This function is intended for use with reset-controls which are shared + * between hardware-blocks. + * + * When a reset-control is shared, the behavior of reset_control_assert / + * deassert is changed, the reset-core will keep track of a deassert_count + * and only (re-)assert the reset after reset_control_assert has been called + * as many times as reset_control_deassert was called. Also see the remark + * about shared reset-controls in the reset_control_assert docs. + * + * Calling reset_control_assert without first calling reset_control_deassert + * is not allowed on a shared reset control. Calling reset_control_reset is + * also not allowed on a shared reset control. + * + * Use of id names is optional. + */ +static inline struct reset_control *reset_control_get_shared( + struct device *dev, const char *id) +{ + return __of_reset_control_get(dev ? dev->of_node : NULL, id, 0, 1); +} + +/** + * of_reset_control_get - Lookup and obtain an exclusive reference to a + * reset controller. * @node: device to be reset by the controller * @id: reset line name * @@ -113,12 +148,12 @@ static inline struct reset_control *reset_control_get_optional( static inline struct reset_control *of_reset_control_get( struct device_node *node, const char *id) { - return __of_reset_control_get(node, id, 0); + return __of_reset_control_get(node, id, 0, 0); } /** - * of_reset_control_get_by_index - Lookup and obtain a reference to a reset - * controller by index. + * of_reset_control_get_by_index - Lookup and obtain an exclusive reference to + * a reset controller by index. * @node: device to be reset by the controller * @index: index of the reset controller * @@ -129,7 +164,7 @@ static inline struct reset_control *of_reset_control_get( static inline struct reset_control *of_reset_control_get_by_index( struct device_node *node, int index) { - return __of_reset_control_get(node, NULL, index); + return __of_reset_control_get(node, NULL, index, 0); } /** @@ -147,13 +182,13 @@ static inline struct reset_control *__must_check devm_reset_control_get( #ifndef CONFIG_RESET_CONTROLLER WARN_ON(1); #endif - return __devm_reset_control_get(dev, id, 0); + return __devm_reset_control_get(dev, id, 0, 0); } static inline struct reset_control *devm_reset_control_get_optional( struct device *dev, const char *id) { - return __devm_reset_control_get(dev, id, 0); + return __devm_reset_control_get(dev, id, 0, 0); } /** @@ -168,7 +203,38 @@ static inline struct reset_control *devm_reset_control_get_optional( static inline struct reset_control *devm_reset_control_get_by_index( struct device *dev, int index) { - return __devm_reset_control_get(dev, NULL, index); + return __devm_reset_control_get(dev, NULL, index, 0); +} + +/** + * devm_reset_control_get_shared - resource managed reset_control_get_shared() + * @dev: device to be reset by the controller + * @id: reset line name + * + * Managed reset_control_get_shared(). For reset controllers returned from + * this function, reset_control_put() is called automatically on driver detach. + * See reset_control_get_shared() for more information. + */ +static inline struct reset_control *devm_reset_control_get_shared( + struct device *dev, const char *id) +{ + return __devm_reset_control_get(dev, id, 0, 1); +} + +/** + * devm_reset_control_get_shared_by_index - resource managed + * reset_control_get_shared + * @dev: device to be reset by the controller + * @index: index of the reset controller + * + * Managed reset_control_get_shared(). For reset controllers returned from + * this function, reset_control_put() is called automatically on driver detach. + * See reset_control_get_shared() for more information. + */ +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, 1); } #endif