1f707079dSWolfram Sang // SPDX-License-Identifier: GPL-2.0 275fa9ea6SGuennadi Liakhovetski /* 375fa9ea6SGuennadi Liakhovetski * Copyright (C) 2013-2014 Renesas Electronics Europe Ltd. 475fa9ea6SGuennadi Liakhovetski * Author: Guennadi Liakhovetski <g.liakhovetski@gmx.de> 575fa9ea6SGuennadi Liakhovetski */ 675fa9ea6SGuennadi Liakhovetski 775fa9ea6SGuennadi Liakhovetski #include <linux/clk.h> 875fa9ea6SGuennadi Liakhovetski #include <linux/delay.h> 975fa9ea6SGuennadi Liakhovetski #include <linux/device.h> 1075fa9ea6SGuennadi Liakhovetski #include <linux/dma-mapping.h> 1175fa9ea6SGuennadi Liakhovetski #include <linux/dmaengine.h> 1275fa9ea6SGuennadi Liakhovetski #include <linux/highmem.h> 1375fa9ea6SGuennadi Liakhovetski #include <linux/interrupt.h> 1475fa9ea6SGuennadi Liakhovetski #include <linux/io.h> 1575fa9ea6SGuennadi Liakhovetski #include <linux/log2.h> 1675fa9ea6SGuennadi Liakhovetski #include <linux/mmc/host.h> 1775fa9ea6SGuennadi Liakhovetski #include <linux/mmc/mmc.h> 1875fa9ea6SGuennadi Liakhovetski #include <linux/mmc/sd.h> 1975fa9ea6SGuennadi Liakhovetski #include <linux/mmc/sdio.h> 2075fa9ea6SGuennadi Liakhovetski #include <linux/module.h> 2175fa9ea6SGuennadi Liakhovetski #include <linux/pagemap.h> 22488aab3dSLars Persson #include <linux/pinctrl/consumer.h> 2375fa9ea6SGuennadi Liakhovetski #include <linux/platform_device.h> 2475fa9ea6SGuennadi Liakhovetski #include <linux/scatterlist.h> 2575fa9ea6SGuennadi Liakhovetski #include <linux/string.h> 2675fa9ea6SGuennadi Liakhovetski #include <linux/time.h> 2775fa9ea6SGuennadi Liakhovetski #include <linux/virtio.h> 2875fa9ea6SGuennadi Liakhovetski #include <linux/workqueue.h> 2975fa9ea6SGuennadi Liakhovetski 3075fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD 0x0000 3175fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_PORT_SEL 0x0004 3275fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_ARG 0x0008 3375fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_STOP 0x0010 3475fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_SECCNT 0x0014 3575fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_RSP10 0x0018 3675fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_RSP32 0x0020 3775fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_RSP54 0x0028 3875fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_RSP76 0x0030 3975fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1 0x0038 4075fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2 0x003c 4175fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_MASK 0x0040 4275fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_MASK 0x0044 4375fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CLK_CTRL 0x0048 4475fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_SIZE 0x004c 4575fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_OPTION 0x0050 4675fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_ERR_STS1 0x0058 4775fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_ERR_STS2 0x005c 4875fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_BUF0 0x0060 4975fa9ea6SGuennadi Liakhovetski #define USDHI6_SDIO_MODE 0x0068 5075fa9ea6SGuennadi Liakhovetski #define USDHI6_SDIO_INFO1 0x006c 5175fa9ea6SGuennadi Liakhovetski #define USDHI6_SDIO_INFO1_MASK 0x0070 5275fa9ea6SGuennadi Liakhovetski #define USDHI6_CC_EXT_MODE 0x01b0 5375fa9ea6SGuennadi Liakhovetski #define USDHI6_SOFT_RST 0x01c0 5475fa9ea6SGuennadi Liakhovetski #define USDHI6_VERSION 0x01c4 5575fa9ea6SGuennadi Liakhovetski #define USDHI6_HOST_MODE 0x01c8 5675fa9ea6SGuennadi Liakhovetski #define USDHI6_SDIF_MODE 0x01cc 5775fa9ea6SGuennadi Liakhovetski 5875fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_APP 0x0040 5975fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_MODE_RSP_AUTO 0x0000 6075fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_MODE_RSP_NONE 0x0300 6175fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_MODE_RSP_R1 0x0400 /* Also R5, R6, R7 */ 6275fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_MODE_RSP_R1B 0x0500 /* R1b */ 6375fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_MODE_RSP_R2 0x0600 6475fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_MODE_RSP_R3 0x0700 /* Also R4 */ 6575fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_DATA 0x0800 6675fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_READ 0x1000 6775fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_MULTI 0x2000 6875fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CMD_CMD12_AUTO_OFF 0x4000 6975fa9ea6SGuennadi Liakhovetski 7075fa9ea6SGuennadi Liakhovetski #define USDHI6_CC_EXT_MODE_SDRW BIT(1) 7175fa9ea6SGuennadi Liakhovetski 7275fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_RSP_END BIT(0) 7375fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_ACCESS_END BIT(2) 7475fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_CARD_OUT BIT(3) 7575fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_CARD_IN BIT(4) 7675fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_CD BIT(5) 7775fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_WP BIT(7) 7875fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_D3_CARD_OUT BIT(8) 7975fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_D3_CARD_IN BIT(9) 8075fa9ea6SGuennadi Liakhovetski 8175fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_CMD_ERR BIT(0) 8275fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_CRC_ERR BIT(1) 8375fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_END_ERR BIT(2) 8475fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_TOUT BIT(3) 8575fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_IWA_ERR BIT(4) 8675fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_IRA_ERR BIT(5) 8775fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_RSP_TOUT BIT(6) 8875fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_SDDAT0 BIT(7) 8975fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_BRE BIT(8) 9075fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_BWE BIT(9) 9175fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_SCLKDIVEN BIT(13) 9275fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_CBSY BIT(14) 9375fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_ILA BIT(15) 9475fa9ea6SGuennadi Liakhovetski 9575fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_CARD_INSERT (USDHI6_SD_INFO1_CARD_IN | USDHI6_SD_INFO1_D3_CARD_IN) 9675fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_CARD_EJECT (USDHI6_SD_INFO1_CARD_OUT | USDHI6_SD_INFO1_D3_CARD_OUT) 9775fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_CARD (USDHI6_SD_INFO1_CARD_INSERT | USDHI6_SD_INFO1_CARD_EJECT) 9875fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_CARD_CD (USDHI6_SD_INFO1_CARD_IN | USDHI6_SD_INFO1_CARD_OUT) 9975fa9ea6SGuennadi Liakhovetski 10075fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_ERR (USDHI6_SD_INFO2_CMD_ERR | \ 10175fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO2_CRC_ERR | USDHI6_SD_INFO2_END_ERR | \ 10275fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO2_TOUT | USDHI6_SD_INFO2_IWA_ERR | \ 10375fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO2_IRA_ERR | USDHI6_SD_INFO2_RSP_TOUT | \ 10475fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO2_ILA) 10575fa9ea6SGuennadi Liakhovetski 10675fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO1_IRQ (USDHI6_SD_INFO1_RSP_END | USDHI6_SD_INFO1_ACCESS_END | \ 10775fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO1_CARD) 10875fa9ea6SGuennadi Liakhovetski 10975fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_INFO2_IRQ (USDHI6_SD_INFO2_ERR | USDHI6_SD_INFO2_BRE | \ 11075fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO2_BWE | 0x0800 | USDHI6_SD_INFO2_ILA) 11175fa9ea6SGuennadi Liakhovetski 11275fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CLK_CTRL_SCLKEN BIT(8) 11375fa9ea6SGuennadi Liakhovetski 11475fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_STOP_STP BIT(0) 11575fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_STOP_SEC BIT(8) 11675fa9ea6SGuennadi Liakhovetski 11775fa9ea6SGuennadi Liakhovetski #define USDHI6_SDIO_INFO1_IOIRQ BIT(0) 11875fa9ea6SGuennadi Liakhovetski #define USDHI6_SDIO_INFO1_EXPUB52 BIT(14) 11975fa9ea6SGuennadi Liakhovetski #define USDHI6_SDIO_INFO1_EXWT BIT(15) 12075fa9ea6SGuennadi Liakhovetski 12175fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_ERR_STS1_CRC_NO_ERROR BIT(13) 12275fa9ea6SGuennadi Liakhovetski 12375fa9ea6SGuennadi Liakhovetski #define USDHI6_SOFT_RST_RESERVED (BIT(1) | BIT(2)) 12475fa9ea6SGuennadi Liakhovetski #define USDHI6_SOFT_RST_RESET BIT(0) 12575fa9ea6SGuennadi Liakhovetski 12675fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_OPTION_TIMEOUT_SHIFT 4 12775fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_OPTION_TIMEOUT_MASK (0xf << USDHI6_SD_OPTION_TIMEOUT_SHIFT) 12875fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_OPTION_WIDTH_1 BIT(15) 12975fa9ea6SGuennadi Liakhovetski 13075fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_PORT_SEL_PORTS_SHIFT 8 13175fa9ea6SGuennadi Liakhovetski 13275fa9ea6SGuennadi Liakhovetski #define USDHI6_SD_CLK_CTRL_DIV_MASK 0xff 13375fa9ea6SGuennadi Liakhovetski 13475fa9ea6SGuennadi Liakhovetski #define USDHI6_SDIO_INFO1_IRQ (USDHI6_SDIO_INFO1_IOIRQ | 3 | \ 13575fa9ea6SGuennadi Liakhovetski USDHI6_SDIO_INFO1_EXPUB52 | USDHI6_SDIO_INFO1_EXWT) 13675fa9ea6SGuennadi Liakhovetski 13775fa9ea6SGuennadi Liakhovetski #define USDHI6_MIN_DMA 64 13875fa9ea6SGuennadi Liakhovetski 13975fa9ea6SGuennadi Liakhovetski enum usdhi6_wait_for { 14075fa9ea6SGuennadi Liakhovetski USDHI6_WAIT_FOR_REQUEST, 14175fa9ea6SGuennadi Liakhovetski USDHI6_WAIT_FOR_CMD, 14275fa9ea6SGuennadi Liakhovetski USDHI6_WAIT_FOR_MREAD, 14375fa9ea6SGuennadi Liakhovetski USDHI6_WAIT_FOR_MWRITE, 14475fa9ea6SGuennadi Liakhovetski USDHI6_WAIT_FOR_READ, 14575fa9ea6SGuennadi Liakhovetski USDHI6_WAIT_FOR_WRITE, 14675fa9ea6SGuennadi Liakhovetski USDHI6_WAIT_FOR_DATA_END, 14775fa9ea6SGuennadi Liakhovetski USDHI6_WAIT_FOR_STOP, 14875fa9ea6SGuennadi Liakhovetski USDHI6_WAIT_FOR_DMA, 14975fa9ea6SGuennadi Liakhovetski }; 15075fa9ea6SGuennadi Liakhovetski 15175fa9ea6SGuennadi Liakhovetski struct usdhi6_page { 15275fa9ea6SGuennadi Liakhovetski struct page *page; 15375fa9ea6SGuennadi Liakhovetski void *mapped; /* mapped page */ 15475fa9ea6SGuennadi Liakhovetski }; 15575fa9ea6SGuennadi Liakhovetski 15675fa9ea6SGuennadi Liakhovetski struct usdhi6_host { 15775fa9ea6SGuennadi Liakhovetski struct mmc_host *mmc; 15875fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq; 15975fa9ea6SGuennadi Liakhovetski void __iomem *base; 16075fa9ea6SGuennadi Liakhovetski struct clk *clk; 16175fa9ea6SGuennadi Liakhovetski 16275fa9ea6SGuennadi Liakhovetski /* SG memory handling */ 16375fa9ea6SGuennadi Liakhovetski 16475fa9ea6SGuennadi Liakhovetski /* Common for multiple and single block requests */ 16575fa9ea6SGuennadi Liakhovetski struct usdhi6_page pg; /* current page from an SG */ 16675fa9ea6SGuennadi Liakhovetski void *blk_page; /* either a mapped page, or the bounce buffer */ 16775fa9ea6SGuennadi Liakhovetski size_t offset; /* offset within a page, including sg->offset */ 16875fa9ea6SGuennadi Liakhovetski 16975fa9ea6SGuennadi Liakhovetski /* Blocks, crossing a page boundary */ 17075fa9ea6SGuennadi Liakhovetski size_t head_len; 17175fa9ea6SGuennadi Liakhovetski struct usdhi6_page head_pg; 17275fa9ea6SGuennadi Liakhovetski 17375fa9ea6SGuennadi Liakhovetski /* A bounce buffer for unaligned blocks or blocks, crossing a page boundary */ 17475fa9ea6SGuennadi Liakhovetski struct scatterlist bounce_sg; 17575fa9ea6SGuennadi Liakhovetski u8 bounce_buf[512]; 17675fa9ea6SGuennadi Liakhovetski 17775fa9ea6SGuennadi Liakhovetski /* Multiple block requests only */ 17875fa9ea6SGuennadi Liakhovetski struct scatterlist *sg; /* current SG segment */ 17975fa9ea6SGuennadi Liakhovetski int page_idx; /* page index within an SG segment */ 18075fa9ea6SGuennadi Liakhovetski 18175fa9ea6SGuennadi Liakhovetski enum usdhi6_wait_for wait; 18275fa9ea6SGuennadi Liakhovetski u32 status_mask; 18375fa9ea6SGuennadi Liakhovetski u32 status2_mask; 18475fa9ea6SGuennadi Liakhovetski u32 sdio_mask; 18575fa9ea6SGuennadi Liakhovetski u32 io_error; 18675fa9ea6SGuennadi Liakhovetski u32 irq_status; 18775fa9ea6SGuennadi Liakhovetski unsigned long imclk; 18875fa9ea6SGuennadi Liakhovetski unsigned long rate; 18975fa9ea6SGuennadi Liakhovetski bool app_cmd; 19075fa9ea6SGuennadi Liakhovetski 19175fa9ea6SGuennadi Liakhovetski /* Timeout handling */ 19275fa9ea6SGuennadi Liakhovetski struct delayed_work timeout_work; 19375fa9ea6SGuennadi Liakhovetski unsigned long timeout; 19475fa9ea6SGuennadi Liakhovetski 19575fa9ea6SGuennadi Liakhovetski /* DMA support */ 19675fa9ea6SGuennadi Liakhovetski struct dma_chan *chan_rx; 19775fa9ea6SGuennadi Liakhovetski struct dma_chan *chan_tx; 19875fa9ea6SGuennadi Liakhovetski bool dma_active; 199488aab3dSLars Persson 200488aab3dSLars Persson /* Pin control */ 201488aab3dSLars Persson struct pinctrl *pinctrl; 202488aab3dSLars Persson struct pinctrl_state *pins_uhs; 20375fa9ea6SGuennadi Liakhovetski }; 20475fa9ea6SGuennadi Liakhovetski 20575fa9ea6SGuennadi Liakhovetski /* I/O primitives */ 20675fa9ea6SGuennadi Liakhovetski 20775fa9ea6SGuennadi Liakhovetski static void usdhi6_write(struct usdhi6_host *host, u32 reg, u32 data) 20875fa9ea6SGuennadi Liakhovetski { 20975fa9ea6SGuennadi Liakhovetski iowrite32(data, host->base + reg); 21075fa9ea6SGuennadi Liakhovetski dev_vdbg(mmc_dev(host->mmc), "%s(0x%p + 0x%x) = 0x%x\n", __func__, 21175fa9ea6SGuennadi Liakhovetski host->base, reg, data); 21275fa9ea6SGuennadi Liakhovetski } 21375fa9ea6SGuennadi Liakhovetski 21475fa9ea6SGuennadi Liakhovetski static void usdhi6_write16(struct usdhi6_host *host, u32 reg, u16 data) 21575fa9ea6SGuennadi Liakhovetski { 21675fa9ea6SGuennadi Liakhovetski iowrite16(data, host->base + reg); 21775fa9ea6SGuennadi Liakhovetski dev_vdbg(mmc_dev(host->mmc), "%s(0x%p + 0x%x) = 0x%x\n", __func__, 21875fa9ea6SGuennadi Liakhovetski host->base, reg, data); 21975fa9ea6SGuennadi Liakhovetski } 22075fa9ea6SGuennadi Liakhovetski 22175fa9ea6SGuennadi Liakhovetski static u32 usdhi6_read(struct usdhi6_host *host, u32 reg) 22275fa9ea6SGuennadi Liakhovetski { 22375fa9ea6SGuennadi Liakhovetski u32 data = ioread32(host->base + reg); 22475fa9ea6SGuennadi Liakhovetski dev_vdbg(mmc_dev(host->mmc), "%s(0x%p + 0x%x) = 0x%x\n", __func__, 22575fa9ea6SGuennadi Liakhovetski host->base, reg, data); 22675fa9ea6SGuennadi Liakhovetski return data; 22775fa9ea6SGuennadi Liakhovetski } 22875fa9ea6SGuennadi Liakhovetski 22975fa9ea6SGuennadi Liakhovetski static u16 usdhi6_read16(struct usdhi6_host *host, u32 reg) 23075fa9ea6SGuennadi Liakhovetski { 23175fa9ea6SGuennadi Liakhovetski u16 data = ioread16(host->base + reg); 23275fa9ea6SGuennadi Liakhovetski dev_vdbg(mmc_dev(host->mmc), "%s(0x%p + 0x%x) = 0x%x\n", __func__, 23375fa9ea6SGuennadi Liakhovetski host->base, reg, data); 23475fa9ea6SGuennadi Liakhovetski return data; 23575fa9ea6SGuennadi Liakhovetski } 23675fa9ea6SGuennadi Liakhovetski 23775fa9ea6SGuennadi Liakhovetski static void usdhi6_irq_enable(struct usdhi6_host *host, u32 info1, u32 info2) 23875fa9ea6SGuennadi Liakhovetski { 23975fa9ea6SGuennadi Liakhovetski host->status_mask = USDHI6_SD_INFO1_IRQ & ~info1; 24075fa9ea6SGuennadi Liakhovetski host->status2_mask = USDHI6_SD_INFO2_IRQ & ~info2; 24175fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_INFO1_MASK, host->status_mask); 24275fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_INFO2_MASK, host->status2_mask); 24375fa9ea6SGuennadi Liakhovetski } 24475fa9ea6SGuennadi Liakhovetski 24575fa9ea6SGuennadi Liakhovetski static void usdhi6_wait_for_resp(struct usdhi6_host *host) 24675fa9ea6SGuennadi Liakhovetski { 24775fa9ea6SGuennadi Liakhovetski usdhi6_irq_enable(host, USDHI6_SD_INFO1_RSP_END | 24875fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO1_ACCESS_END | USDHI6_SD_INFO1_CARD_CD, 24975fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO2_ERR); 25075fa9ea6SGuennadi Liakhovetski } 25175fa9ea6SGuennadi Liakhovetski 25275fa9ea6SGuennadi Liakhovetski static void usdhi6_wait_for_brwe(struct usdhi6_host *host, bool read) 25375fa9ea6SGuennadi Liakhovetski { 25475fa9ea6SGuennadi Liakhovetski usdhi6_irq_enable(host, USDHI6_SD_INFO1_ACCESS_END | 25575fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO1_CARD_CD, USDHI6_SD_INFO2_ERR | 25675fa9ea6SGuennadi Liakhovetski (read ? USDHI6_SD_INFO2_BRE : USDHI6_SD_INFO2_BWE)); 25775fa9ea6SGuennadi Liakhovetski } 25875fa9ea6SGuennadi Liakhovetski 25975fa9ea6SGuennadi Liakhovetski static void usdhi6_only_cd(struct usdhi6_host *host) 26075fa9ea6SGuennadi Liakhovetski { 26175fa9ea6SGuennadi Liakhovetski /* Mask all except card hotplug */ 26275fa9ea6SGuennadi Liakhovetski usdhi6_irq_enable(host, USDHI6_SD_INFO1_CARD_CD, 0); 26375fa9ea6SGuennadi Liakhovetski } 26475fa9ea6SGuennadi Liakhovetski 26575fa9ea6SGuennadi Liakhovetski static void usdhi6_mask_all(struct usdhi6_host *host) 26675fa9ea6SGuennadi Liakhovetski { 26775fa9ea6SGuennadi Liakhovetski usdhi6_irq_enable(host, 0, 0); 26875fa9ea6SGuennadi Liakhovetski } 26975fa9ea6SGuennadi Liakhovetski 27075fa9ea6SGuennadi Liakhovetski static int usdhi6_error_code(struct usdhi6_host *host) 27175fa9ea6SGuennadi Liakhovetski { 27275fa9ea6SGuennadi Liakhovetski u32 err; 27375fa9ea6SGuennadi Liakhovetski 27475fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_STOP, USDHI6_SD_STOP_STP); 27575fa9ea6SGuennadi Liakhovetski 27675fa9ea6SGuennadi Liakhovetski if (host->io_error & 27775fa9ea6SGuennadi Liakhovetski (USDHI6_SD_INFO2_RSP_TOUT | USDHI6_SD_INFO2_TOUT)) { 27875fa9ea6SGuennadi Liakhovetski u32 rsp54 = usdhi6_read(host, USDHI6_SD_RSP54); 27975fa9ea6SGuennadi Liakhovetski int opc = host->mrq ? host->mrq->cmd->opcode : -1; 28075fa9ea6SGuennadi Liakhovetski 28175fa9ea6SGuennadi Liakhovetski err = usdhi6_read(host, USDHI6_SD_ERR_STS2); 28275fa9ea6SGuennadi Liakhovetski /* Response timeout is often normal, don't spam the log */ 28375fa9ea6SGuennadi Liakhovetski if (host->wait == USDHI6_WAIT_FOR_CMD) 28475fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), 28575fa9ea6SGuennadi Liakhovetski "T-out sts 0x%x, resp 0x%x, state %u, CMD%d\n", 28675fa9ea6SGuennadi Liakhovetski err, rsp54, host->wait, opc); 28775fa9ea6SGuennadi Liakhovetski else 28875fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), 28975fa9ea6SGuennadi Liakhovetski "T-out sts 0x%x, resp 0x%x, state %u, CMD%d\n", 29075fa9ea6SGuennadi Liakhovetski err, rsp54, host->wait, opc); 29175fa9ea6SGuennadi Liakhovetski return -ETIMEDOUT; 29275fa9ea6SGuennadi Liakhovetski } 29375fa9ea6SGuennadi Liakhovetski 29475fa9ea6SGuennadi Liakhovetski err = usdhi6_read(host, USDHI6_SD_ERR_STS1); 29575fa9ea6SGuennadi Liakhovetski if (err != USDHI6_SD_ERR_STS1_CRC_NO_ERROR) 29675fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), "Err sts 0x%x, state %u, CMD%d\n", 29775fa9ea6SGuennadi Liakhovetski err, host->wait, host->mrq ? host->mrq->cmd->opcode : -1); 29875fa9ea6SGuennadi Liakhovetski if (host->io_error & USDHI6_SD_INFO2_ILA) 29975fa9ea6SGuennadi Liakhovetski return -EILSEQ; 30075fa9ea6SGuennadi Liakhovetski 30175fa9ea6SGuennadi Liakhovetski return -EIO; 30275fa9ea6SGuennadi Liakhovetski } 30375fa9ea6SGuennadi Liakhovetski 30475fa9ea6SGuennadi Liakhovetski /* Scatter-Gather management */ 30575fa9ea6SGuennadi Liakhovetski 30675fa9ea6SGuennadi Liakhovetski /* 30775fa9ea6SGuennadi Liakhovetski * In PIO mode we have to map each page separately, using kmap(). That way 30875fa9ea6SGuennadi Liakhovetski * adjacent pages are mapped to non-adjacent virtual addresses. That's why we 30975fa9ea6SGuennadi Liakhovetski * have to use a bounce buffer for blocks, crossing page boundaries. Such blocks 31075fa9ea6SGuennadi Liakhovetski * have been observed with an SDIO WiFi card (b43 driver). 31175fa9ea6SGuennadi Liakhovetski */ 31275fa9ea6SGuennadi Liakhovetski static void usdhi6_blk_bounce(struct usdhi6_host *host, 31375fa9ea6SGuennadi Liakhovetski struct scatterlist *sg) 31475fa9ea6SGuennadi Liakhovetski { 31575fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 31675fa9ea6SGuennadi Liakhovetski size_t blk_head = host->head_len; 31775fa9ea6SGuennadi Liakhovetski 31875fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): CMD%u of %u SG: %ux%u @ 0x%x\n", 31975fa9ea6SGuennadi Liakhovetski __func__, host->mrq->cmd->opcode, data->sg_len, 32075fa9ea6SGuennadi Liakhovetski data->blksz, data->blocks, sg->offset); 32175fa9ea6SGuennadi Liakhovetski 32275fa9ea6SGuennadi Liakhovetski host->head_pg.page = host->pg.page; 32375fa9ea6SGuennadi Liakhovetski host->head_pg.mapped = host->pg.mapped; 32475fa9ea6SGuennadi Liakhovetski host->pg.page = nth_page(host->pg.page, 1); 32575fa9ea6SGuennadi Liakhovetski host->pg.mapped = kmap(host->pg.page); 32675fa9ea6SGuennadi Liakhovetski 32775fa9ea6SGuennadi Liakhovetski host->blk_page = host->bounce_buf; 32875fa9ea6SGuennadi Liakhovetski host->offset = 0; 32975fa9ea6SGuennadi Liakhovetski 33075fa9ea6SGuennadi Liakhovetski if (data->flags & MMC_DATA_READ) 33175fa9ea6SGuennadi Liakhovetski return; 33275fa9ea6SGuennadi Liakhovetski 33375fa9ea6SGuennadi Liakhovetski memcpy(host->bounce_buf, host->head_pg.mapped + PAGE_SIZE - blk_head, 33475fa9ea6SGuennadi Liakhovetski blk_head); 33575fa9ea6SGuennadi Liakhovetski memcpy(host->bounce_buf + blk_head, host->pg.mapped, 33675fa9ea6SGuennadi Liakhovetski data->blksz - blk_head); 33775fa9ea6SGuennadi Liakhovetski } 33875fa9ea6SGuennadi Liakhovetski 33975fa9ea6SGuennadi Liakhovetski /* Only called for multiple block IO */ 34075fa9ea6SGuennadi Liakhovetski static void usdhi6_sg_prep(struct usdhi6_host *host) 34175fa9ea6SGuennadi Liakhovetski { 34275fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq = host->mrq; 34375fa9ea6SGuennadi Liakhovetski struct mmc_data *data = mrq->data; 34475fa9ea6SGuennadi Liakhovetski 34575fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_SECCNT, data->blocks); 34675fa9ea6SGuennadi Liakhovetski 34775fa9ea6SGuennadi Liakhovetski host->sg = data->sg; 34875fa9ea6SGuennadi Liakhovetski /* TODO: if we always map, this is redundant */ 34975fa9ea6SGuennadi Liakhovetski host->offset = host->sg->offset; 35075fa9ea6SGuennadi Liakhovetski } 35175fa9ea6SGuennadi Liakhovetski 35275fa9ea6SGuennadi Liakhovetski /* Map the first page in an SG segment: common for multiple and single block IO */ 35375fa9ea6SGuennadi Liakhovetski static void *usdhi6_sg_map(struct usdhi6_host *host) 35475fa9ea6SGuennadi Liakhovetski { 35575fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 35675fa9ea6SGuennadi Liakhovetski struct scatterlist *sg = data->sg_len > 1 ? host->sg : data->sg; 35775fa9ea6SGuennadi Liakhovetski size_t head = PAGE_SIZE - sg->offset; 35875fa9ea6SGuennadi Liakhovetski size_t blk_head = head % data->blksz; 35975fa9ea6SGuennadi Liakhovetski 36075fa9ea6SGuennadi Liakhovetski WARN(host->pg.page, "%p not properly unmapped!\n", host->pg.page); 36175fa9ea6SGuennadi Liakhovetski if (WARN(sg_dma_len(sg) % data->blksz, 36213fe0ec3SGuennadi Liakhovetski "SG size %u isn't a multiple of block size %u\n", 36375fa9ea6SGuennadi Liakhovetski sg_dma_len(sg), data->blksz)) 36475fa9ea6SGuennadi Liakhovetski return NULL; 36575fa9ea6SGuennadi Liakhovetski 36675fa9ea6SGuennadi Liakhovetski host->pg.page = sg_page(sg); 36775fa9ea6SGuennadi Liakhovetski host->pg.mapped = kmap(host->pg.page); 36875fa9ea6SGuennadi Liakhovetski host->offset = sg->offset; 36975fa9ea6SGuennadi Liakhovetski 37075fa9ea6SGuennadi Liakhovetski /* 37175fa9ea6SGuennadi Liakhovetski * Block size must be a power of 2 for multi-block transfers, 37275fa9ea6SGuennadi Liakhovetski * therefore blk_head is equal for all pages in this SG 37375fa9ea6SGuennadi Liakhovetski */ 37475fa9ea6SGuennadi Liakhovetski host->head_len = blk_head; 37575fa9ea6SGuennadi Liakhovetski 37675fa9ea6SGuennadi Liakhovetski if (head < data->blksz) 37775fa9ea6SGuennadi Liakhovetski /* 37875fa9ea6SGuennadi Liakhovetski * The first block in the SG crosses a page boundary. 37975fa9ea6SGuennadi Liakhovetski * Max blksz = 512, so blocks can only span 2 pages 38075fa9ea6SGuennadi Liakhovetski */ 38175fa9ea6SGuennadi Liakhovetski usdhi6_blk_bounce(host, sg); 38275fa9ea6SGuennadi Liakhovetski else 38375fa9ea6SGuennadi Liakhovetski host->blk_page = host->pg.mapped; 38475fa9ea6SGuennadi Liakhovetski 38575fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "Mapped %p (%lx) at %p + %u for CMD%u @ 0x%p\n", 38675fa9ea6SGuennadi Liakhovetski host->pg.page, page_to_pfn(host->pg.page), host->pg.mapped, 38775fa9ea6SGuennadi Liakhovetski sg->offset, host->mrq->cmd->opcode, host->mrq); 38875fa9ea6SGuennadi Liakhovetski 38975fa9ea6SGuennadi Liakhovetski return host->blk_page + host->offset; 39075fa9ea6SGuennadi Liakhovetski } 39175fa9ea6SGuennadi Liakhovetski 39275fa9ea6SGuennadi Liakhovetski /* Unmap the current page: common for multiple and single block IO */ 39375fa9ea6SGuennadi Liakhovetski static void usdhi6_sg_unmap(struct usdhi6_host *host, bool force) 39475fa9ea6SGuennadi Liakhovetski { 39575fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 39675fa9ea6SGuennadi Liakhovetski struct page *page = host->head_pg.page; 39775fa9ea6SGuennadi Liakhovetski 39875fa9ea6SGuennadi Liakhovetski if (page) { 39975fa9ea6SGuennadi Liakhovetski /* Previous block was cross-page boundary */ 40075fa9ea6SGuennadi Liakhovetski struct scatterlist *sg = data->sg_len > 1 ? 40175fa9ea6SGuennadi Liakhovetski host->sg : data->sg; 40275fa9ea6SGuennadi Liakhovetski size_t blk_head = host->head_len; 40375fa9ea6SGuennadi Liakhovetski 40475fa9ea6SGuennadi Liakhovetski if (!data->error && data->flags & MMC_DATA_READ) { 40575fa9ea6SGuennadi Liakhovetski memcpy(host->head_pg.mapped + PAGE_SIZE - blk_head, 40675fa9ea6SGuennadi Liakhovetski host->bounce_buf, blk_head); 40775fa9ea6SGuennadi Liakhovetski memcpy(host->pg.mapped, host->bounce_buf + blk_head, 40875fa9ea6SGuennadi Liakhovetski data->blksz - blk_head); 40975fa9ea6SGuennadi Liakhovetski } 41075fa9ea6SGuennadi Liakhovetski 41175fa9ea6SGuennadi Liakhovetski flush_dcache_page(page); 41275fa9ea6SGuennadi Liakhovetski kunmap(page); 41375fa9ea6SGuennadi Liakhovetski 41475fa9ea6SGuennadi Liakhovetski host->head_pg.page = NULL; 41575fa9ea6SGuennadi Liakhovetski 41675fa9ea6SGuennadi Liakhovetski if (!force && sg_dma_len(sg) + sg->offset > 41775fa9ea6SGuennadi Liakhovetski (host->page_idx << PAGE_SHIFT) + data->blksz - blk_head) 41875fa9ea6SGuennadi Liakhovetski /* More blocks in this SG, don't unmap the next page */ 41975fa9ea6SGuennadi Liakhovetski return; 42075fa9ea6SGuennadi Liakhovetski } 42175fa9ea6SGuennadi Liakhovetski 42275fa9ea6SGuennadi Liakhovetski page = host->pg.page; 42375fa9ea6SGuennadi Liakhovetski if (!page) 42475fa9ea6SGuennadi Liakhovetski return; 42575fa9ea6SGuennadi Liakhovetski 42675fa9ea6SGuennadi Liakhovetski flush_dcache_page(page); 42775fa9ea6SGuennadi Liakhovetski kunmap(page); 42875fa9ea6SGuennadi Liakhovetski 42975fa9ea6SGuennadi Liakhovetski host->pg.page = NULL; 43075fa9ea6SGuennadi Liakhovetski } 43175fa9ea6SGuennadi Liakhovetski 43275fa9ea6SGuennadi Liakhovetski /* Called from MMC_WRITE_MULTIPLE_BLOCK or MMC_READ_MULTIPLE_BLOCK */ 43375fa9ea6SGuennadi Liakhovetski static void usdhi6_sg_advance(struct usdhi6_host *host) 43475fa9ea6SGuennadi Liakhovetski { 43575fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 43675fa9ea6SGuennadi Liakhovetski size_t done, total; 43775fa9ea6SGuennadi Liakhovetski 43875fa9ea6SGuennadi Liakhovetski /* New offset: set at the end of the previous block */ 43975fa9ea6SGuennadi Liakhovetski if (host->head_pg.page) { 44075fa9ea6SGuennadi Liakhovetski /* Finished a cross-page block, jump to the new page */ 44175fa9ea6SGuennadi Liakhovetski host->page_idx++; 44275fa9ea6SGuennadi Liakhovetski host->offset = data->blksz - host->head_len; 44375fa9ea6SGuennadi Liakhovetski host->blk_page = host->pg.mapped; 44475fa9ea6SGuennadi Liakhovetski usdhi6_sg_unmap(host, false); 44575fa9ea6SGuennadi Liakhovetski } else { 44675fa9ea6SGuennadi Liakhovetski host->offset += data->blksz; 44775fa9ea6SGuennadi Liakhovetski /* The completed block didn't cross a page boundary */ 44875fa9ea6SGuennadi Liakhovetski if (host->offset == PAGE_SIZE) { 44975fa9ea6SGuennadi Liakhovetski /* If required, we'll map the page below */ 45075fa9ea6SGuennadi Liakhovetski host->offset = 0; 45175fa9ea6SGuennadi Liakhovetski host->page_idx++; 45275fa9ea6SGuennadi Liakhovetski } 45375fa9ea6SGuennadi Liakhovetski } 45475fa9ea6SGuennadi Liakhovetski 45575fa9ea6SGuennadi Liakhovetski /* 45675fa9ea6SGuennadi Liakhovetski * Now host->blk_page + host->offset point at the end of our last block 45775fa9ea6SGuennadi Liakhovetski * and host->page_idx is the index of the page, in which our new block 45875fa9ea6SGuennadi Liakhovetski * is located, if any 45975fa9ea6SGuennadi Liakhovetski */ 46075fa9ea6SGuennadi Liakhovetski 46175fa9ea6SGuennadi Liakhovetski done = (host->page_idx << PAGE_SHIFT) + host->offset; 46275fa9ea6SGuennadi Liakhovetski total = host->sg->offset + sg_dma_len(host->sg); 46375fa9ea6SGuennadi Liakhovetski 46413fe0ec3SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): %zu of %zu @ %zu\n", __func__, 46575fa9ea6SGuennadi Liakhovetski done, total, host->offset); 46675fa9ea6SGuennadi Liakhovetski 46775fa9ea6SGuennadi Liakhovetski if (done < total && host->offset) { 46875fa9ea6SGuennadi Liakhovetski /* More blocks in this page */ 46975fa9ea6SGuennadi Liakhovetski if (host->offset + data->blksz > PAGE_SIZE) 47075fa9ea6SGuennadi Liakhovetski /* We approached at a block, that spans 2 pages */ 47175fa9ea6SGuennadi Liakhovetski usdhi6_blk_bounce(host, host->sg); 47275fa9ea6SGuennadi Liakhovetski 47375fa9ea6SGuennadi Liakhovetski return; 47475fa9ea6SGuennadi Liakhovetski } 47575fa9ea6SGuennadi Liakhovetski 47675fa9ea6SGuennadi Liakhovetski /* Finished current page or an SG segment */ 47775fa9ea6SGuennadi Liakhovetski usdhi6_sg_unmap(host, false); 47875fa9ea6SGuennadi Liakhovetski 47975fa9ea6SGuennadi Liakhovetski if (done == total) { 48075fa9ea6SGuennadi Liakhovetski /* 48175fa9ea6SGuennadi Liakhovetski * End of an SG segment or the complete SG: jump to the next 48275fa9ea6SGuennadi Liakhovetski * segment, we'll map it later in usdhi6_blk_read() or 48375fa9ea6SGuennadi Liakhovetski * usdhi6_blk_write() 48475fa9ea6SGuennadi Liakhovetski */ 48575fa9ea6SGuennadi Liakhovetski struct scatterlist *next = sg_next(host->sg); 48675fa9ea6SGuennadi Liakhovetski 48775fa9ea6SGuennadi Liakhovetski host->page_idx = 0; 48875fa9ea6SGuennadi Liakhovetski 48975fa9ea6SGuennadi Liakhovetski if (!next) 49075fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_DATA_END; 49175fa9ea6SGuennadi Liakhovetski host->sg = next; 49275fa9ea6SGuennadi Liakhovetski 49375fa9ea6SGuennadi Liakhovetski if (WARN(next && sg_dma_len(next) % data->blksz, 49413fe0ec3SGuennadi Liakhovetski "SG size %u isn't a multiple of block size %u\n", 49575fa9ea6SGuennadi Liakhovetski sg_dma_len(next), data->blksz)) 49675fa9ea6SGuennadi Liakhovetski data->error = -EINVAL; 49775fa9ea6SGuennadi Liakhovetski 49875fa9ea6SGuennadi Liakhovetski return; 49975fa9ea6SGuennadi Liakhovetski } 50075fa9ea6SGuennadi Liakhovetski 50175fa9ea6SGuennadi Liakhovetski /* We cannot get here after crossing a page border */ 50275fa9ea6SGuennadi Liakhovetski 50375fa9ea6SGuennadi Liakhovetski /* Next page in the same SG */ 50475fa9ea6SGuennadi Liakhovetski host->pg.page = nth_page(sg_page(host->sg), host->page_idx); 50575fa9ea6SGuennadi Liakhovetski host->pg.mapped = kmap(host->pg.page); 50675fa9ea6SGuennadi Liakhovetski host->blk_page = host->pg.mapped; 50775fa9ea6SGuennadi Liakhovetski 50875fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "Mapped %p (%lx) at %p for CMD%u @ 0x%p\n", 50975fa9ea6SGuennadi Liakhovetski host->pg.page, page_to_pfn(host->pg.page), host->pg.mapped, 51075fa9ea6SGuennadi Liakhovetski host->mrq->cmd->opcode, host->mrq); 51175fa9ea6SGuennadi Liakhovetski } 51275fa9ea6SGuennadi Liakhovetski 51375fa9ea6SGuennadi Liakhovetski /* DMA handling */ 51475fa9ea6SGuennadi Liakhovetski 51575fa9ea6SGuennadi Liakhovetski static void usdhi6_dma_release(struct usdhi6_host *host) 51675fa9ea6SGuennadi Liakhovetski { 51775fa9ea6SGuennadi Liakhovetski host->dma_active = false; 51875fa9ea6SGuennadi Liakhovetski if (host->chan_tx) { 51975fa9ea6SGuennadi Liakhovetski struct dma_chan *chan = host->chan_tx; 52075fa9ea6SGuennadi Liakhovetski host->chan_tx = NULL; 52175fa9ea6SGuennadi Liakhovetski dma_release_channel(chan); 52275fa9ea6SGuennadi Liakhovetski } 52375fa9ea6SGuennadi Liakhovetski if (host->chan_rx) { 52475fa9ea6SGuennadi Liakhovetski struct dma_chan *chan = host->chan_rx; 52575fa9ea6SGuennadi Liakhovetski host->chan_rx = NULL; 52675fa9ea6SGuennadi Liakhovetski dma_release_channel(chan); 52775fa9ea6SGuennadi Liakhovetski } 52875fa9ea6SGuennadi Liakhovetski } 52975fa9ea6SGuennadi Liakhovetski 53075fa9ea6SGuennadi Liakhovetski static void usdhi6_dma_stop_unmap(struct usdhi6_host *host) 53175fa9ea6SGuennadi Liakhovetski { 53275fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 53375fa9ea6SGuennadi Liakhovetski 53475fa9ea6SGuennadi Liakhovetski if (!host->dma_active) 53575fa9ea6SGuennadi Liakhovetski return; 53675fa9ea6SGuennadi Liakhovetski 53775fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_CC_EXT_MODE, 0); 53875fa9ea6SGuennadi Liakhovetski host->dma_active = false; 53975fa9ea6SGuennadi Liakhovetski 54075fa9ea6SGuennadi Liakhovetski if (data->flags & MMC_DATA_READ) 54175fa9ea6SGuennadi Liakhovetski dma_unmap_sg(host->chan_rx->device->dev, data->sg, 54275fa9ea6SGuennadi Liakhovetski data->sg_len, DMA_FROM_DEVICE); 54375fa9ea6SGuennadi Liakhovetski else 54475fa9ea6SGuennadi Liakhovetski dma_unmap_sg(host->chan_tx->device->dev, data->sg, 54575fa9ea6SGuennadi Liakhovetski data->sg_len, DMA_TO_DEVICE); 54675fa9ea6SGuennadi Liakhovetski } 54775fa9ea6SGuennadi Liakhovetski 54875fa9ea6SGuennadi Liakhovetski static void usdhi6_dma_complete(void *arg) 54975fa9ea6SGuennadi Liakhovetski { 55075fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = arg; 55175fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq = host->mrq; 55275fa9ea6SGuennadi Liakhovetski 55375fa9ea6SGuennadi Liakhovetski if (WARN(!mrq || !mrq->data, "%s: NULL data in DMA completion for %p!\n", 55475fa9ea6SGuennadi Liakhovetski dev_name(mmc_dev(host->mmc)), mrq)) 55575fa9ea6SGuennadi Liakhovetski return; 55675fa9ea6SGuennadi Liakhovetski 55775fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): CMD%u DMA completed\n", __func__, 55875fa9ea6SGuennadi Liakhovetski mrq->cmd->opcode); 55975fa9ea6SGuennadi Liakhovetski 56075fa9ea6SGuennadi Liakhovetski usdhi6_dma_stop_unmap(host); 56175fa9ea6SGuennadi Liakhovetski usdhi6_wait_for_brwe(host, mrq->data->flags & MMC_DATA_READ); 56275fa9ea6SGuennadi Liakhovetski } 56375fa9ea6SGuennadi Liakhovetski 56475fa9ea6SGuennadi Liakhovetski static int usdhi6_dma_setup(struct usdhi6_host *host, struct dma_chan *chan, 56575fa9ea6SGuennadi Liakhovetski enum dma_transfer_direction dir) 56675fa9ea6SGuennadi Liakhovetski { 56775fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 56875fa9ea6SGuennadi Liakhovetski struct scatterlist *sg = data->sg; 56975fa9ea6SGuennadi Liakhovetski struct dma_async_tx_descriptor *desc = NULL; 57075fa9ea6SGuennadi Liakhovetski dma_cookie_t cookie = -EINVAL; 57175fa9ea6SGuennadi Liakhovetski enum dma_data_direction data_dir; 57275fa9ea6SGuennadi Liakhovetski int ret; 57375fa9ea6SGuennadi Liakhovetski 57475fa9ea6SGuennadi Liakhovetski switch (dir) { 57575fa9ea6SGuennadi Liakhovetski case DMA_MEM_TO_DEV: 57675fa9ea6SGuennadi Liakhovetski data_dir = DMA_TO_DEVICE; 57775fa9ea6SGuennadi Liakhovetski break; 57875fa9ea6SGuennadi Liakhovetski case DMA_DEV_TO_MEM: 57975fa9ea6SGuennadi Liakhovetski data_dir = DMA_FROM_DEVICE; 58075fa9ea6SGuennadi Liakhovetski break; 58175fa9ea6SGuennadi Liakhovetski default: 58275fa9ea6SGuennadi Liakhovetski return -EINVAL; 58375fa9ea6SGuennadi Liakhovetski } 58475fa9ea6SGuennadi Liakhovetski 58575fa9ea6SGuennadi Liakhovetski ret = dma_map_sg(chan->device->dev, sg, data->sg_len, data_dir); 58675fa9ea6SGuennadi Liakhovetski if (ret > 0) { 58775fa9ea6SGuennadi Liakhovetski host->dma_active = true; 58875fa9ea6SGuennadi Liakhovetski desc = dmaengine_prep_slave_sg(chan, sg, ret, dir, 58975fa9ea6SGuennadi Liakhovetski DMA_PREP_INTERRUPT | DMA_CTRL_ACK); 59075fa9ea6SGuennadi Liakhovetski } 59175fa9ea6SGuennadi Liakhovetski 59275fa9ea6SGuennadi Liakhovetski if (desc) { 59375fa9ea6SGuennadi Liakhovetski desc->callback = usdhi6_dma_complete; 59475fa9ea6SGuennadi Liakhovetski desc->callback_param = host; 59575fa9ea6SGuennadi Liakhovetski cookie = dmaengine_submit(desc); 59675fa9ea6SGuennadi Liakhovetski } 59775fa9ea6SGuennadi Liakhovetski 59875fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): mapped %d -> %d, cookie %d @ %p\n", 59975fa9ea6SGuennadi Liakhovetski __func__, data->sg_len, ret, cookie, desc); 60075fa9ea6SGuennadi Liakhovetski 60175fa9ea6SGuennadi Liakhovetski if (cookie < 0) { 60275fa9ea6SGuennadi Liakhovetski /* DMA failed, fall back to PIO */ 60375fa9ea6SGuennadi Liakhovetski if (ret >= 0) 60475fa9ea6SGuennadi Liakhovetski ret = cookie; 60575fa9ea6SGuennadi Liakhovetski usdhi6_dma_release(host); 60675fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), 60775fa9ea6SGuennadi Liakhovetski "DMA failed: %d, falling back to PIO\n", ret); 60875fa9ea6SGuennadi Liakhovetski } 60975fa9ea6SGuennadi Liakhovetski 61075fa9ea6SGuennadi Liakhovetski return cookie; 61175fa9ea6SGuennadi Liakhovetski } 61275fa9ea6SGuennadi Liakhovetski 61375fa9ea6SGuennadi Liakhovetski static int usdhi6_dma_start(struct usdhi6_host *host) 61475fa9ea6SGuennadi Liakhovetski { 61575fa9ea6SGuennadi Liakhovetski if (!host->chan_rx || !host->chan_tx) 61675fa9ea6SGuennadi Liakhovetski return -ENODEV; 61775fa9ea6SGuennadi Liakhovetski 61875fa9ea6SGuennadi Liakhovetski if (host->mrq->data->flags & MMC_DATA_READ) 61975fa9ea6SGuennadi Liakhovetski return usdhi6_dma_setup(host, host->chan_rx, DMA_DEV_TO_MEM); 62075fa9ea6SGuennadi Liakhovetski 62175fa9ea6SGuennadi Liakhovetski return usdhi6_dma_setup(host, host->chan_tx, DMA_MEM_TO_DEV); 62275fa9ea6SGuennadi Liakhovetski } 62375fa9ea6SGuennadi Liakhovetski 62475fa9ea6SGuennadi Liakhovetski static void usdhi6_dma_kill(struct usdhi6_host *host) 62575fa9ea6SGuennadi Liakhovetski { 62675fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 62775fa9ea6SGuennadi Liakhovetski 62875fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): SG of %u: %ux%u\n", 62975fa9ea6SGuennadi Liakhovetski __func__, data->sg_len, data->blocks, data->blksz); 63075fa9ea6SGuennadi Liakhovetski /* Abort DMA */ 63175fa9ea6SGuennadi Liakhovetski if (data->flags & MMC_DATA_READ) 63275fa9ea6SGuennadi Liakhovetski dmaengine_terminate_all(host->chan_rx); 63375fa9ea6SGuennadi Liakhovetski else 63475fa9ea6SGuennadi Liakhovetski dmaengine_terminate_all(host->chan_tx); 63575fa9ea6SGuennadi Liakhovetski } 63675fa9ea6SGuennadi Liakhovetski 63775fa9ea6SGuennadi Liakhovetski static void usdhi6_dma_check_error(struct usdhi6_host *host) 63875fa9ea6SGuennadi Liakhovetski { 63975fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 64075fa9ea6SGuennadi Liakhovetski 64175fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): IO error %d, status 0x%x\n", 64275fa9ea6SGuennadi Liakhovetski __func__, host->io_error, usdhi6_read(host, USDHI6_SD_INFO1)); 64375fa9ea6SGuennadi Liakhovetski 64475fa9ea6SGuennadi Liakhovetski if (host->io_error) { 64575fa9ea6SGuennadi Liakhovetski data->error = usdhi6_error_code(host); 64675fa9ea6SGuennadi Liakhovetski data->bytes_xfered = 0; 64775fa9ea6SGuennadi Liakhovetski usdhi6_dma_kill(host); 64875fa9ea6SGuennadi Liakhovetski usdhi6_dma_release(host); 64975fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), 65075fa9ea6SGuennadi Liakhovetski "DMA failed: %d, falling back to PIO\n", data->error); 65175fa9ea6SGuennadi Liakhovetski return; 65275fa9ea6SGuennadi Liakhovetski } 65375fa9ea6SGuennadi Liakhovetski 65475fa9ea6SGuennadi Liakhovetski /* 65575fa9ea6SGuennadi Liakhovetski * The datasheet tells us to check a response from the card, whereas 65675fa9ea6SGuennadi Liakhovetski * responses only come after the command phase, not after the data 65775fa9ea6SGuennadi Liakhovetski * phase. Let's check anyway. 65875fa9ea6SGuennadi Liakhovetski */ 65975fa9ea6SGuennadi Liakhovetski if (host->irq_status & USDHI6_SD_INFO1_RSP_END) 66075fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), "Unexpected response received!\n"); 66175fa9ea6SGuennadi Liakhovetski } 66275fa9ea6SGuennadi Liakhovetski 66375fa9ea6SGuennadi Liakhovetski static void usdhi6_dma_kick(struct usdhi6_host *host) 66475fa9ea6SGuennadi Liakhovetski { 66575fa9ea6SGuennadi Liakhovetski if (host->mrq->data->flags & MMC_DATA_READ) 66675fa9ea6SGuennadi Liakhovetski dma_async_issue_pending(host->chan_rx); 66775fa9ea6SGuennadi Liakhovetski else 66875fa9ea6SGuennadi Liakhovetski dma_async_issue_pending(host->chan_tx); 66975fa9ea6SGuennadi Liakhovetski } 67075fa9ea6SGuennadi Liakhovetski 67175fa9ea6SGuennadi Liakhovetski static void usdhi6_dma_request(struct usdhi6_host *host, phys_addr_t start) 67275fa9ea6SGuennadi Liakhovetski { 67375fa9ea6SGuennadi Liakhovetski struct dma_slave_config cfg = { 67475fa9ea6SGuennadi Liakhovetski .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, 67575fa9ea6SGuennadi Liakhovetski .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, 67675fa9ea6SGuennadi Liakhovetski }; 67775fa9ea6SGuennadi Liakhovetski int ret; 67875fa9ea6SGuennadi Liakhovetski 679e6d82a7bSPeter Ujfalusi host->chan_tx = dma_request_chan(mmc_dev(host->mmc), "tx"); 68075fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s: TX: got channel %p\n", __func__, 68175fa9ea6SGuennadi Liakhovetski host->chan_tx); 68275fa9ea6SGuennadi Liakhovetski 683e6d82a7bSPeter Ujfalusi if (IS_ERR(host->chan_tx)) { 684e6d82a7bSPeter Ujfalusi host->chan_tx = NULL; 68575fa9ea6SGuennadi Liakhovetski return; 686e6d82a7bSPeter Ujfalusi } 68775fa9ea6SGuennadi Liakhovetski 68875fa9ea6SGuennadi Liakhovetski cfg.direction = DMA_MEM_TO_DEV; 68975fa9ea6SGuennadi Liakhovetski cfg.dst_addr = start + USDHI6_SD_BUF0; 69075fa9ea6SGuennadi Liakhovetski cfg.dst_maxburst = 128; /* 128 words * 4 bytes = 512 bytes */ 69175fa9ea6SGuennadi Liakhovetski cfg.src_addr = 0; 69275fa9ea6SGuennadi Liakhovetski ret = dmaengine_slave_config(host->chan_tx, &cfg); 69375fa9ea6SGuennadi Liakhovetski if (ret < 0) 69475fa9ea6SGuennadi Liakhovetski goto e_release_tx; 69575fa9ea6SGuennadi Liakhovetski 696e6d82a7bSPeter Ujfalusi host->chan_rx = dma_request_chan(mmc_dev(host->mmc), "rx"); 69775fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s: RX: got channel %p\n", __func__, 69875fa9ea6SGuennadi Liakhovetski host->chan_rx); 69975fa9ea6SGuennadi Liakhovetski 700e6d82a7bSPeter Ujfalusi if (IS_ERR(host->chan_rx)) { 701e6d82a7bSPeter Ujfalusi host->chan_rx = NULL; 70275fa9ea6SGuennadi Liakhovetski goto e_release_tx; 703e6d82a7bSPeter Ujfalusi } 70475fa9ea6SGuennadi Liakhovetski 70575fa9ea6SGuennadi Liakhovetski cfg.direction = DMA_DEV_TO_MEM; 70675fa9ea6SGuennadi Liakhovetski cfg.src_addr = cfg.dst_addr; 70775fa9ea6SGuennadi Liakhovetski cfg.src_maxburst = 128; /* 128 words * 4 bytes = 512 bytes */ 70875fa9ea6SGuennadi Liakhovetski cfg.dst_addr = 0; 70975fa9ea6SGuennadi Liakhovetski ret = dmaengine_slave_config(host->chan_rx, &cfg); 71075fa9ea6SGuennadi Liakhovetski if (ret < 0) 71175fa9ea6SGuennadi Liakhovetski goto e_release_rx; 71275fa9ea6SGuennadi Liakhovetski 71375fa9ea6SGuennadi Liakhovetski return; 71475fa9ea6SGuennadi Liakhovetski 71575fa9ea6SGuennadi Liakhovetski e_release_rx: 71675fa9ea6SGuennadi Liakhovetski dma_release_channel(host->chan_rx); 71775fa9ea6SGuennadi Liakhovetski host->chan_rx = NULL; 71875fa9ea6SGuennadi Liakhovetski e_release_tx: 71975fa9ea6SGuennadi Liakhovetski dma_release_channel(host->chan_tx); 72075fa9ea6SGuennadi Liakhovetski host->chan_tx = NULL; 72175fa9ea6SGuennadi Liakhovetski } 72275fa9ea6SGuennadi Liakhovetski 72375fa9ea6SGuennadi Liakhovetski /* API helpers */ 72475fa9ea6SGuennadi Liakhovetski 72575fa9ea6SGuennadi Liakhovetski static void usdhi6_clk_set(struct usdhi6_host *host, struct mmc_ios *ios) 72675fa9ea6SGuennadi Liakhovetski { 72775fa9ea6SGuennadi Liakhovetski unsigned long rate = ios->clock; 72875fa9ea6SGuennadi Liakhovetski u32 val; 72975fa9ea6SGuennadi Liakhovetski unsigned int i; 73075fa9ea6SGuennadi Liakhovetski 73175fa9ea6SGuennadi Liakhovetski for (i = 1000; i; i--) { 73275fa9ea6SGuennadi Liakhovetski if (usdhi6_read(host, USDHI6_SD_INFO2) & USDHI6_SD_INFO2_SCLKDIVEN) 73375fa9ea6SGuennadi Liakhovetski break; 73475fa9ea6SGuennadi Liakhovetski usleep_range(10, 100); 73575fa9ea6SGuennadi Liakhovetski } 73675fa9ea6SGuennadi Liakhovetski 73775fa9ea6SGuennadi Liakhovetski if (!i) { 73875fa9ea6SGuennadi Liakhovetski dev_err(mmc_dev(host->mmc), "SD bus busy, clock set aborted\n"); 73975fa9ea6SGuennadi Liakhovetski return; 74075fa9ea6SGuennadi Liakhovetski } 74175fa9ea6SGuennadi Liakhovetski 74275fa9ea6SGuennadi Liakhovetski val = usdhi6_read(host, USDHI6_SD_CLK_CTRL) & ~USDHI6_SD_CLK_CTRL_DIV_MASK; 74375fa9ea6SGuennadi Liakhovetski 74475fa9ea6SGuennadi Liakhovetski if (rate) { 74575fa9ea6SGuennadi Liakhovetski unsigned long new_rate; 74675fa9ea6SGuennadi Liakhovetski 74775fa9ea6SGuennadi Liakhovetski if (host->imclk <= rate) { 74875fa9ea6SGuennadi Liakhovetski if (ios->timing != MMC_TIMING_UHS_DDR50) { 74975fa9ea6SGuennadi Liakhovetski /* Cannot have 1-to-1 clock in DDR mode */ 75075fa9ea6SGuennadi Liakhovetski new_rate = host->imclk; 75175fa9ea6SGuennadi Liakhovetski val |= 0xff; 75275fa9ea6SGuennadi Liakhovetski } else { 75375fa9ea6SGuennadi Liakhovetski new_rate = host->imclk / 2; 75475fa9ea6SGuennadi Liakhovetski } 75575fa9ea6SGuennadi Liakhovetski } else { 75675fa9ea6SGuennadi Liakhovetski unsigned long div = 75775fa9ea6SGuennadi Liakhovetski roundup_pow_of_two(DIV_ROUND_UP(host->imclk, rate)); 75875fa9ea6SGuennadi Liakhovetski val |= div >> 2; 75975fa9ea6SGuennadi Liakhovetski new_rate = host->imclk / div; 76075fa9ea6SGuennadi Liakhovetski } 76175fa9ea6SGuennadi Liakhovetski 76275fa9ea6SGuennadi Liakhovetski if (host->rate == new_rate) 76375fa9ea6SGuennadi Liakhovetski return; 76475fa9ea6SGuennadi Liakhovetski 76575fa9ea6SGuennadi Liakhovetski host->rate = new_rate; 76675fa9ea6SGuennadi Liakhovetski 76775fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "target %lu, div %u, set %lu\n", 76875fa9ea6SGuennadi Liakhovetski rate, (val & 0xff) << 2, new_rate); 76975fa9ea6SGuennadi Liakhovetski } 77075fa9ea6SGuennadi Liakhovetski 77175fa9ea6SGuennadi Liakhovetski /* 77275fa9ea6SGuennadi Liakhovetski * if old or new rate is equal to input rate, have to switch the clock 77375fa9ea6SGuennadi Liakhovetski * off before changing and on after 77475fa9ea6SGuennadi Liakhovetski */ 77575fa9ea6SGuennadi Liakhovetski if (host->imclk == rate || host->imclk == host->rate || !rate) 77675fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_CLK_CTRL, 77775fa9ea6SGuennadi Liakhovetski val & ~USDHI6_SD_CLK_CTRL_SCLKEN); 77875fa9ea6SGuennadi Liakhovetski 77975fa9ea6SGuennadi Liakhovetski if (!rate) { 78075fa9ea6SGuennadi Liakhovetski host->rate = 0; 78175fa9ea6SGuennadi Liakhovetski return; 78275fa9ea6SGuennadi Liakhovetski } 78375fa9ea6SGuennadi Liakhovetski 78475fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_CLK_CTRL, val); 78575fa9ea6SGuennadi Liakhovetski 78675fa9ea6SGuennadi Liakhovetski if (host->imclk == rate || host->imclk == host->rate || 78775fa9ea6SGuennadi Liakhovetski !(val & USDHI6_SD_CLK_CTRL_SCLKEN)) 78875fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_CLK_CTRL, 78975fa9ea6SGuennadi Liakhovetski val | USDHI6_SD_CLK_CTRL_SCLKEN); 79075fa9ea6SGuennadi Liakhovetski } 79175fa9ea6SGuennadi Liakhovetski 79275fa9ea6SGuennadi Liakhovetski static void usdhi6_set_power(struct usdhi6_host *host, struct mmc_ios *ios) 79375fa9ea6SGuennadi Liakhovetski { 79475fa9ea6SGuennadi Liakhovetski struct mmc_host *mmc = host->mmc; 79575fa9ea6SGuennadi Liakhovetski 79675fa9ea6SGuennadi Liakhovetski if (!IS_ERR(mmc->supply.vmmc)) 79775fa9ea6SGuennadi Liakhovetski /* Errors ignored... */ 79875fa9ea6SGuennadi Liakhovetski mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 79975fa9ea6SGuennadi Liakhovetski ios->power_mode ? ios->vdd : 0); 80075fa9ea6SGuennadi Liakhovetski } 80175fa9ea6SGuennadi Liakhovetski 80275fa9ea6SGuennadi Liakhovetski static int usdhi6_reset(struct usdhi6_host *host) 80375fa9ea6SGuennadi Liakhovetski { 80475fa9ea6SGuennadi Liakhovetski int i; 80575fa9ea6SGuennadi Liakhovetski 80675fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SOFT_RST, USDHI6_SOFT_RST_RESERVED); 80775fa9ea6SGuennadi Liakhovetski cpu_relax(); 80875fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SOFT_RST, USDHI6_SOFT_RST_RESERVED | USDHI6_SOFT_RST_RESET); 80975fa9ea6SGuennadi Liakhovetski for (i = 1000; i; i--) 81075fa9ea6SGuennadi Liakhovetski if (usdhi6_read(host, USDHI6_SOFT_RST) & USDHI6_SOFT_RST_RESET) 81175fa9ea6SGuennadi Liakhovetski break; 81275fa9ea6SGuennadi Liakhovetski 81375fa9ea6SGuennadi Liakhovetski return i ? 0 : -ETIMEDOUT; 81475fa9ea6SGuennadi Liakhovetski } 81575fa9ea6SGuennadi Liakhovetski 81675fa9ea6SGuennadi Liakhovetski static void usdhi6_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) 81775fa9ea6SGuennadi Liakhovetski { 81875fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = mmc_priv(mmc); 81975fa9ea6SGuennadi Liakhovetski u32 option, mode; 82075fa9ea6SGuennadi Liakhovetski int ret; 82175fa9ea6SGuennadi Liakhovetski 82275fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(mmc), "%uHz, OCR: %u, power %u, bus-width %u, timing %u\n", 82375fa9ea6SGuennadi Liakhovetski ios->clock, ios->vdd, ios->power_mode, ios->bus_width, ios->timing); 82475fa9ea6SGuennadi Liakhovetski 82575fa9ea6SGuennadi Liakhovetski switch (ios->power_mode) { 82675fa9ea6SGuennadi Liakhovetski case MMC_POWER_OFF: 82775fa9ea6SGuennadi Liakhovetski usdhi6_set_power(host, ios); 82875fa9ea6SGuennadi Liakhovetski usdhi6_only_cd(host); 82975fa9ea6SGuennadi Liakhovetski break; 83075fa9ea6SGuennadi Liakhovetski case MMC_POWER_UP: 83175fa9ea6SGuennadi Liakhovetski /* 83275fa9ea6SGuennadi Liakhovetski * We only also touch USDHI6_SD_OPTION from .request(), which 83375fa9ea6SGuennadi Liakhovetski * cannot race with MMC_POWER_UP 83475fa9ea6SGuennadi Liakhovetski */ 83575fa9ea6SGuennadi Liakhovetski ret = usdhi6_reset(host); 83675fa9ea6SGuennadi Liakhovetski if (ret < 0) { 83775fa9ea6SGuennadi Liakhovetski dev_err(mmc_dev(mmc), "Cannot reset the interface!\n"); 83875fa9ea6SGuennadi Liakhovetski } else { 83975fa9ea6SGuennadi Liakhovetski usdhi6_set_power(host, ios); 84075fa9ea6SGuennadi Liakhovetski usdhi6_only_cd(host); 84175fa9ea6SGuennadi Liakhovetski } 84275fa9ea6SGuennadi Liakhovetski break; 84375fa9ea6SGuennadi Liakhovetski case MMC_POWER_ON: 84475fa9ea6SGuennadi Liakhovetski option = usdhi6_read(host, USDHI6_SD_OPTION); 84575fa9ea6SGuennadi Liakhovetski /* 84675fa9ea6SGuennadi Liakhovetski * The eMMC standard only allows 4 or 8 bits in the DDR mode, 84775fa9ea6SGuennadi Liakhovetski * the same probably holds for SD cards. We check here anyway, 84875fa9ea6SGuennadi Liakhovetski * since the datasheet explicitly requires 4 bits for DDR. 84975fa9ea6SGuennadi Liakhovetski */ 85075fa9ea6SGuennadi Liakhovetski if (ios->bus_width == MMC_BUS_WIDTH_1) { 85175fa9ea6SGuennadi Liakhovetski if (ios->timing == MMC_TIMING_UHS_DDR50) 85275fa9ea6SGuennadi Liakhovetski dev_err(mmc_dev(mmc), 85375fa9ea6SGuennadi Liakhovetski "4 bits are required for DDR\n"); 85475fa9ea6SGuennadi Liakhovetski option |= USDHI6_SD_OPTION_WIDTH_1; 85575fa9ea6SGuennadi Liakhovetski mode = 0; 85675fa9ea6SGuennadi Liakhovetski } else { 85775fa9ea6SGuennadi Liakhovetski option &= ~USDHI6_SD_OPTION_WIDTH_1; 85875fa9ea6SGuennadi Liakhovetski mode = ios->timing == MMC_TIMING_UHS_DDR50; 85975fa9ea6SGuennadi Liakhovetski } 86075fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_OPTION, option); 86175fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SDIF_MODE, mode); 86275fa9ea6SGuennadi Liakhovetski break; 86375fa9ea6SGuennadi Liakhovetski } 86475fa9ea6SGuennadi Liakhovetski 86575fa9ea6SGuennadi Liakhovetski if (host->rate != ios->clock) 86675fa9ea6SGuennadi Liakhovetski usdhi6_clk_set(host, ios); 86775fa9ea6SGuennadi Liakhovetski } 86875fa9ea6SGuennadi Liakhovetski 86975fa9ea6SGuennadi Liakhovetski /* This is data timeout. Response timeout is fixed to 640 clock cycles */ 87075fa9ea6SGuennadi Liakhovetski static void usdhi6_timeout_set(struct usdhi6_host *host) 87175fa9ea6SGuennadi Liakhovetski { 87275fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq = host->mrq; 87375fa9ea6SGuennadi Liakhovetski u32 val; 87475fa9ea6SGuennadi Liakhovetski unsigned long ticks; 87575fa9ea6SGuennadi Liakhovetski 87675fa9ea6SGuennadi Liakhovetski if (!mrq->data) 87775fa9ea6SGuennadi Liakhovetski ticks = host->rate / 1000 * mrq->cmd->busy_timeout; 87875fa9ea6SGuennadi Liakhovetski else 87975fa9ea6SGuennadi Liakhovetski ticks = host->rate / 1000000 * (mrq->data->timeout_ns / 1000) + 88075fa9ea6SGuennadi Liakhovetski mrq->data->timeout_clks; 88175fa9ea6SGuennadi Liakhovetski 88275fa9ea6SGuennadi Liakhovetski if (!ticks || ticks > 1 << 27) 88375fa9ea6SGuennadi Liakhovetski /* Max timeout */ 88475fa9ea6SGuennadi Liakhovetski val = 14; 88575fa9ea6SGuennadi Liakhovetski else if (ticks < 1 << 13) 88675fa9ea6SGuennadi Liakhovetski /* Min timeout */ 88775fa9ea6SGuennadi Liakhovetski val = 0; 88875fa9ea6SGuennadi Liakhovetski else 88975fa9ea6SGuennadi Liakhovetski val = order_base_2(ticks) - 13; 89075fa9ea6SGuennadi Liakhovetski 89175fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "Set %s timeout %lu ticks @ %lu Hz\n", 89275fa9ea6SGuennadi Liakhovetski mrq->data ? "data" : "cmd", ticks, host->rate); 89375fa9ea6SGuennadi Liakhovetski 89475fa9ea6SGuennadi Liakhovetski /* Timeout Counter mask: 0xf0 */ 89575fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_OPTION, (val << USDHI6_SD_OPTION_TIMEOUT_SHIFT) | 89675fa9ea6SGuennadi Liakhovetski (usdhi6_read(host, USDHI6_SD_OPTION) & ~USDHI6_SD_OPTION_TIMEOUT_MASK)); 89775fa9ea6SGuennadi Liakhovetski } 89875fa9ea6SGuennadi Liakhovetski 89975fa9ea6SGuennadi Liakhovetski static void usdhi6_request_done(struct usdhi6_host *host) 90075fa9ea6SGuennadi Liakhovetski { 90175fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq = host->mrq; 90275fa9ea6SGuennadi Liakhovetski struct mmc_data *data = mrq->data; 90375fa9ea6SGuennadi Liakhovetski 90475fa9ea6SGuennadi Liakhovetski if (WARN(host->pg.page || host->head_pg.page, 90513fe0ec3SGuennadi Liakhovetski "Page %p or %p not unmapped: wait %u, CMD%d(%c) @ +0x%zx %ux%u in SG%u!\n", 90675fa9ea6SGuennadi Liakhovetski host->pg.page, host->head_pg.page, host->wait, mrq->cmd->opcode, 90775fa9ea6SGuennadi Liakhovetski data ? (data->flags & MMC_DATA_READ ? 'R' : 'W') : '-', 90875fa9ea6SGuennadi Liakhovetski data ? host->offset : 0, data ? data->blocks : 0, 90975fa9ea6SGuennadi Liakhovetski data ? data->blksz : 0, data ? data->sg_len : 0)) 91075fa9ea6SGuennadi Liakhovetski usdhi6_sg_unmap(host, true); 91175fa9ea6SGuennadi Liakhovetski 91275fa9ea6SGuennadi Liakhovetski if (mrq->cmd->error || 91375fa9ea6SGuennadi Liakhovetski (data && data->error) || 91475fa9ea6SGuennadi Liakhovetski (mrq->stop && mrq->stop->error)) 91575fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(CMD%d: %ux%u): err %d %d %d\n", 91675fa9ea6SGuennadi Liakhovetski __func__, mrq->cmd->opcode, data ? data->blocks : 0, 91775fa9ea6SGuennadi Liakhovetski data ? data->blksz : 0, 91875fa9ea6SGuennadi Liakhovetski mrq->cmd->error, 91975fa9ea6SGuennadi Liakhovetski data ? data->error : 1, 92075fa9ea6SGuennadi Liakhovetski mrq->stop ? mrq->stop->error : 1); 92175fa9ea6SGuennadi Liakhovetski 92275fa9ea6SGuennadi Liakhovetski /* Disable DMA */ 92375fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_CC_EXT_MODE, 0); 92475fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_REQUEST; 92575fa9ea6SGuennadi Liakhovetski host->mrq = NULL; 92675fa9ea6SGuennadi Liakhovetski 92775fa9ea6SGuennadi Liakhovetski mmc_request_done(host->mmc, mrq); 92875fa9ea6SGuennadi Liakhovetski } 92975fa9ea6SGuennadi Liakhovetski 93075fa9ea6SGuennadi Liakhovetski static int usdhi6_cmd_flags(struct usdhi6_host *host) 93175fa9ea6SGuennadi Liakhovetski { 93275fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq = host->mrq; 93375fa9ea6SGuennadi Liakhovetski struct mmc_command *cmd = mrq->cmd; 93475fa9ea6SGuennadi Liakhovetski u16 opc = cmd->opcode; 93575fa9ea6SGuennadi Liakhovetski 93675fa9ea6SGuennadi Liakhovetski if (host->app_cmd) { 93775fa9ea6SGuennadi Liakhovetski host->app_cmd = false; 93875fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_APP; 93975fa9ea6SGuennadi Liakhovetski } 94075fa9ea6SGuennadi Liakhovetski 94175fa9ea6SGuennadi Liakhovetski if (mrq->data) { 94275fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_DATA; 94375fa9ea6SGuennadi Liakhovetski 94475fa9ea6SGuennadi Liakhovetski if (mrq->data->flags & MMC_DATA_READ) 94575fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_READ; 94675fa9ea6SGuennadi Liakhovetski 94775fa9ea6SGuennadi Liakhovetski if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK || 94875fa9ea6SGuennadi Liakhovetski cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK || 94975fa9ea6SGuennadi Liakhovetski (cmd->opcode == SD_IO_RW_EXTENDED && 95075fa9ea6SGuennadi Liakhovetski mrq->data->blocks > 1)) { 95175fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_MULTI; 95275fa9ea6SGuennadi Liakhovetski if (!mrq->stop) 95375fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_CMD12_AUTO_OFF; 95475fa9ea6SGuennadi Liakhovetski } 95575fa9ea6SGuennadi Liakhovetski 95675fa9ea6SGuennadi Liakhovetski switch (mmc_resp_type(cmd)) { 95775fa9ea6SGuennadi Liakhovetski case MMC_RSP_NONE: 95875fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_MODE_RSP_NONE; 95975fa9ea6SGuennadi Liakhovetski break; 96075fa9ea6SGuennadi Liakhovetski case MMC_RSP_R1: 96175fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_MODE_RSP_R1; 96275fa9ea6SGuennadi Liakhovetski break; 96375fa9ea6SGuennadi Liakhovetski case MMC_RSP_R1B: 96475fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_MODE_RSP_R1B; 96575fa9ea6SGuennadi Liakhovetski break; 96675fa9ea6SGuennadi Liakhovetski case MMC_RSP_R2: 96775fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_MODE_RSP_R2; 96875fa9ea6SGuennadi Liakhovetski break; 96975fa9ea6SGuennadi Liakhovetski case MMC_RSP_R3: 97075fa9ea6SGuennadi Liakhovetski opc |= USDHI6_SD_CMD_MODE_RSP_R3; 97175fa9ea6SGuennadi Liakhovetski break; 97275fa9ea6SGuennadi Liakhovetski default: 97375fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), 97475fa9ea6SGuennadi Liakhovetski "Unknown response type %d\n", 97575fa9ea6SGuennadi Liakhovetski mmc_resp_type(cmd)); 97675fa9ea6SGuennadi Liakhovetski return -EINVAL; 97775fa9ea6SGuennadi Liakhovetski } 97875fa9ea6SGuennadi Liakhovetski } 97975fa9ea6SGuennadi Liakhovetski 98075fa9ea6SGuennadi Liakhovetski return opc; 98175fa9ea6SGuennadi Liakhovetski } 98275fa9ea6SGuennadi Liakhovetski 98375fa9ea6SGuennadi Liakhovetski static int usdhi6_rq_start(struct usdhi6_host *host) 98475fa9ea6SGuennadi Liakhovetski { 98575fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq = host->mrq; 98675fa9ea6SGuennadi Liakhovetski struct mmc_command *cmd = mrq->cmd; 98775fa9ea6SGuennadi Liakhovetski struct mmc_data *data = mrq->data; 98875fa9ea6SGuennadi Liakhovetski int opc = usdhi6_cmd_flags(host); 98975fa9ea6SGuennadi Liakhovetski int i; 99075fa9ea6SGuennadi Liakhovetski 99175fa9ea6SGuennadi Liakhovetski if (opc < 0) 99275fa9ea6SGuennadi Liakhovetski return opc; 99375fa9ea6SGuennadi Liakhovetski 99475fa9ea6SGuennadi Liakhovetski for (i = 1000; i; i--) { 99575fa9ea6SGuennadi Liakhovetski if (!(usdhi6_read(host, USDHI6_SD_INFO2) & USDHI6_SD_INFO2_CBSY)) 99675fa9ea6SGuennadi Liakhovetski break; 99775fa9ea6SGuennadi Liakhovetski usleep_range(10, 100); 99875fa9ea6SGuennadi Liakhovetski } 99975fa9ea6SGuennadi Liakhovetski 100075fa9ea6SGuennadi Liakhovetski if (!i) { 100175fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "Command active, request aborted\n"); 100275fa9ea6SGuennadi Liakhovetski return -EAGAIN; 100375fa9ea6SGuennadi Liakhovetski } 100475fa9ea6SGuennadi Liakhovetski 100575fa9ea6SGuennadi Liakhovetski if (data) { 100675fa9ea6SGuennadi Liakhovetski bool use_dma; 100775fa9ea6SGuennadi Liakhovetski int ret = 0; 100875fa9ea6SGuennadi Liakhovetski 100975fa9ea6SGuennadi Liakhovetski host->page_idx = 0; 101075fa9ea6SGuennadi Liakhovetski 101175fa9ea6SGuennadi Liakhovetski if (cmd->opcode == SD_IO_RW_EXTENDED && data->blocks > 1) { 101275fa9ea6SGuennadi Liakhovetski switch (data->blksz) { 101375fa9ea6SGuennadi Liakhovetski case 512: 101475fa9ea6SGuennadi Liakhovetski break; 101575fa9ea6SGuennadi Liakhovetski case 32: 101675fa9ea6SGuennadi Liakhovetski case 64: 101775fa9ea6SGuennadi Liakhovetski case 128: 101875fa9ea6SGuennadi Liakhovetski case 256: 101975fa9ea6SGuennadi Liakhovetski if (mrq->stop) 102075fa9ea6SGuennadi Liakhovetski ret = -EINVAL; 102175fa9ea6SGuennadi Liakhovetski break; 102275fa9ea6SGuennadi Liakhovetski default: 102375fa9ea6SGuennadi Liakhovetski ret = -EINVAL; 102475fa9ea6SGuennadi Liakhovetski } 102575fa9ea6SGuennadi Liakhovetski } else if ((cmd->opcode == MMC_READ_MULTIPLE_BLOCK || 102675fa9ea6SGuennadi Liakhovetski cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) && 102775fa9ea6SGuennadi Liakhovetski data->blksz != 512) { 102875fa9ea6SGuennadi Liakhovetski ret = -EINVAL; 102975fa9ea6SGuennadi Liakhovetski } 103075fa9ea6SGuennadi Liakhovetski 103175fa9ea6SGuennadi Liakhovetski if (ret < 0) { 103275fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), "%s(): %u blocks of %u bytes\n", 103375fa9ea6SGuennadi Liakhovetski __func__, data->blocks, data->blksz); 103475fa9ea6SGuennadi Liakhovetski return -EINVAL; 103575fa9ea6SGuennadi Liakhovetski } 103675fa9ea6SGuennadi Liakhovetski 103775fa9ea6SGuennadi Liakhovetski if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK || 103875fa9ea6SGuennadi Liakhovetski cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK || 103975fa9ea6SGuennadi Liakhovetski (cmd->opcode == SD_IO_RW_EXTENDED && 104075fa9ea6SGuennadi Liakhovetski data->blocks > 1)) 104175fa9ea6SGuennadi Liakhovetski usdhi6_sg_prep(host); 104275fa9ea6SGuennadi Liakhovetski 104375fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_SIZE, data->blksz); 104475fa9ea6SGuennadi Liakhovetski 104575fa9ea6SGuennadi Liakhovetski if ((data->blksz >= USDHI6_MIN_DMA || 104675fa9ea6SGuennadi Liakhovetski data->blocks > 1) && 104775fa9ea6SGuennadi Liakhovetski (data->blksz % 4 || 104875fa9ea6SGuennadi Liakhovetski data->sg->offset % 4)) 104975fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), 105075fa9ea6SGuennadi Liakhovetski "Bad SG of %u: %ux%u @ %u\n", data->sg_len, 105175fa9ea6SGuennadi Liakhovetski data->blksz, data->blocks, data->sg->offset); 105275fa9ea6SGuennadi Liakhovetski 105375fa9ea6SGuennadi Liakhovetski /* Enable DMA for USDHI6_MIN_DMA bytes or more */ 105475fa9ea6SGuennadi Liakhovetski use_dma = data->blksz >= USDHI6_MIN_DMA && 105575fa9ea6SGuennadi Liakhovetski !(data->blksz % 4) && 105675fa9ea6SGuennadi Liakhovetski usdhi6_dma_start(host) >= DMA_MIN_COOKIE; 105775fa9ea6SGuennadi Liakhovetski 105875fa9ea6SGuennadi Liakhovetski if (use_dma) 105975fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_CC_EXT_MODE, USDHI6_CC_EXT_MODE_SDRW); 106075fa9ea6SGuennadi Liakhovetski 106175fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), 106275fa9ea6SGuennadi Liakhovetski "%s(): request opcode %u, %u blocks of %u bytes in %u segments, %s %s @+0x%x%s\n", 106375fa9ea6SGuennadi Liakhovetski __func__, cmd->opcode, data->blocks, data->blksz, 106475fa9ea6SGuennadi Liakhovetski data->sg_len, use_dma ? "DMA" : "PIO", 106575fa9ea6SGuennadi Liakhovetski data->flags & MMC_DATA_READ ? "read" : "write", 106675fa9ea6SGuennadi Liakhovetski data->sg->offset, mrq->stop ? " + stop" : ""); 106775fa9ea6SGuennadi Liakhovetski } else { 106875fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): request opcode %u\n", 106975fa9ea6SGuennadi Liakhovetski __func__, cmd->opcode); 107075fa9ea6SGuennadi Liakhovetski } 107175fa9ea6SGuennadi Liakhovetski 107275fa9ea6SGuennadi Liakhovetski /* We have to get a command completion interrupt with DMA too */ 107375fa9ea6SGuennadi Liakhovetski usdhi6_wait_for_resp(host); 107475fa9ea6SGuennadi Liakhovetski 107575fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_CMD; 107675fa9ea6SGuennadi Liakhovetski schedule_delayed_work(&host->timeout_work, host->timeout); 107775fa9ea6SGuennadi Liakhovetski 107875fa9ea6SGuennadi Liakhovetski /* SEC bit is required to enable block counting by the core */ 107975fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_STOP, 108075fa9ea6SGuennadi Liakhovetski data && data->blocks > 1 ? USDHI6_SD_STOP_SEC : 0); 108175fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_ARG, cmd->arg); 108275fa9ea6SGuennadi Liakhovetski 108375fa9ea6SGuennadi Liakhovetski /* Kick command execution */ 108475fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_CMD, opc); 108575fa9ea6SGuennadi Liakhovetski 108675fa9ea6SGuennadi Liakhovetski return 0; 108775fa9ea6SGuennadi Liakhovetski } 108875fa9ea6SGuennadi Liakhovetski 108975fa9ea6SGuennadi Liakhovetski static void usdhi6_request(struct mmc_host *mmc, struct mmc_request *mrq) 109075fa9ea6SGuennadi Liakhovetski { 109175fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = mmc_priv(mmc); 109275fa9ea6SGuennadi Liakhovetski int ret; 109375fa9ea6SGuennadi Liakhovetski 109475fa9ea6SGuennadi Liakhovetski cancel_delayed_work_sync(&host->timeout_work); 109575fa9ea6SGuennadi Liakhovetski 109675fa9ea6SGuennadi Liakhovetski host->mrq = mrq; 109775fa9ea6SGuennadi Liakhovetski host->sg = NULL; 109875fa9ea6SGuennadi Liakhovetski 109975fa9ea6SGuennadi Liakhovetski usdhi6_timeout_set(host); 110075fa9ea6SGuennadi Liakhovetski ret = usdhi6_rq_start(host); 110175fa9ea6SGuennadi Liakhovetski if (ret < 0) { 110275fa9ea6SGuennadi Liakhovetski mrq->cmd->error = ret; 110375fa9ea6SGuennadi Liakhovetski usdhi6_request_done(host); 110475fa9ea6SGuennadi Liakhovetski } 110575fa9ea6SGuennadi Liakhovetski } 110675fa9ea6SGuennadi Liakhovetski 110775fa9ea6SGuennadi Liakhovetski static int usdhi6_get_cd(struct mmc_host *mmc) 110875fa9ea6SGuennadi Liakhovetski { 110975fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = mmc_priv(mmc); 111075fa9ea6SGuennadi Liakhovetski /* Read is atomic, no need to lock */ 111175fa9ea6SGuennadi Liakhovetski u32 status = usdhi6_read(host, USDHI6_SD_INFO1) & USDHI6_SD_INFO1_CD; 111275fa9ea6SGuennadi Liakhovetski 111375fa9ea6SGuennadi Liakhovetski /* 111475fa9ea6SGuennadi Liakhovetski * level status.CD CD_ACTIVE_HIGH card present 111575fa9ea6SGuennadi Liakhovetski * 1 0 0 0 111675fa9ea6SGuennadi Liakhovetski * 1 0 1 1 111775fa9ea6SGuennadi Liakhovetski * 0 1 0 1 111875fa9ea6SGuennadi Liakhovetski * 0 1 1 0 111975fa9ea6SGuennadi Liakhovetski */ 112075fa9ea6SGuennadi Liakhovetski return !status ^ !(mmc->caps2 & MMC_CAP2_CD_ACTIVE_HIGH); 112175fa9ea6SGuennadi Liakhovetski } 112275fa9ea6SGuennadi Liakhovetski 112375fa9ea6SGuennadi Liakhovetski static int usdhi6_get_ro(struct mmc_host *mmc) 112475fa9ea6SGuennadi Liakhovetski { 112575fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = mmc_priv(mmc); 112675fa9ea6SGuennadi Liakhovetski /* No locking as above */ 112775fa9ea6SGuennadi Liakhovetski u32 status = usdhi6_read(host, USDHI6_SD_INFO1) & USDHI6_SD_INFO1_WP; 112875fa9ea6SGuennadi Liakhovetski 112975fa9ea6SGuennadi Liakhovetski /* 113075fa9ea6SGuennadi Liakhovetski * level status.WP RO_ACTIVE_HIGH card read-only 113175fa9ea6SGuennadi Liakhovetski * 1 0 0 0 113275fa9ea6SGuennadi Liakhovetski * 1 0 1 1 113375fa9ea6SGuennadi Liakhovetski * 0 1 0 1 113475fa9ea6SGuennadi Liakhovetski * 0 1 1 0 113575fa9ea6SGuennadi Liakhovetski */ 113675fa9ea6SGuennadi Liakhovetski return !status ^ !(mmc->caps2 & MMC_CAP2_RO_ACTIVE_HIGH); 113775fa9ea6SGuennadi Liakhovetski } 113875fa9ea6SGuennadi Liakhovetski 113975fa9ea6SGuennadi Liakhovetski static void usdhi6_enable_sdio_irq(struct mmc_host *mmc, int enable) 114075fa9ea6SGuennadi Liakhovetski { 114175fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = mmc_priv(mmc); 114275fa9ea6SGuennadi Liakhovetski 114375fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(mmc), "%s(): %sable\n", __func__, enable ? "en" : "dis"); 114475fa9ea6SGuennadi Liakhovetski 114575fa9ea6SGuennadi Liakhovetski if (enable) { 114675fa9ea6SGuennadi Liakhovetski host->sdio_mask = USDHI6_SDIO_INFO1_IRQ & ~USDHI6_SDIO_INFO1_IOIRQ; 114775fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SDIO_INFO1_MASK, host->sdio_mask); 114875fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SDIO_MODE, 1); 114975fa9ea6SGuennadi Liakhovetski } else { 115075fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SDIO_MODE, 0); 115175fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SDIO_INFO1_MASK, USDHI6_SDIO_INFO1_IRQ); 115275fa9ea6SGuennadi Liakhovetski host->sdio_mask = USDHI6_SDIO_INFO1_IRQ; 115375fa9ea6SGuennadi Liakhovetski } 115475fa9ea6SGuennadi Liakhovetski } 115575fa9ea6SGuennadi Liakhovetski 1156488aab3dSLars Persson static int usdhi6_set_pinstates(struct usdhi6_host *host, int voltage) 1157488aab3dSLars Persson { 1158488aab3dSLars Persson if (IS_ERR(host->pins_uhs)) 1159488aab3dSLars Persson return 0; 1160488aab3dSLars Persson 1161488aab3dSLars Persson switch (voltage) { 1162488aab3dSLars Persson case MMC_SIGNAL_VOLTAGE_180: 1163488aab3dSLars Persson case MMC_SIGNAL_VOLTAGE_120: 1164488aab3dSLars Persson return pinctrl_select_state(host->pinctrl, 1165488aab3dSLars Persson host->pins_uhs); 1166488aab3dSLars Persson 1167488aab3dSLars Persson default: 1168c17e73c3SUlf Hansson return pinctrl_select_default_state(mmc_dev(host->mmc)); 1169488aab3dSLars Persson } 1170488aab3dSLars Persson } 1171488aab3dSLars Persson 11720cd59df9SLars Persson static int usdhi6_sig_volt_switch(struct mmc_host *mmc, struct mmc_ios *ios) 11730cd59df9SLars Persson { 11740cd59df9SLars Persson int ret; 11750cd59df9SLars Persson 11760cd59df9SLars Persson ret = mmc_regulator_set_vqmmc(mmc, ios); 1177488aab3dSLars Persson if (ret < 0) 1178488aab3dSLars Persson return ret; 11790cd59df9SLars Persson 1180488aab3dSLars Persson ret = usdhi6_set_pinstates(mmc_priv(mmc), ios->signal_voltage); 1181488aab3dSLars Persson if (ret) 1182488aab3dSLars Persson dev_warn_once(mmc_dev(mmc), 1183488aab3dSLars Persson "Failed to set pinstate err=%d\n", ret); 11840cd59df9SLars Persson return ret; 11850cd59df9SLars Persson } 11860cd59df9SLars Persson 118757e30c0cSJulia Lawall static const struct mmc_host_ops usdhi6_ops = { 118875fa9ea6SGuennadi Liakhovetski .request = usdhi6_request, 118975fa9ea6SGuennadi Liakhovetski .set_ios = usdhi6_set_ios, 119075fa9ea6SGuennadi Liakhovetski .get_cd = usdhi6_get_cd, 119175fa9ea6SGuennadi Liakhovetski .get_ro = usdhi6_get_ro, 119275fa9ea6SGuennadi Liakhovetski .enable_sdio_irq = usdhi6_enable_sdio_irq, 11930cd59df9SLars Persson .start_signal_voltage_switch = usdhi6_sig_volt_switch, 119475fa9ea6SGuennadi Liakhovetski }; 119575fa9ea6SGuennadi Liakhovetski 119675fa9ea6SGuennadi Liakhovetski /* State machine handlers */ 119775fa9ea6SGuennadi Liakhovetski 119875fa9ea6SGuennadi Liakhovetski static void usdhi6_resp_cmd12(struct usdhi6_host *host) 119975fa9ea6SGuennadi Liakhovetski { 120075fa9ea6SGuennadi Liakhovetski struct mmc_command *cmd = host->mrq->stop; 120175fa9ea6SGuennadi Liakhovetski cmd->resp[0] = usdhi6_read(host, USDHI6_SD_RSP10); 120275fa9ea6SGuennadi Liakhovetski } 120375fa9ea6SGuennadi Liakhovetski 120475fa9ea6SGuennadi Liakhovetski static void usdhi6_resp_read(struct usdhi6_host *host) 120575fa9ea6SGuennadi Liakhovetski { 120675fa9ea6SGuennadi Liakhovetski struct mmc_command *cmd = host->mrq->cmd; 120775fa9ea6SGuennadi Liakhovetski u32 *rsp = cmd->resp, tmp = 0; 120875fa9ea6SGuennadi Liakhovetski int i; 120975fa9ea6SGuennadi Liakhovetski 121075fa9ea6SGuennadi Liakhovetski /* 121175fa9ea6SGuennadi Liakhovetski * RSP10 39-8 121275fa9ea6SGuennadi Liakhovetski * RSP32 71-40 121375fa9ea6SGuennadi Liakhovetski * RSP54 103-72 121475fa9ea6SGuennadi Liakhovetski * RSP76 127-104 121575fa9ea6SGuennadi Liakhovetski * R2-type response: 121675fa9ea6SGuennadi Liakhovetski * resp[0] = r[127..96] 121775fa9ea6SGuennadi Liakhovetski * resp[1] = r[95..64] 121875fa9ea6SGuennadi Liakhovetski * resp[2] = r[63..32] 121975fa9ea6SGuennadi Liakhovetski * resp[3] = r[31..0] 122075fa9ea6SGuennadi Liakhovetski * Other responses: 122175fa9ea6SGuennadi Liakhovetski * resp[0] = r[39..8] 122275fa9ea6SGuennadi Liakhovetski */ 122375fa9ea6SGuennadi Liakhovetski 122475fa9ea6SGuennadi Liakhovetski if (mmc_resp_type(cmd) == MMC_RSP_NONE) 122575fa9ea6SGuennadi Liakhovetski return; 122675fa9ea6SGuennadi Liakhovetski 122775fa9ea6SGuennadi Liakhovetski if (!(host->irq_status & USDHI6_SD_INFO1_RSP_END)) { 122875fa9ea6SGuennadi Liakhovetski dev_err(mmc_dev(host->mmc), 122975fa9ea6SGuennadi Liakhovetski "CMD%d: response expected but is missing!\n", cmd->opcode); 123075fa9ea6SGuennadi Liakhovetski return; 123175fa9ea6SGuennadi Liakhovetski } 123275fa9ea6SGuennadi Liakhovetski 123375fa9ea6SGuennadi Liakhovetski if (mmc_resp_type(cmd) & MMC_RSP_136) 123475fa9ea6SGuennadi Liakhovetski for (i = 0; i < 4; i++) { 123575fa9ea6SGuennadi Liakhovetski if (i) 123675fa9ea6SGuennadi Liakhovetski rsp[3 - i] = tmp >> 24; 123775fa9ea6SGuennadi Liakhovetski tmp = usdhi6_read(host, USDHI6_SD_RSP10 + i * 8); 123875fa9ea6SGuennadi Liakhovetski rsp[3 - i] |= tmp << 8; 123975fa9ea6SGuennadi Liakhovetski } 124075fa9ea6SGuennadi Liakhovetski else if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK || 124175fa9ea6SGuennadi Liakhovetski cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) 124275fa9ea6SGuennadi Liakhovetski /* Read RSP54 to avoid conflict with auto CMD12 */ 124375fa9ea6SGuennadi Liakhovetski rsp[0] = usdhi6_read(host, USDHI6_SD_RSP54); 124475fa9ea6SGuennadi Liakhovetski else 124575fa9ea6SGuennadi Liakhovetski rsp[0] = usdhi6_read(host, USDHI6_SD_RSP10); 124675fa9ea6SGuennadi Liakhovetski 124775fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "Response 0x%x\n", rsp[0]); 124875fa9ea6SGuennadi Liakhovetski } 124975fa9ea6SGuennadi Liakhovetski 125075fa9ea6SGuennadi Liakhovetski static int usdhi6_blk_read(struct usdhi6_host *host) 125175fa9ea6SGuennadi Liakhovetski { 125275fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 125375fa9ea6SGuennadi Liakhovetski u32 *p; 125475fa9ea6SGuennadi Liakhovetski int i, rest; 125575fa9ea6SGuennadi Liakhovetski 125675fa9ea6SGuennadi Liakhovetski if (host->io_error) { 125775fa9ea6SGuennadi Liakhovetski data->error = usdhi6_error_code(host); 125875fa9ea6SGuennadi Liakhovetski goto error; 125975fa9ea6SGuennadi Liakhovetski } 126075fa9ea6SGuennadi Liakhovetski 126175fa9ea6SGuennadi Liakhovetski if (host->pg.page) { 126275fa9ea6SGuennadi Liakhovetski p = host->blk_page + host->offset; 126375fa9ea6SGuennadi Liakhovetski } else { 126475fa9ea6SGuennadi Liakhovetski p = usdhi6_sg_map(host); 126575fa9ea6SGuennadi Liakhovetski if (!p) { 126675fa9ea6SGuennadi Liakhovetski data->error = -ENOMEM; 126775fa9ea6SGuennadi Liakhovetski goto error; 126875fa9ea6SGuennadi Liakhovetski } 126975fa9ea6SGuennadi Liakhovetski } 127075fa9ea6SGuennadi Liakhovetski 127175fa9ea6SGuennadi Liakhovetski for (i = 0; i < data->blksz / 4; i++, p++) 127275fa9ea6SGuennadi Liakhovetski *p = usdhi6_read(host, USDHI6_SD_BUF0); 127375fa9ea6SGuennadi Liakhovetski 127475fa9ea6SGuennadi Liakhovetski rest = data->blksz % 4; 127575fa9ea6SGuennadi Liakhovetski for (i = 0; i < (rest + 1) / 2; i++) { 127675fa9ea6SGuennadi Liakhovetski u16 d = usdhi6_read16(host, USDHI6_SD_BUF0); 127775fa9ea6SGuennadi Liakhovetski ((u8 *)p)[2 * i] = ((u8 *)&d)[0]; 127875fa9ea6SGuennadi Liakhovetski if (rest > 1 && !i) 127975fa9ea6SGuennadi Liakhovetski ((u8 *)p)[2 * i + 1] = ((u8 *)&d)[1]; 128075fa9ea6SGuennadi Liakhovetski } 128175fa9ea6SGuennadi Liakhovetski 128275fa9ea6SGuennadi Liakhovetski return 0; 128375fa9ea6SGuennadi Liakhovetski 128475fa9ea6SGuennadi Liakhovetski error: 128575fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): %d\n", __func__, data->error); 128675fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_REQUEST; 128775fa9ea6SGuennadi Liakhovetski return data->error; 128875fa9ea6SGuennadi Liakhovetski } 128975fa9ea6SGuennadi Liakhovetski 129075fa9ea6SGuennadi Liakhovetski static int usdhi6_blk_write(struct usdhi6_host *host) 129175fa9ea6SGuennadi Liakhovetski { 129275fa9ea6SGuennadi Liakhovetski struct mmc_data *data = host->mrq->data; 129375fa9ea6SGuennadi Liakhovetski u32 *p; 129475fa9ea6SGuennadi Liakhovetski int i, rest; 129575fa9ea6SGuennadi Liakhovetski 129675fa9ea6SGuennadi Liakhovetski if (host->io_error) { 129775fa9ea6SGuennadi Liakhovetski data->error = usdhi6_error_code(host); 129875fa9ea6SGuennadi Liakhovetski goto error; 129975fa9ea6SGuennadi Liakhovetski } 130075fa9ea6SGuennadi Liakhovetski 130175fa9ea6SGuennadi Liakhovetski if (host->pg.page) { 130275fa9ea6SGuennadi Liakhovetski p = host->blk_page + host->offset; 130375fa9ea6SGuennadi Liakhovetski } else { 130475fa9ea6SGuennadi Liakhovetski p = usdhi6_sg_map(host); 130575fa9ea6SGuennadi Liakhovetski if (!p) { 130675fa9ea6SGuennadi Liakhovetski data->error = -ENOMEM; 130775fa9ea6SGuennadi Liakhovetski goto error; 130875fa9ea6SGuennadi Liakhovetski } 130975fa9ea6SGuennadi Liakhovetski } 131075fa9ea6SGuennadi Liakhovetski 131175fa9ea6SGuennadi Liakhovetski for (i = 0; i < data->blksz / 4; i++, p++) 131275fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_BUF0, *p); 131375fa9ea6SGuennadi Liakhovetski 131475fa9ea6SGuennadi Liakhovetski rest = data->blksz % 4; 131575fa9ea6SGuennadi Liakhovetski for (i = 0; i < (rest + 1) / 2; i++) { 131675fa9ea6SGuennadi Liakhovetski u16 d; 131775fa9ea6SGuennadi Liakhovetski ((u8 *)&d)[0] = ((u8 *)p)[2 * i]; 131875fa9ea6SGuennadi Liakhovetski if (rest > 1 && !i) 131975fa9ea6SGuennadi Liakhovetski ((u8 *)&d)[1] = ((u8 *)p)[2 * i + 1]; 132075fa9ea6SGuennadi Liakhovetski else 132175fa9ea6SGuennadi Liakhovetski ((u8 *)&d)[1] = 0; 132275fa9ea6SGuennadi Liakhovetski usdhi6_write16(host, USDHI6_SD_BUF0, d); 132375fa9ea6SGuennadi Liakhovetski } 132475fa9ea6SGuennadi Liakhovetski 132575fa9ea6SGuennadi Liakhovetski return 0; 132675fa9ea6SGuennadi Liakhovetski 132775fa9ea6SGuennadi Liakhovetski error: 132875fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): %d\n", __func__, data->error); 132975fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_REQUEST; 133075fa9ea6SGuennadi Liakhovetski return data->error; 133175fa9ea6SGuennadi Liakhovetski } 133275fa9ea6SGuennadi Liakhovetski 133375fa9ea6SGuennadi Liakhovetski static int usdhi6_stop_cmd(struct usdhi6_host *host) 133475fa9ea6SGuennadi Liakhovetski { 133575fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq = host->mrq; 133675fa9ea6SGuennadi Liakhovetski 133775fa9ea6SGuennadi Liakhovetski switch (mrq->cmd->opcode) { 133875fa9ea6SGuennadi Liakhovetski case MMC_READ_MULTIPLE_BLOCK: 133975fa9ea6SGuennadi Liakhovetski case MMC_WRITE_MULTIPLE_BLOCK: 134075fa9ea6SGuennadi Liakhovetski if (mrq->stop->opcode == MMC_STOP_TRANSMISSION) { 134175fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_STOP; 134275fa9ea6SGuennadi Liakhovetski return 0; 134375fa9ea6SGuennadi Liakhovetski } 13447a019f9bSGustavo A. R. Silva /* fall through - Unsupported STOP command. */ 134575fa9ea6SGuennadi Liakhovetski default: 134675fa9ea6SGuennadi Liakhovetski dev_err(mmc_dev(host->mmc), 134775fa9ea6SGuennadi Liakhovetski "unsupported stop CMD%d for CMD%d\n", 134875fa9ea6SGuennadi Liakhovetski mrq->stop->opcode, mrq->cmd->opcode); 134975fa9ea6SGuennadi Liakhovetski mrq->stop->error = -EOPNOTSUPP; 135075fa9ea6SGuennadi Liakhovetski } 135175fa9ea6SGuennadi Liakhovetski 135275fa9ea6SGuennadi Liakhovetski return -EOPNOTSUPP; 135375fa9ea6SGuennadi Liakhovetski } 135475fa9ea6SGuennadi Liakhovetski 135575fa9ea6SGuennadi Liakhovetski static bool usdhi6_end_cmd(struct usdhi6_host *host) 135675fa9ea6SGuennadi Liakhovetski { 135775fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq = host->mrq; 135875fa9ea6SGuennadi Liakhovetski struct mmc_command *cmd = mrq->cmd; 135975fa9ea6SGuennadi Liakhovetski 136075fa9ea6SGuennadi Liakhovetski if (host->io_error) { 136175fa9ea6SGuennadi Liakhovetski cmd->error = usdhi6_error_code(host); 136275fa9ea6SGuennadi Liakhovetski return false; 136375fa9ea6SGuennadi Liakhovetski } 136475fa9ea6SGuennadi Liakhovetski 136575fa9ea6SGuennadi Liakhovetski usdhi6_resp_read(host); 136675fa9ea6SGuennadi Liakhovetski 136775fa9ea6SGuennadi Liakhovetski if (!mrq->data) 136875fa9ea6SGuennadi Liakhovetski return false; 136975fa9ea6SGuennadi Liakhovetski 137075fa9ea6SGuennadi Liakhovetski if (host->dma_active) { 137175fa9ea6SGuennadi Liakhovetski usdhi6_dma_kick(host); 137275fa9ea6SGuennadi Liakhovetski if (!mrq->stop) 137375fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_DMA; 137475fa9ea6SGuennadi Liakhovetski else if (usdhi6_stop_cmd(host) < 0) 137575fa9ea6SGuennadi Liakhovetski return false; 137675fa9ea6SGuennadi Liakhovetski } else if (mrq->data->flags & MMC_DATA_READ) { 137775fa9ea6SGuennadi Liakhovetski if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK || 137875fa9ea6SGuennadi Liakhovetski (cmd->opcode == SD_IO_RW_EXTENDED && 137975fa9ea6SGuennadi Liakhovetski mrq->data->blocks > 1)) 138075fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_MREAD; 138175fa9ea6SGuennadi Liakhovetski else 138275fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_READ; 138375fa9ea6SGuennadi Liakhovetski } else { 138475fa9ea6SGuennadi Liakhovetski if (cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK || 138575fa9ea6SGuennadi Liakhovetski (cmd->opcode == SD_IO_RW_EXTENDED && 138675fa9ea6SGuennadi Liakhovetski mrq->data->blocks > 1)) 138775fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_MWRITE; 138875fa9ea6SGuennadi Liakhovetski else 138975fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_WRITE; 139075fa9ea6SGuennadi Liakhovetski } 139175fa9ea6SGuennadi Liakhovetski 139275fa9ea6SGuennadi Liakhovetski return true; 139375fa9ea6SGuennadi Liakhovetski } 139475fa9ea6SGuennadi Liakhovetski 139575fa9ea6SGuennadi Liakhovetski static bool usdhi6_read_block(struct usdhi6_host *host) 139675fa9ea6SGuennadi Liakhovetski { 139775fa9ea6SGuennadi Liakhovetski /* ACCESS_END IRQ is already unmasked */ 139875fa9ea6SGuennadi Liakhovetski int ret = usdhi6_blk_read(host); 139975fa9ea6SGuennadi Liakhovetski 140075fa9ea6SGuennadi Liakhovetski /* 140175fa9ea6SGuennadi Liakhovetski * Have to force unmapping both pages: the single block could have been 140275fa9ea6SGuennadi Liakhovetski * cross-page, in which case for single-block IO host->page_idx == 0. 140375fa9ea6SGuennadi Liakhovetski * So, if we don't force, the second page won't be unmapped. 140475fa9ea6SGuennadi Liakhovetski */ 140575fa9ea6SGuennadi Liakhovetski usdhi6_sg_unmap(host, true); 140675fa9ea6SGuennadi Liakhovetski 140775fa9ea6SGuennadi Liakhovetski if (ret < 0) 140875fa9ea6SGuennadi Liakhovetski return false; 140975fa9ea6SGuennadi Liakhovetski 141075fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_DATA_END; 141175fa9ea6SGuennadi Liakhovetski return true; 141275fa9ea6SGuennadi Liakhovetski } 141375fa9ea6SGuennadi Liakhovetski 141475fa9ea6SGuennadi Liakhovetski static bool usdhi6_mread_block(struct usdhi6_host *host) 141575fa9ea6SGuennadi Liakhovetski { 141675fa9ea6SGuennadi Liakhovetski int ret = usdhi6_blk_read(host); 141775fa9ea6SGuennadi Liakhovetski 141875fa9ea6SGuennadi Liakhovetski if (ret < 0) 141975fa9ea6SGuennadi Liakhovetski return false; 142075fa9ea6SGuennadi Liakhovetski 142175fa9ea6SGuennadi Liakhovetski usdhi6_sg_advance(host); 142275fa9ea6SGuennadi Liakhovetski 142375fa9ea6SGuennadi Liakhovetski return !host->mrq->data->error && 142475fa9ea6SGuennadi Liakhovetski (host->wait != USDHI6_WAIT_FOR_DATA_END || !host->mrq->stop); 142575fa9ea6SGuennadi Liakhovetski } 142675fa9ea6SGuennadi Liakhovetski 142775fa9ea6SGuennadi Liakhovetski static bool usdhi6_write_block(struct usdhi6_host *host) 142875fa9ea6SGuennadi Liakhovetski { 142975fa9ea6SGuennadi Liakhovetski int ret = usdhi6_blk_write(host); 143075fa9ea6SGuennadi Liakhovetski 143175fa9ea6SGuennadi Liakhovetski /* See comment in usdhi6_read_block() */ 143275fa9ea6SGuennadi Liakhovetski usdhi6_sg_unmap(host, true); 143375fa9ea6SGuennadi Liakhovetski 143475fa9ea6SGuennadi Liakhovetski if (ret < 0) 143575fa9ea6SGuennadi Liakhovetski return false; 143675fa9ea6SGuennadi Liakhovetski 143775fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_DATA_END; 143875fa9ea6SGuennadi Liakhovetski return true; 143975fa9ea6SGuennadi Liakhovetski } 144075fa9ea6SGuennadi Liakhovetski 144175fa9ea6SGuennadi Liakhovetski static bool usdhi6_mwrite_block(struct usdhi6_host *host) 144275fa9ea6SGuennadi Liakhovetski { 144375fa9ea6SGuennadi Liakhovetski int ret = usdhi6_blk_write(host); 144475fa9ea6SGuennadi Liakhovetski 144575fa9ea6SGuennadi Liakhovetski if (ret < 0) 144675fa9ea6SGuennadi Liakhovetski return false; 144775fa9ea6SGuennadi Liakhovetski 144875fa9ea6SGuennadi Liakhovetski usdhi6_sg_advance(host); 144975fa9ea6SGuennadi Liakhovetski 145075fa9ea6SGuennadi Liakhovetski return !host->mrq->data->error && 145175fa9ea6SGuennadi Liakhovetski (host->wait != USDHI6_WAIT_FOR_DATA_END || !host->mrq->stop); 145275fa9ea6SGuennadi Liakhovetski } 145375fa9ea6SGuennadi Liakhovetski 145475fa9ea6SGuennadi Liakhovetski /* Interrupt & timeout handlers */ 145575fa9ea6SGuennadi Liakhovetski 145675fa9ea6SGuennadi Liakhovetski static irqreturn_t usdhi6_sd_bh(int irq, void *dev_id) 145775fa9ea6SGuennadi Liakhovetski { 145875fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = dev_id; 145975fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq; 146075fa9ea6SGuennadi Liakhovetski struct mmc_command *cmd; 146175fa9ea6SGuennadi Liakhovetski struct mmc_data *data; 146275fa9ea6SGuennadi Liakhovetski bool io_wait = false; 146375fa9ea6SGuennadi Liakhovetski 146475fa9ea6SGuennadi Liakhovetski cancel_delayed_work_sync(&host->timeout_work); 146575fa9ea6SGuennadi Liakhovetski 146675fa9ea6SGuennadi Liakhovetski mrq = host->mrq; 146775fa9ea6SGuennadi Liakhovetski if (!mrq) 146875fa9ea6SGuennadi Liakhovetski return IRQ_HANDLED; 146975fa9ea6SGuennadi Liakhovetski 147075fa9ea6SGuennadi Liakhovetski cmd = mrq->cmd; 147175fa9ea6SGuennadi Liakhovetski data = mrq->data; 147275fa9ea6SGuennadi Liakhovetski 147375fa9ea6SGuennadi Liakhovetski switch (host->wait) { 147475fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_REQUEST: 147575fa9ea6SGuennadi Liakhovetski /* We're too late, the timeout has already kicked in */ 147675fa9ea6SGuennadi Liakhovetski return IRQ_HANDLED; 147775fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_CMD: 147875fa9ea6SGuennadi Liakhovetski /* Wait for data? */ 147975fa9ea6SGuennadi Liakhovetski io_wait = usdhi6_end_cmd(host); 148075fa9ea6SGuennadi Liakhovetski break; 148175fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_MREAD: 148275fa9ea6SGuennadi Liakhovetski /* Wait for more data? */ 148375fa9ea6SGuennadi Liakhovetski io_wait = usdhi6_mread_block(host); 148475fa9ea6SGuennadi Liakhovetski break; 148575fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_READ: 148675fa9ea6SGuennadi Liakhovetski /* Wait for data end? */ 148775fa9ea6SGuennadi Liakhovetski io_wait = usdhi6_read_block(host); 148875fa9ea6SGuennadi Liakhovetski break; 148975fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_MWRITE: 149075fa9ea6SGuennadi Liakhovetski /* Wait data to write? */ 149175fa9ea6SGuennadi Liakhovetski io_wait = usdhi6_mwrite_block(host); 149275fa9ea6SGuennadi Liakhovetski break; 149375fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_WRITE: 149475fa9ea6SGuennadi Liakhovetski /* Wait for data end? */ 149575fa9ea6SGuennadi Liakhovetski io_wait = usdhi6_write_block(host); 149675fa9ea6SGuennadi Liakhovetski break; 149775fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_DMA: 149875fa9ea6SGuennadi Liakhovetski usdhi6_dma_check_error(host); 149975fa9ea6SGuennadi Liakhovetski break; 150075fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_STOP: 150175fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_STOP, 0); 150275fa9ea6SGuennadi Liakhovetski if (host->io_error) { 150375fa9ea6SGuennadi Liakhovetski int ret = usdhi6_error_code(host); 150475fa9ea6SGuennadi Liakhovetski if (mrq->stop) 150575fa9ea6SGuennadi Liakhovetski mrq->stop->error = ret; 150675fa9ea6SGuennadi Liakhovetski else 150775fa9ea6SGuennadi Liakhovetski mrq->data->error = ret; 150875fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), "%s(): %d\n", __func__, ret); 150975fa9ea6SGuennadi Liakhovetski break; 151075fa9ea6SGuennadi Liakhovetski } 151175fa9ea6SGuennadi Liakhovetski usdhi6_resp_cmd12(host); 151275fa9ea6SGuennadi Liakhovetski mrq->stop->error = 0; 151375fa9ea6SGuennadi Liakhovetski break; 151475fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_DATA_END: 151575fa9ea6SGuennadi Liakhovetski if (host->io_error) { 151675fa9ea6SGuennadi Liakhovetski mrq->data->error = usdhi6_error_code(host); 151775fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), "%s(): %d\n", __func__, 151875fa9ea6SGuennadi Liakhovetski mrq->data->error); 151975fa9ea6SGuennadi Liakhovetski } 152075fa9ea6SGuennadi Liakhovetski break; 152175fa9ea6SGuennadi Liakhovetski default: 152275fa9ea6SGuennadi Liakhovetski cmd->error = -EFAULT; 152375fa9ea6SGuennadi Liakhovetski dev_err(mmc_dev(host->mmc), "Invalid state %u\n", host->wait); 152475fa9ea6SGuennadi Liakhovetski usdhi6_request_done(host); 152575fa9ea6SGuennadi Liakhovetski return IRQ_HANDLED; 152675fa9ea6SGuennadi Liakhovetski } 152775fa9ea6SGuennadi Liakhovetski 152875fa9ea6SGuennadi Liakhovetski if (io_wait) { 152975fa9ea6SGuennadi Liakhovetski schedule_delayed_work(&host->timeout_work, host->timeout); 153075fa9ea6SGuennadi Liakhovetski /* Wait for more data or ACCESS_END */ 153175fa9ea6SGuennadi Liakhovetski if (!host->dma_active) 153275fa9ea6SGuennadi Liakhovetski usdhi6_wait_for_brwe(host, mrq->data->flags & MMC_DATA_READ); 153375fa9ea6SGuennadi Liakhovetski return IRQ_HANDLED; 153475fa9ea6SGuennadi Liakhovetski } 153575fa9ea6SGuennadi Liakhovetski 153675fa9ea6SGuennadi Liakhovetski if (!cmd->error) { 153775fa9ea6SGuennadi Liakhovetski if (data) { 153875fa9ea6SGuennadi Liakhovetski if (!data->error) { 153975fa9ea6SGuennadi Liakhovetski if (host->wait != USDHI6_WAIT_FOR_STOP && 154075fa9ea6SGuennadi Liakhovetski host->mrq->stop && 154175fa9ea6SGuennadi Liakhovetski !host->mrq->stop->error && 154275fa9ea6SGuennadi Liakhovetski !usdhi6_stop_cmd(host)) { 154375fa9ea6SGuennadi Liakhovetski /* Sending STOP */ 154475fa9ea6SGuennadi Liakhovetski usdhi6_wait_for_resp(host); 154575fa9ea6SGuennadi Liakhovetski 154675fa9ea6SGuennadi Liakhovetski schedule_delayed_work(&host->timeout_work, 154775fa9ea6SGuennadi Liakhovetski host->timeout); 154875fa9ea6SGuennadi Liakhovetski 154975fa9ea6SGuennadi Liakhovetski return IRQ_HANDLED; 155075fa9ea6SGuennadi Liakhovetski } 155175fa9ea6SGuennadi Liakhovetski 155275fa9ea6SGuennadi Liakhovetski data->bytes_xfered = data->blocks * data->blksz; 155375fa9ea6SGuennadi Liakhovetski } else { 155475fa9ea6SGuennadi Liakhovetski /* Data error: might need to unmap the last page */ 155575fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), "%s(): data error %d\n", 155675fa9ea6SGuennadi Liakhovetski __func__, data->error); 155775fa9ea6SGuennadi Liakhovetski usdhi6_sg_unmap(host, true); 155875fa9ea6SGuennadi Liakhovetski } 155975fa9ea6SGuennadi Liakhovetski } else if (cmd->opcode == MMC_APP_CMD) { 156075fa9ea6SGuennadi Liakhovetski host->app_cmd = true; 156175fa9ea6SGuennadi Liakhovetski } 156275fa9ea6SGuennadi Liakhovetski } 156375fa9ea6SGuennadi Liakhovetski 156475fa9ea6SGuennadi Liakhovetski usdhi6_request_done(host); 156575fa9ea6SGuennadi Liakhovetski 156675fa9ea6SGuennadi Liakhovetski return IRQ_HANDLED; 156775fa9ea6SGuennadi Liakhovetski } 156875fa9ea6SGuennadi Liakhovetski 156975fa9ea6SGuennadi Liakhovetski static irqreturn_t usdhi6_sd(int irq, void *dev_id) 157075fa9ea6SGuennadi Liakhovetski { 157175fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = dev_id; 157275fa9ea6SGuennadi Liakhovetski u16 status, status2, error; 157375fa9ea6SGuennadi Liakhovetski 157475fa9ea6SGuennadi Liakhovetski status = usdhi6_read(host, USDHI6_SD_INFO1) & ~host->status_mask & 157575fa9ea6SGuennadi Liakhovetski ~USDHI6_SD_INFO1_CARD; 157675fa9ea6SGuennadi Liakhovetski status2 = usdhi6_read(host, USDHI6_SD_INFO2) & ~host->status2_mask; 157775fa9ea6SGuennadi Liakhovetski 157875fa9ea6SGuennadi Liakhovetski usdhi6_only_cd(host); 157975fa9ea6SGuennadi Liakhovetski 158075fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), 158175fa9ea6SGuennadi Liakhovetski "IRQ status = 0x%08x, status2 = 0x%08x\n", status, status2); 158275fa9ea6SGuennadi Liakhovetski 158375fa9ea6SGuennadi Liakhovetski if (!status && !status2) 158475fa9ea6SGuennadi Liakhovetski return IRQ_NONE; 158575fa9ea6SGuennadi Liakhovetski 158675fa9ea6SGuennadi Liakhovetski error = status2 & USDHI6_SD_INFO2_ERR; 158775fa9ea6SGuennadi Liakhovetski 158875fa9ea6SGuennadi Liakhovetski /* Ack / clear interrupts */ 158975fa9ea6SGuennadi Liakhovetski if (USDHI6_SD_INFO1_IRQ & status) 159075fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_INFO1, 159175fa9ea6SGuennadi Liakhovetski 0xffff & ~(USDHI6_SD_INFO1_IRQ & status)); 159275fa9ea6SGuennadi Liakhovetski 159375fa9ea6SGuennadi Liakhovetski if (USDHI6_SD_INFO2_IRQ & status2) { 159475fa9ea6SGuennadi Liakhovetski if (error) 159575fa9ea6SGuennadi Liakhovetski /* In error cases BWE and BRE aren't cleared automatically */ 159675fa9ea6SGuennadi Liakhovetski status2 |= USDHI6_SD_INFO2_BWE | USDHI6_SD_INFO2_BRE; 159775fa9ea6SGuennadi Liakhovetski 159875fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SD_INFO2, 159975fa9ea6SGuennadi Liakhovetski 0xffff & ~(USDHI6_SD_INFO2_IRQ & status2)); 160075fa9ea6SGuennadi Liakhovetski } 160175fa9ea6SGuennadi Liakhovetski 160275fa9ea6SGuennadi Liakhovetski host->io_error = error; 160375fa9ea6SGuennadi Liakhovetski host->irq_status = status; 160475fa9ea6SGuennadi Liakhovetski 160575fa9ea6SGuennadi Liakhovetski if (error) { 160675fa9ea6SGuennadi Liakhovetski /* Don't pollute the log with unsupported command timeouts */ 160775fa9ea6SGuennadi Liakhovetski if (host->wait != USDHI6_WAIT_FOR_CMD || 160875fa9ea6SGuennadi Liakhovetski error != USDHI6_SD_INFO2_RSP_TOUT) 160975fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), 161075fa9ea6SGuennadi Liakhovetski "%s(): INFO2 error bits 0x%08x\n", 161175fa9ea6SGuennadi Liakhovetski __func__, error); 161275fa9ea6SGuennadi Liakhovetski else 161375fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), 161475fa9ea6SGuennadi Liakhovetski "%s(): INFO2 error bits 0x%08x\n", 161575fa9ea6SGuennadi Liakhovetski __func__, error); 161675fa9ea6SGuennadi Liakhovetski } 161775fa9ea6SGuennadi Liakhovetski 161875fa9ea6SGuennadi Liakhovetski return IRQ_WAKE_THREAD; 161975fa9ea6SGuennadi Liakhovetski } 162075fa9ea6SGuennadi Liakhovetski 162175fa9ea6SGuennadi Liakhovetski static irqreturn_t usdhi6_sdio(int irq, void *dev_id) 162275fa9ea6SGuennadi Liakhovetski { 162375fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = dev_id; 162475fa9ea6SGuennadi Liakhovetski u32 status = usdhi6_read(host, USDHI6_SDIO_INFO1) & ~host->sdio_mask; 162575fa9ea6SGuennadi Liakhovetski 162675fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), "%s(): status 0x%x\n", __func__, status); 162775fa9ea6SGuennadi Liakhovetski 162875fa9ea6SGuennadi Liakhovetski if (!status) 162975fa9ea6SGuennadi Liakhovetski return IRQ_NONE; 163075fa9ea6SGuennadi Liakhovetski 163175fa9ea6SGuennadi Liakhovetski usdhi6_write(host, USDHI6_SDIO_INFO1, ~status); 163275fa9ea6SGuennadi Liakhovetski 163375fa9ea6SGuennadi Liakhovetski mmc_signal_sdio_irq(host->mmc); 163475fa9ea6SGuennadi Liakhovetski 163575fa9ea6SGuennadi Liakhovetski return IRQ_HANDLED; 163675fa9ea6SGuennadi Liakhovetski } 163775fa9ea6SGuennadi Liakhovetski 163875fa9ea6SGuennadi Liakhovetski static irqreturn_t usdhi6_cd(int irq, void *dev_id) 163975fa9ea6SGuennadi Liakhovetski { 164075fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = dev_id; 164175fa9ea6SGuennadi Liakhovetski struct mmc_host *mmc = host->mmc; 164275fa9ea6SGuennadi Liakhovetski u16 status; 164375fa9ea6SGuennadi Liakhovetski 164475fa9ea6SGuennadi Liakhovetski /* We're only interested in hotplug events here */ 164575fa9ea6SGuennadi Liakhovetski status = usdhi6_read(host, USDHI6_SD_INFO1) & ~host->status_mask & 164675fa9ea6SGuennadi Liakhovetski USDHI6_SD_INFO1_CARD; 164775fa9ea6SGuennadi Liakhovetski 164875fa9ea6SGuennadi Liakhovetski if (!status) 164975fa9ea6SGuennadi Liakhovetski return IRQ_NONE; 165075fa9ea6SGuennadi Liakhovetski 165175fa9ea6SGuennadi Liakhovetski /* Ack */ 16523fe95db1SRabin Vincent usdhi6_write(host, USDHI6_SD_INFO1, ~status); 165375fa9ea6SGuennadi Liakhovetski 165475fa9ea6SGuennadi Liakhovetski if (!work_pending(&mmc->detect.work) && 165575fa9ea6SGuennadi Liakhovetski (((status & USDHI6_SD_INFO1_CARD_INSERT) && 165675fa9ea6SGuennadi Liakhovetski !mmc->card) || 165775fa9ea6SGuennadi Liakhovetski ((status & USDHI6_SD_INFO1_CARD_EJECT) && 165875fa9ea6SGuennadi Liakhovetski mmc->card))) 165975fa9ea6SGuennadi Liakhovetski mmc_detect_change(mmc, msecs_to_jiffies(100)); 166075fa9ea6SGuennadi Liakhovetski 166175fa9ea6SGuennadi Liakhovetski return IRQ_HANDLED; 166275fa9ea6SGuennadi Liakhovetski } 166375fa9ea6SGuennadi Liakhovetski 166475fa9ea6SGuennadi Liakhovetski /* 166575fa9ea6SGuennadi Liakhovetski * Actually this should not be needed, if the built-in timeout works reliably in 166675fa9ea6SGuennadi Liakhovetski * the both PIO cases and DMA never fails. But if DMA does fail, a timeout 166775fa9ea6SGuennadi Liakhovetski * handler might be the only way to catch the error. 166875fa9ea6SGuennadi Liakhovetski */ 166975fa9ea6SGuennadi Liakhovetski static void usdhi6_timeout_work(struct work_struct *work) 167075fa9ea6SGuennadi Liakhovetski { 1671238fc95eSGeliang Tang struct delayed_work *d = to_delayed_work(work); 167275fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = container_of(d, struct usdhi6_host, timeout_work); 167375fa9ea6SGuennadi Liakhovetski struct mmc_request *mrq = host->mrq; 167475fa9ea6SGuennadi Liakhovetski struct mmc_data *data = mrq ? mrq->data : NULL; 167505caee93SRabin Vincent struct scatterlist *sg; 167675fa9ea6SGuennadi Liakhovetski 167775fa9ea6SGuennadi Liakhovetski dev_warn(mmc_dev(host->mmc), 167875fa9ea6SGuennadi Liakhovetski "%s timeout wait %u CMD%d: IRQ 0x%08x:0x%08x, last IRQ 0x%08x\n", 167975fa9ea6SGuennadi Liakhovetski host->dma_active ? "DMA" : "PIO", 168075fa9ea6SGuennadi Liakhovetski host->wait, mrq ? mrq->cmd->opcode : -1, 168175fa9ea6SGuennadi Liakhovetski usdhi6_read(host, USDHI6_SD_INFO1), 168275fa9ea6SGuennadi Liakhovetski usdhi6_read(host, USDHI6_SD_INFO2), host->irq_status); 168375fa9ea6SGuennadi Liakhovetski 168475fa9ea6SGuennadi Liakhovetski if (host->dma_active) { 168575fa9ea6SGuennadi Liakhovetski usdhi6_dma_kill(host); 168675fa9ea6SGuennadi Liakhovetski usdhi6_dma_stop_unmap(host); 168775fa9ea6SGuennadi Liakhovetski } 168875fa9ea6SGuennadi Liakhovetski 168975fa9ea6SGuennadi Liakhovetski switch (host->wait) { 169075fa9ea6SGuennadi Liakhovetski default: 169175fa9ea6SGuennadi Liakhovetski dev_err(mmc_dev(host->mmc), "Invalid state %u\n", host->wait); 16927a019f9bSGustavo A. R. Silva /* fall through - mrq can be NULL, but is impossible. */ 169375fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_CMD: 169475fa9ea6SGuennadi Liakhovetski usdhi6_error_code(host); 169575fa9ea6SGuennadi Liakhovetski if (mrq) 169675fa9ea6SGuennadi Liakhovetski mrq->cmd->error = -ETIMEDOUT; 169775fa9ea6SGuennadi Liakhovetski break; 169875fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_STOP: 169975fa9ea6SGuennadi Liakhovetski usdhi6_error_code(host); 170075fa9ea6SGuennadi Liakhovetski mrq->stop->error = -ETIMEDOUT; 170175fa9ea6SGuennadi Liakhovetski break; 170275fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_DMA: 170375fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_MREAD: 170475fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_MWRITE: 170575fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_READ: 170675fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_WRITE: 170705caee93SRabin Vincent sg = host->sg ?: data->sg; 170875fa9ea6SGuennadi Liakhovetski dev_dbg(mmc_dev(host->mmc), 170913fe0ec3SGuennadi Liakhovetski "%c: page #%u @ +0x%zx %ux%u in SG%u. Current SG %u bytes @ %u\n", 171075fa9ea6SGuennadi Liakhovetski data->flags & MMC_DATA_READ ? 'R' : 'W', host->page_idx, 171175fa9ea6SGuennadi Liakhovetski host->offset, data->blocks, data->blksz, data->sg_len, 1712bb08a7d4SRabin Vincent sg_dma_len(sg), sg->offset); 171375fa9ea6SGuennadi Liakhovetski usdhi6_sg_unmap(host, true); 17147a019f9bSGustavo A. R. Silva /* fall through - page unmapped in USDHI6_WAIT_FOR_DATA_END. */ 171575fa9ea6SGuennadi Liakhovetski case USDHI6_WAIT_FOR_DATA_END: 171675fa9ea6SGuennadi Liakhovetski usdhi6_error_code(host); 171775fa9ea6SGuennadi Liakhovetski data->error = -ETIMEDOUT; 171875fa9ea6SGuennadi Liakhovetski } 171975fa9ea6SGuennadi Liakhovetski 172075fa9ea6SGuennadi Liakhovetski if (mrq) 172175fa9ea6SGuennadi Liakhovetski usdhi6_request_done(host); 172275fa9ea6SGuennadi Liakhovetski } 172375fa9ea6SGuennadi Liakhovetski 172475fa9ea6SGuennadi Liakhovetski /* Probe / release */ 172575fa9ea6SGuennadi Liakhovetski 172675fa9ea6SGuennadi Liakhovetski static const struct of_device_id usdhi6_of_match[] = { 172775fa9ea6SGuennadi Liakhovetski {.compatible = "renesas,usdhi6rol0"}, 172875fa9ea6SGuennadi Liakhovetski {} 172975fa9ea6SGuennadi Liakhovetski }; 173075fa9ea6SGuennadi Liakhovetski MODULE_DEVICE_TABLE(of, usdhi6_of_match); 173175fa9ea6SGuennadi Liakhovetski 173275fa9ea6SGuennadi Liakhovetski static int usdhi6_probe(struct platform_device *pdev) 173375fa9ea6SGuennadi Liakhovetski { 173475fa9ea6SGuennadi Liakhovetski struct device *dev = &pdev->dev; 173575fa9ea6SGuennadi Liakhovetski struct mmc_host *mmc; 173675fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host; 173775fa9ea6SGuennadi Liakhovetski struct resource *res; 173875fa9ea6SGuennadi Liakhovetski int irq_cd, irq_sd, irq_sdio; 173975fa9ea6SGuennadi Liakhovetski u32 version; 174075fa9ea6SGuennadi Liakhovetski int ret; 174175fa9ea6SGuennadi Liakhovetski 174275fa9ea6SGuennadi Liakhovetski if (!dev->of_node) 174375fa9ea6SGuennadi Liakhovetski return -ENODEV; 174475fa9ea6SGuennadi Liakhovetski 174575fa9ea6SGuennadi Liakhovetski irq_cd = platform_get_irq_byname(pdev, "card detect"); 174675fa9ea6SGuennadi Liakhovetski irq_sd = platform_get_irq_byname(pdev, "data"); 174775fa9ea6SGuennadi Liakhovetski irq_sdio = platform_get_irq_byname(pdev, "SDIO"); 174875fa9ea6SGuennadi Liakhovetski if (irq_sd < 0 || irq_sdio < 0) 174975fa9ea6SGuennadi Liakhovetski return -ENODEV; 175075fa9ea6SGuennadi Liakhovetski 175175fa9ea6SGuennadi Liakhovetski mmc = mmc_alloc_host(sizeof(struct usdhi6_host), dev); 175275fa9ea6SGuennadi Liakhovetski if (!mmc) 175375fa9ea6SGuennadi Liakhovetski return -ENOMEM; 175475fa9ea6SGuennadi Liakhovetski 175576726472SRabin Vincent ret = mmc_regulator_get_supply(mmc); 17562d87ddd7SWolfram Sang if (ret) 175776726472SRabin Vincent goto e_free_mmc; 175876726472SRabin Vincent 175975fa9ea6SGuennadi Liakhovetski ret = mmc_of_parse(mmc); 176075fa9ea6SGuennadi Liakhovetski if (ret < 0) 176175fa9ea6SGuennadi Liakhovetski goto e_free_mmc; 176275fa9ea6SGuennadi Liakhovetski 176375fa9ea6SGuennadi Liakhovetski host = mmc_priv(mmc); 176475fa9ea6SGuennadi Liakhovetski host->mmc = mmc; 176575fa9ea6SGuennadi Liakhovetski host->wait = USDHI6_WAIT_FOR_REQUEST; 176675fa9ea6SGuennadi Liakhovetski host->timeout = msecs_to_jiffies(4000); 176775fa9ea6SGuennadi Liakhovetski 1768488aab3dSLars Persson host->pinctrl = devm_pinctrl_get(&pdev->dev); 1769488aab3dSLars Persson if (IS_ERR(host->pinctrl)) { 1770488aab3dSLars Persson ret = PTR_ERR(host->pinctrl); 1771488aab3dSLars Persson goto e_free_mmc; 1772488aab3dSLars Persson } 1773488aab3dSLars Persson 1774488aab3dSLars Persson host->pins_uhs = pinctrl_lookup_state(host->pinctrl, "state_uhs"); 1775488aab3dSLars Persson 177675fa9ea6SGuennadi Liakhovetski res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 177775fa9ea6SGuennadi Liakhovetski host->base = devm_ioremap_resource(dev, res); 177875fa9ea6SGuennadi Liakhovetski if (IS_ERR(host->base)) { 177975fa9ea6SGuennadi Liakhovetski ret = PTR_ERR(host->base); 178075fa9ea6SGuennadi Liakhovetski goto e_free_mmc; 178175fa9ea6SGuennadi Liakhovetski } 178275fa9ea6SGuennadi Liakhovetski 178375fa9ea6SGuennadi Liakhovetski host->clk = devm_clk_get(dev, NULL); 17843b1cac4dSJulia Lawall if (IS_ERR(host->clk)) { 17853b1cac4dSJulia Lawall ret = PTR_ERR(host->clk); 178675fa9ea6SGuennadi Liakhovetski goto e_free_mmc; 17873b1cac4dSJulia Lawall } 178875fa9ea6SGuennadi Liakhovetski 178975fa9ea6SGuennadi Liakhovetski host->imclk = clk_get_rate(host->clk); 179075fa9ea6SGuennadi Liakhovetski 179175fa9ea6SGuennadi Liakhovetski ret = clk_prepare_enable(host->clk); 179275fa9ea6SGuennadi Liakhovetski if (ret < 0) 179375fa9ea6SGuennadi Liakhovetski goto e_free_mmc; 179475fa9ea6SGuennadi Liakhovetski 179575fa9ea6SGuennadi Liakhovetski version = usdhi6_read(host, USDHI6_VERSION); 179675fa9ea6SGuennadi Liakhovetski if ((version & 0xfff) != 0xa0d) { 179775fa9ea6SGuennadi Liakhovetski dev_err(dev, "Version not recognized %x\n", version); 179875fa9ea6SGuennadi Liakhovetski goto e_clk_off; 179975fa9ea6SGuennadi Liakhovetski } 180075fa9ea6SGuennadi Liakhovetski 180175fa9ea6SGuennadi Liakhovetski dev_info(dev, "A USDHI6ROL0 SD host detected with %d ports\n", 180275fa9ea6SGuennadi Liakhovetski usdhi6_read(host, USDHI6_SD_PORT_SEL) >> USDHI6_SD_PORT_SEL_PORTS_SHIFT); 180375fa9ea6SGuennadi Liakhovetski 180475fa9ea6SGuennadi Liakhovetski usdhi6_mask_all(host); 180575fa9ea6SGuennadi Liakhovetski 180675fa9ea6SGuennadi Liakhovetski if (irq_cd >= 0) { 180775fa9ea6SGuennadi Liakhovetski ret = devm_request_irq(dev, irq_cd, usdhi6_cd, 0, 180875fa9ea6SGuennadi Liakhovetski dev_name(dev), host); 180975fa9ea6SGuennadi Liakhovetski if (ret < 0) 181075fa9ea6SGuennadi Liakhovetski goto e_clk_off; 181175fa9ea6SGuennadi Liakhovetski } else { 181275fa9ea6SGuennadi Liakhovetski mmc->caps |= MMC_CAP_NEEDS_POLL; 181375fa9ea6SGuennadi Liakhovetski } 181475fa9ea6SGuennadi Liakhovetski 181575fa9ea6SGuennadi Liakhovetski ret = devm_request_threaded_irq(dev, irq_sd, usdhi6_sd, usdhi6_sd_bh, 0, 181675fa9ea6SGuennadi Liakhovetski dev_name(dev), host); 181775fa9ea6SGuennadi Liakhovetski if (ret < 0) 181875fa9ea6SGuennadi Liakhovetski goto e_clk_off; 181975fa9ea6SGuennadi Liakhovetski 182075fa9ea6SGuennadi Liakhovetski ret = devm_request_irq(dev, irq_sdio, usdhi6_sdio, 0, 182175fa9ea6SGuennadi Liakhovetski dev_name(dev), host); 182275fa9ea6SGuennadi Liakhovetski if (ret < 0) 182375fa9ea6SGuennadi Liakhovetski goto e_clk_off; 182475fa9ea6SGuennadi Liakhovetski 182575fa9ea6SGuennadi Liakhovetski INIT_DELAYED_WORK(&host->timeout_work, usdhi6_timeout_work); 182675fa9ea6SGuennadi Liakhovetski 182775fa9ea6SGuennadi Liakhovetski usdhi6_dma_request(host, res->start); 182875fa9ea6SGuennadi Liakhovetski 182975fa9ea6SGuennadi Liakhovetski mmc->ops = &usdhi6_ops; 183075fa9ea6SGuennadi Liakhovetski mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | 1831701dcef7SLars Persson MMC_CAP_SDIO_IRQ; 183275fa9ea6SGuennadi Liakhovetski /* Set .max_segs to some random number. Feel free to adjust. */ 183375fa9ea6SGuennadi Liakhovetski mmc->max_segs = 32; 183475fa9ea6SGuennadi Liakhovetski mmc->max_blk_size = 512; 183509cbfeafSKirill A. Shutemov mmc->max_req_size = PAGE_SIZE * mmc->max_segs; 183675fa9ea6SGuennadi Liakhovetski mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size; 183775fa9ea6SGuennadi Liakhovetski /* 183875fa9ea6SGuennadi Liakhovetski * Setting .max_seg_size to 1 page would simplify our page-mapping code, 183975fa9ea6SGuennadi Liakhovetski * But OTOH, having large segments makes DMA more efficient. We could 184075fa9ea6SGuennadi Liakhovetski * check, whether we managed to get DMA and fall back to 1 page 184175fa9ea6SGuennadi Liakhovetski * segments, but if we do manage to obtain DMA and then it fails at 184275fa9ea6SGuennadi Liakhovetski * run-time and we fall back to PIO, we will continue getting large 184375fa9ea6SGuennadi Liakhovetski * segments. So, we wouldn't be able to get rid of the code anyway. 184475fa9ea6SGuennadi Liakhovetski */ 184575fa9ea6SGuennadi Liakhovetski mmc->max_seg_size = mmc->max_req_size; 184675fa9ea6SGuennadi Liakhovetski if (!mmc->f_max) 184775fa9ea6SGuennadi Liakhovetski mmc->f_max = host->imclk; 184875fa9ea6SGuennadi Liakhovetski mmc->f_min = host->imclk / 512; 184975fa9ea6SGuennadi Liakhovetski 185075fa9ea6SGuennadi Liakhovetski platform_set_drvdata(pdev, host); 185175fa9ea6SGuennadi Liakhovetski 185275fa9ea6SGuennadi Liakhovetski ret = mmc_add_host(mmc); 185375fa9ea6SGuennadi Liakhovetski if (ret < 0) 185475fa9ea6SGuennadi Liakhovetski goto e_clk_off; 185575fa9ea6SGuennadi Liakhovetski 185675fa9ea6SGuennadi Liakhovetski return 0; 185775fa9ea6SGuennadi Liakhovetski 185875fa9ea6SGuennadi Liakhovetski e_clk_off: 185975fa9ea6SGuennadi Liakhovetski clk_disable_unprepare(host->clk); 186075fa9ea6SGuennadi Liakhovetski e_free_mmc: 186175fa9ea6SGuennadi Liakhovetski mmc_free_host(mmc); 186275fa9ea6SGuennadi Liakhovetski 186375fa9ea6SGuennadi Liakhovetski return ret; 186475fa9ea6SGuennadi Liakhovetski } 186575fa9ea6SGuennadi Liakhovetski 186675fa9ea6SGuennadi Liakhovetski static int usdhi6_remove(struct platform_device *pdev) 186775fa9ea6SGuennadi Liakhovetski { 186875fa9ea6SGuennadi Liakhovetski struct usdhi6_host *host = platform_get_drvdata(pdev); 186975fa9ea6SGuennadi Liakhovetski 187075fa9ea6SGuennadi Liakhovetski mmc_remove_host(host->mmc); 187175fa9ea6SGuennadi Liakhovetski 187275fa9ea6SGuennadi Liakhovetski usdhi6_mask_all(host); 187375fa9ea6SGuennadi Liakhovetski cancel_delayed_work_sync(&host->timeout_work); 187475fa9ea6SGuennadi Liakhovetski usdhi6_dma_release(host); 187575fa9ea6SGuennadi Liakhovetski clk_disable_unprepare(host->clk); 187675fa9ea6SGuennadi Liakhovetski mmc_free_host(host->mmc); 187775fa9ea6SGuennadi Liakhovetski 187875fa9ea6SGuennadi Liakhovetski return 0; 187975fa9ea6SGuennadi Liakhovetski } 188075fa9ea6SGuennadi Liakhovetski 188175fa9ea6SGuennadi Liakhovetski static struct platform_driver usdhi6_driver = { 188275fa9ea6SGuennadi Liakhovetski .probe = usdhi6_probe, 188375fa9ea6SGuennadi Liakhovetski .remove = usdhi6_remove, 188475fa9ea6SGuennadi Liakhovetski .driver = { 188575fa9ea6SGuennadi Liakhovetski .name = "usdhi6rol0", 188675fa9ea6SGuennadi Liakhovetski .of_match_table = usdhi6_of_match, 188775fa9ea6SGuennadi Liakhovetski }, 188875fa9ea6SGuennadi Liakhovetski }; 188975fa9ea6SGuennadi Liakhovetski 189075fa9ea6SGuennadi Liakhovetski module_platform_driver(usdhi6_driver); 189175fa9ea6SGuennadi Liakhovetski 189275fa9ea6SGuennadi Liakhovetski MODULE_DESCRIPTION("Renesas usdhi6rol0 SD/SDIO host driver"); 189375fa9ea6SGuennadi Liakhovetski MODULE_LICENSE("GPL v2"); 189475fa9ea6SGuennadi Liakhovetski MODULE_ALIAS("platform:usdhi6rol0"); 189575fa9ea6SGuennadi Liakhovetski MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); 1896