mmc: Add bcm2835 sdhost controller
The BCM2835 family of SoCs has 2 different SD controllers: One based on the SDHCI spec and a custom, home-grown one. This patch implements a driver for the latter based on the Linux driver. This is needed so that we can make use of device trees that assume driver presence of both SD controllers. Signed-off-by: Alexander Graf <agraf@suse.de>
This commit is contained in:
		
							parent
							
								
									caf2233b28
								
							
						
					
					
						commit
						c8a73a26d6
					
				|  | @ -97,6 +97,7 @@ S:	Orphaned (Since 2017-07) | ||||||
| F:	arch/arm/mach-bcm283x/ | F:	arch/arm/mach-bcm283x/ | ||||||
| F:	drivers/gpio/bcm2835_gpio.c | F:	drivers/gpio/bcm2835_gpio.c | ||||||
| F:	drivers/mmc/bcm2835_sdhci.c | F:	drivers/mmc/bcm2835_sdhci.c | ||||||
|  | F:	drivers/mmc/bcm2835_sdhost.c | ||||||
| F:	drivers/serial/serial_bcm283x_mu.c | F:	drivers/serial/serial_bcm283x_mu.c | ||||||
| F:	drivers/video/bcm2835.c | F:	drivers/video/bcm2835.c | ||||||
| F:	include/dm/platform_data/serial_bcm283x_mu.h | F:	include/dm/platform_data/serial_bcm283x_mu.h | ||||||
|  |  | ||||||
|  | @ -261,6 +261,20 @@ config MMC_UNIPHIER | ||||||
| 	  This selects support for the Matsushita SD/MMC Host Controller on | 	  This selects support for the Matsushita SD/MMC Host Controller on | ||||||
| 	  SocioNext UniPhier and Renesas RCar SoCs. | 	  SocioNext UniPhier and Renesas RCar SoCs. | ||||||
| 
 | 
 | ||||||
|  | config MMC_BCM2835 | ||||||
|  | 	bool "BCM2835 family custom SD/MMC Host Controller support" | ||||||
|  | 	depends on ARCH_BCM283X | ||||||
|  | 	depends on BLK && DM_MMC | ||||||
|  | 	depends on OF_CONTROL | ||||||
|  | 	default y | ||||||
|  | 	help | ||||||
|  | 	  This selects support for the custom SD host controller in the BCM2835 | ||||||
|  | 	  family of devices. | ||||||
|  | 
 | ||||||
|  | 	  If you have a BCM2835 platform with SD or MMC devices, say Y here. | ||||||
|  | 
 | ||||||
|  | 	  If unsure, say N. | ||||||
|  | 
 | ||||||
| config MMC_SANDBOX | config MMC_SANDBOX | ||||||
| 	bool "Sandbox MMC support" | 	bool "Sandbox MMC support" | ||||||
| 	depends on SANDBOX | 	depends on SANDBOX | ||||||
|  |  | ||||||
|  | @ -64,3 +64,4 @@ obj-$(CONFIG_MMC_SDHCI_ZYNQ)		+= zynq_sdhci.o | ||||||
| 
 | 
 | ||||||
| obj-$(CONFIG_MMC_SUNXI)			+= sunxi_mmc.o | obj-$(CONFIG_MMC_SUNXI)			+= sunxi_mmc.o | ||||||
| obj-$(CONFIG_MMC_UNIPHIER)		+= uniphier-sd.o | obj-$(CONFIG_MMC_UNIPHIER)		+= uniphier-sd.o | ||||||
|  | obj-$(CONFIG_MMC_BCM2835)		+= bcm2835_sdhost.o | ||||||
|  |  | ||||||
|  | @ -0,0 +1,979 @@ | ||||||
|  | /*
 | ||||||
|  |  * bcm2835 sdhost driver. | ||||||
|  |  * | ||||||
|  |  * The 2835 has two SD controllers: The Arasan sdhci controller | ||||||
|  |  * (supported by the iproc driver) and a custom sdhost controller | ||||||
|  |  * (supported by this driver). | ||||||
|  |  * | ||||||
|  |  * The sdhci controller supports both sdcard and sdio.  The sdhost | ||||||
|  |  * controller supports the sdcard only, but has better performance. | ||||||
|  |  * Also note that the rpi3 has sdio wifi, so driving the sdcard with | ||||||
|  |  * the sdhost controller allows to use the sdhci controller for wifi | ||||||
|  |  * support. | ||||||
|  |  * | ||||||
|  |  * The configuration is done by devicetree via pin muxing.  Both | ||||||
|  |  * SD controller are available on the same pins (2 pin groups = pin 22 | ||||||
|  |  * to 27 + pin 48 to 53).  So it's possible to use both SD controllers | ||||||
|  |  * at the same time with different pin groups. | ||||||
|  |  * | ||||||
|  |  * This code was ported to U-Boot by | ||||||
|  |  *  Alexander Graf <agraf@suse.de> | ||||||
|  |  * and is based on drivers/mmc/host/bcm2835.c in Linux which is written by | ||||||
|  |  *  Phil Elwell <phil@raspberrypi.org> | ||||||
|  |  *  Copyright (C) 2015-2016 Raspberry Pi (Trading) Ltd. | ||||||
|  |  * which is based on | ||||||
|  |  *  mmc-bcm2835.c by Gellert Weisz | ||||||
|  |  * which is, in turn, based on | ||||||
|  |  *  sdhci-bcm2708.c by Broadcom | ||||||
|  |  *  sdhci-bcm2835.c by Stephen Warren and Oleksandr Tymoshenko | ||||||
|  |  *  sdhci.c and sdhci-pci.c by Pierre Ossman | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier:    GPL-2.0 | ||||||
|  |  */ | ||||||
|  | #include <clk.h> | ||||||
|  | #include <common.h> | ||||||
|  | #include <dm.h> | ||||||
|  | #include <mmc.h> | ||||||
|  | #include <asm/arch/msg.h> | ||||||
|  | #include <asm/unaligned.h> | ||||||
|  | #include <linux/compat.h> | ||||||
|  | #include <linux/io.h> | ||||||
|  | #include <linux/iopoll.h> | ||||||
|  | #include <linux/sizes.h> | ||||||
|  | #include <mach/gpio.h> | ||||||
|  | #include <power/regulator.h> | ||||||
|  | 
 | ||||||
|  | DECLARE_GLOBAL_DATA_PTR; | ||||||
|  | 
 | ||||||
|  | #define msleep(a) udelay(a * 1000) | ||||||
|  | 
 | ||||||
