spi: rockchip-slave: Support dma cyclic for misc devices

Change-Id: I46ad77c9879f1d89dcc682ac201ff6aac5f46bbe
Signed-off-by: Xuhui Lin <xuhui.lin@rock-chips.com>
This commit is contained in:
Xuhui Lin
2025-06-24 15:18:05 +08:00
committed by Tao Huang
parent 2be8f9d5fd
commit dcb85d8d34
3 changed files with 322 additions and 8 deletions

View File

@@ -816,6 +816,16 @@ config SPI_ROCKCHIP_SLAVE
SPI Slave controller.
Rockchip SPI Slave controller support DMA transport and IRQ mode.
config SPI_ROCKCHIP_SLAVE_MISCDEV
bool "Rockchip SPI controller Slave misc devices"
depends on SPI_ROCKCHIP_SLAVE
help
This selects a misc driver for Rockchip SPI Slave controller.
If you say yes to this option, It will register rkspi-slave-devN misc device
for each spi controller slave and support to get the controller register
resource by calling mmap.
config SPI_RB4XX
tristate "Mikrotik RB4XX SPI master"
depends on SPI_MASTER && ATH79

View File

@@ -12,11 +12,13 @@
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/spi/rk-spi-slave.h>
#include <linux/spi/spi.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
@@ -161,16 +163,19 @@ enum rockchip_spi_slave_xfer_mode {
};
struct rockchip_spi {
struct spi_controller *ctlr;
struct device *dev;
struct clk_bulk_data *clks;
unsigned int clk_cnt;
struct dma_async_tx_descriptor *rx_cyclic_desc;
void __iomem *regs;
dma_addr_t dma_addr_rx;
dma_addr_t dma_addr_tx;
u32 *dma_buf;
dma_addr_t dma_phys;
u32 dma_cyclic_period_count;
const void *tx;
void *rx;
@@ -183,6 +188,7 @@ struct rockchip_spi {
u32 version;
/*depth of the FIFO buffer */
u32 fifo_len;
int transfer_size;
int max_transfer_size;
u32 fixed_burst_size;
u8 tx_idle_type; /* 0-SR_TF_EMPTY 1-SR_SLAVE_TX_BUSY */
@@ -190,6 +196,7 @@ struct rockchip_spi {
u8 n_bytes;
u32 speed_hz;
u32 mode;
bool slave_aborted;
bool cs_inactive; /* spi slave tansmition stop when cs inactive */
@@ -198,6 +205,11 @@ struct rockchip_spi {
enum rockchip_spi_slave_xfer_mode xfer_mode;
bool ext_spi_clk;
/* misc */
struct miscdevice miscdev;
struct mutex lock;
size_t write_pos;
size_t read_pos;
bool verbose;
ktime_t dbg_time;
};
@@ -329,12 +341,17 @@ static irqreturn_t rockchip_spi_slave_isr(int irq, void *dev_id)
}
/* When int_cs_inactive comes, spi slave abort */
if (rs->cs_inactive && readl_relaxed(rs->regs + ROCKCHIP_SPI_ISR) & INT_CS_INACTIVE) {
rs->slave_aborted = true;
writel_relaxed(0, rs->regs + ROCKCHIP_SPI_IMR);
writel_relaxed(0xffffffff, rs->regs + ROCKCHIP_SPI_ICR);
complete(&rs->xfer_done);
if (rs->cs_inactive &&
readl_relaxed(rs->regs + ROCKCHIP_SPI_ISR) & INT_CS_INACTIVE) {
/* Means using dma cyclic now */
if (rs->rx_cyclic_desc) {
writel_relaxed(0xffffffff, rs->regs + ROCKCHIP_SPI_ICR);
} else {
rs->slave_aborted = true;
writel_relaxed(0, rs->regs + ROCKCHIP_SPI_IMR);
writel_relaxed(0xffffffff, rs->regs + ROCKCHIP_SPI_ICR);
complete(&rs->xfer_done);
}
}
return IRQ_HANDLED;
@@ -473,6 +490,46 @@ static int rockchip_spi_slave_prepare_dma(struct rockchip_spi *rs,
return 1;
}
static int rockchip_spi_slave_prepare_cyclic_dma(struct rockchip_spi *rs,
struct spi_controller *ctlr,
struct spi_transfer *xfer)
{
struct dma_async_tx_descriptor *rxdesc = NULL;
atomic_set(&rs->state, 0);
if (xfer->rx_buf) {
struct dma_slave_config rxconf = {
.direction = DMA_DEV_TO_MEM,
.src_addr = rs->dma_addr_rx,
.src_addr_width = rs->n_bytes,
.src_maxburst =
rockchip_spi_slave_calc_burst_size(rs, xfer->len / rs->n_bytes),
};
dmaengine_slave_config(ctlr->dma_rx, &rxconf);
rxdesc = dmaengine_prep_dma_cyclic(ctlr->dma_rx, rs->dma_phys, xfer->len,
xfer->len > rs->dma_cyclic_period_count ?
xfer->len / rs->dma_cyclic_period_count : xfer->len,
DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
if (!rxdesc)
return -EINVAL;
rxdesc->callback = NULL;
rxdesc->callback_param = ctlr;
}
/* rx must be started before tx due to spi instinct */
if (rxdesc) {
atomic_or(RXDMA, &rs->state);
ctlr->dma_rx->cookie = dmaengine_submit(rxdesc);
dma_async_issue_pending(ctlr->dma_rx);
rs->rx_cyclic_desc = rxdesc;
}
return 0;
}
static bool rockchip_spi_slave_can_dma(struct spi_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *xfer)
@@ -497,6 +554,7 @@ static int rockchip_spi_slave_config(struct rockchip_spi *rs,
u32 cr1;
u32 dmacr = 0;
u32 val = 0;
u32 mode = 0;
rs->slave_aborted = false;
if (xfer->speed_hz)
@@ -504,8 +562,12 @@ static int rockchip_spi_slave_config(struct rockchip_spi *rs,
else
rs->speed_hz = 100000;
cr0 |= (spi->mode & 0x3U) << CR0_SCPH_OFFSET;
if (spi->mode & SPI_LSB_FIRST)
if (spi)
mode = spi->mode;
else
mode = rs->ctlr->mode_bits;
cr0 |= (mode & 0x3U) << CR0_SCPH_OFFSET;
if (mode & SPI_LSB_FIRST)
cr0 |= CR0_FBM_LSB << CR0_FBM_OFFSET;
if (xfer->rx_buf && xfer->tx_buf) {
@@ -751,11 +813,211 @@ static int rockchip_spi_slave_setup(struct spi_device *spi)
cr0 |= ((spi->mode & 0x3) << CR0_SCPH_OFFSET) | CR0_OPM_SLAVE << CR0_OPM_OFFSET;
writel_relaxed(cr0, rs->regs + ROCKCHIP_SPI_CTRLR0);
rs->mode = spi->mode;
pm_runtime_put(rs->dev);
return 0;
}
static int rockchip_spi_slave_misc_open(struct inode *inode, struct file *file)
{
struct miscdevice *miscdev = file->private_data;
struct rockchip_spi *rs;
rs = container_of(miscdev, struct rockchip_spi, miscdev);
file->private_data = rs;
pm_runtime_get_sync(rs->dev);
return 0;
}
static int rockchip_spi_slave_misc_init_cyclic(struct rockchip_spi *rs, int length)
{
struct spi_transfer *xfer;
int ret = 0;
u32 status;
if (length > rs->max_transfer_size) {
dev_err(rs->dev, "Transfer is too long (%d)\n", length);
return -EINVAL;
}
if (rs->cs_inactive) {
ret = readl_poll_timeout(rs->regs + ROCKCHIP_SPI_SR, status,
status & SR_SS_IN_N, 20,
ROCKCHIP_SPI_CLK_TO_CS_DEASSERT_US);
if (ret) {
dev_err(rs->dev, "The cs of spi master is still asserted\n");
return -EIO;
}
}
xfer = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
if (!xfer)
return -ENOMEM;
xfer->rx_buf = rs->dma_buf;
xfer->tx_buf = NULL;
xfer->len = length;
xfer->bits_per_word = 8;
xfer->speed_hz = 1000000;
rs->n_bytes = xfer->bits_per_word <= 8 ? 1 : 2;
rs->xfer_mode = ROCKCHIP_SPI_DMA;
ret = rockchip_spi_slave_config(rs, NULL, xfer);
if (ret)
goto free_spi_transfer;
rockchip_spi_slave_prepare_cyclic_dma(rs, rs->ctlr, xfer);
/* Record for check */
rs->transfer_size = xfer->len;
if (rs->cs_inactive)
writel_relaxed(INT_CS_INACTIVE, rs->regs + ROCKCHIP_SPI_IMR);
spi_enable_chip(rs, true);
if (rs->ready) {
gpiod_set_value(rs->ready, 0);
gpiod_set_value(rs->ready, 1);
}
free_spi_transfer:
kfree(xfer);
return ret;
}
static int rockchip_spi_slave_misc_deinit_cyclic(struct rockchip_spi *rs)
{
struct spi_controller *ctlr = rs->ctlr;
if (rs->rx_cyclic_desc) {
dmaengine_terminate_async(ctlr->dma_rx);
dmaengine_desc_free(rs->rx_cyclic_desc);
rs->rx_cyclic_desc = NULL;
}
rockchip_spi_slave_stop(ctlr);
rs->read_pos = 0;
rs->write_pos = 0;
return 0;
}
static long rockchip_spi_slave_misc_ioctl(struct file *file, unsigned int cmd,
unsigned long args)
{
struct rockchip_spi *rs = file->private_data;
int length = (int)args;
int ret = 0;
mutex_lock(&rs->lock);
switch (cmd) {
case SPI_SLAVE_INIT_CYCLIC:
ret = rockchip_spi_slave_misc_init_cyclic(rs, length);
if (ret)
dev_err(rs->dev, "DMA cyclic init failed: %d\n", ret);
break;
case SPI_SLAVE_DEINIT_CYCLIC:
ret = rockchip_spi_slave_misc_deinit_cyclic(rs);
if (ret)
dev_err(rs->dev, "DMA cyclic deinit failed: %d\n", ret);
break;
default:
break;
}
mutex_unlock(&rs->lock);
return 0;
}
static ssize_t rockchip_spi_slave_misc_read(struct file *file, char __user *buf,
size_t n, loff_t *offset)
{
struct rockchip_spi *rs = file->private_data;
struct spi_controller *ctlr = rs->ctlr;
size_t to_copy, copied = 0;
int ret = 0;
size_t period_size = n > rs->dma_cyclic_period_count ?
n / rs->dma_cyclic_period_count : n;
size_t cur_index;
struct dma_tx_state state;
size_t data_available;
mutex_lock(&rs->lock);
if (n + (size_t)(*offset) > rs->transfer_size) {
dev_err(rs->dev, "Exceed the expected length: %zu + %lld > %d\n",
n, *offset, rs->transfer_size);
ret = -EINVAL;
goto out;
}
dmaengine_tx_status(ctlr->dma_rx, ctlr->dma_rx->cookie, &state);
cur_index = (period_size - state.residue) % period_size;
if (cur_index < rs->read_pos)
data_available = period_size - rs->read_pos + cur_index;
else
data_available = cur_index - rs->read_pos;
to_copy = min(n, data_available);
if (rs->read_pos + to_copy > period_size) {
if (copy_to_user(buf, rs->dma_buf + rs->read_pos,
period_size - rs->read_pos)) {
ret = -EFAULT;
goto out;
}
if (copy_to_user(buf + period_size - rs->read_pos, rs->dma_buf,
to_copy - period_size + rs->read_pos)) {
ret = -EFAULT;
goto out;
}
} else {
if (copy_to_user(buf, rs->dma_buf + rs->read_pos, to_copy)) {
ret = -EFAULT;
goto out;
}
}
rs->read_pos = (rs->read_pos + to_copy) % period_size;
copied = to_copy;
out:
mutex_unlock(&rs->lock);
return copied ? copied : ret;
}
static int rockchip_spi_slave_misc_release(struct inode *inode, struct file *file)
{
struct rockchip_spi *rs = file->private_data;
mutex_lock(&rs->lock);
rockchip_spi_slave_misc_deinit_cyclic(rs);
mutex_unlock(&rs->lock);
pm_runtime_put(rs->dev);
return 0;
}
static const struct file_operations rockchip_spi_slave_misc_fops = {
.open = rockchip_spi_slave_misc_open,
.release = rockchip_spi_slave_misc_release,
.unlocked_ioctl = rockchip_spi_slave_misc_ioctl,
.read = rockchip_spi_slave_misc_read,
};
static int rockchip_spi_slave_probe(struct platform_device *pdev)
{
int ret = 0;
@@ -774,6 +1036,8 @@ static int rockchip_spi_slave_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, ctlr);
rs = spi_controller_get_devdata(ctlr);
rs->ctlr = ctlr;
mutex_init(&rs->lock);
/* Get basic io resource and map it */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -924,6 +1188,12 @@ static int rockchip_spi_slave_probe(struct platform_device *pdev)
if (!device_property_read_bool(&pdev->dev, "rockchip,dma-support-req-mix"))
rs->dma_timeout = 0;
if (!device_property_read_u32(&pdev->dev, "rockchip,dma-cyclic-period-count", &val))
/* DMA cyclic period count can't be zero */
rs->dma_cyclic_period_count = val ? val : 1;
else
rs->dma_cyclic_period_count = 1;
rs->ready = devm_gpiod_get_optional(&pdev->dev, "ready", GPIOD_OUT_HIGH);
if (IS_ERR(rs->ready)) {
ret = PTR_ERR(rs->ready);
@@ -937,6 +1207,22 @@ static int rockchip_spi_slave_probe(struct platform_device *pdev)
goto err_free_dma_rx;
}
if (IS_ENABLED(CONFIG_SPI_ROCKCHIP_SLAVE_MISCDEV)) {
char misc_name[20];
snprintf(misc_name, sizeof(misc_name), "rkspi-slv-dev%d", ctlr->bus_num);
rs->miscdev.minor = MISC_DYNAMIC_MINOR;
rs->miscdev.name = misc_name;
rs->miscdev.fops = &rockchip_spi_slave_misc_fops;
rs->miscdev.parent = &pdev->dev;
ret = misc_register(&rs->miscdev);
if (ret)
dev_err(&pdev->dev, "failed to register misc device %s\n", misc_name);
else
dev_info(&pdev->dev, "register misc device %s\n", misc_name);
}
dev_info(rs->dev, "slave probed, cs-inactive=%d, ready=%d, ext=%d, dam_buf=%x\n",
rs->cs_inactive, rs->ready ? 1 : 0, rs->ext_spi_clk, (u32)rs->dma_phys);
@@ -961,6 +1247,9 @@ static int rockchip_spi_slave_remove(struct platform_device *pdev)
struct spi_controller *ctlr = spi_controller_get(platform_get_drvdata(pdev));
struct rockchip_spi *rs = spi_controller_get_devdata(ctlr);
if (IS_ENABLED(CONFIG_SPI_ROCKCHIP_SLAVE_MISCDEV))
misc_deregister(&rs->miscdev);
pm_runtime_get_sync(&pdev->dev);
clk_bulk_disable_unprepare(rs->clk_cnt, rs->clks);

View File

@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* Copyright (c) 2025 Rockchip Electronics Co., Ltd.
*/
#ifndef RK_SPI_SLAVE_H
#define RK_SPI_SLAVE_H
#include <linux/types.h>
#define SPI_SLAVE_BASE 's'
#define SPI_SLAVE_INIT_CYCLIC _IOW(SPI_SLAVE_BASE, 0, int)
#define SPI_SLAVE_DEINIT_CYCLIC _IOW(SPI_SLAVE_BASE, 1, int)
#endif