summaryrefslogtreecommitdiffstats
path: root/drivers/mmc/s3c_sdi.c
blob: faf7b83a14ce5c66cf1cf382b5a0cfef4c896db8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
/*
 * S3C24xx SD/MMC driver
 *
 * Based on OpenMoko S3C24xx driver by Harald Welte <laforge@openmoko.org>
 *
 * Copyright (C) 2014 Marek Vasut <marex@denx.de>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <malloc.h>
#include <mmc.h>
#include <errno.h>
#include <asm/arch/s3c24x0_cpu.h>
#include <asm/io.h>
#include <asm/unaligned.h>

#define S3C2440_SDICON_SDRESET		(1 << 8)
#define S3C2410_SDICON_FIFORESET	(1 << 1)
#define S3C2410_SDICON_CLOCKTYPE	(1 << 0)

#define S3C2410_SDICMDCON_LONGRSP	(1 << 10)
#define S3C2410_SDICMDCON_WAITRSP	(1 << 9)
#define S3C2410_SDICMDCON_CMDSTART	(1 << 8)
#define S3C2410_SDICMDCON_SENDERHOST	(1 << 6)
#define S3C2410_SDICMDCON_INDEX		0x3f

#define S3C2410_SDICMDSTAT_CRCFAIL	(1 << 12)
#define S3C2410_SDICMDSTAT_CMDSENT	(1 << 11)
#define S3C2410_SDICMDSTAT_CMDTIMEOUT	(1 << 10)
#define S3C2410_SDICMDSTAT_RSPFIN	(1 << 9)

#define S3C2440_SDIDCON_DS_WORD		(2 << 22)
#define S3C2410_SDIDCON_TXAFTERRESP	(1 << 20)
#define S3C2410_SDIDCON_RXAFTERCMD	(1 << 19)
#define S3C2410_SDIDCON_BLOCKMODE	(1 << 17)
#define S3C2410_SDIDCON_WIDEBUS		(1 << 16)
#define S3C2440_SDIDCON_DATSTART	(1 << 14)
#define S3C2410_SDIDCON_XFER_RXSTART	(2 << 12)
#define S3C2410_SDIDCON_XFER_TXSTART	(3 << 12)
#define S3C2410_SDIDCON_BLKNUM		0x7ff

#define S3C2410_SDIDSTA_FIFOFAIL	(1 << 8)
#define S3C2410_SDIDSTA_CRCFAIL		(1 << 7)
#define S3C2410_SDIDSTA_RXCRCFAIL	(1 << 6)
#define S3C2410_SDIDSTA_DATATIMEOUT	(1 << 5)
#define S3C2410_SDIDSTA_XFERFINISH	(1 << 4)

#define S3C2410_SDIFSTA_TFHALF		(1 << 11)
#define S3C2410_SDIFSTA_COUNTMASK	0x7f

/*
 * WARNING: We only support one SD IP block.
 * NOTE: It's not likely there will ever exist an S3C24xx with two,
 *       at least not in this universe all right.
 */
static int wide_bus;

