aboutsummaryrefslogblamecommitdiff
path: root/projects/board-test/short-test.c
blob: 27b8e7a9017dd2459d69cd18d5c220df2d31fedd (plain) (tree)
































































                                                                                
                                             






































                                                                                                                      
                             

                                    

                                    
 
                              

                             

                                 
 
                           





















































                                                                                              

                               
 
                                

                                     
                                

                                 
                                 

                                            
                               
















                                                                                                              
                             

   
/*
 * Test code that just sends the letters 'a' to 'z' over and
 * over again using USART2.
 *
 * Toggles the BLUE LED slowly and the RED LED for every
 * character sent.
 */
#include "stm32f4xx_hal.h"
#include "stm-init.h"
#include "stm-led.h"
#include "stm-uart.h"

void test_for_shorts(char port, GPIO_TypeDef* GPIOx, uint16_t GPIO_Test_Pins);

//------------------------------------------------------------------------------
// Defines
//------------------------------------------------------------------------------


//------------------------------------------------------------------------------
// Macros
//------------------------------------------------------------------------------

/* These are all the pins used by the FMC interface */
#define GPIOB_PINS  (GPIO_PIN_7)

#define GPIOD_PINS  (GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11	\
		     |GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15	\
		     |GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_3|GPIO_PIN_4	\
		     |GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7)

#define GPIOE_PINS  (GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_7	\
		     |GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11	\
		     |GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15)

#define GPIOF_PINS  (GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3	\
		     |GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_12|GPIO_PIN_13	\
		     |GPIO_PIN_14|GPIO_PIN_15)

#define GPIOG_PINS  (GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3	\
		     |GPIO_PIN_4|GPIO_PIN_5)

#define GPIOH_PINS  (GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11	\
		     |GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15)

#define GPIOI_PINS  (GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_0|GPIO_PIN_1	\
		     |GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_6|GPIO_PIN_7)

int
main()
{
  stm_init();

  // enable gpio clocks
  __GPIOA_CLK_ENABLE();
  __GPIOB_CLK_ENABLE();
  __GPIOD_CLK_ENABLE();
  __GPIOE_CLK_ENABLE();
  __GPIOF_CLK_ENABLE();
  __GPIOG_CLK_ENABLE();
  __GPIOH_CLK_ENABLE();
  __GPIOI_CLK_ENABLE();

  while (1) {
    HAL_GPIO_TogglePin(LED_PORT, LED_GREEN);
    uart_send_string("\r\n\r\n\r\n\r\n\r\n");

    test_for_shorts('B', GPIOB, GPIOB_PINS);
    test_for_shorts('D', GPIOD, GPIOD_PINS);
    test_for_shorts('E', GPIOE, GPIOE_PINS);
    test_for_shorts('F', GPIOF, GPIOF_PINS);
    test_for_shorts('G', GPIOG, GPIOG_PINS);
    test_for_shorts('H', GPIOH, GPIOH_PINS);
    test_for_shorts('I', GPIOI, GPIOI_PINS);
    led_toggle(LED_BLUE);
    HAL_Delay(2000);
  }
}

void configure_all_as_input(GPIO_TypeDef* GPIOx, uint16_t GPIO_Test_Pins)
{
  GPIO_InitTypeDef GPIO_InitStruct;

  /* Configure all pins as input. XXX do all pins (0xffff) instead of just GPIO_Test_Pins? */
  GPIO_InitStruct.Pin = GPIO_Test_Pins;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
  HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}

uint8_t check_no_input(char port, GPIO_TypeDef* GPIOx, uint16_t GPIO_Test_Pins, char wrote_port, uint16_t wrote_value)
{
  uint16_t read;

  /* Read all pins from port at once. XXX check all pins, not just GPIO_Test_Pins? */
  read = (GPIOx->IDR & GPIO_Test_Pins);

  if (! read) {
    /* No unexpected pins read as HIGH */
    return 0;
  }

  led_on(LED_RED);

  uart_send_string("Wrote ");
  uart_send_binary(wrote_value, 16);

  uart_send_string(" to port GPIO");
  uart_send_char(wrote_port);

  uart_send_string(", read ");
  uart_send_binary(read, 16);

  uart_send_string(" from GPIO");
  uart_send_char(port);

  uart_send_string("\r\n");

  return 1;
}

