#include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");