From patchwork Thu Jul 2 17:56:42 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Guinot X-Patchwork-Id: 6711831 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 03D59C05AC for ; Thu, 2 Jul 2015 18:04:44 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id E63E420722 for ; Thu, 2 Jul 2015 18:04:42 +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 DE86F20703 for ; Thu, 2 Jul 2015 18:04:41 +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 1ZAimP-000336-Gy; Thu, 02 Jul 2015 18:00:09 +0000 Received: from vm1.sequanux.org ([188.165.36.56]) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1ZAijZ-0002dK-9x for linux-arm-kernel@lists.infradead.org; Thu, 02 Jul 2015 17:57:14 +0000 Received: from localhost (localhost.localdomain [127.0.0.1]) by vm1.sequanux.org (Postfix) with ESMTP id C9006108AAB; Thu, 2 Jul 2015 19:56:54 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at vm1.sequanux.org Received: from vm1.sequanux.org ([127.0.0.1]) by localhost (vm1.sequanux.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id vTrjKb-zol5Z; Thu, 2 Jul 2015 19:56:53 +0200 (CEST) Received: from localhost (unknown [37.160.43.166]) by vm1.sequanux.org (Postfix) with ESMTPSA id 16649108A45; Thu, 2 Jul 2015 19:56:53 +0200 (CEST) From: Simon Guinot To: Jacek Anaszewski , Bryan Wu , Richard Purdie Subject: [PATCH v2 3/4] leds: leds-ns2: handle can_sleep GPIOs Date: Thu, 2 Jul 2015 19:56:42 +0200 Message-Id: <1435859803-19583-4-git-send-email-simon.guinot@sequanux.org> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1435859803-19583-1-git-send-email-simon.guinot@sequanux.org> References: <1435859803-19583-1-git-send-email-simon.guinot@sequanux.org> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150702_105713_659560_ED6F1D72 X-CRM114-Status: GOOD ( 16.37 ) X-Spam-Score: -2.5 (--) 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: Andrew Lunn , Jason Cooper , devicetree@vger.kernel.org, Vincent Donnefort , linux-leds@vger.kernel.org, Gregory Clement , linux-arm-kernel@lists.infradead.org, Sebastian Hesselbarth 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.8 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 On the board n090401 (Seagate NAS 4-Bay), some of the LEDs are handled by the leds-ns2 driver. This LEDs are connected to an I2C GPIO expander (PCA95554PW) which means that GPIO access may sleep. This patch makes leds-ns2 compatible with such GPIOs by using the *_cansleep() variant of the GPIO functions. As a drawback this functions can't be used safely in a timer context (with the timer LED trigger for example). To fix this issue, a workqueue mechanism (copied from the leds-gpio driver) is used. Note that this patch also updates slightly the ns2_led_sata_store function. The LED state is now retrieved from cached values instead of reading the GPIOs previously. This prevents ns2_led_sata_store from working with a stale LED state (which may happen when a delayed work is pending). Signed-off-by: Simon Guinot Signed-off-by: Vincent Donnefort --- drivers/leds/leds-ns2.c | 75 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 24 deletions(-) Changes for v2: - Fix gpio_{get,set}_*_cansleep() usage while holding a spinlock: Holding a spinlock at this places was not needed. diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c index b0bc03539dbb..b33514d9f427 100644 --- a/drivers/leds/leds-ns2.c +++ b/drivers/leds/leds-ns2.c @@ -31,6 +31,7 @@ #include #include #include +#include "leds.h" /* * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED @@ -43,12 +44,25 @@ struct ns2_led_data { struct led_classdev cdev; unsigned cmd; unsigned slow; + bool can_sleep; + int mode_index; unsigned char sata; /* True when SATA mode active. */ rwlock_t rw_lock; /* Lock GPIOs. */ + struct work_struct work; int num_modes; struct ns2_led_modval *modval; }; +static void ns2_led_work(struct work_struct *work) +{ + struct ns2_led_data *led_dat = + container_of(work, struct ns2_led_data, work); + int i = led_dat->mode_index; + + gpio_set_value_cansleep(led_dat->cmd, led_dat->modval[i].cmd_level); + gpio_set_value_cansleep(led_dat->slow, led_dat->modval[i].slow_level); +} + static int ns2_led_get_mode(struct ns2_led_data *led_dat, enum ns2_led_modes *mode) { @@ -57,10 +71,8 @@ static int ns2_led_get_mode(struct ns2_led_data *led_dat, int cmd_level; int slow_level; - read_lock_irq(&led_dat->rw_lock); - - cmd_level = gpio_get_value(led_dat->cmd); - slow_level = gpio_get_value(led_dat->slow); + cmd_level = gpio_get_value_cansleep(led_dat->cmd); + slow_level = gpio_get_value_cansleep(led_dat->slow); for (i = 0; i < led_dat->num_modes; i++) { if (cmd_level == led_dat->modval[i].cmd_level && @@ -71,8 +83,6 @@ static int ns2_led_get_mode(struct ns2_led_data *led_dat, } } - read_unlock_irq(&led_dat->rw_lock); - return ret; } @@ -80,19 +90,32 @@ static void ns2_led_set_mode(struct ns2_led_data *led_dat, enum ns2_led_modes mode) { int i; + bool found = false; unsigned long flags; - write_lock_irqsave(&led_dat->rw_lock, flags); - - for (i = 0; i < led_dat->num_modes; i++) { + for (i = 0; i < led_dat->num_modes; i++) if (mode == led_dat->modval[i].mode) { - gpio_set_value(led_dat->cmd, - led_dat->modval[i].cmd_level); - gpio_set_value(led_dat->slow, - led_dat->modval[i].slow_level); + found = true; + break; } + + if (!found) + return; + + write_lock_irqsave(&led_dat->rw_lock, flags); + + if (!led_dat->can_sleep) { + gpio_set_value(led_dat->cmd, + led_dat->modval[i].cmd_level); + gpio_set_value(led_dat->slow, + led_dat->modval[i].slow_level); + goto exit_unlock; } + led_dat->mode_index = i; + schedule_work(&led_dat->work); + +exit_unlock: write_unlock_irqrestore(&led_dat->rw_lock, flags); } @@ -122,7 +145,6 @@ static ssize_t ns2_led_sata_store(struct device *dev, container_of(led_cdev, struct ns2_led_data, cdev); int ret; unsigned long enable; - enum ns2_led_modes mode; ret = kstrtoul(buff, 10, &enable); if (ret < 0) @@ -131,19 +153,19 @@ static ssize_t ns2_led_sata_store(struct device *dev, enable = !!enable; if (led_dat->sata == enable) - return count; + goto exit; - ret = ns2_led_get_mode(led_dat, &mode); - if (ret < 0) - return ret; + led_dat->sata = enable; + + if (!led_get_brightness(led_cdev)) + goto exit; - if (enable && mode == NS_V2_LED_ON) + if (enable) ns2_led_set_mode(led_dat, NS_V2_LED_SATA); - if (!enable && mode == NS_V2_LED_SATA) + else ns2_led_set_mode(led_dat, NS_V2_LED_ON); - led_dat->sata = enable; - +exit: return count; } @@ -173,7 +195,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, enum ns2_led_modes mode; ret = devm_gpio_request_one(&pdev->dev, template->cmd, - gpio_get_value(template->cmd) ? + gpio_get_value_cansleep(template->cmd) ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, template->name); if (ret) { @@ -183,7 +205,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, } ret = devm_gpio_request_one(&pdev->dev, template->slow, - gpio_get_value(template->slow) ? + gpio_get_value_cansleep(template->slow) ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, template->name); if (ret) { @@ -202,6 +224,8 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, led_dat->cdev.groups = ns2_led_groups; led_dat->cmd = template->cmd; led_dat->slow = template->slow; + led_dat->can_sleep = gpio_cansleep(led_dat->cmd) | + gpio_cansleep(led_dat->slow); led_dat->modval = template->modval; led_dat->num_modes = template->num_modes; @@ -214,6 +238,8 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, led_dat->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; + INIT_WORK(&led_dat->work, ns2_led_work); + ret = led_classdev_register(&pdev->dev, &led_dat->cdev); if (ret < 0) return ret; @@ -224,6 +250,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, static void delete_ns2_led(struct ns2_led_data *led_dat) { led_classdev_unregister(&led_dat->cdev); + cancel_work_sync(&led_dat->work); } #ifdef CONFIG_OF_GPIO