|
@@ -18,6 +18,7 @@
|
|
|
#include <linux/slab.h>
|
|
|
#include <linux/clk.h>
|
|
|
#include <linux/io.h>
|
|
|
+#include <linux/gpio.h>
|
|
|
|
|
|
#include <linux/mmc/host.h>
|
|
|
|
|
@@ -44,6 +45,8 @@ struct sdhci_s3c {
|
|
|
struct resource *ioarea;
|
|
|
struct s3c_sdhci_platdata *pdata;
|
|
|
unsigned int cur_clk;
|
|
|
+ int ext_cd_irq;
|
|
|
+ int ext_cd_gpio;
|
|
|
|
|
|
struct clk *clk_io;
|
|
|
struct clk *clk_bus[MAX_BUS_CLK];
|
|
@@ -235,6 +238,61 @@ static struct sdhci_ops sdhci_s3c_ops = {
|
|
|
.get_min_clock = sdhci_s3c_get_min_clock,
|
|
|
};
|
|
|
|
|
|
+static void sdhci_s3c_notify_change(struct platform_device *dev, int state)
|
|
|
+{
|
|
|
+ struct sdhci_host *host = platform_get_drvdata(dev);
|
|
|
+ if (host) {
|
|
|
+ mutex_lock(&host->lock);
|
|
|
+ if (state) {
|
|
|
+ dev_dbg(&dev->dev, "card inserted.\n");
|
|
|
+ host->flags &= ~SDHCI_DEVICE_DEAD;
|
|
|
+ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
|
|
|
+ } else {
|
|
|
+ dev_dbg(&dev->dev, "card removed.\n");
|
|
|
+ host->flags |= SDHCI_DEVICE_DEAD;
|
|
|
+ host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
|
|
|
+ }
|
|
|
+ sdhci_card_detect(host);
|
|
|
+ mutex_unlock(&host->lock);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static irqreturn_t sdhci_s3c_gpio_card_detect_thread(int irq, void *dev_id)
|
|
|
+{
|
|
|
+ struct sdhci_s3c *sc = dev_id;
|
|
|
+ int status = gpio_get_value(sc->ext_cd_gpio);
|
|
|
+ if (sc->pdata->ext_cd_gpio_invert)
|
|
|
+ status = !status;
|
|
|
+ sdhci_s3c_notify_change(sc->pdev, status);
|
|
|
+ return IRQ_HANDLED;
|
|
|
+}
|
|
|
+
|
|
|
+static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc)
|
|
|
+{
|
|
|
+ struct s3c_sdhci_platdata *pdata = sc->pdata;
|
|
|
+ struct device *dev = &sc->pdev->dev;
|
|
|
+
|
|
|
+ if (gpio_request(pdata->ext_cd_gpio, "SDHCI EXT CD") == 0) {
|
|
|
+ sc->ext_cd_gpio = pdata->ext_cd_gpio;
|
|
|
+ sc->ext_cd_irq = gpio_to_irq(pdata->ext_cd_gpio);
|
|
|
+ if (sc->ext_cd_irq &&
|
|
|
+ request_threaded_irq(sc->ext_cd_irq, NULL,
|
|
|
+ sdhci_s3c_gpio_card_detect_thread,
|
|
|
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
|
|
+ dev_name(dev), sc) == 0) {
|
|
|
+ int status = gpio_get_value(sc->ext_cd_gpio);
|
|
|
+ if (pdata->ext_cd_gpio_invert)
|
|
|
+ status = !status;
|
|
|
+ sdhci_s3c_notify_change(sc->pdev, status);
|
|
|
+ } else {
|
|
|
+ dev_warn(dev, "cannot request irq for card detect\n");
|
|
|
+ sc->ext_cd_irq = 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ dev_err(dev, "cannot request gpio for card detect\n");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
|
|
|
{
|
|
|
struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
|
|
@@ -272,6 +330,7 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
|
|
|
sc->host = host;
|
|
|
sc->pdev = pdev;
|
|
|
sc->pdata = pdata;
|
|
|
+ sc->ext_cd_gpio = -1; /* invalid gpio number */
|
|
|
|
|
|
platform_set_drvdata(pdev, host);
|
|
|
|
|
@@ -353,6 +412,13 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
|
|
|
* SDHCI block, or a missing configuration that needs to be set. */
|
|
|
host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;
|
|
|
|
|
|
+ if (pdata->cd_type == S3C_SDHCI_CD_NONE ||
|
|
|
+ pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
|
|
|
+ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
|
|
|
+
|
|
|
+ if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
|
|
|
+ host->mmc->caps = MMC_CAP_NONREMOVABLE;
|
|
|
+
|
|
|
host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR |
|
|
|
SDHCI_QUIRK_32BIT_DMA_SIZE);
|
|
|
|
|
@@ -365,6 +431,15 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
|
|
|
goto err_add_host;
|
|
|
}
|
|
|
|
|
|
+ /* The following two methods of card detection might call
|
|
|
+ sdhci_s3c_notify_change() immediately, so they can be called
|
|
|
+ only after sdhci_add_host(). Setup errors are ignored. */
|
|
|
+ if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_init)
|
|
|
+ pdata->ext_cd_init(&sdhci_s3c_notify_change);
|
|
|
+ if (pdata->cd_type == S3C_SDHCI_CD_GPIO &&
|
|
|
+ gpio_is_valid(pdata->ext_cd_gpio))
|
|
|
+ sdhci_s3c_setup_card_detect_gpio(sc);
|
|
|
+
|
|
|
return 0;
|
|
|
|
|
|
err_add_host:
|
|
@@ -389,10 +464,20 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
|
|
|
|
|
|
static int __devexit sdhci_s3c_remove(struct platform_device *pdev)
|
|
|
{
|
|
|
+ struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
|
|
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
|
struct sdhci_s3c *sc = sdhci_priv(host);
|
|
|
int ptr;
|
|
|
|
|
|
+ if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_cleanup)
|
|
|
+ pdata->ext_cd_cleanup(&sdhci_s3c_notify_change);
|
|
|
+
|
|
|
+ if (sc->ext_cd_irq)
|
|
|
+ free_irq(sc->ext_cd_irq, sc);
|
|
|
+
|
|
|
+ if (gpio_is_valid(sc->ext_cd_gpio))
|
|
|
+ gpio_free(sc->ext_cd_gpio);
|
|
|
+
|
|
|
sdhci_remove_host(host, 1);
|
|
|
|
|
|
for (ptr = 0; ptr < 3; ptr++) {
|