• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * drivers/amlogic/media/subtitle/subtitle.c
3  *
4  * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
14  * more details.
15  *
16  */
17 
18 #include <linux/module.h>
19 #include <linux/spinlock.h>
20 #include <linux/kernel.h>
21 #include <linux/device.h>
22 #include <linux/vmalloc.h>
23 #include <linux/major.h>
24 #include <linux/slab.h>
25 #include <linux/cdev.h>
26 #include <linux/fs.h>
27 #include <linux/interrupt.h>
28 #include <linux/amlogic/media/utils/amstream.h>
29 #include <linux/uaccess.h>
30 //#include "amports_priv.h"
31 
32 //#include "amlog.h"
33 //MODULE_AMLOG(AMLOG_DEFAULT_LEVEL, 0, LOG_DEFAULT_LEVEL_DESC,
34 			 //LOG_DEFAULT_MASK_DESC);
35 #define DEVICE_NAME "amsubtitle"
36 /* Dev name as it appears in /proc/devices   */
37 #define DEVICE_CLASS_NAME "subtitle"
38 
39 static int subdevice_open;
40 
41 #define MAX_SUBTITLE_PACKET 10
42 static DEFINE_MUTEX(amsubtitle_mutex);
43 
44 struct subtitle_data_s {
45 	int subtitle_size;
46 	int subtitle_pts;
47 	char *data;
48 };
49 static struct subtitle_data_s subtitle_data[MAX_SUBTITLE_PACKET];
50 static int subtitle_enable = 1;
51 static int subtitle_total;
52 static int subtitle_width;
53 static int subtitle_height;
54 static int subtitle_type = -1;
55 static int subtitle_current;	/* no subtitle */
56 /* sub_index node will be modified by libplayer; amlogicplayer will use */
57 /* it to detect wheather libplayer switch sub finished or not */
58 static int subtitle_index;	/* no subtitle */
59 /* static int subtitle_size = 0; */
60 /* static int subtitle_read_pos = 0; */
61 static int subtitle_write_pos;
62 static int subtitle_start_pts;
63 static int subtitle_fps;
64 static int subtitle_subtype;
65 static int subtitle_reset;
66 /* static int *subltitle_address[MAX_SUBTITLE_PACKET]; */
67 
68 enum subinfo_para_e {
69 	SUB_NULL = -1,
70 	SUB_ENABLE = 0,
71 	SUB_TOTAL,
72 	SUB_WIDTH,
73 	SUB_HEIGHT,
74 	SUB_TYPE,
75 	SUB_CURRENT,
76 	SUB_INDEX,
77 	SUB_WRITE_POS,
78 	SUB_START_PTS,
79 	SUB_FPS,
80 	SUB_SUBTYPE,
81 	SUB_RESET,
82 	SUB_DATA_T_SIZE,
83 	SUB_DATA_T_DATA
84 };
85 
86 struct subinfo_para_s {
87 	enum subinfo_para_e subinfo_type;
88 	int subtitle_info;
89 	char *data;
90 };
91 
92 /* total */
93 /* curr */
94 /* bimap */
95 /* text */
96 /* type */
97 /* info */
98 /* pts */
99 /* duration */
100 /* color pallete */
101 /* width/height */
102 
show_curr(struct class * class,struct class_attribute * attr,char * buf)103 static ssize_t show_curr(struct class *class, struct class_attribute *attr,
104 						 char *buf)
105 {
106 	return sprintf(buf, "%d: current\n", subtitle_current);
107 }
108 
store_curr(struct class * class,struct class_attribute * attr,const char * buf,size_t size)109 static ssize_t store_curr(struct class *class, struct class_attribute *attr,
110 						  const char *buf, size_t size)
111 {
112 	unsigned int curr;
113 	ssize_t r;
114 
115 	r = kstrtoint(buf, 0, &curr);
116 	if (r < 0)
117 		return -EINVAL;
118 
119 
120 	subtitle_current = curr;
121 
122 	return size;
123 }
124 
show_index(struct class * class,struct class_attribute * attr,char * buf)125 static ssize_t show_index(struct class *class, struct class_attribute *attr,
126 						  char *buf)
127 {
128 	return sprintf(buf, "%d: current\n", subtitle_index);
129 }
130 
store_index(struct class * class,struct class_attribute * attr,const char * buf,size_t size)131 static ssize_t store_index(struct class *class, struct class_attribute *attr,
132 						   const char *buf, size_t size)
133 {
134 	unsigned int curr;
135 	ssize_t r;
136 
137 	r = kstrtoint(buf, 0, &curr);
138 	if (r < 0)
139 		return -EINVAL;
140 
141 	subtitle_index = curr;
142 
143 	return size;
144 }
145 
show_reset(struct class * class,struct class_attribute * attr,char * buf)146 static ssize_t show_reset(struct class *class, struct class_attribute *attr,
147 						  char *buf)
148 {
149 	return sprintf(buf, "%d: current\n", subtitle_reset);
150 }
151 
store_reset(struct class * class,struct class_attribute * attr,const char * buf,size_t size)152 static ssize_t store_reset(struct class *class, struct class_attribute *attr,
153 						   const char *buf, size_t size)
154 {
155 	unsigned int reset;
156 	ssize_t r;
157 
158 	r = kstrtoint(buf, 0, &reset);
159 
160 	pr_info("reset is %d\n", reset);
161 	if (r < 0)
162 		return -EINVAL;
163 
164 
165 	subtitle_reset = reset;
166 
167 	return size;
168 }
169 
show_type(struct class * class,struct class_attribute * attr,char * buf)170 static ssize_t show_type(struct class *class, struct class_attribute *attr,
171 						 char *buf)
172 {
173 	return sprintf(buf, "%d: type\n", subtitle_type);
174 }
175 
store_type(struct class * class,struct class_attribute * attr,const char * buf,size_t size)176 static ssize_t store_type(struct class *class, struct class_attribute *attr,
177 						  const char *buf, size_t size)
178 {
179 	unsigned int type;
180 	ssize_t r;
181 
182 	r = kstrtoint(buf, 0, &type);
183 	if (r < 0)
184 		return -EINVAL;
185 
186 	subtitle_type = type;
187 
188 	return size;
189 }
190 
show_width(struct class * class,struct class_attribute * attr,char * buf)191 static ssize_t show_width(struct class *class, struct class_attribute *attr,
192 						  char *buf)
193 {
194 	return sprintf(buf, "%d: width\n", subtitle_width);
195 }
196 
store_width(struct class * class,struct class_attribute * attr,const char * buf,size_t size)197 static ssize_t store_width(struct class *class, struct class_attribute *attr,
198 		const char *buf, size_t size)
199 {
200 	unsigned int width;
201 	ssize_t r;
202 
203 	r = kstrtoint(buf, 0, &width);
204 	if (r < 0)
205 		return -EINVAL;
206 
207 	subtitle_width = width;
208 
209 	return size;
210 }
211 
show_height(struct class * class,struct class_attribute * attr,char * buf)212 static ssize_t show_height(struct class *class, struct class_attribute *attr,
213 						   char *buf)
214 {
215 	return sprintf(buf, "%d: height\n", subtitle_height);
216 }
217 
store_height(struct class * class,struct class_attribute * attr,const char * buf,size_t size)218 static ssize_t store_height(struct class *class, struct class_attribute *attr,
219 				const char *buf, size_t size)
220 {
221 	unsigned int height;
222 	ssize_t r;
223 
224 	r = kstrtoint(buf, 0, &height);
225 	if (r < 0)
226 		return -EINVAL;
227 
228 	subtitle_height = height;
229 
230 	return size;
231 }
232 
show_total(struct class * class,struct class_attribute * attr,char * buf)233 static ssize_t show_total(struct class *class, struct class_attribute *attr,
234 						  char *buf)
235 {
236 	return sprintf(buf, "%d: num\n", subtitle_total);
237 }
238 
store_total(struct class * class,struct class_attribute * attr,const char * buf,size_t size)239 static ssize_t store_total(struct class *class, struct class_attribute *attr,
240 				const char *buf, size_t size)
241 {
242 	unsigned int total;
243 	ssize_t r;
244 
245 	r = kstrtoint(buf, 0, &total);
246 	if (r < 0)
247 		return -EINVAL;
248 	pr_info("subtitle num is %d\n", total);
249 	subtitle_total = total;
250 
251 	return size;
252 }
253 
show_enable(struct class * class,struct class_attribute * attr,char * buf)254 static ssize_t show_enable(struct class *class, struct class_attribute *attr,
255 						   char *buf)
256 {
257 	if (subtitle_enable)
258 		return sprintf(buf, "1: enabled\n");
259 
260 	return sprintf(buf, "0: disabled\n");
261 }
262 
store_enable(struct class * class,struct class_attribute * attr,const char * buf,size_t size)263 static ssize_t store_enable(struct class *class, struct class_attribute *attr,
264 				const char *buf, size_t size)
265 {
266 	unsigned int mode;
267 	ssize_t r;
268 
269 	r = kstrtoint(buf, 0, &mode);
270 	if (r < 0)
271 		return -EINVAL;
272 	pr_info("subtitle enable is %d\n", mode);
273 	subtitle_enable = mode ? 1 : 0;
274 
275 	return size;
276 }
277 
show_size(struct class * class,struct class_attribute * attr,char * buf)278 static ssize_t show_size(struct class *class, struct class_attribute *attr,
279 						 char *buf)
280 {
281 	if (subtitle_enable)
282 		return sprintf(buf, "1: size\n");
283 
284 	return sprintf(buf, "0: size\n");
285 }
286 
store_size(struct class * class,struct class_attribute * attr,const char * buf,size_t size)287 static ssize_t store_size(struct class *class, struct class_attribute *attr,
288 						  const char *buf, size_t size)
289 {
290 	unsigned int ssize;
291 	ssize_t r;
292 
293 	r = kstrtoint(buf, 0, &ssize);
294 	if (r < 0)
295 		return -EINVAL;
296 	pr_info("subtitle size is %d\n", ssize);
297 	subtitle_data[subtitle_write_pos].subtitle_size = ssize;
298 
299 	return size;
300 }
301 
show_startpts(struct class * class,struct class_attribute * attr,char * buf)302 static ssize_t show_startpts(struct class *class, struct class_attribute *attr,
303 							 char *buf)
304 {
305 	return sprintf(buf, "%d: pts\n", subtitle_start_pts);
306 }
307 
store_startpts(struct class * class,struct class_attribute * attr,const char * buf,size_t size)308 static ssize_t store_startpts(struct class *class, struct class_attribute *attr,
309 					const char *buf, size_t size)
310 {
311 	unsigned int spts;
312 	ssize_t r;
313 
314 	r = kstrtoint(buf, 0, &spts);
315 	if (r < 0)
316 		return -EINVAL;
317 	pr_info("subtitle start pts is %x\n", spts);
318 	subtitle_start_pts = spts;
319 
320 	return size;
321 }
322 
show_data(struct class * class,struct class_attribute * attr,char * buf)323 static ssize_t show_data(struct class *class, struct class_attribute *attr,
324 						 char *buf)
325 {
326 #if 0
327 	if (subtitle_data[subtitle_write_pos].data)
328 		return sprintf(buf, "%lld\n",
329 		(unsigned long)(subtitle_data[subtitle_write_pos].data));
330 #endif
331 	return sprintf(buf, "0: disabled\n");
332 }
333 
store_data(struct class * class,struct class_attribute * attr,const char * buf,size_t size)334 static ssize_t store_data(struct class *class, struct class_attribute *attr,
335 						  const char *buf, size_t size)
336 {
337 	unsigned int address;
338 	ssize_t r;
339 
340 	r = kstrtoint(buf, 0, &address);
341 	if ((r == 0))
342 		return -EINVAL;
343 #if 0
344 	if (subtitle_data[subtitle_write_pos].subtitle_size > 0) {
345 		subtitle_data[subtitle_write_pos].data = vmalloc((
346 			subtitle_data[subtitle_write_pos].subtitle_size));
347 		if (subtitle_data[subtitle_write_pos].data)
348 			memcpy(subtitle_data[subtitle_write_pos].data,
349 			(unsigned long *)address,
350 			subtitle_data[subtitle_write_pos].subtitle_size);
351 	}
352 	pr_info("subtitle data address is %x",
353 			(unsigned int)(subtitle_data[subtitle_write_pos].data));
354 #endif
355 	subtitle_write_pos++;
356 	if (subtitle_write_pos >= MAX_SUBTITLE_PACKET)
357 		subtitle_write_pos = 0;
358 	return 1;
359 }
360 
show_fps(struct class * class,struct class_attribute * attr,char * buf)361 static ssize_t show_fps(struct class *class, struct class_attribute *attr,
362 						char *buf)
363 {
364 	return sprintf(buf, "%d: fps\n", subtitle_fps);
365 }
366 
store_fps(struct class * class,struct class_attribute * attr,const char * buf,size_t size)367 static ssize_t store_fps(struct class *class, struct class_attribute *attr,
368 						 const char *buf, size_t size)
369 {
370 	unsigned int ssize;
371 	ssize_t r;
372 
373 	r = kstrtoint(buf, 0, &ssize);
374 	if (r < 0)
375 		return -EINVAL;
376 	pr_info("subtitle fps is %d\n", ssize);
377 	subtitle_fps = ssize;
378 
379 	return size;
380 }
381 
show_subtype(struct class * class,struct class_attribute * attr,char * buf)382 static ssize_t show_subtype(struct class *class, struct class_attribute *attr,
383 							char *buf)
384 {
385 	return sprintf(buf, "%d: subtype\n", subtitle_subtype);
386 }
387 
store_subtype(struct class * class,struct class_attribute * attr,const char * buf,size_t size)388 static ssize_t store_subtype(struct class *class, struct class_attribute *attr,
389 			const char *buf, size_t size)
390 {
391 	unsigned int ssize;
392 	ssize_t r;
393 
394 	r = kstrtoint(buf, 0, &ssize);
395 	if (r < 0)
396 		return -EINVAL;
397 	pr_info("subtitle subtype is %d\n", ssize);
398 	subtitle_subtype = ssize;
399 
400 	return size;
401 }
402 
403 static struct class_attribute subtitle_class_attrs[] = {
404 	__ATTR(enable, 0664, show_enable, store_enable),
405 	__ATTR(total, 0664, show_total, store_total),
406 	__ATTR(width, 0664, show_width, store_width),
407 	__ATTR(height, 0664, show_height, store_height),
408 	__ATTR(type, 0664, show_type, store_type),
409 	__ATTR(curr, 0664, show_curr, store_curr),
410 	__ATTR(index, 0664, show_index, store_index),
411 	__ATTR(size, 0664, show_size, store_size),
412 	__ATTR(data, 0664, show_data, store_data),
413 	__ATTR(startpts, 0664, show_startpts,
414 	store_startpts),
415 	__ATTR(fps, 0664, show_fps, store_fps),
416 	__ATTR(subtype, 0664, show_subtype,
417 	store_subtype),
418 	__ATTR(reset, 0644, show_reset, store_reset),
419 	__ATTR_NULL
420 };
421 
422 
423 /*********************************************************
424  * /dev/amvideo APIs
425  *********************************************************/
amsubtitle_open(struct inode * inode,struct file * file)426 static int amsubtitle_open(struct inode *inode, struct file *file)
427 {
428 	mutex_lock(&amsubtitle_mutex);
429 
430 	if (subdevice_open) {
431 		mutex_unlock(&amsubtitle_mutex);
432 		return -EBUSY;
433 	}
434 
435 	subdevice_open = 1;
436 
437 	try_module_get(THIS_MODULE);
438 
439 	mutex_unlock(&amsubtitle_mutex);
440 
441 	return 0;
442 }
443 
amsubtitle_release(struct inode * inode,struct file * file)444 static int amsubtitle_release(struct inode *inode, struct file *file)
445 {
446 	mutex_lock(&amsubtitle_mutex);
447 
448 	subdevice_open = 0;
449 
450 	module_put(THIS_MODULE);
451 
452 	mutex_unlock(&amsubtitle_mutex);
453 
454 	return 0;
455 }
456 
amsubtitle_ioctl(struct file * file,unsigned int cmd,ulong arg)457 static long amsubtitle_ioctl(struct file *file, unsigned int cmd, ulong arg)
458 {
459 	switch (cmd) {
460 	case AMSTREAM_IOC_GET_SUBTITLE_INFO: {
461 		struct subinfo_para_s Vstates;
462 		struct subinfo_para_s *states = &Vstates;
463 
464 		if (copy_from_user((void *)states,
465 				(void *)arg, sizeof(Vstates)))
466 			return -EFAULT;
467 		switch (states->subinfo_type) {
468 		case SUB_ENABLE:
469 			states->subtitle_info = subtitle_enable;
470 			break;
471 		case SUB_TOTAL:
472 			states->subtitle_info = subtitle_total;
473 			break;
474 		case SUB_WIDTH:
475 			states->subtitle_info = subtitle_width;
476 			break;
477 		case SUB_HEIGHT:
478 			states->subtitle_info = subtitle_height;
479 			break;
480 		case SUB_TYPE:
481 			states->subtitle_info = subtitle_type;
482 			break;
483 		case SUB_CURRENT:
484 			states->subtitle_info = subtitle_current;
485 			break;
486 		case SUB_INDEX:
487 			states->subtitle_info = subtitle_index;
488 			break;
489 		case SUB_WRITE_POS:
490 			states->subtitle_info = subtitle_write_pos;
491 			break;
492 		case SUB_START_PTS:
493 			states->subtitle_info = subtitle_start_pts;
494 			break;
495 		case SUB_FPS:
496 			states->subtitle_info = subtitle_fps;
497 			break;
498 		case SUB_SUBTYPE:
499 			states->subtitle_info = subtitle_subtype;
500 			break;
501 		case SUB_RESET:
502 			states->subtitle_info = subtitle_reset;
503 			break;
504 		case SUB_DATA_T_SIZE:
505 			states->subtitle_info =
506 				subtitle_data[subtitle_write_pos].subtitle_size;
507 			break;
508 		case SUB_DATA_T_DATA: {
509 			if (states->subtitle_info > 0)
510 				states->subtitle_info =
511 				(long)subtitle_data[subtitle_write_pos].data;
512 		}
513 		break;
514 		default:
515 			break;
516 		}
517 		if (copy_to_user((void *)arg, (void *)states, sizeof(Vstates)))
518 			return -EFAULT;
519 	}
520 
521 	break;
522 	case AMSTREAM_IOC_SET_SUBTITLE_INFO: {
523 		struct subinfo_para_s Vstates;
524 		struct subinfo_para_s *states = &Vstates;
525 
526 		if (copy_from_user((void *)states,
527 				(void *)arg, sizeof(Vstates)))
528 			return -EFAULT;
529 		switch (states->subinfo_type) {
530 		case SUB_ENABLE:
531 			subtitle_enable = states->subtitle_info;
532 			break;
533 		case SUB_TOTAL:
534 			subtitle_total = states->subtitle_info;
535 			break;
536 		case SUB_WIDTH:
537 			subtitle_width = states->subtitle_info;
538 			break;
539 		case SUB_HEIGHT:
540 			subtitle_height = states->subtitle_info;
541 			break;
542 		case SUB_TYPE:
543 			subtitle_type = states->subtitle_info;
544 			break;
545 		case SUB_CURRENT:
546 			subtitle_current = states->subtitle_info;
547 			break;
548 		case SUB_INDEX:
549 			subtitle_index = states->subtitle_info;
550 			break;
551 		case SUB_WRITE_POS:
552 			subtitle_write_pos = states->subtitle_info;
553 			break;
554 		case SUB_START_PTS:
555 			subtitle_start_pts = states->subtitle_info;
556 			break;
557 		case SUB_FPS:
558 			subtitle_fps = states->subtitle_info;
559 			break;
560 		case SUB_SUBTYPE:
561 			subtitle_subtype = states->subtitle_info;
562 			break;
563 		case SUB_RESET:
564 			subtitle_reset = states->subtitle_info;
565 			break;
566 		case SUB_DATA_T_SIZE:
567 			subtitle_data[subtitle_write_pos].subtitle_size =
568 				states->subtitle_info;
569 			break;
570 		case SUB_DATA_T_DATA: {
571 			if (states->subtitle_info > 0) {
572 				subtitle_data[subtitle_write_pos].data =
573 					vmalloc((states->subtitle_info));
574 				if (subtitle_data[subtitle_write_pos].data)
575 					memcpy(
576 					subtitle_data[subtitle_write_pos].data,
577 					(char *)states->data,
578 					states->subtitle_info);
579 			}
580 
581 			subtitle_write_pos++;
582 			if (subtitle_write_pos >= MAX_SUBTITLE_PACKET)
583 				subtitle_write_pos = 0;
584 		}
585 		break;
586 		default:
587 			break;
588 		}
589 
590 	}
591 
592 	break;
593 	default:
594 		break;
595 	}
596 
597 	return 0;
598 }
599 
600 #ifdef CONFIG_COMPAT
amsub_compat_ioctl(struct file * file,unsigned int cmd,ulong arg)601 static long amsub_compat_ioctl(struct file *file, unsigned int cmd, ulong arg)
602 {
603 	long ret = 0;
604 
605 	ret = amsubtitle_ioctl(file, cmd, (ulong)compat_ptr(arg));
606 	return ret;
607 }
608 #endif
609 
610 static const struct file_operations amsubtitle_fops = {
611 	.owner = THIS_MODULE,
612 	.open = amsubtitle_open,
613 	.release = amsubtitle_release,
614 	.unlocked_ioctl = amsubtitle_ioctl,
615 #ifdef CONFIG_COMPAT
616 	.compat_ioctl = amsub_compat_ioctl,
617 #endif
618 };
619 
620 static struct device *amsubtitle_dev;
621 static dev_t amsub_devno;
622 static struct class *amsub_clsp;
623 static struct cdev *amsub_cdevp;
624 #define AMSUBTITLE_DEVICE_COUNT 1
625 
create_amsub_attrs(struct class * class)626 static void create_amsub_attrs(struct class *class)
627 {
628 	int i = 0;
629 
630 	for (i = 0; subtitle_class_attrs[i].attr.name; i++) {
631 		if (class_create_file(class, &subtitle_class_attrs[i]) < 0)
632 			break;
633 	}
634 }
635 
remove_amsub_attrs(struct class * class)636 static void remove_amsub_attrs(struct class *class)
637 {
638 	int i = 0;
639 
640 	for (i = 0; subtitle_class_attrs[i].attr.name; i++)
641 		class_remove_file(class, &subtitle_class_attrs[i]);
642 }
643 
subtitle_init(void)644 int subtitle_init(void)
645 {
646 	int ret = 0;
647 
648 	ret = alloc_chrdev_region(&amsub_devno, 0, AMSUBTITLE_DEVICE_COUNT,
649 							  DEVICE_NAME);
650 	if (ret < 0) {
651 		pr_info("amsub: failed to alloc major number\n");
652 		ret = -ENODEV;
653 		return ret;
654 	}
655 
656 	amsub_clsp = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
657 	if (IS_ERR(amsub_clsp)) {
658 		ret = PTR_ERR(amsub_clsp);
659 		goto err1;
660 	}
661 
662 	create_amsub_attrs(amsub_clsp);
663 
664 	amsub_cdevp = kmalloc(sizeof(struct cdev), GFP_KERNEL);
665 	if (!amsub_cdevp) {
666 		/*pr_info("amsub: failed to allocate memory\n");*/
667 		ret = -ENOMEM;
668 		goto err2;
669 	}
670 
671 	cdev_init(amsub_cdevp, &amsubtitle_fops);
672 	amsub_cdevp->owner = THIS_MODULE;
673 	/* connect the major/minor number to cdev */
674 	ret = cdev_add(amsub_cdevp, amsub_devno, AMSUBTITLE_DEVICE_COUNT);
675 	if (ret) {
676 		pr_info("amsub:failed to add cdev\n");
677 		goto err3;
678 	}
679 
680 	amsubtitle_dev = device_create(amsub_clsp,
681 		NULL, MKDEV(MAJOR(amsub_devno), 0),
682 		NULL, DEVICE_NAME);
683 
684 	if (IS_ERR(amsubtitle_dev)) {
685 		pr_err("## Can't create amsubtitle device\n");
686 		goto err4;
687 	}
688 
689 	return 0;
690 
691 err4:
692 	cdev_del(amsub_cdevp);
693 err3:
694 	kfree(amsub_cdevp);
695 err2:
696 	remove_amsub_attrs(amsub_clsp);
697 	class_destroy(amsub_clsp);
698 err1:
699 	unregister_chrdev_region(amsub_devno, 1);
700 
701 	return ret;
702 }
703 EXPORT_SYMBOL(subtitle_init);
704 
subtitle_exit(void)705 void subtitle_exit(void)
706 {
707 	unregister_chrdev_region(amsub_devno, 1);
708 	device_destroy(amsub_clsp, MKDEV(MAJOR(amsub_devno), 0));
709 	cdev_del(amsub_cdevp);
710 	kfree(amsub_cdevp);
711 	remove_amsub_attrs(amsub_clsp);
712 	class_destroy(amsub_clsp);
713 }
714 EXPORT_SYMBOL(subtitle_exit);
715 
716