diff mbox

[1/4] sdhci: Rework some of the quirk behaviour

Message ID 20100913150151.23167.1382.stgit@localhost.localdomain (mailing list archive)
State Deferred, archived
Headers show

Commit Message

Alan Cox Sept. 13, 2010, 3:02 p.m. UTC
None
diff mbox

Patch

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index ce099c4..ffa40f1 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -81,6 +81,17 @@  config MMC_RICOH_MMC
 
 	  If unsure, say Y.
 
+config MMC_SDHCI_INTEL_MID
+	tristate "SDHCI support on Intel MID platforms"
+	depends on MMC_SDHCI_PCI
+	help
+	  This includes support for the SDHCI controllers found on Intel
+	  MID platform systems.
+
+	  If you have a controller with this interface, say Y or M here.
+
+	  If unsure, say N.
+
 config MMC_SDHCI_OF
 	tristate "SDHCI support on OpenFirmware platforms"
 	depends on MMC_SDHCI && PPC_OF
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 697bbfe..e6b3fe7 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -13,6 +13,7 @@  obj-$(CONFIG_MMC_MXC)		+= mxcmmc.o
 obj-$(CONFIG_MMC_SDHCI)		+= sdhci.o
 obj-$(CONFIG_MMC_SDHCI_MV)	+= sdhci-mv.o
 obj-$(CONFIG_MMC_SDHCI_PCI)	+= sdhci-pci.o
+obj-$(CONFIG_MMC_SDHCI_MID)	+= sdhci-intel-mid.o
 obj-$(CONFIG_MMC_SDHCI_S3C)	+= sdhci-s3c.o
 obj-$(CONFIG_MMC_SDHCI_SPEAR)	+= sdhci-spear.o
 obj-$(CONFIG_MMC_WBSD)		+= wbsd.o
