diff mbox

[v3,08/11] mmc: sd: add support for tuning during uhs initialization

Message ID 1303291861-1788-9-git-send-email-arindam.nath@amd.com (mailing list archive)
State New, archived
Headers show

Commit Message

Arindam Nath April 20, 2011, 9:30 a.m. UTC
Host Controller needs tuning during initialization to operate SDR50
and SDR104 UHS-I cards. Whether SDR50 mode actually needs tuning is
indicated by bit 45 of the Host Controller Capabilities register.
A new command CMD19 has been defined in the Physical Layer spec
v3.01 to request the card to send tuning pattern.

We enable Buffer Read Ready interrupt at the very begining of tuning
procedure, because that is the only interrupt generated by the Host
Controller during tuning. We make sure that DMA Enable is set to 0
before actually sending CMD19. The tuning block is sent by the card
to the Host Controller using DAT lines, so we set Data Present
Select (bit 5) in the Command register. The Host Controller is
responsible for doing the verfication of tuning block sent by the
card at the hardware level. After sending CMD19, we wait for Buffer
Read Ready interrupt. In case we don't receive an interrupt after
the specified timeout value, we fall back on fixed sampling clock by
setting Execute Tuning (bit 6) and Sampling Clock Select (bit 7) of
Host Control2 register to 0. Before exiting the tuning procedure, we
disable Buffer Read Ready interrupt.

Signed-off-by: Arindam Nath <arindam.nath@amd.com>
---
 drivers/mmc/core/sd.c     |    6 ++
 drivers/mmc/host/sdhci.c  |  157 ++++++++++++++++++++++++++++++++++++++++++++-
 drivers/mmc/host/sdhci.h  |    3 +
 include/linux/mmc/host.h  |    1 +
 include/linux/mmc/mmc.h   |    1 +
 include/linux/mmc/sdhci.h |    4 +
 6 files changed, 171 insertions(+), 1 deletions(-)

Comments

Philip Rakity April 27, 2011, 6:44 p.m. UTC | #1
Hi Arindam,


On Apr 20, 2011, at 2:30 AM, Arindam Nath wrote:

> Host Controller needs tuning during initialization to operate SDR50
> and SDR104 UHS-I cards. Whether SDR50 mode actually needs tuning is
> indicated by bit 45 of the Host Controller Capabilities register.
> A new command CMD19 has been defined in the Physical Layer spec
> v3.01 to request the card to send tuning pattern.
> 
> We enable Buffer Read Ready interrupt at the very begining of tuning
> procedure, because that is the only interrupt generated by the Host
> Controller during tuning. We make sure that DMA Enable is set to 0
> before actually sending CMD19. The tuning block is sent by the card
> to the Host Controller using DAT lines, so we set Data Present
> Select (bit 5) in the Command register. The Host Controller is
> responsible for doing the verfication of tuning block sent by the
> card at the hardware level. After sending CMD19, we wait for Buffer
> Read Ready interrupt. In case we don't receive an interrupt after
> the specified timeout value, we fall back on fixed sampling clock by
> setting Execute Tuning (bit 6) and Sampling Clock Select (bit 7) of
> Host Control2 register to 0. Before exiting the tuning procedure, we
> disable Buffer Read Ready interrupt.
> 
> Signed-off-by: Arindam Nath <arindam.nath@amd.com>
> ---
> drivers/mmc/core/sd.c     |    6 ++
> drivers/mmc/host/sdhci.c  |  157 ++++++++++++++++++++++++++++++++++++++++++++-
> drivers/mmc/host/sdhci.h  |    3 +
> include/linux/mmc/host.h  |    1 +
> include/linux/mmc/mmc.h   |    1 +
> include/linux/mmc/sdhci.h |    4 +
> 6 files changed, 171 insertions(+), 1 deletions(-)
> 
> diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
> index 1594e51..f3ecdef 100644
> --- a/drivers/mmc/core/sd.c
> +++ b/drivers/mmc/core/sd.c
> @@ -623,6 +623,12 @@ static int mmc_sd_init_uhs_card(struct mmc_card *card)
> 
> 	/* Set current limit for the card */
> 	err = sd_set_current_limit(card, status);
> +	if (err)
> +		goto out;
> +
> +	/* SPI mode doesn't define CMD19 */
> +	if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning)
> +		err = card->host->ops->execute_tuning(card->host);
> 
> out:
> 	kfree(status);
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 9141c5b..9065157 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -38,6 +38,8 @@
> #define SDHCI_USE_LEDS_CLASS
> #endif
> 
> +#define MAX_TUNING_LOOP 40
> +
> static unsigned int debug_quirks = 0;
> 
> static void sdhci_finish_data(struct sdhci_host *);
> @@ -962,7 +964,9 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
> 		flags |= SDHCI_CMD_CRC;
> 	if (cmd->flags & MMC_RSP_OPCODE)
> 		flags |= SDHCI_CMD_INDEX;
> -	if (cmd->data)
> +
> +	/* CMD19 is special in that the Data Present Select should be set */
> +	if (cmd->data || (cmd->opcode == MMC_SEND_TUNING_BLOCK))
> 		flags |= SDHCI_CMD_DATA;
> 
> 	sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
> @@ -1477,12 +1481,146 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> 		return 0;
> }
> 
> +static int sdhci_execute_tuning(struct mmc_host *mmc)
> +{
> +	struct sdhci_host *host;
> +	u16 ctrl;
	 	u32 ier;
> +	int tuning_loop_counter = MAX_TUNING_LOOP;
> +	unsigned long timeout;
> +	int err = 0;
> +
> +	host = mmc_priv(mmc);
> +
> +	disable_irq(host->irq);
> +	spin_lock(&host->lock);

is this needed ?  I believe we should be locked from the core/ layer.
> +
> +	ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +
> +	/*
> +	 * Host Controller needs tuning only in case of SDR104 mode
> +	 * and for SDR50 mode when Use Tuning for SDR50 is set in
> +	 * Capabilities register.
> +	 */
> +	if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) ||
> +	    (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) &&
> +	    (host->flags & SDHCI_SDR50_NEEDS_TUNING)))
> +		ctrl |= SDHCI_CTRL_EXEC_TUNING;
> +	else {
> +		spin_unlock(&host->lock);
> +		enable_irq(host->irq);
> +		return 0;
> +	}


