/*++
*
* 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 .
*
* WonderMedia Technologies, Inc.
* 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C
--*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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");