diff --git a/drivers/mmc/host/sdhci-intel-mid.c b/drivers/mmc/host/sdhci-intel-mid.c
new file mode 100644
index 0000000..bb01fe3
--- /dev/null
+++ b/drivers/mmc/host/sdhci-intel-mid.c
@@ -0,0 +1,173 @@ 
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/leds.h>
+
+#include <linux/mmc/host.h>
+
+#include "sdhci.h"
+
+/*
+ *	Support code for SDHCI on Intel MID platforms. We have lots
+ * 	of quirks specific to these platforms so wrap them up in one place
+ *	that keeps them out of the main flow. We can't just make it a new
+ *	driver as the shdci core code isn't really a library.
+ */
+ 
+static void sdhci_broken_reset(struct sdhci_host *host, u8 mask)
+{
+	unsigned long timeout;
+	u32 uninitialized_var(ier);
+
+	if (host->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
+		if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) &
+			SDHCI_CARD_PRESENT))
+			return;
+	}
+
+	if (host->quirks & SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET)
+		ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+
+	sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET);
+
+	if (mask & SDHCI_RESET_ALL)
+		host->clock = 0;
+
+	/* Wait max 100 ms */
+	timeout = 100;
+
+	/* hw clears the bit when it's done */
+	while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) {
+		if (timeout == 0) {
+			printk(KERN_ERR "%s: Reset 0x%x never completed.\n",
+				mmc_hostname(host->mmc), (int)mask);
+			if (!(host->quirks & SDHCI_QUIRK_BROKEN_RESETALL)) {
+				printk(KERN_ERR "%s: Reset 0x%x never completed.\n",
+					mmc_hostname(host->mmc), (int)mask);
+				sdhci_dumpregs(host);
+			}
+			return;
+		}
+		timeout--;
+		mdelay(1);
+	}
+	if (host->quirks & SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET)
+		sdhci_clear_set_irqs(host, SDHCI_INT_ALL_MASK, ier);
+}
+
+static void sdhci_mid_broken_resetall(struct sdhci_host *host, int down)
+{
+	if (down)
+		sdhci_broken_reset(host, SDHCI_RESET_ALL);
+}
+
+static void sdhci_mid_init_no_reset(struct sdhci_host *host, int soft)
+{
+	u32 intmask;
+
+	intmask = sdhci_readl(host, SDHCI_INT_STATUS);
+	sdhci_writel(host,
+		intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE),
+		SDHCI_INT_STATUS);
+
+        /* Ensure any IRQ left over from pre boot time (eg from Kboot) does
+           not turn up and cause chaos */
+	sdhci_writel(host, 0, SDHCI_INT_ENABLE);
+	sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
+
+	sdhci_clear_set_irqs(host, SDHCI_INT_ALL_MASK,
+		SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT |
+		SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX |
+		SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT |
+		SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE);
+
+	if (soft) {
+		/* force clock reconfiguration */
+		host->clock = 0;
+		sdhci_set_ios(host->mmc, &host->mmc->ios);
+	}
+
+	/* disable wakeup signal during initialization */
+	sdhci_writeb(host, 0x0, SDHCI_WAKE_UP_CONTROL);
+}
+
+/*
+ * HW problem exists in LNW A3 so clock register has to be set
+ * for every command if both SDIO0 and SDIO1 are enabled.
+ */
+static void sdhci_clock_reset(struct sdhci_host *host)
+{
+	u16 clk;
+	unsigned long timeout;
+
+	clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+
+	clk |= SDHCI_CLOCK_CARD_EN;
+	sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+	/* Wait max 10 ms */
+	timeout = 10;
+	while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
+		& SDHCI_CLOCK_INT_STABLE)) {
+		if (timeout == 0) {
+			printk(KERN_ERR "%s: Internal clock never "
+				"stabilised.\n",
+				mmc_hostname(host->mmc));
+			sdhci_dumpregs(host);
+			return;
+		}
+		timeout--;
+		mdelay(1);
+	}
+}
+
+static void sdhci_clockreset_wcmd(struct sdhci_host *host,
+				struct mmc_command *cmd, int flags)
+{
+	sdhci_clock_reset(host);
+ 	sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
+}
+
+static void sdhci_lnw_a3_set_ios(struct sdhci_host *host,
+					struct mmc_ios *ios, u8 ctrl)
+{
+	/*
+	 * For LNW A3, HISPD bit has to be cleared in order to
+	 * enable the 50MHz clock
+	 */
+	if (ios->timing == MMC_TIMING_SD_HS ||
+			ios->timing == MMC_TIMING_MMC_HS)
+		ctrl |= SDHCI_CTRL_HISPD;
+	else
+		ctrl &= ~SDHCI_CTRL_HISPD;
+	sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+}
+
+/*
+ * HW problem exists in LNW A3 which leads to fake interrupt on SDIO1 if SDIO0
+ * and SDIO1 are both enabled.
+ */
+static void sdhci_lnw_a3_unexpected_cmd(struct sdhci_host *host, u32 intmask)
+{
+}
+
+static void sdhci_no_reset(struct sdhci_host *host, u8 mask)
+{
+}
+
+struct sdhci_ops sdhci_intel_mrst_hc = {
+	.reset = sdhci_broken_reset,
+	.init = sdhci_mid_init_no_reset,
+	.set_ios = sdhci_lnw_a3_set_ios,
+	.reset_all = sdhci_broken,reset_all,
+	.write_command = sdhci_clockreset_wcmd,
+	.unexpected_cmd_irq = sdhci_lnw_a3_unexpected_cmd,
+};
+EXPORT_SYMBOL_GPL(sdhci_intel_mrst_hc);
+
+
diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c
index e8aa99d..168b837 100644
--- a/drivers/mmc/host/sdhci-pci.c
+++ b/drivers/mmc/host/sdhci-pci.c
@@ -54,6 +54,8 @@  struct sdhci_pci_fixes {
 	int			(*suspend)(struct sdhci_pci_chip*,
 					pm_message_t);
 	int			(*resume)(struct sdhci_pci_chip*);
+
+	struct sdhci_ops	*host_ops;
 };
 
 struct sdhci_pci_slot {
@@ -397,6 +399,39 @@  static const struct sdhci_pci_fixes sdhci_via = {
 	.probe		= via_probe,
 };
 
+#if defined(CONFIG_SDHCI_INTEL_MID) || defined(CONFIG_SDHCI_INTEL_MID_MODULE)
+/*
+ * ADMA operation is disabled for Moorestown platform due to
+ * hardware bugs.
+ */
+static const struct sdhci_pci_fixes sdhci_intel_mrst_hc0 = {
+	.quirks		= SDHCI_QUIRK_BROKEN_ADMA |
+			SDHCI_QUIRK_BROKEN_MULTIPLE_SLOTS |
+			SDHCI_QUIRK_BROKEN_RESETALL |
+			SDHCI_QUIRK_FORCE_FULL_SPEED_MODE,
+	.probe		= mrst_hc0_probe,
+	.host_ops	= &sdhci_intel_mrst_hc,
+};
+
+static int mrst_hc0_probe(struct sdhci_pci_chip *chip)
+{
+	/*
+	 * slots number is fixed here for MRST as SDIO3 is never used and has
+	 * hardware bugs.
+	 */
+	chip->num_slots = 1;
+}
+
+static const struct sdhci_pci_fixes sdhci_intel_mrst_hc1 = {
+	.quirks		= SDHCI_QUIRK_BROKEN_ADMA |
+			SDHCI_QUIRK_BROKEN_RESETALL |
+			SDHCI_QUIRK_FORCE_FULL_SPEED_MODE,
+	.probe		= mrst_hc1_probe,
+	.host_ops	= &sdhci_intel_mrst_hc,
+};
+
+#endif
+
 static const struct pci_device_id pci_ids[] __devinitdata = {
 	{
 		.vendor		= PCI_VENDOR_ID_RICOH,
@@ -494,6 +529,23 @@  static const struct pci_device_id pci_ids[] __devinitdata = {
 		.driver_data	= (kernel_ulong_t)&sdhci_via,
 	},
 
+#if defined(CONFIG_SDHCI_INTEL_MID) || defined(CONFIG_SDHCI_INTEL_MID_MODULE)
+	{
+		.vendor		= PCI_VENDOR_ID_INTEL,
+		.device		= PCI_DEVICE_ID_INTEL_MRST_SD0,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.driver_data	= (kernel_ulong_t)&sdhci_intel_mrst_hc0,
+	},
+
+	{
+		.vendor		= PCI_VENDOR_ID_INTEL,
+		.device		= PCI_DEVICE_ID_INTEL_MRST_SD1,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.driver_data	= (kernel_ulong_t)&sdhci_intel_mrst_hc1,
+	},
+#endif
 	{	/* Generic SD host controller */
 		PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)
 	},
@@ -509,7 +561,7 @@  MODULE_DEVICE_TABLE(pci, pci_ids);
  *                                                                           *
 \*****************************************************************************/
 
-static int sdhci_pci_enable_dma(struct sdhci_host *host)
+int sdhci_pci_enable_dma(struct sdhci_host *host)
 {
 	struct sdhci_pci_slot *slot;
 	struct pci_dev *pdev;
@@ -533,6 +585,7 @@  static int sdhci_pci_enable_dma(struct sdhci_host *host)
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(sdhci_pci_enable_dma);
 
 static struct sdhci_ops sdhci_pci_ops = {
 	.enable_dma	= sdhci_pci_enable_dma,
@@ -687,7 +740,10 @@  static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
 	slot->pci_bar = bar;
 
 	host->hw_name = "PCI";
-	host->ops = &sdhci_pci_ops;
+	if (chip->fixes->host_ops)
+		host->ops = chip->fixes->host_ops;
+	else
+		host->ops = &sdhci_pci_ops;
 	host->quirks = chip->quirks;
 
 	host->irq = pdev->irq;
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index f608626..fc75b7f 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -94,7 +94,7 @@  static void sdhci_dumpregs(struct sdhci_host *host)
  *                                                                           *
 \*****************************************************************************/
 
-static void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
+void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
 {
 	u32 ier;
 
@@ -104,6 +104,7 @@  static void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
 	sdhci_writel(host, ier, SDHCI_INT_ENABLE);
 	sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
 }
+EXPORT_SYMBOL_GPL(sdhci_clear_set_irqs);
 
 static void sdhci_unmask_irqs(struct sdhci_host *host, u32 irqs)
 {
@@ -143,6 +144,11 @@  static void sdhci_reset(struct sdhci_host *host, u8 mask)
 	unsigned long timeout;
 	u32 uninitialized_var(ier);
 
+	if (host->ops->reset) {
+		host->ops->reset(host, mask);
+		return;
+	}
+
 	if (host->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
 		if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) &
 			SDHCI_CARD_PRESENT))
@@ -176,6 +182,15 @@  static void sdhci_reset(struct sdhci_host *host, u8 mask)
 		sdhci_clear_set_irqs(host, SDHCI_INT_ALL_MASK, ier);
 }
 
+/* Down is true if we are shutting down the port */
+static void sdhci_reset_all(struct sdhci_host *host, int down)
+{
+	if (host->ops->reset_all)
+		host->ops->reset_all(host, down);
+	else
+		sdhci_reset(host, SDHCI_RESET_ALL);
+}
+
 static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
 
 static void sdhci_init(struct sdhci_host *host, int soft)
@@ -183,7 +198,7 @@  static void sdhci_init(struct sdhci_host *host, int soft)
 	if (soft)
 		sdhci_reset(host, SDHCI_RESET_CMD|SDHCI_RESET_DATA);
 	else
-		sdhci_reset(host, SDHCI_RESET_ALL);
+		sdhci_reset_all(host, 0);
 
 	sdhci_clear_set_irqs(host, SDHCI_INT_ALL_MASK,
 		SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT |
@@ -945,7 +960,11 @@  static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
 	if (cmd->data)
 		flags |= SDHCI_CMD_DATA;
 
-	sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
+	if (host->ops->write_command)
+		host->ops->write_command(host, cmd, flags);
+	else
+		sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags),
+						SDHCI_COMMAND);
 }
 
 static void sdhci_finish_command(struct sdhci_host *host)
