firmware/br-ext-chip-ingenic/board/t40/kernel/patches/00000-drivers_mmc_host_sdhc...

805 lines
22 KiB
Diff

diff -drupN a/drivers/mmc/host/sdhci-ingenic.c b/drivers/mmc/host/sdhci-ingenic.c
--- a/drivers/mmc/host/sdhci-ingenic.c 1970-01-01 03:00:00.000000000 +0300
+++ b/drivers/mmc/host/sdhci-ingenic.c 2022-06-09 05:02:30.000000000 +0300
@@ -0,0 +1,800 @@
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_address.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/mmc/host.h>
+#include <soc/cpm.h>
+#include <soc/base.h>
+
+#include "sdhci.h"
+#include "sdhci-ingenic.h"
+
+#define CLK_CTRL
+/* Software redefinition caps */
+#define CAPABILITIES1_SW 0x276dc898
+#define CAPABILITIES2_SW 0
+
+static LIST_HEAD(manual_list);
+#define CPM_MSC0_CLK_R (0xB0000068)
+#define CPM_MSC1_CLK_R (0xB000006C)
+#ifdef CONFIG_FPGA_TEST
+#define MSC_CLK_H_FREQ (0x1 << 20)
+
+static void sdhci_ingenic_fpga_clk(unsigned int clock)
+{
+#define CPM_MSC_CLK_R CPM_MSC0_CLK_R
+//#define CPM_MSC_CLK_R CPM_MSC1_CLK_R
+ unsigned int val;
+
+ if(500000 <= clock){
+ val = readl((const volatile void*)CPM_MSC_CLK_R);
+ val |= MSC_CLK_H_FREQ;
+ writel(val, (void*)CPM_MSC_CLK_R);
+ } else {
+ val = readl((const volatile void*)CPM_MSC_CLK_R);
+ val &= ~MSC_CLK_H_FREQ;
+ writel(val, (void*)CPM_MSC_CLK_R);
+ }
+ //printk("\tclk=%d, CPM_MSC0_CLK_R: %08x\n\n", clock, readl((const volatile void*)CPM_MSC0_CLK_R));
+}
+#endif
+
+static unsigned int sdhci_ingenic_get_cpm_msc(struct sdhci_host *host)
+{
+ char msc_ioaddr[16];
+ unsigned int cpm_msc;
+ sprintf(msc_ioaddr, "0x%x", (unsigned int)host->ioaddr);
+
+ if (!strcmp(msc_ioaddr ,"0xb3060000"))
+ cpm_msc = CPM_MSC0_CLK_R;
+ if (!strcmp(msc_ioaddr ,"0xb3070000"))
+ cpm_msc = CPM_MSC1_CLK_R;
+ return cpm_msc;
+}
+
+/**
+ * sdhci_ingenic_msc_tuning Enable msc controller tuning
+ *
+ * Tuning rx phase
+ * */
+static void sdhci_ingenic_en_msc_tuning(struct sdhci_host *host, unsigned int cpm_msc)
+{
+ if (host->flags & SDHCI_SDR50_NEEDS_TUNING ||
+ host->flags & SDHCI_SDR104_NEEDS_TUNING ||
+ host->flags & SDHCI_HS400_TUNING) {
+ *(volatile unsigned int*)cpm_msc &= ~(0x1 << 20);
+ }
+}
+
+static void sdhci_ingenic_sel_rx_phase(unsigned int cpm_msc)
+{
+ *(volatile unsigned int*)cpm_msc |= (0x1 << 20); // default
+
+ *(volatile unsigned int*)cpm_msc &= ~(0x7 << 17);
+ *(volatile unsigned int*)cpm_msc |= (0x7 << 17); // OK RX 90 TX 270
+}
+
+static void sdhci_ingenic_sel_tx_phase(unsigned int cpm_msc)
+{
+ *(volatile unsigned int*)cpm_msc &= ~(0x3 << 15);
+/* *(volatile unsigned int*)cpm_msc |= (0x2 << 15); // 180 100M OK*/
+ *(volatile unsigned int*)cpm_msc |= (0x3 << 15);
+}
+
+/**
+ * sdhci_ingenic_set_clock - callback on clock change
+ * @host: The SDHCI host being changed
+ * @clock: The clock rate being requested.
+ *
+ * When the card's clock is going to be changed, look at the new frequency
+ * and find the best clock source to go with it.
+*/
+static void sdhci_ingenic_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+#ifndef CONFIG_FPGA_TEST
+ struct sdhci_ingenic *sdhci_ing = sdhci_priv(host);
+ unsigned int cpm_msc = sdhci_ingenic_get_cpm_msc(host);
+ char clkname[16];
+
+
+ if (clock == 0)
+ return;
+
+ sdhci_set_clock(host, clock);
+
+ spin_unlock_irq(&host->lock);
+ sprintf(clkname, "mux_msc%d", sdhci_ing->pdev->id);
+ sdhci_ing->clk_mux = clk_get(NULL, clkname);
+
+ if (clock > 400000) {
+ clk_set_parent(sdhci_ing->clk_mux, sdhci_ing->clk_mpll);
+ } else {
+ clk_set_parent(sdhci_ing->clk_mux, sdhci_ing->clk_ext);
+ *(volatile unsigned int *)0xB0000068 |= 1 << 21;
+ }
+
+ clk_set_rate(sdhci_ing->clk_cgu, clock);
+
+ spin_lock_irq(&host->lock);
+ //printk("%s, set clk: %d, get_clk_rate=%ld\n", __func__, clock, clk_get_rate(sdhci_ing->clk_cgu));
+
+ if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 ||
+ host->mmc->ios.timing == MMC_TIMING_UHS_SDR104) {
+
+ /* RX phase selecte */
+ if (sdhci_ing->pdata->enable_cpm_rx_tuning == 1)
+ sdhci_ingenic_sel_rx_phase(cpm_msc);
+ else
+ sdhci_ingenic_en_msc_tuning(host, cpm_msc);
+ /* TX phase selecte */
+ if (sdhci_ing->pdata->enable_cpm_tx_tuning == 1)
+ sdhci_ingenic_sel_tx_phase(cpm_msc);
+ }
+#else //CONFIG_FPGA_TEST
+ sdhci_ingenic_fpga_clk(clock);
+#endif
+}
+
+#ifdef VOLTAGE_SWITCH
+static void sdhci_ingenic_set_cpm_poc(int power_v18)
+{
+ if (power_v18) {
+ cpm_outl(0x8, CPM_POC);
+ } else {
+ cpm_outl(0x0, CPM_POC);
+ }
+
+ return;
+}
+#endif
+
+/* I/O Driver Strength Types */
+#define INGENIC_TYPE_0 0x0 //30
+#define INGENIC_TYPE_1 0x1 //50
+#define INGENIC_TYPE_2 0x2 //66
+#define INGENIC_TYPE_3 0x3 //100
+
+static int sdhci_ingenic_select_drive_strength(struct sdhci_host *host,
+ struct mmc_card *card,
+ unsigned int max_dtr,
+ int host_drv, int card_drv, int *drv_type)
+{
+ int drive_strength, type;
+
+ type = INGENIC_TYPE_1;
+
+ switch (type) {
+ case INGENIC_TYPE_0:
+ /* Type 0: 50 */
+ *(volatile unsigned int*)(0xb0010500 + 0xb8) |= 0xfff;
+ *(volatile unsigned int*)(0xb0010500 + 0xb4) |= 0x555;
+ drive_strength = 0;
+ break;
+ case INGENIC_TYPE_1:
+ /* Type 1: 33 */
+ *(volatile unsigned int*)(0xb0010500 + 0xb8) |= 0xfff;
+ *drv_type = MMC_CAP_DRIVER_TYPE_A; // GPIO DRV_STR
+ drive_strength = 1;
+ break;
+ case INGENIC_TYPE_2:
+ /* Type 2: 66 */
+ *(volatile unsigned int*)(0xb0010500 + 0xb8) |= 0xfff;
+ *(volatile unsigned int*)(0xb0010500 + 0xb4) |= 0xaaa;
+ *drv_type = MMC_CAP_DRIVER_TYPE_C; // GPIO DRV_STR
+ drive_strength = 2;
+ break;
+ case INGENIC_TYPE_3:
+ /* Type 3: 100 */
+ *(volatile unsigned int*)(0xb0010500 + 0xb8) |= 0xfff;
+ *(volatile unsigned int*)(0xb0010500 + 0xb4) |= 0xfff;
+ *drv_type = MMC_CAP_DRIVER_TYPE_D; // GPIO DRV_STR
+ drive_strength = 3;
+ break;
+ }
+
+ return drive_strength;
+}
+
+#ifdef VOLTAGE_SWITCH
+void sdhci_ingenic_voltage_switch(struct sdhci_host *host)
+{
+ struct sdhci_ingenic *sdhci_ing = sdhci_priv(host);
+ struct sdhci_ingenic_pdata *pdata = sdhci_ing->pdata;
+ unsigned int val;
+
+ if (pdata->sdr_v18 < 0)
+ return;
+
+ /*controlled SD voltage to 1.8V*/
+ val = cpm_inl(CPM_EXCLK_DS) | (1 << 31);
+ cpm_outl(val, CPM_EXCLK_DS);
+
+ /*Set up hardware circuit 1.8V*/
+ val = pdata->sdr_v18;
+
+ gpio_direction_output(val, 1);
+}
+#endif
+
+static struct sdhci_ops sdhci_ingenic_ops = {
+ .set_clock = sdhci_ingenic_set_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .reset = sdhci_reset,
+ .set_uhs_signaling = sdhci_set_uhs_signaling,
+ .select_drive_strength = sdhci_ingenic_select_drive_strength,
+#ifdef VOLTAGE_SWITCH
+ .voltage_switch = sdhci_ingenic_voltage_switch,
+#endif
+};
+
+static void sdhci_ingenic_notify_change(struct platform_device *dev, int state)
+{
+ struct sdhci_host *host = platform_get_drvdata(dev);
+ struct sdhci_ingenic *sdhci_ing = sdhci_priv(host);
+ struct sdhci_ingenic_pdata *pdata = sdhci_ing->pdata;
+ unsigned long flags, val;
+
+ if (host) {
+ spin_lock_irqsave(&host->lock, flags);
+ if (state) {
+ printk("%s: card inserted\n", mmc_hostname(host->mmc));
+ host->flags &= ~SDHCI_DEVICE_DEAD;
+ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+
+ mmc_detect_change(host->mmc, 0);
+ } else {
+ printk("%s: card removed\n", mmc_hostname(host->mmc));
+ host->flags |= SDHCI_DEVICE_DEAD;
+ host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+#ifdef VOLTAGE_SWITCH
+ if (pdata->sdr_v18 > 0) {
+ gpio_direction_output(pdata->sdr_v18, 0);
+
+ val = cpm_inl(CPM_EXCLK_DS) & ~(1 << 31);
+ cpm_outl(val, CPM_EXCLK_DS);
+ }
+#endif
+ mmc_detect_change(host->mmc, 0);
+ }
+ tasklet_schedule(&host->finish_tasklet);
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+}
+
+static irqreturn_t sdhci_ingenic_gpio_card_detect_thread(int irq, void *dev_id)
+{
+ struct sdhci_ingenic *sdhci_ing = (struct sdhci_ingenic *)dev_id;
+ int status = gpio_get_value(sdhci_ing->ext_cd_gpio);
+
+ if (!(sdhci_ing->pdata->gpio->cd.enable_level))
+ status = !status;
+ sdhci_ingenic_notify_change(sdhci_ing->pdev, status);
+ return IRQ_HANDLED;
+}
+
+static void sdhci_ingenic_setup_card_detect_gpio(struct sdhci_ingenic *sdhci_ing)
+{
+ struct sdhci_ingenic_pdata *pdata = sdhci_ing->pdata;
+ struct device *dev = &sdhci_ing->pdev->dev;
+
+ if (devm_gpio_request(dev, pdata->gpio->cd.num, "SDHCI EXT CD") == 0) {
+ sdhci_ing->ext_cd_gpio = pdata->gpio->cd.num;
+ sdhci_ing->ext_cd_irq = gpio_to_irq(pdata->gpio->cd.num);
+ if (sdhci_ing->ext_cd_irq &&
+ request_threaded_irq(sdhci_ing->ext_cd_irq, NULL,
+ sdhci_ingenic_gpio_card_detect_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ dev_name(dev), sdhci_ing) == 0) {
+ int status = gpio_get_value(sdhci_ing->ext_cd_gpio);
+ if (!(pdata->gpio->cd.enable_level))
+ status = !status;
+ sdhci_ingenic_notify_change(sdhci_ing->pdev, status);
+ } else {
+ dev_warn(dev, "cannot request irq for card detect\n");
+ sdhci_ing->ext_cd_irq = 0;
+ }
+ } else {
+ dev_err(dev, "cannot request gpio for card detect\n");
+ }
+}
+
+#ifdef CONFIG_OF
+static void ingenic_mmc_get_gpio(struct device_node *np,
+ struct ingenic_mmc_pin *pin, char *gpioname)
+{
+ int gpio;
+ enum of_gpio_flags flags;
+
+ pin->num = -EBUSY;
+ gpio = of_get_named_gpio_flags(np, gpioname, 0, &flags);
+ if(gpio_is_valid(gpio)) {
+ pin->num = gpio;
+ pin->enable_level =
+ (flags == OF_GPIO_ACTIVE_LOW ? LOW_ENABLE : HIGH_ENABLE);
+ //printk("mmc gpio %s num:%d en-level: %d\n", gpioname, pin->num, pin->enable_level);
+ }
+}
+
+static inline void ingenic_mmc_clk_onoff(struct sdhci_ingenic *ingenic_ing, unsigned int on)
+{
+ if(on) {
+ clk_prepare_enable(ingenic_ing->clk_cgu);
+ clk_prepare_enable(ingenic_ing->clk_gate);
+ } else {
+ clk_disable_unprepare(ingenic_ing->clk_cgu);
+ clk_disable_unprepare(ingenic_ing->clk_gate);
+ }
+}
+
+/**
+ * jzmmc_manual_detect - insert or remove card manually
+ * @index: host->index, namely the index of the controller.
+ * @on: 1 means insert card, 0 means remove card.
+ *
+ * This functions will be called by manually card-detect driver such as
+ * wifi. To enable this mode you can set value pdata.removal = MANUAL.
+ */
+int jzmmc_manual_detect(int index, int on)
+{
+ struct sdhci_ingenic *sdhci_ing;
+ struct sdhci_host *host;
+ struct list_head *pos;
+
+ list_for_each(pos, &manual_list) {
+ sdhci_ing = list_entry(pos, struct sdhci_ingenic, list);
+ if (sdhci_ing->pdev->id == index) {
+ break;
+ } else
+ sdhci_ing = NULL;
+ }
+
+ if(!sdhci_ing) {
+ printk("no manual card detect\n");
+ return -1;
+ }
+
+ host = sdhci_ing->host;
+
+ if (on) {
+ dev_err(&sdhci_ing->pdev->dev, "card insert manually\n");
+ set_bit(INGENIC_MMC_CARD_PRESENT, &sdhci_ing->flags);
+#ifdef CLK_CTRL
+ ingenic_mmc_clk_onoff(sdhci_ing, 1);
+#endif
+ host->flags &= ~SDHCI_DEVICE_DEAD;
+ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+ mmc_detect_change(sdhci_ing->host->mmc, 0);
+ } else {
+ dev_err(&sdhci_ing->pdev->dev, "card remove manually\n");
+ clear_bit(INGENIC_MMC_CARD_PRESENT, &sdhci_ing->flags);
+
+ host->flags |= SDHCI_DEVICE_DEAD;
+ host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+ mmc_detect_change(sdhci_ing->host->mmc, 0);
+#ifdef CLK_CTRL
+ ingenic_mmc_clk_onoff(sdhci_ing, 0);
+#endif
+ }
+
+ tasklet_schedule(&host->finish_tasklet);
+
+ return 0;
+}
+EXPORT_SYMBOL(jzmmc_manual_detect);
+
+/**
+ * ingenic_mmc_clk_ctrl - enable or disable msc clock gate
+ * @index: host->index, namely the index of the controller.
+ * @on: 1-enable msc clock gate, 0-disable msc clock gate.
+ */
+int ingenic_mmc_clk_ctrl(int index, int on)
+{
+ struct sdhci_ingenic *sdhci_ing;
+ struct list_head *pos;
+
+#ifdef CLK_CTRL
+ list_for_each(pos, &manual_list) {
+ sdhci_ing = list_entry(pos, struct sdhci_ingenic, list);
+ if (sdhci_ing->pdev->id == index)
+ break;
+ else
+ sdhci_ing = NULL;
+ }
+
+ if (!sdhci_ing) {
+ printk("no manual card detect\n");
+ return -1;
+ }
+ ingenic_mmc_clk_onoff(sdhci_ing, on);
+#endif
+ return 0;
+}
+EXPORT_SYMBOL(ingenic_mmc_clk_ctrl);
+
+static int sdhci_ingenic_parse_dt(struct device *dev,
+ struct sdhci_host *host,
+ struct sdhci_ingenic_pdata *pdata)
+{
+ struct device_node *np = dev->of_node;
+ struct card_gpio *card_gpio;
+ unsigned int val;
+
+ card_gpio = devm_kzalloc(dev, sizeof(struct card_gpio), GFP_KERNEL);
+ if(!card_gpio)
+ return 0;
+
+ ingenic_mmc_get_gpio(np, &card_gpio->rst, "ingenic,rst-gpios");
+ ingenic_mmc_get_gpio(np, &card_gpio->wp, "ingenic,wp-gpios");
+ ingenic_mmc_get_gpio(np, &card_gpio->pwr, "ingenic,pwr-gpios");
+ ingenic_mmc_get_gpio(np, &card_gpio->cd, "ingenic,cd-gpios");
+ pdata->gpio = card_gpio;
+
+
+ pdata->sdr_v18 = of_get_named_gpio(np, "ingenic,sdr-gpios", 0);
+
+ /* assuming internal card detect that will be configured by pinctrl */
+ pdata->cd_type = SDHCI_INGENIC_CD_INTERNAL;
+
+ if(of_property_read_bool(np, "pio-mode")) {
+ pdata->pio_mode = 1;
+ }
+ if(of_property_read_bool(np, "enable_autocmd12")) {
+ pdata->enable_autocmd12 = 1;
+ }
+ if(of_property_read_bool(np, "enable_cpm_rx_tuning")) {
+ pdata->enable_cpm_rx_tuning = 1;
+ }
+ if(of_property_read_bool(np, "enable_cpm_tx_tuning")) {
+ pdata->enable_cpm_tx_tuning = 1;
+ }
+
+ /* get the card detection method */
+ if (of_get_property(np, "broken-cd", NULL)) {
+ pdata->cd_type = SDHCI_INGENIC_CD_NONE;
+ }
+
+ if (of_get_property(np, "non-removable", NULL)) {
+ pdata->cd_type = SDHCI_INGENIC_CD_PERMANENT;
+ }
+
+ if (of_get_property(np, "cd-inverted", NULL)) {
+ pdata->cd_type = SDHCI_INGENIC_CD_GPIO;
+ }
+
+ if(!(of_property_read_u32(np, "ingenic,sdio_clk", &val)))
+ pdata->sdio_clk = val;
+
+ /*set Power-On-Control Select of GPIO PE group*/
+ if(of_property_read_bool(np, "ingenic,poc-v1.8")) {
+ pdata->poc_v18 = 1;
+ }
+
+ /* if(of_property_read_bool(np, "ingenic,removal-dontcare")) { */
+ /* pdata->removal = DONTCARE; */
+ /* } else if(of_property_read_bool(np, "ingenic,removal-nonremovable")) { */
+ /* pdata->removal = NONREMOVABLE; */
+ /* } else if(of_property_read_bool(np, "ingenic,removal-removable")) { */
+ /* pdata->removal = REMOVABLE; */
+ /* } else if(of_property_read_bool(np, "ingenic,removal-manual")) { */
+ /* pdata->removal = MANUAL; */
+ /* }; */
+
+ /* mmc_of_parse_voltage(np, &pdata->ocr_avail); */
+
+ return 0;
+}
+#else
+static int sdhci_ingenic_parse_dt(struct device *dev,
+ struct sdhci_host *host,
+ struct sdhci_ingenic_pdata *pdata)
+{
+ return -EINVAL;
+}
+#endif
+
+static int sdhci_ingenic_probe(struct platform_device *pdev)
+{
+ struct sdhci_ingenic_pdata *pdata;
+ struct device *dev = &pdev->dev;
+ struct sdhci_host *host;
+ struct sdhci_ingenic *sdhci_ing;
+ char clkname[16];
+ int ret, irq;
+
+ if (!pdev->dev.platform_data && !pdev->dev.of_node) {
+ dev_err(dev, "no device data specified\n");
+ return -ENOENT;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "no irq specified\n");
+ return irq;
+ }
+
+ host = sdhci_alloc_host(dev, sizeof(struct sdhci_ingenic));
+ if (IS_ERR(host)) {
+ dev_err(dev, "sdhci_alloc_host() failed\n");
+ return PTR_ERR(host);
+ }
+ sdhci_ing = sdhci_priv(host);
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ if (pdev->dev.of_node) {
+ ret = sdhci_ingenic_parse_dt(&pdev->dev, host, pdata);
+ if (ret)
+ return ret;
+ } else {
+ memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata));
+ }
+
+ pdev->id = of_alias_get_id(pdev->dev.of_node, "msc");
+
+ sprintf(clkname, "div_msc%d", pdev->id);
+ sdhci_ing->clk_cgu = devm_clk_get(&pdev->dev, clkname);
+ if(!sdhci_ing->clk_cgu) {
+ dev_err(&pdev->dev, "Failed to Get MSC clk!\n");
+ return PTR_ERR(sdhci_ing->clk_cgu);
+ }
+ sprintf(clkname, "gate_msc%d", pdev->id);
+ sdhci_ing->clk_gate = devm_clk_get(&pdev->dev, clkname);
+ if(!sdhci_ing->clk_gate) {
+ dev_err(&pdev->dev, "Failed to Get PWC MSC clk!\n");
+ return PTR_ERR(sdhci_ing->clk_gate);
+ }
+ sdhci_ing->clk_ext = clk_get(NULL, "ext");
+ sdhci_ing->clk_mpll = clk_get(NULL, "mpll");
+
+ ingenic_mmc_clk_onoff(sdhci_ing, 1);
+
+ sdhci_ing->host = host;
+ sdhci_ing->dev = &pdev->dev;
+ sdhci_ing->pdev = pdev;
+ sdhci_ing->pdata = pdata;
+
+ host->ioaddr= of_iomap(pdev->dev.of_node, 0);
+ if (IS_ERR(host->ioaddr)) {
+ return PTR_ERR(host->ioaddr);
+ }
+
+ platform_set_drvdata(pdev, host);
+
+#ifdef VOLTAGE_SWITCH
+ if (pdata->poc_v18){
+ sdhci_ingenic_set_cpm_poc(1);
+ } else {
+ sdhci_ingenic_set_cpm_poc(0);
+ }
+#endif
+
+ /* sdio for WIFI init*/
+ if (pdata->sdio_clk) {
+ ingenic_sdio_wlan_init(&pdev->dev, pdev->id);
+ list_add(&(sdhci_ing->list), &manual_list);
+ }
+
+ if (pdata->sdr_v18 > 0 && \
+ devm_gpio_request(dev, pdata->sdr_v18, "sdr-v18")) {
+ printk("ERROR: no sdr-v18 pin available !!\n");
+ }
+
+ host->hw_name = "ingenic-sdhci";
+ host->ops = &sdhci_ingenic_ops;
+ host->quirks = 0;
+ host->irq = irq;
+
+ /* Software redefinition caps */
+ host->quirks |= SDHCI_QUIRK_MISSING_CAPS;
+ host->caps = CAPABILITIES1_SW;
+ host->caps1 = CAPABILITIES2_SW;
+
+ /* not check wp */
+ host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;
+
+ /* Setup quirks for the controller */
+ host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
+ host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
+
+ /* Data Timeout Counter Value */
+ //host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL;
+ host->timeout_clk = 24000; //TMCLK = 24MHz
+
+ /* This host supports the Auto CMD12 */
+ if(pdata->enable_autocmd12)
+ host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
+
+ /* PIO transfer mode */
+ if(pdata->pio_mode){
+ host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
+ host->quirks |= SDHCI_QUIRK_BROKEN_ADMA;
+ }
+ /* TODO:SoCs need BROKEN_ADMA_ZEROLEN_DESC */
+/* host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC;*/
+
+ if (pdata->cd_type == SDHCI_INGENIC_CD_NONE ||
+ pdata->cd_type == SDHCI_INGENIC_CD_PERMANENT)
+ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+
+ if (pdata->cd_type == SDHCI_INGENIC_CD_PERMANENT)
+ host->mmc->caps = MMC_CAP_NONREMOVABLE;
+
+ if (pdata->pm_caps)
+ host->mmc->pm_caps |= pdata->pm_caps;
+
+ host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR |
+ SDHCI_QUIRK_32BIT_DMA_SIZE);
+
+ /* It supports additional host capabilities if needed */
+ if (pdata->host_caps)
+ host->mmc->caps |= pdata->host_caps;
+
+ if (pdata->host_caps2)
+ host->mmc->caps2 |= pdata->host_caps2;
+
+#ifdef CONFIG_PM_RUNTIME
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_suspend_ignore_children(&pdev->dev, 1);
+#endif
+
+ ret = mmc_of_parse(host->mmc);
+ if (ret) {
+ dev_err(dev, "mmc_of_parse() failed\n");
+ pm_runtime_forbid(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+ return ret;
+ }
+
+ ret = sdhci_add_host(host);
+ if (ret) {
+ dev_err(dev, "sdhci_add_host() failed\n");
+ pm_runtime_forbid(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+ return ret;
+ }
+
+ if (pdata->cd_type == SDHCI_INGENIC_CD_GPIO &&
+ gpio_is_valid(pdata->gpio->cd.num))
+ sdhci_ingenic_setup_card_detect_gpio(sdhci_ing);
+
+#ifdef CONFIG_PM_RUNTIME
+ if (pdata->cd_type != SDHCI_INGENIC_CD_INTERNAL) {
+ clk_disable_unprepare(sdhci_ing->clk_cgu);
+ /* clk_disable_unprepare(sdhci_ing->clk_gate); */
+ }
+#endif
+
+ return 0;
+}
+
+static int sdhci_ingenic_remove(struct platform_device *pdev)
+{
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct sdhci_ingenic *sdhci_ing = sdhci_priv(host);
+
+ if (sdhci_ing->ext_cd_irq)
+ free_irq(sdhci_ing->ext_cd_irq, sdhci_ing);
+
+#ifdef CONFIG_PM_RUNTIME
+ if (pdata->cd_type != SDHCI_INGENIC_CD_INTERNAL){
+ /* clk_prepare_enable(sdhci_ing->clk_gate); */
+ clk_prepare_enable(sdhci_ing->clk_cgu);
+ }
+#endif
+ sdhci_remove_host(host, 1);
+
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ clk_disable_unprepare(sdhci_ing->clk_cgu);
+ /* clk_disable_unprepare(sdhci_ing->clk_gate); */
+
+ sdhci_free_host(host);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sdhci_ingenic_suspend(struct device *dev)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_ingenic *sdhci_ing = sdhci_priv(host) ;
+
+ ingenic_mmc_clk_onoff(sdhci_ing, 0);
+
+ return sdhci_suspend_host(host);
+}
+
+static int sdhci_ingenic_resume(struct device *dev)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_ingenic *sdhci_ing = sdhci_priv(host) ;
+
+ ingenic_mmc_clk_onoff(sdhci_ing, 1);
+
+ return sdhci_resume_host(host);
+}
+#endif
+
+#ifdef CONFIG_PM
+static int sdhci_ingenic_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+
+
+static int sdhci_ingenic_runtime_resume(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops sdhci_ingenic_pmops = {
+ SET_SYSTEM_SLEEP_PM_OPS(sdhci_ingenic_suspend, sdhci_ingenic_resume)
+ SET_RUNTIME_PM_OPS(sdhci_ingenic_runtime_suspend, sdhci_ingenic_runtime_resume,
+ NULL)
+};
+
+#define SDHCI_INGENIC_PMOPS (&sdhci_ingenic_pmops)
+
+#else
+#define SDHCI_INGENIC_PMOPS NULL
+#endif
+
+
+static struct platform_device_id sdhci_ingenic_driver_ids[] = {
+ {
+ .name = "ingenic,sdhci",
+ .driver_data = (kernel_ulong_t)NULL,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, sdhci_ingenic_driver_ids);
+
+#ifdef CONFIG_OF
+static const struct of_device_id sdhci_ingenic_dt_match[] = {
+ {.compatible = "ingenic,sdhci",},
+ {},
+};
+MODULE_DEVICE_TABLE(of, sdhci_ingenic_dt_match);
+#endif
+
+static struct platform_driver sdhci_ingenic_driver = {
+ .probe = sdhci_ingenic_probe,
+ .remove = sdhci_ingenic_remove,
+ .id_table = sdhci_ingenic_driver_ids,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ingenic,sdhci",
+ .pm = SDHCI_INGENIC_PMOPS,
+ .of_match_table = of_match_ptr(sdhci_ingenic_dt_match),
+ },
+};
+
+module_platform_driver(sdhci_ingenic_driver);
+
+
+MODULE_DESCRIPTION("Ingenic SDHCI (MSC) driver");
+MODULE_AUTHOR("Large Dipper <wangquan.shao@ingenic.cn>");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("20160808");