based on exchanges with SD Host Chair we need to set block size and transfer mode.
Directly wriiting TRANSFER_MODE clears DMA bit.  No DMA is needed for SDHCI_INT_DATA_AVAIL
to be signaled. 

Suggest

> +	if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) ||
> +	    (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) &&
> +	    (host->flags & SDHCI_SDR50_NEEDS_TUNING))) {
			ctrl |= SDHCI_CTRL_EXEC_TUNING;		
			sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), SDHCI_BLOCK_SIZE);
			sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE);
	} else {
> +		spin_unlock(&host->lock);
> +		enable_irq(host->irq);
> +		return 0;
> +	}


> +
> +	sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
> +
> +	/*
> +	 * As per the Host Controller spec v3.00, tuning command
> +	 * generates Buffer Read Ready interrupt, so enable that.
> +	 */
> +	sdhci_unmask_irqs(host, SDHCI_INT_DATA_AVAIL);

delete line above -- it keeps the other interrupts enabled but we should only
receive the DATA AVAIL interrupt.  We should protect against a bad controller

change to

>> 	ier = sdhci_readl(host, SDHCI_INT_ENABLE); 
>> 	sdhci_clear_set_irqs(host, ier, SDHCI_INT_DATA_AVAIL); 


This will ONLY enable SDHCI_INT_DATA_AVAIL.
> 
> 
> +
> +	/*
> +	 * Issue CMD19 repeatedly till Execute Tuning is set to 0 or the number
> +	 * of loops reaches 40 times or a timeout of 150ms occurs.
> +	 */
> +	timeout = 150;
> +	do {
> +		u16 mode;
> +		struct mmc_command cmd;
> +		struct mmc_request mrq;
> +
> +		if (!tuning_loop_counter && !timeout)
> +			break;
> +
> +		memset(&cmd, 0, sizeof(struct mmc_command));
> +		cmd.opcode = MMC_SEND_TUNING_BLOCK;
> +		cmd.arg = 0;
> +		cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
> +
> +		memset(&cmd.resp, 0, sizeof(cmd.resp));
> +		cmd.retries = 0;
> +
> +		cmd.data = NULL;
> +		cmd.error = 0;
> +
> +		memset(&mrq, 0, sizeof(struct mmc_request));
> +		mrq.cmd = &cmd;
> +		host->mrq = &mrq;
> +

=====
> +		/* Make sure DMA Enable is set to 0 before tuning */
> +		mode = sdhci_readw(host, SDHCI_TRANSFER_MODE);
> +		if (mode & SDHCI_TRNS_DMA) {
> +			mode &= ~SDHCI_TRNS_DMA;
> +			sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
> +		}
    this code should be deleted and moved up. 
=====
> +
> +		sdhci_send_command(host, &cmd);
> +
> +		host->cmd = NULL;
> +		host->mrq = NULL;
> +
> +		spin_unlock(&host->lock);
> +		enable_irq(host->irq);
> +
> +		/* Wait for Buffer Read Ready interrupt */
> +		wait_event_interruptible_timeout(host->buf_ready_int,
> +					(host->tuning_done == 1),
> +					msecs_to_jiffies(50));
> +		disable_irq(host->irq);
> +		spin_lock(&host->lock);
> +
> +		if (!host->tuning_done) {
> +			printk(KERN_INFO DRIVER_NAME ": Tuning procedure"
> +				" failed, falling back to fixed sampling"
> +				" clock\n");
> +			ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +			ctrl &= ~SDHCI_CTRL_TUNED_CLK;
> +			ctrl &= ~SDHCI_CTRL_EXEC_TUNING;
> +			sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
> +
> +			err = -EIO;
> +			goto out;
> +		}
> +
> +		host->tuning_done = 0;
> +
> +		ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +		tuning_loop_counter--;
> +		timeout--;
> +		mdelay(1);
> +	} while (ctrl & SDHCI_CTRL_EXEC_TUNING);
> +
> +	/*
> +	 * The Host Driver has exhausted the maximum number of loops allowed,
> +	 * so use fixed sampling frequency.
> +	 */
> +	if (!tuning_loop_counter || !timeout) {
> +		ctrl &= ~SDHCI_CTRL_TUNED_CLK;
> +		sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
> +	} else {
> +		if (!(ctrl & SDHCI_CTRL_TUNED_CLK)) {
> +			printk(KERN_INFO DRIVER_NAME ": Tuning procedure"
> +				" failed, falling back to fixed sampling"
> +				" clock\n");
> +			err = -EIO;
> +		}
> +	}
> +
> +out:
> +	sdhci_mask_irqs(host, SDHCI_INT_DATA_AVAIL);
delete the above line
change to
	sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier);


