mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 03:15:31 +09:00
spi: rockchip-flexbus-fspi: Add code
Change-Id: Iad406f70ef4958259f63ef117534ce35fe439b85 Signed-off-by: Jon Lin <jon.lin@rock-chips.com>
This commit is contained in:
@@ -772,6 +772,16 @@ config SPI_ROCKCHIP_MISCDEV
|
||||
for each spi controller and support to get the controller register
|
||||
resource by calling mmap.
|
||||
|
||||
config SPI_ROCKCHIP_FLEXBUS_FSPI
|
||||
tristate "Rockchip Flexbus controller under SPI transmission protocol"
|
||||
depends on ARCH_ROCKCHIP || COMPILE_TEST
|
||||
depends on HAS_IOMEM && HAS_DMA
|
||||
select MFD_ROCKCHIP_FLEXBUS
|
||||
help
|
||||
This enables support for Rockchip Flexbus controller under SPI
|
||||
transmission protocol. This is a specialized controller used to access
|
||||
SPI flash on some Rockchip SOCs.
|
||||
|
||||
config SPI_ROCKCHIP_SFC
|
||||
tristate "Rockchip Serial Flash Controller (SFC)"
|
||||
imply ROCKCHIP_MTD_VENDOR_STORAGE
|
||||
|
||||
@@ -105,6 +105,7 @@ obj-$(CONFIG_SPI_QCOM_GENI) += spi-geni-qcom.o
|
||||
obj-$(CONFIG_SPI_QCOM_QSPI) += spi-qcom-qspi.o
|
||||
obj-$(CONFIG_SPI_QUP) += spi-qup.o
|
||||
obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o
|
||||
obj-$(CONFIG_SPI_ROCKCHIP_FLEXBUS_FSPI) += spi-rockchip-flexbus-fspi.o
|
||||
obj-$(CONFIG_SPI_ROCKCHIP_SFC) += spi-rockchip-sfc.o
|
||||
obj-$(CONFIG_SPI_ROCKCHIP_SLAVE) += spi-rockchip-slave.o
|
||||
obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o
|
||||
|
||||
745
drivers/spi/spi-rockchip-flexbus-fspi.c
Normal file
745
drivers/spi/spi-rockchip-flexbus-fspi.c
Normal file
@@ -0,0 +1,745 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Driver for Rockchip Flexbus FSPI mode
|
||||
*
|
||||
* Copyright (C) 2024 Rockchip Electronics Co., Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spi/spi-mem.h>
|
||||
|
||||
#include <dt-bindings/mfd/rockchip-flexbus.h>
|
||||
#include <linux/mfd/rockchip-flexbus.h>
|
||||
|
||||
#define FLEXBUS_FSPI_ERR_ISR (FLEXBUS_DMA_TIMEOUT_ISR | FLEXBUS_DMA_ERR_ISR | \
|
||||
FLEXBUS_TX_UDF_ISR | FLEXBUS_TX_OVF_ISR)
|
||||
|
||||
/* Flexbus definition */
|
||||
#define FLEXBUS_REVISION_V9 (0x9)
|
||||
|
||||
#define FLEXBUS_QSPI_CMD_MAX (0x8)
|
||||
|
||||
#define FLEXBUS_MAX_IOSIZE (0x4000)
|
||||
#define FLEXBUS_DMA_TIMEOUT_MS (0x1000)
|
||||
|
||||
#define FLEXBUS_MAX_SPEED (150 * 1000 * 1000)
|
||||
#define FLEXBUS_DLL_THRESHOLD_RATE (50 * 1000 * 1000)
|
||||
|
||||
#define FLEXBUS_DLL_TRANING_STEP (10) /* Training step */
|
||||
#define FLEXBUS_DLL_TRANING_VALID_WINDOW (80) /* Valid DLL winbow */
|
||||
|
||||
#define FLEXBUS_DLL_CELLS (0x7f)
|
||||
|
||||
#define FLEXBUS_MAX_CHIPSELECT_NUM (1)
|
||||
#define FLEXBUS_TX_WIDTH (4)
|
||||
|
||||
struct rk_flexbus_fspi {
|
||||
struct device *dev;
|
||||
struct rockchip_flexbus *fb;
|
||||
u32 speed[FLEXBUS_MAX_CHIPSELECT_NUM];
|
||||
u32 cur_speed;
|
||||
u32 cur_real_speed;
|
||||
u8 *tx_buf;
|
||||
u8 *switch_buf;
|
||||
u8 *temp_buf;
|
||||
dma_addr_t dma_temp_buf;
|
||||
struct completion cp;
|
||||
u32 version;
|
||||
u32 max_iosize;
|
||||
u32 dll_cells[FLEXBUS_MAX_CHIPSELECT_NUM];
|
||||
struct gpio_desc **cs_gpiods;
|
||||
struct spi_controller *master;
|
||||
};
|
||||
|
||||
struct rk_flexbus_fspi_xfer {
|
||||
u8 *cmd_buf;
|
||||
u32 cmd_cycles;
|
||||
dma_addr_t buf_addr;
|
||||
u32 buf_cycles;
|
||||
};
|
||||
|
||||
static const unsigned char bit_reverse_table_256[] = {
|
||||
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
|
||||
0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
|
||||
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
|
||||
0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
|
||||
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
|
||||
0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
|
||||
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
|
||||
0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
|
||||
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
|
||||
0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
|
||||
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
|
||||
0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
|
||||
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
|
||||
0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
|
||||
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
|
||||
0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
|
||||
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
|
||||
0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
|
||||
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
|
||||
0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
|
||||
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
|
||||
0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
|
||||
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
|
||||
0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
|
||||
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
|
||||
0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
|
||||
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
|
||||
0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
|
||||
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
|
||||
0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
|
||||
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
|
||||
0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
|
||||
};
|
||||
|
||||
static void flexbus_bit_revert(struct rk_flexbus_fspi *fspi, const u8 *in, u8 *out, u32 len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
out[i] = bit_reverse_table_256[in[i]];
|
||||
}
|
||||
|
||||
static int flexbus_fspi_data_format(struct rk_flexbus_fspi *fspi,
|
||||
const u8 *in, u8 in_lines,
|
||||
u8 *out, u8 out_lines,
|
||||
u32 cycles, u8 *temp_buf)
|
||||
{
|
||||
int in_bits, out_bits, i;
|
||||
|
||||
if (in_lines == 4 && out_lines == 1) {
|
||||
in_bits = cycles * 4;
|
||||
out_bits = cycles;
|
||||
memset(out, 0, (out_bits + 7) / 8);
|
||||
for (i = 0; i < in_bits; i += 4)
|
||||
if (test_bit(i + 1, (void *)in))
|
||||
__set_bit(i / 4, (void *)out);
|
||||
|
||||
return in_bits;
|
||||
} else if (in_lines == 1 && out_lines == 4) {
|
||||
in_bits = cycles;
|
||||
out_bits = in_bits * 4;
|
||||
memset(out, 0, (out_bits + 7) / 8);
|
||||
flexbus_bit_revert(fspi, in, temp_buf, cycles / 8);
|
||||
for (i = 0; i < in_bits; i++)
|
||||
if (test_bit(i, (void *)temp_buf))
|
||||
__set_bit(i * 4, (void *)out);
|
||||
|
||||
return in_bits;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flexbus_data_format_order(struct rk_flexbus_fspi *fspi, u8 *in, u8 *out, u32 bytes)
|
||||
{
|
||||
int i, loops, left;
|
||||
u32 *in32 = (u32 *)in;
|
||||
u32 *out32 = (u32 *)out;
|
||||
|
||||
loops = bytes / 4;
|
||||
left = bytes % 4;
|
||||
for (i = 0; i < loops; i++)
|
||||
out32[i] = __swab32(in32[i]);
|
||||
dev_dbg(fspi->fb->dev, "%s %d %d %d\n", __func__, loops, left, i);
|
||||
switch (left) {
|
||||
case 1:
|
||||
out[i * 4] = in[i * 4];
|
||||
break;
|
||||
case 2:
|
||||
out[i * 4] = in[i * 4 + 1];
|
||||
out[i * 4 + 1] = in[i * 4];
|
||||
break;
|
||||
case 3:
|
||||
out[i * 4] = in[i * 4 + 2];
|
||||
out[i * 4 + 1] = in[i * 4 + 1];
|
||||
out[i * 4 + 2] = in[i * 4];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_init(struct rk_flexbus_fspi *fspi)
|
||||
{
|
||||
u32 ctrl;
|
||||
|
||||
fspi->version = rockchip_flexbus_readl(fspi->fb, FLEXBUS_REVISION) >> 16;
|
||||
fspi->max_iosize = FLEXBUS_MAX_IOSIZE;
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TXWAT_START, 0x10);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_DMA_SRC_LEN0, fspi->max_iosize * FLEXBUS_TX_WIDTH);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_DMA_DST_LEN0, fspi->max_iosize * FLEXBUS_TX_WIDTH);
|
||||
|
||||
ctrl = FLEXBUS_MSB | (1 << FLEXBUS_DFS_SHIFT);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TX_CTL, ctrl);
|
||||
|
||||
/* Using internal clk as sample clock */
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_SLAVE_MODE, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u16 rk_flexbus_fspi_get_max_dll_cells(struct rk_flexbus_fspi *fspi)
|
||||
{
|
||||
return FLEXBUS_DLL_CELLS;
|
||||
}
|
||||
|
||||
static void rk_flexbus_fspi_set_delay_lines(struct rk_flexbus_fspi *fspi, u16 cells, u8 cs)
|
||||
{
|
||||
u16 cell_max = 256;
|
||||
|
||||
if (cells > cell_max)
|
||||
cells = cell_max;
|
||||
|
||||
if (cells) {
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_DLL_EN, 1);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_DLL_NUM, cells);
|
||||
} else {
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_DLL_EN, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_clk_set_rate(struct rk_flexbus_fspi *fspi, unsigned long speed)
|
||||
{
|
||||
return clk_set_rate(fspi->fb->clks[0].clk, speed * 2);
|
||||
}
|
||||
|
||||
static unsigned long rk_flexbus_fspi_clk_get_rate(struct rk_flexbus_fspi *fspi)
|
||||
{
|
||||
return clk_get_rate(fspi->fb->clks[0].clk) / 2;
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_send(struct rk_flexbus_fspi *fspi, struct rk_flexbus_fspi_xfer *cfg)
|
||||
{
|
||||
int ret = 0, timeout_ms = FLEXBUS_DMA_TIMEOUT_MS;
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_CSN_CFG, 0x10001);
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_COM_CTL, FLEXBUS_TX_ONLY);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_IMR, FLEXBUS_TX_DONE_ISR);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_ICR, FLEXBUS_TX_DONE_ISR);
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TX_CMD_LEN, cfg->cmd_cycles);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TX_CMD0, ((u32 *)fspi->tx_buf)[0]);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TX_CMD1, ((u32 *)fspi->tx_buf)[1]);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TX_NUM, cfg->cmd_cycles + cfg->buf_cycles);
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_RX_CTL, 0);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_RX_NUM, 0);
|
||||
|
||||
init_completion(&fspi->cp);
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_DMA_SRC_ADDR0, cfg->buf_addr >> 2);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_ENR, FLEXBUS_TX_ENR);
|
||||
|
||||
if (!wait_for_completion_timeout(&fspi->cp, msecs_to_jiffies(timeout_ms))) {
|
||||
dev_err(fspi->dev, "DMA wait for tx transfer finish timeout\n");
|
||||
ret = -ETIMEDOUT;
|
||||
}
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_ENR, FLEXBUS_TX_DIS);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_ICR, FLEXBUS_TX_DONE_ISR);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_IMR, 0);
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_CSN_CFG, 0x10000);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_send_then_recv_114(struct rk_flexbus_fspi *fspi,
|
||||
struct rk_flexbus_fspi_xfer *cfg)
|
||||
{
|
||||
int ret = 0, timeout_ms = FLEXBUS_DMA_TIMEOUT_MS;
|
||||
u32 ctrl;
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_CSN_CFG, 0x10001);
|
||||
|
||||
ctrl = FLEXBUS_TX_THEN_RX | FLEXBUS_SCLK_SHARE | FLEXBUS_TX_USE_RX;
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_COM_CTL, ctrl);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_IMR, FLEXBUS_RX_DONE_ISR);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_ICR, FLEXBUS_RX_DONE_ISR);
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TX_CMD_LEN, cfg->cmd_cycles);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TX_CMD0, ((u32 *)fspi->tx_buf)[0]);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TX_CMD1, ((u32 *)fspi->tx_buf)[1]);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_TX_NUM, cfg->cmd_cycles);
|
||||
|
||||
ctrl = FLEXBUS_RXD_DY | FLEXBUS_AUTOPAD |
|
||||
FLEXBUS_MSB | 1;
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_RX_CTL, ctrl);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_RX_NUM, cfg->buf_cycles);
|
||||
|
||||
init_completion(&fspi->cp);
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_DMA_DST_ADDR0, cfg->buf_addr >> 2);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_ENR, FLEXBUS_TX_ENR | FLEXBUS_RX_ENR);
|
||||
|
||||
if (!wait_for_completion_timeout(&fspi->cp, msecs_to_jiffies(timeout_ms))) {
|
||||
dev_err(fspi->dev, "DMA wait for tr transfer finish timeout\n");
|
||||
ret = -ETIMEDOUT;
|
||||
}
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_ENR, FLEXBUS_TX_DIS | FLEXBUS_RX_DIS);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_ICR, FLEXBUS_RX_DONE_ISR);
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_IMR, 0);
|
||||
|
||||
rockchip_flexbus_writel(fspi->fb, FLEXBUS_CSN_CFG, 0x10000);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_exec_mem(struct rk_flexbus_fspi *fspi, const struct spi_mem_op *op)
|
||||
{
|
||||
struct rk_flexbus_fspi_xfer cfg = { 0 };
|
||||
u32 cmd_cycles, data_cycles;
|
||||
int ret;
|
||||
|
||||
/* format cmd_addr_dummy */
|
||||
switch (op->addr.nbytes) {
|
||||
case 0:
|
||||
fspi->tx_buf[3] = op->cmd.opcode;
|
||||
fspi->tx_buf[2] = 0xFF;
|
||||
cmd_cycles = 8 + op->dummy.nbytes * 8;
|
||||
break;
|
||||
case 3:
|
||||
fspi->tx_buf[3] = op->cmd.opcode;
|
||||
fspi->tx_buf[2] = op->addr.val >> 16 & 0xFF;
|
||||
fspi->tx_buf[1] = op->addr.val >> 8 & 0xFF;
|
||||
fspi->tx_buf[0] = op->addr.val >> 0 & 0xFF;
|
||||
fspi->tx_buf[7] = 0xFF;
|
||||
fspi->tx_buf[6] = 0xFF;
|
||||
cmd_cycles = 32 + op->dummy.nbytes * 8;
|
||||
break;
|
||||
case 4:
|
||||
fspi->tx_buf[3] = op->cmd.opcode;
|
||||
fspi->tx_buf[2] = op->addr.val >> 24 & 0xFF;
|
||||
fspi->tx_buf[1] = op->addr.val >> 16 & 0xFF;
|
||||
fspi->tx_buf[0] = op->addr.val >> 8 & 0xFF;
|
||||
if (op->dummy.nbytes)
|
||||
fspi->tx_buf[7] = op->addr.val >> 0 & 0xFF;
|
||||
else
|
||||
fspi->tx_buf[4] = op->addr.val >> 0 & 0xFF;
|
||||
fspi->tx_buf[6] = 0xFF;
|
||||
cmd_cycles = 40 + op->dummy.nbytes * 8;
|
||||
break;
|
||||
default:
|
||||
dev_err(fspi->fb->dev, "op->addr.nbytes %d not support!\n", op->addr.nbytes);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* format data */
|
||||
data_cycles = op->data.nbytes * 8 / op->data.buswidth;
|
||||
if (op->data.buf.out) {
|
||||
if (fspi->version == FLEXBUS_REVISION_V9) {
|
||||
flexbus_fspi_data_format(fspi, op->data.buf.out, 1, fspi->temp_buf, 4,
|
||||
data_cycles, fspi->switch_buf);
|
||||
dma_sync_single_for_device(fspi->fb->dev, fspi->dma_temp_buf,
|
||||
op->data.nbytes * 4, DMA_TO_DEVICE);
|
||||
} else {
|
||||
memcpy(fspi->temp_buf, op->data.buf.out, op->data.nbytes);
|
||||
dma_sync_single_for_device(fspi->fb->dev, fspi->dma_temp_buf,
|
||||
op->data.nbytes, DMA_TO_DEVICE);
|
||||
}
|
||||
}
|
||||
|
||||
cfg.cmd_cycles = cmd_cycles;
|
||||
cfg.cmd_buf = fspi->tx_buf;
|
||||
cfg.buf_cycles = data_cycles;
|
||||
cfg.buf_addr = fspi->dma_temp_buf;
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_IN)
|
||||
ret = rk_flexbus_fspi_send_then_recv_114(fspi, &cfg);
|
||||
else
|
||||
ret = rk_flexbus_fspi_send(fspi, &cfg);
|
||||
if (ret) {
|
||||
dev_err(fspi->fb->dev, "cmd=%x data=%d\n", op->cmd.opcode, op->data.nbytes);
|
||||
print_hex_dump(KERN_WARNING, "regs:", DUMP_PREFIX_OFFSET, 4, 4, fspi->fb->base, 0x200, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (op->data.buf.in) {
|
||||
if (op->data.buswidth == 4) {
|
||||
dma_sync_single_for_cpu(fspi->fb->dev, fspi->dma_temp_buf,
|
||||
op->data.nbytes, DMA_FROM_DEVICE);
|
||||
if (fspi->version == FLEXBUS_REVISION_V9)
|
||||
flexbus_data_format_order(fspi, fspi->temp_buf,
|
||||
op->data.buf.in, op->data.nbytes);
|
||||
else
|
||||
memcpy(op->data.buf.in, fspi->temp_buf, op->data.nbytes);
|
||||
} else {
|
||||
dma_sync_single_for_cpu(fspi->fb->dev, fspi->dma_temp_buf,
|
||||
op->data.nbytes * 4, DMA_FROM_DEVICE);
|
||||
flexbus_fspi_data_format(fspi, fspi->temp_buf, 4, op->data.buf.in, 1,
|
||||
data_cycles, fspi->switch_buf);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rk_flexbus_fspi_set_cs_gpio(struct rk_flexbus_fspi *fspi, u8 cs, bool enable)
|
||||
{
|
||||
if (fspi->cs_gpiods) {
|
||||
if (has_acpi_companion(fspi->dev))
|
||||
gpiod_set_value_cansleep(fspi->cs_gpiods[cs], !enable);
|
||||
else
|
||||
/* Polarity handled by GPIO library */
|
||||
gpiod_set_value_cansleep(fspi->cs_gpiods[cs], enable);
|
||||
}
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_exec_op_bypass(struct rk_flexbus_fspi *fspi,
|
||||
struct spi_mem *mem,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
u8 cs = mem->spi->chip_select;
|
||||
u32 ret;
|
||||
|
||||
rk_flexbus_fspi_set_cs_gpio(fspi, cs, true);
|
||||
|
||||
ret = rk_flexbus_fspi_exec_mem(fspi, op);
|
||||
if (ret) {
|
||||
dev_err(fspi->dev, "Failed to exec mem, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
rk_flexbus_fspi_set_cs_gpio(fspi, cs, false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rk_flexbus_fspi_delay_lines_tuning(struct rk_flexbus_fspi *fspi, struct spi_mem *mem)
|
||||
{
|
||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x9F, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(4, NULL, 1));
|
||||
u8 id[4] = { 0 }, id_temp[4] = { 0 };
|
||||
u16 cell_max = (u16)rk_flexbus_fspi_get_max_dll_cells(fspi);
|
||||
u16 right, left = 0;
|
||||
u16 step = FLEXBUS_DLL_TRANING_STEP;
|
||||
bool dll_valid = false;
|
||||
u8 cs = mem->spi->chip_select;
|
||||
|
||||
rk_flexbus_fspi_clk_set_rate(fspi, FLEXBUS_DLL_THRESHOLD_RATE);
|
||||
op.data.buf.in = &id;
|
||||
rk_flexbus_fspi_exec_op_bypass(fspi, mem, &op);
|
||||
if ((0xFF == id[0] && 0xFF == id[1]) ||
|
||||
(0x00 == id[0] && 0x00 == id[1])) {
|
||||
dev_dbg(fspi->fb->dev, "no dev, dll by pass\n");
|
||||
rk_flexbus_fspi_clk_set_rate(fspi, fspi->speed[cs]);
|
||||
fspi->speed[cs] = FLEXBUS_DLL_THRESHOLD_RATE;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
rk_flexbus_fspi_clk_set_rate(fspi, fspi->speed[cs]);
|
||||
op.data.buf.in = &id_temp;
|
||||
for (right = 0; right <= cell_max; right += step) {
|
||||
int ret;
|
||||
|
||||
rk_flexbus_fspi_set_delay_lines(fspi, right, cs);
|
||||
rk_flexbus_fspi_exec_op_bypass(fspi, mem, &op);
|
||||
dev_dbg(fspi->fb->dev, "dll read flash id:%x %x %x\n",
|
||||
id_temp[0], id_temp[1], id_temp[2]);
|
||||
|
||||
ret = memcmp(&id, &id_temp, 3);
|
||||
if (dll_valid && ret) {
|
||||
right -= step;
|
||||
|
||||
break;
|
||||
}
|
||||
if (!dll_valid && !ret)
|
||||
left = right;
|
||||
|
||||
if (!ret)
|
||||
dll_valid = true;
|
||||
|
||||
/* Add cell_max to loop */
|
||||
if (right == cell_max)
|
||||
break;
|
||||
if (right + step > cell_max)
|
||||
right = cell_max - step;
|
||||
}
|
||||
|
||||
if (dll_valid && (right - left) >= FLEXBUS_DLL_TRANING_VALID_WINDOW) {
|
||||
if (left == 0 && right < cell_max)
|
||||
fspi->dll_cells[cs] = left + (right - left) * 2 / 5;
|
||||
else
|
||||
fspi->dll_cells[cs] = left + (right - left) / 2;
|
||||
} else {
|
||||
fspi->dll_cells[cs] = 0;
|
||||
}
|
||||
|
||||
if (fspi->dll_cells[cs]) {
|
||||
dev_dbg(fspi->fb->dev, "%d %d %d dll training pass, %dMHz max_cells=%u ver=%d\n",
|
||||
left, right, fspi->dll_cells[cs], fspi->speed[cs],
|
||||
rk_flexbus_fspi_get_max_dll_cells(fspi), fspi->version);
|
||||
rk_flexbus_fspi_set_delay_lines(fspi, (u16)fspi->dll_cells[cs], cs);
|
||||
} else {
|
||||
dev_err(fspi->fb->dev, "%d %d dll training failed, %dMHz, reduce the frequency\n",
|
||||
left, right, fspi->speed[cs]);
|
||||
rk_flexbus_fspi_set_delay_lines(fspi, 0, cs);
|
||||
rk_flexbus_fspi_clk_set_rate(fspi, FLEXBUS_DLL_THRESHOLD_RATE);
|
||||
mem->spi->max_speed_hz = FLEXBUS_DLL_THRESHOLD_RATE;
|
||||
fspi->cur_speed = FLEXBUS_DLL_THRESHOLD_RATE;
|
||||
fspi->cur_real_speed = rk_flexbus_fspi_clk_get_rate(fspi);
|
||||
fspi->speed[cs] = FLEXBUS_DLL_THRESHOLD_RATE;
|
||||
}
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
||||
{
|
||||
struct rk_flexbus_fspi *fspi = spi_controller_get_devdata(mem->spi->master);
|
||||
int ret;
|
||||
u8 cs = mem->spi->chip_select;
|
||||
|
||||
if (unlikely(mem->spi->max_speed_hz != fspi->speed[cs]) &&
|
||||
!has_acpi_companion(fspi->dev)) {
|
||||
ret = rk_flexbus_fspi_clk_set_rate(fspi, mem->spi->max_speed_hz);
|
||||
if (ret)
|
||||
goto out;
|
||||
fspi->speed[cs] = mem->spi->max_speed_hz;
|
||||
fspi->cur_speed = mem->spi->max_speed_hz;
|
||||
fspi->cur_real_speed = rk_flexbus_fspi_clk_get_rate(fspi);
|
||||
if (fspi->cur_real_speed > FLEXBUS_DLL_THRESHOLD_RATE && 1)
|
||||
rk_flexbus_fspi_delay_lines_tuning(fspi, mem);
|
||||
|
||||
dev_dbg(fspi->dev, "set_freq=%dHz real_freq=%ldHz\n",
|
||||
fspi->speed[cs], rk_flexbus_fspi_clk_get_rate(fspi));
|
||||
}
|
||||
|
||||
rk_flexbus_fspi_set_cs_gpio(fspi, cs, true);
|
||||
|
||||
ret = rk_flexbus_fspi_exec_mem(fspi, op);
|
||||
if (ret) {
|
||||
dev_err(fspi->dev, "Failed to exec mem, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
out:
|
||||
rk_flexbus_fspi_set_cs_gpio(fspi, cs, false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
|
||||
{
|
||||
struct rk_flexbus_fspi *fspi = spi_controller_get_devdata(mem->spi->master);
|
||||
|
||||
op->data.nbytes = min(op->data.nbytes, fspi->max_iosize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool rk_flexbus_fspi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
||||
{
|
||||
if (op->cmd.buswidth > 1 || op->addr.buswidth > 1 ||
|
||||
op->dummy.buswidth > 1 || op->data.buswidth > 4)
|
||||
return false;
|
||||
|
||||
return spi_mem_default_supports_op(mem, op);
|
||||
}
|
||||
|
||||
static const struct spi_controller_mem_ops rk_flexbus_fspi_mem_ops = {
|
||||
.exec_op = rk_flexbus_fspi_exec_mem_op,
|
||||
.adjust_op_size = rk_flexbus_fspi_adjust_op_size,
|
||||
.supports_op = rk_flexbus_fspi_supports_op,
|
||||
};
|
||||
|
||||
static void rk_flexbus_fspi_irq_handler(struct rockchip_flexbus *rkfb, u32 isr)
|
||||
{
|
||||
struct rk_flexbus_fspi *fspi = (struct rk_flexbus_fspi *)rkfb->fb0_data;
|
||||
|
||||
// dev_err(rkfb->dev, "isr=%x\n", isr);
|
||||
rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, isr);
|
||||
if (isr & FLEXBUS_FSPI_ERR_ISR) {
|
||||
if (isr & FLEXBUS_DMA_TIMEOUT_ISR) {
|
||||
dev_err_ratelimited(rkfb->dev, "dma timeout!\n");
|
||||
rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, FLEXBUS_DMA_TIMEOUT_ISR);
|
||||
}
|
||||
if (isr & FLEXBUS_DMA_ERR_ISR) {
|
||||
dev_err_ratelimited(rkfb->dev, "dma err!\n");
|
||||
rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, FLEXBUS_DMA_ERR_ISR);
|
||||
}
|
||||
if (isr & FLEXBUS_TX_UDF_ISR) {
|
||||
dev_err_ratelimited(rkfb->dev, "tx underflow!\n");
|
||||
rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, FLEXBUS_TX_UDF_ISR);
|
||||
}
|
||||
if (isr & FLEXBUS_TX_OVF_ISR) {
|
||||
dev_err_ratelimited(rkfb->dev, "tx overflow!\n");
|
||||
rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, FLEXBUS_TX_OVF_ISR);
|
||||
}
|
||||
}
|
||||
|
||||
if ((isr & FLEXBUS_TX_DONE_ISR) || (isr & FLEXBUS_RX_DONE_ISR))
|
||||
complete(&fspi->cp);
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_get_gpio_descs(struct spi_controller *ctlr, struct rk_flexbus_fspi *fspi)
|
||||
{
|
||||
int nb, i;
|
||||
struct gpio_desc **cs;
|
||||
struct device *dev = &ctlr->dev;
|
||||
unsigned int num_cs_gpios = 0;
|
||||
|
||||
nb = gpiod_count(dev, "fspi-cs");
|
||||
ctlr->num_chipselect = max_t(int, nb, ctlr->num_chipselect);
|
||||
|
||||
if (nb == 0 || nb == -ENOENT)
|
||||
return 0;
|
||||
else if (nb < 0)
|
||||
return nb;
|
||||
|
||||
cs = devm_kcalloc(dev, ctlr->num_chipselect, sizeof(*cs),
|
||||
GFP_KERNEL);
|
||||
if (!cs)
|
||||
return -ENOMEM;
|
||||
fspi->cs_gpiods = cs;
|
||||
|
||||
for (i = 0; i < nb; i++) {
|
||||
cs[i] = devm_gpiod_get_index_optional(dev, "fspi-cs", i,
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(cs[i]))
|
||||
return PTR_ERR(cs[i]);
|
||||
|
||||
if (cs[i]) {
|
||||
/*
|
||||
* If we find a CS GPIO, name it after the device and
|
||||
* chip select line.
|
||||
*/
|
||||
char *gpioname;
|
||||
|
||||
gpioname = devm_kasprintf(dev, GFP_KERNEL, "%s CS%d",
|
||||
dev_name(dev), i);
|
||||
if (!gpioname)
|
||||
return -ENOMEM;
|
||||
gpiod_set_consumer_name(cs[i], gpioname);
|
||||
num_cs_gpios++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_flexbus_fspi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rockchip_flexbus *flexbus_dev = dev_get_drvdata(pdev->dev.parent);
|
||||
struct device *dev = &pdev->dev;
|
||||
struct spi_controller *master;
|
||||
struct rk_flexbus_fspi *fspi;
|
||||
int ret;
|
||||
u32 i, val;
|
||||
|
||||
if (flexbus_dev->opmode0 != ROCKCHIP_FLEXBUS0_OPMODE_SPI ||
|
||||
flexbus_dev->opmode1 != ROCKCHIP_FLEXBUS1_OPMODE_NULL) {
|
||||
dev_err(&pdev->dev, "flexbus opmode mismatch!, fb0=%d fb1=%d\n",
|
||||
flexbus_dev->opmode0, flexbus_dev->opmode1);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
master = devm_spi_alloc_master(&pdev->dev, sizeof(*fspi));
|
||||
if (!master)
|
||||
return -ENOMEM;
|
||||
|
||||
master->flags = SPI_MASTER_HALF_DUPLEX;
|
||||
master->mem_ops = &rk_flexbus_fspi_mem_ops;
|
||||
master->dev.of_node = pdev->dev.of_node;
|
||||
master->max_speed_hz = FLEXBUS_MAX_SPEED;
|
||||
master->num_chipselect = FLEXBUS_MAX_CHIPSELECT_NUM;
|
||||
master->mode_bits = SPI_RX_QUAD;
|
||||
|
||||
fspi = spi_controller_get_devdata(master);
|
||||
fspi->dev = dev;
|
||||
fspi->master = master;
|
||||
fspi->fb = flexbus_dev;
|
||||
|
||||
flexbus_dev->fb0_data = fspi;
|
||||
flexbus_dev->fb0_isr = rk_flexbus_fspi_irq_handler;
|
||||
|
||||
if (has_acpi_companion(&pdev->dev)) {
|
||||
ret = device_property_read_u32(&pdev->dev, "clock-frequency", &val);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to find clock-frequency in ACPI\n");
|
||||
return ret;
|
||||
}
|
||||
for (i = 0; i < FLEXBUS_MAX_CHIPSELECT_NUM; i++)
|
||||
fspi->speed[i] = val;
|
||||
}
|
||||
|
||||
ret = rk_flexbus_fspi_get_gpio_descs(master, fspi);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to get gpio_descs\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, fspi);
|
||||
|
||||
rk_flexbus_fspi_init(fspi);
|
||||
|
||||
fspi->tx_buf = devm_kmalloc(dev, FLEXBUS_QSPI_CMD_MAX, GFP_KERNEL);
|
||||
fspi->switch_buf = devm_kmalloc(dev, fspi->max_iosize * FLEXBUS_TX_WIDTH, GFP_KERNEL);
|
||||
fspi->temp_buf = (u8 *)devm_get_free_pages(dev, GFP_KERNEL | GFP_DMA32,
|
||||
get_order(fspi->max_iosize * FLEXBUS_TX_WIDTH));
|
||||
if (!fspi->temp_buf)
|
||||
return -ENOMEM;
|
||||
fspi->dma_temp_buf = virt_to_phys(fspi->temp_buf);
|
||||
|
||||
ret = devm_spi_register_controller(dev, master);
|
||||
if (ret)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused rk_flexbus_fspi_resume(struct device *dev)
|
||||
{
|
||||
struct rk_flexbus_fspi *fspi = dev_get_drvdata(dev);
|
||||
int i;
|
||||
|
||||
rk_flexbus_fspi_init(fspi);
|
||||
for (i = 0; i < FLEXBUS_MAX_CHIPSELECT_NUM; i++) {
|
||||
if (fspi->dll_cells[i])
|
||||
rk_flexbus_fspi_set_delay_lines(fspi, (u16)fspi->dll_cells[i], i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops rk_flexbus_fspi_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(NULL, rk_flexbus_fspi_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id rk_flexbus_fspi_dt_ids[] = {
|
||||
{ .compatible = "rockchip,flexbus-fspi"},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rk_flexbus_fspi_dt_ids);
|
||||
|
||||
static struct platform_driver rk_flexbus_fspi_driver = {
|
||||
.driver = {
|
||||
.name = "rockchip-flexbus-fspi",
|
||||
.of_match_table = rk_flexbus_fspi_dt_ids,
|
||||
.pm = &rk_flexbus_fspi_pm_ops,
|
||||
},
|
||||
.probe = rk_flexbus_fspi_probe,
|
||||
};
|
||||
module_platform_driver(rk_flexbus_fspi_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Rockchip Flexbus Controller Under SPI Transmission Protocol Driver");
|
||||
MODULE_AUTHOR("Jon Lin <Jon.lin@rock-chips.com>");
|
||||
Reference in New Issue
Block a user