mirror of
https://github.com/linux-sunxi/sunxi-tools.git
synced 2024-11-26 19:33:58 +08:00
3c9bc29f39
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>
188 lines
5.3 KiB
C
188 lines
5.3 KiB
C
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|