aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mmc
diff options
context:
space:
mode:
authorMarek Szyprowski <m.szyprowski@samsung.com>2010-08-10 21:01:58 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2010-08-11 11:59:05 -0400
commit17866e14f3a4f219e94f1374ece7226479418ff8 (patch)
tree4e1a618f9dbcdc982bac4289eec30a87c764c33c /drivers/mmc
parenta1d5646005af1247d6ae78434bb4db15b07a07b2 (diff)
sdhci-s3c: add support for new card detection methods
On some Samsung SoCs not all SDHCI controllers have card detect (CD) line. For some embedded designs it is not even needed, because ususally the device (like SDIO flash memory or wifi controller) is permanently wired to the controller. There are also systems which have a card detect line connected to some of the external interrupt lines or the presence of the card depends on some other actions (like enabling a power regulator). This patch adds support for all these cases. The following card detection methods are possible: 1. internal sdhci host card detect line 2. external event 3. external gpio interrupt 4. no card detect line, controller will poll for the card 5. no card detect line, card is permanently wired to the controller (once detected host won't poll it any more) By default, all existing code would use method #1, what is compatible with the previous version of the driver. In case of external event, two callbacks must be provided in platdata: ext_cd_init and ext_cd_cleanup. Both of them get a callback to a function that notifies the s3c-sdhci host contoller as their argument. That callback function should be called from the even dispatcher to let host notice the card insertion/removal. In case of external gpio interrupt, a gpio pin number must be provided in platdata (ext_cd_gpio parameter), as well as the information about the polarity of that gpio pin (ext_cd_gpio_invert). By default (ext_cd_gpio_invert == 0) gpio value 0 means 'card has been removed', but this can be changed to 'card has been removed' when ext_cd_gpio_invert == 1. This patch adds all required changes to sdhci-s3c driver. Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Cc: <linux-mmc@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/host/sdhci-s3c.c85
-rw-r--r--drivers/mmc/host/sdhci.h1
2 files changed, 86 insertions, 0 deletions
diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c
index f0b819d98a87..0a7f2614c6f0 100644
--- a/drivers/mmc/host/sdhci-s3c.c
+++ b/drivers/mmc/host/sdhci-s3c.c
@@ -18,6 +18,7 @@
18#include <linux/slab.h> 18#include <linux/slab.h>
19#include <linux/clk.h> 19#include <linux/clk.h>
20#include <linux/io.h> 20#include <linux/io.h>
21#include <linux/gpio.h>
21 22
22#include <linux/mmc/host.h> 23#include <linux/mmc/host.h>
23 24
@@ -44,6 +45,8 @@ struct sdhci_s3c {
44 struct resource *ioarea; 45 struct resource *ioarea;
45 struct s3c_sdhci_platdata *pdata; 46 struct s3c_sdhci_platdata *pdata;
46 unsigned int cur_clk; 47 unsigned int cur_clk;
48 int ext_cd_irq;
49 int ext_cd_gpio;
47 50
48 struct clk *clk_io; 51 struct clk *clk_io;
49 struct clk *clk_bus[MAX_BUS_CLK]; 52 struct clk *clk_bus[MAX_BUS_CLK];
@@ -235,6 +238,61 @@ static struct sdhci_ops sdhci_s3c_ops = {
235 .get_min_clock = sdhci_s3c_get_min_clock, 238 .get_min_clock = sdhci_s3c_get_min_clock,
236}; 239};
237 240
241static void sdhci_s3c_notify_change(struct platform_device *dev, int state)
242{
243 struct sdhci_host *host = platform_get_drvdata(dev);
244 if (host) {
245 mutex_lock(&host->lock);
246 if (state) {
247 dev_dbg(&dev->dev, "card inserted.\n");
248 host->flags &= ~SDHCI_DEVICE_DEAD;
249 host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
250 } else {
251 dev_dbg(&dev->dev, "card removed.\n");
252 host->flags |= SDHCI_DEVICE_DEAD;
253 host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
254 }
255 sdhci_card_detect(host);
256 mutex_unlock(&host->lock);
257 }
258}
259
260static irqreturn_t sdhci_s3c_gpio_card_detect_thread(int irq, void *dev_id)
261{
262 struct sdhci_s3c *sc = dev_id;
263 int status = gpio_get_value(sc->ext_cd_gpio);
264 if (sc->pdata->ext_cd_gpio_invert)
265 status = !status;
266 sdhci_s3c_notify_change(sc->pdev, status);
267 return IRQ_HANDLED;
268}
269
270static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc)
271{
272 struct s3c_sdhci_platdata *pdata = sc->pdata;
273 struct device *dev = &sc->pdev->dev;
274
275 if (gpio_request(pdata->ext_cd_gpio, "SDHCI EXT CD") == 0) {
276 sc->ext_cd_gpio = pdata->ext_cd_gpio;
277 sc->ext_cd_irq = gpio_to_irq(pdata->ext_cd_gpio);
278 if (sc->ext_cd_irq &&
279 request_threaded_irq(sc->ext_cd_irq, NULL,
280 sdhci_s3c_gpio_card_detect_thread,
281 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
282 dev_name(dev), sc) == 0) {
283 int status = gpio_get_value(sc->ext_cd_gpio);
284 if (pdata->ext_cd_gpio_invert)
285 status = !status;
286 sdhci_s3c_notify_change(sc->pdev, status);
287 } else {
288 dev_warn(dev, "cannot request irq for card detect\n");
289 sc->ext_cd_irq = 0;
290 }
291 } else {
292 dev_err(dev, "cannot request gpio for card detect\n");
293 }
294}
295
238static int __devinit sdhci_s3c_probe(struct platform_device *pdev) 296static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
239{ 297{
240 struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data; 298 struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
@@ -272,6 +330,7 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
272 sc->host = host; 330 sc->host = host;
273 sc->pdev = pdev; 331 sc->pdev = pdev;
274 sc->pdata = pdata; 332 sc->pdata = pdata;
333 sc->ext_cd_gpio = -1; /* invalid gpio number */
275 334
276 platform_set_drvdata(pdev, host); 335 platform_set_drvdata(pdev, host);
277 336
@@ -353,6 +412,13 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
353 * SDHCI block, or a missing configuration that needs to be set. */ 412 * SDHCI block, or a missing configuration that needs to be set. */
354 host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ; 413 host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;
355 414
415 if (pdata->cd_type == S3C_SDHCI_CD_NONE ||
416 pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
417 host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
418
419 if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
420 host->mmc->caps = MMC_CAP_NONREMOVABLE;
421
356 host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | 422 host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR |
357 SDHCI_QUIRK_32BIT_DMA_SIZE); 423 SDHCI_QUIRK_32BIT_DMA_SIZE);
358 424
@@ -365,6 +431,15 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
365 goto err_add_host; 431 goto err_add_host;
366 } 432 }
367 433
434 /* The following two methods of card detection might call
435 sdhci_s3c_notify_change() immediately, so they can be called
436 only after sdhci_add_host(). Setup errors are ignored. */
437 if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_init)
438 pdata->ext_cd_init(&sdhci_s3c_notify_change);
439 if (pdata->cd_type == S3C_SDHCI_CD_GPIO &&
440 gpio_is_valid(pdata->ext_cd_gpio))
441 sdhci_s3c_setup_card_detect_gpio(sc);
442
368 return 0; 443 return 0;
369 444
370 err_add_host: 445 err_add_host:
@@ -389,10 +464,20 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
389 464
390static int __devexit sdhci_s3c_remove(struct platform_device *pdev) 465static int __devexit sdhci_s3c_remove(struct platform_device *pdev)
391{ 466{
467 struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
392 struct sdhci_host *host = platform_get_drvdata(pdev); 468 struct sdhci_host *host = platform_get_drvdata(pdev);
393 struct sdhci_s3c *sc = sdhci_priv(host); 469 struct sdhci_s3c *sc = sdhci_priv(host);
394 int ptr; 470 int ptr;
395 471
472 if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_cleanup)
473 pdata->ext_cd_cleanup(&sdhci_s3c_notify_change);
474
475 if (sc->ext_cd_irq)
476 free_irq(sc->ext_cd_irq, sc);
477
478 if (gpio_is_valid(sc->ext_cd_gpio))
479 gpio_free(sc->ext_cd_gpio);
480
396 sdhci_remove_host(host, 1); 481 sdhci_remove_host(host, 1);
397 482
398 for (ptr = 0; ptr < 3; ptr++) { 483 for (ptr = 0; ptr < 3; ptr++) {
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 030de492880e..c98315c197ed 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -412,6 +412,7 @@ static inline void *sdhci_priv(struct sdhci_host *host)
412 return (void *)host->private; 412 return (void *)host->private;
413} 413}
414 414
415extern void sdhci_card_detect(struct sdhci_host *host);
415extern int sdhci_add_host(struct sdhci_host *host); 416extern int sdhci_add_host(struct sdhci_host *host);
416extern void sdhci_remove_host(struct sdhci_host *host, int dead); 417extern void sdhci_remove_host(struct sdhci_host *host, int dead);
417 418