static int
s3cmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
{
	struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi();
	uint32_t sdiccon, sdicsta, sdidcon, sdidsta, sdidat, sdifsta;
	uint32_t sdicsta_wait_bit = S3C2410_SDICMDSTAT_CMDSENT;
	unsigned int timeout = 100000;
	int ret = 0, xfer_len, data_offset = 0;
	const uint32_t sdidsta_err_mask = S3C2410_SDIDSTA_FIFOFAIL |
		S3C2410_SDIDSTA_CRCFAIL | S3C2410_SDIDSTA_RXCRCFAIL |
		S3C2410_SDIDSTA_DATATIMEOUT;


	writel(0xffffffff, &sdi_regs->sdicsta);
	writel(0xffffffff, &sdi_regs->sdidsta);
	writel(0xffffffff, &sdi_regs->sdifsta);

	/* Set up data transfer (if applicable). */
	if (data) {
		writel(data->blocksize, &sdi_regs->sdibsize);

		sdidcon = data->blocks & S3C2410_SDIDCON_BLKNUM;
		sdidcon |= S3C2410_SDIDCON_BLOCKMODE;
#if defined(CONFIG_S3C2440)
		sdidcon |= S3C2440_SDIDCON_DS_WORD | S3C2440_SDIDCON_DATSTART;
#endif
		if (wide_bus)
			sdidcon |= S3C2410_SDIDCON_WIDEBUS;

		if (data->flags & MMC_DATA_READ) {
			sdidcon |= S3C2410_SDIDCON_RXAFTERCMD;
			sdidcon |= S3C2410_SDIDCON_XFER_RXSTART;
		} else {
			sdidcon |= S3C2410_SDIDCON_TXAFTERRESP;
			sdidcon |= S3C2410_SDIDCON_XFER_TXSTART;
		}

		writel(sdidcon, &sdi_regs->sdidcon);
	}

	/* Write CMD arg. */
	writel(cmd->cmdarg, &sdi_regs->sdicarg);

	/* Write CMD index. */
	sdiccon = cmd->cmdidx & S3C2410_SDICMDCON_INDEX;
	sdiccon |= S3C2410_SDICMDCON_SENDERHOST;
	sdiccon |= S3C2410_SDICMDCON_CMDSTART;

	/* Command with short response. */
	if (cmd->resp_type & MMC_RSP_PRESENT) {
		sdiccon |= S3C2410_SDICMDCON_WAITRSP;
		sdicsta_wait_bit = S3C2410_SDICMDSTAT_RSPFIN;
	}

	/* Command with long response. */
	if (cmd->resp_type & MMC_RSP_136)
		sdiccon |= S3C2410_SDICMDCON_LONGRSP;

	/* Start the command. */
	writel(sdiccon, &sdi_regs->sdiccon);

	/* Wait for the command to complete or for response. */
	for (timeout = 100000; timeout; timeout--) {
		sdicsta = readl(&sdi_regs->sdicsta);
		if (sdicsta & sdicsta_wait_bit)
			break;

		if (sdicsta & S3C2410_SDICMDSTAT_CMDTIMEOUT)
			timeout = 1;
	}

	/* Clean the status bits. */
	setbits_le32(&sdi_regs->sdicsta, 0xf << 9);

	if (!timeout) {
		puts("S3C SDI: Command timed out!\n");
		ret = -ETIMEDOUT;
		goto error;
	}

	/* Read out the response. */
	if (cmd->resp_type & MMC_RSP_136) {
		cmd->response[0] = readl(&sdi_regs->sdirsp0);
		cmd->response[1] = readl(&sdi_regs->sdirsp1);
		cmd->response[2] = readl(&sdi_regs->sdirsp2);
		cmd->response[3] = readl(&sdi_regs->sdirsp3);
	} else {
		cmd->response[0] = readl(&sdi_regs->sdirsp0);
	}

	/* If there are no data, we're done. */
	if (!data)
		return 0;

	xfer_len = data->blocksize * data->blocks;

	while (xfer_len > 0) {
		sdidsta = readl(&sdi_regs->sdidsta);
		sdifsta = readl(&sdi_regs->sdifsta);

		if (sdidsta & sdidsta_err_mask) {
			printf("S3C SDI: Data error (sdta=0x%08x)\n", sdidsta);
			ret = -EIO;
			goto error;
		}

		if (data->flags & MMC_DATA_READ) {
			if ((sdifsta & S3C2410_SDIFSTA_COUNTMASK) < 4)
				continue;
			sdidat = readl(&sdi_regs->sdidat);
			put_unaligned_le32(sdidat, data->dest + data_offset);
		} else {	/* Write */
			/* TX FIFO half full. */
			if (!(sdifsta & S3C2410_SDIFSTA_TFHALF))
				continue;

			/* TX FIFO is below 32b full, write. */
			sdidat = get_unaligned_le32(data->src + data_offset);
			writel(sdidat, &sdi_regs->sdidat);
		}
		data_offset += 4;
		xfer_len -= 4;
	}

	/* Wait for the command to complete or for response. */
	for (timeout = 100000; timeout; timeout--) {
		sdidsta = readl(&sdi_regs->sdidsta);
		if (sdidsta & S3C2410_SDIDSTA_XFERFINISH)
			break;

		if (sdidsta & S3C2410_SDIDSTA_DATATIMEOUT)
			timeout = 1;
	}

	/* Clear status bits. */
	writel(0x6f8, &sdi_regs->sdidsta);

	if (!timeout) {
		puts("S3C SDI: Command timed out!\n");
		ret = -ETIMEDOUT;
		goto error;
	}

	writel(0, &sdi_regs->sdidcon);

	return 0;
error:
	return ret;
}

