drivers: add dma spinal dmesg driver
Signed-off-by: Dolu1990 <charles.papon.90@gmail.com> Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
This commit is contained in:
@@ -570,6 +570,12 @@ config STM32_MDMA
|
||||
If you have a board based on STM32 SoC and wish to use the master DMA
|
||||
say Y here.
|
||||
|
||||
config SPINAL_LIB_DMASG
|
||||
tristate "SpinalHDL lib DMASG"
|
||||
select DMA_ENGINE
|
||||
help
|
||||
Enable support for SpinalHDL lib DMASG controller.
|
||||
|
||||
config SPRD_DMA
|
||||
tristate "Spreadtrum DMA support"
|
||||
depends on ARCH_SPRD || COMPILE_TEST
|
||||
|
||||
@@ -70,6 +70,7 @@ obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o
|
||||
obj-$(CONFIG_STM32_DMA) += stm32-dma.o
|
||||
obj-$(CONFIG_STM32_DMAMUX) += stm32-dmamux.o
|
||||
obj-$(CONFIG_STM32_MDMA) += stm32-mdma.o
|
||||
obj-$(CONFIG_SPINAL_LIB_DMASG) += spinal-lib-dmasg.o
|
||||
obj-$(CONFIG_SPRD_DMA) += sprd-dma.o
|
||||
obj-$(CONFIG_S3C24XX_DMAC) += s3c24xx-dma.o
|
||||
obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o
|
||||
|
||||
768
drivers/dma/spinal-lib-dmasg.c
Normal file
768
drivers/dma/spinal-lib-dmasg.c
Normal file
@@ -0,0 +1,768 @@
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/dmapool.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_dma.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io-64-nonatomic-lo-hi.h>
|
||||
|
||||
#include "dmaengine.h"
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
|
||||
#define write_u32(data, address) writel(data,address)
|
||||
#define read_u32(address) readl(address)
|
||||
|
||||
|
||||
|
||||
#define BIT_0 (1 << 0)
|
||||
#define BIT_1 (1 << 1)
|
||||
#define BIT_2 (1 << 2)
|
||||
#define BIT_3 (1 << 3)
|
||||
#define BIT_4 (1 << 4)
|
||||
#define BIT_5 (1 << 5)
|
||||
#define BIT_6 (1 << 6)
|
||||
#define BIT_7 (1 << 7)
|
||||
#define BIT_8 (1 << 8)
|
||||
#define BIT_9 (1 << 9)
|
||||
#define BIT_10 (1 << 10)
|
||||
#define BIT_11 (1 << 11)
|
||||
#define BIT_12 (1 << 12)
|
||||
#define BIT_13 (1 << 13)
|
||||
#define BIT_14 (1 << 14)
|
||||
#define BIT_15 (1 << 15)
|
||||
#define BIT_16 (1 << 16)
|
||||
#define BIT_17 (1 << 17)
|
||||
#define BIT_18 (1 << 18)
|
||||
#define BIT_19 (1 << 19)
|
||||
#define BIT_20 (1 << 20)
|
||||
#define BIT_21 (1 << 21)
|
||||
#define BIT_22 (1 << 22)
|
||||
#define BIT_23 (1 << 23)
|
||||
#define BIT_24 (1 << 24)
|
||||
#define BIT_25 (1 << 25)
|
||||
#define BIT_26 (1 << 26)
|
||||
#define BIT_27 (1 << 27)
|
||||
#define BIT_28 (1 << 28)
|
||||
#define BIT_29 (1 << 29)
|
||||
#define BIT_30 (1 << 30)
|
||||
#define BIT_31 (1 << 31)
|
||||
|
||||
|
||||
#define dmasg_ca (base + channel*0x80)
|
||||
#define DMASG_CHANNEL_INPUT_ADDRESS 0x00
|
||||
#define DMASG_CHANNEL_INPUT_STREAM 0x08
|
||||
#define DMASG_CHANNEL_INPUT_CONFIG 0x0C
|
||||
#define DMASG_CHANNEL_INPUT_CONFIG_MEMORY BIT_12
|
||||
#define DMASG_CHANNEL_INPUT_CONFIG_STREAM 0
|
||||
#define DMASG_CHANNEL_INPUT_CONFIG_COMPLETION_ON_PACKET BIT_13
|
||||
#define DMASG_CHANNEL_INPUT_CONFIG_WAIT_ON_PACKET BIT_14
|
||||
|
||||
#define DMASG_CHANNEL_OUTPUT_ADDRESS 0x10
|
||||
#define DMASG_CHANNEL_OUTPUT_STREAM 0x18
|
||||
#define DMASG_CHANNEL_OUTPUT_CONFIG 0x1C
|
||||
#define DMASG_CHANNEL_OUTPUT_CONFIG_MEMORY BIT_12
|
||||
#define DMASG_CHANNEL_OUTPUT_CONFIG_STREAM 0
|
||||
#define DMASG_CHANNEL_OUTPUT_CONFIG_LAST BIT_13
|
||||
|
||||
#define DMASG_CHANNEL_DIRECT_BYTES 0x20
|
||||
#define DMASG_CHANNEL_STATUS 0x2C
|
||||
#define DMASG_CHANNEL_STATUS_DIRECT_START BIT_0
|
||||
#define DMASG_CHANNEL_STATUS_BUSY BIT_0
|
||||
#define DMASG_CHANNEL_STATUS_SELF_RESTART BIT_1
|
||||
#define DMASG_CHANNEL_STATUS_STOP BIT_2
|
||||
#define DMASG_CHANNEL_STATUS_LINKED_LIST_START BIT_4
|
||||
|
||||
#define DMASG_CHANNEL_FIFO 0x40
|
||||
#define DMASG_CHANNEL_PRIORITY 0x44
|
||||
|
||||
#define DMASG_CHANNEL_INTERRUPT_ENABLE 0x50
|
||||
#define DMASG_CHANNEL_INTERRUPT_PENDING 0x54
|
||||
// Interrupt at the end of each descriptor
|
||||
#define DMASG_CHANNEL_INTERRUPT_DESCRIPTOR_COMPLETION_MASK BIT_0
|
||||
// Interrupt at the middle of each descriptor, require the half_completion_interrupt option to be enabled for the channel
|
||||
#define DMASG_CHANNEL_INTERRUPT_DESCRIPTOR_COMPLETION_HALF_MASK BIT_1
|
||||
// Interrupt when the channel is going off (not busy anymore)
|
||||
#define DMASG_CHANNEL_INTERRUPT_CHANNEL_COMPLETION_MASK BIT_2
|
||||
// Interrupt each time that a linked list's descriptor status field is updated
|
||||
#define DMASG_CHANNEL_INTERRUPT_LINKED_LIST_UPDATE_MASK BIT_3
|
||||
// Interrupt each time a S -> M channel has done transferring a packet into the memory
|
||||
#define DMASG_CHANNEL_INTERRUPT_INPUT_PACKET_MASK BIT_4
|
||||
|
||||
#define DMASG_CHANNEL_PROGRESS_BYTES 0x60
|
||||
#define DMASG_CHANNEL_LINKED_LIST_HEAD 0x70
|
||||
|
||||
// Number of bytes (minus one) reserved at the descriptor FROM/TO addresses.
|
||||
// If you want to transfer 10 bytes, this field should take the value 9
|
||||
#define DMASG_DESCRIPTOR_CONTROL_BYTES 0x7FFFFFF
|
||||
//Only for M -> S transfers, specify if a end of packet should be send at the end of the transfer
|
||||
#define DMASG_DESCRIPTOR_CONTROL_END_OF_PACKET BIT_30
|
||||
|
||||
// Number of bytes transferred by the DMA for this descriptor.
|
||||
#define DMASG_DESCRIPTOR_STATUS_BYTES 0x7FFFFFF
|
||||
// Only for S -> M transfers, specify if the descriptor mark the end of a received packet
|
||||
// Can be used when the dmasg_input_stream function is called with completion_on_packet set.
|
||||
#define DMASG_DESCRIPTOR_STATUS_END_OF_PACKET BIT_30
|
||||
// Specify if the descriptor was executed by the DMA.
|
||||
// If the DMA read a completed descriptor, the channel is stopped and will produce a CHANNEL_COMPLETION interrupt.
|
||||
#define DMASG_DESCRIPTOR_STATUS_COMPLETED BIT_31
|
||||
|
||||
|
||||
// byte_per_burst need to be a power of two, can be set to zero if the channel has hardcoded burst length
|
||||
static void dmasg_input_memory(void __iomem * base, u32 channel, u32 address, u32 byte_per_burst){
|
||||
write_u32(address, dmasg_ca + DMASG_CHANNEL_INPUT_ADDRESS);
|
||||
write_u32(DMASG_CHANNEL_INPUT_CONFIG_MEMORY | ((byte_per_burst-1) & 0xFFF), dmasg_ca + DMASG_CHANNEL_INPUT_CONFIG);
|
||||
}
|
||||
|
||||
// byte_per_burst need to be a power of two, can be set to zero if the channel has hardcoded burst length
|
||||
static void dmasg_output_memory(void __iomem * base, u32 channel, u32 address, u32 byte_per_burst){
|
||||
write_u32(address, dmasg_ca + DMASG_CHANNEL_OUTPUT_ADDRESS);
|
||||
write_u32(DMASG_CHANNEL_OUTPUT_CONFIG_MEMORY | ((byte_per_burst-1) & 0xFFF), dmasg_ca + DMASG_CHANNEL_OUTPUT_CONFIG);
|
||||
}
|
||||
|
||||
// port identify which physical input port should be used. ex : If a port can be accessed by 4 channels, and you are the second of those channel, then port=1
|
||||
// wait_on_packet ensure the channel wait the beggining of a packet before capturing the data (avoid desync)
|
||||
// completion_on_packet will limit the descriptor to only contains one packet and force its completion when it is fully transferred into memory.
|
||||
static void dmasg_input_stream(void __iomem * base, u32 channel, u32 port, u32 wait_on_packet, u32 completion_on_packet){
|
||||
write_u32(port << 0, dmasg_ca + DMASG_CHANNEL_INPUT_STREAM);
|
||||
write_u32(DMASG_CHANNEL_INPUT_CONFIG_STREAM | (completion_on_packet ? DMASG_CHANNEL_INPUT_CONFIG_COMPLETION_ON_PACKET : 0) | (wait_on_packet ? DMASG_CHANNEL_INPUT_CONFIG_WAIT_ON_PACKET : 0), dmasg_ca + DMASG_CHANNEL_INPUT_CONFIG);
|
||||
}
|
||||
|
||||
// port identify which physical output port should be used. ex : If a port can be accessed by 4 channels, and you are the second of those channel, then port=1
|
||||
// source is equivalent to the AXI-Stream TID used in the packet
|
||||
// sink is equivalent to the AXI-Stream TDEST used in the packet
|
||||
// last (only for direct DMA control, not linked list) specify if a end of packet should be sent at the end of the transfer
|
||||
static void dmasg_output_stream(void __iomem * base, u32 channel, u32 port, u32 source, u32 sink, u32 last){
|
||||
write_u32(port << 0 | source << 8 | sink << 16, dmasg_ca + DMASG_CHANNEL_OUTPUT_STREAM);
|
||||
write_u32(DMASG_CHANNEL_OUTPUT_CONFIG_STREAM | (last ? DMASG_CHANNEL_OUTPUT_CONFIG_LAST : 0), dmasg_ca + DMASG_CHANNEL_OUTPUT_CONFIG);
|
||||
}
|
||||
|
||||
// Allow to start a channel without using linked list (direct control). Be sure the channel was enabled to support this mode.
|
||||
// channel is the DMA channel ID to use.
|
||||
// bytes is the size of the transfer
|
||||
// self_restart allow the channel to operate into a circular mode. The DESCRIPTOR_COMPLETION_HALF interrupt can be usefull in that mode
|
||||
static void dmasg_direct_start(void __iomem * base, u32 channel, u32 bytes, u32 self_restart){
|
||||
write_u32(bytes-1, dmasg_ca + DMASG_CHANNEL_DIRECT_BYTES);
|
||||
write_u32(DMASG_CHANNEL_STATUS_DIRECT_START | (self_restart ? DMASG_CHANNEL_STATUS_SELF_RESTART : 0), dmasg_ca + DMASG_CHANNEL_STATUS);
|
||||
}
|
||||
|
||||
// Allow to start a channel using a linked list. Be sure the channel was enabled to support this mode.
|
||||
// channel is the DMA channel ID to use.
|
||||
// head specify the address of the linked list's first element. See dmasg_descriptor struct.
|
||||
static void dmasg_linked_list_start(void __iomem * base, u32 channel, u32 head){
|
||||
write_u32((u32) head, dmasg_ca + DMASG_CHANNEL_LINKED_LIST_HEAD);
|
||||
write_u32(DMASG_CHANNEL_STATUS_LINKED_LIST_START, dmasg_ca + DMASG_CHANNEL_STATUS);
|
||||
}
|
||||
|
||||
// Ask a channel to stop itself. None blocking, so you need to pull on dmasg_busy if you want to wait it to be effective.
|
||||
// The status progress (bytes transfered) of the interrupted descriptor will be unknown
|
||||
static void dmasg_stop(void __iomem * base, u32 channel){
|
||||
write_u32(DMASG_CHANNEL_STATUS_STOP, dmasg_ca + DMASG_CHANNEL_STATUS);
|
||||
}
|
||||
|
||||
// See all DMASG_CHANNEL_INTERRUPT_*_MASK defines for possible interrupts
|
||||
// Multiple interrupts can be used at once
|
||||
// This function clear all pending interrupts for the given channel before enabling the mask's interrupts
|
||||
static void dmasg_interrupt_config(void __iomem * base, u32 channel, u32 mask){
|
||||
write_u32(0xFFFFFFFF, dmasg_ca+DMASG_CHANNEL_INTERRUPT_PENDING);
|
||||
write_u32(mask, dmasg_ca+DMASG_CHANNEL_INTERRUPT_ENABLE);
|
||||
}
|
||||
|
||||
// clear the mask's interrupts, you can mask with 0xFFFFFFFF to clear them all
|
||||
static void dmasg_interrupt_pending_clear(void __iomem * base, u32 channel, u32 mask){
|
||||
write_u32(mask, dmasg_ca+DMASG_CHANNEL_INTERRUPT_PENDING);
|
||||
}
|
||||
|
||||
// Check the status of the specified channel.
|
||||
static u32 dmasg_busy(void __iomem * base, u32 channel){
|
||||
return read_u32(dmasg_ca + DMASG_CHANNEL_STATUS) & DMASG_CHANNEL_STATUS_BUSY;
|
||||
}
|
||||
|
||||
// Specify the buffer mapping of the given channel
|
||||
// You don't need to use this function is the buffer address and buffer size are hardcoded in the hardware
|
||||
static void dmasg_buffer(void __iomem * base, u32 channel, u32 fifo_base, u32 fifo_bytes){
|
||||
write_u32(fifo_base << 0 | (fifo_bytes-1) << 16, dmasg_ca+DMASG_CHANNEL_FIFO);
|
||||
}
|
||||
|
||||
static void dmasg_priority(void __iomem * base, u32 channel, u32 priority){
|
||||
write_u32(priority, dmasg_ca + DMASG_CHANNEL_PRIORITY);
|
||||
}
|
||||
|
||||
// Snoop how many bytes were transferred for the current descriptor
|
||||
static u32 dmasg_progress_bytes(void __iomem * base, u32 channel){
|
||||
return read_u32(dmasg_ca + DMASG_CHANNEL_PROGRESS_BYTES);
|
||||
}
|
||||
|
||||
|
||||
#define SPINAL_LIB_DMASG_MAX_TRANS_LEN (1 << 24)
|
||||
|
||||
struct spinal_lib_dmasg_chan {
|
||||
struct spinal_lib_dmasg_device *priv;
|
||||
struct device *dev;
|
||||
int hardware_id;
|
||||
int software_id;
|
||||
|
||||
spinlock_t lock;
|
||||
struct dma_chan common;
|
||||
struct dma_pool *segment_pool;
|
||||
|
||||
struct list_head pending_list;
|
||||
struct spinal_lib_dmasg_tx_descriptor *current_descriptor;
|
||||
struct spinal_lib_dmasg_segment * current_segment;
|
||||
|
||||
int irq;
|
||||
struct tasklet_struct tasklet;
|
||||
};
|
||||
|
||||
|
||||
struct spinal_lib_dmasg_device {
|
||||
void __iomem *regs;
|
||||
struct device *dev;
|
||||
struct dma_device common;
|
||||
|
||||
int chan_count;
|
||||
struct spinal_lib_dmasg_chan *chan;
|
||||
};
|
||||
|
||||
|
||||
#define to_spinal_lib_dmasg_chan(chan) \
|
||||
container_of(chan, struct spinal_lib_dmasg_chan, common)
|
||||
|
||||
#define to_dma_tx_descriptor(tx) \
|
||||
container_of(tx, struct spinal_lib_dmasg_tx_descriptor, async_tx)
|
||||
|
||||
struct spinal_lib_dmasg_tx_descriptor {
|
||||
struct dma_async_tx_descriptor async_tx;
|
||||
struct list_head segments;
|
||||
struct list_head node;
|
||||
|
||||
u32 period_left, period_len;
|
||||
u32 buffer_left, buffer_len;
|
||||
dma_addr_t buf_addr;
|
||||
};
|
||||
|
||||
|
||||
struct spinal_lib_dmasg_segment_hw {
|
||||
u32 status;
|
||||
u32 control;
|
||||
u64 from;
|
||||
u64 to;
|
||||
u64 next;
|
||||
} __aligned(64);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
struct spinal_lib_dmasg_segment {
|
||||
struct spinal_lib_dmasg_segment_hw hw;
|
||||
struct list_head node;
|
||||
struct spinal_lib_dmasg_segment* next;
|
||||
dma_addr_t phys;
|
||||
bool notify;
|
||||
} __aligned(64);
|
||||
|
||||
|
||||
static struct spinal_lib_dmasg_segment *spinal_lib_dmasg_alloc_segment(struct spinal_lib_dmasg_chan *chan)
|
||||
{
|
||||
struct spinal_lib_dmasg_segment *segment;
|
||||
dma_addr_t phys;
|
||||
|
||||
segment = dma_pool_zalloc(chan->segment_pool, GFP_ATOMIC, &phys);
|
||||
if (!segment)
|
||||
return NULL;
|
||||
|
||||
segment->phys = phys;
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
|
||||
static struct spinal_lib_dmasg_tx_descriptor *
|
||||
spinal_lib_dmasg_alloc_tx_descriptor(struct spinal_lib_dmasg_chan *chan)
|
||||
{
|
||||
struct spinal_lib_dmasg_tx_descriptor *desc;
|
||||
|
||||
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
|
||||
if (!desc)
|
||||
return NULL;
|
||||
|
||||
INIT_LIST_HEAD(&desc->segments);
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
static int spinal_lib_dmasg_free_tx_descriptor(struct spinal_lib_dmasg_chan *chan, struct spinal_lib_dmasg_tx_descriptor *desc){
|
||||
struct spinal_lib_dmasg_segment *segment;
|
||||
//printk("spinal_lib_dmasg_free_tx_descriptor %x %x %x\n", (u32)&(desc->segments), (u32)(desc->segments.prev), (u32)(desc->segments.next));
|
||||
|
||||
list_for_each_entry(segment, &desc->segments, node) {
|
||||
dma_pool_free(chan->segment_pool, segment, segment->phys);
|
||||
}
|
||||
|
||||
kzfree(desc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int spinal_lib_dmasg_chan_reset(struct spinal_lib_dmasg_chan *chan)
|
||||
{
|
||||
//printk("spinal_lib_dmasg_chan_reset\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void spinal_lib_dmasg_cyclic_segment_update(struct spinal_lib_dmasg_chan* chan, bool init){
|
||||
unsigned long flags = 0;
|
||||
if(!init)
|
||||
spin_lock_irqsave(&chan->lock, flags);
|
||||
|
||||
// printk("spinal_lib_dmasg_cyclic_segment_update\n");
|
||||
while(1){
|
||||
dma_async_tx_callback callback;
|
||||
void *callback_param;
|
||||
struct spinal_lib_dmasg_segment *segment;
|
||||
struct spinal_lib_dmasg_tx_descriptor *desc;
|
||||
u32 bytes;
|
||||
|
||||
segment = chan->current_segment;
|
||||
desc = chan->current_descriptor;
|
||||
|
||||
if(!segment || !(segment->hw.status & DMASG_DESCRIPTOR_STATUS_COMPLETED)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!init && segment->notify){
|
||||
callback = desc->async_tx.callback;
|
||||
callback_param = desc->async_tx.callback_param;
|
||||
} else {
|
||||
callback = NULL;
|
||||
}
|
||||
|
||||
// Update segment
|
||||
bytes = min_t(u32, min_t(u32, SPINAL_LIB_DMASG_MAX_TRANS_LEN, desc->period_left), desc->buffer_left);
|
||||
segment->hw.control = bytes-1;
|
||||
segment->hw.from = desc->buf_addr + (desc->buffer_len - desc->buffer_left);
|
||||
segment->hw.status = 0;
|
||||
|
||||
|
||||
// Update for next desc
|
||||
desc->period_left -= bytes;
|
||||
desc->buffer_left -= bytes;
|
||||
|
||||
if(desc->period_left == 0){
|
||||
desc->period_left = desc->period_len;
|
||||
segment->notify = true;
|
||||
} else {
|
||||
segment->notify = false;
|
||||
}
|
||||
if(desc->buffer_left == 0){
|
||||
desc->buffer_left = desc->buffer_len;
|
||||
}
|
||||
|
||||
chan->current_segment = segment->next;
|
||||
|
||||
if(callback){
|
||||
spin_unlock_irqrestore(&chan->lock, flags);
|
||||
callback(callback_param);
|
||||
spin_lock_irqsave(&chan->lock, flags);
|
||||
}
|
||||
}
|
||||
if(!init)
|
||||
spin_unlock_irqrestore(&chan->lock, flags);
|
||||
}
|
||||
|
||||
static void spinal_lib_dmasg_do_tasklet(unsigned long data)
|
||||
{
|
||||
struct spinal_lib_dmasg_chan *chan = (struct spinal_lib_dmasg_chan *)data;
|
||||
|
||||
//printk("spinal_lib_dmasg_do_tasklet %x\n", (u32)chan);
|
||||
|
||||
|
||||
spinal_lib_dmasg_cyclic_segment_update(chan, false);
|
||||
|
||||
}
|
||||
|
||||
static irqreturn_t spinal_lib_dmasg_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct spinal_lib_dmasg_chan *chan = dev_id;
|
||||
|
||||
//printk("spinal_lib_dmasg_interrupt %d %p\n", irq, dev_id);
|
||||
|
||||
|
||||
|
||||
dmasg_interrupt_config(chan->priv->regs, chan->hardware_id, DMASG_CHANNEL_INTERRUPT_DESCRIPTOR_COMPLETION_MASK);
|
||||
|
||||
|
||||
tasklet_schedule(&chan->tasklet);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int spinal_lib_dmasg_chan_probe(struct spinal_lib_dmasg_device *priv,
|
||||
struct device_node *node, int software_id)
|
||||
{
|
||||
struct spinal_lib_dmasg_chan *chan;
|
||||
int err;
|
||||
|
||||
//printk("spinal_lib_dmasg_chan_probe enter\n");
|
||||
|
||||
chan = &priv->chan[software_id];
|
||||
chan->dev = priv->dev;
|
||||
chan->priv = priv;
|
||||
chan->software_id = software_id;
|
||||
|
||||
|
||||
spin_lock_init(&chan->lock);
|
||||
INIT_LIST_HEAD(&chan->pending_list);
|
||||
chan->segment_pool = dma_pool_create("spinal_lib_dmasg_segment_pool",
|
||||
chan->dev,
|
||||
sizeof(struct spinal_lib_dmasg_segment),
|
||||
__alignof__(struct spinal_lib_dmasg_segment),
|
||||
0);
|
||||
|
||||
/* Retrieve the channel properties from the device tree */
|
||||
err = of_property_read_s32(node, "reg", &chan->hardware_id );
|
||||
if (err) {
|
||||
dev_err(priv->dev, "missing hardware-id property\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the DMA channel and add it to the DMA engine channels
|
||||
* list.
|
||||
*/
|
||||
chan->common.device = &priv->common;
|
||||
|
||||
list_add_tail(&chan->common.device_node, &priv->common.channels);
|
||||
|
||||
/* Reset the channel */
|
||||
err = spinal_lib_dmasg_chan_reset(chan);
|
||||
if (err < 0) {
|
||||
dev_err(priv->dev, "Reset channel failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Initialize the tasklet */
|
||||
tasklet_init(&chan->tasklet, spinal_lib_dmasg_do_tasklet, (unsigned long)chan);
|
||||
|
||||
/* Request the interrupt */
|
||||
chan->irq = irq_of_parse_and_map(node, 0);
|
||||
err = request_irq(chan->irq, spinal_lib_dmasg_interrupt, IRQF_SHARED,
|
||||
"spinal-lib-dmasg", chan);
|
||||
if (err) {
|
||||
dev_err(chan->dev, "unable to request IRQ %d\n", chan->irq);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spinal_lib_dmasg_alloc_chan_resources(struct dma_chan *dchan)
|
||||
{
|
||||
//printk("spinal_lib_dmasg_alloc_chan_resources\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void spinal_lib_dmasg_free_chan_resources(struct dma_chan *dchan)
|
||||
{
|
||||
//printk("spinal_lib_dmasg_free_chan_resources\n");
|
||||
|
||||
}
|
||||
|
||||
|
||||
static int spinal_lib_dmasg_terminate_all(struct dma_chan *dchan)
|
||||
{
|
||||
struct spinal_lib_dmasg_chan *chan = to_spinal_lib_dmasg_chan(dchan);
|
||||
struct spinal_lib_dmasg_tx_descriptor *desc;
|
||||
unsigned long flags;
|
||||
//printk("spinal_lib_dmasg_terminate_all\n");
|
||||
|
||||
spin_lock_irqsave(&chan->lock, flags);
|
||||
|
||||
dmasg_interrupt_config(chan->priv->regs, chan->hardware_id, 0);
|
||||
dmasg_stop(chan->priv->regs, chan->hardware_id);
|
||||
while(dmasg_busy(chan->priv->regs, chan->hardware_id));
|
||||
|
||||
|
||||
if(chan->current_descriptor){
|
||||
spinal_lib_dmasg_free_tx_descriptor(chan, chan->current_descriptor);
|
||||
chan->current_descriptor = NULL;
|
||||
chan->current_segment = NULL;
|
||||
}
|
||||
|
||||
list_for_each_entry(desc, &chan->pending_list, node) {
|
||||
spinal_lib_dmasg_free_tx_descriptor(chan, desc);
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&chan->pending_list);
|
||||
spin_unlock_irqrestore(&chan->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void spinal_lib_dmasg_issue_pending(struct dma_chan *dchan)
|
||||
{
|
||||
struct spinal_lib_dmasg_chan *chan = to_spinal_lib_dmasg_chan(dchan);
|
||||
struct spinal_lib_dmasg_tx_descriptor *desc;
|
||||
struct spinal_lib_dmasg_segment * head_segment;
|
||||
unsigned long flags;
|
||||
//printk("spinal_lib_dmasg_issue_pending\n");
|
||||
|
||||
spin_lock_irqsave(&chan->lock, flags);
|
||||
|
||||
if (list_empty(&chan->pending_list))
|
||||
goto done;
|
||||
|
||||
desc = list_first_entry(&chan->pending_list, struct spinal_lib_dmasg_tx_descriptor, node);
|
||||
head_segment = list_first_entry(&desc->segments, struct spinal_lib_dmasg_segment, node);
|
||||
|
||||
list_del_init(&desc->node);
|
||||
|
||||
chan->current_descriptor = desc;
|
||||
chan->current_segment = head_segment;
|
||||
|
||||
spinal_lib_dmasg_cyclic_segment_update(chan, true);
|
||||
|
||||
dmasg_interrupt_config(chan->priv->regs, chan->hardware_id, DMASG_CHANNEL_INTERRUPT_DESCRIPTOR_COMPLETION_MASK);
|
||||
dmasg_input_memory(chan->priv->regs, chan->hardware_id, 0, 16);
|
||||
dmasg_output_stream (chan->priv->regs, chan->hardware_id, 0, 0, 0, 1);
|
||||
dmasg_linked_list_start(chan->priv->regs, chan->hardware_id, (u32) head_segment->phys);
|
||||
|
||||
|
||||
|
||||
done:
|
||||
spin_unlock_irqrestore(&chan->lock, flags);
|
||||
}
|
||||
|
||||
static enum dma_status spinal_lib_dmasg_tx_status(struct dma_chan *dchan,
|
||||
dma_cookie_t cookie,
|
||||
struct dma_tx_state *txstate)
|
||||
{
|
||||
//printk("spinal_lib_dmasg_tx_status\n");
|
||||
return DMA_IN_PROGRESS;
|
||||
}
|
||||
|
||||
static struct dma_chan *of_dma_spinal_lib_xlate(struct of_phandle_args *dma_spec,
|
||||
struct of_dma *ofdma)
|
||||
{
|
||||
struct spinal_lib_dmasg_device *priv = ofdma->of_dma_data;
|
||||
int chan_id;
|
||||
|
||||
//printk("of_dma_spinal_lib_xlate !!!!!\n");
|
||||
chan_id = dma_spec->args[0];
|
||||
if (chan_id >= priv->chan_count)
|
||||
return NULL;
|
||||
|
||||
return dma_get_slave_channel(&priv->chan[chan_id].common); // dma_get_slave_channel(&priv->chan[chan_id]->common);
|
||||
}
|
||||
|
||||
|
||||
static dma_cookie_t spinal_lib_dmasg_tx_submit(struct dma_async_tx_descriptor *tx)
|
||||
{
|
||||
struct spinal_lib_dmasg_tx_descriptor *desc = to_dma_tx_descriptor(tx);
|
||||
struct spinal_lib_dmasg_chan *chan = to_spinal_lib_dmasg_chan(tx->chan);
|
||||
dma_cookie_t cookie;
|
||||
unsigned long flags;
|
||||
|
||||
//printk("spinal_lib_dmasg_tx_submit\n");
|
||||
|
||||
spin_lock_irqsave(&chan->lock, flags);
|
||||
|
||||
cookie = dma_cookie_assign(tx);
|
||||
list_add_tail(&desc->node, &chan->pending_list);
|
||||
|
||||
spin_unlock_irqrestore(&chan->lock, flags);
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
|
||||
static struct dma_async_tx_descriptor *spinal_lib_dmasg_prep_dma_cyclic(
|
||||
struct dma_chan *dchan, dma_addr_t buf_addr, size_t buf_len,
|
||||
size_t period_len, enum dma_transfer_direction direction,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct spinal_lib_dmasg_chan *chan = to_spinal_lib_dmasg_chan(dchan);
|
||||
struct spinal_lib_dmasg_tx_descriptor * desc;
|
||||
struct spinal_lib_dmasg_segment *head_segment, *prev = NULL;
|
||||
int i;
|
||||
|
||||
//printk("spinal_lib_dmasg_prep_dma_cyclic\n");
|
||||
|
||||
if (!period_len)
|
||||
return NULL;
|
||||
|
||||
desc = spinal_lib_dmasg_alloc_tx_descriptor(chan);
|
||||
|
||||
dma_async_tx_descriptor_init(&desc->async_tx, &chan->common);
|
||||
desc->async_tx.tx_submit = spinal_lib_dmasg_tx_submit;
|
||||
desc->period_len = period_len;
|
||||
desc->period_left = period_len;
|
||||
desc->buffer_len = buf_len;
|
||||
desc->buffer_left = buf_len;
|
||||
desc->buf_addr = buf_addr;
|
||||
|
||||
for (i = 0; i < 10; ++i) {
|
||||
struct spinal_lib_dmasg_segment *segment;
|
||||
|
||||
segment = spinal_lib_dmasg_alloc_segment(chan);
|
||||
segment->hw.status = DMASG_DESCRIPTOR_STATUS_COMPLETED;
|
||||
|
||||
if (prev){
|
||||
prev->hw.next = segment->phys;
|
||||
prev->next = segment;
|
||||
}
|
||||
|
||||
prev = segment;
|
||||
|
||||
list_add_tail(&segment->node, &desc->segments);
|
||||
}
|
||||
|
||||
head_segment = list_first_entry(&desc->segments, struct spinal_lib_dmasg_segment, node);
|
||||
prev->hw.next = head_segment->phys;
|
||||
prev->next = head_segment;
|
||||
|
||||
return &desc->async_tx;
|
||||
}
|
||||
|
||||
static struct dma_async_tx_descriptor *spinal_lib_dmasg_prep_slave_sg(
|
||||
struct dma_chan *dchan, struct scatterlist *sgl, unsigned int sg_len,
|
||||
enum dma_transfer_direction direction, unsigned long flags,
|
||||
void *context)
|
||||
{
|
||||
//printk("spinal_lib_dmasg_prep_slave_sg\n");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void spinal_lib_dmasg_chan_remove(struct spinal_lib_dmasg_chan *chan)
|
||||
{
|
||||
//printk("spinal_lib_dmasg_chan_remove\n");
|
||||
|
||||
list_del(&chan->common.device_node);
|
||||
}
|
||||
|
||||
static int spinal_lib_dmasg_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct spinal_lib_dmasg_device *priv;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device_node *child = pdev->dev.of_node;
|
||||
int i, err;
|
||||
|
||||
//printk("spinal_lib_dmasg_probe\n");
|
||||
|
||||
/* Allocate and initialize the DMA engine structure */
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
priv->dev = &pdev->dev;
|
||||
priv->common.dev = &pdev->dev;
|
||||
|
||||
/* Count sub channels */
|
||||
priv->chan_count = 0;
|
||||
for_each_child_of_node(node, child) {
|
||||
priv->chan_count += 1;
|
||||
}
|
||||
|
||||
priv->chan = devm_kzalloc(&pdev->dev, sizeof(*priv->chan)*priv->chan_count, GFP_KERNEL);
|
||||
if (!priv->chan)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Request and map I/O memory */
|
||||
priv->regs = devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 0));
|
||||
if (IS_ERR(priv->regs))
|
||||
return PTR_ERR(priv->regs);
|
||||
|
||||
INIT_LIST_HEAD(&priv->common.channels);
|
||||
dma_cap_set(DMA_CYCLIC, priv->common.cap_mask);
|
||||
|
||||
priv->common.device_prep_slave_sg = spinal_lib_dmasg_prep_slave_sg;
|
||||
priv->common.device_alloc_chan_resources = spinal_lib_dmasg_alloc_chan_resources;
|
||||
priv->common.device_free_chan_resources = spinal_lib_dmasg_free_chan_resources;
|
||||
priv->common.device_terminate_all = spinal_lib_dmasg_terminate_all;
|
||||
priv->common.device_tx_status = spinal_lib_dmasg_tx_status;
|
||||
priv->common.device_issue_pending = spinal_lib_dmasg_issue_pending;
|
||||
priv->common.device_prep_dma_cyclic = spinal_lib_dmasg_prep_dma_cyclic;
|
||||
priv->common.residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
/* Initialize the channels */
|
||||
i = 0;
|
||||
for_each_child_of_node(node, child) {
|
||||
err = spinal_lib_dmasg_chan_probe(priv, child, i);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
i++;
|
||||
}
|
||||
|
||||
/* Register the DMA engine with the core */
|
||||
dma_async_device_register(&priv->common);
|
||||
|
||||
err = of_dma_controller_register(node, of_dma_spinal_lib_xlate, priv);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "Unable to register DMA to DT\n");
|
||||
dma_async_device_unregister(&priv->common);
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
dev_info(&pdev->dev, "Probe success\n");
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
dev_info(&pdev->dev, "Probe failure :(\n");
|
||||
for (i = 0; i < priv->chan_count; i++)
|
||||
spinal_lib_dmasg_chan_remove(&priv->chan[i]);
|
||||
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int spinal_lib_dmasg_remove(struct platform_device *pdev)
|
||||
{
|
||||
//printk("spinal_lib_dmasg_remove\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct of_device_id spinal_lib_dmasg_of_ids[] = {
|
||||
{ .compatible = "spinal,lib-dmasg"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, spinal_lib_dmasg_of_ids);
|
||||
|
||||
static struct platform_driver spinal_lib_vdma_driver = {
|
||||
.driver = {
|
||||
.name = "spinal,lib-dmasg",
|
||||
.of_match_table = spinal_lib_dmasg_of_ids,
|
||||
},
|
||||
.probe = spinal_lib_dmasg_probe,
|
||||
.remove = spinal_lib_dmasg_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(spinal_lib_vdma_driver);
|
||||
|
||||
MODULE_AUTHOR("Spinal");
|
||||
MODULE_DESCRIPTION("SpinalHDL DMASG driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
Reference in New Issue
Block a user