You've already forked linux-rockchip
mirror of
https://github.com/armbian/linux-rockchip.git
synced 2026-01-06 11:08:10 -08:00
spi: rockchip-flexbus-spi: Add code
Change-Id: Id78ea8ed2f1730377f5e4afd82c9ab890d8c6fd5 Signed-off-by: Jon Lin <jon.lin@rock-chips.com>
This commit is contained in:
@@ -773,14 +773,24 @@ config SPI_ROCKCHIP_MISCDEV
|
||||
resource by calling mmap.
|
||||
|
||||
config SPI_ROCKCHIP_FLEXBUS_FSPI
|
||||
tristate "Rockchip Flexbus controller under SPI_MEM 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_MEM
|
||||
transmission protocol. This is a specialized controller used to access
|
||||
SPI flash on some Rockchip SOCs.
|
||||
|
||||
config SPI_ROCKCHIP_FLEXBUS_SPI
|
||||
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.
|
||||
transmission protocol. This is a specialized controller used to transfer
|
||||
data on some Rockchip SOCs.
|
||||
|
||||
config SPI_ROCKCHIP_SFC
|
||||
tristate "Rockchip Serial Flash Controller (SFC)"
|
||||
|
||||
@@ -106,6 +106,7 @@ 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_FLEXBUS_SPI) += spi-rockchip-flexbus-spi.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
|
||||
|
||||
401
drivers/spi/spi-rockchip-flexbus-spi.c
Normal file
401
drivers/spi/spi-rockchip-flexbus-spi.c
Normal file
@@ -0,0 +1,401 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Driver for Rockchip Flexbus FSPI mode
|
||||
*
|
||||
* Copyright (C) 2024 Rockchip Electronics Co., Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <dt-bindings/mfd/rockchip-flexbus.h>
|
||||
#include <linux/mfd/rockchip-flexbus.h>
|
||||
|
||||
#define FLEXBUS_SPI_ERR_ISR (FLEXBUS_DMA_TIMEOUT_ISR | FLEXBUS_DMA_ERR_ISR | \
|
||||
FLEXBUS_TX_UDF_ISR | FLEXBUS_TX_OVF_ISR)
|
||||
|
||||
/* Flexbus definition */
|
||||
#define FLEXBUS_MAX_IOSIZE (0x4000)
|
||||
#define FLEXBUS_MAX_SPEED (150 * 1000 * 1000)
|
||||
#define FLEXBUS_MAX_CHIPSELECT_NUM (1)
|
||||
#define FLEXBUS_MAX_DLL_CELLS (0xff)
|
||||
|
||||
#define FLEXBUS_DMA_TIMEOUT_MS (0x1000)
|
||||
|
||||
struct rk_flexbus_spi {
|
||||
struct device *dev;
|
||||
struct rockchip_flexbus *fb;
|
||||
u32 version;
|
||||
u32 cur_speed;
|
||||
u32 max_iosize;
|
||||
u32 dll_cells;
|
||||
u32 dfs;
|
||||
u32 dfs_cfg;
|
||||
u32 mode; /* spi->mode restore */
|
||||
bool irq;
|
||||
u8 *temp_buf;
|
||||
dma_addr_t dma_temp_buf;
|
||||
struct completion cp;
|
||||
};
|
||||
|
||||
static void rk_flexbus_spi_set_delay_lines(struct rk_flexbus_spi *flexbus, u32 cells)
|
||||
{
|
||||
if (cells) {
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_DLL_EN, 1);
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_DLL_NUM, cells);
|
||||
} else {
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_DLL_EN, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int rk_flexbus_spi_wait_idle(struct rk_flexbus_spi *flexbus, u32 timeout_ms)
|
||||
{
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* For short packet data, in order to obtain a better response rate,
|
||||
* the code is busy waiting for a period of time.
|
||||
*/
|
||||
ret = readl_poll_timeout(flexbus->fb->base + FLEXBUS_RISR, status,
|
||||
status & (FLEXBUS_RX_DONE_ISR | FLEXBUS_TX_DONE_ISR), 0,
|
||||
100);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
ret = readl_poll_timeout(flexbus->fb->base + FLEXBUS_RISR, status,
|
||||
status & (FLEXBUS_RX_DONE_ISR | FLEXBUS_TX_DONE_ISR), 20,
|
||||
timeout_ms * 1000);
|
||||
if (ret)
|
||||
dev_err(flexbus->dev, "DMA failed, risr=%x\n", status);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rk_flexbus_spi_send_start(struct rk_flexbus_spi *flexbus, struct spi_device *spi,
|
||||
struct spi_transfer *xfer, dma_addr_t addr)
|
||||
{
|
||||
u32 ctrl, len;
|
||||
|
||||
len = xfer->len * 8 / flexbus->dfs;
|
||||
|
||||
ctrl = FLEXBUS_TX_ONLY;
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_COM_CTL, ctrl);
|
||||
if (flexbus->irq)
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_IMR, FLEXBUS_TX_DONE_ISR);
|
||||
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_TX_NUM, len);
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_DMA_SRC_ADDR0, addr >> 2);
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_ENR, FLEXBUS_TX_ENR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_flexbus_spi_recv_start(struct rk_flexbus_spi *flexbus, struct spi_device *spi,
|
||||
struct spi_transfer *xfer, dma_addr_t addr)
|
||||
{
|
||||
u32 ctrl, len;
|
||||
|
||||
len = xfer->len * 8 / flexbus->dfs;
|
||||
|
||||
ctrl = FLEXBUS_RX_ONLY | FLEXBUS_SCLK_SHARE | FLEXBUS_TX_USE_RX;
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_COM_CTL, ctrl);
|
||||
if (flexbus->irq)
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_IMR, FLEXBUS_RX_DONE_ISR);
|
||||
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_RX_NUM, len);
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_DMA_DST_ADDR0, addr >> 2);
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_ENR, FLEXBUS_RX_ENR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_flexbus_spi_stop(struct rk_flexbus_spi *flexbus)
|
||||
{
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_ENR, 0xFFFF0000);
|
||||
if (flexbus->irq) {
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_ICR, 0);
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_IMR, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_flexbus_spi_set_cs(struct rk_flexbus_spi *flexbus, bool cs_asserted)
|
||||
{
|
||||
if (cs_asserted)
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_CSN_CFG, 0x30003);
|
||||
else
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_CSN_CFG, 0x30000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_flexbus_spi_init(struct rk_flexbus_spi *flexbus)
|
||||
{
|
||||
flexbus->fb->config->grf_config(flexbus->fb, false, 0, 0);
|
||||
|
||||
flexbus->version = rockchip_flexbus_readl(flexbus->fb, FLEXBUS_REVISION) >> 16;
|
||||
flexbus->max_iosize = FLEXBUS_MAX_IOSIZE;
|
||||
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_TXWAT_START, 0x10);
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_DMA_SRC_LEN0, flexbus->max_iosize);
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_DMA_DST_LEN0, flexbus->max_iosize);
|
||||
|
||||
/* Using internal clk as sample clock */
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_SLAVE_MODE, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_flexbus_spi_config(struct rk_flexbus_spi *flexbus, u32 mode)
|
||||
{
|
||||
u32 ctrl;
|
||||
|
||||
/* Din[1] PAD input valid when dfs=1, */
|
||||
if (flexbus->dfs == 1)
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_REMAP, 0x10);
|
||||
else
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_REMAP, 0x0);
|
||||
|
||||
flexbus->fb->config->grf_config(flexbus->fb, false, mode & SPI_CPOL,
|
||||
mode & SPI_CPHA);
|
||||
|
||||
ctrl = FLEXBUS_TX_CTL_UNIT_BYTE | flexbus->dfs_cfg;
|
||||
ctrl |= (mode & 0x3U) << FLEXBUS_TX_CTL_CPHA_SHIFT;
|
||||
if (!(mode & SPI_LSB_FIRST))
|
||||
ctrl |= FLEXBUS_TX_CTL_MSB;
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_TX_CTL, ctrl);
|
||||
|
||||
ctrl = FLEXBUS_RX_CTL_UNIT_BYTE | flexbus->dfs_cfg | FLEXBUS_RXD_DY | FLEXBUS_AUTOPAD;
|
||||
ctrl |= (mode & 0x3U) << FLEXBUS_RX_CTL_CPHA_SHIFT;
|
||||
if (!(mode & SPI_LSB_FIRST))
|
||||
ctrl |= FLEXBUS_RX_CTL_MSB;
|
||||
rockchip_flexbus_writel(flexbus->fb, FLEXBUS_RX_CTL, ctrl);
|
||||
|
||||
rk_flexbus_spi_set_delay_lines(flexbus, flexbus->dll_cells);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rk_flexbus_spi_irq_handler(struct rockchip_flexbus *rkfb, u32 isr)
|
||||
{
|
||||
struct rk_flexbus_spi *flexbus = (struct rk_flexbus_spi *)rkfb->fb0_data;
|
||||
|
||||
// dev_err(rkfb->dev, "isr=%x\n", isr);
|
||||
rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, isr);
|
||||
if (isr & FLEXBUS_SPI_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(&flexbus->cp);
|
||||
}
|
||||
|
||||
static int rockchip_flexbus_spi_transfer_one(struct spi_controller *ctlr,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *xfer)
|
||||
{
|
||||
struct rk_flexbus_spi *flexbus = spi_controller_get_devdata(ctlr);
|
||||
int ret;
|
||||
|
||||
dev_dbg(flexbus->dev, "xfer dfs=%d-%d-%d tx=%p rx=%p len=0x%x\n",
|
||||
flexbus->dfs, xfer->tx_nbits, xfer->rx_nbits,
|
||||
xfer->tx_buf, xfer->rx_buf, xfer->len);
|
||||
|
||||
if (xfer->len & 1 && flexbus->dfs == 16) {
|
||||
dev_err(flexbus->dev, "xfer->len aligned failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (xfer->speed_hz != flexbus->cur_speed) {
|
||||
flexbus->cur_speed = xfer->speed_hz;
|
||||
clk_set_rate(flexbus->fb->clks[0].clk, flexbus->cur_speed * 2);
|
||||
}
|
||||
|
||||
if (xfer->tx_buf) {
|
||||
memcpy(flexbus->temp_buf, xfer->tx_buf, xfer->len);
|
||||
dma_sync_single_for_device(flexbus->fb->dev, flexbus->dma_temp_buf,
|
||||
xfer->len, DMA_TO_DEVICE);
|
||||
}
|
||||
|
||||
if (flexbus->irq)
|
||||
init_completion(&flexbus->cp);
|
||||
if (xfer->tx_buf)
|
||||
ret = rk_flexbus_spi_send_start(flexbus, spi, xfer, flexbus->dma_temp_buf);
|
||||
else
|
||||
ret = rk_flexbus_spi_recv_start(flexbus, spi, xfer, flexbus->dma_temp_buf);
|
||||
if (flexbus->irq) {
|
||||
if (!wait_for_completion_timeout(&flexbus->cp,
|
||||
msecs_to_jiffies(FLEXBUS_DMA_TIMEOUT_MS))) {
|
||||
dev_err(flexbus->dev, "DMA wait for rx transfer finish timeout\n");
|
||||
ret = -ETIMEDOUT;
|
||||
}
|
||||
} else {
|
||||
ret = rk_flexbus_spi_wait_idle(flexbus, FLEXBUS_DMA_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
if (xfer->rx_buf) {
|
||||
dma_sync_single_for_cpu(flexbus->fb->dev, flexbus->dma_temp_buf,
|
||||
xfer->len, DMA_FROM_DEVICE);
|
||||
memcpy(xfer->rx_buf, flexbus->temp_buf, xfer->len);
|
||||
}
|
||||
|
||||
rk_flexbus_spi_stop(flexbus);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rockchip_flexbus_spi_set_cs(struct spi_device *spi, bool enable)
|
||||
{
|
||||
struct rk_flexbus_spi *flexbus = spi_controller_get_devdata(spi->controller);
|
||||
|
||||
rk_flexbus_spi_set_cs(flexbus, !enable);
|
||||
}
|
||||
|
||||
static int rockchip_flexbus_spi_setup(struct spi_device *spi)
|
||||
{
|
||||
struct rk_flexbus_spi *flexbus = spi_controller_get_devdata(spi->controller);
|
||||
|
||||
flexbus->mode = spi->mode;
|
||||
|
||||
return rk_flexbus_spi_config(flexbus, spi->mode);
|
||||
}
|
||||
|
||||
static int rk_flexbus_spi_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_spi *flexbus;
|
||||
int ret;
|
||||
|
||||
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(*flexbus));
|
||||
if (!master)
|
||||
return -ENOMEM;
|
||||
|
||||
master->flags = SPI_MASTER_HALF_DUPLEX;
|
||||
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_CPOL | SPI_CPHA | SPI_LSB_FIRST;
|
||||
master->transfer_one = rockchip_flexbus_spi_transfer_one;
|
||||
master->setup = rockchip_flexbus_spi_setup;
|
||||
master->set_cs = rockchip_flexbus_spi_set_cs;
|
||||
|
||||
flexbus = spi_controller_get_devdata(master);
|
||||
flexbus->dev = dev;
|
||||
flexbus->fb = flexbus_dev;
|
||||
|
||||
device_property_read_u32(&pdev->dev, "rockchip,dll-cells", &flexbus->dll_cells);
|
||||
if (flexbus->dll_cells > FLEXBUS_MAX_DLL_CELLS)
|
||||
flexbus->dll_cells = FLEXBUS_MAX_DLL_CELLS;
|
||||
|
||||
flexbus->irq = !device_property_read_bool(&pdev->dev, "rockchip,poll-only");
|
||||
|
||||
if (device_property_read_u32(&pdev->dev, "rockchip,dfs", &flexbus->dfs)) {
|
||||
dev_err(&pdev->dev, "failed to get dfs\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
switch (flexbus->dfs) {
|
||||
case 1:
|
||||
flexbus->dfs_cfg = flexbus->fb->dfs_reg->dfs_1bit;
|
||||
break;
|
||||
case 2:
|
||||
flexbus->dfs_cfg = flexbus->fb->dfs_reg->dfs_2bit;
|
||||
break;
|
||||
case 4:
|
||||
flexbus->dfs_cfg = flexbus->fb->dfs_reg->dfs_4bit;
|
||||
break;
|
||||
case 8:
|
||||
flexbus->dfs_cfg = flexbus->fb->dfs_reg->dfs_8bit;
|
||||
break;
|
||||
case 16:
|
||||
flexbus->dfs_cfg = flexbus->fb->dfs_reg->dfs_16bit;
|
||||
break;
|
||||
default:
|
||||
dev_err(&pdev->dev, "invalid dfs=%d\n", flexbus->dfs);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
flexbus->temp_buf = (u8 *)devm_get_free_pages(dev, GFP_KERNEL | GFP_DMA32,
|
||||
get_order(FLEXBUS_MAX_IOSIZE));
|
||||
if (!flexbus->temp_buf)
|
||||
return -ENOMEM;
|
||||
flexbus->dma_temp_buf = virt_to_phys(flexbus->temp_buf);
|
||||
|
||||
platform_set_drvdata(pdev, flexbus);
|
||||
|
||||
rockchip_flexbus_set_fb0(flexbus_dev, flexbus, rk_flexbus_spi_irq_handler);
|
||||
rk_flexbus_spi_init(flexbus);
|
||||
|
||||
ret = devm_spi_register_controller(dev, master);
|
||||
if (ret)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_info(&pdev->dev, "dfs=%d dll=%d irq=%d\n", flexbus->dfs,
|
||||
flexbus->dll_cells, flexbus->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_flexbus_spi_resume(struct device *dev)
|
||||
{
|
||||
struct rk_flexbus_spi *flexbus = dev_get_drvdata(dev);
|
||||
|
||||
rk_flexbus_spi_init(flexbus);
|
||||
rk_flexbus_spi_config(flexbus, flexbus->mode);
|
||||
if (flexbus->dll_cells)
|
||||
rk_flexbus_spi_set_delay_lines(flexbus, flexbus->dll_cells);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops rk_flexbus_spi_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(NULL, rk_flexbus_spi_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id rk_flexbus_spi_dt_ids[] = {
|
||||
{ .compatible = "rockchip,flexbus-spi"},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rk_flexbus_spi_dt_ids);
|
||||
|
||||
static struct platform_driver rk_flexbus_spi_driver = {
|
||||
.driver = {
|
||||
.name = "rockchip-flexbus-spi",
|
||||
.of_match_table = rk_flexbus_spi_dt_ids,
|
||||
.pm = &rk_flexbus_spi_pm_ops,
|
||||
},
|
||||
.probe = rk_flexbus_spi_probe,
|
||||
};
|
||||
module_platform_driver(rk_flexbus_spi_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