From 3c9bc29f39fbcffd02c3f0624e7064c249334951 Mon Sep 17 00:00:00 2001 From: Siarhei Siamashka Date: Tue, 24 May 2016 07:59:44 +0300 Subject: [PATCH] fel: Add SPI flash programmer implementation Using the new AAPCS function remote execution support, add support to read from and write to SPI flash connected to a device. This allows flashing boot code to a device. Signed-off-by: Siarhei Siamashka [Andre: adjust to upstream changes] Signed-off-by: Andre Przywara --- Makefile | 3 +- fel-remotefunc-compiler.rb | 2 +- fel-remotefunc-spi-data-transfer.c | 187 ++++++++++++ fel-remotefunc-spi-data-transfer.h | 150 ++++++++++ fel-spiflash.c | 463 +++++++++++++++++++++++++++++ fel-spiflash.h | 34 +++ fel.c | 20 ++ fel_lib.h | 8 + 8 files changed, 865 insertions(+), 2 deletions(-) create mode 100644 fel-remotefunc-spi-data-transfer.c create mode 100644 fel-remotefunc-spi-data-transfer.h create mode 100644 fel-spiflash.c create mode 100644 fel-spiflash.h diff --git a/Makefile b/Makefile index b9de5d9..67d5833 100644 --- a/Makefile +++ b/Makefile @@ -135,8 +135,9 @@ HOST_CFLAGS = $(DEFAULT_CFLAGS) $(CFLAGS) PROGRESS := progress.c progress.h SOC_INFO := soc_info.c soc_info.h FEL_LIB := fel_lib.c fel_lib.h +SPI_FLASH:= fel-spiflash.c fel-spiflash.h fel-remotefunc-spi-data-transfer.h -sunxi-fel: fel.c thunks/fel-to-spl-thunk.h $(PROGRESS) $(SOC_INFO) $(FEL_LIB) +sunxi-fel: fel.c thunks/fel-to-spl-thunk.h $(PROGRESS) $(SOC_INFO) $(FEL_LIB) $(SPI_FLASH) $(CC) $(HOST_CFLAGS) $(LIBUSB_CFLAGS) $(ZLIB_CFLAGS) $(LDFLAGS) -o $@ \ $(filter %.c,$^) $(LIBS) $(LIBUSB_LIBS) $(ZLIB_LIBS) diff --git a/fel-remotefunc-compiler.rb b/fel-remotefunc-compiler.rb index ea1559b..4824d51 100755 --- a/fel-remotefunc-compiler.rb +++ b/fel-remotefunc-compiler.rb @@ -72,7 +72,7 @@ toolchain = toolchains.find { |toolchain| tool_exists("#{toolchain}gcc") } abort "Can't find any usable ARM crosscompiler.\n" unless toolchain # Compile the source file -system("#{toolchain}gcc -c -Os -marm -march=armv7-a -mfloat-abi=soft -fstack-usage -fpic -o #{ARGV[0]}.o #{ARGV[0]}") +system("#{toolchain}gcc -c -O3 -marm -march=armv7-a -mfloat-abi=soft -fstack-usage -fpic -o #{ARGV[0]}.o #{ARGV[0]}") exit($?.to_i) if $?.to_i != 0 # Read the stack usage information diff --git a/fel-remotefunc-spi-data-transfer.c b/fel-remotefunc-spi-data-transfer.c new file mode 100644 index 0000000..b640dc6 --- /dev/null +++ b/fel-remotefunc-spi-data-transfer.c @@ -0,0 +1,187 @@ +/* + * Copyright © 2016 Siarhei Siamashka + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +typedef unsigned int u32; +typedef unsigned char u8; + +#define readl(addr) (*((volatile u32 *)(addr))) +#define writel(v, addr) (*((volatile u32 *)(addr)) = (u32)(v)) +#define readb(addr) (*((volatile u8 *)(addr))) +#define writeb(v, addr) (*((volatile u8 *)(addr)) = (u8)(v)) + +/* + * This is a basic full-duplex SPI data transfer function (we are sending a + * block of data and receiving the same amount of data back), doing the job + * without any help from DMA. And because we can be running in some rather + * adverse conditions (with default PMIC settings, low CPU clock speed and + * CPU caches disabled), it is necessary to use 32-bit accesses to read/write + * the FIFO buffers. As a result, Allwinner A13 with the default 408MHz CPU + * clock speed can successfully handle at least 12 MHz SPI clock speed. + * + * Supports both sun4i and sun6i variants of the SPI controller (they only + * need different hardware register addresses passed as arguments). + */ +static void inline __attribute((always_inline)) spi_data_transfer(void *buf, + u32 bufsize, + void *spi_ctl_reg, + u32 spi_ctl_xch_bitmask, + void *spi_fifo_reg, + void *spi_tx_reg, + void *spi_rx_reg, + void *spi_bc_reg, + void *spi_tc_reg, + void *spi_bcc_reg) +{ + u32 cnt; + u32 rxsize = bufsize; + u32 txsize = bufsize; + u8 *rxbuf8 = buf; + u8 *txbuf8 = buf; + u32 *rxbuf; + u32 *txbuf; + u32 cpsr; + + /* sun6i uses 3 registers, sun4i only needs 2 */ + writel(bufsize, spi_bc_reg); + writel(bufsize, spi_tc_reg); + if (spi_bcc_reg) + writel(bufsize, spi_bcc_reg); + + /* Fill the TX buffer with some initial data */ + cnt = (-(u32)txbuf8 & 3) + 60; + if (cnt > txsize) + cnt = txsize; + while (cnt-- > 0) { + writeb(*txbuf8++, spi_tx_reg); + txsize--; + } + + /* Temporarily disable IRQ & FIQ */ + asm volatile("mrs %0, cpsr" : "=r" (cpsr)); + asm volatile("msr cpsr_c, %0" :: "r" (cpsr | 0xC0)); + + /* Start the data transfer */ + writel(readl(spi_ctl_reg) | spi_ctl_xch_bitmask, spi_ctl_reg); + + /* Read the initial unaligned part of the data */ + cnt = (-(u32)rxbuf8 & 3); + if (cnt > rxsize) + cnt = rxsize; + while (cnt > 0) { + u32 fiforeg = readl(spi_fifo_reg); + int rxfifo = fiforeg & 0x7F; + if (rxfifo > 0) { + *rxbuf8++ = readb(spi_rx_reg); + cnt--; + rxsize--; + } + } + + /* Fast processing of the aligned part (read/write 32-bit at a time) */ + rxbuf = (u32 *)rxbuf8; + txbuf = (u32 *)txbuf8; + while (rxsize >= 4) { + u32 fiforeg = readl(spi_fifo_reg); + int rxfifo = fiforeg & 0x7F; + int txfifo = (fiforeg >> 16) & 0x7F; + if (rxfifo >= 4) { + *rxbuf++ = readl(spi_rx_reg); + rxsize -= 4; + } + if (txfifo < 60 && txsize >= 4) { + writel(*txbuf++, spi_tx_reg); + txsize -= 4; + } + } + + /* Handle the trailing part pf the data */ + rxbuf8 = (u8 *)rxbuf; + txbuf8 = (u8 *)txbuf; + while (rxsize >= 1) { + u32 fiforeg = readl(spi_fifo_reg); + int rxfifo = fiforeg & 0x7F; + int txfifo = (fiforeg >> 16) & 0x7F; + if (rxfifo >= 1) { + *rxbuf8++ = readb(spi_rx_reg); + rxsize -= 1; + } + if (txfifo < 60 && txsize >= 1) { + writeb(*txbuf8++, spi_tx_reg); + txsize -= 1; + } + } + + /* Restore CPSR */ + asm volatile("msr cpsr_c, %0" :: "r" (cpsr)); +} + +void spi_batch_data_transfer(u8 *buf, + void *spi_ctl_reg, + u32 spi_ctl_xch_bitmask, + void *spi_fifo_reg, + void *spi_tx_reg, + void *spi_rx_reg, + void *spi_bc_reg, + void *spi_tc_reg, + void *spi_bcc_reg) +{ + u8 wait_for_completion_cmd[2]; + u8 *backup_buf; + u32 bufsize; + + while (1) { + u32 code = (buf[0] << 8) | buf[1]; + + /* End of data */ + if (code == 0) + return; + + if (code == 0xFFFF) { + /* Wait for completion, part 1 */ + backup_buf = buf; + buf = wait_for_completion_cmd; + wait_for_completion_cmd[0] = 0x05; + bufsize = 2; + } else { + /* Normal buffer */ + buf += 2; + bufsize = code; + } + + spi_data_transfer(buf, bufsize, spi_ctl_reg, spi_ctl_xch_bitmask, + spi_fifo_reg, spi_tx_reg, spi_rx_reg, + spi_bc_reg, spi_tc_reg, spi_bcc_reg); + buf += bufsize; + + if (code == 0xFFFF) { + /* Wait for completion, part 2 */ + buf = backup_buf; + if (wait_for_completion_cmd[1] & 1) { + /* Still busy */ + continue; + } + /* Advance to the next code */ + buf = backup_buf + 2; + } + } +} diff --git a/fel-remotefunc-spi-data-transfer.h b/fel-remotefunc-spi-data-transfer.h new file mode 100644 index 0000000..c1a64cb --- /dev/null +++ b/fel-remotefunc-spi-data-transfer.h @@ -0,0 +1,150 @@ +/* Automatically generated, do not edit! */ + +static void +aw_fel_remotefunc_prepare_spi_batch_data_transfer(feldev_handle *dev, + uint32_t buf, + uint32_t spi_ctl_reg, + uint32_t spi_ctl_xch_bitmask, + uint32_t spi_fifo_reg, + uint32_t spi_tx_reg, + uint32_t spi_rx_reg, + uint32_t spi_bc_reg, + uint32_t spi_tc_reg, + uint32_t spi_bcc_reg) +{ + static uint8_t arm_code[] = { + 0xf0, 0x0f, 0x2d, 0xe9, /* 0: push {r4, r5, r6, r7, r8, r9, sl, fp} */ + 0x18, 0xd0, 0x4d, 0xe2, /* 4: sub sp, sp, #24 */ + 0x38, 0x50, 0x9d, 0xe5, /* 8: ldr r5, [sp, #56] */ + 0x3c, 0x60, 0x9d, 0xe5, /* c: ldr r6, [sp, #60] */ + 0x06, 0x00, 0x8d, 0xe9, /* 10: stmib sp, {r1, r2} */ + 0x00, 0xa0, 0xd0, 0xe5, /* 14: ldrb sl, [r0] */ + 0x01, 0x20, 0xd0, 0xe5, /* 18: ldrb r2, [r0, #1] */ + 0x0a, 0xa4, 0x92, 0xe1, /* 1c: orrs sl, r2, sl, lsl #8 */ + 0x6a, 0x00, 0x00, 0x0a, /* 20: beq 1d0 */ + 0xff, 0x2f, 0x0f, 0xe3, /* 24: movw r2, #65535 */ + 0x02, 0x00, 0x5a, 0xe1, /* 28: cmp sl, r2 */ + 0x18, 0x80, 0x8d, 0x02, /* 2c: addeq r8, sp, #24 */ + 0x02, 0x80, 0x80, 0x12, /* 30: addne r8, r0, #2 */ + 0x05, 0xb0, 0xa0, 0x03, /* 34: moveq fp, #5 */ + 0x48, 0xc0, 0x9d, 0xe5, /* 38: ldr ip, [sp, #72] */ + 0x08, 0xb0, 0x68, 0x05, /* 3c: strbeq fp, [r8, #-8]! */ + 0x00, 0x10, 0x68, 0xe2, /* 40: rsb r1, r8, #0 */ + 0x40, 0x20, 0x9d, 0xe5, /* 44: ldr r2, [sp, #64] */ + 0x03, 0x10, 0x01, 0xe2, /* 48: and r1, r1, #3 */ + 0x44, 0xb0, 0x9d, 0xe5, /* 4c: ldr fp, [sp, #68] */ + 0x0a, 0x70, 0xa0, 0x11, /* 50: movne r7, sl */ + 0x0c, 0x00, 0x8d, 0x05, /* 54: streq r0, [sp, #12] */ + 0x3c, 0x00, 0x81, 0xe2, /* 58: add r0, r1, #60 */ + 0x02, 0x70, 0xa0, 0x03, /* 5c: moveq r7, #2 */ + 0x00, 0x00, 0x5c, 0xe3, /* 60: cmp ip, #0 */ + 0x00, 0x70, 0x82, 0xe5, /* 64: str r7, [r2] */ + 0x08, 0x20, 0xa0, 0xe1, /* 68: mov r2, r8 */ + 0x00, 0x70, 0x8b, 0xe5, /* 6c: str r7, [fp] */ + 0x00, 0x70, 0x8c, 0x15, /* 70: strne r7, [ip] */ + 0x07, 0x00, 0x50, 0xe1, /* 74: cmp r0, r7 */ + 0x07, 0x00, 0xa0, 0x21, /* 78: movcs r0, r7 */ + 0x00, 0x40, 0x88, 0xe0, /* 7c: add r4, r8, r0 */ + 0x01, 0xc0, 0xd2, 0xe4, /* 80: ldrb ip, [r2], #1 */ + 0x04, 0x00, 0x52, 0xe1, /* 84: cmp r2, r4 */ + 0x00, 0xc0, 0xc5, 0xe5, /* 88: strb ip, [r5] */ + 0xfb, 0xff, 0xff, 0x1a, /* 8c: bne 80 */ + 0x07, 0x00, 0x60, 0xe0, /* 90: rsb r0, r0, r7 */ + 0x00, 0x90, 0x0f, 0xe1, /* 94: mrs r9, CPSR */ + 0xc0, 0xc0, 0x89, 0xe3, /* 98: orr ip, r9, #192 */ + 0x0c, 0xf0, 0x21, 0xe1, /* 9c: msr CPSR_c, ip */ + 0x04, 0xc0, 0x9d, 0xe5, /* a0: ldr ip, [sp, #4] */ + 0x07, 0x00, 0x51, 0xe1, /* a4: cmp r1, r7 */ + 0x07, 0x10, 0xa0, 0x21, /* a8: movcs r1, r7 */ + 0x08, 0xb0, 0x9d, 0xe5, /* ac: ldr fp, [sp, #8] */ + 0x00, 0x40, 0x9c, 0xe5, /* b0: ldr r4, [ip] */ + 0x01, 0xc0, 0x88, 0xe0, /* b4: add ip, r8, r1 */ + 0x00, 0xc0, 0x8d, 0xe5, /* b8: str ip, [sp] */ + 0x08, 0xc0, 0xa0, 0xe1, /* bc: mov ip, r8 */ + 0x0b, 0x40, 0x84, 0xe1, /* c0: orr r4, r4, fp */ + 0x04, 0xb0, 0x9d, 0xe5, /* c4: ldr fp, [sp, #4] */ + 0x00, 0x40, 0x8b, 0xe5, /* c8: str r4, [fp] */ + 0x00, 0xb0, 0x9d, 0xe5, /* cc: ldr fp, [sp] */ + 0x0b, 0x00, 0x5c, 0xe1, /* d0: cmp ip, fp */ + 0x06, 0x00, 0x00, 0x0a, /* d4: beq f4 */ + 0x00, 0x40, 0x93, 0xe5, /* d8: ldr r4, [r3] */ + 0x7f, 0x00, 0x14, 0xe3, /* dc: tst r4, #127 */ + 0xfc, 0xff, 0xff, 0x0a, /* e0: beq d8 */ + 0x00, 0x40, 0xd6, 0xe5, /* e4: ldrb r4, [r6] */ + 0x01, 0x40, 0xcc, 0xe4, /* e8: strb r4, [ip], #1 */ + 0x0b, 0x00, 0x5c, 0xe1, /* ec: cmp ip, fp */ + 0xf8, 0xff, 0xff, 0x1a, /* f0: bne d8 */ + 0x07, 0x10, 0x61, 0xe0, /* f4: rsb r1, r1, r7 */ + 0x03, 0x00, 0x51, 0xe3, /* f8: cmp r1, #3 */ + 0x12, 0x00, 0x00, 0x9a, /* fc: bls 14c */ + 0x00, 0x40, 0x93, 0xe5, /* 100: ldr r4, [r3] */ + 0x7f, 0xb0, 0x04, 0xe2, /* 104: and fp, r4, #127 */ + 0x54, 0x48, 0xe6, 0xe7, /* 108: ubfx r4, r4, #16, #7 */ + 0x03, 0x00, 0x5b, 0xe3, /* 10c: cmp fp, #3 */ + 0x04, 0x10, 0x41, 0xc2, /* 110: subgt r1, r1, #4 */ + 0x00, 0xb0, 0x96, 0xc5, /* 114: ldrgt fp, [r6] */ + 0x04, 0xb0, 0x8c, 0xc4, /* 118: strgt fp, [ip], #4 */ + 0x03, 0x00, 0x50, 0xe3, /* 11c: cmp r0, #3 */ + 0x00, 0xb0, 0xa0, 0x93, /* 120: movls fp, #0 */ + 0x01, 0xb0, 0xa0, 0x83, /* 124: movhi fp, #1 */ + 0x3b, 0x00, 0x54, 0xe3, /* 128: cmp r4, #59 */ + 0x00, 0xb0, 0xa0, 0xc3, /* 12c: movgt fp, #0 */ + 0x00, 0x00, 0x5b, 0xe3, /* 130: cmp fp, #0 */ + 0xef, 0xff, 0xff, 0x0a, /* 134: beq f8 */ + 0x04, 0x40, 0x92, 0xe4, /* 138: ldr r4, [r2], #4 */ + 0x03, 0x00, 0x51, 0xe3, /* 13c: cmp r1, #3 */ + 0x04, 0x00, 0x40, 0xe2, /* 140: sub r0, r0, #4 */ + 0x00, 0x40, 0x85, 0xe5, /* 144: str r4, [r5] */ + 0xec, 0xff, 0xff, 0x8a, /* 148: bhi 100 */ + 0x00, 0x00, 0x51, 0xe3, /* 14c: cmp r1, #0 */ + 0x10, 0x00, 0x00, 0x0a, /* 150: beq 198 */ + 0x00, 0x40, 0x93, 0xe5, /* 154: ldr r4, [r3] */ + 0x7f, 0x00, 0x14, 0xe3, /* 158: tst r4, #127 */ + 0x54, 0x48, 0xe6, 0xe7, /* 15c: ubfx r4, r4, #16, #7 */ + 0x01, 0x10, 0x41, 0x12, /* 160: subne r1, r1, #1 */ + 0x00, 0xb0, 0xd6, 0x15, /* 164: ldrbne fp, [r6] */ + 0x01, 0xb0, 0xcc, 0x14, /* 168: strbne fp, [ip], #1 */ + 0x00, 0xb0, 0x90, 0xe2, /* 16c: adds fp, r0, #0 */ + 0x01, 0xb0, 0xa0, 0x13, /* 170: movne fp, #1 */ + 0x3b, 0x00, 0x54, 0xe3, /* 174: cmp r4, #59 */ + 0x00, 0xb0, 0xa0, 0xc3, /* 178: movgt fp, #0 */ + 0x00, 0x00, 0x5b, 0xe3, /* 17c: cmp fp, #0 */ + 0xf1, 0xff, 0xff, 0x0a, /* 180: beq 14c */ + 0x01, 0x40, 0xd2, 0xe4, /* 184: ldrb r4, [r2], #1 */ + 0x00, 0x00, 0x51, 0xe3, /* 188: cmp r1, #0 */ + 0x01, 0x00, 0x40, 0xe2, /* 18c: sub r0, r0, #1 */ + 0x00, 0x40, 0xc5, 0xe5, /* 190: strb r4, [r5] */ + 0xee, 0xff, 0xff, 0x1a, /* 194: bne 154 */ + 0x09, 0xf0, 0x21, 0xe1, /* 198: msr CPSR_c, r9 */ + 0xff, 0xcf, 0x0f, 0xe3, /* 19c: movw ip, #65535 */ + 0x0c, 0x00, 0x5a, 0xe1, /* 1a0: cmp sl, ip */ + 0x07, 0x00, 0x88, 0x10, /* 1a4: addne r0, r8, r7 */ + 0x99, 0xff, 0xff, 0x1a, /* 1a8: bne 14 */ + 0x11, 0x20, 0xdd, 0xe5, /* 1ac: ldrb r2, [sp, #17] */ + 0x01, 0x00, 0x12, 0xe3, /* 1b0: tst r2, #1 */ + 0x08, 0x00, 0x00, 0x1a, /* 1b4: bne 1dc */ + 0x0c, 0xb0, 0x9d, 0xe5, /* 1b8: ldr fp, [sp, #12] */ + 0x02, 0x00, 0x8b, 0xe2, /* 1bc: add r0, fp, #2 */ + 0x00, 0xa0, 0xd0, 0xe5, /* 1c0: ldrb sl, [r0] */ + 0x01, 0x20, 0xd0, 0xe5, /* 1c4: ldrb r2, [r0, #1] */ + 0x0a, 0xa4, 0x92, 0xe1, /* 1c8: orrs sl, r2, sl, lsl #8 */ + 0x94, 0xff, 0xff, 0x1a, /* 1cc: bne 24 */ + 0x18, 0xd0, 0x8d, 0xe2, /* 1d0: add sp, sp, #24 */ + 0xf0, 0x0f, 0xbd, 0xe8, /* 1d4: pop {r4, r5, r6, r7, r8, r9, sl, fp} */ + 0x1e, 0xff, 0x2f, 0xe1, /* 1d8: bx lr */ + 0x0c, 0x00, 0x9d, 0xe5, /* 1dc: ldr r0, [sp, #12] */ + 0x8b, 0xff, 0xff, 0xea, /* 1e0: b 14 */ + }; + uint32_t args[] = { + buf, + spi_ctl_reg, + spi_ctl_xch_bitmask, + spi_fifo_reg, + spi_tx_reg, + spi_rx_reg, + spi_bc_reg, + spi_tc_reg, + spi_bcc_reg + }; + aw_fel_remotefunc_prepare(dev, 56, arm_code, sizeof(arm_code), 9, args); +} diff --git a/fel-spiflash.c b/fel-spiflash.c new file mode 100644 index 0000000..64e205e --- /dev/null +++ b/fel-spiflash.c @@ -0,0 +1,463 @@ +/* + * (C) Copyright 2016 Siarhei Siamashka + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "fel_lib.h" +#include "progress.h" + +#include "fel-remotefunc-spi-data-transfer.h" + +/*****************************************************************************/ + +typedef struct { + uint32_t id; + uint8_t write_enable_cmd; + uint8_t large_erase_cmd; + uint32_t large_erase_size; + uint8_t small_erase_cmd; + uint32_t small_erase_size; + uint8_t program_cmd; + uint32_t program_size; + char *text_description; +} spi_flash_info_t; + +spi_flash_info_t spi_flash_info[] = { + { 0xEF40, 0x6, 0xD8, 64 * 1024, 0x20, 4 * 1024, 0x02, 256, "Winbond W25Qxx" }, +}; + +spi_flash_info_t default_spi_flash_info = { + 0x0000, 0x6, 0xD8, 64 * 1024, 0x20, 4 * 1024, 0x02, 256, "Unknown" +}; + +/*****************************************************************************/ + +uint32_t fel_readl(feldev_handle *dev, uint32_t addr); +void fel_writel(feldev_handle *dev, uint32_t addr, uint32_t val); +#define readl(addr) fel_readl(dev, (addr)) +#define writel(val, addr) fel_writel(dev, (addr), (val)) + +#define PA (0) +#define PB (1) +#define PC (2) + +#define CCM_SPI0_CLK (0x01C20000 + 0xA0) +#define CCM_AHB_GATING0 (0x01C20000 + 0x60) +#define CCM_AHB_GATE_SPI0 (1 << 20) +#define SUN6I_BUS_SOFT_RST_REG0 (0x01C20000 + 0x2C0) +#define SUN6I_SPI0_RST (1 << 20) + +#define SUNXI_GPC_SPI0 (3) +#define SUN50I_GPC_SPI0 (4) + +#define SUN4I_CTL_ENABLE (1 << 0) +#define SUN4I_CTL_MASTER (1 << 1) +#define SUN4I_CTL_TF_RST (1 << 8) +#define SUN4I_CTL_RF_RST (1 << 9) +#define SUN4I_CTL_XCH (1 << 10) + +#define SUN6I_TCR_XCH (1 << 31) + +#define SUN4I_SPI0_CCTL (0x01C05000 + 0x1C) +#define SUN4I_SPI0_CTL (0x01C05000 + 0x08) +#define SUN4I_SPI0_RX (0x01C05000 + 0x00) +#define SUN4I_SPI0_TX (0x01C05000 + 0x04) +#define SUN4I_SPI0_FIFO_STA (0x01C05000 + 0x28) +#define SUN4I_SPI0_BC (0x01C05000 + 0x20) +#define SUN4I_SPI0_TC (0x01C05000 + 0x24) + +#define SUN6I_SPI0_CCTL (0x01C68000 + 0x24) +#define SUN6I_SPI0_GCR (0x01C68000 + 0x04) +#define SUN6I_SPI0_TCR (0x01C68000 + 0x08) +#define SUN6I_SPI0_FIFO_STA (0x01C68000 + 0x1C) +#define SUN6I_SPI0_MBC (0x01C68000 + 0x30) +#define SUN6I_SPI0_MTC (0x01C68000 + 0x34) +#define SUN6I_SPI0_BCC (0x01C68000 + 0x38) +#define SUN6I_SPI0_TXD (0x01C68000 + 0x200) +#define SUN6I_SPI0_RXD (0x01C68000 + 0x300) + +#define CCM_SPI0_CLK_DIV_BY_2 (0x1000) +#define CCM_SPI0_CLK_DIV_BY_4 (0x1001) +#define CCM_SPI0_CLK_DIV_BY_6 (0x1002) + +/* + * Configure pin function on a GPIO port + */ +static void gpio_set_cfgpin(feldev_handle *dev, int port_num, int pin_num, + int val) +{ + uint32_t port_base = 0x01C20800 + port_num * 0x24; + uint32_t cfg_reg = port_base + 4 * (pin_num / 8); + uint32_t pin_idx = pin_num % 8; + uint32_t x = readl(cfg_reg); + x &= ~(0x7 << (pin_idx * 4)); + x |= val << (pin_idx * 4); + writel(x, cfg_reg); +} + +static bool spi_is_sun6i(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + switch (soc_info->soc_id) { + case 0x1623: /* A10 */ + case 0x1625: /* A13 */ + case 0x1651: /* A20 */ + return false; + default: + return true; + } +} + +/* + * Init the SPI0 controller and setup pins muxing. + */ +static bool spi0_init(feldev_handle *dev) +{ + uint32_t reg_val; + soc_info_t *soc_info = dev->soc_info; + if (!soc_info) + return false; + + /* Setup SPI0 pins muxing */ + switch (soc_info->soc_id) { + case 0x1625: /* Allwinner A13 */ + case 0x1680: /* Allwinner H3 */ + case 0x1718: /* Allwinner H5 */ + gpio_set_cfgpin(dev, PC, 0, SUNXI_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 1, SUNXI_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 2, SUNXI_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 3, SUNXI_GPC_SPI0); + break; + case 0x1689: /* Allwinner A64 */ + gpio_set_cfgpin(dev, PC, 0, SUN50I_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 1, SUN50I_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 2, SUN50I_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 3, SUN50I_GPC_SPI0); + break; + default: /* Unknown/Unsupported SoC */ + return false; + } + + reg_val = readl(CCM_AHB_GATING0); + reg_val |= CCM_AHB_GATE_SPI0; + writel(reg_val, CCM_AHB_GATING0); + + /* 24MHz from OSC24M */ + writel((1 << 31), CCM_SPI0_CLK); + /* divide by 4 */ + writel(CCM_SPI0_CLK_DIV_BY_4, spi_is_sun6i(dev) ? SUN6I_SPI0_CCTL : + SUN4I_SPI0_CCTL); + + if (spi_is_sun6i(dev)) { + /* Deassert SPI0 reset */ + reg_val = readl(SUN6I_BUS_SOFT_RST_REG0); + reg_val |= SUN6I_SPI0_RST; + writel(reg_val, SUN6I_BUS_SOFT_RST_REG0); + /* Enable SPI in the master mode and do a soft reset */ + reg_val = readl(SUN6I_SPI0_GCR); + reg_val |= (1 << 31) | 3; + writel(reg_val, SUN6I_SPI0_GCR); + /* Wait for completion */ + while (readl(SUN6I_SPI0_GCR) & (1 << 31)) {} + } else { + reg_val = readl(SUN4I_SPI0_CTL); + reg_val |= SUN4I_CTL_MASTER; + reg_val |= SUN4I_CTL_ENABLE | SUN4I_CTL_TF_RST | SUN4I_CTL_RF_RST; + writel(reg_val, SUN4I_SPI0_CTL); + } + + return true; +} + +/* + * Backup/restore the initial portion of the SRAM, which can be used as + * a temporary data buffer. + */ +static void *backup_sram(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + size_t bufsize = soc_info->scratch_addr - soc_info->spl_addr; + void *buf = malloc(bufsize); + aw_fel_read(dev, soc_info->spl_addr, buf, bufsize); + return buf; +} + +static void restore_sram(feldev_handle *dev, void *buf) +{ + soc_info_t *soc_info = dev->soc_info; + size_t bufsize = soc_info->scratch_addr - soc_info->spl_addr; + aw_fel_write(dev, buf, soc_info->spl_addr, bufsize); + free(buf); +} + +static void prepare_spi_batch_data_transfer(feldev_handle *dev, uint32_t buf) +{ + if (spi_is_sun6i(dev)) { + aw_fel_remotefunc_prepare_spi_batch_data_transfer(dev, + buf, + SUN6I_SPI0_TCR, + SUN6I_TCR_XCH, + SUN6I_SPI0_FIFO_STA, + SUN6I_SPI0_TXD, + SUN6I_SPI0_RXD, + SUN6I_SPI0_MBC, + SUN6I_SPI0_MTC, + SUN6I_SPI0_BCC); + } else { + aw_fel_remotefunc_prepare_spi_batch_data_transfer(dev, + buf, + SUN4I_SPI0_CTL, + SUN4I_CTL_XCH, + SUN4I_SPI0_FIFO_STA, + SUN4I_SPI0_TX, + SUN4I_SPI0_RX, + SUN4I_SPI0_BC, + SUN4I_SPI0_TC, + 0); + } +} + +/* + * Read data from the SPI flash. Use the first 4KiB of SRAM as the data buffer. + */ +void aw_fel_spiflash_read(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + progress_cb_t progress) +{ + soc_info_t *soc_info = dev->soc_info; + void *backup = backup_sram(dev); + uint8_t *buf8 = (uint8_t *)buf; + size_t max_chunk_size = soc_info->scratch_addr - soc_info->spl_addr; + if (max_chunk_size > 0x1000) + max_chunk_size = 0x1000; + uint8_t *cmdbuf = malloc(max_chunk_size); + memset(cmdbuf, 0, max_chunk_size); + aw_fel_write(dev, cmdbuf, soc_info->spl_addr, max_chunk_size); + + if (!spi0_init(dev)) + return; + + prepare_spi_batch_data_transfer(dev, soc_info->spl_addr); + + progress_start(progress, len); + while (len > 0) { + size_t chunk_size = len; + if (chunk_size > max_chunk_size - 8) + chunk_size = max_chunk_size - 8; + + memset(cmdbuf, 0, max_chunk_size); + cmdbuf[0] = (chunk_size + 4) >> 8; + cmdbuf[1] = (chunk_size + 4); + cmdbuf[2] = 3; + cmdbuf[3] = offset >> 16; + cmdbuf[4] = offset >> 8; + cmdbuf[5] = offset; + + if (chunk_size == max_chunk_size - 8) + aw_fel_write(dev, cmdbuf, soc_info->spl_addr, 6); + else + aw_fel_write(dev, cmdbuf, soc_info->spl_addr, chunk_size + 8); + aw_fel_remotefunc_execute(dev, NULL); + aw_fel_read(dev, soc_info->spl_addr + 6, buf8, chunk_size); + + len -= chunk_size; + offset += chunk_size; + buf8 += chunk_size; + progress_update(chunk_size); + } + + free(cmdbuf); + restore_sram(dev, backup); +} + +/* + * Write data to the SPI flash. Use the first 4KiB of SRAM as the data buffer. + */ + +#define CMD_WRITE_ENABLE 0x06 + +void aw_fel_spiflash_write_helper(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + size_t erase_size, uint8_t erase_cmd, + size_t program_size, uint8_t program_cmd) +{ + soc_info_t *soc_info = dev->soc_info; + uint8_t *buf8 = (uint8_t *)buf; + size_t max_chunk_size = soc_info->scratch_addr - soc_info->spl_addr; + size_t cmd_idx; + + if (max_chunk_size > 0x1000) + max_chunk_size = 0x1000; + uint8_t *cmdbuf = malloc(max_chunk_size); + cmd_idx = 0; + + prepare_spi_batch_data_transfer(dev, soc_info->spl_addr); + + while (len > 0) { + while (len > 0 && max_chunk_size - cmd_idx > program_size + 64) { + if (offset % erase_size == 0) { + /* Emit write enable command */ + cmdbuf[cmd_idx++] = 0; + cmdbuf[cmd_idx++] = 1; + cmdbuf[cmd_idx++] = CMD_WRITE_ENABLE; + /* Emit erase command */ + cmdbuf[cmd_idx++] = 0; + cmdbuf[cmd_idx++] = 4; + cmdbuf[cmd_idx++] = erase_cmd; + cmdbuf[cmd_idx++] = offset >> 16; + cmdbuf[cmd_idx++] = offset >> 8; + cmdbuf[cmd_idx++] = offset; + /* Emit wait for completion */ + cmdbuf[cmd_idx++] = 0xFF; + cmdbuf[cmd_idx++] = 0xFF; + } + /* Emit write enable command */ + cmdbuf[cmd_idx++] = 0; + cmdbuf[cmd_idx++] = 1; + cmdbuf[cmd_idx++] = CMD_WRITE_ENABLE; + /* Emit page program command */ + size_t write_count = program_size; + if (write_count > len) + write_count = len; + cmdbuf[cmd_idx++] = (4 + write_count) >> 8; + cmdbuf[cmd_idx++] = 4 + write_count; + cmdbuf[cmd_idx++] = program_cmd; + cmdbuf[cmd_idx++] = offset >> 16; + cmdbuf[cmd_idx++] = offset >> 8; + cmdbuf[cmd_idx++] = offset; + memcpy(cmdbuf + cmd_idx, buf8, write_count); + cmd_idx += write_count; + buf8 += write_count; + len -= write_count; + offset += write_count; + /* Emit wait for completion */ + cmdbuf[cmd_idx++] = 0xFF; + cmdbuf[cmd_idx++] = 0xFF; + } + /* Emit the end marker */ + cmdbuf[cmd_idx++] = 0; + cmdbuf[cmd_idx++] = 0; + + /* Flush */ + aw_fel_write(dev, cmdbuf, soc_info->spl_addr, cmd_idx); + aw_fel_remotefunc_execute(dev, NULL); + cmd_idx = 0; + } + + free(cmdbuf); +} + +void aw_fel_spiflash_write(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + progress_cb_t progress) +{ + void *backup = backup_sram(dev); + uint8_t *buf8 = (uint8_t *)buf; + + spi_flash_info_t *flash_info = &default_spi_flash_info; /* FIXME */ + + if ((offset % flash_info->small_erase_size) != 0) { + fprintf(stderr, "aw_fel_spiflash_write: 'addr' must be %d bytes aligned\n", + flash_info->small_erase_size); + exit(1); + } + + if (!spi0_init(dev)) + return; + + progress_start(progress, len); + while (len > 0) { + size_t write_count; + if ((offset % flash_info->large_erase_size) != 0 || + len < flash_info->large_erase_size) { + + write_count = flash_info->small_erase_size; + if (write_count > len) + write_count = len; + aw_fel_spiflash_write_helper(dev, offset, buf8, + write_count, + flash_info->small_erase_size, flash_info->small_erase_cmd, + flash_info->program_size, flash_info->program_cmd); + } else { + write_count = flash_info->large_erase_size; + if (write_count > len) + write_count = len; + aw_fel_spiflash_write_helper(dev, offset, buf8, + write_count, + flash_info->large_erase_size, flash_info->large_erase_cmd, + flash_info->program_size, flash_info->program_cmd); + } + + len -= write_count; + offset += write_count; + buf8 += write_count; + progress_update(write_count); + } + + restore_sram(dev, backup); +} + +/* + * Use the read JEDEC ID (9Fh) command. + */ +void aw_fel_spiflash_info(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + const char *manufacturer; + unsigned char buf[] = { 0, 4, 0x9F, 0, 0, 0, 0x0, 0x0 }; + void *backup = backup_sram(dev); + + if (!spi0_init(dev)) + return; + + aw_fel_write(dev, buf, soc_info->spl_addr, sizeof(buf)); + prepare_spi_batch_data_transfer(dev, soc_info->spl_addr); + aw_fel_remotefunc_execute(dev, NULL); + aw_fel_read(dev, soc_info->spl_addr, buf, sizeof(buf)); + + restore_sram(dev, backup); + + /* Assume that the MISO pin is either pulled up or down */ + if (buf[5] == 0x00 || buf[5] == 0xFF) { + printf("No SPI flash detected.\n"); + return; + } + + switch (buf[3]) { + case 0xEF: + manufacturer = "Winbond"; + break; + default: + manufacturer = "Unknown"; + break; + } + + printf("Manufacturer: %s (%02Xh), model: %02Xh, size: %d bytes.\n", + manufacturer, buf[3], buf[4], (1 << buf[5])); +} + +/* + * Show a help message about the available "spiflash-*" commands. + */ +void aw_fel_spiflash_help(void) +{ + printf(" spiflash-info Retrieves basic information\n" + " spiflash-read addr length file Write SPI flash contents into file\n" + " spiflash-write addr file Store file contents into SPI flash\n"); +} diff --git a/fel-spiflash.h b/fel-spiflash.h new file mode 100644 index 0000000..03a9dd6 --- /dev/null +++ b/fel-spiflash.h @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2016 Siarhei Siamashka + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _SUNXI_TOOLS_FEL_SPIFLASH_H +#define _SUNXI_TOOLS_FEL_SPIFLASH_H + +#include "fel_lib.h" +#include "progress.h" + +void aw_fel_spiflash_read(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + progress_cb_t progress); +void aw_fel_spiflash_write(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + progress_cb_t progress); +void aw_fel_spiflash_info(feldev_handle *dev); +void aw_fel_spiflash_help(void); +void aw_fel_spi0_init(feldev_handle *dev); + +#endif diff --git a/fel.c b/fel.c index 188e1e3..34395c4 100644 --- a/fel.c +++ b/fel.c @@ -18,6 +18,7 @@ #include "common.h" #include "portable_endian.h" #include "fel_lib.h" +#include "fel-spiflash.h" #include #include @@ -1177,6 +1178,8 @@ void usage(const char *cmd) { " clear address length Clear memory\n" " fill address length value Fill memory\n" , cmd); + printf("\n"); + aw_fel_spiflash_help(); exit(0); } @@ -1347,6 +1350,23 @@ int main(int argc, char **argv) if (!uboot_autostart) printf("Warning: \"uboot\" command failed to detect image! Can't execute U-Boot.\n"); skip=2; + } else if (strcmp(argv[1], "spiflash-info") == 0) { + aw_fel_spiflash_info(handle); + } else if (strcmp(argv[1], "spiflash-read") == 0 && argc > 4) { + size_t size = strtoul(argv[3], NULL, 0); + void *buf = malloc(size); + aw_fel_spiflash_read(handle, strtoul(argv[2], NULL, 0), buf, size, + pflag_active ? progress_bar : NULL); + save_file(argv[4], buf, size); + free(buf); + skip=4; + } else if (strcmp(argv[1], "spiflash-write") == 0 && argc > 3) { + size_t size; + void *buf = load_file(argv[3], &size); + aw_fel_spiflash_write(handle, strtoul(argv[2], NULL, 0), buf, size, + pflag_active ? progress_bar : NULL); + free(buf); + skip=3; } else { pr_fatal("Invalid command %s\n", argv[1]); } diff --git a/fel_lib.h b/fel_lib.h index 62ab8dd..6f0a980 100644 --- a/fel_lib.h +++ b/fel_lib.h @@ -81,4 +81,12 @@ void fel_clrsetbits_le32(feldev_handle *dev, bool fel_get_sid_root_key(feldev_handle *dev, uint32_t *result, bool force_workaround); +bool aw_fel_remotefunc_prepare(feldev_handle *dev, + size_t stack_size, + void *arm_code, + size_t arm_code_size, + size_t num_args, + uint32_t *args); +bool aw_fel_remotefunc_execute(feldev_handle *dev, uint32_t *result); + #endif /* _SUNXI_TOOLS_FEL_LIB_H */