> +	spin_unlock(&host->lock);
> +	enable_irq(host->irq);
> +
> +	return err;
> +}
> +
> static const struct mmc_host_ops sdhci_ops = {
> 	.request	= sdhci_request,
> 	.set_ios	= sdhci_set_ios,
> 	.get_ro		= sdhci_get_ro,
> 	.enable_sdio_irq = sdhci_enable_sdio_irq,
> 	.start_signal_voltage_switch	= sdhci_start_signal_voltage_switch,
> +	.execute_tuning			= sdhci_execute_tuning,
> };
> 
> /*****************************************************************************\
> @@ -1693,6 +1831,16 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
> {
> 	BUG_ON(intmask == 0);
> 
> +	/* CMD19 generates _only_ Buffer Read Ready interrupt */
> +	if (intmask & SDHCI_INT_DATA_AVAIL) {
> +		if (SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND))
> +		    == MMC_SEND_TUNING_BLOCK) {
> +			host->tuning_done = 1;
> +			wake_up(&host->buf_ready_int);
> +			return;
> +		}
> +	}
> +
> 	if (!host->data) {
> 		/*
> 		 * The "data complete" interrupt is also used to
> @@ -2129,6 +2277,10 @@ int sdhci_add_host(struct sdhci_host *host)
> 	if (caps[1] & SDHCI_SUPPORT_DDR50)
> 		mmc->caps |= MMC_CAP_UHS_DDR50;
> 
> +	/* Does the host needs tuning for SDR50? */
> +	if (caps[1] & SDHCI_USE_SDR50_TUNING)
> +		host->flags |= SDHCI_SDR50_NEEDS_TUNING;
> +
> 	/* Driver Type(s) (A, C, D) supported by the host */
> 	if (caps[1] & SDHCI_DRIVER_TYPE_A)
> 		mmc->caps |= MMC_CAP_DRIVER_TYPE_A;
> @@ -2282,6 +2434,9 @@ int sdhci_add_host(struct sdhci_host *host)
> 
> 	setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host);
> 
> +	if (host->version >= SDHCI_SPEC_300)
> +		init_waitqueue_head(&host->buf_ready_int);
> +
> 	ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
> 		mmc_hostname(mmc), host);
> 	if (ret)
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index aed1203..0b782db 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -161,6 +161,8 @@
> #define   SDHCI_CTRL_DRV_TYPE_A		0x0010
> #define   SDHCI_CTRL_DRV_TYPE_C		0x0020
> #define   SDHCI_CTRL_DRV_TYPE_D		0x0030
> +#define  SDHCI_CTRL_EXEC_TUNING		0x0040
> +#define  SDHCI_CTRL_TUNED_CLK		0x0080
> #define  SDHCI_CTRL_PRESET_VAL_ENABLE	0x8000
> 
> #define SDHCI_CAPABILITIES	0x40
> @@ -188,6 +190,7 @@
> #define  SDHCI_DRIVER_TYPE_A	0x00000010
> #define  SDHCI_DRIVER_TYPE_C	0x00000020
> #define  SDHCI_DRIVER_TYPE_D	0x00000040
> +#define  SDHCI_USE_SDR50_TUNING	0x00002000
> 
> #define SDHCI_CAPABILITIES_1	0x44
> 
> diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
> index 52b5dc9..ca7007f 100644
> --- a/include/linux/mmc/host.h
> +++ b/include/linux/mmc/host.h
> @@ -136,6 +136,7 @@ struct mmc_host_ops {
> 	void	(*init_card)(struct mmc_host *host, struct mmc_card *card);
> 
> 	int	(*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios);
> +	int	(*execute_tuning)(struct mmc_host *host);
> };
> 
> struct mmc_card;
> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
> index 373b2bf..9fa5a73 100644
> --- a/include/linux/mmc/mmc.h
> +++ b/include/linux/mmc/mmc.h
> @@ -50,6 +50,7 @@
> #define MMC_SET_BLOCKLEN         16   /* ac   [31:0] block len   R1  */
> #define MMC_READ_SINGLE_BLOCK    17   /* adtc [31:0] data addr   R1  */
> #define MMC_READ_MULTIPLE_BLOCK  18   /* adtc [31:0] data addr   R1  */
> +#define MMC_SEND_TUNING_BLOCK    19   /* adtc                    R1  */
> 
>   /* class 3 */
> #define MMC_WRITE_DAT_UNTIL_STOP 20   /* adtc [31:0] data addr   R1  */
> diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h
> index 83bd9f7..26b6278 100644
> --- a/include/linux/mmc/sdhci.h
> +++ b/include/linux/mmc/sdhci.h
> @@ -109,6 +109,7 @@ struct sdhci_host {
> #define SDHCI_USE_ADMA		(1<<1)	/* Host is ADMA capable */
> #define SDHCI_REQ_USE_DMA	(1<<2)	/* Use DMA for this req. */
> #define SDHCI_DEVICE_DEAD	(1<<3)	/* Device unresponsive */
> +#define SDHCI_SDR50_NEEDS_TUNING (1<<4)	/* SDR50 needs tuning */
> 
> 	unsigned int version;	/* SDHCI spec. version */
> 
> @@ -145,6 +146,9 @@ struct sdhci_host {
> 	unsigned int            ocr_avail_sd;
> 	unsigned int            ocr_avail_mmc;
> 
> +	wait_queue_head_t	buf_ready_int;	/* Waitqueue for Buffer Read Ready interrupt */
> +	unsigned int		tuning_done;	/* Condition flag set when CMD19 succeeds */
> +
> 	unsigned long private[0] ____cacheline_aligned;
> };
> #endif /* __SDHCI_H */
> -- 
> 1.7.1
> 

--
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
Arindam Nath April 28, 2011, 6:02 a.m. UTC | #2
Hi Philip,


> -----Original Message-----
> From: Philip Rakity [mailto:prakity@marvell.com]
> Sent: Thursday, April 28, 2011 12:15 AM
> To: Nath, Arindam
> Cc: cjb@laptop.org; linux-mmc@vger.kernel.org; subhashj@codeaurora.org;
> zhangfei.gao@gmail.com; Su, Henry; Lu, Aaron; anath.amd@gmail.com
> Subject: Re: [PATCH v3 08/11] mmc: sd: add support for tuning during
> uhs initialization
> 
> 

[...]

> > @@ -1477,12 +1481,146 @@ static int
> sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> > 		return 0;
> > }
> >
> > +static int sdhci_execute_tuning(struct mmc_host *mmc)
> > +{
> > +	struct sdhci_host *host;
> > +	u16 ctrl;
> 	 	u32 ier;
> > +	int tuning_loop_counter = MAX_TUNING_LOOP;
> > +	unsigned long timeout;
> > +	int err = 0;
> > +
> > +	host = mmc_priv(mmc);
> > +
> > +	disable_irq(host->irq);
> > +	spin_lock(&host->lock);
> 
> is this needed ?  I believe we should be locked from the core/ layer.

Yes, this is needed. Please note that sdhci_execute_tuning() is not only invoked during initialization, but also when re-tuning timer expires.

> > +
> > +	ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> > +
> > +	/*
> > +	 * Host Controller needs tuning only in case of SDR104 mode
> > +	 * and for SDR50 mode when Use Tuning for SDR50 is set in
> > +	 * Capabilities register.
> > +	 */
> > +	if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) ||
> > +	    (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) &&
> > +	    (host->flags & SDHCI_SDR50_NEEDS_TUNING)))
> > +		ctrl |= SDHCI_CTRL_EXEC_TUNING;
> > +	else {
> > +		spin_unlock(&host->lock);
> > +		enable_irq(host->irq);
> > +		return 0;
> > +	}
> 
> 
> based on exchanges with SD Host Chair we need to set block size and
> transfer mode.
> Directly wriiting TRANSFER_MODE clears DMA bit.  No DMA is needed for
> SDHCI_INT_DATA_AVAIL
> to be signaled.
> 
> Suggest
> 
> > +	if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) ||
> > +	    (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) &&
> > +	    (host->flags & SDHCI_SDR50_NEEDS_TUNING))) {
> 			ctrl |= SDHCI_CTRL_EXEC_TUNING;
> 			sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64),
> SDHCI_BLOCK_SIZE);
> 			sdhci_writew(host, SDHCI_TRNS_READ,
> SDHCI_TRANSFER_MODE);
> 	} else {
> > +		spin_unlock(&host->lock);
> > +		enable_irq(host->irq);
> > +		return 0;
> > +	}
> 

