summaryrefslogtreecommitdiff
path: root/chip/lm4/flash.c
blob: 43f5d5923b7fdb34dbb3d0ebf8544efa4efd1b45 (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
/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* Flash memory module for Chrome EC */

#include "flash.h"
#include "gpio.h"
#include "uart.h"
#include "registers.h"
#include "util.h"

#define BANK_SHIFT 5 /* bank registers have 32bits each, 2^32 */
#define BANK_MASK ((1 << BANK_SHIFT) - 1) /* 5 bits */
#define F_BANK(b) ((b) >> BANK_SHIFT)
#define F_BIT(b) (1 << ((b) & BANK_MASK))

static int usable_flash_size;


int flash_get_size(void)
{
	return usable_flash_size;
}


int flash_get_write_block_size(void)
{
	return FLASH_WRITE_BYTES;
}


int flash_get_erase_block_size(void)
{
	return FLASH_ERASE_BYTES;
}


int flash_get_protect_block_size(void)
{
	return FLASH_PROTECT_BYTES;
}


int flash_read(int offset, int size, char *data)
{
	if (size < 0 || offset > usable_flash_size ||
	    offset + size > usable_flash_size)
		return EC_ERROR_UNKNOWN;  /* Invalid range */

	/* Just read the flash from its memory window. */
	/* TODO: (crosbug.com/p/7473) is this affected by data cache?
	 * That is, if we read a block, then alter it, then read it
	 * again, do we get the old data? */
	memcpy(data, (char *)offset, size);
	return EC_SUCCESS;
}


/* Performs a write-buffer operation.  Buffer (FWB) and address (FMA)
 * must be pre-loaded. */
static int write_buffer(void)
{
	if (!LM4_FLASH_FWBVAL)
		return EC_SUCCESS;  /* Nothing to do */

	/* Clear previous error status */
	LM4_FLASH_FCMISC = LM4_FLASH_FCRIS;

	/* Start write operation at page boundary */
	LM4_FLASH_FMC2 = 0xa4420001;

	/* Wait for write to complete */
	while (LM4_FLASH_FMC2 & 0x01) {}

	/* Check for error conditions - program failed, erase needed,
	 * voltage error. */
	if (LM4_FLASH_FCRIS & 0x2600)
		return EC_ERROR_UNKNOWN;

	return EC_SUCCESS;
}


int flash_write(int offset, int size, const char *data)
{
	const uint32_t *data32 = (const uint32_t *)data;
	int rv;
	int i;

	if (size < 0 || offset > usable_flash_size ||
	    offset + size > usable_flash_size ||
	    (offset | size) & (FLASH_WRITE_BYTES - 1))
		return EC_ERROR_UNKNOWN;  /* Invalid range */

	/* TODO (crosbug.com/p/7478) - safety check - don't allow writing to
	 * the image we're running from */

	/* Get initial page and write buffer index */
	LM4_FLASH_FMA = offset & ~(FLASH_FWB_BYTES - 1);
	i = (offset >> 2) & (FLASH_FWB_WORDS - 1);

	/* Copy words into buffer */
	for ( ; size > 0; size -= 4) {
		LM4_FLASH_FWB[i++] = *data32++;
		if (i == FLASH_FWB_WORDS) {
			rv = write_buffer();
			if (rv != EC_SUCCESS)
				return rv;

			/* Advance to next page */
			i = 0;
			LM4_FLASH_FMA += FLASH_FWB_BYTES;
		}
	}

	/* Handle final partial page, if any */
	if (i > 0) {
		rv = write_buffer();
		if (rv != EC_SUCCESS)
			return rv;
	}
	return EC_SUCCESS;
}


int flash_erase(int offset, int size)
{
	if (size < 0 || offset > usable_flash_size ||
	    offset + size > usable_flash_size ||
	    (offset | size) & (FLASH_ERASE_BYTES - 1))
		return EC_ERROR_UNKNOWN;  /* Invalid range */

	/* TODO (crosbug.com/p/7478) - safety check - don't allow erasing the
	 * image we're running from */

	LM4_FLASH_FCMISC = LM4_FLASH_FCRIS;  /* Clear previous error status */
	LM4_FLASH_FMA = offset;

	for ( ; size > 0; size -= FLASH_ERASE_BYTES) {
		/* Start erase */
		LM4_FLASH_FMC = 0xa4420002;

		/* Wait for erase to complete */
		while (LM4_FLASH_FMC & 0x02) {}

		/* Check for error conditions - erase failed, voltage error */
		if (LM4_FLASH_FCRIS & 0x0a00)
			return EC_ERROR_UNKNOWN;

		LM4_FLASH_FMA += FLASH_ERASE_BYTES;
	}

	return EC_SUCCESS;
}

/* Get write protect status of single flash block
 * return value:
 *   0 - WP
 *   non-zero - writable
 */
static uint32_t get_block_wp(int block)
{
	return LM4_FLASH_FMPPE[F_BANK(block)] & F_BIT(block);
}

static void set_block_wp(int block)
{
	LM4_FLASH_FMPPE[F_BANK(block)] &= ~F_BIT(block);
}

static int find_first_wp_block(void)
{
	int block;
	for (block = 0; block < LM4_FLASH_FSIZE; block++)
		if (get_block_wp(block) == 0)
			return block;
	return -1;
}

static int find_last_wp_block(void)
{
	int block;
	for (block = LM4_FLASH_FSIZE - 1; block >= 0; block--)
		if (get_block_wp(block) == 0)
			return block;
	return -1;
}

static int get_wp_range(int *start, int *nblock)
{
	int start_blk, end_blk;

	start_blk = find_first_wp_block();

	if (start_blk < 0) {
		/* Flash is not write protected */
		*start = 0;
		*nblock = 0;
		return EC_SUCCESS;
	}

	/* TODO: Sanity check the shadow value? */

	end_blk = find_last_wp_block();
	*nblock = end_blk - start_blk + 1;
	*start = start_blk;
	return EC_SUCCESS;
}


static int set_wp_range(int start, int nblock)
{
	int end_blk, block;

	if (nblock == 0)
		return EC_SUCCESS;

	end_blk = (start + nblock - 1);

	for (block = start; block <= end_blk; block++)
		set_block_wp(block);

	return EC_SUCCESS;
}

int flash_get_write_protect_range(int *offset, int *size)
{
	int start, nblock;
	int rv;

	rv = get_wp_range(&start, &nblock);
	if (rv)
		return rv;

	*size = nblock * FLASH_PROTECT_BYTES;
	*offset = start * FLASH_PROTECT_BYTES;
	return EC_SUCCESS;
}

int flash_set_write_protect_range(int offset, int size)
{
	int start, nblock;
	int rv;

	if ((offset < 0) || (size < 0) || ((offset + size) >
			(LM4_FLASH_FSIZE * FLASH_PROTECT_BYTES)))
		return EC_ERROR_UNKNOWN; /* Invalid range */

	rv = flash_get_write_protect_status();

	if (rv & EC_FLASH_WP_RANGE_LOCKED) {
		if (size == 0) {
			/* TODO: Clear shadow if system WP is asserted */
			/* TODO: Reboot EC */
			return EC_SUCCESS;
		}

		return EC_ERROR_UNKNOWN; /* Range locked */
	}

	start = offset / FLASH_PROTECT_BYTES;
	nblock = ((offset + size - 1) / FLASH_PROTECT_BYTES) - start + 1;
	rv = set_wp_range(start, nblock);
	if (rv)
		return rv;

	return EC_SUCCESS;
}


int flash_get_write_protect_status(void)
{
	int start, nblock;
	int rv;

	rv = get_wp_range(&start, &nblock);
	if (rv)
		return rv;

	rv = 0;
	if (nblock)
		rv |= EC_FLASH_WP_RANGE_LOCKED;
	/* TODO: get WP gpio*/

	return rv;
}


int flash_init(void)
{
	/* Calculate usable flash size.  Reserve one protection block
	 * at the top to hold the write protect range.  FSIZE already
	 * returns one less than the number of protection pages. */
	usable_flash_size = LM4_FLASH_FSIZE * FLASH_PROTECT_BYTES;

	/* TODO (crosbug.com/p/7453) - check WP# GPIO.  If it's set and the
	 * flash protect range is set, write the flash protection registers.
	 * Probably cleaner to do this in vboot, since we're going to need to
	 * use the same last block of flash to hold the firmware rollback
	 * counters. */
	return EC_SUCCESS;
}