From patchwork Thu Oct 12 10:40:10 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lukas Wunner X-Patchwork-Id: 824774 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yCS7L2f34z9sRq for ; Thu, 12 Oct 2017 21:41:02 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755604AbdJLKkf (ORCPT ); Thu, 12 Oct 2017 06:40:35 -0400 Received: from mailout1.hostsharing.net ([83.223.95.204]:57057 "EHLO mailout1.hostsharing.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751140AbdJLKkd (ORCPT ); Thu, 12 Oct 2017 06:40:33 -0400 Received: from mailout3.hostsharing.net (mailout3.hostsharing.net [176.9.242.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "*.hostsharing.net", Issuer "COMODO RSA Organization Validation Secure Server CA" (not verified)) by mailout1.hostsharing.net (Postfix) with ESMTPS id 6F8AE10130102; Thu, 12 Oct 2017 12:40:32 +0200 (CEST) Received: from h08.hostsharing.net (h08.hostsharing.net [83.223.95.28]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "*.hostsharing.net", Issuer "COMODO RSA Organization Validation Secure Server CA" (not verified)) by mailout3.hostsharing.net (Postfix) with ESMTPS id 8A944101E6C56; Thu, 12 Oct 2017 12:40:40 +0200 (CEST) Received: from localhost (p4FC97DB7.dip0.t-ipconnect.de [79.201.125.183]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by h08.hostsharing.net (Postfix) with ESMTPSA id B3ED6603DF34; Thu, 12 Oct 2017 12:40:31 +0200 (CEST) X-Mailbox-Line: From 2ca70faf07383482eed6263c00cf9141f930653b Mon Sep 17 00:00:00 2001 Message-Id: <2ca70faf07383482eed6263c00cf9141f930653b.1507797496.git.lukas@wunner.de> In-Reply-To: References: From: Lukas Wunner Date: Thu, 12 Oct 2017 12:40:10 +0200 Subject: [PATCH v2 1/5] bitops: Introduce assign_bit() To: Linus Walleij Cc: Mathias Duckeck , Phil Elwell , linux-gpio@vger.kernel.org, Bart Van Assche , Alasdair Kergon , Mike Snitzer , Andrew Morton , Neil Brown , Peter Zijlstra , Ingo Molnar , Theodore Ts'o , Borislav Petkov , "H. Peter Anvin" , Denys Vlasenko , linux-kernel@vger.kernel.org Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org A common idiom is to assign a value to a bit with: if (value) set_bit(nr, addr); else clear_bit(nr, addr); Likewise common is the one-line expression variant: value ? set_bit(nr, addr) : clear_bit(nr, addr); Commit 9a8ac3ae682e ("dm mpath: cleanup QUEUE_IF_NO_PATH bit manipulation by introducing assign_bit()") introduced assign_bit() to the md subsystem for brevity. Make it available to others, specifically gpiolib and the upcoming driver for Maxim MAX3191x industrial serializer chips. As requested by Peter Zijlstra, change the argument order to reflect traditional "dst = src" in C, hence "assign_bit(nr, addr, value)". Cc: Bart Van Assche Cc: Alasdair Kergon Cc: Mike Snitzer Cc: Linus Walleij Cc: Andrew Morton Cc: Neil Brown Cc: Peter Zijlstra Cc: Ingo Molnar Cc: Theodore Ts'o Cc: Borislav Petkov Cc: "H. Peter Anvin" Cc: Denys Vlasenko Signed-off-by: Lukas Wunner Acked-by: Andrew Morton --- Changes v1 -> v2: - Change the argument order of assign_bit() to reflect traditional "dst = src" in C. (Peter Zijlstra) drivers/md/dm-mpath.c | 22 +++++++--------------- include/linux/bitops.h | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/drivers/md/dm-mpath.c b/drivers/md/dm-mpath.c index 11f273d2f018..0e211de9bc54 100644 --- a/drivers/md/dm-mpath.c +++ b/drivers/md/dm-mpath.c @@ -641,14 +641,6 @@ static void process_queued_bios(struct work_struct *work) blk_finish_plug(&plug); } -static void assign_bit(bool value, long nr, unsigned long *addr) -{ - if (value) - set_bit(nr, addr); - else - clear_bit(nr, addr); -} - /* * If we run out of usable paths, should we queue I/O or error it? */ @@ -658,11 +650,11 @@ static int queue_if_no_path(struct multipath *m, bool queue_if_no_path, unsigned long flags; spin_lock_irqsave(&m->lock, flags); - assign_bit((save_old_value && test_bit(MPATHF_QUEUE_IF_NO_PATH, &m->flags)) || - (!save_old_value && queue_if_no_path), - MPATHF_SAVED_QUEUE_IF_NO_PATH, &m->flags); - assign_bit(queue_if_no_path || dm_noflush_suspending(m->ti), - MPATHF_QUEUE_IF_NO_PATH, &m->flags); + assign_bit(MPATHF_SAVED_QUEUE_IF_NO_PATH, &m->flags, + (save_old_value && test_bit(MPATHF_QUEUE_IF_NO_PATH, &m->flags)) || + (!save_old_value && queue_if_no_path)); + assign_bit(MPATHF_QUEUE_IF_NO_PATH, &m->flags, + queue_if_no_path || dm_noflush_suspending(m->ti)); spin_unlock_irqrestore(&m->lock, flags); if (!queue_if_no_path) { @@ -1588,8 +1580,8 @@ static void multipath_resume(struct dm_target *ti) unsigned long flags; spin_lock_irqsave(&m->lock, flags); - assign_bit(test_bit(MPATHF_SAVED_QUEUE_IF_NO_PATH, &m->flags), - MPATHF_QUEUE_IF_NO_PATH, &m->flags); + assign_bit(MPATHF_QUEUE_IF_NO_PATH, &m->flags, + test_bit(MPATHF_SAVED_QUEUE_IF_NO_PATH, &m->flags)); spin_unlock_irqrestore(&m->lock, flags); } diff --git a/include/linux/bitops.h b/include/linux/bitops.h index 8fbe259b197c..9a874deee6e2 100644 --- a/include/linux/bitops.h +++ b/include/linux/bitops.h @@ -227,6 +227,30 @@ static inline unsigned long __ffs64(u64 word) return __ffs((unsigned long)word); } +/** + * assign_bit - Assign value to a bit in memory + * @nr: the bit to set + * @addr: the address to start counting from + * @value: the value to assign + */ +static __always_inline void assign_bit(long nr, volatile unsigned long *addr, + bool value) +{ + if (value) + set_bit(nr, addr); + else + clear_bit(nr, addr); +} + +static __always_inline void __assign_bit(long nr, volatile unsigned long *addr, + bool value) +{ + if (value) + __set_bit(nr, addr); + else + __clear_bit(nr, addr); +} + #ifdef __KERNEL__ #ifndef set_mask_bits From patchwork Thu Oct 12 10:40:10 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lukas Wunner X-Patchwork-Id: 824773 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yCS7G2ry0z9sRq for ; Thu, 12 Oct 2017 21:40:58 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755850AbdJLKk4 (ORCPT ); Thu, 12 Oct 2017 06:40:56 -0400 Received: from mailout2.hostsharing.net ([83.223.90.233]:58285 "EHLO mailout2.hostsharing.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755835AbdJLKkz (ORCPT ); Thu, 12 Oct 2017 06:40:55 -0400 Received: from h08.hostsharing.net (h08.hostsharing.net [83.223.95.28]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "*.hostsharing.net", Issuer "COMODO RSA Organization Validation Secure Server CA" (not verified)) by mailout2.hostsharing.net (Postfix) with ESMTPS id CA06D103C6120; Thu, 12 Oct 2017 12:40:52 +0200 (CEST) Received: from localhost (p4FC97DB7.dip0.t-ipconnect.de [79.201.125.183]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by h08.hostsharing.net (Postfix) with ESMTPSA id 5ADA1603DF34; Thu, 12 Oct 2017 12:40:52 +0200 (CEST) X-Mailbox-Line: From 487b9e42a3c3f5f50f4e9e90cbce65cdbeef0a79 Mon Sep 17 00:00:00 2001 Message-Id: <487b9e42a3c3f5f50f4e9e90cbce65cdbeef0a79.1507797496.git.lukas@wunner.de> In-Reply-To: References: From: Lukas Wunner Date: Thu, 12 Oct 2017 12:40:10 +0200 Subject: [PATCH v2 2/5] gpio: Introduce ->get_multiple callback To: Linus Walleij Cc: Mathias Duckeck , Phil Elwell , linux-gpio@vger.kernel.org, Rojhalat Ibrahim Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org SPI-attached GPIO controllers typically read out all inputs in one go. If callers desire the values of multipe inputs, ideally a single readout should take place to return the desired values. However the current driver API only offers a ->get callback but no ->get_multiple (unlike ->set_multiple, which is present). Thus, to read multiple inputs, a full readout needs to be performed for every single value (barring driver-internal caching), which is inefficient. In fact, the lack of a ->get_multiple callback has been bemoaned repeatedly by the gpio subsystem maintainer: http://www.spinics.net/lists/linux-gpio/msg10571.html http://www.spinics.net/lists/devicetree/msg121734.html Introduce the missing callback. Add corresponding consumer functions such as gpiod_get_array_value(). Amend linehandle_ioctl() to take advantage of the newly added infrastructure. Update the documentation. Cc: Rojhalat Ibrahim Signed-off-by: Lukas Wunner --- Changes v1 -> v2: - Update documentation. (Linus Walleij) Drop const qualifier from struct gpio_desc ** in all function signatures to avoid a cast when passing a non-const array created with gpiod_get_array(), which is likely the most common use case. Documentation/gpio/consumer.txt | 41 ++++++--- drivers/gpio/gpiolib.c | 179 +++++++++++++++++++++++++++++++++++++--- drivers/gpio/gpiolib.h | 4 + include/linux/gpio/consumer.h | 43 ++++++++++ include/linux/gpio/driver.h | 5 ++ 5 files changed, 250 insertions(+), 22 deletions(-) diff --git a/Documentation/gpio/consumer.txt b/Documentation/gpio/consumer.txt index ddbfa775a78a..63e1bd1d88e3 100644 --- a/Documentation/gpio/consumer.txt +++ b/Documentation/gpio/consumer.txt @@ -295,9 +295,22 @@ as possible, especially by drivers which should not care about the actual physical line level and worry about the logical value instead. -Set multiple GPIO outputs with a single function call ------------------------------------------------------ -The following functions set the output values of an array of GPIOs: +Access multiple GPIOs with a single function call +------------------------------------------------- +The following functions get or set the values of an array of GPIOs: + + int gpiod_get_array_value(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array); + int gpiod_get_raw_array_value(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array); + int gpiod_get_array_value_cansleep(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array); + int gpiod_get_raw_array_value_cansleep(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array); void gpiod_set_array_value(unsigned int array_size, struct gpio_desc **desc_array, @@ -312,34 +325,40 @@ The following functions set the output values of an array of GPIOs: struct gpio_desc **desc_array, int *value_array) -The array can be an arbitrary set of GPIOs. The functions will try to set +The array can be an arbitrary set of GPIOs. The functions will try to access GPIOs belonging to the same bank or chip simultaneously if supported by the corresponding chip driver. In that case a significantly improved performance -can be expected. If simultaneous setting is not possible the GPIOs will be set -sequentially. +can be expected. If simultaneous access is not possible the GPIOs will be +accessed sequentially. -The gpiod_set_array() functions take three arguments: +The functions take three arguments: * array_size - the number of array elements * desc_array - an array of GPIO descriptors - * value_array - an array of values to assign to the GPIOs + * value_array - an array to store the GPIOs' values (get) or + an array of values to assign to the GPIOs (set) The descriptor array can be obtained using the gpiod_get_array() function or one of its variants. If the group of descriptors returned by that function -matches the desired group of GPIOs, those GPIOs can be set by simply using +matches the desired group of GPIOs, those GPIOs can be accessed by simply using the struct gpio_descs returned by gpiod_get_array(): struct gpio_descs *my_gpio_descs = gpiod_get_array(...); gpiod_set_array_value(my_gpio_descs->ndescs, my_gpio_descs->desc, my_gpio_values); -It is also possible to set a completely arbitrary array of descriptors. The +It is also possible to access a completely arbitrary array of descriptors. The descriptors may be obtained using any combination of gpiod_get() and gpiod_get_array(). Afterwards the array of descriptors has to be setup -manually before it can be used with gpiod_set_array(). +manually before it can be passed to one of the above functions. Note that for optimal performance GPIOs belonging to the same chip should be contiguous within the array of descriptors. +The return value of gpiod_get_array_value() and its variants is 0 on success +or negative on error. Note the difference to gpiod_get_value(), which returns +0 or 1 on success to convey the GPIO value. With the array functions, the GPIO +values are stored in value_array rather than passed back as return value. + GPIOs mapped to IRQs -------------------- diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 8113929eb2bd..ff46a1b6299e 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -365,28 +365,28 @@ static long linehandle_ioctl(struct file *filep, unsigned int cmd, struct linehandle_state *lh = filep->private_data; void __user *ip = (void __user *)arg; struct gpiohandle_data ghd; + int vals[GPIOHANDLES_MAX]; int i; if (cmd == GPIOHANDLE_GET_LINE_VALUES_IOCTL) { - int val; + /* TODO: check if descriptors are really input */ + int ret = gpiod_get_array_value_complex(false, + true, + lh->numdescs, + lh->descs, + vals); + if (ret) + return ret; memset(&ghd, 0, sizeof(ghd)); - - /* TODO: check if descriptors are really input */ - for (i = 0; i < lh->numdescs; i++) { - val = gpiod_get_value_cansleep(lh->descs[i]); - if (val < 0) - return val; - ghd.values[i] = val; - } + for (i = 0; i < lh->numdescs; i++) + ghd.values[i] = vals[i]; if (copy_to_user(ip, &ghd, sizeof(ghd))) return -EFAULT; return 0; } else if (cmd == GPIOHANDLE_SET_LINE_VALUES_IOCTL) { - int vals[GPIOHANDLES_MAX]; - /* TODO: check if descriptors are really output */ if (copy_from_user(&ghd, ip, sizeof(ghd))) return -EFAULT; @@ -2466,6 +2466,71 @@ static int gpiod_get_raw_value_commit(const struct gpio_desc *desc) return value; } +static int gpio_chip_get_multiple(struct gpio_chip *chip, + unsigned long *mask, unsigned long *bits) +{ + if (chip->get_multiple) { + return chip->get_multiple(chip, mask, bits); + } else if (chip->get) { + int i, value; + + for_each_set_bit(i, mask, chip->ngpio) { + value = chip->get(chip, i); + if (value < 0) + return value; + __assign_bit(i, bits, value); + } + return 0; + } + return -EIO; +} + +int gpiod_get_array_value_complex(bool raw, bool can_sleep, + unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array) +{ + int i = 0; + + while (i < array_size) { + struct gpio_chip *chip = desc_array[i]->gdev->chip; + unsigned long mask[BITS_TO_LONGS(chip->ngpio)]; + unsigned long bits[BITS_TO_LONGS(chip->ngpio)]; + int first, j, ret; + + if (!can_sleep) + WARN_ON(chip->can_sleep); + + /* collect all inputs belonging to the same chip */ + first = i; + memset(mask, 0, sizeof(mask)); + do { + const struct gpio_desc *desc = desc_array[i]; + int hwgpio = gpio_chip_hwgpio(desc); + + __set_bit(hwgpio, mask); + i++; + } while ((i < array_size) && + (desc_array[i]->gdev->chip == chip)); + + ret = gpio_chip_get_multiple(chip, mask, bits); + if (ret) + return ret; + + for (j = first; j < i; j++) { + const struct gpio_desc *desc = desc_array[j]; + int hwgpio = gpio_chip_hwgpio(desc); + int value = test_bit(hwgpio, bits); + + if (!raw && test_bit(FLAG_ACTIVE_LOW, &desc->flags)) + value = !value; + value_array[j] = value; + trace_gpio_value(desc_to_gpio(desc), 1, value); + } + } + return 0; +} + /** * gpiod_get_raw_value() - return a gpio's raw value * @desc: gpio whose value will be returned @@ -2514,6 +2579,51 @@ int gpiod_get_value(const struct gpio_desc *desc) } EXPORT_SYMBOL_GPL(gpiod_get_value); +/** + * gpiod_get_raw_array_value() - read raw values from an array of GPIOs + * @array_size: number of elements in the descriptor / value arrays + * @desc_array: array of GPIO descriptors whose values will be read + * @value_array: array to store the read values + * + * Read the raw values of the GPIOs, i.e. the values of the physical lines + * without regard for their ACTIVE_LOW status. Return 0 in case of success, + * else an error code. + * + * This function should be called from contexts where we cannot sleep, + * and it will complain if the GPIO chip functions potentially sleep. + */ +int gpiod_get_raw_array_value(unsigned int array_size, + struct gpio_desc **desc_array, int *value_array) +{ + if (!desc_array) + return -EINVAL; + return gpiod_get_array_value_complex(true, false, array_size, + desc_array, value_array); +} +EXPORT_SYMBOL_GPL(gpiod_get_raw_array_value); + +/** + * gpiod_get_array_value() - read values from an array of GPIOs + * @array_size: number of elements in the descriptor / value arrays + * @desc_array: array of GPIO descriptors whose values will be read + * @value_array: array to store the read values + * + * Read the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status + * into account. Return 0 in case of success, else an error code. + * + * This function should be called from contexts where we cannot sleep, + * and it will complain if the GPIO chip functions potentially sleep. + */ +int gpiod_get_array_value(unsigned int array_size, + struct gpio_desc **desc_array, int *value_array) +{ + if (!desc_array) + return -EINVAL; + return gpiod_get_array_value_complex(false, false, array_size, + desc_array, value_array); +} +EXPORT_SYMBOL_GPL(gpiod_get_array_value); + /* * gpio_set_open_drain_value_commit() - Set the open drain gpio's value. * @desc: gpio descriptor whose state need to be set. @@ -2943,6 +3053,53 @@ int gpiod_get_value_cansleep(const struct gpio_desc *desc) EXPORT_SYMBOL_GPL(gpiod_get_value_cansleep); /** + * gpiod_get_raw_array_value_cansleep() - read raw values from an array of GPIOs + * @array_size: number of elements in the descriptor / value arrays + * @desc_array: array of GPIO descriptors whose values will be read + * @value_array: array to store the read values + * + * Read the raw values of the GPIOs, i.e. the values of the physical lines + * without regard for their ACTIVE_LOW status. Return 0 in case of success, + * else an error code. + * + * This function is to be called from contexts that can sleep. + */ +int gpiod_get_raw_array_value_cansleep(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array) +{ + might_sleep_if(extra_checks); + if (!desc_array) + return -EINVAL; + return gpiod_get_array_value_complex(true, true, array_size, + desc_array, value_array); +} +EXPORT_SYMBOL_GPL(gpiod_get_raw_array_value_cansleep); + +/** + * gpiod_get_array_value_cansleep() - read values from an array of GPIOs + * @array_size: number of elements in the descriptor / value arrays + * @desc_array: array of GPIO descriptors whose values will be read + * @value_array: array to store the read values + * + * Read the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status + * into account. Return 0 in case of success, else an error code. + * + * This function is to be called from contexts that can sleep. + */ +int gpiod_get_array_value_cansleep(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array) +{ + might_sleep_if(extra_checks); + if (!desc_array) + return -EINVAL; + return gpiod_get_array_value_complex(false, true, array_size, + desc_array, value_array); +} +EXPORT_SYMBOL_GPL(gpiod_get_array_value_cansleep); + +/** * gpiod_set_raw_value_cansleep() - assign a gpio's raw value * @desc: gpio whose value will be assigned * @value: value to assign diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index d003ccb12781..10a48caf8477 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -180,6 +180,10 @@ static inline bool acpi_can_fallback_to_crs(struct acpi_device *adev, #endif struct gpio_desc *gpiochip_get_desc(struct gpio_chip *chip, u16 hwnum); +int gpiod_get_array_value_complex(bool raw, bool can_sleep, + unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array); void gpiod_set_array_value_complex(bool raw, bool can_sleep, unsigned int array_size, struct gpio_desc **desc_array, diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index 8f702fcbe485..d4920ec1f1da 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -99,10 +99,15 @@ int gpiod_direction_output_raw(struct gpio_desc *desc, int value); /* Value get/set from non-sleeping context */ int gpiod_get_value(const struct gpio_desc *desc); +int gpiod_get_array_value(unsigned int array_size, + struct gpio_desc **desc_array, int *value_array); void gpiod_set_value(struct gpio_desc *desc, int value); void gpiod_set_array_value(unsigned int array_size, struct gpio_desc **desc_array, int *value_array); int gpiod_get_raw_value(const struct gpio_desc *desc); +int gpiod_get_raw_array_value(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array); void gpiod_set_raw_value(struct gpio_desc *desc, int value); void gpiod_set_raw_array_value(unsigned int array_size, struct gpio_desc **desc_array, @@ -110,11 +115,17 @@ void gpiod_set_raw_array_value(unsigned int array_size, /* Value get/set from sleeping context */ int gpiod_get_value_cansleep(const struct gpio_desc *desc); +int gpiod_get_array_value_cansleep(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array); void gpiod_set_value_cansleep(struct gpio_desc *desc, int value); void gpiod_set_array_value_cansleep(unsigned int array_size, struct gpio_desc **desc_array, int *value_array); int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc); +int gpiod_get_raw_array_value_cansleep(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array); void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value); void gpiod_set_raw_array_value_cansleep(unsigned int array_size, struct gpio_desc **desc_array, @@ -305,6 +316,14 @@ static inline int gpiod_get_value(const struct gpio_desc *desc) WARN_ON(1); return 0; } +static inline int gpiod_get_array_value(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array) +{ + /* GPIO can never have been requested */ + WARN_ON(1); + return 0; +} static inline void gpiod_set_value(struct gpio_desc *desc, int value) { /* GPIO can never have been requested */ @@ -323,6 +342,14 @@ static inline int gpiod_get_raw_value(const struct gpio_desc *desc) WARN_ON(1); return 0; } +static inline int gpiod_get_raw_array_value(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array) +{ + /* GPIO can never have been requested */ + WARN_ON(1); + return 0; +} static inline void gpiod_set_raw_value(struct gpio_desc *desc, int value) { /* GPIO can never have been requested */ @@ -342,6 +369,14 @@ static inline int gpiod_get_value_cansleep(const struct gpio_desc *desc) WARN_ON(1); return 0; } +static inline int gpiod_get_array_value_cansleep(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array) +{ + /* GPIO can never have been requested */ + WARN_ON(1); + return 0; +} static inline void gpiod_set_value_cansleep(struct gpio_desc *desc, int value) { /* GPIO can never have been requested */ @@ -360,6 +395,14 @@ static inline int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc) WARN_ON(1); return 0; } +static inline int gpiod_get_raw_array_value_cansleep(unsigned int array_size, + struct gpio_desc **desc_array, + int *value_array) +{ + /* GPIO can never have been requested */ + WARN_ON(1); + return 0; +} static inline void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value) { diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 1de5dd185118..5fe533408b3a 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -35,6 +35,8 @@ struct module; * @direction_input: configures signal "offset" as input, or returns error * @direction_output: configures signal "offset" as output, or returns error * @get: returns value for signal "offset", 0=low, 1=high, or negative error + * @get_multiple: reads values for multiple signals defined by "mask" and + * stores them in "bits", returns 0 on success or negative error * @set: assigns output value for signal "offset" * @set_multiple: assigns output values for multiple signals defined by "mask" * @set_config: optional hook for all kinds of settings. Uses the same @@ -126,6 +128,9 @@ struct gpio_chip { unsigned offset, int value); int (*get)(struct gpio_chip *chip, unsigned offset); + int (*get_multiple)(struct gpio_chip *chip, + unsigned long *mask, + unsigned long *bits); void (*set)(struct gpio_chip *chip, unsigned offset, int value); void (*set_multiple)(struct gpio_chip *chip, From patchwork Thu Oct 12 10:40:10 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lukas Wunner X-Patchwork-Id: 824775 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yCS7d02YRz9sRq for ; Thu, 12 Oct 2017 21:41:17 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754068AbdJLKlQ (ORCPT ); Thu, 12 Oct 2017 06:41:16 -0400 Received: from mailout1.hostsharing.net ([83.223.95.204]:35927 "EHLO mailout1.hostsharing.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755907AbdJLKlP (ORCPT ); Thu, 12 Oct 2017 06:41:15 -0400 Received: from h08.hostsharing.net (h08.hostsharing.net [83.223.95.28]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "*.hostsharing.net", Issuer "COMODO RSA Organization Validation Secure Server CA" (not verified)) by mailout1.hostsharing.net (Postfix) with ESMTPS id 096261037D258; Thu, 12 Oct 2017 12:41:14 +0200 (CEST) Received: from localhost (p4FC97DB7.dip0.t-ipconnect.de [79.201.125.183]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by h08.hostsharing.net (Postfix) with ESMTPSA id A4690603DF34; Thu, 12 Oct 2017 12:41:13 +0200 (CEST) X-Mailbox-Line: From f0c3b0c5514f74717c5783360b60062dfe9b8c0f Mon Sep 17 00:00:00 2001 Message-Id: In-Reply-To: References: From: Lukas Wunner Date: Thu, 12 Oct 2017 12:40:10 +0200 Subject: [PATCH v2 3/5] dt-bindings: Document common property for daisy-chained devices To: Linus Walleij Cc: Mathias Duckeck , Phil Elwell , linux-gpio@vger.kernel.org, devicetree@vger.kernel.org, Rob Herring , Mark Rutland , Jonathan Cameron Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org Many serially-attached GPIO and IIO devices are daisy-chainable. Examples for GPIO devices are Maxim MAX3191x and TI SN65HVS88x: https://datasheets.maximintegrated.com/en/ds/MAX31913.pdf http://www.ti.com/lit/ds/symlink/sn65hvs880.pdf Examples for IIO devices are TI DAC128S085 and TI DAC161S055: http://www.ti.com/lit/ds/symlink/dac128s085.pdf http://www.ti.com/lit/ds/symlink/dac161s055.pdf We already have drivers for daisy-chainable devices in the tree but their devicetree bindings are somewhat inconsistent and ill-named: The gpio-74x164.c driver uses "registers-number" to convey the number of devices in the daisy-chain. (Sans vendor prefix, multiple vendors sell compatible versions of this chip.) The gpio-pisosr.c driver takes a different approach and calculates the number of devices in the daisy-chain by dividing the common "ngpios" property (Documentation/devicetree/bindings/gpio/gpio.txt) by 8 (which assumes that each chip has 8 inputs). Let's standardize on a common "#daisy-chained-devices" property. That name was chosen because it's the term most frequently used in datasheets. (A less frequently used synonym is "cascaded devices".) Signed-off-by: Lukas Wunner Acked-by: Jonathan Cameron Acked-by: Rob Herring --- Changes v1 -> v2: - Newly inserted patch. .../devicetree/bindings/common-properties.txt | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Documentation/devicetree/bindings/common-properties.txt b/Documentation/devicetree/bindings/common-properties.txt index 697714f8d75c..a3448bfa1c82 100644 --- a/Documentation/devicetree/bindings/common-properties.txt +++ b/Documentation/devicetree/bindings/common-properties.txt @@ -1,4 +1,8 @@ Common properties +================= + +Endianness +---------- The Devicetree Specification does not define any properties related to hardware byteswapping, but endianness issues show up frequently in porting Linux to @@ -58,3 +62,25 @@ dev: dev@40031000 { ... little-endian; }; + +Daisy-chained devices +--------------------- + +Many serially-attached GPIO and IIO devices are daisy-chainable. To the +host controller, a daisy-chain appears as a single device, but the number +of inputs and outputs it provides is the sum of inputs and outputs provided +by all of its devices. The driver needs to know how many devices the +daisy-chain comprises to determine the amount of data exchanged, how many +inputs and outputs to register and so on. + +Optional properties: + - #daisy-chained-devices: Number of devices in the daisy-chain (default is 1). + +Example: +gpio@0 { + compatible = "name"; + reg = <0>; + gpio-controller; + #gpio-cells = <2>; + #daisy-chained-devices = <3>; +}; From patchwork Thu Oct 12 10:40:10 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lukas Wunner X-Patchwork-Id: 824777 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yCS815PFwz9t2l for ; Thu, 12 Oct 2017 21:41:37 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754178AbdJLKlg (ORCPT ); Thu, 12 Oct 2017 06:41:36 -0400 Received: from mailout1.hostsharing.net ([83.223.95.204]:36711 "EHLO mailout1.hostsharing.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752840AbdJLKlf (ORCPT ); Thu, 12 Oct 2017 06:41:35 -0400 Received: from h08.hostsharing.net (h08.hostsharing.net [83.223.95.28]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "*.hostsharing.net", Issuer "COMODO RSA Organization Validation Secure Server CA" (not verified)) by mailout1.hostsharing.net (Postfix) with ESMTPS id 900501037D2CD; Thu, 12 Oct 2017 12:41:34 +0200 (CEST) Received: from localhost (p4FC97DB7.dip0.t-ipconnect.de [79.201.125.183]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by h08.hostsharing.net (Postfix) with ESMTPSA id 4BAA4603DF34; Thu, 12 Oct 2017 12:41:34 +0200 (CEST) X-Mailbox-Line: From 57660f421f2080914940806394a9365ac959f5a8 Mon Sep 17 00:00:00 2001 Message-Id: <57660f421f2080914940806394a9365ac959f5a8.1507797496.git.lukas@wunner.de> In-Reply-To: References: From: Lukas Wunner Date: Thu, 12 Oct 2017 12:40:10 +0200 Subject: [PATCH v2 4/5] dt-bindings: gpio: max3191x: Document new driver To: Linus Walleij Cc: Mathias Duckeck , Phil Elwell , linux-gpio@vger.kernel.org, devicetree@vger.kernel.org, Rob Herring , Mark Rutland Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org Add device tree bindings for Maxim MAX3191x industrial serializer. Cc: Mathias Duckeck Signed-off-by: Lukas Wunner Acked-by: Rob Herring --- Changes v1 -> v2: - Add vendor prefix to GPIO identifiers, use boolean instead of integer to select mode, rename boolean to ignore undervoltage alarms, separate compatible strings with newlines. (Rob Herring) .../devicetree/bindings/gpio/gpio-max3191x.txt | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/gpio-max3191x.txt diff --git a/Documentation/devicetree/bindings/gpio/gpio-max3191x.txt b/Documentation/devicetree/bindings/gpio/gpio-max3191x.txt new file mode 100644 index 000000000000..b3a6444b8f45 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/gpio-max3191x.txt @@ -0,0 +1,59 @@ +GPIO driver for Maxim MAX3191x industrial serializer + +Required properties: + - compatible: Must be one of: + "maxim,max31910" + "maxim,max31911" + "maxim,max31912" + "maxim,max31913" + "maxim,max31953" + "maxim,max31963" + - reg: Chip select number. + - gpio-controller: Marks the device node as a GPIO controller. + - #gpio-cells: Should be two. For consumer use see gpio.txt. + +Optional properties: + - #daisy-chained-devices: + Number of chips in the daisy-chain (default is 1). + - maxim,modesel-gpios: GPIO pins to configure modesel of each chip. + The number of GPIOs must equal "#daisy-chained-devices" + (if each chip is driven by a separate pin) or 1 + (if all chips are wired to the same pin). + - maxim,fault-gpios: GPIO pins to read fault of each chip. + The number of GPIOs must equal "#daisy-chained-devices" + or 1. + - maxim,db0-gpios: GPIO pins to configure debounce of each chip. + The number of GPIOs must equal "#daisy-chained-devices" + or 1. + - maxim,db1-gpios: GPIO pins to configure debounce of each chip. + The number of GPIOs must equal "maxim,db0-gpios". + - maxim,modesel-8bit: Boolean whether the modesel pin of the chips is + pulled high (8-bit mode). Use this if the modesel pin + is hardwired and consequently "maxim,modesel-gpios" + cannot be specified. By default if neither this nor + "maxim,modesel-gpios" is given, the driver assumes + that modesel is pulled low (16-bit mode). + - maxim,ignore-undervoltage: + Boolean whether to ignore undervoltage alarms signaled + by the "maxim,fault-gpios" or by the status byte + (in 16-bit mode). Use this if the chips are powered + through 5VOUT instead of VCC24V, in which case they + will constantly signal undervoltage. + +For other required and optional properties of SPI slave nodes please refer to +../spi/spi-bus.txt. + +Example: + gpio@0 { + compatible = "maxim,max31913"; + reg = <0>; + gpio-controller; + #gpio-cells = <2>; + + maxim,modesel-gpios = <&gpio2 23>; + maxim,fault-gpios = <&gpio2 24 GPIO_ACTIVE_LOW>; + maxim,db0-gpios = <&gpio2 25>; + maxim,db1-gpios = <&gpio2 26>; + + spi-max-frequency = <25000000>; + }; From patchwork Thu Oct 12 10:40:10 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lukas Wunner X-Patchwork-Id: 824779 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yCS8b5BHrz9sRq for ; Thu, 12 Oct 2017 21:42:07 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756299AbdJLKmF (ORCPT ); Thu, 12 Oct 2017 06:42:05 -0400 Received: from mailout1.hostsharing.net ([83.223.95.204]:47227 "EHLO mailout1.hostsharing.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754894AbdJLKl5 (ORCPT ); Thu, 12 Oct 2017 06:41:57 -0400 Received: from mailout3.hostsharing.net (mailout3.hostsharing.net [176.9.242.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "*.hostsharing.net", Issuer "COMODO RSA Organization Validation Secure Server CA" (not verified)) by mailout1.hostsharing.net (Postfix) with ESMTPS id 974541037D2CD; Thu, 12 Oct 2017 12:41:55 +0200 (CEST) Received: from h08.hostsharing.net (h08.hostsharing.net [83.223.95.28]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "*.hostsharing.net", Issuer "COMODO RSA Organization Validation Secure Server CA" (not verified)) by mailout3.hostsharing.net (Postfix) with ESMTPS id C5FC2101E6C56; Thu, 12 Oct 2017 12:42:03 +0200 (CEST) Received: from localhost (p4FC97DB7.dip0.t-ipconnect.de [79.201.125.183]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by h08.hostsharing.net (Postfix) with ESMTPSA id E5656603DF34; Thu, 12 Oct 2017 12:41:54 +0200 (CEST) X-Mailbox-Line: From 165c5061f7f309ecb7f1a3d32c8f7df5026f4a47 Mon Sep 17 00:00:00 2001 Message-Id: <165c5061f7f309ecb7f1a3d32c8f7df5026f4a47.1507797496.git.lukas@wunner.de> In-Reply-To: References: From: Lukas Wunner Date: Thu, 12 Oct 2017 12:40:10 +0200 Subject: [PATCH v2 5/5] gpio: Add driver for Maxim MAX3191x industrial serializer To: Linus Walleij Cc: Mathias Duckeck , Phil Elwell , linux-gpio@vger.kernel.org, devicetree@vger.kernel.org, Rob Herring , Mark Rutland Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org The driver was developed for and tested with the MAX31913 built into the Revolution Pi by KUNBUS, but should work with all members of the MAX3191x family: MAX31910: low power MAX31911: LED drivers MAX31912: LED drivers + 2nd voltage monitor + low power MAX31913: LED drivers + 2nd voltage monitor MAX31953: LED drivers + 2nd voltage monitor + isolation MAX31963: LED drivers + 2nd voltage monitor + isolation + buck regulator Cc: Mathias Duckeck Signed-off-by: Lukas Wunner --- Changes v1 -> v2: - Optimize algorithm in max3191x_get_multiple() to iterate over the bits in the mask, instead of iterating over every chip, thus implicitly skipping chips which are not selected at all by the mask. Support configurations where all chips in a daisy-chain share the same modesel, fault or debounce pin. Verify that the number of db0 and db1 GPIOs specified in the DT is identical. drivers/gpio/Kconfig | 10 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-max3191x.c | 492 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 503 insertions(+) create mode 100644 drivers/gpio/gpio-max3191x.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 3388d54ba114..d5282cac6ea6 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1255,6 +1255,16 @@ config GPIO_74X164 shift registers. This driver can be used to provide access to more gpio outputs. +config GPIO_MAX3191X + tristate "Maxim MAX3191x industrial serializer" + select CRC8 + help + GPIO driver for Maxim MAX31910, MAX31911, MAX31912, MAX31913, + MAX31953 and MAX31963 industrial serializer, a daisy-chainable + chip to make 8 digital 24V inputs available via SPI. Supports + CRC checksums to guard against electromagnetic interference, + as well as undervoltage and overtemperature detection. + config GPIO_MAX7301 tristate "Maxim MAX7301 GPIO expander" select GPIO_MAX730X diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index aeb70e9de6f2..cf9ecfb24ef4 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_ARCH_LPC32XX) += gpio-lpc32xx.o obj-$(CONFIG_GPIO_LP873X) += gpio-lp873x.o obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o obj-$(CONFIG_GPIO_LYNXPOINT) += gpio-lynxpoint.o +obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o obj-$(CONFIG_GPIO_MAX7301) += gpio-max7301.o diff --git a/drivers/gpio/gpio-max3191x.c b/drivers/gpio/gpio-max3191x.c new file mode 100644 index 000000000000..f74b1072e84b --- /dev/null +++ b/drivers/gpio/gpio-max3191x.c @@ -0,0 +1,492 @@ +/* + * gpio-max3191x.c - GPIO driver for Maxim MAX3191x industrial serializer + * + * Copyright (C) 2017 KUNBUS GmbH + * + * The MAX3191x makes 8 digital 24V inputs available via SPI. + * Multiple chips can be daisy-chained, the spec does not impose + * a limit on the number of chips and neither does this driver. + * + * Either of two modes is selectable: In 8-bit mode, only the state + * of the inputs is clocked out to achieve high readout speeds; + * In 16-bit mode, an additional status byte is clocked out with + * a CRC and indicator bits for undervoltage and overtemperature. + * The driver returns an error instead of potentially bogus data + * if any of these fault conditions occur. However it does allow + * readout of non-faulting chips in the same daisy-chain. + * + * MAX3191x supports four debounce settings and the driver is + * capable of configuring these differently for each chip in the + * daisy-chain. + * + * If the chips are hardwired to 8-bit mode ("modesel" pulled high), + * gpio-pisosr.c can be used alternatively to this driver. + * + * https://datasheets.maximintegrated.com/en/ds/MAX31910.pdf + * https://datasheets.maximintegrated.com/en/ds/MAX31911.pdf + * https://datasheets.maximintegrated.com/en/ds/MAX31912.pdf + * https://datasheets.maximintegrated.com/en/ds/MAX31913.pdf + * https://datasheets.maximintegrated.com/en/ds/MAX31953-MAX31963.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (version 2) as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +enum max3191x_mode { + STATUS_BYTE_ENABLED, + STATUS_BYTE_DISABLED, +}; + +/** + * struct max3191x_chip - max3191x daisy-chain + * @gpio: GPIO controller struct + * @lock: protects read sequences + * @nchips: number of chips in the daisy-chain + * @mode: current mode, 0 for 16-bit, 1 for 8-bit; + * for simplicity, all chips in the daisy-chain are assumed + * to use the same mode + * @modesel_pins: GPIO pins to configure modesel of each chip + * @fault_pins: GPIO pins to detect fault of each chip + * @db0_pins: GPIO pins to configure debounce of each chip + * @db1_pins: GPIO pins to configure debounce of each chip + * @mesg: SPI message to perform a readout + * @xfer: SPI transfer used by @mesg + * @crc_error: bitmap signaling CRC error for each chip + * @overtemp: bitmap signaling overtemperature alarm for each chip + * @undervolt1: bitmap signaling undervoltage alarm for each chip + * @undervolt2: bitmap signaling undervoltage warning for each chip + * @fault: bitmap signaling assertion of @fault_pins for each chip + * @ignore_uv: whether to ignore undervoltage alarms; + * set by a device property if the chips are powered through + * 5VOUT instead of VCC24V, in which case they will constantly + * signal undervoltage; + * for simplicity, all chips in the daisy-chain are assumed + * to be powered the same way + */ +struct max3191x_chip { + struct gpio_chip gpio; + struct mutex lock; + u32 nchips; + enum max3191x_mode mode; + struct gpio_descs *modesel_pins; + struct gpio_descs *fault_pins; + struct gpio_descs *db0_pins; + struct gpio_descs *db1_pins; + struct spi_message mesg; + struct spi_transfer xfer; + unsigned long *crc_error; + unsigned long *overtemp; + unsigned long *undervolt1; + unsigned long *undervolt2; + unsigned long *fault; + bool ignore_uv; +}; + +#define MAX3191X_NGPIO 8 +#define MAX3191X_CRC8_POLYNOMIAL 0xa8 /* (x^5) + x^4 + x^2 + x^0 */ + +DECLARE_CRC8_TABLE(max3191x_crc8); + +static int max3191x_get_direction(struct gpio_chip *gpio, unsigned int offset) +{ + return 1; /* always in */ +} + +static int max3191x_direction_input(struct gpio_chip *gpio, unsigned int offset) +{ + return 0; +} + +static int max3191x_direction_output(struct gpio_chip *gpio, + unsigned int offset, int value) +{ + return -EINVAL; +} + +static void max3191x_set(struct gpio_chip *gpio, unsigned int offset, int value) +{ } + +static void max3191x_set_multiple(struct gpio_chip *gpio, unsigned long *mask, + unsigned long *bits) +{ } + +static unsigned int max3191x_wordlen(struct max3191x_chip *max3191x) +{ + return max3191x->mode == STATUS_BYTE_ENABLED ? 2 : 1; +} + +static int max3191x_readout_locked(struct max3191x_chip *max3191x) +{ + struct device *dev = max3191x->gpio.parent; + struct spi_device *spi = to_spi_device(dev); + int val, i, ot = 0, uv1 = 0; + + val = spi_sync(spi, &max3191x->mesg); + if (val) { + dev_err_ratelimited(dev, "SPI receive error %d\n", val); + return val; + } + + for (i = 0; i < max3191x->nchips; i++) { + if (max3191x->mode == STATUS_BYTE_ENABLED) { + u8 in = ((u8 *)max3191x->xfer.rx_buf)[i * 2]; + u8 status = ((u8 *)max3191x->xfer.rx_buf)[i * 2 + 1]; + + val = (status & 0xf8) != crc8(max3191x_crc8, &in, 1, 0); + __assign_bit(i, max3191x->crc_error, val); + if (val) + dev_err_ratelimited(dev, + "chip %d: CRC error\n", i); + + ot = (status >> 1) & 1; + __assign_bit(i, max3191x->overtemp, ot); + if (ot) + dev_err_ratelimited(dev, + "chip %d: overtemperature\n", i); + + if (!max3191x->ignore_uv) { + uv1 = !((status >> 2) & 1); + __assign_bit(i, max3191x->undervolt1, uv1); + if (uv1) + dev_err_ratelimited(dev, + "chip %d: undervoltage\n", i); + + val = !(status & 1); + __assign_bit(i, max3191x->undervolt2, val); + if (val && !uv1) + dev_warn_ratelimited(dev, + "chip %d: voltage warn\n", i); + } + } + + if (max3191x->fault_pins && !max3191x->ignore_uv) { + /* fault pin shared by all chips or per chip */ + struct gpio_desc *fault_pin = + (max3191x->fault_pins->ndescs == 1) + ? max3191x->fault_pins->desc[0] + : max3191x->fault_pins->desc[i]; + + val = gpiod_get_value_cansleep(fault_pin); + if (val < 0) { + dev_err_ratelimited(dev, + "GPIO read error %d\n", val); + return val; + } + __assign_bit(i, max3191x->fault, val); + if (val && !uv1 && !ot) + dev_err_ratelimited(dev, + "chip %d: fault\n", i); + } + } + + return 0; +} + +static bool max3191x_chip_is_faulting(struct max3191x_chip *max3191x, + unsigned int chipnum) +{ + /* without status byte the only diagnostic is the fault pin */ + if (!max3191x->ignore_uv && test_bit(chipnum, max3191x->fault)) + return true; + + if (max3191x->mode == STATUS_BYTE_DISABLED) + return false; + + return test_bit(chipnum, max3191x->crc_error) || + test_bit(chipnum, max3191x->overtemp) || + (!max3191x->ignore_uv && + test_bit(chipnum, max3191x->undervolt1)); +} + +static int max3191x_get(struct gpio_chip *gpio, unsigned int offset) +{ + struct max3191x_chip *max3191x = gpiochip_get_data(gpio); + int ret, chipnum, wordlen = max3191x_wordlen(max3191x); + u8 in; + + mutex_lock(&max3191x->lock); + ret = max3191x_readout_locked(max3191x); + if (ret) + goto out_unlock; + + chipnum = offset / MAX3191X_NGPIO; + if (max3191x_chip_is_faulting(max3191x, chipnum)) { + ret = -EIO; + goto out_unlock; + } + + in = ((u8 *)max3191x->xfer.rx_buf)[chipnum * wordlen]; + ret = (in >> (offset % MAX3191X_NGPIO)) & 1; + +out_unlock: + mutex_unlock(&max3191x->lock); + return ret; +} + +static int max3191x_get_multiple(struct gpio_chip *gpio, unsigned long *mask, + unsigned long *bits) +{ + struct max3191x_chip *max3191x = gpiochip_get_data(gpio); + int ret, bit = 0, wordlen = max3191x_wordlen(max3191x); + + mutex_lock(&max3191x->lock); + ret = max3191x_readout_locked(max3191x); + if (ret) + goto out_unlock; + + while ((bit = find_next_bit(mask, gpio->ngpio, bit)) != gpio->ngpio) { + unsigned int chipnum = bit / MAX3191X_NGPIO; + unsigned long in, shift, index; + + if (max3191x_chip_is_faulting(max3191x, chipnum)) { + ret = -EIO; + goto out_unlock; + } + + in = ((u8 *)max3191x->xfer.rx_buf)[chipnum * wordlen]; + shift = round_down(bit % BITS_PER_LONG, MAX3191X_NGPIO); + index = bit / BITS_PER_LONG; + bits[index] &= ~(mask[index] & (0xff << shift)); + bits[index] |= mask[index] & (in << shift); /* copy bits */ + + bit = (chipnum + 1) * MAX3191X_NGPIO; /* go to next chip */ + } + +out_unlock: + mutex_unlock(&max3191x->lock); + return ret; +} + +static int max3191x_set_config(struct gpio_chip *gpio, unsigned int offset, + unsigned long config) +{ + struct max3191x_chip *max3191x = gpiochip_get_data(gpio); + u32 debounce, chipnum, db0_val, db1_val; + + if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE) + return -ENOTSUPP; + + if (!max3191x->db0_pins || !max3191x->db1_pins) + return -EINVAL; + + debounce = pinconf_to_config_argument(config); + switch (debounce) { + case 0: + db0_val = 0; + db1_val = 0; + break; + case 1 ... 25: + db0_val = 0; + db1_val = 1; + break; + case 26 ... 750: + db0_val = 1; + db1_val = 0; + break; + case 751 ... 3000: + db0_val = 1; + db1_val = 1; + break; + default: + return -EINVAL; + } + + if (max3191x->db0_pins->ndescs == 1) + chipnum = 0; /* all chips use the same pair of debounce pins */ + else + chipnum = offset / MAX3191X_NGPIO; /* per chip debounce pins */ + + mutex_lock(&max3191x->lock); + gpiod_set_value_cansleep(max3191x->db0_pins->desc[chipnum], db0_val); + gpiod_set_value_cansleep(max3191x->db1_pins->desc[chipnum], db1_val); + mutex_unlock(&max3191x->lock); + return 0; +} + +static void gpiod_set_array_single_value_cansleep(unsigned int ndescs, + struct gpio_desc **desc, + int value) +{ + int i, values[ndescs]; + + for (i = 0; i < ndescs; i++) + values[i] = value; + + gpiod_set_array_value_cansleep(ndescs, desc, values); +} + +static struct gpio_descs *devm_gpiod_get_array_optional_count( + struct device *dev, const char *con_id, + enum gpiod_flags flags, unsigned int expected) +{ + struct gpio_descs *descs; + int found = gpiod_count(dev, con_id); + + if (found == -ENOENT) + return NULL; + + if (found != expected && found != 1) { + dev_err(dev, "ignoring %s-gpios: found %d, expected %u or 1\n", + con_id, found, expected); + return NULL; + } + + descs = devm_gpiod_get_array_optional(dev, con_id, flags); + + if (IS_ERR(descs)) { + dev_err(dev, "failed to get %s-gpios: %ld\n", + con_id, PTR_ERR(descs)); + return NULL; + } + + return descs; +} + +static int max3191x_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct max3191x_chip *max3191x; + int n, ret; + + max3191x = devm_kzalloc(dev, sizeof(*max3191x), GFP_KERNEL); + if (!max3191x) + return -ENOMEM; + spi_set_drvdata(spi, max3191x); + + max3191x->nchips = 1; + device_property_read_u32(dev, "#daisy-chained-devices", + &max3191x->nchips); + + n = BITS_TO_LONGS(max3191x->nchips); + max3191x->crc_error = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL); + max3191x->undervolt1 = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL); + max3191x->undervolt2 = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL); + max3191x->overtemp = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL); + max3191x->fault = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL); + max3191x->xfer.rx_buf = devm_kcalloc(dev, max3191x->nchips, + 2, GFP_KERNEL); + if (!max3191x->crc_error || !max3191x->undervolt1 || + !max3191x->overtemp || !max3191x->undervolt2 || + !max3191x->fault || !max3191x->xfer.rx_buf) + return -ENOMEM; + + max3191x->modesel_pins = devm_gpiod_get_array_optional_count(dev, + "maxim,modesel", GPIOD_ASIS, max3191x->nchips); + max3191x->fault_pins = devm_gpiod_get_array_optional_count(dev, + "maxim,fault", GPIOD_IN, max3191x->nchips); + max3191x->db0_pins = devm_gpiod_get_array_optional_count(dev, + "maxim,db0", GPIOD_OUT_LOW, max3191x->nchips); + max3191x->db1_pins = devm_gpiod_get_array_optional_count(dev, + "maxim,db1", GPIOD_OUT_LOW, max3191x->nchips); + + max3191x->mode = device_property_read_bool(dev, "maxim,modesel-8bit") + ? STATUS_BYTE_DISABLED : STATUS_BYTE_ENABLED; + if (max3191x->modesel_pins) + gpiod_set_array_single_value_cansleep( + max3191x->modesel_pins->ndescs, + max3191x->modesel_pins->desc, max3191x->mode); + + max3191x->ignore_uv = device_property_read_bool(dev, + "maxim,ignore-undervoltage"); + + if (max3191x->db0_pins && max3191x->db1_pins && + max3191x->db0_pins->ndescs != max3191x->db1_pins->ndescs) { + dev_err(dev, "ignoring maxim,db*-gpios: array len mismatch\n"); + devm_gpiod_put_array(dev, max3191x->db0_pins); + devm_gpiod_put_array(dev, max3191x->db1_pins); + max3191x->db0_pins = NULL; + max3191x->db1_pins = NULL; + } + + max3191x->xfer.len = max3191x->nchips * max3191x_wordlen(max3191x); + spi_message_init_with_transfers(&max3191x->mesg, &max3191x->xfer, 1); + + max3191x->gpio.label = spi->modalias; + max3191x->gpio.owner = THIS_MODULE; + max3191x->gpio.parent = dev; + max3191x->gpio.base = -1; + max3191x->gpio.ngpio = max3191x->nchips * MAX3191X_NGPIO; + max3191x->gpio.can_sleep = true; + + max3191x->gpio.get_direction = max3191x_get_direction; + max3191x->gpio.direction_input = max3191x_direction_input; + max3191x->gpio.direction_output = max3191x_direction_output; + max3191x->gpio.set = max3191x_set; + max3191x->gpio.set_multiple = max3191x_set_multiple; + max3191x->gpio.get = max3191x_get; + max3191x->gpio.get_multiple = max3191x_get_multiple; + max3191x->gpio.set_config = max3191x_set_config; + + mutex_init(&max3191x->lock); + + ret = gpiochip_add_data(&max3191x->gpio, max3191x); + if (ret) { + mutex_destroy(&max3191x->lock); + return ret; + } + + return 0; +} + +static int max3191x_remove(struct spi_device *spi) +{ + struct max3191x_chip *max3191x = spi_get_drvdata(spi); + + gpiochip_remove(&max3191x->gpio); + mutex_destroy(&max3191x->lock); + + return 0; +} + +static int __init max3191x_register_driver(struct spi_driver *sdrv) +{ + crc8_populate_msb(max3191x_crc8, MAX3191X_CRC8_POLYNOMIAL); + return spi_register_driver(sdrv); +} + +#ifdef CONFIG_OF +static const struct of_device_id max3191x_of_id[] = { + { .compatible = "maxim,max31910" }, + { .compatible = "maxim,max31911" }, + { .compatible = "maxim,max31912" }, + { .compatible = "maxim,max31913" }, + { .compatible = "maxim,max31953" }, + { .compatible = "maxim,max31963" }, + { } +}; +MODULE_DEVICE_TABLE(of, max3191x_of_id); +#endif + +static const struct spi_device_id max3191x_spi_id[] = { + { "max31910" }, + { "max31911" }, + { "max31912" }, + { "max31913" }, + { "max31953" }, + { "max31963" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max3191x_spi_id); + +static struct spi_driver max3191x_driver = { + .driver = { + .name = "max3191x", + .of_match_table = of_match_ptr(max3191x_of_id), + }, + .probe = max3191x_probe, + .remove = max3191x_remove, + .id_table = max3191x_spi_id, +}; +module_driver(max3191x_driver, max3191x_register_driver, spi_unregister_driver); + +MODULE_AUTHOR("Lukas Wunner "); +MODULE_DESCRIPTION("GPIO driver for Maxim MAX3191x industrial serializer"); +MODULE_LICENSE("GPL v2");