[PATCH v9 2/4] dmaengine: qcom: gpi: Add lock/unlock TREs for multi-owner I2C transfers
Mukesh Kumar Savaliya <[email protected]> Thu, 13 Aug 2026 19:30:41 +0530
| Newsgroups | org.kernel.vger.linux-i2c,org.kernel.vger.dmaengine,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Some platforms use a QUP-based I2C controller in a configuration where the controller is shared with another system processor (described in DT using qcom,qup-multi-owner). In such setups, GPI hardware lock/unlock TREs can be used to serialize access to the controller. Add support to emit lock and unlock TREs around I2C transfers and increase the maximum TRE count to account for the additional elements. The GPI driver determines lock/unlock TRE placement autonomously based on transaction boundaries, with no guidance required from the DMA client: - A LOCK TRE is inserted before the first transfer in a transaction. The driver detects the start of a new transaction by checking that no lock is currently held (lock_pending == false). - An UNLOCK TRE is inserted after the final write transfer in a transaction. The DMA_PREP_INTERRUPT flag on the descriptor marks the last descriptor in the batch. The multi-owner flag is communicated once via the initial dmaengine_slave_ config call (set_config == 1) through the multi_owner field of struct gpi_i2c_config, and is latched in gchan->multi_owner for the lifetime of the transfer sequence. This keeps the locking mechanism as an internal GPI implementation detail, consistent with the BAM-DMA approach, so DMA consumers remain unaware of the underlying locking hardware. Signed-off-by: Mukesh Kumar Savaliya <[email protected]> --- drivers/dma/qcom/gpi.c | 77 +++++++++++++++++++++++++++++++- include/linux/dma/qcom-gpi-dma.h | 4 ++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c index a5055a6273af..25fe7410bee2 100644 --- a/drivers/dma/qcom/gpi.c +++ b/drivers/dma/qcom/gpi.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. * Copyright (c) 2020, Linaro Limited + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include <dt-bindings/dma/qcom-gpi.h> @@ -67,6 +68,14 @@ #define TRE_DMA_LEN GENMASK(23, 0) #define TRE_DMA_IMMEDIATE_LEN GENMASK(3, 0) +/* Lock TRE */ +#define TRE_LOCK BIT(0) +#define TRE_MINOR_TYPE GENMASK(19, 16) +#define TRE_MAJOR_TYPE GENMASK(23, 20) + +/* Unlock TRE */ +#define TRE_UNLOCK BIT(8) + /* Register offsets from gpi-top */ #define GPII_n_CH_k_CNTXT_0_OFFS(n, k) (0x20000 + (0x4000 * (n)) + (0x80 * (k))) #define GPII_n_CH_k_CNTXT_0_EL_SIZE GENMASK(31, 24) @@ -492,6 +501,8 @@ struct gchan { u32 dir; struct gpi_ring ch_ring; void *config; + bool multi_owner; /* controller is shared; insert lock/unlock TREs */ + bool lock_pending; /* LOCK TRE has been inserted, UNLOCK not yet emitted */ }; struct gpii { @@ -518,7 +529,7 @@ struct gpii { bool ieob_set; }; -#define MAX_TRE 3 +#define MAX_TRE 5 struct gpi_desc { struct virt_dma_desc vd; @@ -1605,6 +1616,7 @@ static int gpi_peripheral_config(struct dma_chan *chan, struct dma_slave_config *config) { struct gchan *gchan = to_gchan(chan); + struct gpi_i2c_config *i2c_cfg; void *new_config; if (!config->peripheral_config) @@ -1617,6 +1629,17 @@ gpi_peripheral_config(struct dma_chan *chan, struct dma_slave_config *config) gchan->config = new_config; memcpy(gchan->config, config->peripheral_config, config->peripheral_size); + /* + * Latch the multi_owner flag from the initial config call so the GPI + * driver can autonomously insert LOCK/UNLOCK TREs without the client + * having to track transfer boundaries. + */ + if (gchan->protocol == QCOM_GPI_I2C) { + i2c_cfg = gchan->config; + if (i2c_cfg->set_config) + gchan->multi_owner = i2c_cfg->multi_owner; + } + return 0; } @@ -1627,10 +1650,33 @@ static int gpi_create_i2c_tre(struct gchan *chan, struct gpi_desc *desc, struct gpi_i2c_config *i2c = chan->config; struct device *dev = chan->gpii->gpi_dev->dev; unsigned int tre_idx = 0; + bool is_last = !!(flags & DMA_PREP_INTERRUPT); dma_addr_t address; struct gpi_tre *tre; unsigned int i; + /* + * Insert a LOCK TRE before the first transfer of a multi-owner + * transaction. The GPI driver detects the transaction start + * autonomously: if multi_owner is set and no lock has been issued + * since the last unlock (lock_pending == false), this is the first + * descriptor in a new transaction. + */ + if (chan->multi_owner && !chan->lock_pending) { + tre = &desc->tre[tre_idx]; + tre_idx++; + + tre->dword[0] = 0; + tre->dword[1] = 0; + tre->dword[2] = 0; + tre->dword[3] = u32_encode_bits(1, TRE_LOCK); + tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOB); + tre->dword[3] |= u32_encode_bits(0, TRE_MINOR_TYPE); + tre->dword[3] |= u32_encode_bits(3, TRE_MAJOR_TYPE); + + chan->lock_pending = true; + } + /* first create config tre if applicable */ if (i2c->set_config) { tre = &desc->tre[tre_idx]; @@ -1690,6 +1736,35 @@ static int gpi_create_i2c_tre(struct gchan *chan, struct gpi_desc *desc, if (!(flags & DMA_PREP_INTERRUPT)) tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_BEI); + + /* + * Chain the DMA TRE to the UNLOCK TRE when this is the last + * descriptor in the transaction (DMA_PREP_INTERRUPT set) and + * the channel is in multi-owner mode. + */ + if (chan->multi_owner && is_last && i2c->op != I2C_READ) + tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_CHAIN); + } + + /* + * Insert an UNLOCK TRE after the last write transfer of a multi-owner + * transaction. DMA_PREP_INTERRUPT marks the final descriptor in the + * batch; reads carry their own completion event so the unlock follows + * the write leg of each read message. + */ + if (chan->multi_owner && is_last && i2c->op != I2C_READ) { + tre = &desc->tre[tre_idx]; + tre_idx++; + + tre->dword[0] = 0; + tre->dword[1] = 0; + tre->dword[2] = 0; + tre->dword[3] = u32_encode_bits(1, TRE_UNLOCK); + tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOB); + tre->dword[3] |= u32_encode_bits(1, TRE_MINOR_TYPE); + tre->dword[3] |= u32_encode_bits(3, TRE_MAJOR_TYPE); + + chan->lock_pending = false; } for (i = 0; i < tre_idx; i++) diff --git a/include/linux/dma/qcom-gpi-dma.h b/include/linux/dma/qcom-gpi-dma.h index 332be28427e4..590ca7935f2b 100644 --- a/include/linux/dma/qcom-gpi-dma.h +++ b/include/linux/dma/qcom-gpi-dma.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2020, Linaro Limited + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #ifndef QCOM_GPI_DMA_H @@ -68,6 +69,8 @@ enum i2c_op { * @rx_len: receive length for buffer * @op: i2c cmd * @multi_msg: is part of multi i2c r-w msgs + * @multi_owner: controller is shared with another system processor; + * the GPI driver will insert lock/unlock TREs automatically */ struct gpi_i2c_config { u8 set_config; @@ -81,6 +84,7 @@ struct gpi_i2c_config { u32 rx_len; enum i2c_op op; bool multi_msg; + bool multi_owner; }; #endif /* QCOM_GPI_DMA_H */ -- 2.43.0