1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Generic push-switch framework |
4 | * |
5 | * Copyright (C) 2006 Paul Mundt |
6 | */ |
7 | #include <linux/init.h> |
8 | #include <linux/slab.h> |
9 | #include <linux/module.h> |
10 | #include <linux/interrupt.h> |
11 | #include <linux/platform_device.h> |
12 | #include <asm/push-switch.h> |
13 | |
14 | #define DRV_NAME "push-switch" |
15 | #define DRV_VERSION "0.1.1" |
16 | |
17 | static ssize_t switch_show(struct device *dev, |
18 | struct device_attribute *attr, |
19 | char *buf) |
20 | { |
21 | struct push_switch_platform_info *psw_info = dev->platform_data; |
22 | return sprintf(buf, fmt: "%s\n" , psw_info->name); |
23 | } |
24 | static DEVICE_ATTR_RO(switch); |
25 | |
26 | static void switch_timer(struct timer_list *t) |
27 | { |
28 | struct push_switch *psw = from_timer(psw, t, debounce); |
29 | |
30 | schedule_work(work: &psw->work); |
31 | } |
32 | |
33 | static void switch_work_handler(struct work_struct *work) |
34 | { |
35 | struct push_switch *psw = container_of(work, struct push_switch, work); |
36 | struct platform_device *pdev = psw->pdev; |
37 | |
38 | psw->state = 0; |
39 | |
40 | kobject_uevent(kobj: &pdev->dev.kobj, action: KOBJ_CHANGE); |
41 | } |
42 | |
43 | static int switch_drv_probe(struct platform_device *pdev) |
44 | { |
45 | struct push_switch_platform_info *psw_info; |
46 | struct push_switch *psw; |
47 | int ret, irq; |
48 | |
49 | psw = kzalloc(sizeof(struct push_switch), GFP_KERNEL); |
50 | if (unlikely(!psw)) |
51 | return -ENOMEM; |
52 | |
53 | irq = platform_get_irq(pdev, 0); |
54 | if (unlikely(irq < 0)) { |
55 | ret = -ENODEV; |
56 | goto err; |
57 | } |
58 | |
59 | psw_info = pdev->dev.platform_data; |
60 | BUG_ON(!psw_info); |
61 | |
62 | ret = request_irq(irq, handler: psw_info->irq_handler, |
63 | flags: psw_info->irq_flags, |
64 | name: psw_info->name ? psw_info->name : DRV_NAME, dev: pdev); |
65 | if (unlikely(ret < 0)) |
66 | goto err; |
67 | |
68 | if (psw_info->name) { |
69 | ret = device_create_file(device: &pdev->dev, entry: &dev_attr_switch); |
70 | if (unlikely(ret)) { |
71 | dev_err(&pdev->dev, "Failed creating device attrs\n" ); |
72 | ret = -EINVAL; |
73 | goto err_irq; |
74 | } |
75 | } |
76 | |
77 | INIT_WORK(&psw->work, switch_work_handler); |
78 | timer_setup(&psw->debounce, switch_timer, 0); |
79 | |
80 | /* Workqueue API brain-damage */ |
81 | psw->pdev = pdev; |
82 | |
83 | platform_set_drvdata(pdev, data: psw); |
84 | |
85 | return 0; |
86 | |
87 | err_irq: |
88 | free_irq(irq, pdev); |
89 | err: |
90 | kfree(objp: psw); |
91 | return ret; |
92 | } |
93 | |
94 | static int switch_drv_remove(struct platform_device *pdev) |
95 | { |
96 | struct push_switch *psw = platform_get_drvdata(pdev); |
97 | struct push_switch_platform_info *psw_info = pdev->dev.platform_data; |
98 | int irq = platform_get_irq(pdev, 0); |
99 | |
100 | if (psw_info->name) |
101 | device_remove_file(dev: &pdev->dev, attr: &dev_attr_switch); |
102 | |
103 | platform_set_drvdata(pdev, NULL); |
104 | timer_shutdown_sync(timer: &psw->debounce); |
105 | flush_work(work: &psw->work); |
106 | free_irq(irq, pdev); |
107 | |
108 | kfree(objp: psw); |
109 | |
110 | return 0; |
111 | } |
112 | |
113 | static struct platform_driver switch_driver = { |
114 | .probe = switch_drv_probe, |
115 | .remove = switch_drv_remove, |
116 | .driver = { |
117 | .name = DRV_NAME, |
118 | }, |
119 | }; |
120 | |
121 | static int __init switch_init(void) |
122 | { |
123 | printk(KERN_NOTICE DRV_NAME ": version %s loaded\n" , DRV_VERSION); |
124 | return platform_driver_register(&switch_driver); |
125 | } |
126 | |
127 | static void __exit switch_exit(void) |
128 | { |
129 | platform_driver_unregister(&switch_driver); |
130 | } |
131 | module_init(switch_init); |
132 | module_exit(switch_exit); |
133 | |
134 | MODULE_VERSION(DRV_VERSION); |
135 | MODULE_AUTHOR("Paul Mundt" ); |
136 | MODULE_LICENSE("GPL v2" ); |
137 | |