This suggestion looks okay to me.

> 
> > +
> > +	sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
> > +
> > +	/*
> > +	 * As per the Host Controller spec v3.00, tuning command
> > +	 * generates Buffer Read Ready interrupt, so enable that.
> > +	 */
> > +	sdhci_unmask_irqs(host, SDHCI_INT_DATA_AVAIL);
> 
> delete line above -- it keeps the other interrupts enabled but we
> should only
> receive the DATA AVAIL interrupt.  We should protect against a bad
> controller
> 
> change to
> 
> >> 	ier = sdhci_readl(host, SDHCI_INT_ENABLE);
> >> 	sdhci_clear_set_irqs(host, ier, SDHCI_INT_DATA_AVAIL);
> 
> 
> This will ONLY enable SDHCI_INT_DATA_AVAIL.

Even though the spec clearly mentions that,

"While the tuning sequence is being performed, the Host Controller does not generate interrupts (including Command Complete) except Buffer Read Ready and CMD19 response errors are not indicated."

I think your suggestion would make sense of buggy controllers.

> >
> >
> > +
> > +	/*
> > +	 * Issue CMD19 repeatedly till Execute Tuning is set to 0 or the
> number
> > +	 * of loops reaches 40 times or a timeout of 150ms occurs.
> > +	 */
> > +	timeout = 150;
> > +	do {
> > +		u16 mode;
> > +		struct mmc_command cmd;
> > +		struct mmc_request mrq;
> > +
> > +		if (!tuning_loop_counter && !timeout)
> > +			break;
> > +
> > +		memset(&cmd, 0, sizeof(struct mmc_command));
> > +		cmd.opcode = MMC_SEND_TUNING_BLOCK;
> > +		cmd.arg = 0;
> > +		cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
> > +
> > +		memset(&cmd.resp, 0, sizeof(cmd.resp));
> > +		cmd.retries = 0;
> > +
> > +		cmd.data = NULL;
> > +		cmd.error = 0;
> > +
> > +		memset(&mrq, 0, sizeof(struct mmc_request));
> > +		mrq.cmd = &cmd;
> > +		host->mrq = &mrq;
> > +
> 
> =====
> > +		/* Make sure DMA Enable is set to 0 before tuning */
> > +		mode = sdhci_readw(host, SDHCI_TRANSFER_MODE);
> > +		if (mode & SDHCI_TRNS_DMA) {
> > +			mode &= ~SDHCI_TRNS_DMA;
> > +			sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
> > +		}
>     this code should be deleted and moved up.

IMO, it should better stay here. Even though there can be no data transfer commands during tuning procedure, but there can still be commands using CMD lines only, so there is a possibility of interleaving. In that case, it's better to program TRANSFER_MODE register every time before sending CMD19. Please consider re-tuning mode 1.

Thanks,
Arindam



--
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
Ryan Liu April 28, 2011, 8:29 a.m. UTC | #3
Hi list,

Maybe some simple questions, but I do not find good answers from google.
The timer is setup in sdhci_add_host, and modified to 10s later in
sdhci_send_command.
1. This timer seems to bu used to indicate that host controller does not
send IRQ to the cpu core(ex. ARM) after 10s since sdhci_send_command. Am
I right?
2. Why is 10s? Isn't it too long? User space applications may produce
large MMC requests to the MMC queue.
    Also, cancle the requests will waste much time. The user may think
the device is dead.
Thanks.

Ryan
--
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/sd.c b/drivers/mmc/core/sd.c
index 1594e51..f3ecdef 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -623,6 +623,12 @@  static int mmc_sd_init_uhs_card(struct mmc_card *card)
 
 	/* Set current limit for the card */
 	err = sd_set_current_limit(card, status);
+	if (err)
+		goto out;
+
+	/* SPI mode doesn't define CMD19 */
+	if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning)
+		err = card->host->ops->execute_tuning(card->host);
 
 out:
 	kfree(status);
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 9141c5b..9065157 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -38,6 +38,8 @@ 
 #define SDHCI_USE_LEDS_CLASS
 #endif
 
