247 lines
6.0 KiB
C
247 lines
6.0 KiB
C
/* 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"
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Matt.Sickler@daktronics.com");
|
|
|
|
#define KPC_DMA_CHAR_MAJOR UNNAMED_MAJOR
|
|
#define KPC_DMA_NUM_MINORS BIT(MINORBITS)
|
|
static DEFINE_MUTEX(kpc_dma_mtx);
|
|
static int assigned_major_num;
|
|
static LIST_HEAD(kpc_dma_list);
|
|
|
|
/********** kpc_dma_list list management **********/
|
|
struct kpc_dma_device *kpc_dma_lookup_device(int minor)
|
|
{
|
|
struct kpc_dma_device *c;
|
|
|
|
mutex_lock(&kpc_dma_mtx);
|
|
list_for_each_entry(c, &kpc_dma_list, list) {
|
|
if (c->pldev->id == minor) {
|
|
goto out;
|
|
}
|
|
}
|
|
c = NULL; // not-found case
|
|
out:
|
|
mutex_unlock(&kpc_dma_mtx);
|
|
return c;
|
|
}
|
|
|
|
static void kpc_dma_add_device(struct kpc_dma_device *ldev)
|
|
{
|
|
mutex_lock(&kpc_dma_mtx);
|
|
list_add(&ldev->list, &kpc_dma_list);
|
|
mutex_unlock(&kpc_dma_mtx);
|
|
}
|
|
|
|
static void kpc_dma_del_device(struct kpc_dma_device *ldev)
|
|
{
|
|
mutex_lock(&kpc_dma_mtx);
|
|
list_del(&ldev->list);
|
|
mutex_unlock(&kpc_dma_mtx);
|
|
}
|
|
|
|
/********** SysFS Attributes **********/
|
|
static ssize_t show_engine_regs(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct kpc_dma_device *ldev;
|
|
struct platform_device *pldev = to_platform_device(dev);
|
|
|
|
if (!pldev)
|
|
return 0;
|
|
ldev = platform_get_drvdata(pldev);
|
|
if (!ldev)
|
|
return 0;
|
|
|
|
return scnprintf(buf, PAGE_SIZE,
|
|
"EngineControlStatus = 0x%08x\n"
|
|
"RegNextDescPtr = 0x%08x\n"
|
|
"RegSWDescPtr = 0x%08x\n"
|
|
"RegCompletedDescPtr = 0x%08x\n"
|
|
"desc_pool_first = %p\n"
|
|
"desc_pool_last = %p\n"
|
|
"desc_next = %p\n"
|
|
"desc_completed = %p\n",
|
|
readl(ldev->eng_regs + 1),
|
|
readl(ldev->eng_regs + 2),
|
|
readl(ldev->eng_regs + 3),
|
|
readl(ldev->eng_regs + 4),
|
|
ldev->desc_pool_first,
|
|
ldev->desc_pool_last,
|
|
ldev->desc_next,
|
|
ldev->desc_completed
|
|
);
|
|
}
|
|
static DEVICE_ATTR(engine_regs, 0444, show_engine_regs, NULL);
|
|
|
|
static const struct attribute *ndd_attr_list[] = {
|
|
&dev_attr_engine_regs.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct class *kpc_dma_class;
|
|
|
|
/********** Platform Driver Functions **********/
|
|
static
|
|
int kpc_dma_probe(struct platform_device *pldev)
|
|
{
|
|
struct resource *r = NULL;
|
|
int rv = 0;
|
|
dev_t dev;
|
|
|
|
struct kpc_dma_device *ldev = kzalloc(sizeof(struct kpc_dma_device), GFP_KERNEL);
|
|
|
|
if (!ldev) {
|
|
dev_err(&pldev->dev, "%s: unable to kzalloc space for kpc_dma_device\n", __func__);
|
|
rv = -ENOMEM;
|
|
goto err_rv;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&ldev->list);
|
|
|
|
ldev->pldev = pldev;
|
|
platform_set_drvdata(pldev, ldev);
|
|
atomic_set(&ldev->open_count, 1);
|
|
|
|
mutex_init(&ldev->sem);
|
|
lock_engine(ldev);
|
|
|
|
// Get Engine regs resource
|
|
r = platform_get_resource(pldev, IORESOURCE_MEM, 0);
|
|
if (!r) {
|
|
dev_err(&ldev->pldev->dev, "%s: didn't get the engine regs resource!\n", __func__);
|
|
rv = -ENXIO;
|
|
goto err_kfree;
|
|
}
|
|
ldev->eng_regs = ioremap_nocache(r->start, resource_size(r));
|
|
if (!ldev->eng_regs) {
|
|
dev_err(&ldev->pldev->dev, "%s: failed to ioremap engine regs!\n", __func__);
|
|
rv = -ENXIO;
|
|
goto err_kfree;
|
|
}
|
|
|
|
r = platform_get_resource(pldev, IORESOURCE_IRQ, 0);
|
|
if (!r) {
|
|
dev_err(&ldev->pldev->dev, "%s: didn't get the IRQ resource!\n", __func__);
|
|
rv = -ENXIO;
|
|
goto err_kfree;
|
|
}
|
|
ldev->irq = r->start;
|
|
|
|
// Setup miscdev struct
|
|
dev = MKDEV(assigned_major_num, pldev->id);
|
|
ldev->kpc_dma_dev = device_create(kpc_dma_class, &pldev->dev, dev, ldev, "kpc_dma%d", pldev->id);
|
|
if (IS_ERR(ldev->kpc_dma_dev)) {
|
|
dev_err(&ldev->pldev->dev, "%s: device_create failed: %d\n", __func__, rv);
|
|
goto err_kfree;
|
|
}
|
|
|
|
// Setup the DMA engine
|
|
rv = setup_dma_engine(ldev, 30);
|
|
if (rv) {
|
|
dev_err(&ldev->pldev->dev, "%s: failed to setup_dma_engine: %d\n", __func__, rv);
|
|
goto err_misc_dereg;
|
|
}
|
|
|
|
// Setup the sysfs files
|
|
rv = sysfs_create_files(&(ldev->pldev->dev.kobj), ndd_attr_list);
|
|
if (rv) {
|
|
dev_err(&ldev->pldev->dev, "%s: Failed to add sysfs files: %d\n", __func__, rv);
|
|
goto err_destroy_eng;
|
|
}
|
|
|
|
kpc_dma_add_device(ldev);
|
|
|
|
return 0;
|
|
|
|
err_destroy_eng:
|
|
destroy_dma_engine(ldev);
|
|
err_misc_dereg:
|
|
device_destroy(kpc_dma_class, dev);
|
|
err_kfree:
|
|
kfree(ldev);
|
|
err_rv:
|
|
return rv;
|
|
}
|
|
|
|
static
|
|
int kpc_dma_remove(struct platform_device *pldev)
|
|
{
|
|
struct kpc_dma_device *ldev = platform_get_drvdata(pldev);
|
|
|
|
if (!ldev)
|
|
return -ENXIO;
|
|
|
|
lock_engine(ldev);
|
|
sysfs_remove_files(&(ldev->pldev->dev.kobj), ndd_attr_list);
|
|
destroy_dma_engine(ldev);
|
|
kpc_dma_del_device(ldev);
|
|
device_destroy(kpc_dma_class, MKDEV(assigned_major_num, ldev->pldev->id));
|
|
kfree(ldev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/********** Driver Functions **********/
|
|
static struct platform_driver kpc_dma_plat_driver_i = {
|
|
.probe = kpc_dma_probe,
|
|
.remove = kpc_dma_remove,
|
|
.driver = {
|
|
.name = KP_DRIVER_NAME_DMA_CONTROLLER,
|
|
},
|
|
};
|
|
|
|
static
|
|
int __init kpc_dma_driver_init(void)
|
|
{
|
|
int err;
|
|
|
|
err = __register_chrdev(KPC_DMA_CHAR_MAJOR, 0, KPC_DMA_NUM_MINORS, "kpc_dma", &kpc_dma_fops);
|
|
if (err < 0) {
|
|
pr_err("Can't allocate a major number (%d) for kpc_dma (err = %d)\n", KPC_DMA_CHAR_MAJOR, err);
|
|
goto fail_chrdev_register;
|
|
}
|
|
assigned_major_num = err;
|
|
|
|
kpc_dma_class = class_create(THIS_MODULE, "kpc_dma");
|
|
err = PTR_ERR(kpc_dma_class);
|
|
if (IS_ERR(kpc_dma_class)) {
|
|
pr_err("Can't create class kpc_dma (err = %d)\n", err);
|
|
goto fail_class_create;
|
|
}
|
|
|
|
err = platform_driver_register(&kpc_dma_plat_driver_i);
|
|
if (err) {
|
|
pr_err("Can't register platform driver for kpc_dma (err = %d)\n", err);
|
|
goto fail_platdriver_register;
|
|
}
|
|
|
|
return err;
|
|
|
|
fail_platdriver_register:
|
|
class_destroy(kpc_dma_class);
|
|
fail_class_create:
|
|
__unregister_chrdev(KPC_DMA_CHAR_MAJOR, 0, KPC_DMA_NUM_MINORS, "kpc_dma");
|
|
fail_chrdev_register:
|
|
return err;
|
|
}
|
|
module_init(kpc_dma_driver_init);
|
|
|
|
static
|
|
void __exit kpc_dma_driver_exit(void)
|
|
{
|
|
platform_driver_unregister(&kpc_dma_plat_driver_i);
|
|
class_destroy(kpc_dma_class);
|
|
__unregister_chrdev(KPC_DMA_CHAR_MAJOR, 0, KPC_DMA_NUM_MINORS, "kpc_dma");
|
|
}
|
|
module_exit(kpc_dma_driver_exit);
|