ARM: sunxi: Add driver for SD/MMC hosts found on sunxi SoCs
Subsystems (5)
v13 cover ARM: sunxi: Add driver for SD/MMC hosts found on sunxi SoCs
Hi All,
Here is lucky number v13 of the sunxi-mmc patch-set David Lanzend?rfer and I have been working on, with the __clk_get_hw call moved from sunxi-mmc.c to clk-sunxi.c where it belongs.
Mike, the first patch in this series modifies clk_sunxi_mmc_phase_control to take a struct clk * as argument rather then a struct clk_hw *, and should go upstream through the clk tree. Feel free to squash this into the patch adding the clk_sunxi_mmc_phase_control function if you prefer.
The second patch in this series is the patch adding the actual mmc driver and should go in through the mmc tree.
Changes since v1:
-Using mmc_of_parse instead of diy dt parsing -Adding nodes for all mmc controller to the dtsi files,
including sofar unused controllers
-Using generic GPIO slot library for WP/CD -Adding additional MMC device nodes into DTSI files
Changes since v2:
-Add missing Signed-off-by tags
-Stop using __raw_readl / __raw_writel so that barriers are properly used -Adding missing new lines
-Adding missing patch for automatic reparenting of clocks
Changes since v3:
-Move clk_enable / disable into host_init / exit (Hans) -Fix hang on boot caused by irq storm (Hans)
Changes since v4:
-moving sunxi-mci.{c/h} to sunxi-mmc.{c/h} -removing camel cases from the defines in sunxi-mmc.h -moving defines out of the struct definition
since this is bad coding style
-adding documentation for the device tree binding Changes since v5:
-adding host initialization for when the sdio irq is enabled
(just to make sure having a defined state at all time)
-add mmc support fixup: set pullup on cd pins -fixup: Don't set MMC_CAP_NEEDS_POLL / MMC_CAP_4_BIT_DATA
Changes since v6:
-fixing copyright info in sunxi-mmc.*
-s/__SUNXI_MCI_H__/__SUNXI_MMC_H__/g
-s/SDXC_RESPONSE_/SDXC_RESP_/g
-s/define/definitions <- Comment from Priit Laes
Changes since v7:
-Merge sunxi-mmc.h into sunxi-mmc.c
-Various style fixes / cleanups based on Maxime's review
-sun6i support
-Fix a race condition in interrupt / tasklet interaction
-Split the dts patches into 3 per platform:
1) Add mmc nodes to the dtsi
2) Add mmc pinmux to the dtsi
3) Add mmc nodes to the various board files
-Moved setting of bus-width and cd gpio polarity from .dtsi to the board-files -Added sun6i dts patches
Changes since v8:
-Don't claim MMC_CAP_SDIO_IRQ by default, sdio-irq support appears to not
always be reliable. Can be re-added to the caps on a per board basis through dts
-Added EXPORT_SYMBOL(clk_sunxi_mmc_phase_control)
-Moved bus-width and cd-inverted dts attributes for sun6i from dtsi to dts -Squashed patches adding sun6i-a31-m9.dts and mmc support for m9 together -Added a patch enabling the sdio wifi on the cubietruck in dts
Changes since v9:
-Drop the sun5i and sun6i dts pinmux patches as those have already been
accepted
-Rename the mmc clock for the controller from "mod" to "mmc" so as
to not confuse it with a regular mod0 clock
-Rename pinmux for the reference design card-detect pin from cd_pin_a to
cd_pin_reference_design
Changes since v10:
-Dropped all dts and clk patches, as they are all accepted now -Cleanup some somewhat weird names (leftovers from the android code):
s/mci_readl/mmc_readl/
s/mci_writel/mmc_write/
s/smc_host/host/
-Switched to mmc_regulator_get_supply API and added fixed regulator nodes to
all board files (introducing new dts patches)
-Got rid of duplicate ios info
-Stop checking card-detect before each request
-Drop usleep after changing the clock rate
-Drop incomplete / wrong voltage switching support
-Drop the large comment with values copy/pasted from the spec -Move enable/disable clk + reset + irq calls from power up / down to
probe / remove
-Make sunxi_mmc_clk_set_rate / sunxi_mmc_oclk_onoff return an error -Use a threaded interrupt handler for sending an explicit stop on data errors -Fix sunxi_mmc_send_manual_stop to properly stop ongoing SD_IO_RW_EXTENDED cmds
Changes since v11:
-Instead of adding a reg_vmmc0 regulator add a fixed reg_vcc3v3 regulator, as
we may need it in other places too
-Also add a vmmc-supply entry to thr non mmc0 nodes on the 2 boards which have
multiple sdcard slots.
Changes since v12:
-Drop all dts patches, as they are all merged for going upstream -Add a patch modifying clk_sunxi_mmc_phase_control to take a struct clk * as
argument rather then a struct clk_hw *
-Modify the patch adding the actual sunxi-mmc driver for the
clk_sunxi_mmc_phase_control API change
Regards,
Hans
PATCH v13 1/2 clk: sunxi: Fixup clk_sunxi_mmc_phase_control to take a clk rather then a hw_clk
__clk_get_hw is supposed to be used by clk providers, not clk consumers.
--- drivers/clk/sunxi/clk-sunxi.c | 3 ++- include/linux/clk/sunxi.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-)
drivers/clk/sunxi/clk-sunxi.c +2 -1
@@ -510,11 +510,12 @@ CLK_OF_DECLARE(sun7i_a20_gmac, "allwinner,sun7i-a20-gmac-clk",* clk_sunxi_mmc_phase_control() - configures MMC clock phase control */ -void clk_sunxi_mmc_phase_control (struct clk_hw *hw,u8 sample,u8 output) +void clk_sunxi_mmc_phase_control (struct clk *clk,u8 sample,u8 output) {#define to_clk_composite (_hw) container_of(_hw, struct clk_composite, hw)#define to_clk_factors (_hw) container_of(_hw, struct clk_factors, hw) +struct clk_hw *hw =__clk_get_hw (clk);struct clk_composite *composite =to_clk_composite (hw);struct clk_hw *rate_hw = composite->rate_hw;struct clk_factors *factors =to_clk_factors (rate_hw);
include/linux/clk/sunxi.h +1 -1
@@ -17,6 +17,6 @@#include <linux/clk.h> -void clk_sunxi_mmc_phase_control (struct clk_hw *hw,u8 sample,u8 output); +void clk_sunxi_mmc_phase_control (struct clk *clk,u8 sample,u8 output);#endif
Signature · 1 line
1.9.0
PATCH v13 1/2 clk: sunxi: Fixup clk_sunxi_mmc_phase_control to take a clk rather then a hw_clk
Quoted 3 lines · Hans de Goede
__clk_get_hwis supposed to be used by clk providers, not clk consumers.Signed-off-by Hans de Goede <[email protected]>
Quoted 38 lines
--- drivers/clk/sunxi/clk-sunxi.c | 3 ++- include/linux/clk/sunxi.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-)drivers/clk/sunxi/clk-sunxi.c +2
-1@@ -510,11 +510,12 @@ CLK_OF_DECLARE(sun7i_a20_gmac, "allwinner,sun7i-a20-gmac-clk",* clk_sunxi_mmc_phase_control() - configures MMC clock phase control */ -void clk_sunxi_mmc_phase_control (struct clk_hw *hw,u8 sample,u8 output) +void clk_sunxi_mmc_phase_control (struct clk *clk,u8 sample,u8 output) {#define to_clk_composite (_hw) container_of(_hw, struct clk_composite, hw)#define to_clk_factors (_hw) container_of(_hw, struct clk_factors, hw) +struct clk_hw *hw =__clk_get_hw (clk);struct clk_composite *composite =to_clk_composite (hw);struct clk_hw *rate_hw = composite->rate_hw;struct clk_factors *factors =to_clk_factors (rate_hw);include/linux/clk/sunxi.h +1
-2@@ -17,6 +17,6 @@#include <linux/clk.h> -void clk_sunxi_mmc_phase_control (struct clk_hw *hw,u8 sample,u8 output); +void clk_sunxi_mmc_phase_control (struct clk *clk,u8 sample,u8 output);#endif --1.9.0
PATCH v13 2/2 ARM: sunxi: Add driver for SD/MMC hosts found on Allwinner sunxi SoCs
In reply to Hans de GoedeFrom: David Lanzend??rfer <[email protected]>
The Allwinner sunxi mmc host uses dma in bus-master mode using a built-in designware idmac controller, which is identical to the one found in the mmc-dw hosts. However the rest of the host is not identical to mmc-dw, it deals with sending stop commands in hardware which makes it significantly different from the mmc-dw devices.
HdG: Various cleanups and fixes.
--- .../devicetree/bindings/mmc/sunxi-mmc.txt | 43 + drivers/mmc/host/Kconfig | 7 + drivers/mmc/host/Makefile | 2 + drivers/mmc/host/sunxi-mmc.c | 1049 ++++++++++++++++++++ 4 files changed, 1101 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/sunxi-mmc.txt create mode 100644 drivers/mmc/host/sunxi-mmc.c
Documentation/devicetree/bindings/mmc/sunxi-mmc.txt new file +43 -0
@@ -0,0 +1,43 @@ +* Allwinner sunxi MMC controller + +The highspeed MMC host controller on Allwinner SoCs provides an interface +for MMC, SD and SDIO types of memory cards. + +Supported maximum speeds are the ones of the eMMC standard 4.5 as well +as the speed of SD standard 3.0. +Absolute maximum transfer rate is 200MB/s + +Required properties: + - compatible : "allwinner,sun4i-a10-mmc" or "allwinner,sun5i-a13-mmc" + - reg : mmc controller base registers + - clocks : a list with 2 phandle + clock specifier pairs + - clock-names : must contain "ahb" and "mmc" + - interrupts : mmc controller interrupt + +Optional properties: + - resets : phandle + reset specifier pair + - reset-names : must contain "ahb" + - for cd, bus-width and additional generic mmc parameters + please refer to mmc.txt within this directory + +Examples: + - Within .dtsi: + mmc0: mmc at 01c0f000 { + compatible = "allwinner,sun5i-a13-mmc"; + reg = <0x01c0f000 0x1000>; + clocks = <&ahb_gates 8>, <&mmc0_clk>; + clock-names = "ahb", "mod"; + interrupts = <0 32 4>; + status = "disabled"; + }; + + - Within dts: + mmc0: mmc at 01c0f000 { + pinctrl-names = "default", "default"; + pinctrl-0 = <&mmc0_pins_a>; + pinctrl-1 = <&mmc0_cd_pin_reference_design>; + bus-width = <4>; + cd-gpios = <&pio 7 1 0>; /* PH1 */ + cd-inverted; + status = "okay"; + };
drivers/mmc/host/Kconfig +7 -0
@@ -694,3 +694,10 @@ config MMC_REALTEK_PCI help Say Y here to include driver code to support SD/MMC card interface of Realtek PCI-E card reader + +config MMC_SUNXI + tristate "Allwinner sunxi SD/MMC Host Controller support" + depends on ARCH_SUNXI + help + This selects support for the SD/MMC Host Controller on + Allwinner sunxi SoCs.
drivers/mmc/host/Makefile +2 -0
@@ -53,6 +53,8 @@ obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o +obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o + obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o
drivers/mmc/host/sunxi-mmc.c new file +1049 -0
1,049 lines not on this page · raw message
Signature · 1 line
1.9.0
PATCH v13 2/2 ARM: sunxi: Add driver for SD/MMC hosts found on Allwinner sunxi SoCs
Quoted 8 lines · On Monday 12 May 2014 14:04:48 Hans de Goede wrote:
From: David Lanzend?rfer <[email protected]>
The Allwinner sunxi mmc host uses dma in bus-master mode using a built-in designware idmac controller, which is identical to the one found in the mmc-dw hosts. However the rest of the host is not identical to mmc-dw, it deals with sending stop commands in hardware which makes it significantly different from the mmc-dw devices.
Quoted 5 lines
HdG: Various cleanups and fixes.
Signed-off-by David Lanzend?rfer <[email protected]>Signed-off-by Hans de Goede <[email protected]>
PATCH v13 2/2 ARM: sunxi: Add driver for SD/MMC hosts found on Allwinner sunxi SoCs
In reply to Hans de GoedeQuoted 9 lines · Hans de Goede
From: David Lanzend?rfer<[email protected]>
The Allwinner sunxi mmc host uses dma in bus-master mode using a built-in designware idmac controller, which is identical to the one found in the mmc-dw hosts. However the rest of the host is not identical to mmc-dw, it deals with sending stop commands in hardware which makes it significantly different from the mmc-dw devices.
HdG: Various cleanups and fixes.
Just nitpicking, but usually the above line should be added below the original signoff, so:
Quoted 1 line
Signed-off-by David Lanzend?rfer<[email protected]>
[hdegoede at redhat.com: various cleanups and fixes]
Quoted 1 line
Signed-off-by Hans de Goede<[email protected]>
As is documented in SubmittingPatches.
Gr. AvS
Quoted 1,148 lines
--- .../devicetree/bindings/mmc/sunxi-mmc.txt | 43 + drivers/mmc/host/Kconfig | 7 + drivers/mmc/host/Makefile | 2 + drivers/mmc/host/sunxi-mmc.c | 1049 ++++++++++++++++++++ 4 files changed, 1101 insertions(+)create mode 100644
Documentation/devicetree/bindings/mmc/sunxi-mmc.txtcreate mode 100644drivers/mmc/host/sunxi-mmc.cDocumentation/devicetree/bindings/mmc/sunxi-mmc.txt new file +43
-0@@ -0,0 +1,43 @@ +* Allwinner sunxi MMC controller + +The highspeed MMC host controller on Allwinner SoCs provides an interface +for MMC, SD and SDIO types of memory cards. + +Supported maximum speeds are the ones of the eMMC standard 4.5 as well +as the speed of SD standard 3.0. +Absolute maximum transfer rate is 200MB/s + +Required properties: + - compatible : "allwinner,sun4i-a10-mmc" or "allwinner,sun5i-a13-mmc" + - reg : mmc controller base registers + - clocks : a list with 2 phandle + clock specifier pairs + - clock-names : must contain "ahb" and "mmc" + - interrupts : mmc controller interrupt + +Optional properties: + - resets : phandle + reset specifier pair + - reset-names : must contain "ahb" + - for cd, bus-width and additional generic mmc parameters + please refer to mmc.txt within this directory + +Examples: + - Within .dtsi: + mmc0: mmc at 01c0f000 { + compatible = "allwinner,sun5i-a13-mmc"; + reg =<0x01c0f000 0x1000>; + clocks =<&ahb_gates 8>,<&mmc0_clk>; + clock-names = "ahb", "mod"; + interrupts =<0 32 4>; + status = "disabled"; + }; + + - Within dts: + mmc0: mmc at 01c0f000 { + pinctrl-names = "default", "default"; + pinctrl-0 =<&mmc0_pins_a>; + pinctrl-1 =<&mmc0_cd_pin_reference_design>; + bus-width =<4>; + cd-gpios =<&pio 7 1 0>; /* PH1 */ + cd-inverted; + status = "okay"; + };drivers/mmc/host/Kconfig +7
-0@@ -694,3 +694,10 @@ config MMC_REALTEK_PCI help Say Y here to include driver code to support SD/MMC card interface of Realtek PCI-E card reader + +config MMC_SUNXI + tristate "Allwinner sunxi SD/MMC Host Controller support" + depends on ARCH_SUNXI + help + This selects support for the SD/MMC Host Controller on + Allwinner sunxi SoCs.drivers/mmc/host/Makefile +2
-0@@ -53,6 +53,8 @@ obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o +obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o + obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.odrivers/mmc/host/sunxi-mmc.c new file +1049
-0@@ -0,0 +1,1049 @@ +/* +* Driver for sunxi SD/MMC host controllers +* (C) Copyright 2007-2011 Reuuimlla Technology Co., Ltd. +* (C) Copyright 2007-2011 Aaron Maoye<[email protected]> +* (C) Copyright 2013-2014 O2S GmbH<www.o2s.ch> +* (C) Copyright 2013-2014 David Lanzend?rfer<[email protected]> +* (C) Copyright 2013-2014 Hans de Goede<[email protected]> +* +* 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. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/err.h> + +#include <linux/clk.h> +#include <linux/clk-private.h> +#include <linux/clk/sunxi.h> + +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/scatterlist.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/reset.h> + +#include <linux/of_address.h> +#include <linux/of_gpio.h> +#include <linux/of_platform.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/core.h> +#include <linux/mmc/card.h> +#include <linux/mmc/slot-gpio.h> + +/* register offset definitions */ +#define SDXC_REG_GCTRL (0x00)/* SMC Global Control Register */ +#define SDXC_REG_CLKCR (0x04)/* SMC Clock Control Register */ +#define SDXC_REG_TMOUT (0x08)/* SMC Time Out Register */ +#define SDXC_REG_WIDTH (0x0C)/* SMC Bus Width Register */ +#define SDXC_REG_BLKSZ (0x10)/* SMC Block Size Register */ +#define SDXC_REG_BCNTR (0x14)/* SMC Byte Count Register */ +#define SDXC_REG_CMDR (0x18)/* SMC Command Register */ +#define SDXC_REG_CARG (0x1C)/* SMC Argument Register */ +#define SDXC_REG_RESP0 (0x20)/* SMC Response Register 0 */ +#define SDXC_REG_RESP1 (0x24)/* SMC Response Register 1 */ +#define SDXC_REG_RESP2 (0x28)/* SMC Response Register 2 */ +#define SDXC_REG_RESP3 (0x2C)/* SMC Response Register 3 */ +#define SDXC_REG_IMASK (0x30)/* SMC Interrupt Mask Register */ +#define SDXC_REG_MISTA (0x34)/* SMC Masked Interrupt Status Register */ +#define SDXC_REG_RINTR (0x38)/* SMC Raw Interrupt Status Register */ +#define SDXC_REG_STAS (0x3C)/* SMC Status Register */ +#define SDXC_REG_FTRGL (0x40)/* SMC FIFO Threshold Watermark Registe */ +#define SDXC_REG_FUNS (0x44)/* SMC Function Select Register */ +#define SDXC_REG_CBCR (0x48)/* SMC CIU Byte Count Register */ +#define SDXC_REG_BBCR (0x4C)/* SMC BIU Byte Count Register */ +#define SDXC_REG_DBGC (0x50)/* SMC Debug Enable Register */ +#define SDXC_REG_HWRST (0x78)/* SMC Card Hardware Reset for Register */ +#define SDXC_REG_DMAC (0x80)/* SMC IDMAC Control Register */ +#define SDXC_REG_DLBA (0x84)/* SMC IDMAC Descriptor List Base Addre */ +#define SDXC_REG_IDST (0x88)/* SMC IDMAC Status Register */ +#define SDXC_REG_IDIE (0x8C)/* SMC IDMAC Interrupt Enable Register */ +#define SDXC_REG_CHDA (0x90) +#define SDXC_REG_CBDA (0x94) + +#define mmc_readl (host, reg) \ + readl((host)->reg_base + SDXC_##reg) +#define mmc_writel (host, reg, value) \ + writel((value), (host)->reg_base + SDXC_##reg) + +/* global control register bits */ +#define SDXC_SOFT_RESET BIT(0) +#define SDXC_FIFO_RESET BIT(1) +#define SDXC_DMA_RESET BIT(2) +#define SDXC_INTERRUPT_ENABLE_BIT BIT(4) +#define SDXC_DMA_ENABLE_BIT BIT(5) +#define SDXC_DEBOUNCE_ENABLE_BIT BIT(8) +#define SDXC_POSEDGE_LATCH_DATA BIT(9) +#define SDXC_DDR_MODE BIT(10) +#define SDXC_MEMORY_ACCESS_DONE BIT(29) +#define SDXC_ACCESS_DONE_DIRECT BIT(30) +#define SDXC_ACCESS_BY_AHB BIT(31) +#define SDXC_ACCESS_BY_DMA (0<< 31) +#define SDXC_HARDWARE_RESET \ + (SDXC_SOFT_RESET | SDXC_FIFO_RESET | SDXC_DMA_RESET) + +/* clock control bits */ +#define SDXC_CARD_CLOCK_ON BIT(16) +#define SDXC_LOW_POWER_ON BIT(17) + +/* bus width */ +#define SDXC_WIDTH1 0 +#define SDXC_WIDTH4 1 +#define SDXC_WIDTH8 2 + +/* smc command bits */ +#define SDXC_RESP_EXPIRE BIT(6) +#define SDXC_LONG_RESPONSE BIT(7) +#define SDXC_CHECK_RESPONSE_CRC BIT(8) +#define SDXC_DATA_EXPIRE BIT(9) +#define SDXC_WRITE BIT(10) +#define SDXC_SEQUENCE_MODE BIT(11) +#define SDXC_SEND_AUTO_STOP BIT(12) +#define SDXC_WAIT_PRE_OVER BIT(13) +#define SDXC_STOP_ABORT_CMD BIT(14) +#define SDXC_SEND_INIT_SEQUENCE BIT(15) +#define SDXC_UPCLK_ONLY BIT(21) +#define SDXC_READ_CEATA_DEV BIT(22) +#define SDXC_CCS_EXPIRE BIT(23) +#define SDXC_ENABLE_BIT_BOOT BIT(24) +#define SDXC_ALT_BOOT_OPTIONS BIT(25) +#define SDXC_BOOT_ACK_EXPIRE BIT(26) +#define SDXC_BOOT_ABORT BIT(27) +#define SDXC_VOLTAGE_SWITCH BIT(28) +#define SDXC_USE_HOLD_REGISTER BIT(29) +#define SDXC_START BIT(31) + +/* interrupt bits */ +#define SDXC_RESP_ERROR BIT(1) +#define SDXC_COMMAND_DONE BIT(2) +#define SDXC_DATA_OVER BIT(3) +#define SDXC_TX_DATA_REQUEST BIT(4) +#define SDXC_RX_DATA_REQUEST BIT(5) +#define SDXC_RESP_CRC_ERROR BIT(6) +#define SDXC_DATA_CRC_ERROR BIT(7) +#define SDXC_RESP_TIMEOUT BIT(8) +#define SDXC_DATA_TIMEOUT BIT(9) +#define SDXC_VOLTAGE_CHANGE_DONE BIT(10) +#define SDXC_FIFO_RUN_ERROR BIT(11) +#define SDXC_HARD_WARE_LOCKED BIT(12) +#define SDXC_START_BIT_ERROR BIT(13) +#define SDXC_AUTO_COMMAND_DONE BIT(14) +#define SDXC_END_BIT_ERROR BIT(15) +#define SDXC_SDIO_INTERRUPT BIT(16) +#define SDXC_CARD_INSERT BIT(30) +#define SDXC_CARD_REMOVE BIT(31) +#define SDXC_INTERRUPT_ERROR_BIT \ + (SDXC_RESP_ERROR | SDXC_RESP_CRC_ERROR | SDXC_DATA_CRC_ERROR | \ + SDXC_RESP_TIMEOUT | SDXC_DATA_TIMEOUT | SDXC_FIFO_RUN_ERROR | \ + SDXC_HARD_WARE_LOCKED | SDXC_START_BIT_ERROR | SDXC_END_BIT_ERROR) +#define SDXC_INTERRUPT_DONE_BIT \ + (SDXC_AUTO_COMMAND_DONE | SDXC_DATA_OVER | \ + SDXC_COMMAND_DONE | SDXC_VOLTAGE_CHANGE_DONE) + +/* status */ +#define SDXC_RXWL_FLAG BIT(0) +#define SDXC_TXWL_FLAG BIT(1) +#define SDXC_FIFO_EMPTY BIT(2) +#define SDXC_FIFO_FULL BIT(3) +#define SDXC_CARD_PRESENT BIT(8) +#define SDXC_CARD_DATA_BUSY BIT(9) +#define SDXC_DATA_FSM_BUSY BIT(10) +#define SDXC_DMA_REQUEST BIT(31) +#define SDXC_FIFO_SIZE 16 + +/* Function select */ +#define SDXC_CEATA_ON (0xceaa<< 16) +#define SDXC_SEND_IRQ_RESPONSE BIT(0) +#define SDXC_SDIO_READ_WAIT BIT(1) +#define SDXC_ABORT_READ_DATA BIT(2) +#define SDXC_SEND_CCSD BIT(8) +#define SDXC_SEND_AUTO_STOPCCSD BIT(9) +#define SDXC_CEATA_DEV_IRQ_ENABLE BIT(10) + +/* IDMA controller bus mod bit field */ +#define SDXC_IDMAC_SOFT_RESET BIT(0) +#define SDXC_IDMAC_FIX_BURST BIT(1) +#define SDXC_IDMAC_IDMA_ON BIT(7) +#define SDXC_IDMAC_REFETCH_DES BIT(31) + +/* IDMA status bit field */ +#define SDXC_IDMAC_TRANSMIT_INTERRUPT BIT(0) +#define SDXC_IDMAC_RECEIVE_INTERRUPT BIT(1) +#define SDXC_IDMAC_FATAL_BUS_ERROR BIT(2) +#define SDXC_IDMAC_DESTINATION_INVALID BIT(4) +#define SDXC_IDMAC_CARD_ERROR_SUM BIT(5) +#define SDXC_IDMAC_NORMAL_INTERRUPT_SUM BIT(8) +#define SDXC_IDMAC_ABNORMAL_INTERRUPT_SUM BIT(9) +#define SDXC_IDMAC_HOST_ABORT_INTERRUPT BIT(10) +#define SDXC_IDMAC_IDLE (0<< 13) +#define SDXC_IDMAC_SUSPEND (1<< 13) +#define SDXC_IDMAC_DESC_READ (2<< 13) +#define SDXC_IDMAC_DESC_CHECK (3<< 13) +#define SDXC_IDMAC_READ_REQUEST_WAIT (4<< 13) +#define SDXC_IDMAC_WRITE_REQUEST_WAIT (5<< 13) +#define SDXC_IDMAC_READ (6<< 13) +#define SDXC_IDMAC_WRITE (7<< 13) +#define SDXC_IDMAC_DESC_CLOSE (8<< 13) + +/* +* If the idma-des-size-bits of property is ie 13, bufsize bits are: +* Bits 0-12: buf1 size +* Bits 13-25: buf2 size +* Bits 26-31: not used +* Since we only ever set buf1 size, we can simply store it directly. +*/ +#define SDXC_IDMAC_DES0_DIC BIT(1)/* disable interrupt on completion */ +#define SDXC_IDMAC_DES0_LD BIT(2)/* last descriptor */ +#define SDXC_IDMAC_DES0_FD BIT(3)/* first descriptor */ +#define SDXC_IDMAC_DES0_CH BIT(4)/* chain mode */ +#define SDXC_IDMAC_DES0_ER BIT(5)/* end of ring */ +#define SDXC_IDMAC_DES0_CES BIT(30)/* card error summary */ +#define SDXC_IDMAC_DES0_OWN BIT(31)/* 1-idma owns it, 0-host owns it */ + +struct sunxi_idma_des { +u32 config; +u32 buf_size; +u32 buf_addr_ptr1; +u32 buf_addr_ptr2; +}; + +struct sunxi_mmc_host { +struct mmc_host *mmc; +struct reset_control *reset; + +/* IO mapping base */ +void __iomem *reg_base; + +/* clock management */ +struct clk *clk_ahb; +struct clk *clk_mmc; + +/* irq */ +spinlock_t lock; +int irq; +u32 int_sum; +u32 sdio_imask; + +/* dma */ +u32 idma_des_size_bits; +dma_addr_t sg_dma; +void *sg_cpu; +bool wait_dma; + +struct mmc_request *mrq; +struct mmc_request *manual_stop_mrq; +int ferror; +}; + +static int sunxi_mmc_reset_host (struct sunxi_mmc_host *host) +{ +unsigned long expire = jiffies +msecs_to_jiffies (250 ); +u32 rval; + +mmc_writel (host,REG_CMDR ,SDXC_HARDWARE_RESET ); +do { + rval =mmc_readl (host,REG_GCTRL ); + }while (time_before (jiffies, expire)&& (rval&SDXC_HARDWARE_RESET )); + +if (rval&SDXC_HARDWARE_RESET ) { +dev_err (mmc_dev (host->mmc),"fatal err reset timeout\n" ); +return -EIO ; + } + +return 0 ; +} + +static int sunxi_mmc_init_host (struct mmc_host *mmc) +{ +u32 rval; +struct sunxi_mmc_host *host =mmc_priv (mmc); + +if (sunxi_mmc_reset_host (host)) +return -EIO ; + +mmc_writel (host,REG_FTRGL ,0x20070008 ); +mmc_writel (host,REG_TMOUT ,0xffffffff ); +mmc_writel (host,REG_IMASK , host->sdio_imask); +mmc_writel (host,REG_RINTR ,0xffffffff ); +mmc_writel (host,REG_DBGC ,0xdeb ); +mmc_writel (host,REG_FUNS ,SDXC_CEATA_ON ); +mmc_writel (host,REG_DLBA , host->sg_dma); + + rval =mmc_readl (host,REG_GCTRL ); + rval |=SDXC_INTERRUPT_ENABLE_BIT ; + rval&= ~SDXC_ACCESS_DONE_DIRECT ; +mmc_writel (host,REG_GCTRL , rval); + +return 0 ; +} + +static void sunxi_mmc_init_idma_des (struct sunxi_mmc_host *host, +struct mmc_data *data) +{ +struct sunxi_idma_des *pdes = (struct sunxi_idma_des *)host->sg_cpu; +struct sunxi_idma_des *pdes_pa = (struct sunxi_idma_des *)host->sg_dma; +int i, max_len = (1 << host->idma_des_size_bits); + +for (i =0 ; i< data->sg_len; i++) { + pdes[i].config =SDXC_IDMAC_DES0_CH |SDXC_IDMAC_DES0_OWN | +SDXC_IDMAC_DES0_DIC ; + +if (data->sg[i].length == max_len) + pdes[i].buf_size =0 ;/* 0 == max_len */ +else + pdes[i].buf_size = data->sg[i].length; + + pdes[i].buf_addr_ptr1 =sg_dma_address (&data->sg[i]); + pdes[i].buf_addr_ptr2 = (u32)&pdes_pa[i +1 ]; + } + + pdes[0 ].config |=SDXC_IDMAC_DES0_FD ; + pdes[i -1 ].config =SDXC_IDMAC_DES0_OWN |SDXC_IDMAC_DES0_LD ; + +/* +* Avoid the io-store starting the idmac hitting io-mem before the +* descriptors hit the main-mem. +*/ +wmb (); +} + +static enum dma_data_direction sunxi_mmc_get_dma_dir (struct mmc_data *data) +{ +if (data->flags&MMC_DATA_WRITE ) +return DMA_TO_DEVICE ; +else +return DMA_FROM_DEVICE ; +} + +static int sunxi_mmc_map_dma (struct sunxi_mmc_host *host, +struct mmc_data *data) +{ +u32 i, dma_len; +struct scatterlist *sg; + + dma_len =dma_map_sg (mmc_dev (host->mmc), data->sg, data->sg_len, +sunxi_mmc_get_dma_dir (data)); +if (dma_len ==0 ) { +dev_err (mmc_dev (host->mmc),"dma_map_sg failed\n" ); +return -ENOMEM ; + } + +for_each_sg (data->sg, sg, data->sg_len, i) { +if (sg->offset&3 || sg->length&3 ) { +dev_err (mmc_dev (host->mmc), +"unaligned scatterlist: os %x length %d\n" , + sg->offset, sg->length); +return -EINVAL ; + } + } + +return 0 ; +} + +static void sunxi_mmc_start_dma (struct sunxi_mmc_host *host, +struct mmc_data *data) +{ +u32 rval; + +sunxi_mmc_init_idma_des (host, data); + + rval =mmc_readl (host,REG_GCTRL ); + rval |=SDXC_DMA_ENABLE_BIT ; +mmc_writel (host,REG_GCTRL , rval); + rval |=SDXC_DMA_RESET ; +mmc_writel (host,REG_GCTRL , rval); + +mmc_writel (host,REG_DMAC ,SDXC_IDMAC_SOFT_RESET ); + +if (!(data->flags&MMC_DATA_WRITE )) +mmc_writel (host,REG_IDIE ,SDXC_IDMAC_RECEIVE_INTERRUPT ); + +mmc_writel (host,REG_DMAC , +SDXC_IDMAC_FIX_BURST |SDXC_IDMAC_IDMA_ON ); +} + +static void sunxi_mmc_send_manual_stop (struct sunxi_mmc_host *host, +struct mmc_request *req) +{ +u32 arg, cmd_val, ri; +unsigned long expire = jiffies +msecs_to_jiffies (1000 ); + + cmd_val =SDXC_START |SDXC_RESP_EXPIRE | +SDXC_STOP_ABORT_CMD |SDXC_CHECK_RESPONSE_CRC ; + +if (req->cmd->opcode ==SD_IO_RW_EXTENDED ) { + cmd_val |=SD_IO_RW_DIRECT ; + arg = (1 <<31 ) | (0 <<28 ) | (SDIO_CCCR_ABORT <<9 ) | + ((req->cmd->arg>>28 )&0x7 ); + }else { + cmd_val |=MMC_STOP_TRANSMISSION ; + arg =0 ; + } + +mmc_writel (host,REG_CARG , arg); +mmc_writel (host,REG_CMDR , cmd_val); + +do { + ri =mmc_readl (host,REG_RINTR ); + }while (!(ri& (SDXC_COMMAND_DONE |SDXC_INTERRUPT_ERROR_BIT ))&& +time_before (jiffies, expire)); + +if (!(ri&SDXC_COMMAND_DONE ) || (ri&SDXC_INTERRUPT_ERROR_BIT )) { +dev_err (mmc_dev (host->mmc),"send stop command failed\n" ); +if (req->stop) + req->stop->resp[0 ] = -ETIMEDOUT ; + }else { +if (req->stop) + req->stop->resp[0 ] =mmc_readl (host,REG_RESP0 ); + } + +mmc_writel (host,REG_RINTR ,0xffff ); +} + +static void sunxi_mmc_dump_errinfo (struct sunxi_mmc_host *host) +{ +struct mmc_command *cmd = host->mrq->cmd; +struct mmc_data *data = host->mrq->data; + +/* For some cmds timeout is normal with sd/mmc cards */ +if ((host->int_sum&SDXC_INTERRUPT_ERROR_BIT ) == +SDXC_RESP_TIMEOUT && (cmd->opcode ==SD_IO_SEND_OP_COND || + cmd->opcode ==SD_IO_RW_DIRECT )) +return ; + +dev_err (mmc_dev (host->mmc), +"smc %d err, cmd %d,%s%s%s%s%s%s%s%s%s%s !!\n" , + host->mmc->index, cmd->opcode, + data ? (data->flags&MMC_DATA_WRITE ?" WR" :" RD" ) :"" , + host->int_sum&SDXC_RESP_ERROR ?" RE" :"" , + host->int_sum&SDXC_RESP_CRC_ERROR ?" RCE" :"" , + host->int_sum&SDXC_DATA_CRC_ERROR ?" DCE" :"" , + host->int_sum&SDXC_RESP_TIMEOUT ?" RTO" :"" , + host->int_sum&SDXC_DATA_TIMEOUT ?" DTO" :"" , + host->int_sum&SDXC_FIFO_RUN_ERROR ?" FE" :"" , + host->int_sum&SDXC_HARD_WARE_LOCKED ?" HL" :"" , + host->int_sum&SDXC_START_BIT_ERROR ?" SBE" :"" , + host->int_sum&SDXC_END_BIT_ERROR ?" EBE" :"" + ); +} + +/* Called in interrupt context! */ +static irqreturn_t sunxi_mmc_finalize_request (struct sunxi_mmc_host *host) +{ +struct mmc_request *mrq = host->mrq; +struct mmc_data *data = mrq->data; +u32 rval; + +mmc_writel (host,REG_IMASK , host->sdio_imask); +mmc_writel (host,REG_IDIE ,0 ); + +if (host->int_sum&SDXC_INTERRUPT_ERROR_BIT ) { +sunxi_mmc_dump_errinfo (host); + mrq->cmd->error = -ETIMEDOUT ; + +if (data) { + data->error = -ETIMEDOUT ; + host->manual_stop_mrq = mrq; + } + +if (mrq->stop) + mrq->stop->error = -ETIMEDOUT ; + }else { +if (mrq->cmd->flags&MMC_RSP_136 ) { + mrq->cmd->resp[0 ] =mmc_readl (host,REG_RESP3 ); + mrq->cmd->resp[1 ] =mmc_readl (host,REG_RESP2 ); + mrq->cmd->resp[2 ] =mmc_readl (host,REG_RESP1 ); + mrq->cmd->resp[3 ] =mmc_readl (host,REG_RESP0 ); + }else { + mrq->cmd->resp[0 ] =mmc_readl (host,REG_RESP0 ); + } + +if (data) + data->bytes_xfered = data->blocks * data->blksz; + } + +if (data) { +mmc_writel (host,REG_IDST ,0x337 ); +mmc_writel (host,REG_DMAC ,0 ); + rval =mmc_readl (host,REG_GCTRL ); + rval |=SDXC_DMA_RESET ; +mmc_writel (host,REG_GCTRL , rval); + rval&= ~SDXC_DMA_ENABLE_BIT ; +mmc_writel (host,REG_GCTRL , rval); + rval |=SDXC_FIFO_RESET ; +mmc_writel (host,REG_GCTRL , rval); +dma_unmap_sg (mmc_dev (host->mmc), data->sg, data->sg_len, +sunxi_mmc_get_dma_dir (data)); + } + +mmc_writel (host,REG_RINTR ,0xffff ); + + host->mrq =NULL ; + host->int_sum =0 ; + host->wait_dma = false; + +return host->manual_stop_mrq ?IRQ_WAKE_THREAD :IRQ_HANDLED ; +} + +static irqreturn_t sunxi_mmc_irq (int irq,void *dev_id) +{ +struct sunxi_mmc_host *host = dev_id; +struct mmc_request *mrq; +u32 msk_int, idma_int; +bool finalize = false; +bool sdio_int = false; +irqreturn_t ret =IRQ_HANDLED ; + +spin_lock (&host->lock); + + idma_int =mmc_readl (host,REG_IDST ); + msk_int =mmc_readl (host,REG_MISTA ); + +dev_dbg (mmc_dev (host->mmc),"irq: rq %p mi %08x idi %08x\n" , + host->mrq, msk_int, idma_int); + + mrq = host->mrq; +if (mrq) { +if (idma_int&SDXC_IDMAC_RECEIVE_INTERRUPT ) + host->wait_dma = false; + + host->int_sum |= msk_int; + +/* Wait for COMMAND_DONE on RESPONSE_TIMEOUT before finalize */ +if ((host->int_sum&SDXC_RESP_TIMEOUT )&& + !(host->int_sum&SDXC_COMMAND_DONE )) +mmc_writel (host,REG_IMASK , + host->sdio_imask |SDXC_COMMAND_DONE ); +/* Don't wait for dma on error */ +else if (host->int_sum&SDXC_INTERRUPT_ERROR_BIT ) + finalize = true; +else if ((host->int_sum&SDXC_INTERRUPT_DONE_BIT )&& + !host->wait_dma) + finalize = true; + } + +if (msk_int&SDXC_SDIO_INTERRUPT ) + sdio_int = true; + +mmc_writel (host,REG_RINTR , msk_int); +mmc_writel (host,REG_IDST , idma_int); + +if (finalize) + ret =sunxi_mmc_finalize_request (host); + +spin_unlock (&host->lock); + +if (finalize&& ret ==IRQ_HANDLED ) +mmc_request_done (host->mmc, mrq); + +if (sdio_int) +mmc_signal_sdio_irq (host->mmc); + +return ret; +} + +static irqreturn_t sunxi_mmc_handle_manual_stop (int irq,void *dev_id) +{ +struct sunxi_mmc_host *host = dev_id; +struct mmc_request *mrq; +unsigned long iflags; + +spin_lock_irqsave (&host->lock, iflags); + mrq = host->manual_stop_mrq; +spin_unlock_irqrestore (&host->lock, iflags); + +if (!mrq) { +dev_err (mmc_dev (host->mmc),"no request for manual stop\n" ); +return IRQ_HANDLED ; + } + +dev_err (mmc_dev (host->mmc),"data error, sending stop command\n" ); +sunxi_mmc_send_manual_stop (host, mrq); + +spin_lock_irqsave (&host->lock, iflags); + host->manual_stop_mrq =NULL ; +spin_unlock_irqrestore (&host->lock, iflags); + +mmc_request_done (host->mmc, mrq); + +return IRQ_HANDLED ; +} + +static int sunxi_mmc_oclk_onoff (struct sunxi_mmc_host *host,u32 oclk_en) +{ +unsigned long expire = jiffies +msecs_to_jiffies (250 ); +u32 rval; + + rval =mmc_readl (host,REG_CLKCR ); + rval&= ~(SDXC_CARD_CLOCK_ON |SDXC_LOW_POWER_ON ); + +if (oclk_en) + rval |=SDXC_CARD_CLOCK_ON ; + +mmc_writel (host,REG_CLKCR , rval); + + rval =SDXC_START |SDXC_UPCLK_ONLY |SDXC_WAIT_PRE_OVER ; +mmc_writel (host,REG_CMDR , rval); + +do { + rval =mmc_readl (host,REG_CMDR ); + }while (time_before (jiffies, expire)&& (rval&SDXC_START )); + +/* clear irq status bits set by the command */ +mmc_writel (host,REG_RINTR , +mmc_readl (host,REG_RINTR )& ~SDXC_SDIO_INTERRUPT ); + +if (rval&SDXC_START ) { +dev_err (mmc_dev (host->mmc),"fatal err update clk timeout\n" ); +return -EIO ; + } + +return 0 ; +} + +static int sunxi_mmc_clk_set_rate (struct sunxi_mmc_host *host, +struct mmc_ios *ios) +{ +u32 rate, oclk_dly, rval, sclk_dly, src_clk; +int ret; + + rate =clk_round_rate (host->clk_mmc, ios->clock); +dev_dbg (mmc_dev (host->mmc),"setting clk to %d, rounded %d\n" , + ios->clock, rate); + +/* setting clock rate */ + ret =clk_set_rate (host->clk_mmc, rate); +if (ret) { +dev_err (mmc_dev (host->mmc),"error setting clk to %d: %d\n" , + rate, ret); +return ret; + } + + ret =sunxi_mmc_oclk_onoff (host,0 ); +if (ret) +return ret; + +/* clear internal divider */ + rval =mmc_readl (host,REG_CLKCR ); + rval&= ~0xff ; +mmc_writel (host,REG_CLKCR , rval); + +/* determine delays */ +if (rate<=400000 ) { + oclk_dly =0 ; + sclk_dly =7 ; + }else if (rate<=25000000 ) { + oclk_dly =0 ; + sclk_dly =5 ; + }else if (rate<=50000000 ) { +if (ios->timing ==MMC_TIMING_UHS_DDR50 ) { + oclk_dly =2 ; + sclk_dly =4 ; + }else { + oclk_dly =3 ; + sclk_dly =5 ; + } + }else { +/* rate> 50000000 */ + oclk_dly =2 ; + sclk_dly =4 ; + } + + src_clk =clk_get_rate (clk_get_parent (host->clk_mmc)); +if (src_clk>=300000000 && src_clk<=400000000 ) { +if (oclk_dly) + oclk_dly--; +if (sclk_dly) + sclk_dly--; + } + +clk_sunxi_mmc_phase_control (host->clk_mmc, sclk_dly, oclk_dly); + +return sunxi_mmc_oclk_onoff (host,1 ); +} + +static void sunxi_mmc_set_ios (struct mmc_host *mmc,struct mmc_ios *ios) +{ +struct sunxi_mmc_host *host =mmc_priv (mmc); +u32 rval; + +/* Set the power state */ +switch (ios->power_mode) { +case MMC_POWER_ON : +break ; + +case MMC_POWER_UP : +mmc_regulator_set_ocr (mmc, mmc->supply.vmmc, ios->vdd); + + host->ferror =sunxi_mmc_init_host (mmc); +if (host->ferror) +return ; + +dev_dbg (mmc_dev (mmc),"power on!\n" ); +break ; + +case MMC_POWER_OFF : +dev_dbg (mmc_dev (mmc),"power off!\n" ); +sunxi_mmc_reset_host (host); +mmc_regulator_set_ocr (mmc, mmc->supply.vmmc,0 ); +break ; + } + +/* set bus width */ +switch (ios->bus_width) { +case MMC_BUS_WIDTH_1 : +mmc_writel (host,REG_WIDTH ,SDXC_WIDTH1 ); +break ; +case MMC_BUS_WIDTH_4 : +mmc_writel (host,REG_WIDTH ,SDXC_WIDTH4 ); +break ; +case MMC_BUS_WIDTH_8 : +mmc_writel (host,REG_WIDTH ,SDXC_WIDTH8 ); +break ; + } + +/* set ddr mode */ + rval =mmc_readl (host,REG_GCTRL ); +if (ios->timing ==MMC_TIMING_UHS_DDR50 ) + rval |=SDXC_DDR_MODE ; +else + rval&= ~SDXC_DDR_MODE ; +mmc_writel (host,REG_GCTRL , rval); + +/* set up clock */ +if (ios->clock&& ios->power_mode) { + host->ferror =sunxi_mmc_clk_set_rate (host, ios); +/* Android code had a usleep_range(50000, 55000); here */ + } +} + +static void sunxi_mmc_enable_sdio_irq (struct mmc_host *mmc,int enable) +{ +struct sunxi_mmc_host *host =mmc_priv (mmc); +unsigned long flags; +u32 imask; + +spin_lock_irqsave (&host->lock, flags); + + imask =mmc_readl (host,REG_IMASK ); +if (enable) { + host->sdio_imask =SDXC_SDIO_INTERRUPT ; + imask |=SDXC_SDIO_INTERRUPT ; + }else { + host->sdio_imask =0 ; + imask&= ~SDXC_SDIO_INTERRUPT ; + } +mmc_writel (host,REG_IMASK , imask); +spin_unlock_irqrestore (&host->lock, flags); +} + +static void sunxi_mmc_hw_reset (struct mmc_host *mmc) +{ +struct sunxi_mmc_host *host =mmc_priv (mmc); +mmc_writel (host,REG_HWRST ,0 ); +udelay (10 ); +mmc_writel (host,REG_HWRST ,1 ); +udelay (300 ); +} + +static void sunxi_mmc_request (struct mmc_host *mmc,struct mmc_request *mrq) +{ +struct sunxi_mmc_host *host =mmc_priv (mmc); +struct mmc_command *cmd = mrq->cmd; +struct mmc_data *data = mrq->data; +unsigned long iflags; +u32 imask =SDXC_INTERRUPT_ERROR_BIT ; +u32 cmd_val =SDXC_START | (cmd->opcode&0x3f ); +int ret; + +/* Check for set_ios errors (should never happen) */ +if (host->ferror) { + mrq->cmd->error = host->ferror; +mmc_request_done (mmc, mrq); +return ; + } + +if (data) { + ret =sunxi_mmc_map_dma (host, data); +if (ret<0 ) { +dev_err (mmc_dev (mmc),"map DMA failed\n" ); + cmd->error = ret; + data->error = ret; +mmc_request_done (mmc, mrq); +return ; + } + } + +if (cmd->opcode ==MMC_GO_IDLE_STATE ) { + cmd_val |=SDXC_SEND_INIT_SEQUENCE ; + imask |=SDXC_COMMAND_DONE ; + } + +if (cmd->flags&MMC_RSP_PRESENT ) { + cmd_val |=SDXC_RESP_EXPIRE ; +if (cmd->flags&MMC_RSP_136 ) + cmd_val |=SDXC_LONG_RESPONSE ; +if (cmd->flags&MMC_RSP_CRC ) + cmd_val |=SDXC_CHECK_RESPONSE_CRC ; + +if ((cmd->flags&MMC_CMD_MASK ) ==MMC_CMD_ADTC ) { + cmd_val |=SDXC_DATA_EXPIRE |SDXC_WAIT_PRE_OVER ; +if (cmd->data->flags&MMC_DATA_STREAM ) { + imask |=SDXC_AUTO_COMMAND_DONE ; + cmd_val |=SDXC_SEQUENCE_MODE | +SDXC_SEND_AUTO_STOP ; + } + +if (cmd->data->stop) { + imask |=SDXC_AUTO_COMMAND_DONE ; + cmd_val |=SDXC_SEND_AUTO_STOP ; + }else { + imask |=SDXC_DATA_OVER ; + } + +if (cmd->data->flags&MMC_DATA_WRITE ) + cmd_val |=SDXC_WRITE ; +else + host->wait_dma = true; + }else { + imask |=SDXC_COMMAND_DONE ; + } + }else { + imask |=SDXC_COMMAND_DONE ; + } + +dev_dbg (mmc_dev (mmc),"cmd %d(%08x) arg %x ie 0x%08x len %d\n" , + cmd_val&0x3f , cmd_val, cmd->arg, imask, + mrq->data ? mrq->data->blksz * mrq->data->blocks :0 ); + +spin_lock_irqsave (&host->lock, iflags); + +if (host->mrq || host->manual_stop_mrq) { +spin_unlock_irqrestore (&host->lock, iflags); + +if (data) +dma_unmap_sg (mmc_dev (mmc), data->sg, data->sg_len, +sunxi_mmc_get_dma_dir (data)); + +dev_err (mmc_dev (mmc),"request already pending\n" ); + mrq->cmd->error = -EBUSY ; +mmc_request_done (mmc, mrq); +return ; + } + +if (data) { +mmc_writel (host,REG_BLKSZ , data->blksz); +mmc_writel (host,REG_BCNTR , data->blksz * data->blocks); +sunxi_mmc_start_dma (host, data); + } + + host->mrq = mrq; +mmc_writel (host,REG_IMASK , host->sdio_imask | imask); +mmc_writel (host,REG_CARG , cmd->arg); +mmc_writel (host,REG_CMDR , cmd_val); + +spin_unlock_irqrestore (&host->lock, iflags); +} + +static const struct of_device_id sunxi_mmc_of_match[] = { + { .compatible ="allwinner,sun4i-a10-mmc" , }, + { .compatible ="allwinner,sun5i-a13-mmc" , }, + {/* sentinel */ } +}; +MODULE_DEVICE_TABLE (of, sunxi_mmc_of_match); + +static struct mmc_host_ops sunxi_mmc_ops = { + .request = sunxi_mmc_request, + .set_ios = sunxi_mmc_set_ios, + .get_ro = mmc_gpio_get_ro, + .get_cd = mmc_gpio_get_cd, + .enable_sdio_irq = sunxi_mmc_enable_sdio_irq, + .hw_reset = sunxi_mmc_hw_reset, +}; + +static int sunxi_mmc_resource_request (struct sunxi_mmc_host *host, +struct platform_device *pdev) +{ +struct device_node *np = pdev->dev.of_node; +int ret; + +if (of_device_is_compatible (np,"allwinner,sun4i-a10-mmc" )) + host->idma_des_size_bits =13 ; +else + host->idma_des_size_bits =16 ; + + ret =mmc_regulator_get_supply (host->mmc); +if (ret) { +if (ret != -EPROBE_DEFER ) +dev_err (&pdev->dev,"Could not get vmmc supply\n" ); +return ret; + } + + host->reg_base =devm_ioremap_resource (&pdev->dev, +platform_get_resource (pdev,IORESOURCE_MEM ,0 )); +if (IS_ERR (host->reg_base)) +return PTR_ERR (host->reg_base); + + host->clk_ahb =devm_clk_get (&pdev->dev,"ahb" ); +if (IS_ERR (host->clk_ahb)) { +dev_err (&pdev->dev,"Could not get ahb clock\n" ); +return PTR_ERR (host->clk_ahb); + } + + host->clk_mmc =devm_clk_get (&pdev->dev,"mmc" ); +if (IS_ERR (host->clk_mmc)) { +dev_err (&pdev->dev,"Could not get mmc clock\n" ); +return PTR_ERR (host->clk_mmc); + } + + host->reset =devm_reset_control_get (&pdev->dev,"ahb" ); + + ret =clk_prepare_enable (host->clk_ahb); +if (ret) { +dev_err (&pdev->dev,"Enable ahb clk err %d\n" , ret); +return ret; + } + + ret =clk_prepare_enable (host->clk_mmc); +if (ret) { +dev_err (&pdev->dev,"Enable mmc clk err %d\n" , ret); + goto error_disable_clk_ahb; + } + +if (!IS_ERR (host->reset)) { + ret =reset_control_deassert (host->reset); +if (ret) { +dev_err (&pdev->dev,"reset err %d\n" , ret); + goto error_disable_clk_mmc; + } + } + +/* +* Sometimes the controller asserts the irq on boot for some reason, +* make sure the controller is in a sane state before enabling irqs. +*/ + ret =sunxi_mmc_reset_host (host); +if (ret) + goto error_assert_reset; + + host->irq =platform_get_irq (pdev,0 ); +return devm_request_threaded_irq (&pdev->dev, host->irq, sunxi_mmc_irq, + sunxi_mmc_handle_manual_stop,0 ,"sunxi-mmc" , host); + +error_assert_reset: +if (!IS_ERR (host->reset)) +reset_control_assert (host->reset); +error_disable_clk_mmc: +clk_disable_unprepare (host->clk_mmc); +error_disable_clk_ahb: +clk_disable_unprepare (host->clk_ahb); +return ret; +} + +static int sunxi_mmc_probe (struct platform_device *pdev) +{ +struct sunxi_mmc_host *host; +struct mmc_host *mmc; +int ret; + + mmc =mmc_alloc_host (sizeof (struct sunxi_mmc_host ),&pdev->dev); +if (!mmc) { +dev_err (&pdev->dev,"mmc alloc host failed\n" ); +return -ENOMEM ; + } + + host =mmc_priv (mmc); + host->mmc = mmc; +spin_lock_init (&host->lock); + + ret =sunxi_mmc_resource_request (host, pdev); +if (ret) + goto error_free_host; + + host->sg_cpu =dma_alloc_coherent (&pdev->dev,PAGE_SIZE , + &host->sg_dma,GFP_KERNEL ); +if (!host->sg_cpu) { +dev_err (&pdev->dev,"Failed to allocate DMA descriptor mem\n" ); + ret = -ENOMEM ; + goto error_free_host; + } + + mmc->ops =&sunxi_mmc_ops; + mmc->max_blk_count =8192 ; + mmc->max_blk_size =4096 ; + mmc->max_segs =PAGE_SIZE /sizeof (struct sunxi_idma_des ); + mmc->max_seg_size = (1 << host->idma_des_size_bits); + mmc->max_req_size = mmc->max_seg_size * mmc->max_segs; +/* 400kHz ~ 50MHz */ + mmc->f_min =400000 ; + mmc->f_max =50000000 ; + mmc->caps |=MMC_CAP_MMC_HIGHSPEED |MMC_CAP_SD_HIGHSPEED ; + + ret =mmc_of_parse (mmc); +if (ret) + goto error_free_dma; + + ret =mmc_add_host (mmc); +if (ret) + goto error_free_dma; + +dev_info (&pdev->dev,"base:0x%p irq:%u\n" , host->reg_base, host->irq); +platform_set_drvdata (pdev, mmc); +return 0 ; + +error_free_dma: +dma_free_coherent (&pdev->dev,PAGE_SIZE , host->sg_cpu, host->sg_dma); +error_free_host: +mmc_free_host (mmc); +return ret; +} + +static int sunxi_mmc_remove (struct platform_device *pdev) +{ +struct mmc_host *mmc =platform_get_drvdata (pdev); +struct sunxi_mmc_host *host =mmc_priv (mmc); + +mmc_remove_host (mmc); +disable_irq (host->irq); +sunxi_mmc_reset_host (host); + +if (!IS_ERR (host->reset)) +reset_control_assert (host->reset); + +clk_disable_unprepare (host->clk_mmc); +clk_disable_unprepare (host->clk_ahb); + +dma_free_coherent (&pdev->dev,PAGE_SIZE , host->sg_cpu, host->sg_dma); +mmc_free_host (mmc); + +return 0 ; +} + +static struct platform_driver sunxi_mmc_driver = { + .driver = { + .name ="sunxi-mmc" , + .owner =THIS_MODULE , + .of_match_table =of_match_ptr (sunxi_mmc_of_match), + }, + .probe = sunxi_mmc_probe, + .remove = sunxi_mmc_remove, +}; +module_platform_driver (sunxi_mmc_driver); + +MODULE_DESCRIPTION ("Allwinner's SD/MMC Card Controller Driver" ); +MODULE_LICENSE ("GPL v2" ); +MODULE_AUTHOR ("David Lanzend?rfer<[email protected]>" ); +MODULE_ALIAS ("platform:sunxi-mmc" );_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel at lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
PATCH v13 2/2 ARM: sunxi: Add driver for SD/MMC hosts found on Allwinner sunxi SoCs
Quoted 20 lines · Arend van Spriel
Quoted 11 lines · Hans de Goede
From: David Lanzend?rfer<[email protected]>
The Allwinner sunxi mmc host uses dma in bus-master mode using a built-in designware idmac controller, which is identical to the one found in the mmc-dw
hosts. However the rest of the host is not identical to mmc-dw, it deals with
sending stop commands in hardware which makes it significantly different from the mmc-dw devices.Quoted 1 line
HdG: Various cleanups and fixes.
Just nitpicking, but usually the above line should be added below the original signoff, so:
Quoted 1 line
Signed-off-by David Lanzend?rfer<[email protected]>
Quoted 8 lines
[hdegoede at redhat.com: various cleanups and fixes]
Quoted 1 line
Signed-off-by Hans de Goede<[email protected]>As is documented in SubmittingPatches.
Gr. AvS
This looks good to me, thanks Hans!
I will include this in the next PR I send to Chris, I will also take the liberty to fixup the commit msg as pointed out by Arend.
Kind regards
Ulf Hansson
PATCH v13 2/2 ARM: sunxi: Add driver for SD/MMC hosts found on Allwinner sunxi SoCs
Hi,
Quoted 28 lines · Ulf Hansson
Quoted 27 lines · Arend van Spriel
Quoted 11 lines · Hans de Goede
From: David Lanzend?rfer<[email protected]>
The Allwinner sunxi mmc host uses dma in bus-master mode using a built-in designware idmac controller, which is identical to the one found in the mmc-dw
hosts. However the rest of the host is not identical to mmc-dw, it deals with
sending stop commands in hardware which makes it significantly different from the mmc-dw devices.Quoted 1 line
HdG: Various cleanups and fixes.
Just nitpicking, but usually the above line should be added below the original signoff, so:
Quoted 1 line
Signed-off-by David Lanzend?rfer<[email protected]>[hdegoede at redhat.com: various cleanups and fixes]
Quoted 1 line
Signed-off-by Hans de Goede<[email protected]>As is documented in SubmittingPatches.
Quoted 7 lines
Quoted 1 line
Gr. AvS
This looks good to me, thanks Hans!
I will include this in the next PR I send to Chris, I will also take the liberty to fixup the commit msg as pointed out by Arend.
Great, thanks!
Regards,
Hans