162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Generic push-switch framework 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2006 Paul Mundt 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include <linux/init.h> 862306a36Sopenharmony_ci#include <linux/slab.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/interrupt.h> 1162306a36Sopenharmony_ci#include <linux/platform_device.h> 1262306a36Sopenharmony_ci#include <asm/push-switch.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#define DRV_NAME "push-switch" 1562306a36Sopenharmony_ci#define DRV_VERSION "0.1.1" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic ssize_t switch_show(struct device *dev, 1862306a36Sopenharmony_ci struct device_attribute *attr, 1962306a36Sopenharmony_ci char *buf) 2062306a36Sopenharmony_ci{ 2162306a36Sopenharmony_ci struct push_switch_platform_info *psw_info = dev->platform_data; 2262306a36Sopenharmony_ci return sprintf(buf, "%s\n", psw_info->name); 2362306a36Sopenharmony_ci} 2462306a36Sopenharmony_cistatic DEVICE_ATTR_RO(switch); 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic void switch_timer(struct timer_list *t) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci struct push_switch *psw = from_timer(psw, t, debounce); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci schedule_work(&psw->work); 3162306a36Sopenharmony_ci} 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic void switch_work_handler(struct work_struct *work) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci struct push_switch *psw = container_of(work, struct push_switch, work); 3662306a36Sopenharmony_ci struct platform_device *pdev = psw->pdev; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci psw->state = 0; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci kobject_uevent(&pdev->dev.kobj, KOBJ_CHANGE); 4162306a36Sopenharmony_ci} 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic int switch_drv_probe(struct platform_device *pdev) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci struct push_switch_platform_info *psw_info; 4662306a36Sopenharmony_ci struct push_switch *psw; 4762306a36Sopenharmony_ci int ret, irq; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci psw = kzalloc(sizeof(struct push_switch), GFP_KERNEL); 5062306a36Sopenharmony_ci if (unlikely(!psw)) 5162306a36Sopenharmony_ci return -ENOMEM; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 5462306a36Sopenharmony_ci if (unlikely(irq < 0)) { 5562306a36Sopenharmony_ci ret = -ENODEV; 5662306a36Sopenharmony_ci goto err; 5762306a36Sopenharmony_ci } 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci psw_info = pdev->dev.platform_data; 6062306a36Sopenharmony_ci BUG_ON(!psw_info); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci ret = request_irq(irq, psw_info->irq_handler, 6362306a36Sopenharmony_ci psw_info->irq_flags, 6462306a36Sopenharmony_ci psw_info->name ? psw_info->name : DRV_NAME, pdev); 6562306a36Sopenharmony_ci if (unlikely(ret < 0)) 6662306a36Sopenharmony_ci goto err; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if (psw_info->name) { 6962306a36Sopenharmony_ci ret = device_create_file(&pdev->dev, &dev_attr_switch); 7062306a36Sopenharmony_ci if (unlikely(ret)) { 7162306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed creating device attrs\n"); 7262306a36Sopenharmony_ci ret = -EINVAL; 7362306a36Sopenharmony_ci goto err_irq; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci } 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci INIT_WORK(&psw->work, switch_work_handler); 7862306a36Sopenharmony_ci timer_setup(&psw->debounce, switch_timer, 0); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci /* Workqueue API brain-damage */ 8162306a36Sopenharmony_ci psw->pdev = pdev; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci platform_set_drvdata(pdev, psw); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cierr_irq: 8862306a36Sopenharmony_ci free_irq(irq, pdev); 8962306a36Sopenharmony_cierr: 9062306a36Sopenharmony_ci kfree(psw); 9162306a36Sopenharmony_ci return ret; 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic int switch_drv_remove(struct platform_device *pdev) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci struct push_switch *psw = platform_get_drvdata(pdev); 9762306a36Sopenharmony_ci struct push_switch_platform_info *psw_info = pdev->dev.platform_data; 9862306a36Sopenharmony_ci int irq = platform_get_irq(pdev, 0); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci if (psw_info->name) 10162306a36Sopenharmony_ci device_remove_file(&pdev->dev, &dev_attr_switch); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci platform_set_drvdata(pdev, NULL); 10462306a36Sopenharmony_ci timer_shutdown_sync(&psw->debounce); 10562306a36Sopenharmony_ci flush_work(&psw->work); 10662306a36Sopenharmony_ci free_irq(irq, pdev); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci kfree(psw); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci return 0; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic struct platform_driver switch_driver = { 11462306a36Sopenharmony_ci .probe = switch_drv_probe, 11562306a36Sopenharmony_ci .remove = switch_drv_remove, 11662306a36Sopenharmony_ci .driver = { 11762306a36Sopenharmony_ci .name = DRV_NAME, 11862306a36Sopenharmony_ci }, 11962306a36Sopenharmony_ci}; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic int __init switch_init(void) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci printk(KERN_NOTICE DRV_NAME ": version %s loaded\n", DRV_VERSION); 12462306a36Sopenharmony_ci return platform_driver_register(&switch_driver); 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic void __exit switch_exit(void) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci platform_driver_unregister(&switch_driver); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_cimodule_init(switch_init); 13262306a36Sopenharmony_cimodule_exit(switch_exit); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ciMODULE_VERSION(DRV_VERSION); 13562306a36Sopenharmony_ciMODULE_AUTHOR("Paul Mundt"); 13662306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 137