aboutsummaryrefslogtreecommitdiff
path: root/drivers/io/vexpress_nor/io_vexpress_nor_hw.c
blob: 6e563c9f04ee4c1b9646cea4a81449c9116644e4 (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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/*
 * Copyright (c) 2018, Arm Limited. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <assert.h>
#include <debug.h>
#include <mmio.h>
#include <string.h>
#include "io_vexpress_nor_internal.h"
#include "norflash.h"

/* Device Id information */
#define NOR_DEVICE_ID_LOCK_CONFIGURATION          0x02
#define NOR_DEVICE_ID_BLOCK_LOCKED                (1 << 0)
#define NOR_DEVICE_ID_BLOCK_LOCKED_DOWN           (1 << 1)

/* Status Register Bits */
#define NOR_SR_BIT_WRITE                          ((1 << 23) | (1 << 7))
#define NOR_SR_BIT_ERASE                          ((1 << 21) | (1 << 5))
#define NOR_SR_BIT_PROGRAM                        ((1 << 20) | (1 << 4))
#define NOR_SR_BIT_VPP                            ((1 << 19) | (1 << 3))
#define NOR_SR_BIT_BLOCK_LOCKED                   ((1 << 17) | (1 << 1))

/*
 * On chip buffer size for buffered programming operations
 * There are 2 chips, each chip can buffer up to 32 (16-bit)words.
 * Therefore the total size of the buffer is 2 x 32 x 2 = 128 bytes.
 */
#define NOR_MAX_BUFFER_SIZE_IN_BYTES        128
#define NOR_MAX_BUFFER_SIZE_IN_WORDS        (NOR_MAX_BUFFER_SIZE_IN_BYTES / 4)

#define MAX_BUFFERED_PROG_ITERATIONS 1000
#define LOW_16_BITS                  0x0000FFFF
#define FOLD_32BIT_INTO_16BIT(value) ((value >> 16) | (value & LOW_16_BITS))
#define BOUNDARY_OF_32_WORDS         0x7F

#define CHECK_VPP_RANGE_ERROR(status_register, address)			\
		do {							\
			if ((status_register) & NOR_SR_BIT_VPP) {	\
				ERROR("%s (address:0x%X): "		\
				"VPP Range Error\n", __func__, address);\
				err = IO_FAIL;				\
			}						\
		} while (0)

#define CHECK_BLOCK_LOCK_ERROR(status_register, address)		\
	do {								\
		if ((status_register) & NOR_SR_BIT_BLOCK_LOCKED) {	\
			ERROR("%s (address:0x%X): Device Protect "	\
				"Error\n", __func__, address);		\
			err = IO_FAIL;					\
		}							\
	} while (0)

#define CHECK_BLOCK_ERASE_ERROR(status_register, block_offset)		\
	do {								\
		if ((status_register) & NOR_SR_BIT_ERASE) {		\
			ERROR("%s (block_offset=0x%08x:	"		\
				"Block Erase Error status_register"	\
				":0x%x\n",  __func__, block_offset,	\
				status_register);			\
			err = IO_FAIL;					\
		}							\
	} while (0)

#define CHECK_SR_BIT_PROGRAM_ERROR(status_register, address)		\
	do {								\
		if ((status_register) & NOR_SR_BIT_PROGRAM) {		\
			ERROR("%s(address:0x%X): Program Error\n",	\
				__func__, address);			\
			err = IO_FAIL;					\
		}							\
	} while (0)

/* Helper macros to access two flash banks in parallel */
#define NOR_2X16(d)			((d << 16) | (d & 0xffff))

static inline void nor_send_cmd(uintptr_t base_addr, unsigned long cmd)
{
	mmio_write_32(base_addr, NOR_2X16(cmd));
}

static uint32_t flash_read_status(const io_nor_flash_spec_t *device)
{
	/* Prepare to read the status register */
	nor_send_cmd(device->device_address, NOR_CMD_READ_STATUS_REG);

	return mmio_read_32(device->device_address);
}

static uint32_t flash_wait_until_complete(const io_nor_flash_spec_t *device)
{
	uint32_t lock_status;

	/* Wait until the status register gives us the all clear */
	do {
		lock_status = flash_read_status(device);
	} while ((lock_status & NOR_SR_BIT_WRITE) != NOR_SR_BIT_WRITE);

	return lock_status;
}

static int flash_block_is_locked(uint32_t block_offset)
{
	uint32_t lock_status;

	uintptr_t addr = block_offset + (NOR_DEVICE_ID_LOCK_CONFIGURATION << 2);

	/* Send command for reading device id */
	nor_send_cmd(addr, NOR_CMD_READ_ID_CODE);

	/* Read block lock status */
	lock_status = mmio_read_32(addr);

	/* Decode block lock status */
	lock_status = FOLD_32BIT_INTO_16BIT(lock_status);

	if ((lock_status & NOR_DEVICE_ID_BLOCK_LOCKED_DOWN) != 0)
		WARN("flash_block_is_locked: Block LOCKED DOWN\n");

	return lock_status & NOR_DEVICE_ID_BLOCK_LOCKED;
}


static void flash_perform_lock_operation(const io_nor_flash_spec_t *device,
						uint32_t block_offset,
						uint32_t lock_operation)
{
	assert ((lock_operation == NOR_UNLOCK_BLOCK) ||
		(lock_operation == NOR_LOCK_BLOCK));

	/* Request a lock setup */
	nor_send_cmd(block_offset, NOR_CMD_LOCK_UNLOCK);

	/* Request lock or unlock */
	nor_send_cmd(block_offset, lock_operation);

	/* Wait until status register shows device is free */
	flash_wait_until_complete(device);

	/* Put device back into Read Array mode */
	nor_send_cmd(block_offset, NOR_CMD_READ_ARRAY);
}

static void flash_unlock_block_if_necessary(const io_nor_flash_spec_t *device,
						 uint32_t block_offset)
{
	if (flash_block_is_locked(block_offset) != 0)
		flash_perform_lock_operation(device, block_offset,
						NOR_UNLOCK_BLOCK);
}


static int flash_erase_block(const io_nor_flash_spec_t *device,
					  uint32_t block_offset)
{
	int err = IO_SUCCESS;
	uint32_t status_register;

	/* Request a block erase and then confirm it */
	nor_send_cmd(block_offset, NOR_CMD_BLOCK_ERASE);
	nor_send_cmd(block_offset, NOR_CMD_BLOCK_ERASE_ACK);

	/* Wait for the write to complete and then check for any errors;
	 * i.e. check the Status Register */
	status_register = flash_wait_until_complete(device);

	CHECK_VPP_RANGE_ERROR(status_register, block_offset);

	if ((status_register & (NOR_SR_BIT_ERASE | NOR_SR_BIT_PROGRAM)) ==
				(NOR_SR_BIT_ERASE | NOR_SR_BIT_PROGRAM)) {
		ERROR("%s(block_offset=0x%08x: "
			  "Command Sequence Error\n", __func__, block_offset);
		err = IO_FAIL;
	}

	CHECK_BLOCK_ERASE_ERROR(status_register, block_offset);

	CHECK_BLOCK_LOCK_ERROR(status_register, block_offset);

	if (err) {
		/* Clear the Status Register */
		nor_send_cmd(device->device_address, NOR_CMD_CLEAR_STATUS_REG);
	}

	/* Put device back into Read Array mode */
	nor_send_cmd(device->device_address, NOR_CMD_READ_ARRAY);

	return err;
}

/*
 * Writes data to the NOR Flash using the Buffered Programming method.
 *
 * The maximum size of the on-chip buffer is 32-words, because of hardware
 * restrictions. Therefore this function will only handle buffers up to 32
 * words or 128 bytes. To deal with larger buffers, call this function again.
 *
 * This function presumes that both the offset and the offset+BufferSize
 * fit entirely within the NOR Flash. Therefore these conditions will not
 * be checked here.
 *
 * In buffered programming, if the target address is not at the beginning of a
 * 32-bit word boundary, then programming time is doubled and power consumption
 * is increased. Therefore, it is a requirement to align buffer writes to
 * 32-bit word boundaries.
 */
static int flash_write_buffer(const io_nor_flash_spec_t *device,
				uint32_t offset,
				const uint32_t *buffer,
				uint32_t buffer_size)
{
	int err = IO_SUCCESS;
	uint32_t size_in_words;
	uint32_t count;
	volatile uint32_t *data;
	uint32_t timeout;
	int is_buffer_available = 0;
	uint32_t status_register;

	timeout = MAX_BUFFERED_PROG_ITERATIONS;
	is_buffer_available = 0;

	/* Check that the target offset does not cross a 32-word boundary. */
	if ((offset & BOUNDARY_OF_32_WORDS) != 0)
		return IO_FAIL;

	/* This implementation requires the buffer to be 32bit aligned. */
	if (((uintptr_t)buffer & (sizeof(uint32_t) - 1)) != 0)
		return IO_FAIL;

	/* Check there are some data to program */
	assert(buffer_size > 0);

	/* Check that the buffer size does not exceed the maximum hardware
	 * buffer size on chip.
	 */
	assert(buffer_size <= NOR_MAX_BUFFER_SIZE_IN_BYTES);

	/* Check that the buffer size is a multiple of 32-bit words */
	assert((buffer_size % 4) == 0);

	/* Pre-programming conditions checked, now start the algorithm. */

	/* Prepare the data destination address */
	data = (uint32_t *)(uintptr_t)offset;

	/* Check the availability of the buffer */
	do {
		/* Issue the Buffered Program Setup command */
		nor_send_cmd(offset, NOR_CMD_BUFFERED_PROGRAM);

		/* Read back the status register bit#7 from the same offset */
		if (((*data) & NOR_SR_BIT_WRITE) == NOR_SR_BIT_WRITE)
			is_buffer_available = 1;

		/* Update the loop counter */
		timeout--;
	} while ((timeout > 0) && (is_buffer_available == 0));

	/* The buffer was not available for writing */
	if (timeout == 0) {
		err = IO_FAIL;
		goto exit;
	}

	/* From now on we work in 32-bit words */
	size_in_words = buffer_size / sizeof(uint32_t);

	/* Write the word count, which is (buffer_size_in_words - 1),
	 * because word count 0 means one word. */
	nor_send_cmd(offset, size_in_words - 1);

	/* Write the data to the NOR Flash, advancing each address by 4 bytes */
	for (count = 0; count < size_in_words; count++, data++, buffer++)
		*data = *buffer;

	/* Issue the Buffered Program Confirm command, to start the programming
	 * operation */
	nor_send_cmd(device->device_address, NOR_CMD_BUFFERED_PROGRAM_ACK);

	/* Wait for the write to complete and then check for any errors;
	 * i.e. check the Status Register */
	status_register = flash_wait_until_complete(device);

	/* Perform a full status check:
	 * Mask the relevant bits of Status Register.
	 * Everything should be zero, if not, we have a problem */

	CHECK_VPP_RANGE_ERROR(status_register, offset);

	CHECK_SR_BIT_PROGRAM_ERROR(status_register, offset);

	CHECK_BLOCK_LOCK_ERROR(status_register, offset);

	if (err != IO_SUCCESS) {
		/* Clear the Status Register */
		nor_send_cmd(device->device_address,
			     NOR_CMD_CLEAR_STATUS_REG);
	}

exit:
	/* Put device back into Read Array mode */
	nor_send_cmd(device->device_address, NOR_CMD_READ_ARRAY);

	return err;
}

static int flash_write_single_word(const io_nor_flash_spec_t *device,
				int32_t offset, uint32_t data)
{
	int err = IO_SUCCESS;
	uint32_t status_register;

	/* NOR Flash Programming: Request a write single word command */
	nor_send_cmd(offset, NOR_CMD_WORD_PROGRAM);

	/* Store the word into NOR Flash; */
	mmio_write_32(offset, data);

	/* Wait for the write to complete and then check for any errors;
	 * i.e. check the Status Register */
	status_register = flash_wait_until_complete(device);

	/* Perform a full status check: */
	/* Mask the relevant bits of Status Register.
	 * Everything should be zero, if not, we have a problem */

	CHECK_VPP_RANGE_ERROR(status_register, offset);

	CHECK_SR_BIT_PROGRAM_ERROR(status_register, offset);

	CHECK_BLOCK_LOCK_ERROR(status_register, offset);

	if (err != IO_SUCCESS)
		/* Clear the Status Register */
		nor_send_cmd(device->device_address,
					NOR_CMD_CLEAR_STATUS_REG);

	/* Put device back into Read Array mode */
	nor_send_cmd(device->device_address, NOR_CMD_READ_ARRAY);

	return err;
}

int flash_block_write(file_state_t *fp, uint32_t offset,
		const uintptr_t buffer, size_t *written)
{
	int ret;
	uintptr_t buffer_ptr = buffer;
	uint32_t buffer_size;
	uint32_t remaining = fp->block_spec->block_size;
	uint32_t flash_pos = fp->block_spec->region_address + offset;
	uint32_t block_offset = flash_pos;

	/* address passed should be block aligned */
	assert(!(offset % fp->block_spec->block_size));

	/* Unlock block */
	flash_unlock_block_if_necessary(fp->block_spec, block_offset);

	/* Erase one block */
	ret = flash_erase_block(fp->block_spec, flash_pos);

	if (ret != IO_SUCCESS)
		/* Perform lock operation as we unlocked it */
		goto lock_block;

	/* Start by using NOR Flash buffer while the buffer size is a multiple
	 * of 32-bit */
	while ((remaining >= sizeof(uint32_t)) && (ret == IO_SUCCESS)) {
		if (remaining >= NOR_MAX_BUFFER_SIZE_IN_BYTES)
			buffer_size = NOR_MAX_BUFFER_SIZE_IN_BYTES;
		else
			/* Copy the remaining 32bit words of the buffer */
			buffer_size = remaining & (sizeof(uint32_t) - 1);

		ret = flash_write_buffer(fp->block_spec, flash_pos,
				(const uint32_t *)buffer_ptr, buffer_size);
		flash_pos += buffer_size;
		remaining -= buffer_size;
		buffer_ptr += buffer_size;

	}

	/* Write the remaining bytes */
	while ((remaining > 0) && (ret == IO_SUCCESS)) {
		ret = flash_write_single_word(fp->block_spec,
						flash_pos++, buffer_ptr++);
		remaining--;
	}

	if (ret == IO_SUCCESS)
		*written = fp->block_spec->block_size;

lock_block:
	/* Lock the block once done */
	flash_perform_lock_operation(fp->block_spec,
					block_offset,
					NOR_LOCK_BLOCK);

	return ret;
}

/* In case of partial write we need to save the block into a temporary buffer */
static char block_buffer[NOR_FLASH_BLOCK_SIZE];

int flash_partial_write(file_state_t *fp, uint32_t offset,
		const uintptr_t buffer, size_t length, size_t *written)
{
	uintptr_t block_start;
	uint32_t block_size;
	uint32_t block_offset;
	int ret;

	assert((fp != NULL) && (fp->block_spec != NULL));
	assert(written != NULL);

	block_size = fp->block_spec->block_size;
	/* Start address of the block to write */
	block_start = (offset / block_size) * block_size;

	/* Ensure 'block_buffer' is big enough to contain a copy of the block.
	 * 'block_buffer' is reserved at build time - so it might not match  */
	assert(sizeof(block_buffer) >= block_size);

	/*
	 * Check the buffer fits inside a single block.
	 * It must not span several blocks
	 */
	assert((offset / block_size) ==
		  ((offset + length - 1) / block_size));

	/* Make a copy of the block from flash to a temporary buffer */
	memcpy(block_buffer, (void *)(fp->block_spec->region_address +
						block_start), block_size);

	/* Calculate the offset of the buffer into the block */
	block_offset = offset % block_size;

	/* update the content of the block buffer */
	memcpy(block_buffer + block_offset, (void *)buffer, length);

	/* Write the block buffer back */
	ret = flash_block_write(fp, block_start,
					(uintptr_t)block_buffer, written);
	if (ret == IO_SUCCESS)
		*written = length;

	return ret;
}