+#define MAX_TUNING_LOOP 40
+
 static unsigned int debug_quirks = 0;
 
 static void sdhci_finish_data(struct sdhci_host *);
@@ -962,7 +964,9 @@  static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
 		flags |= SDHCI_CMD_CRC;
 	if (cmd->flags & MMC_RSP_OPCODE)
 		flags |= SDHCI_CMD_INDEX;
-	if (cmd->data)
+
+	/* CMD19 is special in that the Data Present Select should be set */
+	if (cmd->data || (cmd->opcode == MMC_SEND_TUNING_BLOCK))
 		flags |= SDHCI_CMD_DATA;
 
 	sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
@@ -1477,12 +1481,146 @@  static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
 		return 0;
 }
 
+static int sdhci_execute_tuning(struct mmc_host *mmc)
+{
+	struct sdhci_host *host;
+	u16 ctrl;
+	int tuning_loop_counter = MAX_TUNING_LOOP;
+	unsigned long timeout;
+	int err = 0;
+
+	host = mmc_priv(mmc);
+
+	disable_irq(host->irq);
+	spin_lock(&host->lock);
+
+	ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+
+	/*
+	 * Host Controller needs tuning only in case of SDR104 mode
+	 * and for SDR50 mode when Use Tuning for SDR50 is set in
+	 * Capabilities register.
+	 */
+	if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) ||
+	    (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) &&
+	    (host->flags & SDHCI_SDR50_NEEDS_TUNING)))
+		ctrl |= SDHCI_CTRL_EXEC_TUNING;
+	else {
+		spin_unlock(&host->lock);
+		enable_irq(host->irq);
+		return 0;
+	}
+
+	sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+
+	/*
+	 * As per the Host Controller spec v3.00, tuning command
+	 * generates Buffer Read Ready interrupt, so enable that.
+	 */
+	sdhci_unmask_irqs(host, SDHCI_INT_DATA_AVAIL);
+
+	/*
+	 * Issue CMD19 repeatedly till Execute Tuning is set to 0 or the number
+	 * of loops reaches 40 times or a timeout of 150ms occurs.
+	 */
+	timeout = 150;
+	do {
+		u16 mode;
+		struct mmc_command cmd;
+		struct mmc_request mrq;
+
+		if (!tuning_loop_counter && !timeout)
+			break;
+
+		memset(&cmd, 0, sizeof(struct mmc_command));
+		cmd.opcode = MMC_SEND_TUNING_BLOCK;
+		cmd.arg = 0;
+		cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+		memset(&cmd.resp, 0, sizeof(cmd.resp));
+		cmd.retries = 0;
+
+		cmd.data = NULL;
+		cmd.error = 0;
+
+		memset(&mrq, 0, sizeof(struct mmc_request));
+		mrq.cmd = &cmd;
+		host->mrq = &mrq;
+
+		/* Make sure DMA Enable is set to 0 before tuning */
+		mode = sdhci_readw(host, SDHCI_TRANSFER_MODE);
+		if (mode & SDHCI_TRNS_DMA) {
+			mode &= ~SDHCI_TRNS_DMA;
+			sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
+		}
+
+		sdhci_send_command(host, &cmd);
+
+		host->cmd = NULL;
+		host->mrq = NULL;
+
+		spin_unlock(&host->lock);
+		enable_irq(host->irq);
+
+		/* Wait for Buffer Read Ready interrupt */
+		wait_event_interruptible_timeout(host->buf_ready_int,
+					(host->tuning_done == 1),
+					msecs_to_jiffies(50));
+		disable_irq(host->irq);
+		spin_lock(&host->lock);
+
+		if (!host->tuning_done) {
+			printk(KERN_INFO DRIVER_NAME ": Tuning procedure"
+				" failed, falling back to fixed sampling"
+				" clock\n");
+			ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+			ctrl &= ~SDHCI_CTRL_TUNED_CLK;
+			ctrl &= ~SDHCI_CTRL_EXEC_TUNING;
+			sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+
+			err = -EIO;
+			goto out;
+		}
+
+		host->tuning_done = 0;
+
+		ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+		tuning_loop_counter--;
+		timeout--;
+		mdelay(1);
+	} while (ctrl & SDHCI_CTRL_EXEC_TUNING);
+
+	/*
+	 * The Host Driver has exhausted the maximum number of loops allowed,
+	 * so use fixed sampling frequency.
+	 */
+	if (!tuning_loop_counter || !timeout) {
+		ctrl &= ~SDHCI_CTRL_TUNED_CLK;
+		sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+	} else {
+		if (!(ctrl & SDHCI_CTRL_TUNED_CLK)) {
+			printk(KERN_INFO DRIVER_NAME ": Tuning procedure"
+				" failed, falling back to fixed sampling"
+				" clock\n");
+			err = -EIO;
+		}
+	}
+
+out:
+	sdhci_mask_irqs(host, SDHCI_INT_DATA_AVAIL);
+	spin_unlock(&host->lock);
+	enable_irq(host->irq);
+
+	return err;
+}
+
 static const struct mmc_host_ops sdhci_ops = {
 	.request	= sdhci_request,
 	.set_ios	= sdhci_set_ios,
 	.get_ro		= sdhci_get_ro,
 	.enable_sdio_irq = sdhci_enable_sdio_irq,
 	.start_signal_voltage_switch	= sdhci_start_signal_voltage_switch,
+	.execute_tuning			= sdhci_execute_tuning,
 };
 
 /*****************************************************************************\
@@ -1693,6 +1831,16 @@  static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
 {
 	BUG_ON(intmask == 0);
 
+	/* CMD19 generates _only_ Buffer Read Ready interrupt */
+	if (intmask & SDHCI_INT_DATA_AVAIL) {
+		if (SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND))
+		    == MMC_SEND_TUNING_BLOCK) {
+			host->tuning_done = 1;
+			wake_up(&host->buf_ready_int);
+			return;
+		}
+	}
+
 	if (!host->data) {
 		/*
 		 * The "data complete" interrupt is also used to
@@ -2129,6 +2277,10 @@  int sdhci_add_host(struct sdhci_host *host)
 	if (caps[1] & SDHCI_SUPPORT_DDR50)
 		mmc->caps |= MMC_CAP_UHS_DDR50;
 
+	/* Does the host needs tuning for SDR50? */
+	if (caps[1] & SDHCI_USE_SDR50_TUNING)
+		host->flags |= SDHCI_SDR50_NEEDS_TUNING;
+
 	/* Driver Type(s) (A, C, D) supported by the host */
 	if (caps[1] & SDHCI_DRIVER_TYPE_A)
 		mmc->caps |= MMC_CAP_DRIVER_TYPE_A;
@@ -2282,6 +2434,9 @@  int sdhci_add_host(struct sdhci_host *host)
 
 	setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host);
 
