summaryrefslogtreecommitdiffstats
path: root/drivers/mci/sdhci.c
blob: 172c8343a199e700b52fa93cdd14be2deb6c7ec4 (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
// SPDX-License-Identifier: GPL-2.0

#include <common.h>
#include <driver.h>
#include <mci.h>
#include <io.h>

#include "sdhci.h"

void sdhci_read_response(struct sdhci *sdhci, struct mci_cmd *cmd)
{
	if (cmd->resp_type & MMC_RSP_136) {
		u32 cmdrsp3, cmdrsp2, cmdrsp1, cmdrsp0;

		cmdrsp3 = sdhci_read32(sdhci, SDHCI_RESPONSE_3);
		cmdrsp2 = sdhci_read32(sdhci, SDHCI_RESPONSE_2);
		cmdrsp1 = sdhci_read32(sdhci, SDHCI_RESPONSE_1);
		cmdrsp0 = sdhci_read32(sdhci, SDHCI_RESPONSE_0);
		cmd->response[0] = (cmdrsp3 << 8) | (cmdrsp2 >> 24);
		cmd->response[1] = (cmdrsp2 << 8) | (cmdrsp1 >> 24);
		cmd->response[2] = (cmdrsp1 << 8) | (cmdrsp0 >> 24);
		cmd->response[3] = (cmdrsp0 << 8);
	} else {
		cmd->response[0] = sdhci_read32(sdhci, SDHCI_RESPONSE_0);
	}
}

void sdhci_set_cmd_xfer_mode(struct sdhci *host, struct mci_cmd *cmd,
			     struct mci_data *data, bool dma, u32 *command,
			     u32 *xfer)
{
	*command = 0;
	*xfer = 0;

	if (!(cmd->resp_type & MMC_RSP_PRESENT))
		*command |= SDHCI_RESP_NONE;
	else if (cmd->resp_type & MMC_RSP_136)
		*command |= SDHCI_RESP_TYPE_136;
	else if (cmd->resp_type & MMC_RSP_BUSY)
		*command |= SDHCI_RESP_TYPE_48_BUSY;
	else
		*command |= SDHCI_RESP_TYPE_48;

	if (cmd->resp_type & MMC_RSP_CRC)
		*command |= SDHCI_CMD_CRC_CHECK_EN;
	if (cmd->resp_type & MMC_RSP_OPCODE)
		*command |= SDHCI_CMD_INDEX_CHECK_EN;

	*command |= SDHCI_CMD_INDEX(cmd->cmdidx);

	if (data) {
		*command |= SDHCI_DATA_PRESENT;

		*xfer |= SDHCI_BLOCK_COUNT_EN;

		if (data->blocks > 1)
			*xfer |= SDHCI_MULTIPLE_BLOCKS;

		if (data->flags & MMC_DATA_READ)
			*xfer |= SDHCI_DATA_TO_HOST;

		if (dma)
			*xfer |= SDHCI_DMA_EN;
	}
}

static void sdhci_rx_pio(struct sdhci *sdhci, struct mci_data *data,
			 unsigned int block)
{
	u32 *buf = (u32 *)data->dest;
	int i;

	buf += block * data->blocksize / sizeof(u32);

	for (i = 0; i < data->blocksize / sizeof(u32); i++)
		buf[i] = sdhci_read32(sdhci, SDHCI_BUFFER);
}

static void sdhci_tx_pio(struct sdhci *sdhci, struct mci_data *data,
			 unsigned int block)
{
	const u32 *buf = (const u32 *)data->src;
	int i;

	buf += block * data->blocksize / sizeof(u32);

	for (i = 0; i < data->blocksize / sizeof(u32); i++)
		sdhci_write32(sdhci, SDHCI_BUFFER, buf[i]);
}

#ifdef __PBL__
/*
 * Stubs to make timeout logic below work in PBL
 */

#define get_time_ns()		0
/*
 * Use time in us as a busy counter timeout value
 */
#define is_timeout(s, t)	((s)++ > ((t) / 1000))

#endif

int sdhci_transfer_data(struct sdhci *sdhci, struct mci_data *data)
{
	unsigned int block = 0;
	u32 stat, prs;
	uint64_t start = get_time_ns();

	do {
		stat = sdhci_read32(sdhci, SDHCI_INT_STATUS);
		if (stat & SDHCI_INT_ERROR)
			return -EIO;

		if (block >= data->blocks)
			continue;

		prs = sdhci_read32(sdhci, SDHCI_PRESENT_STATE);

		if (prs & SDHCI_BUFFER_READ_ENABLE &&
		    data->flags & MMC_DATA_READ) {
			sdhci_rx_pio(sdhci, data, block);
			block++;
			start = get_time_ns();
		}

		if (prs & SDHCI_BUFFER_WRITE_ENABLE &&
		    !(data->flags & MMC_DATA_READ)) {
			sdhci_tx_pio(sdhci, data, block);
			block++;
			start = get_time_ns();
		}

		if (is_timeout(start, 10 * SECOND))
			return -ETIMEDOUT;

	} while (!(stat & SDHCI_INT_XFER_COMPLETE));

	return 0;
}