diff mbox

mmc: power class support

Message ID AANLkTin_=jScYXaXNStx-RfmBopbY43fuV8ZdhcS4M3K@mail.gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Russ Knize April 1, 2011, 3:58 p.m. UTC
None

Comments

Andrei Warkentin April 4, 2011, 4:11 a.m. UTC | #1
On Fri, Apr 1, 2011 at 7:28 PM, Andrei Warkentin <andreiw@motorola.com> wrote:
> Hi Russ,
>
> On Fri, Apr 1, 2011 at 10:58 AM, Russ Knize <Russ.Knize@motorola.com> wrote:
>> +                       /*
>> +                        * Ignore switch errors from buggy cards that actually
>> +                        * do switch successfully.
>> +                        */
>> +                       err = mmc_set_power_class(host, max_dtr, bus_width);
>> +                       if (err && err != -EBADMSG)
>> +                               goto free_card;
>> +
>
> 1) host->card is not yet set at this moment, you probably want to pass
> card to mmc_set_power_class as well .
> 2) It seems like you will call mmc_set_power_class even if bus_width
> == MMC_BUS_WIDTH_1. Is that supposed to work? 7.6.3 seems to imply
> power classes only apply to 4-bit and 8-bit bus widths. As does the
> paragraph on top of Page 138 of eMMC 4.41 spec, which says that for
> 1-bit bus configuration the default applies (class 0).
> 3) Since the current code supports DDR modes, you probably want to
> extend support for PWR_CL_DDR_52_(360|195) as well.
>
> Just curious, for the cards reporting switch errors but nonetheless
> switching to the power class, do they update the EXT_CSD field?
>

There was just a patch posted by Chuanxiao Dong ([PATCH v4 0/3]mmc:
enable TRIM/ERASE caps for SDHCI host) against SDHCI core to enable
setting proper timeout for R1B response-type commands, one of which is
CMD6 (SWITCH). The patch is applicable to the kernel sources you've
tested your patch on. Were the failures you've seen for cards erroring
on MMC_SWITCH timeouts? Could you test with that change for the parts
affected, and see if your problem in programming power level goes
away?

A
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 1f453ac..a9a6658 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -954,6 +954,63 @@  void mmc_set_timing(struct mmc_host *host,
unsigned int timing)
 }

 /*
+ * Try to upgrade the power class to the maximum supported.  The maximum power
+ * class supported by a card is stored as 8 values in 4 EXT_CSD bytes.  The
+ * value that applies depends on the operating voltage, bus speed, and bus
+ * width.
+ */
+int mmc_set_power_class(struct mmc_host *host, unsigned int hz,
+                         unsigned int width)
+{
+	struct mmc_card *card = host->card;
+	int class_index = MMC_EXT_CSD_PWR_CL(EXT_CSD_PWR_CL_52_195);
+	u8 power_class = 0;
+	int err = 0;
+
+	/*
+	 * The spec is vague about what voltage threshold is used to determine
+	 * which class value to use, but they probably intend it for "low"
+	 * (1.7V-1.95V) versus "high" (2.7V-3.6V) voltage cards.
+	 */
+	if (host->ocr >= MMC_VDD_27_28)
+		class_index = MMC_EXT_CSD_PWR_CL(EXT_CSD_PWR_CL_52_360);
+
+	/*
+	 * More vagueness here.  Assume that the threshold is at 26MHz.
+	 */
+	if (hz <= 26000000)
+		class_index++;
+
+	power_class = card->ext_csd.power_class[class_index];
+	if (width == MMC_BUS_WIDTH_4)
+		power_class &= 0xF;
+	else if (width == MMC_BUS_WIDTH_8)
+		power_class = (power_class & 0xF0) >> 4;
+
+	if (power_class > host->max_power_class)
+		power_class = host->max_power_class;
+
+	if (power_class > 0) {
+		pr_debug("%s: power class %d (max %d), %u HZ, width %d, OCR=0x%08X\n",
+			mmc_hostname(card->host),
+			power_class,
+			host->max_power_class,
+			hz,
+			width,
+			card->host->ocr);
+
+		err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+			EXT_CSD_POWER_CLASS, power_class);
+		if (err) {
+			pr_warning("%s: switch power class failed (%d)\n",
+				mmc_hostname(card->host), err);
+		}
+	}
+
+	return err;
+}
+
+/*
  * Apply power to the MMC stack.  This is a two-stage process.
  * First, we enable power to the card without the clock running.
  * We then wait a bit for the power to stabilise.  Finally,
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index 20b1c08..8e5847d 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -42,6 +42,8 @@  void mmc_set_bus_width_ddr(struct mmc_host *host,
unsigned int width,
 			   unsigned int ddr);
 u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
 void mmc_set_timing(struct mmc_host *host, unsigned int timing);
+int mmc_set_power_class(struct mmc_host *host, unsigned int hz,
+                        unsigned int width);

 static inline void mmc_delay(unsigned int ms)
 {
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 2d48800..47f779d 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -304,6 +304,14 @@  static int mmc_read_ext_csd(struct mmc_card *card)

 	if (card->ext_csd.rev >= 4) {
 		/*
+		 * Read power class table.
+		 */
+		int i;
+		for (i = EXT_CSD_PWR_CL_52_195;
+		     i <= EXT_CSD_PWR_CL_26_360; i++)
+			card->ext_csd.power_class[MMC_EXT_CSD_PWR_CL(i)] =
+			                                           ext_csd[i];
+		/*
 		 * Enhanced area feature support -- check whether the eMMC
 		 * card has the Enhanced area enabled.  If so, export enhanced
 		 * area offset and size to user by adding sysfs interface.
@@ -648,6 +656,15 @@  static int mmc_init_card(struct mmc_host *host, u32 ocr,
 			bus_width = bus_widths[idx];
 			if (bus_width == MMC_BUS_WIDTH_1)
 				ddr = 0; /* no DDR for 1-bit width */
