1 // SPDX-License-Identifier: GPL-2.0+
2 #include <linux/init.h>
3 #include <linux/module.h>
4 #include <linux/types.h>
5 #include <linux/io.h>
6 #include <linux/export.h>
7 #include <linux/slab.h>
8 #include <linux/platform_device.h>
9 #include <linux/fs.h>
10 #include <linux/rwsem.h>
11 #include "kpc_dma_driver.h"
12 
13 MODULE_LICENSE("GPL");
14 MODULE_AUTHOR("Matt.Sickler@daktronics.com");
15 
16 #define KPC_DMA_CHAR_MAJOR    UNNAMED_MAJOR
17 #define KPC_DMA_NUM_MINORS    BIT(MINORBITS)
18 static DEFINE_MUTEX(kpc_dma_mtx);
19 static int assigned_major_num;
20 static LIST_HEAD(kpc_dma_list);
21 
22 /**********  kpc_dma_list list management  **********/
kpc_dma_lookup_device(int minor)23 struct kpc_dma_device *kpc_dma_lookup_device(int minor)
24 {
25 	struct kpc_dma_device *c;
26 
27 	mutex_lock(&kpc_dma_mtx);
28 	list_for_each_entry(c, &kpc_dma_list, list) {
29 		if (c->pldev->id == minor)
30 			goto out;
31 	}
32 	c = NULL; // not-found case
33 out:
34 	mutex_unlock(&kpc_dma_mtx);
35 	return c;
36 }
37 
kpc_dma_add_device(struct kpc_dma_device * ldev)38 static void kpc_dma_add_device(struct kpc_dma_device *ldev)
39 {
40 	mutex_lock(&kpc_dma_mtx);
41 	list_add(&ldev->list, &kpc_dma_list);
42 	mutex_unlock(&kpc_dma_mtx);
43 }
44 
kpc_dma_del_device(struct kpc_dma_device * ldev)45 static void kpc_dma_del_device(struct kpc_dma_device *ldev)
46 {
47 	mutex_lock(&kpc_dma_mtx);
48 	list_del(&ldev->list);
49 	mutex_unlock(&kpc_dma_mtx);
50 }
51 
52 /**********  SysFS Attributes **********/
show_engine_regs(struct device * dev,struct device_attribute * attr,char * buf)53 static ssize_t  show_engine_regs(struct device *dev, struct device_attribute *attr, char *buf)
54 {
55 	struct kpc_dma_device *ldev;
56 	struct platform_device *pldev = to_platform_device(dev);
57 
58 	if (!pldev)
59 		return 0;
60 	ldev = platform_get_drvdata(pldev);
61 	if (!ldev)
62 		return 0;
63 
64 	return scnprintf(buf, PAGE_SIZE,
65 		"EngineControlStatus      = 0x%08x\n"
66 		"RegNextDescPtr           = 0x%08x\n"
67 		"RegSWDescPtr             = 0x%08x\n"
68 		"RegCompletedDescPtr      = 0x%08x\n"
69 		"desc_pool_first          = %p\n"
70 		"desc_pool_last           = %p\n"
71 		"desc_next                = %p\n"
72 		"desc_completed           = %p\n",
73 		readl(ldev->eng_regs + 1),
74 		readl(ldev->eng_regs + 2),
75 		readl(ldev->eng_regs + 3),
76 		readl(ldev->eng_regs + 4),
77 		ldev->desc_pool_first,
78 		ldev->desc_pool_last,
79 		ldev->desc_next,
80 		ldev->desc_completed
81 	);
82 }
83 static DEVICE_ATTR(engine_regs, 0444, show_engine_regs, NULL);
84 
85 static const struct attribute *ndd_attr_list[] = {
86 	&dev_attr_engine_regs.attr,
87 	NULL,
88 };
89 
90 static struct class *kpc_dma_class;
91 
92 /**********  Platform Driver Functions  **********/
93 static
kpc_dma_probe(struct platform_device * pldev)94 int  kpc_dma_probe(struct platform_device *pldev)
95 {
96 	struct resource *r = NULL;
97 	int rv = 0;
98 	dev_t dev;
99 
100 	struct kpc_dma_device *ldev = kzalloc(sizeof(*ldev), GFP_KERNEL);
101 
102 	if (!ldev) {
103 		dev_err(&pldev->dev, "%s: unable to kzalloc space for kpc_dma_device\n", __func__);
104 		rv = -ENOMEM;
105 		goto err_rv;
106 	}
107 
108 	INIT_LIST_HEAD(&ldev->list);
109 
110 	ldev->pldev = pldev;
111 	platform_set_drvdata(pldev, ldev);
112 	atomic_set(&ldev->open_count, 1);
113 
114 	mutex_init(&ldev->sem);
115 	lock_engine(ldev);
116 
117 	// Get Engine regs resource
118 	r = platform_get_resource(pldev, IORESOURCE_MEM, 0);
119 	if (!r) {
120 		dev_err(&ldev->pldev->dev, "%s: didn't get the engine regs resource!\n", __func__);
121 		rv = -ENXIO;
122 		goto err_kfree;
123 	}
124 	ldev->eng_regs = ioremap(r->start, resource_size(r));
125 	if (!ldev->eng_regs) {
126 		dev_err(&ldev->pldev->dev, "%s: failed to ioremap engine regs!\n", __func__);
127 		rv = -ENXIO;
128 		goto err_kfree;
129 	}
130 
131 	r = platform_get_resource(pldev, IORESOURCE_IRQ, 0);
132 	if (!r) {
133 		dev_err(&ldev->pldev->dev, "%s: didn't get the IRQ resource!\n", __func__);
134 		rv = -ENXIO;
135 		goto err_kfree;
136 	}
137 	ldev->irq = r->start;
138 
139 	// Setup miscdev struct
140 	dev = MKDEV(assigned_major_num, pldev->id);
141 	ldev->kpc_dma_dev = device_create(kpc_dma_class, &pldev->dev, dev, ldev, "kpc_dma%d", pldev->id);
142 	if (IS_ERR(ldev->kpc_dma_dev)) {
143 		rv = PTR_ERR(ldev->kpc_dma_dev);
144 		dev_err(&ldev->pldev->dev, "%s: device_create failed: %d\n", __func__, rv);
145 		goto err_kfree;
146 	}
147 
148 	// Setup the DMA engine
149 	rv = setup_dma_engine(ldev, 30);
150 	if (rv) {
151 		dev_err(&ldev->pldev->dev, "%s: failed to setup_dma_engine: %d\n", __func__, rv);
152 		goto err_misc_dereg;
153 	}
154 
155 	// Setup the sysfs files
156 	rv = sysfs_create_files(&(ldev->pldev->dev.kobj), ndd_attr_list);
157 	if (rv) {
158 		dev_err(&ldev->pldev->dev, "%s: Failed to add sysfs files: %d\n", __func__, rv);
159 		goto err_destroy_eng;
160 	}
161 
162 	kpc_dma_add_device(ldev);
163 
164 	return 0;
165 
166  err_destroy_eng:
167 	destroy_dma_engine(ldev);
168  err_misc_dereg:
169 	device_destroy(kpc_dma_class, dev);
170  err_kfree:
171 	kfree(ldev);
172  err_rv:
173 	return rv;
174 }
175 
176 static
kpc_dma_remove(struct platform_device * pldev)177 int  kpc_dma_remove(struct platform_device *pldev)
178 {
179 	struct kpc_dma_device *ldev = platform_get_drvdata(pldev);
180 
181 	if (!ldev)
182 		return -ENXIO;
183 
184 	lock_engine(ldev);
185 	sysfs_remove_files(&(ldev->pldev->dev.kobj), ndd_attr_list);
186 	destroy_dma_engine(ldev);
187 	kpc_dma_del_device(ldev);
188 	device_destroy(kpc_dma_class, MKDEV(assigned_major_num, ldev->pldev->id));
189 	kfree(ldev);
190 
191 	return 0;
192 }
193 
194 /**********  Driver Functions  **********/
195 static struct platform_driver kpc_dma_plat_driver_i = {
196 	.probe        = kpc_dma_probe,
197 	.remove       = kpc_dma_remove,
198 	.driver = {
199 		.name   = KP_DRIVER_NAME_DMA_CONTROLLER,
200 	},
201 };
202 
203 static
kpc_dma_driver_init(void)204 int __init kpc_dma_driver_init(void)
205 {
206 	int err;
207 
208 	err = __register_chrdev(KPC_DMA_CHAR_MAJOR, 0, KPC_DMA_NUM_MINORS, "kpc_dma", &kpc_dma_fops);
209 	if (err < 0) {
210 		pr_err("Can't allocate a major number (%d) for kpc_dma (err = %d)\n", KPC_DMA_CHAR_MAJOR, err);
211 		goto fail_chrdev_register;
212 	}
213 	assigned_major_num = err;
214 
215 	kpc_dma_class = class_create(THIS_MODULE, "kpc_dma");
216 	err = PTR_ERR(kpc_dma_class);
217 	if (IS_ERR(kpc_dma_class)) {
218 		pr_err("Can't create class kpc_dma (err = %d)\n", err);
219 		goto fail_class_create;
220 	}
221 
222 	err = platform_driver_register(&kpc_dma_plat_driver_i);
223 	if (err) {
224 		pr_err("Can't register platform driver for kpc_dma (err = %d)\n", err);
225 		goto fail_platdriver_register;
226 	}
227 
228 	return err;
229 
230 fail_platdriver_register:
231 	class_destroy(kpc_dma_class);
232 fail_class_create:
233 	__unregister_chrdev(KPC_DMA_CHAR_MAJOR, 0, KPC_DMA_NUM_MINORS, "kpc_dma");
234 fail_chrdev_register:
235 	return err;
236 }
237 module_init(kpc_dma_driver_init);
238 
239 static
kpc_dma_driver_exit(void)240 void __exit kpc_dma_driver_exit(void)
241 {
242 	platform_driver_unregister(&kpc_dma_plat_driver_i);
243 	class_destroy(kpc_dma_class);
244 	__unregister_chrdev(KPC_DMA_CHAR_MAJOR, 0, KPC_DMA_NUM_MINORS, "kpc_dma");
245 }
246 module_exit(kpc_dma_driver_exit);
247