aboutsummaryrefslogblamecommitdiff
path: root/stm-ice40mkm.c
blob: 5a03525f03ccc3b516bbe96e72d3bd48b4a86f57 (plain) (tree)








































































































































































































                                                                                                             
/*
 * stm-ice40mkm.c
 * --------------
 * Functions for configuring the Lattice iCE40-based master key memory.
 *
 * Copyright 2021 The Commons Conservancy Cryptech Project
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * 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 copyright holder 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 "stm-ice40mkm.h"
#include "stm-init.h"
#include "stm-fpgacfg.h"

/* handle to SPI3 */
static SPI_HandleTypeDef hspi_ice40mkm;

/*
 * initialize SPI3 peripheral and assert reset signal
 */
void ice40mkm_init(void)
{
	/* initialize GPIO pins */
	ICE40MKM_GPIO_INIT();

	/*
	 * datasheet says up to 45 MHz is fine, but that's apparently for master mode
	 * slave speed not explicitly defined, but TN-02001-3.2 says only up to 25 MHz
	 * 4x prescaler is used for 22.5 MHz (2x works fine, but might be out of specs)
	 */

	/* SPI3 (iCE40 slave SPI configuration interface) init function */
	hspi_ice40mkm.Instance = SPI3;
	hspi_ice40mkm.Init.Mode = SPI_MODE_MASTER;
	hspi_ice40mkm.Init.Direction = SPI_DIRECTION_2LINES;
	hspi_ice40mkm.Init.DataSize = SPI_DATASIZE_8BIT;
	hspi_ice40mkm.Init.CLKPolarity = SPI_POLARITY_HIGH;
	hspi_ice40mkm.Init.CLKPhase = SPI_PHASE_2EDGE;
	hspi_ice40mkm.Init.NSS = SPI_NSS_SOFT;
	hspi_ice40mkm.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
	hspi_ice40mkm.Init.FirstBit = SPI_FIRSTBIT_MSB;
	hspi_ice40mkm.Init.TIMode = SPI_TIMODE_DISABLE;
	hspi_ice40mkm.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
	hspi_ice40mkm.Init.CRCPolynomial = 10;
	HAL_SPI_Init(&hspi_ice40mkm);

	/* assert iCE40 reset */
	_ice40mkm_reset_assert();
}

/*
 * force iCE40 into slave SPI mode
 * read bitstream from the end of the main FPGA's config memory
 * send the bitstream to iCE40
 * check, that "config done" went high
 *
 * return codes:
 * 0 = success
 * 1 = serial transfer timeout
 * 2 = bad magic marker (image missing?)
 * 3 = config done did not go high (crc error, etc)
 */
int ice40mkm_configure(void)
{
	HAL_StatusTypeDef prom_ok;

	size_t c;
	uint8_t prom_buf[N25Q128_PAGE_SIZE];
	uint8_t prom_marker[] = ICE40_MAGIC_MARKER;
	int prom_marker_bad = 0, cdone_state;

	uint32_t prom_addr, prom_offset = N25Q128_SECTOR_SIZE *
			(N25Q128_NUM_SECTORS - ICE40_BITSTREAM_SECTORS);

	uint32_t page, num_pages = ICE40_BITSTREAM_SECTORS *
			(N25Q128_SECTOR_SIZE / N25Q128_PAGE_SIZE);

	/*
	 * assuming ice40mkm_init() has already been called (reset
	 * has already been asserted), activate chip-select and
	 * de-assert reset to force device into slave mode, then
	 * wait for 2 ms to allow internal "housekeeping" to finish
	 */

	_ice40mkm_chip_select();		//
	HAL_Delay(1);					// minimum reset duration is 200 ns
	_ice40mkm_reset_deassert();		//
	HAL_Delay(2);					// minimum wait time is 1200 us

	/*
	 * send 8 dummy clock ticks when chip select is high,
	 * then revert to low chip select
	 */
	_ice40mkm_chip_deselect();
	prom_ok = HAL_SPI_Transmit(&hspi_ice40mkm, prom_buf, 1, N25Q128_SPI_TIMEOUT);
	if (prom_ok != HAL_OK) return 1;
	_ice40mkm_chip_select();

	/*
	 * device is now ready to receive the bitstream
	 *
	 * the bitstream is stored in the last two sectors of the main
	 * FPGA configuration memory
	 *
	 * the very first page of the bitstream starts with a magic marker,
	 * which can be used to detect whether the bitstream is present
	 */

	/* claim access to FPGA config storage */
	fpgacfg_access_control(ALLOW_ARM);

	/*
	 * read the bitstream page-by-page and send it over SPI3
	 * note, that bitstream is slightly smaller than two sectors, so we're
	 * sending some 0xFF's after the binary image, which is harmless
	 * (just some dummy clock cycles)
	 */
	prom_addr = prom_offset;
	for (page=0; page<num_pages; page++)
	{
		/* read next page */
		prom_ok = fpgacfg_read_data(prom_addr, prom_buf, N25Q128_PAGE_SIZE);
		if (prom_ok != HAL_OK) break;

		/* check magic marker */
		if (page == 0) {
			for (c=0; c<sizeof prom_marker; c++)
				if (prom_buf[c] != prom_marker[c]) {
					prom_marker_bad = 1;
					break;
				}
		}

		/* send page to iCE40 */
		prom_ok = HAL_SPI_Transmit(&hspi_ice40mkm, prom_buf, N25Q128_PAGE_SIZE, N25Q128_SPI_TIMEOUT);
		if (prom_ok != HAL_OK) break;

		/* increment address */
		prom_addr += N25Q128_PAGE_SIZE;
	}

	/* relinquish access to FPGA config storage */
	fpgacfg_access_control(ALLOW_FPGA);

	/* check, that magic marker was correct */
	/* check, that all the pages were written */
	if (prom_marker_bad) return 2;
	else if (page != num_pages) return 1;

	/* clear chip-select */
	_ice40mkm_chip_deselect();

	/*
	 * we should wait for at least 100 clock ticks before sampling
	 * the CDONE pin, we can only wait for multiples of 8, so let's
	 * wait for 128 (16 bytes) to be on the safe side
	 */
	prom_ok = HAL_SPI_Transmit(&hspi_ice40mkm, prom_buf, 16, N25Q128_SPI_TIMEOUT);
	if (prom_ok != HAL_OK) return 3;

	/* now check config done pin */
	cdone_state = _ice40mkm_cdone();
	if (!cdone_state) return 3;

	/*
	 * we should wait for at least 49 clock ticks after CDONE turns high to
	 * release SPI pins for user application, we're not using them anyway, but
	 * let's still follow vendor's recommendations just in case; we can only wait
	 * for multiples of 8, so let's wait for 64 ticks (8 bytes) instead of 49.
	 */
	prom_ok = HAL_SPI_Transmit(&hspi_ice40mkm, prom_buf, 8, N25Q128_SPI_TIMEOUT);
	if (prom_ok != HAL_OK) return 1;

	/* cool, everything went fine, zero code means success */
	return 0;
}