+
+			/*
+			 * Ignore switch errors from buggy cards that actually
+			 * do switch successfully.
+			 */
+			err = mmc_set_power_class(host, max_dtr, bus_width);
+			if (err && err != -EBADMSG)
+				goto free_card;
+
 			err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
 					 EXT_CSD_BUS_WIDTH,
 					 ext_csd_bits[idx][0]);
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 9e15f41..12740d4 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1981,6 +1981,8 @@  int sdhci_add_host(struct sdhci_host *host)
 	 */
 	mmc->max_blk_count = (host->quirks & SDHCI_QUIRK_NO_MULTIBLOCK) ? 1 : 65535;

+	mmc->max_power_class = host->max_power_class;
+
 	/*
 	 * Init tasklets.
 	 */
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 557b732..e18dff0 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -46,6 +46,8 @@  struct mmc_ext_csd {
 	u8			erase_group_def;
 	u8			sec_feature_support;
 	u8			bootconfig;
+	u8			power_class[4];
+#define MMC_EXT_CSD_PWR_CL(b)	(b - EXT_CSD_PWR_CL_52_195)
 	unsigned int		sa_timeout;		/* Units: 100ns */
 	unsigned int		hs_max_dtr;
 	unsigned int		sectors;
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index bcb793e..7509e39 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -227,6 +227,8 @@  struct mmc_host {
 	const struct mmc_bus_ops *bus_ops;	/* current bus driver */
 	unsigned int		bus_refs;	/* reference counter */

+	unsigned int		max_power_class;
+
 	unsigned int		sdio_irqs;
 	struct task_struct	*sdio_irq_thread;
 	atomic_t		sdio_irq_thread_abort;
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
index b5ec88f..2e3a7fd 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -260,9 +260,14 @@  struct _mmc_csd {
 #define EXT_CSD_ERASED_MEM_CONT		181	/* RO */
 #define EXT_CSD_BUS_WIDTH		183	/* R/W */
 #define EXT_CSD_HS_TIMING		185	/* R/W */
+#define EXT_CSD_POWER_CLASS		187	/* R/W */
 #define EXT_CSD_REV			192	/* RO */
 #define EXT_CSD_STRUCTURE		194	/* RO */
 #define EXT_CSD_CARD_TYPE		196	/* RO */
+#define EXT_CSD_PWR_CL_52_195		200	/* RO */
+#define EXT_CSD_PWR_CL_26_195		201	/* RO */
+#define EXT_CSD_PWR_CL_52_360		202	/* RO */
+#define EXT_CSD_PWR_CL_26_360		203	/* RO */
 #define EXT_CSD_SEC_CNT			212	/* RO, 4 bytes */
 #define EXT_CSD_S_A_TIMEOUT		217	/* RO */
 #define EXT_CSD_HC_WP_GRP_SIZE		221	/* RO */
diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h
index 83bd9f7..b1c30f3 100644
--- a/include/linux/mmc/sdhci.h
+++ b/include/linux/mmc/sdhci.h
@@ -145,6 +145,8 @@  struct sdhci_host {
 	unsigned int            ocr_avail_sd;
 	unsigned int            ocr_avail_mmc;

+	unsigned int		max_power_class;
+
 	unsigned long private[0] ____cacheline_aligned;
 };
 #endif /* __SDHCI_H */