From 9147340a7eec07d985ccc8c9ecb87e211306c73d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonard=20G=C3=B6hrs?= Date: Fri, 10 Mar 2023 10:20:52 +0100 Subject: [PATCH 01/22] spi: core: add spi_split_transfers_maxwords MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add spi_split_transfers_maxwords() function that splits spi_transfers transparently into multiple transfers that are below a given number of SPI words. This function reuses most of its code from spi_split_transfers_maxsize() and for transfers with eight or less bits per word actually behaves the same. Signed-off-by: Leonard Göhrs Link: https://lore.kernel.org/r/20230310092053.1006459-1-l.goehrs@pengutronix.de Signed-off-by: Mark Brown --- drivers/spi/spi.c | 49 +++++++++++++++++++++++++++++++++++++++++ include/linux/spi/spi.h | 4 ++++ 2 files changed, 53 insertions(+) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 8800e61c8ea829..ee04f0bf22dd2f 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -3667,6 +3667,55 @@ int spi_split_transfers_maxsize(struct spi_controller *ctlr, } EXPORT_SYMBOL_GPL(spi_split_transfers_maxsize); + +/** + * spi_split_transfers_maxwords - split spi transfers into multiple transfers + * when an individual transfer exceeds a + * certain number of SPI words + * @ctlr: the @spi_controller for this transfer + * @msg: the @spi_message to transform + * @maxwords: the number of words to limit each transfer to + * @gfp: GFP allocation flags + * + * Return: status of transformation + */ +int spi_split_transfers_maxwords(struct spi_controller *ctlr, + struct spi_message *msg, + size_t maxwords, + gfp_t gfp) +{ + struct spi_transfer *xfer; + + /* + * Iterate over the transfer_list, + * but note that xfer is advanced to the last transfer inserted + * to avoid checking sizes again unnecessarily (also xfer does + * potentially belong to a different list by the time the + * replacement has happened). + */ + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + size_t maxsize; + int ret; + + if (xfer->bits_per_word <= 8) + maxsize = maxwords; + else if (xfer->bits_per_word <= 16) + maxsize = 2 * maxwords; + else + maxsize = 4 * maxwords; + + if (xfer->len > maxsize) { + ret = __spi_split_transfer_maxsize(ctlr, msg, &xfer, + maxsize, gfp); + if (ret) + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(spi_split_transfers_maxwords); + /*-------------------------------------------------------------------------*/ /* Core methods for SPI controller protocol drivers. Some of the diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 29ddbf2508f345..9f7531a1dc2fb3 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -1277,6 +1277,10 @@ extern int spi_split_transfers_maxsize(struct spi_controller *ctlr, struct spi_message *msg, size_t maxsize, gfp_t gfp); +extern int spi_split_transfers_maxwords(struct spi_controller *ctlr, + struct spi_message *msg, + size_t maxwords, + gfp_t gfp); /*---------------------------------------------------------------------------*/ From 8fbf5c524feea018026ccfb5f006303be53efbae Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 23 Jan 2024 15:49:46 -0600 Subject: [PATCH 02/22] spi: consolidate setting message->spi Previously, __spi_sync() and __spi_async() set message->spi to the spi device independently after calling __spi_validate(). __spi_validate() also would conditionally set this if it needed to split the message since it wasn't set yet. Since both __spi_sync() and __spi_async() call __spi_validate(), we can consolidate this into only setting message->spi once (unconditionally) in __spi_validate(). This will also save any future callers of __spi_validate() from also needing to set message->spi. Signed-off-by: David Lechner Link: https://msgid.link/r/20240123214946.2616786-1-dlechner@baylibre.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index ee04f0bf22dd2f..9b90aa1450ecc0 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -3908,6 +3908,8 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message) if (list_empty(&message->transfers)) return -EINVAL; + message->spi = spi; + if (spi->cs_index_mask) cs_num = ffs(spi->cs_index_mask) - 1; @@ -3925,9 +3927,6 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message) maxsize = (spi->bits_per_word + 7) / 8; - /* spi_split_transfers_maxsize() requires message->spi */ - message->spi = spi; - ret = spi_split_transfers_maxsize(ctlr, message, maxsize, GFP_KERNEL); if (ret) @@ -4064,8 +4063,6 @@ static int __spi_async(struct spi_device *spi, struct spi_message *message) if (!ctlr->transfer) return -ENOTSUPP; - message->spi = spi; - SPI_STATISTICS_INCREMENT_FIELD(ctlr->pcpu_statistics, spi_async); SPI_STATISTICS_INCREMENT_FIELD(spi->pcpu_statistics, spi_async); @@ -4242,8 +4239,6 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message) if (status != 0) return status; - message->spi = spi; - SPI_STATISTICS_INCREMENT_FIELD(ctlr->pcpu_statistics, spi_sync); SPI_STATISTICS_INCREMENT_FIELD(spi->pcpu_statistics, spi_sync); From d0189726c094a173d9571996850f0180dda4a8f5 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 25 Jan 2024 14:53:09 -0600 Subject: [PATCH 03/22] spi: fix finalize message on error return In __spi_pump_transfer_message(), the message was not finalized in the first error return as it is in the other error return paths. Not finalizing the message could cause anything waiting on the message to complete to hang forever. This adds the missing call to spi_finalize_current_message(). Fixes: ae7d2346dc89 ("spi: Don't use the message queue if possible in spi_sync") Signed-off-by: David Lechner Link: https://msgid.link/r/20240125205312.3458541-2-dlechner@baylibre.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 9b90aa1450ecc0..66fb89c0418d9d 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1709,6 +1709,10 @@ static int __spi_pump_transfer_message(struct spi_controller *ctlr, pm_runtime_put_noidle(ctlr->dev.parent); dev_err(&ctlr->dev, "Failed to power device: %d\n", ret); + + msg->status = ret; + spi_finalize_current_message(ctlr); + return ret; } } From 46f286d0d7aea753219e7cbbb7e62414671e3c9c Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 25 Jan 2024 17:47:31 -0600 Subject: [PATCH 04/22] spi: avoid double validation in __spi_sync() The __spi_sync() function calls __spi_validate() early in the function. Later, it can call spi_async_locked() which calls __spi_validate() again. __spi_validate() is an expensive function, so we can improve performance measurably by avoiding calling it twice. Instead of calling spi_async_locked(), we can call __spi_async() with the spin lock held. spi_async_locked() is removed since there are no more callers. Signed-off-by: David Lechner Link: https://msgid.link/r/20240125234732.3530278-2-dlechner@baylibre.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 58 +++++------------------------------------------ 1 file changed, 6 insertions(+), 52 deletions(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 66fb89c0418d9d..776336336e604d 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -4136,57 +4136,6 @@ int spi_async(struct spi_device *spi, struct spi_message *message) } EXPORT_SYMBOL_GPL(spi_async); -/** - * spi_async_locked - version of spi_async with exclusive bus usage - * @spi: device with which data will be exchanged - * @message: describes the data transfers, including completion callback - * Context: any (irqs may be blocked, etc) - * - * This call may be used in_irq and other contexts which can't sleep, - * as well as from task contexts which can sleep. - * - * The completion callback is invoked in a context which can't sleep. - * Before that invocation, the value of message->status is undefined. - * When the callback is issued, message->status holds either zero (to - * indicate complete success) or a negative error code. After that - * callback returns, the driver which issued the transfer request may - * deallocate the associated memory; it's no longer in use by any SPI - * core or controller driver code. - * - * Note that although all messages to a spi_device are handled in - * FIFO order, messages may go to different devices in other orders. - * Some device might be higher priority, or have various "hard" access - * time requirements, for example. - * - * On detection of any fault during the transfer, processing of - * the entire message is aborted, and the device is deselected. - * Until returning from the associated message completion callback, - * no other spi_message queued to that device will be processed. - * (This rule applies equally to all the synchronous transfer calls, - * which are wrappers around this core asynchronous primitive.) - * - * Return: zero on success, else a negative error code. - */ -static int spi_async_locked(struct spi_device *spi, struct spi_message *message) -{ - struct spi_controller *ctlr = spi->controller; - int ret; - unsigned long flags; - - ret = __spi_validate(spi, message); - if (ret != 0) - return ret; - - spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags); - - ret = __spi_async(spi, message); - - spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags); - - return ret; - -} - static void __spi_transfer_message_noqueue(struct spi_controller *ctlr, struct spi_message *msg) { bool was_busy; @@ -4236,6 +4185,7 @@ static void spi_complete(void *arg) static int __spi_sync(struct spi_device *spi, struct spi_message *message) { DECLARE_COMPLETION_ONSTACK(done); + unsigned long flags; int status; struct spi_controller *ctlr = spi->controller; @@ -4274,7 +4224,11 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message) */ message->complete = spi_complete; message->context = &done; - status = spi_async_locked(spi, message); + + spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags); + status = __spi_async(spi, message); + spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags); + if (status == 0) { wait_for_completion(&done); status = message->status; From 6eb7910d605b64bfc0955a12d48353be18aa5439 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 26 Jan 2024 15:23:57 -0600 Subject: [PATCH 05/22] spi: move split xfers for CS_WORD emulation This moves splitting transfers for CS_WORD software emulation to the same place where we split transfers for controller-specific reasons. This fixes a few subtle bugs. The calculation for maxsize was wrong for bit sizes between 17 and 24. This is fixed by making use of spi_split_transfers_maxwords() which already has the correct calculation. Also, since this indirectly calls spi_res_alloc(), to avoid leaking resources, spi_finalize_current_message() would need to be called on all error paths in __spi_validate() and callers of __spi_validate() would need to do the same. This is fixed by moving the call to __spi_pump_transfer_message() where it is already splitting transfers for other reasons and correctly releases resources in the subsequent error paths. Fixes: cbaa62e0094a ("spi: add software implementation for SPI_CS_WORD") Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240126212358.3916280-2-dlechner@baylibre.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 73 +++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 776336336e604d..23a12066c76407 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1701,6 +1701,7 @@ static int __spi_pump_transfer_message(struct spi_controller *ctlr, struct spi_message *msg, bool was_busy) { struct spi_transfer *xfer; + u32 cs_num = 0; int ret; if (!was_busy && ctlr->auto_runtime_pm) { @@ -1739,13 +1740,40 @@ static int __spi_pump_transfer_message(struct spi_controller *ctlr, trace_spi_message_start(msg); - ret = spi_split_transfers_maxsize(ctlr, msg, - spi_max_transfer_size(msg->spi), - GFP_KERNEL | GFP_DMA); - if (ret) { - msg->status = ret; - spi_finalize_current_message(ctlr); - return ret; + if (msg->spi->cs_index_mask) + cs_num = ffs(msg->spi->cs_index_mask) - 1; + + /* + * If an SPI controller does not support toggling the CS line on each + * transfer (indicated by the SPI_CS_WORD flag) or we are using a GPIO + * for the CS line, we can emulate the CS-per-word hardware function by + * splitting transfers into one-word transfers and ensuring that + * cs_change is set for each transfer. + */ + if ((msg->spi->mode & SPI_CS_WORD) && + (!(ctlr->mode_bits & SPI_CS_WORD) || spi_get_csgpiod(msg->spi, cs_num))) { + ret = spi_split_transfers_maxwords(ctlr, msg, 1, GFP_KERNEL); + if (ret) { + msg->status = ret; + spi_finalize_current_message(ctlr); + return ret; + } + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + /* Don't change cs_change on the last entry in the list */ + if (list_is_last(&xfer->transfer_list, &msg->transfers)) + break; + xfer->cs_change = 1; + } + } else { + ret = spi_split_transfers_maxsize(ctlr, msg, + spi_max_transfer_size(msg->spi), + GFP_KERNEL | GFP_DMA); + if (ret) { + msg->status = ret; + spi_finalize_current_message(ctlr); + return ret; + } } if (ctlr->prepare_message) { @@ -3907,43 +3935,12 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message) struct spi_controller *ctlr = spi->controller; struct spi_transfer *xfer; int w_size; - u32 cs_num = 0; if (list_empty(&message->transfers)) return -EINVAL; message->spi = spi; - if (spi->cs_index_mask) - cs_num = ffs(spi->cs_index_mask) - 1; - - /* - * If an SPI controller does not support toggling the CS line on each - * transfer (indicated by the SPI_CS_WORD flag) or we are using a GPIO - * for the CS line, we can emulate the CS-per-word hardware function by - * splitting transfers into one-word transfers and ensuring that - * cs_change is set for each transfer. - */ - if ((spi->mode & SPI_CS_WORD) && (!(ctlr->mode_bits & SPI_CS_WORD) || - spi_get_csgpiod(spi, cs_num))) { - size_t maxsize; - int ret; - - maxsize = (spi->bits_per_word + 7) / 8; - - ret = spi_split_transfers_maxsize(ctlr, message, maxsize, - GFP_KERNEL); - if (ret) - return ret; - - list_for_each_entry(xfer, &message->transfers, transfer_list) { - /* Don't change cs_change on the last entry in the list */ - if (list_is_last(&xfer->transfer_list, &message->transfers)) - break; - xfer->cs_change = 1; - } - } - /* * Half-duplex links include original MicroWire, and ones with * only one data pin like SPI_3WIRE (switches direction) or where From cd609f4be2a84006f966466ac4146e7059042cda Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 6 Feb 2024 14:06:46 -0600 Subject: [PATCH 06/22] spi: drop gpf arg from __spi_split_transfer_maxsize() The __spi_split_transfer_maxsize() function has a gpf argument to allow callers to specify the type of memory allocation that needs to be used. However, this function only allocates struct spi_transfer and is not intended to be used from atomic contexts so this type should always be GFP_KERNEL, so we can just drop the argument. Some callers of these functions also passed GFP_DMA, but since only struct spi_transfer is allocated and not any tx/rx buffers, this is not actually necessary and is removed in this commit. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240206200648.1782234-1-dlechner@baylibre.com Signed-off-by: Mark Brown --- drivers/spi/spi-stm32.c | 4 +--- drivers/spi/spi.c | 22 ++++++++-------------- include/linux/spi/spi.h | 6 ++---- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c index def09cf0dc147c..861103206a2b4e 100644 --- a/drivers/spi/spi-stm32.c +++ b/drivers/spi/spi-stm32.c @@ -984,9 +984,7 @@ static int stm32_spi_prepare_msg(struct spi_master *master, if (spi->cfg->set_number_of_data) { int ret; - ret = spi_split_transfers_maxsize(master, msg, - STM32H7_SPI_TSIZE_MAX, - GFP_KERNEL | GFP_DMA); + ret = spi_split_transfers_maxwords(ctrl, msg, spi->t_size_max); if (ret) return ret; } diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 23a12066c76407..9cd1f8f67b9e8e 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1752,7 +1752,7 @@ static int __spi_pump_transfer_message(struct spi_controller *ctlr, */ if ((msg->spi->mode & SPI_CS_WORD) && (!(ctlr->mode_bits & SPI_CS_WORD) || spi_get_csgpiod(msg->spi, cs_num))) { - ret = spi_split_transfers_maxwords(ctlr, msg, 1, GFP_KERNEL); + ret = spi_split_transfers_maxwords(ctlr, msg, 1); if (ret) { msg->status = ret; spi_finalize_current_message(ctlr); @@ -1767,8 +1767,7 @@ static int __spi_pump_transfer_message(struct spi_controller *ctlr, } } else { ret = spi_split_transfers_maxsize(ctlr, msg, - spi_max_transfer_size(msg->spi), - GFP_KERNEL | GFP_DMA); + spi_max_transfer_size(msg->spi)); if (ret) { msg->status = ret; spi_finalize_current_message(ctlr); @@ -3595,8 +3594,7 @@ static struct spi_replaced_transfers *spi_replace_transfers( static int __spi_split_transfer_maxsize(struct spi_controller *ctlr, struct spi_message *msg, struct spi_transfer **xferp, - size_t maxsize, - gfp_t gfp) + size_t maxsize) { struct spi_transfer *xfer = *xferp, *xfers; struct spi_replaced_transfers *srt; @@ -3607,7 +3605,7 @@ static int __spi_split_transfer_maxsize(struct spi_controller *ctlr, count = DIV_ROUND_UP(xfer->len, maxsize); /* Create replacement */ - srt = spi_replace_transfers(msg, xfer, 1, count, NULL, 0, gfp); + srt = spi_replace_transfers(msg, xfer, 1, count, NULL, 0, GFP_KERNEL); if (IS_ERR(srt)) return PTR_ERR(srt); xfers = srt->inserted_transfers; @@ -3667,14 +3665,12 @@ static int __spi_split_transfer_maxsize(struct spi_controller *ctlr, * @ctlr: the @spi_controller for this transfer * @msg: the @spi_message to transform * @maxsize: the maximum when to apply this - * @gfp: GFP allocation flags * * Return: status of transformation */ int spi_split_transfers_maxsize(struct spi_controller *ctlr, struct spi_message *msg, - size_t maxsize, - gfp_t gfp) + size_t maxsize) { struct spi_transfer *xfer; int ret; @@ -3689,7 +3685,7 @@ int spi_split_transfers_maxsize(struct spi_controller *ctlr, list_for_each_entry(xfer, &msg->transfers, transfer_list) { if (xfer->len > maxsize) { ret = __spi_split_transfer_maxsize(ctlr, msg, &xfer, - maxsize, gfp); + maxsize); if (ret) return ret; } @@ -3707,14 +3703,12 @@ EXPORT_SYMBOL_GPL(spi_split_transfers_maxsize); * @ctlr: the @spi_controller for this transfer * @msg: the @spi_message to transform * @maxwords: the number of words to limit each transfer to - * @gfp: GFP allocation flags * * Return: status of transformation */ int spi_split_transfers_maxwords(struct spi_controller *ctlr, struct spi_message *msg, - size_t maxwords, - gfp_t gfp) + size_t maxwords) { struct spi_transfer *xfer; @@ -3738,7 +3732,7 @@ int spi_split_transfers_maxwords(struct spi_controller *ctlr, if (xfer->len > maxsize) { ret = __spi_split_transfer_maxsize(ctlr, msg, &xfer, - maxsize, gfp); + maxsize); if (ret) return ret; } diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 9f7531a1dc2fb3..d28c48ad8f8d46 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -1275,12 +1275,10 @@ struct spi_replaced_transfers { extern int spi_split_transfers_maxsize(struct spi_controller *ctlr, struct spi_message *msg, - size_t maxsize, - gfp_t gfp); + size_t maxsize); extern int spi_split_transfers_maxwords(struct spi_controller *ctlr, struct spi_message *msg, - size_t maxwords, - gfp_t gfp); + size_t maxwords); /*---------------------------------------------------------------------------*/ From deed7716709626ed6419932d7cef2691d99b4f2e Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 19 Feb 2024 16:33:18 -0600 Subject: [PATCH 07/22] spi: add spi_optimize_message() APIs This adds a new spi_optimize_message() function that can be used to optimize SPI messages that are used more than once. Peripheral drivers that use the same message multiple times can use this API to perform SPI message validation and controller-specific optimizations once and then reuse the message while avoiding the overhead of revalidating the message on each spi_(a)sync() call. Internally, the SPI core will also call this function for each message if the peripheral driver did not explicitly call it. This is done to so that controller drivers don't have to have multiple code paths for optimized and non-optimized messages. A hook is provided for controller drivers to perform controller-specific optimizations. Suggested-by: Martin Sperl Link: https://lore.kernel.org/linux-spi/39DEC004-10A1-47EF-9D77-276188D2580C@martin.sperl.org/ Signed-off-by: David Lechner Link: https://msgid.link/r/20240219-mainline-spi-precook-message-v2-1-4a762c6701b9@baylibre.com Reviewed-by: Jonathan Cameron Signed-off-by: Mark Brown --- drivers/spi/spi.c | 151 ++++++++++++++++++++++++++++++++++++++-- include/linux/spi/spi.h | 20 ++++++ 2 files changed, 167 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 9cd1f8f67b9e8e..c317bcbcb9aa92 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -2102,6 +2102,41 @@ struct spi_message *spi_get_next_queued_message(struct spi_controller *ctlr) } EXPORT_SYMBOL_GPL(spi_get_next_queued_message); +/* + * __spi_unoptimize_message - shared implementation of spi_unoptimize_message() + * and spi_maybe_unoptimize_message() + * @msg: the message to unoptimize + * + * Peripheral drivers should use spi_unoptimize_message() and callers inside + * core should use spi_maybe_unoptimize_message() rather than calling this + * function directly. + * + * It is not valid to call this on a message that is not currently optimized. + */ +static void __spi_unoptimize_message(struct spi_message *msg) +{ + struct spi_controller *ctlr = msg->spi->controller; + + if (ctlr->unoptimize_message) + ctlr->unoptimize_message(msg); + + msg->optimized = false; + msg->opt_state = NULL; +} + +/* + * spi_maybe_unoptimize_message - unoptimize msg not managed by a peripheral + * @msg: the message to unoptimize + * + * This function is used to unoptimize a message if and only if it was + * optimized by the core (via spi_maybe_optimize_message()). + */ +static void spi_maybe_unoptimize_message(struct spi_message *msg) +{ + if (!msg->pre_optimized && msg->optimized) + __spi_unoptimize_message(msg); +} + /** * spi_finalize_current_message() - the current message is complete * @ctlr: the controller to return the message to @@ -2149,6 +2184,8 @@ void spi_finalize_current_message(struct spi_controller *ctlr) mesg->prepared = false; + spi_maybe_unoptimize_message(mesg); + WRITE_ONCE(ctlr->cur_msg_incomplete, false); smp_mb(); /* See __spi_pump_transfer_message()... */ if (READ_ONCE(ctlr->cur_msg_need_completion)) @@ -4046,6 +4083,110 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message) return 0; } +/* + * __spi_optimize_message - shared implementation for spi_optimize_message() + * and spi_maybe_optimize_message() + * @spi: the device that will be used for the message + * @msg: the message to optimize + * + * Peripheral drivers will call spi_optimize_message() and the spi core will + * call spi_maybe_optimize_message() instead of calling this directly. + * + * It is not valid to call this on a message that has already been optimized. + * + * Return: zero on success, else a negative error code + */ +static int __spi_optimize_message(struct spi_device *spi, + struct spi_message *msg) +{ + struct spi_controller *ctlr = spi->controller; + int ret; + + ret = __spi_validate(spi, msg); + if (ret) + return ret; + + if (ctlr->optimize_message) { + ret = ctlr->optimize_message(msg); + if (ret) + return ret; + } + + msg->optimized = true; + + return 0; +} + +/* + * spi_maybe_optimize_message - optimize message if it isn't already pre-optimized + * @spi: the device that will be used for the message + * @msg: the message to optimize + * Return: zero on success, else a negative error code + */ +static int spi_maybe_optimize_message(struct spi_device *spi, + struct spi_message *msg) +{ + if (msg->pre_optimized) + return 0; + + return __spi_optimize_message(spi, msg); +} + +/** + * spi_optimize_message - do any one-time validation and setup for a SPI message + * @spi: the device that will be used for the message + * @msg: the message to optimize + * + * Peripheral drivers that reuse the same message repeatedly may call this to + * perform as much message prep as possible once, rather than repeating it each + * time a message transfer is performed to improve throughput and reduce CPU + * usage. + * + * Once a message has been optimized, it cannot be modified with the exception + * of updating the contents of any xfer->tx_buf (the pointer can't be changed, + * only the data in the memory it points to). + * + * Calls to this function must be balanced with calls to spi_unoptimize_message() + * to avoid leaking resources. + * + * Context: can sleep + * Return: zero on success, else a negative error code + */ +int spi_optimize_message(struct spi_device *spi, struct spi_message *msg) +{ + int ret; + + ret = __spi_optimize_message(spi, msg); + if (ret) + return ret; + + /* + * This flag indicates that the peripheral driver called spi_optimize_message() + * and therefore we shouldn't unoptimize message automatically when finalizing + * the message but rather wait until spi_unoptimize_message() is called + * by the peripheral driver. + */ + msg->pre_optimized = true; + + return 0; +} +EXPORT_SYMBOL_GPL(spi_optimize_message); + +/** + * spi_unoptimize_message - releases any resources allocated by spi_optimize_message() + * @msg: the message to unoptimize + * + * Calls to this function must be balanced with calls to spi_optimize_message(). + * + * Context: can sleep + */ +void spi_unoptimize_message(struct spi_message *msg) +{ + __spi_unoptimize_message(msg); + msg->pre_optimized = false; +} +EXPORT_SYMBOL_GPL(spi_unoptimize_message); + static int __spi_async(struct spi_device *spi, struct spi_message *message) { struct spi_controller *ctlr = spi->controller; @@ -4110,8 +4251,8 @@ int spi_async(struct spi_device *spi, struct spi_message *message) int ret; unsigned long flags; - ret = __spi_validate(spi, message); - if (ret != 0) + ret = spi_maybe_optimize_message(spi, message); + if (ret) return ret; spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags); @@ -4123,6 +4264,8 @@ int spi_async(struct spi_device *spi, struct spi_message *message) spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags); + spi_maybe_unoptimize_message(message); + return ret; } EXPORT_SYMBOL_GPL(spi_async); @@ -4180,8 +4323,8 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message) int status; struct spi_controller *ctlr = spi->controller; - status = __spi_validate(spi, message); - if (status != 0) + status = spi_maybe_optimize_message(spi, message); + if (status) return status; SPI_STATISTICS_INCREMENT_FIELD(ctlr->pcpu_statistics, spi_sync); diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index d28c48ad8f8d46..b52a9c30235e85 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -457,6 +457,8 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch * * @set_cs: set the logic level of the chip select line. May be called * from interrupt context. + * @optimize_message: optimize the message for reuse + * @unoptimize_message: release resources allocated by optimize_message * @prepare_message: set up the controller to transfer a single message, * for example doing DMA mapping. Called from threaded * context. @@ -686,6 +688,8 @@ struct spi_controller { struct completion xfer_completion; size_t max_dma_len; + int (*optimize_message)(struct spi_message *msg); + int (*unoptimize_message)(struct spi_message *msg); int (*prepare_transfer_hardware)(struct spi_controller *ctlr); int (*transfer_one_message)(struct spi_controller *ctlr, struct spi_message *mesg); @@ -1044,6 +1048,8 @@ struct spi_transfer { * @spi: SPI device to which the transaction is queued * @is_dma_mapped: if true, the caller provided both dma and cpu virtual * addresses for each transfer buffer + * @pre_optimized: peripheral driver pre-optimized the message + * @optimized: the message is in the optimized state * @complete: called to report transaction completions * @context: the argument to complete() when it's called * @frame_length: the total number of bytes in the message @@ -1052,6 +1058,7 @@ struct spi_transfer { * @status: zero for success, else negative errno * @queue: for use by whichever driver currently owns the message * @state: for use by whichever driver currently owns the message + * @opt_state: for use by whichever driver currently owns the message * @resources: for resource management when the spi message is processed * @prepared: spi_prepare_message was called for the this message * @@ -1076,6 +1083,11 @@ struct spi_message { unsigned is_dma_mapped:1; + /* spi_optimize_message() was called for this message */ + bool pre_optimized; + /* __spi_optimize_message() was called for this message */ + bool optimized; + /* REVISIT: we might want a flag affecting the behavior of the * last transfer ... allowing things like "read 16 bit length L" * immediately followed by "read L bytes". Basically imposing @@ -1100,6 +1112,11 @@ struct spi_message { */ struct list_head queue; void *state; + /* + * Optional state for use by controller driver between calls to + * __spi_optimize_message() and __spi_unoptimize_message(). + */ + void *opt_state; /* List of spi_res reources when the spi message is processed */ struct list_head resources; @@ -1185,6 +1202,9 @@ static inline void spi_message_free(struct spi_message *m) kfree(m); } +extern int spi_optimize_message(struct spi_device *spi, struct spi_message *msg); +extern void spi_unoptimize_message(struct spi_message *msg); + extern int spi_setup(struct spi_device *spi); extern int spi_async(struct spi_device *spi, struct spi_message *message); extern int spi_slave_abort(struct spi_device *spi); From 8b20785f8ee97dd8caf0e3badbcab8e012efa4fe Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 19 Feb 2024 16:33:19 -0600 Subject: [PATCH 08/22] spi: move splitting transfers to spi_optimize_message() Splitting transfers is an expensive operation so we can potentially optimize it by doing it only once per optimization of the message instead of repeating each time the message is transferred. The transfer splitting functions are currently the only user of spi_res_alloc() so spi_res_release() can be safely moved at this time from spi_finalize_current_message() to spi_unoptimize_message(). The doc comments of the public functions for splitting transfers are also updated so that callers will know when it is safe to call them to ensure proper resource management. Reviewed-by: Jonathan Cameron Signed-off-by: David Lechner Link: https://msgid.link/r/20240219-mainline-spi-precook-message-v2-2-4a762c6701b9@baylibre.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 118 ++++++++++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 46 deletions(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index c317bcbcb9aa92..f7f4be36283270 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1701,7 +1701,6 @@ static int __spi_pump_transfer_message(struct spi_controller *ctlr, struct spi_message *msg, bool was_busy) { struct spi_transfer *xfer; - u32 cs_num = 0; int ret; if (!was_busy && ctlr->auto_runtime_pm) { @@ -1740,41 +1739,6 @@ static int __spi_pump_transfer_message(struct spi_controller *ctlr, trace_spi_message_start(msg); - if (msg->spi->cs_index_mask) - cs_num = ffs(msg->spi->cs_index_mask) - 1; - - /* - * If an SPI controller does not support toggling the CS line on each - * transfer (indicated by the SPI_CS_WORD flag) or we are using a GPIO - * for the CS line, we can emulate the CS-per-word hardware function by - * splitting transfers into one-word transfers and ensuring that - * cs_change is set for each transfer. - */ - if ((msg->spi->mode & SPI_CS_WORD) && - (!(ctlr->mode_bits & SPI_CS_WORD) || spi_get_csgpiod(msg->spi, cs_num))) { - ret = spi_split_transfers_maxwords(ctlr, msg, 1); - if (ret) { - msg->status = ret; - spi_finalize_current_message(ctlr); - return ret; - } - - list_for_each_entry(xfer, &msg->transfers, transfer_list) { - /* Don't change cs_change on the last entry in the list */ - if (list_is_last(&xfer->transfer_list, &msg->transfers)) - break; - xfer->cs_change = 1; - } - } else { - ret = spi_split_transfers_maxsize(ctlr, msg, - spi_max_transfer_size(msg->spi)); - if (ret) { - msg->status = ret; - spi_finalize_current_message(ctlr); - return ret; - } - } - if (ctlr->prepare_message) { ret = ctlr->prepare_message(ctlr, msg); if (ret) { @@ -2120,6 +2084,8 @@ static void __spi_unoptimize_message(struct spi_message *msg) if (ctlr->unoptimize_message) ctlr->unoptimize_message(msg); + spi_res_release(ctlr, msg); + msg->optimized = false; msg->opt_state = NULL; } @@ -2165,15 +2131,6 @@ void spi_finalize_current_message(struct spi_controller *ctlr) spi_unmap_msg(ctlr, mesg); - /* - * In the prepare_messages callback the SPI bus has the opportunity - * to split a transfer to smaller chunks. - * - * Release the split transfers here since spi_map_msg() is done on - * the split transfers. - */ - spi_res_release(ctlr, mesg); - if (mesg->prepared && ctlr->unprepare_message) { ret = ctlr->unprepare_message(ctlr, mesg); if (ret) { @@ -3703,6 +3660,10 @@ static int __spi_split_transfer_maxsize(struct spi_controller *ctlr, * @msg: the @spi_message to transform * @maxsize: the maximum when to apply this * + * This function allocates resources that are automatically freed during the + * spi message unoptimize phase so this function should only be called from + * optimize_message callbacks. + * * Return: status of transformation */ int spi_split_transfers_maxsize(struct spi_controller *ctlr, @@ -3741,6 +3702,10 @@ EXPORT_SYMBOL_GPL(spi_split_transfers_maxsize); * @msg: the @spi_message to transform * @maxwords: the number of words to limit each transfer to * + * This function allocates resources that are automatically freed during the + * spi message unoptimize phase so this function should only be called from + * optimize_message callbacks. + * * Return: status of transformation */ int spi_split_transfers_maxwords(struct spi_controller *ctlr, @@ -4083,6 +4048,61 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message) return 0; } +/* + * spi_split_transfers - generic handling of transfer splitting + * @msg: the message to split + * + * Under certain conditions, a SPI controller may not support arbitrary + * transfer sizes or other features required by a peripheral. This function + * will split the transfers in the message into smaller transfers that are + * supported by the controller. + * + * Controllers with special requirements not covered here can also split + * transfers in the optimize_message() callback. + * + * Context: can sleep + * Return: zero on success, else a negative error code + */ +static int spi_split_transfers(struct spi_message *msg) +{ + struct spi_controller *ctlr = msg->spi->controller; + struct spi_transfer *xfer; + u32 cs_num = 0; + int ret; + + if (msg->spi->cs_index_mask) + cs_num = ffs(msg->spi->cs_index_mask) - 1; + + /* + * If an SPI controller does not support toggling the CS line on each + * transfer (indicated by the SPI_CS_WORD flag) or we are using a GPIO + * for the CS line, we can emulate the CS-per-word hardware function by + * splitting transfers into one-word transfers and ensuring that + * cs_change is set for each transfer. + */ + if ((msg->spi->mode & SPI_CS_WORD) && + (!(ctlr->mode_bits & SPI_CS_WORD) || spi_get_csgpiod(msg->spi, cs_num))) { + ret = spi_split_transfers_maxwords(ctlr, msg, 1); + if (ret) + return ret; + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + /* Don't change cs_change on the last entry in the list */ + if (list_is_last(&xfer->transfer_list, &msg->transfers)) + break; + + xfer->cs_change = 1; + } + } else { + ret = spi_split_transfers_maxsize(ctlr, msg, + spi_max_transfer_size(msg->spi)); + if (ret) + return ret; + } + + return 0; +} + /* * __spi_optimize_message - shared implementation for spi_optimize_message() * and spi_maybe_optimize_message() @@ -4106,10 +4126,16 @@ static int __spi_optimize_message(struct spi_device *spi, if (ret) return ret; + ret = spi_split_transfers(msg); + if (ret) + return ret; + if (ctlr->optimize_message) { ret = ctlr->optimize_message(msg); - if (ret) + if (ret) { + spi_res_release(ctlr, msg); return ret; + } } msg->optimized = true; From 4b8320217c74041ab8c5e2bb175a795f006853a0 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 17 Nov 2023 14:12:52 -0600 Subject: [PATCH 09/22] dt-bindings: spi: axi-spi-engine: convert to yaml This converts the axi-spi-engine binding to yaml. There are a few minor fixes in the conversion: * Added maintainers. * Added descriptions for the clocks. * Fixed the double "@" in the example. * Added a comma between the clocks in the example. Signed-off-by: David Lechner Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20231117-axi-spi-engine-series-1-v1-1-cc59db999b87@baylibre.com Signed-off-by: Mark Brown --- .../bindings/spi/adi,axi-spi-engine.txt | 31 --------- .../bindings/spi/adi,axi-spi-engine.yaml | 66 +++++++++++++++++++ 2 files changed, 66 insertions(+), 31 deletions(-) delete mode 100644 Documentation/devicetree/bindings/spi/adi,axi-spi-engine.txt create mode 100644 Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml diff --git a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.txt b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.txt deleted file mode 100644 index 8a18d71e68791f..00000000000000 --- a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.txt +++ /dev/null @@ -1,31 +0,0 @@ -Analog Devices AXI SPI Engine controller Device Tree Bindings - -Required properties: -- compatible : Must be "adi,axi-spi-engine-1.00.a"" -- reg : Physical base address and size of the register map. -- interrupts : Property with a value describing the interrupt - number. -- clock-names : List of input clock names - "s_axi_aclk", "spi_clk" -- clocks : Clock phandles and specifiers (See clock bindings for - details on clock-names and clocks). -- #address-cells : Must be <1> -- #size-cells : Must be <0> - -Optional subnodes: - Subnodes are use to represent the SPI slave devices connected to the SPI - master. They follow the generic SPI bindings as outlined in spi-bus.txt. - -Example: - - spi@@44a00000 { - compatible = "adi,axi-spi-engine-1.00.a"; - reg = <0x44a00000 0x1000>; - interrupts = <0 56 4>; - clocks = <&clkc 15 &clkc 15>; - clock-names = "s_axi_aclk", "spi_clk"; - - #address-cells = <1>; - #size-cells = <0>; - - /* SPI devices */ - }; diff --git a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml new file mode 100644 index 00000000000000..d48faa42d025b0 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/adi,axi-spi-engine.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AXI SPI Engine Controller + +description: | + The AXI SPI Engine controller is part of the SPI Engine framework[1] and + allows memory mapped access to the SPI Engine control bus. This allows it + to be used as a general purpose software driven SPI controller as well as + some optional advanced acceleration and offloading capabilities. + + [1] https://wiki.analog.com/resources/fpga/peripherals/spi_engine + +maintainers: + - Michael Hennerich + - Nuno Sá + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +properties: + compatible: + const: adi,axi-spi-engine-1.00.a + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: The AXI interconnect clock. + - description: The SPI controller clock. + + clock-names: + items: + - const: s_axi_aclk + - const: spi_clk + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +unevaluatedProperties: false + +examples: + - | + spi@44a00000 { + compatible = "adi,axi-spi-engine-1.00.a"; + reg = <0x44a00000 0x1000>; + interrupts = <0 56 4>; + clocks = <&clkc 15>, <&clkc 15>; + clock-names = "s_axi_aclk", "spi_clk"; + + #address-cells = <1>; + #size-cells = <0>; + + /* SPI devices */ + }; From b7674b690472990ab8d1ec13f623435a344ef30f Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 7 Mar 2024 15:22:57 -0600 Subject: [PATCH 10/22] dt-bindings: spi: add copy of adi,adi-spi-engine.yaml This adds a copy of the mainline DT bindings for the AXI SPI Engine with the adi-ex vendor prefix for use in the ADI tree. Signed-off-by: David Lechner --- .../bindings/spi/adi-ex,axi-spi-engine.yaml | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/adi-ex,axi-spi-engine.yaml diff --git a/Documentation/devicetree/bindings/spi/adi-ex,axi-spi-engine.yaml b/Documentation/devicetree/bindings/spi/adi-ex,axi-spi-engine.yaml new file mode 100644 index 00000000000000..c51cb936a64aaf --- /dev/null +++ b/Documentation/devicetree/bindings/spi/adi-ex,axi-spi-engine.yaml @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/adi-ex,axi-spi-engine.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AXI SPI Engine Controller + +description: | + The AXI SPI Engine controller is part of the SPI Engine framework[1] and + allows memory mapped access to the SPI Engine control bus. This allows it + to be used as a general purpose software driven SPI controller as well as + some optional advanced acceleration and offloading capabilities. + + [1] https://wiki.analog.com/resources/fpga/peripherals/spi_engine + +maintainers: + - Michael Hennerich + - Nuno Sá + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +properties: + compatible: + const: adi-ex,axi-spi-engine-1.00.a + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: The AXI interconnect clock. + - description: The SPI controller clock. + + clock-names: + items: + - const: s_axi_aclk + - const: spi_clk + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +unevaluatedProperties: false + +examples: + - | + spi@44a00000 { + compatible = "adi,axi-spi-engine-1.00.a"; + reg = <0x44a00000 0x1000>; + interrupts = <0 56 4>; + clocks = <&clkc 15>, <&clkc 15>; + clock-names = "s_axi_aclk", "spi_clk"; + + #address-cells = <1>; + #size-cells = <0>; + + /* SPI devices */ + }; From d20ea38b7dc7451c6549bfd7cdea833ea187a415 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 7 Mar 2024 15:30:10 -0600 Subject: [PATCH 11/22] spi: Add copy of upstream spi-axi-spi-engine.c This is a copy of the upstream (v6.9) spi-axi-spi-engine.c backported to the ADI tree and with the -ex suffix added to indicate that this file will have out of tree changes. Signed-off-by: David Lechner --- drivers/spi/Makefile | 1 + drivers/spi/spi-axi-spi-engine-ex.c | 685 ++++++++++++++++++++++++++++ 2 files changed, 686 insertions(+) create mode 100644 drivers/spi/spi-axi-spi-engine-ex.c diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index e245883da62456..88f59e1d13bc76 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_SPI_AT91_USART) += spi-at91-usart.o obj-$(CONFIG_SPI_ATH79) += spi-ath79.o obj-$(CONFIG_SPI_AU1550) += spi-au1550.o obj-$(CONFIG_SPI_AXI_SPI_ENGINE) += spi-axi-spi-engine.o +obj-$(CONFIG_SPI_AXI_SPI_ENGINE) += spi-axi-spi-engine-ex.o obj-$(CONFIG_SPI_BCM2835) += spi-bcm2835.o obj-$(CONFIG_SPI_BCM2835AUX) += spi-bcm2835aux.o obj-$(CONFIG_SPI_BCM63XX) += spi-bcm63xx.o diff --git a/drivers/spi/spi-axi-spi-engine-ex.c b/drivers/spi/spi-axi-spi-engine-ex.c new file mode 100644 index 00000000000000..226e40234498af --- /dev/null +++ b/drivers/spi/spi-axi-spi-engine-ex.c @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPI-Engine SPI controller driver + * Copyright 2015 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPI_ENGINE_REG_RESET 0x40 + +#define SPI_ENGINE_REG_INT_ENABLE 0x80 +#define SPI_ENGINE_REG_INT_PENDING 0x84 +#define SPI_ENGINE_REG_INT_SOURCE 0x88 + +#define SPI_ENGINE_REG_SYNC_ID 0xc0 + +#define SPI_ENGINE_REG_CMD_FIFO_ROOM 0xd0 +#define SPI_ENGINE_REG_SDO_FIFO_ROOM 0xd4 +#define SPI_ENGINE_REG_SDI_FIFO_LEVEL 0xd8 + +#define SPI_ENGINE_REG_CMD_FIFO 0xe0 +#define SPI_ENGINE_REG_SDO_DATA_FIFO 0xe4 +#define SPI_ENGINE_REG_SDI_DATA_FIFO 0xe8 +#define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK 0xec + +#define SPI_ENGINE_INT_CMD_ALMOST_EMPTY BIT(0) +#define SPI_ENGINE_INT_SDO_ALMOST_EMPTY BIT(1) +#define SPI_ENGINE_INT_SDI_ALMOST_FULL BIT(2) +#define SPI_ENGINE_INT_SYNC BIT(3) + +#define SPI_ENGINE_CONFIG_CPHA BIT(0) +#define SPI_ENGINE_CONFIG_CPOL BIT(1) +#define SPI_ENGINE_CONFIG_3WIRE BIT(2) + +#define SPI_ENGINE_INST_TRANSFER 0x0 +#define SPI_ENGINE_INST_ASSERT 0x1 +#define SPI_ENGINE_INST_WRITE 0x2 +#define SPI_ENGINE_INST_MISC 0x3 + +#define SPI_ENGINE_CMD_REG_CLK_DIV 0x0 +#define SPI_ENGINE_CMD_REG_CONFIG 0x1 +#define SPI_ENGINE_CMD_REG_XFER_BITS 0x2 + +#define SPI_ENGINE_MISC_SYNC 0x0 +#define SPI_ENGINE_MISC_SLEEP 0x1 + +#define SPI_ENGINE_TRANSFER_WRITE 0x1 +#define SPI_ENGINE_TRANSFER_READ 0x2 + +/* Arbitrary sync ID for use by host->cur_msg */ +#define AXI_SPI_ENGINE_CUR_MSG_SYNC_ID 0x1 + +#define SPI_ENGINE_CMD(inst, arg1, arg2) \ + (((inst) << 12) | ((arg1) << 8) | (arg2)) + +#define SPI_ENGINE_CMD_TRANSFER(flags, n) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_TRANSFER, (flags), (n)) +#define SPI_ENGINE_CMD_ASSERT(delay, cs) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_ASSERT, (delay), (cs)) +#define SPI_ENGINE_CMD_WRITE(reg, val) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_WRITE, (reg), (val)) +#define SPI_ENGINE_CMD_SLEEP(delay) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SLEEP, (delay)) +#define SPI_ENGINE_CMD_SYNC(id) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SYNC, (id)) + +struct spi_engine_program { + unsigned int length; + uint16_t instructions[]; +}; + +/** + * struct spi_engine_message_state - SPI engine per-message state + */ +struct spi_engine_message_state { + /** @cmd_length: Number of elements in cmd_buf array. */ + unsigned cmd_length; + /** @cmd_buf: Array of commands not yet written to CMD FIFO. */ + const uint16_t *cmd_buf; + /** @tx_xfer: Next xfer with tx_buf not yet fully written to TX FIFO. */ + struct spi_transfer *tx_xfer; + /** @tx_length: Size of tx_buf in bytes. */ + unsigned int tx_length; + /** @tx_buf: Bytes not yet written to TX FIFO. */ + const uint8_t *tx_buf; + /** @rx_xfer: Next xfer with rx_buf not yet fully written to RX FIFO. */ + struct spi_transfer *rx_xfer; + /** @rx_length: Size of tx_buf in bytes. */ + unsigned int rx_length; + /** @rx_buf: Bytes not yet written to the RX FIFO. */ + uint8_t *rx_buf; +}; + +struct spi_engine { + struct clk *clk; + struct clk *ref_clk; + + spinlock_t lock; + + void __iomem *base; + struct spi_engine_message_state msg_state; + struct completion msg_complete; + unsigned int int_enable; +}; + +static void spi_engine_program_add_cmd(struct spi_engine_program *p, + bool dry, uint16_t cmd) +{ + p->length++; + + if (!dry) + p->instructions[p->length - 1] = cmd; +} + +static unsigned int spi_engine_get_config(struct spi_device *spi) +{ + unsigned int config = 0; + + if (spi->mode & SPI_CPOL) + config |= SPI_ENGINE_CONFIG_CPOL; + if (spi->mode & SPI_CPHA) + config |= SPI_ENGINE_CONFIG_CPHA; + if (spi->mode & SPI_3WIRE) + config |= SPI_ENGINE_CONFIG_3WIRE; + + return config; +} + +static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry, + struct spi_transfer *xfer) +{ + unsigned int len; + + if (xfer->bits_per_word <= 8) + len = xfer->len; + else if (xfer->bits_per_word <= 16) + len = xfer->len / 2; + else + len = xfer->len / 4; + + while (len) { + unsigned int n = min(len, 256U); + unsigned int flags = 0; + + if (xfer->tx_buf) + flags |= SPI_ENGINE_TRANSFER_WRITE; + if (xfer->rx_buf) + flags |= SPI_ENGINE_TRANSFER_READ; + + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_TRANSFER(flags, n - 1)); + len -= n; + } +} + +static void spi_engine_gen_sleep(struct spi_engine_program *p, bool dry, + int delay_ns, u32 sclk_hz) +{ + unsigned int t; + + /* negative delay indicates error, e.g. from spi_delay_to_ns() */ + if (delay_ns <= 0) + return; + + /* rounding down since executing the instruction adds a couple of ticks delay */ + t = DIV_ROUND_DOWN_ULL((u64)delay_ns * sclk_hz, NSEC_PER_SEC); + while (t) { + unsigned int n = min(t, 256U); + + spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_SLEEP(n - 1)); + t -= n; + } +} + +static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry, + struct spi_device *spi, bool assert) +{ + unsigned int mask = 0xff; + + if (assert) + mask ^= BIT(spi_get_chipselect(spi, 0)); + + spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_ASSERT(0, mask)); +} + +/* + * Performs precompile steps on the message. + * + * The SPI core does most of the message/transfer validation and filling in + * fields for us via __spi_validate(). This fixes up anything remaining not + * done there. + * + * NB: This is separate from spi_engine_compile_message() because the latter + * is called twice and would otherwise result in double-evaluation. + */ +static void spi_engine_precompile_message(struct spi_message *msg) +{ + unsigned int clk_div, max_hz = msg->spi->controller->max_speed_hz; + struct spi_transfer *xfer; + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + clk_div = DIV_ROUND_UP(max_hz, xfer->speed_hz); + xfer->effective_speed_hz = max_hz / min(clk_div, 256U); + } +} + +static void spi_engine_compile_message(struct spi_message *msg, bool dry, + struct spi_engine_program *p) +{ + struct spi_device *spi = msg->spi; + struct spi_controller *host = spi->controller; + struct spi_transfer *xfer; + int clk_div, new_clk_div; + bool keep_cs = false; + u8 bits_per_word = 0; + + clk_div = 1; + + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CONFIG, + spi_engine_get_config(spi))); + + xfer = list_first_entry(&msg->transfers, struct spi_transfer, transfer_list); + spi_engine_gen_cs(p, dry, spi, !xfer->cs_off); + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + new_clk_div = host->max_speed_hz / xfer->effective_speed_hz; + if (new_clk_div != clk_div) { + clk_div = new_clk_div; + /* actual divider used is register value + 1 */ + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV, + clk_div - 1)); + } + + if (bits_per_word != xfer->bits_per_word) { + bits_per_word = xfer->bits_per_word; + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_XFER_BITS, + bits_per_word)); + } + + spi_engine_gen_xfer(p, dry, xfer); + spi_engine_gen_sleep(p, dry, spi_delay_to_ns(&xfer->delay, xfer), + xfer->effective_speed_hz); + + if (xfer->cs_change) { + if (list_is_last(&xfer->transfer_list, &msg->transfers)) { + keep_cs = true; + } else { + if (!xfer->cs_off) + spi_engine_gen_cs(p, dry, spi, false); + + spi_engine_gen_sleep(p, dry, spi_delay_to_ns( + &xfer->cs_change_delay, xfer), + xfer->effective_speed_hz); + + if (!list_next_entry(xfer, transfer_list)->cs_off) + spi_engine_gen_cs(p, dry, spi, true); + } + } else if (!list_is_last(&xfer->transfer_list, &msg->transfers) && + xfer->cs_off != list_next_entry(xfer, transfer_list)->cs_off) { + spi_engine_gen_cs(p, dry, spi, xfer->cs_off); + } + } + + if (!keep_cs) + spi_engine_gen_cs(p, dry, spi, false); + + /* + * Restore clockdiv to default so that future gen_sleep commands don't + * have to be aware of the current register state. + */ + if (clk_div != 1) + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV, 0)); +} + +static void spi_engine_xfer_next(struct spi_message *msg, + struct spi_transfer **_xfer) +{ + struct spi_transfer *xfer = *_xfer; + + if (!xfer) { + xfer = list_first_entry(&msg->transfers, + struct spi_transfer, transfer_list); + } else if (list_is_last(&xfer->transfer_list, &msg->transfers)) { + xfer = NULL; + } else { + xfer = list_next_entry(xfer, transfer_list); + } + + *_xfer = xfer; +} + +static void spi_engine_tx_next(struct spi_message *msg) +{ + struct spi_engine_message_state *st = msg->state; + struct spi_transfer *xfer = st->tx_xfer; + + do { + spi_engine_xfer_next(msg, &xfer); + } while (xfer && !xfer->tx_buf); + + st->tx_xfer = xfer; + if (xfer) { + st->tx_length = xfer->len; + st->tx_buf = xfer->tx_buf; + } else { + st->tx_buf = NULL; + } +} + +static void spi_engine_rx_next(struct spi_message *msg) +{ + struct spi_engine_message_state *st = msg->state; + struct spi_transfer *xfer = st->rx_xfer; + + do { + spi_engine_xfer_next(msg, &xfer); + } while (xfer && !xfer->rx_buf); + + st->rx_xfer = xfer; + if (xfer) { + st->rx_length = xfer->len; + st->rx_buf = xfer->rx_buf; + } else { + st->rx_buf = NULL; + } +} + +static bool spi_engine_write_cmd_fifo(struct spi_engine *spi_engine, + struct spi_message *msg) +{ + void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_CMD_FIFO; + struct spi_engine_message_state *st = msg->state; + unsigned int n, m, i; + const uint16_t *buf; + + n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_CMD_FIFO_ROOM); + while (n && st->cmd_length) { + m = min(n, st->cmd_length); + buf = st->cmd_buf; + for (i = 0; i < m; i++) + writel_relaxed(buf[i], addr); + st->cmd_buf += m; + st->cmd_length -= m; + n -= m; + } + + return st->cmd_length != 0; +} + +static bool spi_engine_write_tx_fifo(struct spi_engine *spi_engine, + struct spi_message *msg) +{ + void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDO_DATA_FIFO; + struct spi_engine_message_state *st = msg->state; + unsigned int n, m, i; + + n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDO_FIFO_ROOM); + while (n && st->tx_length) { + if (st->tx_xfer->bits_per_word <= 8) { + const u8 *buf = st->tx_buf; + + m = min(n, st->tx_length); + for (i = 0; i < m; i++) + writel_relaxed(buf[i], addr); + st->tx_buf += m; + st->tx_length -= m; + } else if (st->tx_xfer->bits_per_word <= 16) { + const u16 *buf = (const u16 *)st->tx_buf; + + m = min(n, st->tx_length / 2); + for (i = 0; i < m; i++) + writel_relaxed(buf[i], addr); + st->tx_buf += m * 2; + st->tx_length -= m * 2; + } else { + const u32 *buf = (const u32 *)st->tx_buf; + + m = min(n, st->tx_length / 4); + for (i = 0; i < m; i++) + writel_relaxed(buf[i], addr); + st->tx_buf += m * 4; + st->tx_length -= m * 4; + } + n -= m; + if (st->tx_length == 0) + spi_engine_tx_next(msg); + } + + return st->tx_length != 0; +} + +static bool spi_engine_read_rx_fifo(struct spi_engine *spi_engine, + struct spi_message *msg) +{ + void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDI_DATA_FIFO; + struct spi_engine_message_state *st = msg->state; + unsigned int n, m, i; + + n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDI_FIFO_LEVEL); + while (n && st->rx_length) { + if (st->rx_xfer->bits_per_word <= 8) { + u8 *buf = st->rx_buf; + + m = min(n, st->rx_length); + for (i = 0; i < m; i++) + buf[i] = readl_relaxed(addr); + st->rx_buf += m; + st->rx_length -= m; + } else if (st->rx_xfer->bits_per_word <= 16) { + u16 *buf = (u16 *)st->rx_buf; + + m = min(n, st->rx_length / 2); + for (i = 0; i < m; i++) + buf[i] = readl_relaxed(addr); + st->rx_buf += m * 2; + st->rx_length -= m * 2; + } else { + u32 *buf = (u32 *)st->rx_buf; + + m = min(n, st->rx_length / 4); + for (i = 0; i < m; i++) + buf[i] = readl_relaxed(addr); + st->rx_buf += m * 4; + st->rx_length -= m * 4; + } + n -= m; + if (st->rx_length == 0) + spi_engine_rx_next(msg); + } + + return st->rx_length != 0; +} + +static irqreturn_t spi_engine_irq(int irq, void *devid) +{ + struct spi_controller *host = devid; + struct spi_message *msg = host->cur_msg; + struct spi_engine *spi_engine = spi_controller_get_devdata(host); + unsigned int disable_int = 0; + unsigned int pending; + int completed_id = -1; + + pending = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + + if (pending & SPI_ENGINE_INT_SYNC) { + writel_relaxed(SPI_ENGINE_INT_SYNC, + spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + completed_id = readl_relaxed( + spi_engine->base + SPI_ENGINE_REG_SYNC_ID); + } + + spin_lock(&spi_engine->lock); + + if (pending & SPI_ENGINE_INT_CMD_ALMOST_EMPTY) { + if (!spi_engine_write_cmd_fifo(spi_engine, msg)) + disable_int |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY; + } + + if (pending & SPI_ENGINE_INT_SDO_ALMOST_EMPTY) { + if (!spi_engine_write_tx_fifo(spi_engine, msg)) + disable_int |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY; + } + + if (pending & (SPI_ENGINE_INT_SDI_ALMOST_FULL | SPI_ENGINE_INT_SYNC)) { + if (!spi_engine_read_rx_fifo(spi_engine, msg)) + disable_int |= SPI_ENGINE_INT_SDI_ALMOST_FULL; + } + + if (pending & SPI_ENGINE_INT_SYNC && msg) { + if (completed_id == AXI_SPI_ENGINE_CUR_MSG_SYNC_ID) { + msg->status = 0; + msg->actual_length = msg->frame_length; + complete(&spi_engine->msg_complete); + disable_int |= SPI_ENGINE_INT_SYNC; + } + } + + if (disable_int) { + spi_engine->int_enable &= ~disable_int; + writel_relaxed(spi_engine->int_enable, + spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + } + + spin_unlock(&spi_engine->lock); + + return IRQ_HANDLED; +} + +static int spi_engine_optimize_message(struct spi_message *msg) +{ + struct spi_engine_program p_dry, *p; + + spi_engine_precompile_message(msg); + + p_dry.length = 0; + spi_engine_compile_message(msg, true, &p_dry); + + p = kzalloc(struct_size(p, instructions, p_dry.length + 1), GFP_KERNEL); + if (!p) + return -ENOMEM; + + spi_engine_compile_message(msg, false, p); + + spi_engine_program_add_cmd(p, false, SPI_ENGINE_CMD_SYNC( + AXI_SPI_ENGINE_CUR_MSG_SYNC_ID)); + + msg->opt_state = p; + + return 0; +} + +static int spi_engine_unoptimize_message(struct spi_message *msg) +{ + kfree(msg->opt_state); + + return 0; +} + +static int spi_engine_transfer_one_message(struct spi_controller *host, + struct spi_message *msg) +{ + struct spi_engine *spi_engine = spi_controller_get_devdata(host); + struct spi_engine_message_state *st = &spi_engine->msg_state; + struct spi_engine_program *p = msg->opt_state; + unsigned int int_enable = 0; + unsigned long flags; + + /* reinitialize message state for this transfer */ + memset(st, 0, sizeof(*st)); + st->cmd_buf = p->instructions; + st->cmd_length = p->length; + msg->state = st; + + reinit_completion(&spi_engine->msg_complete); + + spin_lock_irqsave(&spi_engine->lock, flags); + + if (spi_engine_write_cmd_fifo(spi_engine, msg)) + int_enable |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY; + + spi_engine_tx_next(msg); + if (spi_engine_write_tx_fifo(spi_engine, msg)) + int_enable |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY; + + spi_engine_rx_next(msg); + if (st->rx_length != 0) + int_enable |= SPI_ENGINE_INT_SDI_ALMOST_FULL; + + int_enable |= SPI_ENGINE_INT_SYNC; + + writel_relaxed(int_enable, + spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + spi_engine->int_enable = int_enable; + spin_unlock_irqrestore(&spi_engine->lock, flags); + + if (!wait_for_completion_timeout(&spi_engine->msg_complete, + msecs_to_jiffies(5000))) { + dev_err(&host->dev, + "Timeout occurred while waiting for transfer to complete. Hardware is probably broken.\n"); + msg->status = -ETIMEDOUT; + } + + spi_finalize_current_message(host); + + return msg->status; +} + +static void spi_engine_release_hw(void *p) +{ + struct spi_engine *spi_engine = p; + + writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + writel_relaxed(0x01, spi_engine->base + SPI_ENGINE_REG_RESET); +} + +static int spi_engine_probe(struct platform_device *pdev) +{ + struct spi_engine *spi_engine; + struct spi_controller *host; + unsigned int version; + int irq; + int ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + host = devm_spi_alloc_master(&pdev->dev, sizeof(*spi_engine)); + if (!host) + return -ENOMEM; + + spi_engine = spi_controller_get_devdata(host); + + spin_lock_init(&spi_engine->lock); + init_completion(&spi_engine->msg_complete); + + spi_engine->clk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk"); + if (IS_ERR(spi_engine->clk)) + return PTR_ERR(spi_engine->clk); + + spi_engine->ref_clk = devm_clk_get_enabled(&pdev->dev, "spi_clk"); + if (IS_ERR(spi_engine->ref_clk)) + return PTR_ERR(spi_engine->ref_clk); + + spi_engine->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(spi_engine->base)) + return PTR_ERR(spi_engine->base); + + version = readl(spi_engine->base + ADI_AXI_REG_VERSION); + if (ADI_AXI_PCORE_VER_MAJOR(version) != 1) { + dev_err(&pdev->dev, "Unsupported peripheral version %u.%u.%c\n", + ADI_AXI_PCORE_VER_MAJOR(version), + ADI_AXI_PCORE_VER_MINOR(version), + ADI_AXI_PCORE_VER_PATCH(version)); + return -ENODEV; + } + + writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_RESET); + writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + + ret = devm_add_action_or_reset(&pdev->dev, spi_engine_release_hw, + spi_engine); + if (ret) + return ret; + + ret = devm_request_irq(&pdev->dev, irq, spi_engine_irq, 0, pdev->name, + host); + if (ret) + return ret; + + host->dev.of_node = pdev->dev.of_node; + host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_3WIRE; + host->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); + host->max_speed_hz = clk_get_rate(spi_engine->ref_clk) / 2; + host->transfer_one_message = spi_engine_transfer_one_message; + host->optimize_message = spi_engine_optimize_message; + host->unoptimize_message = spi_engine_unoptimize_message; + host->num_chipselect = 8; + + if (host->max_speed_hz == 0) + return dev_err_probe(&pdev->dev, -EINVAL, "spi_clk rate is 0"); + + ret = devm_spi_register_controller(&pdev->dev, host); + if (ret) + return ret; + + platform_set_drvdata(pdev, host); + + return 0; +} + +static const struct of_device_id spi_engine_match_table[] = { + { .compatible = "adi-ex,axi-spi-engine-1.00.a" }, + { }, +}; +MODULE_DEVICE_TABLE(of, spi_engine_match_table); + +static struct platform_driver spi_engine_driver = { + .probe = spi_engine_probe, + .driver = { + .name = "spi-engine-ex", + .of_match_table = spi_engine_match_table, + }, +}; +module_platform_driver(spi_engine_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices SPI engine peripheral driver"); +MODULE_LICENSE("GPL"); From 2d0a1949d8a2bf3d0f8d4ee87fec0ce95fd7b05b Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 7 Mar 2024 16:17:00 -0600 Subject: [PATCH 12/22] spi: axi-spi-engine-ex: port offload functions This ports the existing out-of-tree offload functions from spi-axi-spi-engine to spi-axi-spi-engine-ex. API is the same. Function names contain "ex" to avoid conflicts with the existing driver. Signed-off-by: David Lechner --- drivers/spi/spi-axi-spi-engine-ex.c | 93 +++++++++++++++++++++++++++++ include/linux/spi/spi-engine-ex.h | 40 +++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 include/linux/spi/spi-engine-ex.h diff --git a/drivers/spi/spi-axi-spi-engine-ex.c b/drivers/spi/spi-axi-spi-engine-ex.c index 226e40234498af..8c19c11d644376 100644 --- a/drivers/spi/spi-axi-spi-engine-ex.c +++ b/drivers/spi/spi-axi-spi-engine-ex.c @@ -33,11 +33,20 @@ #define SPI_ENGINE_REG_SDI_DATA_FIFO 0xe8 #define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK 0xec +#define SPI_ENGINE_REG_OFFLOAD_CTRL(x) (0x100 + 0x20 * (x)) +#define SPI_ENGINE_REG_OFFLOAD_STATUS(x) (0x104 + 0x20 * (x)) +#define SPI_ENGINE_REG_OFFLOAD_RESET(x) (0x108 + 0x20 * (x)) +#define SPI_ENGINE_REG_OFFLOAD_CMD_MEM(x) (0x110 + 0x20 * (x)) +#define SPI_ENGINE_REG_OFFLOAD_SDO_MEM(x) (0x114 + 0x20 * (x)) + #define SPI_ENGINE_INT_CMD_ALMOST_EMPTY BIT(0) #define SPI_ENGINE_INT_SDO_ALMOST_EMPTY BIT(1) #define SPI_ENGINE_INT_SDI_ALMOST_FULL BIT(2) #define SPI_ENGINE_INT_SYNC BIT(3) +#define SPI_ENGINE_OFFLOAD_CTRL_ENABLE BIT(0) +#define SPI_ENGINE_OFFLOAD_STATUS_ENABLED BIT(0) + #define SPI_ENGINE_CONFIG_CPHA BIT(0) #define SPI_ENGINE_CONFIG_CPOL BIT(1) #define SPI_ENGINE_CONFIG_3WIRE BIT(2) @@ -286,6 +295,90 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry, SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV, 0)); } +bool spi_engine_ex_offload_supported(struct spi_device *spi) +{ + if (strcmp(spi->controller->dev.parent->driver->name, "spi-engine-ex") != 0) + return false; + + return true; +} +EXPORT_SYMBOL_GPL(spi_engine_ex_offload_supported); + +void spi_engine_ex_offload_enable(struct spi_device *spi, bool enable) +{ + struct spi_controller *host = spi->controller; + struct spi_engine *spi_engine = spi_controller_get_devdata(host); + unsigned int reg; + + reg = readl(spi_engine->base + SPI_ENGINE_REG_OFFLOAD_CTRL(0)); + + if (enable) + reg |= SPI_ENGINE_OFFLOAD_CTRL_ENABLE; + else + reg &= ~SPI_ENGINE_OFFLOAD_CTRL_ENABLE; + + writel(reg, spi_engine->base + SPI_ENGINE_REG_OFFLOAD_CTRL(0)); +} +EXPORT_SYMBOL_GPL(spi_engine_ex_offload_enable); + +int spi_engine_ex_offload_load_msg(struct spi_device *spi, + struct spi_message *msg) +{ + struct spi_controller *host = spi->controller; + struct spi_engine *spi_engine = spi_controller_get_devdata(host); + struct spi_engine_program *p = msg->opt_state; + struct spi_transfer *xfer; + void __iomem *cmd_addr; + void __iomem *sdo_addr; + unsigned int i; + + /* + * Ensure that __spi_validate_msg() has been run on the msg and that + * msg->opt_state contains the compiled program. + */ + if (!msg->pre_optimized) { + dev_err(&spi->dev, "requires optimized message\n"); + return -EINVAL; + } + + /* clear the offload FIFO memories */ + writel(1, spi_engine->base + SPI_ENGINE_REG_OFFLOAD_RESET(0)); + writel(0, spi_engine->base + SPI_ENGINE_REG_OFFLOAD_RESET(0)); + + cmd_addr = spi_engine->base + SPI_ENGINE_REG_OFFLOAD_CMD_MEM(0); + sdo_addr = spi_engine->base + SPI_ENGINE_REG_OFFLOAD_SDO_MEM(0); + + /* TODO: validate that we don't exceed the offload CMD FIFO size */ + for (i = 0; i < p->length; i++) + writel(p->instructions[i], cmd_addr); + + /* TODO: validate that we don't exceed the offload SDO FIFO size */ + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + if (!xfer->tx_buf) + continue; + + if (xfer->bits_per_word <= 8) { + const u8 *buf = xfer->tx_buf; + + for (i = 0; i < xfer->len; i++) + writel_relaxed(buf[i], sdo_addr); + } else if (xfer->bits_per_word <= 16) { + const u16 *buf = (const u16 *)xfer->tx_buf; + + for (i = 0; i < xfer->len / 2; i++) + writel_relaxed(buf[i], sdo_addr); + } else { + const u32 *buf = (const u32 *)xfer->tx_buf; + + for (i = 0; i < xfer->len / 4; i++) + writel_relaxed(buf[i], sdo_addr); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(spi_engine_ex_offload_load_msg); + static void spi_engine_xfer_next(struct spi_message *msg, struct spi_transfer **_xfer) { diff --git a/include/linux/spi/spi-engine-ex.h b/include/linux/spi/spi-engine-ex.h new file mode 100644 index 00000000000000..364a1589d42628 --- /dev/null +++ b/include/linux/spi/spi-engine-ex.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * SPI-Engine SPI controller driver + * Copyright 2015 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#ifndef _INCLUDE_LINUX_SPI_SPI_ENGINE_EX_H_ +#define _INCLUDE_LINUX_SPI_SPI_ENGINE_EX_H_ + +struct spi_device; +struct spi_message; + +#ifdef CONFIG_SPI_AXI_SPI_ENGINE + +bool spi_engine_ex_offload_supported(struct spi_device *spi); +void spi_engine_ex_offload_enable(struct spi_device *spi, bool enable); +int spi_engine_ex_offload_load_msg(struct spi_device *spi, + struct spi_message *msg); + +#else + +static inline bool spi_engine_ex_offload_supported(struct spi_device *spi) +{ + return false; +} + +static inline void spi_engine_ex_offload_enable(struct spi_device *spi, bool enable) +{ +} + +static inline int spi_engine_ex_offload_load_msg(struct spi_device *spi, + struct spi_message *msg) +{ + return -ENODEV; +} + +#endif + +#endif From c885a9aa4abf4c78ebe403ec1f11ce9b8c531b7b Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 12 Mar 2024 12:20:32 -0500 Subject: [PATCH 13/22] Revert "iio: adc: ad_pulsar: initial support for turbo gpio" This reverts commit c86eef0a4eba3202dbf6586070a29ea241ef1d3e. This functionality will be replaced by a backport of the upstream ad7944 driver, so we no longer need this. Signed-off-by: David Lechner --- drivers/iio/adc/ad_pulsar.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/iio/adc/ad_pulsar.c b/drivers/iio/adc/ad_pulsar.c index 638a1823ee11c3..52fc181acbaa5c 100644 --- a/drivers/iio/adc/ad_pulsar.c +++ b/drivers/iio/adc/ad_pulsar.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -328,7 +327,6 @@ struct ad_pulsar_adc { const struct ad_pulsar_chip_info *info; struct iio_chan_spec *channels; struct spi_transfer *seq_xfer; - struct gpio_desc *turbo_gpio; unsigned int cfg; unsigned long ref_clk_rate; struct pwm_device *cnv; @@ -913,13 +911,6 @@ static int ad_pulsar_probe(struct spi_device *spi) if (ret) return ret; - /* REVISIT: for now, turbo mode is always enabled */ - adc->turbo_gpio = devm_gpiod_get_optional(&spi->dev, "turbo", - GPIOD_OUT_HIGH); - if (IS_ERR(adc->turbo_gpio)) - return dev_err_probe(&spi->dev, PTR_ERR(adc->turbo_gpio), - "Failed to get turbo GPIO\n"); - return devm_iio_device_register(&spi->dev, indio_dev); } From 5dd784941e7e97af778c1cd2ba793871408728cc Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 12 Mar 2024 11:20:41 -0500 Subject: [PATCH 14/22] Revert "iio: adc: ad_pulsar: add ad7944, ad7985, ad7986" This reverts commit 9168247570a435356133ebac62c0bedb9e334e5a. We will be replacing this functionality with the a backport of the mainline ad7944 driver. Signed-off-by: David Lechner --- drivers/iio/adc/ad_pulsar.c | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/drivers/iio/adc/ad_pulsar.c b/drivers/iio/adc/ad_pulsar.c index 52fc181acbaa5c..3fe0af2f493586 100644 --- a/drivers/iio/adc/ad_pulsar.c +++ b/drivers/iio/adc/ad_pulsar.c @@ -135,24 +135,6 @@ static const struct ad_pulsar_chip_info ad7988_1_chip_info = { .sclk_rate = 80000000 }; -static const struct ad_pulsar_chip_info ad7986_chip_info = { - .name = "ad7986", - .input_type = DIFFERENTIAL, - .max_rate = 2000000, - .resolution = 18, - .num_channels = 1, - .sclk_rate = 80000000 -}; - -static const struct ad_pulsar_chip_info ad7985_chip_info = { - .name = "ad7985", - .input_type = DIFFERENTIAL, - .max_rate = 2500000, - .resolution = 16, - .num_channels = 1, - .sclk_rate = 80000000 -}; - static const struct ad_pulsar_chip_info ad7984_chip_info = { .name = "ad7984", .input_type = DIFFERENTIAL, @@ -209,15 +191,6 @@ static const struct ad_pulsar_chip_info ad7946_chip_info = { .sclk_rate = 40000000 }; -static const struct ad_pulsar_chip_info ad7944_chip_info = { - .name = "ad7944", - .input_type = SINGLE_ENDED, - .max_rate = 25000000, - .resolution = 14, - .num_channels = 1, - .sclk_rate = 40000000 -}; - static const struct ad_pulsar_chip_info ad7942_chip_info = { .name = "ad7942", .input_type = SINGLE_ENDED, @@ -917,15 +890,12 @@ static int ad_pulsar_probe(struct spi_device *spi) static const struct of_device_id ad_pulsar_of_match[] = { { .compatible = "adi,pulsar,ad7988-5", .data = &ad7988_5_chip_info }, { .compatible = "adi,pulsar,ad7988-1", .data = &ad7988_1_chip_info }, - { .compatible = "adi,pulsar,ad7986", .data = &ad7986_chip_info }, - { .compatible = "adi,pulsar,ad7985", .data = &ad7985_chip_info }, { .compatible = "adi,pulsar,ad7984", .data = &ad7984_chip_info }, { .compatible = "adi,pulsar,ad7983", .data = &ad7983_chip_info }, { .compatible = "adi,pulsar,ad7982", .data = &ad7982_chip_info }, { .compatible = "adi,pulsar,ad7980", .data = &ad7980_chip_info }, { .compatible = "adi,pulsar,ad7949", .data = &ad7949_chip_info }, { .compatible = "adi,pulsar,ad7946", .data = &ad7946_chip_info }, - { .compatible = "adi,pulsar,ad7944", .data = &ad7944_chip_info }, { .compatible = "adi,pulsar,ad7942", .data = &ad7942_chip_info }, { .compatible = "adi,pulsar,ad7699", .data = &ad7699_chip_info }, { .compatible = "adi,pulsar,ad7693", .data = &ad7693_chip_info }, @@ -944,15 +914,12 @@ MODULE_DEVICE_TABLE(of, ad_pulsar_of_match); static const struct spi_device_id ad_pulsar_spi_id[] = { { "adi,pulsar,ad7988-5", (kernel_ulong_t)&ad7988_5_chip_info }, { "adi,pulsar,ad7988-1", (kernel_ulong_t)&ad7988_1_chip_info }, - { "adi,pulsar,ad7986", (kernel_ulong_t)&ad7986_chip_info }, - { "adi,pulsar,ad7985", (kernel_ulong_t)&ad7985_chip_info }, { "adi,pulsar,ad7984", (kernel_ulong_t)&ad7984_chip_info }, { "adi,pulsar,ad7983", (kernel_ulong_t)&ad7983_chip_info }, { "adi,pulsar,ad7982", (kernel_ulong_t)&ad7982_chip_info }, { "adi,pulsar,ad7980", (kernel_ulong_t)&ad7980_chip_info }, { "adi,pulsar,ad7949", (kernel_ulong_t)&ad7949_chip_info }, { "adi,pulsar,ad7946", (kernel_ulong_t)&ad7946_chip_info }, - { "adi,pulsar,ad7944", (kernel_ulong_t)&ad7944_chip_info }, { "adi,pulsar,ad7942", (kernel_ulong_t)&ad7942_chip_info }, { "adi,pulsar,ad7699", (kernel_ulong_t)&ad7699_chip_info }, { "adi,pulsar,ad7693", (kernel_ulong_t)&ad7693_chip_info }, From 5ffc0ac17ba5d88f6ba1e096d4c0e1e297182aee Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 12 Mar 2024 11:22:27 -0500 Subject: [PATCH 15/22] Revert "dt-bindings: iio: adc: adi,pulsar: add ad7944, ad7985, ad7986" This reverts commit b3a4218081e5fd1ed1117e33257abcf5252c3bcf. We will be replacing this with a backport of the upstream adi,ad7944 bindings. Signed-off-by: David Lechner --- .../bindings/iio/adc/adi,pulsar.yaml | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,pulsar.yaml b/Documentation/devicetree/bindings/iio/adc/adi,pulsar.yaml index 7a2d54af5326eb..f8161f6c303e35 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,pulsar.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,pulsar.yaml @@ -13,14 +13,11 @@ description: | Analog Devices PulSAR family Analog to Digital Converters with SPI support https://www.analog.com/en/products/ad7988-5.html https://www.analog.com/en/products/ad7988-1.html - https://www.analog.com/en/products/ad7986.html - https://www.analog.com/en/products/ad7985.html https://www.analog.com/en/products/ad7984.html https://www.analog.com/en/products/ad7983.html https://www.analog.com/en/products/ad7982.html https://www.analog.com/en/products/ad7980.html https://www.analog.com/en/products/ad7949.html - https://www.analog.com/en/products/ad7944.html https://www.analog.com/en/products/ad7946.html https://www.analog.com/en/products/ad7942.html https://www.analog.com/en/products/ad7699.html @@ -48,15 +45,12 @@ properties: enum: - adi,pulsar,ad7988-5 - adi,pulsar,ad7988-1 - - adi,pulsar,ad7986 - - adi,pulsar,ad7985 - adi,pulsar,ad7984 - adi,pulsar,ad7983 - adi,pulsar,ad7982 - adi,pulsar,ad7980 - adi,pulsar,ad7949 - adi,pulsar,ad7946 - - adi,pulsar,ad7944 - adi,pulsar,ad7942 - adi,pulsar,ad7699 - adi,pulsar,ad7693 @@ -110,12 +104,6 @@ properties: vref-supply: description: Voltage regulator for the reference voltage. - turbo-gpios: - maxItems: 1 - description: - GPIO used to enable the turbo mode. This mode is used to increase the - sampling rate of the ADC at the expense of power consumption. - patternProperties: "^channel@([0-8])$": type: object @@ -177,18 +165,6 @@ allOf: required: - adi,single-channel - - if: - properties: - compatible: - contains: - enum: - - adi,pulsar,ad7986 - - adi,pulsar,ad7985 - - adi,pulsar,ad7944 - then: - required: - - turbo-gpios - required: - compatible - reg From a9131064cffdb20ca787b8a3f79ae974883dc135 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 4 Mar 2024 13:48:46 -0600 Subject: [PATCH 16/22] dt-bindings: iio: adc: add ad7944 ADCs This adds a new binding for the Analog Devices, Inc. AD7944, AD7985, and AD7986 ADCs. Reviewed-by: Rob Herring Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240304-ad7944-mainline-v5-1-f0a38cea8901@baylibre.com Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ad7944.yaml | 213 ++++++++++++++++++ MAINTAINERS | 8 + 2 files changed, 221 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml new file mode 100644 index 00000000000000..d17d184842d329 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml @@ -0,0 +1,213 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad7944.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices PulSAR LFCSP Analog to Digital Converters + +maintainers: + - Michael Hennerich + - Nuno Sá + +description: | + A family of pin-compatible single channel differential analog to digital + converters with SPI support in a LFCSP package. + + * https://www.analog.com/en/products/ad7944.html + * https://www.analog.com/en/products/ad7985.html + * https://www.analog.com/en/products/ad7986.html + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - adi,ad7944 + - adi,ad7985 + - adi,ad7986 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 111111111 + + spi-cpol: true + spi-cpha: true + + adi,spi-mode: + $ref: /schemas/types.yaml#/definitions/string + enum: [ single, chain ] + description: | + This property indicates the SPI wiring configuration. + + When this property is omitted, it is assumed that the device is using what + the datasheet calls "4-wire mode". This is the conventional SPI mode used + when there are multiple devices on the same bus. In this mode, the CNV + line is used to initiate the conversion and the SDI line is connected to + CS on the SPI controller. + + When this property is present, it indicates that the device is using one + of the following alternative wiring configurations: + + * single: The datasheet calls this "3-wire mode". (NOTE: The datasheet's + definition of 3-wire mode is NOT at all related to the standard + spi-3wire property!) This mode is often used when the ADC is the only + device on the bus. In this mode, SDI is tied to VIO, and the CNV line + can be connected to the CS line of the SPI controller or to a GPIO, in + which case the CS line of the controller is unused. + * chain: The datasheet calls this "chain mode". This mode is used to save + on wiring when multiple ADCs are used. In this mode, the SDI line of + one chip is tied to the SDO of the next chip in the chain and the SDI of + the last chip in the chain is tied to GND. Only the first chip in the + chain is connected to the SPI bus. The CNV line of all chips are tied + together. The CS line of the SPI controller can be used as the CNV line + only if it is active high. + + '#daisy-chained-devices': true + + avdd-supply: + description: A 2.5V supply that powers the analog circuitry. + + dvdd-supply: + description: A 2.5V supply that powers the digital circuitry. + + vio-supply: + description: + A 1.8V to 2.7V supply for the digital inputs and outputs. + + bvdd-supply: + description: + A voltage supply for the buffered power. When using an external reference + without an internal buffer (PDREF high, REFIN low), this should be + connected to the same supply as ref-supply. Otherwise, when using an + internal reference or an external reference with an internal buffer, this + is connected to a 5V supply. + + ref-supply: + description: + Voltage regulator for the external reference voltage (REF). This property + is omitted when using an internal reference. + + refin-supply: + description: + Voltage regulator for the reference buffer input (REFIN). When using an + external buffer with internal reference, this should be connected to a + 1.2V external reference voltage supply. Otherwise, this property is + omitted. + + cnv-gpios: + description: + The Convert Input (CNV). This input has multiple functions. It initiates + the conversions and selects the SPI mode of the device (chain or CS). In + 'single' mode, this property is omitted if the CNV pin is connected to the + CS line of the SPI controller. + maxItems: 1 + + turbo-gpios: + description: + GPIO connected to the TURBO line. If omitted, it is assumed that the TURBO + line is hard-wired and the state is determined by the adi,always-turbo + property. + maxItems: 1 + + adi,always-turbo: + type: boolean + description: + When present, this property indicates that the TURBO line is hard-wired + and the state is always high. If neither this property nor turbo-gpios is + present, the TURBO line is assumed to be hard-wired and the state is + always low. + + interrupts: + description: + The SDO pin can also function as a busy indicator. This node should be + connected to an interrupt that is triggered when the SDO line goes low + while the SDI line is high and the CNV line is low ('single' mode) or the + SDI line is low and the CNV line is high ('multi' mode); or when the SDO + line goes high while the SDI and CNV lines are high (chain mode), + maxItems: 1 + +required: + - compatible + - reg + - avdd-supply + - dvdd-supply + - vio-supply + - bvdd-supply + +allOf: + # ref-supply and refin-supply are mutually exclusive (neither is also valid) + - if: + required: + - ref-supply + then: + properties: + refin-supply: false + - if: + required: + - refin-supply + then: + properties: + ref-supply: false + # in '4-wire' mode, cnv-gpios is required, for other modes it is optional + - if: + not: + required: + - adi,spi-mode + then: + required: + - cnv-gpios + # chain mode has lower SCLK max rate and doesn't work when TURBO is enabled + - if: + required: + - adi,spi-mode + properties: + adi,spi-mode: + const: chain + then: + properties: + spi-max-frequency: + maximum: 90909090 + adi,always-turbo: false + required: + - '#daisy-chained-devices' + else: + properties: + '#daisy-chained-devices': false + # turbo-gpios and adi,always-turbo are mutually exclusive + - if: + required: + - turbo-gpios + then: + properties: + adi,always-turbo: false + - if: + required: + - adi,always-turbo + then: + properties: + turbo-gpios: false + +unevaluatedProperties: false + +examples: + - | + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + adc@0 { + compatible = "adi,ad7944"; + reg = <0>; + spi-cpha; + spi-max-frequency = <111111111>; + avdd-supply = <&supply_2_5V>; + dvdd-supply = <&supply_2_5V>; + vio-supply = <&supply_1_8V>; + bvdd-supply = <&supply_5V>; + cnv-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>; + turbo-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 5313eda9f722df..45e2f378603f16 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -499,6 +499,14 @@ W: http://wiki.analog.com/AD7879 W: https://ez.analog.com/linux-software-drivers F: drivers/input/touchscreen/ad7879.c +AD7944 ADC DRIVER (AD7944/AD7985/AD7986) +M: Michael Hennerich +M: Nuno Sá +R: David Lechner +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml + ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR) M: Jiri Kosina S: Maintained From 55eac624139de5b9f5a3bae8f80ed735efe2c57f Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 4 Mar 2024 13:48:47 -0600 Subject: [PATCH 17/22] iio: adc: ad7944: add driver for AD7944/AD7985/AD7986 This adds a driver for the Analog Devices Inc. AD7944, AD7985, and AD7986 ADCs. These are a family of pin-compatible ADCs that can sample at rates up to 2.5 MSPS. The initial driver adds support for sampling at lower rates using the usual IIO triggered buffer and can handle all 3 possible reference voltage configurations. Signed-off-by: David Lechner Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240304-ad7944-mainline-v5-2-f0a38cea8901@baylibre.com Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad7944.c | 416 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 428 insertions(+) create mode 100644 drivers/iio/adc/ad7944.c diff --git a/MAINTAINERS b/MAINTAINERS index 45e2f378603f16..5d611cffd468e5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -506,6 +506,7 @@ R: David Lechner S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml +F: drivers/iio/adc/ad7944.c ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR) M: Jiri Kosina diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index fe415bd9803174..53699b253df3f5 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -321,6 +321,16 @@ config AD7923 To compile this driver as a module, choose M here: the module will be called ad7923. +config AD7944 + tristate "Analog Devices AD7944 and similar ADCs driver" + depends on SPI + help + Say yes here to build support for Analog Devices + AD7944, AD7985, AD7986 ADCs. + + To compile this driver as a module, choose M here: the + module will be called ad7944 + config AD7949 tristate "Analog Devices AD7949 and similar ADCs driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 7ab88ecc08b33f..61b33f58fb5b1b 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_AD7780) += ad7780.o obj-$(CONFIG_AD7791) += ad7791.o obj-$(CONFIG_AD7793) += ad7793.o obj-$(CONFIG_AD7887) += ad7887.o +obj-$(CONFIG_AD7944) += ad7944.o obj-$(CONFIG_AD7949) += ad7949.o obj-$(CONFIG_AD799X) += ad799x.o obj-$(CONFIG_ADI_AXI_ADC) += adi-axi-adc.o diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c new file mode 100644 index 00000000000000..adb007cdd2875a --- /dev/null +++ b/drivers/iio/adc/ad7944.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD7944/85/86 PulSAR ADC family driver. + * + * Copyright 2024 Analog Devices, Inc. + * Copyright 2024 BayLibre, SAS + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define AD7944_INTERNAL_REF_MV 4096 + +struct ad7944_timing_spec { + /* Normal mode max conversion time (t_{CONV}). */ + unsigned int conv_ns; + /* TURBO mode max conversion time (t_{CONV}). */ + unsigned int turbo_conv_ns; +}; + +struct ad7944_adc { + struct spi_device *spi; + /* Chip-specific timing specifications. */ + const struct ad7944_timing_spec *timing_spec; + /* GPIO connected to CNV pin. */ + struct gpio_desc *cnv; + /* Optional GPIO to enable turbo mode. */ + struct gpio_desc *turbo; + /* Indicates TURBO is hard-wired to be always enabled. */ + bool always_turbo; + /* Reference voltage (millivolts). */ + unsigned int ref_mv; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + struct { + union { + u16 u16; + u32 u32; + } raw; + u64 timestamp __aligned(8); + } sample __aligned(IIO_DMA_MINALIGN); +}; + +static const struct ad7944_timing_spec ad7944_timing_spec = { + .conv_ns = 420, + .turbo_conv_ns = 320, +}; + +static const struct ad7944_timing_spec ad7986_timing_spec = { + .conv_ns = 500, + .turbo_conv_ns = 400, +}; + +struct ad7944_chip_info { + const char *name; + const struct ad7944_timing_spec *timing_spec; + const struct iio_chan_spec channels[2]; +}; + +/* + * AD7944_DEFINE_CHIP_INFO - Define a chip info structure for a specific chip + * @_name: The name of the chip + * @_ts: The timing specification for the chip + * @_bits: The number of bits in the conversion result + * @_diff: Whether the chip is true differential or not + */ +#define AD7944_DEFINE_CHIP_INFO(_name, _ts, _bits, _diff) \ +static const struct ad7944_chip_info _name##_chip_info = { \ + .name = #_name, \ + .timing_spec = &_ts##_timing_spec, \ + .channels = { \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .differential = _diff, \ + .channel = 0, \ + .channel2 = _diff ? 1 : 0, \ + .scan_index = 0, \ + .scan_type.sign = _diff ? 's' : 'u', \ + .scan_type.realbits = _bits, \ + .scan_type.storagebits = _bits > 16 ? 32 : 16, \ + .scan_type.endianness = IIO_CPU, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + }, \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ + }, \ +} + +/* pseudo-differential with ground sense */ +AD7944_DEFINE_CHIP_INFO(ad7944, ad7944, 14, 0); +AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 16, 0); +/* fully differential */ +AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1); + +/* + * ad7944_4wire_mode_conversion - Perform a 4-wire mode conversion and acquisition + * @adc: The ADC device structure + * @chan: The channel specification + * Return: 0 on success, a negative error code on failure + * + * Upon successful return adc->sample.raw will contain the conversion result. + */ +static int ad7944_4wire_mode_conversion(struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer xfers[] = { + { + /* + * NB: can get better performance from some SPI + * controllers if we use the same bits_per_word + * in every transfer. + */ + .bits_per_word = chan->scan_type.realbits, + /* + * CS has to be high for full conversion time to avoid + * triggering the busy indication. + */ + .cs_off = 1, + .delay = { + .value = t_conv_ns, + .unit = SPI_DELAY_UNIT_NSECS, + }, + + }, + { + .rx_buf = &adc->sample.raw, + .len = BITS_TO_BYTES(chan->scan_type.storagebits), + .bits_per_word = chan->scan_type.realbits, + }, + }; + int ret; + + /* + * In 4-wire mode, the CNV line is held high for the entire conversion + * and acquisition process. + */ + gpiod_set_value_cansleep(adc->cnv, 1); + ret = spi_sync_transfer(adc->spi, xfers, ARRAY_SIZE(xfers)); + gpiod_set_value_cansleep(adc->cnv, 0); + + return ret; +} + +static int ad7944_single_conversion(struct ad7944_adc *adc, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + + ret = ad7944_4wire_mode_conversion(adc, chan); + if (ret) + return ret; + + if (chan->scan_type.storagebits > 16) + *val = adc->sample.raw.u32; + else + *val = adc->sample.raw.u16; + + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, chan->scan_type.realbits - 1); + + return IIO_VAL_INT; +} + +static int ad7944_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = ad7944_single_conversion(adc, chan, val); + iio_device_release_direct_mode(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = adc->ref_mv; + + if (chan->scan_type.sign == 's') + *val2 = chan->scan_type.realbits - 1; + else + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info ad7944_iio_info = { + .read_raw = &ad7944_read_raw, +}; + +static irqreturn_t ad7944_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7944_adc *adc = iio_priv(indio_dev); + int ret; + + ret = ad7944_4wire_mode_conversion(adc, &indio_dev->channels[0]); + if (ret) + goto out; + + iio_push_to_buffers_with_timestamp(indio_dev, &adc->sample.raw, + pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const char * const ad7944_power_supplies[] = { + "avdd", "dvdd", "bvdd", "vio" +}; + +static void ad7944_ref_disable(void *ref) +{ + regulator_disable(ref); +} + +static int ad7944_probe(struct spi_device *spi) +{ + const struct ad7944_chip_info *chip_info; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad7944_adc *adc; + bool have_refin = false; + struct regulator *ref; + int ret; + + /* + * driver currently only supports the conventional "4-wire" mode and + * not other special wiring configurations. + */ + if (device_property_present(dev, "adi,spi-mode")) + return dev_err_probe(dev, -EINVAL, + "adi,spi-mode is not currently supported\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return dev_err_probe(dev, -EINVAL, "no chip info\n"); + + adc->timing_spec = chip_info->timing_spec; + + /* + * Some chips use unusual word sizes, so check now instead of waiting + * for the first xfer. + */ + if (!spi_is_bpw_supported(spi, chip_info->channels[0].scan_type.realbits)) + return dev_err_probe(dev, -EINVAL, + "SPI host does not support %d bits per word\n", + chip_info->channels[0].scan_type.realbits); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ad7944_power_supplies), + ad7944_power_supplies); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + /* + * Sort out what is being used for the reference voltage. Options are: + * - internal reference: neither REF or REFIN is connected + * - internal reference with external buffer: REF not connected, REFIN + * is connected + * - external reference: REF is connected, REFIN is not connected + */ + + ref = devm_regulator_get_optional(dev, "ref"); + if (IS_ERR(ref)) { + if (PTR_ERR(ref) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(ref), + "failed to get REF supply\n"); + + ref = NULL; + } + + ret = devm_regulator_get_enable_optional(dev, "refin"); + if (ret == 0) + have_refin = true; + else if (ret != -ENODEV) + return dev_err_probe(dev, ret, + "failed to get and enable REFIN supply\n"); + + if (have_refin && ref) + return dev_err_probe(dev, -EINVAL, + "cannot have both refin and ref supplies\n"); + + if (ref) { + ret = regulator_enable(ref); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable REF supply\n"); + + ret = devm_add_action_or_reset(dev, ad7944_ref_disable, ref); + if (ret) + return ret; + + ret = regulator_get_voltage(ref); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to get REF voltage\n"); + + /* external reference */ + adc->ref_mv = ret / 1000; + } else { + /* internal reference */ + adc->ref_mv = AD7944_INTERNAL_REF_MV; + } + + /* + * CNV gpio is required in 4-wire mode which is the only currently + * supported mode. + */ + adc->cnv = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW); + if (IS_ERR(adc->cnv)) + return dev_err_probe(dev, PTR_ERR(adc->cnv), + "failed to get CNV GPIO\n"); + + adc->turbo = devm_gpiod_get_optional(dev, "turbo", GPIOD_OUT_LOW); + if (IS_ERR(adc->turbo)) + return dev_err_probe(dev, PTR_ERR(adc->turbo), + "failed to get TURBO GPIO\n"); + + adc->always_turbo = device_property_present(dev, "adi,always-turbo"); + + if (adc->turbo && adc->always_turbo) + return dev_err_probe(dev, -EINVAL, + "cannot have both turbo-gpios and adi,always-turbo\n"); + + indio_dev->name = chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad7944_iio_info; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = ARRAY_SIZE(chip_info->channels); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad7944_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7944_of_match[] = { + { .compatible = "adi,ad7944", .data = &ad7944_chip_info }, + { .compatible = "adi,ad7985", .data = &ad7985_chip_info }, + { .compatible = "adi,ad7986", .data = &ad7986_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7944_of_match); + +static const struct spi_device_id ad7944_spi_id[] = { + { "ad7944", (kernel_ulong_t)&ad7944_chip_info }, + { "ad7985", (kernel_ulong_t)&ad7985_chip_info }, + { "ad7986", (kernel_ulong_t)&ad7986_chip_info }, + { } + +}; +MODULE_DEVICE_TABLE(spi, ad7944_spi_id); + +static struct spi_driver ad7944_driver = { + .driver = { + .name = "ad7944", + .of_match_table = ad7944_of_match, + }, + .probe = ad7944_probe, + .id_table = ad7944_spi_id, +}; +module_spi_driver(ad7944_driver); + +MODULE_AUTHOR("David Lechner "); +MODULE_DESCRIPTION("Analog Devices AD7944 PulSAR ADC family driver"); +MODULE_LICENSE("GPL"); From fcea39942debd569016d5c8d674852b7dc9a1b72 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 29 Feb 2024 17:41:27 -0600 Subject: [PATCH 18/22] iio: adc: ad7944: Add support for "3-wire mode" This adds support for AD7944 ADCs wired in "3-wire mode". (NOTE: 3-wire is the datasheet name for this wiring configuration and has nothing to do with SPI_3WIRE.) In the 3-wire mode, the SPI controller CS line can be wired to the CNV line on the ADC and used to trigger conversions rather that using a separate GPIO line. Signed-off-by: David Lechner --- drivers/iio/adc/ad7944.c | 157 ++++++++++++++++++++++++++++++++++----- 1 file changed, 139 insertions(+), 18 deletions(-) diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c index adb007cdd2875a..d5ec6b5a41c7da 100644 --- a/drivers/iio/adc/ad7944.c +++ b/drivers/iio/adc/ad7944.c @@ -32,8 +32,25 @@ struct ad7944_timing_spec { unsigned int turbo_conv_ns; }; +enum ad7944_spi_mode { + /* datasheet calls this "4-wire mode" */ + AD7944_SPI_MODE_DEFAULT, + /* datasheet calls this "3-wire mode" (not related to SPI_3WIRE!) */ + AD7944_SPI_MODE_SINGLE, + /* datasheet calls this "chain mode" */ + AD7944_SPI_MODE_CHAIN, +}; + +/* maps adi,spi-mode property value to enum */ +static const char * const ad7944_spi_modes[] = { + [AD7944_SPI_MODE_DEFAULT] = "", + [AD7944_SPI_MODE_SINGLE] = "single", + [AD7944_SPI_MODE_CHAIN] = "chain", +}; + struct ad7944_adc { struct spi_device *spi; + enum ad7944_spi_mode spi_mode; /* Chip-specific timing specifications. */ const struct ad7944_timing_spec *timing_spec; /* GPIO connected to CNV pin. */ @@ -58,6 +75,9 @@ struct ad7944_adc { } sample __aligned(IIO_DMA_MINALIGN); }; +/* quite time before CNV rising edge */ +#define T_QUIET_NS 20 + static const struct ad7944_timing_spec ad7944_timing_spec = { .conv_ns = 420, .turbo_conv_ns = 320, @@ -110,6 +130,65 @@ AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 16, 0); /* fully differential */ AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1); +/* + * ad7944_3wire_cs_mode_conversion - Perform a 3-wire CS mode conversion and + * acquisition + * @adc: The ADC device structure + * @chan: The channel specification + * Return: 0 on success, a negative error code on failure + * + * This performs a conversion and reads data when the chip is wired in 3-wire + * mode with the CNV line on the ADC tied to the CS line on the SPI controller. + * + * Upon successful return adc->sample.raw will contain the conversion result. + */ +static int ad7944_3wire_cs_mode_conversion(struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer xfers[] = { + { + /* + * NB: can get better performance from some SPI + * controllers if we use the same bits_per_word + * in every transfer. + */ + .bits_per_word = chan->scan_type.realbits, + /* + * CS is tied to CNV and we need a low to high + * transition to start the conversion, so place CNV + * low for t_QUIET to prepare for this. + */ + .delay = { + .value = T_QUIET_NS, + .unit = SPI_DELAY_UNIT_NSECS, + }, + + }, + { + .bits_per_word = chan->scan_type.realbits, + /* + * CS has to be high for full conversion time to avoid + * triggering the busy indication. + */ + .cs_off = 1, + .delay = { + .value = t_conv_ns, + .unit = SPI_DELAY_UNIT_NSECS, + }, + }, + { + /* Then we can read the data during the acquisition phase */ + .rx_buf = &adc->sample.raw, + .len = BITS_TO_BYTES(chan->scan_type.storagebits), + .bits_per_word = chan->scan_type.realbits, + }, + }; + + return spi_sync_transfer(adc->spi, xfers, ARRAY_SIZE(xfers)); +} + /* * ad7944_4wire_mode_conversion - Perform a 4-wire mode conversion and acquisition * @adc: The ADC device structure @@ -167,9 +246,22 @@ static int ad7944_single_conversion(struct ad7944_adc *adc, { int ret; - ret = ad7944_4wire_mode_conversion(adc, chan); - if (ret) - return ret; + switch (adc->spi_mode) { + case AD7944_SPI_MODE_DEFAULT: + ret = ad7944_4wire_mode_conversion(adc, chan); + if (ret) + return ret; + + break; + case AD7944_SPI_MODE_SINGLE: + ret = ad7944_3wire_cs_mode_conversion(adc, chan); + if (ret) + return ret; + + break; + default: + return -EOPNOTSUPP; + } if (chan->scan_type.storagebits > 16) *val = adc->sample.raw.u32; @@ -230,9 +322,23 @@ static irqreturn_t ad7944_trigger_handler(int irq, void *p) struct ad7944_adc *adc = iio_priv(indio_dev); int ret; - ret = ad7944_4wire_mode_conversion(adc, &indio_dev->channels[0]); - if (ret) + switch (adc->spi_mode) { + case AD7944_SPI_MODE_DEFAULT: + ret = ad7944_4wire_mode_conversion(adc, &indio_dev->channels[0]); + if (ret) + goto out; + + break; + case AD7944_SPI_MODE_SINGLE: + ret = ad7944_3wire_cs_mode_conversion(adc, &indio_dev->channels[0]); + if (ret) + goto out; + + break; + default: + /* not supported */ goto out; + } iio_push_to_buffers_with_timestamp(indio_dev, &adc->sample.raw, pf->timestamp); @@ -260,16 +366,9 @@ static int ad7944_probe(struct spi_device *spi) struct ad7944_adc *adc; bool have_refin = false; struct regulator *ref; + const char *str_val; int ret; - /* - * driver currently only supports the conventional "4-wire" mode and - * not other special wiring configurations. - */ - if (device_property_present(dev, "adi,spi-mode")) - return dev_err_probe(dev, -EINVAL, - "adi,spi-mode is not currently supported\n"); - indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); if (!indio_dev) return -ENOMEM; @@ -283,6 +382,22 @@ static int ad7944_probe(struct spi_device *spi) adc->timing_spec = chip_info->timing_spec; + if (device_property_read_string(dev, "adi,spi-mode", &str_val) == 0) { + ret = sysfs_match_string(ad7944_spi_modes, str_val); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, + "unsupported adi,spi-mode\n"); + + adc->spi_mode = ret; + } else { + /* absence of adi,spi-mode property means default mode */ + adc->spi_mode = AD7944_SPI_MODE_DEFAULT; + } + + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) + return dev_err_probe(dev, -EINVAL, + "chain mode is not implemented\n"); + /* * Some chips use unusual word sizes, so check now instead of waiting * for the first xfer. @@ -349,15 +464,17 @@ static int ad7944_probe(struct spi_device *spi) adc->ref_mv = AD7944_INTERNAL_REF_MV; } - /* - * CNV gpio is required in 4-wire mode which is the only currently - * supported mode. - */ - adc->cnv = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW); + adc->cnv = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW); if (IS_ERR(adc->cnv)) return dev_err_probe(dev, PTR_ERR(adc->cnv), "failed to get CNV GPIO\n"); + if (!adc->cnv && adc->spi_mode == AD7944_SPI_MODE_DEFAULT) + return dev_err_probe(&spi->dev, -EINVAL, "CNV GPIO is required\n"); + if (adc->cnv && adc->spi_mode != AD7944_SPI_MODE_DEFAULT) + return dev_err_probe(&spi->dev, -EINVAL, + "CNV GPIO in single and chain mode is not currently supported\n"); + adc->turbo = devm_gpiod_get_optional(dev, "turbo", GPIOD_OUT_LOW); if (IS_ERR(adc->turbo)) return dev_err_probe(dev, PTR_ERR(adc->turbo), @@ -369,6 +486,10 @@ static int ad7944_probe(struct spi_device *spi) return dev_err_probe(dev, -EINVAL, "cannot have both turbo-gpios and adi,always-turbo\n"); + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN && adc->always_turbo) + return dev_err_probe(dev, -EINVAL, + "cannot have both chain mode and always turbo\n"); + indio_dev->name = chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &ad7944_iio_info; From d10dc7b10fcad2f049213ed10dabe1fc65a39905 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 5 Mar 2024 12:23:05 -0600 Subject: [PATCH 19/22] iio: adc: ad7944: use spi_optimize_message() This modifies the ad7944 driver to use spi_optimize_message() to reduce CPU usage and increase the max sample rate by avoiding repeating validation of the spi message on each transfer. Signed-off-by: David Lechner --- drivers/iio/adc/ad7944.c | 177 +++++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c index d5ec6b5a41c7da..0fd7d89f33447a 100644 --- a/drivers/iio/adc/ad7944.c +++ b/drivers/iio/adc/ad7944.c @@ -51,6 +51,8 @@ static const char * const ad7944_spi_modes[] = { struct ad7944_adc { struct spi_device *spi; enum ad7944_spi_mode spi_mode; + struct spi_transfer xfers[3]; + struct spi_message msg; /* Chip-specific timing specifications. */ const struct ad7944_timing_spec *timing_spec; /* GPIO connected to CNV pin. */ @@ -130,6 +132,88 @@ AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 16, 0); /* fully differential */ AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1); +static void ad7944_unoptimize_msg(void *msg) +{ + spi_unoptimize_message(msg); +} + +static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer *xfers = adc->xfers; + int ret; + + /* + * NB: can get better performance from some SPI controllers if we use + * the same bits_per_word in every transfer. + */ + xfers[0].bits_per_word = chan->scan_type.realbits; + /* + * CS is tied to CNV and we need a low to high transition to start the + * conversion, so place CNV low for t_QUIET to prepare for this. + */ + xfers[0].delay.value = T_QUIET_NS; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + /* + * CS has to be high for full conversion time to avoid triggering the + * busy indication. + */ + xfers[1].cs_off = 1; + xfers[1].delay.value = t_conv_ns; + xfers[1].delay.unit = SPI_DELAY_UNIT_NSECS; + xfers[0].bits_per_word = chan->scan_type.realbits; + + /* Then we can read the data during the acquisition phase */ + xfers[2].rx_buf = &adc->sample.raw; + xfers[2].len = BITS_TO_BYTES(chan->scan_type.storagebits); + xfers[2].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->msg, xfers, 3); + + ret = spi_optimize_message(adc->spi, &adc->msg); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg); +} + +static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer *xfers = adc->xfers; + int ret; + + /* + * NB: can get better performance from some SPI controllers if we use + * the same bits_per_word in every transfer. + */ + xfers[0].bits_per_word = chan->scan_type.realbits; + /* + * CS has to be high for full conversion time to avoid triggering the + * busy indication. + */ + xfers[0].cs_off = 1; + xfers[0].delay.value = t_conv_ns; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].rx_buf = &adc->sample.raw; + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits); + xfers[1].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->msg, xfers, 3); + + ret = spi_optimize_message(adc->spi, &adc->msg); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg); +} + /* * ad7944_3wire_cs_mode_conversion - Perform a 3-wire CS mode conversion and * acquisition @@ -145,48 +229,7 @@ AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1); static int ad7944_3wire_cs_mode_conversion(struct ad7944_adc *adc, const struct iio_chan_spec *chan) { - unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns - : adc->timing_spec->conv_ns; - struct spi_transfer xfers[] = { - { - /* - * NB: can get better performance from some SPI - * controllers if we use the same bits_per_word - * in every transfer. - */ - .bits_per_word = chan->scan_type.realbits, - /* - * CS is tied to CNV and we need a low to high - * transition to start the conversion, so place CNV - * low for t_QUIET to prepare for this. - */ - .delay = { - .value = T_QUIET_NS, - .unit = SPI_DELAY_UNIT_NSECS, - }, - - }, - { - .bits_per_word = chan->scan_type.realbits, - /* - * CS has to be high for full conversion time to avoid - * triggering the busy indication. - */ - .cs_off = 1, - .delay = { - .value = t_conv_ns, - .unit = SPI_DELAY_UNIT_NSECS, - }, - }, - { - /* Then we can read the data during the acquisition phase */ - .rx_buf = &adc->sample.raw, - .len = BITS_TO_BYTES(chan->scan_type.storagebits), - .bits_per_word = chan->scan_type.realbits, - }, - }; - - return spi_sync_transfer(adc->spi, xfers, ARRAY_SIZE(xfers)); + return spi_sync(adc->spi, &adc->msg); } /* @@ -200,33 +243,6 @@ static int ad7944_3wire_cs_mode_conversion(struct ad7944_adc *adc, static int ad7944_4wire_mode_conversion(struct ad7944_adc *adc, const struct iio_chan_spec *chan) { - unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns - : adc->timing_spec->conv_ns; - struct spi_transfer xfers[] = { - { - /* - * NB: can get better performance from some SPI - * controllers if we use the same bits_per_word - * in every transfer. - */ - .bits_per_word = chan->scan_type.realbits, - /* - * CS has to be high for full conversion time to avoid - * triggering the busy indication. - */ - .cs_off = 1, - .delay = { - .value = t_conv_ns, - .unit = SPI_DELAY_UNIT_NSECS, - }, - - }, - { - .rx_buf = &adc->sample.raw, - .len = BITS_TO_BYTES(chan->scan_type.storagebits), - .bits_per_word = chan->scan_type.realbits, - }, - }; int ret; /* @@ -234,7 +250,7 @@ static int ad7944_4wire_mode_conversion(struct ad7944_adc *adc, * and acquisition process. */ gpiod_set_value_cansleep(adc->cnv, 1); - ret = spi_sync_transfer(adc->spi, xfers, ARRAY_SIZE(xfers)); + ret = spi_sync(adc->spi, &adc->msg); gpiod_set_value_cansleep(adc->cnv, 0); return ret; @@ -394,10 +410,6 @@ static int ad7944_probe(struct spi_device *spi) adc->spi_mode = AD7944_SPI_MODE_DEFAULT; } - if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) - return dev_err_probe(dev, -EINVAL, - "chain mode is not implemented\n"); - /* * Some chips use unusual word sizes, so check now instead of waiting * for the first xfer. @@ -490,6 +502,23 @@ static int ad7944_probe(struct spi_device *spi) return dev_err_probe(dev, -EINVAL, "cannot have both chain mode and always turbo\n"); + switch (adc->spi_mode) { + case AD7944_SPI_MODE_DEFAULT: + ret = ad7944_4wire_mode_init_msg(dev, adc, &chip_info->channels[0]); + if (ret) + return ret; + + break; + case AD7944_SPI_MODE_SINGLE: + ret = ad7944_3wire_cs_mode_init_msg(dev, adc, &chip_info->channels[0]); + if (ret) + return ret; + + break; + case AD7944_SPI_MODE_CHAIN: + return dev_err_probe(dev, -EINVAL, "chain mode is not implemented\n"); + } + indio_dev->name = chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &ad7944_iio_info; From e5a6a890b0b1960070abe5435dab446aedb79d48 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 12 Mar 2024 11:43:08 -0500 Subject: [PATCH 20/22] iio: Kconfig.adi: imply AD7944 This adds the new AD7944 driver to the list of drivers that are enabled by default in the ADI tree. Signed-off-by: David Lechner --- drivers/iio/Kconfig.adi | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/Kconfig.adi b/drivers/iio/Kconfig.adi index dcc4d19bcd6dd2..5dc3dbae0295ca 100644 --- a/drivers/iio/Kconfig.adi +++ b/drivers/iio/Kconfig.adi @@ -48,6 +48,7 @@ config IIO_ALL_ADI_DRIVERS imply AD7298 imply AD738X imply AD7923 + imply AD7944 imply AD7949 imply AD7476 imply AD7606_IFACE_PARALLEL From 4ee3c3dbee9b2f3d715d51c1dc177f1ee6549a3a Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 7 Mar 2024 17:23:56 -0600 Subject: [PATCH 21/22] arm: dts: update ad4744 dts for new driver This updates the ad4744/85/86 eval board dts files for the new spi-axi-spi-engine-ex and ad4744 drivers and 3-wire variant of the HDL. Signed-off-by: David Lechner --- arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts | 193 +++++++++++------- arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts | 193 +++++++++++------- arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts | 83 +++++--- 3 files changed, 287 insertions(+), 182 deletions(-) diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts index 5ff34cb1943b53..30f1e6db4f1854 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts @@ -1,15 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Analog Devices AD7944 + * Analog Devices EVAL-AD7944FMCZ * https://www.analog.com/en/products/ad7944.html * * hdl_project: * board_revision: <> * - * Copyright (C) 2023 Analog Devices Inc. + * Copyright (C) 2024 Analog Devices Inc. */ /dts-v1/; +#include #include #include @@ -17,84 +18,118 @@ #include "zynq-zed-adv7511.dtsi" / { - vref: ad7944-internal-reference-regulator { - compatible = "regulator-fixed"; - regulator-name = "AD7944 internal reference"; - regulator-min-microvolt = <4096000>; - regulator-max-microvolt = <4096000>; - regulator-always-on; - }; + eval_u3: eval-board-u3-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +2.5V supply (U3)"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-always-on; + }; + + eval_u5: eval-board-u5-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +5V supply (U5)"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; + + eval_u10: eval-board-u10-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +5V supply (U10)"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; + + eval_u12: eval-board-u12-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +2.5V supply (U12)"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-always-on; + }; }; &fpga_axi { - adc_trigger: pwm@44b00000 { - compatible = "adi,axi-pwmgen"; - reg = <0x44b00000 0x1000>; - label = "adc_conversion_trigger"; - #pwm-cells = <2>; - clocks = <&spi_clk>; - }; - - rx_dma: rx-dmac@44a30000 { - compatible = "adi,axi-dmac-1.00.a"; - reg = <0x44a30000 0x1000>; - #dma-cells = <1>; - interrupts = <0 57 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&clkc 17>; - - adi,channels { - #size-cells = <0>; - #address-cells = <1>; - - dma-channel@0 { - reg = <0>; - adi,source-bus-width = <32>; - adi,source-bus-type = <1>; - adi,destination-bus-width = <64>; - adi,destination-bus-type = <0>; - }; - }; - }; - - spi_clk: clock-controller@44a70000 { - compatible = "adi,axi-clkgen-2.00.a"; - reg = <0x44a70000 0x1000>; - #clock-cells = <0>; - clocks = <&clkc 15>, <&clkc 15>; - clock-names = "s_axi_aclk", "clkin1"; - clock-output-names = "spi_clk"; - }; - - axi_spi_engine_0: spi@44a00000 { - compatible = "adi,axi-spi-engine-1.00.a"; - reg = <0x44a00000 0x1000>; - interrupt-parent = <&intc>; - interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&clkc 15>, <&spi_clk>; - clock-names = "s_axi_aclk", "spi_clk"; - num-cs = <1>; - - #address-cells = <0x1>; - #size-cells = <0x0>; - - ad7944: adc@0 { - #address-cells = <1>; - #size-cells = <0>; - compatible = "adi,pulsar,ad7944"; - reg = <0>; - spi-max-frequency = <80000000>; - turbo-gpios = <&gpio0 87 GPIO_ACTIVE_HIGH>; - clocks = <&spi_clk>; - clock-names = "ref_clk"; - dmas = <&rx_dma 0>; - dma-names = "rx"; - pwms = <&adc_trigger 0 0>; - pwm-names = "cnv"; - vref-supply = <&vref>; - channel@0 { - reg = <0>; - diff-channels = <0 1>; - }; - }; - }; + adc_trigger: pwm@44b00000 { + compatible = "adi,axi-pwmgen"; + reg = <0x44b00000 0x1000>; + #pwm-cells = <2>; + clocks = <&spi_clk>; + }; + + rx_dma: rx-dmac@44a30000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x44a30000 0x1000>; + #dma-cells = <1>; + interrupts = <0 57 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 17>; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma-channel@0 { + reg = <0>; + adi,source-bus-width = <32>; + adi,source-bus-type = ; + adi,destination-bus-width = <64>; + adi,destination-bus-type = ; + }; + }; + }; + + spi_clk: clock-controller@44a70000 { + compatible = "adi,axi-clkgen-2.00.a"; + reg = <0x44a70000 0x1000>; + #clock-cells = <0>; + clocks = <&clkc 15>, <&clkc 15>; + clock-names = "s_axi_aclk", "clkin1"; + clock-output-names = "spi_clk"; + + /* needs to be high enough to allow >= 91.5MHz SCLK for turbo */ + assigned-clocks = <&spi_clk>; + assigned-clock-rates = <185000000>; + }; + + axi_spi_engine_0: spi@44a00000 { + compatible = "adi-ex,axi-spi-engine-1.00.a"; + reg = <0x44a00000 0x1000>; + interrupt-parent = <&intc>; + interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15>, <&spi_clk>; + clock-names = "s_axi_aclk", "spi_clk"; + + #address-cells = <1>; + #size-cells = <0>; + + ad7944: adc@0 { + compatible = "adi,ad7944"; + reg = <0>; + spi-max-frequency = <111111111>; /* 9 ns period */ + adi,spi-mode = "single"; + avdd-supply = <&eval_u12>; + dvdd-supply = <&eval_u12>; + vio-supply = <&eval_u3>; + bvdd-supply = <&eval_u10>; + ref-supply = <&eval_u5>; + turbo-gpios = <&gpio0 87 GPIO_ACTIVE_HIGH>; + + /* out of tree extensions */ + dmas = <&rx_dma 0>; + dma-names = "rx"; + pwms = <&adc_trigger 0 0>; + pwm-names = "cnv"; + }; + }; +}; + +&gpio0 { + ad7944-mode { + /* pull SDI line on ADC high for 3-wire mode */ + gpio-hog; + gpios = <88 GPIO_ACTIVE_HIGH>; + output-high; + }; }; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts index 8364800e7bebbb..11e9f1975325fb 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts @@ -1,15 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Analog Devices AD7985 + * Analog Devices EVAL-AD7985FMCZ * https://www.analog.com/en/products/ad7985.html * * hdl_project: * board_revision: <> * - * Copyright (C) 2023 Analog Devices Inc. + * Copyright (C) 2024 Analog Devices Inc. */ /dts-v1/; +#include #include #include @@ -17,84 +18,118 @@ #include "zynq-zed-adv7511.dtsi" / { - vref: ad7985-internal-reference-regulator { - compatible = "regulator-fixed"; - regulator-name = "AD7985 internal reference"; - regulator-min-microvolt = <4096000>; - regulator-max-microvolt = <4096000>; - regulator-always-on; - }; + eval_u3: eval-board-u3-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +2.5V supply (U3)"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-always-on; + }; + + eval_u5: eval-board-u5-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +5V supply (U5)"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; + + eval_u10: eval-board-u10-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +5V supply (U10)"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; + + eval_u12: eval-board-u12-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +2.5V supply (U12)"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-always-on; + }; }; &fpga_axi { - adc_trigger: pwm@44b00000 { - compatible = "adi,axi-pwmgen"; - reg = <0x44b00000 0x1000>; - label = "adc_conversion_trigger"; - #pwm-cells = <2>; - clocks = <&spi_clk>; - }; - - rx_dma: rx-dmac@44a30000 { - compatible = "adi,axi-dmac-1.00.a"; - reg = <0x44a30000 0x1000>; - #dma-cells = <1>; - interrupts = <0 57 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&clkc 17>; - - adi,channels { - #size-cells = <0>; - #address-cells = <1>; - - dma-channel@0 { - reg = <0>; - adi,source-bus-width = <32>; - adi,source-bus-type = <1>; - adi,destination-bus-width = <64>; - adi,destination-bus-type = <0>; - }; - }; - }; - - spi_clk: clock-controller@44a70000 { - compatible = "adi,axi-clkgen-2.00.a"; - reg = <0x44a70000 0x1000>; - #clock-cells = <0>; - clocks = <&clkc 15>, <&clkc 15>; - clock-names = "s_axi_aclk", "clkin1"; - clock-output-names = "spi_clk"; - }; - - axi_spi_engine_0: spi@44a00000 { - compatible = "adi,axi-spi-engine-1.00.a"; - reg = <0x44a00000 0x1000>; - interrupt-parent = <&intc>; - interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&clkc 15>, <&spi_clk>; - clock-names = "s_axi_aclk", "spi_clk"; - num-cs = <1>; - - #address-cells = <0x1>; - #size-cells = <0x0>; - - ad7985: adc@0 { - #address-cells = <1>; - #size-cells = <0>; - compatible = "adi,pulsar,ad7985"; - reg = <0>; - spi-max-frequency = <80000000>; - turbo-gpios = <&gpio0 87 GPIO_ACTIVE_HIGH>; - clocks = <&spi_clk>; - clock-names = "ref_clk"; - dmas = <&rx_dma 0>; - dma-names = "rx"; - pwms = <&adc_trigger 0 0>; - pwm-names = "cnv"; - vref-supply = <&vref>; - channel@0 { - reg = <0>; - diff-channels = <0 1>; - }; - }; - }; + adc_trigger: pwm@44b00000 { + compatible = "adi,axi-pwmgen"; + reg = <0x44b00000 0x1000>; + #pwm-cells = <2>; + clocks = <&spi_clk>; + }; + + rx_dma: rx-dmac@44a30000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x44a30000 0x1000>; + #dma-cells = <1>; + interrupts = <0 57 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 17>; + + adi,channels { + #size-cells = <0>; + #address-cells = <1>; + + dma-channel@0 { + reg = <0>; + adi,source-bus-width = <32>; + adi,source-bus-type = ; + adi,destination-bus-width = <64>; + adi,destination-bus-type = ; + }; + }; + }; + + spi_clk: clock-controller@44a70000 { + compatible = "adi,axi-clkgen-2.00.a"; + reg = <0x44a70000 0x1000>; + #clock-cells = <0>; + clocks = <&clkc 15>, <&clkc 15>; + clock-names = "s_axi_aclk", "clkin1"; + clock-output-names = "spi_clk"; + + /* needs to be high enough to allow >= 91.5MHz SCLK for turbo */ + assigned-clocks = <&spi_clk>; + assigned-clock-rates = <185000000>; + }; + + axi_spi_engine_0: spi@44a00000 { + compatible = "adi-ex,axi-spi-engine-1.00.a"; + reg = <0x44a00000 0x1000>; + interrupt-parent = <&intc>; + interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15>, <&spi_clk>; + clock-names = "s_axi_aclk", "spi_clk"; + + #address-cells = <1>; + #size-cells = <0>; + + ad7985: adc@0 { + compatible = "adi,ad7985"; + reg = <0>; + spi-max-frequency = <111111111>; /* 9 ns period */ + adi,spi-mode = "single"; + avdd-supply = <&eval_u12>; + dvdd-supply = <&eval_u12>; + vio-supply = <&eval_u3>; + bvdd-supply = <&eval_u10>; + ref-supply = <&eval_u5>; + turbo-gpios = <&gpio0 87 GPIO_ACTIVE_HIGH>; + + /* out of tree extensions */ + dmas = <&rx_dma 0>; + dma-names = "rx"; + pwms = <&adc_trigger 0 0>; + pwm-names = "cnv"; + }; + }; +}; + +&gpio0 { + ad7985-mode { + /* pull SDI line on ADC high for 3-wire mode */ + gpio-hog; + gpios = <88 GPIO_ACTIVE_HIGH>; + output-high; + }; }; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts index e26a1ab608c313..d72aada18a9080 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts @@ -1,15 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Analog Devices AD7986 + * Analog Devices EVAL-AD7986FMCZ * https://www.analog.com/en/products/ad7986.html * * hdl_project: * board_revision: <> * - * Copyright (C) 2023 Analog Devices Inc. + * Copyright (C) 2024 Analog Devices Inc. */ /dts-v1/; +#include #include #include @@ -17,11 +18,35 @@ #include "zynq-zed-adv7511.dtsi" / { - vref: ad7986-internal-reference-regulator { + eval_u3: eval-board-u3-regulator { compatible = "regulator-fixed"; - regulator-name = "AD7986 internal reference"; - regulator-min-microvolt = <4096000>; - regulator-max-microvolt = <4096000>; + regulator-name = "EVAL +2.5V supply (U3)"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-always-on; + }; + + eval_u5: eval-board-u5-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +5V supply (U5)"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; + + eval_u10: eval-board-u10-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +5V supply (U10)"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + }; + + eval_u12: eval-board-u12-regulator { + compatible = "regulator-fixed"; + regulator-name = "EVAL +2.5V supply (U12)"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; regulator-always-on; }; }; @@ -30,7 +55,6 @@ adc_trigger: pwm@44b00000 { compatible = "adi,axi-pwmgen"; reg = <0x44b00000 0x1000>; - label = "adc_conversion_trigger"; #pwm-cells = <2>; clocks = <&spi_clk>; }; @@ -49,9 +73,9 @@ dma-channel@0 { reg = <0>; adi,source-bus-width = <32>; - adi,source-bus-type = <1>; + adi,source-bus-type = ; adi,destination-bus-width = <64>; - adi,destination-bus-type = <0>; + adi,destination-bus-type = ; }; }; }; @@ -63,38 +87,49 @@ clocks = <&clkc 15>, <&clkc 15>; clock-names = "s_axi_aclk", "clkin1"; clock-output-names = "spi_clk"; + + /* needs to be high enough to allow >= 91.5MHz SCLK for turbo */ + assigned-clocks = <&spi_clk>; + assigned-clock-rates = <185000000>; }; axi_spi_engine_0: spi@44a00000 { - compatible = "adi,axi-spi-engine-1.00.a"; + compatible = "adi-ex,axi-spi-engine-1.00.a"; reg = <0x44a00000 0x1000>; interrupt-parent = <&intc>; interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clkc 15>, <&spi_clk>; clock-names = "s_axi_aclk", "spi_clk"; - num-cs = <1>; - #address-cells = <0x1>; - #size-cells = <0x0>; + #address-cells = <1>; + #size-cells = <0>; ad7986: adc@0 { - #address-cells = <1>; - #size-cells = <0>; - compatible = "adi,pulsar,ad7986"; + compatible = "adi,ad7986"; reg = <0>; - spi-max-frequency = <80000000>; + spi-max-frequency = <111111111>; /* 9 ns period */ + adi,spi-mode = "single"; + avdd-supply = <&eval_u12>; + dvdd-supply = <&eval_u12>; + vio-supply = <&eval_u3>; + bvdd-supply = <&eval_u10>; + ref-supply = <&eval_u5>; turbo-gpios = <&gpio0 87 GPIO_ACTIVE_HIGH>; - clocks = <&spi_clk>; - clock-names = "ref_clk"; + + /* out of tree extensions */ dmas = <&rx_dma 0>; dma-names = "rx"; pwms = <&adc_trigger 0 0>; pwm-names = "cnv"; - vref-supply = <&vref>; - channel@0 { - reg = <0>; - diff-channels = <0 1>; - }; }; }; }; + +&gpio0 { + ad7986-mode { + /* pull SDI line on ADC high for 3-wire mode */ + gpio-hog; + gpios = <88 GPIO_ACTIVE_HIGH>; + output-high; + }; +}; From d73dba4315e692f5f0ea634a8efb297f77fe8ed0 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 8 Mar 2024 13:09:58 -0600 Subject: [PATCH 22/22] iio: adc: ad7944: add spi offload support This adds support for SPI Engine offload to the ad7944-ex driver. This allows reaching the max sample rate of 2/2.5 MSPS when the chip is wired in 3-wire mode. Signed-off-by: David Lechner --- drivers/iio/adc/ad7944.c | 245 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 240 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c index 0fd7d89f33447a..4ca9e183c12148 100644 --- a/drivers/iio/adc/ad7944.c +++ b/drivers/iio/adc/ad7944.c @@ -14,15 +14,25 @@ #include #include #include +#include #include +#include #include #include #include #include +#include #include #include +/* + * Older SPI Engine HDL versions have a bug where the offload is level triggered + * instead of edge triggered, so we use a small duty cycle to allow sampling + * at lower rates without spurious triggers. + */ +#define AD7944_PWM_TRIGGER_DUTY_CYCLE_NS 20 +#define AD7944_DEFAULT_SAMPLE_FREQ_HZ 10000 /* arbitrary */ #define AD7944_INTERNAL_REF_MV 4096 struct ad7944_timing_spec { @@ -30,6 +40,10 @@ struct ad7944_timing_spec { unsigned int conv_ns; /* TURBO mode max conversion time (t_{CONV}). */ unsigned int turbo_conv_ns; + /* Normal mode data read during conversion time (t_{DATA}). */ + unsigned int data_ns; + /* TURBO mode data read during conversion time (t_{DATA}). */ + unsigned int turbo_data_ns; }; enum ad7944_spi_mode { @@ -53,6 +67,8 @@ struct ad7944_adc { enum ad7944_spi_mode spi_mode; struct spi_transfer xfers[3]; struct spi_message msg; + struct spi_transfer turbo_xfers[3]; + struct spi_message turbo_msg; /* Chip-specific timing specifications. */ const struct ad7944_timing_spec *timing_spec; /* GPIO connected to CNV pin. */ @@ -63,6 +79,8 @@ struct ad7944_adc { bool always_turbo; /* Reference voltage (millivolts). */ unsigned int ref_mv; + /* SPI offload trigger. */ + struct pwm_device *pwm; /* * DMA (thus cache coherency maintenance) requires the @@ -77,17 +95,25 @@ struct ad7944_adc { } sample __aligned(IIO_DMA_MINALIGN); }; +/* minimum CNV high time */ +#define T_CNVH_NS 10 +/* CS low to data valid time */ +#define T_EN_NS 5 /* quite time before CNV rising edge */ #define T_QUIET_NS 20 static const struct ad7944_timing_spec ad7944_timing_spec = { .conv_ns = 420, .turbo_conv_ns = 320, + .data_ns = 290, + .turbo_data_ns = 190, }; static const struct ad7944_timing_spec ad7986_timing_spec = { .conv_ns = 500, .turbo_conv_ns = 400, + .data_ns = 300, + .turbo_data_ns = 200, }; struct ad7944_chip_info { @@ -180,6 +206,57 @@ static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct ad7944_adc * return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg); } +static int ad7944_3wire_cs_mode_init_turbo_msg(struct device *dev, + struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + struct spi_transfer *xfers = adc->turbo_xfers; + int ret; + + /* + * This sequence of xfers performs a read during conversion in 3-wire CS + * mode with timings for when TURBO mode is enabled. Using this msg will + * only work with SPI offloads since the timing needs to be precise to + * actually be able to read during the conversion time. + */ + + /* change CNV to high to trigger conversion */ + xfers[0].cs_off = 1; + xfers[0].bits_per_word = chan->scan_type.realbits; + xfers[0].delay.value = T_CNVH_NS; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + /* read sample data from previous conversion */ + xfers[1].rx_buf = &adc->sample.raw; + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits); + xfers[1].bits_per_word = chan->scan_type.realbits; + /* + * CNV has to be high at end of conversion to avoid triggering the busy + * signal on the SDO line. + */ + xfers[1].cs_change = 1; + /* t[CONV] - t[CNVH] - t[EN] - t[DATA] */ + xfers[1].cs_change_delay.value = adc->timing_spec->turbo_conv_ns - + T_CNVH_NS - T_EN_NS - + adc->timing_spec->turbo_data_ns; + xfers[1].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + + /* + * Since this is the last xfer, this changes CNV to low and keeps it + * there until the next xfer. + */ + xfers[2].cs_change = 1; + xfers[2].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->turbo_msg, xfers, 3); + + ret = spi_optimize_message(adc->spi, &adc->turbo_msg); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->turbo_msg); +} + static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc, const struct iio_chan_spec *chan) { @@ -327,8 +404,132 @@ static int ad7944_read_raw(struct iio_dev *indio_dev, } } +static ssize_t ad7944_sampling_frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7944_adc *adc = iio_priv(indio_dev); + u64 period_ns; + + if (!adc->pwm) + return -ENODEV; + + period_ns = pwm_get_period(adc->pwm); + + return sysfs_emit(buf, "%llu\n", div64_u64(NSEC_PER_SEC, period_ns)); +} + +static ssize_t ad7944_sampling_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7944_adc *adc = iio_priv(indio_dev); + u64 period_ns; + u32 val; + int ret; + + if (!adc->pwm) + return -ENODEV; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + if (val == 0) + return -EINVAL; + + period_ns = div_u64(NSEC_PER_SEC, val); + + ret = pwm_config(adc->pwm, AD7944_PWM_TRIGGER_DUTY_CYCLE_NS, period_ns); + if (ret) + return ret; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(0644, ad7944_sampling_frequency_show, + ad7944_sampling_frequency_store); + +static struct attribute *ad7944_attrs[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL +}; + +static umode_t ad7944_attrs_is_visible(struct kobject *kobj, + struct attribute *attr, int unused) +{ + struct device *dev = kobj_to_dev(kobj); + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7944_adc *adc = iio_priv(indio_dev); + + /* hide sampling_frequency attribute when there is no pwm */ + if (attr == &iio_dev_attr_sampling_frequency.dev_attr.attr && !adc->pwm) + return 0; + + /* show all other attributes */ + return attr->mode; +} + +static const struct attribute_group ad7944_group = { + .attrs = ad7944_attrs, + .is_visible = ad7944_attrs_is_visible, +}; + static const struct iio_info ad7944_iio_info = { .read_raw = &ad7944_read_raw, + .attrs = &ad7944_group, +}; + +static int ad7944_offload_ex_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + switch (adc->spi_mode) { + case AD7944_SPI_MODE_DEFAULT: + return spi_engine_ex_offload_load_msg(adc->spi, &adc->msg); + case AD7944_SPI_MODE_SINGLE: + /* REVISIT: could do non-turbo for lower sample rates */ + gpiod_set_value_cansleep(adc->turbo, 1); + return spi_engine_ex_offload_load_msg(adc->spi, &adc->turbo_msg); + default: + return -EOPNOTSUPP; + } +} + +static int ad7944_offload_ex_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + spi_engine_ex_offload_enable(adc->spi, true); + + return 0; +} + +static int ad7944_offload_ex_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + spi_engine_ex_offload_enable(adc->spi, false); + + return 0; +} + +static int ad7944_offload_ex_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + gpiod_set_value_cansleep(adc->turbo, 0); + + return 0; +} + +static const struct iio_buffer_setup_ops ad7944_offload_ex_buffer_setup_ops = { + .preenable = &ad7944_offload_ex_buffer_preenable, + .postenable = &ad7944_offload_ex_buffer_postenable, + .predisable = &ad7944_offload_ex_buffer_predisable, + .postdisable = &ad7944_offload_ex_buffer_postdisable, }; static irqreturn_t ad7944_trigger_handler(int irq, void *p) @@ -514,6 +715,10 @@ static int ad7944_probe(struct spi_device *spi) if (ret) return ret; + ret = ad7944_3wire_cs_mode_init_turbo_msg(dev, adc, &chip_info->channels[0]); + if (ret) + return ret; + break; case AD7944_SPI_MODE_CHAIN: return dev_err_probe(dev, -EINVAL, "chain mode is not implemented\n"); @@ -525,11 +730,41 @@ static int ad7944_probe(struct spi_device *spi) indio_dev->channels = chip_info->channels; indio_dev->num_channels = ARRAY_SIZE(chip_info->channels); - ret = devm_iio_triggered_buffer_setup(dev, indio_dev, - iio_pollfunc_store_time, - ad7944_trigger_handler, NULL); - if (ret) - return ret; + if (spi_engine_ex_offload_supported(spi)) { + struct pwm_state state = { + .period = NSEC_PER_SEC / AD7944_DEFAULT_SAMPLE_FREQ_HZ, + .duty_cycle = AD7944_PWM_TRIGGER_DUTY_CYCLE_NS, + .enabled = true, + .time_unit = PWM_UNIT_NSEC, + }; + + adc->pwm = devm_pwm_get(dev, NULL); + if (IS_ERR(adc->pwm)) + return dev_err_probe(dev, PTR_ERR(adc->pwm), + "failed to get PWM\n"); + + ret = pwm_apply_state(adc->pwm, &state); + if (ret) + return dev_err_probe(dev, ret, + "failed to apply PWM state\n"); + + ret = devm_iio_dmaengine_buffer_setup(dev, indio_dev, "rx", + IIO_BUFFER_DIRECTION_IN); + if (ret) + return ret; + + indio_dev->setup_ops = &ad7944_offload_ex_buffer_setup_ops; + + /* can't have soft timestamp with SPI offload */ + indio_dev->num_channels--; + } else { + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad7944_trigger_handler, + NULL); + if (ret) + return ret; + } return devm_iio_device_register(dev, indio_dev); }