* [PATCH v2 0/2] Add support for Eswin EIC7700 SD/eMMC controller
@ 2025-09-12 9:34 hehuan1
2025-09-12 9:37 ` [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 hehuan1
2025-09-12 9:38 ` [PATCH v2 2/2] mmc: sdhci-of-dwcmshc: Add support for " hehuan1
0 siblings, 2 replies; 9+ messages in thread
From: hehuan1 @ 2025-09-12 9:34 UTC (permalink / raw)
To: ulf.hansson, robh, krzk+dt, conor+dt, jszhang, adrian.hunter,
p.zabel, linux-mmc, devicetree, linux-kernel
Cc: ningyu, linmin, pinkesh.vaghela, xuxiang, luyulin, dongxuyang,
zhangsenchuan, weishangjuan, lizhi2, caohang, hehuan1
From: Huan He <hehuan1@eswincomputing.com>
Updates:
Changes in v2:
- Delete the previous separate driver and yaml binding file
- Update snps,dwcmshc-sdhci.yaml to add support for Eswin EIC7700
- Add the new compautible string: "eswin,eic7700-dwcmshc"
- Add new properties: clock-output-names, '#clock-cells',
drive-impedance-ohm, eswin,hsp-sp-csr and eswin,syscrg-csr
- Add customized reset-names for EIC7700 platform
- Update sdhci-of-dwcmshc.c to add support for Eswin EIC7700
- Add a new struct eic7700_priv to hold Eswin-specific data,
including clock phases, register mappings, and drive
impedance configuration
- Implement EIC7700-specific sdhci_ops
- set_clock: support core clock configuration with phase delay
- reset: add PHY reset and configuration
- set_uhs_signaling: support HS400 DLL lock
- platform_execute_tuning: implement delay line tuning and phase
code adjustment
- Add initialization routine (eic7700_init)
- Integrate the new platform data and ops into the driver's match table
- Link to v1: https://lore.kernel.org/all/20250516091259.774-1-dongxuyang@eswincomputing.com/
Huan He (2):
dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700
mmc: sdhci-of-dwcmshc: Add support for Eswin EIC7700
.../bindings/mmc/snps,dwcmshc-sdhci.yaml | 81 +-
drivers/mmc/host/sdhci-of-dwcmshc.c | 770 ++++++++++++++++++
2 files changed, 845 insertions(+), 6 deletions(-)
--
2.25.1
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 2025-09-12 9:34 [PATCH v2 0/2] Add support for Eswin EIC7700 SD/eMMC controller hehuan1 @ 2025-09-12 9:37 ` hehuan1 2025-09-12 19:10 ` Conor Dooley 2025-09-12 9:38 ` [PATCH v2 2/2] mmc: sdhci-of-dwcmshc: Add support for " hehuan1 1 sibling, 1 reply; 9+ messages in thread From: hehuan1 @ 2025-09-12 9:37 UTC (permalink / raw) To: ulf.hansson, robh, krzk+dt, conor+dt, jszhang, adrian.hunter, p.zabel, linux-mmc, devicetree, linux-kernel Cc: ningyu, linmin, pinkesh.vaghela, xuxiang, luyulin, dongxuyang, zhangsenchuan, weishangjuan, lizhi2, caohang, hehuan1 From: Huan He <hehuan1@eswincomputing.com> EIC7700 use Synopsys dwcmshc IP for SD/eMMC controllers. Add Eswin EIC7700 support in sdhci-of-dwcmshc.yaml. Signed-off-by: Huan He <hehuan1@eswincomputing.com> --- .../bindings/mmc/snps,dwcmshc-sdhci.yaml | 81 +++++++++++++++++-- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml index f882219a0a26..e0f34bc28e0c 100644 --- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml +++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml @@ -30,6 +30,7 @@ properties: - sophgo,sg2002-dwcmshc - sophgo,sg2042-dwcmshc - thead,th1520-dwcmshc + - eswin,eic7700-dwcmshc reg: maxItems: 1 @@ -52,17 +53,51 @@ properties: maxItems: 5 reset-names: - items: - - const: core - - const: bus - - const: axi - - const: block - - const: timer + maxItems: 5 rockchip,txclk-tapnum: description: Specify the number of delay for tx sampling. $ref: /schemas/types.yaml#/definitions/uint8 + clock-output-names: + maxItems: 1 + description: + The name of the clock output representing the card clock, + consumed by the PHY. + + '#clock-cells': + enum: [0] + description: + Specifies how many cells are used when referencing the + exported clock from another node. This property indicates + that the clock output has no extra parameters and represents + the card clock. + + eswin,hsp-sp-csr: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - description: Phandle to HSP(High-Speed Peripheral) device + - description: Offset of the stability status register for + internal clock + - description: Offset of the stability register for host + regulator voltage. + description: | + High-Speed Peripheral device needed to configure internal + clocks, and the power. + + eswin,syscrg-csr: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - description: Phandle to system CRG(System Clock and Reset + Generator) device + - description: Offset of core clock control register + description: | + System Clock and Reset Generator device needed to configure + core clock. + + drive-impedance-ohm: + description: Specifies the drive impedance in Ohm. + enum: [33, 40, 50, 66, 100] + required: - compatible - reg @@ -110,6 +145,40 @@ allOf: - const: block - const: timer + - if: + properties: + compatible: + contains: + const: eswin,eic7700-dwcmshc + then: + properties: + resets: + minItems: 4 + maxItems: 4 + reset-names: + items: + - const: arstn + - const: phy_rst + - const: prstn + - const: txrx_rst + required: + - clock-output-names + - '#clock-cells' + - eswin,hsp-sp-csr + - eswin,syscrg-csr + - drive-impedance-ohm + else: + properties: + resets: + maxItems: 5 + reset-names: + items: + - const: core + - const: bus + - const: axi + - const: block + - const: timer + - if: properties: compatible: -- 2.25.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 2025-09-12 9:37 ` [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 hehuan1 @ 2025-09-12 19:10 ` Conor Dooley 2025-09-23 5:45 ` 何欢 0 siblings, 1 reply; 9+ messages in thread From: Conor Dooley @ 2025-09-12 19:10 UTC (permalink / raw) To: hehuan1 Cc: ulf.hansson, robh, krzk+dt, conor+dt, jszhang, adrian.hunter, p.zabel, linux-mmc, devicetree, linux-kernel, ningyu, linmin, pinkesh.vaghela, xuxiang, luyulin, dongxuyang, zhangsenchuan, weishangjuan, lizhi2, caohang [-- Attachment #1: Type: text/plain, Size: 5191 bytes --] On Fri, Sep 12, 2025 at 05:37:13PM +0800, hehuan1@eswincomputing.com wrote: > From: Huan He <hehuan1@eswincomputing.com> > > EIC7700 use Synopsys dwcmshc IP for SD/eMMC controllers. > Add Eswin EIC7700 support in sdhci-of-dwcmshc.yaml. > > Signed-off-by: Huan He <hehuan1@eswincomputing.com> > --- > .../bindings/mmc/snps,dwcmshc-sdhci.yaml | 81 +++++++++++++++++-- > 1 file changed, 75 insertions(+), 6 deletions(-) > > diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > index f882219a0a26..e0f34bc28e0c 100644 > --- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > +++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > @@ -30,6 +30,7 @@ properties: > - sophgo,sg2002-dwcmshc > - sophgo,sg2042-dwcmshc > - thead,th1520-dwcmshc > + - eswin,eic7700-dwcmshc > > reg: > maxItems: 1 > @@ -52,17 +53,51 @@ properties: > maxItems: 5 > > reset-names: > - items: > - - const: core > - - const: bus > - - const: axi > - - const: block > - - const: timer > + maxItems: 5 > > rockchip,txclk-tapnum: > description: Specify the number of delay for tx sampling. > $ref: /schemas/types.yaml#/definitions/uint8 > > + clock-output-names: > + maxItems: 1 > + description: > + The name of the clock output representing the card clock, > + consumed by the PHY. You have one clock, why do you need this? > + > + '#clock-cells': > + enum: [0] const: 0 > + description: > + Specifies how many cells are used when referencing the > + exported clock from another node. This property indicates > + that the clock output has no extra parameters and represents > + the card clock. This description is not needed. > + > + eswin,hsp-sp-csr: > + $ref: /schemas/types.yaml#/definitions/phandle-array > + items: > + - description: Phandle to HSP(High-Speed Peripheral) device > + - description: Offset of the stability status register for > + internal clock > + - description: Offset of the stability register for host > + regulator voltage. > + description: | > + High-Speed Peripheral device needed to configure internal > + clocks, and the power. > + > + eswin,syscrg-csr: > + $ref: /schemas/types.yaml#/definitions/phandle-array > + items: > + - description: Phandle to system CRG(System Clock and Reset > + Generator) device > + - description: Offset of core clock control register > + description: | > + System Clock and Reset Generator device needed to configure > + core clock. This reeks of improper clock tree description. Why can you not just request the rate that you need via the common clk framework? Likewise for reset. You already have a clocks property that has to include the core clock, so I don't see why you need another property to get around it. As a result, I'm also suspicious of your hsp-sp-csr, but these at least appear to be internal clocks if your description is to be believed. I'd like you to explain exactly what those clocks do and what the "HSP" actually is. What other peripherals use it? Also, your driver turns on this hsp clock but never turns it off. Same for the power. I want to see the full dts for what you're doing here before I approve this, there's too much here that looks wrong. > + > + drive-impedance-ohm: How come this one has no eswin prefix? Also, the unit is "Ohms", not "Ohm". Additionally, any eswin properties should be restricted to eswin devices only. > + description: Specifies the drive impedance in Ohm. > + enum: [33, 40, 50, 66, 100] > + > required: > - compatible > - reg > @@ -110,6 +145,40 @@ allOf: > - const: block > - const: timer > > + - if: > + properties: > + compatible: > + contains: > + const: eswin,eic7700-dwcmshc > + then: > + properties: > + resets: > + minItems: 4 > + maxItems: 4 > + reset-names: > + items: > + - const: arstn > + - const: phy_rst > + - const: prstn > + - const: txrx_rst How come you're so drastically different to the other devices? Also, putting "_rst" in a reset name is pointless. These are all resets after all by nature. Cheers, Conor. > + required: > + - clock-output-names > + - '#clock-cells' > + - eswin,hsp-sp-csr > + - eswin,syscrg-csr > + - drive-impedance-ohm > + else: > + properties: > + resets: > + maxItems: 5 > + reset-names: > + items: > + - const: core > + - const: bus > + - const: axi > + - const: block > + - const: timer > + > - if: > properties: > compatible: > -- > 2.25.1 > [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: Re: [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 2025-09-12 19:10 ` Conor Dooley @ 2025-09-23 5:45 ` 何欢 2025-09-24 19:43 ` Conor Dooley 0 siblings, 1 reply; 9+ messages in thread From: 何欢 @ 2025-09-23 5:45 UTC (permalink / raw) To: Conor Dooley Cc: ulf.hansson, robh, krzk+dt, conor+dt, jszhang, adrian.hunter, p.zabel, linux-mmc, devicetree, linux-kernel, ningyu, linmin, pinkesh.vaghela, xuxiang, luyulin, dongxuyang, zhangsenchuan, weishangjuan, lizhi2, caohang Dear Conor, Thank you for your valuable and professional suggestions. Please find our explanations embedded below your comments in the original email. Best regards, He Huan Eswin Computing > -----原始邮件----- > 发件人: "Conor Dooley" <conor@kernel.org> > 发送时间:2025-09-13 03:10:04 (星期六) > 收件人: hehuan1@eswincomputing.com > 抄送: ulf.hansson@linaro.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, jszhang@kernel.org, adrian.hunter@intel.com, p.zabel@pengutronix.de, linux-mmc@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, xuxiang@eswincomputing.com, luyulin@eswincomputing.com, dongxuyang@eswincomputing.com, zhangsenchuan@eswincomputing.com, weishangjuan@eswincomputing.com, lizhi2@eswincomputing.com, caohang@eswincomputing.com > 主题: Re: [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 > > On Fri, Sep 12, 2025 at 05:37:13PM +0800, hehuan1@eswincomputing.com wrote: > > From: Huan He <hehuan1@eswincomputing.com> > > > > EIC7700 use Synopsys dwcmshc IP for SD/eMMC controllers. > > Add Eswin EIC7700 support in sdhci-of-dwcmshc.yaml. > > > > Signed-off-by: Huan He <hehuan1@eswincomputing.com> > > --- > > .../bindings/mmc/snps,dwcmshc-sdhci.yaml | 81 +++++++++++++++++-- > > 1 file changed, 75 insertions(+), 6 deletions(-) > > > > diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > > index f882219a0a26..e0f34bc28e0c 100644 > > --- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > > +++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > > @@ -30,6 +30,7 @@ properties: > > - sophgo,sg2002-dwcmshc > > - sophgo,sg2042-dwcmshc > > - thead,th1520-dwcmshc > > + - eswin,eic7700-dwcmshc > > > > reg: > > maxItems: 1 > > @@ -52,17 +53,51 @@ properties: > > maxItems: 5 > > > > reset-names: > > - items: > > - - const: core > > - - const: bus > > - - const: axi > > - - const: block > > - - const: timer > > + maxItems: 5 > > > > rockchip,txclk-tapnum: > > description: Specify the number of delay for tx sampling. > > $ref: /schemas/types.yaml#/definitions/uint8 > > > > + clock-output-names: > > + maxItems: 1 > > + description: > > + The name of the clock output representing the card clock, > > + consumed by the PHY. > > You have one clock, why do you need this? Thank you for the feedback. I will remove it in the next version. > > > + > > + '#clock-cells': > > + enum: [0] > > const: 0 > > > + description: > > + Specifies how many cells are used when referencing the > > + exported clock from another node. This property indicates > > + that the clock output has no extra parameters and represents > > + the card clock. > > This description is not needed. > > > + > > + eswin,hsp-sp-csr: > > + $ref: /schemas/types.yaml#/definitions/phandle-array > > + items: > > + - description: Phandle to HSP(High-Speed Peripheral) device > > + - description: Offset of the stability status register for > > + internal clock > > + - description: Offset of the stability register for host > > + regulator voltage. > > + description: | > > + High-Speed Peripheral device needed to configure internal > > + clocks, and the power. > > + > > + eswin,syscrg-csr: > > + $ref: /schemas/types.yaml#/definitions/phandle-array > > + items: > > + - description: Phandle to system CRG(System Clock and Reset > > + Generator) device > > + - description: Offset of core clock control register > > + description: | > > + System Clock and Reset Generator device needed to configure > > + core clock. > > This reeks of improper clock tree description. Why can you not just > request the rate that you need via the common clk framework? Likewise > for reset. You already have a clocks property that has to include the > core clock, so I don't see why you need another property to get around > it. Thank you for the feedback. You are absolutely right; We've taken your advice. In v3 of the patchset, we have completely removed the eswin,syscrg-csr property. The device tree binding now relies solely on the standard clocks and clock-names properties to acquire the necessary clock. > > As a result, I'm also suspicious of your hsp-sp-csr, but these at least > appear to be internal clocks if your description is to be believed. > I'd like you to explain exactly what those clocks do and what the "HSP" > actually is. What other peripherals use it? Thank you for raising this. Your concerns regarding the hsp-sp-csr clocks are valid. The functionality and purpose of the HSP (hsp-sp-csr) were explained in our previous patch series for the USB module. The relevant discussion can be found here: https://lore.kernel.org/linux-usb/17731a13.1cce.19974dfc64d.Coremail.caohang@eswincomputing.com/ Please let us know this explanation has addressed your doubts. We're happy to provide further details if needed. > > Also, your driver turns on this hsp clock but never turns it off. Same > for the power. The writes to hsp_int_status and hsp_pwr_ctrl are not enabling clocks or power rails.They are stability assertions. Assert clock stability: Write a value to the hsp_int_status register. This signals to the eMMC controller that platform clocks (axi master bus clock, internal core base clock, timer clock) are enabled and stable. Assert voltage stability: Write a value to hsp_pwr_ctrl. This signals that VDD is stable and permits transition to high-speed modes (e.g., UHS-I). > > I want to see the full dts for what you're doing here before I approve > this, there's too much here that looks wrong. The full dts is as follows: sdhci_emmc: mmc@50450000 { compatible = "eswin,eic7700-dwcmshc"; reg = <0x0 0x50450000 0x0 0x10000>; clocks = <&clock 264>, <&clock 546>; clock-names = "core", "bus"; assigned-clocks = <&clock 264>; assigned-clock-rates = <200000000>; resets = <&reset 75>, <&reset 72>, <&reset 88>, <&reset 92>; reset-names = "txrx", "phy", "bus", "axi"; interrupt-parent = <&plic>; interrupts = <79>; bus-width = <8>; non-removable; mmc-hs400-1_8v; max-frequency = <200000000>; #size-cells = <2>; no-sdio; no-sd; eswin,hsp-sp-csr = <&hsp_sp_csr 0x508 0x50c>; eswin,drive-impedance-ohms = <50>; }; sdio: mmc@0x50460000{ compatible = "eswin,eic7700-dwcmshc"; reg = <0x0 0x50460000 0x0 0x10000>; clocks = <&clock 265>, <&clock 546>; clock-names ="core","bus"; resets = <&reset 76>, <&reset 73>, <&reset 87>, <&reset 91>; reset-names = "txrx","phy", "bus", "axi"; interrupt-parent = <&plic>; interrupts = <81>; clock-frequency = <208000000>; max-frequency = <208000000>; #address-cells = <1>; #size-cells = <0>; bus-width = <4>; no-sdio; no-mmc; eswin,hsp-sp-csr = <&hsp_sp_csr 0x608 0x60c>; eswin,drive-impedance-ohms = <33>; }; > > > + > > + drive-impedance-ohm: > > How come this one has no eswin prefix? Also, the unit is "Ohms", not > "Ohm". In version 3, we renamed the property from drive-impedance-ohm to eswin,drive-impedance-ohms. > > Additionally, any eswin properties should be restricted to eswin devices > only. > > > + description: Specifies the drive impedance in Ohm. > > + enum: [33, 40, 50, 66, 100] > > + > > required: > > - compatible > > - reg > > @@ -110,6 +145,40 @@ allOf: > > - const: block > > - const: timer > > > > + - if: > > + properties: > > + compatible: > > + contains: > > + const: eswin,eic7700-dwcmshc > > + then: > > + properties: > > + resets: > > + minItems: 4 > > + maxItems: 4 > > + reset-names: > > + items: > > + - const: arstn > > + - const: phy_rst > > + - const: prstn > > + - const: txrx_rst > > How come you're so drastically different to the other devices? > Also, putting "_rst" in a reset name is pointless. These are all resets > after all by nature. We have simplified the names as follows: reset-names: items: - const: axi - const: phy - const: bus - const: txrx Regarding the functionality of these resets: prst and arst: correspond to the resets for the bus and AXI domains. txrx: is used for the reset of the internal transmit and receive clock domains. phy: is used for the reset of the internal PHY. This will be corrected in the next patch. Is this correct? > > Cheers, > Conor. > > > + required: > > + - clock-output-names > > + - '#clock-cells' > > + - eswin,hsp-sp-csr > > + - eswin,syscrg-csr > > + - drive-impedance-ohm > > + else: > > + properties: > > + resets: > > + maxItems: 5 > > + reset-names: > > + items: > > + - const: core > > + - const: bus > > + - const: axi > > + - const: block > > + - const: timer > > + > > - if: > > properties: > > compatible: > > -- > > 2.25.1 > > ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: Re: [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 2025-09-23 5:45 ` 何欢 @ 2025-09-24 19:43 ` Conor Dooley 2025-09-25 9:37 ` 何欢 0 siblings, 1 reply; 9+ messages in thread From: Conor Dooley @ 2025-09-24 19:43 UTC (permalink / raw) To: 何欢 Cc: ulf.hansson, robh, krzk+dt, conor+dt, jszhang, adrian.hunter, p.zabel, linux-mmc, devicetree, linux-kernel, ningyu, linmin, pinkesh.vaghela, xuxiang, luyulin, dongxuyang, zhangsenchuan, weishangjuan, lizhi2, caohang [-- Attachment #1: Type: text/plain, Size: 10408 bytes --] On Tue, Sep 23, 2025 at 01:45:46PM +0800, 何欢 wrote: > Dear Conor, > Thank you for your valuable and professional suggestions. > Please find our explanations embedded below your comments in the > original email. > > Best regards, > > He Huan > Eswin Computing > > > -----原始邮件----- > > 发件人: "Conor Dooley" <conor@kernel.org> > > 发送时间:2025-09-13 03:10:04 (星期六) > > 收件人: hehuan1@eswincomputing.com > > 抄送: ulf.hansson@linaro.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, jszhang@kernel.org, adrian.hunter@intel.com, p.zabel@pengutronix.de, linux-mmc@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, xuxiang@eswincomputing.com, luyulin@eswincomputing.com, dongxuyang@eswincomputing.com, zhangsenchuan@eswincomputing.com, weishangjuan@eswincomputing.com, lizhi2@eswincomputing.com, caohang@eswincomputing.com > > 主题: Re: [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 > > > > On Fri, Sep 12, 2025 at 05:37:13PM +0800, hehuan1@eswincomputing.com wrote: > > > From: Huan He <hehuan1@eswincomputing.com> > > > > > > EIC7700 use Synopsys dwcmshc IP for SD/eMMC controllers. > > > Add Eswin EIC7700 support in sdhci-of-dwcmshc.yaml. > > > > > > Signed-off-by: Huan He <hehuan1@eswincomputing.com> > > > --- > > > .../bindings/mmc/snps,dwcmshc-sdhci.yaml | 81 +++++++++++++++++-- > > > 1 file changed, 75 insertions(+), 6 deletions(-) > > > > > > diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > > > index f882219a0a26..e0f34bc28e0c 100644 > > > --- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > > > +++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > > > @@ -30,6 +30,7 @@ properties: > > > - sophgo,sg2002-dwcmshc > > > - sophgo,sg2042-dwcmshc > > > - thead,th1520-dwcmshc > > > + - eswin,eic7700-dwcmshc > > > > > > reg: > > > maxItems: 1 > > > @@ -52,17 +53,51 @@ properties: > > > maxItems: 5 > > > > > > reset-names: > > > - items: > > > - - const: core > > > - - const: bus > > > - - const: axi > > > - - const: block > > > - - const: timer > > > + maxItems: 5 > > > > > > rockchip,txclk-tapnum: > > > description: Specify the number of delay for tx sampling. > > > $ref: /schemas/types.yaml#/definitions/uint8 > > > > > > + clock-output-names: > > > + maxItems: 1 > > > + description: > > > + The name of the clock output representing the card clock, > > > + consumed by the PHY. > > > > You have one clock, why do you need this? > > Thank you for the feedback. I will remove it in the next version. > > > > > > + > > > + '#clock-cells': > > > + enum: [0] > > > > const: 0 > > > > > + description: > > > + Specifies how many cells are used when referencing the > > > + exported clock from another node. This property indicates > > > + that the clock output has no extra parameters and represents > > > + the card clock. > > > > This description is not needed. > > > > > + > > > + eswin,hsp-sp-csr: > > > + $ref: /schemas/types.yaml#/definitions/phandle-array > > > + items: > > > + - description: Phandle to HSP(High-Speed Peripheral) device > > > + - description: Offset of the stability status register for > > > + internal clock > > > + - description: Offset of the stability register for host > > > + regulator voltage. > > > + description: | > > > + High-Speed Peripheral device needed to configure internal > > > + clocks, and the power. > > > + > > > + eswin,syscrg-csr: > > > + $ref: /schemas/types.yaml#/definitions/phandle-array > > > + items: > > > + - description: Phandle to system CRG(System Clock and Reset > > > + Generator) device > > > + - description: Offset of core clock control register > > > + description: | > > > + System Clock and Reset Generator device needed to configure > > > + core clock. > > > > This reeks of improper clock tree description. Why can you not just > > request the rate that you need via the common clk framework? Likewise > > for reset. You already have a clocks property that has to include the > > core clock, so I don't see why you need another property to get around > > it. > > Thank you for the feedback. You are absolutely right; We've taken your > advice. In v3 of the patchset, we have completely removed the > eswin,syscrg-csr property. The device tree binding now relies solely > on the standard clocks and clock-names properties to acquire the > necessary clock. Okay cool. > > As a result, I'm also suspicious of your hsp-sp-csr, but these at least > > appear to be internal clocks if your description is to be believed. > > I'd like you to explain exactly what those clocks do and what the "HSP" > > actually is. What other peripherals use it? > > Thank you for raising this. Your concerns regarding the hsp-sp-csr > clocks are valid. > The functionality and purpose of the HSP (hsp-sp-csr) were explained > in our previous patch series for the USB module. The relevant > discussion can be found here: > https://lore.kernel.org/linux-usb/17731a13.1cce.19974dfc64d.Coremail.caohang@eswincomputing.com/ > Please let us know this explanation has addressed your doubts. We're > happy to provide further details if needed. I'll address this on the usb thread, thanks for the explanation there. > > Also, your driver turns on this hsp clock but never turns it off. Same > > for the power. > > The writes to hsp_int_status and hsp_pwr_ctrl are not enabling clocks > or power rails.They are stability assertions. Do you still need to "remove" the assertions if the driver is removed, and the clocks get disabled? Or is that not a concern, because the hardware can't do anything relevant without the driver loaded? If it;s not a concern, then that seems okay to me. > Assert clock stability: Write a value to the hsp_int_status register. > This signals to the eMMC controller that platform clocks (axi master > bus clock, internal core base clock, timer clock) are enabled and > stable. > Assert voltage stability: Write a value to hsp_pwr_ctrl. This signals > that VDD is stable and permits transition to high-speed modes (e.g., > UHS-I). > > > > > I want to see the full dts for what you're doing here before I approve > > this, there's too much here that looks wrong. Okay, that doesn't look too bad, with the updates you've made to remove the sysrg-csr property. > > The full dts is as follows: > sdhci_emmc: mmc@50450000 { > compatible = "eswin,eic7700-dwcmshc"; > reg = <0x0 0x50450000 0x0 0x10000>; > clocks = <&clock 264>, <&clock 546>; > clock-names = "core", "bus"; > assigned-clocks = <&clock 264>; > assigned-clock-rates = <200000000>; > resets = <&reset 75>, <&reset 72>, <&reset 88>, <&reset 92>; > reset-names = "txrx", "phy", "bus", "axi"; > interrupt-parent = <&plic>; > interrupts = <79>; > bus-width = <8>; > non-removable; > mmc-hs400-1_8v; > max-frequency = <200000000>; > #size-cells = <2>; > no-sdio; > no-sd; > eswin,hsp-sp-csr = <&hsp_sp_csr 0x508 0x50c>; > eswin,drive-impedance-ohms = <50>; > }; > > sdio: mmc@0x50460000{ > compatible = "eswin,eic7700-dwcmshc"; > reg = <0x0 0x50460000 0x0 0x10000>; > clocks = <&clock 265>, <&clock 546>; > clock-names ="core","bus"; > resets = <&reset 76>, <&reset 73>, <&reset 87>, <&reset 91>; > reset-names = "txrx","phy", "bus", "axi"; > interrupt-parent = <&plic>; > interrupts = <81>; > clock-frequency = <208000000>; > max-frequency = <208000000>; > #address-cells = <1>; > #size-cells = <0>; > bus-width = <4>; > no-sdio; > no-mmc; > eswin,hsp-sp-csr = <&hsp_sp_csr 0x608 0x60c>; > eswin,drive-impedance-ohms = <33>; > }; > > > > > > + > > > + drive-impedance-ohm: > > > > How come this one has no eswin prefix? Also, the unit is "Ohms", not > > "Ohm". > > In version 3, we renamed the property from drive-impedance-ohm to > eswin,drive-impedance-ohms. > > > > > Additionally, any eswin properties should be restricted to eswin devices > > only. > > > > > + description: Specifies the drive impedance in Ohm. > > > + enum: [33, 40, 50, 66, 100] > > > + > > > required: > > > - compatible > > > - regsdhci_eic7700_dt_parse_clk_phases > > > @@ -110,6 +145,40 @@ allOf: > > > - const: block > > > - const: timer > > > > > > + - if: > > > + properties: > > > + compatible: > > > + contains: > > > + const: eswin,eic7700-dwcmshc > > > + then: > > > + properties: > > > + resets: > > > + minItems: 4 > > > + maxItems: 4 > > > + reset-names: > > > + items: > > > + - const: arstn > > > + - const: phy_rst > > > + - const: prstn > > > + - const: txrx_rst > > > > How come you're so drastically different to the other devices? > > Also, putting "_rst" in a reset name is pointless. These are all resets > > after all by nature.sdhci_eic7700_dt_parse_clk_phases > > We have simplified the names as follows: > reset-names: > items: > - const: axi > - const: phy > - const: bus > - const: txrx > Regarding the functionality of these resets: > prst and arst: correspond to the resets for the bus and AXI domains. > txrx: is used for the reset of the internal transmit and receive clock > domains. > phy: is used for the reset of the internal PHY. > This will be corrected in the next patch. Is this correct? I don't know if it is correct or not, but it looks better than before. Can you explain why you aren't using the "normal" 5 resets that other devices do? [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: Re: Re: [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 2025-09-24 19:43 ` Conor Dooley @ 2025-09-25 9:37 ` 何欢 2025-09-25 19:32 ` Conor Dooley 0 siblings, 1 reply; 9+ messages in thread From: 何欢 @ 2025-09-25 9:37 UTC (permalink / raw) To: Conor Dooley Cc: ulf.hansson, robh, krzk+dt, conor+dt, jszhang, adrian.hunter, p.zabel, linux-mmc, devicetree, linux-kernel, ningyu, linmin, pinkesh.vaghela, xuxiang, luyulin, dongxuyang, zhangsenchuan, weishangjuan, lizhi2, caohang Dear Conor, Thank you for your valuable and professional suggestions. Please find our explanations embedded below your comments in the original email. Best regards, He Huan Eswin Computing > -----原始邮件----- > 发件人: "Conor Dooley" <conor@kernel.org> > 发送时间:2025-09-25 03:43:15 (星期四) > 收件人: 何欢 <hehuan1@eswincomputing.com> > 抄送: ulf.hansson@linaro.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, jszhang@kernel.org, adrian.hunter@intel.com, p.zabel@pengutronix.de, linux-mmc@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, xuxiang@eswincomputing.com, luyulin@eswincomputing.com, dongxuyang@eswincomputing.com, zhangsenchuan@eswincomputing.com, weishangjuan@eswincomputing.com, lizhi2@eswincomputing.com, caohang@eswincomputing.com > 主题: Re: Re: [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 > > On Tue, Sep 23, 2025 at 01:45:46PM +0800, 何欢 wrote: > > Dear Conor, > > Thank you for your valuable and professional suggestions. > > Please find our explanations embedded below your comments in the > > original email. > > > > Best regards, > > > > He Huan > > Eswin Computing > > > > > -----原始邮件----- > > > 发件人: "Conor Dooley" <conor@kernel.org> > > > 发送时间:2025-09-13 03:10:04 (星期六) > > > 收件人: hehuan1@eswincomputing.com > > > 抄送: ulf.hansson@linaro.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, jszhang@kernel.org, adrian.hunter@intel.com, p.zabel@pengutronix.de, linux-mmc@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, xuxiang@eswincomputing.com, luyulin@eswincomputing.com, dongxuyang@eswincomputing.com, zhangsenchuan@eswincomputing.com, weishangjuan@eswincomputing.com, lizhi2@eswincomputing.com, caohang@eswincomputing.com > > > 主题: Re: [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 > > > > > > On Fri, Sep 12, 2025 at 05:37:13PM +0800, hehuan1@eswincomputing.com wrote: > > > > From: Huan He <hehuan1@eswincomputing.com> > > > > > > > > EIC7700 use Synopsys dwcmshc IP for SD/eMMC controllers. > > > > Add Eswin EIC7700 support in sdhci-of-dwcmshc.yaml. > > > > > > > > Signed-off-by: Huan He <hehuan1@eswincomputing.com> > > > > --- > > > > .../bindings/mmc/snps,dwcmshc-sdhci.yaml | 81 +++++++++++++++++-- > > > > 1 file changed, 75 insertions(+), 6 deletions(-) > > > > > > > > diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > > > > index f882219a0a26..e0f34bc28e0c 100644 > > > > --- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > > > > +++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml > > > > @@ -30,6 +30,7 @@ properties: > > > > - sophgo,sg2002-dwcmshc > > > > - sophgo,sg2042-dwcmshc > > > > - thead,th1520-dwcmshc > > > > + - eswin,eic7700-dwcmshc > > > > > > > > reg: > > > > maxItems: 1 > > > > @@ -52,17 +53,51 @@ properties: > > > > maxItems: 5 > > > > > > > > reset-names: > > > > - items: > > > > - - const: core > > > > - - const: bus > > > > - - const: axi > > > > - - const: block > > > > - - const: timer > > > > + maxItems: 5 > > > > > > > > rockchip,txclk-tapnum: > > > > description: Specify the number of delay for tx sampling. > > > > $ref: /schemas/types.yaml#/definitions/uint8 > > > > > > > > + clock-output-names: > > > > + maxItems: 1 > > > > + description: > > > > + The name of the clock output representing the card clock, > > > > + consumed by the PHY. > > > > > > You have one clock, why do you need this? > > > > Thank you for the feedback. I will remove it in the next version. > > > > > > > > > + > > > > + '#clock-cells': > > > > + enum: [0] > > > > > > const: 0 > > > > > > > + description: > > > > + Specifies how many cells are used when referencing the > > > > + exported clock from another node. This property indicates > > > > + that the clock output has no extra parameters and represents > > > > + the card clock. > > > > > > This description is not needed. > > > > > > > + > > > > + eswin,hsp-sp-csr: > > > > + $ref: /schemas/types.yaml#/definitions/phandle-array > > > > + items: > > > > + - description: Phandle to HSP(High-Speed Peripheral) device > > > > + - description: Offset of the stability status register for > > > > + internal clock > > > > + - description: Offset of the stability register for host > > > > + regulator voltage. > > > > + description: | > > > > + High-Speed Peripheral device needed to configure internal > > > > + clocks, and the power. > > > > + > > > > + eswin,syscrg-csr: > > > > + $ref: /schemas/types.yaml#/definitions/phandle-array > > > > + items: > > > > + - description: Phandle to system CRG(System Clock and Reset > > > > + Generator) device > > > > + - description: Offset of core clock control register > > > > + description: | > > > > + System Clock and Reset Generator device needed to configure > > > > + core clock. > > > > > > This reeks of improper clock tree description. Why can you not just > > > request the rate that you need via the common clk framework? Likewise > > > for reset. You already have a clocks property that has to include the > > > core clock, so I don't see why you need another property to get around > > > it. > > > > Thank you for the feedback. You are absolutely right; We've taken your > > advice. In v3 of the patchset, we have completely removed the > > eswin,syscrg-csr property. The device tree binding now relies solely > > on the standard clocks and clock-names properties to acquire the > > necessary clock. > > Okay cool. > > > > As a result, I'm also suspicious of your hsp-sp-csr, but these at least > > > appear to be internal clocks if your description is to be believed. > > > I'd like you to explain exactly what those clocks do and what the "HSP" > > > actually is. What other peripherals use it? > > > > Thank you for raising this. Your concerns regarding the hsp-sp-csr > > clocks are valid. > > The functionality and purpose of the HSP (hsp-sp-csr) were explained > > in our previous patch series for the USB module. The relevant > > discussion can be found here: > > https://lore.kernel.org/linux-usb/17731a13.1cce.19974dfc64d.Coremail.caohang@eswincomputing.com/ > > Please let us know this explanation has addressed your doubts. We're > > happy to provide further details if needed. > > I'll address this on the usb thread, thanks for the explanation there. > > > > Also, your driver turns on this hsp clock but never turns it off. Same > > > for the power. > > > > The writes to hsp_int_status and hsp_pwr_ctrl are not enabling clocks > > or power rails.They are stability assertions. > > Do you still need to "remove" the assertions if the driver is removed, > and the clocks get disabled? Or is that not a concern, because the > hardware can't do anything relevant without the driver loaded? If it;s > not a concern, then that seems okay to me. The writes to hsp_int_status and hsp_pwr_ctrl are just status indicators. They assert that clocks and voltages are stable. There is no need to clear these assertions when the driver is removed or clocks are disabled. This is because the hardware cannot perform any relevant operations without the driver loaded. Whether these registers are written or not has no impact on hardware behavior when the clocks are off. > > Assert clock stability: Write a value to the hsp_int_status register. > > This signals to the eMMC controller that platform clocks (axi master > > bus clock, internal core base clock, timer clock) are enabled and > > stable. > > Assert voltage stability: Write a value to hsp_pwr_ctrl. This signals > > that VDD is stable and permits transition to high-speed modes (e.g., > > UHS-I). > > > > > > > > I want to see the full dts for what you're doing here before I approve > > > this, there's too much here that looks wrong. > > Okay, that doesn't look too bad, with the updates you've made to remove > the sysrg-csr property. > > > > > The full dts is as follows: > > sdhci_emmc: mmc@50450000 { > > compatible = "eswin,eic7700-dwcmshc"; > > reg = <0x0 0x50450000 0x0 0x10000>; > > clocks = <&clock 264>, <&clock 546>; > > clock-names = "core", "bus"; > > assigned-clocks = <&clock 264>; > > assigned-clock-rates = <200000000>; > > resets = <&reset 75>, <&reset 72>, <&reset 88>, <&reset 92>; > > reset-names = "txrx", "phy", "bus", "axi"; > > interrupt-parent = <&plic>; > > interrupts = <79>; > > bus-width = <8>; > > non-removable; > > mmc-hs400-1_8v; > > max-frequency = <200000000>; > > #size-cells = <2>; > > no-sdio; > > no-sd; > > eswin,hsp-sp-csr = <&hsp_sp_csr 0x508 0x50c>; > > eswin,drive-impedance-ohms = <50>; > > }; > > > > sdio: mmc@0x50460000{ > > compatible = "eswin,eic7700-dwcmshc"; > > reg = <0x0 0x50460000 0x0 0x10000>; > > clocks = <&clock 265>, <&clock 546>; > > clock-names ="core","bus"; > > resets = <&reset 76>, <&reset 73>, <&reset 87>, <&reset 91>; > > reset-names = "txrx","phy", "bus", "axi"; > > interrupt-parent = <&plic>; > > interrupts = <81>; > > clock-frequency = <208000000>; > > max-frequency = <208000000>; > > #address-cells = <1>; > > #size-cells = <0>; > > bus-width = <4>; > > no-sdio; > > no-mmc; > > eswin,hsp-sp-csr = <&hsp_sp_csr 0x608 0x60c>; > > eswin,drive-impedance-ohms = <33>; > > }; > > > > > > > > > + > > > > + drive-impedance-ohm: > > > > > > How come this one has no eswin prefix? Also, the unit is "Ohms", not > > > "Ohm". > > > > In version 3, we renamed the property from drive-impedance-ohm to > > eswin,drive-impedance-ohms. > > > > > > > > Additionally, any eswin properties should be restricted to eswin devices > > > only. > > > > > > > + description: Specifies the drive impedance in Ohm. > > > > + enum: [33, 40, 50, 66, 100] > > > > + > > > > required: > > > > - compatible > > > > - regsdhci_eic7700_dt_parse_clk_phases > > > > @@ -110,6 +145,40 @@ allOf: > > > > - const: block > > > > - const: timer > > > > > > > > + - if: > > > > + properties: > > > > + compatible: > > > > + contains: > > > > + const: eswin,eic7700-dwcmshc > > > > + then: > > > > + properties: > > > > + resets: > > > > + minItems: 4 > > > > + maxItems: 4 > > > > + reset-names: > > > > + items: > > > > + - const: arstn > > > > + - const: phy_rst > > > > + - const: prstn > > > > + - const: txrx_rst > > > > > > How come you're so drastically different to the other devices? > > > Also, putting "_rst" in a reset name is pointless. These are all resets > > > after all by nature.sdhci_eic7700_dt_parse_clk_phases > > > > We have simplified the names as follows: > > reset-names: > > items: > > - const: axi > > - const: phy > > - const: bus > > - const: txrx > > Regarding the functionality of these resets: > > prst and arst: correspond to the resets for the bus and AXI domains. > > txrx: is used for the reset of the internal transmit and receive clock > > domains. > > phy: is used for the reset of the internal PHY. > > This will be corrected in the next patch. Is this correct? > > I don't know if it is correct or not, but it looks better than before. > Can you explain why you aren't using the "normal" 5 resets that other > devices do? Our reset naming is based on our hardware's reset design. Although we do not follow the typical 5-reset naming used by other vendors, the functionality is equivalent. Each of our resets covers the corresponding hardware domains. There is no one-to-one correspondence, but our implementation includes all necessary reset functionality. ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: Re: Re: [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 2025-09-25 9:37 ` 何欢 @ 2025-09-25 19:32 ` Conor Dooley 0 siblings, 0 replies; 9+ messages in thread From: Conor Dooley @ 2025-09-25 19:32 UTC (permalink / raw) To: 何欢 Cc: ulf.hansson, robh, krzk+dt, conor+dt, jszhang, adrian.hunter, p.zabel, linux-mmc, devicetree, linux-kernel, ningyu, linmin, pinkesh.vaghela, xuxiang, luyulin, dongxuyang, zhangsenchuan, weishangjuan, lizhi2, caohang [-- Attachment #1: Type: text/plain, Size: 4186 bytes --] On Thu, Sep 25, 2025 at 05:37:59PM +0800, 何欢 wrote: > Dear Conor, > Thank you for your valuable and professional suggestions. > Please find our explanations embedded below your comments in the > original email. FWIW, there's no need to thank me on every mail! > > > > As a result, I'm also suspicious of your hsp-sp-csr, but these at least > > > > appear to be internal clocks if your description is to be believed. > > > > I'd like you to explain exactly what those clocks do and what the "HSP" > > > > actually is. What other peripherals use it? > > > > > > Thank you for raising this. Your concerns regarding the hsp-sp-csr > > > clocks are valid. > > > The functionality and purpose of the HSP (hsp-sp-csr) were explained > > > in our previous patch series for the USB module. The relevant > > > discussion can be found here: > > > https://lore.kernel.org/linux-usb/17731a13.1cce.19974dfc64d.Coremail.caohang@eswincomputing.com/ > > > Please let us know this explanation has addressed your doubts. We're > > > happy to provide further details if needed. > > > > I'll address this on the usb thread, thanks for the explanation there. > > > > > > Also, your driver turns on this hsp clock but never turns it off. Same > > > > for the power. > > > > > > The writes to hsp_int_status and hsp_pwr_ctrl are not enabling clocks > > > or power rails.They are stability assertions. > > > > Do you still need to "remove" the assertions if the driver is removed, > > and the clocks get disabled? Or is that not a concern, because the > > hardware can't do anything relevant without the driver loaded? If it;s > > not a concern, then that seems okay to me. > > The writes to hsp_int_status and hsp_pwr_ctrl are just status indicators. > They assert that clocks and voltages are stable. There is no need to clear > these assertions when the driver is removed or clocks are disabled. This > is because the hardware cannot perform any relevant operations without the > driver loaded. Whether these registers are written or not has no impact on > hardware behavior when the clocks are off. Okay, cool. That sounds fine to me. > > > > > + - if: > > > > > + properties: > > > > > + compatible: > > > > > + contains: > > > > > + const: eswin,eic7700-dwcmshc > > > > > + then: > > > > > + properties: > > > > > + resets: > > > > > + minItems: 4 > > > > > + maxItems: 4 > > > > > + reset-names: > > > > > + items: > > > > > + - const: arstn > > > > > + - const: phy_rst > > > > > + - const: prstn > > > > > + - const: txrx_rst > > > > > > > > How come you're so drastically different to the other devices? > > > > Also, putting "_rst" in a reset name is pointless. These are all resets > > > > after all by nature.sdhci_eic7700_dt_parse_clk_phases > > > > > > We have simplified the names as follows: > > > reset-names: > > > items: > > > - const: axi > > > - const: phy > > > - const: bus > > > - const: txrx > > > Regarding the functionality of these resets: > > > prst and arst: correspond to the resets for the bus and AXI domains. > > > txrx: is used for the reset of the internal transmit and receive clock > > > domains. > > > phy: is used for the reset of the internal PHY. > > > This will be corrected in the next patch. Is this correct? > > > > I don't know if it is correct or not, but it looks better than before. > > Can you explain why you aren't using the "normal" 5 resets that other > > devices do? > > Our reset naming is based on our hardware's reset design. Although we do > not follow the typical 5-reset naming used by other vendors, the > functionality is equivalent. Each of our resets covers the corresponding > hardware domains. There is no one-to-one correspondence, but our > implementation includes all necessary reset functionality. Okay. It's just unusual that you have something significantly different to what the other 10+ devices have. It's fine to be different if your hardware genuinely is not the same. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 2/2] mmc: sdhci-of-dwcmshc: Add support for Eswin EIC7700 2025-09-12 9:34 [PATCH v2 0/2] Add support for Eswin EIC7700 SD/eMMC controller hehuan1 2025-09-12 9:37 ` [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 hehuan1 @ 2025-09-12 9:38 ` hehuan1 2025-09-18 17:36 ` Adrian Hunter 1 sibling, 1 reply; 9+ messages in thread From: hehuan1 @ 2025-09-12 9:38 UTC (permalink / raw) To: ulf.hansson, robh, krzk+dt, conor+dt, jszhang, adrian.hunter, p.zabel, linux-mmc, devicetree, linux-kernel Cc: ningyu, linmin, pinkesh.vaghela, xuxiang, luyulin, dongxuyang, zhangsenchuan, weishangjuan, lizhi2, caohang, hehuan1 From: Huan He <hehuan1@eswincomputing.com> Add support for the mmc controller in the Eswin EIC7700 with the new compatible "eswin,eic7700-dwcmshc". Implement custom sdhci_ops for set_clock, reset, set_uhs_signaling, platform_execute_tuning. Signed-off-by: Huan He <hehuan1@eswincomputing.com> --- drivers/mmc/host/sdhci-of-dwcmshc.c | 770 ++++++++++++++++++++++++++++ 1 file changed, 770 insertions(+) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index ee6b1096f709..dd16c7a3bda7 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -11,6 +11,7 @@ #include <linux/arm-smccc.h> #include <linux/bitfield.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/dma-mapping.h> #include <linux/iopoll.h> #include <linux/kernel.h> @@ -19,8 +20,11 @@ #include <linux/platform_device.h> #include <linux/pm_domain.h> #include <linux/pm_runtime.h> +#include <linux/regmap.h> #include <linux/reset.h> #include <linux/sizes.h> +#include <linux/mfd/syscon.h> +#include <linux/units.h> #include "sdhci-pltfm.h" #include "cqhci.h" @@ -194,6 +198,10 @@ #define PHY_DLLDL_CNFG_SLV_INPSEL_MASK GENMASK(6, 5) /* bits [6:5] */ #define PHY_DLLDL_CNFG_SLV_INPSEL 0x3 /* clock source select for slave DL */ +#define PHY_DLL_OFFST_R (DWC_MSHC_PTR_PHY_R + 0x29) +#define PHY_DLLBT_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x2c) +#define PHY_DLL_STATUS_R (DWC_MSHC_PTR_PHY_R + 0x2e) + #define FLAG_IO_FIXED_1V8 BIT(0) #define BOUNDARY_OK(addr, len) \ @@ -206,6 +214,48 @@ /* SMC call for BlueField-3 eMMC RST_N */ #define BLUEFIELD_SMC_SET_EMMC_RST_N 0x82000007 +/* Eswin specific Registers */ +#define EIC7700_CARD_CLK_STABLE BIT(28) +#define EIC7700_INT_BCLK_STABLE BIT(16) +#define EIC7700_INT_ACLK_STABLE BIT(8) +#define EIC7700_INT_TMCLK_STABLE BIT(0) +#define EIC7700_INT_CLK_STABLE (EIC7700_CARD_CLK_STABLE | \ + EIC7700_INT_ACLK_STABLE | \ + EIC7700_INT_BCLK_STABLE | \ + EIC7700_INT_TMCLK_STABLE) +#define EIC7700_HOST_VAL_STABLE BIT(0) +#define EIC7700_CORE_CLK_ENABLE BIT(16) +#define EIC7700_CORE_CLK_FREQ_SHIFT 4 +#define EIC7700_CORE_CLK_FREQ_MASK 0xfffu +#define EIC7700_CORE_CLK_SEL_BIT BIT(0) + +/* strength definition */ +#define PHYCTRL_DR_33OHM 0xee +#define PHYCTRL_DR_40OHM 0xcc +#define PHYCTRL_DR_50OHM 0x88 +#define PHYCTRL_DR_66OHM 0x44 +#define PHYCTRL_DR_100OHM 0x00 + +#define LATENCY_LT_BIT_OFFSET 19 +#define LATENCY_LT_MASK 0x3 +#define LATENCY_LT_3 0x2 +#define VENDOR_AT_SATA_R 0x544 + +#define MAX_PHASE_CODE 0xff +#define TUNING_RANGE_THRESHOLD 40 + +#define PHY_CLK_MAX_DELAY_MASK 0x7f +#define PHY_PAD_SP_DRIVE_SHIF 16 + +#define EIC7700_CORE_CLK_SRC_208MHZ (208 * HZ_PER_MHZ) +#define EIC7700_CORE_CLK_SRC_200MHZ (200 * HZ_PER_MHZ) +#define MAX_CORE_CLK_DIV 0xfff +#define DLL_LOCK_STS BIT(0) +#define DLL_ERROR_STS BIT(1) +#define PHY_DELAY_CODE_MAX 0x7f +#define PHY_DELAY_CODE_EMMC 0x17 +#define PHY_DELAY_CODE_SD 0x55 + enum dwcmshc_rk_type { DWCMSHC_RK3568, DWCMSHC_RK3588, @@ -217,6 +267,18 @@ struct rk35xx_priv { u8 txclk_tapnum; }; +struct eic7700_priv { + struct sdhci_host *host; + struct clk_hw sdcardclk_hw; + struct clk *sdcardclk; + int clk_phase_out[MMC_TIMING_MMC_HS400 + 1]; + void (*set_clk_delays)(struct sdhci_host *host); + struct reset_control *reset; + struct regmap *crg_regmap; + unsigned int crg_core_clk; + unsigned int drive_impedance; +}; + #define DWCMSHC_MAX_OTHER_CLKS 3 struct dwcmshc_priv { @@ -238,6 +300,8 @@ struct dwcmshc_pltfm_data { void (*postinit)(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv); }; +static void dwcmshc_disable_card_clk(struct sdhci_host *host); + static int dwcmshc_get_enable_other_clks(struct device *dev, struct dwcmshc_priv *priv, int num_clks, @@ -1095,6 +1159,685 @@ static int sg2042_init(struct device *dev, struct sdhci_host *host, ARRAY_SIZE(clk_ids), clk_ids); } +static void sdhci_eic7700_enable_card_clk(struct sdhci_host *host) +{ + u16 clk; + int ret; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + clk |= SDHCI_CLOCK_INT_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + /* Wait max 150 ms */ + ret = read_poll_timeout(sdhci_readw, clk, clk & SDHCI_CLOCK_INT_STABLE, + 10, 150000, false, host, SDHCI_CLOCK_CONTROL); + if (ret) { + pr_err("%s: Internal clock never stabilised.\n", + mmc_hostname(host->mmc)); + return; + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + usleep_range(1000, 2000); +} + +static void eic7700_mshc_coreclk_disable(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + struct eic7700_priv *priv = dwc_priv->priv; + u32 val; + + regmap_read(priv->crg_regmap, priv->crg_core_clk, &val); + val &= ~EIC7700_CORE_CLK_ENABLE; + regmap_write(priv->crg_regmap, priv->crg_core_clk, val); +} + +static void eic7700_mshc_coreclk_config(struct sdhci_host *host, + u16 divisor, unsigned int flag_sel) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + struct eic7700_priv *priv = dwc_priv->priv; + u32 val; + + regmap_read(priv->crg_regmap, priv->crg_core_clk, &val); + val &= ~EIC7700_CORE_CLK_ENABLE; + regmap_write(priv->crg_regmap, priv->crg_core_clk, val); + usleep_range(10, 20); + + val &= ~(EIC7700_CORE_CLK_FREQ_MASK << EIC7700_CORE_CLK_FREQ_SHIFT); + val |= (divisor & EIC7700_CORE_CLK_FREQ_MASK) << + EIC7700_CORE_CLK_FREQ_SHIFT; + val &= ~(EIC7700_CORE_CLK_SEL_BIT); + val |= flag_sel; + regmap_write(priv->crg_regmap, priv->crg_core_clk, val); + + usleep_range(50, 60); + val |= EIC7700_CORE_CLK_ENABLE; + regmap_write(priv->crg_regmap, priv->crg_core_clk, val); + usleep_range(1000, 1100); +} + +static void sdhci_eic7700_set_core_clock(struct sdhci_host *host, + unsigned int clock) +{ + unsigned int flag_sel, max_clk; + unsigned int div, divide; + + host->mmc->actual_clock = clock; + + if (clock == 0) { + eic7700_mshc_coreclk_disable(host); + return; + } + + if (EIC7700_CORE_CLK_SRC_208MHZ % clock == 0) { + flag_sel = 1; + max_clk = EIC7700_CORE_CLK_SRC_208MHZ; + } else { + flag_sel = 0; + max_clk = EIC7700_CORE_CLK_SRC_200MHZ; + } + + for (div = 1; div <= MAX_CORE_CLK_DIV; div++) { + if ((max_clk / div) <= clock) + break; + } + div--; + + if (div == 0 || div == 1) + divide = 2; + else + divide = (div + 1) * 2; + + dwcmshc_disable_card_clk(host); + eic7700_mshc_coreclk_config(host, divide, flag_sel); + sdhci_eic7700_enable_card_clk(host); + usleep_range(2000, 3000); +} + +static void sdhci_eic7700_set_clock(struct sdhci_host *host, unsigned int clock) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + struct eic7700_priv *priv = dwc_priv->priv; + + /* Set the Input and Output Clock Phase Delays */ + if (priv->set_clk_delays) + priv->set_clk_delays(host); + + sdhci_eic7700_set_core_clock(host, clock); +} + +static void sdhci_eic7700_config_phy_delay(struct sdhci_host *host, int delay) +{ + delay &= PHY_CLK_MAX_DELAY_MASK; + + /* phy clk delay line config */ + sdhci_writeb(host, PHY_SDCLKDL_CNFG_UPDATE, PHY_SDCLKDL_CNFG_R); + sdhci_writeb(host, delay, PHY_SDCLKDL_DC_R); + sdhci_writeb(host, 0x0, PHY_SDCLKDL_CNFG_R); +} + +static void sdhci_eic7700_config_phy(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + u32 emmc_caps = MMC_CAP2_NO_SD | MMC_CAP2_NO_SDIO; + struct eic7700_priv *priv = dwc_priv->priv; + unsigned int val, drv; + + drv = priv->drive_impedance << PHY_PAD_SP_DRIVE_SHIF; + + if ((host->mmc->caps2 & emmc_caps) == emmc_caps) { + val = sdhci_readw(host, dwc_priv->vendor_specific_area1 + + DWCMSHC_EMMC_CONTROL); + val |= DWCMSHC_CARD_IS_EMMC; + sdhci_writew(host, val, dwc_priv->vendor_specific_area1 + + DWCMSHC_EMMC_CONTROL); + } + + dwcmshc_disable_card_clk(host); + + /* reset phy, config phy's pad */ + sdhci_writel(host, drv | (~PHY_CNFG_RSTN_DEASSERT), PHY_CNFG_R); + + /* configure phy pads */ + val = FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, + PHY_PAD_TXSLEW_CTRL_N_SG2042); + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, + PHY_PAD_TXSLEW_CTRL_N_SG2042); + val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, + PHY_PAD_WEAKPULL_PULLUP); + val |= PHY_PAD_RXSEL_1V8; + sdhci_writew(host, val, PHY_CMDPAD_CNFG_R); + sdhci_writew(host, val, PHY_DATAPAD_CNFG_R); + sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R); + + /* Clock PAD Setting */ + val = FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, + PHY_PAD_TXSLEW_CTRL_N_SG2042); + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, + PHY_PAD_TXSLEW_CTRL_N_SG2042); + sdhci_writew(host, val, PHY_CLKPAD_CNFG_R); + + /* PHY strobe PAD setting (EMMC only) */ + if ((host->mmc->caps2 & emmc_caps) == emmc_caps) { + val = FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, + PHY_PAD_TXSLEW_CTRL_N_SG2042); + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, + PHY_PAD_TXSLEW_CTRL_N_SG2042); + val |= PHY_PAD_RXSEL_1V8; + sdhci_writew(host, val, PHY_STBPAD_CNFG_R); + } + usleep_range(2000, 3000); + sdhci_writel(host, drv | PHY_CNFG_RSTN_DEASSERT, PHY_CNFG_R); + sdhci_eic7700_config_phy_delay(host, dwc_priv->delay_line); + sdhci_eic7700_enable_card_clk(host); +} + +static void sdhci_eic7700_reset(struct sdhci_host *host, u8 mask) +{ + sdhci_reset(host, mask); + + /* after reset all, the phy's config will be clear */ + if (mask == SDHCI_RESET_ALL) + sdhci_eic7700_config_phy(host); +} + +static unsigned long +sdhci_eic7700_sdcardclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct eic7700_priv *priv = + container_of(hw, struct eic7700_priv, sdcardclk_hw); + + return priv->host->mmc->actual_clock; +} + +static const struct clk_ops eic7700_sdcardclk_ops = { + .recalc_rate = sdhci_eic7700_sdcardclk_recalc_rate, +}; + +static int sdhci_eic7700_register_sdcardclk(struct eic7700_priv *priv, + struct clk *clk, struct device *dev) +{ + struct device_node *np = dev->of_node; + struct clk_init_data sdcardclk_init; + const char *parent_clk_name; + int ret; + + ret = of_property_read_string_index(np, "clock-output-names", 0, + &sdcardclk_init.name); + if (ret) { + dev_err(dev, "DT has #clock-cells but no clock-output-names\n"); + return ret; + } + + parent_clk_name = __clk_get_name(clk); + sdcardclk_init.parent_names = &parent_clk_name; + sdcardclk_init.num_parents = 1; + sdcardclk_init.flags = CLK_GET_RATE_NOCACHE; + sdcardclk_init.ops = &eic7700_sdcardclk_ops; + + priv->sdcardclk_hw.init = &sdcardclk_init; + priv->sdcardclk = devm_clk_register(dev, &priv->sdcardclk_hw); + if (IS_ERR(priv->sdcardclk)) + return PTR_ERR(priv->sdcardclk); + priv->sdcardclk_hw.init = NULL; + + ret = of_clk_add_provider(np, of_clk_src_simple_get, + priv->sdcardclk); + if (ret) + dev_err(dev, "Failed to add sdcard clock provider\n"); + + return ret; +} + +static int sdhci_eic7700_register_sdclk(struct dwcmshc_priv *dwc_priv, + struct clk *clk, struct device *dev) +{ + struct device_node *np = dev->of_node; + int ret; + + /* Providing a clock to the PHY is optional; no error if missing */ + if (!of_property_present(np, "#clock-cells")) + return 0; + + ret = sdhci_eic7700_register_sdcardclk(dwc_priv->priv, clk, dev); + + return ret; +} + +static int sdhci_eic7700_reset_init(struct device *dev, + struct eic7700_priv *priv) +{ + int ret; + + priv->reset = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(priv->reset)) { + ret = PTR_ERR(priv->reset); + dev_err(dev, "failed to get reset control %d\n", ret); + return ret; + } + + ret = reset_control_assert(priv->reset); + if (ret) { + dev_err(dev, "Failed to assert reset signals: %d\n", ret); + return ret; + } + usleep_range(2000, 2100); + ret = reset_control_deassert(priv->reset); + if (ret) { + dev_err(dev, "Failed to deassert reset signals: %d\n", ret); + return ret; + } + + return ret; +} + +static void sdhci_eic7700_set_clk_delays(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + struct eic7700_priv *priv = dwc_priv->priv; + + clk_set_phase(priv->sdcardclk, + priv->clk_phase_out[host->timing]); +} + +static void sdhci_eic7700_dt_read_clk_phase(struct device *dev, + struct eic7700_priv *priv, + unsigned int timing, + const char *prop) +{ + struct device_node *np = dev->of_node; + int clk_phase[2] = {0}; + + /* + * Read Tap Delay values from DT, if the DT does not contain the Tap + * Values then use the pre-defined values. + */ + if (of_property_read_variable_u32_array(np, prop, &clk_phase[0], 2, + 0)) { + dev_dbg(dev, "Using predefined clock phase for %s = %d\n", + prop, priv->clk_phase_out[timing]); + return; + } + + /* Only use the output clock delays in order */ + priv->clk_phase_out[timing] = clk_phase[1]; +} + +static void sdhci_eic7700_dt_parse_clk_phases(struct device *dev, + struct dwcmshc_priv *dwc_priv) +{ + struct eic7700_priv *priv = dwc_priv->priv; + + priv->set_clk_delays = sdhci_eic7700_set_clk_delays; + + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_LEGACY, + "clk-phase-legacy"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_MMC_HS, + "clk-phase-mmc-hs"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_SD_HS, + "clk-phase-sd-hs"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_SDR12, + "clk-phase-uhs-sdr12"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_SDR25, + "clk-phase-uhs-sdr25"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_SDR50, + "clk-phase-uhs-sdr50"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_SDR104, + "clk-phase-uhs-sdr104"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_DDR50, + "clk-phase-uhs-ddr50"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_MMC_DDR52, + "clk-phase-mmc-ddr52"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_MMC_HS200, + "clk-phase-mmc-hs200"); + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_MMC_HS400, + "clk-phase-mmc-hs400"); +} + +static unsigned int eic7700_convert_drive_impedance_ohm(struct device *dev, + unsigned int dr_ohm) +{ + switch (dr_ohm) { + case 100: + return PHYCTRL_DR_100OHM; + case 66: + return PHYCTRL_DR_66OHM; + case 50: + return PHYCTRL_DR_50OHM; + case 40: + return PHYCTRL_DR_40OHM; + case 33: + return PHYCTRL_DR_33OHM; + } + + dev_warn(dev, "Invalid value %u for drive-impedance-ohm.\n", dr_ohm); + return PHYCTRL_DR_50OHM; +} + +static int sdhci_eic7700_delay_tuning(struct sdhci_host *host, u32 opcode) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + int cmd_error, delay, ret, i; + int delay_min = -1; + int delay_max = -1; + + for (i = 0; i <= PHY_DELAY_CODE_MAX; i++) { + dwcmshc_disable_card_clk(host); + sdhci_eic7700_config_phy_delay(host, i); + sdhci_eic7700_enable_card_clk(host); + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); + if (ret) { + host->ops->reset(host, + SDHCI_RESET_CMD | SDHCI_RESET_DATA); + usleep_range(200, 210); + if (delay_min != -1 && delay_max != -1) + break; + } else { + if (delay_min == -1) { + delay_min = i; + continue; + } else { + delay_max = i; + continue; + } + } + } + if (delay_min == -1 && delay_max == -1) { + pr_err("%s: delay code tuning failed!\n", + mmc_hostname(host->mmc)); + dwcmshc_disable_card_clk(host); + sdhci_eic7700_config_phy_delay(host, dwc_priv->delay_line); + sdhci_eic7700_enable_card_clk(host); + + return ret; + } + + delay = (delay_min + delay_max) / 2; + dwcmshc_disable_card_clk(host); + sdhci_eic7700_config_phy_delay(host, delay); + sdhci_eic7700_enable_card_clk(host); + + return 0; +} + +static int sdhci_eic7700_phase_code_tuning(struct sdhci_host *host, u32 opcode) +{ + u32 sd_caps = MMC_CAP2_NO_MMC | MMC_CAP2_NO_SDIO; + int cmd_error, ret, i; + bool is_sdio = false; + int phase_code = -1; + int code_range = -1; + int code_min = -1; + int code_max = -1; + + if ((host->mmc->caps2 & sd_caps) == sd_caps) + is_sdio = true; + + for (i = 0; i <= MAX_PHASE_CODE; i++) { + dwcmshc_disable_card_clk(host); + sdhci_writew(host, i, VENDOR_AT_SATA_R); + sdhci_eic7700_enable_card_clk(host); + + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); + host->ops->reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + + if (ret) { + /* SDIO specific range tracking */ + if (is_sdio && code_min != -1 && code_max != -1) { + if (code_max - code_min > code_range) { + code_range = code_max - code_min; + phase_code = (code_min + code_max) / 2; + if (code_range > TUNING_RANGE_THRESHOLD) + break; + } + code_min = -1; + code_max = -1; + } + /* EMMC breaks after first valid range */ + if (!is_sdio && code_min != -1 && code_max != -1) + break; + } else { + /* Track valid phase code range */ + if (code_min == -1) { + code_min = i; + if (!is_sdio) + continue; + } + code_max = i; + if (is_sdio && i == MAX_PHASE_CODE) { + if (code_max - code_min > code_range) { + code_range = code_max - code_min; + phase_code = (code_min + code_max) / 2; + } + } + } + } + + /* Handle tuning failure case */ + if ((is_sdio && phase_code == -1) || + (!is_sdio && code_min == -1 && code_max == -1)) { + pr_err("%s: phase code tuning failed!\n", + mmc_hostname(host->mmc)); + dwcmshc_disable_card_clk(host); + sdhci_writew(host, 0, VENDOR_AT_SATA_R); + sdhci_eic7700_enable_card_clk(host); + return -EIO; + } + if (!is_sdio) + phase_code = (code_min + code_max) / 2; + + dwcmshc_disable_card_clk(host); + sdhci_writew(host, phase_code, VENDOR_AT_SATA_R); + sdhci_eic7700_enable_card_clk(host); + + /* SDIO specific final verification */ + if (is_sdio) { + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); + host->ops->reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + if (ret) { + pr_err("%s: Final phase code 0x%x verification failed!\n", + mmc_hostname(host->mmc), phase_code); + return ret; + } + } + + return 0; +} + +static int sdhci_eic7700_executing_tuning(struct sdhci_host *host, u32 opcode) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); + u32 emmc_caps = MMC_CAP2_NO_SD | MMC_CAP2_NO_SDIO; + u16 ctrl; + u32 val; + int ret; + + dwcmshc_disable_card_clk(host); + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl &= ~SDHCI_CTRL_TUNED_CLK; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + + val = sdhci_readl(host, priv->vendor_specific_area1 + + DWCMSHC_EMMC_ATCTRL); + val |= AT_CTRL_SW_TUNE_EN; + sdhci_writew(host, val, priv->vendor_specific_area1 + + DWCMSHC_EMMC_ATCTRL); + + sdhci_writew(host, 0, VENDOR_AT_SATA_R); + + sdhci_eic7700_enable_card_clk(host); + + sdhci_writew(host, 0x0, SDHCI_CMD_DATA); + + if ((host->mmc->caps2 & emmc_caps) == emmc_caps) { + ret = sdhci_eic7700_delay_tuning(host, opcode); + if (ret) + return ret; + } + + ret = sdhci_eic7700_phase_code_tuning(host, opcode); + if (ret) + return ret; + + return 0; +} + +static void sdhci_eic7700_set_uhs_signaling(struct sdhci_host *host, + unsigned int timing) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); + u8 status; + u32 val; + int ret; + + dwcmshc_set_uhs_signaling(host, timing); + + /* here need make dll locked when in hs400 at 200MHz */ + if (timing == MMC_TIMING_MMC_HS400 && host->clock == 200000000) { + dwcmshc_disable_card_clk(host); + + val = sdhci_readl(host, priv->vendor_specific_area1 + + DWCMSHC_EMMC_ATCTRL); + val &= ~(LATENCY_LT_MASK << LATENCY_LT_BIT_OFFSET); + val |= (LATENCY_LT_3 << LATENCY_LT_MASK); + sdhci_writew(host, val, priv->vendor_specific_area1 + + DWCMSHC_EMMC_ATCTRL); + + sdhci_writeb(host, 0x23, PHY_DLL_CNFG1_R); + sdhci_writeb(host, 0x02, PHY_DLL_CNFG2_R); + sdhci_writeb(host, 0x60, PHY_DLLDL_CNFG_R); + sdhci_writeb(host, 0x00, PHY_DLL_OFFST_R); + sdhci_writew(host, 0xffff, PHY_DLLBT_CNFG_R); + + sdhci_eic7700_enable_card_clk(host); + sdhci_writeb(host, PHY_DLL_CTRL_ENABLE, PHY_DLL_CTRL_R); + usleep_range(100, 110); + + ret = read_poll_timeout(sdhci_readb, status, + status & DLL_LOCK_STS, 100, 1000000, + false, host, PHY_DLL_STATUS_R); + if (ret) { + pr_err("%s: DLL lock timeout! status: 0x%x\n", + mmc_hostname(host->mmc), status); + return; + } + + status = sdhci_readb(host, PHY_DLL_STATUS_R); + if (status & DLL_ERROR_STS) { + pr_err("%s: DLL lock failed!err_status:0x%x\n", + mmc_hostname(host->mmc), status); + } + } +} + +static void sdhci_eic7700_set_uhs_wrapper(struct sdhci_host *host, + unsigned int timing) +{ + u32 sd_caps = MMC_CAP2_NO_MMC | MMC_CAP2_NO_SDIO; + + if ((host->mmc->caps2 & sd_caps) == sd_caps) + sdhci_set_uhs_signaling(host, timing); + else + sdhci_eic7700_set_uhs_signaling(host, timing); +} + +static int eic7700_init(struct device *dev, struct sdhci_host *host, + struct dwcmshc_priv *dwc_priv) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + u32 emmc_caps = MMC_CAP2_NO_SD | MMC_CAP2_NO_SDIO; + unsigned int val, hsp_int_status, hsp_pwr_ctrl; + struct of_phandle_args args; + struct eic7700_priv *priv; + struct regmap *hsp_regmap; + int ret; + + priv = devm_kzalloc(dev, sizeof(struct eic7700_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->host = host; + dwc_priv->priv = priv; + + ret = sdhci_eic7700_register_sdclk(dwc_priv, pltfm_host->clk, dev); + if (ret) + return ret; + + ret = sdhci_eic7700_reset_init(dev, dwc_priv->priv); + if (ret) { + dev_err(dev, "failed to reset\n"); + return ret; + } + + ret = of_parse_phandle_with_fixed_args(dev->of_node, + "eswin,syscrg-csr", 1, 0, &args); + if (ret) { + dev_err(dev, "Fail to parse 'eswin,syscrg-csr' phandle (%d)\n", + ret); + return ret; + } + priv->crg_regmap = syscon_node_to_regmap(args.np); + if (IS_ERR(priv->crg_regmap)) { + dev_err(dev, "Failed to get regmap for 'eswin,syscrg-csr'\n"); + of_node_put(args.np); + return ret; + } + + priv->crg_core_clk = args.args[0]; + of_node_put(args.np); + + ret = of_parse_phandle_with_fixed_args(dev->of_node, + "eswin,hsp-sp-csr", 2, 0, &args); + if (ret) { + dev_err(dev, "Fail to parse 'eswin,hsp-sp-csr' phandle (%d)\n", + ret); + return ret; + } + + hsp_regmap = syscon_node_to_regmap(args.np); + if (IS_ERR(hsp_regmap)) { + dev_err(dev, "Failed to get regmap for 'eswin,hsp-sp-csr'\n"); + of_node_put(args.np); + return ret; + } + hsp_int_status = args.args[0]; + hsp_pwr_ctrl = args.args[1]; + of_node_put(args.np); + regmap_write(hsp_regmap, hsp_int_status, + EIC7700_INT_CLK_STABLE); + regmap_write(hsp_regmap, hsp_pwr_ctrl, + EIC7700_HOST_VAL_STABLE); + + if ((host->mmc->caps2 & emmc_caps) == emmc_caps) + dwc_priv->delay_line = PHY_DELAY_CODE_EMMC; + else + dwc_priv->delay_line = PHY_DELAY_CODE_SD; + + if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val)) + priv->drive_impedance = + eic7700_convert_drive_impedance_ohm(dev, val); + + sdhci_eic7700_dt_parse_clk_phases(dev, dwc_priv); + return 0; +} + static const struct sdhci_ops sdhci_dwcmshc_ops = { .set_clock = sdhci_set_clock, .set_bus_width = sdhci_set_bus_width, @@ -1169,6 +1912,18 @@ static const struct sdhci_ops sdhci_dwcmshc_sg2042_ops = { .platform_execute_tuning = th1520_execute_tuning, }; +static const struct sdhci_ops sdhci_dwcmshc_eic7700_ops = { + .set_clock = sdhci_eic7700_set_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, + .get_timeout_clock = sdhci_pltfm_clk_get_max_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_eic7700_reset, + .set_uhs_signaling = sdhci_eic7700_set_uhs_wrapper, + .set_power = sdhci_set_power_and_bus_voltage, + .irq = dwcmshc_cqe_irq_handler, + .platform_execute_tuning = sdhci_eic7700_executing_tuning, +}; + static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = { .pdata = { .ops = &sdhci_dwcmshc_ops, @@ -1238,6 +1993,17 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_sg2042_pdata = { .init = sg2042_init, }; +static const struct dwcmshc_pltfm_data sdhci_dwcmshc_eic7700_pdata = { + .pdata = { + .ops = &sdhci_dwcmshc_eic7700_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | + SDHCI_QUIRK_BROKEN_TIMEOUT_VAL, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | + SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, + }, + .init = eic7700_init, +}; + static const struct cqhci_host_ops dwcmshc_cqhci_ops = { .enable = dwcmshc_sdhci_cqe_enable, .disable = sdhci_cqe_disable, @@ -1338,6 +2104,10 @@ static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { .compatible = "sophgo,sg2042-dwcmshc", .data = &sdhci_dwcmshc_sg2042_pdata, }, + { + .compatible = "eswin,eic7700-dwcmshc", + .data = &sdhci_dwcmshc_eic7700_pdata, + }, {}, }; MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids); -- 2.25.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 2/2] mmc: sdhci-of-dwcmshc: Add support for Eswin EIC7700 2025-09-12 9:38 ` [PATCH v2 2/2] mmc: sdhci-of-dwcmshc: Add support for " hehuan1 @ 2025-09-18 17:36 ` Adrian Hunter 0 siblings, 0 replies; 9+ messages in thread From: Adrian Hunter @ 2025-09-18 17:36 UTC (permalink / raw) To: hehuan1, ulf.hansson, robh, krzk+dt, conor+dt, jszhang, p.zabel, linux-mmc, devicetree, linux-kernel Cc: ningyu, linmin, pinkesh.vaghela, xuxiang, luyulin, dongxuyang, zhangsenchuan, weishangjuan, lizhi2, caohang On 12/09/2025 12:38, hehuan1@eswincomputing.com wrote: > From: Huan He <hehuan1@eswincomputing.com> > > Add support for the mmc controller in the Eswin EIC7700 with the new > compatible "eswin,eic7700-dwcmshc". Implement custom sdhci_ops for > set_clock, reset, set_uhs_signaling, platform_execute_tuning. > > Signed-off-by: Huan He <hehuan1@eswincomputing.com> > --- > drivers/mmc/host/sdhci-of-dwcmshc.c | 770 ++++++++++++++++++++++++++++ > 1 file changed, 770 insertions(+) > > diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c > index ee6b1096f709..dd16c7a3bda7 100644 > --- a/drivers/mmc/host/sdhci-of-dwcmshc.c > +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c > @@ -11,6 +11,7 @@ > #include <linux/arm-smccc.h> > #include <linux/bitfield.h> > #include <linux/clk.h> > +#include <linux/clk-provider.h> > #include <linux/dma-mapping.h> > #include <linux/iopoll.h> > #include <linux/kernel.h> > @@ -19,8 +20,11 @@ > #include <linux/platform_device.h> > #include <linux/pm_domain.h> > #include <linux/pm_runtime.h> > +#include <linux/regmap.h> > #include <linux/reset.h> > #include <linux/sizes.h> > +#include <linux/mfd/syscon.h> > +#include <linux/units.h> > > #include "sdhci-pltfm.h" > #include "cqhci.h" > @@ -194,6 +198,10 @@ > #define PHY_DLLDL_CNFG_SLV_INPSEL_MASK GENMASK(6, 5) /* bits [6:5] */ > #define PHY_DLLDL_CNFG_SLV_INPSEL 0x3 /* clock source select for slave DL */ > > +#define PHY_DLL_OFFST_R (DWC_MSHC_PTR_PHY_R + 0x29) > +#define PHY_DLLBT_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x2c) > +#define PHY_DLL_STATUS_R (DWC_MSHC_PTR_PHY_R + 0x2e) Other PHY registers are introuced with a comment followed by field definitions and values > + > #define FLAG_IO_FIXED_1V8 BIT(0) > > #define BOUNDARY_OK(addr, len) \ > @@ -206,6 +214,48 @@ > /* SMC call for BlueField-3 eMMC RST_N */ > #define BLUEFIELD_SMC_SET_EMMC_RST_N 0x82000007 > > +/* Eswin specific Registers */ > +#define EIC7700_CARD_CLK_STABLE BIT(28) > +#define EIC7700_INT_BCLK_STABLE BIT(16) > +#define EIC7700_INT_ACLK_STABLE BIT(8) > +#define EIC7700_INT_TMCLK_STABLE BIT(0) > +#define EIC7700_INT_CLK_STABLE (EIC7700_CARD_CLK_STABLE | \ > + EIC7700_INT_ACLK_STABLE | \ > + EIC7700_INT_BCLK_STABLE | \ > + EIC7700_INT_TMCLK_STABLE) > +#define EIC7700_HOST_VAL_STABLE BIT(0) > +#define EIC7700_CORE_CLK_ENABLE BIT(16) > +#define EIC7700_CORE_CLK_FREQ_SHIFT 4 > +#define EIC7700_CORE_CLK_FREQ_MASK 0xfffu > +#define EIC7700_CORE_CLK_SEL_BIT BIT(0) > + > +/* strength definition */ > +#define PHYCTRL_DR_33OHM 0xee > +#define PHYCTRL_DR_40OHM 0xcc > +#define PHYCTRL_DR_50OHM 0x88 > +#define PHYCTRL_DR_66OHM 0x44 > +#define PHYCTRL_DR_100OHM 0x00 > + > +#define LATENCY_LT_BIT_OFFSET 19 > +#define LATENCY_LT_MASK 0x3 Prefer GENMASK() and then use FIELD_PREP() > +#define LATENCY_LT_3 0x2 > +#define VENDOR_AT_SATA_R 0x544 > + > +#define MAX_PHASE_CODE 0xff > +#define TUNING_RANGE_THRESHOLD 40 > + > +#define PHY_CLK_MAX_DELAY_MASK 0x7f > +#define PHY_PAD_SP_DRIVE_SHIF 16 Prefer GENMASK() and then use FIELD_PREP() > + > +#define EIC7700_CORE_CLK_SRC_208MHZ (208 * HZ_PER_MHZ) > +#define EIC7700_CORE_CLK_SRC_200MHZ (200 * HZ_PER_MHZ) > +#define MAX_CORE_CLK_DIV 0xfff > +#define DLL_LOCK_STS BIT(0) > +#define DLL_ERROR_STS BIT(1) > +#define PHY_DELAY_CODE_MAX 0x7f > +#define PHY_DELAY_CODE_EMMC 0x17 > +#define PHY_DELAY_CODE_SD 0x55 > + > enum dwcmshc_rk_type { > DWCMSHC_RK3568, > DWCMSHC_RK3588, > @@ -217,6 +267,18 @@ struct rk35xx_priv { > u8 txclk_tapnum; > }; > > +struct eic7700_priv { > + struct sdhci_host *host; > + struct clk_hw sdcardclk_hw; > + struct clk *sdcardclk; > + int clk_phase_out[MMC_TIMING_MMC_HS400 + 1]; > + void (*set_clk_delays)(struct sdhci_host *host); > + struct reset_control *reset; > + struct regmap *crg_regmap; > + unsigned int crg_core_clk; > + unsigned int drive_impedance; > +}; > + > #define DWCMSHC_MAX_OTHER_CLKS 3 > > struct dwcmshc_priv { > @@ -238,6 +300,8 @@ struct dwcmshc_pltfm_data { > void (*postinit)(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv); > }; > > +static void dwcmshc_disable_card_clk(struct sdhci_host *host); Move the function rather than create a forward declaration > + > static int dwcmshc_get_enable_other_clks(struct device *dev, > struct dwcmshc_priv *priv, > int num_clks, > @@ -1095,6 +1159,685 @@ static int sg2042_init(struct device *dev, struct sdhci_host *host, > ARRAY_SIZE(clk_ids), clk_ids); > } > > +static void sdhci_eic7700_enable_card_clk(struct sdhci_host *host) This is being paired with dwcmshc_disable_card_clk(), so maybe dwcmshc_enable_card_clk() is a more readable name > +{ > + u16 clk; > + int ret; > + > + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); > + clk |= SDHCI_CLOCK_INT_EN; > + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); > + > + /* Wait max 150 ms */ > + ret = read_poll_timeout(sdhci_readw, clk, clk & SDHCI_CLOCK_INT_STABLE, > + 10, 150000, false, host, SDHCI_CLOCK_CONTROL); > + if (ret) { > + pr_err("%s: Internal clock never stabilised.\n", > + mmc_hostname(host->mmc)); > + return; > + } > + > + clk |= SDHCI_CLOCK_CARD_EN; > + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); > + usleep_range(1000, 2000); > +} > + > +static void eic7700_mshc_coreclk_disable(struct sdhci_host *host) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); > + struct eic7700_priv *priv = dwc_priv->priv; > + u32 val; > + > + regmap_read(priv->crg_regmap, priv->crg_core_clk, &val); > + val &= ~EIC7700_CORE_CLK_ENABLE; > + regmap_write(priv->crg_regmap, priv->crg_core_clk, val); > +} > + > +static void eic7700_mshc_coreclk_config(struct sdhci_host *host, > + u16 divisor, unsigned int flag_sel) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); > + struct eic7700_priv *priv = dwc_priv->priv; > + u32 val; > + > + regmap_read(priv->crg_regmap, priv->crg_core_clk, &val); > + val &= ~EIC7700_CORE_CLK_ENABLE; > + regmap_write(priv->crg_regmap, priv->crg_core_clk, val); > + usleep_range(10, 20); > + > + val &= ~(EIC7700_CORE_CLK_FREQ_MASK << EIC7700_CORE_CLK_FREQ_SHIFT); > + val |= (divisor & EIC7700_CORE_CLK_FREQ_MASK) << > + EIC7700_CORE_CLK_FREQ_SHIFT; > + val &= ~(EIC7700_CORE_CLK_SEL_BIT); > + val |= flag_sel; > + regmap_write(priv->crg_regmap, priv->crg_core_clk, val); > + > + usleep_range(50, 60); > + val |= EIC7700_CORE_CLK_ENABLE; > + regmap_write(priv->crg_regmap, priv->crg_core_clk, val); > + usleep_range(1000, 1100); > +} So the host controller is not configuring the card clock. Shouldn't the eic7700 clock driver provide this clock? > + > +static void sdhci_eic7700_set_core_clock(struct sdhci_host *host, > + unsigned int clock) > +{ > + unsigned int flag_sel, max_clk; > + unsigned int div, divide; > + > + host->mmc->actual_clock = clock; > + > + if (clock == 0) { > + eic7700_mshc_coreclk_disable(host); > + return; > + } > + > + if (EIC7700_CORE_CLK_SRC_208MHZ % clock == 0) { > + flag_sel = 1; > + max_clk = EIC7700_CORE_CLK_SRC_208MHZ; > + } else { > + flag_sel = 0; > + max_clk = EIC7700_CORE_CLK_SRC_200MHZ; > + } > + > + for (div = 1; div <= MAX_CORE_CLK_DIV; div++) { > + if ((max_clk / div) <= clock) > + break; > + } > + div--; > + > + if (div == 0 || div == 1) > + divide = 2; > + else > + divide = (div + 1) * 2; > + > + dwcmshc_disable_card_clk(host); > + eic7700_mshc_coreclk_config(host, divide, flag_sel); > + sdhci_eic7700_enable_card_clk(host); > + usleep_range(2000, 3000); > +} > + > +static void sdhci_eic7700_set_clock(struct sdhci_host *host, unsigned int clock) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); > + struct eic7700_priv *priv = dwc_priv->priv; > + > + /* Set the Input and Output Clock Phase Delays */ > + if (priv->set_clk_delays) > + priv->set_clk_delays(host); > + > + sdhci_eic7700_set_core_clock(host, clock); > +} > + > +static void sdhci_eic7700_config_phy_delay(struct sdhci_host *host, int delay) > +{ > + delay &= PHY_CLK_MAX_DELAY_MASK; > + > + /* phy clk delay line config */ > + sdhci_writeb(host, PHY_SDCLKDL_CNFG_UPDATE, PHY_SDCLKDL_CNFG_R); > + sdhci_writeb(host, delay, PHY_SDCLKDL_DC_R); > + sdhci_writeb(host, 0x0, PHY_SDCLKDL_CNFG_R); > +} > + > +static void sdhci_eic7700_config_phy(struct sdhci_host *host) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); > + u32 emmc_caps = MMC_CAP2_NO_SD | MMC_CAP2_NO_SDIO; > + struct eic7700_priv *priv = dwc_priv->priv; > + unsigned int val, drv; > + > + drv = priv->drive_impedance << PHY_PAD_SP_DRIVE_SHIF; > + > + if ((host->mmc->caps2 & emmc_caps) == emmc_caps) { > + val = sdhci_readw(host, dwc_priv->vendor_specific_area1 + > + DWCMSHC_EMMC_CONTROL); > + val |= DWCMSHC_CARD_IS_EMMC; > + sdhci_writew(host, val, dwc_priv->vendor_specific_area1 + > + DWCMSHC_EMMC_CONTROL); > + } > + > + dwcmshc_disable_card_clk(host); > + > + /* reset phy, config phy's pad */ > + sdhci_writel(host, drv | (~PHY_CNFG_RSTN_DEASSERT), PHY_CNFG_R); > + > + /* configure phy pads */ > + val = FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, > + PHY_PAD_TXSLEW_CTRL_N_SG2042); Here are elsewhere, avoid wrapping lines if they fit in 100 columns. > + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, > + PHY_PAD_TXSLEW_CTRL_N_SG2042); > + val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, > + PHY_PAD_WEAKPULL_PULLUP); > + val |= PHY_PAD_RXSEL_1V8; > + sdhci_writew(host, val, PHY_CMDPAD_CNFG_R); > + sdhci_writew(host, val, PHY_DATAPAD_CNFG_R); > + sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R); > + > + /* Clock PAD Setting */ > + val = FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, > + PHY_PAD_TXSLEW_CTRL_N_SG2042); > + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, > + PHY_PAD_TXSLEW_CTRL_N_SG2042); > + sdhci_writew(host, val, PHY_CLKPAD_CNFG_R); > + > + /* PHY strobe PAD setting (EMMC only) */ > + if ((host->mmc->caps2 & emmc_caps) == emmc_caps) { > + val = FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, > + PHY_PAD_TXSLEW_CTRL_N_SG2042); > + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, > + PHY_PAD_TXSLEW_CTRL_N_SG2042); > + val |= PHY_PAD_RXSEL_1V8; > + sdhci_writew(host, val, PHY_STBPAD_CNFG_R); > + } > + usleep_range(2000, 3000); > + sdhci_writel(host, drv | PHY_CNFG_RSTN_DEASSERT, PHY_CNFG_R); > + sdhci_eic7700_config_phy_delay(host, dwc_priv->delay_line); > + sdhci_eic7700_enable_card_clk(host); > +} > + > +static void sdhci_eic7700_reset(struct sdhci_host *host, u8 mask) > +{ > + sdhci_reset(host, mask); > + > + /* after reset all, the phy's config will be clear */ > + if (mask == SDHCI_RESET_ALL) > + sdhci_eic7700_config_phy(host); > +} > + > +static unsigned long > +sdhci_eic7700_sdcardclk_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + struct eic7700_priv *priv = > + container_of(hw, struct eic7700_priv, sdcardclk_hw); > + > + return priv->host->mmc->actual_clock; > +} > + > +static const struct clk_ops eic7700_sdcardclk_ops = { > + .recalc_rate = sdhci_eic7700_sdcardclk_recalc_rate, > +}; An sdcardclk like this can be created to communicate the frequency to a PHY driver, but the PHY configuration seems to be in this driver. Is sdcardclk really needed? Also it would seem to be in conflict with having the clock driver provide the clock. > + > +static int sdhci_eic7700_register_sdcardclk(struct eic7700_priv *priv, > + struct clk *clk, struct device *dev) > +{ > + struct device_node *np = dev->of_node; > + struct clk_init_data sdcardclk_init; > + const char *parent_clk_name; > + int ret; > + > + ret = of_property_read_string_index(np, "clock-output-names", 0, > + &sdcardclk_init.name); > + if (ret) { > + dev_err(dev, "DT has #clock-cells but no clock-output-names\n"); > + return ret; > + } > + > + parent_clk_name = __clk_get_name(clk); > + sdcardclk_init.parent_names = &parent_clk_name; > + sdcardclk_init.num_parents = 1; > + sdcardclk_init.flags = CLK_GET_RATE_NOCACHE; > + sdcardclk_init.ops = &eic7700_sdcardclk_ops; > + > + priv->sdcardclk_hw.init = &sdcardclk_init; > + priv->sdcardclk = devm_clk_register(dev, &priv->sdcardclk_hw); > + if (IS_ERR(priv->sdcardclk)) > + return PTR_ERR(priv->sdcardclk); > + priv->sdcardclk_hw.init = NULL; > + > + ret = of_clk_add_provider(np, of_clk_src_simple_get, > + priv->sdcardclk); > + if (ret) > + dev_err(dev, "Failed to add sdcard clock provider\n"); > + > + return ret; > +} > + > +static int sdhci_eic7700_register_sdclk(struct dwcmshc_priv *dwc_priv, > + struct clk *clk, struct device *dev) > +{ > + struct device_node *np = dev->of_node; > + int ret; > + > + /* Providing a clock to the PHY is optional; no error if missing */ > + if (!of_property_present(np, "#clock-cells")) > + return 0; > + > + ret = sdhci_eic7700_register_sdcardclk(dwc_priv->priv, clk, dev); > + > + return ret; > +} > + > +static int sdhci_eic7700_reset_init(struct device *dev, > + struct eic7700_priv *priv) > +{ > + int ret; > + > + priv->reset = devm_reset_control_array_get_optional_exclusive(dev); > + if (IS_ERR(priv->reset)) { > + ret = PTR_ERR(priv->reset); > + dev_err(dev, "failed to get reset control %d\n", ret); > + return ret; > + } > + > + ret = reset_control_assert(priv->reset); > + if (ret) { > + dev_err(dev, "Failed to assert reset signals: %d\n", ret); > + return ret; > + } > + usleep_range(2000, 2100); > + ret = reset_control_deassert(priv->reset); > + if (ret) { > + dev_err(dev, "Failed to deassert reset signals: %d\n", ret); > + return ret; > + } > + > + return ret; > +} > + > +static void sdhci_eic7700_set_clk_delays(struct sdhci_host *host) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); > + struct eic7700_priv *priv = dwc_priv->priv; > + > + clk_set_phase(priv->sdcardclk, > + priv->clk_phase_out[host->timing]); > +} > + > +static void sdhci_eic7700_dt_read_clk_phase(struct device *dev, > + struct eic7700_priv *priv, > + unsigned int timing, > + const char *prop) > +{ > + struct device_node *np = dev->of_node; > + int clk_phase[2] = {0}; > + > + /* > + * Read Tap Delay values from DT, if the DT does not contain the Tap > + * Values then use the pre-defined values. > + */ > + if (of_property_read_variable_u32_array(np, prop, &clk_phase[0], 2, of_property_read_variable_u32_array() returns a positive value on success, negative on error, so this error condition looks wrong. > + 0)) { > + dev_dbg(dev, "Using predefined clock phase for %s = %d\n", > + prop, priv->clk_phase_out[timing]); > + return; > + } > + > + /* Only use the output clock delays in order */ > + priv->clk_phase_out[timing] = clk_phase[1]; > +} > + > +static void sdhci_eic7700_dt_parse_clk_phases(struct device *dev, > + struct dwcmshc_priv *dwc_priv) > +{ > + struct eic7700_priv *priv = dwc_priv->priv; > + > + priv->set_clk_delays = sdhci_eic7700_set_clk_delays; > + > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_LEGACY, > + "clk-phase-legacy"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_MMC_HS, > + "clk-phase-mmc-hs"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_SD_HS, > + "clk-phase-sd-hs"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_SDR12, > + "clk-phase-uhs-sdr12"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_SDR25, > + "clk-phase-uhs-sdr25"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_SDR50, > + "clk-phase-uhs-sdr50"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_SDR104, > + "clk-phase-uhs-sdr104"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_UHS_DDR50, > + "clk-phase-uhs-ddr50"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_MMC_DDR52, > + "clk-phase-mmc-ddr52"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_MMC_HS200, > + "clk-phase-mmc-hs200"); > + sdhci_eic7700_dt_read_clk_phase(dev, priv, MMC_TIMING_MMC_HS400, > + "clk-phase-mmc-hs400"); > +} > + > +static unsigned int eic7700_convert_drive_impedance_ohm(struct device *dev, > + unsigned int dr_ohm) > +{ > + switch (dr_ohm) { > + case 100: > + return PHYCTRL_DR_100OHM; > + case 66: > + return PHYCTRL_DR_66OHM; > + case 50: > + return PHYCTRL_DR_50OHM; > + case 40: > + return PHYCTRL_DR_40OHM; > + case 33: > + return PHYCTRL_DR_33OHM; > + } > + > + dev_warn(dev, "Invalid value %u for drive-impedance-ohm.\n", dr_ohm); > + return PHYCTRL_DR_50OHM; > +} > + > +static int sdhci_eic7700_delay_tuning(struct sdhci_host *host, u32 opcode) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); > + int cmd_error, delay, ret, i; > + int delay_min = -1; > + int delay_max = -1; > + > + for (i = 0; i <= PHY_DELAY_CODE_MAX; i++) { > + dwcmshc_disable_card_clk(host); > + sdhci_eic7700_config_phy_delay(host, i); > + sdhci_eic7700_enable_card_clk(host); > + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); > + if (ret) { > + host->ops->reset(host, > + SDHCI_RESET_CMD | SDHCI_RESET_DATA); > + usleep_range(200, 210); > + if (delay_min != -1 && delay_max != -1) > + break; > + } else { > + if (delay_min == -1) { > + delay_min = i; > + continue; > + } else { > + delay_max = i; > + continue; > + } > + } > + } > + if (delay_min == -1 && delay_max == -1) { > + pr_err("%s: delay code tuning failed!\n", > + mmc_hostname(host->mmc)); > + dwcmshc_disable_card_clk(host); > + sdhci_eic7700_config_phy_delay(host, dwc_priv->delay_line); > + sdhci_eic7700_enable_card_clk(host); > + > + return ret; > + } > + > + delay = (delay_min + delay_max) / 2; > + dwcmshc_disable_card_clk(host); > + sdhci_eic7700_config_phy_delay(host, delay); > + sdhci_eic7700_enable_card_clk(host); > + > + return 0; > +} > + > +static int sdhci_eic7700_phase_code_tuning(struct sdhci_host *host, u32 opcode) > +{ > + u32 sd_caps = MMC_CAP2_NO_MMC | MMC_CAP2_NO_SDIO; > + int cmd_error, ret, i; > + bool is_sdio = false; > + int phase_code = -1; > + int code_range = -1; > + int code_min = -1; > + int code_max = -1; > + > + if ((host->mmc->caps2 & sd_caps) == sd_caps) > + is_sdio = true; SDIO is excluded (MMC_CAP2_NO_SDIO), so 'is_sdio' is not an ideal name. Maybe just 'is_sd' > + > + for (i = 0; i <= MAX_PHASE_CODE; i++) { > + dwcmshc_disable_card_clk(host); > + sdhci_writew(host, i, VENDOR_AT_SATA_R); > + sdhci_eic7700_enable_card_clk(host); > + > + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); > + host->ops->reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); > + > + if (ret) { > + /* SDIO specific range tracking */ > + if (is_sdio && code_min != -1 && code_max != -1) { > + if (code_max - code_min > code_range) { > + code_range = code_max - code_min; > + phase_code = (code_min + code_max) / 2; > + if (code_range > TUNING_RANGE_THRESHOLD) > + break; > + } > + code_min = -1; > + code_max = -1; > + } > + /* EMMC breaks after first valid range */ > + if (!is_sdio && code_min != -1 && code_max != -1) > + break; > + } else { > + /* Track valid phase code range */ > + if (code_min == -1) { > + code_min = i; > + if (!is_sdio) > + continue; > + } > + code_max = i; > + if (is_sdio && i == MAX_PHASE_CODE) { > + if (code_max - code_min > code_range) { > + code_range = code_max - code_min; > + phase_code = (code_min + code_max) / 2; > + } > + } > + } > + } > + > + /* Handle tuning failure case */ > + if ((is_sdio && phase_code == -1) || > + (!is_sdio && code_min == -1 && code_max == -1)) { > + pr_err("%s: phase code tuning failed!\n", > + mmc_hostname(host->mmc)); > + dwcmshc_disable_card_clk(host); > + sdhci_writew(host, 0, VENDOR_AT_SATA_R); What is VENDOR_AT_SATA_R? > + sdhci_eic7700_enable_card_clk(host); > + return -EIO; > + } > + if (!is_sdio) > + phase_code = (code_min + code_max) / 2; > + > + dwcmshc_disable_card_clk(host); > + sdhci_writew(host, phase_code, VENDOR_AT_SATA_R); > + sdhci_eic7700_enable_card_clk(host); > + > + /* SDIO specific final verification */ > + if (is_sdio) { > + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); > + host->ops->reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); > + if (ret) { > + pr_err("%s: Final phase code 0x%x verification failed!\n", > + mmc_hostname(host->mmc), phase_code); > + return ret; > + } > + } > + > + return 0; > +} > + > +static int sdhci_eic7700_executing_tuning(struct sdhci_host *host, u32 opcode) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); > + u32 emmc_caps = MMC_CAP2_NO_SD | MMC_CAP2_NO_SDIO; > + u16 ctrl; > + u32 val; > + int ret; > + > + dwcmshc_disable_card_clk(host); > + > + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); > + ctrl &= ~SDHCI_CTRL_TUNED_CLK; > + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); > + > + val = sdhci_readl(host, priv->vendor_specific_area1 + > + DWCMSHC_EMMC_ATCTRL); > + val |= AT_CTRL_SW_TUNE_EN; > + sdhci_writew(host, val, priv->vendor_specific_area1 + > + DWCMSHC_EMMC_ATCTRL); > + > + sdhci_writew(host, 0, VENDOR_AT_SATA_R); > + > + sdhci_eic7700_enable_card_clk(host); > + > + sdhci_writew(host, 0x0, SDHCI_CMD_DATA); > + > + if ((host->mmc->caps2 & emmc_caps) == emmc_caps) { > + ret = sdhci_eic7700_delay_tuning(host, opcode); > + if (ret) > + return ret; > + } > + > + ret = sdhci_eic7700_phase_code_tuning(host, opcode); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static void sdhci_eic7700_set_uhs_signaling(struct sdhci_host *host, > + unsigned int timing) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); > + u8 status; > + u32 val; > + int ret; > + > + dwcmshc_set_uhs_signaling(host, timing); > + > + /* here need make dll locked when in hs400 at 200MHz */ > + if (timing == MMC_TIMING_MMC_HS400 && host->clock == 200000000) { > + dwcmshc_disable_card_clk(host); > + > + val = sdhci_readl(host, priv->vendor_specific_area1 + > + DWCMSHC_EMMC_ATCTRL); > + val &= ~(LATENCY_LT_MASK << LATENCY_LT_BIT_OFFSET); > + val |= (LATENCY_LT_3 << LATENCY_LT_MASK); Is the use of LATENCY_LT_MASK correct here. Also, prefer GENMASK() and FIELD_PREP() > + sdhci_writew(host, val, priv->vendor_specific_area1 + > + DWCMSHC_EMMC_ATCTRL); > + > + sdhci_writeb(host, 0x23, PHY_DLL_CNFG1_R); > + sdhci_writeb(host, 0x02, PHY_DLL_CNFG2_R); > + sdhci_writeb(host, 0x60, PHY_DLLDL_CNFG_R); > + sdhci_writeb(host, 0x00, PHY_DLL_OFFST_R); > + sdhci_writew(host, 0xffff, PHY_DLLBT_CNFG_R); It would be nicer to have defines for the fields and values instead of magic numbers. > + > + sdhci_eic7700_enable_card_clk(host); > + sdhci_writeb(host, PHY_DLL_CTRL_ENABLE, PHY_DLL_CTRL_R); > + usleep_range(100, 110); > + > + ret = read_poll_timeout(sdhci_readb, status, > + status & DLL_LOCK_STS, 100, 1000000, > + false, host, PHY_DLL_STATUS_R); > + if (ret) { > + pr_err("%s: DLL lock timeout! status: 0x%x\n", > + mmc_hostname(host->mmc), status); > + return; > + } > + > + status = sdhci_readb(host, PHY_DLL_STATUS_R); > + if (status & DLL_ERROR_STS) { > + pr_err("%s: DLL lock failed!err_status:0x%x\n", > + mmc_hostname(host->mmc), status); > + } > + } > +} > + > +static void sdhci_eic7700_set_uhs_wrapper(struct sdhci_host *host, > + unsigned int timing) > +{ > + u32 sd_caps = MMC_CAP2_NO_MMC | MMC_CAP2_NO_SDIO; > + > + if ((host->mmc->caps2 & sd_caps) == sd_caps) > + sdhci_set_uhs_signaling(host, timing); > + else > + sdhci_eic7700_set_uhs_signaling(host, timing); > +} > + > +static int eic7700_init(struct device *dev, struct sdhci_host *host, > + struct dwcmshc_priv *dwc_priv) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + u32 emmc_caps = MMC_CAP2_NO_SD | MMC_CAP2_NO_SDIO; > + unsigned int val, hsp_int_status, hsp_pwr_ctrl; > + struct of_phandle_args args; > + struct eic7700_priv *priv; > + struct regmap *hsp_regmap; > + int ret; > + > + priv = devm_kzalloc(dev, sizeof(struct eic7700_priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->host = host; > + dwc_priv->priv = priv; > + > + ret = sdhci_eic7700_register_sdclk(dwc_priv, pltfm_host->clk, dev); > + if (ret) > + return ret; > + > + ret = sdhci_eic7700_reset_init(dev, dwc_priv->priv); > + if (ret) { > + dev_err(dev, "failed to reset\n"); > + return ret; > + } > + > + ret = of_parse_phandle_with_fixed_args(dev->of_node, > + "eswin,syscrg-csr", 1, 0, &args); > + if (ret) { > + dev_err(dev, "Fail to parse 'eswin,syscrg-csr' phandle (%d)\n", > + ret); > + return ret; > + } > + priv->crg_regmap = syscon_node_to_regmap(args.np); > + if (IS_ERR(priv->crg_regmap)) { > + dev_err(dev, "Failed to get regmap for 'eswin,syscrg-csr'\n"); > + of_node_put(args.np); > + return ret; > + } > + > + priv->crg_core_clk = args.args[0]; > + of_node_put(args.np); > + > + ret = of_parse_phandle_with_fixed_args(dev->of_node, > + "eswin,hsp-sp-csr", 2, 0, &args); > + if (ret) { > + dev_err(dev, "Fail to parse 'eswin,hsp-sp-csr' phandle (%d)\n", > + ret); > + return ret; > + } > + > + hsp_regmap = syscon_node_to_regmap(args.np); > + if (IS_ERR(hsp_regmap)) { > + dev_err(dev, "Failed to get regmap for 'eswin,hsp-sp-csr'\n"); > + of_node_put(args.np); > + return ret; > + } > + hsp_int_status = args.args[0]; > + hsp_pwr_ctrl = args.args[1]; > + of_node_put(args.np); Could use some comments here about what the following regmap_write()s actually do and why. > + regmap_write(hsp_regmap, hsp_int_status, > + EIC7700_INT_CLK_STABLE); > + regmap_write(hsp_regmap, hsp_pwr_ctrl, > + EIC7700_HOST_VAL_STABLE); > + > + if ((host->mmc->caps2 & emmc_caps) == emmc_caps) > + dwc_priv->delay_line = PHY_DELAY_CODE_EMMC; > + else > + dwc_priv->delay_line = PHY_DELAY_CODE_SD; > + > + if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val)) > + priv->drive_impedance = > + eic7700_convert_drive_impedance_ohm(dev, val); > + > + sdhci_eic7700_dt_parse_clk_phases(dev, dwc_priv); > + return 0; > +} > + > static const struct sdhci_ops sdhci_dwcmshc_ops = { > .set_clock = sdhci_set_clock, > .set_bus_width = sdhci_set_bus_width, > @@ -1169,6 +1912,18 @@ static const struct sdhci_ops sdhci_dwcmshc_sg2042_ops = { > .platform_execute_tuning = th1520_execute_tuning, > }; > > +static const struct sdhci_ops sdhci_dwcmshc_eic7700_ops = { > + .set_clock = sdhci_eic7700_set_clock, > + .get_max_clock = sdhci_pltfm_clk_get_max_clock, > + .get_timeout_clock = sdhci_pltfm_clk_get_max_clock, > + .set_bus_width = sdhci_set_bus_width, > + .reset = sdhci_eic7700_reset, > + .set_uhs_signaling = sdhci_eic7700_set_uhs_wrapper, > + .set_power = sdhci_set_power_and_bus_voltage, > + .irq = dwcmshc_cqe_irq_handler, > + .platform_execute_tuning = sdhci_eic7700_executing_tuning, > +}; > + > static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = { > .pdata = { > .ops = &sdhci_dwcmshc_ops, > @@ -1238,6 +1993,17 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_sg2042_pdata = { > .init = sg2042_init, > }; > > +static const struct dwcmshc_pltfm_data sdhci_dwcmshc_eic7700_pdata = { > + .pdata = { > + .ops = &sdhci_dwcmshc_eic7700_ops, > + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | > + SDHCI_QUIRK_BROKEN_TIMEOUT_VAL, > + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | > + SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, > + }, > + .init = eic7700_init, > +}; > + > static const struct cqhci_host_ops dwcmshc_cqhci_ops = { > .enable = dwcmshc_sdhci_cqe_enable, > .disable = sdhci_cqe_disable, > @@ -1338,6 +2104,10 @@ static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { > .compatible = "sophgo,sg2042-dwcmshc", > .data = &sdhci_dwcmshc_sg2042_pdata, > }, > + { > + .compatible = "eswin,eic7700-dwcmshc", > + .data = &sdhci_dwcmshc_eic7700_pdata, > + }, > {}, > }; > MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids); ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-09-25 19:32 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-09-12 9:34 [PATCH v2 0/2] Add support for Eswin EIC7700 SD/eMMC controller hehuan1 2025-09-12 9:37 ` [PATCH v2 1/2] dt-bindings: mmc: sdhci-of-dwcmshc: Add Eswin EIC7700 hehuan1 2025-09-12 19:10 ` Conor Dooley 2025-09-23 5:45 ` 何欢 2025-09-24 19:43 ` Conor Dooley 2025-09-25 9:37 ` 何欢 2025-09-25 19:32 ` Conor Dooley 2025-09-12 9:38 ` [PATCH v2 2/2] mmc: sdhci-of-dwcmshc: Add support for " hehuan1 2025-09-18 17:36 ` Adrian Hunter
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).