Re: [PATCH v1] i2c: i2c-qcom-geni: Add support to share an I2C SE from two subsystem

From: Mukesh Kumar Savaliya
Date: Tue Apr 02 2024 - 02:21:46 EST


Thanks Konrad for detailed review. For dt-bindings sending a separate patch soon, rest comments tried to address and updated patch V2.

On 3/28/2024 2:35 AM, Konrad Dybcio wrote:
On 27.03.2024 11:18 AM, Mukesh Kumar Savaliya wrote:
Add feature to share an I2C serial engine from two subsystem(SS) so that

from -> between
subystem -> subsystems

Done
individual client from different subsystem can work on the same bus.

client -> clients
subystem -> subsystems

Done
"work on the same bus" - access the same bus? Does that concern both
read and write to the same slave device?

yes, access the same bus suits better.
Yes, they can read/write from/to same slave device.

Please give a more specific example. Could that be for example APSS and
TZ?

sure. Example: APSS and TZ, APPS and Modem, Modem and DSP etc.
Changed Enhanced commit log accordingly.

This is possible in GSI mode where driver queues the TRE with required
descriptors and ensures it executes them in a mutual exclusive way.

mutually

Done

the "it" and "them" are confusing, could you reword this?

Sure, i have modified.

Add Lock TRE at the start of the transfer and Unlock TRE at the end of
the transfer protecting the DMA channel. This way not allowing other SS
to queue anything in between and disturb the data path.

'Issue a "Lock TRE" command at the start of the transfer and an "Unlock TRE"
at the end of it. This prevents other subsystems from concurrently
performing DMA transfers through the same GPI channel, so as to avoid
disturbing the data path.'

Would that be a fair representation of what this is trying to achieve?

Yes, that's what i meant to say. I have changed into similar way.

Since the GPIOs are also shared for the i2c bus, do not touch GPIO
configuration while going to runtime suspend and only turn off the
clocks. This will allow other SS to continue to transfer the data.

To realize this, add below change:
1) Check if the Particular I2C requires to be shared during probe() time.

This requires a dt-bindings change. Had you run something like:

./scripts/checkpatch.pl -g $(git describe --abbrev=0)..HEAD

you'dve noticed there's new warnings.

yes, I understand this needs dt-bindings update for new flag.
Though Ran this script now but could not find any warning, i shall update dt-bindings.

2) If shared SE add LOCK TRE inside gpi_create_i2c_tre() before first
message.
3) If shared SE add UNLOCK TRE inside gpi_create_i2c_tre() before
last transfer message.

You already described this above.

sure, i can remove it.
4) Export function geni_se_clks_off() to call explicitly instead of
geni_se_resources_off().

Do we expect other SEs (UART, I3C, SPI) to also support this "shared"
configuration? Would it be beneficial to make the "shared" cases common
and bail out of geni_se_resources_off() early if a SE is marked as such?

As such this is generic GSI based supported feature, But I3C and UART doesn't have GSI mode support Added. Right now, we are enabling this feature for i2 only, in future we may enable for SPI, I3C as and when required. Even Considering commonality in future, shared feature is known to individual SE protocol driver and would be little complex to add into common driver.

Signed-off-by: Mukesh Kumar Savaliya <quic_msavaliy@xxxxxxxxxxx>
---
drivers/dma/qcom/gpi.c | 33 +++++++++++++++++++++++++++++-
drivers/i2c/busses/i2c-qcom-geni.c | 24 ++++++++++++++++------
drivers/soc/qcom/qcom-geni-se.c | 4 +++-
include/linux/dma/qcom-gpi-dma.h | 6 ++++++
include/linux/soc/qcom/geni-se.h | 3 +++

This is a big no-go, you're changing files across 3 different subsystems,
you must split this patch up. In this case, you want one for dma, one for
soc and one for i2c. Check ./scripts/get_maintainer.pl <filename>

Please correct me if this is wrong. The overall change is for i2c in GSI DMA mode. This also requires changes in resource control like TLMM changes. But it's more like integrated feature.
Are you suggesting to make 3 sub-patches under same change ?
5 files changed, 62 insertions(+), 8 deletions(-)

diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c
index 1c93864e0e4d..df276ccf9cbb 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) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <dt-bindings/dma/qcom-gpi.h>
@@ -65,6 +66,12 @@
/* DMA TRE */
#define TRE_DMA_LEN GENMASK(23, 0)
+/* Lock TRE */
+#define TRE_I2C_LOCK_WORD_3 (3 << 20 | 0 << 16 | BIT(0))
+
+/* Unlock TRE */
+#define TRE_I2C_UNLOCK_WORD_3 (3 << 20 | 1 << 16 | BIT(8))

