Marek Vasut | dae0f5c | 2014-07-22 02:34:52 +0200 | [diff] [blame] | 1 | /* |
| 2 | * S3C24xx SD/MMC driver |
| 3 | * |
| 4 | * Based on OpenMoko S3C24xx driver by Harald Welte <laforge@openmoko.org> |
| 5 | * |
| 6 | * Copyright (C) 2014 Marek Vasut <marex@denx.de> |
| 7 | * |
| 8 | * SPDX-License-Identifier: GPL-2.0+ |
| 9 | */ |
| 10 | |
| 11 | #include <common.h> |
| 12 | #include <malloc.h> |
| 13 | #include <mmc.h> |
| 14 | #include <errno.h> |
| 15 | #include <asm/arch/s3c24x0_cpu.h> |
| 16 | #include <asm/io.h> |
| 17 | #include <asm/unaligned.h> |
| 18 | |
| 19 | #define S3C2440_SDICON_SDRESET (1 << 8) |
| 20 | #define S3C2410_SDICON_FIFORESET (1 << 1) |
| 21 | #define S3C2410_SDICON_CLOCKTYPE (1 << 0) |
| 22 | |
| 23 | #define S3C2410_SDICMDCON_LONGRSP (1 << 10) |
| 24 | #define S3C2410_SDICMDCON_WAITRSP (1 << 9) |
| 25 | #define S3C2410_SDICMDCON_CMDSTART (1 << 8) |
| 26 | #define S3C2410_SDICMDCON_SENDERHOST (1 << 6) |
| 27 | #define S3C2410_SDICMDCON_INDEX 0x3f |
| 28 | |
| 29 | #define S3C2410_SDICMDSTAT_CRCFAIL (1 << 12) |
| 30 | #define S3C2410_SDICMDSTAT_CMDSENT (1 << 11) |
| 31 | #define S3C2410_SDICMDSTAT_CMDTIMEOUT (1 << 10) |
| 32 | #define S3C2410_SDICMDSTAT_RSPFIN (1 << 9) |
| 33 | |
| 34 | #define S3C2440_SDIDCON_DS_WORD (2 << 22) |
| 35 | #define S3C2410_SDIDCON_TXAFTERRESP (1 << 20) |
| 36 | #define S3C2410_SDIDCON_RXAFTERCMD (1 << 19) |
| 37 | #define S3C2410_SDIDCON_BLOCKMODE (1 << 17) |
| 38 | #define S3C2410_SDIDCON_WIDEBUS (1 << 16) |
| 39 | #define S3C2440_SDIDCON_DATSTART (1 << 14) |
| 40 | #define S3C2410_SDIDCON_XFER_RXSTART (2 << 12) |
| 41 | #define S3C2410_SDIDCON_XFER_TXSTART (3 << 12) |
| 42 | #define S3C2410_SDIDCON_BLKNUM 0x7ff |
| 43 | |
| 44 | #define S3C2410_SDIDSTA_FIFOFAIL (1 << 8) |
| 45 | #define S3C2410_SDIDSTA_CRCFAIL (1 << 7) |
| 46 | #define S3C2410_SDIDSTA_RXCRCFAIL (1 << 6) |
| 47 | #define S3C2410_SDIDSTA_DATATIMEOUT (1 << 5) |
| 48 | #define S3C2410_SDIDSTA_XFERFINISH (1 << 4) |
| 49 | |
| 50 | #define S3C2410_SDIFSTA_TFHALF (1 << 11) |
| 51 | #define S3C2410_SDIFSTA_COUNTMASK 0x7f |
| 52 | |
| 53 | /* |
| 54 | * WARNING: We only support one SD IP block. |
| 55 | * NOTE: It's not likely there will ever exist an S3C24xx with two, |
| 56 | * at least not in this universe all right. |
| 57 | */ |
| 58 | static int wide_bus; |
| 59 | |
| 60 | static int |
| 61 | s3cmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) |
| 62 | { |
| 63 | struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi(); |
| 64 | uint32_t sdiccon, sdicsta, sdidcon, sdidsta, sdidat, sdifsta; |
| 65 | uint32_t sdicsta_wait_bit = S3C2410_SDICMDSTAT_CMDSENT; |
| 66 | unsigned int timeout = 100000; |
| 67 | int ret = 0, xfer_len, data_offset = 0; |
| 68 | const uint32_t sdidsta_err_mask = S3C2410_SDIDSTA_FIFOFAIL | |
| 69 | S3C2410_SDIDSTA_CRCFAIL | S3C2410_SDIDSTA_RXCRCFAIL | |
| 70 | S3C2410_SDIDSTA_DATATIMEOUT; |
| 71 | |
| 72 | |
| 73 | writel(0xffffffff, &sdi_regs->sdicsta); |
| 74 | writel(0xffffffff, &sdi_regs->sdidsta); |
| 75 | writel(0xffffffff, &sdi_regs->sdifsta); |
| 76 | |
| 77 | /* Set up data transfer (if applicable). */ |
| 78 | if (data) { |
| 79 | writel(data->blocksize, &sdi_regs->sdibsize); |
| 80 | |
| 81 | sdidcon = data->blocks & S3C2410_SDIDCON_BLKNUM; |
| 82 | sdidcon |= S3C2410_SDIDCON_BLOCKMODE; |
| 83 | #if defined(CONFIG_S3C2440) |
| 84 | sdidcon |= S3C2440_SDIDCON_DS_WORD | S3C2440_SDIDCON_DATSTART; |
| 85 | #endif |
| 86 | if (wide_bus) |
| 87 | sdidcon |= S3C2410_SDIDCON_WIDEBUS; |
| 88 | |
| 89 | if (data->flags & MMC_DATA_READ) { |
| 90 | sdidcon |= S3C2410_SDIDCON_RXAFTERCMD; |
| 91 | sdidcon |= S3C2410_SDIDCON_XFER_RXSTART; |
| 92 | } else { |
| 93 | sdidcon |= S3C2410_SDIDCON_TXAFTERRESP; |
| 94 | sdidcon |= S3C2410_SDIDCON_XFER_TXSTART; |
| 95 | } |
| 96 | |
| 97 | writel(sdidcon, &sdi_regs->sdidcon); |
| 98 | } |
| 99 | |
| 100 | /* Write CMD arg. */ |
| 101 | writel(cmd->cmdarg, &sdi_regs->sdicarg); |
| 102 | |
| 103 | /* Write CMD index. */ |
| 104 | sdiccon = cmd->cmdidx & S3C2410_SDICMDCON_INDEX; |
| 105 | sdiccon |= S3C2410_SDICMDCON_SENDERHOST; |
| 106 | sdiccon |= S3C2410_SDICMDCON_CMDSTART; |
| 107 | |
| 108 | /* Command with short response. */ |
| 109 | if (cmd->resp_type & MMC_RSP_PRESENT) { |
| 110 | sdiccon |= S3C2410_SDICMDCON_WAITRSP; |
| 111 | sdicsta_wait_bit = S3C2410_SDICMDSTAT_RSPFIN; |
| 112 | } |
| 113 | |
| 114 | /* Command with long response. */ |
| 115 | if (cmd->resp_type & MMC_RSP_136) |
| 116 | sdiccon |= S3C2410_SDICMDCON_LONGRSP; |
| 117 | |
| 118 | /* Start the command. */ |
| 119 | writel(sdiccon, &sdi_regs->sdiccon); |
| 120 | |
| 121 | /* Wait for the command to complete or for response. */ |
| 122 | for (timeout = 100000; timeout; timeout--) { |
| 123 | sdicsta = readl(&sdi_regs->sdicsta); |
| 124 | if (sdicsta & sdicsta_wait_bit) |
| 125 | break; |
| 126 | |
| 127 | if (sdicsta & S3C2410_SDICMDSTAT_CMDTIMEOUT) |
| 128 | timeout = 1; |
| 129 | } |
| 130 | |
| 131 | /* Clean the status bits. */ |
| 132 | setbits_le32(&sdi_regs->sdicsta, 0xf << 9); |
| 133 | |
| 134 | if (!timeout) { |
| 135 | puts("S3C SDI: Command timed out!\n"); |
Jaehoon Chung | 915ffa5 | 2016-07-19 16:33:36 +0900 | [diff] [blame] | 136 | ret = -ETIMEDOUT; |
Marek Vasut | dae0f5c | 2014-07-22 02:34:52 +0200 | [diff] [blame] | 137 | goto error; |
| 138 | } |
| 139 | |
| 140 | /* Read out the response. */ |
| 141 | if (cmd->resp_type & MMC_RSP_136) { |
| 142 | cmd->response[0] = readl(&sdi_regs->sdirsp0); |
| 143 | cmd->response[1] = readl(&sdi_regs->sdirsp1); |
| 144 | cmd->response[2] = readl(&sdi_regs->sdirsp2); |
| 145 | cmd->response[3] = readl(&sdi_regs->sdirsp3); |
| 146 | } else { |
| 147 | cmd->response[0] = readl(&sdi_regs->sdirsp0); |
| 148 | } |
| 149 | |
| 150 | /* If there are no data, we're done. */ |
| 151 | if (!data) |
| 152 | return 0; |
| 153 | |
| 154 | xfer_len = data->blocksize * data->blocks; |
| 155 | |
| 156 | while (xfer_len > 0) { |
| 157 | sdidsta = readl(&sdi_regs->sdidsta); |
| 158 | sdifsta = readl(&sdi_regs->sdifsta); |
| 159 | |
| 160 | if (sdidsta & sdidsta_err_mask) { |
| 161 | printf("S3C SDI: Data error (sdta=0x%08x)\n", sdidsta); |
| 162 | ret = -EIO; |
| 163 | goto error; |
| 164 | } |
| 165 | |
| 166 | if (data->flags & MMC_DATA_READ) { |
| 167 | if ((sdifsta & S3C2410_SDIFSTA_COUNTMASK) < 4) |
| 168 | continue; |
| 169 | sdidat = readl(&sdi_regs->sdidat); |
| 170 | put_unaligned_le32(sdidat, data->dest + data_offset); |
| 171 | } else { /* Write */ |
| 172 | /* TX FIFO half full. */ |
| 173 | if (!(sdifsta & S3C2410_SDIFSTA_TFHALF)) |
| 174 | continue; |
| 175 | |
| 176 | /* TX FIFO is below 32b full, write. */ |
| 177 | sdidat = get_unaligned_le32(data->src + data_offset); |
| 178 | writel(sdidat, &sdi_regs->sdidat); |
| 179 | } |
| 180 | data_offset += 4; |
| 181 | xfer_len -= 4; |
| 182 | } |
| 183 | |
| 184 | /* Wait for the command to complete or for response. */ |
| 185 | for (timeout = 100000; timeout; timeout--) { |
| 186 | sdidsta = readl(&sdi_regs->sdidsta); |
| 187 | if (sdidsta & S3C2410_SDIDSTA_XFERFINISH) |
| 188 | break; |
| 189 | |
| 190 | if (sdidsta & S3C2410_SDIDSTA_DATATIMEOUT) |
| 191 | timeout = 1; |
| 192 | } |
| 193 | |
| 194 | /* Clear status bits. */ |
| 195 | writel(0x6f8, &sdi_regs->sdidsta); |
| 196 | |
| 197 | if (!timeout) { |
| 198 | puts("S3C SDI: Command timed out!\n"); |
Jaehoon Chung | 915ffa5 | 2016-07-19 16:33:36 +0900 | [diff] [blame] | 199 | ret = -ETIMEDOUT; |
Marek Vasut | dae0f5c | 2014-07-22 02:34:52 +0200 | [diff] [blame] | 200 | goto error; |
| 201 | } |
| 202 | |
| 203 | writel(0, &sdi_regs->sdidcon); |
| 204 | |
| 205 | return 0; |
| 206 | error: |
| 207 | return ret; |
| 208 | } |
| 209 | |
Jaehoon Chung | 07b0b9c | 2016-12-30 15:30:16 +0900 | [diff] [blame] | 210 | static int s3cmmc_set_ios(struct mmc *mmc) |
Marek Vasut | dae0f5c | 2014-07-22 02:34:52 +0200 | [diff] [blame] | 211 | { |
| 212 | struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi(); |
| 213 | uint32_t divider = 0; |
| 214 | |
| 215 | wide_bus = (mmc->bus_width == 4); |
| 216 | |
| 217 | if (!mmc->clock) |
Jaehoon Chung | 07b0b9c | 2016-12-30 15:30:16 +0900 | [diff] [blame] | 218 | return 0; |
Marek Vasut | dae0f5c | 2014-07-22 02:34:52 +0200 | [diff] [blame] | 219 | |
| 220 | divider = DIV_ROUND_UP(get_PCLK(), mmc->clock); |
| 221 | if (divider) |
| 222 | divider--; |
| 223 | |
| 224 | writel(divider, &sdi_regs->sdipre); |
| 225 | mdelay(125); |
Jaehoon Chung | 07b0b9c | 2016-12-30 15:30:16 +0900 | [diff] [blame] | 226 | |
| 227 | return 0; |
Marek Vasut | dae0f5c | 2014-07-22 02:34:52 +0200 | [diff] [blame] | 228 | } |
| 229 | |
| 230 | static int s3cmmc_init(struct mmc *mmc) |
| 231 | { |
| 232 | struct s3c24x0_clock_power *clk_power = s3c24x0_get_base_clock_power(); |
| 233 | struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi(); |
| 234 | |
| 235 | /* Start the clock. */ |
| 236 | setbits_le32(&clk_power->clkcon, 1 << 9); |
| 237 | |
| 238 | #if defined(CONFIG_S3C2440) |
| 239 | writel(S3C2440_SDICON_SDRESET, &sdi_regs->sdicon); |
| 240 | mdelay(10); |
| 241 | writel(0x7fffff, &sdi_regs->sdidtimer); |
| 242 | #else |
| 243 | writel(0xffff, &sdi_regs->sdidtimer); |
| 244 | #endif |
| 245 | writel(MMC_MAX_BLOCK_LEN, &sdi_regs->sdibsize); |
| 246 | writel(0x0, &sdi_regs->sdiimsk); |
| 247 | |
| 248 | writel(S3C2410_SDICON_FIFORESET | S3C2410_SDICON_CLOCKTYPE, |
| 249 | &sdi_regs->sdicon); |
| 250 | |
| 251 | mdelay(125); |
| 252 | |
| 253 | return 0; |
| 254 | } |
| 255 | |
| 256 | struct s3cmmc_priv { |
| 257 | struct mmc_config cfg; |
| 258 | int (*getcd)(struct mmc *); |
| 259 | int (*getwp)(struct mmc *); |
| 260 | }; |
| 261 | |
| 262 | static int s3cmmc_getcd(struct mmc *mmc) |
| 263 | { |
| 264 | struct s3cmmc_priv *priv = mmc->priv; |
| 265 | if (priv->getcd) |
| 266 | return priv->getcd(mmc); |
| 267 | else |
| 268 | return 0; |
| 269 | } |
| 270 | |
| 271 | static int s3cmmc_getwp(struct mmc *mmc) |
| 272 | { |
| 273 | struct s3cmmc_priv *priv = mmc->priv; |
| 274 | if (priv->getwp) |
| 275 | return priv->getwp(mmc); |
| 276 | else |
| 277 | return 0; |
| 278 | } |
| 279 | |
| 280 | static const struct mmc_ops s3cmmc_ops = { |
| 281 | .send_cmd = s3cmmc_send_cmd, |
| 282 | .set_ios = s3cmmc_set_ios, |
| 283 | .init = s3cmmc_init, |
| 284 | .getcd = s3cmmc_getcd, |
| 285 | .getwp = s3cmmc_getwp, |
| 286 | }; |
| 287 | |
| 288 | int s3cmmc_initialize(bd_t *bis, int (*getcd)(struct mmc *), |
| 289 | int (*getwp)(struct mmc *)) |
| 290 | { |
| 291 | struct s3cmmc_priv *priv; |
| 292 | struct mmc *mmc; |
| 293 | struct mmc_config *cfg; |
| 294 | |
| 295 | priv = calloc(1, sizeof(*priv)); |
| 296 | if (!priv) |
| 297 | return -ENOMEM; |
| 298 | cfg = &priv->cfg; |
| 299 | |
| 300 | cfg->name = "S3C MMC"; |
| 301 | cfg->ops = &s3cmmc_ops; |
| 302 | cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; |
Rob Herring | 5a20397 | 2015-03-23 17:56:59 -0500 | [diff] [blame] | 303 | cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS; |
Marek Vasut | dae0f5c | 2014-07-22 02:34:52 +0200 | [diff] [blame] | 304 | cfg->f_min = 400000; |
| 305 | cfg->f_max = get_PCLK() / 2; |
| 306 | cfg->b_max = 0x80; |
| 307 | |
| 308 | #if defined(CONFIG_S3C2410) |
| 309 | /* |
| 310 | * S3C2410 has some bug that prevents reliable |
| 311 | * operation at higher speed |
| 312 | */ |
| 313 | cfg->f_max /= 2; |
| 314 | #endif |
| 315 | |
| 316 | mmc = mmc_create(cfg, priv); |
| 317 | if (!mmc) { |
| 318 | free(priv); |
| 319 | return -ENOMEM; |
| 320 | } |
| 321 | |
| 322 | return 0; |
| 323 | } |