diff options
Diffstat (limited to 'drivers/mmc/host/sdhci-pci.c')
| -rw-r--r-- | drivers/mmc/host/sdhci-pci.c | 896 | 
1 files changed, 758 insertions, 138 deletions
diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 55746bac2f4..52c42fcc284 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -14,65 +14,21 @@  #include <linux/delay.h>  #include <linux/highmem.h> +#include <linux/module.h>  #include <linux/pci.h>  #include <linux/dma-mapping.h>  #include <linux/slab.h>  #include <linux/device.h> -  #include <linux/mmc/host.h> - -#include <asm/scatterlist.h> -#include <asm/io.h> +#include <linux/scatterlist.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/pm_runtime.h> +#include <linux/mmc/sdhci-pci-data.h>  #include "sdhci.h" - -/* - * PCI registers - */ - -#define PCI_SDHCI_IFPIO			0x00 -#define PCI_SDHCI_IFDMA			0x01 -#define PCI_SDHCI_IFVENDOR		0x02 - -#define PCI_SLOT_INFO			0x40	/* 8 bits */ -#define  PCI_SLOT_INFO_SLOTS(x)		((x >> 4) & 7) -#define  PCI_SLOT_INFO_FIRST_BAR_MASK	0x07 - -#define MAX_SLOTS			8 - -struct sdhci_pci_chip; -struct sdhci_pci_slot; - -struct sdhci_pci_fixes { -	unsigned int		quirks; - -	int			(*probe)(struct sdhci_pci_chip*); - -	int			(*probe_slot)(struct sdhci_pci_slot*); -	void			(*remove_slot)(struct sdhci_pci_slot*, int); - -	int			(*suspend)(struct sdhci_pci_chip*, -					pm_message_t); -	int			(*resume)(struct sdhci_pci_chip*); -}; - -struct sdhci_pci_slot { -	struct sdhci_pci_chip	*chip; -	struct sdhci_host	*host; - -	int			pci_bar; -}; - -struct sdhci_pci_chip { -	struct pci_dev		*pdev; - -	unsigned int		quirks; -	const struct sdhci_pci_fixes *fixes; - -	int			num_slots;	/* Slots on controller */ -	struct sdhci_pci_slot	*slots[MAX_SLOTS]; /* Pointers to host slots */ -}; - +#include "sdhci-pci.h" +#include "sdhci-pci-o2micro.h"  /*****************************************************************************\   *                                                                           * @@ -99,6 +55,7 @@ static int ricoh_mmc_probe_slot(struct sdhci_pci_slot *slot)  		SDHCI_TIMEOUT_CLK_UNIT |  		SDHCI_CAN_VDD_330 | +		SDHCI_CAN_DO_HISPD |  		SDHCI_CAN_DO_SDMA;  	return 0;  } @@ -142,40 +99,232 @@ static const struct sdhci_pci_fixes sdhci_ene_714 = {  static const struct sdhci_pci_fixes sdhci_cafe = {  	.quirks		= SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |  			  SDHCI_QUIRK_NO_BUSY_IRQ | +			  SDHCI_QUIRK_BROKEN_CARD_DETECTION |  			  SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,  }; +static int mrst_hc_probe_slot(struct sdhci_pci_slot *slot) +{ +	slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA; +	return 0; +} +  /*   * ADMA operation is disabled for Moorestown platform due to   * hardware bugs.   */ -static int mrst_hc1_probe(struct sdhci_pci_chip *chip) +static int mrst_hc_probe(struct sdhci_pci_chip *chip)  {  	/* -	 * slots number is fixed here for MRST as SDIO3 is never used and has -	 * hardware bugs. +	 * slots number is fixed here for MRST as SDIO3/5 are never used and +	 * have hardware bugs.  	 */  	chip->num_slots = 1;  	return 0;  } +static int pch_hc_probe_slot(struct sdhci_pci_slot *slot) +{ +	slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA; +	return 0; +} + +#ifdef CONFIG_PM_RUNTIME + +static irqreturn_t sdhci_pci_sd_cd(int irq, void *dev_id) +{ +	struct sdhci_pci_slot *slot = dev_id; +	struct sdhci_host *host = slot->host; + +	mmc_detect_change(host->mmc, msecs_to_jiffies(200)); +	return IRQ_HANDLED; +} + +static void sdhci_pci_add_own_cd(struct sdhci_pci_slot *slot) +{ +	int err, irq, gpio = slot->cd_gpio; + +	slot->cd_gpio = -EINVAL; +	slot->cd_irq = -EINVAL; + +	if (!gpio_is_valid(gpio)) +		return; + +	err = gpio_request(gpio, "sd_cd"); +	if (err < 0) +		goto out; + +	err = gpio_direction_input(gpio); +	if (err < 0) +		goto out_free; + +	irq = gpio_to_irq(gpio); +	if (irq < 0) +		goto out_free; + +	err = request_irq(irq, sdhci_pci_sd_cd, IRQF_TRIGGER_RISING | +			  IRQF_TRIGGER_FALLING, "sd_cd", slot); +	if (err) +		goto out_free; + +	slot->cd_gpio = gpio; +	slot->cd_irq = irq; + +	return; + +out_free: +	gpio_free(gpio); +out: +	dev_warn(&slot->chip->pdev->dev, "failed to setup card detect wake up\n"); +} + +static void sdhci_pci_remove_own_cd(struct sdhci_pci_slot *slot) +{ +	if (slot->cd_irq >= 0) +		free_irq(slot->cd_irq, slot); +	if (gpio_is_valid(slot->cd_gpio)) +		gpio_free(slot->cd_gpio); +} + +#else + +static inline void sdhci_pci_add_own_cd(struct sdhci_pci_slot *slot) +{ +} + +static inline void sdhci_pci_remove_own_cd(struct sdhci_pci_slot *slot) +{ +} + +#endif + +static int mfd_emmc_probe_slot(struct sdhci_pci_slot *slot) +{ +	slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE; +	slot->host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC | +				  MMC_CAP2_HC_ERASE_SZ; +	return 0; +} + +static int mfd_sdio_probe_slot(struct sdhci_pci_slot *slot) +{ +	slot->host->mmc->caps |= MMC_CAP_POWER_OFF_CARD | MMC_CAP_NONREMOVABLE; +	return 0; +} +  static const struct sdhci_pci_fixes sdhci_intel_mrst_hc0 = {  	.quirks		= SDHCI_QUIRK_BROKEN_ADMA | SDHCI_QUIRK_NO_HISPD_BIT, +	.probe_slot	= mrst_hc_probe_slot,  }; -static const struct sdhci_pci_fixes sdhci_intel_mrst_hc1 = { +static const struct sdhci_pci_fixes sdhci_intel_mrst_hc1_hc2 = {  	.quirks		= SDHCI_QUIRK_BROKEN_ADMA | SDHCI_QUIRK_NO_HISPD_BIT, -	.probe		= mrst_hc1_probe, +	.probe		= mrst_hc_probe,  };  static const struct sdhci_pci_fixes sdhci_intel_mfd_sd = {  	.quirks		= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, +	.allow_runtime_pm = true, +	.own_cd_for_runtime_pm = true, +}; + +static const struct sdhci_pci_fixes sdhci_intel_mfd_sdio = { +	.quirks		= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, +	.quirks2	= SDHCI_QUIRK2_HOST_OFF_CARD_ON, +	.allow_runtime_pm = true, +	.probe_slot	= mfd_sdio_probe_slot, +}; + +static const struct sdhci_pci_fixes sdhci_intel_mfd_emmc = { +	.quirks		= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, +	.allow_runtime_pm = true, +	.probe_slot	= mfd_emmc_probe_slot, +}; + +static const struct sdhci_pci_fixes sdhci_intel_pch_sdio = { +	.quirks		= SDHCI_QUIRK_BROKEN_ADMA, +	.probe_slot	= pch_hc_probe_slot,  }; -static const struct sdhci_pci_fixes sdhci_intel_mfd_emmc_sdio = { +static void sdhci_pci_int_hw_reset(struct sdhci_host *host) +{ +	u8 reg; + +	reg = sdhci_readb(host, SDHCI_POWER_CONTROL); +	reg |= 0x10; +	sdhci_writeb(host, reg, SDHCI_POWER_CONTROL); +	/* For eMMC, minimum is 1us but give it 9us for good measure */ +	udelay(9); +	reg &= ~0x10; +	sdhci_writeb(host, reg, SDHCI_POWER_CONTROL); +	/* For eMMC, minimum is 200us but give it 300us for good measure */ +	usleep_range(300, 1000); +} + +static int byt_emmc_probe_slot(struct sdhci_pci_slot *slot) +{ +	slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE | +				 MMC_CAP_HW_RESET; +	slot->host->mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ; +	slot->hw_reset = sdhci_pci_int_hw_reset; +	return 0; +} + +static int byt_sdio_probe_slot(struct sdhci_pci_slot *slot) +{ +	slot->host->mmc->caps |= MMC_CAP_POWER_OFF_CARD | MMC_CAP_NONREMOVABLE; +	return 0; +} + +static const struct sdhci_pci_fixes sdhci_intel_byt_emmc = { +	.allow_runtime_pm = true, +	.probe_slot	= byt_emmc_probe_slot, +}; + +static const struct sdhci_pci_fixes sdhci_intel_byt_sdio = { +	.quirks2	= SDHCI_QUIRK2_HOST_OFF_CARD_ON, +	.allow_runtime_pm = true, +	.probe_slot	= byt_sdio_probe_slot, +}; + +static const struct sdhci_pci_fixes sdhci_intel_byt_sd = { +	.quirks2	= SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON, +	.allow_runtime_pm = true, +	.own_cd_for_runtime_pm = true, +}; + +/* Define Host controllers for Intel Merrifield platform */ +#define INTEL_MRFL_EMMC_0	0 +#define INTEL_MRFL_EMMC_1	1 + +static int intel_mrfl_mmc_probe_slot(struct sdhci_pci_slot *slot) +{ +	if ((PCI_FUNC(slot->chip->pdev->devfn) != INTEL_MRFL_EMMC_0) && +	    (PCI_FUNC(slot->chip->pdev->devfn) != INTEL_MRFL_EMMC_1)) +		/* SD support is not ready yet */ +		return -ENODEV; + +	slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE | +				 MMC_CAP_1_8V_DDR; + +	return 0; +} + +static const struct sdhci_pci_fixes sdhci_intel_mrfl_mmc = {  	.quirks		= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, +	.quirks2	= SDHCI_QUIRK2_BROKEN_HS200, +	.probe_slot	= intel_mrfl_mmc_probe_slot,  }; +/* O2Micro extra registers */ +#define O2_SD_LOCK_WP		0xD3 +#define O2_SD_MULTI_VCC3V	0xEE +#define O2_SD_CLKREQ		0xEC +#define O2_SD_CAPS		0xE0 +#define O2_SD_ADMA1		0xE2 +#define O2_SD_ADMA2		0xE7 +#define O2_SD_INF_MOD		0xF1 +  static int jmicron_pmos(struct sdhci_pci_chip *chip, int on)  {  	u8 scratch; @@ -204,6 +353,7 @@ static int jmicron_pmos(struct sdhci_pci_chip *chip, int on)  static int jmicron_probe(struct sdhci_pci_chip *chip)  {  	int ret; +	u16 mmcdev = 0;  	if (chip->pdev->revision == 0) {  		chip->quirks |= SDHCI_QUIRK_32BIT_DMA_ADDR | @@ -225,12 +375,17 @@ static int jmicron_probe(struct sdhci_pci_chip *chip)  	 * 2. The MMC interface has a lower subfunction number  	 *    than the SD interface.  	 */ -	if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_SD) { +	if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_SD) +		mmcdev = PCI_DEVICE_ID_JMICRON_JMB38X_MMC; +	else if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_SD) +		mmcdev = PCI_DEVICE_ID_JMICRON_JMB388_ESD; + +	if (mmcdev) {  		struct pci_dev *sd_dev;  		sd_dev = NULL;  		while ((sd_dev = pci_get_device(PCI_VENDOR_ID_JMICRON, -			PCI_DEVICE_ID_JMICRON_JMB38X_MMC, sd_dev)) != NULL) { +						mmcdev, sd_dev)) != NULL) {  			if ((PCI_SLOT(chip->pdev->devfn) ==  				PCI_SLOT(sd_dev->devfn)) &&  				(chip->pdev->bus == sd_dev->bus)) @@ -255,6 +410,11 @@ static int jmicron_probe(struct sdhci_pci_chip *chip)  		return ret;  	} +	/* quirk for unsable RO-detection on JM388 chips */ +	if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_SD || +	    chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD) +		chip->quirks |= SDHCI_QUIRK_UNSTABLE_RO_DETECT; +  	return 0;  } @@ -290,13 +450,25 @@ static int jmicron_probe_slot(struct sdhci_pci_slot *slot)  			slot->host->quirks |= SDHCI_QUIRK_BROKEN_ADMA;  	} +	/* JM388 MMC doesn't support 1.8V while SD supports it */ +	if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD) { +		slot->host->ocr_avail_sd = MMC_VDD_32_33 | MMC_VDD_33_34 | +			MMC_VDD_29_30 | MMC_VDD_30_31 | +			MMC_VDD_165_195; /* allow 1.8V */ +		slot->host->ocr_avail_mmc = MMC_VDD_32_33 | MMC_VDD_33_34 | +			MMC_VDD_29_30 | MMC_VDD_30_31; /* no 1.8V for MMC */ +	} +  	/*  	 * The secondary interface requires a bit set to get the  	 * interrupts.  	 */ -	if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) +	if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC || +	    slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD)  		jmicron_enable_mmc(slot->host, 1); +	slot->host->mmc->caps |= MMC_CAP_BUS_WIDTH_TEST; +  	return 0;  } @@ -305,16 +477,18 @@ static void jmicron_remove_slot(struct sdhci_pci_slot *slot, int dead)  	if (dead)  		return; -	if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) +	if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC || +	    slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD)  		jmicron_enable_mmc(slot->host, 0);  } -static int jmicron_suspend(struct sdhci_pci_chip *chip, pm_message_t state) +static int jmicron_suspend(struct sdhci_pci_chip *chip)  {  	int i; -	if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) { -		for (i = 0;i < chip->num_slots;i++) +	if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC || +	    chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD) { +		for (i = 0; i < chip->num_slots; i++)  			jmicron_enable_mmc(chip->slots[i]->host, 0);  	} @@ -325,8 +499,9 @@ static int jmicron_resume(struct sdhci_pci_chip *chip)  {  	int ret, i; -	if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) { -		for (i = 0;i < chip->num_slots;i++) +	if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC || +	    chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD) { +		for (i = 0; i < chip->num_slots; i++)  			jmicron_enable_mmc(chip->slots[i]->host, 1);  	} @@ -339,6 +514,13 @@ static int jmicron_resume(struct sdhci_pci_chip *chip)  	return 0;  } +static const struct sdhci_pci_fixes sdhci_o2 = { +	.probe = sdhci_pci_o2_probe, +	.quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, +	.probe_slot = sdhci_pci_o2_probe_slot, +	.resume = sdhci_pci_o2_resume, +}; +  static const struct sdhci_pci_fixes sdhci_jmicron = {  	.probe		= jmicron_probe, @@ -428,7 +610,19 @@ static const struct sdhci_pci_fixes sdhci_via = {  	.probe		= via_probe,  }; -static const struct pci_device_id pci_ids[] __devinitdata = { +static int rtsx_probe_slot(struct sdhci_pci_slot *slot) +{ +	slot->host->mmc->caps2 |= MMC_CAP2_HS200; +	return 0; +} + +static const struct sdhci_pci_fixes sdhci_rtsx = { +	.quirks2	= SDHCI_QUIRK2_PRESET_VALUE_BROKEN | +			SDHCI_QUIRK2_BROKEN_DDR50, +	.probe_slot	= rtsx_probe_slot, +}; + +static const struct pci_device_id pci_ids[] = {  	{  		.vendor		= PCI_VENDOR_ID_RICOH,  		.device		= PCI_DEVICE_ID_RICOH_R5C822, @@ -454,6 +648,14 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  	},  	{ +		.vendor         = PCI_VENDOR_ID_RICOH, +		.device         = 0xe823, +		.subvendor      = PCI_ANY_ID, +		.subdevice      = PCI_ANY_ID, +		.driver_data    = (kernel_ulong_t)&sdhci_ricoh_mmc, +	}, + +	{  		.vendor		= PCI_VENDOR_ID_ENE,  		.device		= PCI_DEVICE_ID_ENE_CB712_SD,  		.subvendor	= PCI_ANY_ID, @@ -510,6 +712,22 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  	},  	{ +		.vendor		= PCI_VENDOR_ID_JMICRON, +		.device		= PCI_DEVICE_ID_JMICRON_JMB388_SD, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_jmicron, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_JMICRON, +		.device		= PCI_DEVICE_ID_JMICRON_JMB388_ESD, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_jmicron, +	}, + +	{  		.vendor		= PCI_VENDOR_ID_SYSKONNECT,  		.device		= 0x8000,  		.subvendor	= PCI_ANY_ID, @@ -526,6 +744,14 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  	},  	{ +		.vendor		= PCI_VENDOR_ID_REALTEK, +		.device		= 0x5250, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_rtsx, +	}, + +	{  		.vendor		= PCI_VENDOR_ID_INTEL,  		.device		= PCI_DEVICE_ID_INTEL_MRST_SD0,  		.subvendor	= PCI_ANY_ID, @@ -538,7 +764,15 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  		.device		= PCI_DEVICE_ID_INTEL_MRST_SD1,  		.subvendor	= PCI_ANY_ID,  		.subdevice	= PCI_ANY_ID, -		.driver_data	= (kernel_ulong_t)&sdhci_intel_mrst_hc1, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mrst_hc1_hc2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_MRST_SD2, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mrst_hc1_hc2,  	},  	{ @@ -554,7 +788,7 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  		.device		= PCI_DEVICE_ID_INTEL_MFD_SDIO1,  		.subvendor	= PCI_ANY_ID,  		.subdevice	= PCI_ANY_ID, -		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_emmc_sdio, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_sdio,  	},  	{ @@ -562,7 +796,7 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  		.device		= PCI_DEVICE_ID_INTEL_MFD_SDIO2,  		.subvendor	= PCI_ANY_ID,  		.subdevice	= PCI_ANY_ID, -		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_emmc_sdio, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_sdio,  	},  	{ @@ -570,7 +804,7 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  		.device		= PCI_DEVICE_ID_INTEL_MFD_EMMC0,  		.subvendor	= PCI_ANY_ID,  		.subdevice	= PCI_ANY_ID, -		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_emmc_sdio, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_emmc,  	},  	{ @@ -578,7 +812,183 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  		.device		= PCI_DEVICE_ID_INTEL_MFD_EMMC1,  		.subvendor	= PCI_ANY_ID,  		.subdevice	= PCI_ANY_ID, -		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_emmc_sdio, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_emmc, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_PCH_SDIO0, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_pch_sdio, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_PCH_SDIO1, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_pch_sdio, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_BYT_EMMC, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_byt_emmc, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_BYT_SDIO, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_byt_sdio, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_BYT_SD, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_byt_sd, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_BYT_EMMC2, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_byt_emmc, +	}, + + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_CLV_SDIO0, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_sd, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_CLV_SDIO1, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_sdio, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_CLV_SDIO2, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_sdio, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_CLV_EMMC0, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_emmc, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_CLV_EMMC1, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mfd_emmc, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_INTEL, +		.device		= PCI_DEVICE_ID_INTEL_MRFL_MMC, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_intel_mrfl_mmc, +	}, +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_8120, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_8220, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_8221, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_8320, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_8321, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_FUJIN2, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_SDS0, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_SDS1, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_SEABIRD0, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2, +	}, + +	{ +		.vendor		= PCI_VENDOR_ID_O2, +		.device		= PCI_DEVICE_ID_O2_SEABIRD1, +		.subvendor	= PCI_ANY_ID, +		.subdevice	= PCI_ANY_ID, +		.driver_data	= (kernel_ulong_t)&sdhci_o2,  	},  	{	/* Generic SD host controller */ @@ -621,8 +1031,59 @@ static int sdhci_pci_enable_dma(struct sdhci_host *host)  	return 0;  } -static struct sdhci_ops sdhci_pci_ops = { +static void sdhci_pci_set_bus_width(struct sdhci_host *host, int width) +{ +	u8 ctrl; + +	ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + +	switch (width) { +	case MMC_BUS_WIDTH_8: +		ctrl |= SDHCI_CTRL_8BITBUS; +		ctrl &= ~SDHCI_CTRL_4BITBUS; +		break; +	case MMC_BUS_WIDTH_4: +		ctrl |= SDHCI_CTRL_4BITBUS; +		ctrl &= ~SDHCI_CTRL_8BITBUS; +		break; +	default: +		ctrl &= ~(SDHCI_CTRL_8BITBUS | SDHCI_CTRL_4BITBUS); +		break; +	} + +	sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); +} + +static void sdhci_pci_gpio_hw_reset(struct sdhci_host *host) +{ +	struct sdhci_pci_slot *slot = sdhci_priv(host); +	int rst_n_gpio = slot->rst_n_gpio; + +	if (!gpio_is_valid(rst_n_gpio)) +		return; +	gpio_set_value_cansleep(rst_n_gpio, 0); +	/* For eMMC, minimum is 1us but give it 10us for good measure */ +	udelay(10); +	gpio_set_value_cansleep(rst_n_gpio, 1); +	/* For eMMC, minimum is 200us but give it 300us for good measure */ +	usleep_range(300, 1000); +} + +static void sdhci_pci_hw_reset(struct sdhci_host *host) +{ +	struct sdhci_pci_slot *slot = sdhci_priv(host); + +	if (slot->hw_reset) +		slot->hw_reset(host); +} + +static const struct sdhci_ops sdhci_pci_ops = { +	.set_clock	= sdhci_set_clock,  	.enable_dma	= sdhci_pci_enable_dma, +	.set_bus_width	= sdhci_pci_set_bus_width, +	.reset		= sdhci_reset, +	.set_uhs_signaling = sdhci_set_uhs_signaling, +	.hw_reset		= sdhci_pci_hw_reset,  };  /*****************************************************************************\ @@ -633,10 +1094,12 @@ static struct sdhci_ops sdhci_pci_ops = {  #ifdef CONFIG_PM -static int sdhci_pci_suspend (struct pci_dev *pdev, pm_message_t state) +static int sdhci_pci_suspend(struct device *dev)  { +	struct pci_dev *pdev = to_pci_dev(dev);  	struct sdhci_pci_chip *chip;  	struct sdhci_pci_slot *slot; +	mmc_pm_flag_t slot_pm_flags;  	mmc_pm_flag_t pm_flags = 0;  	int i, ret; @@ -644,47 +1107,53 @@ static int sdhci_pci_suspend (struct pci_dev *pdev, pm_message_t state)  	if (!chip)  		return 0; -	for (i = 0;i < chip->num_slots;i++) { +	for (i = 0; i < chip->num_slots; i++) {  		slot = chip->slots[i];  		if (!slot)  			continue; -		ret = sdhci_suspend_host(slot->host, state); +		ret = sdhci_suspend_host(slot->host); -		if (ret) { -			for (i--;i >= 0;i--) -				sdhci_resume_host(chip->slots[i]->host); -			return ret; -		} +		if (ret) +			goto err_pci_suspend; + +		slot_pm_flags = slot->host->mmc->pm_flags; +		if (slot_pm_flags & MMC_PM_WAKE_SDIO_IRQ) +			sdhci_enable_irq_wakeups(slot->host); -		pm_flags |= slot->host->mmc->pm_flags; +		pm_flags |= slot_pm_flags;  	}  	if (chip->fixes && chip->fixes->suspend) { -		ret = chip->fixes->suspend(chip, state); -		if (ret) { -			for (i = chip->num_slots - 1;i >= 0;i--) -				sdhci_resume_host(chip->slots[i]->host); -			return ret; -		} +		ret = chip->fixes->suspend(chip); +		if (ret) +			goto err_pci_suspend;  	}  	pci_save_state(pdev);  	if (pm_flags & MMC_PM_KEEP_POWER) { -		if (pm_flags & MMC_PM_WAKE_SDIO_IRQ) +		if (pm_flags & MMC_PM_WAKE_SDIO_IRQ) { +			pci_pme_active(pdev, true);  			pci_enable_wake(pdev, PCI_D3hot, 1); +		}  		pci_set_power_state(pdev, PCI_D3hot);  	} else { -		pci_enable_wake(pdev, pci_choose_state(pdev, state), 0); +		pci_enable_wake(pdev, PCI_D3hot, 0);  		pci_disable_device(pdev); -		pci_set_power_state(pdev, pci_choose_state(pdev, state)); +		pci_set_power_state(pdev, PCI_D3hot);  	}  	return 0; + +err_pci_suspend: +	while (--i >= 0) +		sdhci_resume_host(chip->slots[i]->host); +	return ret;  } -static int sdhci_pci_resume (struct pci_dev *pdev) +static int sdhci_pci_resume(struct device *dev)  { +	struct pci_dev *pdev = to_pci_dev(dev);  	struct sdhci_pci_chip *chip;  	struct sdhci_pci_slot *slot;  	int i, ret; @@ -705,7 +1174,7 @@ static int sdhci_pci_resume (struct pci_dev *pdev)  			return ret;  	} -	for (i = 0;i < chip->num_slots;i++) { +	for (i = 0; i < chip->num_slots; i++) {  		slot = chip->slots[i];  		if (!slot)  			continue; @@ -725,28 +1194,115 @@ static int sdhci_pci_resume (struct pci_dev *pdev)  #endif /* CONFIG_PM */ +#ifdef CONFIG_PM_RUNTIME + +static int sdhci_pci_runtime_suspend(struct device *dev) +{ +	struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); +	struct sdhci_pci_chip *chip; +	struct sdhci_pci_slot *slot; +	int i, ret; + +	chip = pci_get_drvdata(pdev); +	if (!chip) +		return 0; + +	for (i = 0; i < chip->num_slots; i++) { +		slot = chip->slots[i]; +		if (!slot) +			continue; + +		ret = sdhci_runtime_suspend_host(slot->host); + +		if (ret) +			goto err_pci_runtime_suspend; +	} + +	if (chip->fixes && chip->fixes->suspend) { +		ret = chip->fixes->suspend(chip); +		if (ret) +			goto err_pci_runtime_suspend; +	} + +	return 0; + +err_pci_runtime_suspend: +	while (--i >= 0) +		sdhci_runtime_resume_host(chip->slots[i]->host); +	return ret; +} + +static int sdhci_pci_runtime_resume(struct device *dev) +{ +	struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); +	struct sdhci_pci_chip *chip; +	struct sdhci_pci_slot *slot; +	int i, ret; + +	chip = pci_get_drvdata(pdev); +	if (!chip) +		return 0; + +	if (chip->fixes && chip->fixes->resume) { +		ret = chip->fixes->resume(chip); +		if (ret) +			return ret; +	} + +	for (i = 0; i < chip->num_slots; i++) { +		slot = chip->slots[i]; +		if (!slot) +			continue; + +		ret = sdhci_runtime_resume_host(slot->host); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int sdhci_pci_runtime_idle(struct device *dev) +{ +	return 0; +} + +#else + +#define sdhci_pci_runtime_suspend	NULL +#define sdhci_pci_runtime_resume	NULL +#define sdhci_pci_runtime_idle		NULL + +#endif + +static const struct dev_pm_ops sdhci_pci_pm_ops = { +	.suspend = sdhci_pci_suspend, +	.resume = sdhci_pci_resume, +	.runtime_suspend = sdhci_pci_runtime_suspend, +	.runtime_resume = sdhci_pci_runtime_resume, +	.runtime_idle = sdhci_pci_runtime_idle, +}; +  /*****************************************************************************\   *                                                                           *   * Device probing/removal                                                    *   *                                                                           *  \*****************************************************************************/ -static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( -	struct pci_dev *pdev, struct sdhci_pci_chip *chip, int bar) +static struct sdhci_pci_slot *sdhci_pci_probe_slot( +	struct pci_dev *pdev, struct sdhci_pci_chip *chip, int first_bar, +	int slotno)  {  	struct sdhci_pci_slot *slot;  	struct sdhci_host *host; - -	resource_size_t addr; - -	int ret; +	int ret, bar = first_bar + slotno;  	if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {  		dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar);  		return ERR_PTR(-ENODEV);  	} -	if (pci_resource_len(pdev, bar) != 0x100) { +	if (pci_resource_len(pdev, bar) < 0x100) {  		dev_err(&pdev->dev, "Invalid iomem size. You may "  			"experience problems.\n");  	} @@ -772,23 +1328,42 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(  	slot->chip = chip;  	slot->host = host;  	slot->pci_bar = bar; +	slot->rst_n_gpio = -EINVAL; +	slot->cd_gpio = -EINVAL; + +	/* Retrieve platform data if there is any */ +	if (*sdhci_pci_get_data) +		slot->data = sdhci_pci_get_data(pdev, slotno); + +	if (slot->data) { +		if (slot->data->setup) { +			ret = slot->data->setup(slot->data); +			if (ret) { +				dev_err(&pdev->dev, "platform setup failed\n"); +				goto free; +			} +		} +		slot->rst_n_gpio = slot->data->rst_n_gpio; +		slot->cd_gpio = slot->data->cd_gpio; +	}  	host->hw_name = "PCI";  	host->ops = &sdhci_pci_ops;  	host->quirks = chip->quirks; +	host->quirks2 = chip->quirks2;  	host->irq = pdev->irq;  	ret = pci_request_region(pdev, bar, mmc_hostname(host->mmc));  	if (ret) {  		dev_err(&pdev->dev, "cannot request region\n"); -		goto free; +		goto cleanup;  	} -	addr = pci_resource_start(pdev, bar);  	host->ioaddr = pci_ioremap_bar(pdev, bar);  	if (!host->ioaddr) {  		dev_err(&pdev->dev, "failed to remap registers\n"); +		ret = -ENOMEM;  		goto release;  	} @@ -798,15 +1373,42 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(  			goto unmap;  	} +	if (gpio_is_valid(slot->rst_n_gpio)) { +		if (!gpio_request(slot->rst_n_gpio, "eMMC_reset")) { +			gpio_direction_output(slot->rst_n_gpio, 1); +			slot->host->mmc->caps |= MMC_CAP_HW_RESET; +			slot->hw_reset = sdhci_pci_gpio_hw_reset; +		} else { +			dev_warn(&pdev->dev, "failed to request rst_n_gpio\n"); +			slot->rst_n_gpio = -EINVAL; +		} +	} +  	host->mmc->pm_caps = MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ; +	host->mmc->slotno = slotno; +	host->mmc->caps2 |= MMC_CAP2_NO_PRESCAN_POWERUP;  	ret = sdhci_add_host(host);  	if (ret)  		goto remove; +	sdhci_pci_add_own_cd(slot); + +	/* +	 * Check if the chip needs a separate GPIO for card detect to wake up +	 * from runtime suspend.  If it is not there, don't allow runtime PM. +	 * Note sdhci_pci_add_own_cd() sets slot->cd_gpio to -EINVAL on failure. +	 */ +	if (chip->fixes && chip->fixes->own_cd_for_runtime_pm && +	    !gpio_is_valid(slot->cd_gpio)) +		chip->allow_runtime_pm = false; +  	return slot;  remove: +	if (gpio_is_valid(slot->rst_n_gpio)) +		gpio_free(slot->rst_n_gpio); +  	if (chip->fixes && chip->fixes->remove_slot)  		chip->fixes->remove_slot(slot, 0); @@ -816,6 +1418,10 @@ unmap:  release:  	pci_release_region(pdev, bar); +cleanup: +	if (slot->data && slot->data->cleanup) +		slot->data->cleanup(slot->data); +  free:  	sdhci_free_host(host); @@ -827,6 +1433,8 @@ static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)  	int dead;  	u32 scratch; +	sdhci_pci_remove_own_cd(slot); +  	dead = 0;  	scratch = readl(slot->host->ioaddr + SDHCI_INT_STATUS);  	if (scratch == (u32)-1) @@ -834,30 +1442,49 @@ static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)  	sdhci_remove_host(slot->host, dead); +	if (gpio_is_valid(slot->rst_n_gpio)) +		gpio_free(slot->rst_n_gpio); +  	if (slot->chip->fixes && slot->chip->fixes->remove_slot)  		slot->chip->fixes->remove_slot(slot, dead); +	if (slot->data && slot->data->cleanup) +		slot->data->cleanup(slot->data); +  	pci_release_region(slot->chip->pdev, slot->pci_bar);  	sdhci_free_host(slot->host);  } -static int __devinit sdhci_pci_probe(struct pci_dev *pdev, +static void sdhci_pci_runtime_pm_allow(struct device *dev) +{ +	pm_runtime_put_noidle(dev); +	pm_runtime_allow(dev); +	pm_runtime_set_autosuspend_delay(dev, 50); +	pm_runtime_use_autosuspend(dev); +	pm_suspend_ignore_children(dev, 1); +} + +static void sdhci_pci_runtime_pm_forbid(struct device *dev) +{ +	pm_runtime_forbid(dev); +	pm_runtime_get_noresume(dev); +} + +static int sdhci_pci_probe(struct pci_dev *pdev,  				     const struct pci_device_id *ent)  {  	struct sdhci_pci_chip *chip;  	struct sdhci_pci_slot *slot; -	u8 slots, rev, first_bar; +	u8 slots, first_bar;  	int ret, i;  	BUG_ON(pdev == NULL);  	BUG_ON(ent == NULL); -	pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev); -  	dev_info(&pdev->dev, "SDHCI controller found [%04x:%04x] (rev %x)\n", -		 (int)pdev->vendor, (int)pdev->device, (int)rev); +		 (int)pdev->vendor, (int)pdev->device, (int)pdev->revision);  	ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &slots);  	if (ret) @@ -892,9 +1519,12 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev,  	}  	chip->pdev = pdev; -	chip->fixes = (const struct sdhci_pci_fixes*)ent->driver_data; -	if (chip->fixes) +	chip->fixes = (const struct sdhci_pci_fixes *)ent->driver_data; +	if (chip->fixes) {  		chip->quirks = chip->fixes->quirks; +		chip->quirks2 = chip->fixes->quirks2; +		chip->allow_runtime_pm = chip->fixes->allow_runtime_pm; +	}  	chip->num_slots = slots;  	pci_set_drvdata(pdev, chip); @@ -907,10 +1537,10 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev,  	slots = chip->num_slots;	/* Quirk may have changed this */ -	for (i = 0;i < slots;i++) { -		slot = sdhci_pci_probe_slot(pdev, chip, first_bar + i); +	for (i = 0; i < slots; i++) { +		slot = sdhci_pci_probe_slot(pdev, chip, first_bar, i);  		if (IS_ERR(slot)) { -			for (i--;i >= 0;i--) +			for (i--; i >= 0; i--)  				sdhci_pci_remove_slot(chip->slots[i]);  			ret = PTR_ERR(slot);  			goto free; @@ -919,6 +1549,9 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev,  		chip->slots[i] = slot;  	} +	if (chip->allow_runtime_pm) +		sdhci_pci_runtime_pm_allow(&pdev->dev); +  	return 0;  free: @@ -930,7 +1563,7 @@ err:  	return ret;  } -static void __devexit sdhci_pci_remove(struct pci_dev *pdev) +static void sdhci_pci_remove(struct pci_dev *pdev)  {  	int i;  	struct sdhci_pci_chip *chip; @@ -938,7 +1571,10 @@ static void __devexit sdhci_pci_remove(struct pci_dev *pdev)  	chip = pci_get_drvdata(pdev);  	if (chip) { -		for (i = 0;i < chip->num_slots; i++) +		if (chip->allow_runtime_pm) +			sdhci_pci_runtime_pm_forbid(&pdev->dev); + +		for (i = 0; i < chip->num_slots; i++)  			sdhci_pci_remove_slot(chip->slots[i]);  		pci_set_drvdata(pdev, NULL); @@ -949,32 +1585,16 @@ static void __devexit sdhci_pci_remove(struct pci_dev *pdev)  }  static struct pci_driver sdhci_driver = { -	.name = 	"sdhci-pci", +	.name =		"sdhci-pci",  	.id_table =	pci_ids, -	.probe = 	sdhci_pci_probe, -	.remove =	__devexit_p(sdhci_pci_remove), -	.suspend =	sdhci_pci_suspend, -	.resume	=	sdhci_pci_resume, +	.probe =	sdhci_pci_probe, +	.remove =	sdhci_pci_remove, +	.driver =	{ +		.pm =   &sdhci_pci_pm_ops +	},  }; -/*****************************************************************************\ - *                                                                           * - * Driver init/exit                                                          * - *                                                                           * -\*****************************************************************************/ - -static int __init sdhci_drv_init(void) -{ -	return pci_register_driver(&sdhci_driver); -} - -static void __exit sdhci_drv_exit(void) -{ -	pci_unregister_driver(&sdhci_driver); -} - -module_init(sdhci_drv_init); -module_exit(sdhci_drv_exit); +module_pci_driver(sdhci_driver);  MODULE_AUTHOR("Pierre Ossman <pierre@ossman.eu>");  MODULE_DESCRIPTION("Secure Digital Host Controller Interface PCI driver");  | 
