summaryrefslogtreecommitdiff
path: root/basic-serialflash-project/main.c
blob: 8b7feffc9f0f03f88665b8e2788f4554b22439f2 (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
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support 
 * ----------------------------------------------------------------------------
 * Copyright (c) 2008, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */
//------------------------------------------------------------------------------
/// \dir "Basic-serialflash-project"
///
/// !!!Purpose
///
/// The Basic Serialflash project will help new users get familiar with SPI interface 
/// on Atmel's AT91 family of microcontrollers. This project gives you an AT26 
/// serial firmware dataflash programming code so that can help develop your own 
/// SPI devices applications with maximum efficiency.
///
/// You can find following information depends on your needs:
/// - A Spi low level driver performs SPI device Initializes, data transfer and 
/// receive. It can be used by upper SPI driver such as AT26 %dataflash.
/// - A Dataflash driver is based on top of the corresponding Spi driver.
/// It allow user to do operations with %dataflash in a unified way.
///
/// !See also
///    - "spi-flash": Dataflash interface driver.
///
/// !!!Requirements
///
/// This package can be used with all Atmel evaluation kits that have SPI
/// interface and on-board or external Dataflash connected. The package runs at 
/// SRAM or SDRAM, so SDRAM device is needed if you want to run this package in SDRAM.
///
///
/// !!!Description
///
/// The demonstration program tests the dataflash present on the evaluation kit by 
/// erasing and writing each one of its pages.
///
/// !!!Usage
///
/// -# Build the program and download it inside the evaluation board. Please
///    refer to the <a href="http://www.atmel.com/dyn/resources/prod_documents/doc6224.pdf">SAM-BA User Guide</a>,
///    the <a href="http://www.atmel.com/dyn/resources/prod_documents/doc6310.pdf">GNU-Based Software Development</a>
///    application note or to the <a href="ftp://ftp.iar.se/WWWfiles/arm/Guides/EWARM_UserGuide.ENU.pdf">IAR EWARM User Guide</a>,
///    depending on your chosen solution.
/// -# On the computer, open and configure a terminal application
///    (e.g. HyperTerminal on Microsoft Windows) with these settings:
///   - 115200 bauds
///   - 8 bits of data
///   - No parity
///   - 1 stop bit
///   - No flow control
/// -# Start the application.
/// -# Upon startup, the application will output the following lines on the DBGU: 
///    \code
///     -- Basic Serial Firmware Dataflash Project xxx --
///     -- AT91xxxxxx-xx
///     -- Compiled: xxx xx xxxx xx:xx:xx --
///     -I- SPI and At26 initialized 
///    \endcode
/// -# The program will connect to the serial firmware dataflash through the SPI 
///    and start sending commands to it. It will perform the following: 
///    - Read the JEDEC identifier of the device to autodetect it 
///      The next line should indicate if the serial dataflash has been 
///      correctly identified. For example, this is what appears when an 
///      AT26DF321 chip is recognized: 
///    \code
///    -I- AT26DF321 Serial Flash detected 
///    \endcode
///    - Erase the chip 
///    - Check that each page is blank 
///    - Write a "Walking one" pattern on each page: 
///    \code
///    Byte 0 = 00000001
///    Byte 1 = 00000010
///    Byte 2 = 00000100
///    ........ 
///    \endcode
///    - Verify that the pattern has been correctly applied on each page 
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
/// \unit
///
/// !Purpose
///
/// This file contains all the specific code for the basic-serialflash-project.
/// It tests the serial firmware dataflash present on the evaluation kit by 
/// erasing and writing each one of its pages.
/// 
/// !Contents
/// The code can be roughly broken down as follows:
///    - AT26 Dataflash write data function.
///    - AT26 Dataflash read data function.
///    - AT26 Dataflash erase function.
///    - Other AT26 functions (such as AT26_GetStatus())
///    - The main() function, which implements the program behavior.
///       - Initializes an AT26 instance and configures SPI chip select pin.
///       - Config SPI Interrupt Service Routine.
///       - Identifier the AT26 device connected to the evaluation kit.
///       - Test the dataflash by erasing and writing each one of its pages.
/// 
/// !See also
///    - "spi-flash": SPI Dataflash interface driver.
///
/// Please refer to the list of functions in the #Overview# tab of this unit
/// for more detailed information.
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//         Headers
//------------------------------------------------------------------------------

#include <board.h>
#include <dbgu/dbgu.h>
#include <pio/pio.h>
#include <aic/aic.h>
#include <utility/assert.h>
#include <utility/trace.h>
#include <utility/math.h>
#include <memories/spi-flash/at26.h>

#include <string.h>

//------------------------------------------------------------------------------
//         Internal definitions
//------------------------------------------------------------------------------

/// Maximum device page size in bytes.
#define MAXPAGESIZE     256

#if defined(BOARD_AT45_A_SPI_BASE)

/// Address of the SPI peripheral connected to the AT26.
#define SPI_BASE        BOARD_AT45_A_SPI_BASE
/// Peripheral identifier of the SPI connected to the AT26.
#define SPI_ID          BOARD_AT45_A_SPI_ID
/// Chip select value used to select the AT26 chip.
#define SPI_CS          BOARD_AT45_A_NPCS
/// SPI peripheral pins to configure to access the serial flash.
#define SPI_PINS        BOARD_AT45_A_SPI_PINS, BOARD_AT45_A_NPCS_PIN

#elif defined(AT91C_BASE_SPI0)

/// Address of the SPI peripheral connected to the AT26.
#define SPI_BASE        AT91C_BASE_SPI0
/// Peripheral identifier of the SPI connected to the AT26.
#define SPI_ID          AT91C_ID_SPI0
/// Chip select value used to select the AT26 chip.
#define SPI_CS          0
/// SPI peripheral pins to configure to access the serial flash.
#define SPI_PINS        PINS_SPI0, PIN_SPI0_NPCS0

#else

/// Address of the SPI peripheral connected to the AT26.
#define SPI_BASE        AT91C_BASE_SPI
/// Peripheral identifier of the SPI connected to the AT26.
#define SPI_ID          AT91C_ID_SPI
/// Chip select value used to select the AT26 chip.
#define SPI_CS          0
/// SPI peripheral pins to configure to access the serial flash.
#define SPI_PINS        PINS_SPI, PIN_SPI_NPCS0

#endif //#if defined(AT91C_BASE_SPI0)

//------------------------------------------------------------------------------
//         Internal variables
//------------------------------------------------------------------------------

/// SPI driver instance.
static Spid spid;

/// Serial flash driver instance.
static At26 at26;

#define PIN_SPI_nWP0	{1 << 15, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_OUTPUT_1, PIO_DEFAULT}

/// Pins to configure for the application.
static Pin pins[] = {SPI_PINS, PIN_SPI_nWP0};

//------------------------------------------------------------------------------
//         Internal functions
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
/// Interrupt service routine for the SPI peripheral. Forwards the interrupt
/// to the SPI driver.
//------------------------------------------------------------------------------
static void ISR_Spi(void)
{
    SPID_Handler(&spid);
}

//------------------------------------------------------------------------------
/// Reads and returns the status register of the serial flash.
/// \param pAt26  Pointer to an AT26 driver instance.
//------------------------------------------------------------------------------
static unsigned char AT26_ReadStatus(At26 *pAt26)
{
    unsigned char error, status;

    SANITY_CHECK(pAt26);

    // Issue a status read command
    error = AT26_SendCommand(pAt26, AT26_READ_STATUS, 1, &status, 1, 0, 0, 0);
    ASSERT(!error, "-F- AT26_GetStatus: Failed to issue command.\n\r");

    // Wait for transfer to finish
    while (AT26_IsBusy(pAt26));

    return status;
}

//------------------------------------------------------------------------------
/// Writes the given value in the status register of the serial flash device.
/// \param pAt26  Pointer to an AT26 driver instance.
/// \param status  Status to write.
//------------------------------------------------------------------------------
static void AT26_WriteStatus(At26 *pAt26, unsigned char status)
{
    unsigned char error;

    SANITY_CHECK(pAt26);

    // Issue a write status command
    error = AT26_SendCommand(pAt26, AT26_WRITE_STATUS, 1, &status, 1, 0, 0, 0);
    ASSERT(!error, "-F- AT26_WriteStatus: Failed to issue command.\n\r");
    while (AT26_IsBusy(pAt26));
}

//------------------------------------------------------------------------------
/// Waits for the serial flash device to become ready to accept new commands.
/// \param pAt26  Pointer to an AT26 driver instance.
//------------------------------------------------------------------------------
static void AT26_WaitReady(At26 *pAt26)
{
    unsigned char ready = 0;

    SANITY_CHECK(pAt26);

    // Read status register and check busy bit
    while (!ready) {

        ready = ((AT26_ReadStatus(pAt26) & AT26_STATUS_RDYBSY) == AT26_STATUS_RDYBSY_READY);
    }
}

//------------------------------------------------------------------------------
/// Reads and returns the serial flash device ID.
/// \param pAt26  Pointer to an AT26 driver instance.
//------------------------------------------------------------------------------
static unsigned int AT26_ReadJedecId(At26 *pAt26)
{
    unsigned char error;
    unsigned int id = 0;

    SANITY_CHECK(pAt26);
 
    // Issue a read ID command
    error = AT26_SendCommand(pAt26, AT26_READ_JEDEC_ID, 1,
                             (unsigned char *) &id, 3, 0, 0, 0);
    ASSERT(!error, "-F- AT26_GetJedecId: Could not issue command.\n\r");

     // Wait for transfer to finish
    while (AT26_IsBusy(pAt26));

    return id;
}

//------------------------------------------------------------------------------
/// Enables critical writes operation on a serial flash device, such as sector
/// protection, status register, etc.
/// \para pAt26  Pointer to an AT26 driver instance.
//------------------------------------------------------------------------------
static void AT26_EnableWrite(At26 *pAt26)
{
    unsigned char error;

    SANITY_CHECK(pAt26);

    // Issue a write enable command
    error = AT26_SendCommand(pAt26, AT26_WRITE_ENABLE, 1, 0, 0, 0, 0, 0);
    ASSERT(!error, "-F- AT26_EnableWrite: Could not issue command.\n\r");

    // Wait for end of transfer
    while (AT26_IsBusy(pAt26));
}

//------------------------------------------------------------------------------
/// Unprotects the contents of the serial flash device.
/// Returns 0 if the device has been unprotected; otherwise returns
/// SF_PROTECTED.
/// \param pAt26  Pointer to an AT26 driver instance.
//------------------------------------------------------------------------------
static unsigned char AT26_Unprotect(At26 *pAt26)
{
    unsigned char status;

    SANITY_CHECK(pAt26);

    // Get the status register value to check the current protection
    status = AT26_ReadStatus(pAt26);
    if ((status & AT26_STATUS_SWP) == AT26_STATUS_SWP_PROTNONE) {

        // Protection already disabled
        return 0;
    }
    
    // Check if sector protection registers are locked
    if ((status & AT26_STATUS_SPRL) == AT26_STATUS_SPRL_LOCKED) {

        // Unprotect sector protection registers by writing the status reg.
        AT26_EnableWrite(pAt26);
        AT26_WriteStatus(pAt26, 0);
    }
    
    // Perform a global unprotect command
      AT26_EnableWrite(pAt26);
    AT26_WriteStatus(pAt26, 0);
    
    // Check the new status
    if ((status & (AT26_STATUS_SPRL | AT26_STATUS_SWP)) != 0) {

        return AT26_ERROR_PROTECTED;
    }
    else {

        return 0;
    }
}

//------------------------------------------------------------------------------
/// Erases all the content of the memory chip.
/// \param pAt26  Pointer to an AT26 driver instance.
//------------------------------------------------------------------------------
static unsigned char AT26_EraseChip(At26 *pAt26)
{
    unsigned char status;
    unsigned char error;

    SANITY_CHECK(pAt26);

    // Check that the flash is ready an unprotected
    status = AT26_ReadStatus(pAt26);
    if ((status & AT26_STATUS_SWP) != AT26_STATUS_SWP_PROTNONE) {

        TRACE_WARNING("AT26_EraseBlock: Device is protected.\n\r");
        return AT26_ERROR_PROTECTED;
    }
    
    // Enable critical write operation
      AT26_EnableWrite(pAt26);
    
    // Erase the chip
    error = AT26_SendCommand(pAt26, AT26_CHIP_ERASE_2, 1, 0, 0, 0, 0, 0);
    ASSERT(!error, "-F- AT26_ChipErase: Could not issue command.\n\r");
     while (AT26_IsBusy(pAt26));    
    AT26_WaitReady(pAt26);

    return 0;
}

//------------------------------------------------------------------------------
/// Erases the specified 4KB block of the serial firmware dataflash.
/// Returns 0 if successful; otherwise returns AT26_ERROR_PROTECTED if the
/// device is protected or AT26_ERROR_BUSY if it is busy executing a command.
/// \param pAt26  Pointer to an AT26 driver instance.
/// \param address  Address of the block to erase.
//------------------------------------------------------------------------------
static unsigned char AT26_EraseBlock(At26 *pAt26, unsigned int address)
{
    unsigned char status;
    unsigned char error;

    SANITY_CHECK(pAt26);
 
    // Check that the flash is ready an unprotected
    status = AT26_ReadStatus(pAt26);
    if ((status & AT26_STATUS_RDYBSY) != AT26_STATUS_RDYBSY_BUSY) {

        TRACE_WARNING("AT26_EraseBlock: Device is not ready.\n\r");
        return AT26_ERROR_BUSY;
    }
    else if ((status & AT26_STATUS_SWP) != AT26_STATUS_SWP_PROTNONE) {

        TRACE_WARNING("AT26_EraseBlock: Device is protected.\n\r");
        return AT26_ERROR_PROTECTED;
    }

    // Enable critical write operation
      AT26_EnableWrite(pAt26);

    // Start the block erase command
    error = AT26_SendCommand(pAt26, AT26_BLOCK_ERASE_4K, 4, 0, 0, address, 0, 0);
    ASSERT(!error, "-F- AT26_EraseBlock: Could not issue command.\n\r");
    while (AT26_IsBusy(pAt26));
    AT26_WaitReady(pAt26);

    return 0;
}

//------------------------------------------------------------------------------
/// Writes data at the specified address on the serial firmware dataflash. The
/// page(s) to program must have been erased prior to writing. This function
/// handles page boundary crossing automatically.
/// Returns 0 if successful; otherwise, returns AT26_ERROR_PROGRAM is there has
/// been an error during the data programming.
/// \param pAt26  Pointer to an AT26 driver instance.
/// \param pData  Data buffer.
/// \param size  Number of bytes in buffer.
/// \param address  Write address.
//------------------------------------------------------------------------------
static unsigned char AT26_Write(
    At26 *pAt26,
    unsigned char *pData,
    unsigned int size,
    unsigned int address)
{
    unsigned int pageSize;
    unsigned int writeSize;
    unsigned char error;
    unsigned char status;

    SANITY_CHECK(pAt26);
    SANITY_CHECK(pData);

    // Retrieve device page size
    pageSize = AT26_PageSize(&at26);

    // Program one page after the other
    while (size > 0) {

        // Compute number of bytes to program in page
        writeSize = min(size, pageSize - (address % pageSize));
                
        // Enable critical write operation
        AT26_EnableWrite(pAt26);
     
         // Program page
          error = AT26_SendCommand(pAt26, AT26_BYTE_PAGE_PROGRAM, 4,
                           pData, writeSize, address, 0, 0);
        ASSERT(!error, "-F- AT26_WritePage: Failed to issue command.\n\r");
        while (AT26_IsBusy(pAt26));
        AT26_WaitReady(pAt26);

        // Make sure that write was without error
        status = AT26_ReadStatus(pAt26);
        if ((status & AT26_STATUS_EPE) == AT26_STATUS_EPE_ERROR) {

            return AT26_ERROR_PROGRAM;
        }
        
        size -= writeSize;
        address += writeSize;
    }

    return 0;
}

//------------------------------------------------------------------------------
/// Reads data from the specified address on the serial flash.
/// \param pAt26  Pointer to an AT26 driver instance.
/// \param pData  Data buffer.
/// \param size  Number of bytes to read.
/// \param address  Read address.
//------------------------------------------------------------------------------
static void AT26_Read(
    At26 *pAt26,
    unsigned char *pData,
    unsigned int size,
    unsigned int address)
{
    unsigned char error;
    
     // Start a read operation
      error = AT26_SendCommand(pAt26, AT26_READ_ARRAY_LF, 4, pData, size, address, 0, 0);
    ASSERT(!error, "-F- AT26_Read: Could not issue command.\n\r");
    while (AT26_IsBusy(pAt26));
}

//------------------------------------------------------------------------------
//         External functions
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
/// Initializes the serial flash and performs several tests on it.
//------------------------------------------------------------------------------
int main(void)
{
    unsigned int jedecId;
    unsigned int numPages;
    unsigned int pageSize;
    unsigned int i, j;
    unsigned int address;
    unsigned char pBuffer[MAXPAGESIZE];

    // Configure the DBGU
    TRACE_CONFIGURE(DBGU_STANDARD, 115200, BOARD_MCK);
    printf("-- Basic Serial Firmware Dataflash Project %s --\n\r", SOFTPACK_VERSION);
    printf("-- %s\n\r", BOARD_NAME);
    printf("-- Compiled: %s %s --\n\r", __DATE__, __TIME__);

    // Initialize the SPI and serial flash
    PIO_Configure(pins, PIO_LISTSIZE(pins));
    AIC_ConfigureIT(SPI_ID, 0, ISR_Spi);
    SPID_Configure(&spid, SPI_BASE, SPI_ID);
    AT26_Configure(&at26, &spid, SPI_CS);
    AIC_EnableIT(SPI_ID);
    TRACE_INFO("SPI and AT26 drivers initialized\n\r");
    
    // Read the JEDEC ID of the device to identify it
    jedecId = AT26_ReadJedecId(&at26);
    if (AT26_FindDevice(&at26, jedecId)) {

        TRACE_INFO("%s serial flash detected\n\r", AT26_Name(&at26));
    }
    else {

        TRACE_ERROR("Failed to recognize the device (JEDEC ID is 0x%08X).\n\r", jedecId);
        return 1;
    }
    ASSERT(MAXPAGESIZE >= AT26_PageSize(&at26), "-F- MAXPAGESIZE too small\n\r");

    // Get device parameters
    numPages = AT26_PageNumber(&at26);
    pageSize = AT26_PageSize(&at26);

    // Unprotected the flash
    AT26_Unprotect(&at26);
    TRACE_INFO("Flash unprotected\n\r");

    // Erase the chip
    TRACE_INFO("Chip is being erased...\n\r");
    AT26_EraseChip(&at26);
    TRACE_INFO("Checking erase ...\n\r");

    // Check that the chip has been erased correctly
    address = 0;
    for (i=0; i < numPages; i++) {

        TRACE_INFO("Checking page #%u\r", i);
        AT26_Read(&at26, pBuffer, pageSize, address);
        for (j=0; j < pageSize; j++) {

            if (pBuffer[j] != 0xFF) {

                TRACE_ERROR("Failed erase on page%u:byte%u\n\r", i, j);
                TRACE_ERROR(
                          "-E- Expected 0xFF, read 0x%02X\n\r",
                          pBuffer[j]);
                return 2;
            }
        }

        address += pageSize;
    }
    TRACE_INFO("Erase successful.\n\r");

    // Program a "walking one" pattern on each page
    TRACE_INFO("Programming a walking 1 on all pages ...\n\r");
    address = 0;
    for (i=0; i < numPages; i++) {

        TRACE_INFO("Programming page #%u\r", i);

        // Fill buffer
        for (j=0; j < pageSize; j++) {

            pBuffer[j] = 1 << (j & 0x7);
        }

        // Write buffer
        AT26_Write(&at26, pBuffer, pageSize, address);

        // Read page back and check result
        memset(pBuffer, 0, pageSize);
        AT26_Read(&at26, pBuffer, pageSize, address);

        for (j=0; j < pageSize; j++) {

            if (pBuffer[j] != (1 << (j & 0x7))) {

                TRACE_ERROR("Failed program on page%u:byte%u\n\r", i, j);
                TRACE_ERROR(
                          "-E- Expected 0x%02X, read 0x%02X\n\r",
                          1 << (j & 0x7),
                          pBuffer[j]);
                return 3;
            }
        }

        address += pageSize;
    }

    TRACE_INFO("Walking 1 test successful.\n\r");

    return 0;
}

personal git repositories of Harald Welte. Your mileage may vary