Skip to content

Instantly share code, notes, and snippets.

@macromorgan
Last active March 1, 2021 15:26
Show Gist options
  • Save macromorgan/ce520e6bb268ee9a6e79b0e4d0aaa472 to your computer and use it in GitHub Desktop.
Save macromorgan/ce520e6bb268ee9a6e79b0e4d0aaa472 to your computer and use it in GitHub Desktop.
Rockchip SFC Driver for 5.11 Kernels
[ 30.239682] Internal error: synchronous external abort: 96000010 [#1] PREEMPT SMP
[ 30.253908] Modules linked in:
[ 30.257332] CPU: 0 PID: 404 Comm: fdisk Not tainted 5.11.0+ #62
[ 30.263956] Hardware name: ODROID-GO Advance (DT)
[ 30.269215] pstate: 60400005 (nZCv daif +PAN -UAO -TCO BTYPE=--)
[ 30.275935] pc : rockchip_sfc_wait_idle.constprop.0+0x30/0xac
[ 30.282376] lr : rockchip_sfc_wait_idle.constprop.0+0x20/0xac
[ 30.288802] sp : ffffffc01248ba70
[ 30.292503] x29: ffffffc01248ba70 x28: ffffff8004ba7400
[ 30.298452] x27: 000000007fffffff x26: 0000000000000000
[ 30.304398] x25: ffffff80014ac080 x24: 0000000000000000
[ 30.310345] x23: ffffff80014ac0f0 x22: 0000000000000000
[ 30.316290] x21: 0000000000000200 x20: 000000070a967589
[ 30.322238] x19: ffffff80014ac080 x18: 0000000000000000
[ 30.328187] x17: 0000000000000000 x16: 0000000000000000
[ 30.334132] x15: 0000000000000000 x14: 0000000000000000
[ 30.340078] x13: 0000000000000000 x12: 0000000000000000
[ 30.346023] x11: 0000000000000000 x10: 0000000000000000
[ 30.351968] x9 : ffffffc0100d5f34 x8 : 0000000000000000
[ 30.357915] x7 : 0000000000000000 x6 : 0000000000000000
[ 30.363860] x5 : 0000000000000007 x4 : 0000000000000002
[ 30.369808] x3 : 0000000000000000 x2 : 0000000029aaaaab
[ 30.375744] x1 : ffffffc0120e4024 x0 : 0000000709fddf09
[ 30.381695] Call trace:
[ 30.384427] rockchip_sfc_wait_idle.constprop.0+0x30/0xac
[ 30.390468] rockchip_sfc_setup_transfer+0x8c/0x130
[ 30.395926] rockchip_sfc_do_rd_wr+0x1b0/0x1dc
[ 30.400899] rockchip_sfc_read+0x1c/0x28
[ 30.405290] spi_nor_read_data+0x150/0x170
[ 30.409870] spi_nor_read+0xa8/0xfc
[ 30.413776] mtd_read_oob_std+0x70/0x74
[ 30.418071] mtd_read_oob+0xbc/0x13c
[ 30.422074] mtd_read+0x4c/0x7c
[ 30.425588] mtdchar_read+0x18c/0x238
[ 30.429687] vfs_read+0x90/0x120
[ 30.433302] ksys_read+0x78/0xe4
[ 30.436912] __arm64_sys_read+0x20/0x2c
[ 30.441203] el0_svc_common.constprop.0+0xb0/0x120
[ 30.446566] do_el0_svc+0x50/0x80
[ 30.450275] el0_svc+0x24/0x34
[ 30.453696] el0_sync_handler+0xcc/0x154
[ 30.458087] el0_sync+0x174/0x180
[ 30.461806] Code: 91662414 911a0294 f9401661 91009021 (b9400021)
[ 30.468623] ---[ end trace 4da6b643b4cc25f7 ]---
[ 32.625992] SError Interrupt on CPU1, code 0xbf000000 -- SError
[ 32.626009] CPU: 1 PID: 403 Comm: fdisk Not tainted 5.11.0+ #64
[ 32.626014] Hardware name: ODROID-GO Advance (DT)
[ 32.626017] pstate: 60400005 (nZCv daif +PAN -UAO -TCO BTYPE=--)
[ 32.626020] pc : el1_abort+0x34/0x64
[ 32.626024] lr : el1_abort+0x28/0x64
[ 32.626027] sp : ffffffc011fcb950
[ 32.626030] x29: ffffffc011fcb950 x28: ffffff8000b05700
[ 32.626043] x27: ffffff8000d3c0d0 x26: 0000000000000000
[ 32.626051] x25: 0000000000000000 x24: ffffff80055d7400
[ 32.626060] x23: 0000000080400005 x22: ffffffc010689950
[ 32.626068] x21: ffffffc0120e4004 x20: 0000000096000010
[ 32.626076] x19: ffffffc011fcb990 x18: 0000000000000000
[ 32.626085] x17: 0000000000000000 x16: 0000000000000000
[ 32.626094] x15: 0000000000000000 x14: 0000000000000000
[ 32.626102] x13: 0000000000000000 x12: 0000000000000000
[ 32.626110] x11: 0000000000000000 x10: 0000000000000000
[ 32.626118] x9 : ffffffc01068993c x8 : 0000000000000000
[ 32.626127] x7 : 0000000000000000 x6 : 0000000000000000
[ 32.626135] x5 : 0000000000000007 x4 : 0000000000000000
[ 32.626144] x3 : ffffffc010b7d74c x2 : ffffffc010b7d778
[ 32.626152] x1 : 0000000096000010 x0 : 0000000000000000
[ 32.626161] Kernel panic - not syncing: Asynchronous SError Interrupt
[ 32.626165] CPU: 1 PID: 403 Comm: fdisk Not tainted 5.11.0+ #64
[ 32.626168] Hardware name: ODROID-GO Advance (DT)
[ 32.626172] Call trace:
[ 32.626174] dump_backtrace+0x0/0x1dc
[ 32.626177] show_stack+0x20/0x6c
[ 32.626180] dump_stack+0xcc/0x12c
[ 32.626183] panic+0x148/0x320
[ 32.626186] nmi_panic+0x54/0x78
[ 32.626189] arm64_serror_panic+0x78/0x84
[ 32.626192] do_serror+0x38/0x64
[ 32.626195] el1_error+0x7c/0xf8
[ 32.626198] el1_abort+0x34/0x64
[ 32.626201] el1_sync_handler+0x8c/0xb8
[ 32.626204] el1_sync+0x74/0x100
[ 32.626207] rockchip_sfc_do_rd_wr+0xbc/0x1dc
[ 32.626210] rockchip_sfc_read+0x1c/0x28
[ 32.626214] spi_nor_read_data+0x150/0x170
[ 32.626217] spi_nor_read+0xa8/0xfc
[ 32.626220] mtd_read_oob_std+0x70/0x74
[ 32.626223] mtd_read_oob+0xbc/0x13c
[ 32.626226] mtd_read+0x4c/0x7c
[ 32.626228] mtdchar_read+0x18c/0x238
[ 32.626232] vfs_read+0x90/0x120
[ 32.626235] ksys_read+0x78/0xe4
[ 32.626238] __arm64_sys_read+0x20/0x2c
[ 32.626241] el0_svc_common.constprop.0+0xb0/0x120
[ 32.626244] do_el0_svc+0x50/0x80
[ 32.626247] el0_svc+0x24/0x34
[ 32.626250] el0_sync_handler+0xcc/0x154
[ 32.626253] el0_sync+0x174/0x180
[ 32.626376] SMP: stopping secondary CPUs
[ 32.626380] Kernel Offset: disabled
[ 32.626383] CPU features: 0x00040002,20002000
[ 32.626386] Memory Limit: none
/*
* Rockchip Serial Flash Controller Driver
*
* Copyright (c) 2017, Rockchip Inc.
* Author: Shawn Lin <[email protected]>
*
* 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 <linux/bitops.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/dma-mapping.h>
#include <linux/iopoll.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/spi-nor.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
/* System control */
#define SFC_CTRL 0x0
#define SFC_CTRL_COMMON_BITS_1 0x0
#define SFC_CTRL_COMMON_BITS_2 0x1
#define SFC_CTRL_COMMON_BITS_4 0x2
#define SFC_CTRL_DATA_BITS_SHIFT 12
#define SFC_CTRL_ADDR_BITS_SHIFT 10
#define SFC_CTRL_CMD_BITS_SHIFT 8
#define SFC_CTRL_PHASE_SEL_NEGETIVE BIT(1)
/* Interrupt mask */
#define SFC_IMR 0x4
#define SFC_IMR_RX_FULL BIT(0)
#define SFC_IMR_RX_UFLOW BIT(1)
#define SFC_IMR_TX_OFLOW BIT(2)
#define SFC_IMR_TX_EMPTY BIT(3)
#define SFC_IMR_TRAN_FINISH BIT(4)
#define SFC_IMR_BUS_ERR BIT(5)
#define SFC_IMR_NSPI_ERR BIT(6)
#define SFC_IMR_DMA BIT(7)
/* Interrupt clear */
#define SFC_ICLR 0x8
#define SFC_ICLR_RX_FULL BIT(0)
#define SFC_ICLR_RX_UFLOW BIT(1)
#define SFC_ICLR_TX_OFLOW BIT(2)
#define SFC_ICLR_TX_EMPTY BIT(3)
#define SFC_ICLR_TRAN_FINISH BIT(4)
#define SFC_ICLR_BUS_ERR BIT(5)
#define SFC_ICLR_NSPI_ERR BIT(6)
#define SFC_ICLR_DMA BIT(7)
/* FIFO threshold level */
#define SFC_FTLR 0xc
#define SFC_FTLR_TX_SHIFT 0
#define SFC_FTLR_TX_MASK 0x1f
#define SFC_FTLR_RX_SHIFT 8
#define SFC_FTLR_RX_MASK 0x1f
/* Reset FSM and FIFO */
#define SFC_RCVR 0x10
#define SFC_RCVR_RESET BIT(0)
/* Enhanced mode */
#define SFC_AX 0x14
/* Address Bit number */
#define SFC_ABIT 0x18
/* Interrupt status */
#define SFC_ISR 0x1c
#define SFC_ISR_RX_FULL_SHIFT BIT(0)
#define SFC_ISR_RX_UFLOW_SHIFT BIT(1)
#define SFC_ISR_TX_OFLOW_SHIFT BIT(2)
#define SFC_ISR_TX_EMPTY_SHIFT BIT(3)
#define SFC_ISR_TX_FINISH_SHIFT BIT(4)
#define SFC_ISR_BUS_ERR_SHIFT BIT(5)
#define SFC_ISR_NSPI_ERR_SHIFT BIT(6)
#define SFC_ISR_DMA_SHIFT BIT(7)
/* FIFO status */
#define SFC_FSR 0x20
#define SFC_FSR_TX_IS_FULL BIT(0)
#define SFC_FSR_TX_IS_EMPTY BIT(1)
#define SFC_FSR_RX_IS_EMPTY BIT(2)
#define SFC_FSR_RX_IS_FULL BIT(3)
#define SFC_FSR_TXLV_MASK GENMASK(12, 8)
#define SFC_FSR_TXLV_SHIFT 8
#define SFC_FSR_RXLV_MASK GENMASK(20, 16)
#define SFC_FSR_RXLV_SHIFT 16
/* FSM status */
#define SFC_SR 0x24
#define SFC_SR_IS_IDLE 0x0
#define SFC_SR_IS_BUSY 0x1
/* Raw interrupt status */
#define SFC_RISR 0x28
#define SFC_RISR_RX_FULL BIT(0)
#define SFC_RISR_RX_UNDERFLOW BIT(1)
#define SFC_RISR_TX_OVERFLOW BIT(2)
#define SFC_RISR_TX_EMPTY BIT(3)
#define SFC_RISR_TRAN_FINISH BIT(4)
#define SFC_RISR_BUS_ERR BIT(5)
#define SFC_RISR_NSPI_ERR BIT(6)
#define SFC_RISR_DMA BIT(7)
/* Master trigger */
#define SFC_DMA_TRIGGER 0x80
/* Src or Dst addr for master */
#define SFC_DMA_ADDR 0x84
/* Command */
#define SFC_CMD 0x100
#define SFC_CMD_IDX_SHIFT 0
#define SFC_CMD_DUMMY_SHIFT 8
#define SFC_CMD_DIR_RD 0
#define SFC_CMD_DIR_WR 1
#define SFC_CMD_DIR_SHIFT 12
#define SFC_CMD_ADDR_ZERO (0x0 << 14)
#define SFC_CMD_ADDR_24BITS (0x1 << 14)
#define SFC_CMD_ADDR_32BITS (0x2 << 14)
#define SFC_CMD_ADDR_FRS (0x3 << 14)
#define SFC_CMD_TRAN_BYTES_SHIFT 16
#define SFC_CMD_CS_SHIFT 30
/* Address */
#define SFC_ADDR 0x104
/* Data */
#define SFC_DATA 0x108
#define SFC_MAX_CHIPSELECT_NUM 4
/* The SFC can transfer max 16KB - 1 at one time
* we set it to 15.5KB here for alignment.
*/
#define SFC_MAX_TRANS_BYTES (512 * 31)
#define SFC_CMD_DUMMY(x) \
((x) << SFC_CMD_DUMMY_SHIFT)
enum rockchip_sfc_iftype {
IF_TYPE_STD,
IF_TYPE_DUAL,
IF_TYPE_QUAD,
};
struct rockchip_sfc;
struct rockchip_sfc_chip_priv {
u8 cs;
u32 clk_rate;
struct spi_nor nor;
struct rockchip_sfc *sfc;
};
struct rockchip_sfc {
struct device *dev;
struct mutex lock;
void __iomem *regbase;
struct clk *hclk;
struct clk *clk;
/* virtual mapped addr for dma_buffer */
void *buffer;
dma_addr_t dma_buffer;
struct completion cp;
struct rockchip_sfc_chip_priv flash[SFC_MAX_CHIPSELECT_NUM];
u32 num_chip;
bool use_dma;
};
static int get_if_type(struct rockchip_sfc *sfc, enum spi_nor_protocol proto)
{
if (proto == SNOR_PROTO_1_1_2)
return IF_TYPE_DUAL;
else if (proto == SNOR_PROTO_1_1_4)
return IF_TYPE_QUAD;
else if (proto == SNOR_PROTO_1_1_1)
return IF_TYPE_STD;
dev_err(sfc->dev, "unsupported SPI read mode\n");
return -EINVAL;
}
static int rockchip_sfc_reset(struct rockchip_sfc *sfc)
{
int err;
u32 status;
writel_relaxed(SFC_RCVR_RESET, sfc->regbase + SFC_RCVR);
err = readl_poll_timeout(sfc->regbase + SFC_RCVR, status,
!(status & SFC_RCVR_RESET), 20,
jiffies_to_usecs(HZ));
if (err)
dev_err(sfc->dev, "SFC reset never finished\n");
/* Still need to clear the masked interrupt from RISR */
writel_relaxed(SFC_ICLR_RX_FULL | SFC_ICLR_RX_UFLOW |
SFC_ICLR_TX_OFLOW | SFC_ICLR_TX_EMPTY |
SFC_ICLR_TRAN_FINISH | SFC_ICLR_BUS_ERR |
SFC_ICLR_NSPI_ERR | SFC_ICLR_DMA,
sfc->regbase + SFC_ICLR);
dev_info(sfc->dev, "reset\n");
return err;
}
static int rockchip_sfc_init(struct rockchip_sfc *sfc)
{
int err;
err = rockchip_sfc_reset(sfc);
if (err)
return err;
/* Mask all eight interrupts */
writel_relaxed(0xff, sfc->regbase + SFC_IMR);
writel_relaxed(SFC_CTRL_PHASE_SEL_NEGETIVE, sfc->regbase + SFC_CTRL);
return 0;
}
static int rockchip_sfc_prep(struct spi_nor *nor)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
int ret;
mutex_lock(&sfc->lock);
pm_runtime_get_sync(sfc->dev);
ret = clk_set_rate(sfc->clk, priv->clk_rate);
if (ret)
goto out;
ret = clk_prepare_enable(sfc->clk);
if (ret)
goto out;
return 0;
out:
mutex_unlock(&sfc->lock);
return ret;
}
static void rockchip_sfc_unprep(struct spi_nor *nor)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
clk_disable_unprepare(sfc->clk);
mutex_unlock(&sfc->lock);
pm_runtime_mark_last_busy(sfc->dev);
pm_runtime_put_autosuspend(sfc->dev);
}
static inline int rockchip_sfc_get_fifo_level(struct rockchip_sfc *sfc, int wr)
{
u32 fsr = readl_relaxed(sfc->regbase + SFC_FSR);
int level;
if (wr)
level = (fsr & SFC_FSR_TXLV_MASK) >> SFC_FSR_TXLV_SHIFT;
else
level = (fsr & SFC_FSR_RXLV_MASK) >> SFC_FSR_RXLV_SHIFT;
return level;
}
static int rockchip_sfc_wait_fifo_ready(struct rockchip_sfc *sfc, int wr, u32 timeout)
{
unsigned long deadline = jiffies + timeout;
int level;
while (!(level = rockchip_sfc_get_fifo_level(sfc, wr))) {
if (time_after_eq(jiffies, deadline)) {
dev_warn(sfc->dev, "%s fifo timeout\n", wr ? "write" : "read");
return -ETIMEDOUT;
}
udelay(1);
}
return level;
}
/* The SFC_CTRL register is a global control register,
* when the controller is in busy state(SFC_SR),
* SFC_CTRL cannot be set.
*/
static void rockchip_sfc_wait_idle(struct rockchip_sfc *sfc, u32 timeout_us)
{
u32 status;
int ret;
ret = readl_poll_timeout(sfc->regbase + SFC_SR, status,
!(status & SFC_SR_IS_BUSY),
20, timeout_us);
if (ret) {
dev_err(sfc->dev, "wait sfc idle timeout\n");
rockchip_sfc_reset(sfc);
}
}
static void rockchip_sfc_setup_ctrl(struct rockchip_sfc *sfc)
{
u32 reg;
reg = IF_TYPE_STD << SFC_CTRL_DATA_BITS_SHIFT;
reg |= IF_TYPE_STD << SFC_CTRL_ADDR_BITS_SHIFT;
reg |= IF_TYPE_STD << SFC_CTRL_CMD_BITS_SHIFT;
reg |= SFC_CTRL_PHASE_SEL_NEGETIVE;
rockchip_sfc_wait_idle(sfc, 10000);
writel_relaxed(reg, sfc->regbase + SFC_CTRL);
}
static int rockchip_sfc_op_reg(struct spi_nor *nor,
u8 opcode, int len, u8 optype)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
u32 reg;
rockchip_sfc_setup_ctrl(sfc);
reg = opcode << SFC_CMD_IDX_SHIFT;
reg |= len << SFC_CMD_TRAN_BYTES_SHIFT;
reg |= priv->cs << SFC_CMD_CS_SHIFT;
reg |= optype << SFC_CMD_DIR_SHIFT;
writel_relaxed(reg, sfc->regbase + SFC_CMD);
return 0;
}
static int rockchip_sfc_write_fifo(struct rockchip_sfc *sfc, const u8 *buf, int len)
{
u8 bytes = len & 0x3;
u32 dwords;
int tx_level;
u32 write_words;
u32 tmp = 0;
if (len >= 4) {
dwords = len >> 2;
while (dwords) {
tx_level = rockchip_sfc_wait_fifo_ready(sfc, SFC_CMD_DIR_WR, HZ);
if (tx_level < 0)
return tx_level;
write_words = min_t(u32, tx_level, dwords);
iowrite32_rep(sfc->regbase + SFC_DATA, buf, write_words);
buf += write_words << 2;
dwords -= write_words;
}
}
/* write the rest non word aligned bytes */
if (bytes) {
tx_level = rockchip_sfc_wait_fifo_ready(sfc, SFC_CMD_DIR_WR, HZ);
if (tx_level < 0)
return tx_level;
memcpy(&tmp, buf, bytes);
writel_relaxed(tmp, sfc->regbase + SFC_DATA);
}
return len;
}
static int rockchip_sfc_read_fifo(struct rockchip_sfc *sfc, u8 *buf, int len)
{
u8 bytes = len & 0x3;
u32 dwords;
u8 read_words;
int rx_level;
int tmp;
/* word aligned access only */
if (len >= 4) {
dwords = len >> 2;
while (dwords) {
rx_level = rockchip_sfc_wait_fifo_ready(sfc, SFC_CMD_DIR_RD, HZ);
if (rx_level < 0)
return rx_level;
read_words = min_t(u32, rx_level, dwords);
ioread32_rep(sfc->regbase + SFC_DATA, buf, read_words);
buf += read_words << 2;
dwords -= read_words;
}
}
/* read the rest non word aligned bytes */
if (bytes) {
rx_level = rockchip_sfc_wait_fifo_ready(sfc, SFC_CMD_DIR_RD, HZ);
if (rx_level < 0)
return rx_level;
tmp = readl_relaxed(sfc->regbase + SFC_DATA);
memcpy(buf, &tmp, bytes);
}
return len;
}
static int rockchip_sfc_read_reg(struct spi_nor *nor, u8 opcode,
u8 *buf, size_t len)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
int ret;
int trans;
trans = min_t(int, len, SFC_MAX_TRANS_BYTES);
ret = rockchip_sfc_op_reg(nor, opcode, trans, SFC_CMD_DIR_RD);
if (ret)
return ret;
ret = rockchip_sfc_read_fifo(sfc, buf, trans);
if (ret < 0)
return ret;
return 0;
}
static int rockchip_sfc_write_reg(struct spi_nor *nor, u8 opcode,
const u8 *buf, size_t len)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
int ret;
ret = rockchip_sfc_op_reg(nor, opcode, len, SFC_CMD_DIR_WR);
if (ret)
return ret;
ret = rockchip_sfc_write_fifo(sfc, buf, len);
if (ret < 0)
return ret;
return 0;
}
static int rockchip_sfc_erase(struct spi_nor *nor, loff_t offs)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
u32 reg;
rockchip_sfc_setup_ctrl(sfc);
reg = nor->erase_opcode << SFC_CMD_IDX_SHIFT;
reg |= (nor->addr_width == 4) ?
SFC_CMD_ADDR_32BITS : SFC_CMD_ADDR_24BITS;
reg |= priv->cs << SFC_CMD_CS_SHIFT;
reg |= SFC_CMD_DIR_WR << SFC_CMD_DIR_SHIFT;
writel_relaxed(reg, sfc->regbase + SFC_CMD);
writel_relaxed(offs, sfc->regbase + SFC_ADDR);
return 0;
}
static int rockchip_sfc_setup_transfer(struct spi_nor *nor,
loff_t from_to,
size_t len, u8 op_type)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
u8 if_type = IF_TYPE_STD;
u32 reg;
if (op_type == SFC_CMD_DIR_RD)
if_type = get_if_type(sfc, nor->read_proto);
rockchip_sfc_wait_idle(sfc, 10000);
writel_relaxed((if_type << SFC_CTRL_DATA_BITS_SHIFT) |
(IF_TYPE_STD << SFC_CTRL_ADDR_BITS_SHIFT) |
(IF_TYPE_STD << SFC_CTRL_CMD_BITS_SHIFT) |
SFC_CTRL_PHASE_SEL_NEGETIVE,
sfc->regbase + SFC_CTRL);
if (op_type == SFC_CMD_DIR_WR)
reg = nor->program_opcode << SFC_CMD_IDX_SHIFT;
else
reg = nor->read_opcode << SFC_CMD_IDX_SHIFT;
reg |= op_type << SFC_CMD_DIR_SHIFT;
reg |= (nor->addr_width == 4) ?
SFC_CMD_ADDR_32BITS : SFC_CMD_ADDR_24BITS;
reg |= priv->cs << SFC_CMD_CS_SHIFT;
reg |= len << SFC_CMD_TRAN_BYTES_SHIFT;
if (op_type == SFC_CMD_DIR_RD)
reg |= SFC_CMD_DUMMY(nor->read_dummy);
writel_relaxed(reg, sfc->regbase + SFC_CMD);
writel_relaxed(from_to, sfc->regbase + SFC_ADDR);
return 0;
}
static int rockchip_sfc_do_dma_transfer(struct spi_nor *nor, loff_t from_to,
dma_addr_t dma_buf, size_t len,
u8 op_type)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
u32 reg;
int err = 0;
init_completion(&sfc->cp);
writel_relaxed(SFC_ICLR_RX_FULL | SFC_ICLR_RX_UFLOW |
SFC_ICLR_TX_OFLOW | SFC_ICLR_TX_EMPTY |
SFC_ICLR_TRAN_FINISH | SFC_ICLR_BUS_ERR |
SFC_ICLR_NSPI_ERR | SFC_ICLR_DMA,
sfc->regbase + SFC_ICLR);
/* Enable transfer complete interrupt */
reg = readl_relaxed(sfc->regbase + SFC_IMR);
reg &= ~SFC_IMR_TRAN_FINISH;
writel_relaxed(reg, sfc->regbase + SFC_IMR);
err = rockchip_sfc_setup_transfer(nor, from_to, len, op_type);
if (err < 0)
return err;
writel_relaxed(dma_buf, sfc->regbase + SFC_DMA_ADDR);
/*
* Start dma but note that the sfc->dma_buffer is derived from
* dmam_alloc_coherent so we don't actually need any sync operations
* for coherent dma memory.
*/
writel_relaxed(0x1, sfc->regbase + SFC_DMA_TRIGGER);
/* Wait for the interrupt. */
if (!wait_for_completion_timeout(&sfc->cp, msecs_to_jiffies(2000))) {
dev_err(sfc->dev, "DMA wait for transfer finish timeout\n");
err = -ETIMEDOUT;
}
writel_relaxed(SFC_ICLR_RX_FULL | SFC_ICLR_RX_UFLOW |
SFC_ICLR_TX_OFLOW | SFC_ICLR_TX_EMPTY |
SFC_ICLR_TRAN_FINISH | SFC_ICLR_BUS_ERR |
SFC_ICLR_NSPI_ERR | SFC_ICLR_DMA,
sfc->regbase + SFC_ICLR);
/* Disable transfer finish interrupt */
reg = readl_relaxed(sfc->regbase + SFC_IMR);
reg |= SFC_IMR_TRAN_FINISH;
writel_relaxed(reg, sfc->regbase + SFC_IMR);
if (err) {
rockchip_sfc_reset(sfc);
return err;
}
return 0;
}
static inline int rockchip_sfc_pio_write(struct rockchip_sfc *sfc, u_char *buf,
size_t len)
{
return rockchip_sfc_write_fifo(sfc, buf, len);
}
static inline int rockchip_sfc_pio_read(struct rockchip_sfc *sfc, u_char *buf,
size_t len)
{
return rockchip_sfc_read_fifo(sfc, buf, len);
}
static int rockchip_sfc_pio_transfer(struct spi_nor *nor, loff_t from_to,
size_t len, u_char *buf, u8 op_type)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
size_t trans;
int ret;
trans = min_t(size_t, SFC_MAX_TRANS_BYTES, len);
ret = rockchip_sfc_setup_transfer(nor, from_to, trans, op_type);
if (ret < 0)
return ret;
if (op_type == SFC_CMD_DIR_WR)
ret = rockchip_sfc_pio_write(sfc, buf, trans);
else
ret = rockchip_sfc_pio_read(sfc, buf, trans);
return ret;
}
static int rockchip_sfc_dma_transfer(struct spi_nor *nor, loff_t from_to,
size_t len, u_char *buf, u8 op_type)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
size_t trans;
int ret;
trans = min_t(size_t, SFC_MAX_TRANS_BYTES, len);
if (op_type == SFC_CMD_DIR_WR)
memcpy(sfc->buffer, buf, trans);
ret = rockchip_sfc_do_dma_transfer(nor, from_to, sfc->dma_buffer,
trans, op_type);
if (ret) {
dev_warn(nor->dev, "DMA timeout\n");
return ret;
}
if (op_type == SFC_CMD_DIR_RD)
memcpy(buf, sfc->buffer, trans);
return trans;
}
static ssize_t rockchip_sfc_do_rd_wr(struct spi_nor *nor, loff_t from_to,
size_t len, u_char *buf, u32 op_type)
{
struct rockchip_sfc_chip_priv *priv = nor->priv;
struct rockchip_sfc *sfc = priv->sfc;
/* DMA can only handle word anligned transfer chunks */
if (likely(sfc->use_dma) && !(len & 0x3))
return rockchip_sfc_dma_transfer(nor, from_to, len, buf, op_type);
else
return rockchip_sfc_pio_transfer(nor, from_to, len,
(u_char *)buf, op_type);
}
static ssize_t rockchip_sfc_read(struct spi_nor *nor, loff_t from,
size_t len, u_char *read_buf)
{
return rockchip_sfc_do_rd_wr(nor, from, len,
read_buf, SFC_CMD_DIR_RD);
}
static ssize_t rockchip_sfc_write(struct spi_nor *nor, loff_t to,
size_t len, const u_char *write_buf)
{
return rockchip_sfc_do_rd_wr(nor, to, len,
(u_char *)write_buf,
SFC_CMD_DIR_WR);
}
static const struct spi_nor_controller_ops rockchip_sfc_controller_ops = {
.prepare = rockchip_sfc_prep,
.unprepare = rockchip_sfc_unprep,
.read_reg = rockchip_sfc_read_reg,
.write_reg = rockchip_sfc_write_reg,
.read = rockchip_sfc_read,
.write = rockchip_sfc_write,
.erase = rockchip_sfc_erase,
};
static int rockchip_sfc_register(struct device_node *np,
struct rockchip_sfc *sfc)
{
const struct spi_nor_hwcaps hwcaps = {
.mask = SNOR_HWCAPS_READ |
SNOR_HWCAPS_READ_FAST |
SNOR_HWCAPS_READ_1_1_2 |
SNOR_HWCAPS_READ_1_1_4 |
SNOR_HWCAPS_PP,
};
struct device *dev = sfc->dev;
struct mtd_info *mtd;
struct spi_nor *nor;
int ret;
nor = &sfc->flash[sfc->num_chip].nor;
nor->dev = dev;
spi_nor_set_flash_node(nor, np);
ret = of_property_read_u8(np, "reg", &sfc->flash[sfc->num_chip].cs);
if (ret) {
dev_err(dev, "No reg property for %s\n",
np->full_name);
return ret;
}
ret = of_property_read_u32(np, "spi-max-frequency",
&sfc->flash[sfc->num_chip].clk_rate);
if (ret) {
dev_err(dev, "No spi-max-frequency property for %s\n",
np->full_name);
return ret;
}
sfc->flash[sfc->num_chip].sfc = sfc;
nor->priv = &sfc->flash[sfc->num_chip];
nor->controller_ops = &rockchip_sfc_controller_ops;
ret = spi_nor_scan(nor, NULL, &hwcaps);
if (ret)
return ret;
mtd = &nor->mtd;
mtd->name = np->name;
ret = mtd_device_register(mtd, NULL, 0);
if (ret)
return ret;
sfc->num_chip++;
return 0;
}
static void rockchip_sfc_unregister_all(struct rockchip_sfc *sfc)
{
int i;
for (i = 0; i < sfc->num_chip; i++)
mtd_device_unregister(&sfc->flash[i].nor.mtd);
}
static int rockchip_sfc_register_all(struct rockchip_sfc *sfc)
{
struct device *dev = sfc->dev;
struct device_node *np;
int ret;
for_each_available_child_of_node(dev->of_node, np) {
ret = rockchip_sfc_register(np, sfc);
if (ret)
goto fail;
if (sfc->num_chip == SFC_MAX_CHIPSELECT_NUM) {
dev_warn(dev, "Exceeds the max cs limitation\n");
break;
}
}
return 0;
fail:
dev_err(dev, "Failed to register all chips\n");
/* Unregister all the _registered_ nor flash */
rockchip_sfc_unregister_all(sfc);
return ret;
}
static irqreturn_t rockchip_sfc_irq_handler(int irq, void *dev_id)
{
struct rockchip_sfc *sfc = dev_id;
u32 reg;
reg = readl_relaxed(sfc->regbase + SFC_RISR);
/* Clear interrupt */
writel_relaxed(reg, sfc->regbase + SFC_ICLR);
if (reg & SFC_RISR_TRAN_FINISH)
complete(&sfc->cp);
return IRQ_HANDLED;
}
static int rockchip_sfc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct rockchip_sfc *sfc;
int ret;
sfc = devm_kzalloc(dev, sizeof(*sfc), GFP_KERNEL);
if (!sfc)
return -ENOMEM;
platform_set_drvdata(pdev, sfc);
sfc->dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
sfc->regbase = devm_ioremap_resource(dev, res);
if (IS_ERR(sfc->regbase))
return PTR_ERR(sfc->regbase);
sfc->clk = devm_clk_get(&pdev->dev, "sfc");
if (IS_ERR(sfc->clk)) {
dev_err(&pdev->dev, "Failed to get sfc interface clk\n");
return PTR_ERR(sfc->clk);
}
sfc->hclk = devm_clk_get(&pdev->dev, "hsfc");
if (IS_ERR(sfc->hclk)) {
dev_err(&pdev->dev, "Failed to get sfc ahp clk\n");
return PTR_ERR(sfc->hclk);
}
ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
if (ret) {
dev_warn(dev, "Unable to set dma mask\n");
return ret;
}
sfc->buffer = dmam_alloc_coherent(dev, SFC_MAX_TRANS_BYTES,
&sfc->dma_buffer,
GFP_KERNEL);
if (!sfc->buffer)
return -ENOMEM;
mutex_init(&sfc->lock);
ret = clk_prepare_enable(sfc->hclk);
if (ret) {
dev_err(&pdev->dev, "Failed to enable hclk\n");
goto err_hclk;
}
ret = clk_prepare_enable(sfc->clk);
if (ret) {
dev_err(&pdev->dev, "Failed to enable clk\n");
goto err_clk;
}
sfc->use_dma = !of_property_read_bool(sfc->dev->of_node,
"rockchip,sfc-no-dma");
/* Find the irq */
ret = platform_get_irq(pdev, 0);
if (ret < 0) {
dev_err(dev, "Failed to get the irq\n");
goto err_irq;
}
ret = devm_request_irq(dev, ret, rockchip_sfc_irq_handler,
0, pdev->name, sfc);
if (ret) {
dev_err(dev, "Failed to request irq\n");
goto err_irq;
}
sfc->num_chip = 0;
ret = rockchip_sfc_init(sfc);
if (ret)
goto err_irq;
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
pm_runtime_use_autosuspend(&pdev->dev);
ret = rockchip_sfc_register_all(sfc);
if (ret)
goto err_register;
clk_disable_unprepare(sfc->clk);
pm_runtime_put_autosuspend(&pdev->dev);
return 0;
err_register:
pm_runtime_disable(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
err_irq:
clk_disable_unprepare(sfc->clk);
err_clk:
clk_disable_unprepare(sfc->hclk);
err_hclk:
mutex_destroy(&sfc->lock);
return ret;
}
static int rockchip_sfc_remove(struct platform_device *pdev)
{
struct rockchip_sfc *sfc = platform_get_drvdata(pdev);
pm_runtime_get_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
rockchip_sfc_unregister_all(sfc);
mutex_destroy(&sfc->lock);
clk_disable_unprepare(sfc->clk);
clk_disable_unprepare(sfc->hclk);
return 0;
}
/*#ifdef CONFIG_PM
int rockchip_sfc_runtime_suspend(struct device *dev)
{
struct rockchip_sfc *sfc = dev_get_drvdata(dev);
clk_disable_unprepare(sfc->hclk);
return 0;
}
int rockchip_sfc_runtime_resume(struct device *dev)
{
struct rockchip_sfc *sfc = dev_get_drvdata(dev);
clk_prepare_enable(sfc->hclk);
return rockchip_sfc_reset(sfc);
}
#endif /* CONFIG_PM */
static const struct of_device_id rockchip_sfc_dt_ids[] = {
{ .compatible = "rockchip,sfc"},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_sfc_dt_ids);
static const struct dev_pm_ops rockchip_sfc_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
// SET_RUNTIME_PM_OPS(rockchip_sfc_runtime_suspend,
// rockchip_sfc_runtime_resume, NULL)
};
static struct platform_driver rockchip_sfc_driver = {
.driver = {
.name = "rockchip-sfc",
.of_match_table = rockchip_sfc_dt_ids,
.pm = &rockchip_sfc_dev_pm_ops,
},
.probe = rockchip_sfc_probe,
.remove = rockchip_sfc_remove,
};
module_platform_driver(rockchip_sfc_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Rockchip Serial Flash Controller Driver");
MODULE_AUTHOR("Shawn Lin <[email protected]>");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment