From ff3112f5adcc9dc33c107c826f82017db8f889f5 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 8 Mar 2008 23:43:19 +0100 Subject: [PATCH 01/26] mmc: set controller name early Reorganise code so that mmc_hostname() works directly after allocation. That way host drivers can use that name for resource allocations and messages during probing. Signed-off-by: Pierre Ossman --- drivers/mmc/core/host.c | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index c65d203a846d..1d795c5379b5 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -2,7 +2,7 @@ * linux/drivers/mmc/core/host.c * * Copyright (C) 2003 Russell King, All Rights Reserved. - * Copyright (C) 2007 Pierre Ossman + * Copyright (C) 2007-2008 Pierre Ossman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -57,12 +57,25 @@ static DEFINE_SPINLOCK(mmc_host_lock); */ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) { + int err; struct mmc_host *host; + if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL)) + return NULL; + host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL); if (!host) return NULL; + spin_lock(&mmc_host_lock); + err = idr_get_new(&mmc_host_idr, host, &host->index); + spin_unlock(&mmc_host_lock); + if (err) + goto free; + + snprintf(host->class_dev.bus_id, BUS_ID_SIZE, + "mmc%d", host->index); + host->parent = dev; host->class_dev.parent = dev; host->class_dev.class = &mmc_host_class; @@ -85,6 +98,10 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) host->max_blk_count = PAGE_CACHE_SIZE / 512; return host; + +free: + kfree(host); + return NULL; } EXPORT_SYMBOL(mmc_alloc_host); @@ -104,18 +121,6 @@ int mmc_add_host(struct mmc_host *host) WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) && !host->ops->enable_sdio_irq); - if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL)) - return -ENOMEM; - - spin_lock(&mmc_host_lock); - err = idr_get_new(&mmc_host_idr, host, &host->index); - spin_unlock(&mmc_host_lock); - if (err) - return err; - - snprintf(host->class_dev.bus_id, BUS_ID_SIZE, - "mmc%d", host->index); - led_trigger_register_simple(host->class_dev.bus_id, &host->led); err = device_add(&host->class_dev); @@ -144,10 +149,6 @@ void mmc_remove_host(struct mmc_host *host) device_del(&host->class_dev); led_trigger_unregister_simple(host->led); - - spin_lock(&mmc_host_lock); - idr_remove(&mmc_host_idr, host->index); - spin_unlock(&mmc_host_lock); } EXPORT_SYMBOL(mmc_remove_host); @@ -160,6 +161,10 @@ EXPORT_SYMBOL(mmc_remove_host); */ void mmc_free_host(struct mmc_host *host) { + spin_lock(&mmc_host_lock); + idr_remove(&mmc_host_idr, host->index); + spin_unlock(&mmc_host_lock); + put_device(&host->class_dev); } From b69c9058907642f8e1b32076906755c6623ea060 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 8 Mar 2008 23:44:25 +0100 Subject: [PATCH 02/26] sdhci: remove custom controller name Remove the use of the sdhci specific device name and use the mmc controller name instead. Signed-off-by: Pierre Ossman --- drivers/mmc/host/sdhci.c | 44 +++++++++++++++------------------------- drivers/mmc/host/sdhci.h | 5 +---- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 4b673aa2dc3c..71e020d6718d 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1,7 +1,7 @@ /* * linux/drivers/mmc/host/sdhci.c - Secure Digital Host Controller Interface driver * - * Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved. + * Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved. * * 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 @@ -30,10 +30,6 @@ static unsigned int debug_quirks = 0; -/* For multi controllers in one platform case */ -static u16 chip_index = 0; -static spinlock_t index_lock; - /* * Different quirks to handle when the hardware deviates from a strict * interpretation of the SDHCI specification. @@ -1105,7 +1101,8 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) goto out; } - DBG("*** %s got interrupt: 0x%08x\n", host->slot_descr, intmask); + DBG("*** %s got interrupt: 0x%08x\n", + mmc_hostname(host->mmc), intmask); if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { writel(intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE), @@ -1235,7 +1232,7 @@ static int sdhci_resume (struct pci_dev *pdev) if (chip->hosts[i]->flags & SDHCI_USE_DMA) pci_set_master(pdev); ret = request_irq(chip->hosts[i]->irq, sdhci_irq, - IRQF_SHARED, chip->hosts[i]->slot_descr, + IRQF_SHARED, mmc_hostname(chip->hosts[i]->mmc), chip->hosts[i]); if (ret) return ret; @@ -1324,9 +1321,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) DBG("slot %d at 0x%08lx, irq %d\n", slot, host->addr, host->irq); - snprintf(host->slot_descr, 20, "sdhc%d:slot%d", chip->index, slot); - - ret = pci_request_region(pdev, host->bar, host->slot_descr); + ret = pci_request_region(pdev, host->bar, mmc_hostname(mmc)); if (ret) goto free; @@ -1343,7 +1338,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) version = (version & SDHCI_SPEC_VER_MASK) >> SDHCI_SPEC_VER_SHIFT; if (version > 1) { printk(KERN_ERR "%s: Unknown controller version (%d). " - "You may experience problems.\n", host->slot_descr, + "You may experience problems.\n", mmc_hostname(mmc), version); } @@ -1366,13 +1361,13 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) (host->flags & SDHCI_USE_DMA)) { printk(KERN_WARNING "%s: Will use DMA " "mode even though HW doesn't fully " - "claim to support it.\n", host->slot_descr); + "claim to support it.\n", mmc_hostname(mmc)); } if (host->flags & SDHCI_USE_DMA) { if (pci_set_dma_mask(pdev, DMA_32BIT_MASK)) { printk(KERN_WARNING "%s: No suitable DMA available. " - "Falling back to PIO.\n", host->slot_descr); + "Falling back to PIO.\n", mmc_hostname(mmc)); host->flags &= ~SDHCI_USE_DMA; } } @@ -1386,7 +1381,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) (caps & SDHCI_CLOCK_BASE_MASK) >> SDHCI_CLOCK_BASE_SHIFT; if (host->max_clk == 0) { printk(KERN_ERR "%s: Hardware doesn't specify base clock " - "frequency.\n", host->slot_descr); + "frequency.\n", mmc_hostname(mmc)); ret = -ENODEV; goto unmap; } @@ -1396,7 +1391,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) (caps & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT; if (host->timeout_clk == 0) { printk(KERN_ERR "%s: Hardware doesn't specify timeout clock " - "frequency.\n", host->slot_descr); + "frequency.\n", mmc_hostname(mmc)); ret = -ENODEV; goto unmap; } @@ -1424,7 +1419,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) if (mmc->ocr_avail == 0) { printk(KERN_ERR "%s: Hardware doesn't report any " - "support voltages.\n", host->slot_descr); + "support voltages.\n", mmc_hostname(mmc)); ret = -ENODEV; goto unmap; } @@ -1458,8 +1453,8 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) */ mmc->max_blk_size = (caps & SDHCI_MAX_BLOCK_MASK) >> SDHCI_MAX_BLOCK_SHIFT; if (mmc->max_blk_size >= 3) { - printk(KERN_WARNING "%s: Invalid maximum block size, assuming 512\n", - host->slot_descr); + printk(KERN_WARNING "%s: Invalid maximum block size, " + "assuming 512 bytes\n", mmc_hostname(mmc)); mmc->max_blk_size = 512; } else mmc->max_blk_size = 512 << mmc->max_blk_size; @@ -1480,7 +1475,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host); ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, - host->slot_descr, host); + mmc_hostname(mmc), host); if (ret) goto untasklet; @@ -1494,8 +1489,8 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) mmc_add_host(mmc); - printk(KERN_INFO "%s: SDHCI at 0x%08lx irq %d %s\n", mmc_hostname(mmc), - host->addr, host->irq, + printk(KERN_INFO "%s: SDHCI at 0x%08lx irq %d %s\n", + mmc_hostname(mmc), host->addr, host->irq, (host->flags & SDHCI_USE_DMA)?"DMA":"PIO"); return 0; @@ -1589,11 +1584,6 @@ static int __devinit sdhci_probe(struct pci_dev *pdev, chip->num_slots = slots; pci_set_drvdata(pdev, chip); - /* Add for multi controller case */ - spin_lock(&index_lock); - chip->index = chip_index++; - spin_unlock(&index_lock); - for (i = 0;i < slots;i++) { ret = sdhci_probe_slot(pdev, i); if (ret) { @@ -1654,8 +1644,6 @@ static int __init sdhci_drv_init(void) ": Secure Digital Host Controller Interface driver\n"); printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n"); - spin_lock_init(&index_lock); - return pci_register_driver(&sdhci_driver); } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index d5a38f1b755a..3288e209ba44 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -1,7 +1,7 @@ /* * linux/drivers/mmc/host/sdhci.h - Secure Digital Host Controller Interface driver * - * Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved. + * Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved. * * 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 @@ -190,8 +190,6 @@ struct sdhci_host { int offset; /* Offset into current sg */ int remain; /* Bytes left in current */ - char slot_descr[20]; /* Name for reservations */ - int irq; /* Device IRQ */ int bar; /* PCI BAR index */ unsigned long addr; /* Bus address */ @@ -208,7 +206,6 @@ struct sdhci_chip { unsigned long quirks; - int index; /* Index for chip0, chip1 ...*/ int num_slots; /* Slots on controller */ struct sdhci_host *hosts[0]; /* Pointers to hosts */ }; From 2f730fec83be76f1b3b8f0066b3447f55c50d7a0 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 17 Mar 2008 10:29:38 +0100 Subject: [PATCH 03/26] sdhci: allow led to be controlled freely Hook up the controller LED to the LED subsystem, allowing more flexible control than simply indicating an ongoing request. Signed-off-by: Pierre Ossman --- drivers/mmc/host/sdhci.c | 44 ++++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci.h | 4 ++++ 2 files changed, 48 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 71e020d6718d..6250eb5f98a8 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -19,6 +19,8 @@ #include #include +#include + #include #include "sdhci.h" @@ -252,6 +254,24 @@ static void sdhci_deactivate_led(struct sdhci_host *host) writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); } +#ifdef CONFIG_LEDS_CLASS +static void sdhci_led_control(struct led_classdev *led, + enum led_brightness brightness) +{ + struct sdhci_host *host = container_of(led, struct sdhci_host, led); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if (brightness == LED_OFF) + sdhci_deactivate_led(host); + else + sdhci_activate_led(host); + + spin_unlock_irqrestore(&host->lock, flags); +} +#endif + /*****************************************************************************\ * * * Core functions * @@ -769,7 +789,9 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) WARN_ON(host->mrq != NULL); +#ifndef CONFIG_LEDS_CLASS sdhci_activate_led(host); +#endif host->mrq = mrq; @@ -961,7 +983,9 @@ static void sdhci_tasklet_finish(unsigned long param) host->cmd = NULL; host->data = NULL; +#ifndef CONFIG_LEDS_CLASS sdhci_deactivate_led(host); +#endif mmiowb(); spin_unlock_irqrestore(&host->lock, flags); @@ -1485,6 +1509,17 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) sdhci_dumpregs(host); #endif +#ifdef CONFIG_LEDS_CLASS + host->led.name = mmc_hostname(mmc); + host->led.brightness = LED_OFF; + host->led.default_trigger = mmc_hostname(mmc); + host->led.brightness_set = sdhci_led_control; + + ret = led_classdev_register(&pdev->dev, &host->led); + if (ret) + goto reset; +#endif + mmiowb(); mmc_add_host(mmc); @@ -1495,6 +1530,11 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) return 0; +#ifdef CONFIG_LEDS_CLASS +reset: + sdhci_reset(host, SDHCI_RESET_ALL); + free_irq(host->irq, host); +#endif untasklet: tasklet_kill(&host->card_tasklet); tasklet_kill(&host->finish_tasklet); @@ -1522,6 +1562,10 @@ static void sdhci_remove_slot(struct pci_dev *pdev, int slot) mmc_remove_host(mmc); +#ifdef CONFIG_LEDS_CLASS + led_classdev_unregister(&host->led); +#endif + sdhci_reset(host, SDHCI_RESET_ALL); free_irq(host->irq, host); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 3288e209ba44..7fb02e177a3d 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -168,6 +168,10 @@ struct sdhci_host { struct sdhci_chip *chip; struct mmc_host *mmc; /* MMC structure */ +#ifdef CONFIG_LEDS_CLASS + struct led_classdev led; /* LED control */ +#endif + spinlock_t lock; /* Mutex */ int flags; /* Host attributes */ From 6fee65cfde519ae811c3cde47c622271168ca449 Mon Sep 17 00:00:00 2001 From: "Robert P. J. Day" Date: Fri, 28 Mar 2008 14:34:47 -0700 Subject: [PATCH 04/26] mmc: use shorter, equivalent set_current_state() Signed-off-by: Robert P. J. Day Signed-off-by: Andrew Morton Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_irq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index 3bd3021f5e80..c292e124107a 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -128,12 +128,12 @@ static int sdio_irq_thread(void *_host) } } - set_task_state(current, TASK_INTERRUPTIBLE); + set_current_state(TASK_INTERRUPTIBLE); if (host->caps & MMC_CAP_SDIO_IRQ) host->ops->enable_sdio_irq(host, 1); if (!kthread_should_stop()) schedule_timeout(period); - set_task_state(current, TASK_RUNNING); + set_current_state(TASK_RUNNING); } while (!kthread_should_stop()); if (host->caps & MMC_CAP_SDIO_IRQ) From 5ec21b1ccf554593d25fba0f411c03e1d6fd88c5 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Aguiar Date: Wed, 26 Mar 2008 16:08:41 -0400 Subject: [PATCH 05/26] MMC: OMAP: Remove some opcodes from host driver This patch removes some opcodes from host driver so there's no need on putting '#include ', that should not be needed in host drivers. Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 90c358b57d1c..da70467915d6 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -495,15 +495,10 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (status & OMAP_MMC_STAT_CMD_TOUT) { /* Timeouts are routine with some commands */ if (host->cmd) { - if (host->cmd->opcode != MMC_ALL_SEND_CID && - host->cmd->opcode != - MMC_SEND_OP_COND && - host->cmd->opcode != - MMC_APP_CMD && - !mmc_omap_cover_is_open(host)) + if (!mmc_omap_cover_is_open(host)) dev_err(mmc_dev(host->mmc), - "command timeout, CMD %d\n", - host->cmd->opcode); + "command timeout, CMD %d\n", + host->cmd->opcode); host->cmd->error = -ETIMEDOUT; end_command = 1; } From ec2f362a9126237e79a21d5c3cd225f5a07f8e89 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 26 Mar 2008 16:08:45 -0400 Subject: [PATCH 06/26] MMC: OMAP: Remove extra divisor increase As noted by Kyungmin Park, the divisor calculation has an unnecessary increase. Signed-off-by: Tony Lindgren Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index da70467915d6..472e977b7404 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -931,7 +931,6 @@ static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios) if (dsor > 250) dsor = 250; - dsor++; if (ios->bus_width == MMC_BUS_WIDTH_4) dsor |= 1 << 15; From d365abe27db12df25660fe254e09876cea8a9ad8 Mon Sep 17 00:00:00 2001 From: Francisco Alecrim Date: Wed, 26 Mar 2008 16:08:48 -0400 Subject: [PATCH 07/26] MMC: OMAP: Fix the BYTEBLOCK capability removal According with commit 255d01af9a990fd5166f04ed0cc0b30b7b67e81e from Linux-OMAP tree, the BYTEBLOCK capability was removed by Pierre Ossman. MMC_CAP_BYTEBLOCK is not defined causing the compile error: drivers/mmc/host/omap.c: In function `mmc_omap_probe': drivers/mmc/host/omap.c:1077: error: `MMC_CAP_BYTEBLOCK' undeclared (first use in this function) drivers/mmc/host/omap.c:1077: error: (Each undeclared identifier is reported only once drivers/mmc/host/omap.c:1077: error: for each function it appears in.) Signed-off-by: Francisco Alecrim Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 472e977b7404..adf679a157eb 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1070,7 +1070,7 @@ static int __init mmc_omap_probe(struct platform_device *pdev) mmc->f_min = 400000; mmc->f_max = 24000000; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; - mmc->caps = MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK; + mmc->caps = MMC_CAP_MULTIWRITE; if (minfo->wire4) mmc->caps |= MMC_CAP_4_BIT_DATA; From 4bc9e35556bf4444014ba65b80abb2fb9f70899a Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 26 Mar 2008 16:08:53 -0400 Subject: [PATCH 08/26] MMC: OMAP: Remove cover switch handling to allow adding multislot support This patch removes the MMC cover switch handling temporarily to make following multislot patches cleaner. MMC cover switch handling will be added back in later patches after adding basic multislot support. Signed-off-by: Tony Lindgren Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 152 +--------------------------------------- 1 file changed, 1 insertion(+), 151 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index adf679a157eb..7d17c899c394 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -95,8 +95,6 @@ * when the cover switch is open */ #define OMAP_MMC_SWITCH_POLL_DELAY 500 -static int mmc_omap_enable_poll = 1; - struct mmc_omap_host { int initialized; int suspended; @@ -133,62 +131,11 @@ struct mmc_omap_host { short power_pin; short wp_pin; - int switch_pin; struct work_struct switch_work; struct timer_list switch_timer; int switch_last_state; }; -static inline int -mmc_omap_cover_is_open(struct mmc_omap_host *host) -{ - if (host->switch_pin < 0) - return 0; - return omap_get_gpio_datain(host->switch_pin); -} - -static ssize_t -mmc_omap_show_cover_switch(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct mmc_omap_host *host = dev_get_drvdata(dev); - - return sprintf(buf, "%s\n", mmc_omap_cover_is_open(host) ? "open" : - "closed"); -} - -static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL); - -static ssize_t -mmc_omap_show_enable_poll(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return snprintf(buf, PAGE_SIZE, "%d\n", mmc_omap_enable_poll); -} - -static ssize_t -mmc_omap_store_enable_poll(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t size) -{ - int enable_poll; - - if (sscanf(buf, "%10d", &enable_poll) != 1) - return -EINVAL; - - if (enable_poll != mmc_omap_enable_poll) { - struct mmc_omap_host *host = dev_get_drvdata(dev); - - mmc_omap_enable_poll = enable_poll; - if (enable_poll && host->switch_pin >= 0) - schedule_work(&host->switch_work); - } - return size; -} - -static DEVICE_ATTR(enable_poll, 0664, - mmc_omap_show_enable_poll, mmc_omap_store_enable_poll); - static void mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) { @@ -495,8 +442,7 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (status & OMAP_MMC_STAT_CMD_TOUT) { /* Timeouts are routine with some commands */ if (host->cmd) { - if (!mmc_omap_cover_is_open(host)) - dev_err(mmc_dev(host->mmc), + dev_err(mmc_dev(host->mmc), "command timeout, CMD %d\n", host->cmd->opcode); host->cmd->error = -ETIMEDOUT; @@ -544,54 +490,6 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) return IRQ_HANDLED; } -static irqreturn_t mmc_omap_switch_irq(int irq, void *dev_id) -{ - struct mmc_omap_host *host = (struct mmc_omap_host *) dev_id; - - schedule_work(&host->switch_work); - - return IRQ_HANDLED; -} - -static void mmc_omap_switch_timer(unsigned long arg) -{ - struct mmc_omap_host *host = (struct mmc_omap_host *) arg; - - schedule_work(&host->switch_work); -} - -static void mmc_omap_switch_handler(struct work_struct *work) -{ - struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, switch_work); - struct mmc_card *card; - static int complained = 0; - int cards = 0, cover_open; - - if (host->switch_pin == -1) - return; - cover_open = mmc_omap_cover_is_open(host); - if (cover_open != host->switch_last_state) { - kobject_uevent(&host->dev->kobj, KOBJ_CHANGE); - host->switch_last_state = cover_open; - } - mmc_detect_change(host->mmc, 0); - list_for_each_entry(card, &host->mmc->cards, node) { - if (mmc_card_present(card)) - cards++; - } - if (mmc_omap_cover_is_open(host)) { - if (!complained) { - dev_info(mmc_dev(host->mmc), "cover is open\n"); - complained = 1; - } - if (mmc_omap_enable_poll) - mod_timer(&host->switch_timer, jiffies + - msecs_to_jiffies(OMAP_MMC_SWITCH_POLL_DELAY)); - } else { - complained = 0; - } -} - /* Prepare to transfer the next segment of a scatterlist */ static void mmc_omap_prepare_dma(struct mmc_omap_host *host, struct mmc_data *data) @@ -1057,7 +955,6 @@ static int __init mmc_omap_probe(struct platform_device *pdev) * the card detect sensing. */ host->power_pin = minfo->power_pin; - host->switch_pin = minfo->switch_pin; host->wp_pin = minfo->wp_pin; host->use_dma = 1; host->dma_ch = -1; @@ -1102,48 +999,10 @@ static int __init mmc_omap_probe(struct platform_device *pdev) host->dev = &pdev->dev; platform_set_drvdata(pdev, host); - if (host->switch_pin >= 0) { - INIT_WORK(&host->switch_work, mmc_omap_switch_handler); - init_timer(&host->switch_timer); - host->switch_timer.function = mmc_omap_switch_timer; - host->switch_timer.data = (unsigned long) host; - if (omap_request_gpio(host->switch_pin) != 0) { - dev_warn(mmc_dev(host->mmc), "Unable to get GPIO pin for MMC cover switch\n"); - host->switch_pin = -1; - goto no_switch; - } - - omap_set_gpio_direction(host->switch_pin, 1); - ret = request_irq(OMAP_GPIO_IRQ(host->switch_pin), - mmc_omap_switch_irq, IRQF_TRIGGER_RISING, DRIVER_NAME, host); - if (ret) { - dev_warn(mmc_dev(host->mmc), "Unable to get IRQ for MMC cover switch\n"); - omap_free_gpio(host->switch_pin); - host->switch_pin = -1; - goto no_switch; - } - ret = device_create_file(&pdev->dev, &dev_attr_cover_switch); - if (ret == 0) { - ret = device_create_file(&pdev->dev, &dev_attr_enable_poll); - if (ret != 0) - device_remove_file(&pdev->dev, &dev_attr_cover_switch); - } - if (ret) { - dev_warn(mmc_dev(host->mmc), "Unable to create sysfs attributes\n"); - free_irq(OMAP_GPIO_IRQ(host->switch_pin), host); - omap_free_gpio(host->switch_pin); - host->switch_pin = -1; - goto no_switch; - } - if (mmc_omap_enable_poll && mmc_omap_cover_is_open(host)) - schedule_work(&host->switch_work); - } - mmc_add_host(mmc); return 0; -no_switch: /* FIXME: Free other resources too. */ if (host) { if (host->iclk && !IS_ERR(host->iclk)) @@ -1182,15 +1041,6 @@ static int mmc_omap_remove(struct platform_device *pdev) if (host->power_pin >= 0) omap_free_gpio(host->power_pin); - if (host->switch_pin >= 0) { - device_remove_file(&pdev->dev, &dev_attr_enable_poll); - device_remove_file(&pdev->dev, &dev_attr_cover_switch); - free_irq(OMAP_GPIO_IRQ(host->switch_pin), host); - omap_free_gpio(host->switch_pin); - host->switch_pin = -1; - del_timer_sync(&host->switch_timer); - flush_scheduled_work(); - } if (host->iclk && !IS_ERR(host->iclk)) clk_put(host->iclk); if (host->fclk && !IS_ERR(host->fclk)) From abfbe5f7854a083ca324282bf7e39f10bc438313 Mon Sep 17 00:00:00 2001 From: Juha Yrjola Date: Wed, 26 Mar 2008 16:08:57 -0400 Subject: [PATCH 09/26] MMC: OMAP: Introduce new multislot structure and change driver to use it Introduce new MMC multislot structure and change driver to use it. Note that MMC clocking is now enabled in mmc_omap_select_slot() and disabled in mmc_omap_release_slot(). Signed-off-by: Juha Yrjola Signed-off-by: Jarkko Lavinen Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 404 +++++++++++++++++++++++--------- include/asm-arm/arch-omap/mmc.h | 2 + 2 files changed, 295 insertions(+), 111 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 7d17c899c394..59eac7211842 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -95,6 +96,22 @@ * when the cover switch is open */ #define OMAP_MMC_SWITCH_POLL_DELAY 500 +struct mmc_omap_host; + +struct mmc_omap_slot { + int id; + unsigned int vdd; + u16 saved_con; + u16 bus_mode; + unsigned int fclk_freq; + unsigned powered:1; + + struct mmc_request *mrq; + struct mmc_omap_host *host; + struct mmc_host *mmc; + struct omap_mmc_slot_data *pdata; +}; + struct mmc_omap_host { int initialized; int suspended; @@ -129,13 +146,98 @@ struct mmc_omap_host { unsigned dma_len; short power_pin; - short wp_pin; - struct work_struct switch_work; - struct timer_list switch_timer; - int switch_last_state; + struct mmc_omap_slot *slots[OMAP_MMC_MAX_SLOTS]; + struct mmc_omap_slot *current_slot; + spinlock_t slot_lock; + wait_queue_head_t slot_wq; + int nr_slots; + + struct omap_mmc_platform_data *pdata; }; +static void mmc_omap_select_slot(struct mmc_omap_slot *slot, int claimed) +{ + struct mmc_omap_host *host = slot->host; + unsigned long flags; + + if (claimed) + goto no_claim; + spin_lock_irqsave(&host->slot_lock, flags); + while (host->mmc != NULL) { + spin_unlock_irqrestore(&host->slot_lock, flags); + wait_event(host->slot_wq, host->mmc == NULL); + spin_lock_irqsave(&host->slot_lock, flags); + } + host->mmc = slot->mmc; + spin_unlock_irqrestore(&host->slot_lock, flags); +no_claim: + clk_enable(host->fclk); + if (host->current_slot != slot) { + if (host->pdata->switch_slot != NULL) + host->pdata->switch_slot(mmc_dev(slot->mmc), slot->id); + host->current_slot = slot; + } + + /* Doing the dummy read here seems to work around some bug + * at least in OMAP24xx silicon where the command would not + * start after writing the CMD register. Sigh. */ + OMAP_MMC_READ(host, CON); + + OMAP_MMC_WRITE(host, CON, slot->saved_con); +} + +static void mmc_omap_start_request(struct mmc_omap_host *host, + struct mmc_request *req); + +static void mmc_omap_release_slot(struct mmc_omap_slot *slot) +{ + struct mmc_omap_host *host = slot->host; + unsigned long flags; + int i; + + BUG_ON(slot == NULL || host->mmc == NULL); + clk_disable(host->fclk); + + spin_lock_irqsave(&host->slot_lock, flags); + /* Check for any pending requests */ + for (i = 0; i < host->nr_slots; i++) { + struct mmc_omap_slot *new_slot; + struct mmc_request *rq; + + if (host->slots[i] == NULL || host->slots[i]->mrq == NULL) + continue; + + new_slot = host->slots[i]; + /* The current slot should not have a request in queue */ + BUG_ON(new_slot == host->current_slot); + + host->mmc = new_slot->mmc; + spin_unlock_irqrestore(&host->slot_lock, flags); + mmc_omap_select_slot(new_slot, 1); + rq = new_slot->mrq; + new_slot->mrq = NULL; + mmc_omap_start_request(host, rq); + return; + } + + host->mmc = NULL; + wake_up(&host->slot_wq); + spin_unlock_irqrestore(&host->slot_lock, flags); +} + +static ssize_t +mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev); + struct mmc_omap_slot *slot = mmc_priv(mmc); + + return sprintf(buf, "%s\n", slot->pdata->name); +} + +static DEVICE_ATTR(slot_name, S_IRUGO, mmc_omap_show_slot_name, NULL); + static void mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) { @@ -180,7 +282,7 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12); - if (host->bus_mode == MMC_BUSMODE_OPENDRAIN) + if (host->current_slot->bus_mode == MMC_BUSMODE_OPENDRAIN) cmdreg |= 1 << 6; if (cmd->flags & MMC_RSP_BUSY) @@ -189,8 +291,6 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) if (host->data && !(host->data->flags & MMC_DATA_WRITE)) cmdreg |= 1 << 15; - clk_enable(host->fclk); - OMAP_MMC_WRITE(host, CTO, 200); OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff); OMAP_MMC_WRITE(host, ARGH, cmd->arg >> 16); @@ -442,6 +542,8 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (status & OMAP_MMC_STAT_CMD_TOUT) { /* Timeouts are routine with some commands */ if (host->cmd) { + struct mmc_omap_slot *slot = + host->current_slot; dev_err(mmc_dev(host->mmc), "command timeout, CMD %d\n", host->cmd->opcode); @@ -747,11 +849,10 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) } } -static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req) +static void mmc_omap_start_request(struct mmc_omap_host *host, + struct mmc_request *req) { - struct mmc_omap_host *host = mmc_priv(mmc); - - WARN_ON(host->mrq != NULL); + BUG_ON(host->mrq != NULL); host->mrq = req; @@ -760,6 +861,26 @@ static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req) mmc_omap_start_command(host, req->cmd); if (host->dma_in_use) omap_start_dma(host->dma_ch); + BUG_ON(irqs_disabled()); +} + +static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct mmc_omap_slot *slot = mmc_priv(mmc); + struct mmc_omap_host *host = slot->host; + unsigned long flags; + + spin_lock_irqsave(&host->slot_lock, flags); + if (host->mmc != NULL) { + BUG_ON(slot->mrq != NULL); + slot->mrq = req; + spin_unlock_irqrestore(&host->slot_lock, flags); + return; + } else + host->mmc = mmc; + spin_unlock_irqrestore(&host->slot_lock, flags); + mmc_omap_select_slot(slot, 1); + mmc_omap_start_request(host, req); } static void innovator_fpga_socket_power(int on) @@ -813,7 +934,8 @@ static void mmc_omap_power(struct mmc_omap_host *host, int on) static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios) { - struct mmc_omap_host *host = mmc_priv(mmc); + struct mmc_omap_slot *slot = mmc_priv(mmc); + struct mmc_omap_host *host = slot->host; int func_clk_rate = clk_get_rate(host->fclk); int dsor; @@ -830,6 +952,8 @@ static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios) if (dsor > 250) dsor = 250; + slot->fclk_freq = func_clk_rate / dsor; + if (ios->bus_width == MMC_BUS_WIDTH_4) dsor |= 1 << 15; @@ -838,9 +962,9 @@ static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios) static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { - struct mmc_omap_host *host = mmc_priv(mmc); - int dsor; - int i; + struct mmc_omap_slot *slot = mmc_priv(mmc); + struct mmc_omap_host *host = slot->host; + int i, dsor; dsor = mmc_omap_calc_divisor(mmc, ios); host->bus_mode = ios->bus_mode; @@ -878,32 +1002,101 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) clk_disable(host->fclk); } -static int mmc_omap_get_ro(struct mmc_host *mmc) -{ - struct mmc_omap_host *host = mmc_priv(mmc); - - return host->wp_pin && omap_get_gpio_datain(host->wp_pin); -} - static const struct mmc_host_ops mmc_omap_ops = { .request = mmc_omap_request, .set_ios = mmc_omap_set_ios, - .get_ro = mmc_omap_get_ro, }; +static int __init mmc_omap_new_slot(struct mmc_omap_host *host, int id) +{ + struct mmc_omap_slot *slot = NULL; + struct mmc_host *mmc; + int r; + + mmc = mmc_alloc_host(sizeof(struct mmc_omap_slot), host->dev); + if (mmc == NULL) + return -ENOMEM; + + slot = mmc_priv(mmc); + slot->host = host; + slot->mmc = mmc; + slot->id = id; + slot->pdata = &host->pdata->slots[id]; + + host->slots[id] = slot; + + mmc->caps = MMC_CAP_MULTIWRITE; + if (host->pdata->conf.wire4) + mmc->caps |= MMC_CAP_4_BIT_DATA; + + mmc->ops = &mmc_omap_ops; + mmc->f_min = 400000; + + if (cpu_class_is_omap2()) + mmc->f_max = 48000000; + else + mmc->f_max = 24000000; + if (host->pdata->max_freq) + mmc->f_max = min(host->pdata->max_freq, mmc->f_max); + mmc->ocr_avail = slot->pdata->ocr_mask; + + /* Use scatterlist DMA to reduce per-transfer costs. + * NOTE max_seg_size assumption that small blocks aren't + * normally used (except e.g. for reading SD registers). + */ + mmc->max_phys_segs = 32; + mmc->max_hw_segs = 32; + mmc->max_blk_size = 2048; /* BLEN is 11 bits (+1) */ + mmc->max_blk_count = 2048; /* NBLK is 11 bits (+1) */ + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; + mmc->max_seg_size = mmc->max_req_size; + + r = mmc_add_host(mmc); + if (r < 0) + goto err_remove_host; + + if (slot->pdata->name != NULL) { + r = device_create_file(&mmc->class_dev, + &dev_attr_slot_name); + if (r < 0) + goto err_remove_host; + } + + return 0; + +err_remove_host: + mmc_remove_host(mmc); + mmc_free_host(mmc); + return r; +} + +static void mmc_omap_remove_slot(struct mmc_omap_slot *slot) +{ + struct mmc_host *mmc = slot->mmc; + + if (slot->pdata->name != NULL) + device_remove_file(&mmc->class_dev, &dev_attr_slot_name); + + mmc_remove_host(mmc); + mmc_free_host(mmc); +} + static int __init mmc_omap_probe(struct platform_device *pdev) { - struct omap_mmc_conf *minfo = pdev->dev.platform_data; - struct mmc_host *mmc; + struct omap_mmc_platform_data *pdata = pdev->dev.platform_data; struct mmc_omap_host *host = NULL; struct resource *res; - int ret = 0; + int i, ret = 0; int irq; - if (minfo == NULL) { + if (pdata == NULL) { dev_err(&pdev->dev, "platform data missing\n"); return -ENXIO; } + if (pdata->nr_slots == 0) { + dev_err(&pdev->dev, "no slots\n"); + return -ENXIO; + } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); @@ -911,28 +1104,39 @@ static int __init mmc_omap_probe(struct platform_device *pdev) return -ENXIO; res = request_mem_region(res->start, res->end - res->start + 1, - pdev->name); + pdev->name); if (res == NULL) return -EBUSY; - mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev); - if (mmc == NULL) { + host = kzalloc(sizeof(struct mmc_omap_host), GFP_KERNEL); + if (host == NULL) { ret = -ENOMEM; goto err_free_mem_region; } - host = mmc_priv(mmc); - host->mmc = mmc; - spin_lock_init(&host->dma_lock); init_timer(&host->dma_timer); + spin_lock_init(&host->slot_lock); + init_waitqueue_head(&host->slot_wq); + host->dma_timer.function = mmc_omap_dma_timer; host->dma_timer.data = (unsigned long) host; + host->pdata = pdata; + host->dev = &pdev->dev; + platform_set_drvdata(pdev, host); + host->id = pdev->id; host->mem_res = res; host->irq = irq; + host->use_dma = 1; + host->dma_ch = -1; + + host->irq = irq; + host->phys_base = host->mem_res->start; + host->virt_base = (void __iomem *) IO_ADDRESS(host->phys_base); + if (cpu_is_omap24xx()) { host->iclk = clk_get(&pdev->dev, "mmc_ick"); if (IS_ERR(host->iclk)) @@ -950,70 +1154,34 @@ static int __init mmc_omap_probe(struct platform_device *pdev) goto err_free_iclk; } - /* REVISIT: - * Also, use minfo->cover to decide how to manage - * the card detect sensing. - */ - host->power_pin = minfo->power_pin; - host->wp_pin = minfo->wp_pin; - host->use_dma = 1; - host->dma_ch = -1; - - host->irq = irq; - host->phys_base = host->mem_res->start; - host->virt_base = (void __iomem *) IO_ADDRESS(host->phys_base); - - mmc->ops = &mmc_omap_ops; - mmc->f_min = 400000; - mmc->f_max = 24000000; - mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; - mmc->caps = MMC_CAP_MULTIWRITE; - - if (minfo->wire4) - mmc->caps |= MMC_CAP_4_BIT_DATA; - - /* Use scatterlist DMA to reduce per-transfer costs. - * NOTE max_seg_size assumption that small blocks aren't - * normally used (except e.g. for reading SD registers). - */ - mmc->max_phys_segs = 32; - mmc->max_hw_segs = 32; - mmc->max_blk_size = 2048; /* BLEN is 11 bits (+1) */ - mmc->max_blk_count = 2048; /* NBLK is 11 bits (+1) */ - mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; - mmc->max_seg_size = mmc->max_req_size; - - if (host->power_pin >= 0) { - if ((ret = omap_request_gpio(host->power_pin)) != 0) { - dev_err(mmc_dev(host->mmc), - "Unable to get GPIO pin for MMC power\n"); - goto err_free_fclk; - } - omap_set_gpio_direction(host->power_pin, 0); - } - ret = request_irq(host->irq, mmc_omap_irq, 0, DRIVER_NAME, host); if (ret) - goto err_free_power_gpio; + goto err_free_fclk; - host->dev = &pdev->dev; - platform_set_drvdata(pdev, host); + if (pdata->init != NULL) { + ret = pdata->init(&pdev->dev); + if (ret < 0) + goto err_free_irq; + } - mmc_add_host(mmc); + host->nr_slots = pdata->nr_slots; + for (i = 0; i < pdata->nr_slots; i++) { + ret = mmc_omap_new_slot(host, i); + if (ret < 0) { + while (--i >= 0) + mmc_omap_remove_slot(host->slots[i]); + + goto err_plat_cleanup; + } + } return 0; - /* FIXME: Free other resources too. */ - if (host) { - if (host->iclk && !IS_ERR(host->iclk)) - clk_put(host->iclk); - if (host->fclk && !IS_ERR(host->fclk)) - clk_put(host->fclk); - mmc_free_host(host->mmc); - } -err_free_power_gpio: - if (host->power_pin >= 0) - omap_free_gpio(host->power_pin); +err_plat_cleanup: + if (pdata->cleanup) + pdata->cleanup(&pdev->dev); +err_free_irq: + free_irq(host->irq, host); err_free_fclk: clk_put(host->fclk); err_free_iclk: @@ -1022,7 +1190,7 @@ err_free_iclk: clk_put(host->iclk); } err_free_mmc_host: - mmc_free_host(host->mmc); + kfree(host); err_free_mem_region: release_mem_region(res->start, res->end - res->start + 1); return ret; @@ -1031,16 +1199,18 @@ err_free_mem_region: static int mmc_omap_remove(struct platform_device *pdev) { struct mmc_omap_host *host = platform_get_drvdata(pdev); + int i; platform_set_drvdata(pdev, NULL); BUG_ON(host == NULL); - mmc_remove_host(host->mmc); - free_irq(host->irq, host); + for (i = 0; i < host->nr_slots; i++) + mmc_omap_remove_slot(host->slots[i]); + + if (host->pdata->cleanup) + host->pdata->cleanup(&pdev->dev); - if (host->power_pin >= 0) - omap_free_gpio(host->power_pin); if (host->iclk && !IS_ERR(host->iclk)) clk_put(host->iclk); if (host->fclk && !IS_ERR(host->fclk)) @@ -1049,7 +1219,7 @@ static int mmc_omap_remove(struct platform_device *pdev) release_mem_region(pdev->resource[0].start, pdev->resource[0].end - pdev->resource[0].start + 1); - mmc_free_host(host->mmc); + kfree(host); return 0; } @@ -1057,35 +1227,47 @@ static int mmc_omap_remove(struct platform_device *pdev) #ifdef CONFIG_PM static int mmc_omap_suspend(struct platform_device *pdev, pm_message_t mesg) { - int ret = 0; + int i, ret = 0; struct mmc_omap_host *host = platform_get_drvdata(pdev); - if (host && host->suspended) + if (host == NULL || host->suspended) return 0; - if (host) { - ret = mmc_suspend_host(host->mmc, mesg); - if (ret == 0) - host->suspended = 1; + for (i = 0; i < host->nr_slots; i++) { + struct mmc_omap_slot *slot; + + slot = host->slots[i]; + ret = mmc_suspend_host(slot->mmc, mesg); + if (ret < 0) { + while (--i >= 0) { + slot = host->slots[i]; + mmc_resume_host(slot->mmc); + } + return ret; + } } - return ret; + host->suspended = 1; + return 0; } static int mmc_omap_resume(struct platform_device *pdev) { - int ret = 0; + int i, ret = 0; struct mmc_omap_host *host = platform_get_drvdata(pdev); - if (host && !host->suspended) + if (host == NULL || !host->suspended) return 0; - if (host) { - ret = mmc_resume_host(host->mmc); - if (ret == 0) - host->suspended = 0; - } + for (i = 0; i < host->nr_slots; i++) { + struct mmc_omap_slot *slot; + slot = host->slots[i]; + ret = mmc_resume_host(slot->mmc); + if (ret < 0) + return ret; - return ret; + host->suspended = 0; + } + return 0; } #else #define mmc_omap_suspend NULL diff --git a/include/asm-arm/arch-omap/mmc.h b/include/asm-arm/arch-omap/mmc.h index b70e37b61242..c9588f49eb52 100644 --- a/include/asm-arm/arch-omap/mmc.h +++ b/include/asm-arm/arch-omap/mmc.h @@ -18,6 +18,8 @@ #define OMAP_MMC_MAX_SLOTS 2 struct omap_mmc_platform_data { + struct omap_mmc_conf conf; + unsigned enabled:1; /* number of slots on board */ unsigned nr_slots:2; From 5a0f3f1f71e0bc4a843673e8e7cf09a32bb07c32 Mon Sep 17 00:00:00 2001 From: Juha Yrjola Date: Wed, 26 Mar 2008 16:09:08 -0400 Subject: [PATCH 10/26] MMC: OMAP: Add back cover switch support This patch adds back MMC cover switch support in a way that supports multiple slots. Signed-off-by: Juha Yrjola Signed-off-by: Jarkko Lavinen Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 87 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 59eac7211842..712a9608acf7 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -106,6 +106,10 @@ struct mmc_omap_slot { unsigned int fclk_freq; unsigned powered:1; + struct work_struct switch_work; + struct timer_list switch_timer; + unsigned cover_open; + struct mmc_request *mrq; struct mmc_omap_host *host; struct mmc_host *mmc; @@ -226,6 +230,25 @@ static void mmc_omap_release_slot(struct mmc_omap_slot *slot) spin_unlock_irqrestore(&host->slot_lock, flags); } +static inline +int mmc_omap_cover_is_open(struct mmc_omap_slot *slot) +{ + return slot->pdata->get_cover_state(mmc_dev(slot->mmc), slot->id); +} + +static ssize_t +mmc_omap_show_cover_switch(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev); + struct mmc_omap_slot *slot = mmc_priv(mmc); + + return sprintf(buf, "%s\n", mmc_omap_cover_is_open(slot) ? "open" : + "closed"); +} + +static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL); + static ssize_t mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr, char *buf) @@ -544,9 +567,10 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (host->cmd) { struct mmc_omap_slot *slot = host->current_slot; - dev_err(mmc_dev(host->mmc), - "command timeout, CMD %d\n", - host->cmd->opcode); + if (!mmc_omap_cover_is_open(slot)) + dev_err(mmc_dev(host->mmc), + "command timeout, CMD %d\n", + host->cmd->opcode); host->cmd->error = -ETIMEDOUT; end_command = 1; } @@ -592,6 +616,42 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) return IRQ_HANDLED; } +void omap_mmc_notify_cover_event(struct device *dev, int slot, int is_closed) +{ + struct mmc_omap_host *host = dev_get_drvdata(dev); + + BUG_ON(slot >= host->nr_slots); + + /* Other subsystems can call in here before we're initialised. */ + if (host->nr_slots == 0 || !host->slots[slot]) + return; + + schedule_work(&host->slots[slot]->switch_work); +} + +static void mmc_omap_switch_timer(unsigned long arg) +{ + struct mmc_omap_slot *slot = (struct mmc_omap_slot *) arg; + + schedule_work(&slot->switch_work); +} + +static void mmc_omap_cover_handler(struct work_struct *work) +{ + struct mmc_omap_slot *slot = container_of(work, struct mmc_omap_slot, + switch_work); + int cover_open; + + cover_open = mmc_omap_cover_is_open(slot); + if (cover_open != slot->cover_open) { + sysfs_notify(&slot->mmc->class_dev.kobj, NULL, "cover_switch"); + slot->cover_open = cover_open; + dev_info(mmc_dev(slot->mmc), "cover is now %s\n", + cover_open ? "open" : "closed"); + } + mmc_detect_change(slot->mmc, slot->id); +} + /* Prepare to transfer the next segment of a scatterlist */ static void mmc_omap_prepare_dma(struct mmc_omap_host *host, struct mmc_data *data) @@ -1062,8 +1122,24 @@ static int __init mmc_omap_new_slot(struct mmc_omap_host *host, int id) goto err_remove_host; } + if (slot->pdata->get_cover_state != NULL) { + r = device_create_file(&mmc->class_dev, + &dev_attr_cover_switch); + if (r < 0) + goto err_remove_slot_name; + + INIT_WORK(&slot->switch_work, mmc_omap_cover_handler); + init_timer(&slot->switch_timer); + slot->switch_timer.function = mmc_omap_switch_timer; + slot->switch_timer.data = (unsigned long) slot; + schedule_work(&slot->switch_work); + } + return 0; +err_remove_slot_name: + if (slot->pdata->name != NULL) + device_remove_file(&mmc->class_dev, &dev_attr_slot_name); err_remove_host: mmc_remove_host(mmc); mmc_free_host(mmc); @@ -1076,6 +1152,11 @@ static void mmc_omap_remove_slot(struct mmc_omap_slot *slot) if (slot->pdata->name != NULL) device_remove_file(&mmc->class_dev, &dev_attr_slot_name); + if (slot->pdata->get_cover_state != NULL) + device_remove_file(&mmc->class_dev, &dev_attr_cover_switch); + + del_timer_sync(&slot->switch_timer); + flush_scheduled_work(); mmc_remove_host(mmc); mmc_free_host(mmc); From a914ded23565d8688e2b7e435916630def5bdd19 Mon Sep 17 00:00:00 2001 From: Juha Yrjola Date: Wed, 26 Mar 2008 16:09:12 -0400 Subject: [PATCH 11/26] MMC: OMAP: New release dma and abort xfer functions New functions to support MMC multislot: mmc_omap_release_dma() and mmc_omap_abort_xfer(). Signed-off-by: Juha Yrjola Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 77 +++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 712a9608acf7..90e9d68e7998 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -326,27 +326,33 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) OMAP_MMC_WRITE(host, CMD, cmdreg); } +static void +mmc_omap_release_dma(struct mmc_omap_host *host, struct mmc_data *data, + int abort) +{ + enum dma_data_direction dma_data_dir; + + BUG_ON(host->dma_ch < 0); + if (data->error) + omap_stop_dma(host->dma_ch); + /* Release DMA channel lazily */ + mod_timer(&host->dma_timer, jiffies + HZ); + if (data->flags & MMC_DATA_WRITE) + dma_data_dir = DMA_TO_DEVICE; + else + dma_data_dir = DMA_FROM_DEVICE; + dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len, + dma_data_dir); +} + static void mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) { - if (host->dma_in_use) { - enum dma_data_direction dma_data_dir; + if (host->dma_in_use) + mmc_omap_release_dma(host, data, data->error); - BUG_ON(host->dma_ch < 0); - if (data->error) - omap_stop_dma(host->dma_ch); - /* Release DMA channel lazily */ - mod_timer(&host->dma_timer, jiffies + HZ); - if (data->flags & MMC_DATA_WRITE) - dma_data_dir = DMA_TO_DEVICE; - else - dma_data_dir = DMA_FROM_DEVICE; - dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len, - dma_data_dir); - } host->data = NULL; host->sg_len = 0; - clk_disable(host->fclk); /* NOTE: MMC layer will sometimes poll-wait CMD13 next, issuing * dozens of requests until the card finishes writing data. @@ -354,14 +360,44 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) */ if (!data->stop) { + struct mmc_host *mmc; + host->mrq = NULL; - mmc_request_done(host->mmc, data->mrq); + mmc = host->mmc; + mmc_omap_release_slot(host->current_slot); + mmc_request_done(mmc, data->mrq); return; } mmc_omap_start_command(host, data->stop); } +static void +mmc_omap_abort_xfer(struct mmc_omap_host *host, struct mmc_data *data) +{ + int loops; + u16 ie; + + if (host->dma_in_use) + mmc_omap_release_dma(host, data, 1); + + host->data = NULL; + host->sg_len = 0; + + ie = OMAP_MMC_READ(host, IE); + OMAP_MMC_WRITE(host, IE, 0); + OMAP_MMC_WRITE(host, CMD, 1 << 7); + loops = 0; + while (!(OMAP_MMC_READ(host, STAT) & OMAP_MMC_STAT_END_OF_CMD)) { + udelay(1); + loops++; + if (loops == 100000) + break; + } + OMAP_MMC_WRITE(host, STAT, OMAP_MMC_STAT_END_OF_CMD); + OMAP_MMC_WRITE(host, IE, ie); +} + static void mmc_omap_end_of_data(struct mmc_omap_host *host, struct mmc_data *data) { @@ -439,9 +475,14 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) } if (host->data == NULL || cmd->error) { + struct mmc_host *mmc; + + if (host->data != NULL) + mmc_omap_abort_xfer(host, host->data); host->mrq = NULL; - clk_disable(host->fclk); - mmc_request_done(host->mmc, cmd->mrq); + mmc = host->mmc; + mmc_omap_release_slot(host->current_slot); + mmc_request_done(mmc, cmd->mrq); } } From b8f9f0e90a6c3fb999d00301f45bb2213dbe8c00 Mon Sep 17 00:00:00 2001 From: Juha Yrjola Date: Wed, 26 Mar 2008 16:09:16 -0400 Subject: [PATCH 12/26] MMC: OMAP: Fix timeout calculation for MMC multislot support Fix the data timeout calculation for MMC multislot support. Signed-off-by: Juha Yrjola Signed-off-by: Jarkko Lavinen Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 90e9d68e7998..0d3cd3c23f8f 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -861,13 +861,12 @@ static inline void set_cmd_timeout(struct mmc_omap_host *host, struct mmc_reques static inline void set_data_timeout(struct mmc_omap_host *host, struct mmc_request *req) { - int timeout; + unsigned int timeout, cycle_ns; u16 reg; - /* Convert ns to clock cycles by assuming 20MHz frequency - * 1 cycle at 20MHz = 500 ns - */ - timeout = req->data->timeout_clks + req->data->timeout_ns / 500; + cycle_ns = 1000000000 / host->current_slot->fclk_freq; + timeout = req->data->timeout_ns / cycle_ns; + timeout += req->data->timeout_clks; /* Check if we need to use timeout multiplier register */ reg = OMAP_MMC_READ(host, SDIO); From 65b5b6e51bdeab1934a147aec41ecea82d3cc31d Mon Sep 17 00:00:00 2001 From: Juha Yrjola Date: Wed, 26 Mar 2008 16:09:22 -0400 Subject: [PATCH 13/26] MMC: OMAP: Power functions modified to MMC multislot support Modifications at power functions to MMC multislot support. This patch also move board-specific code out of MMC OMAP driver. Signed-off-by: Juha Yrjola Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 88 +++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 51 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 0d3cd3c23f8f..c80cd58cfa07 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -983,52 +983,27 @@ static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req) mmc_omap_start_request(host, req); } -static void innovator_fpga_socket_power(int on) +static void mmc_omap_set_power(struct mmc_omap_slot *slot, int power_on, + int vdd) { -#if defined(CONFIG_MACH_OMAP_INNOVATOR) && defined(CONFIG_ARCH_OMAP15XX) - if (on) { - fpga_write(fpga_read(OMAP1510_FPGA_POWER) | (1 << 3), - OMAP1510_FPGA_POWER); - } else { - fpga_write(fpga_read(OMAP1510_FPGA_POWER) & ~(1 << 3), - OMAP1510_FPGA_POWER); - } -#endif -} + struct mmc_omap_host *host; -/* - * Turn the socket power on/off. Innovator uses FPGA, most boards - * probably use GPIO. - */ -static void mmc_omap_power(struct mmc_omap_host *host, int on) -{ - if (on) { - if (machine_is_omap_innovator()) - innovator_fpga_socket_power(1); - else if (machine_is_omap_h2()) - tps65010_set_gpio_out_value(GPIO3, HIGH); - else if (machine_is_omap_h3()) - /* GPIO 4 of TPS65010 sends SD_EN signal */ - tps65010_set_gpio_out_value(GPIO4, HIGH); - else if (cpu_is_omap24xx()) { - u16 reg = OMAP_MMC_READ(host, CON); - OMAP_MMC_WRITE(host, CON, reg | (1 << 11)); - } else - if (host->power_pin >= 0) - omap_set_gpio_dataout(host->power_pin, 1); - } else { - if (machine_is_omap_innovator()) - innovator_fpga_socket_power(0); - else if (machine_is_omap_h2()) - tps65010_set_gpio_out_value(GPIO3, LOW); - else if (machine_is_omap_h3()) - tps65010_set_gpio_out_value(GPIO4, LOW); - else if (cpu_is_omap24xx()) { - u16 reg = OMAP_MMC_READ(host, CON); - OMAP_MMC_WRITE(host, CON, reg & ~(1 << 11)); - } else - if (host->power_pin >= 0) - omap_set_gpio_dataout(host->power_pin, 0); + host = slot->host; + + if (slot->pdata->set_power != NULL) + slot->pdata->set_power(mmc_dev(slot->mmc), slot->id, power_on, + vdd); + + if (cpu_is_omap24xx()) { + u16 w; + + if (power_on) { + w = OMAP_MMC_READ(host, CON); + OMAP_MMC_WRITE(host, CON, w | (1 << 11)); + } else { + w = OMAP_MMC_READ(host, CON); + OMAP_MMC_WRITE(host, CON, w & ~(1 << 11)); + } } } @@ -1067,23 +1042,31 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) int i, dsor; dsor = mmc_omap_calc_divisor(mmc, ios); - host->bus_mode = ios->bus_mode; - host->hw_bus_mode = host->bus_mode; + + mmc_omap_select_slot(slot, 0); + + if (ios->vdd != slot->vdd) + slot->vdd = ios->vdd; switch (ios->power_mode) { case MMC_POWER_OFF: - mmc_omap_power(host, 0); + mmc_omap_set_power(slot, 0, ios->vdd); break; case MMC_POWER_UP: /* Cannot touch dsor yet, just power up MMC */ - mmc_omap_power(host, 1); - return; + mmc_omap_set_power(slot, 1, ios->vdd); + goto exit; case MMC_POWER_ON: dsor |= 1 << 11; break; } - clk_enable(host->fclk); + if (slot->bus_mode != ios->bus_mode) { + if (slot->pdata->set_bus_mode != NULL) + slot->pdata->set_bus_mode(mmc_dev(mmc), slot->id, + ios->bus_mode); + slot->bus_mode = ios->bus_mode; + } /* On insanely high arm_per frequencies something sometimes * goes somehow out of sync, and the POW bit is not being set, @@ -1091,6 +1074,7 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) * Writing to the CON register twice seems to do the trick. */ for (i = 0; i < 2; i++) OMAP_MMC_WRITE(host, CON, dsor); + slot->saved_con = dsor; if (ios->power_mode == MMC_POWER_ON) { /* Send clock cycles, poll completion */ OMAP_MMC_WRITE(host, IE, 0); @@ -1099,7 +1083,9 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) while ((OMAP_MMC_READ(host, STAT) & 1) == 0); OMAP_MMC_WRITE(host, STAT, 1); } - clk_disable(host->fclk); + +exit: + mmc_omap_release_slot(slot); } static const struct mmc_host_ops mmc_omap_ops = { From 2a50b8889b707b9c7bcd09c0ae4cbcef7dca4e29 Mon Sep 17 00:00:00 2001 From: Juha Yrjola Date: Wed, 26 Mar 2008 16:09:26 -0400 Subject: [PATCH 14/26] MMC: OMAP: General cleanup for MMC multislot support General code cleanup, modifications at some dev_* functions and other hacks at mmc_omap_irq() for MMC multislot support. Signed-off-by: Juha Yrjola Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 46 ++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index c80cd58cfa07..535499f11e7c 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -547,11 +547,12 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) u16 status; int end_command; int end_transfer; - int transfer_error; + int transfer_error, cmd_error; if (host->cmd == NULL && host->data == NULL) { status = OMAP_MMC_READ(host, STAT); - dev_info(mmc_dev(host->mmc),"spurious irq 0x%04x\n", status); + dev_info(mmc_dev(host->slots[0]->mmc), + "Spurious IRQ 0x%04x\n", status); if (status != 0) { OMAP_MMC_WRITE(host, STAT, status); OMAP_MMC_WRITE(host, IE, 0); @@ -562,12 +563,19 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) end_command = 0; end_transfer = 0; transfer_error = 0; + cmd_error = 0; while ((status = OMAP_MMC_READ(host, STAT)) != 0) { + int cmd; + OMAP_MMC_WRITE(host, STAT, status); + if (host->cmd != NULL) + cmd = host->cmd->opcode; + else + cmd = -1; #ifdef CONFIG_MMC_DEBUG dev_dbg(mmc_dev(host->mmc), "MMC IRQ %04x (CMD %d): ", - status, host->cmd != NULL ? host->cmd->opcode : -1); + status, cmd); mmc_omap_report_irq(status); printk("\n"); #endif @@ -579,12 +587,12 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) mmc_omap_xfer_data(host, 1); } - if (status & OMAP_MMC_STAT_END_OF_DATA) { + if (status & OMAP_MMC_STAT_END_OF_DATA) end_transfer = 1; - } if (status & OMAP_MMC_STAT_DATA_TOUT) { - dev_dbg(mmc_dev(host->mmc), "data timeout\n"); + dev_dbg(mmc_dev(host->mmc), "data timeout (CMD%d)\n", + cmd); if (host->data) { host->data->error = -ETIMEDOUT; transfer_error = 1; @@ -608,12 +616,14 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (host->cmd) { struct mmc_omap_slot *slot = host->current_slot; - if (!mmc_omap_cover_is_open(slot)) + if (slot == NULL || + !mmc_omap_cover_is_open(slot)) dev_err(mmc_dev(host->mmc), - "command timeout, CMD %d\n", - host->cmd->opcode); + "command timeout (CMD%d)\n", + cmd); host->cmd->error = -ETIMEDOUT; end_command = 1; + cmd_error = 1; } } @@ -621,9 +631,10 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (host->cmd) { dev_err(mmc_dev(host->mmc), "command CRC error (CMD%d, arg 0x%08x)\n", - host->cmd->opcode, host->cmd->arg); + cmd, host->cmd->arg); host->cmd->error = -EILSEQ; end_command = 1; + cmd_error = 1; } else dev_err(mmc_dev(host->mmc), "command CRC error without cmd?\n"); @@ -632,13 +643,13 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (status & OMAP_MMC_STAT_CARD_ERR) { dev_dbg(mmc_dev(host->mmc), "ignoring card status error (CMD%d)\n", - host->cmd->opcode); + cmd); end_command = 1; } /* * NOTE: On 1610 the END_OF_CMD may come too early when - * starting a write + * starting a write */ if ((status & OMAP_MMC_STAT_END_OF_CMD) && (!(status & OMAP_MMC_STAT_A_EMPTY))) { @@ -646,13 +657,14 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) } } - if (end_command) { + if (end_command) mmc_omap_cmd_done(host, host->cmd); + if (host->data != NULL) { + if (transfer_error) + mmc_omap_xfer_done(host, host->data); + else if (end_transfer) + mmc_omap_end_of_data(host, host->data); } - if (transfer_error) - mmc_omap_xfer_done(host, host->data); - else if (end_transfer) - mmc_omap_end_of_data(host, host->data); return IRQ_HANDLED; } From eb1860bccd01a75b20fd7298af89b9cbda2202ba Mon Sep 17 00:00:00 2001 From: Jarkko Lavinen Date: Wed, 26 Mar 2008 16:09:29 -0400 Subject: [PATCH 15/26] MMC: OMAP: Abort stuck commands When a card is removed while it is being accessed, a command can get stuck so that no timeout or end of command interrupt ever occurs. The command getting stuck is almost always CDM12, but also the other commands can get stuck. Catch a stuck command with a timer and try sending the initialization stream until the controller starts running again and responds with the end of command status. Signed-off-by: Jarkko Lavinen Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 93 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 535499f11e7c..a1dfa74c50ab 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -134,6 +134,9 @@ struct mmc_omap_host { unsigned char bus_mode; unsigned char hw_bus_mode; + struct work_struct cmd_abort; + struct timer_list cmd_timer; + unsigned int sg_len; int sg_idx; u16 * buffer; @@ -314,6 +317,8 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) if (host->data && !(host->data->flags & MMC_DATA_WRITE)) cmdreg |= 1 << 15; + mod_timer(&host->cmd_timer, jiffies + HZ/2); + OMAP_MMC_WRITE(host, CTO, 200); OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff); OMAP_MMC_WRITE(host, ARGH, cmd->arg >> 16); @@ -372,10 +377,38 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) mmc_omap_start_command(host, data->stop); } +static void +mmc_omap_send_abort(struct mmc_omap_host *host) +{ + struct mmc_omap_slot *slot = host->current_slot; + unsigned int restarts, passes, timeout; + u16 stat = 0; + + /* Sending abort takes 80 clocks. Have some extra and round up */ + timeout = (120*1000000 + slot->fclk_freq - 1)/slot->fclk_freq; + restarts = 0; + while (restarts < 10000) { + OMAP_MMC_WRITE(host, STAT, 0xFFFF); + OMAP_MMC_WRITE(host, CMD, (3 << 12) | (1 << 7)); + + passes = 0; + while (passes < timeout) { + stat = OMAP_MMC_READ(host, STAT); + if (stat & OMAP_MMC_STAT_END_OF_CMD) + goto out; + udelay(1); + passes++; + } + + restarts++; + } +out: + OMAP_MMC_WRITE(host, STAT, stat); +} + static void mmc_omap_abort_xfer(struct mmc_omap_host *host, struct mmc_data *data) { - int loops; u16 ie; if (host->dma_in_use) @@ -386,16 +419,8 @@ mmc_omap_abort_xfer(struct mmc_omap_host *host, struct mmc_data *data) ie = OMAP_MMC_READ(host, IE); OMAP_MMC_WRITE(host, IE, 0); - OMAP_MMC_WRITE(host, CMD, 1 << 7); - loops = 0; - while (!(OMAP_MMC_READ(host, STAT) & OMAP_MMC_STAT_END_OF_CMD)) { - udelay(1); - loops++; - if (loops == 100000) - break; - } - OMAP_MMC_WRITE(host, STAT, OMAP_MMC_STAT_END_OF_CMD); OMAP_MMC_WRITE(host, IE, ie); + mmc_omap_send_abort(host); } static void @@ -451,6 +476,8 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) { host->cmd = NULL; + del_timer(&host->cmd_timer); + if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { /* response type 2 */ @@ -486,6 +513,47 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) } } +/* + * Abort stuck command. Can occur when card is removed while it is being + * read. + */ +static void mmc_omap_abort_command(struct work_struct *work) +{ + struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, + cmd_abort); + u16 ie; + + ie = OMAP_MMC_READ(host, IE); + OMAP_MMC_WRITE(host, IE, 0); + + if (!host->cmd) { + OMAP_MMC_WRITE(host, IE, ie); + return; + } + + dev_dbg(mmc_dev(host->mmc), "Aborting stuck command CMD%d\n", + host->cmd->opcode); + + if (host->data && host->dma_in_use) + mmc_omap_release_dma(host, host->data, 1); + + host->data = NULL; + host->sg_len = 0; + + mmc_omap_send_abort(host); + host->cmd->error = -ETIMEDOUT; + mmc_omap_cmd_done(host, host->cmd); + OMAP_MMC_WRITE(host, IE, ie); +} + +static void +mmc_omap_cmd_timer(unsigned long data) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) data; + + schedule_work(&host->cmd_abort); +} + /* PIO only */ static void mmc_omap_sg_to_buf(struct mmc_omap_host *host) @@ -1233,6 +1301,11 @@ static int __init mmc_omap_probe(struct platform_device *pdev) goto err_free_mem_region; } + INIT_WORK(&host->cmd_abort, mmc_omap_abort_command); + init_timer(&host->cmd_timer); + host->cmd_timer.function = mmc_omap_cmd_timer; + host->cmd_timer.data = (unsigned long) host; + spin_lock_init(&host->dma_lock); init_timer(&host->dma_timer); spin_lock_init(&host->slot_lock); From 01e77e13fc5a1e6c00ebb4f688293fa3116315ee Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Aguiar Date: Wed, 26 Mar 2008 16:09:34 -0400 Subject: [PATCH 16/26] MMC: OMAP: Using setup_timer instead of init_timer Using setup_timer() instead of init_timer() on omap.c file. Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index a1dfa74c50ab..9075838f712f 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1235,9 +1235,8 @@ static int __init mmc_omap_new_slot(struct mmc_omap_host *host, int id) goto err_remove_slot_name; INIT_WORK(&slot->switch_work, mmc_omap_cover_handler); - init_timer(&slot->switch_timer); - slot->switch_timer.function = mmc_omap_switch_timer; - slot->switch_timer.data = (unsigned long) slot; + setup_timer(&slot->switch_timer, mmc_omap_switch_timer, + (unsigned long) slot); schedule_work(&slot->switch_work); } @@ -1302,18 +1301,13 @@ static int __init mmc_omap_probe(struct platform_device *pdev) } INIT_WORK(&host->cmd_abort, mmc_omap_abort_command); - init_timer(&host->cmd_timer); - host->cmd_timer.function = mmc_omap_cmd_timer; - host->cmd_timer.data = (unsigned long) host; + setup_timer(&host->cmd_timer, mmc_omap_cmd_timer, (unsigned long) host); spin_lock_init(&host->dma_lock); - init_timer(&host->dma_timer); + setup_timer(&host->dma_timer, mmc_omap_dma_timer, (unsigned long) host); spin_lock_init(&host->slot_lock); init_waitqueue_head(&host->slot_wq); - host->dma_timer.function = mmc_omap_dma_timer; - host->dma_timer.data = (unsigned long) host; - host->pdata = pdata; host->dev = &pdev->dev; platform_set_drvdata(pdev, host); From 8348f0029d85828671e3a1d11db41fe53afbdc0d Mon Sep 17 00:00:00 2001 From: Kyungmin Park Date: Wed, 26 Mar 2008 16:09:38 -0400 Subject: [PATCH 17/26] MMC: OMAP: Check the get_cover_state function pointer if not set If the get_cover_state is not set, it occurs the oops. Signed-off-by: Kyungmin Park Signed-off-by: Tony Lindgren Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 9075838f712f..f7fb97802827 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -236,7 +236,10 @@ static void mmc_omap_release_slot(struct mmc_omap_slot *slot) static inline int mmc_omap_cover_is_open(struct mmc_omap_slot *slot) { - return slot->pdata->get_cover_state(mmc_dev(slot->mmc), slot->id); + if (slot->pdata->get_cover_state) + return slot->pdata->get_cover_state(mmc_dev(slot->mmc), + slot->id); + return 0; } static ssize_t From 7584d276d47a55afaeb614ed16cf306cbe2d6117 Mon Sep 17 00:00:00 2001 From: Jarkko Lavinen Date: Wed, 26 Mar 2008 16:09:42 -0400 Subject: [PATCH 18/26] MMC: OMAP: Use tasklet instead of workqueue for cover switch notification The cover waitqueue is occasionally scheduled twice from timer and the interrupt and oops follows. It would have been possible to fix this problem with spinlocks but using tasklet was a dropin solution with no need for locking. This path also adds some cleanups. Signed-off-by: Jarkko Lavinen Signed-off-by: Hiroshi DOYU Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 75 ++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index f7fb97802827..8f393e8ed42f 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -94,7 +94,7 @@ /* Specifies how often in millisecs to poll for card status changes * when the cover switch is open */ -#define OMAP_MMC_SWITCH_POLL_DELAY 500 +#define OMAP_MMC_COVER_POLL_DELAY 500 struct mmc_omap_host; @@ -106,8 +106,8 @@ struct mmc_omap_slot { unsigned int fclk_freq; unsigned powered:1; - struct work_struct switch_work; - struct timer_list switch_timer; + struct tasklet_struct cover_tasklet; + struct timer_list cover_timer; unsigned cover_open; struct mmc_request *mrq; @@ -740,40 +740,51 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) return IRQ_HANDLED; } -void omap_mmc_notify_cover_event(struct device *dev, int slot, int is_closed) +void omap_mmc_notify_cover_event(struct device *dev, int num, int is_closed) { + int cover_open; struct mmc_omap_host *host = dev_get_drvdata(dev); + struct mmc_omap_slot *slot = host->slots[num]; - BUG_ON(slot >= host->nr_slots); + BUG_ON(num >= host->nr_slots); /* Other subsystems can call in here before we're initialised. */ - if (host->nr_slots == 0 || !host->slots[slot]) + if (host->nr_slots == 0 || !host->slots[num]) return; - schedule_work(&host->slots[slot]->switch_work); -} - -static void mmc_omap_switch_timer(unsigned long arg) -{ - struct mmc_omap_slot *slot = (struct mmc_omap_slot *) arg; - - schedule_work(&slot->switch_work); -} - -static void mmc_omap_cover_handler(struct work_struct *work) -{ - struct mmc_omap_slot *slot = container_of(work, struct mmc_omap_slot, - switch_work); - int cover_open; - cover_open = mmc_omap_cover_is_open(slot); if (cover_open != slot->cover_open) { - sysfs_notify(&slot->mmc->class_dev.kobj, NULL, "cover_switch"); slot->cover_open = cover_open; - dev_info(mmc_dev(slot->mmc), "cover is now %s\n", - cover_open ? "open" : "closed"); + sysfs_notify(&slot->mmc->class_dev.kobj, NULL, "cover_switch"); } - mmc_detect_change(slot->mmc, slot->id); + + tasklet_hi_schedule(&slot->cover_tasklet); +} + +static void mmc_omap_cover_timer(unsigned long arg) +{ + struct mmc_omap_slot *slot = (struct mmc_omap_slot *) arg; + tasklet_schedule(&slot->cover_tasklet); +} + +static void mmc_omap_cover_handler(unsigned long param) +{ + struct mmc_omap_slot *slot = (struct mmc_omap_slot *)param; + int cover_open = mmc_omap_cover_is_open(slot); + + mmc_detect_change(slot->mmc, 0); + if (!cover_open) + return; + + /* + * If no card is inserted, we postpone polling until + * the cover has been closed. + */ + if (slot->mmc->card == NULL || !mmc_card_present(slot->mmc->card)) + return; + + mod_timer(&slot->cover_timer, + jiffies + msecs_to_jiffies(OMAP_MMC_COVER_POLL_DELAY)); } /* Prepare to transfer the next segment of a scatterlist */ @@ -1237,10 +1248,11 @@ static int __init mmc_omap_new_slot(struct mmc_omap_host *host, int id) if (r < 0) goto err_remove_slot_name; - INIT_WORK(&slot->switch_work, mmc_omap_cover_handler); - setup_timer(&slot->switch_timer, mmc_omap_switch_timer, - (unsigned long) slot); - schedule_work(&slot->switch_work); + setup_timer(&slot->cover_timer, mmc_omap_cover_timer, + (unsigned long)slot); + tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler, + (unsigned long)slot); + tasklet_schedule(&slot->cover_tasklet); } return 0; @@ -1263,7 +1275,8 @@ static void mmc_omap_remove_slot(struct mmc_omap_slot *slot) if (slot->pdata->get_cover_state != NULL) device_remove_file(&mmc->class_dev, &dev_attr_cover_switch); - del_timer_sync(&slot->switch_timer); + tasklet_kill(&slot->cover_tasklet); + del_timer_sync(&slot->cover_timer); flush_scheduled_work(); mmc_remove_host(mmc); From 0fb4723d405111a13bb8f04e902eadf14402c7ba Mon Sep 17 00:00:00 2001 From: Jarkko Lavinen Date: Wed, 26 Mar 2008 16:09:48 -0400 Subject: [PATCH 19/26] MMC: OMAP: Move failing command abortion to workqueue Abort failed command from workqueue rather than from an interrupt, allowing longer delays in abortion. Signed-off-by: Jarkko Lavinen Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 82 ++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 8f393e8ed42f..7cc104ce0f07 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -134,8 +134,9 @@ struct mmc_omap_host { unsigned char bus_mode; unsigned char hw_bus_mode; - struct work_struct cmd_abort; - struct timer_list cmd_timer; + struct work_struct cmd_abort_work; + unsigned abort:1; + struct timer_list cmd_abort_timer; unsigned int sg_len; int sg_idx; @@ -320,7 +321,7 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) if (host->data && !(host->data->flags & MMC_DATA_WRITE)) cmdreg |= 1 << 15; - mod_timer(&host->cmd_timer, jiffies + HZ/2); + mod_timer(&host->cmd_abort_timer, jiffies + HZ/2); OMAP_MMC_WRITE(host, CTO, 200); OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff); @@ -381,7 +382,7 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) } static void -mmc_omap_send_abort(struct mmc_omap_host *host) +mmc_omap_send_abort(struct mmc_omap_host *host, int maxloops) { struct mmc_omap_slot *slot = host->current_slot; unsigned int restarts, passes, timeout; @@ -390,7 +391,7 @@ mmc_omap_send_abort(struct mmc_omap_host *host) /* Sending abort takes 80 clocks. Have some extra and round up */ timeout = (120*1000000 + slot->fclk_freq - 1)/slot->fclk_freq; restarts = 0; - while (restarts < 10000) { + while (restarts < maxloops) { OMAP_MMC_WRITE(host, STAT, 0xFFFF); OMAP_MMC_WRITE(host, CMD, (3 << 12) | (1 << 7)); @@ -412,18 +413,13 @@ out: static void mmc_omap_abort_xfer(struct mmc_omap_host *host, struct mmc_data *data) { - u16 ie; - if (host->dma_in_use) mmc_omap_release_dma(host, data, 1); host->data = NULL; host->sg_len = 0; - ie = OMAP_MMC_READ(host, IE); - OMAP_MMC_WRITE(host, IE, 0); - OMAP_MMC_WRITE(host, IE, ie); - mmc_omap_send_abort(host); + mmc_omap_send_abort(host, 10000); } static void @@ -479,7 +475,7 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) { host->cmd = NULL; - del_timer(&host->cmd_timer); + del_timer(&host->cmd_abort_timer); if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { @@ -523,38 +519,48 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) static void mmc_omap_abort_command(struct work_struct *work) { struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, - cmd_abort); - u16 ie; - - ie = OMAP_MMC_READ(host, IE); - OMAP_MMC_WRITE(host, IE, 0); - - if (!host->cmd) { - OMAP_MMC_WRITE(host, IE, ie); - return; - } + cmd_abort_work); + BUG_ON(!host->cmd); dev_dbg(mmc_dev(host->mmc), "Aborting stuck command CMD%d\n", host->cmd->opcode); - if (host->data && host->dma_in_use) - mmc_omap_release_dma(host, host->data, 1); + if (host->cmd->error == 0) + host->cmd->error = -ETIMEDOUT; - host->data = NULL; - host->sg_len = 0; + if (host->data == NULL) { + struct mmc_command *cmd; + struct mmc_host *mmc; - mmc_omap_send_abort(host); - host->cmd->error = -ETIMEDOUT; - mmc_omap_cmd_done(host, host->cmd); - OMAP_MMC_WRITE(host, IE, ie); + cmd = host->cmd; + host->cmd = NULL; + mmc_omap_send_abort(host, 10000); + + host->mrq = NULL; + mmc = host->mmc; + mmc_omap_release_slot(host->current_slot); + mmc_request_done(mmc, cmd->mrq); + } else + mmc_omap_cmd_done(host, host->cmd); + + host->abort = 0; + enable_irq(host->irq); } static void mmc_omap_cmd_timer(unsigned long data) { struct mmc_omap_host *host = (struct mmc_omap_host *) data; + unsigned long flags; - schedule_work(&host->cmd_abort); + spin_lock_irqsave(&host->slot_lock, flags); + if (host->cmd != NULL && !host->abort) { + OMAP_MMC_WRITE(host, IE, 0); + disable_irq(host->irq); + host->abort = 1; + schedule_work(&host->cmd_abort_work); + } + spin_unlock_irqrestore(&host->slot_lock, flags); } /* PIO only */ @@ -728,6 +734,15 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) } } + if (cmd_error && host->data) { + del_timer(&host->cmd_abort_timer); + host->abort = 1; + OMAP_MMC_WRITE(host, IE, 0); + disable_irq(host->irq); + schedule_work(&host->cmd_abort_work); + return IRQ_HANDLED; + } + if (end_command) mmc_omap_cmd_done(host, host->cmd); if (host->data != NULL) { @@ -1316,8 +1331,9 @@ static int __init mmc_omap_probe(struct platform_device *pdev) goto err_free_mem_region; } - INIT_WORK(&host->cmd_abort, mmc_omap_abort_command); - setup_timer(&host->cmd_timer, mmc_omap_cmd_timer, (unsigned long) host); + INIT_WORK(&host->cmd_abort_work, mmc_omap_abort_command); + setup_timer(&host->cmd_abort_timer, mmc_omap_cmd_timer, + (unsigned long) host); spin_lock_init(&host->dma_lock); setup_timer(&host->dma_timer, mmc_omap_dma_timer, (unsigned long) host); From 0807a9b5739a73ba0d0fcd9f36a51794757be881 Mon Sep 17 00:00:00 2001 From: Jarkko Lavinen Date: Wed, 26 Mar 2008 16:09:52 -0400 Subject: [PATCH 20/26] MMC: OMAP: Lazy clock shutdown MMCA spec says the mmc clock should be kept running for at least 8 cycles after the last RW request. Ensure this with lazy clock disable after a request, or with an explicit delay before switching a slot. Signed-off-by: Jarkko Lavinen Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 89 ++++++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 7cc104ce0f07..3d59c5d81b3d 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -161,9 +161,38 @@ struct mmc_omap_host { wait_queue_head_t slot_wq; int nr_slots; + struct timer_list clk_timer; + spinlock_t clk_lock; /* for changing enabled state */ + unsigned int fclk_enabled:1; + struct omap_mmc_platform_data *pdata; }; +void mmc_omap_fclk_offdelay(struct mmc_omap_slot *slot) +{ + unsigned long tick_ns; + + if (slot != NULL && slot->host->fclk_enabled && slot->fclk_freq > 0) { + tick_ns = (1000000000 + slot->fclk_freq - 1) / slot->fclk_freq; + ndelay(8 * tick_ns); + } +} + +void mmc_omap_fclk_enable(struct mmc_omap_host *host, unsigned int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&host->clk_lock, flags); + if (host->fclk_enabled != enable) { + host->fclk_enabled = enable; + if (enable) + clk_enable(host->fclk); + else + clk_disable(host->fclk); + } + spin_unlock_irqrestore(&host->clk_lock, flags); +} + static void mmc_omap_select_slot(struct mmc_omap_slot *slot, int claimed) { struct mmc_omap_host *host = slot->host; @@ -180,32 +209,49 @@ static void mmc_omap_select_slot(struct mmc_omap_slot *slot, int claimed) host->mmc = slot->mmc; spin_unlock_irqrestore(&host->slot_lock, flags); no_claim: - clk_enable(host->fclk); + del_timer(&host->clk_timer); + if (host->current_slot != slot || !claimed) + mmc_omap_fclk_offdelay(host->current_slot); + if (host->current_slot != slot) { + OMAP_MMC_WRITE(host, CON, slot->saved_con & 0xFC00); if (host->pdata->switch_slot != NULL) host->pdata->switch_slot(mmc_dev(slot->mmc), slot->id); host->current_slot = slot; } - /* Doing the dummy read here seems to work around some bug - * at least in OMAP24xx silicon where the command would not - * start after writing the CMD register. Sigh. */ - OMAP_MMC_READ(host, CON); + if (claimed) { + mmc_omap_fclk_enable(host, 1); - OMAP_MMC_WRITE(host, CON, slot->saved_con); + /* Doing the dummy read here seems to work around some bug + * at least in OMAP24xx silicon where the command would not + * start after writing the CMD register. Sigh. */ + OMAP_MMC_READ(host, CON); + + OMAP_MMC_WRITE(host, CON, slot->saved_con); + } else + mmc_omap_fclk_enable(host, 0); } static void mmc_omap_start_request(struct mmc_omap_host *host, struct mmc_request *req); -static void mmc_omap_release_slot(struct mmc_omap_slot *slot) +static void mmc_omap_release_slot(struct mmc_omap_slot *slot, int clk_enabled) { struct mmc_omap_host *host = slot->host; unsigned long flags; int i; BUG_ON(slot == NULL || host->mmc == NULL); - clk_disable(host->fclk); + + if (clk_enabled) + /* Keeps clock running for at least 8 cycles on valid freq */ + mod_timer(&host->clk_timer, jiffies + HZ/10); + else { + del_timer(&host->clk_timer); + mmc_omap_fclk_offdelay(slot); + mmc_omap_fclk_enable(host, 0); + } spin_lock_irqsave(&host->slot_lock, flags); /* Check for any pending requests */ @@ -373,7 +419,7 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) host->mrq = NULL; mmc = host->mmc; - mmc_omap_release_slot(host->current_slot); + mmc_omap_release_slot(host->current_slot, 1); mmc_request_done(mmc, data->mrq); return; } @@ -507,7 +553,7 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) mmc_omap_abort_xfer(host, host->data); host->mrq = NULL; mmc = host->mmc; - mmc_omap_release_slot(host->current_slot); + mmc_omap_release_slot(host->current_slot, 1); mmc_request_done(mmc, cmd->mrq); } } @@ -538,7 +584,7 @@ static void mmc_omap_abort_command(struct work_struct *work) host->mrq = NULL; mmc = host->mmc; - mmc_omap_release_slot(host->current_slot); + mmc_omap_release_slot(host->current_slot, 1); mmc_request_done(mmc, cmd->mrq); } else mmc_omap_cmd_done(host, host->cmd); @@ -576,6 +622,14 @@ mmc_omap_sg_to_buf(struct mmc_omap_host *host) host->buffer_bytes_left = host->total_bytes_left; } +static void +mmc_omap_clk_timer(unsigned long data) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) data; + + mmc_omap_fclk_enable(host, 0); +} + /* PIO only */ static void mmc_omap_xfer_data(struct mmc_omap_host *host, int write) @@ -1149,14 +1203,16 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) struct mmc_omap_slot *slot = mmc_priv(mmc); struct mmc_omap_host *host = slot->host; int i, dsor; - - dsor = mmc_omap_calc_divisor(mmc, ios); + int clk_enabled; mmc_omap_select_slot(slot, 0); + dsor = mmc_omap_calc_divisor(mmc, ios); + if (ios->vdd != slot->vdd) slot->vdd = ios->vdd; + clk_enabled = 0; switch (ios->power_mode) { case MMC_POWER_OFF: mmc_omap_set_power(slot, 0, ios->vdd); @@ -1166,6 +1222,8 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) mmc_omap_set_power(slot, 1, ios->vdd); goto exit; case MMC_POWER_ON: + mmc_omap_fclk_enable(host, 1); + clk_enabled = 1; dsor |= 1 << 11; break; } @@ -1194,7 +1252,7 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) } exit: - mmc_omap_release_slot(slot); + mmc_omap_release_slot(slot, clk_enabled); } static const struct mmc_host_ops mmc_omap_ops = { @@ -1335,6 +1393,9 @@ static int __init mmc_omap_probe(struct platform_device *pdev) setup_timer(&host->cmd_abort_timer, mmc_omap_cmd_timer, (unsigned long) host); + spin_lock_init(&host->clk_lock); + setup_timer(&host->clk_timer, mmc_omap_clk_timer, (unsigned long) host); + spin_lock_init(&host->dma_lock); setup_timer(&host->dma_timer, mmc_omap_dma_timer, (unsigned long) host); spin_lock_init(&host->slot_lock); From 0f602ec79ac4fd2a42075c5a170086ded439f36d Mon Sep 17 00:00:00 2001 From: Jarkko Lavinen Date: Wed, 26 Mar 2008 16:09:58 -0400 Subject: [PATCH 21/26] MMC: OMAP: Start new commands from work queue instead of irq Use work queues for starting new commands instead of starting them directly from irq handler. The command scheduling needs to be delayed a bit for some cards which should not be done from an interrupt. Signed-off-by: Jarkko Lavinen Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 48 +++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 3d59c5d81b3d..ab0974d261e5 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -138,6 +138,11 @@ struct mmc_omap_host { unsigned abort:1; struct timer_list cmd_abort_timer; + struct work_struct slot_release_work; + struct mmc_omap_slot *next_slot; + struct work_struct send_stop_work; + struct mmc_data *stop_data; + unsigned int sg_len; int sg_idx; u16 * buffer; @@ -236,6 +241,21 @@ no_claim: static void mmc_omap_start_request(struct mmc_omap_host *host, struct mmc_request *req); +static void mmc_omap_slot_release_work(struct work_struct *work) +{ + struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, + slot_release_work); + struct mmc_omap_slot *next_slot = host->next_slot; + struct mmc_request *rq; + + host->next_slot = NULL; + mmc_omap_select_slot(next_slot, 1); + + rq = next_slot->mrq; + next_slot->mrq = NULL; + mmc_omap_start_request(host, rq); +} + static void mmc_omap_release_slot(struct mmc_omap_slot *slot, int clk_enabled) { struct mmc_omap_host *host = slot->host; @@ -257,21 +277,19 @@ static void mmc_omap_release_slot(struct mmc_omap_slot *slot, int clk_enabled) /* Check for any pending requests */ for (i = 0; i < host->nr_slots; i++) { struct mmc_omap_slot *new_slot; - struct mmc_request *rq; if (host->slots[i] == NULL || host->slots[i]->mrq == NULL) continue; + BUG_ON(host->next_slot != NULL); new_slot = host->slots[i]; /* The current slot should not have a request in queue */ BUG_ON(new_slot == host->current_slot); + host->next_slot = new_slot; host->mmc = new_slot->mmc; spin_unlock_irqrestore(&host->slot_lock, flags); - mmc_omap_select_slot(new_slot, 1); - rq = new_slot->mrq; - new_slot->mrq = NULL; - mmc_omap_start_request(host, rq); + schedule_work(&host->slot_release_work); return; } @@ -400,6 +418,20 @@ mmc_omap_release_dma(struct mmc_omap_host *host, struct mmc_data *data, dma_data_dir); } +static void mmc_omap_send_stop_work(struct work_struct *work) +{ + struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, + send_stop_work); + struct mmc_omap_slot *slot = host->current_slot; + struct mmc_data *data = host->stop_data; + unsigned long tick_ns; + + tick_ns = (1000000000 + slot->fclk_freq - 1)/slot->fclk_freq; + ndelay(8*tick_ns); + + mmc_omap_start_command(host, data->stop); +} + static void mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) { @@ -424,7 +456,8 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) return; } - mmc_omap_start_command(host, data->stop); + host->stop_data = data; + schedule_work(&host->send_stop_work); } static void @@ -1389,6 +1422,9 @@ static int __init mmc_omap_probe(struct platform_device *pdev) goto err_free_mem_region; } + INIT_WORK(&host->slot_release_work, mmc_omap_slot_release_work); + INIT_WORK(&host->send_stop_work, mmc_omap_send_stop_work); + INIT_WORK(&host->cmd_abort_work, mmc_omap_abort_command); setup_timer(&host->cmd_abort_timer, mmc_omap_cmd_timer, (unsigned long) host); From 9d7c6eee523c78b6ea4b56bd3927860d850616e5 Mon Sep 17 00:00:00 2001 From: Jarkko Lavinen Date: Wed, 26 Mar 2008 16:10:02 -0400 Subject: [PATCH 22/26] MMC: OMAP: Do not busy wait for end of command for ever The limit was a fixed 100k limit in the busy loop, which is not accurate. It would better to have time limit for the worst case which occurs when sending 80 cycles at 400 kHz and takes about 200 microseconds, so limit the max time spend in the busy loop for some 250 microseconds. Signed-off-by: Jarkko Lavinen Signed-off-by: Pierre Ossman --- drivers/mmc/host/omap.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index ab0974d261e5..14759e9f42ad 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1276,11 +1276,17 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) OMAP_MMC_WRITE(host, CON, dsor); slot->saved_con = dsor; if (ios->power_mode == MMC_POWER_ON) { + /* worst case at 400kHz, 80 cycles makes 200 microsecs */ + int usecs = 250; + /* Send clock cycles, poll completion */ OMAP_MMC_WRITE(host, IE, 0); OMAP_MMC_WRITE(host, STAT, 0xffff); OMAP_MMC_WRITE(host, CMD, 1 << 7); - while ((OMAP_MMC_READ(host, STAT) & 1) == 0); + while (usecs > 0 && (OMAP_MMC_READ(host, STAT) & 1) == 0) { + udelay(1); + usecs--; + } OMAP_MMC_WRITE(host, STAT, 1); } From 0b82684c3c19aff092bb303959a31dbe5c965922 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 13 Apr 2008 16:03:38 +0200 Subject: [PATCH 23/26] sdhci: improve no card, no reset quirk The quirk was meant to just inhibit some resets, but ended up blocking all of them. Fortunately, this was just what was needed. Change the comment to reflect reality. Also, this issue has just been observed on Samsung laptops, so reduce the number of chips the quirk affects. Signed-off-by: Pierre Ossman --- drivers/mmc/host/sdhci.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 6250eb5f98a8..07c2048b230b 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -41,7 +41,7 @@ static unsigned int debug_quirks = 0; #define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0) /* Controller has bad caps bits, but really supports DMA */ #define SDHCI_QUIRK_FORCE_DMA (1<<1) -/* Controller doesn't like some resets when there is no card inserted. */ +/* Controller doesn't like to be reset when there is no card inserted. */ #define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<2) /* Controller doesn't like clearing the power reg before a change */ #define SDHCI_QUIRK_SINGLE_POWER_WRITE (1<<3) @@ -69,12 +69,20 @@ static const struct pci_device_id pci_ids[] __devinitdata = { { .vendor = PCI_VENDOR_ID_RICOH, .device = PCI_DEVICE_ID_RICOH_R5C822, - .subvendor = PCI_ANY_ID, + .subvendor = PCI_VENDOR_ID_SAMSUNG, .subdevice = PCI_ANY_ID, .driver_data = SDHCI_QUIRK_FORCE_DMA | SDHCI_QUIRK_NO_CARD_NO_RESET, }, + { + .vendor = PCI_VENDOR_ID_RICOH, + .device = PCI_DEVICE_ID_RICOH_R5C822, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = SDHCI_QUIRK_FORCE_DMA, + }, + { .vendor = PCI_VENDOR_ID_TI, .device = PCI_DEVICE_ID_TI_XX21_XX11_SD, From 261172fd1b23769bc7632047e2cb826c9b8b1a50 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Sun, 13 Apr 2008 21:15:47 +0300 Subject: [PATCH 24/26] mmc: make __mmc_release_bus() static This patch makes the needlessly global __mmc_release_bus() static. Signed-off-by: Adrian Bunk Signed-off-by: Pierre Ossman --- drivers/mmc/core/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index b96667448eb5..ab91b02b2bff 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -516,7 +516,7 @@ static void mmc_power_off(struct mmc_host *host) /* * Cleanup when the last reference to the bus operator is dropped. */ -void __mmc_release_bus(struct mmc_host *host) +static void __mmc_release_bus(struct mmc_host *host) { BUG_ON(!host); BUG_ON(host->bus_refs); From 98b843be56079ad26fe4b9e421fd11b0598b85f3 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Sun, 13 Apr 2008 21:15:50 +0300 Subject: [PATCH 25/26] mmc: proper prototypes for mmc_attach_*() This patch adds proper prototypes for mmc_attach_*() in drivers/mmc/core/core.h Signed-off-by: Adrian Bunk Signed-off-by: Pierre Ossman --- drivers/mmc/core/core.c | 4 ---- drivers/mmc/core/core.h | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index ab91b02b2bff..01ced4c5a61d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -35,10 +35,6 @@ #include "sd_ops.h" #include "sdio_ops.h" -extern int mmc_attach_mmc(struct mmc_host *host, u32 ocr); -extern int mmc_attach_sd(struct mmc_host *host, u32 ocr); -extern int mmc_attach_sdio(struct mmc_host *host, u32 ocr); - static struct workqueue_struct *workqueue; /* diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index cfa8e15b5923..cdb332b7dedc 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -46,6 +46,10 @@ void mmc_rescan(struct work_struct *work); void mmc_start_host(struct mmc_host *host); void mmc_stop_host(struct mmc_host *host); +int mmc_attach_mmc(struct mmc_host *host, u32 ocr); +int mmc_attach_sd(struct mmc_host *host, u32 ocr); +int mmc_attach_sdio(struct mmc_host *host, u32 ocr); + extern int use_spi_crc; #endif From e70aa3fac1ac50c7a75ac676a1489dd1ea3b4be5 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Sun, 13 Apr 2008 21:15:52 +0300 Subject: [PATCH 26/26] mmc: sdio_ops.c should #include "sdio_ops.h" Every file should include the headers containing the externs for its global functions. Signed-off-by: Adrian Bunk Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_ops.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index e1fca588e385..c8fa095a4488 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -17,6 +17,7 @@ #include #include "core.h" +#include "sdio_ops.h" int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) {