* [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support
@ 2026-11-11 3:16 Billy Tsai
2026-11-11 3:16 ` [PATCH v2 1/9] dt-bindings: i3c: Document generic bus timing properties Billy Tsai
` (8 more replies)
0 siblings, 9 replies; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
The Aspeed AST2700 SoC integrates an I3C host controller based on the
MIPI I3C HCI v1.1 programming model, extended with an in-house control
block and a PHY programming window. Both extra register regions are
advertised through a standard HCI vendor extended capability. This
series teaches the existing mipi-i3c-hci driver to drive the AST2700
controller without forking the driver.
AST2700 specifics are expressed entirely as quirks in the match data:
the existing HCI_QUIRK_DMA_ABORT_REQUIRES_PIO_RESET, then four generic
quirks:
- HCI_QUIRK_DAT_INDEX_IS_ADDR for address-indexed DAT slots,
- HCI_QUIRK_DMA_64BIT to configure a 64-bit DMA mask,
- HCI_QUIRK_TX_START_THLD to clear the TX start threshold in DMA mode, and
- HCI_QUIRK_BOARD_PHY_TIMINGS to apply board-specific PHY timing from DT
plus six quirks for everything that depends on the ASPEED vendor
capability:
- HCI_QUIRK_VENDOR_BUS_SETUP to bring up the bus
- HCI_QUIRK_VENDOR_INTR_SUMMARY for ASPEED's interrupt roll-up
- HCI_QUIRK_VENDOR_RESOURCES for further clocks and resets
- HCI_QUIRK_VENDOR_DAA_INDEX for access to DAA index registers
- HCI_QUIRK_IBI_PAYLOAD_THLD to bound IBI transfers at the controller
- HCI_QUIRK_VENDOR_TX_AVAIL for precise ringbuffer operations
Generic MIPI I3C HCI controllers are unaffected.
The series is organised as follows:
- Patch 1 defines timing properties for the devicetree to manage the
impact of board design on bus operation.
- Patch 2 documents the AST2700 devicetree binding.
- Patches 3-5 add the generic HCI quirks AST2700 needs --
address-indexed DAT slots, 64-bit DMA addressing, and clearing the
TX start threshold in DMA mode -- with no AST2700-specific code yet;
nothing sets these quirks until patch 6.
- Patch 6 adds support for the AST2700 I3C controller by implementing
the controller-specific quirks and enabling all necessary quirks in
the MIPI HCI driver to drive the AST2700 controller. It recognizes
the ASPEED vendor extended capability, brings the controller up for
transfers (master mode, PHY timing, reset/clock resources, the
interrupt summary path, master clock stall), and enables all eleven
quirks for the "aspeed,ast2700-i3c-hci" compatible.
- Patch 7 adds the IBI termination threshold, a safeguard against a
misbehaving device that never terminates a payload IBI on its own.
- Patches 8-9 tune bus behaviour: PIO TX queue utilization, and the
PHY's built-in pull-ups for boards without external resistors.
Tested on an AST2700 EVB: the controller probes in both DMA and PIO modes,
completes DAA, and runs CCC, IBI, and private transfers in DMA mode.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
Changes in v2:
- Rebase onto the latest i3c/next
- New patch: move the AST2700 timing properties into the generic
i3c.yaml binding, renamed to match its existing i3c-prefixed naming
convention
- Remove is_aspeed(), replace it with seven discrete quirk bits
- Rename vendor_aspeed.{c,h} to aspeed.{c,h}; keep struct
aspeed_i3c_vendor_data private to aspeed.c
- Add a probe-time check for the ASPEED vendor capability being
missing, ahead of the first vendor register access
- Drop the NULL check in hci_extcap_vendor_ASPEED(); vendor_data is
always allocated by the time it runs
- Fix aspeed_i3c_set_daa_index() to clear all four DAA index register
banks, not just the one it is about to set
- Drop the dma_set_mask_and_coherent() return value check; it cannot
fail for a 64-bit mask
- Revert the pio.h register split; add an accessor function instead
- Drop the unused HCI_DAT_ANY_ADDRESS macro
- Make the Open Drain PHY timing pair all-or-nothing, matching the
Push-Pull pair
- Acquire the vendor clock as mandatory, matching the binding's
required clocks
- Acquire the dma reset with
devm_reset_control_get_optional_shared_deasserted(), instead of a
manual deassert and cleanup action
- Pulse the core reset at probe instead of only deasserting it, and
reassert it on driver detach.
- Limit the SDA hold time override (i3c-pp-sda-hold-ns) to Push-Pull
timing; Open Drain always uses its hardware default
- Link to v1: https://lore.kernel.org/r/20260901-b4-i3c-hci-ast2700-v1-0-19909e7cbd7e@aspeedtech.com
---
Billy Tsai (9):
dt-bindings: i3c: Document generic bus timing properties
dt-bindings: i3c: Document the AST2700 I3C controller
i3c: mipi-i3c-hci: Support address-indexed DAT slots
i3c: mipi-i3c-hci: Add a quirk for 64-bit DMA addressing
i3c: mipi-i3c-hci: Add a quirk to clear the TX start threshold
i3c: mipi-i3c-hci: Add support for the AST2700 I3C controller
i3c: mipi-i3c-hci: Program AST2700 IBI termination threshold
i3c: mipi-i3c-hci: Improve AST2700 PIO TX queue utilization
i3c: mipi-i3c-hci: Support the AST2700 internal pull-ups
.../bindings/i3c/aspeed,ast2700-i3c-hci.yaml | 89 ++++
Documentation/devicetree/bindings/i3c/i3c.yaml | 86 ++++
drivers/i3c/master/mipi-i3c-hci/Makefile | 2 +-
drivers/i3c/master/mipi-i3c-hci/aspeed.c | 500 +++++++++++++++++++++
drivers/i3c/master/mipi-i3c-hci/aspeed.h | 177 ++++++++
drivers/i3c/master/mipi-i3c-hci/cmd_v1.c | 38 +-
drivers/i3c/master/mipi-i3c-hci/core.c | 195 +++++++-
drivers/i3c/master/mipi-i3c-hci/dat.h | 6 +-
drivers/i3c/master/mipi-i3c-hci/dat_v1.c | 63 ++-
drivers/i3c/master/mipi-i3c-hci/ext_caps.c | 2 +
drivers/i3c/master/mipi-i3c-hci/ext_caps.h | 1 +
drivers/i3c/master/mipi-i3c-hci/hci.h | 11 +
drivers/i3c/master/mipi-i3c-hci/pio.c | 32 +-
13 files changed, 1153 insertions(+), 49 deletions(-)
---
base-commit: c899ff589ac6922c7ff72badd8f0664b974bc850
change-id: 20260714-b4-i3c-hci-ast2700-bad726e20c01
Best regards,
--
Billy Tsai <billy_tsai@aspeedtech.com>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 1/9] dt-bindings: i3c: Document generic bus timing properties
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
@ 2026-11-11 3:16 ` Billy Tsai
2026-11-11 16:50 ` Frank Li
2026-11-11 3:16 ` [PATCH v2 2/9] dt-bindings: i3c: Document the AST2700 I3C controller Billy Tsai
` (7 subsequent siblings)
8 siblings, 1 reply; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
The push-pull and open-drain SCL periods, push-pull SDA output hold
time, and the tCAS/tCBP periods around START and STOP conditions are
normally derived from i3c-scl-hz, but board-level trace length,
pull-up strength and capacitive loading can require different values
than what that derivation produces for a given board.
Document properties to override each of these independently, naming
them after the MIPI I3C Basic Specification v1.1.1 timing parameters
they adjust:
- i3c-pp-scl-high-period-ns, i3c-pp-scl-low-period-ns
tHIGH, tLOW: Table 87 (I3C Push-Pull Timing Parameters)
- i3c-od-scl-high-period-ns, i3c-od-scl-low-period-ns
tHIGH, tLOW_OD: Table 86 (I3C Open Drain Timing Parameters)
- i3c-pp-sda-hold-ns
tHD_PP: Table 87 (I3C Push-Pull Timing Parameters)
- i3c-cas-period-ns, i3c-cbp-period-ns
tCAS, tCBP: Table 86, Figures 144/145 (I3C Open Drain Timing
Parameters). These carry the protocol minima as constraints.
- i3c-sr-p-scl-low-period-ns
The SCL low period held while a Controller stalls the clock in
the T-Bit phase before a STOP or repeated START, per Section
5.1.2.5 "Controller Clock Stalling" (Figures 19 and 20). The
specification describes this requirement qualitatively and does
not assign it a dedicated timing symbol, unlike the other
properties above.
Any property left out keeps the derived default.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
Documentation/devicetree/bindings/i3c/i3c.yaml | 86 ++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/Documentation/devicetree/bindings/i3c/i3c.yaml b/Documentation/devicetree/bindings/i3c/i3c.yaml
index 5603f2e7807d..30f65fd1afa7 100644
--- a/Documentation/devicetree/bindings/i3c/i3c.yaml
+++ b/Documentation/devicetree/bindings/i3c/i3c.yaml
@@ -57,6 +57,82 @@ properties:
May not be supported by all controllers.
+ i3c-pp-scl-high-period-ns:
+ description: |
+ Push-pull SCL high period override, in nanoseconds (tHIGH, MIPI
+ I3C Basic Specification v1.1.1 Table 87). When undefined, the
+ value is derived from i3c-scl-hz.
+
+ May not be supported by all controllers.
+
+ i3c-pp-scl-low-period-ns:
+ description: |
+ Push-pull SCL low period override, in nanoseconds (tLOW, MIPI I3C
+ Basic Specification v1.1.1 Table 87). When undefined, the value is
+ derived from i3c-scl-hz.
+
+ May not be supported by all controllers.
+
+ i3c-od-scl-high-period-ns:
+ description: |
+ Open-drain SCL high period override, in nanoseconds (tHIGH, MIPI
+ I3C Basic Specification v1.1.1 Table 86). Open-drain timing is not
+ a function of i3c-scl-hz; when undefined, the controller uses its
+ own default.
+
+ May not be supported by all controllers.
+
+ i3c-od-scl-low-period-ns:
+ description: |
+ Open-drain SCL low period override, in nanoseconds (tLOW_OD, MIPI
+ I3C Basic Specification v1.1.1 Table 86). Open-drain timing is not
+ a function of i3c-scl-hz; when undefined, the controller uses its
+ own default.
+
+ May not be supported by all controllers.
+
+ i3c-pp-sda-hold-ns:
+ description: |
+ Push-pull SDA output hold time override relative to the SCL edge,
+ in nanoseconds (tHD_PP, MIPI I3C Basic Specification v1.1.1 Table
+ 87). When undefined, the controller uses its own default.
+
+ May not be supported by all controllers.
+
+ i3c-cas-period-ns:
+ minimum: 39
+ description: |
+ Clock-after-start (tCAS) period override driven after a START
+ condition, in nanoseconds (MIPI I3C Basic Specification v1.1.1
+ Table 86, Figure 144). When absent it is derived from the SCL low
+ period of the applicable bus context, clamped to the protocol
+ minimum.
+
+ May not be supported by all controllers.
+
+ i3c-cbp-period-ns:
+ minimum: 20
+ description: |
+ Clock-before-stop (tCBP) period override driven before a STOP
+ condition, in nanoseconds (MIPI I3C Basic Specification v1.1.1
+ Table 86, Figure 145). When absent it is derived from the SCL low
+ period of the applicable bus context, clamped to the protocol
+ minimum.
+
+ May not be supported by all controllers.
+
+ i3c-sr-p-scl-low-period-ns:
+ description: |
+ SCL low period override used while extending (stalling) the clock
+ in the T-Bit phase before a STOP or repeated START condition, in
+ nanoseconds (MIPI I3C Basic Specification v1.1.1 Section 5.1.2.5
+ "Controller Clock Stalling", Figures 19 and 20; no dedicated
+ timing symbol is defined there). Kept separate from the push-pull
+ SCL low period to satisfy that requirement. When undefined, the
+ controller uses its own default.
+
+ May not be supported by all controllers.
+
mctp-controller:
type: boolean
description: |
@@ -67,6 +143,16 @@ required:
- "#address-cells"
- "#size-cells"
+dependencies:
+ i3c-pp-scl-high-period-ns:
+ - i3c-pp-scl-low-period-ns
+ i3c-pp-scl-low-period-ns:
+ - i3c-pp-scl-high-period-ns
+ i3c-od-scl-high-period-ns:
+ - i3c-od-scl-low-period-ns
+ i3c-od-scl-low-period-ns:
+ - i3c-od-scl-high-period-ns
+
patternProperties:
"@[0-9a-f]+$":
type: object
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 2/9] dt-bindings: i3c: Document the AST2700 I3C controller
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
2026-11-11 3:16 ` [PATCH v2 1/9] dt-bindings: i3c: Document generic bus timing properties Billy Tsai
@ 2026-11-11 3:16 ` Billy Tsai
2026-11-11 3:16 ` [PATCH v2 3/9] i3c: mipi-i3c-hci: Support address-indexed DAT slots Billy Tsai
` (6 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
The AST2700 I3C controller implements the MIPI I3C HCI programming
model, extended with an ASPEED in-house control block and a PHY
programming window advertised through an HCI vendor extended
capability.
The controller takes a core clock for PHY timing and two resets,
named in reset-names as the controller's own reset and its DMA engine
reset.
Boards without external I3C pull-up resistors can use the PHY's
built-in pull-ups instead, selected with bias-pull-up; the achievable
strength set depends on which pin group backs the bus, since that
fixes the pin voltage domain. Reuse bias-pull-up (pincfg-node.yaml)
for the strength in Ohms; it is declared on this node rather than a
pin-config node since the pull-up switch is part of the I3C PHY, not
the broader SoC pin control IPs.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
We considered expressing bias-pull-up's dependency on a pinctrl state
in the schema, but couldn't find a clean way to say "depends on
whichever pinctrl-N is active" without hard-coding pinctrl-0
specifically, which would be wrong in general (a node's active pin
state doesn't have to sit at index 0). We've added pinctrl-0 to the
example to illustrate the typical usage instead, without a formal
schema-level dependency.
---
.../bindings/i3c/aspeed,ast2700-i3c-hci.yaml | 89 ++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/Documentation/devicetree/bindings/i3c/aspeed,ast2700-i3c-hci.yaml b/Documentation/devicetree/bindings/i3c/aspeed,ast2700-i3c-hci.yaml
new file mode 100644
index 000000000000..873398cbbcf6
--- /dev/null
+++ b/Documentation/devicetree/bindings/i3c/aspeed,ast2700-i3c-hci.yaml
@@ -0,0 +1,89 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i3c/aspeed,ast2700-i3c-hci.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ASPEED AST2700 I3C controller
+
+maintainers:
+ - Billy Tsai <billy_tsai@aspeedtech.com>
+
+description:
+ The AST2700 I3C controller implements the MIPI I3C HCI programming model,
+ extended with an ASPEED in-house control block and a PHY programming window
+ that are advertised through an HCI vendor extended capability.
+
+properties:
+ compatible:
+ const: aspeed,ast2700-i3c-hci
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ resets:
+ items:
+ - description: Core controller reset.
+ - description: DMA engine reset shared by all I3C controllers.
+
+ reset-names:
+ items:
+ - const: core
+ - const: dma
+
+ bias-pull-up:
+ $ref: /schemas/pinctrl/pincfg-node.yaml#/properties/bias-pull-up
+ enum: [177, 231, 261, 317, 400, 470, 550, 600, 750]
+ description:
+ Enable the PHY-internal pull-ups on SCL and SDA and select their
+ strength, in Ohms. High-voltage pins support 400, 470 or 600 Ohms, and
+ low-voltage pins support 177, 231, 261, 317, 400, 550 or 750 Ohms. When
+ absent the internal pull-ups stay disabled and the bus relies on
+ external pull-ups.
+
+ i3c-od-scl-high-period-ns:
+ default: 380
+
+ i3c-od-scl-low-period-ns:
+ default: 620
+
+ i3c-pp-sda-hold-ns:
+ default: 10
+
+ i3c-sr-p-scl-low-period-ns:
+ default: 40
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - resets
+ - reset-names
+
+allOf:
+ - $ref: i3c.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i3c@14c24000 {
+ compatible = "aspeed,ast2700-i3c-hci";
+ reg = <0x14c24000 0x1000>;
+ interrupts = <100>;
+ clocks = <&syscon 20>;
+ resets = <&syscon 37>, <&syscon 34>;
+ reset-names = "core", "dma";
+ bias-pull-up = <177>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i3c4_default>;
+ #address-cells = <3>;
+ #size-cells = <0>;
+ };
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 3/9] i3c: mipi-i3c-hci: Support address-indexed DAT slots
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
2026-11-11 3:16 ` [PATCH v2 1/9] dt-bindings: i3c: Document generic bus timing properties Billy Tsai
2026-11-11 3:16 ` [PATCH v2 2/9] dt-bindings: i3c: Document the AST2700 I3C controller Billy Tsai
@ 2026-11-11 3:16 ` Billy Tsai
2026-11-11 3:16 ` [PATCH v2 4/9] i3c: mipi-i3c-hci: Add a quirk for 64-bit DMA addressing Billy Tsai
` (5 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
HCI v1's DEV_INDEX allocation is sequential: the driver picks any free
DAT slot, with no relationship between the slot number and the
device's I3C address. ASPEED's I3C controllers have long needed to
support more devices than the standard 5-bit DEV_INDEX field
(addresses 0-31) allows, so they use a layout where the DAT slot
number is itself the device's address, requiring DEV_INDEX to widen
to 7 bits (addresses 0-127).
The existence of I2C devices on the bus identifies why the ASPEED
controller design can't work with the driver assuming the 5-bit
DEV_INDEX field: their addresses are fixed by the device or board
configuration, not assigned by the controller, so they cannot be
restricted to addresses below 0x20. The same is true for I3C devices
that only support SETAASA: their dynamic address is their existing
static address, again fixed by the device or board rather than chosen
by the controller. With a 5-bit DEV_INDEX, only slots 0-31 exist, so
any such device landing at 0x20 or above cannot be placed in the DAT
at all, regardless of how many other devices are on the bus.
Add HCI_QUIRK_DAT_INDEX_IS_ADDR for controllers with this layout:
allocate address-indexed DAT slots and emit the wider DEV_INDEX field
mask. Moving an entry on a dynamic address change can fail if its slot
is already taken, so set_dynamic_addr() now returns an error that the
reattach and DAA paths propagate. Controllers without the quirk keep
the existing behavior.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
drivers/i3c/master/mipi-i3c-hci/cmd_v1.c | 35 ++++++++++++------
drivers/i3c/master/mipi-i3c-hci/core.c | 19 +++++++---
drivers/i3c/master/mipi-i3c-hci/dat.h | 6 ++-
drivers/i3c/master/mipi-i3c-hci/dat_v1.c | 63 +++++++++++++++++++++++++++++---
drivers/i3c/master/mipi-i3c-hci/hci.h | 1 +
5 files changed, 99 insertions(+), 25 deletions(-)
diff --git a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c
index 78ad2ec2bd93..d267aa19cb75 100644
--- a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c
+++ b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c
@@ -139,6 +139,14 @@ static enum hci_cmd_mode get_i2c_mode(struct i3c_hci *hci)
return MODE_I2C_Fm;
}
+static u32 cmd_v1_dev_index(struct i3c_hci *hci, unsigned int dat_idx)
+{
+ u32 mask = hci->quirks & HCI_QUIRK_DAT_INDEX_IS_ADDR ?
+ W0_MASK(22, 16) : W0_MASK(20, 16);
+
+ return field_prep(mask, dat_idx);
+}
+
static void fill_data_bytes(struct hci_xfer *xfer, u8 *data,
unsigned int data_len)
{
@@ -193,7 +201,7 @@ static int hci_cmd_v1_prep_ccc(struct i3c_hci *hci,
CMD_0_ATTR_I |
CMD_I0_TID(xfer->cmd_tid) |
CMD_I0_CMD(ccc_cmd) | CMD_I0_CP |
- CMD_I0_DEV_INDEX(dat_idx) |
+ cmd_v1_dev_index(hci, dat_idx) |
CMD_I0_DTT(data_len) |
CMD_I0_MODE(mode);
fill_data_bytes(xfer, data, data_len);
@@ -203,7 +211,7 @@ static int hci_cmd_v1_prep_ccc(struct i3c_hci *hci,
CMD_0_ATTR_R |
CMD_R0_TID(xfer->cmd_tid) |
CMD_R0_CMD(ccc_cmd) | CMD_R0_CP |
- CMD_R0_DEV_INDEX(dat_idx) |
+ cmd_v1_dev_index(hci, dat_idx) |
CMD_R0_MODE(mode) |
(rnw ? CMD_R0_RNW : 0);
xfer->cmd_desc[1] =
@@ -247,7 +255,7 @@ static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci,
xfer->cmd_desc[0] =
CMD_0_ATTR_I |
CMD_I0_TID(xfer->cmd_tid) |
- CMD_I0_DEV_INDEX(dat_idx) |
+ cmd_v1_dev_index(hci, dat_idx) |
CMD_I0_DTT(data_len) |
CMD_I0_MODE(mode) |
cp_cmd;
@@ -257,7 +265,7 @@ static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci,
xfer->cmd_desc[0] =
CMD_0_ATTR_R |
CMD_R0_TID(xfer->cmd_tid) |
- CMD_R0_DEV_INDEX(dat_idx) |
+ cmd_v1_dev_index(hci, dat_idx) |
CMD_R0_MODE(mode) |
(rnw ? CMD_R0_RNW : 0) |
cp_cmd;
@@ -284,7 +292,7 @@ static void hci_cmd_v1_prep_i2c_xfer(struct i3c_hci *hci,
xfer->cmd_desc[0] =
CMD_0_ATTR_I |
CMD_I0_TID(xfer->cmd_tid) |
- CMD_I0_DEV_INDEX(dat_idx) |
+ cmd_v1_dev_index(hci, dat_idx) |
CMD_I0_DTT(data_len) |
CMD_I0_MODE(mode);
fill_data_bytes(xfer, data, data_len);
@@ -293,7 +301,7 @@ static void hci_cmd_v1_prep_i2c_xfer(struct i3c_hci *hci,
xfer->cmd_desc[0] =
CMD_0_ATTR_R |
CMD_R0_TID(xfer->cmd_tid) |
- CMD_R0_DEV_INDEX(dat_idx) |
+ cmd_v1_dev_index(hci, dat_idx) |
CMD_R0_MODE(mode) |
(rnw ? CMD_R0_RNW : 0);
xfer->cmd_desc[1] =
@@ -322,19 +330,22 @@ static int hci_cmd_v1_daa(struct i3c_hci *hci)
* Yes, there is room for improvements.
*/
for (;;) {
- ret = mipi_i3c_hci_dat_v1.alloc_entry(hci);
- if (ret < 0)
- break;
- dat_idx = ret;
ret = i3c_master_get_free_addr(&hci->master, next_addr);
if (ret < 0)
break;
next_addr = ret;
+ ret = mipi_i3c_hci_dat_v1.alloc_entry(hci, next_addr);
+ if (ret < 0)
+ break;
+ dat_idx = ret;
dev_dbg(&hci->master.dev,
"next_addr = 0x%02x, DAA using DAT %d",
next_addr, dat_idx);
- mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, dat_idx, next_addr);
+ ret = mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, dat_idx,
+ next_addr);
+ if (ret < 0)
+ break;
mipi_i3c_hci_dct_index_reset(hci);
xfer->cmd_tid = hci_get_tid();
@@ -342,7 +353,7 @@ static int hci_cmd_v1_daa(struct i3c_hci *hci)
CMD_0_ATTR_A |
CMD_A0_TID(xfer->cmd_tid) |
CMD_A0_CMD(I3C_CCC_ENTDAA) |
- CMD_A0_DEV_INDEX(dat_idx) |
+ cmd_v1_dev_index(hci, dat_idx) |
CMD_A0_DEV_COUNT(1) |
CMD_A0_ROC | CMD_A0_TOC;
xfer->cmd_desc[1] = 0;
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index 23853ca134fb..b8f8254ed19c 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -608,7 +608,8 @@ static int i3c_hci_attach_i3c_dev(struct i3c_dev_desc *dev)
if (!dev_data)
return -ENOMEM;
if (hci->cmd == &mipi_i3c_hci_cmd_v1) {
- ret = mipi_i3c_hci_dat_v1.alloc_entry(hci);
+ ret = mipi_i3c_hci_dat_v1.alloc_entry(hci,
+ dev->info.dyn_addr ?: dev->info.static_addr);
if (ret < 0) {
kfree(dev_data);
return ret;
@@ -628,9 +629,17 @@ static int i3c_hci_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_addr)
struct i3c_hci *hci = to_i3c_hci(m);
struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev);
- if (hci->cmd == &mipi_i3c_hci_cmd_v1)
- mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, dev_data->dat_idx,
- dev->info.dyn_addr);
+ if (hci->cmd == &mipi_i3c_hci_cmd_v1) {
+ int ret;
+
+ ret = mipi_i3c_hci_dat_v1.set_dynamic_addr(hci,
+ dev_data->dat_idx,
+ dev->info.dyn_addr);
+ if (ret)
+ return ret;
+ if (hci->quirks & HCI_QUIRK_DAT_INDEX_IS_ADDR)
+ dev_data->dat_idx = dev->info.dyn_addr;
+ }
return 0;
}
@@ -658,7 +667,7 @@ static int i3c_hci_attach_i2c_dev(struct i2c_dev_desc *dev)
dev_data = kzalloc_obj(*dev_data);
if (!dev_data)
return -ENOMEM;
- ret = mipi_i3c_hci_dat_v1.alloc_entry(hci);
+ ret = mipi_i3c_hci_dat_v1.alloc_entry(hci, dev->addr);
if (ret < 0) {
kfree(dev_data);
return ret;
diff --git a/drivers/i3c/master/mipi-i3c-hci/dat.h b/drivers/i3c/master/mipi-i3c-hci/dat.h
index 31becc6f9aec..622f8e248463 100644
--- a/drivers/i3c/master/mipi-i3c-hci/dat.h
+++ b/drivers/i3c/master/mipi-i3c-hci/dat.h
@@ -10,6 +10,8 @@
#ifndef DAT_H
#define DAT_H
+struct i3c_hci;
+
/* Global DAT flags */
#define DAT_0_I2C_DEVICE W0_BIT_(31)
#define DAT_0_DEV_NACK_RETRY_CNT W0_MASK(30, 29)
@@ -18,9 +20,9 @@
struct hci_dat_ops {
int (*init)(struct i3c_hci *hci);
- int (*alloc_entry)(struct i3c_hci *hci);
+ int (*alloc_entry)(struct i3c_hci *hci, unsigned int address);
void (*free_entry)(struct i3c_hci *hci, unsigned int dat_idx);
- void (*set_dynamic_addr)(struct i3c_hci *hci, unsigned int dat_idx, u8 addr);
+ int (*set_dynamic_addr)(struct i3c_hci *hci, unsigned int dat_idx, u8 addr);
void (*set_static_addr)(struct i3c_hci *hci, unsigned int dat_idx, u8 addr);
void (*set_flags)(struct i3c_hci *hci, unsigned int dat_idx, u32 w0, u32 w1);
void (*clear_flags)(struct i3c_hci *hci, unsigned int dat_idx, u32 w0, u32 w1);
diff --git a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c
index ec0764a27c62..485ad0a44b34 100644
--- a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c
+++ b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c
@@ -89,7 +89,7 @@ static int hci_dat_v1_init(struct i3c_hci *hci)
return 0;
}
-static int hci_dat_v1_alloc_entry(struct i3c_hci *hci)
+static int hci_dat_v1_alloc_entry(struct i3c_hci *hci, unsigned int address)
{
unsigned int dat_idx;
int ret;
@@ -99,9 +99,17 @@ static int hci_dat_v1_alloc_entry(struct i3c_hci *hci)
if (ret)
return ret;
}
- dat_idx = find_first_zero_bit(hci->DAT_data, hci->DAT_entries);
- if (dat_idx >= hci->DAT_entries)
- return -ENOENT;
+ if (hci->quirks & HCI_QUIRK_DAT_INDEX_IS_ADDR) {
+ if (address >= hci->DAT_entries)
+ return -EINVAL;
+ if (test_bit_acquire(address, hci->DAT_data))
+ return -ENOENT;
+ dat_idx = address;
+ } else {
+ dat_idx = find_first_zero_bit(hci->DAT_data, hci->DAT_entries);
+ if (dat_idx >= hci->DAT_entries)
+ return -ENOENT;
+ }
__set_bit(dat_idx, hci->DAT_data);
/* default flags */
@@ -118,9 +126,43 @@ static void hci_dat_v1_free_entry(struct i3c_hci *hci, unsigned int dat_idx)
__clear_bit(dat_idx, hci->DAT_data);
}
-static void hci_dat_v1_set_dynamic_addr(struct i3c_hci *hci,
- unsigned int dat_idx, u8 address)
+static int hci_dat_v1_set_dynamic_addr(struct i3c_hci *hci,
+ unsigned int dat_idx, u8 address)
{
+ if (hci->quirks & HCI_QUIRK_DAT_INDEX_IS_ADDR) {
+ u32 dat_w0, dat_w1;
+
+ /*
+ * The controller derives the device address from the DAT slot
+ * index, so DAT_0_DYNAMIC_ADDRESS is never written: an address
+ * change moves the whole entry to the slot named by the new
+ * address instead.
+ */
+ if (dat_idx == address)
+ return 0;
+ if (address >= hci->DAT_entries) {
+ dev_err(&hci->master.dev, "invalid DAT slot %u\n", address);
+ return -EINVAL;
+ }
+ if (test_bit(address, hci->DAT_data)) {
+ dev_err(&hci->master.dev, "DAT slot %u already in use\n",
+ address);
+ return -EBUSY;
+ }
+
+ dat_w0 = dat_w0_read(dat_idx);
+ dat_w1 = dat_w1_read(dat_idx);
+
+ __set_bit(address, hci->DAT_data);
+ dat_w0_write(address, dat_w0);
+ dat_w1_write(address, dat_w1);
+
+ dat_w0_write(dat_idx, 0);
+ dat_w1_write(dat_idx, 0);
+ __clear_bit(dat_idx, hci->DAT_data);
+ return 0;
+ }
+
u32 dat_w0;
dat_w0 = dat_w0_read(dat_idx);
@@ -128,6 +170,8 @@ static void hci_dat_v1_set_dynamic_addr(struct i3c_hci *hci,
dat_w0 |= FIELD_PREP(DAT_0_DYNAMIC_ADDRESS, address) |
(parity8(address) ? 0 : DAT_0_DYNADDR_PARITY);
dat_w0_write(dat_idx, dat_w0);
+
+ return 0;
}
static void hci_dat_v1_set_static_addr(struct i3c_hci *hci,
@@ -172,6 +216,13 @@ static int hci_dat_v1_get_index(struct i3c_hci *hci, u8 dev_addr)
unsigned int dat_idx;
u32 dat_w0;
+ if (hci->quirks & HCI_QUIRK_DAT_INDEX_IS_ADDR) {
+ if (dev_addr < hci->DAT_entries &&
+ test_bit(dev_addr, hci->DAT_data))
+ return dev_addr;
+ return -ENODEV;
+ }
+
for_each_set_bit(dat_idx, hci->DAT_data, hci->DAT_entries) {
dat_w0 = dat_w0_read(dat_idx);
if (FIELD_GET(DAT_0_DYNAMIC_ADDRESS, dat_w0) == dev_addr)
diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h
index 4288a1b5b491..2e610373600e 100644
--- a/drivers/i3c/master/mipi-i3c-hci/hci.h
+++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
@@ -178,6 +178,7 @@ struct i3c_hci_dev_data {
#define HCI_QUIRK_RPM_PARENT_MANAGED BIT(7) /* Runtime PM managed by parent device */
#define HCI_QUIRK_DMA_ABORT_REQUIRES_PIO_RESET BIT(8) /* Do PIO queue SW resets after DMA abort */
#define HCI_QUIRK_DMA_REQUIRES_HC_ABORT BIT(9) /* Use HC_CONTROL ABORT to abort DMA */
+#define HCI_QUIRK_DAT_INDEX_IS_ADDR BIT(10) /* DAT entries are indexed by device address */
/* global functions */
void mipi_i3c_hci_resume(struct i3c_hci *hci);
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 4/9] i3c: mipi-i3c-hci: Add a quirk for 64-bit DMA addressing
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
` (2 preceding siblings ...)
2026-11-11 3:16 ` [PATCH v2 3/9] i3c: mipi-i3c-hci: Support address-indexed DAT slots Billy Tsai
@ 2026-11-11 3:16 ` Billy Tsai
2026-11-11 3:16 ` [PATCH v2 5/9] i3c: mipi-i3c-hci: Add a quirk to clear the TX start threshold Billy Tsai
` (4 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
The HCI ring base registers are 64 bits wide by specification, but not
every implementation wires the upper half, so the driver leaves the
platform device at the default 32-bit DMA mask unless told otherwise.
ASPEED platforms place all of DRAM above the 32-bit boundary
(0x4_00000000), so under the default mask every dma_alloc_coherent()
call for the DMA rings fails.
Add HCI_QUIRK_DMA_64BIT for controllers whose DMA engine drives the
full address width, and declare a 64-bit streaming and coherent DMA
mask for them before the rings are allocated.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
drivers/i3c/master/mipi-i3c-hci/core.c | 4 ++++
drivers/i3c/master/mipi-i3c-hci/hci.h | 1 +
2 files changed, 5 insertions(+)
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index b8f8254ed19c..25d0aba6b300 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -10,6 +10,7 @@
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/device.h>
+#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/i3c/master.h>
#include <linux/interrupt.h>
@@ -1231,6 +1232,9 @@ static int i3c_hci_probe(struct platform_device *pdev)
if (!hci->quirks && platform_get_device_id(pdev))
hci->quirks = platform_get_device_id(pdev)->driver_data;
+ if (hci->quirks & HCI_QUIRK_DMA_64BIT)
+ dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+
ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &clks);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h
index 2e610373600e..18a28360c728 100644
--- a/drivers/i3c/master/mipi-i3c-hci/hci.h
+++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
@@ -179,6 +179,7 @@ struct i3c_hci_dev_data {
#define HCI_QUIRK_DMA_ABORT_REQUIRES_PIO_RESET BIT(8) /* Do PIO queue SW resets after DMA abort */
#define HCI_QUIRK_DMA_REQUIRES_HC_ABORT BIT(9) /* Use HC_CONTROL ABORT to abort DMA */
#define HCI_QUIRK_DAT_INDEX_IS_ADDR BIT(10) /* DAT entries are indexed by device address */
+#define HCI_QUIRK_DMA_64BIT BIT(11) /* Controller DMA supports 64-bit addressing */
/* global functions */
void mipi_i3c_hci_resume(struct i3c_hci *hci);
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 5/9] i3c: mipi-i3c-hci: Add a quirk to clear the TX start threshold
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
` (3 preceding siblings ...)
2026-11-11 3:16 ` [PATCH v2 4/9] i3c: mipi-i3c-hci: Add a quirk for 64-bit DMA addressing Billy Tsai
@ 2026-11-11 3:16 ` Billy Tsai
2026-11-11 3:16 ` [PATCH v2 6/9] i3c: mipi-i3c-hci: Add support for the AST2700 I3C controller Billy Tsai
` (3 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
The DATA_TX_START_THLD field of the PIO data buffer threshold register
holds off the start of a TX transfer until the FIFO holds a set amount
of data, which in PIO mode cuts down on the number of software writes.
The field resets to 0x1, requiring at least (2 ^ 2) DWORDs (16 bytes)
in the FIFO before transmission starts.
HCI controllers that support both PIO and DMA can expose the two as
separate register blocks; nothing in the specification says selecting
DMA mode disables the PIO block's own gating logic. On ASPEED
platforms it doesn't: DATA_TX_START_THLD still holds up transfer
start regardless of which mode feeds the FIFO, and in DMA mode that
threshold is never satisfied for some transfer sizes:
- 1-4 bytes: uses the Immediate Data Transfer Command.
- 13+ bytes: since the hardware fetches data in 4-byte chunks, a
13-byte transfer fetches 16 bytes into the FIFO and reaches the
threshold.
- 5-12 bytes: the threshold is never reached and the transfer stalls.
Add HCI_QUIRK_TX_START_THLD to clear the field whenever DMA mode is
selected. The clear lives in i3c_hci_set_io_mode() rather than in
probe so the reset-and-restore recovery path also reapplies it after
a controller soft reset.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
drivers/i3c/master/mipi-i3c-hci/core.c | 8 ++++++++
drivers/i3c/master/mipi-i3c-hci/hci.h | 2 ++
drivers/i3c/master/mipi-i3c-hci/pio.c | 7 +++++++
3 files changed, 17 insertions(+)
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index 25d0aba6b300..da77384e985c 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -884,6 +884,14 @@ static int i3c_hci_set_io_mode(struct i3c_hci *hci, bool dma)
else
reg_set(HC_CONTROL, HC_CONTROL_PIO_MODE);
+ /*
+ * On the ASPEED AST2700 the TX start threshold gates transfer start
+ * even in DMA mode. Clear it so DMA transfers are not held back
+ * waiting for a PIO FIFO level that will never be reached.
+ */
+ if (dma && (hci->quirks & HCI_QUIRK_TX_START_THLD) && hci->PIO_regs)
+ mipi_i3c_hci_pio_clr_tx_start_thld(hci);
+
if (!is_version_1_1_or_newer(hci))
return 0;
diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h
index 18a28360c728..3f28f821bf0d 100644
--- a/drivers/i3c/master/mipi-i3c-hci/hci.h
+++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
@@ -180,12 +180,14 @@ struct i3c_hci_dev_data {
#define HCI_QUIRK_DMA_REQUIRES_HC_ABORT BIT(9) /* Use HC_CONTROL ABORT to abort DMA */
#define HCI_QUIRK_DAT_INDEX_IS_ADDR BIT(10) /* DAT entries are indexed by device address */
#define HCI_QUIRK_DMA_64BIT BIT(11) /* Controller DMA supports 64-bit addressing */
+#define HCI_QUIRK_TX_START_THLD BIT(12) /* Clear TX start threshold in DMA mode */
/* global functions */
void mipi_i3c_hci_resume(struct i3c_hci *hci);
void mipi_i3c_hci_abort(struct i3c_hci *hci);
void mipi_i3c_hci_pio_reset(struct i3c_hci *hci);
void mipi_i3c_hci_pio_reset_all_queues(struct i3c_hci *hci);
+void mipi_i3c_hci_pio_clr_tx_start_thld(struct i3c_hci *hci);
void mipi_i3c_hci_dct_index_reset(struct i3c_hci *hci);
void amd_set_od_pp_timing(struct i3c_hci *hci);
void amd_set_resp_buf_thld(struct i3c_hci *hci);
diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c
index 439578a6eb54..a95ef7fc351c 100644
--- a/drivers/i3c/master/mipi-i3c-hci/pio.c
+++ b/drivers/i3c/master/mipi-i3c-hci/pio.c
@@ -118,6 +118,13 @@
STAT_LATENCY_ERRORS | \
STAT_PROG_ERRORS)
+void mipi_i3c_hci_pio_clr_tx_start_thld(struct i3c_hci *hci)
+{
+ u32 val = pio_reg_read(DATA_BUFFER_THLD_CTRL);
+
+ pio_reg_write(DATA_BUFFER_THLD_CTRL, val & ~DATA_TX_START_THLD);
+}
+
struct hci_pio_dev_ibi_data {
struct i3c_generic_ibi_pool *pool;
unsigned int max_len;
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 6/9] i3c: mipi-i3c-hci: Add support for the AST2700 I3C controller
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
` (4 preceding siblings ...)
2026-11-11 3:16 ` [PATCH v2 5/9] i3c: mipi-i3c-hci: Add a quirk to clear the TX start threshold Billy Tsai
@ 2026-11-11 3:16 ` Billy Tsai
2026-11-11 3:16 ` [PATCH v2 7/9] i3c: mipi-i3c-hci: Program AST2700 IBI termination threshold Billy Tsai
` (2 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
The AST2700 I3C controller carries an ASPEED vendor extended
capability describing an in-house control block and a PHY programming
window. Recognize the ASPEED MIPI vendor ID in the extended capability
parser and cache both register bases via the generic vendor_data
pointer, which other vendors (e.g. NXP) also populate.
Bringing the controller up for transfers needs more than the generic
HCI reset sequence: the vendor block has to be switched to master
mode, PHY timing registers programmed from the selected bus rate (with
i3c-*-ns device tree properties to override values derived under
nominal bus loading), and all interrupts funneled through a vendor
summary register whose handler dispatches to the same core and IO
handlers as the generic path. Master clock stall is enabled alongside
master-mode init so an underrun pauses and resumes the transfer
instead of aborting it. Hook this initialization into bus setup and
resume, and acquire the core clock and reset-names resources the
binding describes for it; probe fails if the vendor capability was
expected but the hardware doesn't actually expose the register blocks
it needs.
During normal operation, the vendor DAA index registers must be told
which DAT slot is being assigned during ENTDAA, clearing all four
index register banks each time since the controller scans forward
across all of them for the first bit set to pick the address to
assign. The PIO/IBI FIFOs also need resetting after a DMA error or
abort, which the core already implements behind
HCI_QUIRK_DMA_ABORT_REQUIRES_PIO_RESET, so set that quirk.
Each of these behaviors gets its own quirk bit
(HCI_QUIRK_VENDOR_BUS_SETUP, HCI_QUIRK_BOARD_PHY_TIMINGS,
HCI_QUIRK_VENDOR_INTR_SUMMARY, HCI_QUIRK_VENDOR_RESOURCES,
HCI_QUIRK_VENDOR_DAA_INDEX), set together for the
"aspeed,ast2700-i3c-hci" compatible alongside DAT_INDEX_IS_ADDR,
DMA_64BIT and TX_START_THLD.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
drivers/i3c/master/mipi-i3c-hci/Makefile | 2 +-
drivers/i3c/master/mipi-i3c-hci/aspeed.c | 351 +++++++++++++++++++++++++++++
drivers/i3c/master/mipi-i3c-hci/aspeed.h | 160 +++++++++++++
drivers/i3c/master/mipi-i3c-hci/cmd_v1.c | 3 +
drivers/i3c/master/mipi-i3c-hci/core.c | 148 ++++++++++--
drivers/i3c/master/mipi-i3c-hci/ext_caps.c | 2 +
drivers/i3c/master/mipi-i3c-hci/ext_caps.h | 1 +
drivers/i3c/master/mipi-i3c-hci/hci.h | 7 +
8 files changed, 658 insertions(+), 16 deletions(-)
diff --git a/drivers/i3c/master/mipi-i3c-hci/Makefile b/drivers/i3c/master/mipi-i3c-hci/Makefile
index e3d3ef757035f..ce560b81be01f 100644
--- a/drivers/i3c/master/mipi-i3c-hci/Makefile
+++ b/drivers/i3c/master/mipi-i3c-hci/Makefile
@@ -4,5 +4,5 @@ obj-$(CONFIG_MIPI_I3C_HCI) += mipi-i3c-hci.o
mipi-i3c-hci-y := core.o ext_caps.o pio.o dma.o \
cmd_v1.o cmd_v2.o \
dat_v1.o dct_v1.o \
- hci_quirks.o
+ hci_quirks.o aspeed.o
obj-$(CONFIG_MIPI_I3C_HCI_PCI) += mipi-i3c-hci-pci.o
diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.c b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
new file mode 100644
index 0000000000000..3aa910e786f8e
--- /dev/null
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2026 ASPEED Technology Inc.
+ *
+ * AST2700 vendor capability handling: resource acquisition, register
+ * access, PHY timing initialization and DAA index support.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/i3c/master.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include "hci.h"
+#include "aspeed.h"
+
+/*
+ * The AST2700 vendor extended capability points to an in-house control
+ * block and a PHY programming window inside the controller's register
+ * space. The core clock and reset lines are only specified in the
+ * AST2700 binding as well. All of it is ASPEED-specific, so it is kept
+ * out of the generic struct i3c_hci and reached instead through its
+ * vendor_data pointer.
+ */
+struct aspeed_i3c_vendor_data {
+ void __iomem *inhouse_regs;
+ void __iomem *phy_regs;
+ struct clk *clk;
+};
+
+int hci_extcap_vendor_ASPEED(struct i3c_hci *hci, void __iomem *base)
+{
+ struct aspeed_i3c_vendor_data *vd = to_aspeed_vendor_data(hci);
+
+ vd->inhouse_regs = hci->base_regs + readl(base + 0x04);
+ vd->phy_regs = hci->base_regs + readl(base + 0x08);
+
+ return 0;
+}
+
+bool aspeed_i3c_vendor_cap_found(struct i3c_hci *hci)
+{
+ struct aspeed_i3c_vendor_data *vd = to_aspeed_vendor_data(hci);
+
+ return vd->inhouse_regs && vd->phy_regs;
+}
+
+static void aspeed_i3c_core_rst_assert(void *data)
+{
+ reset_control_assert(data);
+}
+
+int aspeed_i3c_alloc_resources(struct i3c_hci *hci, struct platform_device *pdev)
+{
+ struct aspeed_i3c_vendor_data *vd;
+ struct reset_control *rst;
+ int ret;
+
+ vd = devm_kzalloc(&pdev->dev, sizeof(*vd), GFP_KERNEL);
+ if (!vd)
+ return -ENOMEM;
+ hci->vendor_data = vd;
+
+ rst = devm_reset_control_get_optional_exclusive(&pdev->dev, "core");
+ if (IS_ERR(rst))
+ return PTR_ERR(rst);
+
+ ret = reset_control_assert(rst);
+ if (ret)
+ return ret;
+
+ ret = reset_control_deassert(rst);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(&pdev->dev, aspeed_i3c_core_rst_assert, rst);
+ if (ret)
+ return ret;
+
+ /*
+ * Unlike "core", "dma" is shared across every I3C instance and the
+ * DMA engine behind it does not tolerate being reset once running,
+ * so it is only ever deasserted here, never pulsed.
+ */
+ rst = devm_reset_control_get_optional_shared_deasserted(&pdev->dev, "dma");
+ if (IS_ERR(rst))
+ return PTR_ERR(rst);
+
+ vd->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(vd->clk))
+ return PTR_ERR(vd->clk);
+
+ return 0;
+}
+
+/*
+ * The DAA index registers name the DAT slot being assigned during ENTDAA;
+ * they live in the vendor capability block discovered at probe time. The
+ * controller scans forward from the named slot across all four banks for
+ * the first bit set to find the address to assign, so a bit left over
+ * from a previous round must be cleared or it is mistaken for the current
+ * one. This assumes exactly one index is armed per ENTDAA attempt,
+ * matching hci_cmd_v1_daa()'s one-device-per-command loop; it is not
+ * meant to arm multiple indices ahead of a single multi-device ENTDAA.
+ */
+void aspeed_i3c_set_daa_index(struct i3c_hci *hci, unsigned int dat_idx)
+{
+ void __iomem *inhouse_regs = to_aspeed_vendor_data(hci)->inhouse_regs;
+
+ writel(0, inhouse_regs + ASPEED_I3C_DAA_INDEX0);
+ writel(0, inhouse_regs + ASPEED_I3C_DAA_INDEX1);
+ writel(0, inhouse_regs + ASPEED_I3C_DAA_INDEX2);
+ writel(0, inhouse_regs + ASPEED_I3C_DAA_INDEX3);
+
+ if (dat_idx < 32)
+ writel(BIT(dat_idx), inhouse_regs + ASPEED_I3C_DAA_INDEX0);
+ else if (dat_idx < 64)
+ writel(BIT(dat_idx - 32), inhouse_regs + ASPEED_I3C_DAA_INDEX1);
+ else if (dat_idx < 96)
+ writel(BIT(dat_idx - 64), inhouse_regs + ASPEED_I3C_DAA_INDEX2);
+ else
+ writel(BIT(dat_idx - 96), inhouse_regs + ASPEED_I3C_DAA_INDEX3);
+}
+
+u32 aspeed_i3c_read(struct i3c_hci *hci, u32 reg)
+{
+ return readl(to_aspeed_vendor_data(hci)->inhouse_regs + reg);
+}
+
+void aspeed_i3c_write(struct i3c_hci *hci, u32 reg, u32 val)
+{
+ writel(val, to_aspeed_vendor_data(hci)->inhouse_regs + reg);
+}
+
+static void aspeed_i3c_phy_write(struct i3c_hci *hci, u32 reg, u32 val)
+{
+ writel(val, to_aspeed_vendor_data(hci)->phy_regs + reg);
+}
+
+static u16 aspeed_i3c_ns_to_cnt(unsigned int ns, unsigned long period_ns)
+{
+ unsigned long cycles = DIV_ROUND_CLOSEST(ns, period_ns);
+
+ /* All PHY counter fields are 11 bits wide */
+ return min_t(unsigned long, max_t(unsigned long, cycles, 1) - 1,
+ ASPEED_I3C_PHY_CNT_MAX);
+}
+
+static u32 aspeed_i3c_get_sdr_ctrl0_reg(struct i3c_hci *hci)
+{
+ struct i3c_bus *bus = i3c_master_get_bus(&hci->master);
+
+ if (bus->scl_rate.i3c > 8000000)
+ return ASPEED_I3C_PHY_I3C_SDR0_CTRL0;
+ if (bus->scl_rate.i3c > 6000000)
+ return ASPEED_I3C_PHY_I3C_SDR1_CTRL0;
+ if (bus->scl_rate.i3c > 4000000)
+ return ASPEED_I3C_PHY_I3C_SDR2_CTRL0;
+ if (bus->scl_rate.i3c > 2000000)
+ return ASPEED_I3C_PHY_I3C_SDR3_CTRL0;
+
+ return ASPEED_I3C_PHY_I3C_SDR4_CTRL0;
+}
+
+int aspeed_i3c_phy_init(struct i3c_hci *hci)
+{
+ u16 hcnt, lcnt, total_cnt, min_tbit_cnt, cas_lcnt, cas_cnt, cbp_cnt;
+ u32 pp_sda_hold = 0, cas_ns = 0, cbp_ns = 0, sr_p_low = 0;
+ struct i3c_bus *bus = i3c_master_get_bus(&hci->master);
+ u32 pp_high = 0, pp_low = 0, od_high = 0, od_low = 0;
+ struct device_node *np = hci->master.dev.of_node;
+ u32 sdr_ctrl0_reg, ctrl0, ctrl1, ctrl2;
+ unsigned long core_rate, period_ns;
+
+ core_rate = clk_get_rate(to_aspeed_vendor_data(hci)->clk);
+ if (!core_rate) {
+ dev_err(&hci->master.dev, "invalid core clock rate\n");
+ return -EINVAL;
+ }
+
+ period_ns = DIV_ROUND_UP(NSEC_PER_SEC, core_rate);
+
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FM_CAS_NS, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FM_SU_STO_NS, period_ns);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I2C_FM_CTRL0,
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FM_CTRL0_CAS, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FM_CTRL0_SU_STO, lcnt));
+
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FM_SCL_H_NS, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FM_SCL_L_NS, period_ns);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I2C_FM_CTRL1,
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FM_CTRL1_SCL_H, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FM_CTRL1_SCL_L, lcnt));
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I2C_FM_CTRL2,
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FM_CTRL2_ACK_H, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FM_CTRL2_ACK_L, hcnt));
+
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FM_HD_DAT_NS, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FM_AHD_DAT_NS, period_ns);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I2C_FM_CTRL3,
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FM_CTRL3_HD_DAT, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FM_CTRL3_AHD_DAT, lcnt));
+
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FMP_CAS_NS, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FMP_SU_STO_NS, period_ns);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I2C_FMP_CTRL0,
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FMP_CTRL0_CAS, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FMP_CTRL0_SU_STO, lcnt));
+
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FMP_SCL_H_NS, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FMP_SCL_L_NS, period_ns);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I2C_FMP_CTRL1,
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FMP_CTRL1_SCL_H, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FMP_CTRL1_SCL_L, lcnt));
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I2C_FMP_CTRL2,
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FMP_CTRL2_ACK_H, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FMP_CTRL2_ACK_L, hcnt));
+
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FMP_HD_DAT_NS, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FMP_AHD_DAT_NS, period_ns);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I2C_FMP_CTRL3,
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FMP_CTRL3_HD_DAT, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I2C_FMP_CTRL3_AHD_DAT, lcnt));
+
+ of_property_read_u32(np, "i3c-pp-scl-high-period-ns", &pp_high);
+ of_property_read_u32(np, "i3c-pp-scl-low-period-ns", &pp_low);
+ if (pp_high && pp_low) {
+ hcnt = aspeed_i3c_ns_to_cnt(pp_high, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(pp_low, period_ns);
+ } else {
+ /*
+ * Each of hcnt/lcnt is programmed as N-1 (the PHY counts N
+ * cycles for a stored value of N-1), so pre-subtract 2 here
+ * to cancel the two implicit +1s and match the requested SCL
+ * period exactly. Clamp the pre-subtraction value so
+ * total_cnt cannot underflow when an unrealistically high
+ * scl_rate leaves less than 3 cycles per period.
+ */
+ unsigned long cycles_per_scl = DIV_ROUND_UP(core_rate, bus->scl_rate.i3c);
+
+ total_cnt = max_t(unsigned long, cycles_per_scl, 3) - 2;
+ if (hci->master.bus.mode == I3C_BUS_MODE_PURE) {
+ hcnt = DIV_ROUND_DOWN_ULL(total_cnt * 2, 5);
+ lcnt = total_cnt - hcnt;
+ } else {
+ hcnt = DIV_ROUND_UP(I3C_BUS_THIGH_MIXED_MAX_NS,
+ period_ns) - 1;
+ lcnt = total_cnt - hcnt;
+ }
+ }
+
+ ctrl0 = FIELD_PREP(ASPEED_I3C_PHY_I3C_CTRL0_SCL_H, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_CTRL0_SCL_L, lcnt);
+ sdr_ctrl0_reg = aspeed_i3c_get_sdr_ctrl0_reg(hci);
+ aspeed_i3c_phy_write(hci, sdr_ctrl0_reg, ctrl0);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_SDR0_CTRL0, ctrl0);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_DDR_CTRL0, ctrl0);
+
+ min_tbit_cnt = DIV_ROUND_UP(60, period_ns) - 1;
+ ctrl1 = FIELD_PREP(ASPEED_I3C_PHY_I3C_CTRL1_TBIT_H,
+ max_t(u16, hcnt, min_tbit_cnt)) |
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_CTRL1_TBIT_L,
+ max_t(u16, lcnt, min_tbit_cnt));
+ aspeed_i3c_phy_write(hci, sdr_ctrl0_reg + ASPEED_I3C_PHY_CTRL1_OFFSET,
+ ctrl1);
+ aspeed_i3c_phy_write(hci,
+ ASPEED_I3C_PHY_I3C_SDR0_CTRL0 +
+ ASPEED_I3C_PHY_CTRL1_OFFSET, ctrl1);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_DDR_CTRL1, ctrl1);
+
+ /*
+ * tCAS and tCBP default to a value derived from the SCL low period
+ * of the applicable bus context, clamped to the MIPI I3C minima
+ * (tCAS >= 38.4 ns, tCBP >= 19.2 ns). Explicit device tree values
+ * are used as-is; the binding carries the minima as constraints.
+ */
+ if (hci->master.bus.mode == I3C_BUS_MODE_PURE)
+ cas_lcnt = lcnt;
+ else
+ cas_lcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_I2C_FM_SCL_L_NS,
+ period_ns);
+ of_property_read_u32(np, "i3c-cas-period-ns", &cas_ns);
+ of_property_read_u32(np, "i3c-cbp-period-ns", &cbp_ns);
+ cas_cnt = cas_ns ? aspeed_i3c_ns_to_cnt(cas_ns, period_ns)
+ : max_t(u16, cas_lcnt,
+ DIV_ROUND_UP(ASPEED_I3C_PHY_OD_MIN_CAS_NS_X10,
+ period_ns * 10) - 1);
+ cbp_cnt = cbp_ns ? aspeed_i3c_ns_to_cnt(cbp_ns, period_ns)
+ : max_t(u16, cas_lcnt,
+ DIV_ROUND_UP(ASPEED_I3C_PHY_OD_MIN_CBP_NS_X10,
+ period_ns * 10) - 1);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_OD_CTRL0,
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL0_CAS, cas_cnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL0_CBP, cbp_cnt));
+
+ of_property_read_u32(np, "i3c-od-scl-high-period-ns", &od_high);
+ of_property_read_u32(np, "i3c-od-scl-low-period-ns", &od_low);
+ if (od_high && od_low) {
+ hcnt = aspeed_i3c_ns_to_cnt(od_high, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(od_low, period_ns);
+ } else {
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_OD_SCL_H_NS, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_OD_SCL_L_NS, period_ns);
+ }
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_OD_CTRL1,
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL1_SCL_H, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL1_SCL_L, lcnt));
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_OD_CTRL2,
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL2_ACK_H, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL2_ACK_L, lcnt));
+
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_HD_DAT_NS, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_AHD_DAT_NS, period_ns);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_OD_CTRL3,
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL3_HD_DAT, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL3_AHD_DAT, lcnt));
+
+ of_property_read_u32(np, "i3c-pp-sda-hold-ns", &pp_sda_hold);
+ hcnt = aspeed_i3c_ns_to_cnt(pp_sda_hold ?: ASPEED_I3C_PHY_HD_DAT_NS,
+ period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(pp_sda_hold ?: ASPEED_I3C_PHY_AHD_DAT_NS,
+ period_ns);
+ ctrl2 = FIELD_PREP(ASPEED_I3C_PHY_I3C_CTRL2_HD_PP, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_CTRL2_TBIT_HD_PP, lcnt);
+ aspeed_i3c_phy_write(hci, sdr_ctrl0_reg + ASPEED_I3C_PHY_CTRL2_OFFSET,
+ ctrl2);
+ aspeed_i3c_phy_write(hci,
+ ASPEED_I3C_PHY_I3C_SDR0_CTRL0 +
+ ASPEED_I3C_PHY_CTRL2_OFFSET, ctrl2);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_DDR_CTRL2, ctrl2);
+
+ of_property_read_u32(np, "i3c-sr-p-scl-low-period-ns", &sr_p_low);
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_SR_P_HD_NS, period_ns);
+ lcnt = aspeed_i3c_ns_to_cnt(sr_p_low ?: ASPEED_I3C_PHY_SR_P_SCL_L_NS,
+ period_ns);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_SR_P_PREPARE_CTRL,
+ FIELD_PREP(ASPEED_I3C_PHY_SR_P_PREPARE_HD, hcnt) |
+ FIELD_PREP(ASPEED_I3C_PHY_SR_P_PREPARE_SCL_L, lcnt));
+
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_PULLUP_EN, 0);
+
+ hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_OD_DAP_NS, period_ns);
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_OD_CTRL4,
+ FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL4_DAP, hcnt));
+
+ return 0;
+}
diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.h b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
new file mode 100644
index 0000000000000..7ff1f66b67cd9
--- /dev/null
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Copyright (c) 2026 ASPEED Technology Inc.
+ *
+ * AST2700 specific MIPI I3C HCI definitions
+ */
+
+#ifndef VENDOR_ASPEED_H
+#define VENDOR_ASPEED_H
+
+#include <linux/bitfield.h>
+
+#include "ext_caps.h"
+#include "hci.h"
+
+struct platform_device;
+
+/*
+ * The AST2700 vendor extended capability points to an in-house control
+ * block and a PHY programming window inside the controller's register
+ * space, plus the core clock and reset lines the AST2700 binding
+ * specifies. All of it is ASPEED-specific, so struct aspeed_i3c_vendor_data
+ * stays private to aspeed.c; everyone else reaches it only through the
+ * functions below.
+ */
+struct aspeed_i3c_vendor_data;
+
+static inline struct aspeed_i3c_vendor_data *to_aspeed_vendor_data(struct i3c_hci *hci)
+{
+ return hci->vendor_data;
+}
+
+int hci_extcap_vendor_ASPEED(struct i3c_hci *hci, void __iomem *base);
+int aspeed_i3c_alloc_resources(struct i3c_hci *hci, struct platform_device *pdev);
+bool aspeed_i3c_vendor_cap_found(struct i3c_hci *hci);
+void aspeed_i3c_set_daa_index(struct i3c_hci *hci, unsigned int dat_idx);
+u32 aspeed_i3c_read(struct i3c_hci *hci, u32 reg);
+void aspeed_i3c_write(struct i3c_hci *hci, u32 reg, u32 val);
+int aspeed_i3c_phy_init(struct i3c_hci *hci);
+
+#define ASPEED_I3C_CTRL 0x00
+#define ASPEED_I3C_CTRL_CLOCK_STALL_EN BIT(14)
+#define ASPEED_I3C_CTRL_INIT BIT(4)
+#define ASPEED_I3C_CTRL_INIT_MODE GENMASK(1, 0)
+#define ASPEED_I3C_INIT_MST_MODE 0
+
+#define ASPEED_I3C_DAA_INDEX0 0x10
+#define ASPEED_I3C_DAA_INDEX1 0x14
+#define ASPEED_I3C_DAA_INDEX2 0x18
+#define ASPEED_I3C_DAA_INDEX3 0x1c
+
+#define ASPEED_I3C_INTR_STATUS 0xe0
+#define ASPEED_I3C_INTR_STATUS_ENABLE 0xe4
+#define ASPEED_I3C_INTR_SIGNAL_ENABLE 0xe8
+#define ASPEED_I3C_INTR_SUM_STATUS 0xf0
+#define ASPEED_INTR_SUM_INHOUSE BIT(3)
+#define ASPEED_INTR_SUM_RHS BIT(2)
+#define ASPEED_INTR_SUM_PIO BIT(1)
+#define ASPEED_INTR_SUM_CAP BIT(0)
+
+#define ASPEED_I3C_PHY_I2C_FM_CTRL0 0x08
+#define ASPEED_I3C_PHY_I2C_FM_CTRL0_CAS GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I2C_FM_CTRL0_SU_STO GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I2C_FM_CTRL1 0x0c
+#define ASPEED_I3C_PHY_I2C_FM_CTRL1_SCL_H GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I2C_FM_CTRL1_SCL_L GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I2C_FM_CTRL2 0x10
+#define ASPEED_I3C_PHY_I2C_FM_CTRL2_ACK_H GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I2C_FM_CTRL2_ACK_L GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I2C_FM_CTRL3 0x14
+#define ASPEED_I3C_PHY_I2C_FM_CTRL3_HD_DAT GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I2C_FM_CTRL3_AHD_DAT GENMASK(10, 0)
+
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL0 0x18
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL0_CAS GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL0_SU_STO GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL1 0x1c
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL1_SCL_H GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL1_SCL_L GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL2 0x20
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL2_ACK_H GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL2_ACK_L GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL3 0x24
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL3_HD_DAT GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I2C_FMP_CTRL3_AHD_DAT GENMASK(10, 0)
+
+#define ASPEED_I3C_PHY_I3C_OD_CTRL0 0x28
+#define ASPEED_I3C_PHY_I3C_OD_CTRL0_CAS GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I3C_OD_CTRL0_CBP GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I3C_OD_CTRL1 0x2c
+#define ASPEED_I3C_PHY_I3C_OD_CTRL1_SCL_H GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I3C_OD_CTRL1_SCL_L GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I3C_OD_CTRL2 0x30
+#define ASPEED_I3C_PHY_I3C_OD_CTRL2_ACK_H GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I3C_OD_CTRL2_ACK_L GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I3C_OD_CTRL3 0x34
+#define ASPEED_I3C_PHY_I3C_OD_CTRL3_HD_DAT GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I3C_OD_CTRL3_AHD_DAT GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I3C_OD_CTRL4 0xd8
+#define ASPEED_I3C_PHY_I3C_OD_CTRL4_DAP GENMASK(26, 16)
+
+#define ASPEED_I3C_PHY_I3C_SDR0_CTRL0 0x38
+#define ASPEED_I3C_PHY_I3C_SDR1_CTRL0 0x44
+#define ASPEED_I3C_PHY_I3C_SDR2_CTRL0 0x50
+#define ASPEED_I3C_PHY_I3C_SDR3_CTRL0 0x5c
+#define ASPEED_I3C_PHY_I3C_SDR4_CTRL0 0x68
+#define ASPEED_I3C_PHY_I3C_CTRL0_SCL_H GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I3C_CTRL0_SCL_L GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I3C_CTRL1_TBIT_H GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I3C_CTRL1_TBIT_L GENMASK(10, 0)
+#define ASPEED_I3C_PHY_I3C_CTRL2_HD_PP GENMASK(26, 16)
+#define ASPEED_I3C_PHY_I3C_CTRL2_TBIT_HD_PP GENMASK(10, 0)
+
+#define ASPEED_I3C_PHY_I3C_DDR_CTRL0 0x74
+#define ASPEED_I3C_PHY_I3C_DDR_CTRL1 0x78
+#define ASPEED_I3C_PHY_I3C_DDR_CTRL2 0x7c
+
+#define ASPEED_I3C_PHY_SR_P_PREPARE_CTRL 0x80
+#define ASPEED_I3C_PHY_SR_P_PREPARE_HD GENMASK(26, 16)
+#define ASPEED_I3C_PHY_SR_P_PREPARE_SCL_L GENMASK(10, 0)
+
+#define ASPEED_I3C_PHY_PULLUP_EN 0x98
+
+#define ASPEED_I3C_PHY_CTRL1_OFFSET 0x04
+#define ASPEED_I3C_PHY_CTRL2_OFFSET 0x08
+
+#define ASPEED_I3C_PHY_CNT_MAX GENMASK(10, 0)
+
+#define ASPEED_I3C_PHY_I2C_FM_CAS_NS 1130
+#define ASPEED_I3C_PHY_I2C_FM_SU_STO_NS 1370
+#define ASPEED_I3C_PHY_I2C_FM_SCL_H_NS 1130
+#define ASPEED_I3C_PHY_I2C_FM_SCL_L_NS 1370
+#define ASPEED_I3C_PHY_I2C_FM_HD_DAT_NS 10
+#define ASPEED_I3C_PHY_I2C_FM_AHD_DAT_NS 10
+
+#define ASPEED_I3C_PHY_I2C_FMP_CAS_NS 380
+#define ASPEED_I3C_PHY_I2C_FMP_SU_STO_NS 620
+#define ASPEED_I3C_PHY_I2C_FMP_SCL_H_NS 380
+#define ASPEED_I3C_PHY_I2C_FMP_SCL_L_NS 620
+#define ASPEED_I3C_PHY_I2C_FMP_HD_DAT_NS 10
+#define ASPEED_I3C_PHY_I2C_FMP_AHD_DAT_NS 10
+
+#define ASPEED_I3C_PHY_OD_SCL_H_NS 380
+#define ASPEED_I3C_PHY_OD_SCL_L_NS 620
+#define ASPEED_I3C_PHY_HD_DAT_NS 10
+#define ASPEED_I3C_PHY_AHD_DAT_NS 10
+#define ASPEED_I3C_PHY_OD_DAP_NS 12
+
+#define ASPEED_I3C_PHY_SR_P_HD_NS 16
+#define ASPEED_I3C_PHY_SR_P_SCL_L_NS 40
+
+/*
+ * MIPI I3C minimum timing for the open-drain start/stop, in tenths of a
+ * nanosecond to keep the fractional spec values (tCAS >= 38.4 ns,
+ * tCBP >= 19.2 ns) in integer arithmetic.
+ */
+#define ASPEED_I3C_PHY_OD_MIN_CAS_NS_X10 384
+#define ASPEED_I3C_PHY_OD_MIN_CBP_NS_X10 192
+
+#endif
diff --git a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c
index d267aa19cb75b..04e29313b1fb2 100644
--- a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c
+++ b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c
@@ -14,6 +14,7 @@
#include "cmd.h"
#include "dat.h"
#include "dct.h"
+#include "aspeed.h"
/*
* Address Assignment Command
@@ -338,6 +339,8 @@ static int hci_cmd_v1_daa(struct i3c_hci *hci)
if (ret < 0)
break;
dat_idx = ret;
+ if (hci->quirks & HCI_QUIRK_VENDOR_DAA_INDEX)
+ aspeed_i3c_set_daa_index(hci, dat_idx);
dev_dbg(&hci->master.dev,
"next_addr = 0x%02x, DAA using DAT %d",
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index da77384e985cf..759892a3785b8 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -26,6 +26,7 @@
#include "cmd.h"
#include "dat.h"
#include "ibi.h"
+#include "aspeed.h"
/*
* Host Controller Capabilities and Operation Registers
@@ -140,6 +141,20 @@ struct device *i3c_hci_sysdev(struct device *dev)
return dev->parent && dev_is_pci(dev->parent) ? dev->parent : dev;
}
+static void aspeed_i3c_bus_setup(struct i3c_hci *hci)
+{
+ /*
+ * Enable master clock stall: when the controller cannot keep the
+ * data pipeline fed it holds SCL low instead of underrunning, so a
+ * transfer pauses and resumes cleanly rather than aborting.
+ */
+ aspeed_i3c_write(hci, ASPEED_I3C_CTRL,
+ ASPEED_I3C_CTRL_CLOCK_STALL_EN |
+ ASPEED_I3C_CTRL_INIT |
+ FIELD_PREP(ASPEED_I3C_CTRL_INIT_MODE,
+ ASPEED_I3C_INIT_MST_MODE));
+}
+
static void i3c_hci_set_master_dyn_addr(struct i3c_hci *hci)
{
reg_write(MASTER_DEVICE_ADDR,
@@ -153,6 +168,15 @@ static int i3c_hci_bus_init(struct i3c_master_controller *m)
struct i3c_device_info info;
int ret;
+ if (hci->quirks & HCI_QUIRK_VENDOR_BUS_SETUP)
+ aspeed_i3c_bus_setup(hci);
+
+ if (hci->quirks & HCI_QUIRK_BOARD_PHY_TIMINGS) {
+ ret = aspeed_i3c_phy_init(hci);
+ if (ret)
+ return ret;
+ }
+
if (hci->cmd == &mipi_i3c_hci_cmd_v1) {
ret = mipi_i3c_hci_dat_v1.init(hci);
if (ret)
@@ -826,22 +850,12 @@ static const struct i3c_master_controller_ops i3c_hci_ops = {
.set_dev_nack_retry = i3c_hci_set_dev_nack_retry,
};
-static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
+static irqreturn_t i3c_hci_core_irq(struct i3c_hci *hci)
+__must_hold(&hci->lock)
{
- struct i3c_hci *hci = dev_id;
irqreturn_t result = IRQ_NONE;
u32 val;
- guard(spinlock)(&hci->lock);
-
- /*
- * The IRQ can be shared, so the handler may be called when the IRQ is
- * due to a different device. That could happen when runtime suspended,
- * so exit immediately if IRQs are not expected for this device.
- */
- if (hci->irq_inactive)
- return IRQ_NONE;
-
val = reg_read(INTR_STATUS);
reg_write(INTR_STATUS, val);
dev_dbg(&hci->master.dev, "INTR_STATUS %#x", val);
@@ -864,12 +878,73 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
dev_warn_once(&hci->master.dev,
"unexpected INTR_STATUS %#x\n", val);
+ return result;
+}
+
+static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
+{
+ irqreturn_t result = IRQ_NONE;
+ struct i3c_hci *hci = dev_id;
+
+ guard(spinlock)(&hci->lock);
+
+ /*
+ * The IRQ can be shared, so the handler may be called when the IRQ is
+ * due to a different device. That could happen when runtime suspended,
+ * so exit immediately if IRQs are not expected for this device.
+ */
+ if (hci->irq_inactive)
+ return IRQ_NONE;
+
+ result = i3c_hci_core_irq(hci);
+
if (hci->io->irq_handler(hci))
result = IRQ_HANDLED;
return result;
}
+static irqreturn_t i3c_aspeed_irq_handler(int irq, void *dev_id)
+{
+ struct i3c_hci *hci = dev_id;
+ u32 status, inhouse_status;
+
+ guard(spinlock)(&hci->lock);
+
+ if (hci->irq_inactive)
+ return IRQ_NONE;
+
+ /*
+ * The vendor summary register ORs together the core HCI status
+ * (CAP), the PIO/DMA ring status (PIO/RHS) and the in-house block's
+ * own status (INHOUSE) into single bits, so each source is
+ * dispatched to the handler that knows how to service it.
+ */
+ status = aspeed_i3c_read(hci, ASPEED_I3C_INTR_SUM_STATUS);
+ if (!status)
+ return IRQ_NONE;
+
+ if (status & ASPEED_INTR_SUM_CAP)
+ i3c_hci_core_irq(hci);
+ if (status & (ASPEED_INTR_SUM_PIO | ASPEED_INTR_SUM_RHS))
+ hci->io->irq_handler(hci);
+ if (status & ASPEED_INTR_SUM_INHOUSE) {
+ /*
+ * The in-house block has no dedicated handler; just
+ * acknowledge its status bits so the summary IRQ clears.
+ */
+ inhouse_status = aspeed_i3c_read(hci, ASPEED_I3C_INTR_STATUS);
+ aspeed_i3c_write(hci, ASPEED_I3C_INTR_STATUS, inhouse_status);
+ }
+
+ /*
+ * Unlike i3c_hci_irq_handler(), this IRQ is not shared
+ * (IRQF_SHARED is not passed at request_irq() time), so a nonzero
+ * summary status is always ours to handle.
+ */
+ return IRQ_HANDLED;
+}
+
static inline bool is_version_1_1_or_newer(struct i3c_hci *hci)
{
return hci->version_major > 1 || (hci->version_major == 1 && hci->version_minor > 0);
@@ -921,6 +996,11 @@ static int i3c_hci_reset_and_init(struct i3c_hci *hci)
* Bit 0:5 are defined in IP version < 0.8 but not handled by PIO code
*/
reg_write(INTR_STATUS_ENABLE, GENMASK(31, 10));
+ if (hci->quirks & HCI_QUIRK_VENDOR_INTR_SUMMARY) {
+ aspeed_i3c_write(hci, ASPEED_I3C_INTR_SIGNAL_ENABLE, 0);
+ aspeed_i3c_write(hci, ASPEED_I3C_INTR_STATUS_ENABLE,
+ GENMASK(31, 0));
+ }
/* Make sure our data ordering fits the host's */
regval = reg_read(HC_CONTROL);
@@ -1004,6 +1084,15 @@ static int i3c_hci_do_reset_and_restore(struct i3c_hci *hci)
if (ret)
return -EIO;
+ if (hci->quirks & HCI_QUIRK_VENDOR_BUS_SETUP)
+ aspeed_i3c_bus_setup(hci);
+
+ if (hci->quirks & HCI_QUIRK_BOARD_PHY_TIMINGS) {
+ ret = aspeed_i3c_phy_init(hci);
+ if (ret)
+ return ret;
+ }
+
i3c_hci_set_master_dyn_addr(hci);
mipi_i3c_hci_dat_v1.restore(hci);
@@ -1181,6 +1270,11 @@ static int i3c_hci_init(struct i3c_hci *hci)
if (ret)
return ret;
+ if ((hci->quirks & HCI_QUIRK_VENDOR_RESOURCES) && !aspeed_i3c_vendor_cap_found(hci)) {
+ dev_err(&hci->master.dev, "required vendor capability not found in hardware\n");
+ return -ENODEV;
+ }
+
/* Select our command descriptor model */
switch (FIELD_GET(HC_CAP_CMD_SIZE, hci->caps)) {
case 0:
@@ -1205,6 +1299,8 @@ static int i3c_hci_probe(struct platform_device *pdev)
{
const struct mipi_i3c_hci_platform_data *pdata = pdev->dev.platform_data;
struct clk_bulk_data *clks;
+ irq_handler_t irq_handler;
+ unsigned long irq_flags;
struct i3c_hci *hci;
int irq, ret;
@@ -1233,7 +1329,7 @@ static int i3c_hci_probe(struct platform_device *pdev)
}
platform_set_drvdata(pdev, hci);
- /* temporary for dev_printk's, to be replaced in i3c_master_register */
+ /* Temporary for dev_printk's, replaced with the real value by i3c_master_register(). */
hci->master.dev.init_name = dev_name(&pdev->dev);
hci->quirks = (unsigned long)device_get_match_data(&pdev->dev);
@@ -1243,6 +1339,12 @@ static int i3c_hci_probe(struct platform_device *pdev)
if (hci->quirks & HCI_QUIRK_DMA_64BIT)
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ if (hci->quirks & HCI_QUIRK_VENDOR_RESOURCES) {
+ ret = aspeed_i3c_alloc_resources(hci, pdev);
+ if (ret)
+ return ret;
+ }
+
ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &clks);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
@@ -1254,9 +1356,15 @@ static int i3c_hci_probe(struct platform_device *pdev)
hci->irq_inactive = true;
+ irq_handler = i3c_hci_irq_handler;
+ irq_flags = IRQF_SHARED;
+ if (hci->quirks & HCI_QUIRK_VENDOR_INTR_SUMMARY) {
+ irq_handler = i3c_aspeed_irq_handler;
+ irq_flags = 0;
+ }
+
irq = platform_get_irq(pdev, 0);
- ret = devm_request_irq(&pdev->dev, irq, i3c_hci_irq_handler,
- IRQF_SHARED, NULL, hci);
+ ret = devm_request_irq(&pdev->dev, irq, irq_handler, irq_flags, NULL, hci);
if (ret)
return ret;
@@ -1291,6 +1399,16 @@ static const __maybe_unused struct of_device_id i3c_hci_of_match[] = {
{ .compatible = "microchip,sama7d65-i3c-hci",
.data = (void *)(ulong)(HCI_QUIRK_PIO_MODE | HCI_QUIRK_OD_PP_TIMING |
HCI_QUIRK_RESP_BUF_THLD) },
+ { .compatible = "aspeed,ast2700-i3c-hci",
+ .data = (void *)(ulong)(HCI_QUIRK_DAT_INDEX_IS_ADDR |
+ HCI_QUIRK_DMA_64BIT |
+ HCI_QUIRK_DMA_ABORT_REQUIRES_PIO_RESET |
+ HCI_QUIRK_TX_START_THLD |
+ HCI_QUIRK_VENDOR_BUS_SETUP |
+ HCI_QUIRK_BOARD_PHY_TIMINGS |
+ HCI_QUIRK_VENDOR_INTR_SUMMARY |
+ HCI_QUIRK_VENDOR_RESOURCES |
+ HCI_QUIRK_VENDOR_DAA_INDEX) },
{},
};
MODULE_DEVICE_TABLE(of, i3c_hci_of_match);
diff --git a/drivers/i3c/master/mipi-i3c-hci/ext_caps.c b/drivers/i3c/master/mipi-i3c-hci/ext_caps.c
index 77840fd4aa51f..ab0f18f74a5f7 100644
--- a/drivers/i3c/master/mipi-i3c-hci/ext_caps.c
+++ b/drivers/i3c/master/mipi-i3c-hci/ext_caps.c
@@ -14,6 +14,7 @@
#include "hci.h"
#include "ext_caps.h"
+#include "aspeed.h"
#include "xfer_mode_rate.h"
/* Extended Capability Header */
@@ -221,6 +222,7 @@ struct hci_ext_cap_vendor_specific {
static const struct hci_ext_cap_vendor_specific vendor_ext_caps[] = {
EXT_CAP_VENDOR(NXP, 0xc0, 0x20),
+ EXT_CAP_VENDOR(ASPEED, 0xc0, 0x08),
};
static int hci_extcap_vendor_specific(struct i3c_hci *hci, void __iomem *base,
diff --git a/drivers/i3c/master/mipi-i3c-hci/ext_caps.h b/drivers/i3c/master/mipi-i3c-hci/ext_caps.h
index b15e629951f03..46dd83c0d4c54 100644
--- a/drivers/i3c/master/mipi-i3c-hci/ext_caps.h
+++ b/drivers/i3c/master/mipi-i3c-hci/ext_caps.h
@@ -12,6 +12,7 @@
/* MIPI vendor IDs */
#define MIPI_VENDOR_NXP 0x11b
+#define MIPI_VENDOR_ASPEED 0x3f6
int i3c_hci_parse_ext_caps(struct i3c_hci *hci);
diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h
index 3f28f821bf0dc..2b876dfb1087b 100644
--- a/drivers/i3c/master/mipi-i3c-hci/hci.h
+++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
@@ -181,6 +181,13 @@ struct i3c_hci_dev_data {
#define HCI_QUIRK_DAT_INDEX_IS_ADDR BIT(10) /* DAT entries are indexed by device address */
#define HCI_QUIRK_DMA_64BIT BIT(11) /* Controller DMA supports 64-bit addressing */
#define HCI_QUIRK_TX_START_THLD BIT(12) /* Clear TX start threshold in DMA mode */
+#define HCI_QUIRK_VENDOR_BUS_SETUP BIT(13) /* Vendor block needs setup to bring the bus up */
+#define HCI_QUIRK_BOARD_PHY_TIMINGS BIT(14) /* Apply board-specific PHY timing from DT */
+#define HCI_QUIRK_VENDOR_INTR_SUMMARY BIT(15) /* Vendor interrupts funnel through a summary */
+#define HCI_QUIRK_VENDOR_RESOURCES BIT(16) /* Vendor block needs its own reset lines/clock */
+#define HCI_QUIRK_VENDOR_DAA_INDEX BIT(17) /* DAA needs the DAT slot in a vendor register */
+#define HCI_QUIRK_IBI_PAYLOAD_THLD BIT(18) /* Controller has a payload IBI term threshold */
+#define HCI_QUIRK_VENDOR_TX_AVAIL BIT(19) /* Controller reports exact TX FIFO free space */
/* global functions */
void mipi_i3c_hci_resume(struct i3c_hci *hci);
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 7/9] i3c: mipi-i3c-hci: Program AST2700 IBI termination threshold
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
` (5 preceding siblings ...)
2026-11-11 3:16 ` [PATCH v2 6/9] i3c: mipi-i3c-hci: Add support for the AST2700 I3C controller Billy Tsai
@ 2026-11-11 3:16 ` Billy Tsai
2026-11-11 3:16 ` [PATCH v2 8/9] i3c: mipi-i3c-hci: Improve AST2700 PIO TX queue utilization Billy Tsai
2026-11-11 3:16 ` [PATCH v2 9/9] i3c: mipi-i3c-hci: Support the AST2700 internal pull-ups Billy Tsai
8 siblings, 0 replies; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
For an IBI with payload, it's normally the device that decides when to
stop sending payload bytes. A misbehaving device that never terminates
can leave the controller stuck waiting on the transfer. AST2700 has an
in-house IBI payload length threshold that, once enabled, forces
termination once the received payload reaches it.
When payload IBIs are requested, grow the AST2700 termination
threshold to at least the requested payload length and enable
termination on that value. The register is controller-wide while IBI
requests for different devices can run in parallel, so the
read-modify-write is serialized under the controller lock. Gate this
behind HCI_QUIRK_IBI_PAYLOAD_THLD; controllers without the quirk keep
the existing behavior.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
drivers/i3c/master/mipi-i3c-hci/aspeed.c | 20 ++++++++++++++++++++
drivers/i3c/master/mipi-i3c-hci/aspeed.h | 5 +++++
drivers/i3c/master/mipi-i3c-hci/core.c | 17 ++++++++++++-----
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.c b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
index 3aa910e786f8..4a73e0180a10 100644
--- a/drivers/i3c/master/mipi-i3c-hci/aspeed.c
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
@@ -136,6 +136,26 @@ void aspeed_i3c_write(struct i3c_hci *hci, u32 reg, u32 val)
writel(val, to_aspeed_vendor_data(hci)->inhouse_regs + reg);
}
+void aspeed_i3c_ibi_grow_threshold(struct i3c_hci *hci, unsigned int ibi_max_len)
+{
+ u32 mrl, cur_len;
+
+ /*
+ * The termination threshold is controller-wide while IBI requests
+ * for different devices can run in parallel, so serialize the
+ * read-modify-write.
+ */
+ guard(spinlock_irqsave)(&hci->lock);
+
+ mrl = aspeed_i3c_read(hci, ASPEED_I3C_MST_MRL);
+ cur_len = FIELD_GET(ASPEED_I3C_IBI_TERMINATE_LEN, mrl);
+
+ ibi_max_len = max(ibi_max_len, cur_len);
+ aspeed_i3c_write(hci, ASPEED_I3C_MST_MRL,
+ ASPEED_I3C_IBI_TERMINATE_EN |
+ FIELD_PREP(ASPEED_I3C_IBI_TERMINATE_LEN, ibi_max_len));
+}
+
static void aspeed_i3c_phy_write(struct i3c_hci *hci, u32 reg, u32 val)
{
writel(val, to_aspeed_vendor_data(hci)->phy_regs + reg);
diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.h b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
index 7ff1f66b67cd..7aecce04bba7 100644
--- a/drivers/i3c/master/mipi-i3c-hci/aspeed.h
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
@@ -37,6 +37,7 @@ void aspeed_i3c_set_daa_index(struct i3c_hci *hci, unsigned int dat_idx);
u32 aspeed_i3c_read(struct i3c_hci *hci, u32 reg);
void aspeed_i3c_write(struct i3c_hci *hci, u32 reg, u32 val);
int aspeed_i3c_phy_init(struct i3c_hci *hci);
+void aspeed_i3c_ibi_grow_threshold(struct i3c_hci *hci, unsigned int ibi_max_len);
#define ASPEED_I3C_CTRL 0x00
#define ASPEED_I3C_CTRL_CLOCK_STALL_EN BIT(14)
@@ -44,6 +45,10 @@ int aspeed_i3c_phy_init(struct i3c_hci *hci);
#define ASPEED_I3C_CTRL_INIT_MODE GENMASK(1, 0)
#define ASPEED_I3C_INIT_MST_MODE 0
+#define ASPEED_I3C_MST_MRL 0x08
+#define ASPEED_I3C_IBI_TERMINATE_EN BIT(16)
+#define ASPEED_I3C_IBI_TERMINATE_LEN GENMASK(15, 0)
+
#define ASPEED_I3C_DAA_INDEX0 0x10
#define ASPEED_I3C_DAA_INDEX1 0x14
#define ASPEED_I3C_DAA_INDEX2 0x18
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index 759892a3785b..31fab1fe614c 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -722,15 +722,21 @@ static void i3c_hci_detach_i2c_dev(struct i2c_dev_desc *dev)
static int i3c_hci_request_ibi(struct i3c_dev_desc *dev,
const struct i3c_ibi_setup *req)
{
- struct i3c_master_controller *m = i3c_dev_get_master(dev);
- struct i3c_hci *hci = to_i3c_hci(m);
struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev);
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ unsigned int ibi_max_len = req->max_payload_len;
unsigned int dat_idx = dev_data->dat_idx;
+ struct i3c_hci *hci = to_i3c_hci(m);
- if (req->max_payload_len != 0)
+ if (req->max_payload_len != 0) {
mipi_i3c_hci_dat_v1.set_flags(hci, dat_idx, DAT_0_IBI_PAYLOAD, 0);
- else
+
+ if (hci->quirks & HCI_QUIRK_IBI_PAYLOAD_THLD)
+ aspeed_i3c_ibi_grow_threshold(hci, ibi_max_len);
+ } else {
mipi_i3c_hci_dat_v1.clear_flags(hci, dat_idx, DAT_0_IBI_PAYLOAD, 0);
+ }
+
return hci->io->request_ibi(hci, dev, req);
}
@@ -1408,7 +1414,8 @@ static const __maybe_unused struct of_device_id i3c_hci_of_match[] = {
HCI_QUIRK_BOARD_PHY_TIMINGS |
HCI_QUIRK_VENDOR_INTR_SUMMARY |
HCI_QUIRK_VENDOR_RESOURCES |
- HCI_QUIRK_VENDOR_DAA_INDEX) },
+ HCI_QUIRK_VENDOR_DAA_INDEX |
+ HCI_QUIRK_IBI_PAYLOAD_THLD) },
{},
};
MODULE_DEVICE_TABLE(of, i3c_hci_of_match);
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 8/9] i3c: mipi-i3c-hci: Improve AST2700 PIO TX queue utilization
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
` (6 preceding siblings ...)
2026-11-11 3:16 ` [PATCH v2 7/9] i3c: mipi-i3c-hci: Program AST2700 IBI termination threshold Billy Tsai
@ 2026-11-11 3:16 ` Billy Tsai
2026-11-11 3:16 ` [PATCH v2 9/9] i3c: mipi-i3c-hci: Support the AST2700 internal pull-ups Billy Tsai
8 siblings, 0 replies; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
The PIO TX path only pushes data once STAT_TX_THLD reports that
tx_thresh_size words are free, with the threshold set to half the TX
FIFO. The FIFO is therefore never filled beyond half from a single
wait, which under-utilizes the queue, and sending buffers larger than
the threshold makes software race against the hardware draining the
FIFO, resulting in an unstable flow.
The AST2700 vendor block exposes the TX FIFO read and write pointers,
which give the exact number of free entries. When HCI_QUIRK_VENDOR_TX_AVAIL
is set, size each burst from those pointers, using CIRC_SPACE() to
derive the free-entry count, so the FIFO can be used up to its full
depth. Other controllers keep the existing threshold-based flow.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
drivers/i3c/master/mipi-i3c-hci/aspeed.c | 11 +++++++++++
drivers/i3c/master/mipi-i3c-hci/aspeed.h | 6 ++++++
drivers/i3c/master/mipi-i3c-hci/core.c | 3 ++-
drivers/i3c/master/mipi-i3c-hci/pio.c | 25 +++++++++++++++++++++----
4 files changed, 40 insertions(+), 5 deletions(-)
diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.c b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
index 4a73e0180a10..8f2a00a940f7 100644
--- a/drivers/i3c/master/mipi-i3c-hci/aspeed.c
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
@@ -7,6 +7,7 @@
*/
#include <linux/bitfield.h>
+#include <linux/circ_buf.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/i3c/master.h>
@@ -156,6 +157,16 @@ void aspeed_i3c_ibi_grow_threshold(struct i3c_hci *hci, unsigned int ibi_max_len
FIELD_PREP(ASPEED_I3C_IBI_TERMINATE_LEN, ibi_max_len));
}
+/* Free TX FIFO entries derived from the vendor FIFO read/write pointers */
+unsigned int aspeed_i3c_avail_tx_entries(struct i3c_hci *hci)
+{
+ u32 ptr = aspeed_i3c_read(hci, ASPEED_I3C_QUEUE_PTR0);
+ unsigned int w = FIELD_GET(ASPEED_I3C_QUEUE_PTR0_TX_W, ptr);
+ unsigned int r = FIELD_GET(ASPEED_I3C_QUEUE_PTR0_TX_R, ptr);
+
+ return CIRC_SPACE(w, r, ASPEED_I3C_TX_QUEUE_ENTRIES);
+}
+
static void aspeed_i3c_phy_write(struct i3c_hci *hci, u32 reg, u32 val)
{
writel(val, to_aspeed_vendor_data(hci)->phy_regs + reg);
diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.h b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
index 7aecce04bba7..e80a0dff0dc9 100644
--- a/drivers/i3c/master/mipi-i3c-hci/aspeed.h
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
@@ -38,6 +38,7 @@ u32 aspeed_i3c_read(struct i3c_hci *hci, u32 reg);
void aspeed_i3c_write(struct i3c_hci *hci, u32 reg, u32 val);
int aspeed_i3c_phy_init(struct i3c_hci *hci);
void aspeed_i3c_ibi_grow_threshold(struct i3c_hci *hci, unsigned int ibi_max_len);
+unsigned int aspeed_i3c_avail_tx_entries(struct i3c_hci *hci);
#define ASPEED_I3C_CTRL 0x00
#define ASPEED_I3C_CTRL_CLOCK_STALL_EN BIT(14)
@@ -54,6 +55,11 @@ void aspeed_i3c_ibi_grow_threshold(struct i3c_hci *hci, unsigned int ibi_max_len
#define ASPEED_I3C_DAA_INDEX2 0x18
#define ASPEED_I3C_DAA_INDEX3 0x1c
+#define ASPEED_I3C_QUEUE_PTR0 0xd8
+#define ASPEED_I3C_QUEUE_PTR0_TX_R GENMASK(24, 20)
+#define ASPEED_I3C_QUEUE_PTR0_TX_W GENMASK(16, 12)
+#define ASPEED_I3C_TX_QUEUE_ENTRIES 32
+
#define ASPEED_I3C_INTR_STATUS 0xe0
#define ASPEED_I3C_INTR_STATUS_ENABLE 0xe4
#define ASPEED_I3C_INTR_SIGNAL_ENABLE 0xe8
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index 31fab1fe614c..5e7af7717da7 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -1415,7 +1415,8 @@ static const __maybe_unused struct of_device_id i3c_hci_of_match[] = {
HCI_QUIRK_VENDOR_INTR_SUMMARY |
HCI_QUIRK_VENDOR_RESOURCES |
HCI_QUIRK_VENDOR_DAA_INDEX |
- HCI_QUIRK_IBI_PAYLOAD_THLD) },
+ HCI_QUIRK_IBI_PAYLOAD_THLD |
+ HCI_QUIRK_VENDOR_TX_AVAIL) },
{},
};
MODULE_DEVICE_TABLE(of, i3c_hci_of_match);
diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c
index a95ef7fc351c..b58edff234ea 100644
--- a/drivers/i3c/master/mipi-i3c-hci/pio.c
+++ b/drivers/i3c/master/mipi-i3c-hci/pio.c
@@ -14,6 +14,7 @@
#include "hci.h"
#include "cmd.h"
#include "ibi.h"
+#include "aspeed.h"
/*
* PIO Access Area
@@ -397,11 +398,23 @@ static bool hci_pio_do_tx(struct i3c_hci *hci, struct hci_pio_data *pio)
p += (xfer->data_len - xfer->data_left) / 4;
while (xfer->data_left >= 4) {
- /* bail out if FIFO free space is below set threshold */
- if (!(pio_reg_read(INTR_STATUS) & STAT_TX_THLD))
+ unsigned int avail = pio->tx_thresh_size;
+
+ if (hci->quirks & HCI_QUIRK_VENDOR_TX_AVAIL) {
+ /*
+ * The vendor FIFO pointers give the exact free space,
+ * allowing better TX FIFO utilization than the
+ * threshold status.
+ */
+ avail = aspeed_i3c_avail_tx_entries(hci);
+ if (!avail)
+ return false;
+ } else if (!(pio_reg_read(INTR_STATUS) & STAT_TX_THLD)) {
+ /* bail out if FIFO free space is below set threshold */
return false;
+ }
/* we can fill up to that TX threshold */
- nr_words = min(xfer->data_left / 4, pio->tx_thresh_size);
+ nr_words = min(xfer->data_left / 4, avail);
/* push data into the FIFO */
xfer->data_left -= nr_words * 4;
dev_dbg(&hci->master.dev, "now %d left %d",
@@ -418,8 +431,12 @@ static bool hci_pio_do_tx(struct i3c_hci *hci, struct hci_pio_data *pio)
* also get some bytes past the actual buffer but no one
* should care as they won't be sent out.
*/
- if (!(pio_reg_read(INTR_STATUS) & STAT_TX_THLD))
+ if (hci->quirks & HCI_QUIRK_VENDOR_TX_AVAIL) {
+ if (!aspeed_i3c_avail_tx_entries(hci))
+ return false;
+ } else if (!(pio_reg_read(INTR_STATUS) & STAT_TX_THLD)) {
return false;
+ }
dev_dbg(&hci->master.dev, "trailing %d", xfer->data_left);
pio_reg_write(XFER_DATA_PORT, *p);
xfer->data_left = 0;
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 9/9] i3c: mipi-i3c-hci: Support the AST2700 internal pull-ups
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
` (7 preceding siblings ...)
2026-11-11 3:16 ` [PATCH v2 8/9] i3c: mipi-i3c-hci: Improve AST2700 PIO TX queue utilization Billy Tsai
@ 2026-11-11 3:16 ` Billy Tsai
8 siblings, 0 replies; 11+ messages in thread
From: Billy Tsai @ 2026-11-11 3:16 UTC (permalink / raw)
To: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen
Cc: tbevis, linux-i3c, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, Billy Tsai
Boards that do not populate external I3C pull-up resistors can use the
pull-ups built into the AST2700 PHY.
When bias-pull-up is present, determine the pin voltage domain from
the pinctrl-0 group already selected for the bus and enable the
internal pull-ups on both SCL and SDA at the requested strength.
Currently only pinctrl-0 is inspected; a node selecting its group
through a different pinctrl state is not yet supported. The controller
reset clears the SW force control register, so the internal pull-ups
start disabled unless bias-pull-up requests otherwise. A resistance
with no entry in the domain's table, or an unrecognized pinctrl-0
group, is rejected with an error.
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
drivers/i3c/master/mipi-i3c-hci/aspeed.c | 118 +++++++++++++++++++++++++++++++
drivers/i3c/master/mipi-i3c-hci/aspeed.h | 6 ++
2 files changed, 124 insertions(+)
diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.c b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
index 8f2a00a940f7..1e093fb5d9d9 100644
--- a/drivers/i3c/master/mipi-i3c-hci/aspeed.c
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
@@ -15,6 +15,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
+#include <linux/string.h>
#include "hci.h"
#include "aspeed.h"
@@ -197,6 +198,100 @@ static u32 aspeed_i3c_get_sdr_ctrl0_reg(struct i3c_hci *hci)
return ASPEED_I3C_PHY_I3C_SDR4_CTRL0;
}
+struct aspeed_i3c_pullup_ohms {
+ u32 ohms;
+ u8 code;
+};
+
+/* SW_FORCE_CTRL strength selector to resistance, by pin voltage domain */
+static const struct aspeed_i3c_pullup_ohms aspeed_i3c_pullup_hv[] = {
+ { 600, 1 },
+ { 470, 2 },
+ { 400, 3 },
+};
+
+static const struct aspeed_i3c_pullup_ohms aspeed_i3c_pullup_lv[] = {
+ { 750, 1 },
+ { 550, 2 },
+ { 317, 3 },
+ { 400, 4 },
+ { 261, 5 },
+ { 231, 6 },
+ { 177, 7 },
+};
+
+/* pinctrl-0 group names for each pin voltage domain */
+static const char * const aspeed_i3c_hv_groups[] = {
+ "HVI3C0", "HVI3C1", "HVI3C2", "HVI3C3",
+ "HVI3C12", "HVI3C13", "HVI3C14", "HVI3C15",
+};
+
+static const char * const aspeed_i3c_lv_groups[] = {
+ "I3C4", "I3C5", "I3C6", "I3C7",
+ "I3C8", "I3C9", "I3C10", "I3C11",
+};
+
+static int aspeed_i3c_pin_is_hv(struct i3c_hci *hci, bool *is_hv)
+{
+ struct device_node *np = hci->master.dev.of_node;
+ struct device_node *state;
+ const char *group;
+ int ret, i;
+
+ state = of_parse_phandle(np, "pinctrl-0", 0);
+ if (!state)
+ return -ENODEV;
+
+ ret = of_property_read_string(state, "groups", &group);
+ if (ret)
+ goto put_state;
+
+ for (i = 0; i < ARRAY_SIZE(aspeed_i3c_hv_groups); i++) {
+ if (!strcmp(group, aspeed_i3c_hv_groups[i])) {
+ *is_hv = true;
+ goto put_state;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(aspeed_i3c_lv_groups); i++) {
+ if (!strcmp(group, aspeed_i3c_lv_groups[i])) {
+ *is_hv = false;
+ goto put_state;
+ }
+ }
+
+ dev_err(&hci->master.dev, "unrecognized pinctrl-0 group \"%s\"\n",
+ group);
+ ret = -EINVAL;
+
+put_state:
+ of_node_put(state);
+ return ret;
+}
+
+static int aspeed_i3c_pullup_code(struct i3c_hci *hci, bool is_hv, u32 ohms)
+{
+ const struct aspeed_i3c_pullup_ohms *map;
+ size_t i, n;
+
+ if (is_hv) {
+ map = aspeed_i3c_pullup_hv;
+ n = ARRAY_SIZE(aspeed_i3c_pullup_hv);
+ } else {
+ map = aspeed_i3c_pullup_lv;
+ n = ARRAY_SIZE(aspeed_i3c_pullup_lv);
+ }
+
+ for (i = 0; i < n; i++)
+ if (map[i].ohms == ohms)
+ return map[i].code;
+
+ dev_err(&hci->master.dev,
+ "unsupported internal pull-up %u ohms for %s-voltage domain\n",
+ ohms, is_hv ? "high" : "low");
+ return -EINVAL;
+}
+
int aspeed_i3c_phy_init(struct i3c_hci *hci)
{
u16 hcnt, lcnt, total_cnt, min_tbit_cnt, cas_lcnt, cas_cnt, cbp_cnt;
@@ -206,6 +301,7 @@ int aspeed_i3c_phy_init(struct i3c_hci *hci)
struct device_node *np = hci->master.dev.of_node;
u32 sdr_ctrl0_reg, ctrl0, ctrl1, ctrl2;
unsigned long core_rate, period_ns;
+ u32 pullup_ohms = 0;
core_rate = clk_get_rate(to_aspeed_vendor_data(hci)->clk);
if (!core_rate) {
@@ -374,6 +470,28 @@ int aspeed_i3c_phy_init(struct i3c_hci *hci)
aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_PULLUP_EN, 0);
+ of_property_read_u32(np, "bias-pull-up", &pullup_ohms);
+ if (pullup_ohms) {
+ int ret, strength;
+ bool is_hv;
+
+ ret = aspeed_i3c_pin_is_hv(hci, &is_hv);
+ if (ret)
+ return ret;
+
+ strength = aspeed_i3c_pullup_code(hci, is_hv, pullup_ohms);
+ if (strength < 0)
+ return strength;
+
+ aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_SW_FORCE_CTRL,
+ ASPEED_I3C_PHY_SW_FORCE_SCL_PU_EN |
+ ASPEED_I3C_PHY_SW_FORCE_SDA_PU_EN |
+ FIELD_PREP(ASPEED_I3C_PHY_SW_FORCE_SCL_PU_VAL,
+ strength) |
+ FIELD_PREP(ASPEED_I3C_PHY_SW_FORCE_SDA_PU_VAL,
+ strength));
+ }
+
hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_OD_DAP_NS, period_ns);
aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_OD_CTRL4,
FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL4_DAP, hcnt));
diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.h b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
index e80a0dff0dc9..69d7ae92d3c5 100644
--- a/drivers/i3c/master/mipi-i3c-hci/aspeed.h
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
@@ -69,6 +69,12 @@ unsigned int aspeed_i3c_avail_tx_entries(struct i3c_hci *hci);
#define ASPEED_INTR_SUM_PIO BIT(1)
#define ASPEED_INTR_SUM_CAP BIT(0)
+#define ASPEED_I3C_PHY_SW_FORCE_CTRL 0x04
+#define ASPEED_I3C_PHY_SW_FORCE_SCL_PU_EN BIT(28)
+#define ASPEED_I3C_PHY_SW_FORCE_SDA_PU_EN BIT(24)
+#define ASPEED_I3C_PHY_SW_FORCE_SCL_PU_VAL GENMASK(10, 8)
+#define ASPEED_I3C_PHY_SW_FORCE_SDA_PU_VAL GENMASK(2, 0)
+
#define ASPEED_I3C_PHY_I2C_FM_CTRL0 0x08
#define ASPEED_I3C_PHY_I2C_FM_CTRL0_CAS GENMASK(26, 16)
#define ASPEED_I3C_PHY_I2C_FM_CTRL0_SU_STO GENMASK(10, 0)
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 1/9] dt-bindings: i3c: Document generic bus timing properties
2026-11-11 3:16 ` [PATCH v2 1/9] dt-bindings: i3c: Document generic bus timing properties Billy Tsai
@ 2026-11-11 16:50 ` Frank Li
0 siblings, 0 replies; 11+ messages in thread
From: Frank Li @ 2026-11-11 16:50 UTC (permalink / raw)
To: Billy Tsai
Cc: Alexandre Belloni, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joel Stanley, Andrew Jeffery, Philipp Zabel,
Miquel Raynal, Ryan Chen, tbevis, linux-i3c, devicetree,
linux-arm-kernel, linux-aspeed, linux-kernel
On Wed, Nov 11, 2026 at 11:16:29AM +0800, Billy Tsai wrote:
> The push-pull and open-drain SCL periods, push-pull SDA output hold
> time, and the tCAS/tCBP periods around START and STOP conditions are
> normally derived from i3c-scl-hz, but board-level trace length,
> pull-up strength and capacitive loading can require different values
> than what that derivation produces for a given board.
>
> Document properties to override each of these independently, naming
> them after the MIPI I3C Basic Specification v1.1.1 timing parameters
> they adjust:
>
> - i3c-pp-scl-high-period-ns, i3c-pp-scl-low-period-ns
> tHIGH, tLOW: Table 87 (I3C Push-Pull Timing Parameters)
>
> - i3c-od-scl-high-period-ns, i3c-od-scl-low-period-ns
> tHIGH, tLOW_OD: Table 86 (I3C Open Drain Timing Parameters)
>
> - i3c-pp-sda-hold-ns
> tHD_PP: Table 87 (I3C Push-Pull Timing Parameters)
>
> - i3c-cas-period-ns, i3c-cbp-period-ns
> tCAS, tCBP: Table 86, Figures 144/145 (I3C Open Drain Timing
> Parameters). These carry the protocol minima as constraints.
>
> - i3c-sr-p-scl-low-period-ns
> The SCL low period held while a Controller stalls the clock in
> the T-Bit phase before a STOP or repeated START, per Section
> 5.1.2.5 "Controller Clock Stalling" (Figures 19 and 20). The
> specification describes this requirement qualitatively and does
> not assign it a dedicated timing symbol, unlike the other
> properties above.
>
> Any property left out keeps the derived default.
>
> Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
> ---
> Documentation/devicetree/bindings/i3c/i3c.yaml | 86 ++++++++++++++++++++++++++
> 1 file changed, 86 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/i3c/i3c.yaml b/Documentation/devicetree/bindings/i3c/i3c.yaml
> index 5603f2e7807d..30f65fd1afa7 100644
> --- a/Documentation/devicetree/bindings/i3c/i3c.yaml
> +++ b/Documentation/devicetree/bindings/i3c/i3c.yaml
> @@ -57,6 +57,82 @@ properties:
>
> May not be supported by all controllers.
>
> + i3c-pp-scl-high-period-ns:
> + description: |
Needn't |
> + Push-pull SCL high period override, in nanoseconds (tHIGH, MIPI
> + I3C Basic Specification v1.1.1 Table 87). When undefined, the
> + value is derived from i3c-scl-hz.
> +
> + May not be supported by all controllers.
Add
minimum:
maximum:
to restrict validate rang.
> +
...
> type: boolean
> description: |
> @@ -67,6 +143,16 @@ required:
> - "#address-cells"
> - "#size-cells"
>
> +dependencies:
> + i3c-pp-scl-high-period-ns:
> + - i3c-pp-scl-low-period-ns
> + i3c-pp-scl-low-period-ns:
> + - i3c-pp-scl-high-period-ns
> + i3c-od-scl-high-period-ns:
> + - i3c-od-scl-low-period-ns
> + i3c-od-scl-low-period-ns:
> + - i3c-od-scl-high-period-ns
> +
why need these dependencies? Suppose it should be default value if not
existing.
Frank
> patternProperties:
> "@[0-9a-f]+$":
> type: object
>
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-11-11 16:50 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 3:16 [PATCH v2 0/9] i3c: mipi-i3c-hci: Add Aspeed AST2700 support Billy Tsai
2026-11-11 3:16 ` [PATCH v2 1/9] dt-bindings: i3c: Document generic bus timing properties Billy Tsai
2026-11-11 16:50 ` Frank Li
2026-11-11 3:16 ` [PATCH v2 2/9] dt-bindings: i3c: Document the AST2700 I3C controller Billy Tsai
2026-11-11 3:16 ` [PATCH v2 3/9] i3c: mipi-i3c-hci: Support address-indexed DAT slots Billy Tsai
2026-11-11 3:16 ` [PATCH v2 4/9] i3c: mipi-i3c-hci: Add a quirk for 64-bit DMA addressing Billy Tsai
2026-11-11 3:16 ` [PATCH v2 5/9] i3c: mipi-i3c-hci: Add a quirk to clear the TX start threshold Billy Tsai
2026-11-11 3:16 ` [PATCH v2 6/9] i3c: mipi-i3c-hci: Add support for the AST2700 I3C controller Billy Tsai
2026-11-11 3:16 ` [PATCH v2 7/9] i3c: mipi-i3c-hci: Program AST2700 IBI termination threshold Billy Tsai
2026-11-11 3:16 ` [PATCH v2 8/9] i3c: mipi-i3c-hci: Improve AST2700 PIO TX queue utilization Billy Tsai
2026-11-11 3:16 ` [PATCH v2 9/9] i3c: mipi-i3c-hci: Support the AST2700 internal pull-ups Billy Tsai
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®