static int s3cmmc_set_ios(struct mmc *mmc)
{
	struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi();
	uint32_t divider = 0;

	wide_bus = (mmc->bus_width == 4);

	if (!mmc->clock)
		return 0;

	divider = DIV_ROUND_UP(get_PCLK(), mmc->clock);
	if (divider)
		divider--;

	writel(divider, &sdi_regs->sdipre);
	mdelay(125);

	return 0;
}

static int s3cmmc_init(struct mmc *mmc)
{
	struct s3c24x0_clock_power *clk_power = s3c24x0_get_base_clock_power();
	struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi();

	/* Start the clock. */
	setbits_le32(&clk_power->clkcon, 1 << 9);

#if defined(CONFIG_S3C2440)
	writel(S3C2440_SDICON_SDRESET, &sdi_regs->sdicon);
	mdelay(10);
	writel(0x7fffff, &sdi_regs->sdidtimer);
#else
	writel(0xffff, &sdi_regs->sdidtimer);
#endif
	writel(MMC_MAX_BLOCK_LEN, &sdi_regs->sdibsize);
	writel(0x0, &sdi_regs->sdiimsk);

	writel(S3C2410_SDICON_FIFORESET | S3C2410_SDICON_CLOCKTYPE,
	       &sdi_regs->sdicon);

	mdelay(125);

	return 0;
}

struct s3cmmc_priv {
	struct mmc_config	cfg;
	int (*getcd)(struct mmc *);
	int (*getwp)(struct mmc *);
};

static int s3cmmc_getcd(struct mmc *mmc)
{
	struct s3cmmc_priv *priv = mmc->priv;
	if (priv->getcd)
		return priv->getcd(mmc);
	else
		return 0;
}

static int s3cmmc_getwp(struct mmc *mmc)
{
	struct s3cmmc_priv *priv = mmc->priv;
	if (priv->getwp)
		return priv->getwp(mmc);
	else
		return 0;
}

static const struct mmc_ops s3cmmc_ops = {
	.send_cmd	= s3cmmc_send_cmd,
	.set_ios	= s3cmmc_set_ios,
	.init		= s3cmmc_init,
	.getcd		= s3cmmc_getcd,
	.getwp		= s3cmmc_getwp,
};

int s3cmmc_initialize(bd_t *bis, int (*getcd)(struct mmc *),
		      int (*getwp)(struct mmc *))
{
	struct s3cmmc_priv	*priv;
	struct mmc		*mmc;
	struct mmc_config	*cfg;

	priv = calloc(1, sizeof(*priv));
	if (!priv)
		return -ENOMEM;
	cfg = &priv->cfg;

	cfg->name = "S3C MMC";
	cfg->ops = &s3cmmc_ops;
	cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
	cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS;
	cfg->f_min = 400000;
	cfg->f_max = get_PCLK() / 2;
	cfg->b_max = 0x80;

#if defined(CONFIG_S3C2410)
	/*
	 * S3C2410 has some bug that prevents reliable
	 * operation at higher speed
	 */
	cfg->f_max /= 2;
#endif

	mmc = mmc_create(cfg, priv);
	if (!mmc) {
		free(priv);
		return -ENOMEM;
	}

	return 0;
}