-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Daktronics DMA driver. I've added the SPDX license identifiers, Kconfig entry, and cleaned up as many of the warnings as I could. The AIO support code will be removed in a future patch. Signed-off-by: Matt Sickler <matt.sickler@daktronics.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
- Loading branch information
Matt Sickler
authored and
Greg Kroah-Hartman
committed
Apr 25, 2019
1 parent
52c4dfc
commit 7df9529
Showing
8 changed files
with
1,181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# SPDX-License-Identifier: GPL-2.0 | ||
|
||
obj-m := kpc_dma.o | ||
kpc_dma-objs += dma.o | ||
kpc_dma-objs += fileops.o | ||
kpc_dma-objs += kpc_dma_driver.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
Oops, something went wrong.