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 <siarhei.siamashka@gmail.com>
[Andre: adjust to upstream changes]
Signed-off-by: Andre Przywara <osp@andrep.de>
This commit is contained in:
Siarhei Siamashka 2016-05-24 07:59:44 +03:00 committed by Andre Przywara
parent 1091f3ac6b
commit 3c9bc29f39
8 changed files with 865 additions and 2 deletions

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,187 @@
/*
* Copyright © 2016 Siarhei Siamashka <siarhei.siamashka@gmail.com>
*
* 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;
}
}
}

View File

@ -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 <spi_batch_data_transfer+0x1d0> */
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 <spi_batch_data_transfer+0x80> */
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 <spi_batch_data_transfer+0xf4> */
0x00, 0x40, 0x93, 0xe5, /* d8: ldr r4, [r3] */
0x7f, 0x00, 0x14, 0xe3, /* dc: tst r4, #127 */
0xfc, 0xff, 0xff, 0x0a, /* e0: beq d8 <spi_batch_data_transfer+0xd8> */
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 <spi_batch_data_transfer+0xd8> */
0x07, 0x10, 0x61, 0xe0, /* f4: rsb r1, r1, r7 */
0x03, 0x00, 0x51, 0xe3, /* f8: cmp r1, #3 */
0x12, 0x00, 0x00, 0x9a, /* fc: bls 14c <spi_batch_data_transfer+0x14c> */
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 <spi_batch_data_transfer+0xf8> */
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 <spi_batch_data_transfer+0x100> */
0x00, 0x00, 0x51, 0xe3, /* 14c: cmp r1, #0 */
0x10, 0x00, 0x00, 0x0a, /* 150: beq 198 <spi_batch_data_transfer+0x198> */
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 <spi_batch_data_transfer+0x14c> */
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 <spi_batch_data_transfer+0x154> */
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 <spi_batch_data_transfer+0x14> */
0x11, 0x20, 0xdd, 0xe5, /* 1ac: ldrb r2, [sp, #17] */
0x01, 0x00, 0x12, 0xe3, /* 1b0: tst r2, #1 */
0x08, 0x00, 0x00, 0x1a, /* 1b4: bne 1dc <spi_batch_data_transfer+0x1dc> */
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 <spi_batch_data_transfer+0x24> */
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 <spi_batch_data_transfer+0x14> */
};
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);
}

463
fel-spiflash.c Normal file
View File

@ -0,0 +1,463 @@
/*
* (C) Copyright 2016 Siarhei Siamashka <siarhei.siamashka@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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");
}

34
fel-spiflash.h Normal file
View File

@ -0,0 +1,34 @@
/*
* (C) Copyright 2016 Siarhei Siamashka <siarhei.siamashka@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

20
fel.c
View File

@ -18,6 +18,7 @@
#include "common.h"
#include "portable_endian.h"
#include "fel_lib.h"
#include "fel-spiflash.h"
#include <assert.h>
#include <ctype.h>
@ -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]);
}

View File

@ -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 */