[PATCH 14/16] mmc: host: omap_hsmmc: Enable ADMA2

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



omap hsmmc host controller has ADMA2 feature. Enable it here
for better read and write throughput.

Signed-off-by: Kishon Vijay Abraham I <kishon@xxxxxx>
[misael.lopez@xxxxxx: handle ADMA errors]
Signed-off-by: Misael Lopez Cruz <misael.lopez@xxxxxx>
[nsekhar@xxxxxx: restore adma settings after context loss]
Signed-off-by: Sekhar Nori <nsekhar@xxxxxx>
---
 drivers/mmc/host/omap_hsmmc.c            | 307 +++++++++++++++++++++++++++----
 include/linux/platform_data/hsmmc-omap.h |   1 +
 2 files changed, 271 insertions(+), 37 deletions(-)

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index f47bade0d6fe..e8656b423541 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -48,6 +48,9 @@
 #include <linux/mmc/sd.h>
 
 /* OMAP HSMMC Host Controller Registers */
+#define OMAP_HSMMC_HL_REV	0x0000
+#define OMAP_HSMMC_HL_HWINFO	0x0004
+#define OMAP_HSMMC_HL_SYSCONFIG	0x0010
 #define OMAP_HSMMC_SYSSTATUS	0x0014
 #define OMAP_HSMMC_CON		0x002C
 #define OMAP_HSMMC_DLL		0x0034
@@ -69,7 +72,10 @@
 #define OMAP_HSMMC_AC12		0x013C
 #define OMAP_HSMMC_CAPA		0x0140
 #define OMAP_HSMMC_CAPA2	0x0144
+#define OMAP_HSMMC_ADMAES	0x0154
+#define OMAP_HSMMC_ADMASAL	0x0158
 
+#define MADMA_EN		(1 << 0)
 #define VS18			(1 << 26)
 #define VS30			(1 << 25)
 #define HSS			(1 << 21)
@@ -79,6 +85,7 @@
 #define SDVS_MASK		0x00000E00
 #define SDVSCLR			0xFFFFF1FF
 #define SDVSDET			0x00000400
+#define DMA_SELECT		(2 << 3)
 #define AUTOIDLE		0x1
 #define SDBP			(1 << 8)
 #define DTO			0xe
@@ -100,6 +107,7 @@
 #define FOUR_BIT		(1 << 1)
 #define HSPE			(1 << 2)
 #define IWE			(1 << 24)
+#define DMA_MASTER		(1 << 20)
 #define DDR			(1 << 19)
 #define CLKEXTFREE		(1 << 16)
 #define CTPL			(1 << 11)
@@ -153,10 +161,11 @@
 #define DCRC_EN			(1 << 21)
 #define DEB_EN			(1 << 22)
 #define ACE_EN			(1 << 24)
+#define ADMAE_EN		(1 << 25)
 #define CERR_EN			(1 << 28)
 #define BADA_EN			(1 << 29)
 
-#define INT_EN_MASK (BADA_EN | CERR_EN | ACE_EN | DEB_EN | DCRC_EN |\
+#define INT_EN_MASK (BADA_EN | CERR_EN | ADMAE_EN | ACE_EN | DEB_EN | DCRC_EN |\
 		DTO_EN | CIE_EN | CEB_EN | CCRC_EN | CTO_EN | \
 		BRR_EN | BWR_EN | TC_EN | CC_EN)
 
