aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/kpc2000/kpc_dma/kpc_dma_driver.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/kpc2000/kpc_dma/kpc_dma_driver.c')
-rw-r--r--drivers/staging/kpc2000/kpc_dma/kpc_dma_driver.c248
1 files changed, 248 insertions, 0 deletions
diff --git a/drivers/staging/kpc2000/kpc_dma/kpc_dma_driver.c b/drivers/staging/kpc2000/kpc_dma/kpc_dma_driver.c
new file mode 100644
index 000000000000..aeae58d9bc18
--- /dev/null
+++ b/drivers/staging/kpc2000/kpc_dma/kpc_dma_driver.c
@@ -0,0 +1,248 @@
+/* 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 1 << 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;
+}
+
+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);
+}
+
+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
+ );
+}
+DEVICE_ATTR(engine_regs, 0444, show_engine_regs, NULL);
+
+static const struct attribute * ndd_attr_list[] = {
+ &dev_attr_engine_regs.attr,
+ NULL,
+};
+
+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, "kpc_dma_probe: unable to kzalloc space for kpc_dma_device\n");
+ rv = -ENOMEM;
+ goto err_rv;
+ }
+
+ dev_dbg(&pldev->dev, "kpc_dma_probe(pldev = [%p]) ldev = [%p]\n", pldev, ldev);
+
+ 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, "kpc_dma_probe: didn't get the engine regs resource!\n");
+ rv = -ENXIO;
+ goto err_kfree;
+ }
+ ldev->eng_regs = ioremap_nocache(r->start, resource_size(r));
+ if (!ldev->eng_regs){
+ dev_err(&ldev->pldev->dev, "kpc_dma_probe: failed to ioremap engine regs!\n");
+ rv = -ENXIO;
+ goto err_kfree;
+ }
+
+ r = platform_get_resource(pldev, IORESOURCE_IRQ, 0);
+ if (!r){
+ dev_err(&ldev->pldev->dev, "kpc_dma_probe: didn't get the IRQ resource!\n");
+ 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, "kpc_dma_probe: device_create failed: %d\n", rv);
+ goto err_kfree;
+ }
+
+ // Setup the DMA engine
+ rv = setup_dma_engine(ldev, 30);
+ if (rv){
+ dev_err(&ldev->pldev->dev, "kpc_dma_probe: failed to setup_dma_engine: %d\n", 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, "kpc_dma_probe: Failed to add sysfs files: %d\n", 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;
+
+ dev_dbg(&ldev->pldev->dev, "kpc_dma_remove(pldev = [%p]) ldev = [%p]\n", pldev, ldev);
+
+ 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 **********/
+struct platform_driver kpc_dma_plat_driver_i = {
+ .probe = kpc_dma_probe,
+ .remove = kpc_dma_remove,
+ .driver = {
+ .name = KP_DRIVER_NAME_DMA_CONTROLLER,
+ .owner = THIS_MODULE,
+ },
+};
+
+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);