void test_for_shorts(char port, GPIO_TypeDef* GPIOx, uint16_t GPIO_Test_Pins)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  uint16_t i, fail = 0, Test_Pin, read;

  configure_all_as_input(GPIOB, GPIOB_PINS);
  configure_all_as_input(GPIOD, GPIOD_PINS);
  configure_all_as_input(GPIOE, GPIOE_PINS);
  configure_all_as_input(GPIOF, GPIOF_PINS);
  configure_all_as_input(GPIOG, GPIOG_PINS);
  configure_all_as_input(GPIOH, GPIOH_PINS);
  configure_all_as_input(GPIOI, GPIOI_PINS);

  check_no_input('B', GPIOB, GPIOB_PINS, 'x', 0);
  check_no_input('D', GPIOD, GPIOD_PINS, 'x', 0);
  check_no_input('E', GPIOE, GPIOE_PINS, 'x', 0);
  check_no_input('F', GPIOF, GPIOF_PINS, 'x', 0);
  check_no_input('G', GPIOG, GPIOG_PINS, 'x', 0);
  check_no_input('H', GPIOH, GPIOH_PINS, 'x', 0);
  check_no_input('I', GPIOI, GPIOI_PINS, 'x', 0);

  for (i = 0; i < 31; i++) {
    Test_Pin = 1 << i;
    if (! (GPIO_Test_Pins & Test_Pin)) continue;

    configure_all_as_input(GPIOx, GPIO_Test_Pins);

    /* Change one pin to output */
    GPIO_InitStruct.Pin = Test_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
    HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);

    HAL_GPIO_WritePin(GPIOx, Test_Pin, GPIO_PIN_SET);

    /* Slight delay after setting the output pin. Without this, the Test_Pin
       bit might read as zero, as it is only sampled once every AHB1 clock cycle.
       Reference manual DM00031020 section 8.3.1.
    */
    HAL_Delay(1);

    /* Read all input GPIOs from port at once. XXX check all pins, not just GPIO_Test_Pins? */
    read = GPIOx->IDR & GPIO_Test_Pins;

    if (read == Test_Pin) {
      /* No unexpected pins read as HIGH */
      led_toggle(LED_GREEN);
    } else {
      led_on(LED_RED);
      uart_send_string("GPIO");
      uart_send_char(port);

      uart_send_string(" exp ");
      uart_send_binary(Test_Pin, 16);

      uart_send_string(" got ");
      uart_send_binary(read, 16);

      uart_send_string(" diff ");
      uart_send_binary(read ^ Test_Pin, 16);

      uart_send_string("\r\n");

      fail++;
    }

    /* Check there is no input on any of the other GPIO ports (adjacent pins might live on different ports) */
    if (port != 'B') fail += check_no_input('B', GPIOB, GPIOB_PINS, port, Test_Pin);
    if (port != 'D') fail += check_no_input('D', GPIOD, GPIOD_PINS, port, Test_Pin);
    if (port != 'E') fail += check_no_input('E', GPIOE, GPIOE_PINS, port, Test_Pin);
    if (port != 'F') fail += check_no_input('F', GPIOF, GPIOF_PINS, port, Test_Pin);
    if (port != 'G') fail += check_no_input('G', GPIOG, GPIOG_PINS, port, Test_Pin);
    if (port != 'H') fail += check_no_input('H', GPIOH, GPIOH_PINS, port, Test_Pin);
    if (port != 'I') fail += check_no_input('I', GPIOI, GPIOI_PINS, port, Test_Pin);

    HAL_GPIO_WritePin(GPIOx, Test_Pin, GPIO_PIN_RESET);
  }

  if (fail) {
    uart_send_string("\r\n");
  }
}
ng-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/*
 * spiflash_n25q128.c
 * ------------------
 * Functions and defines for accessing SPI flash with part number n25q128.
 *
 * The Alpha board has two of these SPI flash memorys, the FPGA config memory
 * and the keystore memory.
 *
 * Copyright (c) 2016-2017, NORDUnet A/S 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 following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of the NORDUnet nor the names of its contributors may
 *   be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS 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.
 */

