| /* |
| * Generic push-switch framework |
| * |
| * Copyright (C) 2006 Paul Mundt |
| * |
| * This file is subject to the terms and conditions of the GNU General Public |
| * License. See the file "COPYING" in the main directory of this archive |
| * for more details. |
| */ |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <asm/push-switch.h> |
| |
| #define DRV_NAME "push-switch" |
| #define DRV_VERSION "0.1.1" |
| |
| static ssize_t switch_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct push_switch_platform_info *psw_info = dev->platform_data; |
| return sprintf(buf, "%s\n", psw_info->name); |
| } |
| static DEVICE_ATTR(switch, S_IRUGO, switch_show, NULL); |
| |
| static void switch_timer(unsigned long data) |
| { |
| struct push_switch *psw = (struct push_switch *)data; |
| |
| schedule_work(&psw->work); |
| } |
| |
| static void switch_work_handler(struct work_struct *work) |
| { |
| struct push_switch *psw = container_of(work, struct push_switch, work); |
| struct platform_device *pdev = psw->pdev; |
| |
| psw->state = 0; |
| |
| kobject_uevent(&pdev->dev.kobj, KOBJ_CHANGE); |
| } |
| |
| static int switch_drv_probe(struct platform_device *pdev) |
| { |
| struct push_switch_platform_info *psw_info; |
| struct push_switch *psw; |
| int ret, irq; |
| |
| psw = kzalloc(sizeof(struct push_switch), GFP_KERNEL); |
| if (unlikely(!psw)) |
| return -ENOMEM; |
| |
| irq = platform_get_irq(pdev, 0); |
| if (unlikely(irq < 0)) { |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| psw_info = pdev->dev.platform_data; |
| BUG_ON(!psw_info); |
| |
| ret = request_irq(irq, psw_info->irq_handler, |
| psw_info->irq_flags, |
| psw_info->name ? psw_info->name : DRV_NAME, pdev); |
| if (unlikely(ret < 0)) |
| goto err; |
| |
| if (psw_info->name) { |
| ret = device_create_file(&pdev->dev, &dev_attr_switch); |
| if (unlikely(ret)) { |
| dev_err(&pdev->dev, "Failed creating device attrs\n"); |
| ret = -EINVAL; |
| goto err_irq; |
| } |
| } |
| |
| INIT_WORK(&psw->work, switch_work_handler); |
| init_timer(&psw->debounce); |
| |
| psw->debounce.function = switch_timer; |
| psw->debounce.data = (unsigned long)psw; |
| |
| /* Workqueue API brain-damage */ |
| psw->pdev = pdev; |
| |
| platform_set_drvdata(pdev, psw); |
| |
| return 0; |
| |
| err_irq: |
| free_irq(irq, pdev); |
| err: |
| kfree(psw); |
| return ret; |
| } |
| |
| static int switch_drv_remove(struct platform_device *pdev) |
| { |
| struct push_switch *psw = platform_get_drvdata(pdev); |
| struct push_switch_platform_info *psw_info = pdev->dev.platform_data; |
| int irq = platform_get_irq(pdev, 0); |
| |
| if (psw_info->name) |
| device_remove_file(&pdev->dev, &dev_attr_switch); |
| |
| platform_set_drvdata(pdev, NULL); |
| flush_work(&psw->work); |
| del_timer_sync(&psw->debounce); |
| free_irq(irq, pdev); |
| |
| kfree(psw); |
| |
| return 0; |
| } |
| |
| static struct platform_driver switch_driver = { |
| .probe = switch_drv_probe, |
| .remove = switch_drv_remove, |
| .driver = { |
| .name = DRV_NAME, |
| }, |
| }; |
| |
| static int __init switch_init(void) |
| { |
| printk(KERN_NOTICE DRV_NAME ": version %s loaded\n", DRV_VERSION); |
| return platform_driver_register(&switch_driver); |
| } |
| |
| static void __exit switch_exit(void) |
| { |
| platform_driver_unregister(&switch_driver); |
| } |
| module_init(switch_init); |
| module_exit(switch_exit); |
| |
| MODULE_VERSION(DRV_VERSION); |
| MODULE_AUTHOR("Paul Mundt"); |
| MODULE_LICENSE("GPL v2"); |