|  | #define SDCMD  0x00 /* Command to SD card              - 16 R/W */ | ||||||
|  | #define SDARG  0x04 /* Argument to SD card             - 32 R/W */ | ||||||
|  | #define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */ | ||||||
|  | #define SDCDIV 0x0c /* Start value for clock divider   - 11 R/W */ | ||||||
|  | #define SDRSP0 0x10 /* SD card response (31:0)         - 32 R   */ | ||||||
|  | #define SDRSP1 0x14 /* SD card response (63:32)        - 32 R   */ | ||||||
|  | #define SDRSP2 0x18 /* SD card response (95:64)        - 32 R   */ | ||||||
|  | #define SDRSP3 0x1c /* SD card response (127:96)       - 32 R   */ | ||||||
|  | #define SDHSTS 0x20 /* SD host status                  - 11 R/W */ | ||||||
|  | #define SDVDD  0x30 /* SD card power control           -  1 R/W */ | ||||||
|  | #define SDEDM  0x34 /* Emergency Debug Mode            - 13 R/W */ | ||||||
|  | #define SDHCFG 0x38 /* Host configuration              -  2 R/W */ | ||||||
|  | #define SDHBCT 0x3c /* Host byte count (debug)         - 32 R/W */ | ||||||
|  | #define SDDATA 0x40 /* Data to/from SD card            - 32 R/W */ | ||||||
|  | #define SDHBLC 0x50 /* Host block count (SDIO/SDHC)    -  9 R/W */ | ||||||
|  | 
 | ||||||
|  | #define SDCMD_NEW_FLAG			0x8000 | ||||||
|  | #define SDCMD_FAIL_FLAG			0x4000 | ||||||
|  | #define SDCMD_BUSYWAIT			0x800 | ||||||
|  | #define SDCMD_NO_RESPONSE		0x400 | ||||||
|  | #define SDCMD_LONG_RESPONSE		0x200 | ||||||
|  | #define SDCMD_WRITE_CMD			0x80 | ||||||
|  | #define SDCMD_READ_CMD			0x40 | ||||||
|  | #define SDCMD_CMD_MASK			0x3f | ||||||
|  | 
 | ||||||
|  | #define SDCDIV_MAX_CDIV			0x7ff | ||||||
|  | 
 | ||||||
|  | #define SDHSTS_BUSY_IRPT		0x400 | ||||||
|  | #define SDHSTS_BLOCK_IRPT		0x200 | ||||||
|  | #define SDHSTS_SDIO_IRPT		0x100 | ||||||
|  | #define SDHSTS_REW_TIME_OUT		0x80 | ||||||
|  | #define SDHSTS_CMD_TIME_OUT		0x40 | ||||||
|  | #define SDHSTS_CRC16_ERROR		0x20 | ||||||
|  | #define SDHSTS_CRC7_ERROR		0x10 | ||||||
|  | #define SDHSTS_FIFO_ERROR		0x08 | ||||||
|  | #define SDHSTS_DATA_FLAG		0x01 | ||||||
|  | 
 | ||||||
|  | #define SDHSTS_CLEAR_MASK		(SDHSTS_BUSY_IRPT | \ | ||||||
|  | 					 SDHSTS_BLOCK_IRPT | \ | ||||||
|  | 					 SDHSTS_SDIO_IRPT | \ | ||||||
|  | 					 SDHSTS_REW_TIME_OUT | \ | ||||||
|  | 					 SDHSTS_CMD_TIME_OUT | \ | ||||||
|  | 					 SDHSTS_CRC16_ERROR | \ | ||||||
|  | 					 SDHSTS_CRC7_ERROR | \ | ||||||
|  | 					 SDHSTS_FIFO_ERROR) | ||||||
|  | 
 | ||||||
|  | #define SDHSTS_TRANSFER_ERROR_MASK	(SDHSTS_CRC7_ERROR | \ | ||||||
|  | 					 SDHSTS_CRC16_ERROR | \ | ||||||
|  | 					 SDHSTS_REW_TIME_OUT | \ | ||||||
|  | 					 SDHSTS_FIFO_ERROR) | ||||||
|  | 
 | ||||||
|  | #define SDHSTS_ERROR_MASK		(SDHSTS_CMD_TIME_OUT | \ | ||||||
|  | 					 SDHSTS_TRANSFER_ERROR_MASK) | ||||||
|  | 
 | ||||||
|  | #define SDHCFG_BUSY_IRPT_EN	BIT(10) | ||||||
|  | #define SDHCFG_BLOCK_IRPT_EN	BIT(8) | ||||||
|  | #define SDHCFG_SDIO_IRPT_EN	BIT(5) | ||||||
|  | #define SDHCFG_DATA_IRPT_EN	BIT(4) | ||||||
|  | #define SDHCFG_SLOW_CARD	BIT(3) | ||||||
|  | #define SDHCFG_WIDE_EXT_BUS	BIT(2) | ||||||
|  | #define SDHCFG_WIDE_INT_BUS	BIT(1) | ||||||
|  | #define SDHCFG_REL_CMD_LINE	BIT(0) | ||||||
|  | 
 | ||||||
|  | #define SDVDD_POWER_OFF		0 | ||||||
|  | #define SDVDD_POWER_ON		1 | ||||||
|  | 
 | ||||||
|  | #define SDEDM_FORCE_DATA_MODE	BIT(19) | ||||||
|  | #define SDEDM_CLOCK_PULSE	BIT(20) | ||||||
|  | #define SDEDM_BYPASS		BIT(21) | ||||||
|  | 
 | ||||||