The comments you're introducing don't seem particularly helpful, looking
at the define names. You should also avoid adding random shifted values
and #define the bitfields used in your messages with BIT() and GENMASK()

Sure, Can remove the comment as macro name explains it.
Will define macros with BIT() and GENMASK(). Made changes similar to the existing macros.
+
/* 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)
@@ -522,7 +529,7 @@ struct gpii {
bool ieob_set;
};
-#define MAX_TRE 3
+#define MAX_TRE 5

This almost looks like a separate commit itself? Definitely needs
an explanation.

Agree, Added into commit log explanation.
This is changed because we have added LOCK_TRE and UNLOCK_TRE, hence 3 became 5.
struct gpi_desc {
struct virt_dma_desc vd;
@@ -1644,6 +1651,18 @@ static int gpi_create_i2c_tre(struct gchan *chan, struct gpi_desc *desc,
struct gpi_tre *tre;
unsigned int i;
+ /* create lock tre for first tranfser */
+ if (i2c->shared_se && i2c->first_msg) {
+ tre = &desc->tre[tre_idx];
+ tre_idx++;
+
+ /* lock: chain bit set */

What does this mean?
It guides GSI engine saying there is next TRE queued, Basically not the last TRE. I am removing this comment, as it's code specific.

+ tre->dword[0] = 0;
+ tre->dword[1] = 0;
+ tre->dword[2] = 0;
+ tre->dword[3] = TRE_I2C_LOCK_WORD_3;
+ }
+
/* first create config tre if applicable */
if (i2c->set_config) {
tre = &desc->tre[tre_idx];
@@ -1702,6 +1721,18 @@ static int gpi_create_i2c_tre(struct gchan *chan, struct gpi_desc *desc,
tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOT);
}
+ /* Unlock tre for last transfer */
+ if (i2c->shared_se && i2c->last_msg && i2c->op != I2C_READ) {
+ tre = &desc->tre[tre_idx];
+ tre_idx++;
+
+ /* unlock tre: ieob set */

What does this mean?

It says, end of block interrupt. Engine will generate an interrupt.
But comment is not required as it's code specific.
+ tre->dword[0] = 0;
+ tre->dword[1] = 0;
+ tre->dword[2] = 0;
+ tre->dword[3] = TRE_I2C_UNLOCK_WORD_3;
+ }
+
for (i = 0; i < tre_idx; i++)
dev_dbg(dev, "TRE:%d %x:%x:%x:%x\n", i, desc->tre[i].dword[0],
desc->tre[i].dword[1], desc->tre[i].dword[2], desc->tre[i].dword[3]);
diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index da94df466e83..c5935c5f46e8 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
+// Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
#include <linux/acpi.h>
#include <linux/clk.h>
@@ -99,6 +100,7 @@ struct geni_i2c_dev {
struct dma_chan *rx_c;
bool gpi_mode;
bool abort_done;
+ bool is_shared;
};
struct geni_i2c_desc {
@@ -601,6 +603,7 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
peripheral.clk_div = itr->clk_div;
peripheral.set_config = 1;
peripheral.multi_msg = false;
+ peripheral.shared_se = gi2c->is_shared;
for (i = 0; i < num; i++) {
gi2c->cur = &msgs[i];
@@ -611,6 +614,8 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
if (i < num - 1)
peripheral.stretch = 1;
+ peripheral.first_msg = (i == 0) ? true : false;
+ peripheral.last_msg = (i == num - 1) ? true : false;

Why the ternary operator? == already returns a boolean value

Sure, this was more easier to understand. I have removed ternary logic.

peripheral.addr = msgs[i].addr;
ret = geni_i2c_gpi(gi2c, &msgs[i], &config,
@@ -802,6 +807,11 @@ static int geni_i2c_probe(struct platform_device *pdev)
gi2c->clk_freq_out = KHZ(100);
}
+ if (of_property_read_bool(pdev->dev.of_node, "qcom,shared-se")) {
+ gi2c->is_shared = true;
+ dev_info(&pdev->dev, "Multi-EE usecase with shared SE\n");

How would this line be useful in my kernel log?

It informs that particular SE is shared between SEs from two subsystems, hence respective debug can happen accordingly in case of the issue.
Konrad