#include "spiflash_n25q128.h"

#define N25Q128_NUM_BYTES	(N25Q128_PAGE_SIZE * N25Q128_NUM_PAGES)

#if N25Q128_SECTOR_SIZE    * N25Q128_NUM_SECTORS    != N25Q128_NUM_BYTES || \
    N25Q128_SUBSECTOR_SIZE * N25Q128_NUM_SUBSECTORS != N25Q128_NUM_BYTES
#error Inconsistent definitions for pages / sectors / subsectors
#endif


static inline void _n25q128_select(struct spiflash_ctx *ctx)
{
    HAL_GPIO_WritePin(ctx->cs_n_port, ctx->cs_n_pin, GPIO_PIN_RESET);
}

static inline void _n25q128_deselect(struct spiflash_ctx *ctx)
{
    HAL_GPIO_WritePin(ctx->cs_n_port, ctx->cs_n_pin, GPIO_PIN_SET);
}

/* Read a bit from the status register. */
static inline int _n25q128_get_status_bit(struct spiflash_ctx *ctx, unsigned bitnum)
{
    // tx, rx buffers
    uint8_t spi_tx[2];
    uint8_t spi_rx[2];

    // result
    HAL_StatusTypeDef ok;

    //assert(bitnum < sizeof(uint8_t));

    // send READ STATUS command
    spi_tx[0] = N25Q128_COMMAND_READ_STATUS;

    // send command, read response, deselect
    _n25q128_select(ctx);
    ok = HAL_SPI_TransmitReceive(ctx->hspi, spi_tx, spi_rx, 2, N25Q128_SPI_TIMEOUT);
    _n25q128_deselect(ctx);

    // check
    if (ok != HAL_OK) return -1;

    // done
    return ((spi_rx[1] >> bitnum) & 1);
}

/* Read the Write Enable Latch bit in the status register. */
static inline int _n25q128_get_wel_flag(struct spiflash_ctx *ctx)
{
    return _n25q128_get_status_bit(ctx, 1);
}

/* Read the Write In Progress bit in the status register. */
static inline int _n25q128_get_wip_flag(struct spiflash_ctx *ctx)
{
    return _n25q128_get_status_bit(ctx, 0);
}

/* Wait until the flash memory is done writing */
static int _n25q128_wait_while_wip(struct spiflash_ctx *ctx, uint32_t timeout)
{
    uint32_t tick_end = HAL_GetTick() + timeout;
    int i;

    do {
	i = _n25q128_get_wip_flag(ctx);
	if (i < 0) return 0;
	if (! i) return 1;
    } while (HAL_GetTick() < tick_end);

    return 0;
}

/* Send the Write Enable command */
static int _n25q128_write_enable(struct spiflash_ctx *ctx)
{
    // tx buffer
    uint8_t spi_tx[1];

    // result
    HAL_StatusTypeDef ok;

    // enable writing
    spi_tx[0] = N25Q128_COMMAND_WRITE_ENABLE;

    // activate, send command, deselect
    _n25q128_select(ctx);
    ok = HAL_SPI_Transmit(ctx->hspi, spi_tx, 1, N25Q128_SPI_TIMEOUT);
    _n25q128_deselect(ctx);

    // check
    if (ok != HAL_OK) return -1;

    // make sure, that write enable did the job
    return _n25q128_get_wel_flag(ctx);
}

