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