diff options
author | Waldemar Brodkorb <wbx@openadk.org> | 2015-03-02 18:54:09 +0100 |
---|---|---|
committer | Waldemar Brodkorb <wbx@openadk.org> | 2015-03-02 18:54:09 +0100 |
commit | fde3043e3f9cc58b2b4639f2bb2f6d1ac6b15ed2 (patch) | |
tree | 59dce32391123f41066e4987b60864c3e05fbbc0 | |
parent | 4876df8cb79b2bac3ba9ae02b3609f8d63309c16 (diff) |
add wlan support for 3.18.x via Russel King patches. 0086-0090
-rw-r--r-- | target/arm/solidrun-imx6/patches/3.18.8/solidrun-imx6-wlan.patch | 3252 |
1 files changed, 3252 insertions, 0 deletions
diff --git a/target/arm/solidrun-imx6/patches/3.18.8/solidrun-imx6-wlan.patch b/target/arm/solidrun-imx6/patches/3.18.8/solidrun-imx6-wlan.patch new file mode 100644 index 000000000..3ab3081db --- /dev/null +++ b/target/arm/solidrun-imx6/patches/3.18.8/solidrun-imx6-wlan.patch @@ -0,0 +1,3252 @@ +diff -Nur linux-3.18.8.orig/arch/arm/boot/dts/imx6qdl-cubox-i.dtsi linux-3.18.8/arch/arm/boot/dts/imx6qdl-cubox-i.dtsi +--- linux-3.18.8.orig/arch/arm/boot/dts/imx6qdl-cubox-i.dtsi 2015-02-27 02:49:36.000000000 +0100 ++++ linux-3.18.8/arch/arm/boot/dts/imx6qdl-cubox-i.dtsi 2015-03-02 03:23:14.000000000 +0100 +@@ -170,6 +170,28 @@ + MX6QDL_PAD_SD2_DAT3__SD2_DATA3 0x13059 + >; + }; ++ ++ pinctrl_cubox_i_usdhc2_100mhz: cubox-i-usdhc2-100mhz { ++ fsl,pins = < ++ MX6QDL_PAD_SD2_CMD__SD2_CMD 0x170b9 ++ MX6QDL_PAD_SD2_CLK__SD2_CLK 0x100b9 ++ MX6QDL_PAD_SD2_DAT0__SD2_DATA0 0x170b9 ++ MX6QDL_PAD_SD2_DAT1__SD2_DATA1 0x170b9 ++ MX6QDL_PAD_SD2_DAT2__SD2_DATA2 0x170b9 ++ MX6QDL_PAD_SD2_DAT3__SD2_DATA3 0x130b9 ++ >; ++ }; ++ ++ pinctrl_cubox_i_usdhc2_200mhz: cubox-i-usdhc2-200mhz { ++ fsl,pins = < ++ MX6QDL_PAD_SD2_CMD__SD2_CMD 0x170f9 ++ MX6QDL_PAD_SD2_CLK__SD2_CLK 0x100f9 ++ MX6QDL_PAD_SD2_DAT0__SD2_DATA0 0x170f9 ++ MX6QDL_PAD_SD2_DAT1__SD2_DATA1 0x170f9 ++ MX6QDL_PAD_SD2_DAT2__SD2_DATA2 0x170f9 ++ MX6QDL_PAD_SD2_DAT3__SD2_DATA3 0x130f9 ++ >; ++ }; + }; + }; + +@@ -194,8 +216,10 @@ + }; + + &usdhc2 { +- pinctrl-names = "default"; ++ pinctrl-names = "default", "state_100mhz", "state_200mhz"; + pinctrl-0 = <&pinctrl_cubox_i_usdhc2_aux &pinctrl_cubox_i_usdhc2>; ++ pinctrl-1 = <&pinctrl_cubox_i_usdhc2_aux &pinctrl_cubox_i_usdhc2_100mhz>; ++ pinctrl-2 = <&pinctrl_cubox_i_usdhc2_aux &pinctrl_cubox_i_usdhc2_200mhz>; + vmmc-supply = <®_3p3v>; + cd-gpios = <&gpio1 4 0>; + status = "okay"; +diff -Nur linux-3.18.8.orig/arch/arm/boot/dts/imx6qdl-microsom.dtsi linux-3.18.8/arch/arm/boot/dts/imx6qdl-microsom.dtsi +--- linux-3.18.8.orig/arch/arm/boot/dts/imx6qdl-microsom.dtsi 2015-02-27 02:49:36.000000000 +0100 ++++ linux-3.18.8/arch/arm/boot/dts/imx6qdl-microsom.dtsi 2015-03-02 02:58:12.000000000 +0100 +@@ -1,15 +1,95 @@ + /* + * Copyright (C) 2013,2014 Russell King + */ ++#include <dt-bindings/gpio/gpio.h> ++/ { ++ regulators { ++ compatible = "simple-bus"; ++ ++ reg_brcm_osc: brcm-osc-reg { ++ compatible = "regulator-fixed"; ++ enable-active-high; ++ gpio = <&gpio5 5 0>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_microsom_brcm_osc_reg>; ++ regulator-name = "brcm_osc_reg"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ regulator-always-on; ++ regulator-boot-on; ++ }; ++ ++ reg_brcm: brcm-reg { ++ compatible = "regulator-fixed"; ++ enable-active-high; ++ gpio = <&gpio3 19 0>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_microsom_brcm_reg>; ++ regulator-name = "brcm_reg"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ startup-delay-us = <200000>; ++ }; ++ }; ++}; + + &iomuxc { + microsom { ++ pinctrl_microsom_brcm_bt: microsom-brcm-bt { ++ fsl,pins = < ++ MX6QDL_PAD_CSI0_DAT14__GPIO6_IO00 0x40013070 ++ MX6QDL_PAD_CSI0_DAT15__GPIO6_IO01 0x40013070 ++ MX6QDL_PAD_CSI0_DAT18__GPIO6_IO04 0x40013070 ++ >; ++ }; ++ ++ pinctrl_microsom_brcm_osc_reg: microsom-brcm-osc-reg { ++ fsl,pins = < ++ MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05 0x40013070 ++ >; ++ }; ++ ++ pinctrl_microsom_brcm_reg: microsom-brcm-reg { ++ fsl,pins = < ++ MX6QDL_PAD_EIM_D19__GPIO3_IO19 0x40013070 ++ >; ++ }; ++ ++ pinctrl_microsom_brcm_wifi: microsom-brcm-wifi { ++ fsl,pins = < ++ MX6QDL_PAD_GPIO_8__XTALOSC_REF_CLK_32K 0x1b0b0 ++ MX6QDL_PAD_CSI0_DATA_EN__GPIO5_IO20 0x40013070 ++ MX6QDL_PAD_CSI0_DAT8__GPIO5_IO26 0x40013070 ++ MX6QDL_PAD_CSI0_DAT9__GPIO5_IO27 0x40013070 ++ >; ++ }; ++ + pinctrl_microsom_uart1: microsom-uart1 { + fsl,pins = < + MX6QDL_PAD_CSI0_DAT10__UART1_TX_DATA 0x1b0b1 + MX6QDL_PAD_CSI0_DAT11__UART1_RX_DATA 0x1b0b1 + >; + }; ++ ++ pinctrl_microsom_uart4_1: microsom-uart4 { ++ fsl,pins = < ++ MX6QDL_PAD_CSI0_DAT12__UART4_TX_DATA 0x1b0b1 ++ MX6QDL_PAD_CSI0_DAT13__UART4_RX_DATA 0x1b0b1 ++ MX6QDL_PAD_CSI0_DAT16__UART4_RTS_B 0x1b0b1 ++ MX6QDL_PAD_CSI0_DAT17__UART4_CTS_B 0x1b0b1 ++ >; ++ }; ++ ++ pinctrl_microsom_usdhc1: microsom-usdhc1 { ++ fsl,pins = < ++ MX6QDL_PAD_SD1_CMD__SD1_CMD 0x17059 ++ MX6QDL_PAD_SD1_CLK__SD1_CLK 0x10059 ++ MX6QDL_PAD_SD1_DAT0__SD1_DATA0 0x17059 ++ MX6QDL_PAD_SD1_DAT1__SD1_DATA1 0x17059 ++ MX6QDL_PAD_SD1_DAT2__SD1_DATA2 0x17059 ++ MX6QDL_PAD_SD1_DAT3__SD1_DATA3 0x17059 ++ >; ++ }; + }; + }; + +@@ -18,3 +98,23 @@ + pinctrl-0 = <&pinctrl_microsom_uart1>; + status = "okay"; + }; ++ ++/* UART4 - Connected to optional BRCM Wifi/BT/FM */ ++&uart4 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_microsom_brcm_bt &pinctrl_microsom_uart4_1>; ++ fsl,uart-has-rtscts; ++ status = "okay"; ++}; ++ ++/* USDHC1 - Connected to optional BRCM Wifi/BT/FM */ ++&usdhc1 { ++ card-external-vcc-supply = <®_brcm>; ++ card-reset-gpios = <&gpio5 26 GPIO_ACTIVE_LOW>, <&gpio6 0 GPIO_ACTIVE_LOW>; ++ keep-power-in-suspend; ++ non-removable; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_microsom_brcm_wifi &pinctrl_microsom_usdhc1>; ++ vmmc-supply = <®_brcm>; ++ status = "okay"; ++}; +diff -Nur linux-3.18.8.orig/Documentation/devicetree/bindings/mmc/mmc.txt linux-3.18.8/Documentation/devicetree/bindings/mmc/mmc.txt +--- linux-3.18.8.orig/Documentation/devicetree/bindings/mmc/mmc.txt 2015-02-27 02:49:36.000000000 +0100 ++++ linux-3.18.8/Documentation/devicetree/bindings/mmc/mmc.txt 2015-03-02 03:25:33.000000000 +0100 +@@ -5,6 +5,8 @@ + Interpreted by the OF core: + - reg: Registers location and length. + - interrupts: Interrupts used by the MMC controller. ++- clocks: Clocks needed for the host controller, if any. ++- clock-names: Goes with clocks above. + + Card detection: + If no property below is supplied, host native card detect is used. +@@ -43,6 +45,15 @@ + - dsr: Value the card's (optional) Driver Stage Register (DSR) should be + programmed with. Valid range: [0 .. 0xffff]. + ++Card power and reset control: ++The following properties can be specified for cases where the MMC ++peripheral needs additional reset, regulator and clock lines. It is for ++example common for WiFi/BT adapters to have these separate from the main ++MMC bus: ++ - card-reset-gpios: Specify GPIOs for card reset (reset active low) ++ - card-external-vcc-supply: Regulator to drive (independent) card VCC ++ - clock with name "card_ext_clock": External clock provided to the card ++ + *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" + line levels. We choose to follow the SDHCI standard, which specifies both those +diff -Nur linux-3.18.8.orig/drivers/mmc/core/core.c linux-3.18.8/drivers/mmc/core/core.c +--- linux-3.18.8.orig/drivers/mmc/core/core.c 2015-02-27 02:49:36.000000000 +0100 ++++ linux-3.18.8/drivers/mmc/core/core.c 2015-03-02 03:25:33.000000000 +0100 +@@ -13,11 +13,13 @@ + #include <linux/module.h> + #include <linux/init.h> + #include <linux/interrupt.h> ++#include <linux/clk.h> + #include <linux/completion.h> + #include <linux/device.h> + #include <linux/delay.h> + #include <linux/pagemap.h> + #include <linux/err.h> ++#include <linux/gpio/consumer.h> + #include <linux/leds.h> + #include <linux/scatterlist.h> + #include <linux/log2.h> +@@ -1507,6 +1509,43 @@ + mmc_host_clk_release(host); + } + ++static void mmc_card_power_up(struct mmc_host *host) ++{ ++ int i; ++ struct gpio_desc **gds = host->card_reset_gpios; ++ ++ for (i = 0; i < ARRAY_SIZE(host->card_reset_gpios); i++) { ++ if (gds[i]) { ++ dev_dbg(host->parent, "Asserting reset line %d", i); ++ gpiod_set_value(gds[i], 1); ++ } ++ } ++ ++ if (host->card_regulator) { ++ dev_dbg(host->parent, "Enabling external regulator"); ++ if (regulator_enable(host->card_regulator)) ++ dev_err(host->parent, "Failed to enable external regulator"); ++ } ++ ++ if (host->card_clk) { ++ dev_dbg(host->parent, "Enabling external clock"); ++ clk_prepare_enable(host->card_clk); ++ } ++ ++ /* 2ms delay to let clocks and power settle */ ++ mmc_delay(20); ++ ++ for (i = 0; i < ARRAY_SIZE(host->card_reset_gpios); i++) { ++ if (gds[i]) { ++ dev_dbg(host->parent, "Deasserting reset line %d", i); ++ gpiod_set_value(gds[i], 0); ++ } ++ } ++ ++ /* 2ms delay to after reset release */ ++ mmc_delay(20); ++} ++ + /* + * Apply power to the MMC stack. This is a two-stage process. + * First, we enable power to the card without the clock running. +@@ -1523,6 +1562,9 @@ + if (host->ios.power_mode == MMC_POWER_ON) + return; + ++ /* Power up the card/module first, if needed */ ++ mmc_card_power_up(host); ++ + mmc_host_clk_hold(host); + + host->ios.vdd = fls(ocr) - 1; +diff -Nur linux-3.18.8.orig/drivers/mmc/core/host.c linux-3.18.8/drivers/mmc/core/host.c +--- linux-3.18.8.orig/drivers/mmc/core/host.c 2015-02-27 02:49:36.000000000 +0100 ++++ linux-3.18.8/drivers/mmc/core/host.c 2015-03-02 03:26:23.000000000 +0100 +@@ -12,14 +12,18 @@ + * MMC host class device management + */ + ++#include <linux/kernel.h> ++#include <linux/clk.h> + #include <linux/device.h> + #include <linux/err.h> ++#include <linux/gpio/consumer.h> + #include <linux/idr.h> + #include <linux/of.h> + #include <linux/of_gpio.h> + #include <linux/pagemap.h> + #include <linux/export.h> + #include <linux/leds.h> ++#include <linux/regulator/consumer.h> + #include <linux/slab.h> + #include <linux/suspend.h> + +@@ -466,6 +470,66 @@ + + EXPORT_SYMBOL(mmc_of_parse); + ++static int mmc_of_parse_child(struct mmc_host *host) ++{ ++ struct device_node *np; ++ struct clk *clk; ++ int i; ++ ++ if (!host->parent || !host->parent->of_node) ++ return 0; ++ ++ np = host->parent->of_node; ++ ++ host->card_regulator = regulator_get(host->parent, "card-external-vcc"); ++ if (IS_ERR(host->card_regulator)) { ++ if (PTR_ERR(host->card_regulator) == -EPROBE_DEFER) ++ return PTR_ERR(host->card_regulator); ++ host->card_regulator = NULL; ++ } ++ ++ /* Parse card power/reset/clock control */ ++ if (of_find_property(np, "card-reset-gpios", NULL)) { ++ struct gpio_desc *gpd; ++ int level = 0; ++ ++ /* ++ * If the regulator is enabled, then we can hold the ++ * card in reset with an active high resets. Otherwise, ++ * hold the resets low. ++ */ ++ if (host->card_regulator && regulator_is_enabled(host->card_regulator)) ++ level = 1; ++ ++ for (i = 0; i < ARRAY_SIZE(host->card_reset_gpios); i++) { ++ gpd = devm_gpiod_get_index(host->parent, "card-reset", i); ++ if (IS_ERR(gpd)) { ++ if (PTR_ERR(gpd) == -EPROBE_DEFER) ++ return PTR_ERR(gpd); ++ break; ++ } ++ gpiod_direction_output(gpd, gpiod_is_active_low(gpd) | level); ++ host->card_reset_gpios[i] = gpd; ++ } ++ ++ gpd = devm_gpiod_get_index(host->parent, "card-reset", ARRAY_SIZE(host->card_reset_gpios)); ++ if (!IS_ERR(gpd)) { ++ dev_warn(host->parent, "More reset gpios than we can handle"); ++ gpiod_put(gpd); ++ } ++ } ++ ++ clk = of_clk_get_by_name(np, "card_ext_clock"); ++ if (IS_ERR(clk)) { ++ if (PTR_ERR(clk) == -EPROBE_DEFER) ++ return PTR_ERR(clk); ++ clk = NULL; ++ } ++ host->card_clk = clk; ++ ++ return 0; ++} ++ + /** + * mmc_alloc_host - initialise the per-host structure. + * @extra: sizeof private data structure +@@ -545,6 +609,10 @@ + { + int err; + ++ err = mmc_of_parse_child(host); ++ if (err) ++ return err; ++ + WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) && + !host->ops->enable_sdio_irq); + +diff -Nur linux-3.18.8.orig/drivers/mmc/host/dw_mmc.c linux-3.18.8/drivers/mmc/host/dw_mmc.c +--- linux-3.18.8.orig/drivers/mmc/host/dw_mmc.c 2015-02-27 02:49:36.000000000 +0100 ++++ linux-3.18.8/drivers/mmc/host/dw_mmc.c 2015-03-02 03:25:56.000000000 +0100 +@@ -2211,6 +2211,8 @@ + if (!mmc) + return -ENOMEM; + ++ mmc_of_parse(mmc); ++ + slot = mmc_priv(mmc); + slot->id = id; + slot->mmc = mmc; +diff -Nur linux-3.18.8.orig/drivers/mmc/host/dw_mmc.c.orig linux-3.18.8/drivers/mmc/host/dw_mmc.c.orig +--- linux-3.18.8.orig/drivers/mmc/host/dw_mmc.c.orig 1970-01-01 01:00:00.000000000 +0100 ++++ linux-3.18.8/drivers/mmc/host/dw_mmc.c.orig 2015-02-27 02:49:36.000000000 +0100 +@@ -0,0 +1,2855 @@ ++/* ++ * Synopsys DesignWare Multimedia Card Interface driver ++ * (Based on NXP driver for lpc 31xx) ++ * ++ * Copyright (C) 2009 NXP Semiconductors ++ * Copyright (C) 2009, 2010 Imagination Technologies Ltd. ++ * ++ * 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/blkdev.h> ++#include <linux/clk.h> ++#include <linux/debugfs.h> ++#include <linux/device.h> ++#include <linux/dma-mapping.h> ++#include <linux/err.h> ++#include <linux/init.h> ++#include <linux/interrupt.h> ++#include <linux/ioport.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++#include <linux/seq_file.h> ++#include <linux/slab.h> ++#include <linux/stat.h> ++#include <linux/delay.h> ++#include <linux/irq.h> ++#include <linux/mmc/host.h> ++#include <linux/mmc/mmc.h> ++#include <linux/mmc/sd.h> ++#include <linux/mmc/sdio.h> ++#include <linux/mmc/dw_mmc.h> ++#include <linux/bitops.h> ++#include <linux/regulator/consumer.h> ++#include <linux/workqueue.h> ++#include <linux/of.h> ++#include <linux/of_gpio.h> ++#include <linux/mmc/slot-gpio.h> ++ ++#include "dw_mmc.h" ++ ++/* Common flag combinations */ ++#define DW_MCI_DATA_ERROR_FLAGS (SDMMC_INT_DRTO | SDMMC_INT_DCRC | \ ++ SDMMC_INT_HTO | SDMMC_INT_SBE | \ ++ SDMMC_INT_EBE) ++#define DW_MCI_CMD_ERROR_FLAGS (SDMMC_INT_RTO | SDMMC_INT_RCRC | \ ++ SDMMC_INT_RESP_ERR) ++#define DW_MCI_ERROR_FLAGS (DW_MCI_DATA_ERROR_FLAGS | \ ++ DW_MCI_CMD_ERROR_FLAGS | SDMMC_INT_HLE) ++#define DW_MCI_SEND_STATUS 1 ++#define DW_MCI_RECV_STATUS 2 ++#define DW_MCI_DMA_THRESHOLD 16 ++ ++#define DW_MCI_FREQ_MAX 200000000 /* unit: HZ */ ++#define DW_MCI_FREQ_MIN 400000 /* unit: HZ */ ++ ++#ifdef CONFIG_MMC_DW_IDMAC ++#define IDMAC_INT_CLR (SDMMC_IDMAC_INT_AI | SDMMC_IDMAC_INT_NI | \ ++ SDMMC_IDMAC_INT_CES | SDMMC_IDMAC_INT_DU | \ ++ SDMMC_IDMAC_INT_FBE | SDMMC_IDMAC_INT_RI | \ ++ SDMMC_IDMAC_INT_TI) ++ ++struct idmac_desc { ++ u32 des0; /* Control Descriptor */ ++#define IDMAC_DES0_DIC BIT(1) ++#define IDMAC_DES0_LD BIT(2) ++#define IDMAC_DES0_FD BIT(3) ++#define IDMAC_DES0_CH BIT(4) ++#define IDMAC_DES0_ER BIT(5) ++#define IDMAC_DES0_CES BIT(30) ++#define IDMAC_DES0_OWN BIT(31) ++ ++ u32 des1; /* Buffer sizes */ ++#define IDMAC_SET_BUFFER1_SIZE(d, s) \ ++ ((d)->des1 = ((d)->des1 & 0x03ffe000) | ((s) & 0x1fff)) ++ ++ u32 des2; /* buffer 1 physical address */ ++ ++ u32 des3; /* buffer 2 physical address */ ++}; ++#endif /* CONFIG_MMC_DW_IDMAC */ ++ ++static bool dw_mci_reset(struct dw_mci *host); ++ ++#if defined(CONFIG_DEBUG_FS) ++static int dw_mci_req_show(struct seq_file *s, void *v) ++{ ++ struct dw_mci_slot *slot = s->private; ++ struct mmc_request *mrq; ++ struct mmc_command *cmd; ++ struct mmc_command *stop; ++ struct mmc_data *data; ++ ++ /* Make sure we get a consistent snapshot */ ++ spin_lock_bh(&slot->host->lock); ++ mrq = slot->mrq; ++ ++ if (mrq) { ++ cmd = mrq->cmd; ++ data = mrq->data; ++ stop = mrq->stop; ++ ++ if (cmd) ++ seq_printf(s, ++ "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n", ++ cmd->opcode, cmd->arg, cmd->flags, ++ cmd->resp[0], cmd->resp[1], cmd->resp[2], ++ cmd->resp[2], cmd->error); ++ if (data) ++ seq_printf(s, "DATA %u / %u * %u flg %x err %d\n", ++ data->bytes_xfered, data->blocks, ++ data->blksz, data->flags, data->error); ++ if (stop) ++ seq_printf(s, ++ "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n", ++ stop->opcode, stop->arg, stop->flags, ++ stop->resp[0], stop->resp[1], stop->resp[2], ++ stop->resp[2], stop->error); ++ } ++ ++ spin_unlock_bh(&slot->host->lock); ++ ++ return 0; ++} ++ ++static int dw_mci_req_open(struct inode *inode, struct file *file) ++{ ++ return single_open(file, dw_mci_req_show, inode->i_private); ++} ++ ++static const struct file_operations dw_mci_req_fops = { ++ .owner = THIS_MODULE, ++ .open = dw_mci_req_open, ++ .read = seq_read, ++ .llseek = seq_lseek, ++ .release = single_release, ++}; ++ ++static int dw_mci_regs_show(struct seq_file *s, void *v) ++{ ++ seq_printf(s, "STATUS:\t0x%08x\n", SDMMC_STATUS); ++ seq_printf(s, "RINTSTS:\t0x%08x\n", SDMMC_RINTSTS); ++ seq_printf(s, "CMD:\t0x%08x\n", SDMMC_CMD); ++ seq_printf(s, "CTRL:\t0x%08x\n", SDMMC_CTRL); ++ seq_printf(s, "INTMASK:\t0x%08x\n", SDMMC_INTMASK); ++ seq_printf(s, "CLKENA:\t0x%08x\n", SDMMC_CLKENA); ++ ++ return 0; ++} ++ ++static int dw_mci_regs_open(struct inode *inode, struct file *file) ++{ ++ return single_open(file, dw_mci_regs_show, inode->i_private); ++} ++ ++static const struct file_operations dw_mci_regs_fops = { ++ .owner = THIS_MODULE, ++ .open = dw_mci_regs_open, ++ .read = seq_read, ++ .llseek = seq_lseek, ++ .release = single_release, ++}; ++ ++static void dw_mci_init_debugfs(struct dw_mci_slot *slot) ++{ ++ struct mmc_host *mmc = slot->mmc; ++ struct dw_mci *host = slot->host; ++ struct dentry *root; ++ struct dentry *node; ++ ++ root = mmc->debugfs_root; ++ if (!root) ++ return; ++ ++ node = debugfs_create_file("regs", S_IRUSR, root, host, ++ &dw_mci_regs_fops); ++ if (!node) ++ goto err; ++ ++ node = debugfs_create_file("req", S_IRUSR, root, slot, ++ &dw_mci_req_fops); ++ if (!node) ++ goto err; ++ ++ node = debugfs_create_u32("state", S_IRUSR, root, (u32 *)&host->state); ++ if (!node) ++ goto err; ++ ++ node = debugfs_create_x32("pending_events", S_IRUSR, root, ++ (u32 *)&host->pending_events); ++ if (!node) ++ goto err; ++ ++ node = debugfs_create_x32("completed_events", S_IRUSR, root, ++ (u32 *)&host->completed_events); ++ if (!node) ++ goto err; ++ ++ return; ++ ++err: ++ dev_err(&mmc->class_dev, "failed to initialize debugfs for slot\n"); ++} ++#endif /* defined(CONFIG_DEBUG_FS) */ ++ ++static void mci_send_cmd(struct dw_mci_slot *slot, u32 cmd, u32 arg); ++ ++static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd) ++{ ++ struct mmc_data *data; ++ struct dw_mci_slot *slot = mmc_priv(mmc); ++ struct dw_mci *host = slot->host; ++ const struct dw_mci_drv_data *drv_data = slot->host->drv_data; ++ u32 cmdr; ++ cmd->error = -EINPROGRESS; ++ ++ cmdr = cmd->opcode; ++ ++ if (cmd->opcode == MMC_STOP_TRANSMISSION || ++ cmd->opcode == MMC_GO_IDLE_STATE || ++ cmd->opcode == MMC_GO_INACTIVE_STATE || ++ (cmd->opcode == SD_IO_RW_DIRECT && ++ ((cmd->arg >> 9) & 0x1FFFF) == SDIO_CCCR_ABORT)) ++ cmdr |= SDMMC_CMD_STOP; ++ else if (cmd->opcode != MMC_SEND_STATUS && cmd->data) ++ cmdr |= SDMMC_CMD_PRV_DAT_WAIT; ++ ++ if (cmd->opcode == SD_SWITCH_VOLTAGE) { ++ u32 clk_en_a; ++ ++ /* Special bit makes CMD11 not die */ ++ cmdr |= SDMMC_CMD_VOLT_SWITCH; ++ ++ /* Change state to continue to handle CMD11 weirdness */ ++ WARN_ON(slot->host->state != STATE_SENDING_CMD); ++ slot->host->state = STATE_SENDING_CMD11; ++ ++ /* ++ * We need to disable low power mode (automatic clock stop) ++ * while doing voltage switch so we don't confuse the card, ++ * since stopping the clock is a specific part of the UHS ++ * voltage change dance. ++ * ++ * Note that low power mode (SDMMC_CLKEN_LOW_PWR) will be ++ * unconditionally turned back on in dw_mci_setup_bus() if it's ++ * ever called with a non-zero clock. That shouldn't happen ++ * until the voltage change is all done. ++ */ ++ clk_en_a = mci_readl(host, CLKENA); ++ clk_en_a &= ~(SDMMC_CLKEN_LOW_PWR << slot->id); ++ mci_writel(host, CLKENA, clk_en_a); ++ mci_send_cmd(slot, SDMMC_CMD_UPD_CLK | ++ SDMMC_CMD_PRV_DAT_WAIT, 0); ++ } ++ ++ if (cmd->flags & MMC_RSP_PRESENT) { ++ /* We expect a response, so set this bit */ ++ cmdr |= SDMMC_CMD_RESP_EXP; ++ if (cmd->flags & MMC_RSP_136) ++ cmdr |= SDMMC_CMD_RESP_LONG; ++ } ++ ++ if (cmd->flags & MMC_RSP_CRC) ++ cmdr |= SDMMC_CMD_RESP_CRC; ++ ++ data = cmd->data; ++ if (data) { ++ cmdr |= SDMMC_CMD_DAT_EXP; ++ if (data->flags & MMC_DATA_STREAM) ++ cmdr |= SDMMC_CMD_STRM_MODE; ++ if (data->flags & MMC_DATA_WRITE) ++ cmdr |= SDMMC_CMD_DAT_WR; ++ } ++ ++ if (drv_data && drv_data->prepare_command) ++ drv_data->prepare_command(slot->host, &cmdr); ++ ++ return cmdr; ++} ++ ++static u32 dw_mci_prep_stop_abort(struct dw_mci *host, struct mmc_command *cmd) ++{ ++ struct mmc_command *stop; ++ u32 cmdr; ++ ++ if (!cmd->data) ++ return 0; ++ ++ stop = &host->stop_abort; ++ cmdr = cmd->opcode; ++ memset(stop, 0, sizeof(struct mmc_command)); ++ ++ if (cmdr == MMC_READ_SINGLE_BLOCK || ++ cmdr == MMC_READ_MULTIPLE_BLOCK || ++ cmdr == MMC_WRITE_BLOCK || ++ cmdr == MMC_WRITE_MULTIPLE_BLOCK) { ++ stop->opcode = MMC_STOP_TRANSMISSION; ++ stop->arg = 0; ++ stop->flags = MMC_RSP_R1B | MMC_CMD_AC; ++ } else if (cmdr == SD_IO_RW_EXTENDED) { ++ stop->opcode = SD_IO_RW_DIRECT; ++ stop->arg |= (1 << 31) | (0 << 28) | (SDIO_CCCR_ABORT << 9) | ++ ((cmd->arg >> 28) & 0x7); ++ stop->flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC; ++ } else { ++ return 0; ++ } ++ ++ cmdr = stop->opcode | SDMMC_CMD_STOP | ++ SDMMC_CMD_RESP_CRC | SDMMC_CMD_RESP_EXP; ++ ++ return cmdr; ++} ++ ++static void dw_mci_start_command(struct dw_mci *host, ++ struct mmc_command *cmd, u32 cmd_flags) ++{ ++ host->cmd = cmd; ++ dev_vdbg(host->dev, ++ "start command: ARGR=0x%08x CMDR=0x%08x\n", ++ cmd->arg, cmd_flags); ++ ++ mci_writel(host, CMDARG, cmd->arg); ++ wmb(); ++ ++ mci_writel(host, CMD, cmd_flags | SDMMC_CMD_START); ++} ++ ++static inline void send_stop_abort(struct dw_mci *host, struct mmc_data *data) ++{ ++ struct mmc_command *stop = data->stop ? data->stop : &host->stop_abort; ++ dw_mci_start_command(host, stop, host->stop_cmdr); ++} ++ ++/* DMA interface functions */ ++static void dw_mci_stop_dma(struct dw_mci *host) ++{ ++ if (host->using_dma) { ++ host->dma_ops->stop(host); ++ host->dma_ops->cleanup(host); ++ } ++ ++ /* Data transfer was stopped by the interrupt handler */ ++ set_bit(EVENT_XFER_COMPLETE, &host->pending_events); ++} ++ ++static int dw_mci_get_dma_dir(struct mmc_data *data) ++{ ++ if (data->flags & MMC_DATA_WRITE) ++ return DMA_TO_DEVICE; ++ else ++ return DMA_FROM_DEVICE; ++} ++ ++#ifdef CONFIG_MMC_DW_IDMAC ++static void dw_mci_dma_cleanup(struct dw_mci *host) ++{ ++ struct mmc_data *data = host->data; ++ ++ if (data) ++ if (!data->host_cookie) ++ dma_unmap_sg(host->dev, ++ data->sg, ++ data->sg_len, ++ dw_mci_get_dma_dir(data)); ++} ++ ++static void dw_mci_idmac_reset(struct dw_mci *host) ++{ ++ u32 bmod = mci_readl(host, BMOD); ++ /* Software reset of DMA */ ++ bmod |= SDMMC_IDMAC_SWRESET; ++ mci_writel(host, BMOD, bmod); ++} ++ ++static void dw_mci_idmac_stop_dma(struct dw_mci *host) ++{ ++ u32 temp; ++ ++ /* Disable and reset the IDMAC interface */ ++ temp = mci_readl(host, CTRL); ++ temp &= ~SDMMC_CTRL_USE_IDMAC; ++ temp |= SDMMC_CTRL_DMA_RESET; ++ mci_writel(host, CTRL, temp); ++ ++ /* Stop the IDMAC running */ ++ temp = mci_readl(host, BMOD); ++ temp &= ~(SDMMC_IDMAC_ENABLE | SDMMC_IDMAC_FB); ++ temp |= SDMMC_IDMAC_SWRESET; ++ mci_writel(host, BMOD, temp); ++} ++ ++static void dw_mci_idmac_complete_dma(struct dw_mci *host) ++{ ++ struct mmc_data *data = host->data; ++ ++ dev_vdbg(host->dev, "DMA complete\n"); ++ ++ host->dma_ops->cleanup(host); ++ ++ /* ++ * If the card was removed, data will be NULL. No point in trying to ++ * send the stop command or waiting for NBUSY in this case. ++ */ ++ if (data) { ++ set_bit(EVENT_XFER_COMPLETE, &host->pending_events); ++ tasklet_schedule(&host->tasklet); ++ } ++} ++ ++static void dw_mci_translate_sglist(struct dw_mci *host, struct mmc_data *data, ++ unsigned int sg_len) ++{ ++ int i; ++ struct idmac_desc *desc = host->sg_cpu; ++ ++ for (i = 0; i < sg_len; i++, desc++) { ++ unsigned int length = sg_dma_len(&data->sg[i]); ++ u32 mem_addr = sg_dma_address(&data->sg[i]); ++ ++ /* Set the OWN bit and disable interrupts for this descriptor */ ++ desc->des0 = IDMAC_DES0_OWN | IDMAC_DES0_DIC | IDMAC_DES0_CH; ++ ++ /* Buffer length */ ++ IDMAC_SET_BUFFER1_SIZE(desc, length); ++ ++ /* Physical address to DMA to/from */ ++ desc->des2 = mem_addr; ++ } ++ ++ /* Set first descriptor */ ++ desc = host->sg_cpu; ++ desc->des0 |= IDMAC_DES0_FD; ++ ++ /* Set last descriptor */ ++ desc = host->sg_cpu + (i - 1) * sizeof(struct idmac_desc); ++ desc->des0 &= ~(IDMAC_DES0_CH | IDMAC_DES0_DIC); ++ desc->des0 |= IDMAC_DES0_LD; ++ ++ wmb(); ++} ++ ++static void dw_mci_idmac_start_dma(struct dw_mci *host, unsigned int sg_len) ++{ ++ u32 temp; ++ ++ dw_mci_translate_sglist(host, host->data, sg_len); ++ ++ /* Select IDMAC interface */ ++ temp = mci_readl(host, CTRL); ++ temp |= SDMMC_CTRL_USE_IDMAC; ++ mci_writel(host, CTRL, temp); ++ ++ wmb(); ++ ++ /* Enable the IDMAC */ ++ temp = mci_readl(host, BMOD); ++ temp |= SDMMC_IDMAC_ENABLE | SDMMC_IDMAC_FB; ++ mci_writel(host, BMOD, temp); ++ ++ /* Start it running */ ++ mci_writel(host, PLDMND, 1); ++} ++ ++static int dw_mci_idmac_init(struct dw_mci *host) ++{ ++ struct idmac_desc *p; ++ int i; ++ ++ /* Number of descriptors in the ring buffer */ ++ host->ring_size = PAGE_SIZE / sizeof(struct idmac_desc); ++ ++ /* Forward link the descriptor list */ ++ for (i = 0, p = host->sg_cpu; i < host->ring_size - 1; i++, p++) ++ p->des3 = host->sg_dma + (sizeof(struct idmac_desc) * (i + 1)); ++ ++ /* Set the last descriptor as the end-of-ring descriptor */ ++ p->des3 = host->sg_dma; ++ p->des0 = IDMAC_DES0_ER; ++ ++ dw_mci_idmac_reset(host); ++ ++ /* Mask out interrupts - get Tx & Rx complete only */ ++ mci_writel(host, IDSTS, IDMAC_INT_CLR); ++ mci_writel(host, IDINTEN, SDMMC_IDMAC_INT_NI | SDMMC_IDMAC_INT_RI | ++ SDMMC_IDMAC_INT_TI); ++ ++ /* Set the descriptor base address */ ++ mci_writel(host, DBADDR, host->sg_dma); ++ return 0; ++} ++ ++static const struct dw_mci_dma_ops dw_mci_idmac_ops = { ++ .init = dw_mci_idmac_init, ++ .start = dw_mci_idmac_start_dma, ++ .stop = dw_mci_idmac_stop_dma, ++ .complete = dw_mci_idmac_complete_dma, ++ .cleanup = dw_mci_dma_cleanup, ++}; ++#endif /* CONFIG_MMC_DW_IDMAC */ ++ ++static int dw_mci_pre_dma_transfer(struct dw_mci *host, ++ struct mmc_data *data, ++ bool next) ++{ ++ struct scatterlist *sg; ++ unsigned int i, sg_len; ++ ++ if (!next && data->host_cookie) ++ return data->host_cookie; ++ ++ /* ++ * We don't do DMA on "complex" transfers, i.e. with ++ * non-word-aligned buffers or lengths. Also, we don't bother ++ * with all the DMA setup overhead for short transfers. ++ */ ++ if (data->blocks * data->blksz < DW_MCI_DMA_THRESHOLD) ++ return -EINVAL; ++ ++ if (data->blksz & 3) ++ return -EINVAL; ++ ++ for_each_sg(data->sg, sg, data->sg_len, i) { ++ if (sg->offset & 3 || sg->length & 3) ++ return -EINVAL; ++ } ++ ++ sg_len = dma_map_sg(host->dev, ++ data->sg, ++ data->sg_len, ++ dw_mci_get_dma_dir(data)); ++ if (sg_len == 0) ++ return -EINVAL; ++ ++ if (next) ++ data->host_cookie = sg_len; ++ ++ return sg_len; ++} ++ ++static void dw_mci_pre_req(struct mmc_host *mmc, ++ struct mmc_request *mrq, ++ bool is_first_req) ++{ ++ struct dw_mci_slot *slot = mmc_priv(mmc); ++ struct mmc_data *data = mrq->data; ++ ++ if (!slot->host->use_dma || !data) ++ return; ++ ++ if (data->host_cookie) { ++ data->host_cookie = 0; ++ return; ++ } ++ ++ if (dw_mci_pre_dma_transfer(slot->host, mrq->data, 1) < 0) ++ data->host_cookie = 0; ++} ++ ++static void dw_mci_post_req(struct mmc_host *mmc, ++ struct mmc_request *mrq, ++ int err) ++{ ++ struct dw_mci_slot *slot = mmc_priv(mmc); ++ struct mmc_data *data = mrq->data; ++ ++ if (!slot->host->use_dma || !data) ++ return; ++ ++ if (data->host_cookie) ++ dma_unmap_sg(slot->host->dev, ++ data->sg, ++ data->sg_len, ++ dw_mci_get_dma_dir(data)); ++ data->host_cookie = 0; ++} ++ ++static void dw_mci_adjust_fifoth(struct dw_mci *host, struct mmc_data *data) ++{ ++#ifdef CONFIG_MMC_DW_IDMAC ++ unsigned int blksz = data->blksz; ++ const u32 mszs[] = {1, 4, 8, 16, 32, 64, 128, 256}; ++ u32 fifo_width = 1 << host->data_shift; ++ u32 blksz_depth = blksz / fifo_width, fifoth_val; ++ u32 msize = 0, rx_wmark = 1, tx_wmark, tx_wmark_invers; ++ int idx = (sizeof(mszs) / sizeof(mszs[0])) - 1; ++ ++ tx_wmark = (host->fifo_depth) / 2; ++ tx_wmark_invers = host->fifo_depth - tx_wmark; ++ ++ /* ++ * MSIZE is '1', ++ * if blksz is not a multiple of the FIFO width ++ */ ++ if (blksz % fifo_width) { ++ msize = 0; ++ rx_wmark = 1; ++ goto done; ++ } ++ ++ do { ++ if (!((blksz_depth % mszs[idx]) || ++ (tx_wmark_invers % mszs[idx]))) { ++ msize = idx; ++ rx_wmark = mszs[idx] - 1; ++ break; ++ } ++ } while (--idx > 0); ++ /* ++ * If idx is '0', it won't be tried ++ * Thus, initial values are uesed ++ */ ++done: ++ fifoth_val = SDMMC_SET_FIFOTH(msize, rx_wmark, tx_wmark); ++ mci_writel(host, FIFOTH, fifoth_val); ++#endif ++} ++ ++static void dw_mci_ctrl_rd_thld(struct dw_mci *host, struct mmc_data *data) ++{ ++ unsigned int blksz = data->blksz; ++ u32 blksz_depth, fifo_depth; ++ u16 thld_size; ++ ++ WARN_ON(!(data->flags & MMC_DATA_READ)); ++ ++ /* ++ * CDTHRCTL doesn't exist prior to 240A (in fact that register offset is ++ * in the FIFO region, so we really shouldn't access it). ++ */ ++ if (host->verid < DW_MMC_240A) ++ return; ++ ++ if (host->timing != MMC_TIMING_MMC_HS200 && ++ host->timing != MMC_TIMING_UHS_SDR104) ++ goto disable; ++ ++ blksz_depth = blksz / (1 << host->data_shift); ++ fifo_depth = host->fifo_depth; ++ ++ if (blksz_depth > fifo_depth) ++ goto disable; ++ ++ /* ++ * If (blksz_depth) >= (fifo_depth >> 1), should be 'thld_size <= blksz' ++ * If (blksz_depth) < (fifo_depth >> 1), should be thld_size = blksz ++ * Currently just choose blksz. ++ */ ++ thld_size = blksz; ++ mci_writel(host, CDTHRCTL, SDMMC_SET_RD_THLD(thld_size, 1)); ++ return; ++ ++disable: ++ mci_writel(host, CDTHRCTL, SDMMC_SET_RD_THLD(0, 0)); ++} ++ ++static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) ++{ ++ int sg_len; ++ u32 temp; ++ ++ host->using_dma = 0; ++ ++ /* If we don't have a channel, we can't do DMA */ ++ if (!host->use_dma) ++ return -ENODEV; ++ ++ sg_len = dw_mci_pre_dma_transfer(host, data, 0); ++ if (sg_len < 0) { ++ host->dma_ops->stop(host); ++ return sg_len; ++ } ++ ++ host->using_dma = 1; ++ ++ dev_vdbg(host->dev, ++ "sd sg_cpu: %#lx sg_dma: %#lx sg_len: %d\n", ++ (unsigned long)host->sg_cpu, (unsigned long)host->sg_dma, ++ sg_len); ++ ++ /* ++ * Decide the MSIZE and RX/TX Watermark. ++ * If current block size is same with previous size, ++ * no need to update fifoth. ++ */ ++ if (host->prev_blksz != data->blksz) ++ dw_mci_adjust_fifoth(host, data); ++ ++ /* Enable the DMA interface */ ++ temp = mci_readl(host, CTRL); ++ temp |= SDMMC_CTRL_DMA_ENABLE; ++ mci_writel(host, CTRL, temp); ++ ++ /* Disable RX/TX IRQs, let DMA handle it */ ++ temp = mci_readl(host, INTMASK); ++ temp &= ~(SDMMC_INT_RXDR | SDMMC_INT_TXDR); ++ mci_writel(host, INTMASK, temp); ++ ++ host->dma_ops->start(host, sg_len); ++ ++ return 0; ++} ++ ++static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data) ++{ ++ u32 temp; ++ ++ data->error = -EINPROGRESS; ++ ++ WARN_ON(host->data); ++ host->sg = NULL; ++ host->data = data; ++ ++ if (data->flags & MMC_DATA_READ) { ++ host->dir_status = DW_MCI_RECV_STATUS; ++ dw_mci_ctrl_rd_thld(host, data); ++ } else { ++ host->dir_status = DW_MCI_SEND_STATUS; ++ } ++ ++ if (dw_mci_submit_data_dma(host, data)) { ++ int flags = SG_MITER_ATOMIC; ++ if (host->data->flags & MMC_DATA_READ) ++ flags |= SG_MITER_TO_SG; ++ else ++ flags |= SG_MITER_FROM_SG; ++ ++ sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags); ++ host->sg = data->sg; ++ host->part_buf_start = 0; ++ host->part_buf_count = 0; ++ ++ mci_writel(host, RINTSTS, SDMMC_INT_TXDR | SDMMC_INT_RXDR); ++ temp = mci_readl(host, INTMASK); ++ temp |= SDMMC_INT_TXDR | SDMMC_INT_RXDR; ++ mci_writel(host, INTMASK, temp); ++ ++ temp = mci_readl(host, CTRL); ++ temp &= ~SDMMC_CTRL_DMA_ENABLE; ++ mci_writel(host, CTRL, temp); ++ ++ /* ++ * Use the initial fifoth_val for PIO mode. ++ * If next issued data may be transfered by DMA mode, ++ * prev_blksz should be invalidated. ++ */ ++ mci_writel(host, FIFOTH, host->fifoth_val); ++ host->prev_blksz = 0; ++ } else { ++ /* |