int n25q128_check_id(struct spiflash_ctx *ctx)
{
    // tx, rx buffers
    uint8_t spi_tx[4];
    uint8_t spi_rx[4];

    // result
    HAL_StatusTypeDef ok;

    // send READ ID command
    spi_tx[0] = N25Q128_COMMAND_READ_ID;

    // select, send command & read response, deselect
    _n25q128_select(ctx);
    ok = HAL_SPI_TransmitReceive(ctx->hspi, spi_tx, spi_rx, 4, N25Q128_SPI_TIMEOUT);
    _n25q128_deselect(ctx);

    // check
    if (ok != HAL_OK) return 0;

    // parse response (note, that the very first byte was received during the
    // transfer of the command byte, so it contains garbage and should
    // be ignored here)
    if (spi_rx[1] != N25Q128_ID_MANUFACTURER) return 0;
    if (spi_rx[2] != N25Q128_ID_DEVICE_TYPE) return 0;
    if (spi_rx[3] != N25Q128_ID_DEVICE_CAPACITY) return 0;

    // done
    return 1;
}


int n25q128_write_page(struct spiflash_ctx *ctx, uint32_t page_offset, const uint8_t *page_buffer)
{
    // tx buffer
    uint8_t spi_tx[4];

    // result
    HAL_StatusTypeDef ok;

    // check offset
    if (page_offset >= N25Q128_NUM_PAGES) return 0;

    // enable writing
    if (_n25q128_write_enable(ctx) != 1) return 0;

    // calculate byte address
    uint32_t byte_offset = page_offset * N25Q128_PAGE_SIZE;

    // prepare PROGRAM PAGE command
    spi_tx[0] = N25Q128_COMMAND_PAGE_PROGRAM;
    spi_tx[1] = (uint8_t)(byte_offset >> 16);
    spi_tx[2] = (uint8_t)(byte_offset >>  8);
    spi_tx[3] = (uint8_t)(byte_offset >>  0);

    // activate, send command
    _n25q128_select(ctx);
    ok = HAL_SPI_Transmit(ctx->hspi, spi_tx, 4, N25Q128_SPI_TIMEOUT);

    // check
    if (ok != HAL_OK) {
	_n25q128_deselect(ctx);
	return 0;
    }

    // send data, deselect
    ok = HAL_SPI_Transmit(ctx->hspi, (uint8_t *) page_buffer, N25Q128_PAGE_SIZE, N25Q128_SPI_TIMEOUT);
    _n25q128_deselect(ctx);

    // check
    if (ok != HAL_OK) return 0;

    // wait until write finishes
    if (! _n25q128_wait_while_wip(ctx, 1000)) return 0;

    // done
    return 1;
}


static int n25q128_erase_something(struct spiflash_ctx *ctx, uint8_t command, uint32_t byte_offset)
{
    // check offset
    if (byte_offset >= N25Q128_NUM_BYTES) return 0;

    // tx buffer
    uint8_t spi_tx[4];

    // result
    HAL_StatusTypeDef ok;

    // enable writing
    if (_n25q128_write_enable(ctx) != 1) return 0;

    // send command (ERASE SECTOR or ERASE SUBSECTOR)
    spi_tx[0] = command;
    spi_tx[1] = (uint8_t)(byte_offset >> 16);
    spi_tx[2] = (uint8_t)(byte_offset >>  8);
    spi_tx[3] = (uint8_t)(byte_offset >>  0);

    // activate, send command, deselect
    _n25q128_select(ctx);
    ok = HAL_SPI_Transmit(ctx->hspi, spi_tx, 4, N25Q128_SPI_TIMEOUT);
    _n25q128_deselect(ctx);

    // check
    if (ok != HAL_OK) return 0;

    // wait for erase to finish

    if (! _n25q128_wait_while_wip(ctx, 1000)) return 0;

    // done
    return 1;
}


int n25q128_erase_sector(struct spiflash_ctx *ctx, uint32_t sector_offset)
{
    return n25q128_erase_something(ctx, N25Q128_COMMAND_ERASE_SECTOR,
				   sector_offset * N25Q128_SECTOR_SIZE);
}


int n25q128_erase_subsector(struct spiflash_ctx *ctx, uint32_t subsector_offset)
{
    return n25q128_erase_something(ctx, N25Q128_COMMAND_ERASE_SUBSECTOR,
				   subsector_offset * N25Q128_SUBSECTOR_SIZE);
}


