Merge tag 'mmc-updates-for-3.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc
Pull MMC updates from Chris Ball: "MMC highlights for 3.15: Core: - CONFIG_MMC_UNSAFE_RESUME=y is now default behavior - DT bindings for SDHCI UHS, eMMC HS200, high-speed DDR, at 1.8/1.2V - Add GPIO descriptor based slot-gpio card detect API Drivers: - dw_mmc: Refactor SOCFPGA support as a variant inside dw_mmc-pltfm.c - mmci: Support HW busy detection on ux500 - omap: Support MMC_ERASE - omap_hsmmc: Support MMC_PM_KEEP_POWER, MMC_PM_WAKE_SDIO_IRQ, (a)cmd23 - rtsx: Support pre-req/post-req async - sdhci: Add support for Realtek RTS5250 controllers - sdhci-acpi: Add support for 80860F16, fix 80860F14/SDIO card detect - sdhci-msm: Add new driver for Qualcomm SDHCI chipset support - sdhci-pxav3: Add support for Marvell Armada 380 and 385 SoCs" * tag 'mmc-updates-for-3.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc: (102 commits) mmc: sdhci-acpi: Intel SDIO has broken card detect mmc: sdhci-pxav3: add support for the Armada 38x SDHCI controller mmc: sdhci-msm: Add platform_execute_tuning implementation mmc: sdhci-msm: Initial support for Qualcomm chipsets mmc: sdhci-msm: Qualcomm SDHCI binding documentation sdhci: only reprogram retuning timer when flag is set mmc: rename ARCH_BCM to ARCH_BCM_MOBILE mmc: sdhci: Allow for irq being shared mmc: sdhci-acpi: Add device id 80860F16 mmc: sdhci-acpi: Fix broken card detect for ACPI HID 80860F14 mmc: slot-gpio: Add GPIO descriptor based CD GPIO API mmc: slot-gpio: Split out CD IRQ request into a separate function mmc: slot-gpio: Record GPIO descriptors instead of GPIO numbers Revert "dts: socfpga: Add support for SD/MMC on the SOCFPGA platform" mmc: sdhci-spear: use generic card detection gpio support mmc: sdhci-spear: remove support for power gpio mmc: sdhci-spear: simplify resource handling mmc: sdhci-spear: fix platform_data usage mmc: sdhci-spear: fix error handling paths for DT mmc: sdhci-bcm-kona: fix build errors when built-in ...
This commit is contained in:
@ -26,9 +26,18 @@ Optional properties:
|
|||||||
this system, even if the controller claims it is.
|
this system, even if the controller claims it is.
|
||||||
- cap-sd-highspeed: SD high-speed timing is supported
|
- cap-sd-highspeed: SD high-speed timing is supported
|
||||||
- cap-mmc-highspeed: MMC high-speed timing is supported
|
- cap-mmc-highspeed: MMC high-speed timing is supported
|
||||||
|
- sd-uhs-sdr12: SD UHS SDR12 speed is supported
|
||||||
|
- sd-uhs-sdr25: SD UHS SDR25 speed is supported
|
||||||
|
- sd-uhs-sdr50: SD UHS SDR50 speed is supported
|
||||||
|
- sd-uhs-sdr104: SD UHS SDR104 speed is supported
|
||||||
|
- sd-uhs-ddr50: SD UHS DDR50 speed is supported
|
||||||
- cap-power-off-card: powering off the card is safe
|
- cap-power-off-card: powering off the card is safe
|
||||||
- cap-sdio-irq: enable SDIO IRQ signalling on this interface
|
- cap-sdio-irq: enable SDIO IRQ signalling on this interface
|
||||||
- full-pwr-cycle: full power cycle of the card is supported
|
- full-pwr-cycle: full power cycle of the card is supported
|
||||||
|
- mmc-highspeed-ddr-1_8v: eMMC high-speed DDR mode(1.8V I/O) is supported
|
||||||
|
- mmc-highspeed-ddr-1_2v: eMMC high-speed DDR mode(1.2V I/O) is supported
|
||||||
|
- mmc-hs200-1_8v: eMMC HS200 mode(1.8V I/O) is supported
|
||||||
|
- mmc-hs200-1_2v: eMMC HS200 mode(1.2V I/O) is supported
|
||||||
|
|
||||||
*NOTE* on CD and WP polarity. To use common for all SD/MMC host controllers line
|
*NOTE* on CD and WP polarity. To use common for all SD/MMC host controllers line
|
||||||
polarity properties, we have to fix the meaning of the "normal" and "inverted"
|
polarity properties, we have to fix the meaning of the "normal" and "inverted"
|
||||||
|
55
Documentation/devicetree/bindings/mmc/sdhci-msm.txt
Normal file
55
Documentation/devicetree/bindings/mmc/sdhci-msm.txt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
* Qualcomm SDHCI controller (sdhci-msm)
|
||||||
|
|
||||||
|
This file documents differences between the core properties in mmc.txt
|
||||||
|
and the properties used by the sdhci-msm driver.
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Should contain "qcom,sdhci-msm-v4".
|
||||||
|
- reg: Base address and length of the register in the following order:
|
||||||
|
- Host controller register map (required)
|
||||||
|
- SD Core register map (required)
|
||||||
|
- interrupts: Should contain an interrupt-specifiers for the interrupts:
|
||||||
|
- Host controller interrupt (required)
|
||||||
|
- pinctrl-names: Should contain only one value - "default".
|
||||||
|
- pinctrl-0: Should specify pin control groups used for this controller.
|
||||||
|
- clocks: A list of phandle + clock-specifier pairs for the clocks listed in clock-names.
|
||||||
|
- clock-names: Should contain the following:
|
||||||
|
"iface" - Main peripheral bus clock (PCLK/HCLK - AHB Bus clock) (required)
|
||||||
|
"core" - SDC MMC clock (MCLK) (required)
|
||||||
|
"bus" - SDCC bus voter clock (optional)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
sdhc_1: sdhci@f9824900 {
|
||||||
|
compatible = "qcom,sdhci-msm-v4";
|
||||||
|
reg = <0xf9824900 0x11c>, <0xf9824000 0x800>;
|
||||||
|
interrupts = <0 123 0>;
|
||||||
|
bus-width = <8>;
|
||||||
|
non-removable;
|
||||||
|
|
||||||
|
vmmc = <&pm8941_l20>;
|
||||||
|
vqmmc = <&pm8941_s3>;
|
||||||
|
|
||||||
|
pinctrl-names = "default";
|
||||||
|
pinctrl-0 = <&sdc1_clk &sdc1_cmd &sdc1_data>;
|
||||||
|
|
||||||
|
clocks = <&gcc GCC_SDCC1_APPS_CLK>, <&gcc GCC_SDCC1_AHB_CLK>;
|
||||||
|
clock-names = "core", "iface";
|
||||||
|
};
|
||||||
|
|
||||||
|
sdhc_2: sdhci@f98a4900 {
|
||||||
|
compatible = "qcom,sdhci-msm-v4";
|
||||||
|
reg = <0xf98a4900 0x11c>, <0xf98a4000 0x800>;
|
||||||
|
interrupts = <0 125 0>;
|
||||||
|
bus-width = <4>;
|
||||||
|
cd-gpios = <&msmgpio 62 0x1>;
|
||||||
|
|
||||||
|
vmmc = <&pm8941_l21>;
|
||||||
|
vqmmc = <&pm8941_l13>;
|
||||||
|
|
||||||
|
pinctrl-names = "default";
|
||||||
|
pinctrl-0 = <&sdc2_clk &sdc2_cmd &sdc2_data>;
|
||||||
|
|
||||||
|
clocks = <&gcc GCC_SDCC2_APPS_CLK>, <&gcc GCC_SDCC2_AHB_CLK>;
|
||||||
|
clock-names = "core", "iface";
|
||||||
|
};
|
@ -4,7 +4,14 @@ This file documents differences between the core properties in mmc.txt
|
|||||||
and the properties used by the sdhci-pxav2 and sdhci-pxav3 drivers.
|
and the properties used by the sdhci-pxav2 and sdhci-pxav3 drivers.
|
||||||
|
|
||||||
Required properties:
|
Required properties:
|
||||||
- compatible: Should be "mrvl,pxav2-mmc" or "mrvl,pxav3-mmc".
|
- compatible: Should be "mrvl,pxav2-mmc", "mrvl,pxav3-mmc" or
|
||||||
|
"marvell,armada-380-sdhci".
|
||||||
|
- reg:
|
||||||
|
* for "mrvl,pxav2-mmc" and "mrvl,pxav3-mmc", one register area for
|
||||||
|
the SDHCI registers.
|
||||||
|
* for "marvell,armada-380-sdhci", two register areas. The first one
|
||||||
|
for the SDHCI registers themselves, and the second one for the
|
||||||
|
AXI/Mbus bridge registers of the SDHCI unit.
|
||||||
|
|
||||||
Optional properties:
|
Optional properties:
|
||||||
- mrvl,clk-delay-cycles: Specify a number of cycles to delay for tuning.
|
- mrvl,clk-delay-cycles: Specify a number of cycles to delay for tuning.
|
||||||
@ -19,3 +26,11 @@ sdhci@d4280800 {
|
|||||||
non-removable;
|
non-removable;
|
||||||
mrvl,clk-delay-cycles = <31>;
|
mrvl,clk-delay-cycles = <31>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sdhci@d8000 {
|
||||||
|
compatible = "marvell,armada-380-sdhci";
|
||||||
|
reg = <0xd8000 0x1000>, <0xdc000 0x100>;
|
||||||
|
interrupts = <0 25 0x4>;
|
||||||
|
clocks = <&gateclk 17>;
|
||||||
|
mrvl,clk-delay-cycles = <0x1F>;
|
||||||
|
};
|
||||||
|
@ -10,6 +10,7 @@ Required properties:
|
|||||||
- compatible:
|
- compatible:
|
||||||
Should be "ti,omap2-hsmmc", for OMAP2 controllers
|
Should be "ti,omap2-hsmmc", for OMAP2 controllers
|
||||||
Should be "ti,omap3-hsmmc", for OMAP3 controllers
|
Should be "ti,omap3-hsmmc", for OMAP3 controllers
|
||||||
|
Should be "ti,omap3-pre-es3-hsmmc" for OMAP3 controllers pre ES3.0
|
||||||
Should be "ti,omap4-hsmmc", for OMAP4 controllers
|
Should be "ti,omap4-hsmmc", for OMAP4 controllers
|
||||||
- ti,hwmods: Must be "mmc<n>", n is controller instance starting 1
|
- ti,hwmods: Must be "mmc<n>", n is controller instance starting 1
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
PBIAS internal regulator for SD card dual voltage i/o pads on OMAP SoCs.
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible:
|
||||||
|
- "ti,pbias-omap" for OMAP2, OMAP3, OMAP4, OMAP5, DRA7.
|
||||||
|
- reg: pbias register offset from syscon base and size of pbias register.
|
||||||
|
- syscon : phandle of the system control module
|
||||||
|
- regulator-name : should be
|
||||||
|
pbias_mmc_omap2430 for OMAP2430, OMAP3 SoCs
|
||||||
|
pbias_sim_omap3 for OMAP3 SoCs
|
||||||
|
pbias_mmc_omap4 for OMAP4 SoCs
|
||||||
|
pbias_mmc_omap5 for OMAP5 and DRA7 SoC
|
||||||
|
|
||||||
|
Optional properties:
|
||||||
|
- Any optional property defined in bindings/regulator/regulator.txt
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
pbias_regulator: pbias_regulator {
|
||||||
|
compatible = "ti,pbias-omap";
|
||||||
|
reg = <0 0x4>;
|
||||||
|
syscon = <&omap5_padconf_global>;
|
||||||
|
pbias_mmc_reg: pbias_mmc_omap5 {
|
||||||
|
regulator-name = "pbias_mmc_omap5";
|
||||||
|
regulator-min-microvolt = <1800000>;
|
||||||
|
regulator-max-microvolt = <3000000>;
|
||||||
|
};
|
@ -5930,6 +5930,7 @@ F: include/linux/mfd/
|
|||||||
|
|
||||||
MULTIMEDIA CARD (MMC), SECURE DIGITAL (SD) AND SDIO SUBSYSTEM
|
MULTIMEDIA CARD (MMC), SECURE DIGITAL (SD) AND SDIO SUBSYSTEM
|
||||||
M: Chris Ball <chris@printf.net>
|
M: Chris Ball <chris@printf.net>
|
||||||
|
M: Ulf Hansson <ulf.hansson@linaro.org>
|
||||||
L: linux-mmc@vger.kernel.org
|
L: linux-mmc@vger.kernel.org
|
||||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc.git
|
T: git git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc.git
|
||||||
S: Maintained
|
S: Maintained
|
||||||
|
@ -154,6 +154,22 @@
|
|||||||
ti,hwmods = "counter_32k";
|
ti,hwmods = "counter_32k";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dra7_ctrl_general: tisyscon@4a002e00 {
|
||||||
|
compatible = "syscon";
|
||||||
|
reg = <0x4a002e00 0x7c>;
|
||||||
|
};
|
||||||
|
|
||||||
|
pbias_regulator: pbias_regulator {
|
||||||
|
compatible = "ti,pbias-omap";
|
||||||
|
reg = <0 0x4>;
|
||||||
|
syscon = <&dra7_ctrl_general>;
|
||||||
|
pbias_mmc_reg: pbias_mmc_omap5 {
|
||||||
|
regulator-name = "pbias_mmc_omap5";
|
||||||
|
regulator-min-microvolt = <1800000>;
|
||||||
|
regulator-max-microvolt = <3000000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
dra7_pmx_core: pinmux@4a003400 {
|
dra7_pmx_core: pinmux@4a003400 {
|
||||||
compatible = "pinctrl-single";
|
compatible = "pinctrl-single";
|
||||||
reg = <0x4a003400 0x0464>;
|
reg = <0x4a003400 0x0464>;
|
||||||
@ -543,6 +559,7 @@
|
|||||||
dmas = <&sdma 61>, <&sdma 62>;
|
dmas = <&sdma 61>, <&sdma 62>;
|
||||||
dma-names = "tx", "rx";
|
dma-names = "tx", "rx";
|
||||||
status = "disabled";
|
status = "disabled";
|
||||||
|
pbias-supply = <&pbias_mmc_reg>;
|
||||||
};
|
};
|
||||||
|
|
||||||
mmc2: mmc@480b4000 {
|
mmc2: mmc@480b4000 {
|
||||||
|
@ -29,6 +29,22 @@
|
|||||||
pinctrl-single,function-mask = <0x3f>;
|
pinctrl-single,function-mask = <0x3f>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
omap2_scm_general: tisyscon@49002270 {
|
||||||
|
compatible = "syscon";
|
||||||
|
reg = <0x49002270 0x240>;
|
||||||
|
};
|
||||||
|
|
||||||
|
pbias_regulator: pbias_regulator {
|
||||||
|
compatible = "ti,pbias-omap";
|
||||||
|
reg = <0x230 0x4>;
|
||||||
|
syscon = <&omap2_scm_general>;
|
||||||
|
pbias_mmc_reg: pbias_mmc_omap2430 {
|
||||||
|
regulator-name = "pbias_mmc_omap2430";
|
||||||
|
regulator-min-microvolt = <1800000>;
|
||||||
|
regulator-max-microvolt = <3000000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
gpio1: gpio@4900c000 {
|
gpio1: gpio@4900c000 {
|
||||||
compatible = "ti,omap2-gpio";
|
compatible = "ti,omap2-gpio";
|
||||||
reg = <0x4900c000 0x200>;
|
reg = <0x4900c000 0x200>;
|
||||||
@ -188,6 +204,7 @@
|
|||||||
ti,dual-volt;
|
ti,dual-volt;
|
||||||
dmas = <&sdma 61>, <&sdma 62>;
|
dmas = <&sdma 61>, <&sdma 62>;
|
||||||
dma-names = "tx", "rx";
|
dma-names = "tx", "rx";
|
||||||
|
pbias-supply = <&pbias_mmc_reg>;
|
||||||
};
|
};
|
||||||
|
|
||||||
mmc2: mmc@480b4000 {
|
mmc2: mmc@480b4000 {
|
||||||
|
@ -174,8 +174,20 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
&mmc1 {
|
&mmc1 {
|
||||||
|
/* See 35xx errata 2.1.1.128 in SPRZ278F */
|
||||||
|
compatible = "ti,omap3-pre-es3-hsmmc";
|
||||||
vmmc-supply = <&vmmc1>;
|
vmmc-supply = <&vmmc1>;
|
||||||
bus-width = <4>;
|
bus-width = <4>;
|
||||||
|
pinctrl-names = "default";
|
||||||
|
pinctrl-0 = <&mmc1_pins>;
|
||||||
|
};
|
||||||
|
|
||||||
|
&mmc2 {
|
||||||
|
status="disabled";
|
||||||
|
};
|
||||||
|
|
||||||
|
&mmc3 {
|
||||||
|
status="disabled";
|
||||||
};
|
};
|
||||||
|
|
||||||
&omap3_pmx_core {
|
&omap3_pmx_core {
|
||||||
@ -209,6 +221,17 @@
|
|||||||
0x174 (PIN_OUTPUT | MUX_MODE0) /* hsusb0_stp.hsusb0_stp */
|
0x174 (PIN_OUTPUT | MUX_MODE0) /* hsusb0_stp.hsusb0_stp */
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mmc1_pins: pinmux_mmc1_pins {
|
||||||
|
pinctrl-single,pins = <
|
||||||
|
OMAP3_CORE1_IOPAD(0x2144, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_clk.mmc1_clk */
|
||||||
|
OMAP3_CORE1_IOPAD(0x2146, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_cmd.mmc1_cmd */
|
||||||
|
OMAP3_CORE1_IOPAD(0x2148, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat0.mmc1_dat0 */
|
||||||
|
OMAP3_CORE1_IOPAD(0x214A, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat1.mmc1_dat1 */
|
||||||
|
OMAP3_CORE1_IOPAD(0x214C, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat2.mmc1_dat2 */
|
||||||
|
OMAP3_CORE1_IOPAD(0x214e, PIN_INPUT_PULLUP | MUX_MODE0) /* mmc1_dat3.mmc1_dat3 */
|
||||||
|
>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
&usb_otg_hs {
|
&usb_otg_hs {
|
||||||
|
@ -181,6 +181,22 @@
|
|||||||
pinctrl-single,function-mask = <0xff1f>;
|
pinctrl-single,function-mask = <0xff1f>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
omap3_scm_general: tisyscon@48002270 {
|
||||||
|
compatible = "syscon";
|
||||||
|
reg = <0x48002270 0x2f0>;
|
||||||
|
};
|
||||||
|
|
||||||
|
pbias_regulator: pbias_regulator {
|
||||||
|
compatible = "ti,pbias-omap";
|
||||||
|
reg = <0x2b0 0x4>;
|
||||||
|
syscon = <&omap3_scm_general>;
|
||||||
|
pbias_mmc_reg: pbias_mmc_omap2430 {
|
||||||
|
regulator-name = "pbias_mmc_omap2430";
|
||||||
|
regulator-min-microvolt = <1800000>;
|
||||||
|
regulator-max-microvolt = <3000000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
gpio1: gpio@48310000 {
|
gpio1: gpio@48310000 {
|
||||||
compatible = "ti,omap3-gpio";
|
compatible = "ti,omap3-gpio";
|
||||||
reg = <0x48310000 0x200>;
|
reg = <0x48310000 0x200>;
|
||||||
@ -395,6 +411,7 @@
|
|||||||
ti,dual-volt;
|
ti,dual-volt;
|
||||||
dmas = <&sdma 61>, <&sdma 62>;
|
dmas = <&sdma 61>, <&sdma 62>;
|
||||||
dma-names = "tx", "rx";
|
dma-names = "tx", "rx";
|
||||||
|
pbias-supply = <&pbias_mmc_reg>;
|
||||||
};
|
};
|
||||||
|
|
||||||
mmc2: mmc@480b4000 {
|
mmc2: mmc@480b4000 {
|
||||||
|
@ -191,6 +191,22 @@
|
|||||||
pinctrl-single,function-mask = <0x7fff>;
|
pinctrl-single,function-mask = <0x7fff>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
omap4_padconf_global: tisyscon@4a1005a0 {
|
||||||
|
compatible = "syscon";
|
||||||
|
reg = <0x4a1005a0 0x170>;
|
||||||
|
};
|
||||||
|
|
||||||
|
pbias_regulator: pbias_regulator {
|
||||||
|
compatible = "ti,pbias-omap";
|
||||||
|
reg = <0x60 0x4>;
|
||||||
|
syscon = <&omap4_padconf_global>;
|
||||||
|
pbias_mmc_reg: pbias_mmc_omap4 {
|
||||||
|
regulator-name = "pbias_mmc_omap4";
|
||||||
|
regulator-min-microvolt = <1800000>;
|
||||||
|
regulator-max-microvolt = <3000000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
sdma: dma-controller@4a056000 {
|
sdma: dma-controller@4a056000 {
|
||||||
compatible = "ti,omap4430-sdma";
|
compatible = "ti,omap4430-sdma";
|
||||||
reg = <0x4a056000 0x1000>;
|
reg = <0x4a056000 0x1000>;
|
||||||
@ -427,6 +443,7 @@
|
|||||||
ti,needs-special-reset;
|
ti,needs-special-reset;
|
||||||
dmas = <&sdma 61>, <&sdma 62>;
|
dmas = <&sdma 61>, <&sdma 62>;
|
||||||
dma-names = "tx", "rx";
|
dma-names = "tx", "rx";
|
||||||
|
pbias-supply = <&pbias_mmc_reg>;
|
||||||
};
|
};
|
||||||
|
|
||||||
mmc2: mmc@480b4000 {
|
mmc2: mmc@480b4000 {
|
||||||
|
@ -198,6 +198,22 @@
|
|||||||
pinctrl-single,function-mask = <0x7fff>;
|
pinctrl-single,function-mask = <0x7fff>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
omap5_padconf_global: tisyscon@4a002da0 {
|
||||||
|
compatible = "syscon";
|
||||||
|
reg = <0x4A002da0 0xec>;
|
||||||
|
};
|
||||||
|
|
||||||
|
pbias_regulator: pbias_regulator {
|
||||||
|
compatible = "ti,pbias-omap";
|
||||||
|
reg = <0x60 0x4>;
|
||||||
|
syscon = <&omap5_padconf_global>;
|
||||||
|
pbias_mmc_reg: pbias_mmc_omap5 {
|
||||||
|
regulator-name = "pbias_mmc_omap5";
|
||||||
|
regulator-min-microvolt = <1800000>;
|
||||||
|
regulator-max-microvolt = <3000000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
sdma: dma-controller@4a056000 {
|
sdma: dma-controller@4a056000 {
|
||||||
compatible = "ti,omap4430-sdma";
|
compatible = "ti,omap4430-sdma";
|
||||||
reg = <0x4a056000 0x1000>;
|
reg = <0x4a056000 0x1000>;
|
||||||
@ -480,6 +496,7 @@
|
|||||||
ti,needs-special-reset;
|
ti,needs-special-reset;
|
||||||
dmas = <&sdma 61>, <&sdma 62>;
|
dmas = <&sdma 61>, <&sdma 62>;
|
||||||
dma-names = "tx", "rx";
|
dma-names = "tx", "rx";
|
||||||
|
pbias-supply = <&pbias_mmc_reg>;
|
||||||
};
|
};
|
||||||
|
|
||||||
mmc2: mmc@480b4000 {
|
mmc2: mmc@480b4000 {
|
||||||
|
@ -170,6 +170,7 @@ CONFIG_DRA752_THERMAL=y
|
|||||||
CONFIG_WATCHDOG=y
|
CONFIG_WATCHDOG=y
|
||||||
CONFIG_OMAP_WATCHDOG=y
|
CONFIG_OMAP_WATCHDOG=y
|
||||||
CONFIG_TWL4030_WATCHDOG=y
|
CONFIG_TWL4030_WATCHDOG=y
|
||||||
|
CONFIG_MFD_SYSCON=y
|
||||||
CONFIG_MFD_PALMAS=y
|
CONFIG_MFD_PALMAS=y
|
||||||
CONFIG_MFD_TPS65217=y
|
CONFIG_MFD_TPS65217=y
|
||||||
CONFIG_MFD_TPS65910=y
|
CONFIG_MFD_TPS65910=y
|
||||||
@ -181,6 +182,7 @@ CONFIG_REGULATOR_TPS6507X=y
|
|||||||
CONFIG_REGULATOR_TPS65217=y
|
CONFIG_REGULATOR_TPS65217=y
|
||||||
CONFIG_REGULATOR_TPS65910=y
|
CONFIG_REGULATOR_TPS65910=y
|
||||||
CONFIG_REGULATOR_TWL4030=y
|
CONFIG_REGULATOR_TWL4030=y
|
||||||
|
CONFIG_REGULATOR_PBIAS=y
|
||||||
CONFIG_FB=y
|
CONFIG_FB=y
|
||||||
CONFIG_FIRMWARE_EDID=y
|
CONFIG_FIRMWARE_EDID=y
|
||||||
CONFIG_FB_MODE_HELPERS=y
|
CONFIG_FB_MODE_HELPERS=y
|
||||||
|
@ -338,58 +338,28 @@ int rtsx_pci_transfer_data(struct rtsx_pcr *pcr, struct scatterlist *sglist,
|
|||||||
int num_sg, bool read, int timeout)
|
int num_sg, bool read, int timeout)
|
||||||
{
|
{
|
||||||
struct completion trans_done;
|
struct completion trans_done;
|
||||||
u8 dir;
|
int err = 0, count;
|
||||||
int err = 0, i, count;
|
|
||||||
long timeleft;
|
long timeleft;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
struct scatterlist *sg;
|
|
||||||
enum dma_data_direction dma_dir;
|
|
||||||
u32 val;
|
|
||||||
dma_addr_t addr;
|
|
||||||
unsigned int len;
|
|
||||||
|
|
||||||
dev_dbg(&(pcr->pci->dev), "--> %s: num_sg = %d\n", __func__, num_sg);
|
count = rtsx_pci_dma_map_sg(pcr, sglist, num_sg, read);
|
||||||
|
|
||||||
/* don't transfer data during abort processing */
|
|
||||||
if (pcr->remove_pci)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
if ((sglist == NULL) || (num_sg <= 0))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
if (read) {
|
|
||||||
dir = DEVICE_TO_HOST;
|
|
||||||
dma_dir = DMA_FROM_DEVICE;
|
|
||||||
} else {
|
|
||||||
dir = HOST_TO_DEVICE;
|
|
||||||
dma_dir = DMA_TO_DEVICE;
|
|
||||||
}
|
|
||||||
|
|
||||||
count = dma_map_sg(&(pcr->pci->dev), sglist, num_sg, dma_dir);
|
|
||||||
if (count < 1) {
|
if (count < 1) {
|
||||||
dev_err(&(pcr->pci->dev), "scatterlist map failed\n");
|
dev_err(&(pcr->pci->dev), "scatterlist map failed\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
dev_dbg(&(pcr->pci->dev), "DMA mapping count: %d\n", count);
|
dev_dbg(&(pcr->pci->dev), "DMA mapping count: %d\n", count);
|
||||||
|
|
||||||
val = ((u32)(dir & 0x01) << 29) | TRIG_DMA | ADMA_MODE;
|
|
||||||
pcr->sgi = 0;
|
|
||||||
for_each_sg(sglist, sg, count, i) {
|
|
||||||
addr = sg_dma_address(sg);
|
|
||||||
len = sg_dma_len(sg);
|
|
||||||
rtsx_pci_add_sg_tbl(pcr, addr, len, i == count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock_irqsave(&pcr->lock, flags);
|
spin_lock_irqsave(&pcr->lock, flags);
|
||||||
|
|
||||||
pcr->done = &trans_done;
|
pcr->done = &trans_done;
|
||||||
pcr->trans_result = TRANS_NOT_READY;
|
pcr->trans_result = TRANS_NOT_READY;
|
||||||
init_completion(&trans_done);
|
init_completion(&trans_done);
|
||||||
rtsx_pci_writel(pcr, RTSX_HDBAR, pcr->host_sg_tbl_addr);
|
|
||||||
rtsx_pci_writel(pcr, RTSX_HDBCTLR, val);
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&pcr->lock, flags);
|
spin_unlock_irqrestore(&pcr->lock, flags);
|
||||||
|
|
||||||
|
rtsx_pci_dma_transfer(pcr, sglist, count, read);
|
||||||
|
|
||||||
timeleft = wait_for_completion_interruptible_timeout(
|
timeleft = wait_for_completion_interruptible_timeout(
|
||||||
&trans_done, msecs_to_jiffies(timeout));
|
&trans_done, msecs_to_jiffies(timeout));
|
||||||
if (timeleft <= 0) {
|
if (timeleft <= 0) {
|
||||||
@ -413,7 +383,7 @@ out:
|
|||||||
pcr->done = NULL;
|
pcr->done = NULL;
|
||||||
spin_unlock_irqrestore(&pcr->lock, flags);
|
spin_unlock_irqrestore(&pcr->lock, flags);
|
||||||
|
|
||||||
dma_unmap_sg(&(pcr->pci->dev), sglist, num_sg, dma_dir);
|
rtsx_pci_dma_unmap_sg(pcr, sglist, num_sg, read);
|
||||||
|
|
||||||
if ((err < 0) && (err != -ENODEV))
|
if ((err < 0) && (err != -ENODEV))
|
||||||
rtsx_pci_stop_cmd(pcr);
|
rtsx_pci_stop_cmd(pcr);
|
||||||
@ -425,6 +395,73 @@ out:
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(rtsx_pci_transfer_data);
|
EXPORT_SYMBOL_GPL(rtsx_pci_transfer_data);
|
||||||
|
|
||||||
|
int rtsx_pci_dma_map_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist,
|
||||||
|
int num_sg, bool read)
|
||||||
|
{
|
||||||
|
enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
|
||||||
|
|
||||||
|
if (pcr->remove_pci)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if ((sglist == NULL) || num_sg < 1)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return dma_map_sg(&(pcr->pci->dev), sglist, num_sg, dir);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(rtsx_pci_dma_map_sg);
|
||||||
|
|
||||||
|
int rtsx_pci_dma_unmap_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist,
|
||||||
|
int num_sg, bool read)
|
||||||
|
{
|
||||||
|
enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
|
||||||
|
|
||||||
|
if (pcr->remove_pci)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (sglist == NULL || num_sg < 1)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
dma_unmap_sg(&(pcr->pci->dev), sglist, num_sg, dir);
|
||||||
|
return num_sg;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(rtsx_pci_dma_unmap_sg);
|
||||||
|
|
||||||
|
int rtsx_pci_dma_transfer(struct rtsx_pcr *pcr, struct scatterlist *sglist,
|
||||||
|
int sg_count, bool read)
|
||||||
|
{
|
||||||
|
struct scatterlist *sg;
|
||||||
|
dma_addr_t addr;
|
||||||
|
unsigned int len;
|
||||||
|
int i;
|
||||||
|
u32 val;
|
||||||
|
u8 dir = read ? DEVICE_TO_HOST : HOST_TO_DEVICE;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
if (pcr->remove_pci)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if ((sglist == NULL) || (sg_count < 1))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
val = ((u32)(dir & 0x01) << 29) | TRIG_DMA | ADMA_MODE;
|
||||||
|
pcr->sgi = 0;
|
||||||
|
for_each_sg(sglist, sg, sg_count, i) {
|
||||||
|
addr = sg_dma_address(sg);
|
||||||
|
len = sg_dma_len(sg);
|
||||||
|
rtsx_pci_add_sg_tbl(pcr, addr, len, i == sg_count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_lock_irqsave(&pcr->lock, flags);
|
||||||
|
|
||||||
|
rtsx_pci_writel(pcr, RTSX_HDBAR, pcr->host_sg_tbl_addr);
|
||||||
|
rtsx_pci_writel(pcr, RTSX_HDBCTLR, val);
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&pcr->lock, flags);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(rtsx_pci_dma_transfer);
|
||||||
|
|
||||||
int rtsx_pci_read_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len)
|
int rtsx_pci_read_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
@ -836,6 +873,8 @@ static irqreturn_t rtsx_pci_isr(int irq, void *dev_id)
|
|||||||
int_reg = rtsx_pci_readl(pcr, RTSX_BIPR);
|
int_reg = rtsx_pci_readl(pcr, RTSX_BIPR);
|
||||||
/* Clear interrupt flag */
|
/* Clear interrupt flag */
|
||||||
rtsx_pci_writel(pcr, RTSX_BIPR, int_reg);
|
rtsx_pci_writel(pcr, RTSX_BIPR, int_reg);
|
||||||
|
dev_dbg(&pcr->pci->dev, "=========== BIPR 0x%8x ==========\n", int_reg);
|
||||||
|
|
||||||
if ((int_reg & pcr->bier) == 0) {
|
if ((int_reg & pcr->bier) == 0) {
|
||||||
spin_unlock(&pcr->lock);
|
spin_unlock(&pcr->lock);
|
||||||
return IRQ_NONE;
|
return IRQ_NONE;
|
||||||
@ -866,17 +905,28 @@ static irqreturn_t rtsx_pci_isr(int irq, void *dev_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (int_reg & (NEED_COMPLETE_INT | DELINK_INT)) {
|
if (int_reg & (NEED_COMPLETE_INT | DELINK_INT)) {
|
||||||
if (int_reg & (TRANS_FAIL_INT | DELINK_INT)) {
|
if (int_reg & (TRANS_FAIL_INT | DELINK_INT))
|
||||||
pcr->trans_result = TRANS_RESULT_FAIL;
|
pcr->trans_result = TRANS_RESULT_FAIL;
|
||||||
if (pcr->done)
|
else if (int_reg & TRANS_OK_INT)
|
||||||
complete(pcr->done);
|
|
||||||
} else if (int_reg & TRANS_OK_INT) {
|
|
||||||
pcr->trans_result = TRANS_RESULT_OK;
|
pcr->trans_result = TRANS_RESULT_OK;
|
||||||
if (pcr->done)
|
|
||||||
complete(pcr->done);
|
if (pcr->done)
|
||||||
|
complete(pcr->done);
|
||||||
|
|
||||||
|
if (int_reg & SD_EXIST) {
|
||||||
|
struct rtsx_slot *slot = &pcr->slots[RTSX_SD_CARD];
|
||||||
|
if (slot && slot->done_transfer)
|
||||||
|
slot->done_transfer(slot->p_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int_reg & MS_EXIST) {
|
||||||
|
struct rtsx_slot *slot = &pcr->slots[RTSX_SD_CARD];
|
||||||
|
if (slot && slot->done_transfer)
|
||||||
|
slot->done_transfer(slot->p_dev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (pcr->card_inserted || pcr->card_removed)
|
if (pcr->card_inserted || pcr->card_removed)
|
||||||
schedule_delayed_work(&pcr->carddet_work,
|
schedule_delayed_work(&pcr->carddet_work,
|
||||||
msecs_to_jiffies(200));
|
msecs_to_jiffies(200));
|
||||||
|
@ -415,8 +415,7 @@ static int ioctl_do_sanitize(struct mmc_card *card)
|
|||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (!(mmc_can_sanitize(card) &&
|
if (!mmc_can_sanitize(card)) {
|
||||||
(card->host->caps2 & MMC_CAP2_SANITIZE))) {
|
|
||||||
pr_warn("%s: %s - SANITIZE is not supported\n",
|
pr_warn("%s: %s - SANITIZE is not supported\n",
|
||||||
mmc_hostname(card->host), __func__);
|
mmc_hostname(card->host), __func__);
|
||||||
err = -EOPNOTSUPP;
|
err = -EOPNOTSUPP;
|
||||||
@ -722,19 +721,6 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int send_stop(struct mmc_card *card, u32 *status)
|
|
||||||
{
|
|
||||||
struct mmc_command cmd = {0};
|
|
||||||
int err;
|
|
||||||
|
|
||||||
cmd.opcode = MMC_STOP_TRANSMISSION;
|
|
||||||
cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
|
||||||
err = mmc_wait_for_cmd(card->host, &cmd, 5);
|
|
||||||
if (err == 0)
|
|
||||||
*status = cmd.resp[0];
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_card_status(struct mmc_card *card, u32 *status, int retries)
|
static int get_card_status(struct mmc_card *card, u32 *status, int retries)
|
||||||
{
|
{
|
||||||
struct mmc_command cmd = {0};
|
struct mmc_command cmd = {0};
|
||||||
@ -750,6 +736,99 @@ static int get_card_status(struct mmc_card *card, u32 *status, int retries)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int card_busy_detect(struct mmc_card *card, unsigned int timeout_ms,
|
||||||
|
bool hw_busy_detect, struct request *req, int *gen_err)
|
||||||
|
{
|
||||||
|
unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
|
||||||
|
int err = 0;
|
||||||
|
u32 status;
|
||||||
|
|
||||||
|
do {
|
||||||
|
err = get_card_status(card, &status, 5);
|
||||||
|
if (err) {
|
||||||
|
pr_err("%s: error %d requesting status\n",
|
||||||
|
req->rq_disk->disk_name, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & R1_ERROR) {
|
||||||
|
pr_err("%s: %s: error sending status cmd, status %#x\n",
|
||||||
|
req->rq_disk->disk_name, __func__, status);
|
||||||
|
*gen_err = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We may rely on the host hw to handle busy detection.*/
|
||||||
|
if ((card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) &&
|
||||||
|
hw_busy_detect)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Timeout if the device never becomes ready for data and never
|
||||||
|
* leaves the program state.
|
||||||
|
*/
|
||||||
|
if (time_after(jiffies, timeout)) {
|
||||||
|
pr_err("%s: Card stuck in programming state! %s %s\n",
|
||||||
|
mmc_hostname(card->host),
|
||||||
|
req->rq_disk->disk_name, __func__);
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some cards mishandle the status bits,
|
||||||
|
* so make sure to check both the busy
|
||||||
|
* indication and the card state.
|
||||||
|
*/
|
||||||
|
} while (!(status & R1_READY_FOR_DATA) ||
|
||||||
|
(R1_CURRENT_STATE(status) == R1_STATE_PRG));
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_stop(struct mmc_card *card, unsigned int timeout_ms,
|
||||||
|
struct request *req, int *gen_err, u32 *stop_status)
|
||||||
|
{
|
||||||
|
struct mmc_host *host = card->host;
|
||||||
|
struct mmc_command cmd = {0};
|
||||||
|
int err;
|
||||||
|
bool use_r1b_resp = rq_data_dir(req) == WRITE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normally we use R1B responses for WRITE, but in cases where the host
|
||||||
|
* has specified a max_busy_timeout we need to validate it. A failure
|
||||||
|
* means we need to prevent the host from doing hw busy detection, which
|
||||||
|
* is done by converting to a R1 response instead.
|
||||||
|
*/
|
||||||
|
if (host->max_busy_timeout && (timeout_ms > host->max_busy_timeout))
|
||||||
|
use_r1b_resp = false;
|
||||||
|
|
||||||
|
cmd.opcode = MMC_STOP_TRANSMISSION;
|
||||||
|
if (use_r1b_resp) {
|
||||||
|
cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
||||||
|
cmd.busy_timeout = timeout_ms;
|
||||||
|
} else {
|
||||||
|
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mmc_wait_for_cmd(host, &cmd, 5);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
*stop_status = cmd.resp[0];
|
||||||
|
|
||||||
|
/* No need to check card status in case of READ. */
|
||||||
|
if (rq_data_dir(req) == READ)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!mmc_host_is_spi(host) &&
|
||||||
|
(*stop_status & R1_ERROR)) {
|
||||||
|
pr_err("%s: %s: general error sending stop command, resp %#x\n",
|
||||||
|
req->rq_disk->disk_name, __func__, *stop_status);
|
||||||
|
*gen_err = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return card_busy_detect(card, timeout_ms, use_r1b_resp, req, gen_err);
|
||||||
|
}
|
||||||
|
|
||||||
#define ERR_NOMEDIUM 3
|
#define ERR_NOMEDIUM 3
|
||||||
#define ERR_RETRY 2
|
#define ERR_RETRY 2
|
||||||
#define ERR_ABORT 1
|
#define ERR_ABORT 1
|
||||||
@ -866,26 +945,21 @@ static int mmc_blk_cmd_recovery(struct mmc_card *card, struct request *req,
|
|||||||
*/
|
*/
|
||||||
if (R1_CURRENT_STATE(status) == R1_STATE_DATA ||
|
if (R1_CURRENT_STATE(status) == R1_STATE_DATA ||
|
||||||
R1_CURRENT_STATE(status) == R1_STATE_RCV) {
|
R1_CURRENT_STATE(status) == R1_STATE_RCV) {
|
||||||
err = send_stop(card, &stop_status);
|
err = send_stop(card,
|
||||||
if (err)
|
DIV_ROUND_UP(brq->data.timeout_ns, 1000000),
|
||||||
|
req, gen_err, &stop_status);
|
||||||
|
if (err) {
|
||||||
pr_err("%s: error %d sending stop command\n",
|
pr_err("%s: error %d sending stop command\n",
|
||||||
req->rq_disk->disk_name, err);
|
req->rq_disk->disk_name, err);
|
||||||
|
/*
|
||||||
/*
|
* If the stop cmd also timed out, the card is probably
|
||||||
* If the stop cmd also timed out, the card is probably
|
* not present, so abort. Other errors are bad news too.
|
||||||
* not present, so abort. Other errors are bad news too.
|
*/
|
||||||
*/
|
|
||||||
if (err)
|
|
||||||
return ERR_ABORT;
|
return ERR_ABORT;
|
||||||
|
}
|
||||||
|
|
||||||
if (stop_status & R1_CARD_ECC_FAILED)
|
if (stop_status & R1_CARD_ECC_FAILED)
|
||||||
*ecc_err = 1;
|
*ecc_err = 1;
|
||||||
if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ)
|
|
||||||
if (stop_status & R1_ERROR) {
|
|
||||||
pr_err("%s: %s: general error sending stop command, stop cmd response %#x\n",
|
|
||||||
req->rq_disk->disk_name, __func__,
|
|
||||||
stop_status);
|
|
||||||
*gen_err = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check for set block count errors */
|
/* Check for set block count errors */
|
||||||
@ -1157,8 +1231,7 @@ static int mmc_blk_err_check(struct mmc_card *card,
|
|||||||
* program mode, which we have to wait for it to complete.
|
* program mode, which we have to wait for it to complete.
|
||||||
*/
|
*/
|
||||||
if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) {
|
if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) {
|
||||||
u32 status;
|
int err;
|
||||||
unsigned long timeout;
|
|
||||||
|
|
||||||
/* Check stop command response */
|
/* Check stop command response */
|
||||||
if (brq->stop.resp[0] & R1_ERROR) {
|
if (brq->stop.resp[0] & R1_ERROR) {
|
||||||
@ -1168,39 +1241,10 @@ static int mmc_blk_err_check(struct mmc_card *card,
|
|||||||
gen_err = 1;
|
gen_err = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout = jiffies + msecs_to_jiffies(MMC_BLK_TIMEOUT_MS);
|
err = card_busy_detect(card, MMC_BLK_TIMEOUT_MS, false, req,
|
||||||
do {
|
&gen_err);
|
||||||
int err = get_card_status(card, &status, 5);
|
if (err)
|
||||||
if (err) {
|
return MMC_BLK_CMD_ERR;
|
||||||
pr_err("%s: error %d requesting status\n",
|
|
||||||
req->rq_disk->disk_name, err);
|
|
||||||
return MMC_BLK_CMD_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status & R1_ERROR) {
|
|
||||||
pr_err("%s: %s: general error sending status command, card status %#x\n",
|
|
||||||
req->rq_disk->disk_name, __func__,
|
|
||||||
status);
|
|
||||||
gen_err = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Timeout if the device never becomes ready for data
|
|
||||||
* and never leaves the program state.
|
|
||||||
*/
|
|
||||||
if (time_after(jiffies, timeout)) {
|
|
||||||
pr_err("%s: Card stuck in programming state!"\
|
|
||||||
" %s %s\n", mmc_hostname(card->host),
|
|
||||||
req->rq_disk->disk_name, __func__);
|
|
||||||
|
|
||||||
return MMC_BLK_CMD_ERR;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Some cards mishandle the status bits,
|
|
||||||
* so make sure to check both the busy
|
|
||||||
* indication and the card state.
|
|
||||||
*/
|
|
||||||
} while (!(status & R1_READY_FOR_DATA) ||
|
|
||||||
(R1_CURRENT_STATE(status) == R1_STATE_PRG));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if general error occurs, retry the write operation. */
|
/* if general error occurs, retry the write operation. */
|
||||||
@ -1335,7 +1379,6 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
|
|||||||
brq->data.blksz = 512;
|
brq->data.blksz = 512;
|
||||||
brq->stop.opcode = MMC_STOP_TRANSMISSION;
|
brq->stop.opcode = MMC_STOP_TRANSMISSION;
|
||||||
brq->stop.arg = 0;
|
brq->stop.arg = 0;
|
||||||
brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
|
||||||
brq->data.blocks = blk_rq_sectors(req);
|
brq->data.blocks = blk_rq_sectors(req);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1378,9 +1421,15 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
|
|||||||
if (rq_data_dir(req) == READ) {
|
if (rq_data_dir(req) == READ) {
|
||||||
brq->cmd.opcode = readcmd;
|
brq->cmd.opcode = readcmd;
|
||||||
brq->data.flags |= MMC_DATA_READ;
|
brq->data.flags |= MMC_DATA_READ;
|
||||||
|
if (brq->mrq.stop)
|
||||||
|
brq->stop.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 |
|
||||||
|
MMC_CMD_AC;
|
||||||
} else {
|
} else {
|
||||||
brq->cmd.opcode = writecmd;
|
brq->cmd.opcode = writecmd;
|
||||||
brq->data.flags |= MMC_DATA_WRITE;
|
brq->data.flags |= MMC_DATA_WRITE;
|
||||||
|
if (brq->mrq.stop)
|
||||||
|
brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B |
|
||||||
|
MMC_CMD_AC;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (do_rel_wr)
|
if (do_rel_wr)
|
||||||
|
@ -2,21 +2,6 @@
|
|||||||
# MMC core configuration
|
# MMC core configuration
|
||||||
#
|
#
|
||||||
|
|
||||||
config MMC_UNSAFE_RESUME
|
|
||||||
bool "Assume MMC/SD cards are non-removable (DANGEROUS)"
|
|
||||||
help
|
|
||||||
If you say Y here, the MMC layer will assume that all cards
|
|
||||||
stayed in their respective slots during the suspend. The
|
|
||||||
normal behaviour is to remove them at suspend and
|
|
||||||
redetecting them at resume. Breaking this assumption will
|
|
||||||
in most cases result in data corruption.
|
|
||||||
|
|
||||||
This option is usually just for embedded systems which use
|
|
||||||
a MMC/SD card for rootfs. Most people should say N here.
|
|
||||||
|
|
||||||
This option sets a default which can be overridden by the
|
|
||||||
module parameter "removable=0" or "removable=1".
|
|
||||||
|
|
||||||
config MMC_CLKGATE
|
config MMC_CLKGATE
|
||||||
bool "MMC host clock gating"
|
bool "MMC host clock gating"
|
||||||
help
|
help
|
||||||
|
@ -185,24 +185,16 @@ static int mmc_runtime_suspend(struct device *dev)
|
|||||||
{
|
{
|
||||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||||
struct mmc_host *host = card->host;
|
struct mmc_host *host = card->host;
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
if (host->bus_ops->runtime_suspend)
|
return host->bus_ops->runtime_suspend(host);
|
||||||
ret = host->bus_ops->runtime_suspend(host);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mmc_runtime_resume(struct device *dev)
|
static int mmc_runtime_resume(struct device *dev)
|
||||||
{
|
{
|
||||||
struct mmc_card *card = mmc_dev_to_card(dev);
|
struct mmc_card *card = mmc_dev_to_card(dev);
|
||||||
struct mmc_host *host = card->host;
|
struct mmc_host *host = card->host;
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
if (host->bus_ops->runtime_resume)
|
return host->bus_ops->runtime_resume(host);
|
||||||
ret = host->bus_ops->runtime_resume(host);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mmc_runtime_idle(struct device *dev)
|
static int mmc_runtime_idle(struct device *dev)
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include <linux/mmc/host.h>
|
#include <linux/mmc/host.h>
|
||||||
#include <linux/mmc/mmc.h>
|
#include <linux/mmc/mmc.h>
|
||||||
#include <linux/mmc/sd.h>
|
#include <linux/mmc/sd.h>
|
||||||
|
#include <linux/mmc/slot-gpio.h>
|
||||||
|
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "bus.h"
|
#include "bus.h"
|
||||||
@ -64,23 +65,6 @@ static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };
|
|||||||
bool use_spi_crc = 1;
|
bool use_spi_crc = 1;
|
||||||
module_param(use_spi_crc, bool, 0);
|
module_param(use_spi_crc, bool, 0);
|
||||||
|
|
||||||
/*
|
|
||||||
* We normally treat cards as removed during suspend if they are not
|
|
||||||
* known to be on a non-removable bus, to avoid the risk of writing
|
|
||||||
* back data to a different card after resume. Allow this to be
|
|
||||||
* overridden if necessary.
|
|
||||||
*/
|
|
||||||
#ifdef CONFIG_MMC_UNSAFE_RESUME
|
|
||||||
bool mmc_assume_removable;
|
|
||||||
#else
|
|
||||||
bool mmc_assume_removable = 1;
|
|
||||||
#endif
|
|
||||||
EXPORT_SYMBOL(mmc_assume_removable);
|
|
||||||
module_param_named(removable, mmc_assume_removable, bool, 0644);
|
|
||||||
MODULE_PARM_DESC(
|
|
||||||
removable,
|
|
||||||
"MMC/SD cards are removable and may be removed during suspend");
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Internal function. Schedule delayed work in the MMC work queue.
|
* Internal function. Schedule delayed work in the MMC work queue.
|
||||||
*/
|
*/
|
||||||
@ -302,7 +286,8 @@ void mmc_start_bkops(struct mmc_card *card, bool from_exception)
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||||
EXT_CSD_BKOPS_START, 1, timeout, use_busy_signal, true);
|
EXT_CSD_BKOPS_START, 1, timeout,
|
||||||
|
use_busy_signal, true, false);
|
||||||
if (err) {
|
if (err) {
|
||||||
pr_warn("%s: Error %d starting bkops\n",
|
pr_warn("%s: Error %d starting bkops\n",
|
||||||
mmc_hostname(card->host), err);
|
mmc_hostname(card->host), err);
|
||||||
@ -1950,7 +1935,7 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from,
|
|||||||
cmd.opcode = MMC_ERASE;
|
cmd.opcode = MMC_ERASE;
|
||||||
cmd.arg = arg;
|
cmd.arg = arg;
|
||||||
cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
||||||
cmd.cmd_timeout_ms = mmc_erase_timeout(card, arg, qty);
|
cmd.busy_timeout = mmc_erase_timeout(card, arg, qty);
|
||||||
err = mmc_wait_for_cmd(card->host, &cmd, 0);
|
err = mmc_wait_for_cmd(card->host, &cmd, 0);
|
||||||
if (err) {
|
if (err) {
|
||||||
pr_err("mmc_erase: erase error %d, status %#x\n",
|
pr_err("mmc_erase: erase error %d, status %#x\n",
|
||||||
@ -2137,7 +2122,7 @@ static unsigned int mmc_do_calc_max_discard(struct mmc_card *card,
|
|||||||
y = 0;
|
y = 0;
|
||||||
for (x = 1; x && x <= max_qty && max_qty - x >= qty; x <<= 1) {
|
for (x = 1; x && x <= max_qty && max_qty - x >= qty; x <<= 1) {
|
||||||
timeout = mmc_erase_timeout(card, arg, qty + x);
|
timeout = mmc_erase_timeout(card, arg, qty + x);
|
||||||
if (timeout > host->max_discard_to)
|
if (timeout > host->max_busy_timeout)
|
||||||
break;
|
break;
|
||||||
if (timeout < last_timeout)
|
if (timeout < last_timeout)
|
||||||
break;
|
break;
|
||||||
@ -2169,7 +2154,7 @@ unsigned int mmc_calc_max_discard(struct mmc_card *card)
|
|||||||
struct mmc_host *host = card->host;
|
struct mmc_host *host = card->host;
|
||||||
unsigned int max_discard, max_trim;
|
unsigned int max_discard, max_trim;
|
||||||
|
|
||||||
if (!host->max_discard_to)
|
if (!host->max_busy_timeout)
|
||||||
return UINT_MAX;
|
return UINT_MAX;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2189,7 +2174,7 @@ unsigned int mmc_calc_max_discard(struct mmc_card *card)
|
|||||||
max_discard = 0;
|
max_discard = 0;
|
||||||
}
|
}
|
||||||
pr_debug("%s: calculated max. discard sectors %u for timeout %u ms\n",
|
pr_debug("%s: calculated max. discard sectors %u for timeout %u ms\n",
|
||||||
mmc_hostname(host), max_discard, host->max_discard_to);
|
mmc_hostname(host), max_discard, host->max_busy_timeout);
|
||||||
return max_discard;
|
return max_discard;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(mmc_calc_max_discard);
|
EXPORT_SYMBOL(mmc_calc_max_discard);
|
||||||
@ -2248,9 +2233,6 @@ static int mmc_do_hw_reset(struct mmc_host *host, int check)
|
|||||||
{
|
{
|
||||||
struct mmc_card *card = host->card;
|
struct mmc_card *card = host->card;
|
||||||
|
|
||||||
if (!host->bus_ops->power_restore)
|
|
||||||
return -EOPNOTSUPP;
|
|
||||||
|
|
||||||
if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset)
|
if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset)
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
@ -2352,7 +2334,7 @@ int _mmc_detect_card_removed(struct mmc_host *host)
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if ((host->caps & MMC_CAP_NONREMOVABLE) || !host->bus_ops->alive)
|
if (host->caps & MMC_CAP_NONREMOVABLE)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!host->card || mmc_card_removed(host->card))
|
if (!host->card || mmc_card_removed(host->card))
|
||||||
@ -2435,7 +2417,7 @@ void mmc_rescan(struct work_struct *work)
|
|||||||
* if there is a _removable_ card registered, check whether it is
|
* if there is a _removable_ card registered, check whether it is
|
||||||
* still present
|
* still present
|
||||||
*/
|
*/
|
||||||
if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
|
if (host->bus_ops && !host->bus_dead
|
||||||
&& !(host->caps & MMC_CAP_NONREMOVABLE))
|
&& !(host->caps & MMC_CAP_NONREMOVABLE))
|
||||||
host->bus_ops->detect(host);
|
host->bus_ops->detect(host);
|
||||||
|
|
||||||
@ -2490,6 +2472,7 @@ void mmc_start_host(struct mmc_host *host)
|
|||||||
mmc_power_off(host);
|
mmc_power_off(host);
|
||||||
else
|
else
|
||||||
mmc_power_up(host, host->ocr_avail);
|
mmc_power_up(host, host->ocr_avail);
|
||||||
|
mmc_gpiod_request_cd_irq(host);
|
||||||
_mmc_detect_change(host, 0, false);
|
_mmc_detect_change(host, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2501,6 +2484,8 @@ void mmc_stop_host(struct mmc_host *host)
|
|||||||
host->removed = 1;
|
host->removed = 1;
|
||||||
spin_unlock_irqrestore(&host->lock, flags);
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
#endif
|
#endif
|
||||||
|
if (host->slot.cd_irq >= 0)
|
||||||
|
disable_irq(host->slot.cd_irq);
|
||||||
|
|
||||||
host->rescan_disable = 1;
|
host->rescan_disable = 1;
|
||||||
cancel_delayed_work_sync(&host->detect);
|
cancel_delayed_work_sync(&host->detect);
|
||||||
@ -2537,7 +2522,7 @@ int mmc_power_save_host(struct mmc_host *host)
|
|||||||
|
|
||||||
mmc_bus_get(host);
|
mmc_bus_get(host);
|
||||||
|
|
||||||
if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) {
|
if (!host->bus_ops || host->bus_dead) {
|
||||||
mmc_bus_put(host);
|
mmc_bus_put(host);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
@ -2563,7 +2548,7 @@ int mmc_power_restore_host(struct mmc_host *host)
|
|||||||
|
|
||||||
mmc_bus_get(host);
|
mmc_bus_get(host);
|
||||||
|
|
||||||
if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) {
|
if (!host->bus_ops || host->bus_dead) {
|
||||||
mmc_bus_put(host);
|
mmc_bus_put(host);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
@ -2582,12 +2567,8 @@ EXPORT_SYMBOL(mmc_power_restore_host);
|
|||||||
*/
|
*/
|
||||||
int mmc_flush_cache(struct mmc_card *card)
|
int mmc_flush_cache(struct mmc_card *card)
|
||||||
{
|
{
|
||||||
struct mmc_host *host = card->host;
|
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
if (!(host->caps2 & MMC_CAP2_CACHE_CTRL))
|
|
||||||
return err;
|
|
||||||
|
|
||||||
if (mmc_card_mmc(card) &&
|
if (mmc_card_mmc(card) &&
|
||||||
(card->ext_csd.cache_size > 0) &&
|
(card->ext_csd.cache_size > 0) &&
|
||||||
(card->ext_csd.cache_ctrl & 1)) {
|
(card->ext_csd.cache_ctrl & 1)) {
|
||||||
@ -2602,44 +2583,6 @@ int mmc_flush_cache(struct mmc_card *card)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL(mmc_flush_cache);
|
EXPORT_SYMBOL(mmc_flush_cache);
|
||||||
|
|
||||||
/*
|
|
||||||
* Turn the cache ON/OFF.
|
|
||||||
* Turning the cache OFF shall trigger flushing of the data
|
|
||||||
* to the non-volatile storage.
|
|
||||||
* This function should be called with host claimed
|
|
||||||
*/
|
|
||||||
int mmc_cache_ctrl(struct mmc_host *host, u8 enable)
|
|
||||||
{
|
|
||||||
struct mmc_card *card = host->card;
|
|
||||||
unsigned int timeout;
|
|
||||||
int err = 0;
|
|
||||||
|
|
||||||
if (!(host->caps2 & MMC_CAP2_CACHE_CTRL) ||
|
|
||||||
mmc_card_is_removable(host))
|
|
||||||
return err;
|
|
||||||
|
|
||||||
if (card && mmc_card_mmc(card) &&
|
|
||||||
(card->ext_csd.cache_size > 0)) {
|
|
||||||
enable = !!enable;
|
|
||||||
|
|
||||||
if (card->ext_csd.cache_ctrl ^ enable) {
|
|
||||||
timeout = enable ? card->ext_csd.generic_cmd6_time : 0;
|
|
||||||
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
|
||||||
EXT_CSD_CACHE_CTRL, enable, timeout);
|
|
||||||
if (err)
|
|
||||||
pr_err("%s: cache %s error %d\n",
|
|
||||||
mmc_hostname(card->host),
|
|
||||||
enable ? "on" : "off",
|
|
||||||
err);
|
|
||||||
else
|
|
||||||
card->ext_csd.cache_ctrl = enable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(mmc_cache_ctrl);
|
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM
|
||||||
|
|
||||||
/* Do the card removal on suspend if card is assumed removeable
|
/* Do the card removal on suspend if card is assumed removeable
|
||||||
@ -2668,7 +2611,7 @@ int mmc_pm_notify(struct notifier_block *notify_block,
|
|||||||
/* Validate prerequisites for suspend */
|
/* Validate prerequisites for suspend */
|
||||||
if (host->bus_ops->pre_suspend)
|
if (host->bus_ops->pre_suspend)
|
||||||
err = host->bus_ops->pre_suspend(host);
|
err = host->bus_ops->pre_suspend(host);
|
||||||
if (!err && host->bus_ops->suspend)
|
if (!err)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Calling bus_ops->remove() with a claimed host can deadlock */
|
/* Calling bus_ops->remove() with a claimed host can deadlock */
|
||||||
|
@ -419,6 +419,16 @@ int mmc_of_parse(struct mmc_host *host)
|
|||||||
host->caps |= MMC_CAP_SD_HIGHSPEED;
|
host->caps |= MMC_CAP_SD_HIGHSPEED;
|
||||||
if (of_find_property(np, "cap-mmc-highspeed", &len))
|
if (of_find_property(np, "cap-mmc-highspeed", &len))
|
||||||
host->caps |= MMC_CAP_MMC_HIGHSPEED;
|
host->caps |= MMC_CAP_MMC_HIGHSPEED;
|
||||||
|
if (of_find_property(np, "sd-uhs-sdr12", &len))
|
||||||
|
host->caps |= MMC_CAP_UHS_SDR12;
|
||||||
|
if (of_find_property(np, "sd-uhs-sdr25", &len))
|
||||||
|
host->caps |= MMC_CAP_UHS_SDR25;
|
||||||
|
if (of_find_property(np, "sd-uhs-sdr50", &len))
|
||||||
|
host->caps |= MMC_CAP_UHS_SDR50;
|
||||||
|
if (of_find_property(np, "sd-uhs-sdr104", &len))
|
||||||
|
host->caps |= MMC_CAP_UHS_SDR104;
|
||||||
|
if (of_find_property(np, "sd-uhs-ddr50", &len))
|
||||||
|
host->caps |= MMC_CAP_UHS_DDR50;
|
||||||
if (of_find_property(np, "cap-power-off-card", &len))
|
if (of_find_property(np, "cap-power-off-card", &len))
|
||||||
host->caps |= MMC_CAP_POWER_OFF_CARD;
|
host->caps |= MMC_CAP_POWER_OFF_CARD;
|
||||||
if (of_find_property(np, "cap-sdio-irq", &len))
|
if (of_find_property(np, "cap-sdio-irq", &len))
|
||||||
@ -429,6 +439,14 @@ int mmc_of_parse(struct mmc_host *host)
|
|||||||
host->pm_caps |= MMC_PM_KEEP_POWER;
|
host->pm_caps |= MMC_PM_KEEP_POWER;
|
||||||
if (of_find_property(np, "enable-sdio-wakeup", &len))
|
if (of_find_property(np, "enable-sdio-wakeup", &len))
|
||||||
host->pm_caps |= MMC_PM_WAKE_SDIO_IRQ;
|
host->pm_caps |= MMC_PM_WAKE_SDIO_IRQ;
|
||||||
|
if (of_find_property(np, "mmc-ddr-1_8v", &len))
|
||||||
|
host->caps |= MMC_CAP_1_8V_DDR;
|
||||||
|
if (of_find_property(np, "mmc-ddr-1_2v", &len))
|
||||||
|
host->caps |= MMC_CAP_1_2V_DDR;
|
||||||
|
if (of_find_property(np, "mmc-hs200-1_8v", &len))
|
||||||
|
host->caps2 |= MMC_CAP2_HS200_1_8V_SDR;
|
||||||
|
if (of_find_property(np, "mmc-hs200-1_2v", &len))
|
||||||
|
host->caps2 |= MMC_CAP2_HS200_1_2V_SDR;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -856,8 +856,10 @@ static int mmc_select_hs200(struct mmc_card *card)
|
|||||||
|
|
||||||
/* switch to HS200 mode if bus width set successfully */
|
/* switch to HS200 mode if bus width set successfully */
|
||||||
if (!err)
|
if (!err)
|
||||||
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||||
EXT_CSD_HS_TIMING, 2, 0);
|
EXT_CSD_HS_TIMING, 2,
|
||||||
|
card->ext_csd.generic_cmd6_time,
|
||||||
|
true, true, true);
|
||||||
err:
|
err:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@ -1074,9 +1076,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
|
|||||||
host->caps2 & MMC_CAP2_HS200)
|
host->caps2 & MMC_CAP2_HS200)
|
||||||
err = mmc_select_hs200(card);
|
err = mmc_select_hs200(card);
|
||||||
else if (host->caps & MMC_CAP_MMC_HIGHSPEED)
|
else if (host->caps & MMC_CAP_MMC_HIGHSPEED)
|
||||||
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||||
EXT_CSD_HS_TIMING, 1,
|
EXT_CSD_HS_TIMING, 1,
|
||||||
card->ext_csd.generic_cmd6_time);
|
card->ext_csd.generic_cmd6_time,
|
||||||
|
true, true, true);
|
||||||
|
|
||||||
if (err && err != -EBADMSG)
|
if (err && err != -EBADMSG)
|
||||||
goto free_card;
|
goto free_card;
|
||||||
@ -1287,8 +1290,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
|
|||||||
* If cache size is higher than 0, this indicates
|
* If cache size is higher than 0, this indicates
|
||||||
* the existence of cache and it can be turned on.
|
* the existence of cache and it can be turned on.
|
||||||
*/
|
*/
|
||||||
if ((host->caps2 & MMC_CAP2_CACHE_CTRL) &&
|
if (card->ext_csd.cache_size > 0) {
|
||||||
card->ext_csd.cache_size > 0) {
|
|
||||||
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||||
EXT_CSD_CACHE_CTRL, 1,
|
EXT_CSD_CACHE_CTRL, 1,
|
||||||
card->ext_csd.generic_cmd6_time);
|
card->ext_csd.generic_cmd6_time);
|
||||||
@ -1356,11 +1358,9 @@ static int mmc_sleep(struct mmc_host *host)
|
|||||||
{
|
{
|
||||||
struct mmc_command cmd = {0};
|
struct mmc_command cmd = {0};
|
||||||
struct mmc_card *card = host->card;
|
struct mmc_card *card = host->card;
|
||||||
|
unsigned int timeout_ms = DIV_ROUND_UP(card->ext_csd.sa_timeout, 10000);
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (host->caps2 & MMC_CAP2_NO_SLEEP_CMD)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
err = mmc_deselect_cards(host);
|
err = mmc_deselect_cards(host);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
@ -1369,7 +1369,19 @@ static int mmc_sleep(struct mmc_host *host)
|
|||||||
cmd.arg = card->rca << 16;
|
cmd.arg = card->rca << 16;
|
||||||
cmd.arg |= 1 << 15;
|
cmd.arg |= 1 << 15;
|
||||||
|
|
||||||
cmd.flags = MMC_RSP_R1B | MMC_CMD_AC;
|
/*
|
||||||
|
* If the max_busy_timeout of the host is specified, validate it against
|
||||||
|
* the sleep cmd timeout. A failure means we need to prevent the host
|
||||||
|
* from doing hw busy detection, which is done by converting to a R1
|
||||||
|
* response instead of a R1B.
|
||||||
|
*/
|
||||||
|
if (host->max_busy_timeout && (timeout_ms > host->max_busy_timeout)) {
|
||||||
|
cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||||
|
} else {
|
||||||
|
cmd.flags = MMC_RSP_R1B | MMC_CMD_AC;
|
||||||
|
cmd.busy_timeout = timeout_ms;
|
||||||
|
}
|
||||||
|
|
||||||
err = mmc_wait_for_cmd(host, &cmd, 0);
|
err = mmc_wait_for_cmd(host, &cmd, 0);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
@ -1380,8 +1392,8 @@ static int mmc_sleep(struct mmc_host *host)
|
|||||||
* SEND_STATUS command to poll the status because that command (and most
|
* SEND_STATUS command to poll the status because that command (and most
|
||||||
* others) is invalid while the card sleeps.
|
* others) is invalid while the card sleeps.
|
||||||
*/
|
*/
|
||||||
if (!(host->caps & MMC_CAP_WAIT_WHILE_BUSY))
|
if (!cmd.busy_timeout || !(host->caps & MMC_CAP_WAIT_WHILE_BUSY))
|
||||||
mmc_delay(DIV_ROUND_UP(card->ext_csd.sa_timeout, 10000));
|
mmc_delay(timeout_ms);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@ -1404,7 +1416,7 @@ static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type)
|
|||||||
|
|
||||||
err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||||
EXT_CSD_POWER_OFF_NOTIFICATION,
|
EXT_CSD_POWER_OFF_NOTIFICATION,
|
||||||
notify_type, timeout, true, false);
|
notify_type, timeout, true, false, false);
|
||||||
if (err)
|
if (err)
|
||||||
pr_err("%s: Power Off Notification timed out, %u\n",
|
pr_err("%s: Power Off Notification timed out, %u\n",
|
||||||
mmc_hostname(card->host), timeout);
|
mmc_hostname(card->host), timeout);
|
||||||
@ -1484,7 +1496,7 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mmc_cache_ctrl(host, 0);
|
err = mmc_flush_cache(host->card);
|
||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
@ -1634,16 +1646,6 @@ static int mmc_power_restore(struct mmc_host *host)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const struct mmc_bus_ops mmc_ops = {
|
static const struct mmc_bus_ops mmc_ops = {
|
||||||
.remove = mmc_remove,
|
|
||||||
.detect = mmc_detect,
|
|
||||||
.suspend = NULL,
|
|
||||||
.resume = NULL,
|
|
||||||
.power_restore = mmc_power_restore,
|
|
||||||
.alive = mmc_alive,
|
|
||||||
.shutdown = mmc_shutdown,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct mmc_bus_ops mmc_ops_unsafe = {
|
|
||||||
.remove = mmc_remove,
|
.remove = mmc_remove,
|
||||||
.detect = mmc_detect,
|
.detect = mmc_detect,
|
||||||
.suspend = mmc_suspend,
|
.suspend = mmc_suspend,
|
||||||
@ -1655,17 +1657,6 @@ static const struct mmc_bus_ops mmc_ops_unsafe = {
|
|||||||
.shutdown = mmc_shutdown,
|
.shutdown = mmc_shutdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void mmc_attach_bus_ops(struct mmc_host *host)
|
|
||||||
{
|
|
||||||
const struct mmc_bus_ops *bus_ops;
|
|
||||||
|
|
||||||
if (!mmc_card_is_removable(host))
|
|
||||||
bus_ops = &mmc_ops_unsafe;
|
|
||||||
else
|
|
||||||
bus_ops = &mmc_ops;
|
|
||||||
mmc_attach_bus(host, bus_ops);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Starting point for MMC card init.
|
* Starting point for MMC card init.
|
||||||
*/
|
*/
|
||||||
@ -1685,7 +1676,7 @@ int mmc_attach_mmc(struct mmc_host *host)
|
|||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
mmc_attach_bus_ops(host);
|
mmc_attach_bus(host, &mmc_ops);
|
||||||
if (host->ocr_avail_mmc)
|
if (host->ocr_avail_mmc)
|
||||||
host->ocr_avail = host->ocr_avail_mmc;
|
host->ocr_avail = host->ocr_avail_mmc;
|
||||||
|
|
||||||
|
@ -405,20 +405,30 @@ int mmc_spi_set_crc(struct mmc_host *host, int use_crc)
|
|||||||
* timeout of zero implies maximum possible timeout
|
* timeout of zero implies maximum possible timeout
|
||||||
* @use_busy_signal: use the busy signal as response type
|
* @use_busy_signal: use the busy signal as response type
|
||||||
* @send_status: send status cmd to poll for busy
|
* @send_status: send status cmd to poll for busy
|
||||||
|
* @ignore_crc: ignore CRC errors when sending status cmd to poll for busy
|
||||||
*
|
*
|
||||||
* Modifies the EXT_CSD register for selected card.
|
* Modifies the EXT_CSD register for selected card.
|
||||||
*/
|
*/
|
||||||
int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
|
int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
|
||||||
unsigned int timeout_ms, bool use_busy_signal, bool send_status)
|
unsigned int timeout_ms, bool use_busy_signal, bool send_status,
|
||||||
|
bool ignore_crc)
|
||||||
{
|
{
|
||||||
|
struct mmc_host *host = card->host;
|
||||||
int err;
|
int err;
|
||||||
struct mmc_command cmd = {0};
|
struct mmc_command cmd = {0};
|
||||||
unsigned long timeout;
|
unsigned long timeout;
|
||||||
u32 status = 0;
|
u32 status = 0;
|
||||||
bool ignore_crc = false;
|
bool use_r1b_resp = use_busy_signal;
|
||||||
|
|
||||||
BUG_ON(!card);
|
/*
|
||||||
BUG_ON(!card->host);
|
* If the cmd timeout and the max_busy_timeout of the host are both
|
||||||
|
* specified, let's validate them. A failure means we need to prevent
|
||||||
|
* the host from doing hw busy detection, which is done by converting
|
||||||
|
* to a R1 response instead of a R1B.
|
||||||
|
*/
|
||||||
|
if (timeout_ms && host->max_busy_timeout &&
|
||||||
|
(timeout_ms > host->max_busy_timeout))
|
||||||
|
use_r1b_resp = false;
|
||||||
|
|
||||||
cmd.opcode = MMC_SWITCH;
|
cmd.opcode = MMC_SWITCH;
|
||||||
cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
|
cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
|
||||||
@ -426,17 +436,21 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
|
|||||||
(value << 8) |
|
(value << 8) |
|
||||||
set;
|
set;
|
||||||
cmd.flags = MMC_CMD_AC;
|
cmd.flags = MMC_CMD_AC;
|
||||||
if (use_busy_signal)
|
if (use_r1b_resp) {
|
||||||
cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B;
|
cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B;
|
||||||
else
|
/*
|
||||||
|
* A busy_timeout of zero means the host can decide to use
|
||||||
|
* whatever value it finds suitable.
|
||||||
|
*/
|
||||||
|
cmd.busy_timeout = timeout_ms;
|
||||||
|
} else {
|
||||||
cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1;
|
cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
cmd.cmd_timeout_ms = timeout_ms;
|
|
||||||
if (index == EXT_CSD_SANITIZE_START)
|
if (index == EXT_CSD_SANITIZE_START)
|
||||||
cmd.sanitize_busy = true;
|
cmd.sanitize_busy = true;
|
||||||
|
|
||||||
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
|
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
@ -445,24 +459,27 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Must check status to be sure of no errors
|
* CRC errors shall only be ignored in cases were CMD13 is used to poll
|
||||||
* If CMD13 is to check the busy completion of the timing change,
|
* to detect busy completion.
|
||||||
* disable the check of CRC error.
|
|
||||||
*/
|
*/
|
||||||
if (index == EXT_CSD_HS_TIMING &&
|
if ((host->caps & MMC_CAP_WAIT_WHILE_BUSY) && use_r1b_resp)
|
||||||
!(card->host->caps & MMC_CAP_WAIT_WHILE_BUSY))
|
ignore_crc = false;
|
||||||
ignore_crc = true;
|
|
||||||
|
|
||||||
timeout = jiffies + msecs_to_jiffies(MMC_OPS_TIMEOUT_MS);
|
/* We have an unspecified cmd timeout, use the fallback value. */
|
||||||
|
if (!timeout_ms)
|
||||||
|
timeout_ms = MMC_OPS_TIMEOUT_MS;
|
||||||
|
|
||||||
|
/* Must check status to be sure of no errors. */
|
||||||
|
timeout = jiffies + msecs_to_jiffies(timeout_ms);
|
||||||
do {
|
do {
|
||||||
if (send_status) {
|
if (send_status) {
|
||||||
err = __mmc_send_status(card, &status, ignore_crc);
|
err = __mmc_send_status(card, &status, ignore_crc);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
|
if ((host->caps & MMC_CAP_WAIT_WHILE_BUSY) && use_r1b_resp)
|
||||||
break;
|
break;
|
||||||
if (mmc_host_is_spi(card->host))
|
if (mmc_host_is_spi(host))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -478,18 +495,18 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
|
|||||||
/* Timeout if the device never leaves the program state. */
|
/* Timeout if the device never leaves the program state. */
|
||||||
if (time_after(jiffies, timeout)) {
|
if (time_after(jiffies, timeout)) {
|
||||||
pr_err("%s: Card stuck in programming state! %s\n",
|
pr_err("%s: Card stuck in programming state! %s\n",
|
||||||
mmc_hostname(card->host), __func__);
|
mmc_hostname(host), __func__);
|
||||||
return -ETIMEDOUT;
|
return -ETIMEDOUT;
|
||||||
}
|
}
|
||||||
} while (R1_CURRENT_STATE(status) == R1_STATE_PRG);
|
} while (R1_CURRENT_STATE(status) == R1_STATE_PRG);
|
||||||
|
|
||||||
if (mmc_host_is_spi(card->host)) {
|
if (mmc_host_is_spi(host)) {
|
||||||
if (status & R1_SPI_ILLEGAL_COMMAND)
|
if (status & R1_SPI_ILLEGAL_COMMAND)
|
||||||
return -EBADMSG;
|
return -EBADMSG;
|
||||||
} else {
|
} else {
|
||||||
if (status & 0xFDFFA000)
|
if (status & 0xFDFFA000)
|
||||||
pr_warning("%s: unexpected status %#x after "
|
pr_warn("%s: unexpected status %#x after switch\n",
|
||||||
"switch", mmc_hostname(card->host), status);
|
mmc_hostname(host), status);
|
||||||
if (status & R1_SWITCH_ERROR)
|
if (status & R1_SWITCH_ERROR)
|
||||||
return -EBADMSG;
|
return -EBADMSG;
|
||||||
}
|
}
|
||||||
@ -501,7 +518,8 @@ EXPORT_SYMBOL_GPL(__mmc_switch);
|
|||||||
int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
|
int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
|
||||||
unsigned int timeout_ms)
|
unsigned int timeout_ms)
|
||||||
{
|
{
|
||||||
return __mmc_switch(card, set, index, value, timeout_ms, true, true);
|
return __mmc_switch(card, set, index, value, timeout_ms, true, true,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(mmc_switch);
|
EXPORT_SYMBOL_GPL(mmc_switch);
|
||||||
|
|
||||||
|
@ -1207,16 +1207,6 @@ static int mmc_sd_power_restore(struct mmc_host *host)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const struct mmc_bus_ops mmc_sd_ops = {
|
static const struct mmc_bus_ops mmc_sd_ops = {
|
||||||
.remove = mmc_sd_remove,
|
|
||||||
.detect = mmc_sd_detect,
|
|
||||||
.suspend = NULL,
|
|
||||||
.resume = NULL,
|
|
||||||
.power_restore = mmc_sd_power_restore,
|
|
||||||
.alive = mmc_sd_alive,
|
|
||||||
.shutdown = mmc_sd_suspend,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct mmc_bus_ops mmc_sd_ops_unsafe = {
|
|
||||||
.remove = mmc_sd_remove,
|
.remove = mmc_sd_remove,
|
||||||
.detect = mmc_sd_detect,
|
.detect = mmc_sd_detect,
|
||||||
.runtime_suspend = mmc_sd_runtime_suspend,
|
.runtime_suspend = mmc_sd_runtime_suspend,
|
||||||
@ -1228,17 +1218,6 @@ static const struct mmc_bus_ops mmc_sd_ops_unsafe = {
|
|||||||
.shutdown = mmc_sd_suspend,
|
.shutdown = mmc_sd_suspend,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void mmc_sd_attach_bus_ops(struct mmc_host *host)
|
|
||||||
{
|
|
||||||
const struct mmc_bus_ops *bus_ops;
|
|
||||||
|
|
||||||
if (!mmc_card_is_removable(host))
|
|
||||||
bus_ops = &mmc_sd_ops_unsafe;
|
|
||||||
else
|
|
||||||
bus_ops = &mmc_sd_ops;
|
|
||||||
mmc_attach_bus(host, bus_ops);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Starting point for SD card init.
|
* Starting point for SD card init.
|
||||||
*/
|
*/
|
||||||
@ -1254,7 +1233,7 @@ int mmc_attach_sd(struct mmc_host *host)
|
|||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
mmc_sd_attach_bus_ops(host);
|
mmc_attach_bus(host, &mmc_sd_ops);
|
||||||
if (host->ocr_avail_sd)
|
if (host->ocr_avail_sd)
|
||||||
host->ocr_avail = host->ocr_avail_sd;
|
host->ocr_avail = host->ocr_avail_sd;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/gpio/consumer.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
#include <linux/mmc/host.h>
|
#include <linux/mmc/host.h>
|
||||||
@ -18,8 +19,10 @@
|
|||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
|
||||||
struct mmc_gpio {
|
struct mmc_gpio {
|
||||||
int ro_gpio;
|
struct gpio_desc *ro_gpio;
|
||||||
int cd_gpio;
|
struct gpio_desc *cd_gpio;
|
||||||
|
bool override_ro_active_level;
|
||||||
|
bool override_cd_active_level;
|
||||||
char *ro_label;
|
char *ro_label;
|
||||||
char cd_label[0];
|
char cd_label[0];
|
||||||
};
|
};
|
||||||
@ -57,8 +60,6 @@ static int mmc_gpio_alloc(struct mmc_host *host)
|
|||||||
ctx->ro_label = ctx->cd_label + len;
|
ctx->ro_label = ctx->cd_label + len;
|
||||||
snprintf(ctx->cd_label, len, "%s cd", dev_name(host->parent));
|
snprintf(ctx->cd_label, len, "%s cd", dev_name(host->parent));
|
||||||
snprintf(ctx->ro_label, len, "%s ro", dev_name(host->parent));
|
snprintf(ctx->ro_label, len, "%s ro", dev_name(host->parent));
|
||||||
ctx->cd_gpio = -EINVAL;
|
|
||||||
ctx->ro_gpio = -EINVAL;
|
|
||||||
host->slot.handler_priv = ctx;
|
host->slot.handler_priv = ctx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,11 +73,14 @@ int mmc_gpio_get_ro(struct mmc_host *host)
|
|||||||
{
|
{
|
||||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||||
|
|
||||||
if (!ctx || !gpio_is_valid(ctx->ro_gpio))
|
if (!ctx || !ctx->ro_gpio)
|
||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
|
|
||||||
return !gpio_get_value_cansleep(ctx->ro_gpio) ^
|
if (ctx->override_ro_active_level)
|
||||||
!!(host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH);
|
return !gpiod_get_raw_value_cansleep(ctx->ro_gpio) ^
|
||||||
|
!!(host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH);
|
||||||
|
|
||||||
|
return gpiod_get_value_cansleep(ctx->ro_gpio);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(mmc_gpio_get_ro);
|
EXPORT_SYMBOL(mmc_gpio_get_ro);
|
||||||
|
|
||||||
@ -84,11 +88,14 @@ int mmc_gpio_get_cd(struct mmc_host *host)
|
|||||||
{
|
{
|
||||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||||
|
|
||||||
if (!ctx || !gpio_is_valid(ctx->cd_gpio))
|
if (!ctx || !ctx->cd_gpio)
|
||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
|
|
||||||
return !gpio_get_value_cansleep(ctx->cd_gpio) ^
|
if (ctx->override_cd_active_level)
|
||||||
!!(host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH);
|
return !gpiod_get_raw_value_cansleep(ctx->cd_gpio) ^
|
||||||
|
!!(host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH);
|
||||||
|
|
||||||
|
return gpiod_get_value_cansleep(ctx->cd_gpio);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(mmc_gpio_get_cd);
|
EXPORT_SYMBOL(mmc_gpio_get_cd);
|
||||||
|
|
||||||
@ -125,12 +132,47 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
ctx->ro_gpio = gpio;
|
ctx->override_ro_active_level = true;
|
||||||
|
ctx->ro_gpio = gpio_to_desc(gpio);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(mmc_gpio_request_ro);
|
EXPORT_SYMBOL(mmc_gpio_request_ro);
|
||||||
|
|
||||||
|
void mmc_gpiod_request_cd_irq(struct mmc_host *host)
|
||||||
|
{
|
||||||
|
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||||
|
int ret, irq;
|
||||||
|
|
||||||
|
if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio)
|
||||||
|
return;
|
||||||
|
|
||||||
|
irq = gpiod_to_irq(ctx->cd_gpio);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Even if gpiod_to_irq() returns a valid IRQ number, the platform might
|
||||||
|
* still prefer to poll, e.g., because that IRQ number is already used
|
||||||
|
* by another unit and cannot be shared.
|
||||||
|
*/
|
||||||
|
if (irq >= 0 && host->caps & MMC_CAP_NEEDS_POLL)
|
||||||
|
irq = -EINVAL;
|
||||||
|
|
||||||
|
if (irq >= 0) {
|
||||||
|
ret = devm_request_threaded_irq(&host->class_dev, irq,
|
||||||
|
NULL, mmc_gpio_cd_irqt,
|
||||||
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||||
|
ctx->cd_label, host);
|
||||||
|
if (ret < 0)
|
||||||
|
irq = ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
host->slot.cd_irq = irq;
|
||||||
|
|
||||||
|
if (irq < 0)
|
||||||
|
host->caps |= MMC_CAP_NEEDS_POLL;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(mmc_gpiod_request_cd_irq);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mmc_gpio_request_cd - request a gpio for card-detection
|
* mmc_gpio_request_cd - request a gpio for card-detection
|
||||||
* @host: mmc host
|
* @host: mmc host
|
||||||
@ -154,7 +196,6 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio,
|
|||||||
unsigned int debounce)
|
unsigned int debounce)
|
||||||
{
|
{
|
||||||
struct mmc_gpio *ctx;
|
struct mmc_gpio *ctx;
|
||||||
int irq = gpio_to_irq(gpio);
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = mmc_gpio_alloc(host);
|
ret = mmc_gpio_alloc(host);
|
||||||
@ -179,29 +220,10 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
ctx->override_cd_active_level = true;
|
||||||
* Even if gpio_to_irq() returns a valid IRQ number, the platform might
|
ctx->cd_gpio = gpio_to_desc(gpio);
|
||||||
* still prefer to poll, e.g., because that IRQ number is already used
|
|
||||||
* by another unit and cannot be shared.
|
|
||||||
*/
|
|
||||||
if (irq >= 0 && host->caps & MMC_CAP_NEEDS_POLL)
|
|
||||||
irq = -EINVAL;
|
|
||||||
|
|
||||||
if (irq >= 0) {
|
mmc_gpiod_request_cd_irq(host);
|
||||||
ret = devm_request_threaded_irq(&host->class_dev, irq,
|
|
||||||
NULL, mmc_gpio_cd_irqt,
|
|
||||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
||||||
ctx->cd_label, host);
|
|
||||||
if (ret < 0)
|
|
||||||
irq = ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
host->slot.cd_irq = irq;
|
|
||||||
|
|
||||||
if (irq < 0)
|
|
||||||
host->caps |= MMC_CAP_NEEDS_POLL;
|
|
||||||
|
|
||||||
ctx->cd_gpio = gpio;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -219,11 +241,11 @@ void mmc_gpio_free_ro(struct mmc_host *host)
|
|||||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||||
int gpio;
|
int gpio;
|
||||||
|
|
||||||
if (!ctx || !gpio_is_valid(ctx->ro_gpio))
|
if (!ctx || !ctx->ro_gpio)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
gpio = ctx->ro_gpio;
|
gpio = desc_to_gpio(ctx->ro_gpio);
|
||||||
ctx->ro_gpio = -EINVAL;
|
ctx->ro_gpio = NULL;
|
||||||
|
|
||||||
devm_gpio_free(&host->class_dev, gpio);
|
devm_gpio_free(&host->class_dev, gpio);
|
||||||
}
|
}
|
||||||
@ -241,7 +263,7 @@ void mmc_gpio_free_cd(struct mmc_host *host)
|
|||||||
struct mmc_gpio *ctx = host->slot.handler_priv;
|
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||||
int gpio;
|
int gpio;
|
||||||
|
|
||||||
if (!ctx || !gpio_is_valid(ctx->cd_gpio))
|
if (!ctx || !ctx->cd_gpio)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (host->slot.cd_irq >= 0) {
|
if (host->slot.cd_irq >= 0) {
|
||||||
@ -249,9 +271,87 @@ void mmc_gpio_free_cd(struct mmc_host *host)
|
|||||||
host->slot.cd_irq = -EINVAL;
|
host->slot.cd_irq = -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
gpio = ctx->cd_gpio;
|
gpio = desc_to_gpio(ctx->cd_gpio);
|
||||||
ctx->cd_gpio = -EINVAL;
|
ctx->cd_gpio = NULL;
|
||||||
|
|
||||||
devm_gpio_free(&host->class_dev, gpio);
|
devm_gpio_free(&host->class_dev, gpio);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(mmc_gpio_free_cd);
|
EXPORT_SYMBOL(mmc_gpio_free_cd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mmc_gpiod_request_cd - request a gpio descriptor for card-detection
|
||||||
|
* @host: mmc host
|
||||||
|
* @con_id: function within the GPIO consumer
|
||||||
|
* @idx: index of the GPIO to obtain in the consumer
|
||||||
|
* @override_active_level: ignore %GPIO_ACTIVE_LOW flag
|
||||||
|
* @debounce: debounce time in microseconds
|
||||||
|
*
|
||||||
|
* Use this function in place of mmc_gpio_request_cd() to use the GPIO
|
||||||
|
* descriptor API. Note that it is paired with mmc_gpiod_free_cd() not
|
||||||
|
* mmc_gpio_free_cd(). Note also that it must be called prior to mmc_add_host()
|
||||||
|
* otherwise the caller must also call mmc_gpiod_request_cd_irq().
|
||||||
|
*
|
||||||
|
* Returns zero on success, else an error.
|
||||||
|
*/
|
||||||
|
int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,
|
||||||
|
unsigned int idx, bool override_active_level,
|
||||||
|
unsigned int debounce)
|
||||||
|
{
|
||||||
|
struct mmc_gpio *ctx;
|
||||||
|
struct gpio_desc *desc;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = mmc_gpio_alloc(host);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ctx = host->slot.handler_priv;
|
||||||
|
|
||||||
|
if (!con_id)
|
||||||
|
con_id = ctx->cd_label;
|
||||||
|
|
||||||
|
desc = devm_gpiod_get_index(host->parent, con_id, idx);
|
||||||
|
if (IS_ERR(desc))
|
||||||
|
return PTR_ERR(desc);
|
||||||
|
|
||||||
|
ret = gpiod_direction_input(desc);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (debounce) {
|
||||||
|
ret = gpiod_set_debounce(desc, debounce);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->override_cd_active_level = override_active_level;
|
||||||
|
ctx->cd_gpio = desc;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(mmc_gpiod_request_cd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mmc_gpiod_free_cd - free the card-detection gpio descriptor
|
||||||
|
* @host: mmc host
|
||||||
|
*
|
||||||
|
* It's provided only for cases that client drivers need to manually free
|
||||||
|
* up the card-detection gpio requested by mmc_gpiod_request_cd().
|
||||||
|
*/
|
||||||
|
void mmc_gpiod_free_cd(struct mmc_host *host)
|
||||||
|
{
|
||||||
|
struct mmc_gpio *ctx = host->slot.handler_priv;
|
||||||
|
|
||||||
|
if (!ctx || !ctx->cd_gpio)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (host->slot.cd_irq >= 0) {
|
||||||
|
devm_free_irq(&host->class_dev, host->slot.cd_irq, host);
|
||||||
|
host->slot.cd_irq = -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
devm_gpiod_put(&host->class_dev, ctx->cd_gpio);
|
||||||
|
|
||||||
|
ctx->cd_gpio = NULL;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(mmc_gpiod_free_cd);
|
||||||
|
@ -263,7 +263,7 @@ config MMC_SDHCI_S3C_DMA
|
|||||||
|
|
||||||
config MMC_SDHCI_BCM_KONA
|
config MMC_SDHCI_BCM_KONA
|
||||||
tristate "SDHCI support on Broadcom KONA platform"
|
tristate "SDHCI support on Broadcom KONA platform"
|
||||||
depends on ARCH_BCM
|
depends on ARCH_BCM_MOBILE
|
||||||
select MMC_SDHCI_PLTFM
|
select MMC_SDHCI_PLTFM
|
||||||
help
|
help
|
||||||
This selects the Broadcom Kona Secure Digital Host Controller
|
This selects the Broadcom Kona Secure Digital Host Controller
|
||||||
@ -334,6 +334,19 @@ config MMC_ATMELMCI
|
|||||||
|
|
||||||
If unsure, say N.
|
If unsure, say N.
|
||||||
|
|
||||||
|
config MMC_SDHCI_MSM
|
||||||
|
tristate "Qualcomm SDHCI Controller Support"
|
||||||
|
depends on ARCH_QCOM
|
||||||
|
depends on MMC_SDHCI_PLTFM
|
||||||
|
help
|
||||||
|
This selects the Secure Digital Host Controller Interface (SDHCI)
|
||||||
|
support present in Qualcomm SOCs. The controller supports
|
||||||
|
SD/MMC/SDIO devices.
|
||||||
|
|
||||||
|
If you have a controller with this interface, say Y or M here.
|
||||||
|
|
||||||
|
If unsure, say N.
|
||||||
|
|
||||||
config MMC_MSM
|
config MMC_MSM
|
||||||
tristate "Qualcomm SDCC Controller Support"
|
tristate "Qualcomm SDCC Controller Support"
|
||||||
depends on MMC && (ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50)
|
depends on MMC && (ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50)
|
||||||
@ -580,14 +593,6 @@ config MMC_DW_EXYNOS
|
|||||||
Synopsys DesignWare Memory Card Interface driver. Select this option
|
Synopsys DesignWare Memory Card Interface driver. Select this option
|
||||||
for platforms based on Exynos4 and Exynos5 SoC's.
|
for platforms based on Exynos4 and Exynos5 SoC's.
|
||||||
|
|
||||||
config MMC_DW_SOCFPGA
|
|
||||||
tristate "SOCFPGA specific extensions for Synopsys DW Memory Card Interface"
|
|
||||||
depends on MMC_DW && MFD_SYSCON
|
|
||||||
select MMC_DW_PLTFM
|
|
||||||
help
|
|
||||||
This selects support for Altera SoCFPGA specific extensions to the
|
|
||||||
Synopsys DesignWare Memory Card Interface driver.
|
|
||||||
|
|
||||||
config MMC_DW_K3
|
config MMC_DW_K3
|
||||||
tristate "K3 specific extensions for Synopsys DW Memory Card Interface"
|
tristate "K3 specific extensions for Synopsys DW Memory Card Interface"
|
||||||
depends on MMC_DW
|
depends on MMC_DW
|
||||||
|
@ -43,7 +43,6 @@ obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o
|
|||||||
obj-$(CONFIG_MMC_DW) += dw_mmc.o
|
obj-$(CONFIG_MMC_DW) += dw_mmc.o
|
||||||
obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o
|
obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o
|
||||||
obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
|
obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
|
||||||
obj-$(CONFIG_MMC_DW_SOCFPGA) += dw_mmc-socfpga.o
|
|
||||||
obj-$(CONFIG_MMC_DW_K3) += dw_mmc-k3.o
|
obj-$(CONFIG_MMC_DW_K3) += dw_mmc-k3.o
|
||||||
obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o
|
obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o
|
||||||
obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
|
obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
|
||||||
@ -64,6 +63,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o
|
|||||||
obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o
|
obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o
|
||||||
obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o
|
obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o
|
||||||
obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o
|
obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o
|
||||||
|
obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o
|
||||||
|
|
||||||
ifeq ($(CONFIG_CB710_DEBUG),y)
|
ifeq ($(CONFIG_CB710_DEBUG),y)
|
||||||
CFLAGS-cb710-mmc += -DDEBUG
|
CFLAGS-cb710-mmc += -DDEBUG
|
||||||
|
@ -1192,7 +1192,7 @@ static struct davinci_mmc_config
|
|||||||
struct device_node *np;
|
struct device_node *np;
|
||||||
struct davinci_mmc_config *pdata = pdev->dev.platform_data;
|
struct davinci_mmc_config *pdata = pdev->dev.platform_data;
|
||||||
const struct of_device_id *match =
|
const struct of_device_id *match =
|
||||||
of_match_device(of_match_ptr(davinci_mmc_dt_ids), &pdev->dev);
|
of_match_device(davinci_mmc_dt_ids, &pdev->dev);
|
||||||
u32 data;
|
u32 data;
|
||||||
|
|
||||||
np = pdev->dev.of_node;
|
np = pdev->dev.of_node;
|
||||||
@ -1468,7 +1468,7 @@ static struct platform_driver davinci_mmcsd_driver = {
|
|||||||
.name = "davinci_mmc",
|
.name = "davinci_mmc",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.pm = davinci_mmcsd_pm_ops,
|
.pm = davinci_mmcsd_pm_ops,
|
||||||
.of_match_table = of_match_ptr(davinci_mmc_dt_ids),
|
.of_match_table = davinci_mmc_dt_ids,
|
||||||
},
|
},
|
||||||
.remove = __exit_p(davinci_mmcsd_remove),
|
.remove = __exit_p(davinci_mmcsd_remove),
|
||||||
.id_table = davinci_mmc_devtype,
|
.id_table = davinci_mmc_devtype,
|
||||||
|
@ -50,6 +50,7 @@ static int dw_mci_k3_probe(struct platform_device *pdev)
|
|||||||
return dw_mci_pltfm_register(pdev, drv_data);
|
return dw_mci_pltfm_register(pdev, drv_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_PM_SLEEP
|
||||||
static int dw_mci_k3_suspend(struct device *dev)
|
static int dw_mci_k3_suspend(struct device *dev)
|
||||||
{
|
{
|
||||||
struct dw_mci *host = dev_get_drvdata(dev);
|
struct dw_mci *host = dev_get_drvdata(dev);
|
||||||
@ -75,6 +76,7 @@ static int dw_mci_k3_resume(struct device *dev)
|
|||||||
|
|
||||||
return dw_mci_resume(host);
|
return dw_mci_resume(host);
|
||||||
}
|
}
|
||||||
|
#endif /* CONFIG_PM_SLEEP */
|
||||||
|
|
||||||
static SIMPLE_DEV_PM_OPS(dw_mci_k3_pmops, dw_mci_k3_suspend, dw_mci_k3_resume);
|
static SIMPLE_DEV_PM_OPS(dw_mci_k3_pmops, dw_mci_k3_suspend, dw_mci_k3_resume);
|
||||||
|
|
||||||
|
@ -25,13 +25,17 @@
|
|||||||
#include "dw_mmc.h"
|
#include "dw_mmc.h"
|
||||||
#include "dw_mmc-pltfm.h"
|
#include "dw_mmc-pltfm.h"
|
||||||
|
|
||||||
static void dw_mci_rockchip_prepare_command(struct dw_mci *host, u32 *cmdr)
|
static void dw_mci_pltfm_prepare_command(struct dw_mci *host, u32 *cmdr)
|
||||||
{
|
{
|
||||||
*cmdr |= SDMMC_CMD_USE_HOLD_REG;
|
*cmdr |= SDMMC_CMD_USE_HOLD_REG;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct dw_mci_drv_data rockchip_drv_data = {
|
static const struct dw_mci_drv_data rockchip_drv_data = {
|
||||||
.prepare_command = dw_mci_rockchip_prepare_command,
|
.prepare_command = dw_mci_pltfm_prepare_command,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct dw_mci_drv_data socfpga_drv_data = {
|
||||||
|
.prepare_command = dw_mci_pltfm_prepare_command,
|
||||||
};
|
};
|
||||||
|
|
||||||
int dw_mci_pltfm_register(struct platform_device *pdev,
|
int dw_mci_pltfm_register(struct platform_device *pdev,
|
||||||
@ -92,6 +96,8 @@ static const struct of_device_id dw_mci_pltfm_match[] = {
|
|||||||
{ .compatible = "snps,dw-mshc", },
|
{ .compatible = "snps,dw-mshc", },
|
||||||
{ .compatible = "rockchip,rk2928-dw-mshc",
|
{ .compatible = "rockchip,rk2928-dw-mshc",
|
||||||
.data = &rockchip_drv_data },
|
.data = &rockchip_drv_data },
|
||||||
|
{ .compatible = "altr,socfpga-dw-mshc",
|
||||||
|
.data = &socfpga_drv_data },
|
||||||
{},
|
{},
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(of, dw_mci_pltfm_match);
|
MODULE_DEVICE_TABLE(of, dw_mci_pltfm_match);
|
||||||
@ -123,7 +129,7 @@ static struct platform_driver dw_mci_pltfm_driver = {
|
|||||||
.remove = dw_mci_pltfm_remove,
|
.remove = dw_mci_pltfm_remove,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "dw_mmc",
|
.name = "dw_mmc",
|
||||||
.of_match_table = of_match_ptr(dw_mci_pltfm_match),
|
.of_match_table = dw_mci_pltfm_match,
|
||||||
.pm = &dw_mci_pltfm_pmops,
|
.pm = &dw_mci_pltfm_pmops,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
/*
|
|
||||||
* Altera SoCFPGA Specific Extensions for Synopsys DW Multimedia Card Interface
|
|
||||||
* driver
|
|
||||||
*
|
|
||||||
* Copyright (C) 2012, Samsung Electronics Co., Ltd.
|
|
||||||
* Copyright (C) 2013 Altera Corporation
|
|
||||||
*
|
|
||||||
* 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, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Taken from dw_mmc-exynos.c
|
|
||||||
*/
|
|
||||||
#include <linux/clk.h>
|
|
||||||
#include <linux/mfd/syscon.h>
|
|
||||||
#include <linux/mmc/host.h>
|
|
||||||
#include <linux/mmc/dw_mmc.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/of.h>
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/regmap.h>
|
|
||||||
|
|
||||||
#include "dw_mmc.h"
|
|
||||||
#include "dw_mmc-pltfm.h"
|
|
||||||
|
|
||||||
#define SYSMGR_SDMMCGRP_CTRL_OFFSET 0x108
|
|
||||||
#define DRV_CLK_PHASE_SHIFT_SEL_MASK 0x7
|
|
||||||
#define SYSMGR_SDMMC_CTRL_SET(smplsel, drvsel) \
|
|
||||||
((((smplsel) & 0x7) << 3) | (((drvsel) & 0x7) << 0))
|
|
||||||
|
|
||||||
/* SOCFPGA implementation specific driver private data */
|
|
||||||
struct dw_mci_socfpga_priv_data {
|
|
||||||
u8 ciu_div; /* card interface unit divisor */
|
|
||||||
u32 hs_timing; /* bitmask for CIU clock phase shift */
|
|
||||||
struct regmap *sysreg; /* regmap for system manager register */
|
|
||||||
};
|
|
||||||
|
|
||||||
static int dw_mci_socfpga_priv_init(struct dw_mci *host)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dw_mci_socfpga_setup_clock(struct dw_mci *host)
|
|
||||||
{
|
|
||||||
struct dw_mci_socfpga_priv_data *priv = host->priv;
|
|
||||||
|
|
||||||
clk_disable_unprepare(host->ciu_clk);
|
|
||||||
regmap_write(priv->sysreg, SYSMGR_SDMMCGRP_CTRL_OFFSET,
|
|
||||||
priv->hs_timing);
|
|
||||||
clk_prepare_enable(host->ciu_clk);
|
|
||||||
|
|
||||||
host->bus_hz /= (priv->ciu_div + 1);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dw_mci_socfpga_prepare_command(struct dw_mci *host, u32 *cmdr)
|
|
||||||
{
|
|
||||||
struct dw_mci_socfpga_priv_data *priv = host->priv;
|
|
||||||
|
|
||||||
if (priv->hs_timing & DRV_CLK_PHASE_SHIFT_SEL_MASK)
|
|
||||||
*cmdr |= SDMMC_CMD_USE_HOLD_REG;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dw_mci_socfpga_parse_dt(struct dw_mci *host)
|
|
||||||
{
|
|
||||||
struct dw_mci_socfpga_priv_data *priv;
|
|
||||||
struct device_node *np = host->dev->of_node;
|
|
||||||
u32 timing[2];
|
|
||||||
u32 div = 0;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
|
|
||||||
if (!priv) {
|
|
||||||
dev_err(host->dev, "mem alloc failed for private data\n");
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
priv->sysreg = syscon_regmap_lookup_by_compatible("altr,sys-mgr");
|
|
||||||
if (IS_ERR(priv->sysreg)) {
|
|
||||||
dev_err(host->dev, "regmap for altr,sys-mgr lookup failed.\n");
|
|
||||||
return PTR_ERR(priv->sysreg);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = of_property_read_u32(np, "altr,dw-mshc-ciu-div", &div);
|
|
||||||
if (ret)
|
|
||||||
dev_info(host->dev, "No dw-mshc-ciu-div specified, assuming 1");
|
|
||||||
priv->ciu_div = div;
|
|
||||||
|
|
||||||
ret = of_property_read_u32_array(np,
|
|
||||||
"altr,dw-mshc-sdr-timing", timing, 2);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
priv->hs_timing = SYSMGR_SDMMC_CTRL_SET(timing[0], timing[1]);
|
|
||||||
host->priv = priv;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct dw_mci_drv_data socfpga_drv_data = {
|
|
||||||
.init = dw_mci_socfpga_priv_init,
|
|
||||||
.setup_clock = dw_mci_socfpga_setup_clock,
|
|
||||||
.prepare_command = dw_mci_socfpga_prepare_command,
|
|
||||||
.parse_dt = dw_mci_socfpga_parse_dt,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct of_device_id dw_mci_socfpga_match[] = {
|
|
||||||
{ .compatible = "altr,socfpga-dw-mshc",
|
|
||||||
.data = &socfpga_drv_data, },
|
|
||||||
{},
|
|
||||||
};
|
|
||||||
MODULE_DEVICE_TABLE(of, dw_mci_socfpga_match);
|
|
||||||
|
|
||||||
static int dw_mci_socfpga_probe(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
const struct dw_mci_drv_data *drv_data;
|
|
||||||
const struct of_device_id *match;
|
|
||||||
|
|
||||||
match = of_match_node(dw_mci_socfpga_match, pdev->dev.of_node);
|
|
||||||
drv_data = match->data;
|
|
||||||
return dw_mci_pltfm_register(pdev, drv_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct platform_driver dw_mci_socfpga_pltfm_driver = {
|
|
||||||
.probe = dw_mci_socfpga_probe,
|
|
||||||
.remove = __exit_p(dw_mci_pltfm_remove),
|
|
||||||
.driver = {
|
|
||||||
.name = "dwmmc_socfpga",
|
|
||||||
.of_match_table = dw_mci_socfpga_match,
|
|
||||||
.pm = &dw_mci_pltfm_pmops,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module_platform_driver(dw_mci_socfpga_pltfm_driver);
|
|
||||||
|
|
||||||
MODULE_DESCRIPTION("Altera SOCFPGA Specific DW-MSHC Driver Extension");
|
|
||||||
MODULE_LICENSE("GPL v2");
|
|
||||||
MODULE_ALIAS("platform:dwmmc-socfpga");
|
|
@ -1345,7 +1345,7 @@ static void dw_mci_tasklet_func(unsigned long priv)
|
|||||||
|
|
||||||
if (!err) {
|
if (!err) {
|
||||||
if (!data->stop || mrq->sbc) {
|
if (!data->stop || mrq->sbc) {
|
||||||
if (mrq->sbc)
|
if (mrq->sbc && data->stop)
|
||||||
data->stop->error = 0;
|
data->stop->error = 0;
|
||||||
dw_mci_request_end(host, mrq);
|
dw_mci_request_end(host, mrq);
|
||||||
goto unlock;
|
goto unlock;
|
||||||
|
@ -185,7 +185,7 @@
|
|||||||
|
|
||||||
extern int dw_mci_probe(struct dw_mci *host);
|
extern int dw_mci_probe(struct dw_mci *host);
|
||||||
extern void dw_mci_remove(struct dw_mci *host);
|
extern void dw_mci_remove(struct dw_mci *host);
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM_SLEEP
|
||||||
extern int dw_mci_suspend(struct dw_mci *host);
|
extern int dw_mci_suspend(struct dw_mci *host);
|
||||||
extern int dw_mci_resume(struct dw_mci *host);
|
extern int dw_mci_resume(struct dw_mci *host);
|
||||||
#endif
|
#endif
|
||||||
@ -244,6 +244,7 @@ struct dw_mci_tuning_data {
|
|||||||
* @prepare_command: handle CMD register extensions.
|
* @prepare_command: handle CMD register extensions.
|
||||||
* @set_ios: handle bus specific extensions.
|
* @set_ios: handle bus specific extensions.
|
||||||
* @parse_dt: parse implementation specific device tree properties.
|
* @parse_dt: parse implementation specific device tree properties.
|
||||||
|
* @execute_tuning: implementation specific tuning procedure.
|
||||||
*
|
*
|
||||||
* Provide controller implementation specific extensions. The usage of this
|
* Provide controller implementation specific extensions. The usage of this
|
||||||
* data structure is fully optional and usage of each member in this structure
|
* data structure is fully optional and usage of each member in this structure
|
||||||
|
@ -921,6 +921,29 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd,
|
|||||||
{
|
{
|
||||||
void __iomem *base = host->base;
|
void __iomem *base = host->base;
|
||||||
bool sbc = (cmd == host->mrq->sbc);
|
bool sbc = (cmd == host->mrq->sbc);
|
||||||
|
bool busy_resp = host->variant->busy_detect &&
|
||||||
|
(cmd->flags & MMC_RSP_BUSY);
|
||||||
|
|
||||||
|
/* Check if we need to wait for busy completion. */
|
||||||
|
if (host->busy_status && (status & MCI_ST_CARDBUSY))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Enable busy completion if needed and supported. */
|
||||||
|
if (!host->busy_status && busy_resp &&
|
||||||
|
!(status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT)) &&
|
||||||
|
(readl(base + MMCISTATUS) & MCI_ST_CARDBUSY)) {
|
||||||
|
writel(readl(base + MMCIMASK0) | MCI_ST_BUSYEND,
|
||||||
|
base + MMCIMASK0);
|
||||||
|
host->busy_status = status & (MCI_CMDSENT|MCI_CMDRESPEND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* At busy completion, mask the IRQ and complete the request. */
|
||||||
|
if (host->busy_status) {
|
||||||
|
writel(readl(base + MMCIMASK0) & ~MCI_ST_BUSYEND,
|
||||||
|
base + MMCIMASK0);
|
||||||
|
host->busy_status = 0;
|
||||||
|
}
|
||||||
|
|
||||||
host->cmd = NULL;
|
host->cmd = NULL;
|
||||||
|
|
||||||
@ -1139,20 +1162,30 @@ static irqreturn_t mmci_irq(int irq, void *dev_id)
|
|||||||
status &= ~MCI_IRQ1MASK;
|
status &= ~MCI_IRQ1MASK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We intentionally clear the MCI_ST_CARDBUSY IRQ here (if it's
|
||||||
|
* enabled) since the HW seems to be triggering the IRQ on both
|
||||||
|
* edges while monitoring DAT0 for busy completion.
|
||||||
|
*/
|
||||||
status &= readl(host->base + MMCIMASK0);
|
status &= readl(host->base + MMCIMASK0);
|
||||||
writel(status, host->base + MMCICLEAR);
|
writel(status, host->base + MMCICLEAR);
|
||||||
|
|
||||||
dev_dbg(mmc_dev(host->mmc), "irq0 (data+cmd) %08x\n", status);
|
dev_dbg(mmc_dev(host->mmc), "irq0 (data+cmd) %08x\n", status);
|
||||||
|
|
||||||
|
cmd = host->cmd;
|
||||||
|
if ((status|host->busy_status) & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT|
|
||||||
|
MCI_CMDSENT|MCI_CMDRESPEND) && cmd)
|
||||||
|
mmci_cmd_irq(host, cmd, status);
|
||||||
|
|
||||||
data = host->data;
|
data = host->data;
|
||||||
if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_STARTBITERR|
|
if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_STARTBITERR|
|
||||||
MCI_TXUNDERRUN|MCI_RXOVERRUN|MCI_DATAEND|
|
MCI_TXUNDERRUN|MCI_RXOVERRUN|MCI_DATAEND|
|
||||||
MCI_DATABLOCKEND) && data)
|
MCI_DATABLOCKEND) && data)
|
||||||
mmci_data_irq(host, data, status);
|
mmci_data_irq(host, data, status);
|
||||||
|
|
||||||
cmd = host->cmd;
|
/* Don't poll for busy completion in irq context. */
|
||||||
if (status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT|MCI_CMDSENT|MCI_CMDRESPEND) && cmd)
|
if (host->busy_status)
|
||||||
mmci_cmd_irq(host, cmd, status);
|
status &= ~MCI_ST_CARDBUSY;
|
||||||
|
|
||||||
ret = 1;
|
ret = 1;
|
||||||
} while (status);
|
} while (status);
|
||||||
@ -1503,12 +1536,6 @@ static int mmci_probe(struct amba_device *dev,
|
|||||||
goto clk_disable;
|
goto clk_disable;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variant->busy_detect) {
|
|
||||||
mmci_ops.card_busy = mmci_card_busy;
|
|
||||||
mmci_write_datactrlreg(host, MCI_ST_DPSM_BUSYMODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
mmc->ops = &mmci_ops;
|
|
||||||
/*
|
/*
|
||||||
* The ARM and ST versions of the block have slightly different
|
* The ARM and ST versions of the block have slightly different
|
||||||
* clock divider equations which means that the minimum divider
|
* clock divider equations which means that the minimum divider
|
||||||
@ -1542,6 +1569,15 @@ static int mmci_probe(struct amba_device *dev,
|
|||||||
mmc->caps = plat->capabilities;
|
mmc->caps = plat->capabilities;
|
||||||
mmc->caps2 = plat->capabilities2;
|
mmc->caps2 = plat->capabilities2;
|
||||||
|
|
||||||
|
if (variant->busy_detect) {
|
||||||
|
mmci_ops.card_busy = mmci_card_busy;
|
||||||
|
mmci_write_datactrlreg(host, MCI_ST_DPSM_BUSYMODE);
|
||||||
|
mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
|
||||||
|
mmc->max_busy_timeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mmc->ops = &mmci_ops;
|
||||||
|
|
||||||
/* We support these PM capabilities. */
|
/* We support these PM capabilities. */
|
||||||
mmc->pm_caps = MMC_PM_KEEP_POWER;
|
mmc->pm_caps = MMC_PM_KEEP_POWER;
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@
|
|||||||
/* Extended status bits for the ST Micro variants */
|
/* Extended status bits for the ST Micro variants */
|
||||||
#define MCI_ST_SDIOITMASK (1 << 22)
|
#define MCI_ST_SDIOITMASK (1 << 22)
|
||||||
#define MCI_ST_CEATAENDMASK (1 << 23)
|
#define MCI_ST_CEATAENDMASK (1 << 23)
|
||||||
|
#define MCI_ST_BUSYEND (1 << 24)
|
||||||
|
|
||||||
#define MMCIMASK1 0x040
|
#define MMCIMASK1 0x040
|
||||||
#define MMCIFIFOCNT 0x048
|
#define MMCIFIFOCNT 0x048
|
||||||
@ -187,6 +188,7 @@ struct mmci_host {
|
|||||||
u32 pwr_reg;
|
u32 pwr_reg;
|
||||||
u32 clk_reg;
|
u32 clk_reg;
|
||||||
u32 datactrl_reg;
|
u32 datactrl_reg;
|
||||||
|
u32 busy_status;
|
||||||
bool vqmmc_enabled;
|
bool vqmmc_enabled;
|
||||||
struct mmci_platform_data *plat;
|
struct mmci_platform_data *plat;
|
||||||
struct variant_data *variant;
|
struct variant_data *variant;
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include <linux/omap-dma.h>
|
#include <linux/omap-dma.h>
|
||||||
#include <linux/mmc/host.h>
|
#include <linux/mmc/host.h>
|
||||||
#include <linux/mmc/card.h>
|
#include <linux/mmc/card.h>
|
||||||
|
#include <linux/mmc/mmc.h>
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/scatterlist.h>
|
#include <linux/scatterlist.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
@ -130,7 +131,6 @@ struct mmc_omap_host {
|
|||||||
u32 dma_rx_burst;
|
u32 dma_rx_burst;
|
||||||
struct dma_chan *dma_tx;
|
struct dma_chan *dma_tx;
|
||||||
u32 dma_tx_burst;
|
u32 dma_tx_burst;
|
||||||
struct resource *mem_res;
|
|
||||||
void __iomem *virt_base;
|
void __iomem *virt_base;
|
||||||
unsigned int phys_base;
|
unsigned int phys_base;
|
||||||
int irq;
|
int irq;
|
||||||
@ -153,7 +153,6 @@ struct mmc_omap_host {
|
|||||||
u32 total_bytes_left;
|
u32 total_bytes_left;
|
||||||
|
|
||||||
unsigned features;
|
unsigned features;
|
||||||
unsigned use_dma:1;
|
|
||||||
unsigned brs_received:1, dma_done:1;
|
unsigned brs_received:1, dma_done:1;
|
||||||
unsigned dma_in_use:1;
|
unsigned dma_in_use:1;
|
||||||
spinlock_t dma_lock;
|
spinlock_t dma_lock;
|
||||||
@ -338,6 +337,7 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
|
|||||||
u32 cmdreg;
|
u32 cmdreg;
|
||||||
u32 resptype;
|
u32 resptype;
|
||||||
u32 cmdtype;
|
u32 cmdtype;
|
||||||
|
u16 irq_mask;
|
||||||
|
|
||||||
host->cmd = cmd;
|
host->cmd = cmd;
|
||||||
|
|
||||||
@ -390,12 +390,14 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
|
|||||||
OMAP_MMC_WRITE(host, CTO, 200);
|
OMAP_MMC_WRITE(host, CTO, 200);
|
||||||
OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff);
|
OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff);
|
||||||
OMAP_MMC_WRITE(host, ARGH, cmd->arg >> 16);
|
OMAP_MMC_WRITE(host, ARGH, cmd->arg >> 16);
|
||||||
OMAP_MMC_WRITE(host, IE,
|
irq_mask = OMAP_MMC_STAT_A_EMPTY | OMAP_MMC_STAT_A_FULL |
|
||||||
OMAP_MMC_STAT_A_EMPTY | OMAP_MMC_STAT_A_FULL |
|
OMAP_MMC_STAT_CMD_CRC | OMAP_MMC_STAT_CMD_TOUT |
|
||||||
OMAP_MMC_STAT_CMD_CRC | OMAP_MMC_STAT_CMD_TOUT |
|
OMAP_MMC_STAT_DATA_CRC | OMAP_MMC_STAT_DATA_TOUT |
|
||||||
OMAP_MMC_STAT_DATA_CRC | OMAP_MMC_STAT_DATA_TOUT |
|
OMAP_MMC_STAT_END_OF_CMD | OMAP_MMC_STAT_CARD_ERR |
|
||||||
OMAP_MMC_STAT_END_OF_CMD | OMAP_MMC_STAT_CARD_ERR |
|
OMAP_MMC_STAT_END_OF_DATA;
|
||||||
OMAP_MMC_STAT_END_OF_DATA);
|
if (cmd->opcode == MMC_ERASE)
|
||||||
|
irq_mask &= ~OMAP_MMC_STAT_DATA_TOUT;
|
||||||
|
OMAP_MMC_WRITE(host, IE, irq_mask);
|
||||||
OMAP_MMC_WRITE(host, CMD, cmdreg);
|
OMAP_MMC_WRITE(host, CMD, cmdreg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -945,7 +947,7 @@ static void
|
|||||||
mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
|
mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
|
||||||
{
|
{
|
||||||
struct mmc_data *data = req->data;
|
struct mmc_data *data = req->data;
|
||||||
int i, use_dma, block_size;
|
int i, use_dma = 1, block_size;
|
||||||
unsigned sg_len;
|
unsigned sg_len;
|
||||||
|
|
||||||
host->data = data;
|
host->data = data;
|
||||||
@ -970,13 +972,10 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
|
|||||||
sg_len = (data->blocks == 1) ? 1 : data->sg_len;
|
sg_len = (data->blocks == 1) ? 1 : data->sg_len;
|
||||||
|
|
||||||
/* Only do DMA for entire blocks */
|
/* Only do DMA for entire blocks */
|
||||||
use_dma = host->use_dma;
|
for (i = 0; i < sg_len; i++) {
|
||||||
if (use_dma) {
|
if ((data->sg[i].length % block_size) != 0) {
|
||||||
for (i = 0; i < sg_len; i++) {
|
use_dma = 0;
|
||||||
if ((data->sg[i].length % block_size) != 0) {
|
break;
|
||||||
use_dma = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1239,7 +1238,7 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id)
|
|||||||
|
|
||||||
mmc->caps = 0;
|
mmc->caps = 0;
|
||||||
if (host->pdata->slots[id].wires >= 4)
|
if (host->pdata->slots[id].wires >= 4)
|
||||||
mmc->caps |= MMC_CAP_4_BIT_DATA;
|
mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_ERASE;
|
||||||
|
|
||||||
mmc->ops = &mmc_omap_ops;
|
mmc->ops = &mmc_omap_ops;
|
||||||
mmc->f_min = 400000;
|
mmc->f_min = 400000;
|
||||||
@ -1262,6 +1261,13 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id)
|
|||||||
mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
|
mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
|
||||||
mmc->max_seg_size = mmc->max_req_size;
|
mmc->max_seg_size = mmc->max_req_size;
|
||||||
|
|
||||||
|
if (slot->pdata->get_cover_state != NULL) {
|
||||||
|
setup_timer(&slot->cover_timer, mmc_omap_cover_timer,
|
||||||
|
(unsigned long)slot);
|
||||||
|
tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler,
|
||||||
|
(unsigned long)slot);
|
||||||
|
}
|
||||||
|
|
||||||
r = mmc_add_host(mmc);
|
r = mmc_add_host(mmc);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto err_remove_host;
|
goto err_remove_host;
|
||||||
@ -1278,11 +1284,6 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id)
|
|||||||
&dev_attr_cover_switch);
|
&dev_attr_cover_switch);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto err_remove_slot_name;
|
goto err_remove_slot_name;
|
||||||
|
|
||||||
setup_timer(&slot->cover_timer, mmc_omap_cover_timer,
|
|
||||||
(unsigned long)slot);
|
|
||||||
tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler,
|
|
||||||
(unsigned long)slot);
|
|
||||||
tasklet_schedule(&slot->cover_tasklet);
|
tasklet_schedule(&slot->cover_tasklet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1333,21 +1334,19 @@ static int mmc_omap_probe(struct platform_device *pdev)
|
|||||||
return -EPROBE_DEFER;
|
return -EPROBE_DEFER;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
host = devm_kzalloc(&pdev->dev, sizeof(struct mmc_omap_host),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (host == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
irq = platform_get_irq(pdev, 0);
|
irq = platform_get_irq(pdev, 0);
|
||||||
if (res == NULL || irq < 0)
|
if (irq < 0)
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
|
|
||||||
res = request_mem_region(res->start, resource_size(res),
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
pdev->name);
|
host->virt_base = devm_ioremap_resource(&pdev->dev, res);
|
||||||
if (res == NULL)
|
if (IS_ERR(host->virt_base))
|
||||||
return -EBUSY;
|
return PTR_ERR(host->virt_base);
|
||||||
|
|
||||||
host = kzalloc(sizeof(struct mmc_omap_host), GFP_KERNEL);
|
|
||||||
if (host == NULL) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
goto err_free_mem_region;
|
|
||||||
}
|
|
||||||
|
|
||||||
INIT_WORK(&host->slot_release_work, mmc_omap_slot_release_work);
|
INIT_WORK(&host->slot_release_work, mmc_omap_slot_release_work);
|
||||||
INIT_WORK(&host->send_stop_work, mmc_omap_send_stop_work);
|
INIT_WORK(&host->send_stop_work, mmc_omap_send_stop_work);
|
||||||
@ -1369,20 +1368,11 @@ static int mmc_omap_probe(struct platform_device *pdev)
|
|||||||
platform_set_drvdata(pdev, host);
|
platform_set_drvdata(pdev, host);
|
||||||
|
|
||||||
host->id = pdev->id;
|
host->id = pdev->id;
|
||||||
host->mem_res = res;
|
|
||||||
host->irq = irq;
|
host->irq = irq;
|
||||||
host->use_dma = 1;
|
host->phys_base = res->start;
|
||||||
host->irq = irq;
|
|
||||||
host->phys_base = host->mem_res->start;
|
|
||||||
host->virt_base = ioremap(res->start, resource_size(res));
|
|
||||||
if (!host->virt_base)
|
|
||||||
goto err_ioremap;
|
|
||||||
|
|
||||||
host->iclk = clk_get(&pdev->dev, "ick");
|
host->iclk = clk_get(&pdev->dev, "ick");
|
||||||
if (IS_ERR(host->iclk)) {
|
if (IS_ERR(host->iclk))
|
||||||
ret = PTR_ERR(host->iclk);
|
return PTR_ERR(host->iclk);
|
||||||
goto err_free_mmc_host;
|
|
||||||
}
|
|
||||||
clk_enable(host->iclk);
|
clk_enable(host->iclk);
|
||||||
|
|
||||||
host->fclk = clk_get(&pdev->dev, "fck");
|
host->fclk = clk_get(&pdev->dev, "fck");
|
||||||
@ -1460,12 +1450,6 @@ err_free_dma:
|
|||||||
err_free_iclk:
|
err_free_iclk:
|
||||||
clk_disable(host->iclk);
|
clk_disable(host->iclk);
|
||||||
clk_put(host->iclk);
|
clk_put(host->iclk);
|
||||||
err_free_mmc_host:
|
|
||||||
iounmap(host->virt_base);
|
|
||||||
err_ioremap:
|
|
||||||
kfree(host);
|
|
||||||
err_free_mem_region:
|
|
||||||
release_mem_region(res->start, resource_size(res));
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1493,13 +1477,8 @@ static int mmc_omap_remove(struct platform_device *pdev)
|
|||||||
if (host->dma_rx)
|
if (host->dma_rx)
|
||||||
dma_release_channel(host->dma_rx);
|
dma_release_channel(host->dma_rx);
|
||||||
|
|
||||||
iounmap(host->virt_base);
|
|
||||||
release_mem_region(pdev->resource[0].start,
|
|
||||||
pdev->resource[0].end - pdev->resource[0].start + 1);
|
|
||||||
destroy_workqueue(host->mmc_omap_wq);
|
destroy_workqueue(host->mmc_omap_wq);
|
||||||
|
|
||||||
kfree(host);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
/* OMAP HSMMC Host Controller Registers */
|
/* OMAP HSMMC Host Controller Registers */
|
||||||
#define OMAP_HSMMC_SYSSTATUS 0x0014
|
#define OMAP_HSMMC_SYSSTATUS 0x0014
|
||||||
#define OMAP_HSMMC_CON 0x002C
|
#define OMAP_HSMMC_CON 0x002C
|
||||||
|
#define OMAP_HSMMC_SDMASA 0x0100
|
||||||
#define OMAP_HSMMC_BLK 0x0104
|
#define OMAP_HSMMC_BLK 0x0104
|
||||||
#define OMAP_HSMMC_ARG 0x0108
|
#define OMAP_HSMMC_ARG 0x0108
|
||||||
#define OMAP_HSMMC_CMD 0x010C
|
#define OMAP_HSMMC_CMD 0x010C
|
||||||
@ -58,6 +59,7 @@
|
|||||||
#define OMAP_HSMMC_STAT 0x0130
|
#define OMAP_HSMMC_STAT 0x0130
|
||||||
#define OMAP_HSMMC_IE 0x0134
|
#define OMAP_HSMMC_IE 0x0134
|
||||||
#define OMAP_HSMMC_ISE 0x0138
|
#define OMAP_HSMMC_ISE 0x0138
|
||||||
|
#define OMAP_HSMMC_AC12 0x013C
|
||||||
#define OMAP_HSMMC_CAPA 0x0140
|
#define OMAP_HSMMC_CAPA 0x0140
|
||||||
|
|
||||||
#define VS18 (1 << 26)
|
#define VS18 (1 << 26)
|
||||||
@ -81,6 +83,7 @@
|
|||||||
#define DTO_MASK 0x000F0000
|
#define DTO_MASK 0x000F0000
|
||||||
#define DTO_SHIFT 16
|
#define DTO_SHIFT 16
|
||||||
#define INIT_STREAM (1 << 1)
|
#define INIT_STREAM (1 << 1)
|
||||||
|
#define ACEN_ACMD23 (2 << 2)
|
||||||
#define DP_SELECT (1 << 21)
|
#define DP_SELECT (1 << 21)
|
||||||
#define DDIR (1 << 4)
|
#define DDIR (1 << 4)
|
||||||
#define DMAE 0x1
|
#define DMAE 0x1
|
||||||
@ -97,7 +100,6 @@
|
|||||||
#define SRC (1 << 25)
|
#define SRC (1 << 25)
|
||||||
#define SRD (1 << 26)
|
#define SRD (1 << 26)
|
||||||
#define SOFTRESET (1 << 1)
|
#define SOFTRESET (1 << 1)
|
||||||
#define RESETDONE (1 << 0)
|
|
||||||
|
|
||||||
/* Interrupt masks for IE and ISE register */
|
/* Interrupt masks for IE and ISE register */
|
||||||
#define CC_EN (1 << 0)
|
#define CC_EN (1 << 0)
|
||||||
@ -112,13 +114,21 @@
|
|||||||
#define DTO_EN (1 << 20)
|
#define DTO_EN (1 << 20)
|
||||||
#define DCRC_EN (1 << 21)
|
#define DCRC_EN (1 << 21)
|
||||||
#define DEB_EN (1 << 22)
|
#define DEB_EN (1 << 22)
|
||||||
|
#define ACE_EN (1 << 24)
|
||||||
#define CERR_EN (1 << 28)
|
#define CERR_EN (1 << 28)
|
||||||
#define BADA_EN (1 << 29)
|
#define BADA_EN (1 << 29)
|
||||||
|
|
||||||
#define INT_EN_MASK (BADA_EN | CERR_EN | DEB_EN | DCRC_EN |\
|
#define INT_EN_MASK (BADA_EN | CERR_EN | ACE_EN | DEB_EN | DCRC_EN |\
|
||||||
DTO_EN | CIE_EN | CEB_EN | CCRC_EN | CTO_EN | \
|
DTO_EN | CIE_EN | CEB_EN | CCRC_EN | CTO_EN | \
|
||||||
BRR_EN | BWR_EN | TC_EN | CC_EN)
|
BRR_EN | BWR_EN | TC_EN | CC_EN)
|
||||||
|
|
||||||
|
#define CNI (1 << 7)
|
||||||
|
#define ACIE (1 << 4)
|
||||||
|
#define ACEB (1 << 3)
|
||||||
|
#define ACCE (1 << 2)
|
||||||
|
#define ACTO (1 << 1)
|
||||||
|
#define ACNE (1 << 0)
|
||||||
|
|
||||||
#define MMC_AUTOSUSPEND_DELAY 100
|
#define MMC_AUTOSUSPEND_DELAY 100
|
||||||
#define MMC_TIMEOUT_MS 20 /* 20 mSec */
|
#define MMC_TIMEOUT_MS 20 /* 20 mSec */
|
||||||
#define MMC_TIMEOUT_US 20000 /* 20000 micro Sec */
|
#define MMC_TIMEOUT_US 20000 /* 20000 micro Sec */
|
||||||
@ -126,6 +136,11 @@
|
|||||||
#define OMAP_MMC_MAX_CLOCK 52000000
|
#define OMAP_MMC_MAX_CLOCK 52000000
|
||||||
#define DRIVER_NAME "omap_hsmmc"
|
#define DRIVER_NAME "omap_hsmmc"
|
||||||
|
|
||||||
|
#define VDD_1V8 1800000 /* 180000 uV */
|
||||||
|
#define VDD_3V0 3000000 /* 300000 uV */
|
||||||
|
#define VDD_165_195 (ffs(MMC_VDD_165_195) - 1)
|
||||||
|
|
||||||
|
#define AUTO_CMD23 (1 << 1) /* Auto CMD23 support */
|
||||||
/*
|
/*
|
||||||
* One controller can have multiple slots, like on some omap boards using
|
* One controller can have multiple slots, like on some omap boards using
|
||||||
* omap.c controller driver. Luckily this is not currently done on any known
|
* omap.c controller driver. Luckily this is not currently done on any known
|
||||||
@ -164,7 +179,8 @@ struct omap_hsmmc_host {
|
|||||||
*/
|
*/
|
||||||
struct regulator *vcc;
|
struct regulator *vcc;
|
||||||
struct regulator *vcc_aux;
|
struct regulator *vcc_aux;
|
||||||
int pbias_disable;
|
struct regulator *pbias;
|
||||||
|
bool pbias_enabled;
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
resource_size_t mapbase;
|
resource_size_t mapbase;
|
||||||
spinlock_t irq_lock; /* Prevent races with irq handler */
|
spinlock_t irq_lock; /* Prevent races with irq handler */
|
||||||
@ -188,10 +204,19 @@ struct omap_hsmmc_host {
|
|||||||
int reqs_blocked;
|
int reqs_blocked;
|
||||||
int use_reg;
|
int use_reg;
|
||||||
int req_in_progress;
|
int req_in_progress;
|
||||||
|
unsigned long clk_rate;
|
||||||
|
unsigned int flags;
|
||||||
struct omap_hsmmc_next next_data;
|
struct omap_hsmmc_next next_data;
|
||||||
struct omap_mmc_platform_data *pdata;
|
struct omap_mmc_platform_data *pdata;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct omap_mmc_of_data {
|
||||||
|
u32 reg_offset;
|
||||||
|
u8 controller_flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host);
|
||||||
|
|
||||||
static int omap_hsmmc_card_detect(struct device *dev, int slot)
|
static int omap_hsmmc_card_detect(struct device *dev, int slot)
|
||||||
{
|
{
|
||||||
struct omap_hsmmc_host *host = dev_get_drvdata(dev);
|
struct omap_hsmmc_host *host = dev_get_drvdata(dev);
|
||||||
@ -261,17 +286,19 @@ static int omap_hsmmc_set_power(struct device *dev, int slot, int power_on,
|
|||||||
*/
|
*/
|
||||||
if (!host->vcc)
|
if (!host->vcc)
|
||||||
return 0;
|
return 0;
|
||||||
/*
|
|
||||||
* With DT, never turn OFF the regulator for MMC1. This is because
|
|
||||||
* the pbias cell programming support is still missing when
|
|
||||||
* booting with Device tree
|
|
||||||
*/
|
|
||||||
if (host->pbias_disable && !vdd)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (mmc_slot(host).before_set_reg)
|
if (mmc_slot(host).before_set_reg)
|
||||||
mmc_slot(host).before_set_reg(dev, slot, power_on, vdd);
|
mmc_slot(host).before_set_reg(dev, slot, power_on, vdd);
|
||||||
|
|
||||||
|
if (host->pbias) {
|
||||||
|
if (host->pbias_enabled == 1) {
|
||||||
|
ret = regulator_disable(host->pbias);
|
||||||
|
if (!ret)
|
||||||
|
host->pbias_enabled = 0;
|
||||||
|
}
|
||||||
|
regulator_set_voltage(host->pbias, VDD_3V0, VDD_3V0);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assume Vcc regulator is used only to power the card ... OMAP
|
* Assume Vcc regulator is used only to power the card ... OMAP
|
||||||
* VDDS is used to power the pins, optionally with a transceiver to
|
* VDDS is used to power the pins, optionally with a transceiver to
|
||||||
@ -286,11 +313,12 @@ static int omap_hsmmc_set_power(struct device *dev, int slot, int power_on,
|
|||||||
* chips/cards need an interface voltage rail too.
|
* chips/cards need an interface voltage rail too.
|
||||||
*/
|
*/
|
||||||
if (power_on) {
|
if (power_on) {
|
||||||
ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd);
|
if (host->vcc)
|
||||||
|
ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd);
|
||||||
/* Enable interface voltage rail, if needed */
|
/* Enable interface voltage rail, if needed */
|
||||||
if (ret == 0 && host->vcc_aux) {
|
if (ret == 0 && host->vcc_aux) {
|
||||||
ret = regulator_enable(host->vcc_aux);
|
ret = regulator_enable(host->vcc_aux);
|
||||||
if (ret < 0)
|
if (ret < 0 && host->vcc)
|
||||||
ret = mmc_regulator_set_ocr(host->mmc,
|
ret = mmc_regulator_set_ocr(host->mmc,
|
||||||
host->vcc, 0);
|
host->vcc, 0);
|
||||||
}
|
}
|
||||||
@ -298,16 +326,34 @@ static int omap_hsmmc_set_power(struct device *dev, int slot, int power_on,
|
|||||||
/* Shut down the rail */
|
/* Shut down the rail */
|
||||||
if (host->vcc_aux)
|
if (host->vcc_aux)
|
||||||
ret = regulator_disable(host->vcc_aux);
|
ret = regulator_disable(host->vcc_aux);
|
||||||
if (!ret) {
|
if (host->vcc) {
|
||||||
/* Then proceed to shut down the local regulator */
|
/* Then proceed to shut down the local regulator */
|
||||||
ret = mmc_regulator_set_ocr(host->mmc,
|
ret = mmc_regulator_set_ocr(host->mmc,
|
||||||
host->vcc, 0);
|
host->vcc, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (host->pbias) {
|
||||||
|
if (vdd <= VDD_165_195)
|
||||||
|
ret = regulator_set_voltage(host->pbias, VDD_1V8,
|
||||||
|
VDD_1V8);
|
||||||
|
else
|
||||||
|
ret = regulator_set_voltage(host->pbias, VDD_3V0,
|
||||||
|
VDD_3V0);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error_set_power;
|
||||||
|
|
||||||
|
if (host->pbias_enabled == 0) {
|
||||||
|
ret = regulator_enable(host->pbias);
|
||||||
|
if (!ret)
|
||||||
|
host->pbias_enabled = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mmc_slot(host).after_set_reg)
|
if (mmc_slot(host).after_set_reg)
|
||||||
mmc_slot(host).after_set_reg(dev, slot, power_on, vdd);
|
mmc_slot(host).after_set_reg(dev, slot, power_on, vdd);
|
||||||
|
|
||||||
|
error_set_power:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,12 +362,12 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host)
|
|||||||
struct regulator *reg;
|
struct regulator *reg;
|
||||||
int ocr_value = 0;
|
int ocr_value = 0;
|
||||||
|
|
||||||
reg = regulator_get(host->dev, "vmmc");
|
reg = devm_regulator_get(host->dev, "vmmc");
|
||||||
if (IS_ERR(reg)) {
|
if (IS_ERR(reg)) {
|
||||||
dev_err(host->dev, "vmmc regulator missing\n");
|
dev_err(host->dev, "unable to get vmmc regulator %ld\n",
|
||||||
|
PTR_ERR(reg));
|
||||||
return PTR_ERR(reg);
|
return PTR_ERR(reg);
|
||||||
} else {
|
} else {
|
||||||
mmc_slot(host).set_power = omap_hsmmc_set_power;
|
|
||||||
host->vcc = reg;
|
host->vcc = reg;
|
||||||
ocr_value = mmc_regulator_get_ocrmask(reg);
|
ocr_value = mmc_regulator_get_ocrmask(reg);
|
||||||
if (!mmc_slot(host).ocr_mask) {
|
if (!mmc_slot(host).ocr_mask) {
|
||||||
@ -334,31 +380,29 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host)
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
mmc_slot(host).set_power = omap_hsmmc_set_power;
|
||||||
|
|
||||||
/* Allow an aux regulator */
|
/* Allow an aux regulator */
|
||||||
reg = regulator_get(host->dev, "vmmc_aux");
|
reg = devm_regulator_get_optional(host->dev, "vmmc_aux");
|
||||||
host->vcc_aux = IS_ERR(reg) ? NULL : reg;
|
host->vcc_aux = IS_ERR(reg) ? NULL : reg;
|
||||||
|
|
||||||
/* For eMMC do not power off when not in sleep state */
|
reg = devm_regulator_get_optional(host->dev, "pbias");
|
||||||
if (mmc_slot(host).no_regulator_off_init)
|
host->pbias = IS_ERR(reg) ? NULL : reg;
|
||||||
return 0;
|
|
||||||
/*
|
|
||||||
* UGLY HACK: workaround regulator framework bugs.
|
|
||||||
* When the bootloader leaves a supply active, it's
|
|
||||||
* initialized with zero usecount ... and we can't
|
|
||||||
* disable it without first enabling it. Until the
|
|
||||||
* framework is fixed, we need a workaround like this
|
|
||||||
* (which is safe for MMC, but not in general).
|
|
||||||
*/
|
|
||||||
if (regulator_is_enabled(host->vcc) > 0 ||
|
|
||||||
(host->vcc_aux && regulator_is_enabled(host->vcc_aux))) {
|
|
||||||
int vdd = ffs(mmc_slot(host).ocr_mask) - 1;
|
|
||||||
|
|
||||||
mmc_slot(host).set_power(host->dev, host->slot_id,
|
/* For eMMC do not power off when not in sleep state */
|
||||||
1, vdd);
|
if (mmc_slot(host).no_regulator_off_init)
|
||||||
mmc_slot(host).set_power(host->dev, host->slot_id,
|
return 0;
|
||||||
0, 0);
|
/*
|
||||||
}
|
* To disable boot_on regulator, enable regulator
|
||||||
|
* to increase usecount and then disable it.
|
||||||
|
*/
|
||||||
|
if ((host->vcc && regulator_is_enabled(host->vcc) > 0) ||
|
||||||
|
(host->vcc_aux && regulator_is_enabled(host->vcc_aux))) {
|
||||||
|
int vdd = ffs(mmc_slot(host).ocr_mask) - 1;
|
||||||
|
|
||||||
|
mmc_slot(host).set_power(host->dev, host->slot_id, 1, vdd);
|
||||||
|
mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -366,8 +410,6 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host)
|
|||||||
|
|
||||||
static void omap_hsmmc_reg_put(struct omap_hsmmc_host *host)
|
static void omap_hsmmc_reg_put(struct omap_hsmmc_host *host)
|
||||||
{
|
{
|
||||||
regulator_put(host->vcc);
|
|
||||||
regulator_put(host->vcc_aux);
|
|
||||||
mmc_slot(host).set_power = NULL;
|
mmc_slot(host).set_power = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,9 +647,6 @@ static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
|
|||||||
u32 hctl, capa;
|
u32 hctl, capa;
|
||||||
unsigned long timeout;
|
unsigned long timeout;
|
||||||
|
|
||||||
if (!OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (host->con == OMAP_HSMMC_READ(host->base, CON) &&
|
if (host->con == OMAP_HSMMC_READ(host->base, CON) &&
|
||||||
host->hctl == OMAP_HSMMC_READ(host->base, HCTL) &&
|
host->hctl == OMAP_HSMMC_READ(host->base, HCTL) &&
|
||||||
host->sysctl == OMAP_HSMMC_READ(host->base, SYSCTL) &&
|
host->sysctl == OMAP_HSMMC_READ(host->base, SYSCTL) &&
|
||||||
@ -787,6 +826,11 @@ omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd,
|
|||||||
|
|
||||||
cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22);
|
cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22);
|
||||||
|
|
||||||
|
if ((host->flags & AUTO_CMD23) && mmc_op_multi(cmd->opcode) &&
|
||||||
|
host->mrq->sbc) {
|
||||||
|
cmdreg |= ACEN_ACMD23;
|
||||||
|
OMAP_HSMMC_WRITE(host->base, SDMASA, host->mrq->sbc->arg);
|
||||||
|
}
|
||||||
if (data) {
|
if (data) {
|
||||||
cmdreg |= DP_SELECT | MSBS | BCE;
|
cmdreg |= DP_SELECT | MSBS | BCE;
|
||||||
if (data->flags & MMC_DATA_READ)
|
if (data->flags & MMC_DATA_READ)
|
||||||
@ -864,11 +908,10 @@ omap_hsmmc_xfer_done(struct omap_hsmmc_host *host, struct mmc_data *data)
|
|||||||
else
|
else
|
||||||
data->bytes_xfered = 0;
|
data->bytes_xfered = 0;
|
||||||
|
|
||||||
if (!data->stop) {
|
if (data->stop && (data->error || !host->mrq->sbc))
|
||||||
|
omap_hsmmc_start_command(host, data->stop, NULL);
|
||||||
|
else
|
||||||
omap_hsmmc_request_done(host, data->mrq);
|
omap_hsmmc_request_done(host, data->mrq);
|
||||||
return;
|
|
||||||
}
|
|
||||||
omap_hsmmc_start_command(host, data->stop, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -879,6 +922,14 @@ omap_hsmmc_cmd_done(struct omap_hsmmc_host *host, struct mmc_command *cmd)
|
|||||||
{
|
{
|
||||||
host->cmd = NULL;
|
host->cmd = NULL;
|
||||||
|
|
||||||
|
if (host->mrq->sbc && (host->cmd == host->mrq->sbc) &&
|
||||||
|
!host->mrq->sbc->error && !(host->flags & AUTO_CMD23)) {
|
||||||
|
omap_hsmmc_start_dma_transfer(host);
|
||||||
|
omap_hsmmc_start_command(host, host->mrq->cmd,
|
||||||
|
host->mrq->data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (cmd->flags & MMC_RSP_PRESENT) {
|
if (cmd->flags & MMC_RSP_PRESENT) {
|
||||||
if (cmd->flags & MMC_RSP_136) {
|
if (cmd->flags & MMC_RSP_136) {
|
||||||
/* response type 2 */
|
/* response type 2 */
|
||||||
@ -892,7 +943,7 @@ omap_hsmmc_cmd_done(struct omap_hsmmc_host *host, struct mmc_command *cmd)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((host->data == NULL && !host->response_busy) || cmd->error)
|
if ((host->data == NULL && !host->response_busy) || cmd->error)
|
||||||
omap_hsmmc_request_done(host, cmd->mrq);
|
omap_hsmmc_request_done(host, host->mrq);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1015,6 +1066,7 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
|
|||||||
{
|
{
|
||||||
struct mmc_data *data;
|
struct mmc_data *data;
|
||||||
int end_cmd = 0, end_trans = 0;
|
int end_cmd = 0, end_trans = 0;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
data = host->data;
|
data = host->data;
|
||||||
dev_vdbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status);
|
dev_vdbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status);
|
||||||
@ -1029,6 +1081,20 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
|
|||||||
else if (status & (CCRC_EN | DCRC_EN))
|
else if (status & (CCRC_EN | DCRC_EN))
|
||||||
hsmmc_command_incomplete(host, -EILSEQ, end_cmd);
|
hsmmc_command_incomplete(host, -EILSEQ, end_cmd);
|
||||||
|
|
||||||
|
if (status & ACE_EN) {
|
||||||
|
u32 ac12;
|
||||||
|
ac12 = OMAP_HSMMC_READ(host->base, AC12);
|
||||||
|
if (!(ac12 & ACNE) && host->mrq->sbc) {
|
||||||
|
end_cmd = 1;
|
||||||
|
if (ac12 & ACTO)
|
||||||
|
error = -ETIMEDOUT;
|
||||||
|
else if (ac12 & (ACCE | ACEB | ACIE))
|
||||||
|
error = -EILSEQ;
|
||||||
|
host->mrq->sbc->error = error;
|
||||||
|
hsmmc_command_incomplete(host, error, end_cmd);
|
||||||
|
}
|
||||||
|
dev_dbg(mmc_dev(host->mmc), "AC12 err: 0x%x\n", ac12);
|
||||||
|
}
|
||||||
if (host->data || host->response_busy) {
|
if (host->data || host->response_busy) {
|
||||||
end_trans = !end_cmd;
|
end_trans = !end_cmd;
|
||||||
host->response_busy = 0;
|
host->response_busy = 0;
|
||||||
@ -1236,8 +1302,7 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Check if next job is already prepared */
|
/* Check if next job is already prepared */
|
||||||
if (next ||
|
if (next || data->host_cookie != host->next_data.cookie) {
|
||||||
(!next && data->host_cookie != host->next_data.cookie)) {
|
|
||||||
dma_len = dma_map_sg(chan->device->dev, data->sg, data->sg_len,
|
dma_len = dma_map_sg(chan->device->dev, data->sg, data->sg_len,
|
||||||
omap_hsmmc_get_dma_dir(host, data));
|
omap_hsmmc_get_dma_dir(host, data));
|
||||||
|
|
||||||
@ -1262,7 +1327,7 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host,
|
|||||||
/*
|
/*
|
||||||
* Routine to configure and start DMA for the MMC card
|
* Routine to configure and start DMA for the MMC card
|
||||||
*/
|
*/
|
||||||
static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host,
|
static int omap_hsmmc_setup_dma_transfer(struct omap_hsmmc_host *host,
|
||||||
struct mmc_request *req)
|
struct mmc_request *req)
|
||||||
{
|
{
|
||||||
struct dma_slave_config cfg;
|
struct dma_slave_config cfg;
|
||||||
@ -1321,8 +1386,6 @@ static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host,
|
|||||||
|
|
||||||
host->dma_ch = 1;
|
host->dma_ch = 1;
|
||||||
|
|
||||||
dma_async_issue_pending(chan);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1338,7 +1401,7 @@ static void set_data_timeout(struct omap_hsmmc_host *host,
|
|||||||
if (clkd == 0)
|
if (clkd == 0)
|
||||||
clkd = 1;
|
clkd = 1;
|
||||||
|
|
||||||
cycle_ns = 1000000000 / (clk_get_rate(host->fclk) / clkd);
|
cycle_ns = 1000000000 / (host->clk_rate / clkd);
|
||||||
timeout = timeout_ns / cycle_ns;
|
timeout = timeout_ns / cycle_ns;
|
||||||
timeout += timeout_clks;
|
timeout += timeout_clks;
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
@ -1363,6 +1426,21 @@ static void set_data_timeout(struct omap_hsmmc_host *host,
|
|||||||
OMAP_HSMMC_WRITE(host->base, SYSCTL, reg);
|
OMAP_HSMMC_WRITE(host->base, SYSCTL, reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host)
|
||||||
|
{
|
||||||
|
struct mmc_request *req = host->mrq;
|
||||||
|
struct dma_chan *chan;
|
||||||
|
|
||||||
|
if (!req->data)
|
||||||
|
return;
|
||||||
|
OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
|
||||||
|
| (req->data->blocks << 16));
|
||||||
|
set_data_timeout(host, req->data->timeout_ns,
|
||||||
|
req->data->timeout_clks);
|
||||||
|
chan = omap_hsmmc_get_dma_chan(host, req->data);
|
||||||
|
dma_async_issue_pending(chan);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Configure block length for MMC/SD cards and initiate the transfer.
|
* Configure block length for MMC/SD cards and initiate the transfer.
|
||||||
*/
|
*/
|
||||||
@ -1383,12 +1461,8 @@ omap_hsmmc_prepare_data(struct omap_hsmmc_host *host, struct mmc_request *req)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
|
|
||||||
| (req->data->blocks << 16));
|
|
||||||
set_data_timeout(host, req->data->timeout_ns, req->data->timeout_clks);
|
|
||||||
|
|
||||||
if (host->use_dma) {
|
if (host->use_dma) {
|
||||||
ret = omap_hsmmc_start_dma_transfer(host, req);
|
ret = omap_hsmmc_setup_dma_transfer(host, req);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
dev_err(mmc_dev(host->mmc), "MMC start dma failure\n");
|
dev_err(mmc_dev(host->mmc), "MMC start dma failure\n");
|
||||||
return ret;
|
return ret;
|
||||||
@ -1462,6 +1536,7 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req)
|
|||||||
host->reqs_blocked = 0;
|
host->reqs_blocked = 0;
|
||||||
WARN_ON(host->mrq != NULL);
|
WARN_ON(host->mrq != NULL);
|
||||||
host->mrq = req;
|
host->mrq = req;
|
||||||
|
host->clk_rate = clk_get_rate(host->fclk);
|
||||||
err = omap_hsmmc_prepare_data(host, req);
|
err = omap_hsmmc_prepare_data(host, req);
|
||||||
if (err) {
|
if (err) {
|
||||||
req->cmd->error = err;
|
req->cmd->error = err;
|
||||||
@ -1471,7 +1546,12 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req)
|
|||||||
mmc_request_done(mmc, req);
|
mmc_request_done(mmc, req);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (req->sbc && !(host->flags & AUTO_CMD23)) {
|
||||||
|
omap_hsmmc_start_command(host, req->sbc, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
omap_hsmmc_start_dma_transfer(host);
|
||||||
omap_hsmmc_start_command(host, req->cmd, req->data);
|
omap_hsmmc_start_command(host, req->cmd, req->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1509,13 +1589,7 @@ static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|||||||
* of external transceiver; but they all handle 1.8V.
|
* of external transceiver; but they all handle 1.8V.
|
||||||
*/
|
*/
|
||||||
if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) &&
|
if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) &&
|
||||||
(ios->vdd == DUAL_VOLT_OCR_BIT) &&
|
(ios->vdd == DUAL_VOLT_OCR_BIT)) {
|
||||||
/*
|
|
||||||
* With pbias cell programming missing, this
|
|
||||||
* can't be allowed on MMC1 when booting with device
|
|
||||||
* tree.
|
|
||||||
*/
|
|
||||||
!host->pbias_disable) {
|
|
||||||
/*
|
/*
|
||||||
* The mmc_select_voltage fn of the core does
|
* The mmc_select_voltage fn of the core does
|
||||||
* not seem to set the power_mode to
|
* not seem to set the power_mode to
|
||||||
@ -1678,18 +1752,29 @@ static void omap_hsmmc_debugfs(struct mmc_host *mmc)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_OF
|
#ifdef CONFIG_OF
|
||||||
static u16 omap4_reg_offset = 0x100;
|
static const struct omap_mmc_of_data omap3_pre_es3_mmc_of_data = {
|
||||||
|
/* See 35xx errata 2.1.1.128 in SPRZ278F */
|
||||||
|
.controller_flags = OMAP_HSMMC_BROKEN_MULTIBLOCK_READ,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct omap_mmc_of_data omap4_mmc_of_data = {
|
||||||
|
.reg_offset = 0x100,
|
||||||
|
};
|
||||||
|
|
||||||
static const struct of_device_id omap_mmc_of_match[] = {
|
static const struct of_device_id omap_mmc_of_match[] = {
|
||||||
{
|
{
|
||||||
.compatible = "ti,omap2-hsmmc",
|
.compatible = "ti,omap2-hsmmc",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.compatible = "ti,omap3-pre-es3-hsmmc",
|
||||||
|
.data = &omap3_pre_es3_mmc_of_data,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.compatible = "ti,omap3-hsmmc",
|
.compatible = "ti,omap3-hsmmc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.compatible = "ti,omap4-hsmmc",
|
.compatible = "ti,omap4-hsmmc",
|
||||||
.data = &omap4_reg_offset,
|
.data = &omap4_mmc_of_data,
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
};
|
};
|
||||||
@ -1709,7 +1794,7 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev)
|
|||||||
|
|
||||||
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||||||
if (!pdata)
|
if (!pdata)
|
||||||
return NULL; /* out of memory */
|
return ERR_PTR(-ENOMEM); /* out of memory */
|
||||||
|
|
||||||
if (of_find_property(np, "ti,dual-volt", NULL))
|
if (of_find_property(np, "ti,dual-volt", NULL))
|
||||||
pdata->controller_flags |= OMAP_HSMMC_SUPPORTS_DUAL_VOLT;
|
pdata->controller_flags |= OMAP_HSMMC_SUPPORTS_DUAL_VOLT;
|
||||||
@ -1738,13 +1823,19 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev)
|
|||||||
if (of_find_property(np, "ti,needs-special-hs-handling", NULL))
|
if (of_find_property(np, "ti,needs-special-hs-handling", NULL))
|
||||||
pdata->slots[0].features |= HSMMC_HAS_HSPE_SUPPORT;
|
pdata->slots[0].features |= HSMMC_HAS_HSPE_SUPPORT;
|
||||||
|
|
||||||
|
if (of_find_property(np, "keep-power-in-suspend", NULL))
|
||||||
|
pdata->slots[0].pm_caps |= MMC_PM_KEEP_POWER;
|
||||||
|
|
||||||
|
if (of_find_property(np, "enable-sdio-wakeup", NULL))
|
||||||
|
pdata->slots[0].pm_caps |= MMC_PM_WAKE_SDIO_IRQ;
|
||||||
|
|
||||||
return pdata;
|
return pdata;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static inline struct omap_mmc_platform_data
|
static inline struct omap_mmc_platform_data
|
||||||
*of_get_hsmmc_pdata(struct device *dev)
|
*of_get_hsmmc_pdata(struct device *dev)
|
||||||
{
|
{
|
||||||
return NULL;
|
return ERR_PTR(-EINVAL);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -1759,6 +1850,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|||||||
dma_cap_mask_t mask;
|
dma_cap_mask_t mask;
|
||||||
unsigned tx_req, rx_req;
|
unsigned tx_req, rx_req;
|
||||||
struct pinctrl *pinctrl;
|
struct pinctrl *pinctrl;
|
||||||
|
const struct omap_mmc_of_data *data;
|
||||||
|
|
||||||
match = of_match_device(of_match_ptr(omap_mmc_of_match), &pdev->dev);
|
match = of_match_device(of_match_ptr(omap_mmc_of_match), &pdev->dev);
|
||||||
if (match) {
|
if (match) {
|
||||||
@ -1768,8 +1860,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|||||||
return PTR_ERR(pdata);
|
return PTR_ERR(pdata);
|
||||||
|
|
||||||
if (match->data) {
|
if (match->data) {
|
||||||
const u16 *offsetp = match->data;
|
data = match->data;
|
||||||
pdata->reg_offset = *offsetp;
|
pdata->reg_offset = data->reg_offset;
|
||||||
|
pdata->controller_flags |= data->controller_flags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1814,6 +1907,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|||||||
host->base = ioremap(host->mapbase, SZ_4K);
|
host->base = ioremap(host->mapbase, SZ_4K);
|
||||||
host->power_mode = MMC_POWER_OFF;
|
host->power_mode = MMC_POWER_OFF;
|
||||||
host->next_data.cookie = 1;
|
host->next_data.cookie = 1;
|
||||||
|
host->pbias_enabled = 0;
|
||||||
|
|
||||||
platform_set_drvdata(pdev, host);
|
platform_set_drvdata(pdev, host);
|
||||||
|
|
||||||
@ -1847,10 +1941,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|||||||
|
|
||||||
omap_hsmmc_context_save(host);
|
omap_hsmmc_context_save(host);
|
||||||
|
|
||||||
/* This can be removed once we support PBIAS with DT */
|
|
||||||
if (host->dev->of_node && res->start == 0x4809c000)
|
|
||||||
host->pbias_disable = 1;
|
|
||||||
|
|
||||||
host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck");
|
host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck");
|
||||||
/*
|
/*
|
||||||
* MMC can still work without debounce clock.
|
* MMC can still work without debounce clock.
|
||||||
|
@ -31,14 +31,9 @@
|
|||||||
#include <linux/mfd/rtsx_pci.h>
|
#include <linux/mfd/rtsx_pci.h>
|
||||||
#include <asm/unaligned.h>
|
#include <asm/unaligned.h>
|
||||||
|
|
||||||
/* SD Tuning Data Structure
|
struct realtek_next {
|
||||||
* Record continuous timing phase path
|
unsigned int sg_count;
|
||||||
*/
|
s32 cookie;
|
||||||
struct timing_phase_path {
|
|
||||||
int start;
|
|
||||||
int end;
|
|
||||||
int mid;
|
|
||||||
int len;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct realtek_pci_sdmmc {
|
struct realtek_pci_sdmmc {
|
||||||
@ -46,9 +41,18 @@ struct realtek_pci_sdmmc {
|
|||||||
struct rtsx_pcr *pcr;
|
struct rtsx_pcr *pcr;
|
||||||
struct mmc_host *mmc;
|
struct mmc_host *mmc;
|
||||||
struct mmc_request *mrq;
|
struct mmc_request *mrq;
|
||||||
|
struct mmc_command *cmd;
|
||||||
|
struct mmc_data *data;
|
||||||
|
|
||||||
struct mutex host_mutex;
|
spinlock_t lock;
|
||||||
|
struct timer_list timer;
|
||||||
|
struct tasklet_struct cmd_tasklet;
|
||||||
|
struct tasklet_struct data_tasklet;
|
||||||
|
struct tasklet_struct finish_tasklet;
|
||||||
|
|
||||||
|
u8 rsp_type;
|
||||||
|
u8 rsp_len;
|
||||||
|
int sg_count;
|
||||||
u8 ssc_depth;
|
u8 ssc_depth;
|
||||||
unsigned int clock;
|
unsigned int clock;
|
||||||
bool vpclk;
|
bool vpclk;
|
||||||
@ -58,8 +62,13 @@ struct realtek_pci_sdmmc {
|
|||||||
int power_state;
|
int power_state;
|
||||||
#define SDMMC_POWER_ON 1
|
#define SDMMC_POWER_ON 1
|
||||||
#define SDMMC_POWER_OFF 0
|
#define SDMMC_POWER_OFF 0
|
||||||
|
|
||||||
|
struct realtek_next next_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int sd_start_multi_rw(struct realtek_pci_sdmmc *host,
|
||||||
|
struct mmc_request *mrq);
|
||||||
|
|
||||||
static inline struct device *sdmmc_dev(struct realtek_pci_sdmmc *host)
|
static inline struct device *sdmmc_dev(struct realtek_pci_sdmmc *host)
|
||||||
{
|
{
|
||||||
return &(host->pdev->dev);
|
return &(host->pdev->dev);
|
||||||
@ -96,6 +105,95 @@ static void sd_print_debug_regs(struct realtek_pci_sdmmc *host)
|
|||||||
#define sd_print_debug_regs(host)
|
#define sd_print_debug_regs(host)
|
||||||
#endif /* DEBUG */
|
#endif /* DEBUG */
|
||||||
|
|
||||||
|
static void sd_isr_done_transfer(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
spin_lock(&host->lock);
|
||||||
|
if (host->cmd)
|
||||||
|
tasklet_schedule(&host->cmd_tasklet);
|
||||||
|
if (host->data)
|
||||||
|
tasklet_schedule(&host->data_tasklet);
|
||||||
|
spin_unlock(&host->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sd_request_timeout(unsigned long host_addr)
|
||||||
|
{
|
||||||
|
struct realtek_pci_sdmmc *host = (struct realtek_pci_sdmmc *)host_addr;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
|
|
||||||
|
if (!host->mrq) {
|
||||||
|
dev_err(sdmmc_dev(host), "error: no request exist\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host->cmd)
|
||||||
|
host->cmd->error = -ETIMEDOUT;
|
||||||
|
if (host->data)
|
||||||
|
host->data->error = -ETIMEDOUT;
|
||||||
|
|
||||||
|
dev_dbg(sdmmc_dev(host), "timeout for request\n");
|
||||||
|
|
||||||
|
out:
|
||||||
|
tasklet_schedule(&host->finish_tasklet);
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sd_finish_request(unsigned long host_addr)
|
||||||
|
{
|
||||||
|
struct realtek_pci_sdmmc *host = (struct realtek_pci_sdmmc *)host_addr;
|
||||||
|
struct rtsx_pcr *pcr = host->pcr;
|
||||||
|
struct mmc_request *mrq;
|
||||||
|
struct mmc_command *cmd;
|
||||||
|
struct mmc_data *data;
|
||||||
|
unsigned long flags;
|
||||||
|
bool any_error;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
|
|
||||||
|
del_timer(&host->timer);
|
||||||
|
mrq = host->mrq;
|
||||||
|
if (!mrq) {
|
||||||
|
dev_err(sdmmc_dev(host), "error: no request need finish\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = mrq->cmd;
|
||||||
|
data = mrq->data;
|
||||||
|
|
||||||
|
any_error = (mrq->sbc && mrq->sbc->error) ||
|
||||||
|
(mrq->stop && mrq->stop->error) ||
|
||||||
|
(cmd && cmd->error) || (data && data->error);
|
||||||
|
|
||||||
|
if (any_error) {
|
||||||
|
rtsx_pci_stop_cmd(pcr);
|
||||||
|
sd_clear_error(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
if (any_error)
|
||||||
|
data->bytes_xfered = 0;
|
||||||
|
else
|
||||||
|
data->bytes_xfered = data->blocks * data->blksz;
|
||||||
|
|
||||||
|
if (!data->host_cookie)
|
||||||
|
rtsx_pci_dma_unmap_sg(pcr, data->sg, data->sg_len,
|
||||||
|
data->flags & MMC_DATA_READ);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
host->mrq = NULL;
|
||||||
|
host->cmd = NULL;
|
||||||
|
host->data = NULL;
|
||||||
|
|
||||||
|
out:
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
mutex_unlock(&pcr->pcr_mutex);
|
||||||
|
mmc_request_done(host->mmc, mrq);
|
||||||
|
}
|
||||||
|
|
||||||
static int sd_read_data(struct realtek_pci_sdmmc *host, u8 *cmd, u16 byte_cnt,
|
static int sd_read_data(struct realtek_pci_sdmmc *host, u8 *cmd, u16 byte_cnt,
|
||||||
u8 *buf, int buf_len, int timeout)
|
u8 *buf, int buf_len, int timeout)
|
||||||
{
|
{
|
||||||
@ -213,8 +311,7 @@ static int sd_write_data(struct realtek_pci_sdmmc *host, u8 *cmd, u16 byte_cnt,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host,
|
static void sd_send_cmd(struct realtek_pci_sdmmc *host, struct mmc_command *cmd)
|
||||||
struct mmc_command *cmd)
|
|
||||||
{
|
{
|
||||||
struct rtsx_pcr *pcr = host->pcr;
|
struct rtsx_pcr *pcr = host->pcr;
|
||||||
u8 cmd_idx = (u8)cmd->opcode;
|
u8 cmd_idx = (u8)cmd->opcode;
|
||||||
@ -222,11 +319,14 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host,
|
|||||||
int err = 0;
|
int err = 0;
|
||||||
int timeout = 100;
|
int timeout = 100;
|
||||||
int i;
|
int i;
|
||||||
u8 *ptr;
|
|
||||||
int stat_idx = 0;
|
|
||||||
u8 rsp_type;
|
u8 rsp_type;
|
||||||
int rsp_len = 5;
|
int rsp_len = 5;
|
||||||
bool clock_toggled = false;
|
unsigned long flags;
|
||||||
|
|
||||||
|
if (host->cmd)
|
||||||
|
dev_err(sdmmc_dev(host), "error: cmd already exist\n");
|
||||||
|
|
||||||
|
host->cmd = cmd;
|
||||||
|
|
||||||
dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD %d, arg = 0x%08x\n",
|
dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD %d, arg = 0x%08x\n",
|
||||||
__func__, cmd_idx, arg);
|
__func__, cmd_idx, arg);
|
||||||
@ -261,6 +361,8 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host,
|
|||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
host->rsp_type = rsp_type;
|
||||||
|
host->rsp_len = rsp_len;
|
||||||
|
|
||||||
if (rsp_type == SD_RSP_TYPE_R1b)
|
if (rsp_type == SD_RSP_TYPE_R1b)
|
||||||
timeout = 3000;
|
timeout = 3000;
|
||||||
@ -270,8 +372,6 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host,
|
|||||||
0xFF, SD_CLK_TOGGLE_EN);
|
0xFF, SD_CLK_TOGGLE_EN);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
clock_toggled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rtsx_pci_init_cmd(pcr);
|
rtsx_pci_init_cmd(pcr);
|
||||||
@ -295,25 +395,60 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host,
|
|||||||
/* Read data from ping-pong buffer */
|
/* Read data from ping-pong buffer */
|
||||||
for (i = PPBUF_BASE2; i < PPBUF_BASE2 + 16; i++)
|
for (i = PPBUF_BASE2; i < PPBUF_BASE2 + 16; i++)
|
||||||
rtsx_pci_add_cmd(pcr, READ_REG_CMD, (u16)i, 0, 0);
|
rtsx_pci_add_cmd(pcr, READ_REG_CMD, (u16)i, 0, 0);
|
||||||
stat_idx = 16;
|
|
||||||
} else if (rsp_type != SD_RSP_TYPE_R0) {
|
} else if (rsp_type != SD_RSP_TYPE_R0) {
|
||||||
/* Read data from SD_CMDx registers */
|
/* Read data from SD_CMDx registers */
|
||||||
for (i = SD_CMD0; i <= SD_CMD4; i++)
|
for (i = SD_CMD0; i <= SD_CMD4; i++)
|
||||||
rtsx_pci_add_cmd(pcr, READ_REG_CMD, (u16)i, 0, 0);
|
rtsx_pci_add_cmd(pcr, READ_REG_CMD, (u16)i, 0, 0);
|
||||||
stat_idx = 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rtsx_pci_add_cmd(pcr, READ_REG_CMD, SD_STAT1, 0, 0);
|
rtsx_pci_add_cmd(pcr, READ_REG_CMD, SD_STAT1, 0, 0);
|
||||||
|
|
||||||
err = rtsx_pci_send_cmd(pcr, timeout);
|
mod_timer(&host->timer, jiffies + msecs_to_jiffies(timeout));
|
||||||
if (err < 0) {
|
|
||||||
sd_print_debug_regs(host);
|
spin_lock_irqsave(&pcr->lock, flags);
|
||||||
sd_clear_error(host);
|
pcr->trans_result = TRANS_NOT_READY;
|
||||||
dev_dbg(sdmmc_dev(host),
|
rtsx_pci_send_cmd_no_wait(pcr);
|
||||||
"rtsx_pci_send_cmd error (err = %d)\n", err);
|
spin_unlock_irqrestore(&pcr->lock, flags);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
out:
|
||||||
|
cmd->error = err;
|
||||||
|
tasklet_schedule(&host->finish_tasklet);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sd_get_rsp(unsigned long host_addr)
|
||||||
|
{
|
||||||
|
struct realtek_pci_sdmmc *host = (struct realtek_pci_sdmmc *)host_addr;
|
||||||
|
struct rtsx_pcr *pcr = host->pcr;
|
||||||
|
struct mmc_command *cmd;
|
||||||
|
int i, err = 0, stat_idx;
|
||||||
|
u8 *ptr, rsp_type;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
|
|
||||||
|
cmd = host->cmd;
|
||||||
|
host->cmd = NULL;
|
||||||
|
|
||||||
|
if (!cmd) {
|
||||||
|
dev_err(sdmmc_dev(host), "error: cmd not exist\n");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spin_lock(&pcr->lock);
|
||||||
|
if (pcr->trans_result == TRANS_NO_DEVICE)
|
||||||
|
err = -ENODEV;
|
||||||
|
else if (pcr->trans_result != TRANS_RESULT_OK)
|
||||||
|
err = -EINVAL;
|
||||||
|
spin_unlock(&pcr->lock);
|
||||||
|
|
||||||
|
if (err < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
rsp_type = host->rsp_type;
|
||||||
|
stat_idx = host->rsp_len;
|
||||||
|
|
||||||
if (rsp_type == SD_RSP_TYPE_R0) {
|
if (rsp_type == SD_RSP_TYPE_R0) {
|
||||||
err = 0;
|
err = 0;
|
||||||
goto out;
|
goto out;
|
||||||
@ -350,26 +485,106 @@ static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host,
|
|||||||
cmd->resp[0]);
|
cmd->resp[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cmd == host->mrq->sbc) {
|
||||||
|
sd_send_cmd(host, host->mrq->cmd);
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == host->mrq->stop)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (cmd->data) {
|
||||||
|
sd_start_multi_rw(host, host->mrq);
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
cmd->error = err;
|
cmd->error = err;
|
||||||
|
|
||||||
if (err && clock_toggled)
|
tasklet_schedule(&host->finish_tasklet);
|
||||||
rtsx_pci_write_register(pcr, SD_BUS_STAT,
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sd_rw_multi(struct realtek_pci_sdmmc *host, struct mmc_request *mrq)
|
static int sd_pre_dma_transfer(struct realtek_pci_sdmmc *host,
|
||||||
|
struct mmc_data *data, struct realtek_next *next)
|
||||||
|
{
|
||||||
|
struct rtsx_pcr *pcr = host->pcr;
|
||||||
|
int read = data->flags & MMC_DATA_READ;
|
||||||
|
int sg_count = 0;
|
||||||
|
|
||||||
|
if (!next && data->host_cookie &&
|
||||||
|
data->host_cookie != host->next_data.cookie) {
|
||||||
|
dev_err(sdmmc_dev(host),
|
||||||
|
"error: invalid cookie data[%d] host[%d]\n",
|
||||||
|
data->host_cookie, host->next_data.cookie);
|
||||||
|
data->host_cookie = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next || (!next && data->host_cookie != host->next_data.cookie))
|
||||||
|
sg_count = rtsx_pci_dma_map_sg(pcr,
|
||||||
|
data->sg, data->sg_len, read);
|
||||||
|
else
|
||||||
|
sg_count = host->next_data.sg_count;
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
next->sg_count = sg_count;
|
||||||
|
if (++next->cookie < 0)
|
||||||
|
next->cookie = 1;
|
||||||
|
data->host_cookie = next->cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sg_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sdmmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq,
|
||||||
|
bool is_first_req)
|
||||||
|
{
|
||||||
|
struct realtek_pci_sdmmc *host = mmc_priv(mmc);
|
||||||
|
struct mmc_data *data = mrq->data;
|
||||||
|
|
||||||
|
if (data->host_cookie) {
|
||||||
|
dev_err(sdmmc_dev(host),
|
||||||
|
"error: descard already cookie data[%d]\n",
|
||||||
|
data->host_cookie);
|
||||||
|
data->host_cookie = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_dbg(sdmmc_dev(host), "dma sg prepared: %d\n",
|
||||||
|
sd_pre_dma_transfer(host, data, &host->next_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sdmmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
|
||||||
|
int err)
|
||||||
|
{
|
||||||
|
struct realtek_pci_sdmmc *host = mmc_priv(mmc);
|
||||||
|
struct rtsx_pcr *pcr = host->pcr;
|
||||||
|
struct mmc_data *data = mrq->data;
|
||||||
|
int read = data->flags & MMC_DATA_READ;
|
||||||
|
|
||||||
|
rtsx_pci_dma_unmap_sg(pcr, data->sg, data->sg_len, read);
|
||||||
|
data->host_cookie = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sd_start_multi_rw(struct realtek_pci_sdmmc *host,
|
||||||
|
struct mmc_request *mrq)
|
||||||
{
|
{
|
||||||
struct rtsx_pcr *pcr = host->pcr;
|
struct rtsx_pcr *pcr = host->pcr;
|
||||||
struct mmc_host *mmc = host->mmc;
|
struct mmc_host *mmc = host->mmc;
|
||||||
struct mmc_card *card = mmc->card;
|
struct mmc_card *card = mmc->card;
|
||||||
struct mmc_data *data = mrq->data;
|
struct mmc_data *data = mrq->data;
|
||||||
int uhs = mmc_card_uhs(card);
|
int uhs = mmc_card_uhs(card);
|
||||||
int read = (data->flags & MMC_DATA_READ) ? 1 : 0;
|
int read = data->flags & MMC_DATA_READ;
|
||||||
u8 cfg2, trans_mode;
|
u8 cfg2, trans_mode;
|
||||||
int err;
|
int err;
|
||||||
size_t data_len = data->blksz * data->blocks;
|
size_t data_len = data->blksz * data->blocks;
|
||||||
|
|
||||||
|
if (host->data)
|
||||||
|
dev_err(sdmmc_dev(host), "error: data already exist\n");
|
||||||
|
|
||||||
|
host->data = data;
|
||||||
|
|
||||||
if (read) {
|
if (read) {
|
||||||
cfg2 = SD_CALCULATE_CRC7 | SD_CHECK_CRC16 |
|
cfg2 = SD_CALCULATE_CRC7 | SD_CHECK_CRC16 |
|
||||||
SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_0;
|
SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_0;
|
||||||
@ -420,15 +635,54 @@ static int sd_rw_multi(struct realtek_pci_sdmmc *host, struct mmc_request *mrq)
|
|||||||
rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, SD_TRANSFER,
|
rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, SD_TRANSFER,
|
||||||
SD_TRANSFER_END, SD_TRANSFER_END);
|
SD_TRANSFER_END, SD_TRANSFER_END);
|
||||||
|
|
||||||
|
mod_timer(&host->timer, jiffies + 10 * HZ);
|
||||||
rtsx_pci_send_cmd_no_wait(pcr);
|
rtsx_pci_send_cmd_no_wait(pcr);
|
||||||
|
|
||||||
err = rtsx_pci_transfer_data(pcr, data->sg, data->sg_len, read, 10000);
|
err = rtsx_pci_dma_transfer(pcr, data->sg, host->sg_count, read);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
sd_clear_error(host);
|
data->error = err;
|
||||||
return err;
|
tasklet_schedule(&host->finish_tasklet);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sd_finish_multi_rw(unsigned long host_addr)
|
||||||
|
{
|
||||||
|
struct realtek_pci_sdmmc *host = (struct realtek_pci_sdmmc *)host_addr;
|
||||||
|
struct rtsx_pcr *pcr = host->pcr;
|
||||||
|
struct mmc_data *data;
|
||||||
|
int err = 0;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
|
|
||||||
|
if (!host->data) {
|
||||||
|
dev_err(sdmmc_dev(host), "error: no data exist\n");
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
data = host->data;
|
||||||
|
host->data = NULL;
|
||||||
|
|
||||||
|
if (pcr->trans_result == TRANS_NO_DEVICE)
|
||||||
|
err = -ENODEV;
|
||||||
|
else if (pcr->trans_result != TRANS_RESULT_OK)
|
||||||
|
err = -EINVAL;
|
||||||
|
|
||||||
|
if (err < 0) {
|
||||||
|
data->error = err;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!host->mrq->sbc && data->stop) {
|
||||||
|
sd_send_cmd(host, data->stop);
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
tasklet_schedule(&host->finish_tasklet);
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void sd_enable_initial_mode(struct realtek_pci_sdmmc *host)
|
static inline void sd_enable_initial_mode(struct realtek_pci_sdmmc *host)
|
||||||
@ -511,85 +765,47 @@ static int sd_change_phase(struct realtek_pci_sdmmc *host,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline u32 test_phase_bit(u32 phase_map, unsigned int bit)
|
||||||
|
{
|
||||||
|
bit %= RTSX_PHASE_MAX;
|
||||||
|
return phase_map & (1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sd_get_phase_len(u32 phase_map, unsigned int start_bit)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < RTSX_PHASE_MAX; i++) {
|
||||||
|
if (test_phase_bit(phase_map, start_bit + i) == 0)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return RTSX_PHASE_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
static u8 sd_search_final_phase(struct realtek_pci_sdmmc *host, u32 phase_map)
|
static u8 sd_search_final_phase(struct realtek_pci_sdmmc *host, u32 phase_map)
|
||||||
{
|
{
|
||||||
struct timing_phase_path path[MAX_PHASE + 1];
|
int start = 0, len = 0;
|
||||||
int i, j, cont_path_cnt;
|
int start_final = 0, len_final = 0;
|
||||||
int new_block, max_len, final_path_idx;
|
|
||||||
u8 final_phase = 0xFF;
|
u8 final_phase = 0xFF;
|
||||||
|
|
||||||
/* Parse phase_map, take it as a bit-ring */
|
if (phase_map == 0) {
|
||||||
cont_path_cnt = 0;
|
dev_err(sdmmc_dev(host), "phase error: [map:%x]\n", phase_map);
|
||||||
new_block = 1;
|
return final_phase;
|
||||||
j = 0;
|
}
|
||||||
for (i = 0; i < MAX_PHASE + 1; i++) {
|
|
||||||
if (phase_map & (1 << i)) {
|
while (start < RTSX_PHASE_MAX) {
|
||||||
if (new_block) {
|
len = sd_get_phase_len(phase_map, start);
|
||||||
new_block = 0;
|
if (len_final < len) {
|
||||||
j = cont_path_cnt++;
|
start_final = start;
|
||||||
path[j].start = i;
|
len_final = len;
|
||||||
path[j].end = i;
|
|
||||||
} else {
|
|
||||||
path[j].end = i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_block = 1;
|
|
||||||
if (cont_path_cnt) {
|
|
||||||
/* Calculate path length and middle point */
|
|
||||||
int idx = cont_path_cnt - 1;
|
|
||||||
path[idx].len =
|
|
||||||
path[idx].end - path[idx].start + 1;
|
|
||||||
path[idx].mid =
|
|
||||||
path[idx].start + path[idx].len / 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
start += len ? len : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cont_path_cnt == 0) {
|
final_phase = (start_final + len_final / 2) % RTSX_PHASE_MAX;
|
||||||
dev_dbg(sdmmc_dev(host), "No continuous phase path\n");
|
dev_dbg(sdmmc_dev(host), "phase: [map:%x] [maxlen:%d] [final:%d]\n",
|
||||||
goto finish;
|
phase_map, len_final, final_phase);
|
||||||
} else {
|
|
||||||
/* Calculate last continuous path length and middle point */
|
|
||||||
int idx = cont_path_cnt - 1;
|
|
||||||
path[idx].len = path[idx].end - path[idx].start + 1;
|
|
||||||
path[idx].mid = path[idx].start + path[idx].len / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Connect the first and last continuous paths if they are adjacent */
|
|
||||||
if (!path[0].start && (path[cont_path_cnt - 1].end == MAX_PHASE)) {
|
|
||||||
/* Using negative index */
|
|
||||||
path[0].start = path[cont_path_cnt - 1].start - MAX_PHASE - 1;
|
|
||||||
path[0].len += path[cont_path_cnt - 1].len;
|
|
||||||
path[0].mid = path[0].start + path[0].len / 2;
|
|
||||||
/* Convert negative middle point index to positive one */
|
|
||||||
if (path[0].mid < 0)
|
|
||||||
path[0].mid += MAX_PHASE + 1;
|
|
||||||
cont_path_cnt--;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Choose the longest continuous phase path */
|
|
||||||
max_len = 0;
|
|
||||||
final_phase = 0;
|
|
||||||
final_path_idx = 0;
|
|
||||||
for (i = 0; i < cont_path_cnt; i++) {
|
|
||||||
if (path[i].len > max_len) {
|
|
||||||
max_len = path[i].len;
|
|
||||||
final_phase = (u8)path[i].mid;
|
|
||||||
final_path_idx = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
dev_dbg(sdmmc_dev(host), "path[%d].start = %d\n",
|
|
||||||
i, path[i].start);
|
|
||||||
dev_dbg(sdmmc_dev(host), "path[%d].end = %d\n",
|
|
||||||
i, path[i].end);
|
|
||||||
dev_dbg(sdmmc_dev(host), "path[%d].len = %d\n",
|
|
||||||
i, path[i].len);
|
|
||||||
dev_dbg(sdmmc_dev(host), "path[%d].mid = %d\n",
|
|
||||||
i, path[i].mid);
|
|
||||||
}
|
|
||||||
|
|
||||||
finish:
|
|
||||||
dev_dbg(sdmmc_dev(host), "Final chosen phase: %d\n", final_phase);
|
|
||||||
return final_phase;
|
return final_phase;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,7 +851,7 @@ static int sd_tuning_phase(struct realtek_pci_sdmmc *host,
|
|||||||
int err, i;
|
int err, i;
|
||||||
u32 raw_phase_map = 0;
|
u32 raw_phase_map = 0;
|
||||||
|
|
||||||
for (i = MAX_PHASE; i >= 0; i--) {
|
for (i = 0; i < RTSX_PHASE_MAX; i++) {
|
||||||
err = sd_tuning_rx_cmd(host, opcode, (u8)i);
|
err = sd_tuning_rx_cmd(host, opcode, (u8)i);
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
raw_phase_map |= 1 << i;
|
raw_phase_map |= 1 << i;
|
||||||
@ -685,6 +901,13 @@ static int sd_tuning_rx(struct realtek_pci_sdmmc *host, u8 opcode)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool sd_use_muti_rw(struct mmc_command *cmd)
|
||||||
|
{
|
||||||
|
return mmc_op_multi(cmd->opcode) ||
|
||||||
|
(cmd->opcode == MMC_READ_SINGLE_BLOCK) ||
|
||||||
|
(cmd->opcode == MMC_WRITE_BLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||||
{
|
{
|
||||||
struct realtek_pci_sdmmc *host = mmc_priv(mmc);
|
struct realtek_pci_sdmmc *host = mmc_priv(mmc);
|
||||||
@ -693,6 +916,14 @@ static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|||||||
struct mmc_data *data = mrq->data;
|
struct mmc_data *data = mrq->data;
|
||||||
unsigned int data_size = 0;
|
unsigned int data_size = 0;
|
||||||
int err;
|
int err;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
mutex_lock(&pcr->pcr_mutex);
|
||||||
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
|
|
||||||
|
if (host->mrq)
|
||||||
|
dev_err(sdmmc_dev(host), "error: request already exist\n");
|
||||||
|
host->mrq = mrq;
|
||||||
|
|
||||||
if (host->eject) {
|
if (host->eject) {
|
||||||
cmd->error = -ENOMEDIUM;
|
cmd->error = -ENOMEDIUM;
|
||||||
@ -705,8 +936,6 @@ static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(&pcr->pcr_mutex);
|
|
||||||
|
|
||||||
rtsx_pci_start_run(pcr);
|
rtsx_pci_start_run(pcr);
|
||||||
|
|
||||||
rtsx_pci_switch_clock(pcr, host->clock, host->ssc_depth,
|
rtsx_pci_switch_clock(pcr, host->clock, host->ssc_depth,
|
||||||
@ -715,46 +944,28 @@ static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|||||||
rtsx_pci_write_register(pcr, CARD_SHARE_MODE,
|
rtsx_pci_write_register(pcr, CARD_SHARE_MODE,
|
||||||
CARD_SHARE_MASK, CARD_SHARE_48_SD);
|
CARD_SHARE_MASK, CARD_SHARE_48_SD);
|
||||||
|
|
||||||
mutex_lock(&host->host_mutex);
|
|
||||||
host->mrq = mrq;
|
|
||||||
mutex_unlock(&host->host_mutex);
|
|
||||||
|
|
||||||
if (mrq->data)
|
if (mrq->data)
|
||||||
data_size = data->blocks * data->blksz;
|
data_size = data->blocks * data->blksz;
|
||||||
|
|
||||||
if (!data_size || mmc_op_multi(cmd->opcode) ||
|
if (sd_use_muti_rw(cmd))
|
||||||
(cmd->opcode == MMC_READ_SINGLE_BLOCK) ||
|
host->sg_count = sd_pre_dma_transfer(host, data, NULL);
|
||||||
(cmd->opcode == MMC_WRITE_BLOCK)) {
|
|
||||||
sd_send_cmd_get_rsp(host, cmd);
|
|
||||||
|
|
||||||
if (!cmd->error && data_size) {
|
if (!data_size || sd_use_muti_rw(cmd)) {
|
||||||
sd_rw_multi(host, mrq);
|
if (mrq->sbc)
|
||||||
|
sd_send_cmd(host, mrq->sbc);
|
||||||
if (mmc_op_multi(cmd->opcode) && mrq->stop)
|
|
||||||
sd_send_cmd_get_rsp(host, mrq->stop);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sd_normal_rw(host, mrq);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mrq->data) {
|
|
||||||
if (cmd->error || data->error)
|
|
||||||
data->bytes_xfered = 0;
|
|
||||||
else
|
else
|
||||||
data->bytes_xfered = data->blocks * data->blksz;
|
sd_send_cmd(host, cmd);
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
} else {
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
sd_normal_rw(host, mrq);
|
||||||
|
tasklet_schedule(&host->finish_tasklet);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
mutex_unlock(&pcr->pcr_mutex);
|
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
if (cmd->error)
|
tasklet_schedule(&host->finish_tasklet);
|
||||||
dev_dbg(sdmmc_dev(host), "cmd->error = %d\n", cmd->error);
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
|
||||||
mutex_lock(&host->host_mutex);
|
|
||||||
host->mrq = NULL;
|
|
||||||
mutex_unlock(&host->host_mutex);
|
|
||||||
|
|
||||||
mmc_request_done(mmc, mrq);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sd_set_bus_width(struct realtek_pci_sdmmc *host,
|
static int sd_set_bus_width(struct realtek_pci_sdmmc *host,
|
||||||
@ -1189,6 +1400,8 @@ out:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const struct mmc_host_ops realtek_pci_sdmmc_ops = {
|
static const struct mmc_host_ops realtek_pci_sdmmc_ops = {
|
||||||
|
.pre_req = sdmmc_pre_req,
|
||||||
|
.post_req = sdmmc_post_req,
|
||||||
.request = sdmmc_request,
|
.request = sdmmc_request,
|
||||||
.set_ios = sdmmc_set_ios,
|
.set_ios = sdmmc_set_ios,
|
||||||
.get_ro = sdmmc_get_ro,
|
.get_ro = sdmmc_get_ro,
|
||||||
@ -1252,6 +1465,7 @@ static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev)
|
|||||||
struct realtek_pci_sdmmc *host;
|
struct realtek_pci_sdmmc *host;
|
||||||
struct rtsx_pcr *pcr;
|
struct rtsx_pcr *pcr;
|
||||||
struct pcr_handle *handle = pdev->dev.platform_data;
|
struct pcr_handle *handle = pdev->dev.platform_data;
|
||||||
|
unsigned long host_addr;
|
||||||
|
|
||||||
if (!handle)
|
if (!handle)
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
@ -1275,8 +1489,15 @@ static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev)
|
|||||||
pcr->slots[RTSX_SD_CARD].p_dev = pdev;
|
pcr->slots[RTSX_SD_CARD].p_dev = pdev;
|
||||||
pcr->slots[RTSX_SD_CARD].card_event = rtsx_pci_sdmmc_card_event;
|
pcr->slots[RTSX_SD_CARD].card_event = rtsx_pci_sdmmc_card_event;
|
||||||
|
|
||||||
mutex_init(&host->host_mutex);
|
host_addr = (unsigned long)host;
|
||||||
|
host->next_data.cookie = 1;
|
||||||
|
setup_timer(&host->timer, sd_request_timeout, host_addr);
|
||||||
|
tasklet_init(&host->cmd_tasklet, sd_get_rsp, host_addr);
|
||||||
|
tasklet_init(&host->data_tasklet, sd_finish_multi_rw, host_addr);
|
||||||
|
tasklet_init(&host->finish_tasklet, sd_finish_request, host_addr);
|
||||||
|
spin_lock_init(&host->lock);
|
||||||
|
|
||||||
|
pcr->slots[RTSX_SD_CARD].done_transfer = sd_isr_done_transfer;
|
||||||
realtek_init_host(host);
|
realtek_init_host(host);
|
||||||
|
|
||||||
mmc_add_host(mmc);
|
mmc_add_host(mmc);
|
||||||
@ -1289,6 +1510,8 @@ static int rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev)
|
|||||||
struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev);
|
struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev);
|
||||||
struct rtsx_pcr *pcr;
|
struct rtsx_pcr *pcr;
|
||||||
struct mmc_host *mmc;
|
struct mmc_host *mmc;
|
||||||
|
struct mmc_request *mrq;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
if (!host)
|
if (!host)
|
||||||
return 0;
|
return 0;
|
||||||
@ -1296,25 +1519,37 @@ static int rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev)
|
|||||||
pcr = host->pcr;
|
pcr = host->pcr;
|
||||||
pcr->slots[RTSX_SD_CARD].p_dev = NULL;
|
pcr->slots[RTSX_SD_CARD].p_dev = NULL;
|
||||||
pcr->slots[RTSX_SD_CARD].card_event = NULL;
|
pcr->slots[RTSX_SD_CARD].card_event = NULL;
|
||||||
|
pcr->slots[RTSX_SD_CARD].done_transfer = NULL;
|
||||||
mmc = host->mmc;
|
mmc = host->mmc;
|
||||||
host->eject = true;
|
mrq = host->mrq;
|
||||||
|
|
||||||
mutex_lock(&host->host_mutex);
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
if (host->mrq) {
|
if (host->mrq) {
|
||||||
dev_dbg(&(pdev->dev),
|
dev_dbg(&(pdev->dev),
|
||||||
"%s: Controller removed during transfer\n",
|
"%s: Controller removed during transfer\n",
|
||||||
mmc_hostname(mmc));
|
mmc_hostname(mmc));
|
||||||
|
|
||||||
rtsx_pci_complete_unfinished_transfer(pcr);
|
if (mrq->sbc)
|
||||||
|
mrq->sbc->error = -ENOMEDIUM;
|
||||||
|
if (mrq->cmd)
|
||||||
|
mrq->cmd->error = -ENOMEDIUM;
|
||||||
|
if (mrq->stop)
|
||||||
|
mrq->stop->error = -ENOMEDIUM;
|
||||||
|
if (mrq->data)
|
||||||
|
mrq->data->error = -ENOMEDIUM;
|
||||||
|
|
||||||
host->mrq->cmd->error = -ENOMEDIUM;
|
tasklet_schedule(&host->finish_tasklet);
|
||||||
if (host->mrq->stop)
|
|
||||||
host->mrq->stop->error = -ENOMEDIUM;
|
|
||||||
mmc_request_done(mmc, host->mrq);
|
|
||||||
}
|
}
|
||||||
mutex_unlock(&host->host_mutex);
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
|
||||||
|
del_timer_sync(&host->timer);
|
||||||
|
tasklet_kill(&host->cmd_tasklet);
|
||||||
|
tasklet_kill(&host->data_tasklet);
|
||||||
|
tasklet_kill(&host->finish_tasklet);
|
||||||
|
|
||||||
mmc_remove_host(mmc);
|
mmc_remove_host(mmc);
|
||||||
|
host->eject = true;
|
||||||
|
|
||||||
mmc_free_host(mmc);
|
mmc_free_host(mmc);
|
||||||
|
|
||||||
dev_dbg(&(pdev->dev),
|
dev_dbg(&(pdev->dev),
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/gpio/consumer.h>
|
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/acpi.h>
|
#include <linux/acpi.h>
|
||||||
#include <linux/pm.h>
|
#include <linux/pm.h>
|
||||||
@ -40,13 +39,15 @@
|
|||||||
|
|
||||||
#include <linux/mmc/host.h>
|
#include <linux/mmc/host.h>
|
||||||
#include <linux/mmc/pm.h>
|
#include <linux/mmc/pm.h>
|
||||||
|
#include <linux/mmc/slot-gpio.h>
|
||||||
#include <linux/mmc/sdhci.h>
|
#include <linux/mmc/sdhci.h>
|
||||||
|
|
||||||
#include "sdhci.h"
|
#include "sdhci.h"
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
SDHCI_ACPI_SD_CD = BIT(0),
|
SDHCI_ACPI_SD_CD = BIT(0),
|
||||||
SDHCI_ACPI_RUNTIME_PM = BIT(1),
|
SDHCI_ACPI_RUNTIME_PM = BIT(1),
|
||||||
|
SDHCI_ACPI_SD_CD_OVERRIDE_LEVEL = BIT(2),
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sdhci_acpi_chip {
|
struct sdhci_acpi_chip {
|
||||||
@ -121,6 +122,7 @@ static const struct sdhci_acpi_slot sdhci_acpi_slot_int_emmc = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sdio = {
|
static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sdio = {
|
||||||
|
.quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION,
|
||||||
.quirks2 = SDHCI_QUIRK2_HOST_OFF_CARD_ON,
|
.quirks2 = SDHCI_QUIRK2_HOST_OFF_CARD_ON,
|
||||||
.caps = MMC_CAP_NONREMOVABLE | MMC_CAP_POWER_OFF_CARD,
|
.caps = MMC_CAP_NONREMOVABLE | MMC_CAP_POWER_OFF_CARD,
|
||||||
.flags = SDHCI_ACPI_RUNTIME_PM,
|
.flags = SDHCI_ACPI_RUNTIME_PM,
|
||||||
@ -128,7 +130,8 @@ static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sdio = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sd = {
|
static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sd = {
|
||||||
.flags = SDHCI_ACPI_SD_CD | SDHCI_ACPI_RUNTIME_PM,
|
.flags = SDHCI_ACPI_SD_CD | SDHCI_ACPI_SD_CD_OVERRIDE_LEVEL |
|
||||||
|
SDHCI_ACPI_RUNTIME_PM,
|
||||||
.quirks2 = SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON,
|
.quirks2 = SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -141,6 +144,7 @@ struct sdhci_acpi_uid_slot {
|
|||||||
static const struct sdhci_acpi_uid_slot sdhci_acpi_uids[] = {
|
static const struct sdhci_acpi_uid_slot sdhci_acpi_uids[] = {
|
||||||
{ "80860F14" , "1" , &sdhci_acpi_slot_int_emmc },
|
{ "80860F14" , "1" , &sdhci_acpi_slot_int_emmc },
|
||||||
{ "80860F14" , "3" , &sdhci_acpi_slot_int_sd },
|
{ "80860F14" , "3" , &sdhci_acpi_slot_int_sd },
|
||||||
|
{ "80860F16" , NULL, &sdhci_acpi_slot_int_sd },
|
||||||
{ "INT33BB" , "2" , &sdhci_acpi_slot_int_sdio },
|
{ "INT33BB" , "2" , &sdhci_acpi_slot_int_sdio },
|
||||||
{ "INT33C6" , NULL, &sdhci_acpi_slot_int_sdio },
|
{ "INT33C6" , NULL, &sdhci_acpi_slot_int_sdio },
|
||||||
{ "INT3436" , NULL, &sdhci_acpi_slot_int_sdio },
|
{ "INT3436" , NULL, &sdhci_acpi_slot_int_sdio },
|
||||||
@ -150,6 +154,7 @@ static const struct sdhci_acpi_uid_slot sdhci_acpi_uids[] = {
|
|||||||
|
|
||||||
static const struct acpi_device_id sdhci_acpi_ids[] = {
|
static const struct acpi_device_id sdhci_acpi_ids[] = {
|
||||||
{ "80860F14" },
|
{ "80860F14" },
|
||||||
|
{ "80860F16" },
|
||||||
{ "INT33BB" },
|
{ "INT33BB" },
|
||||||
{ "INT33C6" },
|
{ "INT33C6" },
|
||||||
{ "INT3436" },
|
{ "INT3436" },
|
||||||
@ -192,59 +197,6 @@ static const struct sdhci_acpi_slot *sdhci_acpi_get_slot(acpi_handle handle,
|
|||||||
return slot;
|
return slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_PM_RUNTIME
|
|
||||||
|
|
||||||
static irqreturn_t sdhci_acpi_sd_cd(int irq, void *dev_id)
|
|
||||||
{
|
|
||||||
mmc_detect_change(dev_id, msecs_to_jiffies(200));
|
|
||||||
return IRQ_HANDLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int sdhci_acpi_add_own_cd(struct device *dev, struct mmc_host *mmc)
|
|
||||||
{
|
|
||||||
struct gpio_desc *desc;
|
|
||||||
unsigned long flags;
|
|
||||||
int err, irq;
|
|
||||||
|
|
||||||
desc = devm_gpiod_get_index(dev, "sd_cd", 0);
|
|
||||||
if (IS_ERR(desc)) {
|
|
||||||
err = PTR_ERR(desc);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = gpiod_direction_input(desc);
|
|
||||||
if (err)
|
|
||||||
goto out_free;
|
|
||||||
|
|
||||||
irq = gpiod_to_irq(desc);
|
|
||||||
if (irq < 0) {
|
|
||||||
err = irq;
|
|
||||||
goto out_free;
|
|
||||||
}
|
|
||||||
|
|
||||||
flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
|
|
||||||
err = devm_request_irq(dev, irq, sdhci_acpi_sd_cd, flags, "sd_cd", mmc);
|
|
||||||
if (err)
|
|
||||||
goto out_free;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
out_free:
|
|
||||||
devm_gpiod_put(dev, desc);
|
|
||||||
out:
|
|
||||||
dev_warn(dev, "failed to setup card detect wake up\n");
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
static int sdhci_acpi_add_own_cd(struct device *dev, struct mmc_host *mmc)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int sdhci_acpi_probe(struct platform_device *pdev)
|
static int sdhci_acpi_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
@ -332,15 +284,19 @@ static int sdhci_acpi_probe(struct platform_device *pdev)
|
|||||||
|
|
||||||
host->mmc->caps2 |= MMC_CAP2_NO_PRESCAN_POWERUP;
|
host->mmc->caps2 |= MMC_CAP2_NO_PRESCAN_POWERUP;
|
||||||
|
|
||||||
|
if (sdhci_acpi_flag(c, SDHCI_ACPI_SD_CD)) {
|
||||||
|
bool v = sdhci_acpi_flag(c, SDHCI_ACPI_SD_CD_OVERRIDE_LEVEL);
|
||||||
|
|
||||||
|
if (mmc_gpiod_request_cd(host->mmc, NULL, 0, v, 0)) {
|
||||||
|
dev_warn(dev, "failed to setup card detect gpio\n");
|
||||||
|
c->use_runtime_pm = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = sdhci_add_host(host);
|
err = sdhci_add_host(host);
|
||||||
if (err)
|
if (err)
|
||||||
goto err_free;
|
goto err_free;
|
||||||
|
|
||||||
if (sdhci_acpi_flag(c, SDHCI_ACPI_SD_CD)) {
|
|
||||||
if (sdhci_acpi_add_own_cd(dev, host->mmc))
|
|
||||||
c->use_runtime_pm = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c->use_runtime_pm) {
|
if (c->use_runtime_pm) {
|
||||||
pm_runtime_set_active(dev);
|
pm_runtime_set_active(dev);
|
||||||
pm_suspend_ignore_children(dev, 1);
|
pm_suspend_ignore_children(dev, 1);
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
|
|
||||||
struct sdhci_bcm_kona_dev {
|
struct sdhci_bcm_kona_dev {
|
||||||
struct mutex write_lock; /* protect back to back writes */
|
struct mutex write_lock; /* protect back to back writes */
|
||||||
|
struct clk *external_clk;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -257,6 +258,24 @@ static int sdhci_bcm_kona_probe(struct platform_device *pdev)
|
|||||||
goto err_pltfm_free;
|
goto err_pltfm_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get and enable the external clock */
|
||||||
|
kona_dev->external_clk = devm_clk_get(dev, NULL);
|
||||||
|
if (IS_ERR(kona_dev->external_clk)) {
|
||||||
|
dev_err(dev, "Failed to get external clock\n");
|
||||||
|
ret = PTR_ERR(kona_dev->external_clk);
|
||||||
|
goto err_pltfm_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clk_set_rate(kona_dev->external_clk, host->mmc->f_max) != 0) {
|
||||||
|
dev_err(dev, "Failed to set rate external clock\n");
|
||||||
|
goto err_pltfm_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clk_prepare_enable(kona_dev->external_clk) != 0) {
|
||||||
|
dev_err(dev, "Failed to enable external clock\n");
|
||||||
|
goto err_pltfm_free;
|
||||||
|
}
|
||||||
|
|
||||||
dev_dbg(dev, "non-removable=%c\n",
|
dev_dbg(dev, "non-removable=%c\n",
|
||||||
(host->mmc->caps & MMC_CAP_NONREMOVABLE) ? 'Y' : 'N');
|
(host->mmc->caps & MMC_CAP_NONREMOVABLE) ? 'Y' : 'N');
|
||||||
dev_dbg(dev, "cd_gpio %c, wp_gpio %c\n",
|
dev_dbg(dev, "cd_gpio %c, wp_gpio %c\n",
|
||||||
@ -271,7 +290,7 @@ static int sdhci_bcm_kona_probe(struct platform_device *pdev)
|
|||||||
|
|
||||||
ret = sdhci_bcm_kona_sd_reset(host);
|
ret = sdhci_bcm_kona_sd_reset(host);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto err_pltfm_free;
|
goto err_clk_disable;
|
||||||
|
|
||||||
sdhci_bcm_kona_sd_init(host);
|
sdhci_bcm_kona_sd_init(host);
|
||||||
|
|
||||||
@ -307,6 +326,9 @@ err_remove_host:
|
|||||||
err_reset:
|
err_reset:
|
||||||
sdhci_bcm_kona_sd_reset(host);
|
sdhci_bcm_kona_sd_reset(host);
|
||||||
|
|
||||||
|
err_clk_disable:
|
||||||
|
clk_disable_unprepare(kona_dev->external_clk);
|
||||||
|
|
||||||
err_pltfm_free:
|
err_pltfm_free:
|
||||||
sdhci_pltfm_free(pdev);
|
sdhci_pltfm_free(pdev);
|
||||||
|
|
||||||
@ -314,9 +336,20 @@ err_pltfm_free:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __exit sdhci_bcm_kona_remove(struct platform_device *pdev)
|
static int sdhci_bcm_kona_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
return sdhci_pltfm_unregister(pdev);
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
||||||
|
struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host);
|
||||||
|
struct sdhci_bcm_kona_dev *kona_dev = sdhci_pltfm_priv(pltfm_priv);
|
||||||
|
int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
|
||||||
|
|
||||||
|
sdhci_remove_host(host, dead);
|
||||||
|
|
||||||
|
clk_disable_unprepare(kona_dev->external_clk);
|
||||||
|
|
||||||
|
sdhci_pltfm_free(pdev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct platform_driver sdhci_bcm_kona_driver = {
|
static struct platform_driver sdhci_bcm_kona_driver = {
|
||||||
|
@ -208,7 +208,7 @@ static struct platform_driver sdhci_dove_driver = {
|
|||||||
.name = "sdhci-dove",
|
.name = "sdhci-dove",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.pm = SDHCI_PLTFM_PMOPS,
|
.pm = SDHCI_PLTFM_PMOPS,
|
||||||
.of_match_table = of_match_ptr(sdhci_dove_of_match_table),
|
.of_match_table = sdhci_dove_of_match_table,
|
||||||
},
|
},
|
||||||
.probe = sdhci_dove_probe,
|
.probe = sdhci_dove_probe,
|
||||||
.remove = sdhci_dove_remove,
|
.remove = sdhci_dove_remove,
|
||||||
|
618
drivers/mmc/host/sdhci-msm.c
Normal file
618
drivers/mmc/host/sdhci-msm.c
Normal file
@ -0,0 +1,618 @@
|
|||||||
|
/*
|
||||||
|
* drivers/mmc/host/sdhci-msm.c - Qualcomm SDHCI Platform driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 and
|
||||||
|
* only version 2 as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
#include <linux/regulator/consumer.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/mmc/mmc.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
#include "sdhci-pltfm.h"
|
||||||
|
|
||||||
|
#define CORE_HC_MODE 0x78
|
||||||
|
#define HC_MODE_EN 0x1
|
||||||
|
#define CORE_POWER 0x0
|
||||||
|
#define CORE_SW_RST BIT(7)
|
||||||
|
|
||||||
|
#define MAX_PHASES 16
|
||||||
|
#define CORE_DLL_LOCK BIT(7)
|
||||||
|
#define CORE_DLL_EN BIT(16)
|
||||||
|
#define CORE_CDR_EN BIT(17)
|
||||||
|
#define CORE_CK_OUT_EN BIT(18)
|
||||||
|
#define CORE_CDR_EXT_EN BIT(19)
|
||||||
|
#define CORE_DLL_PDN BIT(29)
|
||||||
|
#define CORE_DLL_RST BIT(30)
|
||||||
|
#define CORE_DLL_CONFIG 0x100
|
||||||
|
#define CORE_DLL_STATUS 0x108
|
||||||
|
|
||||||
|
#define CORE_VENDOR_SPEC 0x10c
|
||||||
|
#define CORE_CLK_PWRSAVE BIT(1)
|
||||||
|
|
||||||
|
#define CDR_SELEXT_SHIFT 20
|
||||||
|
#define CDR_SELEXT_MASK (0xf << CDR_SELEXT_SHIFT)
|
||||||
|
#define CMUX_SHIFT_PHASE_SHIFT 24
|
||||||
|
#define CMUX_SHIFT_PHASE_MASK (7 << CMUX_SHIFT_PHASE_SHIFT)
|
||||||
|
|
||||||
|
static const u32 tuning_block_64[] = {
|
||||||
|
0x00ff0fff, 0xccc3ccff, 0xffcc3cc3, 0xeffefffe,
|
||||||
|
0xddffdfff, 0xfbfffbff, 0xff7fffbf, 0xefbdf777,
|
||||||
|
0xf0fff0ff, 0x3cccfc0f, 0xcfcc33cc, 0xeeffefff,
|
||||||
|
0xfdfffdff, 0xffbfffdf, 0xfff7ffbb, 0xde7b7ff7
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 tuning_block_128[] = {
|
||||||
|
0xff00ffff, 0x0000ffff, 0xccccffff, 0xcccc33cc,
|
||||||
|
0xcc3333cc, 0xffffcccc, 0xffffeeff, 0xffeeeeff,
|
||||||
|
0xffddffff, 0xddddffff, 0xbbffffff, 0xbbffffff,
|
||||||
|
0xffffffbb, 0xffffff77, 0x77ff7777, 0xffeeddbb,
|
||||||
|
0x00ffffff, 0x00ffffff, 0xccffff00, 0xcc33cccc,
|
||||||
|
0x3333cccc, 0xffcccccc, 0xffeeffff, 0xeeeeffff,
|
||||||
|
0xddffffff, 0xddffffff, 0xffffffdd, 0xffffffbb,
|
||||||
|
0xffffbbbb, 0xffff77ff, 0xff7777ff, 0xeeddbb77
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sdhci_msm_host {
|
||||||
|
struct platform_device *pdev;
|
||||||
|
void __iomem *core_mem; /* MSM SDCC mapped address */
|
||||||
|
struct clk *clk; /* main SD/MMC bus clock */
|
||||||
|
struct clk *pclk; /* SDHC peripheral bus clock */
|
||||||
|
struct clk *bus_clk; /* SDHC bus voter clock */
|
||||||
|
struct mmc_host *mmc;
|
||||||
|
struct sdhci_pltfm_data sdhci_msm_pdata;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Platform specific tuning */
|
||||||
|
static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host, u8 poll)
|
||||||
|
{
|
||||||
|
u32 wait_cnt = 50;
|
||||||
|
u8 ck_out_en;
|
||||||
|
struct mmc_host *mmc = host->mmc;
|
||||||
|
|
||||||
|
/* Poll for CK_OUT_EN bit. max. poll time = 50us */
|
||||||
|
ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) &
|
||||||
|
CORE_CK_OUT_EN);
|
||||||
|
|
||||||
|
while (ck_out_en != poll) {
|
||||||
|
if (--wait_cnt == 0) {
|
||||||
|
dev_err(mmc_dev(mmc), "%s: CK_OUT_EN bit is not %d\n",
|
||||||
|
mmc_hostname(mmc), poll);
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
udelay(1);
|
||||||
|
|
||||||
|
ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) &
|
||||||
|
CORE_CK_OUT_EN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
static const u8 grey_coded_phase_table[] = {
|
||||||
|
0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4,
|
||||||
|
0xc, 0xd, 0xf, 0xe, 0xa, 0xb, 0x9, 0x8
|
||||||
|
};
|
||||||
|
unsigned long flags;
|
||||||
|
u32 config;
|
||||||
|
struct mmc_host *mmc = host->mmc;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
|
|
||||||
|
config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
config &= ~(CORE_CDR_EN | CORE_CK_OUT_EN);
|
||||||
|
config |= (CORE_CDR_EXT_EN | CORE_DLL_EN);
|
||||||
|
writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
|
||||||
|
/* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '0' */
|
||||||
|
rc = msm_dll_poll_ck_out_en(host, 0);
|
||||||
|
if (rc)
|
||||||
|
goto err_out;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the selected DLL clock output phase (0 ... 15)
|
||||||
|
* to CDR_SELEXT bit field of DLL_CONFIG register.
|
||||||
|
*/
|
||||||
|
config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
config &= ~CDR_SELEXT_MASK;
|
||||||
|
config |= grey_coded_phase_table[phase] << CDR_SELEXT_SHIFT;
|
||||||
|
writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
|
||||||
|
/* Set CK_OUT_EN bit of DLL_CONFIG register to 1. */
|
||||||
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
|
||||||
|
| CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
|
||||||
|
/* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '1' */
|
||||||
|
rc = msm_dll_poll_ck_out_en(host, 1);
|
||||||
|
if (rc)
|
||||||
|
goto err_out;
|
||||||
|
|
||||||
|
config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
config |= CORE_CDR_EN;
|
||||||
|
config &= ~CORE_CDR_EXT_EN;
|
||||||
|
writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
err_out:
|
||||||
|
dev_err(mmc_dev(mmc), "%s: Failed to set DLL phase: %d\n",
|
||||||
|
mmc_hostname(mmc), phase);
|
||||||
|
out:
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find out the greatest range of consecuitive selected
|
||||||
|
* DLL clock output phases that can be used as sampling
|
||||||
|
* setting for SD3.0 UHS-I card read operation (in SDR104
|
||||||
|
* timing mode) or for eMMC4.5 card read operation (in HS200
|
||||||
|
* timing mode).
|
||||||
|
* Select the 3/4 of the range and configure the DLL with the
|
||||||
|
* selected DLL clock output phase.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int msm_find_most_appropriate_phase(struct sdhci_host *host,
|
||||||
|
u8 *phase_table, u8 total_phases)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} };
|
||||||
|
u8 phases_per_row[MAX_PHASES] = { 0 };
|
||||||
|
int row_index = 0, col_index = 0, selected_row_index = 0, curr_max = 0;
|
||||||
|
int i, cnt, phase_0_raw_index = 0, phase_15_raw_index = 0;
|
||||||
|
bool phase_0_found = false, phase_15_found = false;
|
||||||
|
struct mmc_host *mmc = host->mmc;
|
||||||
|
|
||||||
|
if (!total_phases || (total_phases > MAX_PHASES)) {
|
||||||
|
dev_err(mmc_dev(mmc), "%s: Invalid argument: total_phases=%d\n",
|
||||||
|
mmc_hostname(mmc), total_phases);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (cnt = 0; cnt < total_phases; cnt++) {
|
||||||
|
ranges[row_index][col_index] = phase_table[cnt];
|
||||||
|
phases_per_row[row_index] += 1;
|
||||||
|
col_index++;
|
||||||
|
|
||||||
|
if ((cnt + 1) == total_phases) {
|
||||||
|
continue;
|
||||||
|
/* check if next phase in phase_table is consecutive or not */
|
||||||
|
} else if ((phase_table[cnt] + 1) != phase_table[cnt + 1]) {
|
||||||
|
row_index++;
|
||||||
|
col_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row_index >= MAX_PHASES)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Check if phase-0 is present in first valid window? */
|
||||||
|
if (!ranges[0][0]) {
|
||||||
|
phase_0_found = true;
|
||||||
|
phase_0_raw_index = 0;
|
||||||
|
/* Check if cycle exist between 2 valid windows */
|
||||||
|
for (cnt = 1; cnt <= row_index; cnt++) {
|
||||||
|
if (phases_per_row[cnt]) {
|
||||||
|
for (i = 0; i < phases_per_row[cnt]; i++) {
|
||||||
|
if (ranges[cnt][i] == 15) {
|
||||||
|
phase_15_found = true;
|
||||||
|
phase_15_raw_index = cnt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If 2 valid windows form cycle then merge them as single window */
|
||||||
|
if (phase_0_found && phase_15_found) {
|
||||||
|
/* number of phases in raw where phase 0 is present */
|
||||||
|
u8 phases_0 = phases_per_row[phase_0_raw_index];
|
||||||
|
/* number of phases in raw where phase 15 is present */
|
||||||
|
u8 phases_15 = phases_per_row[phase_15_raw_index];
|
||||||
|
|
||||||
|
if (phases_0 + phases_15 >= MAX_PHASES)
|
||||||
|
/*
|
||||||
|
* If there are more than 1 phase windows then total
|
||||||
|
* number of phases in both the windows should not be
|
||||||
|
* more than or equal to MAX_PHASES.
|
||||||
|
*/
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Merge 2 cyclic windows */
|
||||||
|
i = phases_15;
|
||||||
|
for (cnt = 0; cnt < phases_0; cnt++) {
|
||||||
|
ranges[phase_15_raw_index][i] =
|
||||||
|
ranges[phase_0_raw_index][cnt];
|
||||||
|
if (++i >= MAX_PHASES)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
phases_per_row[phase_0_raw_index] = 0;
|
||||||
|
phases_per_row[phase_15_raw_index] = phases_15 + phases_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (cnt = 0; cnt <= row_index; cnt++) {
|
||||||
|
if (phases_per_row[cnt] > curr_max) {
|
||||||
|
curr_max = phases_per_row[cnt];
|
||||||
|
selected_row_index = cnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i = (curr_max * 3) / 4;
|
||||||
|
if (i)
|
||||||
|
i--;
|
||||||
|
|
||||||
|
ret = ranges[selected_row_index][i];
|
||||||
|
|
||||||
|
if (ret >= MAX_PHASES) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
dev_err(mmc_dev(mmc), "%s: Invalid phase selected=%d\n",
|
||||||
|
mmc_hostname(mmc), ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void msm_cm_dll_set_freq(struct sdhci_host *host)
|
||||||
|
{
|
||||||
|
u32 mclk_freq = 0, config;
|
||||||
|
|
||||||
|
/* Program the MCLK value to MCLK_FREQ bit field */
|
||||||
|
if (host->clock <= 112000000)
|
||||||
|
mclk_freq = 0;
|
||||||
|
else if (host->clock <= 125000000)
|
||||||
|
mclk_freq = 1;
|
||||||
|
else if (host->clock <= 137000000)
|
||||||
|
mclk_freq = 2;
|
||||||
|
else if (host->clock <= 150000000)
|
||||||
|
mclk_freq = 3;
|
||||||
|
else if (host->clock <= 162000000)
|
||||||
|
mclk_freq = 4;
|
||||||
|
else if (host->clock <= 175000000)
|
||||||
|
mclk_freq = 5;
|
||||||
|
else if (host->clock <= 187000000)
|
||||||
|
mclk_freq = 6;
|
||||||
|
else if (host->clock <= 200000000)
|
||||||
|
mclk_freq = 7;
|
||||||
|
|
||||||
|
config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
config &= ~CMUX_SHIFT_PHASE_MASK;
|
||||||
|
config |= mclk_freq << CMUX_SHIFT_PHASE_SHIFT;
|
||||||
|
writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize the DLL (Programmable Delay Line) */
|
||||||
|
static int msm_init_cm_dll(struct sdhci_host *host)
|
||||||
|
{
|
||||||
|
struct mmc_host *mmc = host->mmc;
|
||||||
|
int wait_cnt = 50;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&host->lock, flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure that clock is always enabled when DLL
|
||||||
|
* tuning is in progress. Keeping PWRSAVE ON may
|
||||||
|
* turn off the clock.
|
||||||
|
*/
|
||||||
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC)
|
||||||
|
& ~CORE_CLK_PWRSAVE), host->ioaddr + CORE_VENDOR_SPEC);
|
||||||
|
|
||||||
|
/* Write 1 to DLL_RST bit of DLL_CONFIG register */
|
||||||
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
|
||||||
|
| CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
|
||||||
|
/* Write 1 to DLL_PDN bit of DLL_CONFIG register */
|
||||||
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
|
||||||
|
| CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
msm_cm_dll_set_freq(host);
|
||||||
|
|
||||||
|
/* Write 0 to DLL_RST bit of DLL_CONFIG register */
|
||||||
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
|
||||||
|
& ~CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
|
||||||
|
/* Write 0 to DLL_PDN bit of DLL_CONFIG register */
|
||||||
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
|
||||||
|
& ~CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
|
||||||
|
/* Set DLL_EN bit to 1. */
|
||||||
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
|
||||||
|
| CORE_DLL_EN), host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
|
||||||
|
/* Set CK_OUT_EN bit to 1. */
|
||||||
|
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
|
||||||
|
| CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG);
|
||||||
|
|
||||||
|
/* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */
|
||||||
|
while (!(readl_relaxed(host->ioaddr + CORE_DLL_STATUS) &
|
||||||
|
CORE_DLL_LOCK)) {
|
||||||
|
/* max. wait for 50us sec for LOCK bit to be set */
|
||||||
|
if (--wait_cnt == 0) {
|
||||||
|
dev_err(mmc_dev(mmc), "%s: DLL failed to LOCK\n",
|
||||||
|
mmc_hostname(mmc));
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
udelay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&host->lock, flags);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||||
|
{
|
||||||
|
int tuning_seq_cnt = 3;
|
||||||
|
u8 phase, *data_buf, tuned_phases[16], tuned_phase_cnt = 0;
|
||||||
|
const u32 *tuning_block_pattern = tuning_block_64;
|
||||||
|
int size = sizeof(tuning_block_64); /* Pattern size in bytes */
|
||||||
|
int rc;
|
||||||
|
struct mmc_host *mmc = host->mmc;
|
||||||
|
struct mmc_ios ios = host->mmc->ios;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tuning is required for SDR104, HS200 and HS400 cards and
|
||||||
|
* if clock frequency is greater than 100MHz in these modes.
|
||||||
|
*/
|
||||||
|
if (host->clock <= 100 * 1000 * 1000 ||
|
||||||
|
!((ios.timing == MMC_TIMING_MMC_HS200) ||
|
||||||
|
(ios.timing == MMC_TIMING_UHS_SDR104)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if ((opcode == MMC_SEND_TUNING_BLOCK_HS200) &&
|
||||||
|
(mmc->ios.bus_width == MMC_BUS_WIDTH_8)) {
|
||||||
|
tuning_block_pattern = tuning_block_128;
|
||||||
|
size = sizeof(tuning_block_128);
|
||||||
|
}
|
||||||
|
|
||||||
|
data_buf = kmalloc(size, GFP_KERNEL);
|
||||||
|
if (!data_buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
retry:
|
||||||
|
/* First of all reset the tuning block */
|
||||||
|
rc = msm_init_cm_dll(host);
|
||||||
|
if (rc)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
phase = 0;
|
||||||
|
do {
|
||||||
|
struct mmc_command cmd = { 0 };
|
||||||
|
struct mmc_data data = { 0 };
|
||||||
|
struct mmc_request mrq = {
|
||||||
|
.cmd = &cmd,
|
||||||
|
.data = &data
|
||||||
|
};
|
||||||
|
struct scatterlist sg;
|
||||||
|
|
||||||
|
/* Set the phase in delay line hw block */
|
||||||
|
rc = msm_config_cm_dll_phase(host, phase);
|
||||||
|
if (rc)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
cmd.opcode = opcode;
|
||||||
|
cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||||
|
|
||||||
|
data.blksz = size;
|
||||||
|
data.blocks = 1;
|
||||||
|
data.flags = MMC_DATA_READ;
|
||||||
|
data.timeout_ns = NSEC_PER_SEC; /* 1 second */
|
||||||
|
|
||||||
|
data.sg = &sg;
|
||||||
|
data.sg_len = 1;
|
||||||
|
sg_init_one(&sg, data_buf, size);
|
||||||
|
memset(data_buf, 0, size);
|
||||||
|
mmc_wait_for_req(mmc, &mrq);
|
||||||
|
|
||||||
|
if (!cmd.error && !data.error &&
|
||||||
|
!memcmp(data_buf, tuning_block_pattern, size)) {
|
||||||
|
/* Tuning is successful at this tuning point */
|
||||||
|
tuned_phases[tuned_phase_cnt++] = phase;
|
||||||
|
dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n",
|
||||||
|
mmc_hostname(mmc), phase);
|
||||||
|
}
|
||||||
|
} while (++phase < ARRAY_SIZE(tuned_phases));
|
||||||
|
|
||||||
|
if (tuned_phase_cnt) {
|
||||||
|
rc = msm_find_most_appropriate_phase(host, tuned_phases,
|
||||||
|
tuned_phase_cnt);
|
||||||
|
if (rc < 0)
|
||||||
|
goto out;
|
||||||
|
else
|
||||||
|
phase = rc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Finally set the selected phase in delay
|
||||||
|
* line hw block.
|
||||||
|
*/
|
||||||
|
rc = msm_config_cm_dll_phase(host, phase);
|
||||||
|
if (rc)
|
||||||
|
goto out;
|
||||||
|
dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n",
|
||||||
|
mmc_hostname(mmc), phase);
|
||||||
|
} else {
|
||||||
|
if (--tuning_seq_cnt)
|
||||||
|
goto retry;
|
||||||
|
/* Tuning failed */
|
||||||
|
dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n",
|
||||||
|
mmc_hostname(mmc));
|
||||||
|
rc = -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
kfree(data_buf);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id sdhci_msm_dt_match[] = {
|
||||||
|
{ .compatible = "qcom,sdhci-msm-v4" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match);
|
||||||
|
|
||||||
|
static struct sdhci_ops sdhci_msm_ops = {
|
||||||
|
.platform_execute_tuning = sdhci_msm_execute_tuning,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int sdhci_msm_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct sdhci_host *host;
|
||||||
|
struct sdhci_pltfm_host *pltfm_host;
|
||||||
|
struct sdhci_msm_host *msm_host;
|
||||||
|
struct resource *core_memres;
|
||||||
|
int ret;
|
||||||
|
u16 host_version;
|
||||||
|
|
||||||
|
msm_host = devm_kzalloc(&pdev->dev, sizeof(*msm_host), GFP_KERNEL);
|
||||||
|
if (!msm_host)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops;
|
||||||
|
host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata, 0);
|
||||||
|
if (IS_ERR(host))
|
||||||
|
return PTR_ERR(host);
|
||||||
|
|
||||||
|
pltfm_host = sdhci_priv(host);
|
||||||
|
pltfm_host->priv = msm_host;
|
||||||
|
msm_host->mmc = host->mmc;
|
||||||
|
msm_host->pdev = pdev;
|
||||||
|
|
||||||
|
ret = mmc_of_parse(host->mmc);
|
||||||
|
if (ret)
|
||||||
|
goto pltfm_free;
|
||||||
|
|
||||||
|
sdhci_get_of_property(pdev);
|
||||||
|
|
||||||
|
/* Setup SDCC bus voter clock. */
|
||||||
|
msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus");
|
||||||
|
if (!IS_ERR(msm_host->bus_clk)) {
|
||||||
|
/* Vote for max. clk rate for max. performance */
|
||||||
|
ret = clk_set_rate(msm_host->bus_clk, INT_MAX);
|
||||||
|
if (ret)
|
||||||
|
goto pltfm_free;
|
||||||
|
ret = clk_prepare_enable(msm_host->bus_clk);
|
||||||
|
if (ret)
|
||||||
|
goto pltfm_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup main peripheral bus clock */
|
||||||
|
msm_host->pclk = devm_clk_get(&pdev->dev, "iface");
|
||||||
|
if (IS_ERR(msm_host->pclk)) {
|
||||||
|
ret = PTR_ERR(msm_host->pclk);
|
||||||
|
dev_err(&pdev->dev, "Perpheral clk setup failed (%d)\n", ret);
|
||||||
|
goto bus_clk_disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(msm_host->pclk);
|
||||||
|
if (ret)
|
||||||
|
goto bus_clk_disable;
|
||||||
|
|
||||||
|
/* Setup SDC MMC clock */
|
||||||
|
msm_host->clk = devm_clk_get(&pdev->dev, "core");
|
||||||
|
if (IS_ERR(msm_host->clk)) {
|
||||||
|
ret = PTR_ERR(msm_host->clk);
|
||||||
|
dev_err(&pdev->dev, "SDC MMC clk setup failed (%d)\n", ret);
|
||||||
|
goto pclk_disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(msm_host->clk);
|
||||||
|
if (ret)
|
||||||
|
goto pclk_disable;
|
||||||
|
|
||||||
|
core_memres = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||||
|
msm_host->core_mem = devm_ioremap_resource(&pdev->dev, core_memres);
|
||||||
|
|
||||||
|
if (IS_ERR(msm_host->core_mem)) {
|
||||||
|
dev_err(&pdev->dev, "Failed to remap registers\n");
|
||||||
|
ret = PTR_ERR(msm_host->core_mem);
|
||||||
|
goto clk_disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset the core and Enable SDHC mode */
|
||||||
|
writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) |
|
||||||
|
CORE_SW_RST, msm_host->core_mem + CORE_POWER);
|
||||||
|
|
||||||
|
/* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */
|
||||||
|
usleep_range(1000, 5000);
|
||||||
|
if (readl(msm_host->core_mem + CORE_POWER) & CORE_SW_RST) {
|
||||||
|
dev_err(&pdev->dev, "Stuck in reset\n");
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto clk_disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set HC_MODE_EN bit in HC_MODE register */
|
||||||
|
writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE));
|
||||||
|
|
||||||
|
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
|
||||||
|
host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE;
|
||||||
|
|
||||||
|
host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION));
|
||||||
|
dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n",
|
||||||
|
host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >>
|
||||||
|
SDHCI_VENDOR_VER_SHIFT));
|
||||||
|
|
||||||
|
ret = sdhci_add_host(host);
|
||||||
|
if (ret)
|
||||||
|
goto clk_disable;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
clk_disable:
|
||||||
|
clk_disable_unprepare(msm_host->clk);
|
||||||
|
pclk_disable:
|
||||||
|
clk_disable_unprepare(msm_host->pclk);
|
||||||
|
bus_clk_disable:
|
||||||
|
if (!IS_ERR(msm_host->bus_clk))
|
||||||
|
clk_disable_unprepare(msm_host->bus_clk);
|
||||||
|
pltfm_free:
|
||||||
|
sdhci_pltfm_free(pdev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sdhci_msm_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
||||||
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||||
|
struct sdhci_msm_host *msm_host = pltfm_host->priv;
|
||||||
|
int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) ==
|
||||||
|
0xffffffff);
|
||||||
|
|
||||||
|
sdhci_remove_host(host, dead);
|
||||||
|
sdhci_pltfm_free(pdev);
|
||||||
|
clk_disable_unprepare(msm_host->clk);
|
||||||
|
clk_disable_unprepare(msm_host->pclk);
|
||||||
|
if (!IS_ERR(msm_host->bus_clk))
|
||||||
|
clk_disable_unprepare(msm_host->bus_clk);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver sdhci_msm_driver = {
|
||||||
|
.probe = sdhci_msm_probe,
|
||||||
|
.remove = sdhci_msm_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "sdhci_msm",
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.of_match_table = sdhci_msm_dt_match,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(sdhci_msm_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Qualcomm Secure Digital Host Controller Interface driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
@ -610,6 +610,18 @@ static const struct sdhci_pci_fixes sdhci_via = {
|
|||||||
.probe = via_probe,
|
.probe = via_probe,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int rtsx_probe_slot(struct sdhci_pci_slot *slot)
|
||||||
|
{
|
||||||
|
slot->host->mmc->caps2 |= MMC_CAP2_HS200;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct sdhci_pci_fixes sdhci_rtsx = {
|
||||||
|
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
|
||||||
|
SDHCI_QUIRK2_BROKEN_DDR50,
|
||||||
|
.probe_slot = rtsx_probe_slot,
|
||||||
|
};
|
||||||
|
|
||||||
static const struct pci_device_id pci_ids[] = {
|
static const struct pci_device_id pci_ids[] = {
|
||||||
{
|
{
|
||||||
.vendor = PCI_VENDOR_ID_RICOH,
|
.vendor = PCI_VENDOR_ID_RICOH,
|
||||||
@ -731,6 +743,14 @@ static const struct pci_device_id pci_ids[] = {
|
|||||||
.driver_data = (kernel_ulong_t)&sdhci_via,
|
.driver_data = (kernel_ulong_t)&sdhci_via,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.vendor = PCI_VENDOR_ID_REALTEK,
|
||||||
|
.device = 0x5250,
|
||||||
|
.subvendor = PCI_ANY_ID,
|
||||||
|
.subdevice = PCI_ANY_ID,
|
||||||
|
.driver_data = (kernel_ulong_t)&sdhci_rtsx,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
.vendor = PCI_VENDOR_ID_INTEL,
|
.vendor = PCI_VENDOR_ID_INTEL,
|
||||||
.device = PCI_DEVICE_ID_INTEL_MRST_SD0,
|
.device = PCI_DEVICE_ID_INTEL_MRST_SD0,
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include <linux/of_gpio.h>
|
#include <linux/of_gpio.h>
|
||||||
#include <linux/pm.h>
|
#include <linux/pm.h>
|
||||||
#include <linux/pm_runtime.h>
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/mbus.h>
|
||||||
|
|
||||||
#include "sdhci.h"
|
#include "sdhci.h"
|
||||||
#include "sdhci-pltfm.h"
|
#include "sdhci-pltfm.h"
|
||||||
@ -57,6 +58,60 @@
|
|||||||
#define SDCE_MISC_INT (1<<2)
|
#define SDCE_MISC_INT (1<<2)
|
||||||
#define SDCE_MISC_INT_EN (1<<1)
|
#define SDCE_MISC_INT_EN (1<<1)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These registers are relative to the second register region, for the
|
||||||
|
* MBus bridge.
|
||||||
|
*/
|
||||||
|
#define SDHCI_WINDOW_CTRL(i) (0x80 + ((i) << 3))
|
||||||
|
#define SDHCI_WINDOW_BASE(i) (0x84 + ((i) << 3))
|
||||||
|
#define SDHCI_MAX_WIN_NUM 8
|
||||||
|
|
||||||
|
static int mv_conf_mbus_windows(struct platform_device *pdev,
|
||||||
|
const struct mbus_dram_target_info *dram)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
void __iomem *regs;
|
||||||
|
struct resource *res;
|
||||||
|
|
||||||
|
if (!dram) {
|
||||||
|
dev_err(&pdev->dev, "no mbus dram info\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||||
|
if (!res) {
|
||||||
|
dev_err(&pdev->dev, "cannot get mbus registers\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
regs = ioremap(res->start, resource_size(res));
|
||||||
|
if (!regs) {
|
||||||
|
dev_err(&pdev->dev, "cannot map mbus registers\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < SDHCI_MAX_WIN_NUM; i++) {
|
||||||
|
writel(0, regs + SDHCI_WINDOW_CTRL(i));
|
||||||
|
writel(0, regs + SDHCI_WINDOW_BASE(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < dram->num_cs; i++) {
|
||||||
|
const struct mbus_dram_window *cs = dram->cs + i;
|
||||||
|
|
||||||
|
/* Write size, attributes and target id to control register */
|
||||||
|
writel(((cs->size - 1) & 0xffff0000) |
|
||||||
|
(cs->mbus_attr << 8) |
|
||||||
|
(dram->mbus_dram_target_id << 4) | 1,
|
||||||
|
regs + SDHCI_WINDOW_CTRL(i));
|
||||||
|
/* Write base address to base register */
|
||||||
|
writel(cs->base, regs + SDHCI_WINDOW_BASE(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
iounmap(regs);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void pxav3_set_private_registers(struct sdhci_host *host, u8 mask)
|
static void pxav3_set_private_registers(struct sdhci_host *host, u8 mask)
|
||||||
{
|
{
|
||||||
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
|
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
|
||||||
@ -187,6 +242,9 @@ static const struct of_device_id sdhci_pxav3_of_match[] = {
|
|||||||
{
|
{
|
||||||
.compatible = "mrvl,pxav3-mmc",
|
.compatible = "mrvl,pxav3-mmc",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.compatible = "marvell,armada-380-sdhci",
|
||||||
|
},
|
||||||
{},
|
{},
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(of, sdhci_pxav3_of_match);
|
MODULE_DEVICE_TABLE(of, sdhci_pxav3_of_match);
|
||||||
@ -219,6 +277,7 @@ static int sdhci_pxav3_probe(struct platform_device *pdev)
|
|||||||
struct sdhci_pltfm_host *pltfm_host;
|
struct sdhci_pltfm_host *pltfm_host;
|
||||||
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
|
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
|
struct device_node *np = pdev->dev.of_node;
|
||||||
struct sdhci_host *host = NULL;
|
struct sdhci_host *host = NULL;
|
||||||
struct sdhci_pxa *pxa = NULL;
|
struct sdhci_pxa *pxa = NULL;
|
||||||
const struct of_device_id *match;
|
const struct of_device_id *match;
|
||||||
@ -235,6 +294,14 @@ static int sdhci_pxav3_probe(struct platform_device *pdev)
|
|||||||
kfree(pxa);
|
kfree(pxa);
|
||||||
return PTR_ERR(host);
|
return PTR_ERR(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (of_device_is_compatible(np, "marvell,armada-380-sdhci")) {
|
||||||
|
ret = mv_conf_mbus_windows(pdev, mv_mbus_dram_info());
|
||||||
|
if (ret < 0)
|
||||||
|
goto err_mbus_win;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pltfm_host = sdhci_priv(host);
|
pltfm_host = sdhci_priv(host);
|
||||||
pltfm_host->priv = pxa;
|
pltfm_host->priv = pxa;
|
||||||
|
|
||||||
@ -321,6 +388,7 @@ err_add_host:
|
|||||||
clk_disable_unprepare(clk);
|
clk_disable_unprepare(clk);
|
||||||
clk_put(clk);
|
clk_put(clk);
|
||||||
err_clk_get:
|
err_clk_get:
|
||||||
|
err_mbus_win:
|
||||||
sdhci_pltfm_free(pdev);
|
sdhci_pltfm_free(pdev);
|
||||||
kfree(pxa);
|
kfree(pxa);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -51,12 +51,13 @@ struct sdhci_s3c {
|
|||||||
struct platform_device *pdev;
|
struct platform_device *pdev;
|
||||||
struct resource *ioarea;
|
struct resource *ioarea;
|
||||||
struct s3c_sdhci_platdata *pdata;
|
struct s3c_sdhci_platdata *pdata;
|
||||||
unsigned int cur_clk;
|
int cur_clk;
|
||||||
int ext_cd_irq;
|
int ext_cd_irq;
|
||||||
int ext_cd_gpio;
|
int ext_cd_gpio;
|
||||||
|
|
||||||
struct clk *clk_io;
|
struct clk *clk_io;
|
||||||
struct clk *clk_bus[MAX_BUS_CLK];
|
struct clk *clk_bus[MAX_BUS_CLK];
|
||||||
|
unsigned long clk_rates[MAX_BUS_CLK];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,32 +77,6 @@ static inline struct sdhci_s3c *to_s3c(struct sdhci_host *host)
|
|||||||
return sdhci_priv(host);
|
return sdhci_priv(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* get_curclk - convert ctrl2 register to clock source number
|
|
||||||
* @ctrl2: Control2 register value.
|
|
||||||
*/
|
|
||||||
static u32 get_curclk(u32 ctrl2)
|
|
||||||
{
|
|
||||||
ctrl2 &= S3C_SDHCI_CTRL2_SELBASECLK_MASK;
|
|
||||||
ctrl2 >>= S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
|
|
||||||
|
|
||||||
return ctrl2;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sdhci_s3c_check_sclk(struct sdhci_host *host)
|
|
||||||
{
|
|
||||||
struct sdhci_s3c *ourhost = to_s3c(host);
|
|
||||||
u32 tmp = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
|
|
||||||
|
|
||||||
if (get_curclk(tmp) != ourhost->cur_clk) {
|
|
||||||
dev_dbg(&ourhost->pdev->dev, "restored ctrl2 clock setting\n");
|
|
||||||
|
|
||||||
tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
|
|
||||||
tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
|
|
||||||
writel(tmp, host->ioaddr + S3C_SDHCI_CONTROL2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sdhci_s3c_get_max_clk - callback to get maximum clock frequency.
|
* sdhci_s3c_get_max_clk - callback to get maximum clock frequency.
|
||||||
* @host: The SDHCI host instance.
|
* @host: The SDHCI host instance.
|
||||||
@ -111,20 +86,11 @@ static void sdhci_s3c_check_sclk(struct sdhci_host *host)
|
|||||||
static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host)
|
static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host)
|
||||||
{
|
{
|
||||||
struct sdhci_s3c *ourhost = to_s3c(host);
|
struct sdhci_s3c *ourhost = to_s3c(host);
|
||||||
struct clk *busclk;
|
unsigned long rate, max = 0;
|
||||||
unsigned int rate, max;
|
int src;
|
||||||
int clk;
|
|
||||||
|
|
||||||
/* note, a reset will reset the clock source */
|
for (src = 0; src < MAX_BUS_CLK; src++) {
|
||||||
|
rate = ourhost->clk_rates[src];
|
||||||
sdhci_s3c_check_sclk(host);
|
|
||||||
|
|
||||||
for (max = 0, clk = 0; clk < MAX_BUS_CLK; clk++) {
|
|
||||||
busclk = ourhost->clk_bus[clk];
|
|
||||||
if (!busclk)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
rate = clk_get_rate(busclk);
|
|
||||||
if (rate > max)
|
if (rate > max)
|
||||||
max = rate;
|
max = rate;
|
||||||
}
|
}
|
||||||
@ -144,9 +110,9 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost,
|
|||||||
{
|
{
|
||||||
unsigned long rate;
|
unsigned long rate;
|
||||||
struct clk *clksrc = ourhost->clk_bus[src];
|
struct clk *clksrc = ourhost->clk_bus[src];
|
||||||
int div;
|
int shift;
|
||||||
|
|
||||||
if (!clksrc)
|
if (IS_ERR(clksrc))
|
||||||
return UINT_MAX;
|
return UINT_MAX;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -158,17 +124,24 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost,
|
|||||||
return wanted - rate;
|
return wanted - rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
rate = clk_get_rate(clksrc);
|
rate = ourhost->clk_rates[src];
|
||||||
|
|
||||||
for (div = 1; div < 256; div *= 2) {
|
for (shift = 0; shift <= 8; ++shift) {
|
||||||
if ((rate / div) <= wanted)
|
if ((rate >> shift) <= wanted)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n",
|
if (shift > 8) {
|
||||||
src, rate, wanted, rate / div);
|
dev_dbg(&ourhost->pdev->dev,
|
||||||
|
"clk %d: rate %ld, min rate %lu > wanted %u\n",
|
||||||
|
src, rate, rate / 256, wanted);
|
||||||
|
return UINT_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
return wanted - (rate / div);
|
dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n",
|
||||||
|
src, rate, wanted, rate >> shift);
|
||||||
|
|
||||||
|
return wanted - (rate >> shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -209,20 +182,22 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock)
|
|||||||
struct clk *clk = ourhost->clk_bus[best_src];
|
struct clk *clk = ourhost->clk_bus[best_src];
|
||||||
|
|
||||||
clk_prepare_enable(clk);
|
clk_prepare_enable(clk);
|
||||||
clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]);
|
if (ourhost->cur_clk >= 0)
|
||||||
|
clk_disable_unprepare(
|
||||||
/* turn clock off to card before changing clock source */
|
ourhost->clk_bus[ourhost->cur_clk]);
|
||||||
writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);
|
|
||||||
|
|
||||||
ourhost->cur_clk = best_src;
|
ourhost->cur_clk = best_src;
|
||||||
host->max_clk = clk_get_rate(clk);
|
host->max_clk = ourhost->clk_rates[best_src];
|
||||||
|
|
||||||
ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
|
|
||||||
ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
|
|
||||||
ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
|
|
||||||
writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* turn clock off to card before changing clock source */
|
||||||
|
writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);
|
||||||
|
|
||||||
|
ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
|
||||||
|
ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
|
||||||
|
ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
|
||||||
|
writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);
|
||||||
|
|
||||||
/* reprogram default hardware configuration */
|
/* reprogram default hardware configuration */
|
||||||
writel(S3C64XX_SDHCI_CONTROL4_DRIVE_9mA,
|
writel(S3C64XX_SDHCI_CONTROL4_DRIVE_9mA,
|
||||||
host->ioaddr + S3C64XX_SDHCI_CONTROL4);
|
host->ioaddr + S3C64XX_SDHCI_CONTROL4);
|
||||||
@ -254,17 +229,17 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock)
|
|||||||
static unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host)
|
static unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host)
|
||||||
{
|
{
|
||||||
struct sdhci_s3c *ourhost = to_s3c(host);
|
struct sdhci_s3c *ourhost = to_s3c(host);
|
||||||
unsigned int delta, min = UINT_MAX;
|
unsigned long rate, min = ULONG_MAX;
|
||||||
int src;
|
int src;
|
||||||
|
|
||||||
for (src = 0; src < MAX_BUS_CLK; src++) {
|
for (src = 0; src < MAX_BUS_CLK; src++) {
|
||||||
delta = sdhci_s3c_consider_clock(ourhost, src, 0);
|
rate = ourhost->clk_rates[src] / 256;
|
||||||
if (delta == UINT_MAX)
|
if (!rate)
|
||||||
continue;
|
continue;
|
||||||
/* delta is a negative value in this case */
|
if (rate < min)
|
||||||
if (-delta < min)
|
min = rate;
|
||||||
min = -delta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,20 +247,44 @@ static unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host)
|
|||||||
static unsigned int sdhci_cmu_get_max_clock(struct sdhci_host *host)
|
static unsigned int sdhci_cmu_get_max_clock(struct sdhci_host *host)
|
||||||
{
|
{
|
||||||
struct sdhci_s3c *ourhost = to_s3c(host);
|
struct sdhci_s3c *ourhost = to_s3c(host);
|
||||||
|
unsigned long rate, max = 0;
|
||||||
|
int src;
|
||||||
|
|
||||||
return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], UINT_MAX);
|
for (src = 0; src < MAX_BUS_CLK; src++) {
|
||||||
|
struct clk *clk;
|
||||||
|
|
||||||
|
clk = ourhost->clk_bus[src];
|
||||||
|
if (IS_ERR(clk))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rate = clk_round_rate(clk, ULONG_MAX);
|
||||||
|
if (rate > max)
|
||||||
|
max = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sdhci_cmu_get_min_clock - callback to get minimal supported clock value. */
|
/* sdhci_cmu_get_min_clock - callback to get minimal supported clock value. */
|
||||||
static unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host)
|
static unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host)
|
||||||
{
|
{
|
||||||
struct sdhci_s3c *ourhost = to_s3c(host);
|
struct sdhci_s3c *ourhost = to_s3c(host);
|
||||||
|
unsigned long rate, min = ULONG_MAX;
|
||||||
|
int src;
|
||||||
|
|
||||||
/*
|
for (src = 0; src < MAX_BUS_CLK; src++) {
|
||||||
* initial clock can be in the frequency range of
|
struct clk *clk;
|
||||||
* 100KHz-400KHz, so we set it as max value.
|
|
||||||
*/
|
clk = ourhost->clk_bus[src];
|
||||||
return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], 400000);
|
if (IS_ERR(clk))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rate = clk_round_rate(clk, 0);
|
||||||
|
if (rate < min)
|
||||||
|
min = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sdhci_cmu_set_clock - callback on clock change.*/
|
/* sdhci_cmu_set_clock - callback on clock change.*/
|
||||||
@ -552,6 +551,7 @@ static int sdhci_s3c_probe(struct platform_device *pdev)
|
|||||||
sc->host = host;
|
sc->host = host;
|
||||||
sc->pdev = pdev;
|
sc->pdev = pdev;
|
||||||
sc->pdata = pdata;
|
sc->pdata = pdata;
|
||||||
|
sc->cur_clk = -1;
|
||||||
|
|
||||||
platform_set_drvdata(pdev, host);
|
platform_set_drvdata(pdev, host);
|
||||||
|
|
||||||
@ -566,25 +566,18 @@ static int sdhci_s3c_probe(struct platform_device *pdev)
|
|||||||
clk_prepare_enable(sc->clk_io);
|
clk_prepare_enable(sc->clk_io);
|
||||||
|
|
||||||
for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
|
for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
|
||||||
struct clk *clk;
|
|
||||||
char name[14];
|
char name[14];
|
||||||
|
|
||||||
snprintf(name, 14, "mmc_busclk.%d", ptr);
|
snprintf(name, 14, "mmc_busclk.%d", ptr);
|
||||||
clk = devm_clk_get(dev, name);
|
sc->clk_bus[ptr] = devm_clk_get(dev, name);
|
||||||
if (IS_ERR(clk))
|
if (IS_ERR(sc->clk_bus[ptr]))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
clks++;
|
clks++;
|
||||||
sc->clk_bus[ptr] = clk;
|
sc->clk_rates[ptr] = clk_get_rate(sc->clk_bus[ptr]);
|
||||||
|
|
||||||
/*
|
|
||||||
* save current clock index to know which clock bus
|
|
||||||
* is used later in overriding functions.
|
|
||||||
*/
|
|
||||||
sc->cur_clk = ptr;
|
|
||||||
|
|
||||||
dev_info(dev, "clock source %d: %s (%ld Hz)\n",
|
dev_info(dev, "clock source %d: %s (%ld Hz)\n",
|
||||||
ptr, name, clk_get_rate(clk));
|
ptr, name, sc->clk_rates[ptr]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clks == 0) {
|
if (clks == 0) {
|
||||||
@ -593,10 +586,6 @@ static int sdhci_s3c_probe(struct platform_device *pdev)
|
|||||||
goto err_no_busclks;
|
goto err_no_busclks;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef CONFIG_PM_RUNTIME
|
|
||||||
clk_prepare_enable(sc->clk_bus[sc->cur_clk]);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
host->ioaddr = devm_ioremap_resource(&pdev->dev, res);
|
host->ioaddr = devm_ioremap_resource(&pdev->dev, res);
|
||||||
if (IS_ERR(host->ioaddr)) {
|
if (IS_ERR(host->ioaddr)) {
|
||||||
@ -709,10 +698,6 @@ static int sdhci_s3c_probe(struct platform_device *pdev)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_req_regs:
|
err_req_regs:
|
||||||
#ifndef CONFIG_PM_RUNTIME
|
|
||||||
clk_disable_unprepare(sc->clk_bus[sc->cur_clk]);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
err_no_busclks:
|
err_no_busclks:
|
||||||
clk_disable_unprepare(sc->clk_io);
|
clk_disable_unprepare(sc->clk_io);
|
||||||
|
|
||||||
@ -743,9 +728,6 @@ static int sdhci_s3c_remove(struct platform_device *pdev)
|
|||||||
pm_runtime_dont_use_autosuspend(&pdev->dev);
|
pm_runtime_dont_use_autosuspend(&pdev->dev);
|
||||||
pm_runtime_disable(&pdev->dev);
|
pm_runtime_disable(&pdev->dev);
|
||||||
|
|
||||||
#ifndef CONFIG_PM_RUNTIME
|
|
||||||
clk_disable_unprepare(sc->clk_bus[sc->cur_clk]);
|
|
||||||
#endif
|
|
||||||
clk_disable_unprepare(sc->clk_io);
|
clk_disable_unprepare(sc->clk_io);
|
||||||
|
|
||||||
sdhci_free_host(host);
|
sdhci_free_host(host);
|
||||||
@ -779,7 +761,8 @@ static int sdhci_s3c_runtime_suspend(struct device *dev)
|
|||||||
|
|
||||||
ret = sdhci_runtime_suspend_host(host);
|
ret = sdhci_runtime_suspend_host(host);
|
||||||
|
|
||||||
clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]);
|
if (ourhost->cur_clk >= 0)
|
||||||
|
clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]);
|
||||||
clk_disable_unprepare(busclk);
|
clk_disable_unprepare(busclk);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -792,7 +775,8 @@ static int sdhci_s3c_runtime_resume(struct device *dev)
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
clk_prepare_enable(busclk);
|
clk_prepare_enable(busclk);
|
||||||
clk_prepare_enable(ourhost->clk_bus[ourhost->cur_clk]);
|
if (ourhost->cur_clk >= 0)
|
||||||
|
clk_prepare_enable(ourhost->clk_bus[ourhost->cur_clk]);
|
||||||
ret = sdhci_runtime_resume_host(host);
|
ret = sdhci_runtime_resume_host(host);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/mmc/host.h>
|
#include <linux/mmc/host.h>
|
||||||
#include <linux/mmc/sdhci-spear.h>
|
#include <linux/mmc/sdhci-spear.h>
|
||||||
|
#include <linux/mmc/slot-gpio.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include "sdhci.h"
|
#include "sdhci.h"
|
||||||
|
|
||||||
@ -40,36 +41,6 @@ static const struct sdhci_ops sdhci_pltfm_ops = {
|
|||||||
/* Nothing to do for now. */
|
/* Nothing to do for now. */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* gpio card detection interrupt handler */
|
|
||||||
static irqreturn_t sdhci_gpio_irq(int irq, void *dev_id)
|
|
||||||
{
|
|
||||||
struct platform_device *pdev = dev_id;
|
|
||||||
struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
||||||
struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev);
|
|
||||||
unsigned long gpio_irq_type;
|
|
||||||
int val;
|
|
||||||
|
|
||||||
val = gpio_get_value(sdhci->data->card_int_gpio);
|
|
||||||
|
|
||||||
/* val == 1 -> card removed, val == 0 -> card inserted */
|
|
||||||
/* if card removed - set irq for low level, else vice versa */
|
|
||||||
gpio_irq_type = val ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH;
|
|
||||||
irq_set_irq_type(irq, gpio_irq_type);
|
|
||||||
|
|
||||||
if (sdhci->data->card_power_gpio >= 0) {
|
|
||||||
if (!sdhci->data->power_always_enb) {
|
|
||||||
/* if card inserted, give power, otherwise remove it */
|
|
||||||
val = sdhci->data->power_active_high ? !val : val ;
|
|
||||||
gpio_set_value(sdhci->data->card_power_gpio, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* inform sdhci driver about card insertion/removal */
|
|
||||||
tasklet_schedule(&host->card_tasklet);
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_OF
|
#ifdef CONFIG_OF
|
||||||
static struct sdhci_plat_data *sdhci_probe_config_dt(struct platform_device *pdev)
|
static struct sdhci_plat_data *sdhci_probe_config_dt(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
@ -84,14 +55,12 @@ static struct sdhci_plat_data *sdhci_probe_config_dt(struct platform_device *pde
|
|||||||
/* If pdata is required */
|
/* If pdata is required */
|
||||||
if (cd_gpio != -1) {
|
if (cd_gpio != -1) {
|
||||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||||
if (!pdata) {
|
if (!pdata)
|
||||||
dev_err(&pdev->dev, "DT: kzalloc failed\n");
|
dev_err(&pdev->dev, "DT: kzalloc failed\n");
|
||||||
return ERR_PTR(-ENOMEM);
|
else
|
||||||
}
|
pdata->card_int_gpio = cd_gpio;
|
||||||
}
|
}
|
||||||
|
|
||||||
pdata->card_int_gpio = cd_gpio;
|
|
||||||
|
|
||||||
return pdata;
|
return pdata;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@ -107,41 +76,44 @@ static int sdhci_probe(struct platform_device *pdev)
|
|||||||
struct sdhci_host *host;
|
struct sdhci_host *host;
|
||||||
struct resource *iomem;
|
struct resource *iomem;
|
||||||
struct spear_sdhci *sdhci;
|
struct spear_sdhci *sdhci;
|
||||||
|
struct device *dev;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
dev = pdev->dev.parent ? pdev->dev.parent : &pdev->dev;
|
||||||
if (!iomem) {
|
host = sdhci_alloc_host(dev, sizeof(*sdhci));
|
||||||
ret = -ENOMEM;
|
if (IS_ERR(host)) {
|
||||||
dev_dbg(&pdev->dev, "memory resource not defined\n");
|
ret = PTR_ERR(host);
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!devm_request_mem_region(&pdev->dev, iomem->start,
|
|
||||||
resource_size(iomem), "spear-sdhci")) {
|
|
||||||
ret = -EBUSY;
|
|
||||||
dev_dbg(&pdev->dev, "cannot request region\n");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
sdhci = devm_kzalloc(&pdev->dev, sizeof(*sdhci), GFP_KERNEL);
|
|
||||||
if (!sdhci) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
dev_dbg(&pdev->dev, "cannot allocate memory for sdhci\n");
|
dev_dbg(&pdev->dev, "cannot allocate memory for sdhci\n");
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
host->ioaddr = devm_ioremap_resource(&pdev->dev, iomem);
|
||||||
|
if (IS_ERR(host->ioaddr)) {
|
||||||
|
ret = PTR_ERR(host->ioaddr);
|
||||||
|
dev_dbg(&pdev->dev, "unable to map iomem: %d\n", ret);
|
||||||
|
goto err_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
host->hw_name = "sdhci";
|
||||||
|
host->ops = &sdhci_pltfm_ops;
|
||||||
|
host->irq = platform_get_irq(pdev, 0);
|
||||||
|
host->quirks = SDHCI_QUIRK_BROKEN_ADMA;
|
||||||
|
|
||||||
|
sdhci = sdhci_priv(host);
|
||||||
|
|
||||||
/* clk enable */
|
/* clk enable */
|
||||||
sdhci->clk = clk_get(&pdev->dev, NULL);
|
sdhci->clk = devm_clk_get(&pdev->dev, NULL);
|
||||||
if (IS_ERR(sdhci->clk)) {
|
if (IS_ERR(sdhci->clk)) {
|
||||||
ret = PTR_ERR(sdhci->clk);
|
ret = PTR_ERR(sdhci->clk);
|
||||||
dev_dbg(&pdev->dev, "Error getting clock\n");
|
dev_dbg(&pdev->dev, "Error getting clock\n");
|
||||||
goto err;
|
goto err_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = clk_prepare_enable(sdhci->clk);
|
ret = clk_prepare_enable(sdhci->clk);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_dbg(&pdev->dev, "Error enabling clock\n");
|
dev_dbg(&pdev->dev, "Error enabling clock\n");
|
||||||
goto put_clk;
|
goto err_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = clk_set_rate(sdhci->clk, 50000000);
|
ret = clk_set_rate(sdhci->clk, 50000000);
|
||||||
@ -153,118 +125,42 @@ static int sdhci_probe(struct platform_device *pdev)
|
|||||||
sdhci->data = sdhci_probe_config_dt(pdev);
|
sdhci->data = sdhci_probe_config_dt(pdev);
|
||||||
if (IS_ERR(sdhci->data)) {
|
if (IS_ERR(sdhci->data)) {
|
||||||
dev_err(&pdev->dev, "DT: Failed to get pdata\n");
|
dev_err(&pdev->dev, "DT: Failed to get pdata\n");
|
||||||
return -ENODEV;
|
goto disable_clk;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sdhci->data = dev_get_platdata(&pdev->dev);
|
sdhci->data = dev_get_platdata(&pdev->dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
pdev->dev.platform_data = sdhci;
|
/*
|
||||||
|
* It is optional to use GPIOs for sdhci card detection. If
|
||||||
if (pdev->dev.parent)
|
* sdhci->data is NULL, then use original sdhci lines otherwise
|
||||||
host = sdhci_alloc_host(pdev->dev.parent, 0);
|
* GPIO lines. We use the built-in GPIO support for this.
|
||||||
else
|
*/
|
||||||
host = sdhci_alloc_host(&pdev->dev, 0);
|
if (sdhci->data && sdhci->data->card_int_gpio >= 0) {
|
||||||
|
ret = mmc_gpio_request_cd(host->mmc,
|
||||||
if (IS_ERR(host)) {
|
sdhci->data->card_int_gpio, 0);
|
||||||
ret = PTR_ERR(host);
|
if (ret < 0) {
|
||||||
dev_dbg(&pdev->dev, "error allocating host\n");
|
dev_dbg(&pdev->dev,
|
||||||
goto disable_clk;
|
"failed to request card-detect gpio%d\n",
|
||||||
}
|
sdhci->data->card_int_gpio);
|
||||||
|
goto disable_clk;
|
||||||
host->hw_name = "sdhci";
|
}
|
||||||
host->ops = &sdhci_pltfm_ops;
|
|
||||||
host->irq = platform_get_irq(pdev, 0);
|
|
||||||
host->quirks = SDHCI_QUIRK_BROKEN_ADMA;
|
|
||||||
|
|
||||||
host->ioaddr = devm_ioremap(&pdev->dev, iomem->start,
|
|
||||||
resource_size(iomem));
|
|
||||||
if (!host->ioaddr) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
dev_dbg(&pdev->dev, "failed to remap registers\n");
|
|
||||||
goto free_host;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sdhci_add_host(host);
|
ret = sdhci_add_host(host);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_dbg(&pdev->dev, "error adding host\n");
|
dev_dbg(&pdev->dev, "error adding host\n");
|
||||||
goto free_host;
|
goto disable_clk;
|
||||||
}
|
}
|
||||||
|
|
||||||
platform_set_drvdata(pdev, host);
|
platform_set_drvdata(pdev, host);
|
||||||
|
|
||||||
/*
|
|
||||||
* It is optional to use GPIOs for sdhci Power control & sdhci card
|
|
||||||
* interrupt detection. If sdhci->data is NULL, then use original sdhci
|
|
||||||
* lines otherwise GPIO lines.
|
|
||||||
* If GPIO is selected for power control, then power should be disabled
|
|
||||||
* after card removal and should be enabled when card insertion
|
|
||||||
* interrupt occurs
|
|
||||||
*/
|
|
||||||
if (!sdhci->data)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (sdhci->data->card_power_gpio >= 0) {
|
|
||||||
int val = 0;
|
|
||||||
|
|
||||||
ret = devm_gpio_request(&pdev->dev,
|
|
||||||
sdhci->data->card_power_gpio, "sdhci");
|
|
||||||
if (ret < 0) {
|
|
||||||
dev_dbg(&pdev->dev, "gpio request fail: %d\n",
|
|
||||||
sdhci->data->card_power_gpio);
|
|
||||||
goto set_drvdata;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sdhci->data->power_always_enb)
|
|
||||||
val = sdhci->data->power_active_high;
|
|
||||||
else
|
|
||||||
val = !sdhci->data->power_active_high;
|
|
||||||
|
|
||||||
ret = gpio_direction_output(sdhci->data->card_power_gpio, val);
|
|
||||||
if (ret) {
|
|
||||||
dev_dbg(&pdev->dev, "gpio set direction fail: %d\n",
|
|
||||||
sdhci->data->card_power_gpio);
|
|
||||||
goto set_drvdata;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sdhci->data->card_int_gpio >= 0) {
|
|
||||||
ret = devm_gpio_request(&pdev->dev, sdhci->data->card_int_gpio,
|
|
||||||
"sdhci");
|
|
||||||
if (ret < 0) {
|
|
||||||
dev_dbg(&pdev->dev, "gpio request fail: %d\n",
|
|
||||||
sdhci->data->card_int_gpio);
|
|
||||||
goto set_drvdata;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = gpio_direction_input(sdhci->data->card_int_gpio);
|
|
||||||
if (ret) {
|
|
||||||
dev_dbg(&pdev->dev, "gpio set direction fail: %d\n",
|
|
||||||
sdhci->data->card_int_gpio);
|
|
||||||
goto set_drvdata;
|
|
||||||
}
|
|
||||||
ret = devm_request_irq(&pdev->dev,
|
|
||||||
gpio_to_irq(sdhci->data->card_int_gpio),
|
|
||||||
sdhci_gpio_irq, IRQF_TRIGGER_LOW,
|
|
||||||
mmc_hostname(host->mmc), pdev);
|
|
||||||
if (ret) {
|
|
||||||
dev_dbg(&pdev->dev, "gpio request irq fail: %d\n",
|
|
||||||
sdhci->data->card_int_gpio);
|
|
||||||
goto set_drvdata;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
set_drvdata:
|
|
||||||
sdhci_remove_host(host, 1);
|
|
||||||
free_host:
|
|
||||||
sdhci_free_host(host);
|
|
||||||
disable_clk:
|
disable_clk:
|
||||||
clk_disable_unprepare(sdhci->clk);
|
clk_disable_unprepare(sdhci->clk);
|
||||||
put_clk:
|
err_host:
|
||||||
clk_put(sdhci->clk);
|
sdhci_free_host(host);
|
||||||
err:
|
err:
|
||||||
dev_err(&pdev->dev, "spear-sdhci probe failed: %d\n", ret);
|
dev_err(&pdev->dev, "spear-sdhci probe failed: %d\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
@ -273,7 +169,7 @@ err:
|
|||||||
static int sdhci_remove(struct platform_device *pdev)
|
static int sdhci_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct sdhci_host *host = platform_get_drvdata(pdev);
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
||||||
struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev);
|
struct spear_sdhci *sdhci = sdhci_priv(host);
|
||||||
int dead = 0;
|
int dead = 0;
|
||||||
u32 scratch;
|
u32 scratch;
|
||||||
|
|
||||||
@ -282,9 +178,8 @@ static int sdhci_remove(struct platform_device *pdev)
|
|||||||
dead = 1;
|
dead = 1;
|
||||||
|
|
||||||
sdhci_remove_host(host, dead);
|
sdhci_remove_host(host, dead);
|
||||||
sdhci_free_host(host);
|
|
||||||
clk_disable_unprepare(sdhci->clk);
|
clk_disable_unprepare(sdhci->clk);
|
||||||
clk_put(sdhci->clk);
|
sdhci_free_host(host);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -293,7 +188,7 @@ static int sdhci_remove(struct platform_device *pdev)
|
|||||||
static int sdhci_suspend(struct device *dev)
|
static int sdhci_suspend(struct device *dev)
|
||||||
{
|
{
|
||||||
struct sdhci_host *host = dev_get_drvdata(dev);
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
||||||
struct spear_sdhci *sdhci = dev_get_platdata(dev);
|
struct spear_sdhci *sdhci = sdhci_priv(host);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = sdhci_suspend_host(host);
|
ret = sdhci_suspend_host(host);
|
||||||
@ -306,7 +201,7 @@ static int sdhci_suspend(struct device *dev)
|
|||||||
static int sdhci_resume(struct device *dev)
|
static int sdhci_resume(struct device *dev)
|
||||||
{
|
{
|
||||||
struct sdhci_host *host = dev_get_drvdata(dev);
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
||||||
struct spear_sdhci *sdhci = dev_get_platdata(dev);
|
struct spear_sdhci *sdhci = sdhci_priv(host);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = clk_enable(sdhci->clk);
|
ret = clk_enable(sdhci->clk);
|
||||||
|
@ -675,12 +675,12 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd)
|
|||||||
return 0xE;
|
return 0xE;
|
||||||
|
|
||||||
/* Unspecified timeout, assume max */
|
/* Unspecified timeout, assume max */
|
||||||
if (!data && !cmd->cmd_timeout_ms)
|
if (!data && !cmd->busy_timeout)
|
||||||
return 0xE;
|
return 0xE;
|
||||||
|
|
||||||
/* timeout in us */
|
/* timeout in us */
|
||||||
if (!data)
|
if (!data)
|
||||||
target_timeout = cmd->cmd_timeout_ms * 1000;
|
target_timeout = cmd->busy_timeout * 1000;
|
||||||
else {
|
else {
|
||||||
target_timeout = data->timeout_ns / 1000;
|
target_timeout = data->timeout_ns / 1000;
|
||||||
if (host->clock)
|
if (host->clock)
|
||||||
@ -1019,8 +1019,8 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
timeout = jiffies;
|
timeout = jiffies;
|
||||||
if (!cmd->data && cmd->cmd_timeout_ms > 9000)
|
if (!cmd->data && cmd->busy_timeout > 9000)
|
||||||
timeout += DIV_ROUND_UP(cmd->cmd_timeout_ms, 1000) * HZ + HZ;
|
timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
|
||||||
else
|
else
|
||||||
timeout += 10 * HZ;
|
timeout += 10 * HZ;
|
||||||
mod_timer(&host->timer, timeout);
|
mod_timer(&host->timer, timeout);
|
||||||
@ -2026,12 +2026,11 @@ out:
|
|||||||
host->tuning_count * HZ);
|
host->tuning_count * HZ);
|
||||||
/* Tuning mode 1 limits the maximum data length to 4MB */
|
/* Tuning mode 1 limits the maximum data length to 4MB */
|
||||||
mmc->max_blk_count = (4 * 1024 * 1024) / mmc->max_blk_size;
|
mmc->max_blk_count = (4 * 1024 * 1024) / mmc->max_blk_size;
|
||||||
} else {
|
} else if (host->flags & SDHCI_USING_RETUNING_TIMER) {
|
||||||
host->flags &= ~SDHCI_NEEDS_RETUNING;
|
host->flags &= ~SDHCI_NEEDS_RETUNING;
|
||||||
/* Reload the new initial value for timer */
|
/* Reload the new initial value for timer */
|
||||||
if (host->tuning_mode == SDHCI_TUNING_MODE_1)
|
mod_timer(&host->tuning_timer, jiffies +
|
||||||
mod_timer(&host->tuning_timer, jiffies +
|
host->tuning_count * HZ);
|
||||||
host->tuning_count * HZ);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2434,9 +2433,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
|
|||||||
|
|
||||||
if (host->runtime_suspended) {
|
if (host->runtime_suspended) {
|
||||||
spin_unlock(&host->lock);
|
spin_unlock(&host->lock);
|
||||||
pr_warning("%s: got irq while runtime suspended\n",
|
return IRQ_NONE;
|
||||||
mmc_hostname(host->mmc));
|
|
||||||
return IRQ_HANDLED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
intmask = sdhci_readl(host, SDHCI_INT_STATUS);
|
intmask = sdhci_readl(host, SDHCI_INT_STATUS);
|
||||||
@ -2941,7 +2938,7 @@ int sdhci_add_host(struct sdhci_host *host)
|
|||||||
if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)
|
if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)
|
||||||
host->timeout_clk = mmc->f_max / 1000;
|
host->timeout_clk = mmc->f_max / 1000;
|
||||||
|
|
||||||
mmc->max_discard_to = (1 << 27) / host->timeout_clk;
|
mmc->max_busy_timeout = (1 << 27) / host->timeout_clk;
|
||||||
|
|
||||||
mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23;
|
mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23;
|
||||||
|
|
||||||
@ -3020,7 +3017,8 @@ int sdhci_add_host(struct sdhci_host *host)
|
|||||||
} else if (caps[1] & SDHCI_SUPPORT_SDR50)
|
} else if (caps[1] & SDHCI_SUPPORT_SDR50)
|
||||||
mmc->caps |= MMC_CAP_UHS_SDR50;
|
mmc->caps |= MMC_CAP_UHS_SDR50;
|
||||||
|
|
||||||
if (caps[1] & SDHCI_SUPPORT_DDR50)
|
if ((caps[1] & SDHCI_SUPPORT_DDR50) &&
|
||||||
|
!(host->quirks2 & SDHCI_QUIRK2_BROKEN_DDR50))
|
||||||
mmc->caps |= MMC_CAP_UHS_DDR50;
|
mmc->caps |= MMC_CAP_UHS_DDR50;
|
||||||
|
|
||||||
/* Does the host need tuning for SDR50? */
|
/* Does the host need tuning for SDR50? */
|
||||||
|
@ -37,6 +37,8 @@
|
|||||||
|
|
||||||
struct sh_mobile_sdhi_of_data {
|
struct sh_mobile_sdhi_of_data {
|
||||||
unsigned long tmio_flags;
|
unsigned long tmio_flags;
|
||||||
|
unsigned long capabilities;
|
||||||
|
unsigned long capabilities2;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = {
|
static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = {
|
||||||
@ -45,6 +47,31 @@ static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const struct sh_mobile_sdhi_of_data of_rcar_gen1_compatible = {
|
||||||
|
.tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE,
|
||||||
|
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct sh_mobile_sdhi_of_data of_rcar_gen2_compatible = {
|
||||||
|
.tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE,
|
||||||
|
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
|
||||||
|
.capabilities2 = MMC_CAP2_NO_MULTI_READ,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct of_device_id sh_mobile_sdhi_of_match[] = {
|
||||||
|
{ .compatible = "renesas,sdhi-shmobile" },
|
||||||
|
{ .compatible = "renesas,sdhi-sh7372" },
|
||||||
|
{ .compatible = "renesas,sdhi-sh73a0", .data = &sh_mobile_sdhi_of_cfg[0], },
|
||||||
|
{ .compatible = "renesas,sdhi-r8a73a4", .data = &sh_mobile_sdhi_of_cfg[0], },
|
||||||
|
{ .compatible = "renesas,sdhi-r8a7740", .data = &sh_mobile_sdhi_of_cfg[0], },
|
||||||
|
{ .compatible = "renesas,sdhi-r8a7778", .data = &of_rcar_gen1_compatible, },
|
||||||
|
{ .compatible = "renesas,sdhi-r8a7779", .data = &of_rcar_gen1_compatible, },
|
||||||
|
{ .compatible = "renesas,sdhi-r8a7790", .data = &of_rcar_gen2_compatible, },
|
||||||
|
{ .compatible = "renesas,sdhi-r8a7791", .data = &of_rcar_gen2_compatible, },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, sh_mobile_sdhi_of_match);
|
||||||
|
|
||||||
struct sh_mobile_sdhi {
|
struct sh_mobile_sdhi {
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
struct tmio_mmc_data mmc_data;
|
struct tmio_mmc_data mmc_data;
|
||||||
@ -114,19 +141,6 @@ static const struct sh_mobile_sdhi_ops sdhi_ops = {
|
|||||||
.cd_wakeup = sh_mobile_sdhi_cd_wakeup,
|
.cd_wakeup = sh_mobile_sdhi_cd_wakeup,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct of_device_id sh_mobile_sdhi_of_match[] = {
|
|
||||||
{ .compatible = "renesas,sdhi-shmobile" },
|
|
||||||
{ .compatible = "renesas,sdhi-sh7372" },
|
|
||||||
{ .compatible = "renesas,sdhi-sh73a0", .data = &sh_mobile_sdhi_of_cfg[0], },
|
|
||||||
{ .compatible = "renesas,sdhi-r8a73a4", .data = &sh_mobile_sdhi_of_cfg[0], },
|
|
||||||
{ .compatible = "renesas,sdhi-r8a7740", .data = &sh_mobile_sdhi_of_cfg[0], },
|
|
||||||
{ .compatible = "renesas,sdhi-r8a7778", .data = &sh_mobile_sdhi_of_cfg[0], },
|
|
||||||
{ .compatible = "renesas,sdhi-r8a7779", .data = &sh_mobile_sdhi_of_cfg[0], },
|
|
||||||
{ .compatible = "renesas,sdhi-r8a7790", .data = &sh_mobile_sdhi_of_cfg[0], },
|
|
||||||
{},
|
|
||||||
};
|
|
||||||
MODULE_DEVICE_TABLE(of, sh_mobile_sdhi_of_match);
|
|
||||||
|
|
||||||
static int sh_mobile_sdhi_probe(struct platform_device *pdev)
|
static int sh_mobile_sdhi_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
const struct of_device_id *of_id =
|
const struct of_device_id *of_id =
|
||||||
@ -212,6 +226,8 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
|
|||||||
if (of_id && of_id->data) {
|
if (of_id && of_id->data) {
|
||||||
const struct sh_mobile_sdhi_of_data *of_data = of_id->data;
|
const struct sh_mobile_sdhi_of_data *of_data = of_id->data;
|
||||||
mmc_data->flags |= of_data->tmio_flags;
|
mmc_data->flags |= of_data->tmio_flags;
|
||||||
|
mmc_data->capabilities |= of_data->capabilities;
|
||||||
|
mmc_data->capabilities2 |= of_data->capabilities2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SD control register space size is 0x100, 0x200 for bus_shift=1 */
|
/* SD control register space size is 0x100, 0x200 for bus_shift=1 */
|
||||||
@ -316,10 +332,10 @@ static int sh_mobile_sdhi_remove(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const struct dev_pm_ops tmio_mmc_dev_pm_ops = {
|
static const struct dev_pm_ops tmio_mmc_dev_pm_ops = {
|
||||||
.suspend = tmio_mmc_host_suspend,
|
SET_SYSTEM_SLEEP_PM_OPS(tmio_mmc_host_suspend, tmio_mmc_host_resume)
|
||||||
.resume = tmio_mmc_host_resume,
|
SET_RUNTIME_PM_OPS(tmio_mmc_host_runtime_suspend,
|
||||||
.runtime_suspend = tmio_mmc_host_runtime_suspend,
|
tmio_mmc_host_runtime_resume,
|
||||||
.runtime_resume = tmio_mmc_host_runtime_resume,
|
NULL)
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct platform_driver sh_mobile_sdhi_driver = {
|
static struct platform_driver sh_mobile_sdhi_driver = {
|
||||||
|
@ -23,38 +23,37 @@
|
|||||||
|
|
||||||
#include "tmio_mmc.h"
|
#include "tmio_mmc.h"
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM_SLEEP
|
||||||
static int tmio_mmc_suspend(struct platform_device *dev, pm_message_t state)
|
static int tmio_mmc_suspend(struct device *dev)
|
||||||
{
|
{
|
||||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
struct platform_device *pdev = to_platform_device(dev);
|
||||||
|
const struct mfd_cell *cell = mfd_get_cell(pdev);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = tmio_mmc_host_suspend(&dev->dev);
|
ret = tmio_mmc_host_suspend(dev);
|
||||||
|
|
||||||
/* Tell MFD core it can disable us now.*/
|
/* Tell MFD core it can disable us now.*/
|
||||||
if (!ret && cell->disable)
|
if (!ret && cell->disable)
|
||||||
cell->disable(dev);
|
cell->disable(pdev);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tmio_mmc_resume(struct platform_device *dev)
|
static int tmio_mmc_resume(struct device *dev)
|
||||||
{
|
{
|
||||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
struct platform_device *pdev = to_platform_device(dev);
|
||||||
|
const struct mfd_cell *cell = mfd_get_cell(pdev);
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
/* Tell the MFD core we are ready to be enabled */
|
/* Tell the MFD core we are ready to be enabled */
|
||||||
if (cell->resume)
|
if (cell->resume)
|
||||||
ret = cell->resume(dev);
|
ret = cell->resume(pdev);
|
||||||
|
|
||||||
if (!ret)
|
if (!ret)
|
||||||
ret = tmio_mmc_host_resume(&dev->dev);
|
ret = tmio_mmc_host_resume(dev);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
#define tmio_mmc_suspend NULL
|
|
||||||
#define tmio_mmc_resume NULL
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int tmio_mmc_probe(struct platform_device *pdev)
|
static int tmio_mmc_probe(struct platform_device *pdev)
|
||||||
@ -134,15 +133,18 @@ static int tmio_mmc_remove(struct platform_device *pdev)
|
|||||||
|
|
||||||
/* ------------------- device registration ----------------------- */
|
/* ------------------- device registration ----------------------- */
|
||||||
|
|
||||||
|
static const struct dev_pm_ops tmio_mmc_dev_pm_ops = {
|
||||||
|
SET_SYSTEM_SLEEP_PM_OPS(tmio_mmc_suspend, tmio_mmc_resume)
|
||||||
|
};
|
||||||
|
|
||||||
static struct platform_driver tmio_mmc_driver = {
|
static struct platform_driver tmio_mmc_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "tmio-mmc",
|
.name = "tmio-mmc",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
|
.pm = &tmio_mmc_dev_pm_ops,
|
||||||
},
|
},
|
||||||
.probe = tmio_mmc_probe,
|
.probe = tmio_mmc_probe,
|
||||||
.remove = tmio_mmc_remove,
|
.remove = tmio_mmc_remove,
|
||||||
.suspend = tmio_mmc_suspend,
|
|
||||||
.resume = tmio_mmc_resume,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module_platform_driver(tmio_mmc_driver);
|
module_platform_driver(tmio_mmc_driver);
|
||||||
|
@ -162,16 +162,15 @@ static inline void tmio_mmc_abort_dma(struct tmio_mmc_host *host)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM_SLEEP
|
||||||
int tmio_mmc_host_suspend(struct device *dev);
|
int tmio_mmc_host_suspend(struct device *dev);
|
||||||
int tmio_mmc_host_resume(struct device *dev);
|
int tmio_mmc_host_resume(struct device *dev);
|
||||||
#else
|
|
||||||
#define tmio_mmc_host_suspend NULL
|
|
||||||
#define tmio_mmc_host_resume NULL
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_PM_RUNTIME
|
||||||
int tmio_mmc_host_runtime_suspend(struct device *dev);
|
int tmio_mmc_host_runtime_suspend(struct device *dev);
|
||||||
int tmio_mmc_host_runtime_resume(struct device *dev);
|
int tmio_mmc_host_runtime_resume(struct device *dev);
|
||||||
|
#endif
|
||||||
|
|
||||||
static inline u16 sd_ctrl_read16(struct tmio_mmc_host *host, int addr)
|
static inline u16 sd_ctrl_read16(struct tmio_mmc_host *host, int addr)
|
||||||
{
|
{
|
||||||
|
@ -1142,7 +1142,7 @@ void tmio_mmc_host_remove(struct tmio_mmc_host *host)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL(tmio_mmc_host_remove);
|
EXPORT_SYMBOL(tmio_mmc_host_remove);
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM_SLEEP
|
||||||
int tmio_mmc_host_suspend(struct device *dev)
|
int tmio_mmc_host_suspend(struct device *dev)
|
||||||
{
|
{
|
||||||
struct mmc_host *mmc = dev_get_drvdata(dev);
|
struct mmc_host *mmc = dev_get_drvdata(dev);
|
||||||
@ -1165,9 +1165,9 @@ int tmio_mmc_host_resume(struct device *dev)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(tmio_mmc_host_resume);
|
EXPORT_SYMBOL(tmio_mmc_host_resume);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* CONFIG_PM */
|
#ifdef CONFIG_PM_RUNTIME
|
||||||
|
|
||||||
int tmio_mmc_host_runtime_suspend(struct device *dev)
|
int tmio_mmc_host_runtime_suspend(struct device *dev)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
@ -1184,5 +1184,6 @@ int tmio_mmc_host_runtime_resume(struct device *dev)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(tmio_mmc_host_runtime_resume);
|
EXPORT_SYMBOL(tmio_mmc_host_runtime_resume);
|
||||||
|
#endif
|
||||||
|
|
||||||
MODULE_LICENSE("GPL v2");
|
MODULE_LICENSE("GPL v2");
|
||||||
|
@ -504,7 +504,7 @@ static int ushc_probe(struct usb_interface *intf, const struct usb_device_id *id
|
|||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
ushc->csw = kzalloc(sizeof(struct ushc_cbw), GFP_KERNEL);
|
ushc->csw = kzalloc(sizeof(struct ushc_csw), GFP_KERNEL);
|
||||||
if (ushc->csw == NULL) {
|
if (ushc->csw == NULL) {
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto err;
|
goto err;
|
||||||
|
@ -757,7 +757,7 @@ static int wmt_mci_probe(struct platform_device *pdev)
|
|||||||
struct device_node *np = pdev->dev.of_node;
|
struct device_node *np = pdev->dev.of_node;
|
||||||
const struct of_device_id *of_id =
|
const struct of_device_id *of_id =
|
||||||
of_match_device(wmt_mci_dt_ids, &pdev->dev);
|
of_match_device(wmt_mci_dt_ids, &pdev->dev);
|
||||||
const struct wmt_mci_caps *wmt_caps = of_id->data;
|
const struct wmt_mci_caps *wmt_caps;
|
||||||
int ret;
|
int ret;
|
||||||
int regular_irq, dma_irq;
|
int regular_irq, dma_irq;
|
||||||
|
|
||||||
@ -766,6 +766,8 @@ static int wmt_mci_probe(struct platform_device *pdev)
|
|||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wmt_caps = of_id->data;
|
||||||
|
|
||||||
if (!np) {
|
if (!np) {
|
||||||
dev_err(&pdev->dev, "Missing SDMMC description in devicetree\n");
|
dev_err(&pdev->dev, "Missing SDMMC description in devicetree\n");
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
@ -392,6 +392,15 @@ config REGULATOR_PALMAS
|
|||||||
on the muxing. This is handled automatically in the driver by
|
on the muxing. This is handled automatically in the driver by
|
||||||
reading the mux info from OTP.
|
reading the mux info from OTP.
|
||||||
|
|
||||||
|
config REGULATOR_PBIAS
|
||||||
|
tristate "PBIAS OMAP regulator driver"
|
||||||
|
depends on (ARCH_OMAP || COMPILE_TEST) && MFD_SYSCON
|
||||||
|
help
|
||||||
|
Say y here to support pbias regulator for mmc1:SD card i/o
|
||||||
|
on OMAP SoCs.
|
||||||
|
This driver provides support for OMAP pbias modelled
|
||||||
|
regulators.
|
||||||
|
|
||||||
config REGULATOR_PCAP
|
config REGULATOR_PCAP
|
||||||
tristate "Motorola PCAP2 regulator driver"
|
tristate "Motorola PCAP2 regulator driver"
|
||||||
depends on EZX_PCAP
|
depends on EZX_PCAP
|
||||||
|
@ -55,6 +55,7 @@ obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o
|
|||||||
obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
|
obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
|
||||||
obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o
|
obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o
|
||||||
obj-$(CONFIG_REGULATOR_TPS51632) += tps51632-regulator.o
|
obj-$(CONFIG_REGULATOR_TPS51632) += tps51632-regulator.o
|
||||||
|
obj-$(CONFIG_REGULATOR_PBIAS) += pbias-regulator.o
|
||||||
obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o
|
obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o
|
||||||
obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o
|
obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o
|
||||||
obj-$(CONFIG_REGULATOR_RC5T583) += rc5t583-regulator.o
|
obj-$(CONFIG_REGULATOR_RC5T583) += rc5t583-regulator.o
|
||||||
|
255
drivers/regulator/pbias-regulator.c
Normal file
255
drivers/regulator/pbias-regulator.c
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
/*
|
||||||
|
* pbias-regulator.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com/
|
||||||
|
* Author: Balaji T K <balajitk@ti.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 version 2.
|
||||||
|
*
|
||||||
|
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
||||||
|
* kind, whether express or implied; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mfd/syscon.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/regulator/driver.h>
|
||||||
|
#include <linux/regulator/machine.h>
|
||||||
|
#include <linux/regulator/of_regulator.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
|
||||||
|
struct pbias_reg_info {
|
||||||
|
u32 enable;
|
||||||
|
u32 enable_mask;
|
||||||
|
u32 vmode;
|
||||||
|
unsigned int enable_time;
|
||||||
|
char *name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct pbias_regulator_data {
|
||||||
|
struct regulator_desc desc;
|
||||||
|
void __iomem *pbias_addr;
|
||||||
|
unsigned int pbias_reg;
|
||||||
|
struct regulator_dev *dev;
|
||||||
|
struct regmap *syscon;
|
||||||
|
const struct pbias_reg_info *info;
|
||||||
|
int voltage;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int pbias_regulator_set_voltage(struct regulator_dev *dev,
|
||||||
|
int min_uV, int max_uV, unsigned *selector)
|
||||||
|
{
|
||||||
|
struct pbias_regulator_data *data = rdev_get_drvdata(dev);
|
||||||
|
const struct pbias_reg_info *info = data->info;
|
||||||
|
int ret, vmode;
|
||||||
|
|
||||||
|
if (min_uV <= 1800000)
|
||||||
|
vmode = 0;
|
||||||
|
else if (min_uV > 1800000)
|
||||||
|
vmode = info->vmode;
|
||||||
|
|
||||||
|
ret = regmap_update_bits(data->syscon, data->pbias_reg,
|
||||||
|
info->vmode, vmode);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pbias_regulator_get_voltage(struct regulator_dev *rdev)
|
||||||
|
{
|
||||||
|
struct pbias_regulator_data *data = rdev_get_drvdata(rdev);
|
||||||
|
const struct pbias_reg_info *info = data->info;
|
||||||
|
int value, voltage;
|
||||||
|
|
||||||
|
regmap_read(data->syscon, data->pbias_reg, &value);
|
||||||
|
value &= info->vmode;
|
||||||
|
|
||||||
|
voltage = value ? 3000000 : 1800000;
|
||||||
|
|
||||||
|
return voltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pbias_regulator_enable(struct regulator_dev *rdev)
|
||||||
|
{
|
||||||
|
struct pbias_regulator_data *data = rdev_get_drvdata(rdev);
|
||||||
|
const struct pbias_reg_info *info = data->info;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regmap_update_bits(data->syscon, data->pbias_reg,
|
||||||
|
info->enable_mask, info->enable);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pbias_regulator_disable(struct regulator_dev *rdev)
|
||||||
|
{
|
||||||
|
struct pbias_regulator_data *data = rdev_get_drvdata(rdev);
|
||||||
|
const struct pbias_reg_info *info = data->info;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regmap_update_bits(data->syscon, data->pbias_reg,
|
||||||
|
info->enable_mask, 0);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pbias_regulator_is_enable(struct regulator_dev *rdev)
|
||||||
|
{
|
||||||
|
struct pbias_regulator_data *data = rdev_get_drvdata(rdev);
|
||||||
|
const struct pbias_reg_info *info = data->info;
|
||||||
|
int value;
|
||||||
|
|
||||||
|
regmap_read(data->syscon, data->pbias_reg, &value);
|
||||||
|
|
||||||
|
return (value & info->enable_mask) == info->enable_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct regulator_ops pbias_regulator_voltage_ops = {
|
||||||
|
.set_voltage = pbias_regulator_set_voltage,
|
||||||
|
.get_voltage = pbias_regulator_get_voltage,
|
||||||
|
.enable = pbias_regulator_enable,
|
||||||
|
.disable = pbias_regulator_disable,
|
||||||
|
.is_enabled = pbias_regulator_is_enable,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct pbias_reg_info pbias_mmc_omap2430 = {
|
||||||
|
.enable = BIT(1),
|
||||||
|
.enable_mask = BIT(1),
|
||||||
|
.vmode = BIT(0),
|
||||||
|
.enable_time = 100,
|
||||||
|
.name = "pbias_mmc_omap2430"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct pbias_reg_info pbias_sim_omap3 = {
|
||||||
|
.enable = BIT(9),
|
||||||
|
.enable_mask = BIT(9),
|
||||||
|
.vmode = BIT(8),
|
||||||
|
.enable_time = 100,
|
||||||
|
.name = "pbias_sim_omap3"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct pbias_reg_info pbias_mmc_omap4 = {
|
||||||
|
.enable = BIT(26) | BIT(22),
|
||||||
|
.enable_mask = BIT(26) | BIT(25) | BIT(22),
|
||||||
|
.vmode = BIT(21),
|
||||||
|
.enable_time = 100,
|
||||||
|
.name = "pbias_mmc_omap4"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct pbias_reg_info pbias_mmc_omap5 = {
|
||||||
|
.enable = BIT(27) | BIT(26),
|
||||||
|
.enable_mask = BIT(27) | BIT(25) | BIT(26),
|
||||||
|
.vmode = BIT(21),
|
||||||
|
.enable_time = 100,
|
||||||
|
.name = "pbias_mmc_omap5"
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct of_regulator_match pbias_matches[] = {
|
||||||
|
{ .name = "pbias_mmc_omap2430", .driver_data = (void *)&pbias_mmc_omap2430},
|
||||||
|
{ .name = "pbias_sim_omap3", .driver_data = (void *)&pbias_sim_omap3},
|
||||||
|
{ .name = "pbias_mmc_omap4", .driver_data = (void *)&pbias_mmc_omap4},
|
||||||
|
{ .name = "pbias_mmc_omap5", .driver_data = (void *)&pbias_mmc_omap5},
|
||||||
|
};
|
||||||
|
#define PBIAS_NUM_REGS ARRAY_SIZE(pbias_matches)
|
||||||
|
|
||||||
|
static const struct of_device_id pbias_of_match[] = {
|
||||||
|
{ .compatible = "ti,pbias-omap", },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, pbias_of_match);
|
||||||
|
|
||||||
|
static int pbias_regulator_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device_node *np = pdev->dev.of_node;
|
||||||
|
struct pbias_regulator_data *drvdata;
|
||||||
|
struct resource *res;
|
||||||
|
struct regulator_config cfg = { };
|
||||||
|
struct regmap *syscon;
|
||||||
|
const struct pbias_reg_info *info;
|
||||||
|
int ret = 0;
|
||||||
|
int count, idx, data_idx = 0;
|
||||||
|
|
||||||
|
count = of_regulator_match(&pdev->dev, np, pbias_matches,
|
||||||
|
PBIAS_NUM_REGS);
|
||||||
|
if (count < 0)
|
||||||
|
return count;
|
||||||
|
|
||||||
|
drvdata = devm_kzalloc(&pdev->dev, sizeof(struct pbias_regulator_data)
|
||||||
|
* count, GFP_KERNEL);
|
||||||
|
if (drvdata == NULL) {
|
||||||
|
dev_err(&pdev->dev, "Failed to allocate device data\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
syscon = syscon_regmap_lookup_by_phandle(np, "syscon");
|
||||||
|
if (IS_ERR(syscon))
|
||||||
|
return PTR_ERR(syscon);
|
||||||
|
|
||||||
|
cfg.dev = &pdev->dev;
|
||||||
|
|
||||||
|
for (idx = 0; idx < PBIAS_NUM_REGS && data_idx < count; idx++) {
|
||||||
|
if (!pbias_matches[idx].init_data ||
|
||||||
|
!pbias_matches[idx].of_node)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
info = pbias_matches[idx].driver_data;
|
||||||
|
if (!info)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
if (!res)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
drvdata[data_idx].pbias_reg = res->start;
|
||||||
|
drvdata[data_idx].syscon = syscon;
|
||||||
|
drvdata[data_idx].info = info;
|
||||||
|
drvdata[data_idx].desc.name = info->name;
|
||||||
|
drvdata[data_idx].desc.owner = THIS_MODULE;
|
||||||
|
drvdata[data_idx].desc.type = REGULATOR_VOLTAGE;
|
||||||
|
drvdata[data_idx].desc.ops = &pbias_regulator_voltage_ops;
|
||||||
|
drvdata[data_idx].desc.n_voltages = 2;
|
||||||
|
drvdata[data_idx].desc.enable_time = info->enable_time;
|
||||||
|
|
||||||
|
cfg.init_data = pbias_matches[idx].init_data;
|
||||||
|
cfg.driver_data = &drvdata[data_idx];
|
||||||
|
cfg.of_node = pbias_matches[idx].of_node;
|
||||||
|
|
||||||
|
drvdata[data_idx].dev = devm_regulator_register(&pdev->dev,
|
||||||
|
&drvdata[data_idx].desc, &cfg);
|
||||||
|
if (IS_ERR(drvdata[data_idx].dev)) {
|
||||||
|
ret = PTR_ERR(drvdata[data_idx].dev);
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"Failed to register regulator: %d\n", ret);
|
||||||
|
goto err_regulator;
|
||||||
|
}
|
||||||
|
data_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, drvdata);
|
||||||
|
|
||||||
|
err_regulator:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver pbias_regulator_driver = {
|
||||||
|
.probe = pbias_regulator_probe,
|
||||||
|
.driver = {
|
||||||
|
.name = "pbias-regulator",
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.of_match_table = of_match_ptr(pbias_of_match),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(pbias_regulator_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Balaji T K <balajitk@ti.com>");
|
||||||
|
MODULE_DESCRIPTION("pbias voltage regulator");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_ALIAS("platform:pbias-regulator");
|
@ -45,6 +45,7 @@ struct platform_device;
|
|||||||
struct rtsx_slot {
|
struct rtsx_slot {
|
||||||
struct platform_device *p_dev;
|
struct platform_device *p_dev;
|
||||||
void (*card_event)(struct platform_device *p_dev);
|
void (*card_event)(struct platform_device *p_dev);
|
||||||
|
void (*done_transfer)(struct platform_device *p_dev);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -144,7 +144,7 @@
|
|||||||
#define HOST_TO_DEVICE 0
|
#define HOST_TO_DEVICE 0
|
||||||
#define DEVICE_TO_HOST 1
|
#define DEVICE_TO_HOST 1
|
||||||
|
|
||||||
#define MAX_PHASE 31
|
#define RTSX_PHASE_MAX 32
|
||||||
#define RX_TUNING_CNT 3
|
#define RX_TUNING_CNT 3
|
||||||
|
|
||||||
/* SG descriptor */
|
/* SG descriptor */
|
||||||
@ -943,6 +943,12 @@ void rtsx_pci_send_cmd_no_wait(struct rtsx_pcr *pcr);
|
|||||||
int rtsx_pci_send_cmd(struct rtsx_pcr *pcr, int timeout);
|
int rtsx_pci_send_cmd(struct rtsx_pcr *pcr, int timeout);
|
||||||
int rtsx_pci_transfer_data(struct rtsx_pcr *pcr, struct scatterlist *sglist,
|
int rtsx_pci_transfer_data(struct rtsx_pcr *pcr, struct scatterlist *sglist,
|
||||||
int num_sg, bool read, int timeout);
|
int num_sg, bool read, int timeout);
|
||||||
|
int rtsx_pci_dma_map_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist,
|
||||||
|
int num_sg, bool read);
|
||||||
|
int rtsx_pci_dma_unmap_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist,
|
||||||
|
int num_sg, bool read);
|
||||||
|
int rtsx_pci_dma_transfer(struct rtsx_pcr *pcr, struct scatterlist *sglist,
|
||||||
|
int sg_count, bool read);
|
||||||
int rtsx_pci_read_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len);
|
int rtsx_pci_read_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len);
|
||||||
int rtsx_pci_write_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len);
|
int rtsx_pci_write_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len);
|
||||||
int rtsx_pci_card_pull_ctl_enable(struct rtsx_pcr *pcr, int card);
|
int rtsx_pci_card_pull_ctl_enable(struct rtsx_pcr *pcr, int card);
|
||||||
|
@ -95,7 +95,7 @@ struct mmc_command {
|
|||||||
* actively failing requests
|
* actively failing requests
|
||||||
*/
|
*/
|
||||||
|
|
||||||
unsigned int cmd_timeout_ms; /* in milliseconds */
|
unsigned int busy_timeout; /* busy detect timeout in ms */
|
||||||
/* Set this flag only for blocking sanitize request */
|
/* Set this flag only for blocking sanitize request */
|
||||||
bool sanitize_busy;
|
bool sanitize_busy;
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *,
|
|||||||
struct mmc_command *, int);
|
struct mmc_command *, int);
|
||||||
extern void mmc_start_bkops(struct mmc_card *card, bool from_exception);
|
extern void mmc_start_bkops(struct mmc_card *card, bool from_exception);
|
||||||
extern int __mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int, bool,
|
extern int __mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int, bool,
|
||||||
bool);
|
bool, bool);
|
||||||
extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int);
|
extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int);
|
||||||
extern int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
|
extern int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
|
||||||
|
|
||||||
|
@ -264,15 +264,12 @@ struct mmc_host {
|
|||||||
u32 caps2; /* More host capabilities */
|
u32 caps2; /* More host capabilities */
|
||||||
|
|
||||||
#define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */
|
#define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */
|
||||||
#define MMC_CAP2_CACHE_CTRL (1 << 1) /* Allow cache control */
|
|
||||||
#define MMC_CAP2_FULL_PWR_CYCLE (1 << 2) /* Can do full power cycle */
|
#define MMC_CAP2_FULL_PWR_CYCLE (1 << 2) /* Can do full power cycle */
|
||||||
#define MMC_CAP2_NO_MULTI_READ (1 << 3) /* Multiblock reads don't work */
|
#define MMC_CAP2_NO_MULTI_READ (1 << 3) /* Multiblock reads don't work */
|
||||||
#define MMC_CAP2_NO_SLEEP_CMD (1 << 4) /* Don't allow sleep command */
|
|
||||||
#define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */
|
#define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */
|
||||||
#define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */
|
#define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */
|
||||||
#define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \
|
#define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \
|
||||||
MMC_CAP2_HS200_1_2V_SDR)
|
MMC_CAP2_HS200_1_2V_SDR)
|
||||||
#define MMC_CAP2_BROKEN_VOLTAGE (1 << 7) /* Use the broken voltage */
|
|
||||||
#define MMC_CAP2_HC_ERASE_SZ (1 << 9) /* High-capacity erase size */
|
#define MMC_CAP2_HC_ERASE_SZ (1 << 9) /* High-capacity erase size */
|
||||||
#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */
|
#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */
|
||||||
#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */
|
#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */
|
||||||
@ -281,7 +278,6 @@ struct mmc_host {
|
|||||||
#define MMC_CAP2_PACKED_CMD (MMC_CAP2_PACKED_RD | \
|
#define MMC_CAP2_PACKED_CMD (MMC_CAP2_PACKED_RD | \
|
||||||
MMC_CAP2_PACKED_WR)
|
MMC_CAP2_PACKED_WR)
|
||||||
#define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14) /* Don't power up before scan */
|
#define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14) /* Don't power up before scan */
|
||||||
#define MMC_CAP2_SANITIZE (1 << 15) /* Support Sanitize */
|
|
||||||
|
|
||||||
mmc_pm_flag_t pm_caps; /* supported pm features */
|
mmc_pm_flag_t pm_caps; /* supported pm features */
|
||||||
|
|
||||||
@ -304,7 +300,7 @@ struct mmc_host {
|
|||||||
unsigned int max_req_size; /* maximum number of bytes in one req */
|
unsigned int max_req_size; /* maximum number of bytes in one req */
|
||||||
unsigned int max_blk_size; /* maximum size of one mmc block */
|
unsigned int max_blk_size; /* maximum size of one mmc block */
|
||||||
unsigned int max_blk_count; /* maximum number of blocks in one req */
|
unsigned int max_blk_count; /* maximum number of blocks in one req */
|
||||||
unsigned int max_discard_to; /* max. discard timeout in ms */
|
unsigned int max_busy_timeout; /* max busy timeout in ms */
|
||||||
|
|
||||||
/* private data */
|
/* private data */
|
||||||
spinlock_t lock; /* lock for claim and bus ops */
|
spinlock_t lock; /* lock for claim and bus ops */
|
||||||
@ -388,8 +384,6 @@ int mmc_power_restore_host(struct mmc_host *host);
|
|||||||
void mmc_detect_change(struct mmc_host *, unsigned long delay);
|
void mmc_detect_change(struct mmc_host *, unsigned long delay);
|
||||||
void mmc_request_done(struct mmc_host *, struct mmc_request *);
|
void mmc_request_done(struct mmc_host *, struct mmc_request *);
|
||||||
|
|
||||||
int mmc_cache_ctrl(struct mmc_host *, u8);
|
|
||||||
|
|
||||||
static inline void mmc_signal_sdio_irq(struct mmc_host *host)
|
static inline void mmc_signal_sdio_irq(struct mmc_host *host)
|
||||||
{
|
{
|
||||||
host->ops->enable_sdio_irq(host, 0);
|
host->ops->enable_sdio_irq(host, 0);
|
||||||
@ -424,12 +418,9 @@ static inline int mmc_regulator_get_supply(struct mmc_host *mmc)
|
|||||||
|
|
||||||
int mmc_pm_notify(struct notifier_block *notify_block, unsigned long, void *);
|
int mmc_pm_notify(struct notifier_block *notify_block, unsigned long, void *);
|
||||||
|
|
||||||
/* Module parameter */
|
|
||||||
extern bool mmc_assume_removable;
|
|
||||||
|
|
||||||
static inline int mmc_card_is_removable(struct mmc_host *host)
|
static inline int mmc_card_is_removable(struct mmc_host *host)
|
||||||
{
|
{
|
||||||
return !(host->caps & MMC_CAP_NONREMOVABLE) && mmc_assume_removable;
|
return !(host->caps & MMC_CAP_NONREMOVABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int mmc_card_keep_power(struct mmc_host *host)
|
static inline int mmc_card_keep_power(struct mmc_host *host)
|
||||||
|
@ -18,17 +18,9 @@
|
|||||||
/*
|
/*
|
||||||
* struct sdhci_plat_data: spear sdhci platform data structure
|
* struct sdhci_plat_data: spear sdhci platform data structure
|
||||||
*
|
*
|
||||||
* @card_power_gpio: gpio pin for enabling/disabling power to sdhci socket
|
|
||||||
* @power_active_high: if set, enable power to sdhci socket by setting
|
|
||||||
* card_power_gpio
|
|
||||||
* @power_always_enb: If set, then enable power on probe, otherwise enable only
|
|
||||||
* on card insertion and disable on card removal.
|
|
||||||
* card_int_gpio: gpio pin used for card detection
|
* card_int_gpio: gpio pin used for card detection
|
||||||
*/
|
*/
|
||||||
struct sdhci_plat_data {
|
struct sdhci_plat_data {
|
||||||
int card_power_gpio;
|
|
||||||
int power_active_high;
|
|
||||||
int power_always_enb;
|
|
||||||
int card_int_gpio;
|
int card_int_gpio;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,6 +100,8 @@ struct sdhci_host {
|
|||||||
#define SDHCI_QUIRK2_BROKEN_HOST_CONTROL (1<<5)
|
#define SDHCI_QUIRK2_BROKEN_HOST_CONTROL (1<<5)
|
||||||
/* Controller does not support HS200 */
|
/* Controller does not support HS200 */
|
||||||
#define SDHCI_QUIRK2_BROKEN_HS200 (1<<6)
|
#define SDHCI_QUIRK2_BROKEN_HS200 (1<<6)
|
||||||
|
/* Controller does not support DDR50 */
|
||||||
|
#define SDHCI_QUIRK2_BROKEN_DDR50 (1<<7)
|
||||||
|
|
||||||
int irq; /* Device IRQ */
|
int irq; /* Device IRQ */
|
||||||
void __iomem *ioaddr; /* Mapped address */
|
void __iomem *ioaddr; /* Mapped address */
|
||||||
|
@ -22,4 +22,10 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio,
|
|||||||
unsigned int debounce);
|
unsigned int debounce);
|
||||||
void mmc_gpio_free_cd(struct mmc_host *host);
|
void mmc_gpio_free_cd(struct mmc_host *host);
|
||||||
|
|
||||||
|
int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,
|
||||||
|
unsigned int idx, bool override_active_level,
|
||||||
|
unsigned int debounce);
|
||||||
|
void mmc_gpiod_free_cd(struct mmc_host *host);
|
||||||
|
void mmc_gpiod_request_cd_irq(struct mmc_host *host);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
/*
|
#ifndef __MMC_MSM_SDCC_H
|
||||||
* arch/arm/include/asm/mach/mmc.h
|
#define __MMC_MSM_SDCC_H
|
||||||
*/
|
|
||||||
#ifndef ASMARM_MACH_MMC_H
|
|
||||||
#define ASMARM_MACH_MMC_H
|
|
||||||
|
|
||||||
#include <linux/mmc/host.h>
|
#include <linux/mmc/host.h>
|
||||||
#include <linux/mmc/card.h>
|
#include <linux/mmc/card.h>
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* arch/arm/plat-orion/include/plat/mvsdio.h
|
|
||||||
*
|
|
||||||
* This file is licensed under the terms of the GNU General Public
|
* This file is licensed under the terms of the GNU General Public
|
||||||
* License version 2. This program is licensed "as is" without any
|
* License version 2. This program is licensed "as is" without any
|
||||||
* warranty of any kind, whether express or implied.
|
* warranty of any kind, whether express or implied.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __MACH_MVSDIO_H
|
#ifndef __MMC_MVSDIO_H
|
||||||
#define __MACH_MVSDIO_H
|
#define __MMC_MVSDIO_H
|
||||||
|
|
||||||
#include <linux/mbus.h>
|
#include <linux/mbus.h>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user