-
Notifications
You must be signed in to change notification settings - Fork 1
/
genip_driver.c
314 lines (272 loc) · 9.66 KB
/
genip_driver.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/kdev_t.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include "genip_driver.h"
#include "genip_io.h"
#include "genip_devices.h"
#include "genip_module.h"
// TODO cleanup includes
static int genip_probe(struct platform_device *pdev);
static int genip_remove(struct platform_device *pdev);
static void genip_print_infos(struct genip_device *tes_dev);
// This contains all data needed globally by the driver
struct genip_global_t *genip_global;
static const struct of_device_id genip_of_ids[] = {
{.compatible = GENIP_COMPAT_STR_CDC,
.data = &genip_cdc_pdata},
{.compatible = GENIP_COMPAT_STR_DHD,
.data = &genip_dhd_pdata},
{.compatible = GENIP_COMPAT_STR_WARP,
.data = &genip_warp_pdata},
{.compatible = GENIP_COMPAT_STR_D2D,
.data = &genip_d2d_pdata},
{.compatible = GENIP_COMPAT_STR_FBD,
.data = &genip_fbd_pdata},
{.compatible = GENIP_COMPAT_STR_DSW,
.data = &genip_dsw_pdata},
{}};
static struct platform_driver genip_driver = {
.driver = {
.name = GENIP_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(genip_of_ids),
},
.probe = genip_probe,
.remove = genip_remove,
};
static void genip_print_infos(struct genip_device *tes_dev) {
uint32_t hwversion;
dev_warn(tes_dev->base_dev, "This driver is PRELIMINARY. Do NOT use in production environment!\n");
dev_info(tes_dev->base_dev, "Base address:\t0x%08lx - 0x%08lx\n",
tes_dev->base_phys, tes_dev->base_phys + tes_dev->span);
dev_info(tes_dev->base_dev, "IRQ:\t%d\n", tes_dev->irq_no);
hwversion = genip_read_reg(tes_dev->mmio, tes_dev->platform_data->version_reg);
dev_info(tes_dev->base_dev, "IP core rev. 0x%08X\n", hwversion);
}
/*
* probe function (init new hardware)
* copy all neccessary data from device tree description to local data structure and initialize the driver part.
* */
static int genip_probe(struct platform_device *pdev) {
struct resource *mem;
int result = 0;
int irq;
struct genip_device *tes_dev;
const struct of_device_id *of_device_match;
dev_t current_dev_t;
uint32_t hwversion;
struct genip_device *str_dev; /* required to readout device data from phandles */
struct genip_stream_dev *info_dev; /* holding streaming device name and layer info */
struct platform_device *str_pdev;
struct of_phandle_args str_args;
int str_idx = 0; /* index for streaming devices */
int str_anz_dev = 0; /* count of streaming devices */
int str_ret; /* returnvalue of parse_phandle */
// calculate the new dev_t for the device created here
current_dev_t = MKDEV(genip_global->major, genip_global->dev_count);
genip_global->dev_count++;
// alloc memory for device data
tes_dev = devm_kzalloc(&pdev->dev, sizeof(struct genip_device), GFP_KERNEL);
if (!tes_dev) {
dev_err(&pdev->dev,
"Memory allocation for driver struct failed!\n");
result = -ENOMEM;
goto ALLOC_MEM_FAILED;
}
// retrieve data from device tree
of_device_match = of_match_device(genip_of_ids, &pdev->dev);
tes_dev->platform_data = of_device_match->data;
platform_set_drvdata(pdev, tes_dev);
tes_dev->base_dev = &pdev->dev;
// copy data from device tree to device struct
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
tes_dev->mmio = devm_ioremap_resource(&pdev->dev, mem);
dev_dbg(&pdev->dev, "Mapped IO from 0x%x to 0x%p\n", mem->start,
tes_dev->mmio);
if (IS_ERR(tes_dev->mmio)) {
result = PTR_ERR(tes_dev->mmio);
goto IOREMAP_FAILED;
}
tes_dev->base_phys = mem->start;
tes_dev->span = mem->end - mem->start;
// check IP core version
hwversion = genip_read_reg(tes_dev->mmio, tes_dev->platform_data->version_reg)
& tes_dev->platform_data->version_reg_mask;
if(hwversion != tes_dev->platform_data->version_reg_expected) {
dev_err(&pdev->dev, "Unsupported IP core version: %08x\n", hwversion);
result = -EINVAL;
goto UNSUPPORTED;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Could not get platform IRQ number\n");
return irq;
}
tes_dev->irq_no = irq;
// init spinlock and irq
spin_lock_init(&tes_dev->irq_slck);
init_waitqueue_head(&tes_dev->irq_waitq);
// register device and create sys-file
tes_dev->base_dev = device_create(genip_global->class, NULL,
current_dev_t, tes_dev, tes_dev->platform_data->fs_dev_name);
if (!tes_dev->base_dev) {
dev_err(tes_dev->base_dev, "can't create device: %s\n", tes_dev->platform_data->fs_dev_name);
result = -EBUSY;
goto DEVICE_CREATE_FAILED;
}
// register IRQ
if (devm_request_irq(&pdev->dev, tes_dev->irq_no, genip_irq_handler, 0, tes_dev->platform_data->irq_name, (void *)tes_dev)) {
dev_err(tes_dev->base_dev, "can't register irq %d\n", tes_dev->irq_no);
result = -EBUSY;
goto IRQ_FAILED;
}
// set pointer to current device in global struct
genip_global->device_by_minor[MINOR(current_dev_t)] = tes_dev;
// probe finished; print some infos
genip_print_infos(tes_dev);
// check if device property exists
if (device_property_present((struct device *)&pdev->dev, "stream_dev")) {
// alloc memory for phandle device data
str_dev = devm_kzalloc(&pdev->dev, sizeof(struct genip_device), GFP_KERNEL);
if (!str_dev) {
dev_err(&pdev->dev,
"Memory allocation for driver struct failed!\n");
result = -ENOMEM;
goto ALLOC_MEM_FAILED;
}
while (str_idx < GENIP_MAX_STREAMS) {
// check if a phandle exists in device property
str_ret = of_parse_phandle_with_fixed_args(((struct device *)&pdev->dev)->of_node, "stream_dev", 1, str_idx, &str_args);
if (str_ret == 0) {
// get platform_device from streaming device
str_pdev = of_find_device_by_node(str_args.np);
if (!str_pdev) {
result = -ENODEV;
goto STREAM_DEVICE_FAILED;
}
// retrieve data from device tree
of_device_match = of_match_device(genip_of_ids, &str_pdev->dev);
str_dev->platform_data = of_device_match->data;
platform_set_drvdata(str_pdev, str_dev);
str_dev->base_dev = &str_pdev->dev;
// alloc memory for phandle device data
info_dev = devm_kzalloc(&pdev->dev, sizeof(struct genip_stream_dev), GFP_KERNEL);
if (!info_dev) {
dev_err(&pdev->dev,
"Memory allocation for driver struct failed!\n");
result = -ENOMEM;
goto ALLOC_MEM_FAILED;
}
// copy fd name and layer to info device structure
strncpy(info_dev->dev_name, str_dev->platform_data->fs_dev_name, DEV_NAME_MAX_LEN);
info_dev->layer = str_args.args[0];
// fill tes_dev with info
tes_dev->stream_dev[str_idx] = info_dev;
dev_info(tes_dev->base_dev, "Found streaming device /dev/%s connected to layer %d\n",
tes_dev->stream_dev[str_idx]->dev_name, tes_dev->stream_dev[str_idx]->layer);
str_anz_dev++;
} else {
break;
}
str_idx++;
}
tes_dev->connected_stream_dev_count = str_anz_dev;
}
return 0;
IRQ_FAILED:
device_destroy(genip_global->class, tes_dev->base_dev->devt);
DEVICE_CREATE_FAILED:
IOREMAP_FAILED:
UNSUPPORTED:
ALLOC_MEM_FAILED:
STREAM_DEVICE_FAILED:
genip_global->dev_count--; // this was calculated at the start of the function
return result;
}
static int genip_remove(struct platform_device *pdev) {
struct genip_device *tes_dev = platform_get_drvdata(pdev);
device_destroy(genip_global->class, tes_dev->base_dev->devt);
return 0;
}
static int __init _genip_init(void) {
int result = 0;
dev_t first_dev_t;
// alloc mem for global struct
genip_global = kzalloc(sizeof(struct genip_global_t), GFP_KERNEL);
if (!genip_global) {
pr_err("Memory allocation for driver global struct failed!\n");
result = -ENOMEM;
goto ALLOC_GLOBAL_FAILED;
}
// create class
genip_global->class = class_create(THIS_MODULE, GENIP_DEVCLASS_NAME);
if (!genip_global->class) {
pr_err("cannot create class %s\n", GENIP_DEVCLASS_NAME);
result = -EBUSY;
goto CLASS_FAILED;
}
// alloc chrdev region
result = alloc_chrdev_region(&first_dev_t, 0, 1, GENIP_CHRDEV_NAME);
genip_global->major = MAJOR(first_dev_t);
if (result < 0) {
pr_err("cannot allocate chrdev region: %s\n", GENIP_CHRDEV_NAME);
goto CHRDEV_FAILED;
}
// init & add chrdev
cdev_init(&genip_global->cdev, &genip_fops);
genip_global->cdev.owner = THIS_MODULE;
result = cdev_add(&genip_global->cdev, first_dev_t, GENIP_MAX_DEVICES);
if (result) {
pr_err("can't register cdev\n");
goto CDEV_ADD_FAILED;
}
// register actual driver
// this invokes a call to genip_probe for each ip core
result = platform_driver_register(&genip_driver);
if (result) {
pr_err("failed to register platform driver for %s\n", GENIP_DRIVER_NAME);
goto PLATFORM_REGISTER_FAILED;
}
pr_info("%s device driver successfully initialized!\n", GENIP_DRIVER_NAME);
return 0;
PLATFORM_REGISTER_FAILED:
cdev_del(&genip_global->cdev);
CDEV_ADD_FAILED:
unregister_chrdev_region(first_dev_t, GENIP_MAX_DEVICES);
CHRDEV_FAILED:
class_destroy(genip_global->class);
CLASS_FAILED:
ALLOC_GLOBAL_FAILED:
return result;
}
static void __exit _genip_exit(void) {
platform_driver_unregister(&genip_driver);
class_destroy(genip_global->class);
cdev_del(&genip_global->cdev);
unregister_chrdev_region(MKDEV(genip_global->major, 0), GENIP_MAX_DEVICES);
kfree(genip_global);
}
module_init(_genip_init);
module_exit(_genip_exit);
MODULE_AUTHOR("TES Electronic Solutions GmbH");
MODULE_DESCRIPTION("Kernel driver for all TES IP cores");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(of, genip_of_ids);