int n25q128_erase_bulk(struct spiflash_ctx *ctx)
{
    // tx buffer
    uint8_t spi_tx[1];

    // result
    HAL_StatusTypeDef ok;

    // enable writing
    if (_n25q128_write_enable(ctx) != 1) return 0;

    // send command
    spi_tx[0] = N25Q128_COMMAND_ERASE_BULK;

    // activate, send command, deselect
    _n25q128_select(ctx);
    ok = HAL_SPI_Transmit(ctx->hspi, spi_tx, 1, N25Q128_SPI_TIMEOUT);
    _n25q128_deselect(ctx);

    // check
    if (ok != HAL_OK) return 0;

    // wait for erase to finish

    if (! _n25q128_wait_while_wip(ctx, 60000)) return 0;

    // done
    return 1;
}


/* This function writes of a number of pages to the flash memory.
 * The caller is responsible for ensuring that the pages have been erased.
 */
int n25q128_write_data(struct spiflash_ctx *ctx, uint32_t offset, const uint8_t *buf, const uint32_t len)
{
    uint32_t page;

    /*
     * The data sheet says:
     * If the bits of the least significant address, which is the starting
     * address, are not all zero, all data transmitted beyond the end of the
     * current page is programmed from the starting address of the same page.
     * If the number of bytes sent to the device exceed the maximum page size,
     * previously latched data is discarded and only the last maximum
     * page-size number of data bytes are guaranteed to be programmed
     * correctly within the same page. If the number of bytes sent to the
     * device is less than the maximum page size, they are correctly
     * programmed at the specified addresses without any effect on the other
     * bytes of the same page.
     *
     * This is sufficiently confusing that it makes sense to constrain the API
     * to page alignment in address and length, because that's how we're using
     * it anyway.
     */

    if ((offset % N25Q128_PAGE_SIZE) != 0) return -1;
    if ((len % N25Q128_PAGE_SIZE) != 0) return -2;

    for (page = 0; page < len / N25Q128_PAGE_SIZE; page++) {
	if (! n25q128_write_page(ctx, offset / N25Q128_PAGE_SIZE, buf)) {
	    return -6;
	}
	buf += N25Q128_PAGE_SIZE;
	offset += N25Q128_PAGE_SIZE;
    }

    return 1;
}

/* This function reads zero or more pages from the SPI flash. */
int n25q128_read_data(struct spiflash_ctx *ctx, uint32_t offset, uint8_t *buf, const uint32_t len)
{
    // tx buffer
    uint8_t spi_tx[4];

    // result
    HAL_StatusTypeDef ok;

    /*
     * The data sheet says:
     * The addressed byte can be at any location, and the address
     * automatically increments to the next address after each byte of data is
     * shifted out; therefore, the entire memory can be read with a single
     * command. The operation is terminated by driving S# [chip select] HIGH
     * at any time during data output.
     *
     * We're only going to call this with page-aligned address and length, but
     * we're not going to enforce it here.
     */

    // avoid overflow
    if (offset + len > N25Q128_NUM_BYTES) return -3;

    // prepare READ command
    spi_tx[0] = N25Q128_COMMAND_READ;
    spi_tx[1] = (uint8_t)(offset >> 16);
    spi_tx[2] = (uint8_t)(offset >>  8);
    spi_tx[3] = (uint8_t)(offset >>  0);

    // activate, send command
    _n25q128_select(ctx);
    ok = HAL_SPI_Transmit(ctx->hspi, spi_tx, 4, N25Q128_SPI_TIMEOUT);

    // check
    if (ok != HAL_OK) {
	_n25q128_deselect(ctx);
	return 0;
    }

    // read response, deselect
    ok = HAL_SPI_Receive(ctx->hspi, buf, len, N25Q128_SPI_TIMEOUT);
    _n25q128_deselect(ctx);

    // check
    if (ok != HAL_OK) return 0;

    // done
    return 1;
}