@@ -206,6 +215,33 @@
 #define OMAP_HSMMC_WRITE(base, reg, val) \
 	__raw_writel((val), (base) + OMAP_HSMMC_##reg)
 
+struct omap_hsmmc_adma_desc {
+	u8 attr;
+	u8 reserved;
+	u16 len;
+	u32 addr;
+} __packed;
+
+#define ADMA_DESC_SIZE			8
+
+#define ADMA_MAX_LEN			65532
+
+/* Decriptor table defines */
+#define ADMA_DESC_ATTR_VALID		BIT(0)
+#define ADMA_DESC_ATTR_END		BIT(1)
+#define ADMA_DESC_ATTR_INT		BIT(2)
+#define ADMA_DESC_ATTR_ACT1		BIT(4)
+#define ADMA_DESC_ATTR_ACT2		BIT(5)
+
+#define ADMA_DESC_TRANSFER_DATA		ADMA_DESC_ATTR_ACT2
+#define ADMA_DESC_LINK_DESC	(ADMA_DESC_ATTR_ACT1 | ADMA_DESC_ATTR_ACT2)
+
+/* ADMA error status */
+#define AES_MASK		0x3
+#define ST_STOP			0x0
+#define ST_FDS			0x1
+#define ST_TFR			0x3
+
 struct omap_hsmmc_next {
 	unsigned int	dma_len;
 	s32		cookie;
@@ -239,6 +275,7 @@ struct omap_hsmmc_host {
 	int			irq;
 	int			wake_irq;
 	int			dma_ch;
+	int			use_adma;
 	struct dma_chan		*tx_chan;
 	struct dma_chan		*rx_chan;
 	int			response_busy;
@@ -270,6 +307,9 @@ struct omap_hsmmc_host {
 	struct pinctrl_state	*hs_pinctrl_state;
 	struct pinctrl_state	*ddr_1_8v_pinctrl_state;
 
+	struct omap_hsmmc_adma_desc *adma_desc_table;
+	dma_addr_t              adma_desc_table_addr;
+
 	/* return MMC cover switch state, can be NULL if not supported.
 	 *
 	 * possible return values:
@@ -840,6 +880,18 @@ static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
 	OMAP_HSMMC_WRITE(host->base, IE, 0);
 	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
 
+	if (host->use_adma) {
+		u32 val;
+
+		val = OMAP_HSMMC_READ(host->base, CON);
+		val |= DMA_MASTER;
+		OMAP_HSMMC_WRITE(host->base, CON, val);
+
+		val = OMAP_HSMMC_READ(host->base, HCTL);
+		val |= DMA_SELECT;
+		OMAP_HSMMC_WRITE(host->base, HCTL, val);
+	}
+
 	/* Do not initialize card-specific things if the power is off */
 	if (host->power_mode == MMC_POWER_OFF)
 		goto out;
@@ -1054,6 +1106,10 @@ omap_hsmmc_xfer_done(struct omap_hsmmc_host *host, struct mmc_data *data)
 		return;
 	}
 
+	if (host->use_adma && host->data && !data->host_cookie)
+		dma_unmap_sg(host->dev, data->sg, data->sg_len,
+			     mmc_get_dma_dir(data));
+
 	host->data = NULL;
 
 	if (!data->error)
@@ -1115,13 +1171,17 @@ static void omap_hsmmc_dma_cleanup(struct omap_hsmmc_host *host, int errno)
 	host->dma_ch = -1;
 	spin_unlock_irqrestore(&host->irq_lock, flags);
 
-	if (dma_ch != -1) {
-		struct dma_chan *chan = omap_hsmmc_get_dma_chan(host, host->data);
-
+	if (host->use_adma) {
+		dma_unmap_sg(host->dev, host->data->sg, host->data->sg_len,
+			     mmc_get_dma_dir(host->data));
+		host->data->host_cookie = 0;
+	} else if (dma_ch != -1) {
+		struct dma_chan *chan = omap_hsmmc_get_dma_chan(host,
+								host->data);
 		dmaengine_terminate_all(chan);
 		dma_unmap_sg(chan->device->dev,
-			host->data->sg, host->data->sg_len,
-			mmc_get_dma_dir(host->data));
+			     host->data->sg, host->data->sg_len,
+			     mmc_get_dma_dir(host->data));
 
 		host->data->host_cookie = 0;
 	}
@@ -1216,6 +1276,35 @@ static void hsmmc_command_incomplete(struct omap_hsmmc_host *host,
 		host->mrq->cmd->error = err;
 }
 
+static void omap_hsmmc_adma_err(struct omap_hsmmc_host *host)
+{
+	u32 admaes, admasal;
+
+	admaes = OMAP_HSMMC_READ(host->base, ADMAES);
+	admasal = OMAP_HSMMC_READ(host->base, ADMASAL);
+
+	switch (admaes & AES_MASK) {
+	case ST_STOP:
+		dev_err(mmc_dev(host->mmc),
+			"ADMA err: ST_STOP, desc at 0x%08x follows the erroneous one\n",
+			admasal);
+		break;
+	case ST_FDS:
+		dev_err(mmc_dev(host->mmc),
+			"ADMA err: ST_FDS, erroneous desc at 0x%08x\n",
+			admasal);
+		break;
+	case ST_TFR:
+		dev_err(mmc_dev(host->mmc),
+			"ADMA err: ST_TFR, desc at 0x%08x follows the erroneous one\n",
+			admasal);
+		break;
+	default:
+		dev_warn(mmc_dev(host->mmc), "Unexpected ADMA error state\n");
+		break;
+	}
+}
+
 static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
 {
 	struct mmc_data *data;
@@ -1234,6 +1323,13 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
 			end_trans = !end_cmd;
 			host->response_busy = 0;
 		}
+
+		if (status & ADMAE_EN) {
+			omap_hsmmc_adma_err(host);
+			end_trans = 1;
+			data->error = -EIO;
+		}
+
 		if (status & (CTO_EN | DTO_EN))
 			hsmmc_command_incomplete(host, -ETIMEDOUT, end_cmd);
 		else if (status & (CCRC_EN | DCRC_EN | DEB_EN | CEB_EN |
@@ -1415,6 +1511,7 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host,
 				       struct dma_chan *chan)
 {
 	int dma_len;
+	struct device *dev;
 
 	if (!next && data->host_cookie &&
 	    data->host_cookie != host->next_data.cookie) {
@@ -1424,11 +1521,15 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host,
 		data->host_cookie = 0;
 	}
 
+	if (chan)
+		dev = chan->device->dev;
+	else
+		dev = mmc_dev(host->mmc);
+
 	/* Check if next job is already prepared */
 	if (next || data->host_cookie != host->next_data.cookie) {
-		dma_len = dma_map_sg(chan->device->dev, data->sg, data->sg_len,
+		dma_len = dma_map_sg(dev, data->sg, data->sg_len,
 				     mmc_get_dma_dir(data));
-
 	} else {
 		dma_len = host->next_data.dma_len;
 		host->next_data.dma_len = 0;
@@ -1601,8 +1702,58 @@ static void omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host)
 				| (req->data->blocks << 16));
 	set_data_timeout(host, req->data->timeout_ns,
 				req->data->timeout_clks);
-	chan = omap_hsmmc_get_dma_chan(host, req->data);
-	dma_async_issue_pending(chan);
+
+	if (host->use_adma) {
+		OMAP_HSMMC_WRITE(host->base, ADMASAL,
+				 (u32)host->adma_desc_table_addr);
+	} else {
+		chan = omap_hsmmc_get_dma_chan(host, req->data);
+		dma_async_issue_pending(chan);
+	}
+}
+
+static int omap_hsmmc_write_adma_desc(struct omap_hsmmc_host *host, void *desc,
+				      dma_addr_t addr, u16 len, u8 attr)
+{
+	struct omap_hsmmc_adma_desc *dma_desc = desc;
+
+	dma_desc->len = len;
+	dma_desc->addr = (u32)addr;
+	dma_desc->reserved = 0;
+	dma_desc->attr = attr;
+
+	return 0;
+}
+
+static int omap_hsmmc_setup_adma_transfer(struct omap_hsmmc_host *host,
+					  struct mmc_request *req)
+{
+	struct mmc_data *data = req->data;
+	struct scatterlist *sg;
+	int i;
+	int len;
+	int ret;
+	dma_addr_t addr;
+	struct omap_hsmmc_adma_desc *dma_desc;
+
+	ret = omap_hsmmc_pre_dma_transfer(host, data, NULL, NULL);
+	if (ret)
+		return ret;
+
+	dma_desc = host->adma_desc_table;
+	for_each_sg(data->sg, sg, host->dma_len, i) {
+		addr = sg_dma_address(sg);
+		len = sg_dma_len(sg);
+		WARN_ON(len > ADMA_MAX_LEN);
+		omap_hsmmc_write_adma_desc(host, dma_desc, addr, len,
+					   ADMA_DESC_ATTR_VALID |
+					   ADMA_DESC_TRANSFER_DATA);
+		dma_desc++;
+	}
+	omap_hsmmc_write_adma_desc(host, dma_desc, 0, 0, ADMA_DESC_ATTR_END |
+				   ADMA_DESC_ATTR_VALID);
+
+	return 0;
 }
 
 /*
@@ -1633,10 +1784,18 @@ omap_hsmmc_prepare_data(struct omap_hsmmc_host *host, struct mmc_request *req)
 		return 0;
 	}
 
-	ret = omap_hsmmc_setup_dma_transfer(host, req);
-	if (ret != 0) {
-		dev_err(mmc_dev(host->mmc), "MMC start dma failure\n");
-		return ret;
+	if (host->use_adma) {
+		ret = omap_hsmmc_setup_adma_transfer(host, req);
+		if (ret != 0) {
+			dev_err(mmc_dev(host->mmc), "MMC adma setup failed\n");
+			return ret;
+		}
+	} else {
+		ret = omap_hsmmc_setup_dma_transfer(host, req);
+		if (ret != 0) {
+			dev_err(mmc_dev(host->mmc), "MMC start dma failure\n");
+			return ret;
+		}
 	}
 	return 0;
 }
@@ -1646,11 +1805,18 @@ static void omap_hsmmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
 {
 	struct omap_hsmmc_host *host = mmc_priv(mmc);
 	struct mmc_data *data = mrq->data;
+	struct device *dev;
+	struct dma_chan *c;
 
 	if (data->host_cookie) {
-		struct dma_chan *c = omap_hsmmc_get_dma_chan(host, data);
+		if (host->use_adma) {
+			dev = mmc_dev(mmc);
+		} else {
+			c = omap_hsmmc_get_dma_chan(host, mrq->data);
+			dev = c->device->dev;
+		}
 
-		dma_unmap_sg(c->device->dev, data->sg, data->sg_len,
+		dma_unmap_sg(dev, data->sg, data->sg_len,
 			     mmc_get_dma_dir(data));
 		data->host_cookie = 0;
 	}
@@ -1666,7 +1832,8 @@ static void omap_hsmmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq)
 		return ;
 	}
 
-	c = omap_hsmmc_get_dma_chan(host, mrq->data);
+	if (!host->use_adma)
+		c = omap_hsmmc_get_dma_chan(host, mrq->data);
 
 	if (omap_hsmmc_pre_dma_transfer(host, mrq->data,
 					&host->next_data, c))
@@ -2325,6 +2492,7 @@ static const struct omap_mmc_of_data omap3_pre_es3_mmc_of_data = {
 
 static const struct omap_mmc_of_data omap4_mmc_of_data = {
 	.reg_offset = 0x100,
+	.controller_flags = OMAP_HSMMC_HAS_HWPARAM,
 };
 static const struct omap_mmc_of_data am33xx_mmc_of_data = {
 	.reg_offset = 0x100,
@@ -2334,7 +2502,8 @@ static const struct omap_mmc_of_data am33xx_mmc_of_data = {
 static const struct omap_mmc_of_data dra7_mmc_of_data = {
 	.reg_offset = 0x100,
 	.controller_flags = OMAP_HSMMC_SWAKEUP_MISSING |
-			    OMAP_HSMMC_REQUIRE_IODELAY,
+			    OMAP_HSMMC_REQUIRE_IODELAY |
+			    OMAP_HSMMC_HAS_HWPARAM,
 };
 
 static const struct of_device_id omap_mmc_of_match[] = {
@@ -2506,6 +2675,64 @@ static int omap_hsmmc_config_iodelay_pinctrl_state(struct omap_hsmmc_host *host)
 	return 0;
 }
 
+static int omap_hsmmc_adma_init(struct omap_hsmmc_host *host)
+{
+	struct mmc_host *mmc = host->mmc;
+	u32 val;
+
+	host->adma_desc_table = dma_alloc_coherent(host->dev, ADMA_DESC_SIZE *
+						   (mmc->max_segs + 1),
+						   &host->adma_desc_table_addr,
+						   GFP_KERNEL);
+	if (!host->adma_desc_table) {
+		dev_err(host->dev, "failed to allocate adma desc table\n");
+		return -ENOMEM;
+	}
+
+	val = OMAP_HSMMC_READ(host->base, HCTL);
+	val |= DMA_SELECT;
+	OMAP_HSMMC_WRITE(host->base, HCTL, val);
+
+	val = OMAP_HSMMC_READ(host->base, CON);
+	val |= DMA_MASTER;
+	OMAP_HSMMC_WRITE(host->base, CON, val);
+
+	return 0;
+}
+
+static void omap_hsmmc_adma_exit(struct omap_hsmmc_host *host)
+{
+	struct mmc_host *mmc = host->mmc;
+
+	dma_free_coherent(host->dev, ADMA_DESC_SIZE * (mmc->max_segs + 1),
+			  host->adma_desc_table, host->adma_desc_table_addr);
+}
+
+static int omap_hsmmc_dma_init(struct omap_hsmmc_host *host)
+{
+	host->rx_chan = dma_request_chan(host->dev, "rx");
+	if (IS_ERR(host->rx_chan)) {
+		dev_err(mmc_dev(host->mmc), "RX DMA channel request failed\n");
+		return PTR_ERR(host->rx_chan);
+	}
+
+	host->tx_chan = dma_request_chan(host->dev, "tx");
+	if (IS_ERR(host->tx_chan)) {
+		dev_err(mmc_dev(host->mmc), "TX DMA channel request failed\n");
+		return PTR_ERR(host->tx_chan);
+	}
+
+	return 0;
+}
+
+static void omap_hsmmc_dma_exit(struct omap_hsmmc_host *host)
+{
+	if (!IS_ERR_OR_NULL(host->tx_chan))
+		dma_release_channel(host->tx_chan);
+	if (!IS_ERR_OR_NULL(host->rx_chan))
+		dma_release_channel(host->rx_chan);
+}
+
 static int omap_hsmmc_probe(struct platform_device *pdev)
 {
 	struct omap_hsmmc_platform_data *pdata = pdev->dev.platform_data;
@@ -2513,6 +2740,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	struct omap_hsmmc_host *host = NULL;
 	struct resource *res;
 	int ret, irq;
+	u32 val;
 	const struct of_device_id *match;
 	const struct omap_mmc_of_data *data;
 	void __iomem *base;
@@ -2568,6 +2796,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	host->next_data.cookie = 1;
 	host->pbias_enabled = 0;
 	host->vqmmc_enabled = 0;
+	host->use_adma	= false;
 
 	ret = omap_hsmmc_gpio_init(mmc, host, pdata);
 	if (ret)
@@ -2629,6 +2858,12 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 		host->dbclk = NULL;
 	}
 
+	if (host->pdata->controller_flags & OMAP_HSMMC_HAS_HWPARAM) {
+		val = OMAP_HSMMC_READ(base, HL_HWINFO);
+		if (val & MADMA_EN)
+			host->use_adma = true;
+	}
+
 	/* Since we do only SG emulation, we can have as many segs
 	 * as we want. */
 	mmc->max_segs = 1024;
@@ -2636,7 +2871,10 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	mmc->max_blk_size = 512;       /* Block Length at max can be 1024 */
 	mmc->max_blk_count = 0xFFFF;    /* No. of Blocks is 16 bits */
 	mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
-	mmc->max_seg_size = mmc->max_req_size;
+	if (host->use_adma)
+		mmc->max_seg_size = ADMA_MAX_LEN;
+	else
+		mmc->max_seg_size = mmc->max_req_size;
 
 	mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED |
 		     MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_ERASE;
@@ -2656,19 +2894,12 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_pinctrl;
 
-	host->rx_chan = dma_request_chan(&pdev->dev, "rx");
-	if (IS_ERR(host->rx_chan)) {
-		dev_err(mmc_dev(host->mmc), "RX DMA channel request failed\n");
-		ret = PTR_ERR(host->rx_chan);
-		goto err_irq;
-	}
-
-	host->tx_chan = dma_request_chan(&pdev->dev, "tx");
-	if (IS_ERR(host->tx_chan)) {
-		dev_err(mmc_dev(host->mmc), "TX DMA channel request failed\n");
-		ret = PTR_ERR(host->tx_chan);
+	if (host->use_adma)
+		ret = omap_hsmmc_adma_init(host);
+	else
+		ret = omap_hsmmc_dma_init(host);
+	if (ret)
 		goto err_irq;
-	}
 
 	/* Request IRQ for MMC operations */
 	ret = devm_request_irq(&pdev->dev, host->irq, omap_hsmmc_irq, 0,
@@ -2724,10 +2955,10 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 err_slot_name:
 	mmc_remove_host(mmc);
 err_irq:
-	if (!IS_ERR_OR_NULL(host->tx_chan))
-		dma_release_channel(host->tx_chan);
-	if (!IS_ERR_OR_NULL(host->rx_chan))
-		dma_release_channel(host->rx_chan);
+	if (host->use_adma)
+		omap_hsmmc_adma_exit(host);
+	else
+		omap_hsmmc_dma_exit(host);
 err_pinctrl:
 	if (host->dbclk)
 		clk_disable_unprepare(host->dbclk);
@@ -2749,8 +2980,10 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
 	pm_runtime_get_sync(host->dev);
 	mmc_remove_host(host->mmc);
 
-	dma_release_channel(host->tx_chan);
-	dma_release_channel(host->rx_chan);
+	if (host->use_adma)
+		omap_hsmmc_adma_exit(host);
+	else
+		omap_hsmmc_dma_exit(host);
 
 	del_timer_sync(&host->timer);
 
diff --git a/include/linux/platform_data/hsmmc-omap.h b/include/linux/platform_data/hsmmc-omap.h
index 8e771851e07a..c3f2a34db97a 100644
--- a/include/linux/platform_data/hsmmc-omap.h
+++ b/include/linux/platform_data/hsmmc-omap.h
@@ -28,6 +28,7 @@
 #define OMAP_HSMMC_BROKEN_MULTIBLOCK_READ	BIT(1)
 #define OMAP_HSMMC_SWAKEUP_MISSING		BIT(2)
 #define OMAP_HSMMC_REQUIRE_IODELAY		BIT(3)
+#define OMAP_HSMMC_HAS_HWPARAM			BIT(4)
 
 struct omap_hsmmc_dev_attr {
 	u8 flags;
-- 
2.11.0

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



[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux