mirror of
https://github.com/openwrt/openwrt.git
synced 2025-03-13 15:56:55 +00:00
Siflower SF21A6826/SF21H8898 are a family of RISC-V SoCs with: * Quad-core T-Head C908 (1.125G for SF21A6826, 1.25G for SF21H8898) * DDR3/DDR4 memory controller * 1 QSGMII 4x1G * 1 SGMII/2500Base-X 2.5G * 1 additional RGMII on SF21H8898 * Network offloading engine for L2 switching and L3 NAT * 2 PCIE Gen2 lanes, operating in either one PCIE Gen2x2 or two PCIE Gen2x1 mode * 1 USB2.0 Link: https://github.com/openwrt/openwrt/pull/17115 Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
532 lines
13 KiB
C
532 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* A driver for Siflower SF21A6826/SF21H8898 QSPI controller.
|
|
*
|
|
* Based on the AMBA PL022 driver:
|
|
* Copyright (C) 2008-2012 ST-Ericsson AB
|
|
* Copyright (C) 2006 STMicroelectronics Pvt. Ltd.
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sizes.h>
|
|
|
|
#include <linux/spi/spi-mem.h>
|
|
#include <linux/spi/spi.h>
|
|
|
|
#define SF_SSP_FIFO_DEPTH 0x100
|
|
|
|
#define SSP_CR0 0x000
|
|
#define SSP_CR1 0x004
|
|
#define SSP_DR 0x008
|
|
#define SSP_SR 0x00C
|
|
#define SSP_CPSR 0x010
|
|
#define SSP_IMSC 0x014
|
|
#define SSP_RIS 0x018
|
|
#define SSP_MIS 0x01C
|
|
#define SSP_ICR 0x020
|
|
#define SSP_DMACR 0x024
|
|
#define SSP_FIFO_LEVEL 0x028
|
|
#define SSP_EXSPI_CMD0 0x02C
|
|
#define SSP_EXSPI_CMD1 0x030
|
|
#define SSP_EXSPI_CMD2 0x034
|
|
|
|
/* SSP Control Register 0 - SSP_CR0 */
|
|
#define SSP_CR0_EXSPI_FRAME (0x3 << 4)
|
|
#define SSP_CR0_SPO (0x1 << 6)
|
|
#define SSP_CR0_SPH (0x1 << 7)
|
|
#define SSP_CR0_BIT_MODE(x) ((x)-1)
|
|
#define SSP_SCR_SHFT 8
|
|
|
|
/* SSP Control Register 1 - SSP_CR1 */
|
|
#define SSP_CR1_MASK_SSE (0x1 << 1)
|
|
|
|
/* SSP Status Register - SSP_SR */
|
|
#define SSP_SR_MASK_TFE (0x1 << 0) /* Transmit FIFO empty */
|
|
#define SSP_SR_MASK_TNF (0x1 << 1) /* Transmit FIFO not full */
|
|
#define SSP_SR_MASK_RNE (0x1 << 2) /* Receive FIFO not empty */
|
|
#define SSP_SR_MASK_RFF (0x1 << 3) /* Receive FIFO full */
|
|
#define SSP_SR_MASK_BSY (0x1 << 4) /* Busy Flag */
|
|
|
|
/* SSP FIFO Threshold Register - SSP_FIFO_LEVEL */
|
|
#define SSP_FIFO_LEVEL_RX GENMASK(14, 8) /* Receive FIFO watermark */
|
|
#define SSP_FIFO_LEVEL_TX GENMASK(6, 0) /* Transmit FIFO watermark */
|
|
#define DFLT_THRESH_RX 32
|
|
#define DFLT_THRESH_TX 32
|
|
|
|
/* SSP Raw Interrupt Status Register - SSP_RIS */
|
|
#define SSP_RIS_MASK_RORRIS (0x1 << 0) /* Receive Overrun */
|
|
#define SSP_RIS_MASK_RTRIS (0x1 << 1) /* Receive Timeout */
|
|
#define SSP_RIS_MASK_RXRIS (0x1 << 2) /* Receive FIFO Raw Interrupt status */
|
|
#define SSP_RIS_MASK_TXRIS (0x1 << 3) /* Transmit FIFO Raw Interrupt status */
|
|
|
|
/* EXSPI command register 0 SSP_EXSPI_CMD0 */
|
|
#define EXSPI_CMD0_CMD_COUNT BIT(0) /* cmd byte, must be set at last */
|
|
#define EXSPI_CMD0_ADDR_COUNT GENMASK(2, 1) /* addr bytes */
|
|
#define EXSPI_CMD0_EHC_COUNT BIT(3) /* Set 1 for 4-byte address mode */
|
|
#define EXSPI_CMD0_TX_COUNT GENMASK(14, 4) /* TX data bytes */
|
|
#define EXSPI_CMD0_VALID BIT(15) /* Set 1 to make the cmd to be run */
|
|
|
|
/* EXSPI command register 1 SSP_EXSPI_CMD1 */
|
|
#define EXSPI_CMD1_DUMMY_COUNT GENMASK(3, 0) /* dummy bytes */
|
|
#define EXSPI_CMD1_RX_COUNT GENMASK(14, 4) /* RX data bytes */
|
|
|
|
/* EXSPI command register 2 SSP_EXSPI_CMD2 */
|
|
/* Set 1 for 1-wire, 2 for 2-wire, 3 for 4-wire */
|
|
#define EXSPI_CMD2_CMD_IO_MODE GENMASK(1, 0) /* cmd IO mode */
|
|
#define EXSPI_CMD2_ADDR_IO_MODE GENMASK(3, 2) /* addr IO mode */
|
|
#define EXSPI_CMD2_DATA_IO_MODE GENMASK(5, 4) /* data IO mode */
|
|
|
|
/* SSP Clock Defaults */
|
|
#define SSP_DEFAULT_CLKRATE 0x2
|
|
#define SSP_DEFAULT_PRESCALE 0x40
|
|
|
|
/* SSP Clock Parameter ranges */
|
|
#define CPSDVR_MIN 0x02
|
|
#define CPSDVR_MAX 0xFE
|
|
#define SCR_MIN 0x00
|
|
#define SCR_MAX 0xFF
|
|
|
|
#define SF_READ_TIMEOUT (10 * HZ)
|
|
#define MAX_S_BUF 100
|
|
|
|
struct sf_qspi {
|
|
void __iomem *base;
|
|
struct clk *clk, *apbclk;
|
|
struct device *dev;
|
|
};
|
|
|
|
struct ssp_clock_params {
|
|
u32 freq;
|
|
u8 cpsdvsr; /* value from 2 to 254 (even only!) */
|
|
u8 scr; /* value from 0 to 255 */
|
|
};
|
|
|
|
struct chip_data {
|
|
u32 freq;
|
|
u32 cr0;
|
|
u16 cpsr;
|
|
};
|
|
|
|
static void sf_qspi_flush_rxfifo(struct sf_qspi *s)
|
|
{
|
|
while (readw(s->base + SSP_SR) & SSP_SR_MASK_RNE)
|
|
readw(s->base + SSP_DR);
|
|
}
|
|
|
|
static int sf_qspi_wait_not_busy(struct sf_qspi *s)
|
|
{
|
|
unsigned long timeout = jiffies + SF_READ_TIMEOUT;
|
|
|
|
do {
|
|
if (!(readw(s->base + SSP_SR) & SSP_SR_MASK_BSY))
|
|
return 0;
|
|
|
|
cond_resched();
|
|
} while (time_after(timeout, jiffies));
|
|
|
|
dev_err(s->dev, "I/O timed out\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int sf_qspi_wait_rx_not_empty(struct sf_qspi *s)
|
|
{
|
|
unsigned long timeout = jiffies + SF_READ_TIMEOUT;
|
|
|
|
do {
|
|
if (readw(s->base + SSP_SR) & SSP_SR_MASK_RNE)
|
|
return 0;
|
|
|
|
cond_resched();
|
|
} while (time_after(timeout, jiffies));
|
|
|
|
dev_err(s->dev, "read timed out\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int sf_qspi_wait_rxfifo(struct sf_qspi *s)
|
|
{
|
|
unsigned long timeout = jiffies + SF_READ_TIMEOUT;
|
|
|
|
do {
|
|
if (readw(s->base + SSP_RIS) & SSP_RIS_MASK_RXRIS)
|
|
return 0;
|
|
|
|
cond_resched();
|
|
} while (time_after(timeout, jiffies));
|
|
|
|
dev_err(s->dev, "read timed out\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void sf_qspi_enable(struct sf_qspi *s)
|
|
{
|
|
/* Enable the SPI hardware */
|
|
writew(SSP_CR1_MASK_SSE, s->base + SSP_CR1);
|
|
}
|
|
|
|
static void sf_qspi_disable(struct sf_qspi *s)
|
|
{
|
|
/* Disable the SPI hardware */
|
|
writew(0, s->base + SSP_CR1);
|
|
}
|
|
|
|
static void sf_qspi_xmit(struct sf_qspi *s, unsigned int nbytes, const u8 *out)
|
|
{
|
|
while (nbytes--)
|
|
writew(*out++, s->base + SSP_DR);
|
|
}
|
|
|
|
static int sf_qspi_rcv(struct sf_qspi *s, unsigned int nbytes, u8 *in)
|
|
{
|
|
int ret, i;
|
|
|
|
while (nbytes >= DFLT_THRESH_RX) {
|
|
/* wait for RX FIFO to reach the threshold */
|
|
ret = sf_qspi_wait_rxfifo(s);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < DFLT_THRESH_RX; i++)
|
|
*in++ = readw(s->base + SSP_DR);
|
|
|
|
nbytes -= DFLT_THRESH_RX;
|
|
}
|
|
|
|
/* read the remaining data */
|
|
while (nbytes) {
|
|
ret = sf_qspi_wait_rx_not_empty(s);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*in++ = readw(s->base + SSP_DR);
|
|
nbytes--;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sf_qspi_set_param(struct sf_qspi *s, const struct spi_mem_op *op)
|
|
{
|
|
unsigned int tx_count = 0, rx_count = 0;
|
|
u8 cmd_io, addr_io, data_io;
|
|
u8 cmd_count, addr_count, ehc_count;
|
|
|
|
cmd_io = op->cmd.buswidth == 4 ? 3 : op->cmd.buswidth;
|
|
addr_io = op->addr.buswidth == 4 ? 3 : op->addr.buswidth;
|
|
data_io = op->data.buswidth == 4 ? 3 : op->data.buswidth;
|
|
|
|
if (op->data.nbytes) {
|
|
if (op->data.dir == SPI_MEM_DATA_IN)
|
|
rx_count = op->data.nbytes;
|
|
else
|
|
tx_count = op->data.nbytes;
|
|
}
|
|
if (op->addr.nbytes > 3) {
|
|
addr_count = 3;
|
|
ehc_count = 1;
|
|
} else {
|
|
addr_count = op->addr.nbytes;
|
|
ehc_count = 0;
|
|
}
|
|
cmd_count = op->cmd.nbytes;
|
|
|
|
writew(FIELD_PREP(EXSPI_CMD2_CMD_IO_MODE, cmd_io) |
|
|
FIELD_PREP(EXSPI_CMD2_ADDR_IO_MODE, addr_io) |
|
|
FIELD_PREP(EXSPI_CMD2_DATA_IO_MODE, data_io),
|
|
s->base + SSP_EXSPI_CMD2);
|
|
writew(FIELD_PREP(EXSPI_CMD1_DUMMY_COUNT, op->dummy.nbytes) |
|
|
FIELD_PREP(EXSPI_CMD1_RX_COUNT, rx_count),
|
|
s->base + SSP_EXSPI_CMD1);
|
|
writew(EXSPI_CMD0_VALID |
|
|
FIELD_PREP(EXSPI_CMD0_CMD_COUNT, op->cmd.nbytes) |
|
|
FIELD_PREP(EXSPI_CMD0_ADDR_COUNT, addr_count) |
|
|
FIELD_PREP(EXSPI_CMD0_EHC_COUNT, ehc_count) |
|
|
FIELD_PREP(EXSPI_CMD0_TX_COUNT, tx_count),
|
|
s->base + SSP_EXSPI_CMD0);
|
|
}
|
|
|
|
static int sf_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
|
{
|
|
struct sf_qspi *s = spi_controller_get_devdata(mem->spi->master);
|
|
struct chip_data *chip = spi_get_ctldata(mem->spi);
|
|
unsigned int pops = 0;
|
|
int ret, i, op_len;
|
|
const u8 *tx_buf = NULL;
|
|
u8 *rx_buf = NULL, op_buf[MAX_S_BUF];
|
|
|
|
writew(chip->cr0, s->base + SSP_CR0);
|
|
writew(chip->cpsr, s->base + SSP_CPSR);
|
|
|
|
if (op->data.nbytes) {
|
|
if (op->data.dir == SPI_MEM_DATA_IN)
|
|
rx_buf = op->data.buf.in;
|
|
else
|
|
tx_buf = op->data.buf.out;
|
|
}
|
|
op_len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
|
|
sf_qspi_set_param(s, op);
|
|
|
|
op_buf[pops++] = op->cmd.opcode;
|
|
if (op->addr.nbytes) {
|
|
for (i = 0; i < op->addr.nbytes; i++)
|
|
op_buf[pops + i] = op->addr.val >>
|
|
(8 * (op->addr.nbytes - i - 1));
|
|
pops += op->addr.nbytes;
|
|
}
|
|
|
|
sf_qspi_flush_rxfifo(s);
|
|
memset(op_buf + pops, 0xff, op->dummy.nbytes);
|
|
sf_qspi_xmit(s, op_len, op_buf);
|
|
if (tx_buf) {
|
|
sf_qspi_xmit(s, op->data.nbytes, tx_buf);
|
|
}
|
|
sf_qspi_enable(s);
|
|
if (rx_buf)
|
|
ret = sf_qspi_rcv(s, op->data.nbytes, rx_buf);
|
|
else
|
|
ret = sf_qspi_wait_not_busy(s);
|
|
|
|
sf_qspi_disable(s);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sf_qspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
|
|
{
|
|
u32 nbytes;
|
|
|
|
nbytes = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
|
|
if (nbytes >= SF_SSP_FIFO_DEPTH)
|
|
return -ENOTSUPP;
|
|
|
|
if (op->data.dir == SPI_MEM_DATA_IN)
|
|
op->data.nbytes =
|
|
min_t(unsigned int, op->data.nbytes, SF_SSP_FIFO_DEPTH);
|
|
else
|
|
op->data.nbytes = min_t(unsigned int, op->data.nbytes,
|
|
SF_SSP_FIFO_DEPTH - nbytes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool sf_qspi_supports_op(struct spi_mem *mem,
|
|
const struct spi_mem_op *op)
|
|
{
|
|
if (!spi_mem_default_supports_op(mem, op))
|
|
return false;
|
|
|
|
/* dummy buswidth must be the same as addr */
|
|
if (op->addr.nbytes && op->dummy.nbytes &&
|
|
op->addr.buswidth != op->dummy.buswidth)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline u32 spi_rate(u32 rate, u16 cpsdvsr, u16 scr)
|
|
{
|
|
return rate / (cpsdvsr * (1 + scr));
|
|
}
|
|
|
|
static int calculate_effective_freq(struct sf_qspi *s, int freq,
|
|
struct ssp_clock_params *clk_freq)
|
|
{
|
|
/* Lets calculate the frequency parameters */
|
|
u16 cpsdvsr = CPSDVR_MIN;
|
|
u32 rate, rate_scaled, max_tclk, min_tclk, scr;
|
|
u32 best_freq = 0, best_cpsdvsr = 0, best_scr = 0, tmp, found = 0;
|
|
|
|
rate = clk_get_rate(s->clk);
|
|
/* cpsdvscr = 2 & scr 0 */
|
|
max_tclk = spi_rate(rate, CPSDVR_MIN, SCR_MIN);
|
|
if (freq > max_tclk) {
|
|
dev_warn(
|
|
s->dev,
|
|
"Requested SPI frequency %d Hz is more than maximum: %d Hz\n",
|
|
freq, max_tclk);
|
|
clk_freq->freq = max_tclk;
|
|
clk_freq->cpsdvsr = CPSDVR_MIN;
|
|
clk_freq->scr = SCR_MIN;
|
|
return 0;
|
|
}
|
|
|
|
/* cpsdvsr = 254 & scr = 255 */
|
|
min_tclk = spi_rate(rate, CPSDVR_MAX, SCR_MAX);
|
|
if (freq < min_tclk) {
|
|
dev_err(s->dev,
|
|
"Requested SPI frequency %d Hz is less than minimum: %d Hz\n",
|
|
freq, min_tclk);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* best_freq will give closest possible available rate (<= requested
|
|
* freq) for all values of scr & cpsdvsr.
|
|
*/
|
|
while ((cpsdvsr <= CPSDVR_MAX) && !found) {
|
|
rate_scaled = rate / cpsdvsr;
|
|
|
|
if (rate_scaled < freq)
|
|
break;
|
|
|
|
scr = DIV_ROUND_UP(rate_scaled, freq) - 1;
|
|
if (scr > SCR_MAX)
|
|
continue;
|
|
|
|
tmp = spi_rate(rate, cpsdvsr, scr);
|
|
|
|
/*
|
|
* If found exact value, mark found and break.
|
|
* If found more closer value, update and break.
|
|
*/
|
|
if (tmp > best_freq) {
|
|
best_freq = tmp;
|
|
best_cpsdvsr = cpsdvsr;
|
|
best_scr = scr;
|
|
|
|
if (tmp == freq)
|
|
found = 1;
|
|
}
|
|
|
|
cpsdvsr += 2;
|
|
}
|
|
|
|
clk_freq->freq = best_freq;
|
|
clk_freq->cpsdvsr = (u8) (best_cpsdvsr & 0xFF);
|
|
clk_freq->scr = (u8) (best_scr & 0xFF);
|
|
dev_dbg(s->dev,
|
|
"SSP Target Frequency is: %u, Effective Frequency is %u\n",
|
|
freq, best_freq);
|
|
dev_dbg(s->dev, "SSP cpsdvsr = %d, scr = %d\n",
|
|
clk_freq->cpsdvsr, clk_freq->scr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sf_qspi_setup(struct spi_device *spi)
|
|
{
|
|
struct sf_qspi *s = spi_controller_get_devdata(spi->controller);
|
|
struct ssp_clock_params clk_freq = { .cpsdvsr = 0, .scr = 0 };
|
|
struct chip_data *chip;
|
|
int ret = 0;
|
|
u16 cr0 = 0;
|
|
|
|
if (!spi->max_speed_hz)
|
|
return -EINVAL;
|
|
|
|
ret = calculate_effective_freq(s, spi->max_speed_hz, &clk_freq);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
cr0 = SSP_CR0_BIT_MODE(8);
|
|
cr0 |= clk_freq.scr << 8;
|
|
/*set module*/
|
|
cr0 &= ~(SSP_CR0_SPH | SSP_CR0_SPO);
|
|
if (spi->mode & SPI_CPHA)
|
|
cr0 |= SSP_CR0_SPH;
|
|
if (spi->mode & SPI_CPOL)
|
|
cr0 |= SSP_CR0_SPO;
|
|
cr0 |= SSP_CR0_EXSPI_FRAME;
|
|
|
|
chip->freq = clk_freq.freq;
|
|
chip->cr0 = cr0;
|
|
chip->cpsr = clk_freq.cpsdvsr;
|
|
|
|
spi_set_ctldata(spi, chip);
|
|
return 0;
|
|
}
|
|
|
|
static void sf_qspi_cleanup(struct spi_device *spi)
|
|
{
|
|
struct chip_data *chip = spi_get_ctldata(spi);
|
|
|
|
spi_set_ctldata(spi, NULL);
|
|
kfree(chip);
|
|
}
|
|
|
|
static const struct spi_controller_mem_ops sf_qspi_mem_ops = {
|
|
.supports_op = sf_qspi_supports_op,
|
|
.adjust_op_size = sf_qspi_adjust_op_size,
|
|
.exec_op = sf_qspi_exec_op,
|
|
};
|
|
|
|
static int sf_qspi_probe(struct platform_device *pdev)
|
|
{
|
|
struct spi_controller *master;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct sf_qspi *s;
|
|
int ret;
|
|
|
|
master = devm_spi_alloc_master(&pdev->dev, sizeof(*s));
|
|
if (!master)
|
|
return -ENOMEM;
|
|
master->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL |
|
|
SPI_TX_QUAD;
|
|
s = spi_controller_get_devdata(master);
|
|
s->dev = dev;
|
|
platform_set_drvdata(pdev, s);
|
|
|
|
s->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(s->base))
|
|
return dev_err_probe(dev, PTR_ERR(s->base),
|
|
"failed to remap memory resources.\n");
|
|
|
|
s->clk = devm_clk_get_enabled(dev, "sspclk");
|
|
if (IS_ERR(s->clk))
|
|
return dev_err_probe(dev, PTR_ERR(s->clk),
|
|
"failed to get and enable sspclk.\n");
|
|
|
|
s->apbclk = devm_clk_get_enabled(dev, "apb_pclk");
|
|
if (IS_ERR(s->apbclk))
|
|
return dev_err_probe(dev, PTR_ERR(s->apbclk),
|
|
"failed to get and enable apb_pclk.\n");
|
|
|
|
master->cleanup = sf_qspi_cleanup;
|
|
master->setup = sf_qspi_setup;
|
|
master->use_gpio_descriptors = true;
|
|
master->mem_ops = &sf_qspi_mem_ops;
|
|
master->dev.of_node = np;
|
|
|
|
writew(FIELD_PREP(SSP_FIFO_LEVEL_RX, DFLT_THRESH_RX) |
|
|
FIELD_PREP(SSP_FIFO_LEVEL_TX, DFLT_THRESH_TX),
|
|
s->base + SSP_FIFO_LEVEL);
|
|
|
|
ret = devm_spi_register_controller(dev, master);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret,
|
|
"failed to register controller.\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id sf_qspi_ids[] = {
|
|
{.compatible = "siflower,sf21-qspi"},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sf_qspi_ids);
|
|
|
|
static struct platform_driver sf_qspi_driver = {
|
|
.driver = {
|
|
.name = "sf21_qspi",
|
|
.of_match_table = sf_qspi_ids,
|
|
},
|
|
.probe = sf_qspi_probe,
|
|
};
|
|
module_platform_driver(sf_qspi_driver);
|