+	if (host->version >= SDHCI_SPEC_300)
+		init_waitqueue_head(&host->buf_ready_int);
+
 	ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
 		mmc_hostname(mmc), host);
 	if (ret)
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index aed1203..0b782db 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -161,6 +161,8 @@ 
 #define   SDHCI_CTRL_DRV_TYPE_A		0x0010
 #define   SDHCI_CTRL_DRV_TYPE_C		0x0020
 #define   SDHCI_CTRL_DRV_TYPE_D		0x0030
+#define  SDHCI_CTRL_EXEC_TUNING		0x0040
+#define  SDHCI_CTRL_TUNED_CLK		0x0080
 #define  SDHCI_CTRL_PRESET_VAL_ENABLE	0x8000
 
 #define SDHCI_CAPABILITIES	0x40
@@ -188,6 +190,7 @@ 
 #define  SDHCI_DRIVER_TYPE_A	0x00000010
 #define  SDHCI_DRIVER_TYPE_C	0x00000020
 #define  SDHCI_DRIVER_TYPE_D	0x00000040
+#define  SDHCI_USE_SDR50_TUNING	0x00002000
 
 #define SDHCI_CAPABILITIES_1	0x44
 
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 52b5dc9..ca7007f 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -136,6 +136,7 @@  struct mmc_host_ops {
 	void	(*init_card)(struct mmc_host *host, struct mmc_card *card);
 
 	int	(*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios);
+	int	(*execute_tuning)(struct mmc_host *host);
 };
 
 struct mmc_card;
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
index 373b2bf..9fa5a73 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -50,6 +50,7 @@ 
 #define MMC_SET_BLOCKLEN         16   /* ac   [31:0] block len   R1  */
 #define MMC_READ_SINGLE_BLOCK    17   /* adtc [31:0] data addr   R1  */
 #define MMC_READ_MULTIPLE_BLOCK  18   /* adtc [31:0] data addr   R1  */
