mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-04 18:09:57 +00:00
MMC core:
- Introduce a host helper function to share re-tuning progress MMC host: - sdhci: Add support for v4 host mode - sdhci-of-arasan: Add Support for AM654 MMC and PHY - sdhci-sprd: Add support for Spreadtrum's host controller - sdhci-tegra: Add support for HS400 enhanced strobe - sdhci-tegra: Enable UHS/HS200 modes for Tegra186/210 - sdhci-tegra: Add support for HS400 delay line calibration - sdhci-tegra: Add support for pad calibration - sdhci-of-dwcmshc: Address 128MB DMA boundary limitation - sdhci-of-esdhc: Add support for tuning erratum A008171 - sdhci-iproc: Add ACPI support - mediatek: Add support for MT8183 - mediatek: Improve the support for tuning - mediatek: Add bus clock control for MT2712 - jz4740: Add support for the JZ4725B - mmci: Add support for the stm32 sdmmc variant - mmci: Add support for an optional reset control - mmci: Add some new variant specific properties/callbacks - mmci: Re-structure DMA code to prepare for new variants - renesas_sdhi: Add support for r8a77470, r8a7744 and r8a774a1 - renesas_sdhi_internal_dmac: Whitelist r8a77970 and r8a774a1 - tmio/uniphier-sd: Add new UniPhier SD/eMMC controller driver - tmio/renesas_sdhi: Deal properly with SCC detection during re-tune - tmio/renesas_sdhi: Refactor/consolidate clock management - omap_hsmmc: Drop cover detection and some unused platform data - dw_mmc-exynos: Enable tuning for more speed modes - sunxi: Clarify the new timing mode and enable it for the A64 controller - various: Convert to slot GPIO descriptors -----BEGIN PGP SIGNATURE----- iQJLBAABCgA1FiEEugLDXPmKSktSkQsV/iaEJXNYjCkFAlvNvV0XHHVsZi5oYW5z c29uQGxpbmFyby5vcmcACgkQ/iaEJXNYjCmstxAAzI10FKr/RiEd4l1w3AFcjPZ4 iGrsepaKg38LKeWwVXdu0FGfE+phcR7IrR6UqEzvPMFoajRiZa6K6q52r+gwDimA wNpTfci3yaRSJjg99DmQ5Eu1cLNbApQYzczkq7+hDna1PABm3ddT7HSlcuweALkV 73Ozt5PeBtYDofTgXZos2pPJc3Uqfwe6ZWdL95Q/I45SragQtnBce6P8O/oOP3RL 2Dzqy3jiXTuOCruV94Wwse4MBwec92pE4l1FKFQ505Hi8D0bs31CMLggXb49VDxG kl4zPqppbYdGqBpiCoKOMis1rYmSlgrskYzCGcq/TVzzQWNfj3sXtUAXDm31oy7c syZalIVejUpPiIfL01be92nA2dBHRuNaraCj6Mrx9NVkaKnKYhUax26gbN0PfZXt 6/OgIK5kcJ2Imf5j9hNjPChQ4uudjzaFi9K+1WWpZ+XC/b3JxdGhKKxibWwzz4+V kOk4RBp+aWBnUUTBPYo3syhicU7dUfcfTSCHz7+XQnWphZvog3W05onCdsi4O1GA s24w3rXZC3OOBsKGt5pE1XyV+oDfv/4BpxNiIe/utnW3bS+0OagpLgV8FBU1pKNP /EMEvnHV2G1rEF1C8y6ZJgGTFan8pCR/k5MbadeRChmwK/6B/AhW9KIKamHCJoE4 5rh0gyWdmJHMLj62ssw= =/4yi -----END PGP SIGNATURE----- Merge tag 'mmc-v4.20' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc Pull MMC updates from Ulf Hansson: "MMC core: - Introduce a host helper function to share re-tuning progress MMC host: - sdhci: Add support for v4 host mode - sdhci-of-arasan: Add Support for AM654 MMC and PHY - sdhci-sprd: Add support for Spreadtrum's host controller - sdhci-tegra: Add support for HS400 enhanced strobe - sdhci-tegra: Enable UHS/HS200 modes for Tegra186/210 - sdhci-tegra: Add support for HS400 delay line calibration - sdhci-tegra: Add support for pad calibration - sdhci-of-dwcmshc: Address 128MB DMA boundary limitation - sdhci-of-esdhc: Add support for tuning erratum A008171 - sdhci-iproc: Add ACPI support - mediatek: Add support for MT8183 - mediatek: Improve the support for tuning - mediatek: Add bus clock control for MT2712 - jz4740: Add support for the JZ4725B - mmci: Add support for the stm32 sdmmc variant - mmci: Add support for an optional reset control - mmci: Add some new variant specific properties/callbacks - mmci: Re-structure DMA code to prepare for new variants - renesas_sdhi: Add support for r8a77470, r8a7744 and r8a774a1 - renesas_sdhi_internal_dmac: Whitelist r8a77970 and r8a774a1 - tmio/uniphier-sd: Add new UniPhier SD/eMMC controller driver - tmio/renesas_sdhi: Deal properly with SCC detection during re-tune - tmio/renesas_sdhi: Refactor/consolidate clock management - omap_hsmmc: Drop cover detection and some unused platform data - dw_mmc-exynos: Enable tuning for more speed modes - sunxi: Clarify the new timing mode and enable it for the A64 controller - various: Convert to slot GPIO descriptors" * tag 'mmc-v4.20' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc: (129 commits) mmc: mediatek: drop too much code of tuning method mmc: mediatek: add MT8183 MMC driver support mmc: mediatek: tune CMD/DATA together mmc: mediatek: fix cannot receive new request when msdc_cmd_is_ready fail mmc: mediatek: fill the actual clock for mmc debugfs mmc: dt-bindings: add support for MT8183 SoC mmc: uniphier-sd: avoid using broken DMA RX channel mmc: uniphier-sd: fix DMA disabling mmc: tmio: simplify the DMA mode test mmc: tmio: remove TMIO_MMC_HAVE_HIGH_REG flag mmc: tmio: move MFD variant reset to a platform hook mmc: renesas_sdhi: Add r8a77470 SDHI1 support dt-bindings: mmc: renesas_sdhi: Add r8a77470 support mmc: mmci: add stm32 sdmmc variant dt-bindings: mmci: add stm32 sdmmc variant mmc: mmci: add stm32 sdmmc registers mmc: mmci: add clock divider for stm32 sdmmc mmc: mmci: add optional reset property dt-bindings: mmci: add optional reset property mmc: mmci: add variant property to not read datacnt ...
This commit is contained in:
commit
1650ac5306
62 changed files with 4067 additions and 968 deletions
|
@ -15,6 +15,7 @@ Required Properties:
|
|||
- "arasan,sdhci-5.1": generic Arasan SDHCI 5.1 PHY
|
||||
- "rockchip,rk3399-sdhci-5.1", "arasan,sdhci-5.1": rk3399 eMMC PHY
|
||||
For this device it is strongly suggested to include arasan,soc-ctl-syscon.
|
||||
- "ti,am654-sdhci-5.1", "arasan,sdhci-5.1": TI AM654 MMC PHY
|
||||
- reg: From mmc bindings: Register location and length.
|
||||
- clocks: From clock bindings: Handles to clock inputs.
|
||||
- clock-names: From clock bindings: Tuple including "clk_xin" and "clk_ahb"
|
||||
|
|
|
@ -7,6 +7,7 @@ described in mmc.txt.
|
|||
Required properties:
|
||||
- compatible: Should be one of the following:
|
||||
- "ingenic,jz4740-mmc" for the JZ4740
|
||||
- "ingenic,jz4725b-mmc" for the JZ4725B
|
||||
- "ingenic,jz4780-mmc" for the JZ4780
|
||||
- reg: Should contain the MMC controller registers location and length.
|
||||
- interrupts: Should contain the interrupt specifier of the MMC controller.
|
||||
|
|
|
@ -15,8 +15,11 @@ Required properties:
|
|||
Optional properties:
|
||||
- arm,primecell-periphid : contains the PrimeCell Peripheral ID, it overrides
|
||||
the ID provided by the HW
|
||||
- resets : phandle to internal reset line.
|
||||
Should be defined for sdmmc variant.
|
||||
- vqmmc-supply : phandle to the regulator device tree node, mentioned
|
||||
as the VCCQ/VDD_IO supply in the eMMC/SD specs.
|
||||
specific for ux500 variant:
|
||||
- st,sig-dir-dat0 : bus signal direction pin used for DAT[0].
|
||||
- st,sig-dir-dat2 : bus signal direction pin used for DAT[2].
|
||||
- st,sig-dir-dat31 : bus signal direction pin used for DAT[3] and DAT[1].
|
||||
|
@ -24,6 +27,14 @@ Optional properties:
|
|||
- st,sig-dir-cmd : cmd signal direction pin used for CMD.
|
||||
- st,sig-pin-fbclk : feedback clock signal pin used.
|
||||
|
||||
specific for sdmmc variant:
|
||||
- st,sig-dir : signal direction polarity used for cmd, dat0 dat123.
|
||||
- st,neg-edge : data & command phase relation, generated on
|
||||
sd clock falling edge.
|
||||
- st,use-ckin : use ckin pin from an external driver to sample
|
||||
the receive data (example: with voltage
|
||||
switch transceiver).
|
||||
|
||||
Deprecated properties:
|
||||
- mmc-cap-mmc-highspeed : indicates whether MMC is high speed capable.
|
||||
- mmc-cap-sd-highspeed : indicates whether SD is high speed capable.
|
||||
|
|
|
@ -10,6 +10,7 @@ Required properties:
|
|||
- compatible: value should be either of the following.
|
||||
"mediatek,mt8135-mmc": for mmc host ip compatible with mt8135
|
||||
"mediatek,mt8173-mmc": for mmc host ip compatible with mt8173
|
||||
"mediatek,mt8183-mmc": for mmc host ip compatible with mt8183
|
||||
"mediatek,mt2701-mmc": for mmc host ip compatible with mt2701
|
||||
"mediatek,mt2712-mmc": for mmc host ip compatible with mt2712
|
||||
"mediatek,mt7622-mmc": for MT7622 SoC
|
||||
|
@ -22,6 +23,7 @@ Required properties:
|
|||
"source" - source clock (required)
|
||||
"hclk" - HCLK which used for host (required)
|
||||
"source_cg" - independent source clock gate (required for MT2712)
|
||||
"bus_clk" - bus clock used for internal register access (required for MT2712 MSDC0/3)
|
||||
- pinctrl-names: should be "default", "state_uhs"
|
||||
- pinctrl-0: should contain default/high speed pin ctrl
|
||||
- pinctrl-1: should contain uhs mode pin ctrl
|
||||
|
|
|
@ -38,3 +38,75 @@ sdhci@c8000200 {
|
|||
power-gpios = <&gpio 155 0>; /* gpio PT3 */
|
||||
bus-width = <8>;
|
||||
};
|
||||
|
||||
Optional properties for Tegra210 and Tegra186:
|
||||
- pinctrl-names, pinctrl-0, pinctrl-1 : Specify pad voltage
|
||||
configurations. Valid pinctrl-names are "sdmmc-3v3" and "sdmmc-1v8"
|
||||
for controllers supporting multiple voltage levels. The order of names
|
||||
should correspond to the pin configuration states in pinctrl-0 and
|
||||
pinctrl-1.
|
||||
- nvidia,only-1-8-v : The presence of this property indicates that the
|
||||
controller operates at a 1.8 V fixed I/O voltage.
|
||||
- nvidia,pad-autocal-pull-up-offset-3v3,
|
||||
nvidia,pad-autocal-pull-down-offset-3v3 : Specify drive strength
|
||||
calibration offsets for 3.3 V signaling modes.
|
||||
- nvidia,pad-autocal-pull-up-offset-1v8,
|
||||
nvidia,pad-autocal-pull-down-offset-1v8 : Specify drive strength
|
||||
calibration offsets for 1.8 V signaling modes.
|
||||
- nvidia,pad-autocal-pull-up-offset-3v3-timeout,
|
||||
nvidia,pad-autocal-pull-down-offset-3v3-timeout : Specify drive
|
||||
strength used as a fallback in case the automatic calibration times
|
||||
out on a 3.3 V signaling mode.
|
||||
- nvidia,pad-autocal-pull-up-offset-1v8-timeout,
|
||||
nvidia,pad-autocal-pull-down-offset-1v8-timeout : Specify drive
|
||||
strength used as a fallback in case the automatic calibration times
|
||||
out on a 1.8 V signaling mode.
|
||||
- nvidia,pad-autocal-pull-up-offset-sdr104,
|
||||
nvidia,pad-autocal-pull-down-offset-sdr104 : Specify drive strength
|
||||
calibration offsets for SDR104 mode.
|
||||
- nvidia,pad-autocal-pull-up-offset-hs400,
|
||||
nvidia,pad-autocal-pull-down-offset-hs400 : Specify drive strength
|
||||
calibration offsets for HS400 mode.
|
||||
- nvidia,default-tap : Specify the default inbound sampling clock
|
||||
trimmer value for non-tunable modes.
|
||||
- nvidia,default-trim : Specify the default outbound clock trimmer
|
||||
value.
|
||||
- nvidia,dqs-trim : Specify DQS trim value for HS400 timing
|
||||
|
||||
Notes on the pad calibration pull up and pulldown offset values:
|
||||
- The property values are drive codes which are programmed into the
|
||||
PD_OFFSET and PU_OFFSET sections of the
|
||||
SDHCI_TEGRA_AUTO_CAL_CONFIG register.
|
||||
- A higher value corresponds to higher drive strength. Please refer
|
||||
to the reference manual of the SoC for correct values.
|
||||
- The SDR104 and HS400 timing specific values are used in
|
||||
corresponding modes if specified.
|
||||
|
||||
Notes on tap and trim values:
|
||||
- The values are used for compensating trace length differences
|
||||
by adjusting the sampling point.
|
||||
- The values are programmed to the Vendor Clock Control Register.
|
||||
Please refer to the reference manual of the SoC for correct
|
||||
values.
|
||||
- The DQS trim values are only used on controllers which support
|
||||
HS400 timing. Only SDMMC4 on Tegra210 and Tegra 186 supports
|
||||
HS400.
|
||||
|
||||
Example:
|
||||
sdhci@700b0000 {
|
||||
compatible = "nvidia,tegra210-sdhci", "nvidia,tegra124-sdhci";
|
||||
reg = <0x0 0x700b0000 0x0 0x200>;
|
||||
interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&tegra_car TEGRA210_CLK_SDMMC1>;
|
||||
clock-names = "sdhci";
|
||||
resets = <&tegra_car 14>;
|
||||
reset-names = "sdhci";
|
||||
pinctrl-names = "sdmmc-3v3", "sdmmc-1v8";
|
||||
pinctrl-0 = <&sdmmc1_3v3>;
|
||||
pinctrl-1 = <&sdmmc1_1v8>;
|
||||
nvidia,pad-autocal-pull-up-offset-3v3 = <0x00>;
|
||||
nvidia,pad-autocal-pull-down-offset-3v3 = <0x7d>;
|
||||
nvidia,pad-autocal-pull-up-offset-1v8 = <0x7b>;
|
||||
nvidia,pad-autocal-pull-down-offset-1v8 = <0x7b>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ Required properties:
|
|||
- "renesas,mmcif-r8a73a4" for the MMCIF found in r8a73a4 SoCs
|
||||
- "renesas,mmcif-r8a7740" for the MMCIF found in r8a7740 SoCs
|
||||
- "renesas,mmcif-r8a7743" for the MMCIF found in r8a7743 SoCs
|
||||
- "renesas,mmcif-r8a7744" for the MMCIF found in r8a7744 SoCs
|
||||
- "renesas,mmcif-r8a7745" for the MMCIF found in r8a7745 SoCs
|
||||
- "renesas,mmcif-r8a7778" for the MMCIF found in r8a7778 SoCs
|
||||
- "renesas,mmcif-r8a7790" for the MMCIF found in r8a7790 SoCs
|
||||
|
@ -23,7 +24,8 @@ Required properties:
|
|||
- interrupts: Some SoCs have only 1 shared interrupt, while others have either
|
||||
2 or 3 individual interrupts (error, int, card detect). Below is the number
|
||||
of interrupts for each SoC:
|
||||
1: r8a73a4, r8a7743, r8a7745, r8a7778, r8a7790, r8a7791, r8a7793, r8a7794
|
||||
1: r8a73a4, r8a7743, r8a7744, r8a7745, r8a7778, r8a7790, r8a7791, r8a7793,
|
||||
r8a7794
|
||||
2: r8a7740, sh73a0
|
||||
3: r7s72100
|
||||
|
||||
|
|
41
Documentation/devicetree/bindings/mmc/sdhci-sprd.txt
Normal file
41
Documentation/devicetree/bindings/mmc/sdhci-sprd.txt
Normal file
|
@ -0,0 +1,41 @@
|
|||
* Spreadtrum SDHCI controller (sdhci-sprd)
|
||||
|
||||
The Secure Digital (SD) Host controller on Spreadtrum SoCs provides an interface
|
||||
for MMC, SD and SDIO types of cards.
|
||||
|
||||
This file documents differences between the core properties in mmc.txt
|
||||
and the properties used by the sdhci-sprd driver.
|
||||
|
||||
Required properties:
|
||||
- compatible: Should contain "sprd,sdhci-r11".
|
||||
- reg: physical base address of the controller and length.
|
||||
- interrupts: Interrupts used by the SDHCI controller.
|
||||
- clocks: Should contain phandle for the clock feeding the SDHCI controller
|
||||
- clock-names: Should contain the following:
|
||||
"sdio" - SDIO source clock (required)
|
||||
"enable" - gate clock which used for enabling/disabling the device (required)
|
||||
|
||||
Optional properties:
|
||||
- assigned-clocks: the same with "sdio" clock
|
||||
- assigned-clock-parents: the default parent of "sdio" clock
|
||||
|
||||
Examples:
|
||||
|
||||
sdio0: sdio@20600000 {
|
||||
compatible = "sprd,sdhci-r11";
|
||||
reg = <0 0x20600000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
|
||||
|
||||
clock-names = "sdio", "enable";
|
||||
clocks = <&ap_clk CLK_EMMC_2X>,
|
||||
<&apahb_gate CLK_EMMC_EB>;
|
||||
assigned-clocks = <&ap_clk CLK_EMMC_2X>;
|
||||
assigned-clock-parents = <&rpll CLK_RPLL_390M>;
|
||||
|
||||
bus-width = <8>;
|
||||
non-removable;
|
||||
no-sdio;
|
||||
no-sd;
|
||||
cap-mmc-hw-reset;
|
||||
status = "okay";
|
||||
};
|
|
@ -16,7 +16,11 @@ Required properties:
|
|||
"renesas,sdhi-r8a73a4" - SDHI IP on R8A73A4 SoC
|
||||
"renesas,sdhi-r8a7740" - SDHI IP on R8A7740 SoC
|
||||
"renesas,sdhi-r8a7743" - SDHI IP on R8A7743 SoC
|
||||
"renesas,sdhi-r8a7744" - SDHI IP on R8A7744 SoC
|
||||
"renesas,sdhi-r8a7745" - SDHI IP on R8A7745 SoC
|
||||
"renesas,sdhi-r8a774a1" - SDHI IP on R8A774A1 SoC
|
||||
"renesas,sdhi-r8a77470" - SDHI IP on R8A77470 SoC
|
||||
"renesas,sdhi-mmc-r8a77470" - SDHI/MMC IP on R8A77470 SoC
|
||||
"renesas,sdhi-r8a7778" - SDHI IP on R8A7778 SoC
|
||||
"renesas,sdhi-r8a7779" - SDHI IP on R8A7779 SoC
|
||||
"renesas,sdhi-r8a7790" - SDHI IP on R8A7790 SoC
|
||||
|
@ -27,14 +31,16 @@ Required properties:
|
|||
"renesas,sdhi-r8a7795" - SDHI IP on R8A7795 SoC
|
||||
"renesas,sdhi-r8a7796" - SDHI IP on R8A7796 SoC
|
||||
"renesas,sdhi-r8a77965" - SDHI IP on R8A77965 SoC
|
||||
"renesas,sdhi-r8a77970" - SDHI IP on R8A77970 SoC
|
||||
"renesas,sdhi-r8a77980" - SDHI IP on R8A77980 SoC
|
||||
"renesas,sdhi-r8a77990" - SDHI IP on R8A77990 SoC
|
||||
"renesas,sdhi-r8a77995" - SDHI IP on R8A77995 SoC
|
||||
"renesas,sdhi-shmobile" - a generic sh-mobile SDHI controller
|
||||
"renesas,rcar-gen1-sdhi" - a generic R-Car Gen1 SDHI controller
|
||||
"renesas,rcar-gen2-sdhi" - a generic R-Car Gen2 or RZ/G1
|
||||
"renesas,rcar-gen2-sdhi" - a generic R-Car Gen2 and RZ/G1 SDHI
|
||||
(not SDHI/MMC) controller
|
||||
"renesas,rcar-gen3-sdhi" - a generic R-Car Gen3 or RZ/G2
|
||||
SDHI controller
|
||||
"renesas,rcar-gen3-sdhi" - a generic R-Car Gen3 SDHI controller
|
||||
|
||||
|
||||
When compatible with the generic version, nodes must list
|
||||
|
|
55
Documentation/devicetree/bindings/mmc/uniphier-sd.txt
Normal file
55
Documentation/devicetree/bindings/mmc/uniphier-sd.txt
Normal file
|
@ -0,0 +1,55 @@
|
|||
UniPhier SD/eMMC controller
|
||||
|
||||
Required properties:
|
||||
- compatible: should be one of the following:
|
||||
"socionext,uniphier-sd-v2.91" - IP version 2.91
|
||||
"socionext,uniphier-sd-v3.1" - IP version 3.1
|
||||
"socionext,uniphier-sd-v3.1.1" - IP version 3.1.1
|
||||
- reg: offset and length of the register set for the device.
|
||||
- interrupts: a single interrupt specifier.
|
||||
- clocks: a single clock specifier of the controller clock.
|
||||
- reset-names: should contain the following:
|
||||
"host" - mandatory for all versions
|
||||
"bridge" - should exist only for "socionext,uniphier-sd-v2.91"
|
||||
"hw" - should exist if eMMC hw reset line is available
|
||||
- resets: a list of reset specifiers, corresponding to the reset-names
|
||||
|
||||
Optional properties:
|
||||
- pinctrl-names: if present, should contain the following:
|
||||
"default" - should exist for all instances
|
||||
"uhs" - should exist for SD instance with UHS support
|
||||
- pinctrl-0: pin control state for the default mode
|
||||
- pinctrl-1: pin control state for the UHS mode
|
||||
- dma-names: should be "rx-tx" if present.
|
||||
This property can exist only for "socionext,uniphier-sd-v2.91".
|
||||
- dmas: a single DMA channel specifier
|
||||
This property can exist only for "socionext,uniphier-sd-v2.91".
|
||||
- bus-width: see mmc.txt
|
||||
- cap-sd-highspeed: see mmc.txt
|
||||
- cap-mmc-highspeed: see mmc.txt
|
||||
- sd-uhs-sdr12: see mmc.txt
|
||||
- sd-uhs-sdr25: see mmc.txt
|
||||
- sd-uhs-sdr50: see mmc.txt
|
||||
- cap-mmc-hw-reset: should exist if reset-names contains "hw". see mmc.txt
|
||||
- non-removable: see mmc.txt
|
||||
|
||||
Example:
|
||||
|
||||
sd: sdhc@5a400000 {
|
||||
compatible = "socionext,uniphier-sd-v2.91";
|
||||
reg = <0x5a400000 0x200>;
|
||||
interrupts = <0 76 4>;
|
||||
pinctrl-names = "default", "uhs";
|
||||
pinctrl-0 = <&pinctrl_sd>;
|
||||
pinctrl-1 = <&pinctrl_sd_uhs>;
|
||||
clocks = <&mio_clk 0>;
|
||||
reset-names = "host", "bridge";
|
||||
resets = <&mio_rst 0>, <&mio_rst 3>;
|
||||
dma-names = "rx-tx";
|
||||
dmas = <&dmac 4>;
|
||||
bus-width = <4>;
|
||||
cap-sd-highspeed;
|
||||
sd-uhs-sdr12;
|
||||
sd-uhs-sdr25;
|
||||
sd-uhs-sdr50;
|
||||
};
|
|
@ -2195,6 +2195,7 @@ F: drivers/clk/uniphier/
|
|||
F: drivers/gpio/gpio-uniphier.c
|
||||
F: drivers/i2c/busses/i2c-uniphier*
|
||||
F: drivers/irqchip/irq-uniphier-aidet.c
|
||||
F: drivers/mmc/host/uniphier-sd.c
|
||||
F: drivers/pinctrl/uniphier/
|
||||
F: drivers/reset/reset-uniphier.c
|
||||
F: drivers/tty/serial/8250/8250_uniphier.c
|
||||
|
|
|
@ -80,8 +80,6 @@ static unsigned int mmc_status(struct device *dev)
|
|||
static struct mmci_platform_data mmc_data = {
|
||||
.ocr_mask = MMC_VDD_32_33|MMC_VDD_33_34,
|
||||
.status = mmc_status,
|
||||
.gpio_wp = -1,
|
||||
.gpio_cd = -1,
|
||||
};
|
||||
|
||||
static u64 notrace intcp_read_sched_clock(void)
|
||||
|
|
|
@ -12,8 +12,6 @@ struct omap2_hsmmc_info {
|
|||
u8 mmc; /* controller 1/2/3 */
|
||||
u32 caps; /* 4/8 wires and any additional host
|
||||
* capabilities OR'd (ref. linux/mmc/host.h) */
|
||||
int gpio_cd; /* or -EINVAL */
|
||||
int gpio_wp; /* or -EINVAL */
|
||||
struct platform_device *pdev; /* mmc controller instance */
|
||||
/* init some special card */
|
||||
void (*init_card)(struct mmc_card *card);
|
||||
|
|
|
@ -376,8 +376,6 @@ static struct omap2_hsmmc_info pandora_mmc3[] = {
|
|||
{
|
||||
.mmc = 3,
|
||||
.caps = MMC_CAP_4_BIT_DATA | MMC_CAP_POWER_OFF_CARD,
|
||||
.gpio_cd = -EINVAL,
|
||||
.gpio_wp = -EINVAL,
|
||||
.init_card = pandora_wl1251_init_card,
|
||||
},
|
||||
{} /* Terminator */
|
||||
|
|
|
@ -89,15 +89,11 @@ unsigned int mmc_status(struct device *dev)
|
|||
static struct mmci_platform_data mmc0_plat_data = {
|
||||
.ocr_mask = MMC_VDD_32_33|MMC_VDD_33_34,
|
||||
.status = mmc_status,
|
||||
.gpio_wp = -1,
|
||||
.gpio_cd = -1,
|
||||
};
|
||||
|
||||
static struct mmci_platform_data mmc1_plat_data = {
|
||||
.ocr_mask = MMC_VDD_32_33|MMC_VDD_33_34,
|
||||
.status = mmc_status,
|
||||
.gpio_wp = -1,
|
||||
.gpio_cd = -1,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -14,7 +14,7 @@ config PWRSEQ_EMMC
|
|||
|
||||
config PWRSEQ_SD8787
|
||||
tristate "HW reset support for SD8787 BT + Wifi module"
|
||||
depends on OF && (MWIFIEX || BT_MRVL_SDIO)
|
||||
depends on OF && (MWIFIEX || BT_MRVL_SDIO || LIBERTAS_SDIO)
|
||||
help
|
||||
This selects hardware reset support for the SD8787 BT + Wifi
|
||||
module. By default this option is set to n.
|
||||
|
|
|
@ -34,6 +34,16 @@ config MMC_QCOM_DML
|
|||
|
||||
if unsure, say N.
|
||||
|
||||
config MMC_STM32_SDMMC
|
||||
bool "STMicroelectronics STM32 SDMMC Controller"
|
||||
depends on MMC_ARMMMCI
|
||||
default y
|
||||
help
|
||||
This selects the STMicroelectronics STM32 SDMMC host controller.
|
||||
If you have a STM32 sdmmc host with internal DMA say Y here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_PXA
|
||||
tristate "Intel PXA25x/26x/27x Multimedia Card Interface support"
|
||||
depends on ARCH_PXA
|
||||
|
@ -345,6 +355,7 @@ config MMC_SDHCI_IPROC
|
|||
tristate "SDHCI support for the BCM2835 & iProc SD/MMC Controller"
|
||||
depends on ARCH_BCM2835 || ARCH_BCM_IPROC || COMPILE_TEST
|
||||
depends on MMC_SDHCI_PLTFM
|
||||
depends on OF || ACPI
|
||||
default ARCH_BCM_IPROC
|
||||
select MMC_SDHCI_IO_ACCESSORS
|
||||
help
|
||||
|
@ -592,6 +603,19 @@ config MMC_SDRICOH_CS
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called sdricoh_cs.
|
||||
|
||||
config MMC_SDHCI_SPRD
|
||||
tristate "Spreadtrum SDIO host Controller"
|
||||
depends on ARCH_SPRD
|
||||
depends on MMC_SDHCI_PLTFM
|
||||
select MMC_SDHCI_IO_ACCESSORS
|
||||
help
|
||||
This selects the SDIO Host Controller in Spreadtrum
|
||||
SoCs, this driver supports R11(IP version: R11P0).
|
||||
|
||||
If you have a controller with this interface, say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_TMIO_CORE
|
||||
tristate
|
||||
|
||||
|
@ -622,14 +646,24 @@ config MMC_SDHI_SYS_DMAC
|
|||
|
||||
config MMC_SDHI_INTERNAL_DMAC
|
||||
tristate "DMA for SDHI SD/SDIO controllers using on-chip bus mastering"
|
||||
depends on ARM64 || COMPILE_TEST
|
||||
depends on ARM64 || ARCH_R8A77470 || COMPILE_TEST
|
||||
depends on MMC_SDHI
|
||||
default MMC_SDHI if ARM64
|
||||
default MMC_SDHI if (ARM64 || ARCH_R8A77470)
|
||||
help
|
||||
This provides DMA support for SDHI SD/SDIO controllers
|
||||
using on-chip bus mastering. This supports the controllers
|
||||
found in arm64 based SoCs.
|
||||
|
||||
config MMC_UNIPHIER
|
||||
tristate "UniPhier SD/eMMC Host Controller support"
|
||||
depends on ARCH_UNIPHIER || COMPILE_TEST
|
||||
depends on OF
|
||||
select MMC_TMIO_CORE
|
||||
help
|
||||
This provides support for the SD/eMMC controller found in
|
||||
UniPhier SoCs. The eMMC variant of this controller is used
|
||||
only for 32-bit SoCs.
|
||||
|
||||
config MMC_CB710
|
||||
tristate "ENE CB710 MMC/SD Interface support"
|
||||
depends on PCI
|
||||
|
@ -772,7 +806,7 @@ config MMC_SH_MMCIF
|
|||
|
||||
config MMC_JZ4740
|
||||
tristate "Ingenic JZ47xx SD/Multimedia Card Interface support"
|
||||
depends on MACH_JZ4740 || MACH_JZ4780
|
||||
depends on MIPS
|
||||
help
|
||||
This selects support for the SD/MMC controller on Ingenic
|
||||
JZ4740, JZ4750, JZ4770 and JZ4780 SoCs.
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
obj-$(CONFIG_MMC_ARMMMCI) += armmmci.o
|
||||
armmmci-y := mmci.o
|
||||
armmmci-$(CONFIG_MMC_QCOM_DML) += mmci_qcom_dml.o
|
||||
armmmci-$(CONFIG_MMC_STM32_SDMMC) += mmci_stm32_sdmmc.o
|
||||
obj-$(CONFIG_MMC_PXA) += pxamci.o
|
||||
obj-$(CONFIG_MMC_MXC) += mxcmmc.o
|
||||
obj-$(CONFIG_MMC_MXS) += mxs-mmc.o
|
||||
|
@ -42,6 +43,7 @@ obj-$(CONFIG_MMC_TMIO_CORE) += tmio_mmc_core.o
|
|||
obj-$(CONFIG_MMC_SDHI) += renesas_sdhi_core.o
|
||||
obj-$(CONFIG_MMC_SDHI_SYS_DMAC) += renesas_sdhi_sys_dmac.o
|
||||
obj-$(CONFIG_MMC_SDHI_INTERNAL_DMAC) += renesas_sdhi_internal_dmac.o
|
||||
obj-$(CONFIG_MMC_UNIPHIER) += uniphier-sd.o
|
||||
obj-$(CONFIG_MMC_CB710) += cb710-mmc.o
|
||||
obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
|
||||
octeon-mmc-objs := cavium.o cavium-octeon.o
|
||||
|
@ -91,6 +93,7 @@ obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o
|
|||
obj-$(CONFIG_MMC_SDHCI_MICROCHIP_PIC32) += sdhci-pic32.o
|
||||
obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o
|
||||
obj-$(CONFIG_MMC_SDHCI_OMAP) += sdhci-omap.o
|
||||
obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o
|
||||
obj-$(CONFIG_MMC_CQHCI) += cqhci.o
|
||||
|
||||
ifeq ($(CONFIG_CB710_DEBUG),y)
|
||||
|
|
|
@ -253,6 +253,8 @@ static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing)
|
|||
if (timing == MMC_TIMING_MMC_HS400) {
|
||||
dqs |= DATA_STROBE_EN;
|
||||
strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay);
|
||||
} else if (timing == MMC_TIMING_UHS_SDR104) {
|
||||
dqs &= 0xffffff00;
|
||||
} else {
|
||||
dqs &= ~DATA_STROBE_EN;
|
||||
}
|
||||
|
@ -312,6 +314,15 @@ static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
|
|||
if (ios->bus_width == MMC_BUS_WIDTH_8)
|
||||
wanted <<= 1;
|
||||
break;
|
||||
case MMC_TIMING_UHS_SDR104:
|
||||
case MMC_TIMING_UHS_SDR50:
|
||||
clksel = (priv->sdr_timing & 0xfff8ffff) |
|
||||
(priv->ciu_div << 16);
|
||||
break;
|
||||
case MMC_TIMING_UHS_DDR50:
|
||||
clksel = (priv->ddr_timing & 0xfff8ffff) |
|
||||
(priv->ciu_div << 16);
|
||||
break;
|
||||
default:
|
||||
clksel = priv->sdr_timing;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,12 @@ struct hi3798cv200_priv {
|
|||
struct clk *drive_clk;
|
||||
};
|
||||
|
||||
static unsigned long dw_mci_hi3798cv200_caps[] = {
|
||||
MMC_CAP_CMD23,
|
||||
MMC_CAP_CMD23,
|
||||
MMC_CAP_CMD23
|
||||
};
|
||||
|
||||
static void dw_mci_hi3798cv200_set_ios(struct dw_mci *host, struct mmc_ios *ios)
|
||||
{
|
||||
struct hi3798cv200_priv *priv = host->priv;
|
||||
|
@ -160,6 +166,8 @@ static int dw_mci_hi3798cv200_init(struct dw_mci *host)
|
|||
}
|
||||
|
||||
static const struct dw_mci_drv_data hi3798cv200_data = {
|
||||
.caps = dw_mci_hi3798cv200_caps,
|
||||
.num_caps = ARRAY_SIZE(dw_mci_hi3798cv200_caps),
|
||||
.init = dw_mci_hi3798cv200_init,
|
||||
.set_ios = dw_mci_hi3798cv200_set_ios,
|
||||
.execute_tuning = dw_mci_hi3798cv200_execute_tuning,
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
|
||||
enum jz4740_mmc_version {
|
||||
JZ_MMC_JZ4740,
|
||||
JZ_MMC_JZ4750,
|
||||
JZ_MMC_JZ4725B,
|
||||
JZ_MMC_JZ4780,
|
||||
};
|
||||
|
||||
|
@ -176,7 +176,7 @@ struct jz4740_mmc_host {
|
|||
static void jz4740_mmc_write_irq_mask(struct jz4740_mmc_host *host,
|
||||
uint32_t val)
|
||||
{
|
||||
if (host->version >= JZ_MMC_JZ4750)
|
||||
if (host->version >= JZ_MMC_JZ4725B)
|
||||
return writel(val, host->base + JZ_REG_MMC_IMASK);
|
||||
else
|
||||
return writew(val, host->base + JZ_REG_MMC_IMASK);
|
||||
|
@ -1012,6 +1012,7 @@ static void jz4740_mmc_free_gpios(struct platform_device *pdev)
|
|||
|
||||
static const struct of_device_id jz4740_mmc_of_match[] = {
|
||||
{ .compatible = "ingenic,jz4740-mmc", .data = (void *) JZ_MMC_JZ4740 },
|
||||
{ .compatible = "ingenic,jz4725b-mmc", .data = (void *)JZ_MMC_JZ4725B },
|
||||
{ .compatible = "ingenic,jz4780-mmc", .data = (void *) JZ_MMC_JZ4780 },
|
||||
{},
|
||||
};
|
||||
|
|
|
@ -294,7 +294,7 @@ static void meson_mx_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|||
switch (ios->power_mode) {
|
||||
case MMC_POWER_OFF:
|
||||
vdd = 0;
|
||||
/* fall-through: */
|
||||
/* fall through */
|
||||
case MMC_POWER_UP:
|
||||
if (!IS_ERR(mmc->supply.vmmc)) {
|
||||
host->error = mmc_regulator_set_ocr(mmc,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,6 +23,14 @@
|
|||
#define MCI_ST_DATA31DIREN (1 << 5)
|
||||
#define MCI_ST_FBCLKEN (1 << 7)
|
||||
#define MCI_ST_DATA74DIREN (1 << 8)
|
||||
/*
|
||||
* The STM32 sdmmc does not have PWR_UP/OD/ROD
|
||||
* and uses the power register for
|
||||
*/
|
||||
#define MCI_STM32_PWR_CYC 0x02
|
||||
#define MCI_STM32_VSWITCH BIT(2)
|
||||
#define MCI_STM32_VSWITCHEN BIT(3)
|
||||
#define MCI_STM32_DIRPOL BIT(4)
|
||||
|
||||
#define MMCICLOCK 0x004
|
||||
#define MCI_CLK_ENABLE (1 << 8)
|
||||
|
@ -50,6 +58,19 @@
|
|||
#define MCI_QCOM_CLK_SELECT_IN_FBCLK BIT(15)
|
||||
#define MCI_QCOM_CLK_SELECT_IN_DDR_MODE (BIT(14) | BIT(15))
|
||||
|
||||
/* Modified on STM32 sdmmc */
|
||||
#define MCI_STM32_CLK_CLKDIV_MSK GENMASK(9, 0)
|
||||
#define MCI_STM32_CLK_WIDEBUS_4 BIT(14)
|
||||
#define MCI_STM32_CLK_WIDEBUS_8 BIT(15)
|
||||
#define MCI_STM32_CLK_NEGEDGE BIT(16)
|
||||
#define MCI_STM32_CLK_HWFCEN BIT(17)
|
||||
#define MCI_STM32_CLK_DDR BIT(18)
|
||||
#define MCI_STM32_CLK_BUSSPEED BIT(19)
|
||||
#define MCI_STM32_CLK_SEL_MSK GENMASK(21, 20)
|
||||
#define MCI_STM32_CLK_SELCK (0 << 20)
|
||||
#define MCI_STM32_CLK_SELCKIN (1 << 20)
|
||||
#define MCI_STM32_CLK_SELFBCK (2 << 20)
|
||||
|
||||
#define MMCIARGUMENT 0x008
|
||||
|
||||
/* The command register controls the Command Path State Machine (CPSM) */
|
||||
|
@ -72,6 +93,15 @@
|
|||
#define MCI_CPSM_QCOM_CCSDISABLE BIT(15)
|
||||
#define MCI_CPSM_QCOM_AUTO_CMD19 BIT(16)
|
||||
#define MCI_CPSM_QCOM_AUTO_CMD21 BIT(21)
|
||||
/* Command register in STM32 sdmmc versions */
|
||||
#define MCI_CPSM_STM32_CMDTRANS BIT(6)
|
||||
#define MCI_CPSM_STM32_CMDSTOP BIT(7)
|
||||
#define MCI_CPSM_STM32_WAITRESP_MASK GENMASK(9, 8)
|
||||
#define MCI_CPSM_STM32_NORSP (0 << 8)
|
||||
#define MCI_CPSM_STM32_SRSP_CRC (1 << 8)
|
||||
#define MCI_CPSM_STM32_SRSP (2 << 8)
|
||||
#define MCI_CPSM_STM32_LRSP_CRC (3 << 8)
|
||||
#define MCI_CPSM_STM32_ENABLE BIT(12)
|
||||
|
||||
#define MMCIRESPCMD 0x010
|
||||
#define MMCIRESPONSE0 0x014
|
||||
|
@ -130,6 +160,8 @@
|
|||
#define MCI_ST_SDIOIT (1 << 22)
|
||||
#define MCI_ST_CEATAEND (1 << 23)
|
||||
#define MCI_ST_CARDBUSY (1 << 24)
|
||||
/* Extended status bits for the STM32 variants */
|
||||
#define MCI_STM32_BUSYD0 BIT(20)
|
||||
|
||||
#define MMCICLEAR 0x038
|
||||
#define MCI_CMDCRCFAILCLR (1 << 0)
|
||||
|
@ -175,21 +207,45 @@
|
|||
#define MCI_ST_SDIOITMASK (1 << 22)
|
||||
#define MCI_ST_CEATAENDMASK (1 << 23)
|
||||
#define MCI_ST_BUSYENDMASK (1 << 24)
|
||||
/* Extended status bits for the STM32 variants */
|
||||
#define MCI_STM32_BUSYD0ENDMASK BIT(21)
|
||||
|
||||
#define MMCIMASK1 0x040
|
||||
#define MMCIFIFOCNT 0x048
|
||||
#define MMCIFIFO 0x080 /* to 0x0bc */
|
||||
|
||||
/* STM32 sdmmc registers for IDMA (Internal DMA) */
|
||||
#define MMCI_STM32_IDMACTRLR 0x050
|
||||
#define MMCI_STM32_IDMAEN BIT(0)
|
||||
#define MMCI_STM32_IDMALLIEN BIT(1)
|
||||
|
||||
#define MMCI_STM32_IDMABSIZER 0x054
|
||||
#define MMCI_STM32_IDMABNDT_SHIFT 5
|
||||
#define MMCI_STM32_IDMABNDT_MASK GENMASK(12, 5)
|
||||
|
||||
#define MMCI_STM32_IDMABASE0R 0x058
|
||||
|
||||
#define MMCI_STM32_IDMALAR 0x64
|
||||
#define MMCI_STM32_IDMALA_MASK GENMASK(13, 0)
|
||||
#define MMCI_STM32_ABR BIT(29)
|
||||
#define MMCI_STM32_ULS BIT(30)
|
||||
#define MMCI_STM32_ULA BIT(31)
|
||||
|
||||
#define MMCI_STM32_IDMABAR 0x68
|
||||
|
||||
#define MCI_IRQENABLE \
|
||||
(MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \
|
||||
MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \
|
||||
MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_STARTBITERRMASK)
|
||||
(MCI_CMDCRCFAILMASK | MCI_DATACRCFAILMASK | MCI_CMDTIMEOUTMASK | \
|
||||
MCI_DATATIMEOUTMASK | MCI_TXUNDERRUNMASK | MCI_RXOVERRUNMASK | \
|
||||
MCI_CMDRESPENDMASK | MCI_CMDSENTMASK)
|
||||
|
||||
/* These interrupts are directed to IRQ1 when two IRQ lines are available */
|
||||
#define MCI_IRQ1MASK \
|
||||
#define MCI_IRQ_PIO_MASK \
|
||||
(MCI_RXFIFOHALFFULLMASK | MCI_RXDATAAVLBLMASK | \
|
||||
MCI_TXFIFOHALFEMPTYMASK)
|
||||
|
||||
#define MCI_IRQ_PIO_STM32_MASK \
|
||||
(MCI_RXFIFOHALFFULLMASK | MCI_TXFIFOHALFEMPTYMASK)
|
||||
|
||||
#define NR_SG 128
|
||||
|
||||
#define MMCI_PINCTRL_STATE_OPENDRAIN "opendrain"
|
||||
|
@ -204,6 +260,10 @@ struct mmci_host;
|
|||
* @clkreg_enable: enable value for MMCICLOCK register
|
||||
* @clkreg_8bit_bus_enable: enable value for 8 bit bus
|
||||
* @clkreg_neg_edge_enable: enable value for inverted data/cmd output
|
||||
* @cmdreg_cpsm_enable: enable value for CPSM
|
||||
* @cmdreg_lrsp_crc: enable value for long response with crc
|
||||
* @cmdreg_srsp_crc: enable value for short response with crc
|
||||
* @cmdreg_srsp: enable value for short response without crc
|
||||
* @datalength_bits: number of bits in the MMCIDATALENGTH register
|
||||
* @fifosize: number of bytes that can be written when MMCI_TXFIFOEMPTY
|
||||
* is asserted (likewise for RX)
|
||||
|
@ -212,11 +272,17 @@ struct mmci_host;
|
|||
* @data_cmd_enable: enable value for data commands.
|
||||
* @st_sdio: enable ST specific SDIO logic
|
||||
* @st_clkdiv: true if using a ST-specific clock divider algorithm
|
||||
* @stm32_clkdiv: true if using a STM32-specific clock divider algorithm
|
||||
* @datactrl_mask_ddrmode: ddr mode mask in datactrl register.
|
||||
* @blksz_datactrl16: true if Block size is at b16..b30 position in datactrl register
|
||||
* @blksz_datactrl4: true if Block size is at b4..b16 position in datactrl
|
||||
* register
|
||||
* @datactrl_mask_sdio: SDIO enable mask in datactrl register
|
||||
* @datactrl_blksz: block size in power of two
|
||||
* @datactrl_dpsm_enable: enable value for DPSM
|
||||
* @datactrl_first: true if data must be setup before send command
|
||||
* @datacnt_useless: true if you could not use datacnt register to read
|
||||
* remaining data
|
||||
* @pwrreg_powerup: power up value for MMCIPOWER register
|
||||
* @f_max: maximum clk frequency supported by the controller.
|
||||
* @signal_direction: input/out direction of bus signals can be indicated
|
||||
|
@ -233,53 +299,75 @@ struct mmci_host;
|
|||
* @qcom_dml: enables qcom specific dma glue for dma transfers.
|
||||
* @reversed_irq_handling: handle data irq before cmd irq.
|
||||
* @mmcimask1: true if variant have a MMCIMASK1 register.
|
||||
* @irq_pio_mask: bitmask used to manage interrupt pio transfert in mmcimask
|
||||
* register
|
||||
* @start_err: bitmask identifying the STARTBITERR bit inside MMCISTATUS
|
||||
* register.
|
||||
* @opendrain: bitmask identifying the OPENDRAIN bit inside MMCIPOWER register
|
||||
* @dma_lli: true if variant has dma link list feature.
|
||||
* @stm32_idmabsize_mask: stm32 sdmmc idma buffer size.
|
||||
*/
|
||||
struct variant_data {
|
||||
unsigned int clkreg;
|
||||
unsigned int clkreg_enable;
|
||||
unsigned int clkreg_8bit_bus_enable;
|
||||
unsigned int clkreg_neg_edge_enable;
|
||||
unsigned int cmdreg_cpsm_enable;
|
||||
unsigned int cmdreg_lrsp_crc;
|
||||
unsigned int cmdreg_srsp_crc;
|
||||
unsigned int cmdreg_srsp;
|
||||
unsigned int datalength_bits;
|
||||
unsigned int fifosize;
|
||||
unsigned int fifohalfsize;
|
||||
unsigned int data_cmd_enable;
|
||||
unsigned int datactrl_mask_ddrmode;
|
||||
unsigned int datactrl_mask_sdio;
|
||||
bool st_sdio;
|
||||
bool st_clkdiv;
|
||||
bool blksz_datactrl16;
|
||||
bool blksz_datactrl4;
|
||||
unsigned int datactrl_blocksz;
|
||||
unsigned int datactrl_dpsm_enable;
|
||||
u8 datactrl_first:1;
|
||||
u8 datacnt_useless:1;
|
||||
u8 st_sdio:1;
|
||||
u8 st_clkdiv:1;
|
||||
u8 stm32_clkdiv:1;
|
||||
u8 blksz_datactrl16:1;
|
||||
u8 blksz_datactrl4:1;
|
||||
u32 pwrreg_powerup;
|
||||
u32 f_max;
|
||||
bool signal_direction;
|
||||
bool pwrreg_clkgate;
|
||||
bool busy_detect;
|
||||
u8 signal_direction:1;
|
||||
u8 pwrreg_clkgate:1;
|
||||
u8 busy_detect:1;
|
||||
u32 busy_dpsm_flag;
|
||||
u32 busy_detect_flag;
|
||||
u32 busy_detect_mask;
|
||||
bool pwrreg_nopower;
|
||||
bool explicit_mclk_control;
|
||||
bool qcom_fifo;
|
||||
bool qcom_dml;
|
||||
bool reversed_irq_handling;
|
||||
bool mmcimask1;
|
||||
u8 pwrreg_nopower:1;
|
||||
u8 explicit_mclk_control:1;
|
||||
u8 qcom_fifo:1;
|
||||
u8 qcom_dml:1;
|
||||
u8 reversed_irq_handling:1;
|
||||
u8 mmcimask1:1;
|
||||
unsigned int irq_pio_mask;
|
||||
u32 start_err;
|
||||
u32 opendrain;
|
||||
u8 dma_lli:1;
|
||||
u32 stm32_idmabsize_mask;
|
||||
void (*init)(struct mmci_host *host);
|
||||
};
|
||||
|
||||
/* mmci variant callbacks */
|
||||
struct mmci_host_ops {
|
||||
void (*dma_setup)(struct mmci_host *host);
|
||||
};
|
||||
|
||||
struct mmci_host_next {
|
||||
struct dma_async_tx_descriptor *dma_desc;
|
||||
struct dma_chan *dma_chan;
|
||||
s32 cookie;
|
||||
int (*validate_data)(struct mmci_host *host, struct mmc_data *data);
|
||||
int (*prep_data)(struct mmci_host *host, struct mmc_data *data,
|
||||
bool next);
|
||||
void (*unprep_data)(struct mmci_host *host, struct mmc_data *data,
|
||||
int err);
|
||||
void (*get_next_data)(struct mmci_host *host, struct mmc_data *data);
|
||||
int (*dma_setup)(struct mmci_host *host);
|
||||
void (*dma_release)(struct mmci_host *host);
|
||||
int (*dma_start)(struct mmci_host *host, unsigned int *datactrl);
|
||||
void (*dma_finalize)(struct mmci_host *host, struct mmc_data *data);
|
||||
void (*dma_error)(struct mmci_host *host);
|
||||
void (*set_clkreg)(struct mmci_host *host, unsigned int desired);
|
||||
void (*set_pwrreg)(struct mmci_host *host, unsigned int pwr);
|
||||
};
|
||||
|
||||
struct mmci_host {
|
||||
|
@ -290,7 +378,9 @@ struct mmci_host {
|
|||
struct mmc_data *data;
|
||||
struct mmc_host *mmc;
|
||||
struct clk *clk;
|
||||
bool singleirq;
|
||||
u8 singleirq:1;
|
||||
|
||||
struct reset_control *rst;
|
||||
|
||||
spinlock_t lock;
|
||||
|
||||
|
@ -301,10 +391,11 @@ struct mmci_host {
|
|||
u32 pwr_reg;
|
||||
u32 pwr_reg_add;
|
||||
u32 clk_reg;
|
||||
u32 clk_reg_add;
|
||||
u32 datactrl_reg;
|
||||
u32 busy_status;
|
||||
u32 mask1_reg;
|
||||
bool vqmmc_enabled;
|
||||
u8 vqmmc_enabled:1;
|
||||
struct mmci_platform_data *plat;
|
||||
struct mmci_host_ops *ops;
|
||||
struct variant_data *variant;
|
||||
|
@ -323,18 +414,25 @@ struct mmci_host {
|
|||
unsigned int size;
|
||||
int (*get_rx_fifocnt)(struct mmci_host *h, u32 status, int remain);
|
||||
|
||||
#ifdef CONFIG_DMA_ENGINE
|
||||
/* DMA stuff */
|
||||
struct dma_chan *dma_current;
|
||||
struct dma_chan *dma_rx_channel;
|
||||
struct dma_chan *dma_tx_channel;
|
||||
struct dma_async_tx_descriptor *dma_desc_current;
|
||||
struct mmci_host_next next_data;
|
||||
bool dma_in_progress;
|
||||
u8 use_dma:1;
|
||||
u8 dma_in_progress:1;
|
||||
void *dma_priv;
|
||||
|
||||
#define dma_inprogress(host) ((host)->dma_in_progress)
|
||||
#else
|
||||
#define dma_inprogress(host) (0)
|
||||
#endif
|
||||
s32 next_cookie;
|
||||
};
|
||||
|
||||
#define dma_inprogress(host) ((host)->dma_in_progress)
|
||||
|
||||
void mmci_write_clkreg(struct mmci_host *host, u32 clk);
|
||||
void mmci_write_pwrreg(struct mmci_host *host, u32 pwr);
|
||||
|
||||
int mmci_dmae_prep_data(struct mmci_host *host, struct mmc_data *data,
|
||||
bool next);
|
||||
void mmci_dmae_unprep_data(struct mmci_host *host, struct mmc_data *data,
|
||||
int err);
|
||||
void mmci_dmae_get_next_data(struct mmci_host *host, struct mmc_data *data);
|
||||
int mmci_dmae_setup(struct mmci_host *host);
|
||||
void mmci_dmae_release(struct mmci_host *host);
|
||||
int mmci_dmae_start(struct mmci_host *host, unsigned int *datactrl);
|
||||
void mmci_dmae_finalize(struct mmci_host *host, struct mmc_data *data);
|
||||
void mmci_dmae_error(struct mmci_host *host);
|
||||
|
|
|
@ -119,19 +119,23 @@ static int of_get_dml_pipe_index(struct device_node *np, const char *name)
|
|||
}
|
||||
|
||||
/* Initialize the dml hardware connected to SD Card controller */
|
||||
static void qcom_dma_setup(struct mmci_host *host)
|
||||
static int qcom_dma_setup(struct mmci_host *host)
|
||||
{
|
||||
u32 config;
|
||||
void __iomem *base;
|
||||
int consumer_id, producer_id;
|
||||
struct device_node *np = host->mmc->parent->of_node;
|
||||
|
||||
if (mmci_dmae_setup(host))
|
||||
return -EINVAL;
|
||||
|
||||
consumer_id = of_get_dml_pipe_index(np, "tx");
|
||||
producer_id = of_get_dml_pipe_index(np, "rx");
|
||||
|
||||
if (producer_id < 0 || consumer_id < 0) {
|
||||
host->variant->qcom_dml = false;
|
||||
return;
|
||||
mmci_dmae_release(host);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
base = host->base + DML_OFFSET;
|
||||
|
@ -175,10 +179,19 @@ static void qcom_dma_setup(struct mmci_host *host)
|
|||
|
||||
/* Make sure dml initialization is finished */
|
||||
mb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct mmci_host_ops qcom_variant_ops = {
|
||||
.prep_data = mmci_dmae_prep_data,
|
||||
.unprep_data = mmci_dmae_unprep_data,
|
||||
.get_next_data = mmci_dmae_get_next_data,
|
||||
.dma_setup = qcom_dma_setup,
|
||||
.dma_release = mmci_dmae_release,
|
||||
.dma_start = mmci_dmae_start,
|
||||
.dma_finalize = mmci_dmae_finalize,
|
||||
.dma_error = mmci_dmae_error,
|
||||
};
|
||||
|
||||
void qcom_variant_init(struct mmci_host *host)
|
||||
|
|
282
drivers/mmc/host/mmci_stm32_sdmmc.c
Normal file
282
drivers/mmc/host/mmci_stm32_sdmmc.c
Normal file
|
@ -0,0 +1,282 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) STMicroelectronics 2018 - All Rights Reserved
|
||||
* Author: Ludovic.barre@st.com for STMicroelectronics.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include "mmci.h"
|
||||
|
||||
#define SDMMC_LLI_BUF_LEN PAGE_SIZE
|
||||
#define SDMMC_IDMA_BURST BIT(MMCI_STM32_IDMABNDT_SHIFT)
|
||||
|
||||
struct sdmmc_lli_desc {
|
||||
u32 idmalar;
|
||||
u32 idmabase;
|
||||
u32 idmasize;
|
||||
};
|
||||
|
||||
struct sdmmc_priv {
|
||||
dma_addr_t sg_dma;
|
||||
void *sg_cpu;
|
||||
};
|
||||
|
||||
int sdmmc_idma_validate_data(struct mmci_host *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct scatterlist *sg;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* idma has constraints on idmabase & idmasize for each element
|
||||
* excepted the last element which has no constraint on idmasize
|
||||
*/
|
||||
for_each_sg(data->sg, sg, data->sg_len - 1, i) {
|
||||
if (!IS_ALIGNED(sg_dma_address(data->sg), sizeof(u32)) ||
|
||||
!IS_ALIGNED(sg_dma_len(data->sg), SDMMC_IDMA_BURST)) {
|
||||
dev_err(mmc_dev(host->mmc),
|
||||
"unaligned scatterlist: ofst:%x length:%d\n",
|
||||
data->sg->offset, data->sg->length);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IS_ALIGNED(sg_dma_address(data->sg), sizeof(u32))) {
|
||||
dev_err(mmc_dev(host->mmc),
|
||||
"unaligned last scatterlist: ofst:%x length:%d\n",
|
||||
data->sg->offset, data->sg->length);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _sdmmc_idma_prep_data(struct mmci_host *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
int n_elem;
|
||||
|
||||
n_elem = dma_map_sg(mmc_dev(host->mmc),
|
||||
data->sg,
|
||||
data->sg_len,
|
||||
mmc_get_dma_dir(data));
|
||||
|
||||
if (!n_elem) {
|
||||
dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdmmc_idma_prep_data(struct mmci_host *host,
|
||||
struct mmc_data *data, bool next)
|
||||
{
|
||||
/* Check if job is already prepared. */
|
||||
if (!next && data->host_cookie == host->next_cookie)
|
||||
return 0;
|
||||
|
||||
return _sdmmc_idma_prep_data(host, data);
|
||||
}
|
||||
|
||||
static void sdmmc_idma_unprep_data(struct mmci_host *host,
|
||||
struct mmc_data *data, int err)
|
||||
{
|
||||
dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
|
||||
mmc_get_dma_dir(data));
|
||||
}
|
||||
|
||||
static int sdmmc_idma_setup(struct mmci_host *host)
|
||||
{
|
||||
struct sdmmc_priv *idma;
|
||||
|
||||
idma = devm_kzalloc(mmc_dev(host->mmc), sizeof(*idma), GFP_KERNEL);
|
||||
if (!idma)
|
||||
return -ENOMEM;
|
||||
|
||||
host->dma_priv = idma;
|
||||
|
||||
if (host->variant->dma_lli) {
|
||||
idma->sg_cpu = dmam_alloc_coherent(mmc_dev(host->mmc),
|
||||
SDMMC_LLI_BUF_LEN,
|
||||
&idma->sg_dma, GFP_KERNEL);
|
||||
if (!idma->sg_cpu) {
|
||||
dev_err(mmc_dev(host->mmc),
|
||||
"Failed to alloc IDMA descriptor\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
host->mmc->max_segs = SDMMC_LLI_BUF_LEN /
|
||||
sizeof(struct sdmmc_lli_desc);
|
||||
host->mmc->max_seg_size = host->variant->stm32_idmabsize_mask;
|
||||
} else {
|
||||
host->mmc->max_segs = 1;
|
||||
host->mmc->max_seg_size = host->mmc->max_req_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdmmc_idma_start(struct mmci_host *host, unsigned int *datactrl)
|
||||
|
||||
{
|
||||
struct sdmmc_priv *idma = host->dma_priv;
|
||||
struct sdmmc_lli_desc *desc = (struct sdmmc_lli_desc *)idma->sg_cpu;
|
||||
struct mmc_data *data = host->data;
|
||||
struct scatterlist *sg;
|
||||
int i;
|
||||
|
||||
if (!host->variant->dma_lli || data->sg_len == 1) {
|
||||
writel_relaxed(sg_dma_address(data->sg),
|
||||
host->base + MMCI_STM32_IDMABASE0R);
|
||||
writel_relaxed(MMCI_STM32_IDMAEN,
|
||||
host->base + MMCI_STM32_IDMACTRLR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for_each_sg(data->sg, sg, data->sg_len, i) {
|
||||
desc[i].idmalar = (i + 1) * sizeof(struct sdmmc_lli_desc);
|
||||
desc[i].idmalar |= MMCI_STM32_ULA | MMCI_STM32_ULS
|
||||
| MMCI_STM32_ABR;
|
||||
desc[i].idmabase = sg_dma_address(sg);
|
||||
desc[i].idmasize = sg_dma_len(sg);
|
||||
}
|
||||
|
||||
/* notice the end of link list */
|
||||
desc[data->sg_len - 1].idmalar &= ~MMCI_STM32_ULA;
|
||||
|
||||
dma_wmb();
|
||||
writel_relaxed(idma->sg_dma, host->base + MMCI_STM32_IDMABAR);
|
||||
writel_relaxed(desc[0].idmalar, host->base + MMCI_STM32_IDMALAR);
|
||||
writel_relaxed(desc[0].idmabase, host->base + MMCI_STM32_IDMABASE0R);
|
||||
writel_relaxed(desc[0].idmasize, host->base + MMCI_STM32_IDMABSIZER);
|
||||
writel_relaxed(MMCI_STM32_IDMAEN | MMCI_STM32_IDMALLIEN,
|
||||
host->base + MMCI_STM32_IDMACTRLR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sdmmc_idma_finalize(struct mmci_host *host, struct mmc_data *data)
|
||||
{
|
||||
writel_relaxed(0, host->base + MMCI_STM32_IDMACTRLR);
|
||||
}
|
||||
|
||||
static void mmci_sdmmc_set_clkreg(struct mmci_host *host, unsigned int desired)
|
||||
{
|
||||
unsigned int clk = 0, ddr = 0;
|
||||
|
||||
if (host->mmc->ios.timing == MMC_TIMING_MMC_DDR52 ||
|
||||
host->mmc->ios.timing == MMC_TIMING_UHS_DDR50)
|
||||
ddr = MCI_STM32_CLK_DDR;
|
||||
|
||||
/*
|
||||
* cclk = mclk / (2 * clkdiv)
|
||||
* clkdiv 0 => bypass
|
||||
* in ddr mode bypass is not possible
|
||||
*/
|
||||
if (desired) {
|
||||
if (desired >= host->mclk && !ddr) {
|
||||
host->cclk = host->mclk;
|
||||
} else {
|
||||
clk = DIV_ROUND_UP(host->mclk, 2 * desired);
|
||||
if (clk > MCI_STM32_CLK_CLKDIV_MSK)
|
||||
clk = MCI_STM32_CLK_CLKDIV_MSK;
|
||||
host->cclk = host->mclk / (2 * clk);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* while power-on phase the clock can't be define to 0,
|
||||
* Only power-off and power-cyc deactivate the clock.
|
||||
* if desired clock is 0, set max divider
|
||||
*/
|
||||
clk = MCI_STM32_CLK_CLKDIV_MSK;
|
||||
host->cclk = host->mclk / (2 * clk);
|
||||
}
|
||||
|
||||
/* Set actual clock for debug */
|
||||
if (host->mmc->ios.power_mode == MMC_POWER_ON)
|
||||
host->mmc->actual_clock = host->cclk;
|
||||
else
|
||||
host->mmc->actual_clock = 0;
|
||||
|
||||
if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_4)
|
||||
clk |= MCI_STM32_CLK_WIDEBUS_4;
|
||||
if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_8)
|
||||
clk |= MCI_STM32_CLK_WIDEBUS_8;
|
||||
|
||||
clk |= MCI_STM32_CLK_HWFCEN;
|
||||
clk |= host->clk_reg_add;
|
||||
clk |= ddr;
|
||||
|
||||
/*
|
||||
* SDMMC_FBCK is selected when an external Delay Block is needed
|
||||
* with SDR104.
|
||||
*/
|
||||
if (host->mmc->ios.timing >= MMC_TIMING_UHS_SDR50) {
|
||||
clk |= MCI_STM32_CLK_BUSSPEED;
|
||||
if (host->mmc->ios.timing == MMC_TIMING_UHS_SDR104) {
|
||||
clk &= ~MCI_STM32_CLK_SEL_MSK;
|
||||
clk |= MCI_STM32_CLK_SELFBCK;
|
||||
}
|
||||
}
|
||||
|
||||
mmci_write_clkreg(host, clk);
|
||||
}
|
||||
|
||||
static void mmci_sdmmc_set_pwrreg(struct mmci_host *host, unsigned int pwr)
|
||||
{
|
||||
struct mmc_ios ios = host->mmc->ios;
|
||||
|
||||
pwr = host->pwr_reg_add;
|
||||
|
||||
if (ios.power_mode == MMC_POWER_OFF) {
|
||||
/* Only a reset could power-off sdmmc */
|
||||
reset_control_assert(host->rst);
|
||||
udelay(2);
|
||||
reset_control_deassert(host->rst);
|
||||
|
||||
/*
|
||||
* Set the SDMMC in Power-cycle state.
|
||||
* This will make that the SDMMC_D[7:0], SDMMC_CMD and SDMMC_CK
|
||||
* are driven low, to prevent the Card from being supplied
|
||||
* through the signal lines.
|
||||
*/
|
||||
mmci_write_pwrreg(host, MCI_STM32_PWR_CYC | pwr);
|
||||
} else if (ios.power_mode == MMC_POWER_ON) {
|
||||
/*
|
||||
* After power-off (reset): the irq mask defined in probe
|
||||
* functionis lost
|
||||
* ault irq mask (probe) must be activated
|
||||
*/
|
||||
writel(MCI_IRQENABLE | host->variant->start_err,
|
||||
host->base + MMCIMASK0);
|
||||
|
||||
/*
|
||||
* After a power-cycle state, we must set the SDMMC in
|
||||
* Power-off. The SDMMC_D[7:0], SDMMC_CMD and SDMMC_CK are
|
||||
* driven high. Then we can set the SDMMC to Power-on state
|
||||
*/
|
||||
mmci_write_pwrreg(host, MCI_PWR_OFF | pwr);
|
||||
mdelay(1);
|
||||
mmci_write_pwrreg(host, MCI_PWR_ON | pwr);
|
||||
}
|
||||
}
|
||||
|
||||
static struct mmci_host_ops sdmmc_variant_ops = {
|
||||
.validate_data = sdmmc_idma_validate_data,
|
||||
.prep_data = sdmmc_idma_prep_data,
|
||||
.unprep_data = sdmmc_idma_unprep_data,
|
||||
.dma_setup = sdmmc_idma_setup,
|
||||
.dma_start = sdmmc_idma_start,
|
||||
.dma_finalize = sdmmc_idma_finalize,
|
||||
.set_clkreg = mmci_sdmmc_set_clkreg,
|
||||
.set_pwrreg = mmci_sdmmc_set_pwrreg,
|
||||
};
|
||||
|
||||
void sdmmc_variant_init(struct mmci_host *host)
|
||||
{
|
||||
host->ops = &sdmmc_variant_ops;
|
||||
}
|
|
@ -86,6 +86,13 @@
|
|||
#define EMMC50_CFG3 0x220
|
||||
#define SDC_FIFO_CFG 0x228
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
/* Top Pad Register Offset */
|
||||
/*--------------------------------------------------------------------------*/
|
||||
#define EMMC_TOP_CONTROL 0x00
|
||||
#define EMMC_TOP_CMD 0x04
|
||||
#define EMMC50_PAD_DS_TUNE 0x0c
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
/* Register Mask */
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
@ -261,6 +268,23 @@
|
|||
#define SDC_FIFO_CFG_WRVALIDSEL (0x1 << 24) /* RW */
|
||||
#define SDC_FIFO_CFG_RDVALIDSEL (0x1 << 25) /* RW */
|
||||
|
||||
/* EMMC_TOP_CONTROL mask */
|
||||
#define PAD_RXDLY_SEL (0x1 << 0) /* RW */
|
||||
#define DELAY_EN (0x1 << 1) /* RW */
|
||||
#define PAD_DAT_RD_RXDLY2 (0x1f << 2) /* RW */
|
||||
#define PAD_DAT_RD_RXDLY (0x1f << 7) /* RW */
|
||||
#define PAD_DAT_RD_RXDLY2_SEL (0x1 << 12) /* RW */
|
||||
#define PAD_DAT_RD_RXDLY_SEL (0x1 << 13) /* RW */
|
||||
#define DATA_K_VALUE_SEL (0x1 << 14) /* RW */
|
||||
#define SDC_RX_ENH_EN (0x1 << 15) /* TW */
|
||||
|
||||
/* EMMC_TOP_CMD mask */
|
||||
#define PAD_CMD_RXDLY2 (0x1f << 0) /* RW */
|
||||
#define PAD_CMD_RXDLY (0x1f << 5) /* RW */
|
||||
#define PAD_CMD_RD_RXDLY2_SEL (0x1 << 10) /* RW */
|
||||
#define PAD_CMD_RD_RXDLY_SEL (0x1 << 11) /* RW */
|
||||
#define PAD_CMD_TX_DLY (0x1f << 12) /* RW */
|
||||
|
||||
#define REQ_CMD_EIO (0x1 << 0)
|
||||
#define REQ_CMD_TMO (0x1 << 1)
|
||||
#define REQ_DAT_ERR (0x1 << 2)
|
||||
|
@ -333,6 +357,9 @@ struct msdc_save_para {
|
|||
u32 emmc50_cfg0;
|
||||
u32 emmc50_cfg3;
|
||||
u32 sdc_fifo_cfg;
|
||||
u32 emmc_top_control;
|
||||
u32 emmc_top_cmd;
|
||||
u32 emmc50_pad_ds_tune;
|
||||
};
|
||||
|
||||
struct mtk_mmc_compatible {
|
||||
|
@ -351,6 +378,8 @@ struct msdc_tune_para {
|
|||
u32 iocon;
|
||||
u32 pad_tune;
|
||||
u32 pad_cmd_tune;
|
||||
u32 emmc_top_control;
|
||||
u32 emmc_top_cmd;
|
||||
};
|
||||
|
||||
struct msdc_delay_phase {
|
||||
|
@ -372,6 +401,7 @@ struct msdc_host {
|
|||
int error;
|
||||
|
||||
void __iomem *base; /* host base address */
|
||||
void __iomem *top_base; /* host top register base address */
|
||||
|
||||
struct msdc_dma dma; /* dma channel */
|
||||
u64 dma_mask;
|
||||
|
@ -387,10 +417,10 @@ struct msdc_host {
|
|||
|
||||
struct clk *src_clk; /* msdc source clock */
|
||||
struct clk *h_clk; /* msdc h_clk */
|
||||
struct clk *bus_clk; /* bus clock which used to access register */
|
||||
struct clk *src_clk_cg; /* msdc source clock control gate */
|
||||
u32 mclk; /* mmc subsystem clock frequency */
|
||||
u32 src_clk_freq; /* source clock frequency */
|
||||
u32 sclk; /* SD/MS bus clock frequency */
|
||||
unsigned char timing;
|
||||
bool vqmmc_enabled;
|
||||
u32 latch_ck;
|
||||
|
@ -429,6 +459,18 @@ static const struct mtk_mmc_compatible mt8173_compat = {
|
|||
.support_64g = false,
|
||||
};
|
||||
|
||||
static const struct mtk_mmc_compatible mt8183_compat = {
|
||||
.clk_div_bits = 12,
|
||||
.hs400_tune = false,
|
||||
.pad_tune_reg = MSDC_PAD_TUNE0,
|
||||
.async_fifo = true,
|
||||
.data_tune = true,
|
||||
.busy_check = true,
|
||||
.stop_clk_fix = true,
|
||||
.enhance_rx = true,
|
||||
.support_64g = true,
|
||||
};
|
||||
|
||||
static const struct mtk_mmc_compatible mt2701_compat = {
|
||||
.clk_div_bits = 12,
|
||||
.hs400_tune = false,
|
||||
|
@ -468,6 +510,7 @@ static const struct mtk_mmc_compatible mt7622_compat = {
|
|||
static const struct of_device_id msdc_of_ids[] = {
|
||||
{ .compatible = "mediatek,mt8135-mmc", .data = &mt8135_compat},
|
||||
{ .compatible = "mediatek,mt8173-mmc", .data = &mt8173_compat},
|
||||
{ .compatible = "mediatek,mt8183-mmc", .data = &mt8183_compat},
|
||||
{ .compatible = "mediatek,mt2701-mmc", .data = &mt2701_compat},
|
||||
{ .compatible = "mediatek,mt2712-mmc", .data = &mt2712_compat},
|
||||
{ .compatible = "mediatek,mt7622-mmc", .data = &mt7622_compat},
|
||||
|
@ -635,10 +678,10 @@ static void msdc_set_timeout(struct msdc_host *host, u32 ns, u32 clks)
|
|||
|
||||
host->timeout_ns = ns;
|
||||
host->timeout_clks = clks;
|
||||
if (host->sclk == 0) {
|
||||
if (host->mmc->actual_clock == 0) {
|
||||
timeout = 0;
|
||||
} else {
|
||||
clk_ns = 1000000000UL / host->sclk;
|
||||
clk_ns = 1000000000UL / host->mmc->actual_clock;
|
||||
timeout = (ns + clk_ns - 1) / clk_ns + clks;
|
||||
/* in 1048576 sclk cycle unit */
|
||||
timeout = (timeout + (0x1 << 20) - 1) >> 20;
|
||||
|
@ -660,12 +703,14 @@ static void msdc_gate_clock(struct msdc_host *host)
|
|||
{
|
||||
clk_disable_unprepare(host->src_clk_cg);
|
||||
clk_disable_unprepare(host->src_clk);
|
||||
clk_disable_unprepare(host->bus_clk);
|
||||
clk_disable_unprepare(host->h_clk);
|
||||
}
|
||||
|
||||
static void msdc_ungate_clock(struct msdc_host *host)
|
||||
{
|
||||
clk_prepare_enable(host->h_clk);
|
||||
clk_prepare_enable(host->bus_clk);
|
||||
clk_prepare_enable(host->src_clk);
|
||||
clk_prepare_enable(host->src_clk_cg);
|
||||
while (!(readl(host->base + MSDC_CFG) & MSDC_CFG_CKSTB))
|
||||
|
@ -683,6 +728,7 @@ static void msdc_set_mclk(struct msdc_host *host, unsigned char timing, u32 hz)
|
|||
if (!hz) {
|
||||
dev_dbg(host->dev, "set mclk to 0\n");
|
||||
host->mclk = 0;
|
||||
host->mmc->actual_clock = 0;
|
||||
sdr_clr_bits(host->base + MSDC_CFG, MSDC_CFG_CKPDN);
|
||||
return;
|
||||
}
|
||||
|
@ -761,7 +807,7 @@ static void msdc_set_mclk(struct msdc_host *host, unsigned char timing, u32 hz)
|
|||
while (!(readl(host->base + MSDC_CFG) & MSDC_CFG_CKSTB))
|
||||
cpu_relax();
|
||||
sdr_set_bits(host->base + MSDC_CFG, MSDC_CFG_CKPDN);
|
||||
host->sclk = sclk;
|
||||
host->mmc->actual_clock = sclk;
|
||||
host->mclk = hz;
|
||||
host->timing = timing;
|
||||
/* need because clk changed. */
|
||||
|
@ -772,14 +818,30 @@ static void msdc_set_mclk(struct msdc_host *host, unsigned char timing, u32 hz)
|
|||
* mmc_select_hs400() will drop to 50Mhz and High speed mode,
|
||||
* tune result of hs200/200Mhz is not suitable for 50Mhz
|
||||
*/
|
||||
if (host->sclk <= 52000000) {
|
||||
if (host->mmc->actual_clock <= 52000000) {
|
||||
writel(host->def_tune_para.iocon, host->base + MSDC_IOCON);
|
||||
writel(host->def_tune_para.pad_tune, host->base + tune_reg);
|
||||
if (host->top_base) {
|
||||
writel(host->def_tune_para.emmc_top_control,
|
||||
host->top_base + EMMC_TOP_CONTROL);
|
||||
writel(host->def_tune_para.emmc_top_cmd,
|
||||
host->top_base + EMMC_TOP_CMD);
|
||||
} else {
|
||||
writel(host->def_tune_para.pad_tune,
|
||||
host->base + tune_reg);
|
||||
}
|
||||
} else {
|
||||
writel(host->saved_tune_para.iocon, host->base + MSDC_IOCON);
|
||||
writel(host->saved_tune_para.pad_tune, host->base + tune_reg);
|
||||
writel(host->saved_tune_para.pad_cmd_tune,
|
||||
host->base + PAD_CMD_TUNE);
|
||||
if (host->top_base) {
|
||||
writel(host->saved_tune_para.emmc_top_control,
|
||||
host->top_base + EMMC_TOP_CONTROL);
|
||||
writel(host->saved_tune_para.emmc_top_cmd,
|
||||
host->top_base + EMMC_TOP_CMD);
|
||||
} else {
|
||||
writel(host->saved_tune_para.pad_tune,
|
||||
host->base + tune_reg);
|
||||
}
|
||||
}
|
||||
|
||||
if (timing == MMC_TIMING_MMC_HS400 &&
|
||||
|
@ -787,7 +849,8 @@ static void msdc_set_mclk(struct msdc_host *host, unsigned char timing, u32 hz)
|
|||
sdr_set_field(host->base + PAD_CMD_TUNE,
|
||||
MSDC_PAD_TUNE_CMDRRDLY,
|
||||
host->hs400_cmd_int_delay);
|
||||
dev_dbg(host->dev, "sclk: %d, timing: %d\n", host->sclk, timing);
|
||||
dev_dbg(host->dev, "sclk: %d, timing: %d\n", host->mmc->actual_clock,
|
||||
timing);
|
||||
}
|
||||
|
||||
static inline u32 msdc_cmd_find_resp(struct msdc_host *host,
|
||||
|
@ -1055,6 +1118,7 @@ static void msdc_start_command(struct msdc_host *host,
|
|||
WARN_ON(host->cmd);
|
||||
host->cmd = cmd;
|
||||
|
||||
mod_delayed_work(system_wq, &host->req_timeout, DAT_TIMEOUT);
|
||||
if (!msdc_cmd_is_ready(host, mrq, cmd))
|
||||
return;
|
||||
|
||||
|
@ -1066,7 +1130,6 @@ static void msdc_start_command(struct msdc_host *host,
|
|||
|
||||
cmd->error = 0;
|
||||
rawcmd = msdc_cmd_prepare_raw_cmd(host, mrq, cmd);
|
||||
mod_delayed_work(system_wq, &host->req_timeout, DAT_TIMEOUT);
|
||||
|
||||
sdr_set_bits(host->base + MSDC_INTEN, cmd_ints_mask);
|
||||
writel(cmd->arg, host->base + SDC_ARG);
|
||||
|
@ -1351,7 +1414,12 @@ static void msdc_init_hw(struct msdc_host *host)
|
|||
val = readl(host->base + MSDC_INT);
|
||||
writel(val, host->base + MSDC_INT);
|
||||
|
||||
writel(0, host->base + tune_reg);
|
||||
if (host->top_base) {
|
||||
writel(0, host->top_base + EMMC_TOP_CONTROL);
|
||||
writel(0, host->top_base + EMMC_TOP_CMD);
|
||||
} else {
|
||||
writel(0, host->base + tune_reg);
|
||||
}
|
||||
writel(0, host->base + MSDC_IOCON);
|
||||
sdr_set_field(host->base + MSDC_IOCON, MSDC_IOCON_DDLSEL, 0);
|
||||
writel(0x403c0046, host->base + MSDC_PATCH_BIT);
|
||||
|
@ -1375,8 +1443,12 @@ static void msdc_init_hw(struct msdc_host *host)
|
|||
sdr_set_field(host->base + MSDC_PATCH_BIT2,
|
||||
MSDC_PB2_RESPWAIT, 3);
|
||||
if (host->dev_comp->enhance_rx) {
|
||||
sdr_set_bits(host->base + SDC_ADV_CFG0,
|
||||
SDC_RX_ENHANCE_EN);
|
||||
if (host->top_base)
|
||||
sdr_set_bits(host->top_base + EMMC_TOP_CONTROL,
|
||||
SDC_RX_ENH_EN);
|
||||
else
|
||||
sdr_set_bits(host->base + SDC_ADV_CFG0,
|
||||
SDC_RX_ENHANCE_EN);
|
||||
} else {
|
||||
sdr_set_field(host->base + MSDC_PATCH_BIT2,
|
||||
MSDC_PB2_RESPSTSENSEL, 2);
|
||||
|
@ -1394,11 +1466,26 @@ static void msdc_init_hw(struct msdc_host *host)
|
|||
sdr_set_bits(host->base + MSDC_PATCH_BIT2,
|
||||
MSDC_PB2_SUPPORT_64G);
|
||||
if (host->dev_comp->data_tune) {
|
||||
sdr_set_bits(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_RD_SEL | MSDC_PAD_TUNE_CMD_SEL);
|
||||
if (host->top_base) {
|
||||
sdr_set_bits(host->top_base + EMMC_TOP_CONTROL,
|
||||
PAD_DAT_RD_RXDLY_SEL);
|
||||
sdr_clr_bits(host->top_base + EMMC_TOP_CONTROL,
|
||||
DATA_K_VALUE_SEL);
|
||||
sdr_set_bits(host->top_base + EMMC_TOP_CMD,
|
||||
PAD_CMD_RD_RXDLY_SEL);
|
||||
} else {
|
||||
sdr_set_bits(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_RD_SEL |
|
||||
MSDC_PAD_TUNE_CMD_SEL);
|
||||
}
|
||||
} else {
|
||||
/* choose clock tune */
|
||||
sdr_set_bits(host->base + tune_reg, MSDC_PAD_TUNE_RXDLYSEL);
|
||||
if (host->top_base)
|
||||
sdr_set_bits(host->top_base + EMMC_TOP_CONTROL,
|
||||
PAD_RXDLY_SEL);
|
||||
else
|
||||
sdr_set_bits(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_RXDLYSEL);
|
||||
}
|
||||
|
||||
/* Configure to enable SDIO mode.
|
||||
|
@ -1413,9 +1500,20 @@ static void msdc_init_hw(struct msdc_host *host)
|
|||
sdr_set_field(host->base + SDC_CFG, SDC_CFG_DTOC, 3);
|
||||
|
||||
host->def_tune_para.iocon = readl(host->base + MSDC_IOCON);
|
||||
host->def_tune_para.pad_tune = readl(host->base + tune_reg);
|
||||
host->saved_tune_para.iocon = readl(host->base + MSDC_IOCON);
|
||||
host->saved_tune_para.pad_tune = readl(host->base + tune_reg);
|
||||
if (host->top_base) {
|
||||
host->def_tune_para.emmc_top_control =
|
||||
readl(host->top_base + EMMC_TOP_CONTROL);
|
||||
host->def_tune_para.emmc_top_cmd =
|
||||
readl(host->top_base + EMMC_TOP_CMD);
|
||||
host->saved_tune_para.emmc_top_control =
|
||||
readl(host->top_base + EMMC_TOP_CONTROL);
|
||||
host->saved_tune_para.emmc_top_cmd =
|
||||
readl(host->top_base + EMMC_TOP_CMD);
|
||||
} else {
|
||||
host->def_tune_para.pad_tune = readl(host->base + tune_reg);
|
||||
host->saved_tune_para.pad_tune = readl(host->base + tune_reg);
|
||||
}
|
||||
dev_dbg(host->dev, "init hardware done!");
|
||||
}
|
||||
|
||||
|
@ -1563,6 +1661,30 @@ static struct msdc_delay_phase get_best_delay(struct msdc_host *host, u32 delay)
|
|||
return delay_phase;
|
||||
}
|
||||
|
||||
static inline void msdc_set_cmd_delay(struct msdc_host *host, u32 value)
|
||||
{
|
||||
u32 tune_reg = host->dev_comp->pad_tune_reg;
|
||||
|
||||
if (host->top_base)
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CMD, PAD_CMD_RXDLY,
|
||||
value);
|
||||
else
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_CMDRDLY,
|
||||
value);
|
||||
}
|
||||
|
||||
static inline void msdc_set_data_delay(struct msdc_host *host, u32 value)
|
||||
{
|
||||
u32 tune_reg = host->dev_comp->pad_tune_reg;
|
||||
|
||||
if (host->top_base)
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CONTROL,
|
||||
PAD_DAT_RD_RXDLY, value);
|
||||
else
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_DATRRDLY,
|
||||
value);
|
||||
}
|
||||
|
||||
static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
|
||||
{
|
||||
struct msdc_host *host = mmc_priv(mmc);
|
||||
|
@ -1583,8 +1705,7 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
|
|||
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
for (i = 0 ; i < PAD_DELAY_MAX; i++) {
|
||||
sdr_set_field(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_CMDRDLY, i);
|
||||
msdc_set_cmd_delay(host, i);
|
||||
/*
|
||||
* Using the same parameters, it may sometimes pass the test,
|
||||
* but sometimes it may fail. To make sure the parameters are
|
||||
|
@ -1608,8 +1729,7 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
|
|||
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
for (i = 0; i < PAD_DELAY_MAX; i++) {
|
||||
sdr_set_field(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_CMDRDLY, i);
|
||||
msdc_set_cmd_delay(host, i);
|
||||
/*
|
||||
* Using the same parameters, it may sometimes pass the test,
|
||||
* but sometimes it may fail. To make sure the parameters are
|
||||
|
@ -1633,15 +1753,13 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
|
|||
final_maxlen = final_fall_delay.maxlen;
|
||||
if (final_maxlen == final_rise_delay.maxlen) {
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_CMDRDLY,
|
||||
final_rise_delay.final_phase);
|
||||
final_delay = final_rise_delay.final_phase;
|
||||
} else {
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_CMDRDLY,
|
||||
final_fall_delay.final_phase);
|
||||
final_delay = final_fall_delay.final_phase;
|
||||
}
|
||||
msdc_set_cmd_delay(host, final_delay);
|
||||
|
||||
if (host->dev_comp->async_fifo || host->hs200_cmd_int_delay)
|
||||
goto skip_internal;
|
||||
|
||||
|
@ -1716,7 +1834,6 @@ static int msdc_tune_data(struct mmc_host *mmc, u32 opcode)
|
|||
u32 rise_delay = 0, fall_delay = 0;
|
||||
struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0,};
|
||||
u8 final_delay, final_maxlen;
|
||||
u32 tune_reg = host->dev_comp->pad_tune_reg;
|
||||
int i, ret;
|
||||
|
||||
sdr_set_field(host->base + MSDC_PATCH_BIT, MSDC_INT_DAT_LATCH_CK_SEL,
|
||||
|
@ -1724,8 +1841,7 @@ static int msdc_tune_data(struct mmc_host *mmc, u32 opcode)
|
|||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL);
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL);
|
||||
for (i = 0 ; i < PAD_DELAY_MAX; i++) {
|
||||
sdr_set_field(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_DATRRDLY, i);
|
||||
msdc_set_data_delay(host, i);
|
||||
ret = mmc_send_tuning(mmc, opcode, NULL);
|
||||
if (!ret)
|
||||
rise_delay |= (1 << i);
|
||||
|
@ -1739,8 +1855,7 @@ static int msdc_tune_data(struct mmc_host *mmc, u32 opcode)
|
|||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL);
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL);
|
||||
for (i = 0; i < PAD_DELAY_MAX; i++) {
|
||||
sdr_set_field(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_DATRRDLY, i);
|
||||
msdc_set_data_delay(host, i);
|
||||
ret = mmc_send_tuning(mmc, opcode, NULL);
|
||||
if (!ret)
|
||||
fall_delay |= (1 << i);
|
||||
|
@ -1752,20 +1867,79 @@ static int msdc_tune_data(struct mmc_host *mmc, u32 opcode)
|
|||
if (final_maxlen == final_rise_delay.maxlen) {
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL);
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL);
|
||||
sdr_set_field(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_DATRRDLY,
|
||||
final_rise_delay.final_phase);
|
||||
final_delay = final_rise_delay.final_phase;
|
||||
} else {
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL);
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL);
|
||||
sdr_set_field(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_DATRRDLY,
|
||||
final_fall_delay.final_phase);
|
||||
final_delay = final_fall_delay.final_phase;
|
||||
}
|
||||
msdc_set_data_delay(host, final_delay);
|
||||
|
||||
dev_dbg(host->dev, "Final data pad delay: %x\n", final_delay);
|
||||
return final_delay == 0xff ? -EIO : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* MSDC IP which supports data tune + async fifo can do CMD/DAT tune
|
||||
* together, which can save the tuning time.
|
||||
*/
|
||||
static int msdc_tune_together(struct mmc_host *mmc, u32 opcode)
|
||||
{
|
||||
struct msdc_host *host = mmc_priv(mmc);
|
||||
u32 rise_delay = 0, fall_delay = 0;
|
||||
struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0,};
|
||||
u8 final_delay, final_maxlen;
|
||||
int i, ret;
|
||||
|
||||
sdr_set_field(host->base + MSDC_PATCH_BIT, MSDC_INT_DAT_LATCH_CK_SEL,
|
||||
host->latch_ck);
|
||||
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
sdr_clr_bits(host->base + MSDC_IOCON,
|
||||
MSDC_IOCON_DSPL | MSDC_IOCON_W_DSPL);
|
||||
for (i = 0 ; i < PAD_DELAY_MAX; i++) {
|
||||
msdc_set_cmd_delay(host, i);
|
||||
msdc_set_data_delay(host, i);
|
||||
ret = mmc_send_tuning(mmc, opcode, NULL);
|
||||
if (!ret)
|
||||
rise_delay |= (1 << i);
|
||||
}
|
||||
final_rise_delay = get_best_delay(host, rise_delay);
|
||||
/* if rising edge has enough margin, then do not scan falling edge */
|
||||
if (final_rise_delay.maxlen >= 12 ||
|
||||
(final_rise_delay.start == 0 && final_rise_delay.maxlen >= 4))
|
||||
goto skip_fall;
|
||||
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
sdr_set_bits(host->base + MSDC_IOCON,
|
||||
MSDC_IOCON_DSPL | MSDC_IOCON_W_DSPL);
|
||||
for (i = 0; i < PAD_DELAY_MAX; i++) {
|
||||
msdc_set_cmd_delay(host, i);
|
||||
msdc_set_data_delay(host, i);
|
||||
ret = mmc_send_tuning(mmc, opcode, NULL);
|
||||
if (!ret)
|
||||
fall_delay |= (1 << i);
|
||||
}
|
||||
final_fall_delay = get_best_delay(host, fall_delay);
|
||||
|
||||
skip_fall:
|
||||
final_maxlen = max(final_rise_delay.maxlen, final_fall_delay.maxlen);
|
||||
if (final_maxlen == final_rise_delay.maxlen) {
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
sdr_clr_bits(host->base + MSDC_IOCON,
|
||||
MSDC_IOCON_DSPL | MSDC_IOCON_W_DSPL);
|
||||
final_delay = final_rise_delay.final_phase;
|
||||
} else {
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
sdr_set_bits(host->base + MSDC_IOCON,
|
||||
MSDC_IOCON_DSPL | MSDC_IOCON_W_DSPL);
|
||||
final_delay = final_fall_delay.final_phase;
|
||||
}
|
||||
|
||||
dev_dbg(host->dev, "Final data pad delay: %x\n", final_delay);
|
||||
msdc_set_cmd_delay(host, final_delay);
|
||||
msdc_set_data_delay(host, final_delay);
|
||||
|
||||
dev_dbg(host->dev, "Final pad delay: %x\n", final_delay);
|
||||
return final_delay == 0xff ? -EIO : 0;
|
||||
}
|
||||
|
||||
|
@ -1775,6 +1949,15 @@ static int msdc_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|||
int ret;
|
||||
u32 tune_reg = host->dev_comp->pad_tune_reg;
|
||||
|
||||
if (host->dev_comp->data_tune && host->dev_comp->async_fifo) {
|
||||
ret = msdc_tune_together(mmc, opcode);
|
||||
if (host->hs400_mode) {
|
||||
sdr_clr_bits(host->base + MSDC_IOCON,
|
||||
MSDC_IOCON_DSPL | MSDC_IOCON_W_DSPL);
|
||||
msdc_set_data_delay(host, 0);
|
||||
}
|
||||
goto tune_done;
|
||||
}
|
||||
if (host->hs400_mode &&
|
||||
host->dev_comp->hs400_tune)
|
||||
ret = hs400_tune_response(mmc, opcode);
|
||||
|
@ -1790,9 +1973,16 @@ static int msdc_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|||
dev_err(host->dev, "Tune data fail!\n");
|
||||
}
|
||||
|
||||
tune_done:
|
||||
host->saved_tune_para.iocon = readl(host->base + MSDC_IOCON);
|
||||
host->saved_tune_para.pad_tune = readl(host->base + tune_reg);
|
||||
host->saved_tune_para.pad_cmd_tune = readl(host->base + PAD_CMD_TUNE);
|
||||
if (host->top_base) {
|
||||
host->saved_tune_para.emmc_top_control = readl(host->top_base +
|
||||
EMMC_TOP_CONTROL);
|
||||
host->saved_tune_para.emmc_top_cmd = readl(host->top_base +
|
||||
EMMC_TOP_CMD);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1801,7 +1991,11 @@ static int msdc_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios)
|
|||
struct msdc_host *host = mmc_priv(mmc);
|
||||
host->hs400_mode = true;
|
||||
|
||||
writel(host->hs400_ds_delay, host->base + PAD_DS_TUNE);
|
||||
if (host->top_base)
|
||||
writel(host->hs400_ds_delay,
|
||||
host->top_base + EMMC50_PAD_DS_TUNE);
|
||||
else
|
||||
writel(host->hs400_ds_delay, host->base + PAD_DS_TUNE);
|
||||
/* hs400 mode must set it to 0 */
|
||||
sdr_clr_bits(host->base + MSDC_PATCH_BIT2, MSDC_PATCH_BIT2_CFGCRCSTS);
|
||||
/* to improve read performance, set outstanding to 2 */
|
||||
|
@ -1884,6 +2078,11 @@ static int msdc_drv_probe(struct platform_device *pdev)
|
|||
goto host_free;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
host->top_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(host->top_base))
|
||||
host->top_base = NULL;
|
||||
|
||||
ret = mmc_regulator_get_supply(mmc);
|
||||
if (ret)
|
||||
goto host_free;
|
||||
|
@ -1900,6 +2099,9 @@ static int msdc_drv_probe(struct platform_device *pdev)
|
|||
goto host_free;
|
||||
}
|
||||
|
||||
host->bus_clk = devm_clk_get(&pdev->dev, "bus_clk");
|
||||
if (IS_ERR(host->bus_clk))
|
||||
host->bus_clk = NULL;
|
||||
/*source clock control gate is optional clock*/
|
||||
host->src_clk_cg = devm_clk_get(&pdev->dev, "source_cg");
|
||||
if (IS_ERR(host->src_clk_cg))
|
||||
|
@ -2049,7 +2251,6 @@ static void msdc_save_reg(struct msdc_host *host)
|
|||
host->save_para.msdc_cfg = readl(host->base + MSDC_CFG);
|
||||
host->save_para.iocon = readl(host->base + MSDC_IOCON);
|
||||
host->save_para.sdc_cfg = readl(host->base + SDC_CFG);
|
||||
host->save_para.pad_tune = readl(host->base + tune_reg);
|
||||
host->save_para.patch_bit0 = readl(host->base + MSDC_PATCH_BIT);
|
||||
host->save_para.patch_bit1 = readl(host->base + MSDC_PATCH_BIT1);
|
||||
host->save_para.patch_bit2 = readl(host->base + MSDC_PATCH_BIT2);
|
||||
|
@ -2058,6 +2259,16 @@ static void msdc_save_reg(struct msdc_host *host)
|
|||
host->save_para.emmc50_cfg0 = readl(host->base + EMMC50_CFG0);
|
||||
host->save_para.emmc50_cfg3 = readl(host->base + EMMC50_CFG3);
|
||||
host->save_para.sdc_fifo_cfg = readl(host->base + SDC_FIFO_CFG);
|
||||
if (host->top_base) {
|
||||
host->save_para.emmc_top_control =
|
||||
readl(host->top_base + EMMC_TOP_CONTROL);
|
||||
host->save_para.emmc_top_cmd =
|
||||
readl(host->top_base + EMMC_TOP_CMD);
|
||||
host->save_para.emmc50_pad_ds_tune =
|
||||
readl(host->top_base + EMMC50_PAD_DS_TUNE);
|
||||
} else {
|
||||
host->save_para.pad_tune = readl(host->base + tune_reg);
|
||||
}
|
||||
}
|
||||
|
||||
static void msdc_restore_reg(struct msdc_host *host)
|
||||
|
@ -2067,7 +2278,6 @@ static void msdc_restore_reg(struct msdc_host *host)
|
|||
writel(host->save_para.msdc_cfg, host->base + MSDC_CFG);
|
||||
writel(host->save_para.iocon, host->base + MSDC_IOCON);
|
||||
writel(host->save_para.sdc_cfg, host->base + SDC_CFG);
|
||||
writel(host->save_para.pad_tune, host->base + tune_reg);
|
||||
writel(host->save_para.patch_bit0, host->base + MSDC_PATCH_BIT);
|
||||
writel(host->save_para.patch_bit1, host->base + MSDC_PATCH_BIT1);
|
||||
writel(host->save_para.patch_bit2, host->base + MSDC_PATCH_BIT2);
|
||||
|
@ -2076,6 +2286,16 @@ static void msdc_restore_reg(struct msdc_host *host)
|
|||
writel(host->save_para.emmc50_cfg0, host->base + EMMC50_CFG0);
|
||||
writel(host->save_para.emmc50_cfg3, host->base + EMMC50_CFG3);
|
||||
writel(host->save_para.sdc_fifo_cfg, host->base + SDC_FIFO_CFG);
|
||||
if (host->top_base) {
|
||||
writel(host->save_para.emmc_top_control,
|
||||
host->top_base + EMMC_TOP_CONTROL);
|
||||
writel(host->save_para.emmc_top_cmd,
|
||||
host->top_base + EMMC_TOP_CMD);
|
||||
writel(host->save_para.emmc50_pad_ds_tune,
|
||||
host->top_base + EMMC50_PAD_DS_TUNE);
|
||||
} else {
|
||||
writel(host->save_para.pad_tune, host->base + tune_reg);
|
||||
}
|
||||
}
|
||||
|
||||
static int msdc_runtime_suspend(struct device *dev)
|
||||
|
|
|
@ -728,7 +728,6 @@ static void mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat)
|
|||
static irqreturn_t mxcmci_irq(int irq, void *devid)
|
||||
{
|
||||
struct mxcmci_host *host = devid;
|
||||
unsigned long flags;
|
||||
bool sdio_irq;
|
||||
u32 stat;
|
||||
|
||||
|
@ -740,9 +739,9 @@ static irqreturn_t mxcmci_irq(int irq, void *devid)
|
|||
|
||||
dev_dbg(mmc_dev(host->mmc), "%s: 0x%08x\n", __func__, stat);
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
spin_lock(&host->lock);
|
||||
sdio_irq = (stat & STATUS_SDIO_INT_ACTIVE) && host->use_sdio;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
spin_unlock(&host->lock);
|
||||
|
||||
if (mxcmci_use_dma(host) && (stat & (STATUS_WRITE_OP_DONE)))
|
||||
mxcmci_writel(host, STATUS_WRITE_OP_DONE, MMC_REG_STATUS);
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/core.h>
|
||||
|
@ -38,7 +37,6 @@
|
|||
#include <linux/mmc/slot-gpio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
@ -198,7 +196,6 @@ struct omap_hsmmc_host {
|
|||
struct dma_chan *rx_chan;
|
||||
int response_busy;
|
||||
int context_loss;
|
||||
int protect_card;
|
||||
int reqs_blocked;
|
||||
int req_in_progress;
|
||||
unsigned long clk_rate;
|
||||
|
@ -207,16 +204,6 @@ struct omap_hsmmc_host {
|
|||
#define HSMMC_SDIO_IRQ_ENABLED (1 << 1) /* SDIO irq enabled */
|
||||
struct omap_hsmmc_next next_data;
|
||||
struct omap_hsmmc_platform_data *pdata;
|
||||
|
||||
/* return MMC cover switch state, can be NULL if not supported.
|
||||
*
|
||||
* possible return values:
|
||||
* 0 - closed
|
||||
* 1 - open
|
||||
*/
|
||||
int (*get_cover_state)(struct device *dev);
|
||||
|
||||
int (*card_detect)(struct device *dev);
|
||||
};
|
||||
|
||||
struct omap_mmc_of_data {
|
||||
|
@ -226,20 +213,6 @@ struct omap_mmc_of_data {
|
|||
|
||||
static void omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host);
|
||||
|
||||
static int omap_hsmmc_card_detect(struct device *dev)
|
||||
{
|
||||
struct omap_hsmmc_host *host = dev_get_drvdata(dev);
|
||||
|
||||
return mmc_gpio_get_cd(host->mmc);
|
||||
}
|
||||
|
||||
static int omap_hsmmc_get_cover_state(struct device *dev)
|
||||
{
|
||||
struct omap_hsmmc_host *host = dev_get_drvdata(dev);
|
||||
|
||||
return mmc_gpio_get_cd(host->mmc);
|
||||
}
|
||||
|
||||
static int omap_hsmmc_enable_supply(struct mmc_host *mmc)
|
||||
{
|
||||
int ret;
|
||||
|
@ -484,38 +457,6 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t omap_hsmmc_cover_irq(int irq, void *dev_id);
|
||||
|
||||
static int omap_hsmmc_gpio_init(struct mmc_host *mmc,
|
||||
struct omap_hsmmc_host *host,
|
||||
struct omap_hsmmc_platform_data *pdata)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (gpio_is_valid(pdata->gpio_cod)) {
|
||||
ret = mmc_gpio_request_cd(mmc, pdata->gpio_cod, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
host->get_cover_state = omap_hsmmc_get_cover_state;
|
||||
mmc_gpio_set_cd_isr(mmc, omap_hsmmc_cover_irq);
|
||||
} else if (gpio_is_valid(pdata->gpio_cd)) {
|
||||
ret = mmc_gpio_request_cd(mmc, pdata->gpio_cd, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
host->card_detect = omap_hsmmc_card_detect;
|
||||
}
|
||||
|
||||
if (gpio_is_valid(pdata->gpio_wp)) {
|
||||
ret = mmc_gpio_request_ro(mmc, pdata->gpio_wp);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start clock to the card
|
||||
*/
|
||||
|
@ -781,9 +722,6 @@ static void send_init_stream(struct omap_hsmmc_host *host)
|
|||
int reg = 0;
|
||||
unsigned long timeout;
|
||||
|
||||
if (host->protect_card)
|
||||
return;
|
||||
|
||||
disable_irq(host->irq);
|
||||
|
||||
OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
|
||||
|
@ -804,29 +742,6 @@ static void send_init_stream(struct omap_hsmmc_host *host)
|
|||
enable_irq(host->irq);
|
||||
}
|
||||
|
||||
static inline
|
||||
int omap_hsmmc_cover_is_closed(struct omap_hsmmc_host *host)
|
||||
{
|
||||
int r = 1;
|
||||
|
||||
if (host->get_cover_state)
|
||||
r = host->get_cover_state(host->dev);
|
||||
return r;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
omap_hsmmc_show_cover_switch(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
|
||||
struct omap_hsmmc_host *host = mmc_priv(mmc);
|
||||
|
||||
return sprintf(buf, "%s\n",
|
||||
omap_hsmmc_cover_is_closed(host) ? "closed" : "open");
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(cover_switch, S_IRUGO, omap_hsmmc_show_cover_switch, NULL);
|
||||
|
||||
static ssize_t
|
||||
omap_hsmmc_show_slot_name(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
|
@ -1247,44 +1162,6 @@ static int omap_hsmmc_switch_opcond(struct omap_hsmmc_host *host, int vdd)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Protect the card while the cover is open */
|
||||
static void omap_hsmmc_protect_card(struct omap_hsmmc_host *host)
|
||||
{
|
||||
if (!host->get_cover_state)
|
||||
return;
|
||||
|
||||
host->reqs_blocked = 0;
|
||||
if (host->get_cover_state(host->dev)) {
|
||||
if (host->protect_card) {
|
||||
dev_info(host->dev, "%s: cover is closed, "
|
||||
"card is now accessible\n",
|
||||
mmc_hostname(host->mmc));
|
||||
host->protect_card = 0;
|
||||
}
|
||||
} else {
|
||||
if (!host->protect_card) {
|
||||
dev_info(host->dev, "%s: cover is open, "
|
||||
"card is now inaccessible\n",
|
||||
mmc_hostname(host->mmc));
|
||||
host->protect_card = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* irq handler when (cell-phone) cover is mounted/removed
|
||||
*/
|
||||
static irqreturn_t omap_hsmmc_cover_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct omap_hsmmc_host *host = dev_id;
|
||||
|
||||
sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch");
|
||||
|
||||
omap_hsmmc_protect_card(host);
|
||||
mmc_detect_change(host->mmc, (HZ * 200) / 1000);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void omap_hsmmc_dma_callback(void *param)
|
||||
{
|
||||
struct omap_hsmmc_host *host = param;
|
||||
|
@ -1555,24 +1432,7 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req)
|
|||
|
||||
BUG_ON(host->req_in_progress);
|
||||
BUG_ON(host->dma_ch != -1);
|
||||
if (host->protect_card) {
|
||||
if (host->reqs_blocked < 3) {
|
||||
/*
|
||||
* Ensure the controller is left in a consistent
|
||||
* state by resetting the command and data state
|
||||
* machines.
|
||||
*/
|
||||
omap_hsmmc_reset_controller_fsm(host, SRD);
|
||||
omap_hsmmc_reset_controller_fsm(host, SRC);
|
||||
host->reqs_blocked += 1;
|
||||
}
|
||||
req->cmd->error = -EBADF;
|
||||
if (req->data)
|
||||
req->data->error = -EBADF;
|
||||
req->cmd->retries = 0;
|
||||
mmc_request_done(mmc, req);
|
||||
return;
|
||||
} else if (host->reqs_blocked)
|
||||
if (host->reqs_blocked)
|
||||
host->reqs_blocked = 0;
|
||||
WARN_ON(host->mrq != NULL);
|
||||
host->mrq = req;
|
||||
|
@ -1646,15 +1506,6 @@ static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|||
omap_hsmmc_set_bus_mode(host);
|
||||
}
|
||||
|
||||
static int omap_hsmmc_get_cd(struct mmc_host *mmc)
|
||||
{
|
||||
struct omap_hsmmc_host *host = mmc_priv(mmc);
|
||||
|
||||
if (!host->card_detect)
|
||||
return -ENOSYS;
|
||||
return host->card_detect(host->dev);
|
||||
}
|
||||
|
||||
static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_card *card)
|
||||
{
|
||||
struct omap_hsmmc_host *host = mmc_priv(mmc);
|
||||
|
@ -1793,7 +1644,7 @@ static struct mmc_host_ops omap_hsmmc_ops = {
|
|||
.pre_req = omap_hsmmc_pre_req,
|
||||
.request = omap_hsmmc_request,
|
||||
.set_ios = omap_hsmmc_set_ios,
|
||||
.get_cd = omap_hsmmc_get_cd,
|
||||
.get_cd = mmc_gpio_get_cd,
|
||||
.get_ro = mmc_gpio_get_ro,
|
||||
.init_card = omap_hsmmc_init_card,
|
||||
.enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
|
||||
|
@ -1920,10 +1771,6 @@ static struct omap_hsmmc_platform_data *of_get_hsmmc_pdata(struct device *dev)
|
|||
if (of_find_property(np, "ti,dual-volt", NULL))
|
||||
pdata->controller_flags |= OMAP_HSMMC_SUPPORTS_DUAL_VOLT;
|
||||
|
||||
pdata->gpio_cd = -EINVAL;
|
||||
pdata->gpio_cod = -EINVAL;
|
||||
pdata->gpio_wp = -EINVAL;
|
||||
|
||||
if (of_find_property(np, "ti,non-removable", NULL)) {
|
||||
pdata->nonremovable = true;
|
||||
pdata->no_regulator_off_init = true;
|
||||
|
@ -2008,10 +1855,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|||
host->pbias_enabled = 0;
|
||||
host->vqmmc_enabled = 0;
|
||||
|
||||
ret = omap_hsmmc_gpio_init(mmc, host, pdata);
|
||||
if (ret)
|
||||
goto err_gpio;
|
||||
|
||||
platform_set_drvdata(pdev, host);
|
||||
|
||||
if (pdev->dev.of_node)
|
||||
|
@ -2125,8 +1968,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|||
if (!ret)
|
||||
mmc->caps |= MMC_CAP_SDIO_IRQ;
|
||||
|
||||
omap_hsmmc_protect_card(host);
|
||||
|
||||
mmc_add_host(mmc);
|
||||
|
||||
if (mmc_pdata(host)->name != NULL) {
|
||||
|
@ -2134,12 +1975,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|||
if (ret < 0)
|
||||
goto err_slot_name;
|
||||
}
|
||||
if (host->get_cover_state) {
|
||||
ret = device_create_file(&mmc->class_dev,
|
||||
&dev_attr_cover_switch);
|
||||
if (ret < 0)
|
||||
goto err_slot_name;
|
||||
}
|
||||
|
||||
omap_hsmmc_debugfs(mmc);
|
||||
pm_runtime_mark_last_busy(host->dev);
|
||||
|
@ -2161,7 +1996,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|||
if (host->dbclk)
|
||||
clk_disable_unprepare(host->dbclk);
|
||||
err1:
|
||||
err_gpio:
|
||||
mmc_free_host(mmc);
|
||||
err:
|
||||
return ret;
|
||||
|
@ -2231,7 +2065,6 @@ static int omap_hsmmc_resume(struct device *dev)
|
|||
if (!(host->mmc->pm_flags & MMC_PM_KEEP_POWER))
|
||||
omap_hsmmc_conf_bus_power(host);
|
||||
|
||||
omap_hsmmc_protect_card(host);
|
||||
pm_runtime_mark_last_busy(host->dev);
|
||||
pm_runtime_put_autosuspend(host->dev);
|
||||
return 0;
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Renesas Mobile SDHI
|
||||
*
|
||||
* Copyright (C) 2017 Horms Solutions Ltd., Simon Horman
|
||||
* Copyright (C) 2017 Renesas Electronics Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef RENESAS_SDHI_H
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Renesas SDHI
|
||||
*
|
||||
|
@ -6,10 +7,6 @@
|
|||
* Copyright (C) 2016-17 Horms Solutions, Simon Horman
|
||||
* Copyright (C) 2009 Magnus Damm
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Based on "Compaq ASIC3 support":
|
||||
*
|
||||
* Copyright 2001 Compaq Computer Corporation.
|
||||
|
@ -155,6 +152,52 @@ static unsigned int renesas_sdhi_clk_update(struct tmio_mmc_host *host,
|
|||
return ret == 0 ? best_freq : clk_get_rate(priv->clk);
|
||||
}
|
||||
|
||||
static void renesas_sdhi_set_clock(struct tmio_mmc_host *host,
|
||||
unsigned int new_clock)
|
||||
{
|
||||
u32 clk = 0, clock;
|
||||
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
|
||||
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
||||
|
||||
if (new_clock == 0)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Both HS400 and HS200/SD104 set 200MHz, but some devices need to
|
||||
* set 400MHz to distinguish the CPG settings in HS400.
|
||||
*/
|
||||
if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400 &&
|
||||
host->pdata->flags & TMIO_MMC_HAVE_4TAP_HS400 &&
|
||||
new_clock == 200000000)
|
||||
new_clock = 400000000;
|
||||
|
||||
clock = renesas_sdhi_clk_update(host, new_clock) / 512;
|
||||
|
||||
for (clk = 0x80000080; new_clock >= (clock << 1); clk >>= 1)
|
||||
clock <<= 1;
|
||||
|
||||
/* 1/1 clock is option */
|
||||
if ((host->pdata->flags & TMIO_MMC_CLK_ACTUAL) && ((clk >> 22) & 0x1)) {
|
||||
if (!(host->mmc->ios.timing == MMC_TIMING_MMC_HS400))
|
||||
clk |= 0xff;
|
||||
else
|
||||
clk &= ~0xff;
|
||||
}
|
||||
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, clk & CLK_CTL_DIV_MASK);
|
||||
if (!(host->pdata->flags & TMIO_MMC_MIN_RCAR2))
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
|
||||
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
||||
|
||||
out:
|
||||
/* HW engineers overrode docs: no sleep needed on R-Car2+ */
|
||||
if (!(host->pdata->flags & TMIO_MMC_MIN_RCAR2))
|
||||
usleep_range(10000, 11000);
|
||||
}
|
||||
|
||||
static void renesas_sdhi_clk_disable(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct renesas_sdhi *priv = host_to_priv(host);
|
||||
|
@ -443,6 +486,19 @@ static int renesas_sdhi_select_tuning(struct tmio_mmc_host *host)
|
|||
static bool renesas_sdhi_check_scc_error(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct renesas_sdhi *priv = host_to_priv(host);
|
||||
bool use_4tap = host->pdata->flags & TMIO_MMC_HAVE_4TAP_HS400;
|
||||
|
||||
/*
|
||||
* Skip checking SCC errors when running on 4 taps in HS400 mode as
|
||||
* any retuning would still result in the same 4 taps being used.
|
||||
*/
|
||||
if (!(host->mmc->ios.timing == MMC_TIMING_UHS_SDR104) &&
|
||||
!(host->mmc->ios.timing == MMC_TIMING_MMC_HS200) &&
|
||||
!(host->mmc->ios.timing == MMC_TIMING_MMC_HS400 && !use_4tap))
|
||||
return false;
|
||||
|
||||
if (mmc_doing_retune(host->mmc))
|
||||
return false;
|
||||
|
||||
/* Check SCC error */
|
||||
if (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL) &
|
||||
|
@ -620,8 +676,8 @@ int renesas_sdhi_probe(struct platform_device *pdev,
|
|||
|
||||
host->write16_hook = renesas_sdhi_write16_hook;
|
||||
host->clk_enable = renesas_sdhi_clk_enable;
|
||||
host->clk_update = renesas_sdhi_clk_update;
|
||||
host->clk_disable = renesas_sdhi_clk_disable;
|
||||
host->set_clock = renesas_sdhi_set_clock;
|
||||
host->multi_io_quirk = renesas_sdhi_multi_io_quirk;
|
||||
host->dma_ops = dma_ops;
|
||||
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* DMA support for Internal DMAC with SDHI SD/SDIO controller
|
||||
*
|
||||
* Copyright (C) 2016-17 Renesas Electronics Corporation
|
||||
* Copyright (C) 2016-17 Horms Solutions, Simon Horman
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
|
@ -35,8 +32,8 @@
|
|||
|
||||
/* DM_CM_DTRAN_MODE */
|
||||
#define DTRAN_MODE_CH_NUM_CH0 0 /* "downstream" = for write commands */
|
||||
#define DTRAN_MODE_CH_NUM_CH1 BIT(16) /* "uptream" = for read commands */
|
||||
#define DTRAN_MODE_BUS_WID_TH (BIT(5) | BIT(4))
|
||||
#define DTRAN_MODE_CH_NUM_CH1 BIT(16) /* "upstream" = for read commands */
|
||||
#define DTRAN_MODE_BUS_WIDTH (BIT(5) | BIT(4))
|
||||
#define DTRAN_MODE_ADDR_MODE BIT(0) /* 1 = Increment address */
|
||||
|
||||
/* DM_CM_DTRAN_CTRL */
|
||||
|
@ -116,6 +113,7 @@ static const struct renesas_sdhi_of_data of_rcar_gen3_compatible = {
|
|||
};
|
||||
|
||||
static const struct of_device_id renesas_sdhi_internal_dmac_of_match[] = {
|
||||
{ .compatible = "renesas,sdhi-mmc-r8a77470", .data = &of_rcar_gen3_compatible, },
|
||||
{ .compatible = "renesas,sdhi-r8a7795", .data = &of_rcar_r8a7795_compatible, },
|
||||
{ .compatible = "renesas,sdhi-r8a7796", .data = &of_rcar_r8a7795_compatible, },
|
||||
{ .compatible = "renesas,rcar-gen3-sdhi", .data = &of_rcar_gen3_compatible, },
|
||||
|
@ -174,7 +172,7 @@ renesas_sdhi_internal_dmac_start_dma(struct tmio_mmc_host *host,
|
|||
struct mmc_data *data)
|
||||
{
|
||||
struct scatterlist *sg = host->sg_ptr;
|
||||
u32 dtran_mode = DTRAN_MODE_BUS_WID_TH | DTRAN_MODE_ADDR_MODE;
|
||||
u32 dtran_mode = DTRAN_MODE_BUS_WIDTH | DTRAN_MODE_ADDR_MODE;
|
||||
|
||||
if (!dma_map_sg(&host->pdev->dev, sg, host->sg_len,
|
||||
mmc_get_dma_dir(data)))
|
||||
|
@ -201,13 +199,14 @@ renesas_sdhi_internal_dmac_start_dma(struct tmio_mmc_host *host,
|
|||
renesas_sdhi_internal_dmac_dm_write(host, DM_DTRAN_ADDR,
|
||||
sg_dma_address(sg));
|
||||
|
||||
host->dma_on = true;
|
||||
|
||||
return;
|
||||
|
||||
force_pio_with_unmap:
|
||||
dma_unmap_sg(&host->pdev->dev, sg, host->sg_len, mmc_get_dma_dir(data));
|
||||
|
||||
force_pio:
|
||||
host->force_pio = true;
|
||||
renesas_sdhi_internal_dmac_enable_dma(host, false);
|
||||
}
|
||||
|
||||
|
@ -291,16 +290,19 @@ static const struct tmio_mmc_dma_ops renesas_sdhi_internal_dmac_dma_ops = {
|
|||
* Whitelist of specific R-Car Gen3 SoC ES versions to use this DMAC
|
||||
* implementation as others may use a different implementation.
|
||||
*/
|
||||
static const struct soc_device_attribute gen3_soc_whitelist[] = {
|
||||
static const struct soc_device_attribute soc_whitelist[] = {
|
||||
/* specific ones */
|
||||
{ .soc_id = "r8a7795", .revision = "ES1.*",
|
||||
.data = (void *)BIT(SDHI_INTERNAL_DMAC_ONE_RX_ONLY) },
|
||||
{ .soc_id = "r8a7796", .revision = "ES1.0",
|
||||
.data = (void *)BIT(SDHI_INTERNAL_DMAC_ONE_RX_ONLY) },
|
||||
/* generic ones */
|
||||
{ .soc_id = "r8a774a1" },
|
||||
{ .soc_id = "r8a77470" },
|
||||
{ .soc_id = "r8a7795" },
|
||||
{ .soc_id = "r8a7796" },
|
||||
{ .soc_id = "r8a77965" },
|
||||
{ .soc_id = "r8a77970" },
|
||||
{ .soc_id = "r8a77980" },
|
||||
{ .soc_id = "r8a77995" },
|
||||
{ /* sentinel */ }
|
||||
|
@ -308,13 +310,21 @@ static const struct soc_device_attribute gen3_soc_whitelist[] = {
|
|||
|
||||
static int renesas_sdhi_internal_dmac_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct soc_device_attribute *soc = soc_device_match(gen3_soc_whitelist);
|
||||
const struct soc_device_attribute *soc = soc_device_match(soc_whitelist);
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
if (!soc)
|
||||
return -ENODEV;
|
||||
|
||||
global_flags |= (unsigned long)soc->data;
|
||||
|
||||
dev->dma_parms = devm_kzalloc(dev, sizeof(*dev->dma_parms), GFP_KERNEL);
|
||||
if (!dev->dma_parms)
|
||||
return -ENOMEM;
|
||||
|
||||
/* value is max of SD_SECCNT. Confirmed by HW engineers */
|
||||
dma_set_max_seg_size(dev, 0xffffffff);
|
||||
|
||||
return renesas_sdhi_probe(pdev, &renesas_sdhi_internal_dmac_dma_ops);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* DMA support use of SYS DMAC with SDHI SD/SDIO controller
|
||||
*
|
||||
|
@ -5,10 +6,6 @@
|
|||
* Copyright (C) 2016-17 Sang Engineering, Wolfram Sang
|
||||
* Copyright (C) 2017 Horms Solutions, Simon Horman
|
||||
* Copyright (C) 2010-2011 Guennadi Liakhovetski
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
|
@ -213,10 +210,8 @@ static void renesas_sdhi_sys_dmac_start_dma_rx(struct tmio_mmc_host *host)
|
|||
goto pio;
|
||||
}
|
||||
|
||||
if (sg->length < TMIO_MMC_MIN_DMA_LEN) {
|
||||
host->force_pio = true;
|
||||
if (sg->length < TMIO_MMC_MIN_DMA_LEN)
|
||||
return;
|
||||
}
|
||||
|
||||
/* The only sg element can be unaligned, use our bounce buffer then */
|
||||
if (!aligned) {
|
||||
|
@ -240,6 +235,7 @@ static void renesas_sdhi_sys_dmac_start_dma_rx(struct tmio_mmc_host *host)
|
|||
desc = NULL;
|
||||
ret = cookie;
|
||||
}
|
||||
host->dma_on = true;
|
||||
}
|
||||
pio:
|
||||
if (!desc) {
|
||||
|
@ -286,10 +282,8 @@ static void renesas_sdhi_sys_dmac_start_dma_tx(struct tmio_mmc_host *host)
|
|||
goto pio;
|
||||
}
|
||||
|
||||
if (sg->length < TMIO_MMC_MIN_DMA_LEN) {
|
||||
host->force_pio = true;
|
||||
if (sg->length < TMIO_MMC_MIN_DMA_LEN)
|
||||
return;
|
||||
}
|
||||
|
||||
/* The only sg element can be unaligned, use our bounce buffer then */
|
||||
if (!aligned) {
|
||||
|
@ -318,6 +312,7 @@ static void renesas_sdhi_sys_dmac_start_dma_tx(struct tmio_mmc_host *host)
|
|||
desc = NULL;
|
||||
ret = cookie;
|
||||
}
|
||||
host->dma_on = true;
|
||||
}
|
||||
pio:
|
||||
if (!desc) {
|
||||
|
|
|
@ -76,6 +76,7 @@ struct sdhci_acpi_slot {
|
|||
size_t priv_size;
|
||||
int (*probe_slot)(struct platform_device *, const char *, const char *);
|
||||
int (*remove_slot)(struct platform_device *);
|
||||
int (*free_slot)(struct platform_device *pdev);
|
||||
int (*setup_host)(struct platform_device *pdev);
|
||||
};
|
||||
|
||||
|
@ -470,10 +471,70 @@ static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sd = {
|
|||
.priv_size = sizeof(struct intel_host),
|
||||
};
|
||||
|
||||
#define VENDOR_SPECIFIC_PWRCTL_CLEAR_REG 0x1a8
|
||||
#define VENDOR_SPECIFIC_PWRCTL_CTL_REG 0x1ac
|
||||
static irqreturn_t sdhci_acpi_qcom_handler(int irq, void *ptr)
|
||||
{
|
||||
struct sdhci_host *host = ptr;
|
||||
|
||||
sdhci_writel(host, 0x3, VENDOR_SPECIFIC_PWRCTL_CLEAR_REG);
|
||||
sdhci_writel(host, 0x1, VENDOR_SPECIFIC_PWRCTL_CTL_REG);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int qcom_probe_slot(struct platform_device *pdev, const char *hid,
|
||||
const char *uid)
|
||||
{
|
||||
struct sdhci_acpi_host *c = platform_get_drvdata(pdev);
|
||||
struct sdhci_host *host = c->host;
|
||||
int *irq = sdhci_acpi_priv(c);
|
||||
|
||||
*irq = -EINVAL;
|
||||
|
||||
if (strcmp(hid, "QCOM8051"))
|
||||
return 0;
|
||||
|
||||
*irq = platform_get_irq(pdev, 1);
|
||||
if (*irq < 0)
|
||||
return 0;
|
||||
|
||||
return request_threaded_irq(*irq, NULL, sdhci_acpi_qcom_handler,
|
||||
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
|
||||
"sdhci_qcom", host);
|
||||
}
|
||||
|
||||
static int qcom_free_slot(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct sdhci_acpi_host *c = platform_get_drvdata(pdev);
|
||||
struct sdhci_host *host = c->host;
|
||||
struct acpi_device *adev;
|
||||
int *irq = sdhci_acpi_priv(c);
|
||||
const char *hid;
|
||||
|
||||
adev = ACPI_COMPANION(dev);
|
||||
if (!adev)
|
||||
return -ENODEV;
|
||||
|
||||
hid = acpi_device_hid(adev);
|
||||
if (strcmp(hid, "QCOM8051"))
|
||||
return 0;
|
||||
|
||||
if (*irq < 0)
|
||||
return 0;
|
||||
|
||||
free_irq(*irq, host);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct sdhci_acpi_slot sdhci_acpi_slot_qcom_sd_3v = {
|
||||
.quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION,
|
||||
.quirks2 = SDHCI_QUIRK2_NO_1_8_V,
|
||||
.caps = MMC_CAP_NONREMOVABLE,
|
||||
.priv_size = sizeof(int),
|
||||
.probe_slot = qcom_probe_slot,
|
||||
.free_slot = qcom_free_slot,
|
||||
};
|
||||
|
||||
static const struct sdhci_acpi_slot sdhci_acpi_slot_qcom_sd = {
|
||||
|
@ -756,6 +817,9 @@ static int sdhci_acpi_probe(struct platform_device *pdev)
|
|||
err_cleanup:
|
||||
sdhci_cleanup_host(c->host);
|
||||
err_free:
|
||||
if (c->slot && c->slot->free_slot)
|
||||
c->slot->free_slot(pdev);
|
||||
|
||||
sdhci_free_host(c->host);
|
||||
return err;
|
||||
}
|
||||
|
@ -777,6 +841,10 @@ static int sdhci_acpi_remove(struct platform_device *pdev)
|
|||
|
||||
dead = (sdhci_readl(c->host, SDHCI_INT_STATUS) == ~0);
|
||||
sdhci_remove_host(c->host, dead);
|
||||
|
||||
if (c->slot && c->slot->free_slot)
|
||||
c->slot->free_slot(pdev);
|
||||
|
||||
sdhci_free_host(c->host);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
/* Tuning Block Control Register */
|
||||
#define ESDHC_TBCTL 0x120
|
||||
#define ESDHC_TB_EN 0x00000004
|
||||
#define ESDHC_TBPTR 0x128
|
||||
|
||||
/* Control Register for DMA transfer */
|
||||
#define ESDHC_DMA_SYSCTL 0x40c
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
* iProc SDHCI platform driver
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
@ -162,9 +163,19 @@ static void sdhci_iproc_writeb(struct sdhci_host *host, u8 val, int reg)
|
|||
sdhci_iproc_writel(host, newval, reg & ~3);
|
||||
}
|
||||
|
||||
static unsigned int sdhci_iproc_get_max_clock(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
|
||||
if (pltfm_host->clk)
|
||||
return sdhci_pltfm_clk_get_max_clock(host);
|
||||
else
|
||||
return pltfm_host->clock;
|
||||
}
|
||||
|
||||
static const struct sdhci_ops sdhci_iproc_ops = {
|
||||
.set_clock = sdhci_set_clock,
|
||||
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
|
||||
.get_max_clock = sdhci_iproc_get_max_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.reset = sdhci_reset,
|
||||
.set_uhs_signaling = sdhci_set_uhs_signaling,
|
||||
|
@ -178,7 +189,7 @@ static const struct sdhci_ops sdhci_iproc_32only_ops = {
|
|||
.write_w = sdhci_iproc_writew,
|
||||
.write_b = sdhci_iproc_writeb,
|
||||
.set_clock = sdhci_set_clock,
|
||||
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
|
||||
.get_max_clock = sdhci_iproc_get_max_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.reset = sdhci_reset,
|
||||
.set_uhs_signaling = sdhci_set_uhs_signaling,
|
||||
|
@ -256,19 +267,25 @@ static const struct of_device_id sdhci_iproc_of_match[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(of, sdhci_iproc_of_match);
|
||||
|
||||
static const struct acpi_device_id sdhci_iproc_acpi_ids[] = {
|
||||
{ .id = "BRCM5871", .driver_data = (kernel_ulong_t)&iproc_cygnus_data },
|
||||
{ .id = "BRCM5872", .driver_data = (kernel_ulong_t)&iproc_data },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, sdhci_iproc_acpi_ids);
|
||||
|
||||
static int sdhci_iproc_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct of_device_id *match;
|
||||
const struct sdhci_iproc_data *iproc_data;
|
||||
struct device *dev = &pdev->dev;
|
||||
const struct sdhci_iproc_data *iproc_data = NULL;
|
||||
struct sdhci_host *host;
|
||||
struct sdhci_iproc_host *iproc_host;
|
||||
struct sdhci_pltfm_host *pltfm_host;
|
||||
int ret;
|
||||
|
||||
match = of_match_device(sdhci_iproc_of_match, &pdev->dev);
|
||||
if (!match)
|
||||
return -EINVAL;
|
||||
iproc_data = match->data;
|
||||
iproc_data = device_get_match_data(dev);
|
||||
if (!iproc_data)
|
||||
return -ENODEV;
|
||||
|
||||
host = sdhci_pltfm_init(pdev, iproc_data->pdata, sizeof(*iproc_host));
|
||||
if (IS_ERR(host))
|
||||
|
@ -280,19 +297,21 @@ static int sdhci_iproc_probe(struct platform_device *pdev)
|
|||
iproc_host->data = iproc_data;
|
||||
|
||||
mmc_of_parse(host->mmc);
|
||||
sdhci_get_of_property(pdev);
|
||||
sdhci_get_property(pdev);
|
||||
|
||||
host->mmc->caps |= iproc_host->data->mmc_caps;
|
||||
|
||||
pltfm_host->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(pltfm_host->clk)) {
|
||||
ret = PTR_ERR(pltfm_host->clk);
|
||||
goto err;
|
||||
}
|
||||
ret = clk_prepare_enable(pltfm_host->clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable host clk\n");
|
||||
goto err;
|
||||
if (dev->of_node) {
|
||||
pltfm_host->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(pltfm_host->clk)) {
|
||||
ret = PTR_ERR(pltfm_host->clk);
|
||||
goto err;
|
||||
}
|
||||
ret = clk_prepare_enable(pltfm_host->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable host clk\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (iproc_host->data->pdata->quirks & SDHCI_QUIRK_MISSING_CAPS) {
|
||||
|
@ -307,7 +326,8 @@ static int sdhci_iproc_probe(struct platform_device *pdev)
|
|||
return 0;
|
||||
|
||||
err_clk:
|
||||
clk_disable_unprepare(pltfm_host->clk);
|
||||
if (dev->of_node)
|
||||
clk_disable_unprepare(pltfm_host->clk);
|
||||
err:
|
||||
sdhci_pltfm_free(pdev);
|
||||
return ret;
|
||||
|
@ -317,6 +337,7 @@ static struct platform_driver sdhci_iproc_driver = {
|
|||
.driver = {
|
||||
.name = "sdhci-iproc",
|
||||
.of_match_table = sdhci_iproc_of_match,
|
||||
.acpi_match_table = ACPI_PTR(sdhci_iproc_acpi_ids),
|
||||
.pm = &sdhci_pltfm_pmops,
|
||||
},
|
||||
.probe = sdhci_iproc_probe,
|
||||
|
|
|
@ -107,6 +107,11 @@ struct sdhci_arasan_data {
|
|||
#define SDHCI_ARASAN_QUIRK_CLOCK_UNSTABLE BIT(1)
|
||||
};
|
||||
|
||||
struct sdhci_arasan_of_data {
|
||||
const struct sdhci_arasan_soc_ctl_map *soc_ctl_map;
|
||||
const struct sdhci_pltfm_data *pdata;
|
||||
};
|
||||
|
||||
static const struct sdhci_arasan_soc_ctl_map rk3399_soc_ctl_map = {
|
||||
.baseclkfreq = { .reg = 0xf000, .width = 8, .shift = 8 },
|
||||
.clockmultiplier = { .reg = 0xf02c, .width = 8, .shift = 0},
|
||||
|
@ -226,6 +231,25 @@ static void sdhci_arasan_set_clock(struct sdhci_host *host, unsigned int clock)
|
|||
}
|
||||
}
|
||||
|
||||
static void sdhci_arasan_am654_set_clock(struct sdhci_host *host,
|
||||
unsigned int clock)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
|
||||
|
||||
if (sdhci_arasan->is_phy_on) {
|
||||
phy_power_off(sdhci_arasan->phy);
|
||||
sdhci_arasan->is_phy_on = false;
|
||||
}
|
||||
|
||||
sdhci_set_clock(host, clock);
|
||||
|
||||
if (clock > PHY_CLK_TOO_SLOW_HZ) {
|
||||
phy_power_on(sdhci_arasan->phy);
|
||||
sdhci_arasan->is_phy_on = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void sdhci_arasan_hs400_enhanced_strobe(struct mmc_host *mmc,
|
||||
struct mmc_ios *ios)
|
||||
{
|
||||
|
@ -307,6 +331,33 @@ static const struct sdhci_pltfm_data sdhci_arasan_pdata = {
|
|||
SDHCI_QUIRK2_STOP_WITH_TC,
|
||||
};
|
||||
|
||||
static struct sdhci_arasan_of_data sdhci_arasan_data = {
|
||||
.pdata = &sdhci_arasan_pdata,
|
||||
};
|
||||
|
||||
static const struct sdhci_ops sdhci_arasan_am654_ops = {
|
||||
.set_clock = sdhci_arasan_am654_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_arasan_reset,
|
||||
.set_uhs_signaling = sdhci_set_uhs_signaling,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_arasan_am654_pdata = {
|
||||
.ops = &sdhci_arasan_am654_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
|
||||
SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
|
||||
SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
|
||||
SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN |
|
||||
SDHCI_QUIRK2_CAPS_BIT63_FOR_HS400,
|
||||
};
|
||||
|
||||
static const struct sdhci_arasan_of_data sdhci_arasan_am654_data = {
|
||||
.pdata = &sdhci_arasan_am654_pdata,
|
||||
};
|
||||
|
||||
static u32 sdhci_arasan_cqhci_irq(struct sdhci_host *host, u32 intmask)
|
||||
{
|
||||
int cmd_error = 0;
|
||||
|
@ -363,6 +414,11 @@ static const struct sdhci_pltfm_data sdhci_arasan_cqe_pdata = {
|
|||
SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
|
||||
};
|
||||
|
||||
static struct sdhci_arasan_of_data sdhci_arasan_rk3399_data = {
|
||||
.soc_ctl_map = &rk3399_soc_ctl_map,
|
||||
.pdata = &sdhci_arasan_cqe_pdata,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
/**
|
||||
* sdhci_arasan_suspend - Suspend method for the driver
|
||||
|
@ -462,14 +518,25 @@ static const struct of_device_id sdhci_arasan_of_match[] = {
|
|||
/* SoC-specific compatible strings w/ soc_ctl_map */
|
||||
{
|
||||
.compatible = "rockchip,rk3399-sdhci-5.1",
|
||||
.data = &rk3399_soc_ctl_map,
|
||||
.data = &sdhci_arasan_rk3399_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,am654-sdhci-5.1",
|
||||
.data = &sdhci_arasan_am654_data,
|
||||
},
|
||||
|
||||
/* Generic compatible below here */
|
||||
{ .compatible = "arasan,sdhci-8.9a" },
|
||||
{ .compatible = "arasan,sdhci-5.1" },
|
||||
{ .compatible = "arasan,sdhci-4.9a" },
|
||||
|
||||
{
|
||||
.compatible = "arasan,sdhci-8.9a",
|
||||
.data = &sdhci_arasan_data,
|
||||
},
|
||||
{
|
||||
.compatible = "arasan,sdhci-5.1",
|
||||
.data = &sdhci_arasan_data,
|
||||
},
|
||||
{
|
||||
.compatible = "arasan,sdhci-4.9a",
|
||||
.data = &sdhci_arasan_data,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sdhci_arasan_of_match);
|
||||
|
@ -707,14 +774,11 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
|
|||
struct sdhci_pltfm_host *pltfm_host;
|
||||
struct sdhci_arasan_data *sdhci_arasan;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
const struct sdhci_pltfm_data *pdata;
|
||||
const struct sdhci_arasan_of_data *data;
|
||||
|
||||
if (of_device_is_compatible(pdev->dev.of_node, "arasan,sdhci-5.1"))
|
||||
pdata = &sdhci_arasan_cqe_pdata;
|
||||
else
|
||||
pdata = &sdhci_arasan_pdata;
|
||||
|
||||
host = sdhci_pltfm_init(pdev, pdata, sizeof(*sdhci_arasan));
|
||||
match = of_match_node(sdhci_arasan_of_match, pdev->dev.of_node);
|
||||
data = match->data;
|
||||
host = sdhci_pltfm_init(pdev, data->pdata, sizeof(*sdhci_arasan));
|
||||
|
||||
if (IS_ERR(host))
|
||||
return PTR_ERR(host);
|
||||
|
@ -723,8 +787,7 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
|
|||
sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
|
||||
sdhci_arasan->host = host;
|
||||
|
||||
match = of_match_node(sdhci_arasan_of_match, pdev->dev.of_node);
|
||||
sdhci_arasan->soc_ctl_map = match->data;
|
||||
sdhci_arasan->soc_ctl_map = data->soc_ctl_map;
|
||||
|
||||
node = of_parse_phandle(pdev->dev.of_node, "arasan,soc-ctl-syscon", 0);
|
||||
if (node) {
|
||||
|
@ -788,7 +851,8 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
|
|||
|
||||
ret = mmc_of_parse(host->mmc);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "parsing dt failed (%d)\n", ret);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "parsing dt failed (%d)\n", ret);
|
||||
goto unreg_clk;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,21 +8,51 @@
|
|||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#include "sdhci-pltfm.h"
|
||||
|
||||
#define BOUNDARY_OK(addr, len) \
|
||||
((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1)))
|
||||
|
||||
struct dwcmshc_priv {
|
||||
struct clk *bus_clk;
|
||||
};
|
||||
|
||||
/*
|
||||
* If DMA addr spans 128MB boundary, we split the DMA transfer into two
|
||||
* so that each DMA transfer doesn't exceed the boundary.
|
||||
*/
|
||||
static void dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc,
|
||||
dma_addr_t addr, int len, unsigned int cmd)
|
||||
{
|
||||
int tmplen, offset;
|
||||
|
||||
if (likely(!len || BOUNDARY_OK(addr, len))) {
|
||||
sdhci_adma_write_desc(host, desc, addr, len, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
offset = addr & (SZ_128M - 1);
|
||||
tmplen = SZ_128M - offset;
|
||||
sdhci_adma_write_desc(host, desc, addr, tmplen, cmd);
|
||||
|
||||
addr += tmplen;
|
||||
len -= tmplen;
|
||||
sdhci_adma_write_desc(host, desc, addr, len, cmd);
|
||||
}
|
||||
|
||||
static const struct sdhci_ops sdhci_dwcmshc_ops = {
|
||||
.set_clock = sdhci_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.set_uhs_signaling = sdhci_set_uhs_signaling,
|
||||
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
|
||||
.reset = sdhci_reset,
|
||||
.adma_write_desc = dwcmshc_adma_write_desc,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
|
||||
|
@ -36,12 +66,21 @@ static int dwcmshc_probe(struct platform_device *pdev)
|
|||
struct sdhci_host *host;
|
||||
struct dwcmshc_priv *priv;
|
||||
int err;
|
||||
u32 extra;
|
||||
|
||||
host = sdhci_pltfm_init(pdev, &sdhci_dwcmshc_pdata,
|
||||
sizeof(struct dwcmshc_priv));
|
||||
if (IS_ERR(host))
|
||||
return PTR_ERR(host);
|
||||
|
||||
/*
|
||||
* extra adma table cnt for cross 128M boundary handling.
|
||||
*/
|
||||
extra = DIV_ROUND_UP_ULL(dma_get_required_mask(&pdev->dev), SZ_128M);
|
||||
if (extra > SDHCI_MAX_SEGS)
|
||||
extra = SDHCI_MAX_SEGS;
|
||||
host->adma_table_cnt += extra;
|
||||
|
||||
pltfm_host = sdhci_priv(host);
|
||||
priv = sdhci_pltfm_priv(pltfm_host);
|
||||
|
||||
|
|
|
@ -78,8 +78,10 @@ struct sdhci_esdhc {
|
|||
u8 vendor_ver;
|
||||
u8 spec_ver;
|
||||
bool quirk_incorrect_hostver;
|
||||
bool quirk_fixup_tuning;
|
||||
unsigned int peripheral_clock;
|
||||
const struct esdhc_clk_fixup *clk_fixup;
|
||||
u32 div_ratio;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -580,6 +582,7 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock)
|
|||
dev_dbg(mmc_dev(host->mmc), "desired SD clock: %d, actual: %d\n",
|
||||
clock, host->max_clk / pre_div / div);
|
||||
host->mmc->actual_clock = host->max_clk / pre_div / div;
|
||||
esdhc->div_ratio = pre_div * div;
|
||||
pre_div >>= 1;
|
||||
div--;
|
||||
|
||||
|
@ -712,9 +715,24 @@ static int esdhc_signal_voltage_switch(struct mmc_host *mmc,
|
|||
}
|
||||
}
|
||||
|
||||
static struct soc_device_attribute soc_fixup_tuning[] = {
|
||||
{ .family = "QorIQ T1040", .revision = "1.0", },
|
||||
{ .family = "QorIQ T2080", .revision = "1.0", },
|
||||
{ .family = "QorIQ T1023", .revision = "1.0", },
|
||||
{ .family = "QorIQ LS1021A", .revision = "1.0", },
|
||||
{ .family = "QorIQ LS1080A", .revision = "1.0", },
|
||||
{ .family = "QorIQ LS2080A", .revision = "1.0", },
|
||||
{ .family = "QorIQ LS1012A", .revision = "1.0", },
|
||||
{ .family = "QorIQ LS1043A", .revision = "1.*", },
|
||||
{ .family = "QorIQ LS1046A", .revision = "1.0", },
|
||||
{ },
|
||||
};
|
||||
|
||||
static int esdhc_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
||||
{
|
||||
struct sdhci_host *host = mmc_priv(mmc);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
|
||||
u32 val;
|
||||
|
||||
/* Use tuning block for tuning procedure */
|
||||
|
@ -728,7 +746,26 @@ static int esdhc_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|||
sdhci_writel(host, val, ESDHC_TBCTL);
|
||||
esdhc_clock_enable(host, true);
|
||||
|
||||
return sdhci_execute_tuning(mmc, opcode);
|
||||
sdhci_execute_tuning(mmc, opcode);
|
||||
if (host->tuning_err == -EAGAIN && esdhc->quirk_fixup_tuning) {
|
||||
|
||||
/* program TBPTR[TB_WNDW_END_PTR] = 3*DIV_RATIO and
|
||||
* program TBPTR[TB_WNDW_START_PTR] = 5*DIV_RATIO
|
||||
*/
|
||||
val = sdhci_readl(host, ESDHC_TBPTR);
|
||||
val = (val & ~((0x7f << 8) | 0x7f)) |
|
||||
(3 * esdhc->div_ratio) | ((5 * esdhc->div_ratio) << 8);
|
||||
sdhci_writel(host, val, ESDHC_TBPTR);
|
||||
|
||||
/* program the software tuning mode by setting
|
||||
* TBCTL[TB_MODE]=2'h3
|
||||
*/
|
||||
val = sdhci_readl(host, ESDHC_TBCTL);
|
||||
val |= 0x3;
|
||||
sdhci_writel(host, val, ESDHC_TBCTL);
|
||||
sdhci_execute_tuning(mmc, opcode);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
@ -903,6 +940,11 @@ static int sdhci_esdhc_probe(struct platform_device *pdev)
|
|||
|
||||
pltfm_host = sdhci_priv(host);
|
||||
esdhc = sdhci_pltfm_priv(pltfm_host);
|
||||
if (soc_device_match(soc_fixup_tuning))
|
||||
esdhc->quirk_fixup_tuning = true;
|
||||
else
|
||||
esdhc->quirk_fixup_tuning = false;
|
||||
|
||||
if (esdhc->vendor_ver == VENDOR_V_22)
|
||||
host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23;
|
||||
|
||||
|
|
|
@ -490,6 +490,9 @@ int sdhci_pci_o2_probe(struct sdhci_pci_chip *chip)
|
|||
pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch);
|
||||
break;
|
||||
case PCI_DEVICE_ID_O2_SEABIRD0:
|
||||
if (chip->pdev->revision == 0x01)
|
||||
chip->quirks |= SDHCI_QUIRK_DELAY_AFTER_POWER;
|
||||
/* fall through */
|
||||
case PCI_DEVICE_ID_O2_SEABIRD1:
|
||||
/* UnLock WP */
|
||||
ret = pci_read_config_byte(chip->pdev,
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/of.h>
|
||||
#ifdef CONFIG_PPC
|
||||
#include <asm/machdep.h>
|
||||
|
@ -51,11 +52,10 @@ static const struct sdhci_ops sdhci_pltfm_ops = {
|
|||
.set_uhs_signaling = sdhci_set_uhs_signaling,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static bool sdhci_of_wp_inverted(struct device_node *np)
|
||||
static bool sdhci_wp_inverted(struct device *dev)
|
||||
{
|
||||
if (of_get_property(np, "sdhci,wp-inverted", NULL) ||
|
||||
of_get_property(np, "wp-inverted", NULL))
|
||||
if (device_property_present(dev, "sdhci,wp-inverted") ||
|
||||
device_property_present(dev, "wp-inverted"))
|
||||
return true;
|
||||
|
||||
/* Old device trees don't have the wp-inverted property. */
|
||||
|
@ -66,29 +66,14 @@ static bool sdhci_of_wp_inverted(struct device_node *np)
|
|||
#endif /* CONFIG_PPC */
|
||||
}
|
||||
|
||||
void sdhci_get_of_property(struct platform_device *pdev)
|
||||
#ifdef CONFIG_OF
|
||||
static void sdhci_get_compatibility(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct sdhci_host *host = platform_get_drvdata(pdev);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
u32 bus_width;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
|
||||
if (of_get_property(np, "sdhci,auto-cmd12", NULL))
|
||||
host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
|
||||
|
||||
if (of_get_property(np, "sdhci,1-bit-only", NULL) ||
|
||||
(of_property_read_u32(np, "bus-width", &bus_width) == 0 &&
|
||||
bus_width == 1))
|
||||
host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
|
||||
|
||||
if (sdhci_of_wp_inverted(np))
|
||||
host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;
|
||||
|
||||
if (of_get_property(np, "broken-cd", NULL))
|
||||
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
|
||||
|
||||
if (of_get_property(np, "no-1-8-v", NULL))
|
||||
host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V;
|
||||
if (!np)
|
||||
return;
|
||||
|
||||
if (of_device_is_compatible(np, "fsl,p2020-rev1-esdhc"))
|
||||
host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
|
||||
|
@ -98,20 +83,47 @@ void sdhci_get_of_property(struct platform_device *pdev)
|
|||
of_device_is_compatible(np, "fsl,t4240-esdhc") ||
|
||||
of_device_is_compatible(np, "fsl,mpc8536-esdhc"))
|
||||
host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL;
|
||||
|
||||
of_property_read_u32(np, "clock-frequency", &pltfm_host->clock);
|
||||
|
||||
if (of_find_property(np, "keep-power-in-suspend", NULL))
|
||||
host->mmc->pm_caps |= MMC_PM_KEEP_POWER;
|
||||
|
||||
if (of_property_read_bool(np, "wakeup-source") ||
|
||||
of_property_read_bool(np, "enable-sdio-wakeup")) /* legacy */
|
||||
host->mmc->pm_caps |= MMC_PM_WAKE_SDIO_IRQ;
|
||||
}
|
||||
#else
|
||||
void sdhci_get_of_property(struct platform_device *pdev) {}
|
||||
void sdhci_get_compatibility(struct platform_device *pdev) {}
|
||||
#endif /* CONFIG_OF */
|
||||
EXPORT_SYMBOL_GPL(sdhci_get_of_property);
|
||||
|
||||
void sdhci_get_property(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct sdhci_host *host = platform_get_drvdata(pdev);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
u32 bus_width;
|
||||
|
||||
if (device_property_present(dev, "sdhci,auto-cmd12"))
|
||||
host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
|
||||
|
||||
if (device_property_present(dev, "sdhci,1-bit-only") ||
|
||||
(device_property_read_u32(dev, "bus-width", &bus_width) == 0 &&
|
||||
bus_width == 1))
|
||||
host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
|
||||
|
||||
if (sdhci_wp_inverted(dev))
|
||||
host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;
|
||||
|
||||
if (device_property_present(dev, "broken-cd"))
|
||||
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
|
||||
|
||||
if (device_property_present(dev, "no-1-8-v"))
|
||||
host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V;
|
||||
|
||||
sdhci_get_compatibility(pdev);
|
||||
|
||||
device_property_read_u32(dev, "clock-frequency", &pltfm_host->clock);
|
||||
|
||||
if (device_property_present(dev, "keep-power-in-suspend"))
|
||||
host->mmc->pm_caps |= MMC_PM_KEEP_POWER;
|
||||
|
||||
if (device_property_read_bool(dev, "wakeup-source") ||
|
||||
device_property_read_bool(dev, "enable-sdio-wakeup")) /* legacy */
|
||||
host->mmc->pm_caps |= MMC_PM_WAKE_SDIO_IRQ;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdhci_get_property);
|
||||
|
||||
struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
|
||||
const struct sdhci_pltfm_data *pdata,
|
||||
|
@ -184,7 +196,7 @@ int sdhci_pltfm_register(struct platform_device *pdev,
|
|||
if (IS_ERR(host))
|
||||
return PTR_ERR(host);
|
||||
|
||||
sdhci_get_of_property(pdev);
|
||||
sdhci_get_property(pdev);
|
||||
|
||||
ret = sdhci_add_host(host);
|
||||
if (ret)
|
||||
|
|
|
@ -90,7 +90,12 @@ static inline void sdhci_be32bs_writeb(struct sdhci_host *host, u8 val, int reg)
|
|||
}
|
||||
#endif /* CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER */
|
||||
|
||||
extern void sdhci_get_of_property(struct platform_device *pdev);
|
||||
void sdhci_get_property(struct platform_device *pdev);
|
||||
|
||||
static inline void sdhci_get_of_property(struct platform_device *pdev)
|
||||
{
|
||||
return sdhci_get_property(pdev);
|
||||
}
|
||||
|
||||
extern struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
|
||||
const struct sdhci_pltfm_data *pdata,
|
||||
|
|
|
@ -21,17 +21,14 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/slot-gpio.h>
|
||||
#include <linux/platform_data/pxa_sdhci.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/mbus.h>
|
||||
|
@ -452,16 +449,6 @@ static int sdhci_pxav3_probe(struct platform_device *pdev)
|
|||
host->mmc->caps2 |= pdata->host_caps2;
|
||||
if (pdata->pm_caps)
|
||||
host->mmc->pm_caps |= pdata->pm_caps;
|
||||
|
||||
if (gpio_is_valid(pdata->ext_cd_gpio)) {
|
||||
ret = mmc_gpio_request_cd(host->mmc, pdata->ext_cd_gpio,
|
||||
0);
|
||||
if (ret) {
|
||||
dev_err(mmc_dev(host->mmc),
|
||||
"failed to allocate card detect gpio\n");
|
||||
goto err_cd_req;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pm_runtime_get_noresume(&pdev->dev);
|
||||
|
@ -486,7 +473,6 @@ static int sdhci_pxav3_probe(struct platform_device *pdev)
|
|||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
err_of_parse:
|
||||
err_cd_req:
|
||||
err_mbus_win:
|
||||
clk_disable_unprepare(pxa->clk_io);
|
||||
clk_disable_unprepare(pxa->clk_core);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <linux/mmc/host.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/mmc/slot-gpio.h>
|
||||
#include "sdhci-pltfm.h"
|
||||
|
||||
|
@ -19,10 +18,6 @@
|
|||
#define SDHCI_SIRF_8BITBUS BIT(3)
|
||||
#define SIRF_TUNING_COUNT 16384
|
||||
|
||||
struct sdhci_sirf_priv {
|
||||
int gpio_cd;
|
||||
};
|
||||
|
||||
static void sdhci_sirf_set_bus_width(struct sdhci_host *host, int width)
|
||||
{
|
||||
u8 ctrl;
|
||||
|
@ -170,9 +165,7 @@ static int sdhci_sirf_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct sdhci_host *host;
|
||||
struct sdhci_pltfm_host *pltfm_host;
|
||||
struct sdhci_sirf_priv *priv;
|
||||
struct clk *clk;
|
||||
int gpio_cd;
|
||||
int ret;
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, NULL);
|
||||
|
@ -181,19 +174,12 @@ static int sdhci_sirf_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
if (pdev->dev.of_node)
|
||||
gpio_cd = of_get_named_gpio(pdev->dev.of_node, "cd-gpios", 0);
|
||||
else
|
||||
gpio_cd = -EINVAL;
|
||||
|
||||
host = sdhci_pltfm_init(pdev, &sdhci_sirf_pdata, sizeof(struct sdhci_sirf_priv));
|
||||
host = sdhci_pltfm_init(pdev, &sdhci_sirf_pdata, 0);
|
||||
if (IS_ERR(host))
|
||||
return PTR_ERR(host);
|
||||
|
||||
pltfm_host = sdhci_priv(host);
|
||||
pltfm_host->clk = clk;
|
||||
priv = sdhci_pltfm_priv(pltfm_host);
|
||||
priv->gpio_cd = gpio_cd;
|
||||
|
||||
sdhci_get_of_property(pdev);
|
||||
|
||||
|
@ -209,15 +195,11 @@ static int sdhci_sirf_probe(struct platform_device *pdev)
|
|||
* We must request the IRQ after sdhci_add_host(), as the tasklet only
|
||||
* gets setup in sdhci_add_host() and we oops.
|
||||
*/
|
||||
if (gpio_is_valid(priv->gpio_cd)) {
|
||||
ret = mmc_gpio_request_cd(host->mmc, priv->gpio_cd, 0);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "card detect irq request failed: %d\n",
|
||||
ret);
|
||||
goto err_request_cd;
|
||||
}
|
||||
ret = mmc_gpiod_request_cd(host->mmc, "cd", 0, false, 0, NULL);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
goto err_request_cd;
|
||||
if (!ret)
|
||||
mmc_gpiod_request_cd_irq(host->mmc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
|
|
|
@ -15,13 +15,11 @@
|
|||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/slab.h>
|
||||
|
@ -32,7 +30,6 @@
|
|||
|
||||
struct spear_sdhci {
|
||||
struct clk *clk;
|
||||
int card_int_gpio;
|
||||
};
|
||||
|
||||
/* sdhci ops */
|
||||
|
@ -43,18 +40,6 @@ static const struct sdhci_ops sdhci_pltfm_ops = {
|
|||
.set_uhs_signaling = sdhci_set_uhs_signaling,
|
||||
};
|
||||
|
||||
static void sdhci_probe_config_dt(struct device_node *np,
|
||||
struct spear_sdhci *host)
|
||||
{
|
||||
int cd_gpio;
|
||||
|
||||
cd_gpio = of_get_named_gpio(np, "cd-gpios", 0);
|
||||
if (!gpio_is_valid(cd_gpio))
|
||||
cd_gpio = -1;
|
||||
|
||||
host->card_int_gpio = cd_gpio;
|
||||
}
|
||||
|
||||
static int sdhci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sdhci_host *host;
|
||||
|
@ -109,21 +94,13 @@ static int sdhci_probe(struct platform_device *pdev)
|
|||
dev_dbg(&pdev->dev, "Error setting desired clk, clk=%lu\n",
|
||||
clk_get_rate(sdhci->clk));
|
||||
|
||||
sdhci_probe_config_dt(pdev->dev.of_node, sdhci);
|
||||
/*
|
||||
* It is optional to use GPIOs for sdhci card detection. If
|
||||
* sdhci->card_int_gpio < 0, then use original sdhci lines otherwise
|
||||
* GPIO lines. We use the built-in GPIO support for this.
|
||||
* It is optional to use GPIOs for sdhci card detection. If we
|
||||
* find a descriptor using slot GPIO, we use it.
|
||||
*/
|
||||
if (sdhci->card_int_gpio >= 0) {
|
||||
ret = mmc_gpio_request_cd(host->mmc, sdhci->card_int_gpio, 0);
|
||||
if (ret < 0) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"failed to request card-detect gpio%d\n",
|
||||
sdhci->card_int_gpio);
|
||||
goto disable_clk;
|
||||
}
|
||||
}
|
||||
ret = mmc_gpiod_request_cd(host->mmc, "cd", 0, false, 0, NULL);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
goto disable_clk;
|
||||
|
||||
ret = sdhci_add_host(host);
|
||||
if (ret)
|
||||
|
|
498
drivers/mmc/host/sdhci-sprd.c
Normal file
498
drivers/mmc/host/sdhci-sprd.c
Normal file
|
@ -0,0 +1,498 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// Secure Digital Host Controller
|
||||
//
|
||||
// Copyright (C) 2018 Spreadtrum, Inc.
|
||||
// Author: Chunyan Zhang <chunyan.zhang@unisoc.com>
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "sdhci-pltfm.h"
|
||||
|
||||
/* SDHCI_ARGUMENT2 register high 16bit */
|
||||
#define SDHCI_SPRD_ARG2_STUFF GENMASK(31, 16)
|
||||
|
||||
#define SDHCI_SPRD_REG_32_DLL_DLY_OFFSET 0x208
|
||||
#define SDHCIBSPRD_IT_WR_DLY_INV BIT(5)
|
||||
#define SDHCI_SPRD_BIT_CMD_DLY_INV BIT(13)
|
||||
#define SDHCI_SPRD_BIT_POSRD_DLY_INV BIT(21)
|
||||
#define SDHCI_SPRD_BIT_NEGRD_DLY_INV BIT(29)
|
||||
|
||||
#define SDHCI_SPRD_REG_32_BUSY_POSI 0x250
|
||||
#define SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN BIT(25)
|
||||
#define SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN BIT(24)
|
||||
|
||||
#define SDHCI_SPRD_REG_DEBOUNCE 0x28C
|
||||
#define SDHCI_SPRD_BIT_DLL_BAK BIT(0)
|
||||
#define SDHCI_SPRD_BIT_DLL_VAL BIT(1)
|
||||
|
||||
#define SDHCI_SPRD_INT_SIGNAL_MASK 0x1B7F410B
|
||||
|
||||
/* SDHCI_HOST_CONTROL2 */
|
||||
#define SDHCI_SPRD_CTRL_HS200 0x0005
|
||||
#define SDHCI_SPRD_CTRL_HS400 0x0006
|
||||
|
||||
/*
|
||||
* According to the standard specification, BIT(3) of SDHCI_SOFTWARE_RESET is
|
||||
* reserved, and only used on Spreadtrum's design, the hardware cannot work
|
||||
* if this bit is cleared.
|
||||
* 1 : normal work
|
||||
* 0 : hardware reset
|
||||
*/
|
||||
#define SDHCI_HW_RESET_CARD BIT(3)
|
||||
|
||||
#define SDHCI_SPRD_MAX_CUR 0xFFFFFF
|
||||
#define SDHCI_SPRD_CLK_MAX_DIV 1023
|
||||
|
||||
#define SDHCI_SPRD_CLK_DEF_RATE 26000000
|
||||
|
||||
struct sdhci_sprd_host {
|
||||
u32 version;
|
||||
struct clk *clk_sdio;
|
||||
struct clk *clk_enable;
|
||||
u32 base_rate;
|
||||
int flags; /* backup of host attribute */
|
||||
};
|
||||
|
||||
#define TO_SPRD_HOST(host) sdhci_pltfm_priv(sdhci_priv(host))
|
||||
|
||||
static void sdhci_sprd_init_config(struct sdhci_host *host)
|
||||
{
|
||||
u16 val;
|
||||
|
||||
/* set dll backup mode */
|
||||
val = sdhci_readl(host, SDHCI_SPRD_REG_DEBOUNCE);
|
||||
val |= SDHCI_SPRD_BIT_DLL_BAK | SDHCI_SPRD_BIT_DLL_VAL;
|
||||
sdhci_writel(host, val, SDHCI_SPRD_REG_DEBOUNCE);
|
||||
}
|
||||
|
||||
static inline u32 sdhci_sprd_readl(struct sdhci_host *host, int reg)
|
||||
{
|
||||
if (unlikely(reg == SDHCI_MAX_CURRENT))
|
||||
return SDHCI_SPRD_MAX_CUR;
|
||||
|
||||
return readl_relaxed(host->ioaddr + reg);
|
||||
}
|
||||
|
||||
static inline void sdhci_sprd_writel(struct sdhci_host *host, u32 val, int reg)
|
||||
{
|
||||
/* SDHCI_MAX_CURRENT is reserved on Spreadtrum's platform */
|
||||
if (unlikely(reg == SDHCI_MAX_CURRENT))
|
||||
return;
|
||||
|
||||
if (unlikely(reg == SDHCI_SIGNAL_ENABLE || reg == SDHCI_INT_ENABLE))
|
||||
val = val & SDHCI_SPRD_INT_SIGNAL_MASK;
|
||||
|
||||
writel_relaxed(val, host->ioaddr + reg);
|
||||
}
|
||||
|
||||
static inline void sdhci_sprd_writew(struct sdhci_host *host, u16 val, int reg)
|
||||
{
|
||||
/* SDHCI_BLOCK_COUNT is Read Only on Spreadtrum's platform */
|
||||
if (unlikely(reg == SDHCI_BLOCK_COUNT))
|
||||
return;
|
||||
|
||||
writew_relaxed(val, host->ioaddr + reg);
|
||||
}
|
||||
|
||||
static inline void sdhci_sprd_writeb(struct sdhci_host *host, u8 val, int reg)
|
||||
{
|
||||
/*
|
||||
* Since BIT(3) of SDHCI_SOFTWARE_RESET is reserved according to the
|
||||
* standard specification, sdhci_reset() write this register directly
|
||||
* without checking other reserved bits, that will clear BIT(3) which
|
||||
* is defined as hardware reset on Spreadtrum's platform and clearing
|
||||
* it by mistake will lead the card not work. So here we need to work
|
||||
* around it.
|
||||
*/
|
||||
if (unlikely(reg == SDHCI_SOFTWARE_RESET)) {
|
||||
if (readb_relaxed(host->ioaddr + reg) & SDHCI_HW_RESET_CARD)
|
||||
val |= SDHCI_HW_RESET_CARD;
|
||||
}
|
||||
|
||||
writeb_relaxed(val, host->ioaddr + reg);
|
||||
}
|
||||
|
||||
static inline void sdhci_sprd_sd_clk_off(struct sdhci_host *host)
|
||||
{
|
||||
u16 ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
||||
|
||||
ctrl &= ~SDHCI_CLOCK_CARD_EN;
|
||||
sdhci_writew(host, ctrl, SDHCI_CLOCK_CONTROL);
|
||||
}
|
||||
|
||||
static inline void
|
||||
sdhci_sprd_set_dll_invert(struct sdhci_host *host, u32 mask, bool en)
|
||||
{
|
||||
u32 dll_dly_offset;
|
||||
|
||||
dll_dly_offset = sdhci_readl(host, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
|
||||
if (en)
|
||||
dll_dly_offset |= mask;
|
||||
else
|
||||
dll_dly_offset &= ~mask;
|
||||
sdhci_writel(host, dll_dly_offset, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
|
||||
}
|
||||
|
||||
static inline u32 sdhci_sprd_calc_div(u32 base_clk, u32 clk)
|
||||
{
|
||||
u32 div;
|
||||
|
||||
/* select 2x clock source */
|
||||
if (base_clk <= clk * 2)
|
||||
return 0;
|
||||
|
||||
div = (u32) (base_clk / (clk * 2));
|
||||
|
||||
if ((base_clk / div) > (clk * 2))
|
||||
div++;
|
||||
|
||||
if (div > SDHCI_SPRD_CLK_MAX_DIV)
|
||||
div = SDHCI_SPRD_CLK_MAX_DIV;
|
||||
|
||||
if (div % 2)
|
||||
div = (div + 1) / 2;
|
||||
else
|
||||
div = div / 2;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
static inline void _sdhci_sprd_set_clock(struct sdhci_host *host,
|
||||
unsigned int clk)
|
||||
{
|
||||
struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
|
||||
u32 div, val, mask;
|
||||
|
||||
div = sdhci_sprd_calc_div(sprd_host->base_rate, clk);
|
||||
|
||||
clk |= ((div & 0x300) >> 2) | ((div & 0xFF) << 8);
|
||||
sdhci_enable_clk(host, clk);
|
||||
|
||||
/* enable auto gate sdhc_enable_auto_gate */
|
||||
val = sdhci_readl(host, SDHCI_SPRD_REG_32_BUSY_POSI);
|
||||
mask = SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN |
|
||||
SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN;
|
||||
if (mask != (val & mask)) {
|
||||
val |= mask;
|
||||
sdhci_writel(host, val, SDHCI_SPRD_REG_32_BUSY_POSI);
|
||||
}
|
||||
}
|
||||
|
||||
static void sdhci_sprd_set_clock(struct sdhci_host *host, unsigned int clock)
|
||||
{
|
||||
bool en = false;
|
||||
|
||||
if (clock == 0) {
|
||||
sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
|
||||
} else if (clock != host->clock) {
|
||||
sdhci_sprd_sd_clk_off(host);
|
||||
_sdhci_sprd_set_clock(host, clock);
|
||||
|
||||
if (clock <= 400000)
|
||||
en = true;
|
||||
sdhci_sprd_set_dll_invert(host, SDHCI_SPRD_BIT_CMD_DLY_INV |
|
||||
SDHCI_SPRD_BIT_POSRD_DLY_INV, en);
|
||||
} else {
|
||||
_sdhci_sprd_set_clock(host, clock);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int sdhci_sprd_get_max_clock(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
|
||||
|
||||
return clk_round_rate(sprd_host->clk_sdio, ULONG_MAX);
|
||||
}
|
||||
|
||||
static unsigned int sdhci_sprd_get_min_clock(struct sdhci_host *host)
|
||||
{
|
||||
return 400000;
|
||||
}
|
||||
|
||||
static void sdhci_sprd_set_uhs_signaling(struct sdhci_host *host,
|
||||
unsigned int timing)
|
||||
{
|
||||
u16 ctrl_2;
|
||||
|
||||
if (timing == host->timing)
|
||||
return;
|
||||
|
||||
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
||||
/* Select Bus Speed Mode for host */
|
||||
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
|
||||
switch (timing) {
|
||||
case MMC_TIMING_UHS_SDR12:
|
||||
ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
|
||||
break;
|
||||
case MMC_TIMING_MMC_HS:
|
||||
case MMC_TIMING_SD_HS:
|
||||
case MMC_TIMING_UHS_SDR25:
|
||||
ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
|
||||
break;
|
||||
case MMC_TIMING_UHS_SDR50:
|
||||
ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
|
||||
break;
|
||||
case MMC_TIMING_UHS_SDR104:
|
||||
ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
|
||||
break;
|
||||
case MMC_TIMING_UHS_DDR50:
|
||||
case MMC_TIMING_MMC_DDR52:
|
||||
ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
|
||||
break;
|
||||
case MMC_TIMING_MMC_HS200:
|
||||
ctrl_2 |= SDHCI_SPRD_CTRL_HS200;
|
||||
break;
|
||||
case MMC_TIMING_MMC_HS400:
|
||||
ctrl_2 |= SDHCI_SPRD_CTRL_HS400;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
||||
}
|
||||
|
||||
static void sdhci_sprd_hw_reset(struct sdhci_host *host)
|
||||
{
|
||||
int val;
|
||||
|
||||
/*
|
||||
* Note: don't use sdhci_writeb() API here since it is redirected to
|
||||
* sdhci_sprd_writeb() in which we have a workaround for
|
||||
* SDHCI_SOFTWARE_RESET which would make bit SDHCI_HW_RESET_CARD can
|
||||
* not be cleared.
|
||||
*/
|
||||
val = readb_relaxed(host->ioaddr + SDHCI_SOFTWARE_RESET);
|
||||
val &= ~SDHCI_HW_RESET_CARD;
|
||||
writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
|
||||
/* wait for 10 us */
|
||||
usleep_range(10, 20);
|
||||
|
||||
val |= SDHCI_HW_RESET_CARD;
|
||||
writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
|
||||
usleep_range(300, 500);
|
||||
}
|
||||
|
||||
static struct sdhci_ops sdhci_sprd_ops = {
|
||||
.read_l = sdhci_sprd_readl,
|
||||
.write_l = sdhci_sprd_writel,
|
||||
.write_b = sdhci_sprd_writeb,
|
||||
.set_clock = sdhci_sprd_set_clock,
|
||||
.get_max_clock = sdhci_sprd_get_max_clock,
|
||||
.get_min_clock = sdhci_sprd_get_min_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.reset = sdhci_reset,
|
||||
.set_uhs_signaling = sdhci_sprd_set_uhs_signaling,
|
||||
.hw_reset = sdhci_sprd_hw_reset,
|
||||
};
|
||||
|
||||
static void sdhci_sprd_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
{
|
||||
struct sdhci_host *host = mmc_priv(mmc);
|
||||
struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
|
||||
|
||||
host->flags |= sprd_host->flags & SDHCI_AUTO_CMD23;
|
||||
|
||||
/*
|
||||
* From version 4.10 onward, ARGUMENT2 register is also as 32-bit
|
||||
* block count register which doesn't support stuff bits of
|
||||
* CMD23 argument on Spreadtrum's sd host controller.
|
||||
*/
|
||||
if (host->version >= SDHCI_SPEC_410 &&
|
||||
mrq->sbc && (mrq->sbc->arg & SDHCI_SPRD_ARG2_STUFF) &&
|
||||
(host->flags & SDHCI_AUTO_CMD23))
|
||||
host->flags &= ~SDHCI_AUTO_CMD23;
|
||||
|
||||
sdhci_request(mmc, mrq);
|
||||
}
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_sprd_pdata = {
|
||||
.quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK,
|
||||
.quirks2 = SDHCI_QUIRK2_BROKEN_HS200 |
|
||||
SDHCI_QUIRK2_USE_32BIT_BLK_CNT,
|
||||
.ops = &sdhci_sprd_ops,
|
||||
};
|
||||
|
||||
static int sdhci_sprd_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sdhci_host *host;
|
||||
struct sdhci_sprd_host *sprd_host;
|
||||
struct clk *clk;
|
||||
int ret = 0;
|
||||
|
||||
host = sdhci_pltfm_init(pdev, &sdhci_sprd_pdata, sizeof(*sprd_host));
|
||||
if (IS_ERR(host))
|
||||
return PTR_ERR(host);
|
||||
|
||||
host->dma_mask = DMA_BIT_MASK(64);
|
||||
pdev->dev.dma_mask = &host->dma_mask;
|
||||
host->mmc_host_ops.request = sdhci_sprd_request;
|
||||
|
||||
host->mmc->caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
|
||||
MMC_CAP_ERASE | MMC_CAP_CMD23;
|
||||
ret = mmc_of_parse(host->mmc);
|
||||
if (ret)
|
||||
goto pltfm_free;
|
||||
|
||||
sprd_host = TO_SPRD_HOST(host);
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, "sdio");
|
||||
if (IS_ERR(clk)) {
|
||||
ret = PTR_ERR(clk);
|
||||
goto pltfm_free;
|
||||
}
|
||||
sprd_host->clk_sdio = clk;
|
||||
sprd_host->base_rate = clk_get_rate(sprd_host->clk_sdio);
|
||||
if (!sprd_host->base_rate)
|
||||
sprd_host->base_rate = SDHCI_SPRD_CLK_DEF_RATE;
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, "enable");
|
||||
if (IS_ERR(clk)) {
|
||||
ret = PTR_ERR(clk);
|
||||
goto pltfm_free;
|
||||
}
|
||||
sprd_host->clk_enable = clk;
|
||||
|
||||
ret = clk_prepare_enable(sprd_host->clk_sdio);
|
||||
if (ret)
|
||||
goto pltfm_free;
|
||||
|
||||
clk_prepare_enable(sprd_host->clk_enable);
|
||||
if (ret)
|
||||
goto clk_disable;
|
||||
|
||||
sdhci_sprd_init_config(host);
|
||||
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
|
||||
sprd_host->version = ((host->version & SDHCI_VENDOR_VER_MASK) >>
|
||||
SDHCI_VENDOR_VER_SHIFT);
|
||||
|
||||
pm_runtime_get_noresume(&pdev->dev);
|
||||
pm_runtime_set_active(&pdev->dev);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
pm_suspend_ignore_children(&pdev->dev, 1);
|
||||
|
||||
sdhci_enable_v4_mode(host);
|
||||
|
||||
ret = sdhci_setup_host(host);
|
||||
if (ret)
|
||||
goto pm_runtime_disable;
|
||||
|
||||
sprd_host->flags = host->flags;
|
||||
|
||||
ret = __sdhci_add_host(host);
|
||||
if (ret)
|
||||
goto err_cleanup_host;
|
||||
|
||||
pm_runtime_mark_last_busy(&pdev->dev);
|
||||
pm_runtime_put_autosuspend(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_cleanup_host:
|
||||
sdhci_cleanup_host(host);
|
||||
|
||||
pm_runtime_disable:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_set_suspended(&pdev->dev);
|
||||
|
||||
clk_disable_unprepare(sprd_host->clk_enable);
|
||||
|
||||
clk_disable:
|
||||
clk_disable_unprepare(sprd_host->clk_sdio);
|
||||
|
||||
pltfm_free:
|
||||
sdhci_pltfm_free(pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdhci_sprd_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sdhci_host *host = platform_get_drvdata(pdev);
|
||||
struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
|
||||
struct mmc_host *mmc = host->mmc;
|
||||
|
||||
mmc_remove_host(mmc);
|
||||
clk_disable_unprepare(sprd_host->clk_sdio);
|
||||
clk_disable_unprepare(sprd_host->clk_enable);
|
||||
|
||||
mmc_free_host(mmc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id sdhci_sprd_of_match[] = {
|
||||
{ .compatible = "sprd,sdhci-r11", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sdhci_sprd_of_match);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int sdhci_sprd_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct sdhci_host *host = dev_get_drvdata(dev);
|
||||
struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
|
||||
|
||||
sdhci_runtime_suspend_host(host);
|
||||
|
||||
clk_disable_unprepare(sprd_host->clk_sdio);
|
||||
clk_disable_unprepare(sprd_host->clk_enable);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdhci_sprd_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct sdhci_host *host = dev_get_drvdata(dev);
|
||||
struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(sprd_host->clk_enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(sprd_host->clk_sdio);
|
||||
if (ret) {
|
||||
clk_disable_unprepare(sprd_host->clk_enable);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sdhci_runtime_resume_host(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops sdhci_sprd_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
SET_RUNTIME_PM_OPS(sdhci_sprd_runtime_suspend,
|
||||
sdhci_sprd_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static struct platform_driver sdhci_sprd_driver = {
|
||||
.probe = sdhci_sprd_probe,
|
||||
.remove = sdhci_sprd_remove,
|
||||
.driver = {
|
||||
.name = "sdhci_sprd_r11",
|
||||
.of_match_table = of_match_ptr(sdhci_sprd_of_match),
|
||||
.pm = &sdhci_sprd_pm_ops,
|
||||
},
|
||||
};
|
||||
module_platform_driver(sdhci_sprd_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Spreadtrum sdio host controller r11 driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:sdhci-sprd-r11");
|
|
@ -16,17 +16,21 @@
|
|||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/slot-gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/ktime.h>
|
||||
|
||||
#include "sdhci-pltfm.h"
|
||||
|
||||
|
@ -34,40 +38,96 @@
|
|||
#define SDHCI_TEGRA_VENDOR_CLOCK_CTRL 0x100
|
||||
#define SDHCI_CLOCK_CTRL_TAP_MASK 0x00ff0000
|
||||
#define SDHCI_CLOCK_CTRL_TAP_SHIFT 16
|
||||
#define SDHCI_CLOCK_CTRL_TRIM_MASK 0x1f000000
|
||||
#define SDHCI_CLOCK_CTRL_TRIM_SHIFT 24
|
||||
#define SDHCI_CLOCK_CTRL_SDR50_TUNING_OVERRIDE BIT(5)
|
||||
#define SDHCI_CLOCK_CTRL_PADPIPE_CLKEN_OVERRIDE BIT(3)
|
||||
#define SDHCI_CLOCK_CTRL_SPI_MODE_CLKEN_OVERRIDE BIT(2)
|
||||
|
||||
#define SDHCI_TEGRA_VENDOR_MISC_CTRL 0x120
|
||||
#define SDHCI_MISC_CTRL_ENABLE_SDR104 0x8
|
||||
#define SDHCI_MISC_CTRL_ENABLE_SDR50 0x10
|
||||
#define SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300 0x20
|
||||
#define SDHCI_MISC_CTRL_ENABLE_DDR50 0x200
|
||||
#define SDHCI_TEGRA_VENDOR_SYS_SW_CTRL 0x104
|
||||
#define SDHCI_TEGRA_SYS_SW_CTRL_ENHANCED_STROBE BIT(31)
|
||||
|
||||
#define SDHCI_TEGRA_AUTO_CAL_CONFIG 0x1e4
|
||||
#define SDHCI_AUTO_CAL_START BIT(31)
|
||||
#define SDHCI_AUTO_CAL_ENABLE BIT(29)
|
||||
#define SDHCI_TEGRA_VENDOR_CAP_OVERRIDES 0x10c
|
||||
#define SDHCI_TEGRA_CAP_OVERRIDES_DQS_TRIM_MASK 0x00003f00
|
||||
#define SDHCI_TEGRA_CAP_OVERRIDES_DQS_TRIM_SHIFT 8
|
||||
|
||||
#define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0)
|
||||
#define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1)
|
||||
#define NVQUIRK_ENABLE_SDHCI_SPEC_300 BIT(2)
|
||||
#define NVQUIRK_ENABLE_SDR50 BIT(3)
|
||||
#define NVQUIRK_ENABLE_SDR104 BIT(4)
|
||||
#define NVQUIRK_ENABLE_DDR50 BIT(5)
|
||||
#define NVQUIRK_HAS_PADCALIB BIT(6)
|
||||
#define SDHCI_TEGRA_VENDOR_MISC_CTRL 0x120
|
||||
#define SDHCI_MISC_CTRL_ENABLE_SDR104 0x8
|
||||
#define SDHCI_MISC_CTRL_ENABLE_SDR50 0x10
|
||||
#define SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300 0x20
|
||||
#define SDHCI_MISC_CTRL_ENABLE_DDR50 0x200
|
||||
|
||||
#define SDHCI_TEGRA_VENDOR_DLLCAL_CFG 0x1b0
|
||||
#define SDHCI_TEGRA_DLLCAL_CALIBRATE BIT(31)
|
||||
|
||||
#define SDHCI_TEGRA_VENDOR_DLLCAL_STA 0x1bc
|
||||
#define SDHCI_TEGRA_DLLCAL_STA_ACTIVE BIT(31)
|
||||
|
||||
#define SDHCI_VNDR_TUN_CTRL0_0 0x1c0
|
||||
#define SDHCI_VNDR_TUN_CTRL0_TUN_HW_TAP 0x20000
|
||||
|
||||
#define SDHCI_TEGRA_AUTO_CAL_CONFIG 0x1e4
|
||||
#define SDHCI_AUTO_CAL_START BIT(31)
|
||||
#define SDHCI_AUTO_CAL_ENABLE BIT(29)
|
||||
#define SDHCI_AUTO_CAL_PDPU_OFFSET_MASK 0x0000ffff
|
||||
|
||||
#define SDHCI_TEGRA_SDMEM_COMP_PADCTRL 0x1e0
|
||||
#define SDHCI_TEGRA_SDMEM_COMP_PADCTRL_VREF_SEL_MASK 0x0000000f
|
||||
#define SDHCI_TEGRA_SDMEM_COMP_PADCTRL_VREF_SEL_VAL 0x7
|
||||
#define SDHCI_TEGRA_SDMEM_COMP_PADCTRL_E_INPUT_E_PWRD BIT(31)
|
||||
|
||||
#define SDHCI_TEGRA_AUTO_CAL_STATUS 0x1ec
|
||||
#define SDHCI_TEGRA_AUTO_CAL_ACTIVE BIT(31)
|
||||
|
||||
#define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0)
|
||||
#define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1)
|
||||
#define NVQUIRK_ENABLE_SDHCI_SPEC_300 BIT(2)
|
||||
#define NVQUIRK_ENABLE_SDR50 BIT(3)
|
||||
#define NVQUIRK_ENABLE_SDR104 BIT(4)
|
||||
#define NVQUIRK_ENABLE_DDR50 BIT(5)
|
||||
#define NVQUIRK_HAS_PADCALIB BIT(6)
|
||||
#define NVQUIRK_NEEDS_PAD_CONTROL BIT(7)
|
||||
#define NVQUIRK_DIS_CARD_CLK_CONFIG_TAP BIT(8)
|
||||
|
||||
struct sdhci_tegra_soc_data {
|
||||
const struct sdhci_pltfm_data *pdata;
|
||||
u32 nvquirks;
|
||||
};
|
||||
|
||||
/* Magic pull up and pull down pad calibration offsets */
|
||||
struct sdhci_tegra_autocal_offsets {
|
||||
u32 pull_up_3v3;
|
||||
u32 pull_down_3v3;
|
||||
u32 pull_up_3v3_timeout;
|
||||
u32 pull_down_3v3_timeout;
|
||||
u32 pull_up_1v8;
|
||||
u32 pull_down_1v8;
|
||||
u32 pull_up_1v8_timeout;
|
||||
u32 pull_down_1v8_timeout;
|
||||
u32 pull_up_sdr104;
|
||||
u32 pull_down_sdr104;
|
||||
u32 pull_up_hs400;
|
||||
u32 pull_down_hs400;
|
||||
};
|
||||
|
||||
struct sdhci_tegra {
|
||||
const struct sdhci_tegra_soc_data *soc_data;
|
||||
struct gpio_desc *power_gpio;
|
||||
bool ddr_signaling;
|
||||
bool pad_calib_required;
|
||||
bool pad_control_available;
|
||||
|
||||
struct reset_control *rst;
|
||||
struct pinctrl *pinctrl_sdmmc;
|
||||
struct pinctrl_state *pinctrl_state_3v3;
|
||||
struct pinctrl_state *pinctrl_state_1v8;
|
||||
|
||||
struct sdhci_tegra_autocal_offsets autocal_offsets;
|
||||
ktime_t last_calib;
|
||||
|
||||
u32 default_tap;
|
||||
u32 default_trim;
|
||||
u32 dqs_trim;
|
||||
};
|
||||
|
||||
static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg)
|
||||
|
@ -133,23 +193,149 @@ static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg)
|
|||
}
|
||||
}
|
||||
|
||||
static bool tegra_sdhci_configure_card_clk(struct sdhci_host *host, bool enable)
|
||||
{
|
||||
bool status;
|
||||
u32 reg;
|
||||
|
||||
reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
||||
status = !!(reg & SDHCI_CLOCK_CARD_EN);
|
||||
|
||||
if (status == enable)
|
||||
return status;
|
||||
|
||||
if (enable)
|
||||
reg |= SDHCI_CLOCK_CARD_EN;
|
||||
else
|
||||
reg &= ~SDHCI_CLOCK_CARD_EN;
|
||||
|
||||
sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void tegra210_sdhci_writew(struct sdhci_host *host, u16 val, int reg)
|
||||
{
|
||||
bool is_tuning_cmd = 0;
|
||||
bool clk_enabled;
|
||||
u8 cmd;
|
||||
|
||||
if (reg == SDHCI_COMMAND) {
|
||||
cmd = SDHCI_GET_CMD(val);
|
||||
is_tuning_cmd = cmd == MMC_SEND_TUNING_BLOCK ||
|
||||
cmd == MMC_SEND_TUNING_BLOCK_HS200;
|
||||
}
|
||||
|
||||
if (is_tuning_cmd)
|
||||
clk_enabled = tegra_sdhci_configure_card_clk(host, 0);
|
||||
|
||||
writew(val, host->ioaddr + reg);
|
||||
|
||||
if (is_tuning_cmd) {
|
||||
udelay(1);
|
||||
tegra_sdhci_configure_card_clk(host, clk_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host)
|
||||
{
|
||||
return mmc_gpio_get_ro(host->mmc);
|
||||
}
|
||||
|
||||
static bool tegra_sdhci_is_pad_and_regulator_valid(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
int has_1v8, has_3v3;
|
||||
|
||||
/*
|
||||
* The SoCs which have NVQUIRK_NEEDS_PAD_CONTROL require software pad
|
||||
* voltage configuration in order to perform voltage switching. This
|
||||
* means that valid pinctrl info is required on SDHCI instances capable
|
||||
* of performing voltage switching. Whether or not an SDHCI instance is
|
||||
* capable of voltage switching is determined based on the regulator.
|
||||
*/
|
||||
|
||||
if (!(tegra_host->soc_data->nvquirks & NVQUIRK_NEEDS_PAD_CONTROL))
|
||||
return true;
|
||||
|
||||
if (IS_ERR(host->mmc->supply.vqmmc))
|
||||
return false;
|
||||
|
||||
has_1v8 = regulator_is_supported_voltage(host->mmc->supply.vqmmc,
|
||||
1700000, 1950000);
|
||||
|
||||
has_3v3 = regulator_is_supported_voltage(host->mmc->supply.vqmmc,
|
||||
2700000, 3600000);
|
||||
|
||||
if (has_1v8 == 1 && has_3v3 == 1)
|
||||
return tegra_host->pad_control_available;
|
||||
|
||||
/* Fixed voltage, no pad control required. */
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tegra_sdhci_set_tap(struct sdhci_host *host, unsigned int tap)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data;
|
||||
bool card_clk_enabled = false;
|
||||
u32 reg;
|
||||
|
||||
/*
|
||||
* Touching the tap values is a bit tricky on some SoC generations.
|
||||
* The quirk enables a workaround for a glitch that sometimes occurs if
|
||||
* the tap values are changed.
|
||||
*/
|
||||
|
||||
if (soc_data->nvquirks & NVQUIRK_DIS_CARD_CLK_CONFIG_TAP)
|
||||
card_clk_enabled = tegra_sdhci_configure_card_clk(host, false);
|
||||
|
||||
reg = sdhci_readl(host, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
|
||||
reg &= ~SDHCI_CLOCK_CTRL_TAP_MASK;
|
||||
reg |= tap << SDHCI_CLOCK_CTRL_TAP_SHIFT;
|
||||
sdhci_writel(host, reg, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
|
||||
|
||||
if (soc_data->nvquirks & NVQUIRK_DIS_CARD_CLK_CONFIG_TAP &&
|
||||
card_clk_enabled) {
|
||||
udelay(1);
|
||||
sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
|
||||
tegra_sdhci_configure_card_clk(host, card_clk_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
static void tegra_sdhci_hs400_enhanced_strobe(struct mmc_host *mmc,
|
||||
struct mmc_ios *ios)
|
||||
{
|
||||
struct sdhci_host *host = mmc_priv(mmc);
|
||||
u32 val;
|
||||
|
||||
val = sdhci_readl(host, SDHCI_TEGRA_VENDOR_SYS_SW_CTRL);
|
||||
|
||||
if (ios->enhanced_strobe)
|
||||
val |= SDHCI_TEGRA_SYS_SW_CTRL_ENHANCED_STROBE;
|
||||
else
|
||||
val &= ~SDHCI_TEGRA_SYS_SW_CTRL_ENHANCED_STROBE;
|
||||
|
||||
sdhci_writel(host, val, SDHCI_TEGRA_VENDOR_SYS_SW_CTRL);
|
||||
|
||||
}
|
||||
|
||||
static void tegra_sdhci_reset(struct sdhci_host *host, u8 mask)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data;
|
||||
u32 misc_ctrl, clk_ctrl;
|
||||
u32 misc_ctrl, clk_ctrl, pad_ctrl;
|
||||
|
||||
sdhci_reset(host, mask);
|
||||
|
||||
if (!(mask & SDHCI_RESET_ALL))
|
||||
return;
|
||||
|
||||
tegra_sdhci_set_tap(host, tegra_host->default_tap);
|
||||
|
||||
misc_ctrl = sdhci_readl(host, SDHCI_TEGRA_VENDOR_MISC_CTRL);
|
||||
clk_ctrl = sdhci_readl(host, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
|
||||
|
||||
|
@ -158,15 +344,10 @@ static void tegra_sdhci_reset(struct sdhci_host *host, u8 mask)
|
|||
SDHCI_MISC_CTRL_ENABLE_DDR50 |
|
||||
SDHCI_MISC_CTRL_ENABLE_SDR104);
|
||||
|
||||
clk_ctrl &= ~SDHCI_CLOCK_CTRL_SPI_MODE_CLKEN_OVERRIDE;
|
||||
clk_ctrl &= ~(SDHCI_CLOCK_CTRL_TRIM_MASK |
|
||||
SDHCI_CLOCK_CTRL_SPI_MODE_CLKEN_OVERRIDE);
|
||||
|
||||
/*
|
||||
* If the board does not define a regulator for the SDHCI
|
||||
* IO voltage, then don't advertise support for UHS modes
|
||||
* even if the device supports it because the IO voltage
|
||||
* cannot be configured.
|
||||
*/
|
||||
if (!IS_ERR(host->mmc->supply.vqmmc)) {
|
||||
if (tegra_sdhci_is_pad_and_regulator_valid(host)) {
|
||||
/* Erratum: Enable SDHCI spec v3.00 support */
|
||||
if (soc_data->nvquirks & NVQUIRK_ENABLE_SDHCI_SPEC_300)
|
||||
misc_ctrl |= SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300;
|
||||
|
@ -181,24 +362,237 @@ static void tegra_sdhci_reset(struct sdhci_host *host, u8 mask)
|
|||
clk_ctrl |= SDHCI_CLOCK_CTRL_SDR50_TUNING_OVERRIDE;
|
||||
}
|
||||
|
||||
clk_ctrl |= tegra_host->default_trim << SDHCI_CLOCK_CTRL_TRIM_SHIFT;
|
||||
|
||||
sdhci_writel(host, misc_ctrl, SDHCI_TEGRA_VENDOR_MISC_CTRL);
|
||||
sdhci_writel(host, clk_ctrl, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
|
||||
|
||||
if (soc_data->nvquirks & NVQUIRK_HAS_PADCALIB)
|
||||
if (soc_data->nvquirks & NVQUIRK_HAS_PADCALIB) {
|
||||
pad_ctrl = sdhci_readl(host, SDHCI_TEGRA_SDMEM_COMP_PADCTRL);
|
||||
pad_ctrl &= ~SDHCI_TEGRA_SDMEM_COMP_PADCTRL_VREF_SEL_MASK;
|
||||
pad_ctrl |= SDHCI_TEGRA_SDMEM_COMP_PADCTRL_VREF_SEL_VAL;
|
||||
sdhci_writel(host, pad_ctrl, SDHCI_TEGRA_SDMEM_COMP_PADCTRL);
|
||||
|
||||
tegra_host->pad_calib_required = true;
|
||||
}
|
||||
|
||||
tegra_host->ddr_signaling = false;
|
||||
}
|
||||
|
||||
static void tegra_sdhci_pad_autocalib(struct sdhci_host *host)
|
||||
static void tegra_sdhci_configure_cal_pad(struct sdhci_host *host, bool enable)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
mdelay(1);
|
||||
/*
|
||||
* Enable or disable the additional I/O pad used by the drive strength
|
||||
* calibration process.
|
||||
*/
|
||||
val = sdhci_readl(host, SDHCI_TEGRA_SDMEM_COMP_PADCTRL);
|
||||
|
||||
val = sdhci_readl(host, SDHCI_TEGRA_AUTO_CAL_CONFIG);
|
||||
val |= SDHCI_AUTO_CAL_ENABLE | SDHCI_AUTO_CAL_START;
|
||||
sdhci_writel(host,val, SDHCI_TEGRA_AUTO_CAL_CONFIG);
|
||||
if (enable)
|
||||
val |= SDHCI_TEGRA_SDMEM_COMP_PADCTRL_E_INPUT_E_PWRD;
|
||||
else
|
||||
val &= ~SDHCI_TEGRA_SDMEM_COMP_PADCTRL_E_INPUT_E_PWRD;
|
||||
|
||||
sdhci_writel(host, val, SDHCI_TEGRA_SDMEM_COMP_PADCTRL);
|
||||
|
||||
if (enable)
|
||||
usleep_range(1, 2);
|
||||
}
|
||||
|
||||
static void tegra_sdhci_set_pad_autocal_offset(struct sdhci_host *host,
|
||||
u16 pdpu)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = sdhci_readl(host, SDHCI_TEGRA_AUTO_CAL_CONFIG);
|
||||
reg &= ~SDHCI_AUTO_CAL_PDPU_OFFSET_MASK;
|
||||
reg |= pdpu;
|
||||
sdhci_writel(host, reg, SDHCI_TEGRA_AUTO_CAL_CONFIG);
|
||||
}
|
||||
|
||||
static void tegra_sdhci_pad_autocalib(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
struct sdhci_tegra_autocal_offsets offsets =
|
||||
tegra_host->autocal_offsets;
|
||||
struct mmc_ios *ios = &host->mmc->ios;
|
||||
bool card_clk_enabled;
|
||||
u16 pdpu;
|
||||
u32 reg;
|
||||
int ret;
|
||||
|
||||
switch (ios->timing) {
|
||||
case MMC_TIMING_UHS_SDR104:
|
||||
pdpu = offsets.pull_down_sdr104 << 8 | offsets.pull_up_sdr104;
|
||||
break;
|
||||
case MMC_TIMING_MMC_HS400:
|
||||
pdpu = offsets.pull_down_hs400 << 8 | offsets.pull_up_hs400;
|
||||
break;
|
||||
default:
|
||||
if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180)
|
||||
pdpu = offsets.pull_down_1v8 << 8 | offsets.pull_up_1v8;
|
||||
else
|
||||
pdpu = offsets.pull_down_3v3 << 8 | offsets.pull_up_3v3;
|
||||
}
|
||||
|
||||
tegra_sdhci_set_pad_autocal_offset(host, pdpu);
|
||||
|
||||
card_clk_enabled = tegra_sdhci_configure_card_clk(host, false);
|
||||
|
||||
tegra_sdhci_configure_cal_pad(host, true);
|
||||
|
||||
reg = sdhci_readl(host, SDHCI_TEGRA_AUTO_CAL_CONFIG);
|
||||
reg |= SDHCI_AUTO_CAL_ENABLE | SDHCI_AUTO_CAL_START;
|
||||
sdhci_writel(host, reg, SDHCI_TEGRA_AUTO_CAL_CONFIG);
|
||||
|
||||
usleep_range(1, 2);
|
||||
/* 10 ms timeout */
|
||||
ret = readl_poll_timeout(host->ioaddr + SDHCI_TEGRA_AUTO_CAL_STATUS,
|
||||
reg, !(reg & SDHCI_TEGRA_AUTO_CAL_ACTIVE),
|
||||
1000, 10000);
|
||||
|
||||
tegra_sdhci_configure_cal_pad(host, false);
|
||||
|
||||
tegra_sdhci_configure_card_clk(host, card_clk_enabled);
|
||||
|
||||
if (ret) {
|
||||
dev_err(mmc_dev(host->mmc), "Pad autocal timed out\n");
|
||||
|
||||
if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180)
|
||||
pdpu = offsets.pull_down_1v8_timeout << 8 |
|
||||
offsets.pull_up_1v8_timeout;
|
||||
else
|
||||
pdpu = offsets.pull_down_3v3_timeout << 8 |
|
||||
offsets.pull_up_3v3_timeout;
|
||||
|
||||
/* Disable automatic calibration and use fixed offsets */
|
||||
reg = sdhci_readl(host, SDHCI_TEGRA_AUTO_CAL_CONFIG);
|
||||
reg &= ~SDHCI_AUTO_CAL_ENABLE;
|
||||
sdhci_writel(host, reg, SDHCI_TEGRA_AUTO_CAL_CONFIG);
|
||||
|
||||
tegra_sdhci_set_pad_autocal_offset(host, pdpu);
|
||||
}
|
||||
}
|
||||
|
||||
static void tegra_sdhci_parse_pad_autocal_dt(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
struct sdhci_tegra_autocal_offsets *autocal =
|
||||
&tegra_host->autocal_offsets;
|
||||
int err;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-up-offset-3v3",
|
||||
&autocal->pull_up_3v3);
|
||||
if (err)
|
||||
autocal->pull_up_3v3 = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-down-offset-3v3",
|
||||
&autocal->pull_down_3v3);
|
||||
if (err)
|
||||
autocal->pull_down_3v3 = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-up-offset-1v8",
|
||||
&autocal->pull_up_1v8);
|
||||
if (err)
|
||||
autocal->pull_up_1v8 = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-down-offset-1v8",
|
||||
&autocal->pull_down_1v8);
|
||||
if (err)
|
||||
autocal->pull_down_1v8 = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-up-offset-3v3-timeout",
|
||||
&autocal->pull_up_3v3);
|
||||
if (err)
|
||||
autocal->pull_up_3v3_timeout = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-down-offset-3v3-timeout",
|
||||
&autocal->pull_down_3v3);
|
||||
if (err)
|
||||
autocal->pull_down_3v3_timeout = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-up-offset-1v8-timeout",
|
||||
&autocal->pull_up_1v8);
|
||||
if (err)
|
||||
autocal->pull_up_1v8_timeout = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-down-offset-1v8-timeout",
|
||||
&autocal->pull_down_1v8);
|
||||
if (err)
|
||||
autocal->pull_down_1v8_timeout = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-up-offset-sdr104",
|
||||
&autocal->pull_up_sdr104);
|
||||
if (err)
|
||||
autocal->pull_up_sdr104 = autocal->pull_up_1v8;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-down-offset-sdr104",
|
||||
&autocal->pull_down_sdr104);
|
||||
if (err)
|
||||
autocal->pull_down_sdr104 = autocal->pull_down_1v8;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-up-offset-hs400",
|
||||
&autocal->pull_up_hs400);
|
||||
if (err)
|
||||
autocal->pull_up_hs400 = autocal->pull_up_1v8;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent,
|
||||
"nvidia,pad-autocal-pull-down-offset-hs400",
|
||||
&autocal->pull_down_hs400);
|
||||
if (err)
|
||||
autocal->pull_down_hs400 = autocal->pull_down_1v8;
|
||||
}
|
||||
|
||||
static void tegra_sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
{
|
||||
struct sdhci_host *host = mmc_priv(mmc);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
ktime_t since_calib = ktime_sub(ktime_get(), tegra_host->last_calib);
|
||||
|
||||
/* 100 ms calibration interval is specified in the TRM */
|
||||
if (ktime_to_ms(since_calib) > 100) {
|
||||
tegra_sdhci_pad_autocalib(host);
|
||||
tegra_host->last_calib = ktime_get();
|
||||
}
|
||||
|
||||
sdhci_request(mmc, mrq);
|
||||
}
|
||||
|
||||
static void tegra_sdhci_parse_tap_and_trim(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
int err;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent, "nvidia,default-tap",
|
||||
&tegra_host->default_tap);
|
||||
if (err)
|
||||
tegra_host->default_tap = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent, "nvidia,default-trim",
|
||||
&tegra_host->default_trim);
|
||||
if (err)
|
||||
tegra_host->default_trim = 0;
|
||||
|
||||
err = device_property_read_u32(host->mmc->parent, "nvidia,dqs-trim",
|
||||
&tegra_host->dqs_trim);
|
||||
if (err)
|
||||
tegra_host->dqs_trim = 0x11;
|
||||
}
|
||||
|
||||
static void tegra_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
|
||||
|
@ -237,19 +631,6 @@ static void tegra_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
|
|||
}
|
||||
}
|
||||
|
||||
static void tegra_sdhci_set_uhs_signaling(struct sdhci_host *host,
|
||||
unsigned timing)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
|
||||
if (timing == MMC_TIMING_UHS_DDR50 ||
|
||||
timing == MMC_TIMING_MMC_DDR52)
|
||||
tegra_host->ddr_signaling = true;
|
||||
|
||||
sdhci_set_uhs_signaling(host, timing);
|
||||
}
|
||||
|
||||
static unsigned int tegra_sdhci_get_max_clock(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
|
@ -257,14 +638,75 @@ static unsigned int tegra_sdhci_get_max_clock(struct sdhci_host *host)
|
|||
return clk_round_rate(pltfm_host->clk, UINT_MAX);
|
||||
}
|
||||
|
||||
static void tegra_sdhci_set_tap(struct sdhci_host *host, unsigned int tap)
|
||||
static void tegra_sdhci_set_dqs_trim(struct sdhci_host *host, u8 trim)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = sdhci_readl(host, SDHCI_TEGRA_VENDOR_CAP_OVERRIDES);
|
||||
val &= ~SDHCI_TEGRA_CAP_OVERRIDES_DQS_TRIM_MASK;
|
||||
val |= trim << SDHCI_TEGRA_CAP_OVERRIDES_DQS_TRIM_SHIFT;
|
||||
sdhci_writel(host, val, SDHCI_TEGRA_VENDOR_CAP_OVERRIDES);
|
||||
}
|
||||
|
||||
static void tegra_sdhci_hs400_dll_cal(struct sdhci_host *host)
|
||||
{
|
||||
u32 reg;
|
||||
int err;
|
||||
|
||||
reg = sdhci_readl(host, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
|
||||
reg &= ~SDHCI_CLOCK_CTRL_TAP_MASK;
|
||||
reg |= tap << SDHCI_CLOCK_CTRL_TAP_SHIFT;
|
||||
sdhci_writel(host, reg, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
|
||||
reg = sdhci_readl(host, SDHCI_TEGRA_VENDOR_DLLCAL_CFG);
|
||||
reg |= SDHCI_TEGRA_DLLCAL_CALIBRATE;
|
||||
sdhci_writel(host, reg, SDHCI_TEGRA_VENDOR_DLLCAL_CFG);
|
||||
|
||||
/* 1 ms sleep, 5 ms timeout */
|
||||
err = readl_poll_timeout(host->ioaddr + SDHCI_TEGRA_VENDOR_DLLCAL_STA,
|
||||
reg, !(reg & SDHCI_TEGRA_DLLCAL_STA_ACTIVE),
|
||||
1000, 5000);
|
||||
if (err)
|
||||
dev_err(mmc_dev(host->mmc),
|
||||
"HS400 delay line calibration timed out\n");
|
||||
}
|
||||
|
||||
static void tegra_sdhci_set_uhs_signaling(struct sdhci_host *host,
|
||||
unsigned timing)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
bool set_default_tap = false;
|
||||
bool set_dqs_trim = false;
|
||||
bool do_hs400_dll_cal = false;
|
||||
|
||||
switch (timing) {
|
||||
case MMC_TIMING_UHS_SDR50:
|
||||
case MMC_TIMING_UHS_SDR104:
|
||||
case MMC_TIMING_MMC_HS200:
|
||||
/* Don't set default tap on tunable modes. */
|
||||
break;
|
||||
case MMC_TIMING_MMC_HS400:
|
||||
set_dqs_trim = true;
|
||||
do_hs400_dll_cal = true;
|
||||
break;
|
||||
case MMC_TIMING_MMC_DDR52:
|
||||
case MMC_TIMING_UHS_DDR50:
|
||||
tegra_host->ddr_signaling = true;
|
||||
set_default_tap = true;
|
||||
break;
|
||||
default:
|
||||
set_default_tap = true;
|
||||
break;
|
||||
}
|
||||
|
||||
sdhci_set_uhs_signaling(host, timing);
|
||||
|
||||
tegra_sdhci_pad_autocalib(host);
|
||||
|
||||
if (set_default_tap)
|
||||
tegra_sdhci_set_tap(host, tegra_host->default_tap);
|
||||
|
||||
if (set_dqs_trim)
|
||||
tegra_sdhci_set_dqs_trim(host, tegra_host->dqs_trim);
|
||||
|
||||
if (do_hs400_dll_cal)
|
||||
tegra_sdhci_hs400_dll_cal(host);
|
||||
}
|
||||
|
||||
static int tegra_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
|
@ -301,6 +743,89 @@ static int tegra_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
|||
return mmc_send_tuning(host->mmc, opcode, NULL);
|
||||
}
|
||||
|
||||
static int tegra_sdhci_set_padctrl(struct sdhci_host *host, int voltage)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
int ret;
|
||||
|
||||
if (!tegra_host->pad_control_available)
|
||||
return 0;
|
||||
|
||||
if (voltage == MMC_SIGNAL_VOLTAGE_180) {
|
||||
ret = pinctrl_select_state(tegra_host->pinctrl_sdmmc,
|
||||
tegra_host->pinctrl_state_1v8);
|
||||
if (ret < 0)
|
||||
dev_err(mmc_dev(host->mmc),
|
||||
"setting 1.8V failed, ret: %d\n", ret);
|
||||
} else {
|
||||
ret = pinctrl_select_state(tegra_host->pinctrl_sdmmc,
|
||||
tegra_host->pinctrl_state_3v3);
|
||||
if (ret < 0)
|
||||
dev_err(mmc_dev(host->mmc),
|
||||
"setting 3.3V failed, ret: %d\n", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdhci_tegra_start_signal_voltage_switch(struct mmc_host *mmc,
|
||||
struct mmc_ios *ios)
|
||||
{
|
||||
struct sdhci_host *host = mmc_priv(mmc);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
int ret = 0;
|
||||
|
||||
if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) {
|
||||
ret = tegra_sdhci_set_padctrl(host, ios->signal_voltage);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = sdhci_start_signal_voltage_switch(mmc, ios);
|
||||
} else if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180) {
|
||||
ret = sdhci_start_signal_voltage_switch(mmc, ios);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = tegra_sdhci_set_padctrl(host, ios->signal_voltage);
|
||||
}
|
||||
|
||||
if (tegra_host->pad_calib_required)
|
||||
tegra_sdhci_pad_autocalib(host);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tegra_sdhci_init_pinctrl_info(struct device *dev,
|
||||
struct sdhci_tegra *tegra_host)
|
||||
{
|
||||
tegra_host->pinctrl_sdmmc = devm_pinctrl_get(dev);
|
||||
if (IS_ERR(tegra_host->pinctrl_sdmmc)) {
|
||||
dev_dbg(dev, "No pinctrl info, err: %ld\n",
|
||||
PTR_ERR(tegra_host->pinctrl_sdmmc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
tegra_host->pinctrl_state_3v3 =
|
||||
pinctrl_lookup_state(tegra_host->pinctrl_sdmmc, "sdmmc-3v3");
|
||||
if (IS_ERR(tegra_host->pinctrl_state_3v3)) {
|
||||
dev_warn(dev, "Missing 3.3V pad state, err: %ld\n",
|
||||
PTR_ERR(tegra_host->pinctrl_state_3v3));
|
||||
return -1;
|
||||
}
|
||||
|
||||
tegra_host->pinctrl_state_1v8 =
|
||||
pinctrl_lookup_state(tegra_host->pinctrl_sdmmc, "sdmmc-1v8");
|
||||
if (IS_ERR(tegra_host->pinctrl_state_1v8)) {
|
||||
dev_warn(dev, "Missing 1.8V pad state, err: %ld\n",
|
||||
PTR_ERR(tegra_host->pinctrl_state_1v8));
|
||||
return -1;
|
||||
}
|
||||
|
||||
tegra_host->pad_control_available = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tegra_sdhci_voltage_switch(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
|
@ -421,6 +946,19 @@ static const struct sdhci_tegra_soc_data soc_data_tegra124 = {
|
|||
.pdata = &sdhci_tegra124_pdata,
|
||||
};
|
||||
|
||||
static const struct sdhci_ops tegra210_sdhci_ops = {
|
||||
.get_ro = tegra_sdhci_get_ro,
|
||||
.read_w = tegra_sdhci_readw,
|
||||
.write_w = tegra210_sdhci_writew,
|
||||
.write_l = tegra_sdhci_writel,
|
||||
.set_clock = tegra_sdhci_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.reset = tegra_sdhci_reset,
|
||||
.set_uhs_signaling = tegra_sdhci_set_uhs_signaling,
|
||||
.voltage_switch = tegra_sdhci_voltage_switch,
|
||||
.get_max_clock = tegra_sdhci_get_max_clock,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_tegra210_pdata = {
|
||||
.quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
|
||||
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
|
||||
|
@ -429,11 +967,28 @@ static const struct sdhci_pltfm_data sdhci_tegra210_pdata = {
|
|||
SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC |
|
||||
SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
|
||||
.ops = &tegra114_sdhci_ops,
|
||||
.ops = &tegra210_sdhci_ops,
|
||||
};
|
||||
|
||||
static const struct sdhci_tegra_soc_data soc_data_tegra210 = {
|
||||
.pdata = &sdhci_tegra210_pdata,
|
||||
.nvquirks = NVQUIRK_NEEDS_PAD_CONTROL |
|
||||
NVQUIRK_HAS_PADCALIB |
|
||||
NVQUIRK_DIS_CARD_CLK_CONFIG_TAP |
|
||||
NVQUIRK_ENABLE_SDR50 |
|
||||
NVQUIRK_ENABLE_SDR104,
|
||||
};
|
||||
|
||||
static const struct sdhci_ops tegra186_sdhci_ops = {
|
||||
.get_ro = tegra_sdhci_get_ro,
|
||||
.read_w = tegra_sdhci_readw,
|
||||
.write_l = tegra_sdhci_writel,
|
||||
.set_clock = tegra_sdhci_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.reset = tegra_sdhci_reset,
|
||||
.set_uhs_signaling = tegra_sdhci_set_uhs_signaling,
|
||||
.voltage_switch = tegra_sdhci_voltage_switch,
|
||||
.get_max_clock = tegra_sdhci_get_max_clock,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_tegra186_pdata = {
|
||||
|
@ -452,11 +1007,16 @@ static const struct sdhci_pltfm_data sdhci_tegra186_pdata = {
|
|||
* But it is not supported as of now.
|
||||
*/
|
||||
SDHCI_QUIRK2_BROKEN_64_BIT_DMA,
|
||||
.ops = &tegra114_sdhci_ops,
|
||||
.ops = &tegra186_sdhci_ops,
|
||||
};
|
||||
|
||||
static const struct sdhci_tegra_soc_data soc_data_tegra186 = {
|
||||
.pdata = &sdhci_tegra186_pdata,
|
||||
.nvquirks = NVQUIRK_NEEDS_PAD_CONTROL |
|
||||
NVQUIRK_HAS_PADCALIB |
|
||||
NVQUIRK_DIS_CARD_CLK_CONFIG_TAP |
|
||||
NVQUIRK_ENABLE_SDR50 |
|
||||
NVQUIRK_ENABLE_SDR104,
|
||||
};
|
||||
|
||||
static const struct of_device_id sdhci_tegra_dt_match[] = {
|
||||
|
@ -493,8 +1053,23 @@ static int sdhci_tegra_probe(struct platform_device *pdev)
|
|||
tegra_host = sdhci_pltfm_priv(pltfm_host);
|
||||
tegra_host->ddr_signaling = false;
|
||||
tegra_host->pad_calib_required = false;
|
||||
tegra_host->pad_control_available = false;
|
||||
tegra_host->soc_data = soc_data;
|
||||
|
||||
if (soc_data->nvquirks & NVQUIRK_NEEDS_PAD_CONTROL) {
|
||||
rc = tegra_sdhci_init_pinctrl_info(&pdev->dev, tegra_host);
|
||||
if (rc == 0)
|
||||
host->mmc_host_ops.start_signal_voltage_switch =
|
||||
sdhci_tegra_start_signal_voltage_switch;
|
||||
}
|
||||
|
||||
/* Hook to periodically rerun pad calibration */
|
||||
if (soc_data->nvquirks & NVQUIRK_HAS_PADCALIB)
|
||||
host->mmc_host_ops.request = tegra_sdhci_request;
|
||||
|
||||
host->mmc_host_ops.hs400_enhanced_strobe =
|
||||
tegra_sdhci_hs400_enhanced_strobe;
|
||||
|
||||
rc = mmc_of_parse(host->mmc);
|
||||
if (rc)
|
||||
goto err_parse_dt;
|
||||
|
@ -502,6 +1077,10 @@ static int sdhci_tegra_probe(struct platform_device *pdev)
|
|||
if (tegra_host->soc_data->nvquirks & NVQUIRK_ENABLE_DDR50)
|
||||
host->mmc->caps |= MMC_CAP_1_8V_DDR;
|
||||
|
||||
tegra_sdhci_parse_pad_autocal_dt(host);
|
||||
|
||||
tegra_sdhci_parse_tap_and_trim(host);
|
||||
|
||||
tegra_host->power_gpio = devm_gpiod_get_optional(&pdev->dev, "power",
|
||||
GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(tegra_host->power_gpio)) {
|
||||
|
|
|
@ -660,8 +660,8 @@ static int get_dt_pad_ctrl_data(struct sdhci_host *host,
|
|||
return 0;
|
||||
|
||||
if (of_address_to_resource(np, 1, &iomem)) {
|
||||
dev_err(mmc_dev(host->mmc), "Unable to find SoC PAD ctrl register address for %s\n",
|
||||
np->name);
|
||||
dev_err(mmc_dev(host->mmc), "Unable to find SoC PAD ctrl register address for %pOFn\n",
|
||||
np);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
|
@ -123,6 +123,29 @@ EXPORT_SYMBOL_GPL(sdhci_dumpregs);
|
|||
* *
|
||||
\*****************************************************************************/
|
||||
|
||||
static void sdhci_do_enable_v4_mode(struct sdhci_host *host)
|
||||
{
|
||||
u16 ctrl2;
|
||||
|
||||
ctrl2 = sdhci_readb(host, SDHCI_HOST_CONTROL2);
|
||||
if (ctrl2 & SDHCI_CTRL_V4_MODE)
|
||||
return;
|
||||
|
||||
ctrl2 |= SDHCI_CTRL_V4_MODE;
|
||||
sdhci_writeb(host, ctrl2, SDHCI_HOST_CONTROL);
|
||||
}
|
||||
|
||||
/*
|
||||
* This can be called before sdhci_add_host() by Vendor's host controller
|
||||
* driver to enable v4 mode if supported.
|
||||
*/
|
||||
void sdhci_enable_v4_mode(struct sdhci_host *host)
|
||||
{
|
||||
host->v4_mode = true;
|
||||
sdhci_do_enable_v4_mode(host);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdhci_enable_v4_mode);
|
||||
|
||||
static inline bool sdhci_data_line_cmd(struct mmc_command *cmd)
|
||||
{
|
||||
return cmd->data || cmd->flags & MMC_RSP_BUSY;
|
||||
|
@ -243,6 +266,52 @@ static void sdhci_set_default_irqs(struct sdhci_host *host)
|
|||
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
|
||||
}
|
||||
|
||||
static void sdhci_config_dma(struct sdhci_host *host)
|
||||
{
|
||||
u8 ctrl;
|
||||
u16 ctrl2;
|
||||
|
||||
if (host->version < SDHCI_SPEC_200)
|
||||
return;
|
||||
|
||||
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
|
||||
|
||||
/*
|
||||
* Always adjust the DMA selection as some controllers
|
||||
* (e.g. JMicron) can't do PIO properly when the selection
|
||||
* is ADMA.
|
||||
*/
|
||||
ctrl &= ~SDHCI_CTRL_DMA_MASK;
|
||||
if (!(host->flags & SDHCI_REQ_USE_DMA))
|
||||
goto out;
|
||||
|
||||
/* Note if DMA Select is zero then SDMA is selected */
|
||||
if (host->flags & SDHCI_USE_ADMA)
|
||||
ctrl |= SDHCI_CTRL_ADMA32;
|
||||
|
||||
if (host->flags & SDHCI_USE_64_BIT_DMA) {
|
||||
/*
|
||||
* If v4 mode, all supported DMA can be 64-bit addressing if
|
||||
* controller supports 64-bit system address, otherwise only
|
||||
* ADMA can support 64-bit addressing.
|
||||
*/
|
||||
if (host->v4_mode) {
|
||||
ctrl2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
||||
ctrl2 |= SDHCI_CTRL_64BIT_ADDR;
|
||||
sdhci_writew(host, ctrl2, SDHCI_HOST_CONTROL2);
|
||||
} else if (host->flags & SDHCI_USE_ADMA) {
|
||||
/*
|
||||
* Don't need to undo SDHCI_CTRL_ADMA32 in order to
|
||||
* set SDHCI_CTRL_ADMA64.
|
||||
*/
|
||||
ctrl |= SDHCI_CTRL_ADMA64;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
|
||||
}
|
||||
|
||||
static void sdhci_init(struct sdhci_host *host, int soft)
|
||||
{
|
||||
struct mmc_host *mmc = host->mmc;
|
||||
|
@ -252,6 +321,9 @@ static void sdhci_init(struct sdhci_host *host, int soft)
|
|||
else
|
||||
sdhci_do_reset(host, SDHCI_RESET_ALL);
|
||||
|
||||
if (host->v4_mode)
|
||||
sdhci_do_enable_v4_mode(host);
|
||||
|
||||
sdhci_set_default_irqs(host);
|
||||
|
||||
host->cqe_on = false;
|
||||
|
@ -554,10 +626,10 @@ static void sdhci_kunmap_atomic(void *buffer, unsigned long *flags)
|
|||
local_irq_restore(*flags);
|
||||
}
|
||||
|
||||
static void sdhci_adma_write_desc(struct sdhci_host *host, void *desc,
|
||||
dma_addr_t addr, int len, unsigned cmd)
|
||||
void sdhci_adma_write_desc(struct sdhci_host *host, void **desc,
|
||||
dma_addr_t addr, int len, unsigned int cmd)
|
||||
{
|
||||
struct sdhci_adma2_64_desc *dma_desc = desc;
|
||||
struct sdhci_adma2_64_desc *dma_desc = *desc;
|
||||
|
||||
/* 32-bit and 64-bit descriptors have these members in same position */
|
||||
dma_desc->cmd = cpu_to_le16(cmd);
|
||||
|
@ -566,6 +638,19 @@ static void sdhci_adma_write_desc(struct sdhci_host *host, void *desc,
|
|||
|
||||
if (host->flags & SDHCI_USE_64_BIT_DMA)
|
||||
dma_desc->addr_hi = cpu_to_le32((u64)addr >> 32);
|
||||
|
||||
*desc += host->desc_sz;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdhci_adma_write_desc);
|
||||
|
||||
static inline void __sdhci_adma_write_desc(struct sdhci_host *host,
|
||||
void **desc, dma_addr_t addr,
|
||||
int len, unsigned int cmd)
|
||||
{
|
||||
if (host->ops->adma_write_desc)
|
||||
host->ops->adma_write_desc(host, desc, addr, len, cmd);
|
||||
else
|
||||
sdhci_adma_write_desc(host, desc, addr, len, cmd);
|
||||
}
|
||||
|
||||
static void sdhci_adma_mark_end(void *desc)
|
||||
|
@ -618,28 +703,24 @@ static void sdhci_adma_table_pre(struct sdhci_host *host,
|
|||
}
|
||||
|
||||
/* tran, valid */
|
||||
sdhci_adma_write_desc(host, desc, align_addr, offset,
|
||||
ADMA2_TRAN_VALID);
|
||||
__sdhci_adma_write_desc(host, &desc, align_addr,
|
||||
offset, ADMA2_TRAN_VALID);
|
||||
|
||||
BUG_ON(offset > 65536);
|
||||
|
||||
align += SDHCI_ADMA2_ALIGN;
|
||||
align_addr += SDHCI_ADMA2_ALIGN;
|
||||
|
||||
desc += host->desc_sz;
|
||||
|
||||
addr += offset;
|
||||
len -= offset;
|
||||
}
|
||||
|
||||
BUG_ON(len > 65536);
|
||||
|
||||
if (len) {
|
||||
/* tran, valid */
|
||||
sdhci_adma_write_desc(host, desc, addr, len,
|
||||
ADMA2_TRAN_VALID);
|
||||
desc += host->desc_sz;
|
||||
}
|
||||
/* tran, valid */
|
||||
if (len)
|
||||
__sdhci_adma_write_desc(host, &desc, addr, len,
|
||||
ADMA2_TRAN_VALID);
|
||||
|
||||
/*
|
||||
* If this triggers then we have a calculation bug
|
||||
|
@ -656,7 +737,7 @@ static void sdhci_adma_table_pre(struct sdhci_host *host,
|
|||
}
|
||||
} else {
|
||||
/* Add a terminating entry - nop, end, valid */
|
||||
sdhci_adma_write_desc(host, desc, 0, 0, ADMA2_NOP_END_VALID);
|
||||
__sdhci_adma_write_desc(host, &desc, 0, 0, ADMA2_NOP_END_VALID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -701,7 +782,7 @@ static void sdhci_adma_table_post(struct sdhci_host *host,
|
|||
}
|
||||
}
|
||||
|
||||
static u32 sdhci_sdma_address(struct sdhci_host *host)
|
||||
static dma_addr_t sdhci_sdma_address(struct sdhci_host *host)
|
||||
{
|
||||
if (host->bounce_buffer)
|
||||
return host->bounce_addr;
|
||||
|
@ -709,6 +790,17 @@ static u32 sdhci_sdma_address(struct sdhci_host *host)
|
|||
return sg_dma_address(host->data->sg);
|
||||
}
|
||||
|
||||
static void sdhci_set_sdma_addr(struct sdhci_host *host, dma_addr_t addr)
|
||||
{
|
||||
if (host->v4_mode) {
|
||||
sdhci_writel(host, addr, SDHCI_ADMA_ADDRESS);
|
||||
if (host->flags & SDHCI_USE_64_BIT_DMA)
|
||||
sdhci_writel(host, (u64)addr >> 32, SDHCI_ADMA_ADDRESS_HI);
|
||||
} else {
|
||||
sdhci_writel(host, addr, SDHCI_DMA_ADDRESS);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int sdhci_target_timeout(struct sdhci_host *host,
|
||||
struct mmc_command *cmd,
|
||||
struct mmc_data *data)
|
||||
|
@ -876,7 +968,6 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
|
|||
|
||||
static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
|
||||
{
|
||||
u8 ctrl;
|
||||
struct mmc_data *data = cmd->data;
|
||||
|
||||
host->data_timeout = 0;
|
||||
|
@ -968,30 +1059,11 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
|
|||
SDHCI_ADMA_ADDRESS_HI);
|
||||
} else {
|
||||
WARN_ON(sg_cnt != 1);
|
||||
sdhci_writel(host, sdhci_sdma_address(host),
|
||||
SDHCI_DMA_ADDRESS);
|
||||
sdhci_set_sdma_addr(host, sdhci_sdma_address(host));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Always adjust the DMA selection as some controllers
|
||||
* (e.g. JMicron) can't do PIO properly when the selection
|
||||
* is ADMA.
|
||||
*/
|
||||
if (host->version >= SDHCI_SPEC_200) {
|
||||
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
|
||||
ctrl &= ~SDHCI_CTRL_DMA_MASK;
|
||||
if ((host->flags & SDHCI_REQ_USE_DMA) &&
|
||||
(host->flags & SDHCI_USE_ADMA)) {
|
||||
if (host->flags & SDHCI_USE_64_BIT_DMA)
|
||||
ctrl |= SDHCI_CTRL_ADMA64;
|
||||
else
|
||||
ctrl |= SDHCI_CTRL_ADMA32;
|
||||
} else {
|
||||
ctrl |= SDHCI_CTRL_SDMA;
|
||||
}
|
||||
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
|
||||
}
|
||||
sdhci_config_dma(host);
|
||||
|
||||
if (!(host->flags & SDHCI_REQ_USE_DMA)) {
|
||||
int flags;
|
||||
|
@ -1010,7 +1082,19 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
|
|||
/* Set the DMA boundary value and block size */
|
||||
sdhci_writew(host, SDHCI_MAKE_BLKSZ(host->sdma_boundary, data->blksz),
|
||||
SDHCI_BLOCK_SIZE);
|
||||
sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
|
||||
|
||||
/*
|
||||
* For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count
|
||||
* can be supported, in that case 16-bit block count register must be 0.
|
||||
*/
|
||||
if (host->version >= SDHCI_SPEC_410 && host->v4_mode &&
|
||||
(host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) {
|
||||
if (sdhci_readw(host, SDHCI_BLOCK_COUNT))
|
||||
sdhci_writew(host, 0, SDHCI_BLOCK_COUNT);
|
||||
sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT);
|
||||
} else {
|
||||
sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
|
||||
|
@ -1020,6 +1104,43 @@ static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
|
|||
!mrq->cap_cmd_during_tfr;
|
||||
}
|
||||
|
||||
static inline void sdhci_auto_cmd_select(struct sdhci_host *host,
|
||||
struct mmc_command *cmd,
|
||||
u16 *mode)
|
||||
{
|
||||
bool use_cmd12 = sdhci_auto_cmd12(host, cmd->mrq) &&
|
||||
(cmd->opcode != SD_IO_RW_EXTENDED);
|
||||
bool use_cmd23 = cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23);
|
||||
u16 ctrl2;
|
||||
|
||||
/*
|
||||
* In case of Version 4.10 or later, use of 'Auto CMD Auto
|
||||
* Select' is recommended rather than use of 'Auto CMD12
|
||||
* Enable' or 'Auto CMD23 Enable'.
|
||||
*/
|
||||
if (host->version >= SDHCI_SPEC_410 && (use_cmd12 || use_cmd23)) {
|
||||
*mode |= SDHCI_TRNS_AUTO_SEL;
|
||||
|
||||
ctrl2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
||||
if (use_cmd23)
|
||||
ctrl2 |= SDHCI_CMD23_ENABLE;
|
||||
else
|
||||
ctrl2 &= ~SDHCI_CMD23_ENABLE;
|
||||
sdhci_writew(host, ctrl2, SDHCI_HOST_CONTROL2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are sending CMD23, CMD12 never gets sent
|
||||
* on successful completion (so no Auto-CMD12).
|
||||
*/
|
||||
if (use_cmd12)
|
||||
*mode |= SDHCI_TRNS_AUTO_CMD12;
|
||||
else if (use_cmd23)
|
||||
*mode |= SDHCI_TRNS_AUTO_CMD23;
|
||||
}
|
||||
|
||||
static void sdhci_set_transfer_mode(struct sdhci_host *host,
|
||||
struct mmc_command *cmd)
|
||||
{
|
||||
|
@ -1048,17 +1169,9 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
|
|||
|
||||
if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
|
||||
mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
|
||||
/*
|
||||
* If we are sending CMD23, CMD12 never gets sent
|
||||
* on successful completion (so no Auto-CMD12).
|
||||
*/
|
||||
if (sdhci_auto_cmd12(host, cmd->mrq) &&
|
||||
(cmd->opcode != SD_IO_RW_EXTENDED))
|
||||
mode |= SDHCI_TRNS_AUTO_CMD12;
|
||||
else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
|
||||
mode |= SDHCI_TRNS_AUTO_CMD23;
|
||||
sdhci_auto_cmd_select(host, cmd, &mode);
|
||||
if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23))
|
||||
sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
|
||||
}
|
||||
}
|
||||
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
|
@ -1630,7 +1743,7 @@ EXPORT_SYMBOL_GPL(sdhci_set_power);
|
|||
* *
|
||||
\*****************************************************************************/
|
||||
|
||||
static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
{
|
||||
struct sdhci_host *host;
|
||||
int present;
|
||||
|
@ -1669,6 +1782,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|||
mmiowb();
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdhci_request);
|
||||
|
||||
void sdhci_set_bus_width(struct sdhci_host *host, int width)
|
||||
{
|
||||
|
@ -2219,7 +2333,7 @@ void sdhci_send_tuning(struct sdhci_host *host, u32 opcode)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(sdhci_send_tuning);
|
||||
|
||||
static void __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
static int __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
@ -2236,13 +2350,13 @@ static void __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
|||
pr_info("%s: Tuning timeout, falling back to fixed sampling clock\n",
|
||||
mmc_hostname(host->mmc));
|
||||
sdhci_abort_tuning(host, opcode);
|
||||
return;
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
||||
if (!(ctrl & SDHCI_CTRL_EXEC_TUNING)) {
|
||||
if (ctrl & SDHCI_CTRL_TUNED_CLK)
|
||||
return; /* Success! */
|
||||
return 0; /* Success! */
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2254,6 +2368,7 @@ static void __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
|||
pr_info("%s: Tuning failed, falling back to fixed sampling clock\n",
|
||||
mmc_hostname(host->mmc));
|
||||
sdhci_reset_tuning(host);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
||||
|
@ -2315,7 +2430,7 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|||
|
||||
sdhci_start_tuning(host);
|
||||
|
||||
__sdhci_execute_tuning(host, opcode);
|
||||
host->tuning_err = __sdhci_execute_tuning(host, opcode);
|
||||
|
||||
sdhci_end_tuning(host);
|
||||
out:
|
||||
|
@ -2802,7 +2917,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
|
|||
* some controllers are faulty, don't trust them.
|
||||
*/
|
||||
if (intmask & SDHCI_INT_DMA_END) {
|
||||
u32 dmastart, dmanow;
|
||||
dma_addr_t dmastart, dmanow;
|
||||
|
||||
dmastart = sdhci_sdma_address(host);
|
||||
dmanow = dmastart + host->data->bytes_xfered;
|
||||
|
@ -2810,12 +2925,12 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
|
|||
* Force update to the next DMA block boundary.
|
||||
*/
|
||||
dmanow = (dmanow &
|
||||
~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) +
|
||||
~((dma_addr_t)SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) +
|
||||
SDHCI_DEFAULT_BOUNDARY_SIZE;
|
||||
host->data->bytes_xfered = dmanow - dmastart;
|
||||
DBG("DMA base 0x%08x, transferred 0x%06x bytes, next 0x%08x\n",
|
||||
dmastart, host->data->bytes_xfered, dmanow);
|
||||
sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS);
|
||||
DBG("DMA base %pad, transferred 0x%06x bytes, next %pad\n",
|
||||
&dmastart, host->data->bytes_xfered, &dmanow);
|
||||
sdhci_set_sdma_addr(host, dmanow);
|
||||
}
|
||||
|
||||
if (intmask & SDHCI_INT_DATA_END) {
|
||||
|
@ -3322,6 +3437,13 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev,
|
|||
|
||||
host->sdma_boundary = SDHCI_DEFAULT_BOUNDARY_ARG;
|
||||
|
||||
/*
|
||||
* The DMA table descriptor count is calculated as the maximum
|
||||
* number of segments times 2, to allow for an alignment
|
||||
* descriptor for each segment, plus 1 for a nop end descriptor.
|
||||
*/
|
||||
host->adma_table_cnt = SDHCI_MAX_SEGS * 2 + 1;
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
|
@ -3376,6 +3498,9 @@ void __sdhci_read_caps(struct sdhci_host *host, u16 *ver, u32 *caps, u32 *caps1)
|
|||
|
||||
sdhci_do_reset(host, SDHCI_RESET_ALL);
|
||||
|
||||
if (host->v4_mode)
|
||||
sdhci_do_enable_v4_mode(host);
|
||||
|
||||
of_property_read_u64(mmc_dev(host->mmc)->of_node,
|
||||
"sdhci-caps-mask", &dt_caps_mask);
|
||||
of_property_read_u64(mmc_dev(host->mmc)->of_node,
|
||||
|
@ -3470,6 +3595,19 @@ static int sdhci_allocate_bounce_buffer(struct sdhci_host *host)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline bool sdhci_can_64bit_dma(struct sdhci_host *host)
|
||||
{
|
||||
/*
|
||||
* According to SD Host Controller spec v4.10, bit[27] added from
|
||||
* version 4.10 in Capabilities Register is used as 64-bit System
|
||||
* Address support for V4 mode.
|
||||
*/
|
||||
if (host->version >= SDHCI_SPEC_410 && host->v4_mode)
|
||||
return host->caps & SDHCI_CAN_64BIT_V4;
|
||||
|
||||
return host->caps & SDHCI_CAN_64BIT;
|
||||
}
|
||||
|
||||
int sdhci_setup_host(struct sdhci_host *host)
|
||||
{
|
||||
struct mmc_host *mmc;
|
||||
|
@ -3506,7 +3644,7 @@ int sdhci_setup_host(struct sdhci_host *host)
|
|||
|
||||
override_timeout_clk = host->timeout_clk;
|
||||
|
||||
if (host->version > SDHCI_SPEC_300) {
|
||||
if (host->version > SDHCI_SPEC_420) {
|
||||
pr_err("%s: Unknown controller version (%d). You may experience problems.\n",
|
||||
mmc_hostname(mmc), host->version);
|
||||
}
|
||||
|
@ -3541,7 +3679,7 @@ int sdhci_setup_host(struct sdhci_host *host)
|
|||
* SDHCI_QUIRK2_BROKEN_64_BIT_DMA must be left to the drivers to
|
||||
* implement.
|
||||
*/
|
||||
if (host->caps & SDHCI_CAN_64BIT)
|
||||
if (sdhci_can_64bit_dma(host))
|
||||
host->flags |= SDHCI_USE_64_BIT_DMA;
|
||||
|
||||
if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
|
||||
|
@ -3559,32 +3697,30 @@ int sdhci_setup_host(struct sdhci_host *host)
|
|||
}
|
||||
}
|
||||
|
||||
/* SDMA does not support 64-bit DMA */
|
||||
if (host->flags & SDHCI_USE_64_BIT_DMA)
|
||||
/* SDMA does not support 64-bit DMA if v4 mode not set */
|
||||
if ((host->flags & SDHCI_USE_64_BIT_DMA) && !host->v4_mode)
|
||||
host->flags &= ~SDHCI_USE_SDMA;
|
||||
|
||||
if (host->flags & SDHCI_USE_ADMA) {
|
||||
dma_addr_t dma;
|
||||
void *buf;
|
||||
|
||||
/*
|
||||
* The DMA descriptor table size is calculated as the maximum
|
||||
* number of segments times 2, to allow for an alignment
|
||||
* descriptor for each segment, plus 1 for a nop end descriptor,
|
||||
* all multipled by the descriptor size.
|
||||
*/
|
||||
if (host->flags & SDHCI_USE_64_BIT_DMA) {
|
||||
host->adma_table_sz = (SDHCI_MAX_SEGS * 2 + 1) *
|
||||
SDHCI_ADMA2_64_DESC_SZ;
|
||||
host->desc_sz = SDHCI_ADMA2_64_DESC_SZ;
|
||||
host->adma_table_sz = host->adma_table_cnt *
|
||||
SDHCI_ADMA2_64_DESC_SZ(host);
|
||||
host->desc_sz = SDHCI_ADMA2_64_DESC_SZ(host);
|
||||
} else {
|
||||
host->adma_table_sz = (SDHCI_MAX_SEGS * 2 + 1) *
|
||||
host->adma_table_sz = host->adma_table_cnt *
|
||||
SDHCI_ADMA2_32_DESC_SZ;
|
||||
host->desc_sz = SDHCI_ADMA2_32_DESC_SZ;
|
||||
}
|
||||
|
||||
host->align_buffer_sz = SDHCI_MAX_SEGS * SDHCI_ADMA2_ALIGN;
|
||||
buf = dma_alloc_coherent(mmc_dev(mmc), host->align_buffer_sz +
|
||||
/*
|
||||
* Use zalloc to zero the reserved high 32-bits of 128-bit
|
||||
* descriptors so that they never need to be written.
|
||||
*/
|
||||
buf = dma_zalloc_coherent(mmc_dev(mmc), host->align_buffer_sz +
|
||||
host->adma_table_sz, &dma, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
pr_warn("%s: Unable to allocate ADMA buffers - falling back to standard DMA\n",
|
||||
|
@ -3708,10 +3844,13 @@ int sdhci_setup_host(struct sdhci_host *host)
|
|||
if (host->quirks & SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12)
|
||||
host->flags |= SDHCI_AUTO_CMD12;
|
||||
|
||||
/* Auto-CMD23 stuff only works in ADMA or PIO. */
|
||||
/*
|
||||
* For v3 mode, Auto-CMD23 stuff only works in ADMA or PIO.
|
||||
* For v4 mode, SDMA may use Auto-CMD23 as well.
|
||||
*/
|
||||
if ((host->version >= SDHCI_SPEC_300) &&
|
||||
((host->flags & SDHCI_USE_ADMA) ||
|
||||
!(host->flags & SDHCI_USE_SDMA)) &&
|
||||
!(host->flags & SDHCI_USE_SDMA) || host->v4_mode) &&
|
||||
!(host->quirks2 & SDHCI_QUIRK2_ACMD23_BROKEN)) {
|
||||
host->flags |= SDHCI_AUTO_CMD23;
|
||||
DBG("Auto-CMD23 available\n");
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#define SDHCI_DMA_ADDRESS 0x00
|
||||
#define SDHCI_ARGUMENT2 SDHCI_DMA_ADDRESS
|
||||
#define SDHCI_32BIT_BLK_CNT SDHCI_DMA_ADDRESS
|
||||
|
||||
#define SDHCI_BLOCK_SIZE 0x04
|
||||
#define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))
|
||||
|
@ -41,6 +42,7 @@
|
|||
#define SDHCI_TRNS_BLK_CNT_EN 0x02
|
||||
#define SDHCI_TRNS_AUTO_CMD12 0x04
|
||||
#define SDHCI_TRNS_AUTO_CMD23 0x08
|
||||
#define SDHCI_TRNS_AUTO_SEL 0x0C
|
||||
#define SDHCI_TRNS_READ 0x10
|
||||
#define SDHCI_TRNS_MULTI 0x20
|
||||
|
||||
|
@ -184,6 +186,9 @@
|
|||
#define SDHCI_CTRL_DRV_TYPE_D 0x0030
|
||||
#define SDHCI_CTRL_EXEC_TUNING 0x0040
|
||||
#define SDHCI_CTRL_TUNED_CLK 0x0080
|
||||
#define SDHCI_CMD23_ENABLE 0x0800
|
||||
#define SDHCI_CTRL_V4_MODE 0x1000
|
||||
#define SDHCI_CTRL_64BIT_ADDR 0x2000
|
||||
#define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000
|
||||
|
||||
#define SDHCI_CAPABILITIES 0x40
|
||||
|
@ -204,6 +209,7 @@
|
|||
#define SDHCI_CAN_VDD_330 0x01000000
|
||||
#define SDHCI_CAN_VDD_300 0x02000000
|
||||
#define SDHCI_CAN_VDD_180 0x04000000
|
||||
#define SDHCI_CAN_64BIT_V4 0x08000000
|
||||
#define SDHCI_CAN_64BIT 0x10000000
|
||||
|
||||
#define SDHCI_SUPPORT_SDR50 0x00000001
|
||||
|
@ -270,6 +276,9 @@
|
|||
#define SDHCI_SPEC_100 0
|
||||
#define SDHCI_SPEC_200 1
|
||||
#define SDHCI_SPEC_300 2
|
||||
#define SDHCI_SPEC_400 3
|
||||
#define SDHCI_SPEC_410 4
|
||||
#define SDHCI_SPEC_420 5
|
||||
|
||||
/*
|
||||
* End of controller registers.
|
||||
|
@ -305,8 +314,14 @@ struct sdhci_adma2_32_desc {
|
|||
*/
|
||||
#define SDHCI_ADMA2_DESC_ALIGN 8
|
||||
|
||||
/* ADMA2 64-bit DMA descriptor size */
|
||||
#define SDHCI_ADMA2_64_DESC_SZ 12
|
||||
/*
|
||||
* ADMA2 64-bit DMA descriptor size
|
||||
* According to SD Host Controller spec v4.10, there are two kinds of
|
||||
* descriptors for 64-bit addressing mode: 96-bit Descriptor and 128-bit
|
||||
* Descriptor, if Host Version 4 Enable is set in the Host Control 2
|
||||
* register, 128-bit Descriptor will be selected.
|
||||
*/
|
||||
#define SDHCI_ADMA2_64_DESC_SZ(host) ((host)->v4_mode ? 16 : 12)
|
||||
|
||||
/*
|
||||
* ADMA2 64-bit descriptor. Note 12-byte descriptor can't always be 8-byte
|
||||
|
@ -450,6 +465,13 @@ struct sdhci_host {
|
|||
* obtainable timeout.
|
||||
*/
|
||||
#define SDHCI_QUIRK2_DISABLE_HW_TIMEOUT (1<<17)
|
||||
/*
|
||||
* 32-bit block count may not support eMMC where upper bits of CMD23 are used
|
||||
* for other purposes. Consequently we support 16-bit block count by default.
|
||||
* Otherwise, SDHCI_QUIRK2_USE_32BIT_BLK_CNT can be selected to use 32-bit
|
||||
* block count.
|
||||
*/
|
||||
#define SDHCI_QUIRK2_USE_32BIT_BLK_CNT (1<<18)
|
||||
|
||||
int irq; /* Device IRQ */
|
||||
void __iomem *ioaddr; /* Mapped address */
|
||||
|
@ -501,6 +523,7 @@ struct sdhci_host {
|
|||
bool preset_enabled; /* Preset is enabled */
|
||||
bool pending_reset; /* Cmd/data reset is pending */
|
||||
bool irq_wake_enabled; /* IRQ wakeup is enabled */
|
||||
bool v4_mode; /* Host Version 4 Enable */
|
||||
|
||||
struct mmc_request *mrqs_done[SDHCI_MAX_MRQS]; /* Requests done */
|
||||
struct mmc_command *cmd; /* Current command */
|
||||
|
@ -554,6 +577,7 @@ struct sdhci_host {
|
|||
|
||||
unsigned int tuning_count; /* Timer count for re-tuning */
|
||||
unsigned int tuning_mode; /* Re-tuning mode supported by host */
|
||||
unsigned int tuning_err; /* Error code for re-tuning */
|
||||
#define SDHCI_TUNING_MODE_1 0
|
||||
#define SDHCI_TUNING_MODE_2 1
|
||||
#define SDHCI_TUNING_MODE_3 2
|
||||
|
@ -563,6 +587,9 @@ struct sdhci_host {
|
|||
/* Host SDMA buffer boundary. */
|
||||
u32 sdma_boundary;
|
||||
|
||||
/* Host ADMA table count */
|
||||
u32 adma_table_cnt;
|
||||
|
||||
u64 data_timeout;
|
||||
|
||||
unsigned long private[0] ____cacheline_aligned;
|
||||
|
@ -603,6 +630,8 @@ struct sdhci_ops {
|
|||
void (*adma_workaround)(struct sdhci_host *host, u32 intmask);
|
||||
void (*card_event)(struct sdhci_host *host);
|
||||
void (*voltage_switch)(struct sdhci_host *host);
|
||||
void (*adma_write_desc)(struct sdhci_host *host, void **desc,
|
||||
dma_addr_t addr, int len, unsigned int cmd);
|
||||
};
|
||||
|
||||
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
|
||||
|
@ -725,6 +754,7 @@ void sdhci_set_power(struct sdhci_host *host, unsigned char mode,
|
|||
unsigned short vdd);
|
||||
void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
|
||||
unsigned short vdd);
|
||||
void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq);
|
||||
void sdhci_set_bus_width(struct sdhci_host *host, int width);
|
||||
void sdhci_reset(struct sdhci_host *host, u8 mask);
|
||||
void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
|
||||
|
@ -733,6 +763,8 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
|
|||
int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
|
||||
struct mmc_ios *ios);
|
||||
void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable);
|
||||
void sdhci_adma_write_desc(struct sdhci_host *host, void **desc,
|
||||
dma_addr_t addr, int len, unsigned int cmd);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
int sdhci_suspend_host(struct sdhci_host *host);
|
||||
|
@ -747,6 +779,7 @@ bool sdhci_cqe_irq(struct sdhci_host *host, u32 intmask, int *cmd_error,
|
|||
int *data_error);
|
||||
|
||||
void sdhci_dumpregs(struct sdhci_host *host);
|
||||
void sdhci_enable_v4_mode(struct sdhci_host *host);
|
||||
|
||||
void sdhci_start_tuning(struct sdhci_host *host);
|
||||
void sdhci_end_tuning(struct sdhci_host *host);
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* MMCIF eMMC driver.
|
||||
*
|
||||
* Copyright (C) 2010 Renesas Solutions Corp.
|
||||
* Yusuke Goda <yusuke.goda.sx@renesas.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -1573,6 +1570,6 @@ static struct platform_driver sh_mmcif_driver = {
|
|||
module_platform_driver(sh_mmcif_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SuperH on-chip MMC/eMMC interface driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:" DRIVER_NAME);
|
||||
MODULE_AUTHOR("Yusuke Goda <yusuke.goda.sx@renesas.com>");
|
||||
|
|
|
@ -258,11 +258,16 @@ struct sunxi_mmc_cfg {
|
|||
/* Does DATA0 needs to be masked while the clock is updated */
|
||||
bool mask_data0;
|
||||
|
||||
/* hardware only supports new timing mode */
|
||||
/*
|
||||
* hardware only supports new timing mode, either due to lack of
|
||||
* a mode switch in the clock controller, or the mmc controller
|
||||
* is permanently configured in the new timing mode, without the
|
||||
* NTSR mode switch.
|
||||
*/
|
||||
bool needs_new_timings;
|
||||
|
||||
/* hardware can switch between old and new timing modes */
|
||||
bool has_timings_switch;
|
||||
/* clock hardware can switch between old and new timing modes */
|
||||
bool ccu_has_timings_switch;
|
||||
};
|
||||
|
||||
struct sunxi_mmc_host {
|
||||
|
@ -787,7 +792,7 @@ static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host,
|
|||
clock <<= 1;
|
||||
}
|
||||
|
||||
if (host->use_new_timings && host->cfg->has_timings_switch) {
|
||||
if (host->use_new_timings && host->cfg->ccu_has_timings_switch) {
|
||||
ret = sunxi_ccu_set_mmc_timing_mode(host->clk_mmc, true);
|
||||
if (ret) {
|
||||
dev_err(mmc_dev(mmc),
|
||||
|
@ -822,6 +827,12 @@ static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host,
|
|||
/* update card clock rate to account for internal divider */
|
||||
rate /= div;
|
||||
|
||||
/*
|
||||
* Configure the controller to use the new timing mode if needed.
|
||||
* On controllers that only support the new timing mode, such as
|
||||
* the eMMC controller on the A64, this register does not exist,
|
||||
* and any writes to it are ignored.
|
||||
*/
|
||||
if (host->use_new_timings) {
|
||||
/* Don't touch the delay bits */
|
||||
rval = mmc_readl(host, REG_SD_NTSR);
|
||||
|
@ -1145,7 +1156,7 @@ static const struct sunxi_mmc_cfg sun8i_a83t_emmc_cfg = {
|
|||
.idma_des_size_bits = 16,
|
||||
.clk_delays = sunxi_mmc_clk_delays,
|
||||
.can_calibrate = false,
|
||||
.has_timings_switch = true,
|
||||
.ccu_has_timings_switch = true,
|
||||
};
|
||||
|
||||
static const struct sunxi_mmc_cfg sun9i_a80_cfg = {
|
||||
|
@ -1166,6 +1177,7 @@ static const struct sunxi_mmc_cfg sun50i_a64_emmc_cfg = {
|
|||
.idma_des_size_bits = 13,
|
||||
.clk_delays = NULL,
|
||||
.can_calibrate = true,
|
||||
.needs_new_timings = true,
|
||||
};
|
||||
|
||||
static const struct of_device_id sunxi_mmc_of_match[] = {
|
||||
|
@ -1351,7 +1363,7 @@ static int sunxi_mmc_probe(struct platform_device *pdev)
|
|||
goto error_free_host;
|
||||
}
|
||||
|
||||
if (host->cfg->has_timings_switch) {
|
||||
if (host->cfg->ccu_has_timings_switch) {
|
||||
/*
|
||||
* Supports both old and new timing modes.
|
||||
* Try setting the clk to new timing mode.
|
||||
|
|
|
@ -336,7 +336,8 @@ static unsigned int tifm_sd_op_flags(struct mmc_command *cmd)
|
|||
rc |= TIFM_MMCSD_RSP_R0;
|
||||
break;
|
||||
case MMC_RSP_R1B:
|
||||
rc |= TIFM_MMCSD_RSP_BUSY; // deliberate fall-through
|
||||
rc |= TIFM_MMCSD_RSP_BUSY;
|
||||
/* fall-through */
|
||||
case MMC_RSP_R1:
|
||||
rc |= TIFM_MMCSD_RSP_R1;
|
||||
break;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Driver for the MMC / SD / SDIO cell found in:
|
||||
*
|
||||
|
@ -7,12 +8,9 @@
|
|||
* Copyright (C) 2017 Horms Solutions, Simon Horman
|
||||
* Copyright (C) 2007 Ian Molton
|
||||
* Copyright (C) 2004 Ian Molton
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/tmio.h>
|
||||
|
@ -23,6 +21,76 @@
|
|||
|
||||
#include "tmio_mmc.h"
|
||||
|
||||
/* Registers specific to this variant */
|
||||
#define CTL_SDIO_REGS 0x100
|
||||
#define CTL_CLK_AND_WAIT_CTL 0x138
|
||||
#define CTL_RESET_SDIO 0x1e0
|
||||
|
||||
static void tmio_mmc_clk_start(struct tmio_mmc_host *host)
|
||||
{
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
|
||||
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
||||
|
||||
usleep_range(10000, 11000);
|
||||
sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0100);
|
||||
usleep_range(10000, 11000);
|
||||
}
|
||||
|
||||
static void tmio_mmc_clk_stop(struct tmio_mmc_host *host)
|
||||
{
|
||||
sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0000);
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
|
||||
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
||||
|
||||
usleep_range(10000, 11000);
|
||||
}
|
||||
|
||||
static void tmio_mmc_set_clock(struct tmio_mmc_host *host,
|
||||
unsigned int new_clock)
|
||||
{
|
||||
unsigned int divisor;
|
||||
u32 clk = 0;
|
||||
int clk_sel;
|
||||
|
||||
if (new_clock == 0) {
|
||||
tmio_mmc_clk_stop(host);
|
||||
return;
|
||||
}
|
||||
|
||||
divisor = host->pdata->hclk / new_clock;
|
||||
|
||||
/* bit7 set: 1/512, ... bit0 set: 1/4, all bits clear: 1/2 */
|
||||
clk_sel = (divisor <= 1);
|
||||
clk = clk_sel ? 0 : (roundup_pow_of_two(divisor) >> 2);
|
||||
|
||||
host->pdata->set_clk_div(host->pdev, clk_sel);
|
||||
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
|
||||
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, clk & CLK_CTL_DIV_MASK);
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
tmio_mmc_clk_start(host);
|
||||
}
|
||||
|
||||
static void tmio_mmc_reset(struct tmio_mmc_host *host)
|
||||
{
|
||||
/* FIXME - should we set stop clock reg here */
|
||||
sd_ctrl_write16(host, CTL_RESET_SD, 0x0000);
|
||||
sd_ctrl_write16(host, CTL_RESET_SDIO, 0x0000);
|
||||
usleep_range(10000, 11000);
|
||||
sd_ctrl_write16(host, CTL_RESET_SD, 0x0001);
|
||||
sd_ctrl_write16(host, CTL_RESET_SDIO, 0x0001);
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
if (host->pdata->flags & TMIO_MMC_SDIO_IRQ) {
|
||||
sd_ctrl_write16(host, CTL_SDIO_IRQ_MASK, host->sdio_irq_mask);
|
||||
sd_ctrl_write16(host, CTL_TRANSACTION_CTL, 0x0001);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tmio_mmc_suspend(struct device *dev)
|
||||
{
|
||||
|
@ -90,8 +158,6 @@ static int tmio_mmc_probe(struct platform_device *pdev)
|
|||
goto cell_disable;
|
||||
}
|
||||
|
||||
pdata->flags |= TMIO_MMC_HAVE_HIGH_REG;
|
||||
|
||||
host = tmio_mmc_host_alloc(pdev, pdata);
|
||||
if (IS_ERR(host)) {
|
||||
ret = PTR_ERR(host);
|
||||
|
@ -100,6 +166,8 @@ static int tmio_mmc_probe(struct platform_device *pdev)
|
|||
|
||||
/* SD control register space size is 0x200, 0x400 for bus_shift=1 */
|
||||
host->bus_shift = resource_size(res) >> 10;
|
||||
host->set_clock = tmio_mmc_set_clock;
|
||||
host->reset = tmio_mmc_reset;
|
||||
|
||||
host->mmc->f_max = pdata->hclk;
|
||||
host->mmc->f_min = pdata->hclk / 512;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Driver for the MMC / SD / SDIO cell found in:
|
||||
*
|
||||
|
@ -8,11 +9,6 @@
|
|||
* Copyright (C) 2016-17 Horms Solutions, Simon Horman
|
||||
* Copyright (C) 2007 Ian Molton
|
||||
* Copyright (C) 2004 Ian Molton
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TMIO_MMC_H
|
||||
|
@ -47,9 +43,6 @@
|
|||
#define CTL_RESET_SD 0xe0
|
||||
#define CTL_VERSION 0xe2
|
||||
#define CTL_SDIF_MODE 0xe6
|
||||
#define CTL_SDIO_REGS 0x100
|
||||
#define CTL_CLK_AND_WAIT_CTL 0x138
|
||||
#define CTL_RESET_SDIO 0x1e0
|
||||
|
||||
/* Definitions for values the CTL_STOP_INTERNAL_ACTION register can take */
|
||||
#define TMIO_STOP_STP BIT(0)
|
||||
|
@ -133,7 +126,6 @@ struct tmio_mmc_host {
|
|||
|
||||
/* Callbacks for clock / power control */
|
||||
void (*set_pwr)(struct platform_device *host, int state);
|
||||
void (*set_clk_div)(struct platform_device *host, int state);
|
||||
|
||||
/* pio related stuff */
|
||||
struct scatterlist *sg_ptr;
|
||||
|
@ -146,7 +138,7 @@ struct tmio_mmc_host {
|
|||
struct tmio_mmc_data *pdata;
|
||||
|
||||
/* DMA support */
|
||||
bool force_pio;
|
||||
bool dma_on;
|
||||
struct dma_chan *chan_rx;
|
||||
struct dma_chan *chan_tx;
|
||||
struct tasklet_struct dma_issue;
|
||||
|
@ -170,14 +162,14 @@ struct tmio_mmc_host {
|
|||
|
||||
/* Mandatory callback */
|
||||
int (*clk_enable)(struct tmio_mmc_host *host);
|
||||
void (*set_clock)(struct tmio_mmc_host *host, unsigned int clock);
|
||||
|
||||
/* Optional callbacks */
|
||||
unsigned int (*clk_update)(struct tmio_mmc_host *host,
|
||||
unsigned int new_clock);
|
||||
void (*clk_disable)(struct tmio_mmc_host *host);
|
||||
int (*multi_io_quirk)(struct mmc_card *card,
|
||||
unsigned int direction, int blk_size);
|
||||
int (*write16_hook)(struct tmio_mmc_host *host, int addr);
|
||||
void (*reset)(struct tmio_mmc_host *host);
|
||||
void (*hw_reset)(struct tmio_mmc_host *host);
|
||||
void (*prepare_tuning)(struct tmio_mmc_host *host, unsigned long tap);
|
||||
bool (*check_scc_error)(struct tmio_mmc_host *host);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Driver for the MMC / SD / SDIO IP found in:
|
||||
*
|
||||
|
@ -10,10 +11,6 @@
|
|||
* Copyright (C) 2007 Ian Molton
|
||||
* Copyright (C) 2004 Ian Molton
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This driver draws mainly on scattered spec sheets, Reverse engineering
|
||||
* of the toshiba e800 SD driver and some parts of the 2.4 ASIC3 driver (4 bit
|
||||
* support). (Further 4 bit support from a later datasheet).
|
||||
|
@ -160,100 +157,18 @@ static void tmio_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
|
|||
}
|
||||
}
|
||||
|
||||
static void tmio_mmc_clk_start(struct tmio_mmc_host *host)
|
||||
{
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
|
||||
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
||||
|
||||
/* HW engineers overrode docs: no sleep needed on R-Car2+ */
|
||||
if (!(host->pdata->flags & TMIO_MMC_MIN_RCAR2))
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
if (host->pdata->flags & TMIO_MMC_HAVE_HIGH_REG) {
|
||||
sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0100);
|
||||
usleep_range(10000, 11000);
|
||||
}
|
||||
}
|
||||
|
||||
static void tmio_mmc_clk_stop(struct tmio_mmc_host *host)
|
||||
{
|
||||
if (host->pdata->flags & TMIO_MMC_HAVE_HIGH_REG) {
|
||||
sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0000);
|
||||
usleep_range(10000, 11000);
|
||||
}
|
||||
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
|
||||
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
||||
|
||||
/* HW engineers overrode docs: no sleep needed on R-Car2+ */
|
||||
if (!(host->pdata->flags & TMIO_MMC_MIN_RCAR2))
|
||||
usleep_range(10000, 11000);
|
||||
}
|
||||
|
||||
static void tmio_mmc_set_clock(struct tmio_mmc_host *host,
|
||||
unsigned int new_clock)
|
||||
{
|
||||
u32 clk = 0, clock;
|
||||
|
||||
if (new_clock == 0) {
|
||||
tmio_mmc_clk_stop(host);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* Both HS400 and HS200/SD104 set 200MHz, but some devices need to
|
||||
* set 400MHz to distinguish the CPG settings in HS400.
|
||||
*/
|
||||
if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400 &&
|
||||
host->pdata->flags & TMIO_MMC_HAVE_4TAP_HS400 &&
|
||||
new_clock == 200000000)
|
||||
new_clock = 400000000;
|
||||
|
||||
if (host->clk_update)
|
||||
clock = host->clk_update(host, new_clock) / 512;
|
||||
else
|
||||
clock = host->mmc->f_min;
|
||||
|
||||
for (clk = 0x80000080; new_clock >= (clock << 1); clk >>= 1)
|
||||
clock <<= 1;
|
||||
|
||||
/* 1/1 clock is option */
|
||||
if ((host->pdata->flags & TMIO_MMC_CLK_ACTUAL) &&
|
||||
((clk >> 22) & 0x1)) {
|
||||
if (!(host->mmc->ios.timing == MMC_TIMING_MMC_HS400))
|
||||
clk |= 0xff;
|
||||
else
|
||||
clk &= ~0xff;
|
||||
}
|
||||
|
||||
if (host->set_clk_div)
|
||||
host->set_clk_div(host->pdev, (clk >> 22) & 1);
|
||||
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
|
||||
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
||||
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, clk & CLK_CTL_DIV_MASK);
|
||||
if (!(host->pdata->flags & TMIO_MMC_MIN_RCAR2))
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
tmio_mmc_clk_start(host);
|
||||
}
|
||||
|
||||
static void tmio_mmc_reset(struct tmio_mmc_host *host)
|
||||
{
|
||||
/* FIXME - should we set stop clock reg here */
|
||||
sd_ctrl_write16(host, CTL_RESET_SD, 0x0000);
|
||||
if (host->pdata->flags & TMIO_MMC_HAVE_HIGH_REG)
|
||||
sd_ctrl_write16(host, CTL_RESET_SDIO, 0x0000);
|
||||
usleep_range(10000, 11000);
|
||||
sd_ctrl_write16(host, CTL_RESET_SD, 0x0001);
|
||||
if (host->pdata->flags & TMIO_MMC_HAVE_HIGH_REG)
|
||||
sd_ctrl_write16(host, CTL_RESET_SDIO, 0x0001);
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
if (host->pdata->flags & TMIO_MMC_SDIO_IRQ) {
|
||||
sd_ctrl_write16(host, CTL_SDIO_IRQ_MASK, host->sdio_irq_mask);
|
||||
sd_ctrl_write16(host, CTL_TRANSACTION_CTL, 0x0001);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void tmio_mmc_reset_work(struct work_struct *work)
|
||||
|
@ -294,7 +209,7 @@ static void tmio_mmc_reset_work(struct work_struct *work)
|
|||
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
|
||||
tmio_mmc_reset(host);
|
||||
host->reset(host);
|
||||
|
||||
/* Ready for new calls */
|
||||
host->mrq = NULL;
|
||||
|
@ -446,7 +361,7 @@ static void tmio_mmc_pio_irq(struct tmio_mmc_host *host)
|
|||
unsigned int count;
|
||||
unsigned long flags;
|
||||
|
||||
if ((host->chan_tx || host->chan_rx) && !host->force_pio) {
|
||||
if (host->dma_on) {
|
||||
pr_err("PIO IRQ in DMA mode!\n");
|
||||
return;
|
||||
} else if (!data) {
|
||||
|
@ -518,7 +433,7 @@ void tmio_mmc_do_data_irq(struct tmio_mmc_host *host)
|
|||
*/
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
if (host->chan_rx && !host->force_pio)
|
||||
if (host->dma_on)
|
||||
tmio_mmc_check_bounce_buffer(host);
|
||||
dev_dbg(&host->pdev->dev, "Complete Rx request %p\n",
|
||||
host->mrq);
|
||||
|
@ -555,7 +470,7 @@ static void tmio_mmc_data_irq(struct tmio_mmc_host *host, unsigned int stat)
|
|||
if (stat & TMIO_STAT_CRCFAIL || stat & TMIO_STAT_STOPBIT_ERR ||
|
||||
stat & TMIO_STAT_TXUNDERRUN)
|
||||
data->error = -EILSEQ;
|
||||
if (host->chan_tx && (data->flags & MMC_DATA_WRITE) && !host->force_pio) {
|
||||
if (host->dma_on && (data->flags & MMC_DATA_WRITE)) {
|
||||
u32 status = sd_ctrl_read16_and_16_as_32(host, CTL_STATUS);
|
||||
bool done = false;
|
||||
|
||||
|
@ -579,7 +494,7 @@ static void tmio_mmc_data_irq(struct tmio_mmc_host *host, unsigned int stat)
|
|||
tmio_mmc_disable_mmc_irqs(host, TMIO_STAT_DATAEND);
|
||||
tmio_mmc_dataend_dma(host);
|
||||
}
|
||||
} else if (host->chan_rx && (data->flags & MMC_DATA_READ) && !host->force_pio) {
|
||||
} else if (host->dma_on && (data->flags & MMC_DATA_READ)) {
|
||||
tmio_mmc_disable_mmc_irqs(host, TMIO_STAT_DATAEND);
|
||||
tmio_mmc_dataend_dma(host);
|
||||
} else {
|
||||
|
@ -632,7 +547,7 @@ static void tmio_mmc_cmd_irq(struct tmio_mmc_host *host, unsigned int stat)
|
|||
*/
|
||||
if (host->data && (!cmd->error || cmd->error == -EILSEQ)) {
|
||||
if (host->data->flags & MMC_DATA_READ) {
|
||||
if (host->force_pio || !host->chan_rx) {
|
||||
if (!host->dma_on) {
|
||||
tmio_mmc_enable_mmc_irqs(host, TMIO_MASK_READOP);
|
||||
} else {
|
||||
tmio_mmc_disable_mmc_irqs(host,
|
||||
|
@ -640,7 +555,7 @@ static void tmio_mmc_cmd_irq(struct tmio_mmc_host *host, unsigned int stat)
|
|||
tasklet_schedule(&host->dma_issue);
|
||||
}
|
||||
} else {
|
||||
if (host->force_pio || !host->chan_tx) {
|
||||
if (!host->dma_on) {
|
||||
tmio_mmc_enable_mmc_irqs(host, TMIO_MASK_WRITEOP);
|
||||
} else {
|
||||
tmio_mmc_disable_mmc_irqs(host,
|
||||
|
@ -770,7 +685,7 @@ static int tmio_mmc_start_data(struct tmio_mmc_host *host,
|
|||
|
||||
tmio_mmc_init_sg(host, data);
|
||||
host->data = data;
|
||||
host->force_pio = false;
|
||||
host->dma_on = false;
|
||||
|
||||
/* Set transfer length / blocksize */
|
||||
sd_ctrl_write16(host, CTL_SD_XFER_LEN, data->blksz);
|
||||
|
@ -919,8 +834,8 @@ static void tmio_mmc_finish_request(struct tmio_mmc_host *host)
|
|||
if (mrq->cmd->error || (mrq->data && mrq->data->error))
|
||||
tmio_mmc_abort_dma(host);
|
||||
|
||||
if (host->check_scc_error)
|
||||
host->check_scc_error(host);
|
||||
if (host->check_scc_error && host->check_scc_error(host))
|
||||
mrq->cmd->error = -EILSEQ;
|
||||
|
||||
/* If SET_BLOCK_COUNT, continue with main command */
|
||||
if (host->mrq && !mrq->cmd->error) {
|
||||
|
@ -1043,15 +958,15 @@ static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|||
switch (ios->power_mode) {
|
||||
case MMC_POWER_OFF:
|
||||
tmio_mmc_power_off(host);
|
||||
tmio_mmc_clk_stop(host);
|
||||
host->set_clock(host, 0);
|
||||
break;
|
||||
case MMC_POWER_UP:
|
||||
tmio_mmc_power_on(host, ios->vdd);
|
||||
tmio_mmc_set_clock(host, ios->clock);
|
||||
host->set_clock(host, ios->clock);
|
||||
tmio_mmc_set_bus_width(host, ios->bus_width);
|
||||
break;
|
||||
case MMC_POWER_ON:
|
||||
tmio_mmc_set_clock(host, ios->clock);
|
||||
host->set_clock(host, ios->clock);
|
||||
tmio_mmc_set_bus_width(host, ios->bus_width);
|
||||
break;
|
||||
}
|
||||
|
@ -1237,7 +1152,7 @@ int tmio_mmc_host_probe(struct tmio_mmc_host *_host)
|
|||
int ret;
|
||||
|
||||
/*
|
||||
* Check the sanity of mmc->f_min to prevent tmio_mmc_set_clock() from
|
||||
* Check the sanity of mmc->f_min to prevent host->set_clock() from
|
||||
* looping forever...
|
||||
*/
|
||||
if (mmc->f_min == 0)
|
||||
|
@ -1247,7 +1162,6 @@ int tmio_mmc_host_probe(struct tmio_mmc_host *_host)
|
|||
_host->write16_hook = NULL;
|
||||
|
||||
_host->set_pwr = pdata->set_pwr;
|
||||
_host->set_clk_div = pdata->set_clk_div;
|
||||
|
||||
ret = tmio_mmc_init_ocr(_host);
|
||||
if (ret < 0)
|
||||
|
@ -1290,6 +1204,9 @@ int tmio_mmc_host_probe(struct tmio_mmc_host *_host)
|
|||
mmc->caps & MMC_CAP_NEEDS_POLL ||
|
||||
!mmc_card_is_removable(mmc));
|
||||
|
||||
if (!_host->reset)
|
||||
_host->reset = tmio_mmc_reset;
|
||||
|
||||
/*
|
||||
* On Gen2+, eMMC with NONREMOVABLE currently fails because native
|
||||
* hotplug gets disabled. It seems RuntimePM related yet we need further
|
||||
|
@ -1310,8 +1227,8 @@ int tmio_mmc_host_probe(struct tmio_mmc_host *_host)
|
|||
if (pdata->flags & TMIO_MMC_SDIO_IRQ)
|
||||
_host->sdio_irq_mask = TMIO_SDIO_MASK_ALL;
|
||||
|
||||
tmio_mmc_clk_stop(_host);
|
||||
tmio_mmc_reset(_host);
|
||||
_host->set_clock(_host, 0);
|
||||
_host->reset(_host);
|
||||
|
||||
_host->sdcard_irq_mask = sd_ctrl_read16_and_16_as_32(_host, CTL_IRQ_MASK);
|
||||
tmio_mmc_disable_mmc_irqs(_host, TMIO_MASK_ALL);
|
||||
|
@ -1394,7 +1311,7 @@ int tmio_mmc_host_runtime_suspend(struct device *dev)
|
|||
tmio_mmc_disable_mmc_irqs(host, TMIO_MASK_ALL);
|
||||
|
||||
if (host->clk_cache)
|
||||
tmio_mmc_clk_stop(host);
|
||||
host->set_clock(host, 0);
|
||||
|
||||
tmio_mmc_clk_disable(host);
|
||||
|
||||
|
@ -1411,11 +1328,11 @@ int tmio_mmc_host_runtime_resume(struct device *dev)
|
|||
{
|
||||
struct tmio_mmc_host *host = dev_get_drvdata(dev);
|
||||
|
||||
tmio_mmc_reset(host);
|
||||
host->reset(host);
|
||||
tmio_mmc_clk_enable(host);
|
||||
|
||||
if (host->clk_cache)
|
||||
tmio_mmc_set_clock(host, host->clk_cache);
|
||||
host->set_clock(host, host->clk_cache);
|
||||
|
||||
if (host->native_hotplug)
|
||||
tmio_mmc_enable_mmc_irqs(host,
|
||||
|
|
698
drivers/mmc/host/uniphier-sd.c
Normal file
698
drivers/mmc/host/uniphier-sd.c
Normal file
|
@ -0,0 +1,698 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// Copyright (C) 2017-2018 Socionext Inc.
|
||||
// Author: Masahiro Yamada <yamada.masahiro@socionext.com>
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/mfd/tmio.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include "tmio_mmc.h"
|
||||
|
||||
#define UNIPHIER_SD_CLK_CTL_DIV1024 BIT(16)
|
||||
#define UNIPHIER_SD_CLK_CTL_DIV1 BIT(10)
|
||||
#define UNIPHIER_SD_CLKCTL_OFFEN BIT(9) // auto SDCLK stop
|
||||
#define UNIPHIER_SD_CC_EXT_MODE 0x1b0
|
||||
#define UNIPHIER_SD_CC_EXT_MODE_DMA BIT(1)
|
||||
#define UNIPHIER_SD_HOST_MODE 0x1c8
|
||||
#define UNIPHIER_SD_VOLT 0x1e4
|
||||
#define UNIPHIER_SD_VOLT_MASK GENMASK(1, 0)
|
||||
#define UNIPHIER_SD_VOLT_OFF 0
|
||||
#define UNIPHIER_SD_VOLT_330 1 // 3.3V signal
|
||||
#define UNIPHIER_SD_VOLT_180 2 // 1.8V signal
|
||||
#define UNIPHIER_SD_DMA_MODE 0x410
|
||||
#define UNIPHIER_SD_DMA_MODE_DIR_MASK GENMASK(17, 16)
|
||||
#define UNIPHIER_SD_DMA_MODE_DIR_TO_DEV 0
|
||||
#define UNIPHIER_SD_DMA_MODE_DIR_FROM_DEV 1
|
||||
#define UNIPHIER_SD_DMA_MODE_WIDTH_MASK GENMASK(5, 4)
|
||||
#define UNIPHIER_SD_DMA_MODE_WIDTH_8 0
|
||||
#define UNIPHIER_SD_DMA_MODE_WIDTH_16 1
|
||||
#define UNIPHIER_SD_DMA_MODE_WIDTH_32 2
|
||||
#define UNIPHIER_SD_DMA_MODE_WIDTH_64 3
|
||||
#define UNIPHIER_SD_DMA_MODE_ADDR_INC BIT(0) // 1: inc, 0: fixed
|
||||
#define UNIPHIER_SD_DMA_CTL 0x414
|
||||
#define UNIPHIER_SD_DMA_CTL_START BIT(0) // start DMA (auto cleared)
|
||||
#define UNIPHIER_SD_DMA_RST 0x418
|
||||
#define UNIPHIER_SD_DMA_RST_CH1 BIT(9)
|
||||
#define UNIPHIER_SD_DMA_RST_CH0 BIT(8)
|
||||
#define UNIPHIER_SD_DMA_ADDR_L 0x440
|
||||
#define UNIPHIER_SD_DMA_ADDR_H 0x444
|
||||
|
||||
/*
|
||||
* IP is extended to support various features: built-in DMA engine,
|
||||
* 1/1024 divisor, etc.
|
||||
*/
|
||||
#define UNIPHIER_SD_CAP_EXTENDED_IP BIT(0)
|
||||
/* RX channel of the built-in DMA controller is broken (Pro5) */
|
||||
#define UNIPHIER_SD_CAP_BROKEN_DMA_RX BIT(1)
|
||||
|
||||
struct uniphier_sd_priv {
|
||||
struct tmio_mmc_data tmio_data;
|
||||
struct pinctrl *pinctrl;
|
||||
struct pinctrl_state *pinstate_default;
|
||||
struct pinctrl_state *pinstate_uhs;
|
||||
struct clk *clk;
|
||||
struct reset_control *rst;
|
||||
struct reset_control *rst_br;
|
||||
struct reset_control *rst_hw;
|
||||
struct dma_chan *chan;
|
||||
enum dma_data_direction dma_dir;
|
||||
unsigned long clk_rate;
|
||||
unsigned long caps;
|
||||
};
|
||||
|
||||
static void *uniphier_sd_priv(struct tmio_mmc_host *host)
|
||||
{
|
||||
return container_of(host->pdata, struct uniphier_sd_priv, tmio_data);
|
||||
}
|
||||
|
||||
static void uniphier_sd_dma_endisable(struct tmio_mmc_host *host, int enable)
|
||||
{
|
||||
sd_ctrl_write16(host, CTL_DMA_ENABLE, enable ? DMA_ENABLE_DMASDRW : 0);
|
||||
}
|
||||
|
||||
/* external DMA engine */
|
||||
static void uniphier_sd_external_dma_issue(unsigned long arg)
|
||||
{
|
||||
struct tmio_mmc_host *host = (void *)arg;
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
|
||||
uniphier_sd_dma_endisable(host, 1);
|
||||
dma_async_issue_pending(priv->chan);
|
||||
}
|
||||
|
||||
static void uniphier_sd_external_dma_callback(void *param,
|
||||
const struct dmaengine_result *result)
|
||||
{
|
||||
struct tmio_mmc_host *host = param;
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
unsigned long flags;
|
||||
|
||||
dma_unmap_sg(mmc_dev(host->mmc), host->sg_ptr, host->sg_len,
|
||||
priv->dma_dir);
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
|
||||
if (result->result == DMA_TRANS_NOERROR) {
|
||||
/*
|
||||
* When the external DMA engine is enabled, strangely enough,
|
||||
* the DATAEND flag can be asserted even if the DMA engine has
|
||||
* not been kicked yet. Enable the TMIO_STAT_DATAEND irq only
|
||||
* after we make sure the DMA engine finishes the transfer,
|
||||
* hence, in this callback.
|
||||
*/
|
||||
tmio_mmc_enable_mmc_irqs(host, TMIO_STAT_DATAEND);
|
||||
} else {
|
||||
host->data->error = -ETIMEDOUT;
|
||||
tmio_mmc_do_data_irq(host);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
static void uniphier_sd_external_dma_start(struct tmio_mmc_host *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
enum dma_transfer_direction dma_tx_dir;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
dma_cookie_t cookie;
|
||||
int sg_len;
|
||||
|
||||
if (!priv->chan)
|
||||
goto force_pio;
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
priv->dma_dir = DMA_FROM_DEVICE;
|
||||
dma_tx_dir = DMA_DEV_TO_MEM;
|
||||
} else {
|
||||
priv->dma_dir = DMA_TO_DEVICE;
|
||||
dma_tx_dir = DMA_MEM_TO_DEV;
|
||||
}
|
||||
|
||||
sg_len = dma_map_sg(mmc_dev(host->mmc), host->sg_ptr, host->sg_len,
|
||||
priv->dma_dir);
|
||||
if (sg_len == 0)
|
||||
goto force_pio;
|
||||
|
||||
desc = dmaengine_prep_slave_sg(priv->chan, host->sg_ptr, sg_len,
|
||||
dma_tx_dir, DMA_CTRL_ACK);
|
||||
if (!desc)
|
||||
goto unmap_sg;
|
||||
|
||||
desc->callback_result = uniphier_sd_external_dma_callback;
|
||||
desc->callback_param = host;
|
||||
|
||||
cookie = dmaengine_submit(desc);
|
||||
if (cookie < 0)
|
||||
goto unmap_sg;
|
||||
|
||||
host->dma_on = true;
|
||||
|
||||
return;
|
||||
|
||||
unmap_sg:
|
||||
dma_unmap_sg(mmc_dev(host->mmc), host->sg_ptr, host->sg_len,
|
||||
priv->dma_dir);
|
||||
force_pio:
|
||||
uniphier_sd_dma_endisable(host, 0);
|
||||
}
|
||||
|
||||
static void uniphier_sd_external_dma_enable(struct tmio_mmc_host *host,
|
||||
bool enable)
|
||||
{
|
||||
}
|
||||
|
||||
static void uniphier_sd_external_dma_request(struct tmio_mmc_host *host,
|
||||
struct tmio_mmc_data *pdata)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
struct dma_chan *chan;
|
||||
|
||||
chan = dma_request_chan(mmc_dev(host->mmc), "rx-tx");
|
||||
if (IS_ERR(chan)) {
|
||||
dev_warn(mmc_dev(host->mmc),
|
||||
"failed to request DMA channel. falling back to PIO\n");
|
||||
return; /* just use PIO even for -EPROBE_DEFER */
|
||||
}
|
||||
|
||||
/* this driver uses a single channel for both RX an TX */
|
||||
priv->chan = chan;
|
||||
host->chan_rx = chan;
|
||||
host->chan_tx = chan;
|
||||
|
||||
tasklet_init(&host->dma_issue, uniphier_sd_external_dma_issue,
|
||||
(unsigned long)host);
|
||||
}
|
||||
|
||||
static void uniphier_sd_external_dma_release(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
|
||||
if (priv->chan)
|
||||
dma_release_channel(priv->chan);
|
||||
}
|
||||
|
||||
static void uniphier_sd_external_dma_abort(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
|
||||
uniphier_sd_dma_endisable(host, 0);
|
||||
|
||||
if (priv->chan)
|
||||
dmaengine_terminate_sync(priv->chan);
|
||||
}
|
||||
|
||||
static void uniphier_sd_external_dma_dataend(struct tmio_mmc_host *host)
|
||||
{
|
||||
uniphier_sd_dma_endisable(host, 0);
|
||||
|
||||
tmio_mmc_do_data_irq(host);
|
||||
}
|
||||
|
||||
static const struct tmio_mmc_dma_ops uniphier_sd_external_dma_ops = {
|
||||
.start = uniphier_sd_external_dma_start,
|
||||
.enable = uniphier_sd_external_dma_enable,
|
||||
.request = uniphier_sd_external_dma_request,
|
||||
.release = uniphier_sd_external_dma_release,
|
||||
.abort = uniphier_sd_external_dma_abort,
|
||||
.dataend = uniphier_sd_external_dma_dataend,
|
||||
};
|
||||
|
||||
static void uniphier_sd_internal_dma_issue(unsigned long arg)
|
||||
{
|
||||
struct tmio_mmc_host *host = (void *)arg;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
tmio_mmc_enable_mmc_irqs(host, TMIO_STAT_DATAEND);
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
|
||||
uniphier_sd_dma_endisable(host, 1);
|
||||
writel(UNIPHIER_SD_DMA_CTL_START, host->ctl + UNIPHIER_SD_DMA_CTL);
|
||||
}
|
||||
|
||||
static void uniphier_sd_internal_dma_start(struct tmio_mmc_host *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
struct scatterlist *sg = host->sg_ptr;
|
||||
dma_addr_t dma_addr;
|
||||
unsigned int dma_mode_dir;
|
||||
u32 dma_mode;
|
||||
int sg_len;
|
||||
|
||||
if ((data->flags & MMC_DATA_READ) && !host->chan_rx)
|
||||
goto force_pio;
|
||||
|
||||
if (WARN_ON(host->sg_len != 1))
|
||||
goto force_pio;
|
||||
|
||||
if (!IS_ALIGNED(sg->offset, 8))
|
||||
goto force_pio;
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
priv->dma_dir = DMA_FROM_DEVICE;
|
||||
dma_mode_dir = UNIPHIER_SD_DMA_MODE_DIR_FROM_DEV;
|
||||
} else {
|
||||
priv->dma_dir = DMA_TO_DEVICE;
|
||||
dma_mode_dir = UNIPHIER_SD_DMA_MODE_DIR_TO_DEV;
|
||||
}
|
||||
|
||||
sg_len = dma_map_sg(mmc_dev(host->mmc), sg, 1, priv->dma_dir);
|
||||
if (sg_len == 0)
|
||||
goto force_pio;
|
||||
|
||||
dma_mode = FIELD_PREP(UNIPHIER_SD_DMA_MODE_DIR_MASK, dma_mode_dir);
|
||||
dma_mode |= FIELD_PREP(UNIPHIER_SD_DMA_MODE_WIDTH_MASK,
|
||||
UNIPHIER_SD_DMA_MODE_WIDTH_64);
|
||||
dma_mode |= UNIPHIER_SD_DMA_MODE_ADDR_INC;
|
||||
|
||||
writel(dma_mode, host->ctl + UNIPHIER_SD_DMA_MODE);
|
||||
|
||||
dma_addr = sg_dma_address(data->sg);
|
||||
writel(lower_32_bits(dma_addr), host->ctl + UNIPHIER_SD_DMA_ADDR_L);
|
||||
writel(upper_32_bits(dma_addr), host->ctl + UNIPHIER_SD_DMA_ADDR_H);
|
||||
|
||||
host->dma_on = true;
|
||||
|
||||
return;
|
||||
force_pio:
|
||||
uniphier_sd_dma_endisable(host, 0);
|
||||
}
|
||||
|
||||
static void uniphier_sd_internal_dma_enable(struct tmio_mmc_host *host,
|
||||
bool enable)
|
||||
{
|
||||
}
|
||||
|
||||
static void uniphier_sd_internal_dma_request(struct tmio_mmc_host *host,
|
||||
struct tmio_mmc_data *pdata)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
|
||||
/*
|
||||
* Due to a hardware bug, Pro5 cannot use DMA for RX.
|
||||
* We can still use DMA for TX, but PIO for RX.
|
||||
*/
|
||||
if (!(priv->caps & UNIPHIER_SD_CAP_BROKEN_DMA_RX))
|
||||
host->chan_rx = (void *)0xdeadbeaf;
|
||||
|
||||
host->chan_tx = (void *)0xdeadbeaf;
|
||||
|
||||
tasklet_init(&host->dma_issue, uniphier_sd_internal_dma_issue,
|
||||
(unsigned long)host);
|
||||
}
|
||||
|
||||
static void uniphier_sd_internal_dma_release(struct tmio_mmc_host *host)
|
||||
{
|
||||
/* Each value is set to zero to assume "disabling" each DMA */
|
||||
host->chan_rx = NULL;
|
||||
host->chan_tx = NULL;
|
||||
}
|
||||
|
||||
static void uniphier_sd_internal_dma_abort(struct tmio_mmc_host *host)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
uniphier_sd_dma_endisable(host, 0);
|
||||
|
||||
tmp = readl(host->ctl + UNIPHIER_SD_DMA_RST);
|
||||
tmp &= ~(UNIPHIER_SD_DMA_RST_CH1 | UNIPHIER_SD_DMA_RST_CH0);
|
||||
writel(tmp, host->ctl + UNIPHIER_SD_DMA_RST);
|
||||
|
||||
tmp |= UNIPHIER_SD_DMA_RST_CH1 | UNIPHIER_SD_DMA_RST_CH0;
|
||||
writel(tmp, host->ctl + UNIPHIER_SD_DMA_RST);
|
||||
}
|
||||
|
||||
static void uniphier_sd_internal_dma_dataend(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
|
||||
uniphier_sd_dma_endisable(host, 0);
|
||||
dma_unmap_sg(mmc_dev(host->mmc), host->sg_ptr, 1, priv->dma_dir);
|
||||
|
||||
tmio_mmc_do_data_irq(host);
|
||||
}
|
||||
|
||||
static const struct tmio_mmc_dma_ops uniphier_sd_internal_dma_ops = {
|
||||
.start = uniphier_sd_internal_dma_start,
|
||||
.enable = uniphier_sd_internal_dma_enable,
|
||||
.request = uniphier_sd_internal_dma_request,
|
||||
.release = uniphier_sd_internal_dma_release,
|
||||
.abort = uniphier_sd_internal_dma_abort,
|
||||
.dataend = uniphier_sd_internal_dma_dataend,
|
||||
};
|
||||
|
||||
static int uniphier_sd_clk_enable(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
struct mmc_host *mmc = host->mmc;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(priv->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_set_rate(priv->clk, ULONG_MAX);
|
||||
if (ret)
|
||||
goto disable_clk;
|
||||
|
||||
priv->clk_rate = clk_get_rate(priv->clk);
|
||||
|
||||
/* If max-frequency property is set, use it. */
|
||||
if (!mmc->f_max)
|
||||
mmc->f_max = priv->clk_rate;
|
||||
|
||||
/*
|
||||
* 1/512 is the finest divisor in the original IP. Newer versions
|
||||
* also supports 1/1024 divisor. (UniPhier-specific extension)
|
||||
*/
|
||||
if (priv->caps & UNIPHIER_SD_CAP_EXTENDED_IP)
|
||||
mmc->f_min = priv->clk_rate / 1024;
|
||||
else
|
||||
mmc->f_min = priv->clk_rate / 512;
|
||||
|
||||
ret = reset_control_deassert(priv->rst);
|
||||
if (ret)
|
||||
goto disable_clk;
|
||||
|
||||
ret = reset_control_deassert(priv->rst_br);
|
||||
if (ret)
|
||||
goto assert_rst;
|
||||
|
||||
return 0;
|
||||
|
||||
assert_rst:
|
||||
reset_control_assert(priv->rst);
|
||||
disable_clk:
|
||||
clk_disable_unprepare(priv->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void uniphier_sd_clk_disable(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
|
||||
reset_control_assert(priv->rst_br);
|
||||
reset_control_assert(priv->rst);
|
||||
clk_disable_unprepare(priv->clk);
|
||||
}
|
||||
|
||||
static void uniphier_sd_hw_reset(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
|
||||
reset_control_assert(priv->rst_hw);
|
||||
/* For eMMC, minimum is 1us but give it 9us for good measure */
|
||||
udelay(9);
|
||||
reset_control_deassert(priv->rst_hw);
|
||||
/* For eMMC, minimum is 200us but give it 300us for good measure */
|
||||
usleep_range(300, 1000);
|
||||
}
|
||||
|
||||
static void uniphier_sd_set_clock(struct tmio_mmc_host *host,
|
||||
unsigned int clock)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
unsigned long divisor;
|
||||
u32 tmp;
|
||||
|
||||
tmp = readl(host->ctl + (CTL_SD_CARD_CLK_CTL << 1));
|
||||
|
||||
/* stop the clock before changing its rate to avoid a glitch signal */
|
||||
tmp &= ~CLK_CTL_SCLKEN;
|
||||
writel(tmp, host->ctl + (CTL_SD_CARD_CLK_CTL << 1));
|
||||
|
||||
if (clock == 0)
|
||||
return;
|
||||
|
||||
tmp &= ~UNIPHIER_SD_CLK_CTL_DIV1024;
|
||||
tmp &= ~UNIPHIER_SD_CLK_CTL_DIV1;
|
||||
tmp &= ~CLK_CTL_DIV_MASK;
|
||||
|
||||
divisor = priv->clk_rate / clock;
|
||||
|
||||
/*
|
||||
* In the original IP, bit[7:0] represents the divisor.
|
||||
* bit7 set: 1/512, ... bit0 set:1/4, all bits clear: 1/2
|
||||
*
|
||||
* The IP does not define a way to achieve 1/1. For UniPhier variants,
|
||||
* bit10 is used for 1/1. Newer versions of UniPhier variants use
|
||||
* bit16 for 1/1024.
|
||||
*/
|
||||
if (divisor <= 1)
|
||||
tmp |= UNIPHIER_SD_CLK_CTL_DIV1;
|
||||
else if (priv->caps & UNIPHIER_SD_CAP_EXTENDED_IP && divisor > 512)
|
||||
tmp |= UNIPHIER_SD_CLK_CTL_DIV1024;
|
||||
else
|
||||
tmp |= roundup_pow_of_two(divisor) >> 2;
|
||||
|
||||
writel(tmp, host->ctl + (CTL_SD_CARD_CLK_CTL << 1));
|
||||
|
||||
tmp |= CLK_CTL_SCLKEN;
|
||||
writel(tmp, host->ctl + (CTL_SD_CARD_CLK_CTL << 1));
|
||||
}
|
||||
|
||||
static void uniphier_sd_host_init(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* Connected to 32bit AXI.
|
||||
* This register holds settings for SoC-specific internal bus
|
||||
* connection. What is worse, the register spec was changed,
|
||||
* breaking the backward compatibility. Write an appropriate
|
||||
* value depending on a flag associated with a compatible string.
|
||||
*/
|
||||
if (priv->caps & UNIPHIER_SD_CAP_EXTENDED_IP)
|
||||
val = 0x00000101;
|
||||
else
|
||||
val = 0x00000000;
|
||||
|
||||
writel(val, host->ctl + UNIPHIER_SD_HOST_MODE);
|
||||
|
||||
val = 0;
|
||||
/*
|
||||
* If supported, the controller can automatically
|
||||
* enable/disable the clock line to the card.
|
||||
*/
|
||||
if (priv->caps & UNIPHIER_SD_CAP_EXTENDED_IP)
|
||||
val |= UNIPHIER_SD_CLKCTL_OFFEN;
|
||||
|
||||
writel(val, host->ctl + (CTL_SD_CARD_CLK_CTL << 1));
|
||||
}
|
||||
|
||||
static int uniphier_sd_start_signal_voltage_switch(struct mmc_host *mmc,
|
||||
struct mmc_ios *ios)
|
||||
{
|
||||
struct tmio_mmc_host *host = mmc_priv(mmc);
|
||||
struct uniphier_sd_priv *priv = uniphier_sd_priv(host);
|
||||
struct pinctrl_state *pinstate;
|
||||
u32 val, tmp;
|
||||
|
||||
switch (ios->signal_voltage) {
|
||||
case MMC_SIGNAL_VOLTAGE_330:
|
||||
val = UNIPHIER_SD_VOLT_330;
|
||||
pinstate = priv->pinstate_default;
|
||||
break;
|
||||
case MMC_SIGNAL_VOLTAGE_180:
|
||||
val = UNIPHIER_SD_VOLT_180;
|
||||
pinstate = priv->pinstate_uhs;
|
||||
break;
|
||||
default:
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
tmp = readl(host->ctl + UNIPHIER_SD_VOLT);
|
||||
tmp &= ~UNIPHIER_SD_VOLT_MASK;
|
||||
tmp |= FIELD_PREP(UNIPHIER_SD_VOLT_MASK, val);
|
||||
writel(tmp, host->ctl + UNIPHIER_SD_VOLT);
|
||||
|
||||
pinctrl_select_state(priv->pinctrl, pinstate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_sd_uhs_init(struct tmio_mmc_host *host,
|
||||
struct uniphier_sd_priv *priv)
|
||||
{
|
||||
priv->pinctrl = devm_pinctrl_get(mmc_dev(host->mmc));
|
||||
if (IS_ERR(priv->pinctrl))
|
||||
return PTR_ERR(priv->pinctrl);
|
||||
|
||||
priv->pinstate_default = pinctrl_lookup_state(priv->pinctrl,
|
||||
PINCTRL_STATE_DEFAULT);
|
||||
if (IS_ERR(priv->pinstate_default))
|
||||
return PTR_ERR(priv->pinstate_default);
|
||||
|
||||
priv->pinstate_uhs = pinctrl_lookup_state(priv->pinctrl, "uhs");
|
||||
if (IS_ERR(priv->pinstate_uhs))
|
||||
return PTR_ERR(priv->pinstate_uhs);
|
||||
|
||||
host->ops.start_signal_voltage_switch =
|
||||
uniphier_sd_start_signal_voltage_switch;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_sd_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct uniphier_sd_priv *priv;
|
||||
struct tmio_mmc_data *tmio_data;
|
||||
struct tmio_mmc_host *host;
|
||||
int irq, ret;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(dev, "failed to get IRQ number");
|
||||
return irq;
|
||||
}
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->caps = (unsigned long)of_device_get_match_data(dev);
|
||||
|
||||
priv->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(priv->clk)) {
|
||||
dev_err(dev, "failed to get clock\n");
|
||||
return PTR_ERR(priv->clk);
|
||||
}
|
||||
|
||||
priv->rst = devm_reset_control_get_shared(dev, "host");
|
||||
if (IS_ERR(priv->rst)) {
|
||||
dev_err(dev, "failed to get host reset\n");
|
||||
return PTR_ERR(priv->rst);
|
||||
}
|
||||
|
||||
/* old version has one more reset */
|
||||
if (!(priv->caps & UNIPHIER_SD_CAP_EXTENDED_IP)) {
|
||||
priv->rst_br = devm_reset_control_get_shared(dev, "bridge");
|
||||
if (IS_ERR(priv->rst_br)) {
|
||||
dev_err(dev, "failed to get bridge reset\n");
|
||||
return PTR_ERR(priv->rst_br);
|
||||
}
|
||||
}
|
||||
|
||||
tmio_data = &priv->tmio_data;
|
||||
tmio_data->flags |= TMIO_MMC_32BIT_DATA_PORT;
|
||||
|
||||
host = tmio_mmc_host_alloc(pdev, tmio_data);
|
||||
if (IS_ERR(host))
|
||||
return PTR_ERR(host);
|
||||
|
||||
if (host->mmc->caps & MMC_CAP_HW_RESET) {
|
||||
priv->rst_hw = devm_reset_control_get_exclusive(dev, "hw");
|
||||
if (IS_ERR(priv->rst_hw)) {
|
||||
dev_err(dev, "failed to get hw reset\n");
|
||||
ret = PTR_ERR(priv->rst_hw);
|
||||
goto free_host;
|
||||
}
|
||||
host->hw_reset = uniphier_sd_hw_reset;
|
||||
}
|
||||
|
||||
if (host->mmc->caps & MMC_CAP_UHS) {
|
||||
ret = uniphier_sd_uhs_init(host, priv);
|
||||
if (ret) {
|
||||
dev_warn(dev,
|
||||
"failed to setup UHS (error %d). Disabling UHS.",
|
||||
ret);
|
||||
host->mmc->caps &= ~MMC_CAP_UHS;
|
||||
}
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, irq, tmio_mmc_irq, IRQF_SHARED,
|
||||
dev_name(dev), host);
|
||||
if (ret)
|
||||
goto free_host;
|
||||
|
||||
if (priv->caps & UNIPHIER_SD_CAP_EXTENDED_IP)
|
||||
host->dma_ops = &uniphier_sd_internal_dma_ops;
|
||||
else
|
||||
host->dma_ops = &uniphier_sd_external_dma_ops;
|
||||
|
||||
host->bus_shift = 1;
|
||||
host->clk_enable = uniphier_sd_clk_enable;
|
||||
host->clk_disable = uniphier_sd_clk_disable;
|
||||
host->set_clock = uniphier_sd_set_clock;
|
||||
|
||||
ret = uniphier_sd_clk_enable(host);
|
||||
if (ret)
|
||||
goto free_host;
|
||||
|
||||
uniphier_sd_host_init(host);
|
||||
|
||||
tmio_data->ocr_mask = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
if (host->mmc->caps & MMC_CAP_UHS)
|
||||
tmio_data->ocr_mask |= MMC_VDD_165_195;
|
||||
|
||||
tmio_data->max_segs = 1;
|
||||
tmio_data->max_blk_count = U16_MAX;
|
||||
|
||||
ret = tmio_mmc_host_probe(host);
|
||||
if (ret)
|
||||
goto free_host;
|
||||
|
||||
return 0;
|
||||
|
||||
free_host:
|
||||
tmio_mmc_host_free(host);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uniphier_sd_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tmio_mmc_host *host = platform_get_drvdata(pdev);
|
||||
|
||||
tmio_mmc_host_remove(host);
|
||||
uniphier_sd_clk_disable(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id uniphier_sd_match[] = {
|
||||
{
|
||||
.compatible = "socionext,uniphier-sd-v2.91",
|
||||
},
|
||||
{
|
||||
.compatible = "socionext,uniphier-sd-v3.1",
|
||||
.data = (void *)(UNIPHIER_SD_CAP_EXTENDED_IP |
|
||||
UNIPHIER_SD_CAP_BROKEN_DMA_RX),
|
||||
},
|
||||
{
|
||||
.compatible = "socionext,uniphier-sd-v3.1.1",
|
||||
.data = (void *)UNIPHIER_SD_CAP_EXTENDED_IP,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, uniphier_sd_match);
|
||||
|
||||
static struct platform_driver uniphier_sd_driver = {
|
||||
.probe = uniphier_sd_probe,
|
||||
.remove = uniphier_sd_remove,
|
||||
.driver = {
|
||||
.name = "uniphier-sd",
|
||||
.of_match_table = uniphier_sd_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(uniphier_sd_driver);
|
||||
|
||||
MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>");
|
||||
MODULE_DESCRIPTION("UniPhier SD/eMMC host controller driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -1,10 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Renesas Electronics Europe Ltd.
|
||||
* Author: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of version 2 of the GNU General Public License as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
|
|
|
@ -18,20 +18,13 @@
|
|||
* mask into a value to be binary (or set some other custom bits
|
||||
* in MMCIPWR) or:ed and written into the MMCIPWR register of the
|
||||
* block. May also control external power based on the power_mode.
|
||||
* @status: if no GPIO read function was given to the block in
|
||||
* gpio_wp (below) this function will be called to determine
|
||||
* whether a card is present in the MMC slot or not
|
||||
* @gpio_wp: read this GPIO pin to see if the card is write protected
|
||||
* @gpio_cd: read this GPIO pin to detect card insertion
|
||||
* @cd_invert: true if the gpio_cd pin value is active low
|
||||
* @status: if no GPIO line was given to the block in this function will
|
||||
* be called to determine whether a card is present in the MMC slot or not
|
||||
*/
|
||||
struct mmci_platform_data {
|
||||
unsigned int ocr_mask;
|
||||
int (*ios_handler)(struct device *, struct mmc_ios *);
|
||||
unsigned int (*status)(struct device *);
|
||||
int gpio_wp;
|
||||
int gpio_cd;
|
||||
bool cd_invert;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -61,13 +61,6 @@
|
|||
*/
|
||||
#define TMIO_MMC_USE_GPIO_CD BIT(5)
|
||||
|
||||
/*
|
||||
* Some controllers doesn't have over 0x100 register.
|
||||
* it is used to checking accessibility of
|
||||
* CTL_SD_CARD_CLK_CTL / CTL_CLK_AND_WAIT_CTL
|
||||
*/
|
||||
#define TMIO_MMC_HAVE_HIGH_REG BIT(6)
|
||||
|
||||
/*
|
||||
* Some controllers have CMD12 automatically
|
||||
* issue/non-issue register
|
||||
|
|
|
@ -569,6 +569,11 @@ static inline bool mmc_can_retune(struct mmc_host *host)
|
|||
return host->can_retune == 1;
|
||||
}
|
||||
|
||||
static inline bool mmc_doing_retune(struct mmc_host *host)
|
||||
{
|
||||
return host->doing_retune == 1;
|
||||
}
|
||||
|
||||
static inline enum dma_data_direction mmc_get_dma_dir(struct mmc_data *data)
|
||||
{
|
||||
return data->flags & MMC_DATA_WRITE ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
|
||||
|
|
|
@ -70,9 +70,6 @@ struct omap_hsmmc_platform_data {
|
|||
/* string specifying a particular variant of hardware */
|
||||
char *version;
|
||||
|
||||
int gpio_cd; /* gpio (card detect) */
|
||||
int gpio_cod; /* gpio (cover detect) */
|
||||
int gpio_wp; /* gpio (write protect) */
|
||||
/* if we have special card, init it using this callback */
|
||||
void (*init_card)(struct mmc_card *card);
|
||||
|
||||
|
|
|
@ -33,8 +33,6 @@
|
|||
* 1: choose feedback clk + delay value
|
||||
* 2: choose internal clk
|
||||
* @clk_delay_enable: enable clk_delay or not, used on pxa910
|
||||
* @ext_cd_gpio: gpio pin used for external CD line
|
||||
* @ext_cd_gpio_invert: invert values for external CD gpio line
|
||||
* @max_speed: the maximum speed supported
|
||||
* @host_caps: Standard MMC host capabilities bit field.
|
||||
* @quirks: quirks of platfrom
|
||||
|
@ -46,8 +44,6 @@ struct sdhci_pxa_platdata {
|
|||
unsigned int clk_delay_cycles;
|
||||
unsigned int clk_delay_sel;
|
||||
bool clk_delay_enable;
|
||||
unsigned int ext_cd_gpio;
|
||||
bool ext_cd_gpio_invert;
|
||||
unsigned int max_speed;
|
||||
u32 host_caps;
|
||||
u32 host_caps2;
|
||||
|
|
Loading…
Reference in a new issue