@@ -1186,9 +1205,13 @@  static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 	else
 		ctrl &= ~SDHCI_CTRL_HISPD;
 
-	sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+	if (host->ops->set_ios)
+		host->ops->set_ios(host, ios, ctrl);
+	else
+		sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
 
-	/*
+	/*	FIXME: This can also be moved to ops->set_ios
+	 *
 	 * Some (ENE) controllers go apeshit on some ios operation,
 	 * signalling timeout and CRC errors even on CMD0. Resetting
 	 * it on each ios seems to solve the problem.
@@ -1383,10 +1406,13 @@  static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask)
 	BUG_ON(intmask == 0);
 
 	if (!host->cmd) {
-		printk(KERN_ERR "%s: Got command interrupt 0x%08x even "
-			"though no command operation was in progress.\n",
-			mmc_hostname(host->mmc), (unsigned)intmask);
-		sdhci_dumpregs(host);
+		if (host->ops->unexpected_cmd_irq)
+			host->ops->unexpected_cmd_irq(host, intmask);
+		else {
+			printk(KERN_ERR "%s: Got command interrupt 0x%08x even though no command operation was in progress.\n",
+				mmc_hostname(host->mmc), (unsigned)intmask);
+			sdhci_dumpregs(host);
+		}
 		return;
 	}
 
@@ -1703,7 +1729,7 @@  int sdhci_add_host(struct sdhci_host *host)
 	if (debug_quirks)
 		host->quirks = debug_quirks;
 
-	sdhci_reset(host, SDHCI_RESET_ALL);
+	sdhci_reset_all(host, 0);
 
 	host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
 	host->version = (host->version & SDHCI_SPEC_VER_MASK)
@@ -1829,6 +1855,9 @@  int sdhci_add_host(struct sdhci_host *host)
 	if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
 		mmc->caps |= MMC_CAP_NEEDS_POLL;
 
+	if (host->ops->set_caps)
+		host->ops->set_caps(host);
+
 	mmc->ocr_avail = 0;
 	if (caps & SDHCI_CAN_VDD_330)
 		mmc->ocr_avail |= MMC_VDD_32_33|MMC_VDD_33_34;
@@ -1881,7 +1910,7 @@  int sdhci_add_host(struct sdhci_host *host)
 	} else {
 		mmc->max_blk_size = (caps & SDHCI_MAX_BLOCK_MASK) >>
 				SDHCI_MAX_BLOCK_SHIFT;
-		if (mmc->max_blk_size >= 3) {
+		if (mmc->max_blk_size > 3) {
 			printk(KERN_WARNING "%s: Invalid maximum block size, "
 				"assuming 512 bytes\n", mmc_hostname(mmc));
 			mmc->max_blk_size = 0;
@@ -1952,7 +1981,7 @@  int sdhci_add_host(struct sdhci_host *host)
 
 #ifdef SDHCI_USE_LEDS_CLASS
 reset:
-	sdhci_reset(host, SDHCI_RESET_ALL);
+	sdhci_reset_all(host, 1);
 	free_irq(host->irq, host);
 #endif
 untasklet:
@@ -1993,7 +2022,7 @@  void sdhci_remove_host(struct sdhci_host *host, int dead)
 #endif
 
 	if (!dead)
-		sdhci_reset(host, SDHCI_RESET_ALL);
+		sdhci_reset_all(host, 1);
 
 	free_irq(host->irq, host);
 
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index d316bc7..8c7d9e8 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -323,6 +323,17 @@  struct sdhci_ops {
 	unsigned int	(*get_max_clock)(struct sdhci_host *host);
 	unsigned int	(*get_min_clock)(struct sdhci_host *host);
 	unsigned int	(*get_timeout_clock)(struct sdhci_host *host);
+
+	/* Interface hooks to allow drivers to override standard behaviour */
+	void	(*reset)(struct sdhci_host *host, u8 mask);
+	void	(*reset_all)(struct sdhci_host *host, int down);
+	void	(*init)(struct sdhci_host *host, int soft);
+	void 	(*write_command)(struct sdhci_host *host,
+				struct mmc_command *cmd, int flags);
+	void	(*set_ios)(struct sdhci_host *host,
+				struct mmc_ios *ios, u8 ctrl);
+	void	(*set_caps)(struct sdhci_host *host);
+	void	(*unexpected_cmd_irq)(struct sdhci_host *host, u32 intmask);
 };
 
 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
@@ -427,4 +438,11 @@  extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state);
 extern int sdhci_resume_host(struct sdhci_host *host);
 #endif
 
+extern void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
+extern int sdhci_pci_enable_dma(struct sdhci_host *host);
+
+/* Host function sets from driver support files */
+extern struct sdhci_ops sdhci_intel_mrst_hc;
+
+
 #endif /* __SDHCI_H */
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 9438660..674316e 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -2410,6 +2410,8 @@ 
 #define PCI_DEVICE_ID_INTEL_82375	0x0482
 #define PCI_DEVICE_ID_INTEL_82424	0x0483
 #define PCI_DEVICE_ID_INTEL_82378	0x0484
+#define PCI_DEVICE_ID_INTEL_MRST_SD0	0x0807
+#define PCI_DEVICE_ID_INTEL_MRST_SD1	0x0808
 #define PCI_DEVICE_ID_INTEL_I960	0x0960
 #define PCI_DEVICE_ID_INTEL_I960RM	0x0962
 #define PCI_DEVICE_ID_INTEL_8257X_SOL	0x1062