diff options
author | Lee Jones <lee.jones@linaro.org> | 2013-01-14 11:10:36 -0500 |
---|---|---|
committer | Lee Jones <lee.jones@linaro.org> | 2013-02-04 03:31:36 -0500 |
commit | 4b8ac08256781c59b20bfd86fddcf5d620833752 (patch) | |
tree | 38ae77a810412dd288a0b5224841374f5b6f6125 | |
parent | e0f4fec030ce412666cc127702adbf0a6cfa0855 (diff) |
mfd: ab8500-debugfs: Provide a means for a user subscribe to IRQs
Allow users to subscribe to and view IRQ events live from debugfs.
Signed-off-by: Lee Jones <lee.jones@linaro.org>
-rw-r--r-- | drivers/mfd/ab8500-debugfs.c | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 5a8e707bc038..2e40e12fc687 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c | |||
@@ -11,6 +11,9 @@ | |||
11 | #include <linux/module.h> | 11 | #include <linux/module.h> |
12 | #include <linux/debugfs.h> | 12 | #include <linux/debugfs.h> |
13 | #include <linux/platform_device.h> | 13 | #include <linux/platform_device.h> |
14 | #include <linux/interrupt.h> | ||
15 | #include <linux/kobject.h> | ||
16 | #include <linux/slab.h> | ||
14 | 17 | ||
15 | #include <linux/mfd/abx500.h> | 18 | #include <linux/mfd/abx500.h> |
16 | #include <linux/mfd/abx500/ab8500.h> | 19 | #include <linux/mfd/abx500/ab8500.h> |
@@ -18,6 +21,9 @@ | |||
18 | static u32 debug_bank; | 21 | static u32 debug_bank; |
19 | static u32 debug_address; | 22 | static u32 debug_address; |
20 | 23 | ||
24 | static int irq_first; | ||
25 | static int irq_last; | ||
26 | |||
21 | /** | 27 | /** |
22 | * struct ab8500_reg_range | 28 | * struct ab8500_reg_range |
23 | * @first: the first address of the range | 29 | * @first: the first address of the range |
@@ -354,6 +360,21 @@ static struct ab8500_prcmu_ranges debug_ranges[AB8500_NUM_BANKS] = { | |||
354 | }, | 360 | }, |
355 | }; | 361 | }; |
356 | 362 | ||
363 | static irqreturn_t ab8500_debug_handler(int irq, void *data) | ||
364 | { | ||
365 | char buf[16]; | ||
366 | struct kobject *kobj = (struct kobject *)data; | ||
367 | |||
368 | /* | ||
369 | * This makes it possible to use poll for events (POLLPRI | POLLERR) | ||
370 | * from userspace on sysfs file named irq-<nr> | ||
371 | */ | ||
372 | sprintf(buf, "irq-%d", irq); | ||
373 | sysfs_notify(kobj, NULL, buf); | ||
374 | |||
375 | return IRQ_HANDLED; | ||
376 | } | ||
377 | |||
357 | static int ab8500_registers_print(struct seq_file *s, void *p) | 378 | static int ab8500_registers_print(struct seq_file *s, void *p) |
358 | { | 379 | { |
359 | struct device *dev = s->private; | 380 | struct device *dev = s->private; |
@@ -519,6 +540,131 @@ static ssize_t ab8500_val_write(struct file *file, | |||
519 | return count; | 540 | return count; |
520 | } | 541 | } |
521 | 542 | ||
543 | static int ab8500_subscribe_unsubscribe_print(struct seq_file *s, void *p) | ||
544 | { | ||
545 | seq_printf(s, "%d\n", irq_first); | ||
546 | |||
547 | return 0; | ||
548 | } | ||
549 | |||
550 | static int ab8500_subscribe_unsubscribe_open(struct inode *inode, | ||
551 | struct file *file) | ||
552 | { | ||
553 | return single_open(file, ab8500_subscribe_unsubscribe_print, | ||
554 | inode->i_private); | ||
555 | } | ||
556 | |||
557 | /* | ||
558 | * This function is used for all interrupts and will always print | ||
559 | * the same string. It is however this file sysfs_notify called on. | ||
560 | * Userspace should read this file and then poll. When an event occur | ||
561 | * the blocking poll will be released. | ||
562 | */ | ||
563 | static ssize_t show_irq(struct device *dev, | ||
564 | struct device_attribute *attr, char *buf) | ||
565 | { | ||
566 | return sprintf(buf, "irq\n"); | ||
567 | } | ||
568 | |||
569 | static struct device_attribute *dev_attr[AB8500_NR_IRQS]; | ||
570 | static char *event_name[AB8500_NR_IRQS]; | ||
571 | |||
572 | static ssize_t ab8500_subscribe_write(struct file *file, | ||
573 | const char __user *user_buf, | ||
574 | size_t count, loff_t *ppos) | ||
575 | { | ||
576 | struct device *dev = ((struct seq_file *)(file->private_data))->private; | ||
577 | char buf[32]; | ||
578 | int buf_size; | ||
579 | unsigned long user_val; | ||
580 | int err; | ||
581 | |||
582 | /* Get userspace string and assure termination */ | ||
583 | buf_size = min(count, (sizeof(buf)-1)); | ||
584 | if (copy_from_user(buf, user_buf, buf_size)) | ||
585 | return -EFAULT; | ||
586 | buf[buf_size] = 0; | ||
587 | |||
588 | err = strict_strtoul(buf, 0, &user_val); | ||
589 | if (err) | ||
590 | return -EINVAL; | ||
591 | if (user_val < irq_first) { | ||
592 | dev_err(dev, "debugfs error input < %d\n", irq_first); | ||
593 | return -EINVAL; | ||
594 | } | ||
595 | if (user_val > irq_last) { | ||
596 | dev_err(dev, "debugfs error input > %d\n", irq_last); | ||
597 | return -EINVAL; | ||
598 | } | ||
599 | |||
600 | /* | ||
601 | * This will create a sysfs file named irq-<nr> which userspace can | ||
602 | * use to select or poll and get the AB8500 events | ||
603 | */ | ||
604 | dev_attr[user_val] = kmalloc(sizeof(struct device_attribute), | ||
605 | GFP_KERNEL); | ||
606 | event_name[user_val] = kmalloc(buf_size, GFP_KERNEL); | ||
607 | sprintf(event_name[user_val], "irq-%lu", user_val); | ||
608 | dev_attr[user_val]->show = show_irq; | ||
609 | dev_attr[user_val]->store = NULL; | ||
610 | dev_attr[user_val]->attr.name = event_name[user_val]; | ||
611 | dev_attr[user_val]->attr.mode = S_IRUGO; | ||
612 | err = sysfs_create_file(&dev->kobj, &dev_attr[user_val]->attr); | ||
613 | if (err < 0) { | ||
614 | printk(KERN_ERR "sysfs_create_file failed %d\n", err); | ||
615 | return err; | ||
616 | } | ||
617 | |||
618 | err = request_threaded_irq(user_val, NULL, ab8500_debug_handler, | ||
619 | IRQF_SHARED | IRQF_NO_SUSPEND, | ||
620 | "ab8500-debug", &dev->kobj); | ||
621 | if (err < 0) { | ||
622 | printk(KERN_ERR "request_threaded_irq failed %d, %lu\n", | ||
623 | err, user_val); | ||
624 | return err; | ||
625 | } | ||
626 | |||
627 | return buf_size; | ||
628 | } | ||
629 | |||
630 | static ssize_t ab8500_unsubscribe_write(struct file *file, | ||
631 | const char __user *user_buf, | ||
632 | size_t count, loff_t *ppos) | ||
633 | { | ||
634 | struct device *dev = ((struct seq_file *)(file->private_data))->private; | ||
635 | char buf[32]; | ||
636 | int buf_size; | ||
637 | unsigned long user_val; | ||
638 | int err; | ||
639 | |||
640 | /* Get userspace string and assure termination */ | ||
641 | buf_size = min(count, (sizeof(buf)-1)); | ||
642 | if (copy_from_user(buf, user_buf, buf_size)) | ||
643 | return -EFAULT; | ||
644 | buf[buf_size] = 0; | ||
645 | |||
646 | err = strict_strtoul(buf, 0, &user_val); | ||
647 | if (err) | ||
648 | return -EINVAL; | ||
649 | if (user_val < irq_first) { | ||
650 | dev_err(dev, "debugfs error input < %d\n", irq_first); | ||
651 | return -EINVAL; | ||
652 | } | ||
653 | if (user_val > irq_last) { | ||
654 | dev_err(dev, "debugfs error input > %d\n", irq_last); | ||
655 | return -EINVAL; | ||
656 | } | ||
657 | |||
658 | free_irq(user_val, &dev->kobj); | ||
659 | kfree(event_name[user_val]); | ||
660 | kfree(dev_attr[user_val]); | ||
661 | |||
662 | if (dev_attr[user_val]) | ||
663 | sysfs_remove_file(&dev->kobj, &dev_attr[user_val]->attr); | ||
664 | |||
665 | return buf_size; | ||
666 | } | ||
667 | |||
522 | static const struct file_operations ab8500_bank_fops = { | 668 | static const struct file_operations ab8500_bank_fops = { |
523 | .open = ab8500_bank_open, | 669 | .open = ab8500_bank_open, |
524 | .write = ab8500_bank_write, | 670 | .write = ab8500_bank_write, |
@@ -546,17 +692,51 @@ static const struct file_operations ab8500_val_fops = { | |||
546 | .owner = THIS_MODULE, | 692 | .owner = THIS_MODULE, |
547 | }; | 693 | }; |
548 | 694 | ||
695 | static const struct file_operations ab8500_subscribe_fops = { | ||
696 | .open = ab8500_subscribe_unsubscribe_open, | ||
697 | .write = ab8500_subscribe_write, | ||
698 | .read = seq_read, | ||
699 | .llseek = seq_lseek, | ||
700 | .release = single_release, | ||
701 | .owner = THIS_MODULE, | ||
702 | }; | ||
703 | |||
704 | static const struct file_operations ab8500_unsubscribe_fops = { | ||
705 | .open = ab8500_subscribe_unsubscribe_open, | ||
706 | .write = ab8500_unsubscribe_write, | ||
707 | .read = seq_read, | ||
708 | .llseek = seq_lseek, | ||
709 | .release = single_release, | ||
710 | .owner = THIS_MODULE, | ||
711 | }; | ||
712 | |||
549 | static struct dentry *ab8500_dir; | 713 | static struct dentry *ab8500_dir; |
550 | static struct dentry *ab8500_reg_file; | 714 | static struct dentry *ab8500_reg_file; |
551 | static struct dentry *ab8500_bank_file; | 715 | static struct dentry *ab8500_bank_file; |
552 | static struct dentry *ab8500_address_file; | 716 | static struct dentry *ab8500_address_file; |
553 | static struct dentry *ab8500_val_file; | 717 | static struct dentry *ab8500_val_file; |
718 | static struct dentry *ab8500_subscribe_file; | ||
719 | static struct dentry *ab8500_unsubscribe_file; | ||
554 | 720 | ||
555 | static int ab8500_debug_probe(struct platform_device *plf) | 721 | static int ab8500_debug_probe(struct platform_device *plf) |
556 | { | 722 | { |
557 | debug_bank = AB8500_MISC; | 723 | debug_bank = AB8500_MISC; |
558 | debug_address = AB8500_REV_REG & 0x00FF; | 724 | debug_address = AB8500_REV_REG & 0x00FF; |
559 | 725 | ||
726 | irq_first = platform_get_irq_byname(plf, "IRQ_FIRST"); | ||
727 | if (irq_first < 0) { | ||
728 | dev_err(&plf->dev, "First irq not found, err %d\n", | ||
729 | irq_first); | ||
730 | return irq_first; | ||
731 | } | ||
732 | |||
733 | irq_last = platform_get_irq_byname(plf, "IRQ_LAST"); | ||
734 | if (irq_last < 0) { | ||
735 | dev_err(&plf->dev, "Last irq not found, err %d\n", | ||
736 | irq_last); | ||
737 | return irq_last; | ||
738 | } | ||
739 | |||
560 | ab8500_dir = debugfs_create_dir(AB8500_NAME_STRING, NULL); | 740 | ab8500_dir = debugfs_create_dir(AB8500_NAME_STRING, NULL); |
561 | if (!ab8500_dir) | 741 | if (!ab8500_dir) |
562 | goto exit_no_debugfs; | 742 | goto exit_no_debugfs; |
@@ -582,8 +762,26 @@ static int ab8500_debug_probe(struct platform_device *plf) | |||
582 | if (!ab8500_val_file) | 762 | if (!ab8500_val_file) |
583 | goto exit_destroy_address; | 763 | goto exit_destroy_address; |
584 | 764 | ||
765 | ab8500_subscribe_file = | ||
766 | debugfs_create_file("irq-subscribe", | ||
767 | (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, | ||
768 | &ab8500_subscribe_fops); | ||
769 | if (!ab8500_subscribe_file) | ||
770 | goto exit_destroy_val; | ||
771 | |||
772 | ab8500_unsubscribe_file = | ||
773 | debugfs_create_file("irq-unsubscribe", | ||
774 | (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, | ||
775 | &ab8500_unsubscribe_fops); | ||
776 | if (!ab8500_unsubscribe_file) | ||
777 | goto exit_destroy_subscribe; | ||
778 | |||
585 | return 0; | 779 | return 0; |
586 | 780 | ||
781 | exit_destroy_subscribe: | ||
782 | debugfs_remove(ab8500_subscribe_file); | ||
783 | exit_destroy_val: | ||
784 | debugfs_remove(ab8500_val_file); | ||
587 | exit_destroy_address: | 785 | exit_destroy_address: |
588 | debugfs_remove(ab8500_address_file); | 786 | debugfs_remove(ab8500_address_file); |
589 | exit_destroy_bank: | 787 | exit_destroy_bank: |