sunxi-tools/fel-spiflash.c
Chen-Yu Tsai 76089c82d0
Merge pull request #173 from apritzel/f1c100
Allwinner F1C100 support
2022-03-29 21:10:49 +08:00

607 lines
18 KiB
C

/*
* (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[] = {
{ .id = 0xEF40, .write_enable_cmd = 0x6,
.large_erase_cmd = 0xD8, .large_erase_size = 64 * 1024,
.small_erase_cmd = 0x20, .small_erase_size = 4 * 1024,
.program_cmd = 0x02, .program_size = 256,
.text_description = "Winbond W25Qxx" },
{ .id = 0xC220, .write_enable_cmd = 0x6,
.large_erase_cmd = 0xD8, .large_erase_size = 64 * 1024,
.small_erase_cmd = 0x20, .small_erase_size = 4 * 1024,
.program_cmd = 0x02, .program_size = 256,
.text_description = "Macronix MX25Lxxxx" },
{ .id = 0x1C70, .write_enable_cmd = 0x6,
.large_erase_cmd = 0xD8, .large_erase_size = 64 * 1024,
.small_erase_cmd = 0x20, .small_erase_size = 4 * 1024,
.program_cmd = 0x02, .program_size = 256,
.text_description = "Eon EN25QHxx" },
};
spi_flash_info_t default_spi_flash_info = {
.id = 0x0000, .write_enable_cmd = 0x6,
.large_erase_cmd = 0xD8, .large_erase_size = 64 * 1024,
.small_erase_cmd = 0x20, .small_erase_size = 4 * 1024,
.program_cmd = 0x02, .program_size = 256,
.text_description = "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 SUNIV_PLL6_CTL (0x01c20000 + 0x28)
#define SUNIV_AHB_APB_CFG (0x01c20000 + 0x54)
#define H6_CCM_SPI0_CLK (0x03001000 + 0x940)
#define H6_CCM_SPI_BGR (0x03001000 + 0x96C)
#define H6_CCM_SPI0_GATE_RESET (1 << 0 | 1 << 16)
#define SUNIV_GPC_SPI0 (2)
#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 (1U << 31)
#define SUN4I_SPI0_CCTL (spi_base(dev) + 0x1C)
#define SUN4I_SPI0_CTL (spi_base(dev) + 0x08)
#define SUN4I_SPI0_RX (spi_base(dev) + 0x00)
#define SUN4I_SPI0_TX (spi_base(dev) + 0x04)
#define SUN4I_SPI0_FIFO_STA (spi_base(dev) + 0x28)
#define SUN4I_SPI0_BC (spi_base(dev) + 0x20)
#define SUN4I_SPI0_TC (spi_base(dev) + 0x24)
#define SUN6I_SPI0_CCTL (spi_base(dev) + 0x24)
#define SUN6I_SPI0_GCR (spi_base(dev) + 0x04)
#define SUN6I_SPI0_TCR (spi_base(dev) + 0x08)
#define SUN6I_SPI0_FIFO_STA (spi_base(dev) + 0x1C)
#define SUN6I_SPI0_MBC (spi_base(dev) + 0x30)
#define SUN6I_SPI0_MTC (spi_base(dev) + 0x34)
#define SUN6I_SPI0_BCC (spi_base(dev) + 0x38)
#define SUN6I_SPI0_TXD (spi_base(dev) + 0x200)
#define SUN6I_SPI0_RXD (spi_base(dev) + 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)
#define CCM_SPI0_CLK_DIV_BY_32 (0x100f)
static uint32_t gpio_base(feldev_handle *dev)
{
soc_info_t *soc_info = dev->soc_info;
switch (soc_info->soc_id) {
case 0x1816: /* V536 */
case 0x1817: /* V831 */
case 0x1728: /* H6 */
case 0x1823: /* H616 */
return 0x0300B000;
default:
return 0x01C20800;
}
}
static uint32_t spi_base(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 */
case 0x1663: /* F1C100s */
case 0x1701: /* R40 */
return 0x01C05000;
case 0x1816: /* V536 */
case 0x1817: /* V831 */
case 0x1728: /* H6 */
case 0x1823: /* H616 */
return 0x05010000;
default:
return 0x01C68000;
}
}
/*
* 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 = gpio_base(dev) + 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;
}
}
static bool soc_is_h6_style(feldev_handle *dev)
{
soc_info_t *soc_info = dev->soc_info;
switch (soc_info->soc_id) {
case 0x1816: /* V536 */
case 0x1817: /* V831 */
case 0x1728: /* H6 */
case 0x1823: /* H616 */
return true;
default:
return false;
}
}
/*
* 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) {
printf("Unable to fetch device information. "
"Possibly unknown device.\n");
return false;
}
/* Setup SPI0 pins muxing */
switch (soc_info->soc_id) {
case 0x1663: /* Allwinner F1C100s/F1C600/R6/F1C100A/F1C500 */
gpio_set_cfgpin(dev, PC, 0, SUNIV_GPC_SPI0);
gpio_set_cfgpin(dev, PC, 1, SUNIV_GPC_SPI0);
gpio_set_cfgpin(dev, PC, 2, SUNIV_GPC_SPI0);
gpio_set_cfgpin(dev, PC, 3, SUNIV_GPC_SPI0);
break;
case 0x1625: /* Allwinner A13 */
case 0x1680: /* Allwinner H3 */
case 0x1681: /* Allwinner V3s */
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 0x1623: /* Allwinner A10 */
case 0x1651: /* Allwinner A20 */
case 0x1701: /* Allwinner R40 */
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, 23, 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;
case 0x1816: /* Allwinner V536 */
case 0x1817: /* Allwinner V831 */
gpio_set_cfgpin(dev, PC, 1, SUN50I_GPC_SPI0); /* SPI0-CS */
/* fall-through */
case 0x1728: /* Allwinner H6 */
gpio_set_cfgpin(dev, PC, 0, SUN50I_GPC_SPI0);
gpio_set_cfgpin(dev, PC, 2, SUN50I_GPC_SPI0);
gpio_set_cfgpin(dev, PC, 3, SUN50I_GPC_SPI0);
/* PC5 is SPI0-CS on the H6, and SPI0-HOLD on the V831 */
gpio_set_cfgpin(dev, PC, 5, SUN50I_GPC_SPI0);
break;
case 0x1823: /* Allwinner H616 */
gpio_set_cfgpin(dev, PC, 0, SUN50I_GPC_SPI0); /* SPI0_CLK */
gpio_set_cfgpin(dev, PC, 2, SUN50I_GPC_SPI0); /* SPI0_MOSI */
gpio_set_cfgpin(dev, PC, 3, SUN50I_GPC_SPI0); /* SPI0_CS0 */
gpio_set_cfgpin(dev, PC, 4, SUN50I_GPC_SPI0); /* SPI0_MISO */
break;
default: /* Unknown/Unsupported SoC */
printf("SPI support not implemented yet for %x (%s)!\n",
soc_info->soc_id, soc_info->name);
return false;
}
if (soc_is_h6_style(dev)) {
reg_val = readl(H6_CCM_SPI_BGR);
reg_val |= H6_CCM_SPI0_GATE_RESET;
writel(reg_val, H6_CCM_SPI_BGR);
} else {
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);
}
reg_val = readl(CCM_AHB_GATING0);
reg_val |= CCM_AHB_GATE_SPI0;
writel(reg_val, CCM_AHB_GATING0);
}
if (soc_info->soc_id == 0x1663) { /* suniv F1C100s */
/*
* suniv doesn't have a module clock for SPI0 and the clock
* source is always the AHB clock. Setup AHB to 200 MHz by
* setting PLL6 to 600 MHz with a divider of 3, then program
* the internal SPI dividier to 32.
*/
/* Set PLL6 to 600MHz */
writel(0x80041801, SUNIV_PLL6_CTL);
/* PLL6:AHB:APB = 6:2:1 */
writel(0x00003180, SUNIV_AHB_APB_CFG);
/* divide by 32 */
writel(CCM_SPI0_CLK_DIV_BY_32, SUN6I_SPI0_CCTL);
} else {
/* divide 24MHz OSC by 4 */
writel(CCM_SPI0_CLK_DIV_BY_4,
spi_is_sun6i(dev) ? SUN6I_SPI0_CCTL : SUN4I_SPI0_CCTL);
/* Choose 24MHz from OSC24M and enable clock */
writel(1U << 31,
soc_is_h6_style(dev) ? H6_CCM_SPI0_CLK : CCM_SPI0_CLK);
}
if (spi_is_sun6i(dev)) {
/* Enable SPI in the master mode and do a soft reset */
reg_val = readl(SUN6I_SPI0_GCR);
reg_val |= (1U << 31) | 3;
writel(reg_val, SUN6I_SPI0_GCR);
/* Wait for completion */
while (readl(SUN6I_SPI0_GCR) & (1U << 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;
case 0xC2:
manufacturer = "Macronix";
break;
case 0x1C:
manufacturer = "Eon";
break;
default:
manufacturer = "Unknown";
break;
}
printf("Manufacturer: %s (%02Xh), model: %02Xh, size: %d bytes.\n",
manufacturer, buf[3], buf[4], (1U << 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");
}