From patchwork Thu May 9 05:55:42 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Daudt X-Patchwork-Id: 2543211 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) by patchwork2.kernel.org (Postfix) with ESMTP id 7124CDF24C for ; Thu, 9 May 2013 05:56:46 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UaJqO-0003uG-U0; Thu, 09 May 2013 05:56:45 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1UaJqM-0007hz-9C; Thu, 09 May 2013 05:56:42 +0000 Received: from mms3.broadcom.com ([216.31.210.19]) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1UaJqI-0007hY-CT for linux-arm-kernel@lists.infradead.org; Thu, 09 May 2013 05:56:40 +0000 Received: from [10.9.208.57] by mms3.broadcom.com with ESMTP (Broadcom SMTP Relay (Email Firewall v6.5)); Wed, 08 May 2013 22:47:32 -0700 X-Server-Uuid: B86B6450-0931-4310-942E-F00ED04CA7AF Received: from IRVEXCHSMTP1.corp.ad.broadcom.com (10.9.207.51) by IRVEXCHCAS08.corp.ad.broadcom.com (10.9.208.57) with Microsoft SMTP Server (TLS) id 14.1.438.0; Wed, 8 May 2013 22:55:58 -0700 Received: from mail-irva-13.broadcom.com (10.10.10.20) by IRVEXCHSMTP1.corp.ad.broadcom.com (10.9.207.51) with Microsoft SMTP Server id 14.1.438.0; Wed, 8 May 2013 22:55:58 -0700 Received: from mothra.ric.broadcom.com (test-csd10.ric.broadcom.com [10.136.18.149]) by mail-irva-13.broadcom.com (Postfix) with ESMTP id 570CD39289; Wed, 8 May 2013 22:55:57 -0700 (PDT) From: "Christian Daudt" To: "Grant Likely" , "Rob Herring" , "Rob Landley" , "Russell King" , "Chris Ball" , "Stephen Warren" , "Olof Johansson" , "Greg Kroah-Hartman" , "Wei WANG" , "Ludovic Desroches" , "Arnd Bergmann" , "Mike A. Chan" , devicetree-discuss@lists.ozlabs.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-mmc@vger.kernel.org Subject: [PATCH] ARM: mmc: bcm281xx SDHCI driver Date: Wed, 8 May 2013 22:55:42 -0700 Message-ID: <1368078942-31265-1-git-send-email-csd@broadcom.com> X-Mailer: git-send-email 1.7.10.4 MIME-Version: 1.0 X-WSS-ID: 7D95E7FE2L813528356-01-01 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130509_015638_608753_25CE6687 X-CRM114-Status: GOOD ( 24.59 ) X-Spam-Score: -5.6 (-----) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-5.6 points) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/, medium trust [216.31.210.19 listed in list.dnswl.org] -1.4 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: csd_b@daudt.org, Christian Daudt X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org Add SDHCI driver for the Broadcom 281xx SoCs. Also add bindings for it into bcm281xx dts files. Still missing: - power managemement Signed-off-by: Christian Daudt diff --git a/Documentation/devicetree/bindings/mmc/bcm,kona-sdhci.txt b/Documentation/devicetree/bindings/mmc/bcm,kona-sdhci.txt new file mode 100644 index 0000000..ad1c4bd --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/bcm,kona-sdhci.txt @@ -0,0 +1,16 @@ +Broadcom BCM281xx SDHCI driver + +This file documents differences between the core properties in mmc.txt +and the properties in the bcm281xx driver. + +Required properties: +- compatible : Should be "bcm,kona-sdhci" + +Example: + +sdio2: sdio@0x3f1a0000 { + compatible = "bcm,kona-sdhci"; + reg = <0x3f1a0000 0x10000>; + interrupts = <0x0 74 0x4>; +}; + diff --git a/arch/arm/boot/dts/bcm11351-brt.dts b/arch/arm/boot/dts/bcm11351-brt.dts index 248067c..9ae3404 100644 --- a/arch/arm/boot/dts/bcm11351-brt.dts +++ b/arch/arm/boot/dts/bcm11351-brt.dts @@ -27,4 +27,21 @@ status = "okay"; }; + sdio0: sdio@0x3f180000 { + max-frequency = <48000000>; + status = "okay"; + }; + + sdio1: sdio@0x3f190000 { + non-removable; + max-frequency = <48000000>; + status = "okay"; + }; + + sdio3: sdio@0x3f1b0000 { + max-frequency = <48000000>; + status = "okay"; + }; + + }; diff --git a/arch/arm/boot/dts/bcm11351.dtsi b/arch/arm/boot/dts/bcm11351.dtsi index ad13588..6606b41 100644 --- a/arch/arm/boot/dts/bcm11351.dtsi +++ b/arch/arm/boot/dts/bcm11351.dtsi @@ -47,4 +47,33 @@ cache-unified; cache-level = <2>; }; + + sdio0: sdio@0x3f180000 { + compatible = "bcm,kona-sdhci"; + reg = <0x3f180000 0x10000>; + interrupts = <0x0 77 0x4>; + status = "disabled"; + }; + + sdio1: sdio@0x3f190000 { + compatible = "bcm,kona-sdhci"; + reg = <0x3f190000 0x10000>; + interrupts = <0x0 76 0x4>; + status = "disabled"; + }; + + sdio2: sdio@0x3f1a0000 { + compatible = "bcm,kona-sdhci"; + reg = <0x3f1a0000 0x10000>; + interrupts = <0x0 74 0x4>; + status = "disabled"; + }; + + sdio3: sdio@0x3f1b0000 { + compatible = "bcm,kona-sdhci"; + reg = <0x3f1b0000 0x10000>; + interrupts = <0x0 73 0x4>; + status = "disabled"; + }; + }; diff --git a/arch/arm/configs/bcm_defconfig b/arch/arm/configs/bcm_defconfig index e3bf2d6..65edf6d 100644 --- a/arch/arm/configs/bcm_defconfig +++ b/arch/arm/configs/bcm_defconfig @@ -78,6 +78,13 @@ CONFIG_BACKLIGHT_LCD_SUPPORT=y CONFIG_LCD_CLASS_DEVICE=y CONFIG_BACKLIGHT_CLASS_DEVICE=y # CONFIG_USB_SUPPORT is not set +CONFIG_MMC=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_TEST=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_BCM_KONA=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y CONFIG_LEDS_TRIGGERS=y diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index d88219e..e067c5a 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -238,6 +238,16 @@ config MMC_SDHCI_S3C_DMA YMMV. +config MMC_SDHCI_BCM_KONA + tristate "SDHCI support on Broadcom KONA platform" + depends on ARCH_BCM && MMC_SDHCI_PLTFM + help + This selects the Broadcom Kona Secure Digital Host Controller + Interface(SDHCI) support. + This is used in Broadcom mobile SoCs. + + If you have a controller with this interface, say Y or M here. + config MMC_SDHCI_BCM2835 tristate "SDHCI platform support for the BCM2835 SD/MMC Controller" depends on ARCH_BCM2835 diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index c380e3c..a9f582b 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o obj-$(CONFIG_MMC_SDHCI_TEGRA) += sdhci-tegra.o obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o +obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o ifeq ($(CONFIG_CB710_DEBUG),y) diff --git a/drivers/mmc/host/sdhci-bcm-kona.c b/drivers/mmc/host/sdhci-bcm-kona.c new file mode 100644 index 0000000..27188b9 --- /dev/null +++ b/drivers/mmc/host/sdhci-bcm-kona.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2013 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sdhci-pltfm.h" +#include "sdhci.h" + +#define SDHCI_SOFT_RESET 0x01000000 +#define KONA_SDHOST_CORECTRL 0x8000 +#define KONA_SDHOST_CD_PINCTRL 0x00000008 +#define KONA_SDHOST_STOP_HCLK 0x00000004 +#define KONA_SDHOST_RESET 0x00000002 +#define KONA_SDHOST_EN 0x00000001 + +#define KONA_SDHOST_CORESTAT 0x8004 +#define KONA_SDHOST_WP 0x00000002 +#define KONA_SDHOST_CD_SW 0x00000001 + +#define KONA_SDHOST_COREIMR 0x8008 +#define KONA_SDHOST_IP 0x00000001 + +#define KONA_SDHOST_COREISR 0x800C +#define KONA_SDHOST_COREIMSR 0x8010 +#define KONA_SDHOST_COREDBG1 0x8014 +#define KONA_SDHOST_COREGPO_MASK 0x8018 + +#define SD_DETECT_GPIO_DEBOUNCE_128MS 128 + +#define KONA_MMC_AUTOSUSPEND_DELAY (50) + +struct sdhci_bcm_kona_cfg { + unsigned int max_freq; + int is_8bit; + int irq; + int cd_gpio; + int wp_gpio; + int non_removable; +}; + +struct sdhci_bcm_kona_dev { + struct sdhci_bcm_kona_cfg *cfg; + struct device *dev; + struct sdhci_host *host; + struct clk *peri_clk; + struct clk *sleep_clk; +}; + + +static int sdhci_bcm_kona_sd_reset(struct sdhci_host *host) +{ + unsigned int val; + unsigned long timeout; + + /* This timeout should be sufficent for core to reset */ + timeout = jiffies + msecs_to_jiffies(100); + + /* reset the host using the top level reset */ + val = sdhci_readl(host, KONA_SDHOST_CORECTRL); + val |= KONA_SDHOST_RESET; + sdhci_writel(host, val, KONA_SDHOST_CORECTRL); + + while (!(sdhci_readl(host, KONA_SDHOST_CORECTRL) & KONA_SDHOST_RESET)) { + if (time_is_before_jiffies(timeout)) { + pr_err("Error: sd host is stuck in reset!!!\n"); + return -EFAULT; + } + } + + /* bring the host out of reset */ + val = sdhci_readl(host, KONA_SDHOST_CORECTRL); + val &= ~KONA_SDHOST_RESET; + + /* + * Back-to-Back register write needs a delay of 1ms at bootup (min 10uS) + * Back-to-Back writes to same register needs delay when SD bus clock + * is very low w.r.t AHB clock, mainly during boot-time and during card + * insert-removal. + */ + udelay(1000); + sdhci_writel(host, val, KONA_SDHOST_CORECTRL); + + return 0; +} + +static void sdhci_bcm_kona_sd_init(struct sdhci_host *host) +{ + unsigned int val; + + /* enable the interrupt from the IP core */ + val = sdhci_readl(host, KONA_SDHOST_COREIMR); + val |= KONA_SDHOST_IP; + sdhci_writel(host, val, KONA_SDHOST_COREIMR); + + /* Enable the AHB clock gating module to the host */ + val = sdhci_readl(host, KONA_SDHOST_CORECTRL); + val |= KONA_SDHOST_EN; + + /* + * Back-to-Back register write needs a delay of 1ms at bootup (min 10uS) + * Back-to-Back writes to same register needs delay when SD bus clock + * is very low w.r.t AHB clock, mainly during boot-time and during card + * insert-removal. + */ + udelay(1000); + sdhci_writel(host, val, KONA_SDHOST_CORECTRL); +} + +/* + * Software emulation of the SD card insertion/removal. Set insert=1 for insert + * and insert=0 for removal. The card detection is done by GPIO. For Broadcom + * IP to function properly the bit 0 of CORESTAT register needs to be set/reset + * to generate the CD IRQ handled in sdhci.c which schedules card_tasklet. + */ +static int sdhci_bcm_kona_sd_card_emulate(struct sdhci_host *host, int insert) +{ + struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host); + struct sdhci_bcm_kona_dev *kona_dev = pltfm_priv->priv; + u32 val; + unsigned long flags; + + /* this function can be called from various contexts including ISR */ + spin_lock_irqsave(&host->lock, flags); + /* Ensure SD bus scanning to detect media change */ + host->mmc->rescan_disable = 0; + + /* + * Back-to-Back register write needs a delay of min 10uS. + * Back-to-Back writes to same register needs delay when SD bus clock + * is very low w.r.t AHB clock, mainly during boot-time and during card + * insert-removal. + * We keep 20uS + */ + udelay(20); + val = sdhci_readl(host, KONA_SDHOST_CORESTAT); + + if (insert) { + if (kona_dev->cfg->wp_gpio >= 0) { + int wp_status = gpio_get_value(kona_dev->cfg->wp_gpio); + + if (wp_status) + val |= KONA_SDHOST_WP; + else + val &= ~KONA_SDHOST_WP; + } + + val |= KONA_SDHOST_CD_SW; + sdhci_writel(host, val, KONA_SDHOST_CORESTAT); + } else { + val &= ~KONA_SDHOST_CD_SW; + sdhci_writel(host, val, KONA_SDHOST_CORESTAT); + } + spin_unlock_irqrestore(&host->lock, flags); + + return 0; +} + +/* + * SD card detection interrupt handler + */ +static irqreturn_t sdhci_bcm_kona_pltfm_cd_interrupt(int irq, void *host) +{ + struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host); + struct sdhci_bcm_kona_dev *kona_dev = pltfm_priv->priv; + + if (gpio_get_value_cansleep(kona_dev->cfg->cd_gpio) == 0) { + pr_debug("card inserted\n"); + sdhci_bcm_kona_sd_card_emulate(host, 1); + } else { + pr_debug("card removed\n"); + sdhci_bcm_kona_sd_card_emulate(host, 0); + } + + return IRQ_HANDLED; +} + +/* + * Get the base clock. Use central clock source for now. Not sure if different + * clock speed to each dev is allowed + */ +static unsigned int sdhci_bcm_kona_get_max_clk(struct sdhci_host *host) +{ + struct sdhci_bcm_kona_dev *kona_dev; + struct sdhci_pltfm_host *pltfm_priv = sdhci_priv(host); + kona_dev = pltfm_priv->priv; + + return kona_dev->cfg->max_freq; +} + +static unsigned int sdhci_bcm_kona_get_timeout_clock(struct sdhci_host *host) +{ + return sdhci_bcm_kona_get_max_clk(host); +} + +static void sdhci_bcm_kona_init_74_clocks(struct sdhci_host *host, + u8 power_mode) +{ + if (power_mode == MMC_POWER_OFF) + return; + else + mdelay(10); +} + +static struct sdhci_ops sdhci_bcm_kona_ops = { + .get_max_clock = sdhci_bcm_kona_get_max_clk, + .get_timeout_clock = sdhci_bcm_kona_get_timeout_clock, + .platform_send_init_74_clocks = sdhci_bcm_kona_init_74_clocks, +}; + +static struct sdhci_pltfm_data sdhci_pltfm_data_kona = { + .ops = &sdhci_bcm_kona_ops, + .quirks = SDHCI_QUIRK_NO_CARD_NO_RESET | + SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | SDHCI_QUIRK_32BIT_DMA_ADDR | + SDHCI_QUIRK_32BIT_DMA_SIZE | SDHCI_QUIRK_32BIT_ADMA_SIZE | + SDHCI_QUIRK_FORCE_BLK_SZ_2048 | + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, +}; + +static const struct of_device_id sdhci_bcm_kona_of_match[] __initdata = { + { .compatible = "bcm,kona-sdhci", .data = &sdhci_pltfm_data_kona }, + {} +}; +MODULE_DEVICE_TABLE(of, sdhci_bcm_kona_of_match); + +static struct sdhci_bcm_kona_cfg * __init sdhci_bcm_kona_parse_dt( + struct platform_device *pdev) +{ + struct sdhci_bcm_kona_cfg *cfg; + struct device_node *np = pdev->dev.of_node; + u32 temp; + + if (!np) + return NULL; + + cfg = devm_kzalloc(&pdev->dev, sizeof(*cfg), GFP_KERNEL); + if (!cfg) { + dev_err(&pdev->dev, "Can't allocate platform cfg\n"); + return NULL; + } + + cfg->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); + cfg->wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); + if (of_get_property(np, "non-removable", NULL)) + cfg->non_removable = 1; + + if (of_property_read_u32(np, "bus-width", &temp) == 0 && temp == 8) + cfg->is_8bit = 1; + + if (of_property_read_u32(np, "max-frequency", &cfg->max_freq)) { + dev_err(&pdev->dev, "Missing max-freq for SDHCI cfg\n"); + return NULL; + } + return cfg; +} + +static int __init sdhci_bcm_kona_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct sdhci_bcm_kona_cfg *kona_cfg = NULL; + const struct sdhci_pltfm_data *plat_data; + struct sdhci_bcm_kona_dev *kona_dev = NULL; + struct sdhci_pltfm_host *pltfm_priv; + struct device *dev = &pdev->dev; + struct sdhci_host *host; + int ret; + unsigned int irq; + ret = 0; + + match = of_match_device(sdhci_bcm_kona_of_match, &pdev->dev); + if (!match) + plat_data = &sdhci_pltfm_data_kona; + else + plat_data = match->data; + + host = sdhci_pltfm_init(pdev, (struct sdhci_pltfm_data *)plat_data); + if (IS_ERR(host)) + return PTR_ERR(host); + + kona_cfg = dev->platform_data; + if (!kona_cfg) + kona_cfg = sdhci_bcm_kona_parse_dt(pdev); + + if (!kona_cfg) { + ret = -ENXIO; + goto err_pltfm_free; + } + + dev_dbg(dev, "%s: inited. IOADDR=%p\n", __func__, host->ioaddr); + + pltfm_priv = sdhci_priv(host); + + kona_dev = devm_kzalloc(dev, sizeof(*kona_dev), GFP_KERNEL); + if (!kona_dev) { + dev_err(dev, "Can't allocate kona_dev\n"); + ret = -ENOMEM; + goto err_pltfm_free; + } + kona_dev->cfg = kona_cfg; + kona_dev->dev = dev; + pltfm_priv->priv = kona_dev; + + dev_dbg(dev, "non-removable=%c\n", + (kona_cfg->non_removable) ? 'Y' : 'N'); + dev_dbg(dev, "cd_gpio %d, wp_gpio %d\n", kona_cfg->cd_gpio, + kona_cfg->wp_gpio); + + if (kona_cfg->non_removable) { + host->mmc->caps |= MMC_CAP_NONREMOVABLE; + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + } + + dev_dbg(dev, "is_8bit=%c\n", (kona_cfg->is_8bit) ? 'Y' : 'N'); + if (kona_cfg->is_8bit) + host->mmc->caps |= MMC_CAP_8_BIT_DATA; + + ret = sdhci_bcm_kona_sd_reset(host); + if (ret) + goto err_pltfm_free; + + sdhci_bcm_kona_sd_init(host); + + ret = sdhci_add_host(host); + if (ret) { + dev_err(dev, "Failed sdhci_add_host\n"); + goto err_reset; + } + + /* if device is eMMC, emulate card insert right here */ + if (kona_cfg->non_removable) { + ret = sdhci_bcm_kona_sd_card_emulate(host, 1); + if (ret) { + dev_err(dev, + "unable to emulate card insertion\n"); + goto err_remove_host; + } + } else if (kona_dev->cfg->cd_gpio >= 0) { + ret = devm_gpio_request(dev, kona_dev->cfg->cd_gpio, "sdio cd"); + if (ret < 0) { + dev_err(kona_dev->dev, + "Unable to request GPIO pin %d\n", + kona_dev->cfg->cd_gpio); + goto err_remove_host; + } + + gpio_direction_input(kona_dev->cfg->cd_gpio); + + /* Set debounce for SD Card detect to maximum value (128ms) + * + * NOTE-1: If gpio_set_debounce() returns error we still + * continue with the default debounce value set. Another reason + * for doing this is that on rhea-ray boards the SD Detect GPIO + * is on GPIO Expander and gpio_set_debounce() will return error + * and if we return error from here, then probe() would fail and + * SD detection would always fail. + * + * NOTE-2: We also give a msleep() of the "debounce" time here + * so that we give enough time for the debounce to stabilize + * before we read the gpio value in gpio_get_value_cansleep(). + */ + ret = gpio_set_debounce(kona_dev->cfg->cd_gpio, + (SD_DETECT_GPIO_DEBOUNCE_128MS * 1000)); + if (ret < 0) { + dev_err(kona_dev->dev, + "%s: gpio set debounce failed." + "default debounce value assumed\n", __func__); + } + + /* Sleep for 128ms to allow debounce to stabilize */ + msleep(SD_DETECT_GPIO_DEBOUNCE_128MS); + /* request irq for cd_gpio after the gpio debounce is + * stabilized, otherwise, some bogus gpio interrupts might be + * triggered. + */ + irq = gpio_to_irq(kona_dev->cfg->cd_gpio); + ret = devm_request_threaded_irq(dev, + irq, + NULL, + sdhci_bcm_kona_pltfm_cd_interrupt, + IRQF_TRIGGER_FALLING| + IRQF_TRIGGER_RISING | + IRQF_ONESHOT | + IRQF_NO_SUSPEND, "sdio cd", host); + if (ret) { + dev_err(kona_dev->dev, + "Unable to request card detection irq=%d" + " for gpio=%d ret=%d\n", + gpio_to_irq(kona_dev->cfg->cd_gpio), + kona_dev->cfg->cd_gpio, ret); + goto err_remove_host; + } + if (kona_dev->cfg->wp_gpio >= 0) { + ret = devm_gpio_request(dev, + kona_dev->cfg->wp_gpio, "sdio wp"); + if (ret < 0) { + dev_err(&pdev->dev, + "Unable to request WP pin %d\n", + kona_dev->cfg->wp_gpio); + kona_dev->cfg->wp_gpio = -1; + } else { + gpio_direction_input(kona_dev->cfg->wp_gpio); + } + } + + /* + * Since the card detection GPIO interrupt is configured to be + * edge sensitive, check the initial GPIO value here, emulate + * only if the card is present + */ + if (gpio_get_value_cansleep(kona_dev->cfg->cd_gpio) == 0) + sdhci_bcm_kona_sd_card_emulate(host, 1); + } + + dev_dbg(dev, "initialized properly\n"); + return 0; + +err_remove_host: + sdhci_remove_host(host, 0); + +err_reset: + sdhci_bcm_kona_sd_reset(host); + +err_pltfm_free: + sdhci_pltfm_free(pdev); + + dev_err(dev, "Probing of sdhci-pltfm failed: %d\n", ret); + return ret; +} + +static int __exit sdhci_bcm_kona_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_bcm_kona_dev *kona_dev = pltfm_host->priv; + int dead; + u32 scratch; + + dead = 0; + scratch = readl(host->ioaddr + SDHCI_INT_STATUS); + if (scratch == (u32)-1) + dead = 1; + sdhci_remove_host(host, dead); + + sdhci_free_host(host); + + return 0; +} + +static struct platform_driver sdhci_bcm_kona_driver = { + .driver = { + .name = "sdhci-kona", + .owner = THIS_MODULE, + .pm = SDHCI_PLTFM_PMOPS, + .of_match_table = of_match_ptr(sdhci_bcm_kona_of_match), + }, + .probe = sdhci_bcm_kona_probe, + .remove = __exit_p(sdhci_bcm_kona_remove), +}; +module_platform_driver(sdhci_bcm_kona_driver); + +MODULE_DESCRIPTION("SDHCI driver for Broadcom Kona platform"); +MODULE_AUTHOR("Broadcom"); +MODULE_LICENSE("GPL v2"); +