diff options
Diffstat (limited to 'drivers/staging/kpc2000/kpc_dma/dma.c')
-rw-r--r-- | drivers/staging/kpc2000/kpc_dma/dma.c | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/drivers/staging/kpc2000/kpc_dma/dma.c b/drivers/staging/kpc2000/kpc_dma/dma.c new file mode 100644 index 000000000000..6959bac11388 --- /dev/null +++ b/drivers/staging/kpc2000/kpc_dma/dma.c @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <asm/io.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/rwsem.h> +#include "kpc_dma_driver.h" + +/********** IRQ Handlers **********/ +static +irqreturn_t ndd_irq_handler(int irq, void *dev_id) +{ + struct kpc_dma_device *ldev = (struct kpc_dma_device*)dev_id; + + if ((GetEngineControl(ldev) & ENG_CTL_IRQ_ACTIVE) || (ldev->desc_completed->MyDMAAddr != GetEngineCompletePtr(ldev))) + schedule_work(&ldev->irq_work); + + return IRQ_HANDLED; +} + +static +void ndd_irq_worker(struct work_struct *ws) +{ + struct kpc_dma_descriptor *cur; + struct kpc_dma_device *eng = container_of(ws, struct kpc_dma_device, irq_work); + lock_engine(eng); + + if (GetEngineCompletePtr(eng) == 0) + goto out; + + if (eng->desc_completed->MyDMAAddr == GetEngineCompletePtr(eng)) + goto out; + + cur = eng->desc_completed; + do { + cur = cur->Next; + dev_dbg(&eng->pldev->dev, "Handling completed descriptor %p (acd = %p)\n", cur, cur->acd); + BUG_ON(cur == eng->desc_next); // Ordering failure. + + if (cur->DescControlFlags & DMA_DESC_CTL_SOP){ + eng->accumulated_bytes = 0; + eng->accumulated_flags = 0; + } + + eng->accumulated_bytes += cur->DescByteCount; + if (cur->DescStatusFlags & DMA_DESC_STS_ERROR) + eng->accumulated_flags |= ACD_FLAG_ENG_ACCUM_ERROR; + + if (cur->DescStatusFlags & DMA_DESC_STS_SHORT) + eng->accumulated_flags |= ACD_FLAG_ENG_ACCUM_SHORT; + + if (cur->DescControlFlags & DMA_DESC_CTL_EOP){ + if (cur->acd) + transfer_complete_cb(cur->acd, eng->accumulated_bytes, eng->accumulated_flags | ACD_FLAG_DONE); + } + + eng->desc_completed = cur; + } while (cur->MyDMAAddr != GetEngineCompletePtr(eng)); + + out: + SetClearEngineControl(eng, ENG_CTL_IRQ_ACTIVE, 0); + + unlock_engine(eng); +} + + +/********** DMA Engine Init/Teardown **********/ +void start_dma_engine(struct kpc_dma_device *eng) +{ + eng->desc_next = eng->desc_pool_first; + eng->desc_completed = eng->desc_pool_last; + + // Setup the engine pointer registers + SetEngineNextPtr(eng, eng->desc_pool_first); + SetEngineSWPtr(eng, eng->desc_pool_first); + ClearEngineCompletePtr(eng); + + WriteEngineControl(eng, ENG_CTL_DMA_ENABLE | ENG_CTL_IRQ_ENABLE); +} + +int setup_dma_engine(struct kpc_dma_device *eng, u32 desc_cnt) +{ + u32 caps; + struct kpc_dma_descriptor * cur; + struct kpc_dma_descriptor * next; + dma_addr_t next_handle; + dma_addr_t head_handle; + unsigned int i; + int rv; + dev_dbg(&eng->pldev->dev, "Setting up DMA engine [%p]\n", eng); + + caps = GetEngineCapabilities(eng); + + if (WARN(!(caps & ENG_CAP_PRESENT), "setup_dma_engine() called for DMA Engine at %p which isn't present in hardware!\n", eng)) + return -ENXIO; + + if (caps & ENG_CAP_DIRECTION){ + eng->dir = DMA_FROM_DEVICE; + } else { + eng->dir = DMA_TO_DEVICE; + } + + eng->desc_pool_cnt = desc_cnt; + eng->desc_pool = dma_pool_create("KPC DMA Descriptors", &eng->pldev->dev, sizeof(struct kpc_dma_descriptor), DMA_DESC_ALIGNMENT, 4096); + + eng->desc_pool_first = dma_pool_alloc(eng->desc_pool, GFP_KERNEL | GFP_DMA, &head_handle); + if (!eng->desc_pool_first){ + dev_err(&eng->pldev->dev, "setup_dma_engine: couldn't allocate desc_pool_first!\n"); + dma_pool_destroy(eng->desc_pool); + return -ENOMEM; + } + + eng->desc_pool_first->MyDMAAddr = head_handle; + clear_desc(eng->desc_pool_first); + + cur = eng->desc_pool_first; + for (i = 1 ; i < eng->desc_pool_cnt ; i++){ + next = dma_pool_alloc(eng->desc_pool, GFP_KERNEL | GFP_DMA, &next_handle); + if (next == NULL) + goto done_alloc; + + clear_desc(next); + next->MyDMAAddr = next_handle; + + cur->DescNextDescPtr = next_handle; + cur->Next = next; + cur = next; + } + + done_alloc: + // Link the last descriptor back to the first, so it's a circular linked list + cur->Next = eng->desc_pool_first; + cur->DescNextDescPtr = eng->desc_pool_first->MyDMAAddr; + + eng->desc_pool_last = cur; + eng->desc_completed = eng->desc_pool_last; + + // Setup work queue + INIT_WORK(&eng->irq_work, ndd_irq_worker); + + // Grab IRQ line + rv = request_irq(eng->irq, ndd_irq_handler, IRQF_SHARED, KP_DRIVER_NAME_DMA_CONTROLLER, eng); + if (rv){ + dev_err(&eng->pldev->dev, "setup_dma_engine: failed to request_irq: %d\n", rv); + return rv; + } + + // Turn on the engine! + start_dma_engine(eng); + unlock_engine(eng); + + return 0; +} + +void stop_dma_engine(struct kpc_dma_device *eng) +{ + unsigned long timeout; + dev_dbg(&eng->pldev->dev, "Destroying DMA engine [%p]\n", eng); + + // Disable the descriptor engine + WriteEngineControl(eng, 0); + + // Wait for descriptor engine to finish current operaion + timeout = jiffies + (HZ / 2); + while (GetEngineControl(eng) & ENG_CTL_DMA_RUNNING){ + if (time_after(jiffies, timeout)){ + dev_crit(&eng->pldev->dev, "DMA_RUNNING still asserted!\n"); + break; + } + } + + // Request a reset + WriteEngineControl(eng, ENG_CTL_DMA_RESET_REQUEST); + + // Wait for reset request to be processed + timeout = jiffies + (HZ / 2); + while (GetEngineControl(eng) & (ENG_CTL_DMA_RUNNING | ENG_CTL_DMA_RESET_REQUEST)){ + if (time_after(jiffies, timeout)){ + dev_crit(&eng->pldev->dev, "ENG_CTL_DMA_RESET_REQUEST still asserted!\n"); + break; + } + } + + // Request a reset + WriteEngineControl(eng, ENG_CTL_DMA_RESET); + + // And wait for reset to complete + timeout = jiffies + (HZ / 2); + while (GetEngineControl(eng) & ENG_CTL_DMA_RESET){ + if (time_after(jiffies, timeout)){ + dev_crit(&eng->pldev->dev, "DMA_RESET still asserted!\n"); + break; + } + } + + // Clear any persistent bits just to make sure there is no residue from the reset + SetClearEngineControl(eng, (ENG_CTL_IRQ_ACTIVE | ENG_CTL_DESC_COMPLETE | ENG_CTL_DESC_ALIGN_ERR | ENG_CTL_DESC_FETCH_ERR | ENG_CTL_SW_ABORT_ERR | ENG_CTL_DESC_CHAIN_END | ENG_CTL_DMA_WAITING_PERSIST), 0); + + // Reset performance counters + + // Completely disable the engine + WriteEngineControl(eng, 0); +} + +void destroy_dma_engine(struct kpc_dma_device *eng) +{ + struct kpc_dma_descriptor * cur; + dma_addr_t cur_handle; + unsigned int i; + + stop_dma_engine(eng); + + cur = eng->desc_pool_first; + cur_handle = eng->desc_pool_first->MyDMAAddr; + + for (i = 0 ; i < eng->desc_pool_cnt ; i++){ + struct kpc_dma_descriptor *next = cur->Next; + dma_addr_t next_handle = cur->DescNextDescPtr; + dma_pool_free(eng->desc_pool, cur, cur_handle); + cur_handle = next_handle; + cur = next; + } + + dma_pool_destroy(eng->desc_pool); + + free_irq(eng->irq, eng); +} + + + +/********** Helper Functions **********/ +int count_descriptors_available(struct kpc_dma_device *eng) +{ + u32 count = 0; + struct kpc_dma_descriptor *cur = eng->desc_next; + while (cur != eng->desc_completed){ + BUG_ON(cur == NULL); + count++; + cur = cur->Next; + } + return count; +} + +void clear_desc(struct kpc_dma_descriptor *desc) +{ + if (desc == NULL) + return; + desc->DescByteCount = 0; + desc->DescStatusErrorFlags = 0; + desc->DescStatusFlags = 0; + desc->DescUserControlLS = 0; + desc->DescUserControlMS = 0; + desc->DescCardAddrLS = 0; + desc->DescBufferByteCount = 0; + desc->DescCardAddrMS = 0; + desc->DescControlFlags = 0; + desc->DescSystemAddrLS = 0; + desc->DescSystemAddrMS = 0; + desc->acd = NULL; +} |