/* * Copyright (c) 2013-2015 liaoyongming@allwinnertech.com * * * 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, write to the Free Software * * ChangeLog * status : power off working suspend working * ___________ ______________________ * acc : ___________| |____________________________| need to wake up when rising, by power on * ___________ ____________________________ * acc_irq : |___________|need to suspend when rising |______________________ * * gsensor : |wakeup by gsensor,recording * * 3G : |wakeup by 3G, recording */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ACC_KEY_CODE KEY_HOME struct sunxi_acc_device { u32 irq_hd; struct gpio_config pio; struct input_dev *idev; }; static struct sunxi_acc_device sunxi_acc_dev; static int acc_irq_status; static int acc_irq_wakeup_enable; static ssize_t sunxi_acc_irq_wakeup_enable_show(struct class *class, struct class_attribute *attr, char *buf) { int ret = 0; ret = sprintf(buf, "%d\n", acc_irq_wakeup_enable); return ret; } static ssize_t sunxi_acc_irq_wakeup_enable_store(struct class *class, struct class_attribute *attr, const char *buf, size_t count) { int enable; if (buf == NULL) return -1; enable = simple_strtol(buf, NULL, 0); acc_irq_wakeup_enable = (enable > 0) ? 1 : 0; if (acc_irq_wakeup_enable) enable_wakeup_src(CPUS_GPIO_SRC, sunxi_acc_dev.pio.gpio); else disable_wakeup_src(CPUS_GPIO_SRC, sunxi_acc_dev.pio.gpio); return count; } static ssize_t sunxi_acc_irq_status_show(struct class *dev, struct class_attribute *attr, char *buf) { int ret = 0; ret = sprintf(buf, "%d\n", acc_irq_status); acc_irq_status = 0; return ret; } static ssize_t sunxi_acc_io_status_show(struct class *dev, struct class_attribute *attr, char *buf) { int ret = 0; ret = sprintf(buf, "%d\n", gpio_get_value(sunxi_acc_dev.pio.gpio)); return ret; } static struct class_attribute acc_status_attrs[] = { __ATTR(io_status, S_IRUGO, sunxi_acc_io_status_show, NULL), __ATTR(irq_status, S_IRUGO, sunxi_acc_irq_status_show, NULL), __ATTR(irq_wakeup_enable, S_IRUGO | S_IWUGO, sunxi_acc_irq_wakeup_enable_show, sunxi_acc_irq_wakeup_enable_store), __ATTR_NULL }; static struct class sunxi_acc_status_attr_group = { .name = "gpio_acc", .class_attrs = acc_status_attrs, }; static int sunxi_acc_create_input_device(struct sunxi_acc_device *acc_dev) { acc_dev->idev = input_allocate_device(); if (!acc_dev->idev) { pr_err("[sunxi acc]: not enought memory for input device\n"); return -ENOMEM; } acc_dev->idev->name = "acc_dec"; acc_dev->idev->phys = "acc_dec/input0"; acc_dev->idev->id.bustype = BUS_HOST; acc_dev->idev->id.vendor = 0x0001; acc_dev->idev->id.product = 0x0001; acc_dev->idev->id.version = 0x0100; #ifdef REPORT_REPEAT_KEY_BY_INPUT_CORE acc_dev->idev->evbit[0] = BIT_MASK(EV_KEY)|BIT_MASK(EV_REP); printk(KERN_DEBUG "REPORT_REPEAT_KEY_BY_INPUT_CORE is defined, support report repeat key value. \n"); #else acc_dev->idev->evbit[0] = BIT_MASK(EV_KEY); #endif set_bit(ACC_KEY_CODE, acc_dev->idev->keybit); if (input_register_device(acc_dev->idev)) { pr_err("[sunxi acc]: input register device failed\n"); input_free_device(acc_dev->idev); return -ENOMEM; } return 0; } static int sunxi_acc_free_input_device(struct sunxi_acc_device *acc_dev) { if (acc_dev->idev) { input_unregister_device(acc_dev->idev); } return 0; } static int sunxi_acc_irq_onoff(int onoff) { if (onoff) { pr_info("acc irq enable\n"); if (sunxi_acc_dev.pio.gpio < 1024) enable_irq(sunxi_acc_dev.irq_hd); else axp_gpio_irq_enable(0, sunxi_acc_dev.pio.gpio); } else { pr_info("acc irq disable\n"); if (sunxi_acc_dev.pio.gpio < 1024) disable_irq_nosync(sunxi_acc_dev.irq_hd); else axp_gpio_irq_disable(0, sunxi_acc_dev.pio.gpio); } return 0; } static int sunxi_acc_gpio_free(void) { if (sunxi_acc_dev.pio.gpio < 1024) free_irq(sunxi_acc_dev.irq_hd, NULL); else axp_gpio_irq_free(0, sunxi_acc_dev.pio.gpio); return 0; } static u32 sunxi_acc_irq_func(int irq, void *para) { pr_info("sunxi acc irq happened\n"); acc_irq_status = 1; input_report_key(sunxi_acc_dev.idev, ACC_KEY_CODE, 1); input_sync(sunxi_acc_dev.idev); mdelay(100); input_report_key(sunxi_acc_dev.idev, ACC_KEY_CODE, 0); input_sync(sunxi_acc_dev.idev); return 0; } static int sunxi_acc_gpio_request(struct platform_device *pdev) { int ret = -1; struct device_node *np = pdev->dev.of_node; sunxi_acc_dev.pio.gpio = of_get_named_gpio_flags(np, "acc_int", 0, (enum of_gpio_flags *)(&(sunxi_acc_dev.pio))); if (!gpio_is_valid(sunxi_acc_dev.pio.gpio)) { pr_err("[sunxi acc]: gpio key is invalied\n"); ret = -EPERM; goto exit; } if (sunxi_acc_dev.pio.gpio < 1024) { pr_info("request cpu gpio irq\n"); sunxi_acc_dev.irq_hd = gpio_to_irq(sunxi_acc_dev.pio.gpio); ret = request_irq(sunxi_acc_dev.irq_hd, sunxi_acc_irq_func, IRQF_NO_SUSPEND | IRQF_TRIGGER_RISING, "sunxi acc", NULL); if (IS_ERR_VALUE(ret)) { pr_err("sunxi acc cpux gpio request irq failed\n"); goto exit; } } else { pr_info("request axp gpio irq\n"); ret = axp_gpio_irq_request(0, sunxi_acc_dev.pio.gpio, sunxi_acc_irq_func, NULL); if (IS_ERR_VALUE(ret)) { pr_err("sunxi acc axp gpio request irq failed\n"); goto exit; } axp_gpio_irq_set_type(0, sunxi_acc_dev.pio.gpio, AXP_GPIO_IRQF_TRIGGER_RISING); axp_gpio_irq_enable(0, sunxi_acc_dev.pio.gpio); } exit: printk("%s\n", __func__); return ret; } int sunxi_acc_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; int ret = -1; if (!of_device_is_available(np)) { pr_err("%s: sunxi acc is diable\n", __func__); ret = -EPERM; goto exit; } memset(&sunxi_acc_dev, 0, sizeof(struct sunxi_acc_device)); ret = sunxi_acc_create_input_device(&sunxi_acc_dev); if (ret) { pr_err("[sunxi acc]: create input device err\n"); goto exit; } ret = sunxi_acc_gpio_request(pdev); if (ret) { pr_err("[sunxi acc]: request irq failed\n"); goto exit_dev; } ret = class_register(&sunxi_acc_status_attr_group); if (ret) { pr_err("create device file failed\n"); ret = -EINVAL; goto exit_irq; } pr_err("[sunxi acc] %s ok\n", __func__); return 0; exit_irq: sunxi_acc_gpio_free(); exit_dev: sunxi_acc_free_input_device(&sunxi_acc_dev); exit: printk("%s exit\n", __func__); return ret; } static int sunxi_acc_remove(struct platform_device *pdev) { sunxi_acc_gpio_free(); class_unregister(&sunxi_acc_status_attr_group); sunxi_acc_free_input_device(&sunxi_acc_dev); return 0; } static void sunxi_acc_shutdown(struct platform_device *pdev) { return; } static int sunxi_acc_suspend(struct device *dev) { if (!acc_irq_wakeup_enable) sunxi_acc_irq_onoff(0); return 0; } static int sunxi_acc_resume(struct device *dev) { if (!acc_irq_wakeup_enable) sunxi_acc_irq_onoff(1); return 0; } static const struct dev_pm_ops sunxi_acc_pm_ops = { .suspend = sunxi_acc_suspend, .resume = sunxi_acc_resume, }; static const struct of_device_id sunxi_acc_of_match[] = { {.compatible = "allwinner,sunxi-acc-det"}, {}, }; MODULE_DEVICE_TABLE(of, sunxi_acc_of_match); static struct platform_driver sunxi_acc_device_driver = { .probe = sunxi_acc_probe, .remove = sunxi_acc_remove, .shutdown = sunxi_acc_shutdown, .driver = { .name = "sunxi-acc-det", .owner = THIS_MODULE, .pm = &sunxi_acc_pm_ops, .of_match_table = of_match_ptr(sunxi_acc_of_match), }, }; static int __init sunxi_acc_init(void) { return platform_driver_register(&sunxi_acc_device_driver); } static void __exit sunxi_acc_exit(void) { platform_driver_unregister(&sunxi_acc_device_driver); } module_init(sunxi_acc_init); module_exit(sunxi_acc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR(" <@> "); MODULE_DESCRIPTION("Keyboard driver for GPIOs");