18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Generic push-switch framework 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2006 Paul Mundt 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include <linux/init.h> 88c2ecf20Sopenharmony_ci#include <linux/slab.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 128c2ecf20Sopenharmony_ci#include <asm/push-switch.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#define DRV_NAME "push-switch" 158c2ecf20Sopenharmony_ci#define DRV_VERSION "0.1.1" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistatic ssize_t switch_show(struct device *dev, 188c2ecf20Sopenharmony_ci struct device_attribute *attr, 198c2ecf20Sopenharmony_ci char *buf) 208c2ecf20Sopenharmony_ci{ 218c2ecf20Sopenharmony_ci struct push_switch_platform_info *psw_info = dev->platform_data; 228c2ecf20Sopenharmony_ci return sprintf(buf, "%s\n", psw_info->name); 238c2ecf20Sopenharmony_ci} 248c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(switch); 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic void switch_timer(struct timer_list *t) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci struct push_switch *psw = from_timer(psw, t, debounce); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci schedule_work(&psw->work); 318c2ecf20Sopenharmony_ci} 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic void switch_work_handler(struct work_struct *work) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci struct push_switch *psw = container_of(work, struct push_switch, work); 368c2ecf20Sopenharmony_ci struct platform_device *pdev = psw->pdev; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci psw->state = 0; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci kobject_uevent(&pdev->dev.kobj, KOBJ_CHANGE); 418c2ecf20Sopenharmony_ci} 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic int switch_drv_probe(struct platform_device *pdev) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci struct push_switch_platform_info *psw_info; 468c2ecf20Sopenharmony_ci struct push_switch *psw; 478c2ecf20Sopenharmony_ci int ret, irq; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci psw = kzalloc(sizeof(struct push_switch), GFP_KERNEL); 508c2ecf20Sopenharmony_ci if (unlikely(!psw)) 518c2ecf20Sopenharmony_ci return -ENOMEM; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 548c2ecf20Sopenharmony_ci if (unlikely(irq < 0)) { 558c2ecf20Sopenharmony_ci ret = -ENODEV; 568c2ecf20Sopenharmony_ci goto err; 578c2ecf20Sopenharmony_ci } 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci psw_info = pdev->dev.platform_data; 608c2ecf20Sopenharmony_ci BUG_ON(!psw_info); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci ret = request_irq(irq, psw_info->irq_handler, 638c2ecf20Sopenharmony_ci psw_info->irq_flags, 648c2ecf20Sopenharmony_ci psw_info->name ? psw_info->name : DRV_NAME, pdev); 658c2ecf20Sopenharmony_ci if (unlikely(ret < 0)) 668c2ecf20Sopenharmony_ci goto err; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci if (psw_info->name) { 698c2ecf20Sopenharmony_ci ret = device_create_file(&pdev->dev, &dev_attr_switch); 708c2ecf20Sopenharmony_ci if (unlikely(ret)) { 718c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed creating device attrs\n"); 728c2ecf20Sopenharmony_ci ret = -EINVAL; 738c2ecf20Sopenharmony_ci goto err_irq; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci } 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci INIT_WORK(&psw->work, switch_work_handler); 788c2ecf20Sopenharmony_ci timer_setup(&psw->debounce, switch_timer, 0); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* Workqueue API brain-damage */ 818c2ecf20Sopenharmony_ci psw->pdev = pdev; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, psw); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci return 0; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cierr_irq: 888c2ecf20Sopenharmony_ci free_irq(irq, pdev); 898c2ecf20Sopenharmony_cierr: 908c2ecf20Sopenharmony_ci kfree(psw); 918c2ecf20Sopenharmony_ci return ret; 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic int switch_drv_remove(struct platform_device *pdev) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci struct push_switch *psw = platform_get_drvdata(pdev); 978c2ecf20Sopenharmony_ci struct push_switch_platform_info *psw_info = pdev->dev.platform_data; 988c2ecf20Sopenharmony_ci int irq = platform_get_irq(pdev, 0); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci if (psw_info->name) 1018c2ecf20Sopenharmony_ci device_remove_file(&pdev->dev, &dev_attr_switch); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, NULL); 1048c2ecf20Sopenharmony_ci flush_work(&psw->work); 1058c2ecf20Sopenharmony_ci del_timer_sync(&psw->debounce); 1068c2ecf20Sopenharmony_ci free_irq(irq, pdev); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci kfree(psw); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci return 0; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic struct platform_driver switch_driver = { 1148c2ecf20Sopenharmony_ci .probe = switch_drv_probe, 1158c2ecf20Sopenharmony_ci .remove = switch_drv_remove, 1168c2ecf20Sopenharmony_ci .driver = { 1178c2ecf20Sopenharmony_ci .name = DRV_NAME, 1188c2ecf20Sopenharmony_ci }, 1198c2ecf20Sopenharmony_ci}; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic int __init switch_init(void) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci printk(KERN_NOTICE DRV_NAME ": version %s loaded\n", DRV_VERSION); 1248c2ecf20Sopenharmony_ci return platform_driver_register(&switch_driver); 1258c2ecf20Sopenharmony_ci} 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_cistatic void __exit switch_exit(void) 1288c2ecf20Sopenharmony_ci{ 1298c2ecf20Sopenharmony_ci platform_driver_unregister(&switch_driver); 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_cimodule_init(switch_init); 1328c2ecf20Sopenharmony_cimodule_exit(switch_exit); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ciMODULE_VERSION(DRV_VERSION); 1358c2ecf20Sopenharmony_ciMODULE_AUTHOR("Paul Mundt"); 1368c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 137