sh: generic push-switch framework.
Paul Mundt [Fri, 24 Nov 2006 02:22:57 +0000 (11:22 +0900)]
This adds support for a generic push switch framework. Adaptable for
various switches, including GPIO switches and the push switches commonly
found on Renesas debug boards.

This allows switch states to be trivially reported through sysfs.

Signed-off-by: Paul Mundt <lethal@linux-sh.org>

arch/sh/Kconfig
arch/sh/drivers/Kconfig [new file with mode: 0644]
arch/sh/drivers/Makefile
arch/sh/drivers/push-switch.c [new file with mode: 0644]
include/asm-sh/push-switch.h [new file with mode: 0644]

index d62183f..a03f155 100644 (file)
@@ -473,6 +473,8 @@ config HEARTBEAT
          behavior is platform-dependent, but normally the flash frequency is
          a hyperbolic function of the 5-minute load average.
 
+source "arch/sh/drivers/Kconfig"
+
 endmenu
 
 config ISA_DMA_API
diff --git a/arch/sh/drivers/Kconfig b/arch/sh/drivers/Kconfig
new file mode 100644 (file)
index 0000000..c54c758
--- /dev/null
@@ -0,0 +1,9 @@
+menu "Additional SuperH Device Drivers"
+
+config PUSH_SWITCH
+       tristate "Push switch support"
+       help
+         This enables support for the push switch framework, a simple
+         framework that allows for sysfs driven switch status reporting.
+
+endmenu
index 338c372..bf18dbf 100644 (file)
@@ -5,4 +5,4 @@
 obj-$(CONFIG_PCI)              += pci/
 obj-$(CONFIG_SH_DMA)           += dma/
 obj-$(CONFIG_SUPERHYWAY)       += superhyway/
-
+obj-$(CONFIG_PUSH_SWITCH)      += push-switch.o
diff --git a/arch/sh/drivers/push-switch.c b/arch/sh/drivers/push-switch.c
new file mode 100644 (file)
index 0000000..f2b9157
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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/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.0"
+
+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(void *data)
+{
+       struct platform_device *pdev = data;
+       struct push_switch *psw = platform_get_drvdata(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,
+                         IRQF_DISABLED | 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, pdev);
+       init_timer(&psw->debounce);
+
+       psw->debounce.function = switch_timer;
+       psw->debounce.data = (unsigned long)psw;
+
+       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_scheduled_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("GPLv2");
diff --git a/include/asm-sh/push-switch.h b/include/asm-sh/push-switch.h
new file mode 100644 (file)
index 0000000..dfc6bad
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef __ASM_SH_PUSH_SWITCH_H
+#define __ASM_SH_PUSH_SWITCH_H
+
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+
+struct push_switch {
+       /* switch state */
+       unsigned int            state:1;
+       /* debounce timer */
+       struct timer_list       debounce;
+       /* workqueue */
+       struct work_struct      work;
+};
+
+struct push_switch_platform_info {
+       /* IRQ handler */
+       irqreturn_t             (*irq_handler)(int irq, void *data);
+       /* Special IRQ flags */
+       unsigned int            irq_flags;
+       /* Bit location of switch */
+       unsigned int            bit;
+       /* Symbolic switch name */
+       const char              *name;
+};
+
+#endif /* __ASM_SH_PUSH_SWITCH_H */