+#define MMC_SEND_TUNING_BLOCK    19   /* adtc                    R1  */
 
   /* class 3 */
 #define MMC_WRITE_DAT_UNTIL_STOP 20   /* adtc [31:0] data addr   R1  */
diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h
index 83bd9f7..26b6278 100644
--- a/include/linux/mmc/sdhci.h
+++ b/include/linux/mmc/sdhci.h
@@ -109,6 +109,7 @@  struct sdhci_host {
 #define SDHCI_USE_ADMA		(1<<1)	/* Host is ADMA capable */
 #define SDHCI_REQ_USE_DMA	(1<<2)	/* Use DMA for this req. */
 #define SDHCI_DEVICE_DEAD	(1<<3)	/* Device unresponsive */
+#define SDHCI_SDR50_NEEDS_TUNING (1<<4)	/* SDR50 needs tuning */
 
 	unsigned int version;	/* SDHCI spec. version */
 
@@ -145,6 +146,9 @@  struct sdhci_host {
 	unsigned int            ocr_avail_sd;
 	unsigned int            ocr_avail_mmc;
 
+	wait_queue_head_t	buf_ready_int;	/* Waitqueue for Buffer Read Ready interrupt */
+	unsigned int		tuning_done;	/* Condition flag set when CMD19 succeeds */
+
 	unsigned long private[0] ____cacheline_aligned;
 };
 #endif /* __SDHCI_H */