summaryrefslogtreecommitdiff
path: root/drivers/mmc/bfin_sdh.c
blob: 8d59d46c646878b866834bf72eb9649c1e2b5f4f (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
/*
 * Driver for Blackfin on-chip SDH controller
 *
 * Copyright (c) 2008-2009 Analog Devices Inc.
 *
 * Licensed under the GPL-2 or later.
 */

#include <common.h>
#include <malloc.h>
#include <part.h>
#include <mmc.h>

#include <asm/io.h>
#include <asm/errno.h>
#include <asm/byteorder.h>
#include <asm/blackfin.h>
#include <asm/portmux.h>
#include <asm/mach-common/bits/sdh.h>
#include <asm/mach-common/bits/dma.h>

#if defined(__ADSPBF50x__) || defined(__ADSPBF51x__)
# define bfin_read_SDH_PWR_CTL		bfin_read_RSI_PWR_CONTROL
# define bfin_write_SDH_PWR_CTL		bfin_write_RSI_PWR_CONTROL
# define bfin_read_SDH_CLK_CTL		bfin_read_RSI_CLK_CONTROL
# define bfin_write_SDH_CLK_CTL		bfin_write_RSI_CLK_CONTROL
# define bfin_write_SDH_ARGUMENT	bfin_write_RSI_ARGUMENT
# define bfin_write_SDH_COMMAND		bfin_write_RSI_COMMAND
# define bfin_read_SDH_RESPONSE0	bfin_read_RSI_RESPONSE0
# define bfin_read_SDH_RESPONSE1	bfin_read_RSI_RESPONSE1
# define bfin_read_SDH_RESPONSE2	bfin_read_RSI_RESPONSE2
# define bfin_read_SDH_RESPONSE3	bfin_read_RSI_RESPONSE3
# define bfin_write_SDH_DATA_TIMER	bfin_write_RSI_DATA_TIMER
# define bfin_write_SDH_DATA_LGTH	bfin_write_RSI_DATA_LGTH
# define bfin_read_SDH_DATA_CTL		bfin_read_RSI_DATA_CONTROL
# define bfin_write_SDH_DATA_CTL	bfin_write_RSI_DATA_CONTROL
# define bfin_read_SDH_STATUS		bfin_read_RSI_STATUS
# define bfin_write_SDH_STATUS_CLR 	bfin_write_RSI_STATUSCL
# define bfin_read_SDH_CFG		bfin_read_RSI_CONFIG
# define bfin_write_SDH_CFG		bfin_write_RSI_CONFIG
# define bfin_write_DMA_START_ADDR	bfin_write_DMA4_START_ADDR
# define bfin_write_DMA_X_COUNT		bfin_write_DMA4_X_COUNT
# define bfin_write_DMA_X_MODIFY	bfin_write_DMA4_X_MODIFY
# define bfin_write_DMA_CONFIG		bfin_write_DMA4_CONFIG
# define PORTMUX_PINS \
	{ P_RSI_DATA0, P_RSI_DATA1, P_RSI_DATA2, P_RSI_DATA3, P_RSI_CMD, P_RSI_CLK, 0 }
#elif defined(__ADSPBF54x__)
# define bfin_write_DMA_START_ADDR	bfin_write_DMA22_START_ADDR
# define bfin_write_DMA_X_COUNT		bfin_write_DMA22_X_COUNT
# define bfin_write_DMA_X_MODIFY	bfin_write_DMA22_X_MODIFY
# define bfin_write_DMA_CONFIG		bfin_write_DMA22_CONFIG
# define PORTMUX_PINS \
	{ P_SD_D0, P_SD_D1, P_SD_D2, P_SD_D3, P_SD_CLK, P_SD_CMD, 0 }
#else
# error no support for this proc yet
#endif

static int
sdh_send_cmd(struct mmc *mmc, struct mmc_cmd *mmc_cmd)
{
	unsigned int status, timeout;
	int cmd = mmc_cmd->cmdidx;
	int flags = mmc_cmd->resp_type;
	int arg = mmc_cmd->cmdarg;
	int ret;
	u16 sdh_cmd;

	sdh_cmd = cmd | CMD_E;
	if (flags & MMC_RSP_PRESENT)
		sdh_cmd |= CMD_RSP;
	if (flags & MMC_RSP_136)
		sdh_cmd |= CMD_L_RSP;

	bfin_write_SDH_ARGUMENT(arg);
	bfin_write_SDH_COMMAND(sdh_cmd);

	/* wait for a while */
	timeout = 0;
	do {
		if (++timeout > 1000000) {
			status = CMD_TIME_OUT;
			break;
		}
		udelay(1);
		status = bfin_read_SDH_STATUS();
	} while (!(status & (CMD_SENT | CMD_RESP_END | CMD_TIME_OUT |
		CMD_CRC_FAIL)));

	if (flags & MMC_RSP_PRESENT) {
		mmc_cmd->response[0] = bfin_read_SDH_RESPONSE0();
		if (flags & MMC_RSP_136) {
			mmc_cmd->response[1] = bfin_read_SDH_RESPONSE1();
			mmc_cmd->response[2] = bfin_read_SDH_RESPONSE2();
			mmc_cmd->response[3] = bfin_read_SDH_RESPONSE3();
		}
	}

	if (status & CMD_TIME_OUT)
		ret = TIMEOUT;
	else if (status & CMD_CRC_FAIL && flags & MMC_RSP_CRC)
		ret = COMM_ERR;
	else
		ret = 0;

	bfin_write_SDH_STATUS_CLR(CMD_SENT_STAT | CMD_RESP_END_STAT |
				CMD_TIMEOUT_STAT | CMD_CRC_FAIL_STAT);

	return ret;
}

/* set data for single block transfer */
static int sdh_setup_data(struct mmc *mmc, struct mmc_data *data)
{
	u16 data_ctl = 0;
	u16 dma_cfg = 0;
	int ret = 0;
	unsigned long data_size = data->blocksize * data->blocks;

	/* Don't support write yet. */
	if (data->flags & MMC_DATA_WRITE)
		return UNUSABLE_ERR;
	data_ctl |= ((ffs(data_size) - 1) << 4);
	data_ctl |= DTX_DIR;
	bfin_write_SDH_DATA_CTL(data_ctl);
	dma_cfg = WDSIZE_32 | RESTART | WNR | DMAEN;

	bfin_write_SDH_DATA_TIMER(-1);

	blackfin_dcache_flush_invalidate_range(data->dest,
			data->dest + data_size);
	/* configure DMA */
	bfin_write_DMA_START_ADDR(data->dest);
	bfin_write_DMA_X_COUNT(data_size / 4);
	bfin_write_DMA_X_MODIFY(4);
	bfin_write_DMA_CONFIG(dma_cfg);
	bfin_write_SDH_DATA_LGTH(data_size);
	/* kick off transfer */
	bfin_write_SDH_DATA_CTL(bfin_read_SDH_DATA_CTL() | DTX_DMA_E | DTX_E);

	return ret;
}


static int bfin_sdh_request(struct mmc *mmc, struct mmc_cmd *cmd,
		struct mmc_data *data)
{
	u32 status;
	int ret = 0;

	ret = sdh_send_cmd(mmc, cmd);
	if (ret) {
		printf("sending CMD%d failed\n", cmd->cmdidx);
		return ret;
	}
	if (data) {
		ret = sdh_setup_data(mmc, data);
		do {
			udelay(1);
			status = bfin_read_SDH_STATUS();
		} while (!(status & (DAT_BLK_END | DAT_END | DAT_TIME_OUT | DAT_CRC_FAIL | RX_OVERRUN)));

		if (status & DAT_TIME_OUT) {
			bfin_write_SDH_STATUS_CLR(DAT_TIMEOUT_STAT);
			ret |= TIMEOUT;
		} else if (status & (DAT_CRC_FAIL | RX_OVERRUN)) {
			bfin_write_SDH_STATUS_CLR(DAT_CRC_FAIL_STAT | RX_OVERRUN_STAT);
			ret |= COMM_ERR;
		} else
			bfin_write_SDH_STATUS_CLR(DAT_BLK_END_STAT | DAT_END_STAT);

		if (ret) {
			printf("tranfering data failed\n");
			return ret;
		}
	}
	return 0;
}

static void sdh_set_clk(unsigned long clk)
{
	unsigned long sys_clk;
	unsigned long clk_div;
	u16 clk_ctl = 0;

	clk_ctl = bfin_read_SDH_CLK_CTL();
	if (clk) {
		/* setting SD_CLK */
		sys_clk = get_sclk();
		bfin_write_SDH_CLK_CTL(clk_ctl & ~CLK_E);
		if (sys_clk % (2 * clk) == 0)
			clk_div = sys_clk / (2 * clk) - 1;
		else
			clk_div = sys_clk / (2 * clk);

		if (clk_div > 0xff)
			clk_div = 0xff;
		clk_ctl |= (clk_div & 0xff);
		clk_ctl |= CLK_E;
		bfin_write_SDH_CLK_CTL(clk_ctl);
	} else
		bfin_write_SDH_CLK_CTL(clk_ctl & ~CLK_E);
}

static void bfin_sdh_set_ios(struct mmc *mmc)
{
	u16 cfg = 0;
	u16 clk_ctl = 0;

	if (mmc->bus_width == 4) {
		cfg = bfin_read_SDH_CFG();
		cfg &= ~0x80;
		cfg |= 0x40;
		bfin_write_SDH_CFG(cfg);
		clk_ctl |= WIDE_BUS;
	}
	bfin_write_SDH_CLK_CTL(clk_ctl);
	sdh_set_clk(mmc->clock);
}

static int bfin_sdh_init(struct mmc *mmc)
{
	const unsigned short pins[] = PORTMUX_PINS;
	u16 pwr_ctl = 0;

	/* Initialize sdh controller */
	peripheral_request_list(pins, "bfin_sdh");
#if defined(__ADSPBF54x__)
	bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1);
#endif
	bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN);
	/* Disable card detect pin */
	bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | 0x60);

	pwr_ctl |= ROD_CTL;
	pwr_ctl |= PWR_ON;
	bfin_write_SDH_PWR_CTL(pwr_ctl);
	return 0;
}


int bfin_mmc_init(bd_t *bis)
{
	struct mmc *mmc = NULL;

	mmc = malloc(sizeof(struct mmc));

	if (!mmc)
		return -ENOMEM;
	sprintf(mmc->name, "Blackfin SDH");
	mmc->send_cmd = bfin_sdh_request;
	mmc->set_ios = bfin_sdh_set_ios;
	mmc->init = bfin_sdh_init;
	mmc->getcd = NULL;
	mmc->host_caps = MMC_MODE_4BIT;

	mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
	mmc->f_max = get_sclk();
	mmc->f_min = mmc->f_max >> 9;

	mmc->b_max = 0;

	mmc_register(mmc);

	return 0;
}