mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-05 18:41:58 +09:00
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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
15
include/uapi/linux/spi/rk-spi-slave.h
Normal file
15
include/uapi/linux/spi/rk-spi-slave.h
Normal 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
|
||||
Reference in New Issue
Block a user