|  | #define SDEDM_FIFO_FILL_SHIFT	4 | ||||||
|  | #define SDEDM_FIFO_FILL_MASK	0x1f | ||||||
|  | static u32 edm_fifo_fill(u32 edm) | ||||||
|  | { | ||||||
|  | 	return (edm >> SDEDM_FIFO_FILL_SHIFT) & SDEDM_FIFO_FILL_MASK; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #define SDEDM_WRITE_THRESHOLD_SHIFT	9 | ||||||
|  | #define SDEDM_READ_THRESHOLD_SHIFT	14 | ||||||
|  | #define SDEDM_THRESHOLD_MASK		0x1f | ||||||
|  | 
 | ||||||
|  | #define SDEDM_FSM_MASK		0xf | ||||||
|  | #define SDEDM_FSM_IDENTMODE	0x0 | ||||||
|  | #define SDEDM_FSM_DATAMODE	0x1 | ||||||
|  | #define SDEDM_FSM_READDATA	0x2 | ||||||
|  | #define SDEDM_FSM_WRITEDATA	0x3 | ||||||
|  | #define SDEDM_FSM_READWAIT	0x4 | ||||||
|  | #define SDEDM_FSM_READCRC	0x5 | ||||||
|  | #define SDEDM_FSM_WRITECRC	0x6 | ||||||
|  | #define SDEDM_FSM_WRITEWAIT1	0x7 | ||||||
|  | #define SDEDM_FSM_POWERDOWN	0x8 | ||||||
|  | #define SDEDM_FSM_POWERUP	0x9 | ||||||
|  | #define SDEDM_FSM_WRITESTART1	0xa | ||||||
|  | #define SDEDM_FSM_WRITESTART2	0xb | ||||||
|  | #define SDEDM_FSM_GENPULSES	0xc | ||||||
|  | #define SDEDM_FSM_WRITEWAIT2	0xd | ||||||
|  | #define SDEDM_FSM_STARTPOWDOWN	0xf | ||||||
|  | 
 | ||||||
|  | #define SDDATA_FIFO_WORDS	16 | ||||||
|  | 
 | ||||||
|  | #define FIFO_READ_THRESHOLD	4 | ||||||
|  | #define FIFO_WRITE_THRESHOLD	4 | ||||||
|  | #define SDDATA_FIFO_PIO_BURST	8 | ||||||
|  | 
 | ||||||
|  | #define SDHST_TIMEOUT_MAX_USEC	100000 | ||||||
|  | 
 | ||||||
|  | struct bcm2835_plat { | ||||||
|  | 	struct mmc_config cfg; | ||||||
|  | 	struct mmc mmc; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct bcm2835_host { | ||||||
|  | 	void __iomem		*ioaddr; | ||||||
|  | 	u32			phys_addr; | ||||||
|  | 
 | ||||||
|  | 	int			clock;		/* Current clock speed */ | ||||||
|  | 	unsigned int		max_clk;	/* Max possible freq */ | ||||||
|  | 	unsigned int		blocks;		/* remaining PIO blocks */ | ||||||
|  | 	int			irq;		/* Device IRQ */ | ||||||
|  | 
 | ||||||
|  | 	u32			ns_per_fifo_word; | ||||||
|  | 
 | ||||||
|  | 	/* cached registers */ | ||||||
|  | 	u32			hcfg; | ||||||
|  | 	u32			cdiv; | ||||||
|  | 
 | ||||||
|  | 	struct mmc_cmd	*cmd;		/* Current command */ | ||||||
|  | 	struct mmc_data		*data;		/* Current data request */ | ||||||
|  | 	bool			data_complete:1;/* Data finished before cmd */ | ||||||
|  | 	bool			use_busy:1;	/* Wait for busy interrupt */ | ||||||
|  | 	bool			wait_data_complete:1;	/* Wait for data */ | ||||||
|  | 
 | ||||||
|  | 	/* for threaded irq handler */ | ||||||
|  | 	bool			irq_block; | ||||||
|  | 	bool			irq_busy; | ||||||
|  | 	bool			irq_data; | ||||||
|  | 
 | ||||||
|  | 	struct udevice		*dev; | ||||||
|  | 	struct mmc		*mmc; | ||||||
|  | 	struct bcm2835_plat	*plat; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void bcm2835_dumpregs(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	dev_dbg(dev, "=========== REGISTER DUMP ===========\n"); | ||||||
|  | 	dev_dbg(dev, "SDCMD  0x%08x\n", readl(host->ioaddr + SDCMD)); | ||||||
|  | 	dev_dbg(dev, "SDARG  0x%08x\n", readl(host->ioaddr + SDARG)); | ||||||
|  | 	dev_dbg(dev, "SDTOUT 0x%08x\n", readl(host->ioaddr + SDTOUT)); | ||||||
|  | 	dev_dbg(dev, "SDCDIV 0x%08x\n", readl(host->ioaddr + SDCDIV)); | ||||||
|  | 	dev_dbg(dev, "SDRSP0 0x%08x\n", readl(host->ioaddr + SDRSP0)); | ||||||
|  | 	dev_dbg(dev, "SDRSP1 0x%08x\n", readl(host->ioaddr + SDRSP1)); | ||||||
|  | 	dev_dbg(dev, "SDRSP2 0x%08x\n", readl(host->ioaddr + SDRSP2)); | ||||||
|  | 	dev_dbg(dev, "SDRSP3 0x%08x\n", readl(host->ioaddr + SDRSP3)); | ||||||
|  | 	dev_dbg(dev, "SDHSTS 0x%08x\n", readl(host->ioaddr + SDHSTS)); | ||||||
|  | 	dev_dbg(dev, "SDVDD  0x%08x\n", readl(host->ioaddr + SDVDD)); | ||||||
|  | 	dev_dbg(dev, "SDEDM  0x%08x\n", readl(host->ioaddr + SDEDM)); | ||||||
|  | 	dev_dbg(dev, "SDHCFG 0x%08x\n", readl(host->ioaddr + SDHCFG)); | ||||||
|  | 	dev_dbg(dev, "SDHBCT 0x%08x\n", readl(host->ioaddr + SDHBCT)); | ||||||
|  | 	dev_dbg(dev, "SDHBLC 0x%08x\n", readl(host->ioaddr + SDHBLC)); | ||||||
|  | 	dev_dbg(dev, "===========================================\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_reset_internal(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	u32 temp; | ||||||
|  | 
 | ||||||
|  | 	writel(SDVDD_POWER_OFF, host->ioaddr + SDVDD); | ||||||
|  | 	writel(0, host->ioaddr + SDCMD); | ||||||
|  | 	writel(0, host->ioaddr + SDARG); | ||||||
|  | 	/* Set timeout to a big enough value so we don't hit it */ | ||||||
|  | 	writel(0xf00000, host->ioaddr + SDTOUT); | ||||||
|  | 	writel(0, host->ioaddr + SDCDIV); | ||||||
|  | 	/* Clear status register */ | ||||||
|  | 	writel(SDHSTS_CLEAR_MASK, host->ioaddr + SDHSTS); | ||||||
|  | 	writel(0, host->ioaddr + SDHCFG); | ||||||
|  | 	writel(0, host->ioaddr + SDHBCT); | ||||||
|  | 	writel(0, host->ioaddr + SDHBLC); | ||||||
|  | 
 | ||||||
|  | 	/* Limit fifo usage due to silicon bug */ | ||||||
|  | 	temp = readl(host->ioaddr + SDEDM); | ||||||
|  | 	temp &= ~((SDEDM_THRESHOLD_MASK << SDEDM_READ_THRESHOLD_SHIFT) | | ||||||
|  | 		  (SDEDM_THRESHOLD_MASK << SDEDM_WRITE_THRESHOLD_SHIFT)); | ||||||
|  | 	temp |= (FIFO_READ_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT) | | ||||||
|  | 		(FIFO_WRITE_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT); | ||||||
|  | 	writel(temp, host->ioaddr + SDEDM); | ||||||
|  | 	/* Wait for FIFO threshold to populate */ | ||||||
|  | 	msleep(20); | ||||||
|  | 	writel(SDVDD_POWER_ON, host->ioaddr + SDVDD); | ||||||
|  | 	/* Wait for all components to go through power on cycle */ | ||||||
|  | 	msleep(20); | ||||||
|  | 	host->clock = 0; | ||||||
|  | 	writel(host->hcfg, host->ioaddr + SDHCFG); | ||||||
|  | 	writel(host->cdiv, host->ioaddr + SDCDIV); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_finish_command(struct bcm2835_host *host); | ||||||
|  | 
 | ||||||
|  | static void bcm2835_wait_transfer_complete(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	int timediff; | ||||||
|  | 	u32 alternate_idle; | ||||||
|  | 
 | ||||||
|  | 	alternate_idle = (host->data->flags & MMC_DATA_READ) ? | ||||||
|  | 		SDEDM_FSM_READWAIT : SDEDM_FSM_WRITESTART1; | ||||||
|  | 
 | ||||||
|  | 	timediff = 0; | ||||||
|  | 
 | ||||||
|  | 	while (1) { | ||||||
|  | 		u32 edm, fsm; | ||||||
|  | 
 | ||||||
|  | 		edm = readl(host->ioaddr + SDEDM); | ||||||
|  | 		fsm = edm & SDEDM_FSM_MASK; | ||||||
|  | 
 | ||||||
|  | 		if ((fsm == SDEDM_FSM_IDENTMODE) || | ||||||
|  | 		    (fsm == SDEDM_FSM_DATAMODE)) | ||||||
|  | 			break; | ||||||
|  | 		if (fsm == alternate_idle) { | ||||||
|  | 			writel(edm | SDEDM_FORCE_DATA_MODE, | ||||||
|  | 			       host->ioaddr + SDEDM); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/* Error out after 100000 register reads (~1s) */ | ||||||
|  | 		if (timediff++ == 100000) { | ||||||
|  | 			dev_err(host->dev, | ||||||
|  | 				"wait_transfer_complete - still waiting after %d retries\n", | ||||||
|  | 				timediff); | ||||||
|  | 			bcm2835_dumpregs(host); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_transfer_block_pio(struct bcm2835_host *host, bool is_read) | ||||||
|  | { | ||||||
|  | 	struct mmc_data *data = host->data; | ||||||
|  | 	size_t blksize = data->blocksize; | ||||||
|  | 	int copy_words; | ||||||
|  | 	u32 hsts = 0; | ||||||
|  | 	u32 *buf; | ||||||
|  | 
 | ||||||
|  | 	if (blksize % sizeof(u32)) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	buf = is_read ? (u32 *)data->dest : (u32 *)data->src; | ||||||
|  | 
 | ||||||
|  | 	if (is_read) | ||||||
|  | 		data->dest += blksize; | ||||||
|  | 	else | ||||||
|  | 		data->src += blksize; | ||||||
|  | 
 | ||||||
|  | 	copy_words = blksize / sizeof(u32); | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * Copy all contents from/to the FIFO as far as it reaches, | ||||||
|  | 	 * then wait for it to fill/empty again and rewind. | ||||||
|  | 	 */ | ||||||
|  | 	while (copy_words) { | ||||||
|  | 		int burst_words, words; | ||||||
|  | 		u32 edm; | ||||||
|  | 
 | ||||||
|  | 		burst_words = min(SDDATA_FIFO_PIO_BURST, copy_words); | ||||||
|  | 		edm = readl(host->ioaddr + SDEDM); | ||||||
|  | 		if (is_read) | ||||||
|  | 			words = edm_fifo_fill(edm); | ||||||
|  | 		else | ||||||
|  | 			words = SDDATA_FIFO_WORDS - edm_fifo_fill(edm); | ||||||
|  | 
 | ||||||
|  | 		if (words < burst_words) { | ||||||
|  | 			int fsm_state = (edm & SDEDM_FSM_MASK); | ||||||
|  | 
 | ||||||
|  | 			if ((is_read && | ||||||
|  | 			     (fsm_state != SDEDM_FSM_READDATA && | ||||||
|  | 			      fsm_state != SDEDM_FSM_READWAIT && | ||||||
|  | 			      fsm_state != SDEDM_FSM_READCRC)) || | ||||||
|  | 			    (!is_read && | ||||||
|  | 			     (fsm_state != SDEDM_FSM_WRITEDATA && | ||||||
|  | 			      fsm_state != SDEDM_FSM_WRITESTART1 && | ||||||
|  | 			      fsm_state != SDEDM_FSM_WRITESTART2))) { | ||||||
|  | 				hsts = readl(host->ioaddr + SDHSTS); | ||||||
|  | 				printf("fsm %x, hsts %08x\n", fsm_state, hsts); | ||||||
|  | 				if (hsts & SDHSTS_ERROR_MASK) | ||||||
|  | 					break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			continue; | ||||||
|  | 		} else if (words > copy_words) { | ||||||
|  | 			words = copy_words; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		copy_words -= words; | ||||||
|  | 
 | ||||||
|  | 		/* Copy current chunk to/from the FIFO */ | ||||||
|  | 		while (words) { | ||||||
|  | 			if (is_read) | ||||||
|  | 				*(buf++) = readl(host->ioaddr + SDDATA); | ||||||
|  | 			else | ||||||
|  | 				writel(*(buf++), host->ioaddr + SDDATA); | ||||||
|  | 			words--; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_transfer_pio(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	u32 sdhsts; | ||||||
|  | 	bool is_read; | ||||||
|  | 	int ret = 0; | ||||||
|  | 
 | ||||||
|  | 	is_read = (host->data->flags & MMC_DATA_READ) != 0; | ||||||
|  | 	ret = bcm2835_transfer_block_pio(host, is_read); | ||||||
|  | 
 | ||||||
|  | 	if (host->wait_data_complete) | ||||||
|  | 		bcm2835_wait_transfer_complete(host); | ||||||
|  | 
 | ||||||
|  | 	sdhsts = readl(host->ioaddr + SDHSTS); | ||||||
|  | 	if (sdhsts & (SDHSTS_CRC16_ERROR | | ||||||
|  | 		      SDHSTS_CRC7_ERROR | | ||||||
|  | 		      SDHSTS_FIFO_ERROR)) { | ||||||
|  | 		printf("%s transfer error - HSTS %08x\n", | ||||||
|  | 		       is_read ? "read" : "write", sdhsts); | ||||||
|  | 		ret =  -EILSEQ; | ||||||
|  | 	} else if ((sdhsts & (SDHSTS_CMD_TIME_OUT | | ||||||
|  | 			      SDHSTS_REW_TIME_OUT))) { | ||||||
|  | 		printf("%s timeout error - HSTS %08x\n", | ||||||
|  | 		       is_read ? "read" : "write", sdhsts); | ||||||
|  | 		ret = -ETIMEDOUT; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_set_transfer_irqs(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	u32 all_irqs = SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN | | ||||||
|  | 		SDHCFG_BUSY_IRPT_EN; | ||||||
|  | 
 | ||||||
|  | 	host->hcfg = (host->hcfg & ~all_irqs) | | ||||||
|  | 		SDHCFG_DATA_IRPT_EN | | ||||||
|  | 		SDHCFG_BUSY_IRPT_EN; | ||||||
|  | 
 | ||||||
|  | 	writel(host->hcfg, host->ioaddr + SDHCFG); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static | ||||||
|  | void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_cmd *cmd, | ||||||
|  | 			  struct mmc_data *data) | ||||||
|  | { | ||||||
|  | 	WARN_ON(host->data); | ||||||
|  | 
 | ||||||
|  | 	host->data = data; | ||||||
|  | 	if (!data) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	host->wait_data_complete = cmd->cmdidx != MMC_CMD_READ_MULTIPLE_BLOCK; | ||||||
|  | 	host->data_complete = false; | ||||||
|  | 
 | ||||||
|  | 	/* Use PIO */ | ||||||
|  | 	host->blocks = data->blocks; | ||||||
|  | 
 | ||||||
|  | 	bcm2835_set_transfer_irqs(host); | ||||||
|  | 
 | ||||||
|  | 	writel(data->blocksize, host->ioaddr + SDHBCT); | ||||||
|  | 	writel(data->blocks, host->ioaddr + SDHBLC); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static u32 bcm2835_read_wait_sdcmd(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	u32 value; | ||||||
|  | 	int ret; | ||||||
|  | 	int timeout_us = SDHST_TIMEOUT_MAX_USEC; | ||||||
|  | 
 | ||||||
|  | 	ret = readl_poll_timeout(host->ioaddr + SDCMD, value, | ||||||
|  | 				 !(value & SDCMD_NEW_FLAG), timeout_us); | ||||||
|  | 	if (ret == -ETIMEDOUT) | ||||||
|  | 		printf("%s: timeout (%d us)\n", __func__, timeout_us); | ||||||
|  | 
 | ||||||
|  | 	return value; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_send_command(struct bcm2835_host *host, struct mmc_cmd *cmd, | ||||||
|  | 				struct mmc_data *data) | ||||||
|  | { | ||||||
|  | 	u32 sdcmd, sdhsts; | ||||||
|  | 
 | ||||||
|  | 	WARN_ON(host->cmd); | ||||||
|  | 
 | ||||||
|  | 	if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) { | ||||||
|  | 		printf("unsupported response type!\n"); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sdcmd = bcm2835_read_wait_sdcmd(host); | ||||||
|  | 	if (sdcmd & SDCMD_NEW_FLAG) { | ||||||
|  | 		printf("previous command never completed.\n"); | ||||||
|  | 		bcm2835_dumpregs(host); | ||||||
|  | 		return -EBUSY; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	host->cmd = cmd; | ||||||
|  | 
 | ||||||
|  | 	/* Clear any error flags */ | ||||||
|  | 	sdhsts = readl(host->ioaddr + SDHSTS); | ||||||
|  | 	if (sdhsts & SDHSTS_ERROR_MASK) | ||||||
|  | 		writel(sdhsts, host->ioaddr + SDHSTS); | ||||||
|  | 
 | ||||||
|  | 	bcm2835_prepare_data(host, cmd, data); | ||||||
|  | 
 | ||||||
|  | 	writel(cmd->cmdarg, host->ioaddr + SDARG); | ||||||
|  | 
 | ||||||
|  | 	sdcmd = cmd->cmdidx & SDCMD_CMD_MASK; | ||||||
|  | 
 | ||||||
|  | 	host->use_busy = false; | ||||||
|  | 	if (!(cmd->resp_type & MMC_RSP_PRESENT)) { | ||||||
|  | 		sdcmd |= SDCMD_NO_RESPONSE; | ||||||
|  | 	} else { | ||||||
|  | 		if (cmd->resp_type & MMC_RSP_136) | ||||||
|  | 			sdcmd |= SDCMD_LONG_RESPONSE; | ||||||
|  | 		if (cmd->resp_type & MMC_RSP_BUSY) { | ||||||
|  | 			sdcmd |= SDCMD_BUSYWAIT; | ||||||
|  | 			host->use_busy = true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (data) { | ||||||
|  | 		if (data->flags & MMC_DATA_WRITE) | ||||||
|  | 			sdcmd |= SDCMD_WRITE_CMD; | ||||||
|  | 		if (data->flags & MMC_DATA_READ) | ||||||
|  | 			sdcmd |= SDCMD_READ_CMD; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	writel(sdcmd | SDCMD_NEW_FLAG, host->ioaddr + SDCMD); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_transfer_complete(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	int ret = 0; | ||||||
|  | 
 | ||||||
|  | 	WARN_ON(!host->data_complete); | ||||||
|  | 
 | ||||||
|  | 	host->data = NULL; | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_finish_data(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN); | ||||||
|  | 	writel(host->hcfg, host->ioaddr + SDHCFG); | ||||||
|  | 
 | ||||||
|  | 	host->data_complete = true; | ||||||
|  | 
 | ||||||
|  | 	if (host->cmd) { | ||||||
|  | 		/* Data managed to finish before the
 | ||||||
|  | 		 * command completed. Make sure we do | ||||||
|  | 		 * things in the proper order. | ||||||
|  | 		 */ | ||||||
|  | 		dev_dbg(dev, "Finished early - HSTS %08x\n", | ||||||
|  | 			readl(host->ioaddr + SDHSTS)); | ||||||
|  | 	} else { | ||||||
|  | 		bcm2835_transfer_complete(host); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_finish_command(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	struct mmc_cmd *cmd = host->cmd; | ||||||
|  | 	u32 sdcmd; | ||||||
|  | 	int ret = 0; | ||||||
|  | 
 | ||||||
|  | 	sdcmd = bcm2835_read_wait_sdcmd(host); | ||||||
|  | 
 | ||||||
|  | 	/* Check for errors */ | ||||||
|  | 	if (sdcmd & SDCMD_NEW_FLAG) { | ||||||
|  | 		printf("command never completed.\n"); | ||||||
|  | 		bcm2835_dumpregs(host); | ||||||
|  | 		return -EIO; | ||||||
|  | 	} else if (sdcmd & SDCMD_FAIL_FLAG) { | ||||||
|  | 		u32 sdhsts = readl(host->ioaddr + SDHSTS); | ||||||
|  | 
 | ||||||
|  | 		/* Clear the errors */ | ||||||
|  | 		writel(SDHSTS_ERROR_MASK, host->ioaddr + SDHSTS); | ||||||
|  | 
 | ||||||
|  | 		if (!(sdhsts & SDHSTS_CRC7_ERROR) || | ||||||
|  | 		    (host->cmd->cmdidx != MMC_CMD_SEND_OP_COND)) { | ||||||
|  | 			if (sdhsts & SDHSTS_CMD_TIME_OUT) { | ||||||
|  | 				ret = -ETIMEDOUT; | ||||||
|  | 			} else { | ||||||
|  | 				printf("unexpected command %d error\n", | ||||||
|  | 				       host->cmd->cmdidx); | ||||||
|  | 				bcm2835_dumpregs(host); | ||||||
|  | 				ret = -EILSEQ; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return ret; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (cmd->resp_type & MMC_RSP_PRESENT) { | ||||||
|  | 		if (cmd->resp_type & MMC_RSP_136) { | ||||||
|  | 			int i; | ||||||
|  | 
 | ||||||
|  | 			for (i = 0; i < 4; i++) { | ||||||
|  | 				cmd->response[3 - i] = | ||||||
|  | 					readl(host->ioaddr + SDRSP0 + i * 4); | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			cmd->response[0] = readl(host->ioaddr + SDRSP0); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Processed actual command. */ | ||||||
|  | 	host->cmd = NULL; | ||||||
|  | 	if (host->data && host->data_complete) | ||||||
|  | 		ret = bcm2835_transfer_complete(host); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_check_cmd_error(struct bcm2835_host *host, u32 intmask) | ||||||
|  | { | ||||||
|  | 	int ret = -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	if (!(intmask & SDHSTS_ERROR_MASK)) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	if (!host->cmd) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	printf("sdhost_busy_irq: intmask %08x\n", intmask); | ||||||
|  | 	if (intmask & SDHSTS_CRC7_ERROR) { | ||||||
|  | 		ret = -EILSEQ; | ||||||
|  | 	} else if (intmask & (SDHSTS_CRC16_ERROR | | ||||||
|  | 			      SDHSTS_FIFO_ERROR)) { | ||||||
|  | 		ret = -EILSEQ; | ||||||
|  | 	} else if (intmask & (SDHSTS_REW_TIME_OUT | SDHSTS_CMD_TIME_OUT)) { | ||||||
|  | 		ret = -ETIMEDOUT; | ||||||
|  | 	} | ||||||
|  | 	bcm2835_dumpregs(host); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_check_data_error(struct bcm2835_host *host, u32 intmask) | ||||||
|  | { | ||||||
|  | 	int ret = 0; | ||||||
|  | 
 | ||||||
|  | 	if (!host->data) | ||||||
|  | 		return 0; | ||||||
|  | 	if (intmask & (SDHSTS_CRC16_ERROR | SDHSTS_FIFO_ERROR)) | ||||||
|  | 		ret = -EILSEQ; | ||||||
|  | 	if (intmask & SDHSTS_REW_TIME_OUT) | ||||||
|  | 		ret = -ETIMEDOUT; | ||||||
|  | 
 | ||||||
|  | 	if (ret) | ||||||
|  | 		printf("%s:%d %d\n", __func__, __LINE__, ret); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_busy_irq(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	if (WARN_ON(!host->cmd)) { | ||||||
|  | 		bcm2835_dumpregs(host); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (WARN_ON(!host->use_busy)) { | ||||||
|  | 		bcm2835_dumpregs(host); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	host->use_busy = false; | ||||||
|  | 
 | ||||||
|  | 	bcm2835_finish_command(host); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_data_irq(struct bcm2835_host *host, u32 intmask) | ||||||
|  | { | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * There are no dedicated data/space available interrupt | ||||||
|  | 	 * status bits, so it is necessary to use the single shared | ||||||
|  | 	 * data/space available FIFO status bits. It is therefore not | ||||||
|  | 	 * an error to get here when there is no data transfer in | ||||||
|  | 	 * progress. | ||||||
|  | 	 */ | ||||||
|  | 	if (!host->data) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	ret = bcm2835_check_data_error(host, intmask); | ||||||
|  | 	if (ret) | ||||||
|  | 		goto finished; | ||||||
|  | 
 | ||||||
|  | 	if (host->data->flags & MMC_DATA_WRITE) { | ||||||
|  | 		/* Use the block interrupt for writes after the first block */ | ||||||
|  | 		host->hcfg &= ~(SDHCFG_DATA_IRPT_EN); | ||||||
|  | 		host->hcfg |= SDHCFG_BLOCK_IRPT_EN; | ||||||
|  | 		writel(host->hcfg, host->ioaddr + SDHCFG); | ||||||
|  | 		bcm2835_transfer_pio(host); | ||||||
|  | 	} else { | ||||||
|  | 		bcm2835_transfer_pio(host); | ||||||
|  | 		host->blocks--; | ||||||
|  | 		if ((host->blocks == 0)) | ||||||
|  | 			goto finished; | ||||||
|  | 	} | ||||||
|  | 	return; | ||||||
|  | 
 | ||||||
|  | finished: | ||||||
|  | 	host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN); | ||||||
|  | 	writel(host->hcfg, host->ioaddr + SDHCFG); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_data_threaded_irq(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	if (!host->data) | ||||||
|  | 		return; | ||||||
|  | 	if ((host->blocks == 0)) | ||||||
|  | 		bcm2835_finish_data(host); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_block_irq(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	if (WARN_ON(!host->data)) { | ||||||
|  | 		bcm2835_dumpregs(host); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	WARN_ON(!host->blocks); | ||||||
|  | 	if ((--host->blocks == 0)) | ||||||
|  | 		bcm2835_finish_data(host); | ||||||
|  | 	else | ||||||
|  | 		bcm2835_transfer_pio(host); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static irqreturn_t bcm2835_irq(int irq, void *dev_id) | ||||||
|  | { | ||||||
|  | 	irqreturn_t result = IRQ_NONE; | ||||||
|  | 	struct bcm2835_host *host = dev_id; | ||||||
|  | 	u32 intmask; | ||||||
|  | 
 | ||||||
|  | 	intmask = readl(host->ioaddr + SDHSTS); | ||||||
|  | 
 | ||||||
|  | 	writel(SDHSTS_BUSY_IRPT | | ||||||
|  | 	       SDHSTS_BLOCK_IRPT | | ||||||
|  | 	       SDHSTS_SDIO_IRPT | | ||||||
|  | 	       SDHSTS_DATA_FLAG, | ||||||
|  | 	       host->ioaddr + SDHSTS); | ||||||
|  | 
 | ||||||
|  | 	if (intmask & SDHSTS_BLOCK_IRPT) { | ||||||
|  | 		bcm2835_check_data_error(host, intmask); | ||||||
|  | 		host->irq_block = true; | ||||||
|  | 		result = IRQ_WAKE_THREAD; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (intmask & SDHSTS_BUSY_IRPT) { | ||||||
|  | 		if (!bcm2835_check_cmd_error(host, intmask)) { | ||||||
|  | 			host->irq_busy = true; | ||||||
|  | 			result = IRQ_WAKE_THREAD; | ||||||
|  | 		} else { | ||||||
|  | 			result = IRQ_HANDLED; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* There is no true data interrupt status bit, so it is
 | ||||||
|  | 	 * necessary to qualify the data flag with the interrupt | ||||||
|  | 	 * enable bit. | ||||||
|  | 	 */ | ||||||
|  | 	if ((intmask & SDHSTS_DATA_FLAG) && | ||||||
|  | 	    (host->hcfg & SDHCFG_DATA_IRPT_EN)) { | ||||||
|  | 		bcm2835_data_irq(host, intmask); | ||||||
|  | 		host->irq_data = true; | ||||||
|  | 		result = IRQ_WAKE_THREAD; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static irqreturn_t bcm2835_threaded_irq(int irq, void *dev_id) | ||||||
|  | { | ||||||
|  | 	struct bcm2835_host *host = dev_id; | ||||||
|  | 
 | ||||||
|  | 	if (host->irq_block) { | ||||||
|  | 		host->irq_block = false; | ||||||
|  | 		bcm2835_block_irq(host); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (host->irq_busy) { | ||||||
|  | 		host->irq_busy = false; | ||||||
|  | 		bcm2835_busy_irq(host); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (host->irq_data) { | ||||||
|  | 		host->irq_data = false; | ||||||
|  | 		bcm2835_data_threaded_irq(host); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return IRQ_HANDLED; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_irq_poll(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	u32 intmask; | ||||||
|  | 
 | ||||||
|  | 	while (1) { | ||||||
|  | 		intmask = readl(host->ioaddr + SDHSTS); | ||||||
|  | 		if (intmask & (SDHSTS_BUSY_IRPT | SDHSTS_BLOCK_IRPT | | ||||||
|  | 			       SDHSTS_SDIO_IRPT | SDHSTS_DATA_FLAG)) { | ||||||
|  | 			bcm2835_irq(0, host); | ||||||
|  | 			bcm2835_threaded_irq(0, host); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock) | ||||||
|  | { | ||||||
|  | 	int div; | ||||||
|  | 
 | ||||||
|  | 	/* The SDCDIV register has 11 bits, and holds (div - 2).  But
 | ||||||
|  | 	 * in data mode the max is 50MHz wihout a minimum, and only | ||||||
|  | 	 * the bottom 3 bits are used. Since the switch over is | ||||||
|  | 	 * automatic (unless we have marked the card as slow...), | ||||||
|  | 	 * chosen values have to make sense in both modes.  Ident mode | ||||||
|  | 	 * must be 100-400KHz, so can range check the requested | ||||||
|  | 	 * clock. CMD15 must be used to return to data mode, so this | ||||||
|  | 	 * can be monitored. | ||||||
|  | 	 * | ||||||
|  | 	 * clock 250MHz -> 0->125MHz, 1->83.3MHz, 2->62.5MHz, 3->50.0MHz | ||||||
|  | 	 *                 4->41.7MHz, 5->35.7MHz, 6->31.3MHz, 7->27.8MHz | ||||||
|  | 	 * | ||||||
|  | 	 *		 623->400KHz/27.8MHz | ||||||
|  | 	 *		 reset value (507)->491159/50MHz | ||||||
|  | 	 * | ||||||
|  | 	 * BUT, the 3-bit clock divisor in data mode is too small if | ||||||
|  | 	 * the core clock is higher than 250MHz, so instead use the | ||||||
|  | 	 * SLOW_CARD configuration bit to force the use of the ident | ||||||
|  | 	 * clock divisor at all times. | ||||||
|  | 	 */ | ||||||
|  | 
 | ||||||
|  | 	if (clock < 100000) { | ||||||
|  | 		/* Can't stop the clock, but make it as slow as possible
 | ||||||
|  | 		 * to show willing | ||||||
|  | 		 */ | ||||||
|  | 		host->cdiv = SDCDIV_MAX_CDIV; | ||||||
|  | 		writel(host->cdiv, host->ioaddr + SDCDIV); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	div = host->max_clk / clock; | ||||||
|  | 	if (div < 2) | ||||||
|  | 		div = 2; | ||||||
|  | 	if ((host->max_clk / div) > clock) | ||||||
|  | 		div++; | ||||||
|  | 	div -= 2; | ||||||
|  | 
 | ||||||
|  | 	if (div > SDCDIV_MAX_CDIV) | ||||||
|  | 		div = SDCDIV_MAX_CDIV; | ||||||
|  | 
 | ||||||
|  | 	clock = host->max_clk / (div + 2); | ||||||
|  | 	host->mmc->clock = clock; | ||||||
|  | 
 | ||||||
|  | 	/* Calibrate some delays */ | ||||||
|  | 
 | ||||||
|  | 	host->ns_per_fifo_word = (1000000000 / clock) * | ||||||
|  | 		((host->mmc->card_caps & MMC_MODE_4BIT) ? 8 : 32); | ||||||
|  | 
 | ||||||
|  | 	host->cdiv = div; | ||||||
|  | 	writel(host->cdiv, host->ioaddr + SDCDIV); | ||||||
|  | 
 | ||||||
|  | 	/* Set the timeout to 500ms */ | ||||||
|  | 	writel(host->mmc->clock / 2, host->ioaddr + SDTOUT); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline int is_power_of_2(u64 x) | ||||||
|  | { | ||||||
|  | 	return !(x & (x - 1)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, | ||||||
|  | 			    struct mmc_data *data) | ||||||
|  | { | ||||||
|  | 	struct bcm2835_host *host = dev_get_priv(dev); | ||||||
|  | 	u32 edm, fsm; | ||||||
|  | 	int ret = 0; | ||||||
|  | 
 | ||||||
|  | 	if (data && !is_power_of_2(data->blocksize)) { | ||||||
|  | 		printf("unsupported block size (%d bytes)\n", data->blocksize); | ||||||
|  | 
 | ||||||
|  | 		if (cmd) | ||||||
|  | 			return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	edm = readl(host->ioaddr + SDEDM); | ||||||
|  | 	fsm = edm & SDEDM_FSM_MASK; | ||||||
|  | 
 | ||||||
|  | 	if ((fsm != SDEDM_FSM_IDENTMODE) && | ||||||
|  | 	    (fsm != SDEDM_FSM_DATAMODE) && | ||||||
|  | 	    (cmd && cmd->cmdidx != MMC_CMD_STOP_TRANSMISSION)) { | ||||||
|  | 		printf("previous command (%d) not complete (EDM %08x)\n", | ||||||
|  | 		       readl(host->ioaddr + SDCMD) & SDCMD_CMD_MASK, edm); | ||||||
|  | 		bcm2835_dumpregs(host); | ||||||
|  | 
 | ||||||
|  | 		if (cmd) | ||||||
|  | 			return -EILSEQ; | ||||||
|  | 
 | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (cmd) { | ||||||
|  | 		ret = bcm2835_send_command(host, cmd, data); | ||||||
|  | 		if (!ret && !host->use_busy) | ||||||
|  | 			ret = bcm2835_finish_command(host); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Wait for completion of busy signal or data transfer */ | ||||||
|  | 	while (host->use_busy || host->data) | ||||||
|  | 		bcm2835_irq_poll(host); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_set_ios(struct udevice *dev) | ||||||
|  | { | ||||||
|  | 	struct bcm2835_host *host = dev_get_priv(dev); | ||||||
|  | 	struct mmc *mmc = mmc_get_mmc_dev(dev); | ||||||
|  | 
 | ||||||
|  | 	if (!mmc->clock || mmc->clock != host->clock) { | ||||||
|  | 		bcm2835_set_clock(host, mmc->clock); | ||||||
|  | 		host->clock = mmc->clock; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* set bus width */ | ||||||
|  | 	host->hcfg &= ~SDHCFG_WIDE_EXT_BUS; | ||||||
|  | 	if (mmc->bus_width == 4) | ||||||
|  | 		host->hcfg |= SDHCFG_WIDE_EXT_BUS; | ||||||
|  | 
 | ||||||
|  | 	host->hcfg |= SDHCFG_WIDE_INT_BUS; | ||||||
|  | 
 | ||||||
|  | 	/* Disable clever clock switching, to cope with fast core clocks */ | ||||||
|  | 	host->hcfg |= SDHCFG_SLOW_CARD; | ||||||
|  | 
 | ||||||
|  | 	writel(host->hcfg, host->ioaddr + SDHCFG); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bcm2835_add_host(struct bcm2835_host *host) | ||||||
|  | { | ||||||
|  | 	struct mmc_config *cfg = &host->plat->cfg; | ||||||
|  | 
 | ||||||
|  | 	cfg->f_max = host->max_clk; | ||||||
|  | 	cfg->f_min = host->max_clk / SDCDIV_MAX_CDIV; | ||||||
|  | 	cfg->b_max = 65535; | ||||||
|  | 
 | ||||||
|  | 	dev_dbg(dev, "f_max %d, f_min %d\n", | ||||||
|  | 		cfg->f_max, cfg->f_min); | ||||||
|  | 
 | ||||||
|  | 	/* host controller capabilities */ | ||||||
|  | 	cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS | MMC_MODE_HS_52MHz; | ||||||
|  | 
 | ||||||
|  | 	/* report supported voltage ranges */ | ||||||
|  | 	cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; | ||||||
|  | 
 | ||||||
|  | 	/* Set interrupt enables */ | ||||||
|  | 	host->hcfg = SDHCFG_BUSY_IRPT_EN; | ||||||
|  | 
 | ||||||
|  | 	bcm2835_reset_internal(host); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bcm2835_probe(struct udevice *dev) | ||||||
|  | { | ||||||
|  | 	struct bcm2835_plat *plat = dev_get_platdata(dev); | ||||||
|  | 	struct bcm2835_host *host = dev_get_priv(dev); | ||||||
|  | 	struct mmc *mmc = mmc_get_mmc_dev(dev); | ||||||
|  | 	struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); | ||||||
|  | 
 | ||||||
|  | 	host->dev = dev; | ||||||
|  | 	host->mmc = mmc; | ||||||
|  | 	host->plat = plat; | ||||||
|  | 	upriv->mmc = &plat->mmc; | ||||||
|  | 	plat->cfg.name = dev->name; | ||||||
|  | 
 | ||||||
|  | 	host->phys_addr = devfdt_get_addr(dev); | ||||||
|  | 	if (host->phys_addr == FDT_ADDR_T_NONE) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	host->ioaddr = devm_ioremap(dev, host->phys_addr, SZ_256); | ||||||
|  | 	if (!host->ioaddr) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	host->max_clk = bcm2835_get_mmc_clock(); | ||||||
|  | 
 | ||||||
|  | 	bcm2835_add_host(host); | ||||||
|  | 
 | ||||||
|  | 	dev_dbg(dev, "%s -> OK\n", __func__); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const struct udevice_id bcm2835_match[] = { | ||||||
|  | 	{ .compatible = "brcm,bcm2835-sdhost" }, | ||||||
|  | 	{ } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const struct dm_mmc_ops bcm2835_ops = { | ||||||
|  | 	.send_cmd = bcm2835_send_cmd, | ||||||
|  | 	.set_ios = bcm2835_set_ios, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static int bcm2835_bind(struct udevice *dev) | ||||||
|  | { | ||||||
|  | 	struct bcm2835_plat *plat = dev_get_platdata(dev); | ||||||
|  | 
 | ||||||
|  | 	return mmc_bind(dev, &plat->mmc, &plat->cfg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | U_BOOT_DRIVER(bcm2835_sdhost) = { | ||||||
|  | 	.name = "bcm2835-sdhost", | ||||||
|  | 	.id = UCLASS_MMC, | ||||||
|  | 	.of_match = bcm2835_match, | ||||||
|  | 	.bind = bcm2835_bind, | ||||||
|  | 	.probe = bcm2835_probe, | ||||||
|  | 	.priv_auto_alloc_size = sizeof(struct bcm2835_host), | ||||||
|  | 	.platdata_auto_alloc_size = sizeof(struct bcm2835_plat), | ||||||
|  | 	.ops = &bcm2835_ops, | ||||||
|  | }; | ||||||
		Loading…
	
		Reference in New Issue