summaryrefslogtreecommitdiff
path: root/drivers/switch/wmt/wmt_switch.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/switch/wmt/wmt_switch.c')
-rwxr-xr-xdrivers/switch/wmt/wmt_switch.c424
1 files changed, 424 insertions, 0 deletions
diff --git a/drivers/switch/wmt/wmt_switch.c b/drivers/switch/wmt/wmt_switch.c
new file mode 100755
index 00000000..b4b9c1d1
--- /dev/null
+++ b/drivers/switch/wmt/wmt_switch.c
@@ -0,0 +1,424 @@
+/*++
+ *
+ * Copyright c 2010 WonderMedia Technologies, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * WonderMedia Technologies, Inc.
+ * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C
+--*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+#include <linux/interrupt.h>
+#include <asm/irq.h>
+#include <linux/workqueue.h>
+#include <linux/switch.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/input.h>
+#include <linux/suspend.h>
+
+#include <linux/gpio.h>
+#include <mach/wmt_iomux.h>
+
+#define WMT_SWITCH_TIMER_INTERVAL 250 // ms
+#define WMT_WIREDKEY_TIMER_INTERVAL 100 // ms
+
+#define WMT_WIREDKEY_PLUGIN_DEBOUNCE 2000 // ms
+#define WMT_WIREDKEY_PRESS_COUNT 1
+
+extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen);
+
+struct wmt_switch_priv {
+ struct work_struct switch_work;
+ struct timer_list switch_timer;
+
+ struct input_dev *idev;
+ struct work_struct wiredkey_work;
+ struct timer_list wiredkey_timer;
+ struct notifier_block pm_notifier;
+
+ int prepare_suspend;
+};
+
+struct wmt_switch {
+ struct list_head list;
+ int gpio;
+ int active;
+ int state;
+ struct switch_dev switch_dev;
+};
+
+struct wmt_wiredkey {
+ struct list_head list;
+ int gpio;
+ int active;
+ unsigned int keycode;
+ int count;
+};
+
+static LIST_HEAD(wmt_switch_list);
+static LIST_HEAD(wmt_wiredkey_list);
+
+static void wmt_switch_do_work(struct work_struct *work)
+{
+ struct wmt_switch_priv *priv = container_of(work, struct wmt_switch_priv, switch_work);
+ struct wmt_switch *wmt_switch;
+ int new;
+
+ list_for_each_entry(wmt_switch, &wmt_switch_list, list) {
+ new = gpio_get_value(wmt_switch->gpio);
+ if (new != wmt_switch->state) {
+ if (new == wmt_switch->active)
+ switch_set_state(&wmt_switch->switch_dev, 1);
+ else
+ switch_set_state(&wmt_switch->switch_dev, 0);
+
+ wmt_switch->state = new;
+
+ if (!list_empty(&wmt_wiredkey_list) && !strcmp(wmt_switch->switch_dev.name, "h2w")) {
+ if (wmt_switch->switch_dev.state > 0) {
+ // headset plugin, start hook-key detect in 2s for debounce
+ mod_timer(&priv->wiredkey_timer, jiffies + msecs_to_jiffies(WMT_WIREDKEY_PLUGIN_DEBOUNCE));
+ }
+ else {
+ // headset plugout, stop hook-key detect
+ del_timer_sync(&priv->wiredkey_timer);
+ }
+ }
+ }
+ }
+}
+
+static void wmt_switch_timer_handler(unsigned long data)
+{
+ struct wmt_switch_priv *priv = (struct wmt_switch_priv *)data;
+ schedule_work(&priv->switch_work);
+ mod_timer(&priv->switch_timer, jiffies + msecs_to_jiffies(WMT_SWITCH_TIMER_INTERVAL));
+}
+
+static void wmt_wiredkey_do_work(struct work_struct *work)
+{
+ struct wmt_switch_priv *priv = container_of(work, struct wmt_switch_priv, wiredkey_work);
+ struct wmt_wiredkey *wmt_wiredkey;
+ int new;
+ if (priv->prepare_suspend)
+ return;
+ list_for_each_entry(wmt_wiredkey, &wmt_wiredkey_list, list) {
+ new = gpio_get_value(wmt_wiredkey->gpio);
+ if (new != wmt_wiredkey->active) {
+ // key release
+ if (wmt_wiredkey->count > WMT_WIREDKEY_PRESS_COUNT) {
+ printk("Wired-key(%d): release\n", wmt_wiredkey->keycode);
+ input_event(priv->idev, EV_KEY, wmt_wiredkey->keycode, 0);
+ input_sync(priv->idev);
+ }
+ wmt_wiredkey->count = 0; // reset press count caused by key-release
+ }
+ else {
+ // key pressed
+ if (wmt_wiredkey->count == WMT_WIREDKEY_PRESS_COUNT) {
+ printk("Wired-key(%d): pressed\n", wmt_wiredkey->keycode);
+ input_event(priv->idev, EV_KEY, wmt_wiredkey->keycode, 1);
+ input_sync(priv->idev);
+ }
+ wmt_wiredkey->count++; // increass press count caused by key-pressed
+ }
+ }
+}
+
+static void wmt_wiredkey_timer_handler(unsigned long data)
+{
+ struct wmt_switch_priv *priv = (struct wmt_switch_priv *)data;
+ schedule_work(&priv->wiredkey_work);
+ mod_timer(&priv->wiredkey_timer, jiffies + msecs_to_jiffies(WMT_WIREDKEY_TIMER_INTERVAL));
+}
+
+static int wmt_switch_pm_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct wmt_switch_priv *priv = container_of(this, struct wmt_switch_priv, pm_notifier);
+ //return 0;//it goes before driver suspend, well system fails to suspend.
+ //our timer can't be added again, so move it to driver suspend. 2013-9-30
+ switch (event) {
+ case PM_POST_HIBERNATION:
+ case PM_POST_SUSPEND:
+ break;
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ priv->prepare_suspend = 1;// add 2013-10-21
+ //del_timer_sync(&priv->switch_timer);
+ //del_timer_sync(&priv->wiredkey_timer);
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+ return 0;
+}
+
+static int __devinit wmt_switch_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct wmt_switch_priv *priv;
+ struct wmt_switch *wmt_switch, *next;
+ struct wmt_wiredkey *wmt_wiredkey, *next2;
+
+ priv = kzalloc(sizeof(struct wmt_switch_priv), GFP_KERNEL);
+ if (priv == NULL)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, priv);
+
+ list_for_each_entry_safe(wmt_switch, next, &wmt_switch_list, list) {
+ ret = gpio_request(wmt_switch->gpio, wmt_switch->switch_dev.name);
+ if (ret < 0) {
+ printk("Switch(%s): gpio(%d) request failed\n",
+ wmt_switch->switch_dev.name, wmt_switch->gpio);
+ list_del_init(&wmt_switch->list);
+ kfree(wmt_switch);
+ }
+ else {
+ printk("Switch(%s): gpio(%d) request success\n",
+ wmt_switch->switch_dev.name, wmt_switch->gpio);
+ gpio_direction_input(wmt_switch->gpio);
+ wmt_switch->state = -1;
+ switch_dev_register(&wmt_switch->switch_dev);
+ }
+ }
+
+ if (list_empty(&wmt_switch_list)) {
+ kfree(priv);
+ return -EINVAL;
+ }
+
+ list_for_each_entry_safe(wmt_wiredkey, next2, &wmt_wiredkey_list, list) {
+ ret = gpio_request(wmt_wiredkey->gpio, "Wired-key");
+ if (ret < 0) {
+ printk("Wired-key: gpio(%d) request failed\n", wmt_wiredkey->gpio);
+ list_del_init(&wmt_wiredkey->list);
+ kfree(wmt_wiredkey);
+ }
+ else {
+ printk("Wired-key: gpio(%d) request success\n", wmt_wiredkey->gpio);
+ gpio_direction_input(wmt_wiredkey->gpio);
+ wmt_wiredkey->count = 0;
+ }
+ }
+
+ // register input device for wired-key
+ priv->idev = input_allocate_device();
+ if (priv->idev == NULL) {
+ printk(KERN_ERR "Wired-key: input_allocate_device failed\n");
+ return -ENOMEM;
+ }
+ priv->idev->name = "Wired-key";
+ priv->idev->phys = "Wired-key";
+ set_bit(EV_KEY, priv->idev->evbit);
+ list_for_each_entry(wmt_wiredkey, &wmt_wiredkey_list, list) {
+ set_bit(wmt_wiredkey->keycode, priv->idev->keybit);
+ }
+ input_register_device(priv->idev);
+
+ // init wired-key devices
+ INIT_WORK(&priv->wiredkey_work, wmt_wiredkey_do_work);
+ init_timer(&priv->wiredkey_timer);
+ priv->wiredkey_timer.data = (unsigned long)priv;
+ priv->wiredkey_timer.function = wmt_wiredkey_timer_handler;
+
+ // init switch devices
+ INIT_WORK(&priv->switch_work, wmt_switch_do_work);
+ init_timer(&priv->switch_timer);
+ priv->switch_timer.data = (unsigned long)priv;
+ priv->switch_timer.function = wmt_switch_timer_handler;
+ mod_timer(&priv->switch_timer, jiffies + msecs_to_jiffies(WMT_SWITCH_TIMER_INTERVAL));
+
+ // register pm event
+ priv->pm_notifier.notifier_call = wmt_switch_pm_event;
+ register_pm_notifier(&priv->pm_notifier);
+
+ return 0;
+}
+
+static int __devexit wmt_switch_remove(struct platform_device *pdev)
+{
+ struct wmt_switch_priv *priv = platform_get_drvdata(pdev);
+ struct wmt_switch *wmt_switch, *next;
+ struct wmt_wiredkey *wmt_wiredkey, *next2;
+
+ unregister_pm_notifier(&priv->pm_notifier);
+
+ del_timer_sync(&priv->switch_timer);
+ del_timer_sync(&priv->wiredkey_timer);
+
+ list_for_each_entry_safe(wmt_wiredkey, next2, &wmt_wiredkey_list, list) {
+ gpio_free(wmt_wiredkey->gpio);
+ list_del_init(&wmt_wiredkey->list);
+ kfree(wmt_wiredkey);
+ }
+
+ list_for_each_entry_safe(wmt_switch, next, &wmt_switch_list, list) {
+ switch_dev_unregister(&wmt_switch->switch_dev);
+ gpio_free(wmt_switch->gpio);
+ list_del_init(&wmt_switch->list);
+ kfree(wmt_switch);
+ }
+
+ input_unregister_device(priv->idev);
+
+ kfree(priv);
+ return 0;
+}
+
+static int wmt_switch_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct wmt_switch_priv *priv = platform_get_drvdata(pdev);
+ printk("<<<< %s\n", __FUNCTION__);
+ del_timer_sync(&priv->wiredkey_timer);
+ del_timer_sync(&priv->switch_timer);
+ return 0;
+}
+
+static int wmt_switch_resume(struct platform_device *pdev)
+{
+ struct wmt_switch_priv *priv = platform_get_drvdata(pdev);
+ struct wmt_switch *wmt_switch;
+ printk("<<<< %s\n", __FUNCTION__);
+ priv->prepare_suspend = 0;//add 2013-10-21
+
+ mod_timer(&priv->switch_timer, jiffies + msecs_to_jiffies(500));
+
+ // if headset plugin, start hook-key detect
+ list_for_each_entry(wmt_switch, &wmt_switch_list, list) {
+ if (!list_empty(&wmt_wiredkey_list) && !strcmp(wmt_switch->switch_dev.name, "h2w")) {
+ if (wmt_switch->switch_dev.state > 0)
+ mod_timer(&priv->wiredkey_timer, jiffies + msecs_to_jiffies(WMT_WIREDKEY_PLUGIN_DEBOUNCE));
+ }
+
+ if (!strcmp(wmt_switch->switch_dev.name, "rotation") && !wmt_switch->active)
+ {
+ wmt_gpio_setpull(wmt_switch->gpio, WMT_GPIO_PULL_UP);
+ }
+ }
+ return 0;
+}
+
+static struct platform_driver wmt_switch_driver = {
+ .driver = {
+ .name = "wmt-switch",
+ .owner = THIS_MODULE,
+ },
+ .probe = wmt_switch_probe,
+ .remove = __devexit_p(wmt_switch_remove),
+ .suspend = wmt_switch_suspend,
+ .resume = wmt_switch_resume,
+};
+
+static __init int wmt_switch_init(void)
+{
+ int ret, gpio, active, keycode;
+ char buf[128];
+ int len = ARRAY_SIZE(buf);
+ struct wmt_switch *wmt_switch;
+ struct wmt_wiredkey *wmt_wiredkey;
+
+ ret = wmt_getsyspara("wmt.switch.sim", buf, &len);
+ if (ret == 0) {
+ sscanf(buf, "%d:%d", &gpio, &active);
+ if (gpio_is_valid(gpio)) {
+ wmt_switch = kzalloc(sizeof(*wmt_switch), GFP_KERNEL);
+ if (wmt_switch) {
+ wmt_switch->switch_dev.name = "sim";
+ wmt_switch->gpio = gpio;
+ wmt_switch->active = active;
+ list_add_tail(&wmt_switch->list, &wmt_switch_list);
+ }
+ }
+ }
+
+ ret = wmt_getsyspara("wmt.switch.sim2", buf, &len);
+ if (ret == 0) {
+ sscanf(buf, "%d:%d", &gpio, &active);
+ if (gpio_is_valid(gpio)) {
+ wmt_switch = kzalloc(sizeof(*wmt_switch), GFP_KERNEL);
+ if (wmt_switch) {
+ wmt_switch->switch_dev.name = "sim2";
+ wmt_switch->gpio = gpio;
+ wmt_switch->active = active;
+ list_add_tail(&wmt_switch->list, &wmt_switch_list);
+ }
+ }
+ }
+
+ ret = wmt_getsyspara("wmt.switch.hs", buf, &len);
+ if (ret == 0) {
+ sscanf(buf, "%d:%d", &gpio, &active);
+ if (gpio_is_valid(gpio)) {
+ wmt_switch = kzalloc(sizeof(*wmt_switch), GFP_KERNEL);
+ if (wmt_switch) {
+ wmt_switch->switch_dev.name = "h2w";
+ wmt_switch->gpio = gpio;
+ wmt_switch->active = active;
+ list_add_tail(&wmt_switch->list, &wmt_switch_list);
+ }
+ }
+ }
+ //add by rambo 2013-8-28, for lock key.
+ //well also can be used for other ways,so we call emukey.
+ ret = wmt_getsyspara("wmt.switch.rotation", buf, &len);
+ if (ret == 0) {
+ sscanf(buf, "%d:%d", &gpio, &active);
+ if (gpio_is_valid(gpio)) {
+ wmt_switch = kzalloc(sizeof(*wmt_switch), GFP_KERNEL);
+ if (wmt_switch) {
+ wmt_switch->switch_dev.name = "rotation";
+ wmt_switch->gpio = gpio;
+ wmt_switch->active = active;
+ list_add_tail(&wmt_switch->list, &wmt_switch_list);
+ }
+ if (active == 0)
+ wmt_gpio_setpull(gpio, WMT_GPIO_PULL_UP);
+ }
+ }
+
+ ret = wmt_getsyspara("wmt.wirekey.hook", buf, &len);
+ if (ret == 0) {
+ sscanf(buf, "%d:%d:%d", &gpio, &active, &keycode);
+ if (gpio_is_valid(gpio)) {
+ wmt_wiredkey = kzalloc(sizeof(*wmt_wiredkey), GFP_KERNEL);
+ if (wmt_wiredkey) {
+ wmt_wiredkey->gpio = gpio;
+ wmt_wiredkey->active = active;
+ wmt_wiredkey->keycode = keycode;
+ list_add_tail(&wmt_wiredkey->list, &wmt_wiredkey_list);
+ }
+ }
+ }
+
+ return platform_driver_register(&wmt_switch_driver);
+}
+module_init(wmt_switch_init);
+
+static __exit void wmt_switch_exit(void)
+{
+ platform_driver_unregister(&wmt_switch_driver);
+}
+module_exit(wmt_switch_exit);
+
+MODULE_DESCRIPTION("WMT GPIO Switch");
+MODULE_AUTHOR("WonderMedia Technologies, Inc.");
+MODULE_LICENSE("GPL");