| /* |
| * Copyright (C) 2012 Motorola, 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, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| * Adds ability to program periodic interrupts from user space that |
| * can wake the phone out of low power modes. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/miscdevice.h> |
| #include <linux/input.h> |
| #include <linux/wakelock.h> |
| #include <linux/workqueue.h> |
| #include <linux/m4sensorhub.h> |
| #include <linux/slab.h> |
| |
| #define STILLMODE_CLIENT_DRIVER_NAME "m4sensorhub_stillmode" |
| #define STILLMODE_DEFAULT_TIMEOUT 600 /* 10 minutes */ |
| |
| static DEFINE_MUTEX(state_access); |
| |
| enum m4_stillmode_type { |
| MOTION, |
| STILL, |
| }; |
| |
| struct stillmode_client { |
| struct m4sensorhub_data *m4sensorhub; |
| struct input_dev *input_dev; |
| enum m4_stillmode_type state; |
| struct work_struct queued_work; |
| u16 timeout; |
| }; |
| |
| static struct stillmode_client *g_stillmode_data; |
| |
| static int stillmode_set_timeout(struct stillmode_client *stillmode_client_data, |
| u16 timeout) |
| { |
| int ret; |
| |
| ret = m4sensorhub_reg_write(stillmode_client_data->m4sensorhub, |
| M4SH_REG_POWER_STILLMODETIMEOUT, |
| (char *)&timeout, m4sh_no_mask); |
| if (ret == m4sensorhub_reg_getsize(stillmode_client_data->m4sensorhub, |
| M4SH_REG_POWER_STILLMODETIMEOUT)) { |
| stillmode_client_data->timeout = timeout; |
| ret = 0; |
| } else |
| ret = -EIO; |
| |
| return ret; |
| |
| } |
| |
| static void stillmode_set_state(struct stillmode_client *stillmode_client_data, |
| enum m4_stillmode_type state) |
| { |
| mutex_lock(&state_access); |
| if (stillmode_client_data->state == state) { |
| mutex_unlock(&state_access); |
| printk(KERN_WARNING "M4SH duplicate stillmode update (%s)\n", |
| (state == STILL) ? "still" : "moving"); |
| } else { |
| stillmode_client_data->state = state; |
| mutex_unlock(&state_access); |
| |
| input_report_switch(stillmode_client_data->input_dev, |
| SW_STILL_MODE, |
| (stillmode_client_data->state == STILL)); |
| input_sync(stillmode_client_data->input_dev); |
| printk(KERN_INFO "stillmode state changed to %s (%d)\n", |
| (state == STILL) ? "still" : "moving", state); |
| } |
| } |
| |
| static int m4_stillmode_exit(void) |
| { |
| struct stillmode_client *stillmode_client_data = g_stillmode_data; |
| int ret = 0; |
| |
| KDEBUG(M4SH_INFO, "Resetting stillmode timer\n"); |
| |
| /* writing timeout value to M4 resets its timer */ |
| ret = stillmode_set_timeout(stillmode_client_data, |
| stillmode_client_data->timeout); |
| if (ret == 0) { |
| if (stillmode_client_data->state == STILL) |
| stillmode_set_state(stillmode_client_data, MOTION); |
| } else |
| KDEBUG(M4SH_ERROR, "M4SH Error setting timeout (%d)\n", ret); |
| |
| return ret; |
| } |
| |
| |
| int m4sensorhub_stillmode_exit(void) |
| { |
| return m4_stillmode_exit(); |
| } |
| EXPORT_SYMBOL_GPL(m4sensorhub_stillmode_exit); |
| |
| |
| static void m4sensorhub_stillmode_work(struct work_struct *work) |
| { |
| m4sensorhub_stillmode_exit(); |
| } |
| |
| static void m4_handle_stillmode_irq(enum m4sensorhub_irqs int_event, |
| void *stillmode_data) |
| { |
| struct stillmode_client *stillmode_client_data = stillmode_data; |
| enum m4_stillmode_type new_state; |
| |
| KDEBUG(M4SH_INFO, "%s() got irq %d (%s)\n", __func__, int_event, |
| int_event == M4SH_IRQ_STILL_DETECTED ? "STILL_MODE" : "MOTION_MODE"); |
| |
| switch (int_event) { |
| case (M4SH_IRQ_STILL_DETECTED): |
| new_state = STILL; |
| break; |
| case (M4SH_IRQ_MOTION_DETECTED): |
| new_state = MOTION; |
| break; |
| default: |
| printk(KERN_ERR "%s() Unexpected irq: %d\n", |
| __func__, int_event); |
| return; |
| break; |
| } |
| |
| stillmode_set_state(stillmode_client_data, new_state); |
| } |
| |
| static ssize_t m4_stillmode_getstate(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct stillmode_client *stillmode_client_data = |
| platform_get_drvdata(pdev); |
| |
| return sprintf(buf, "%d \n", stillmode_client_data->state); |
| } |
| |
| static ssize_t m4_stillmode_setstate(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct stillmode_client *stillmode_client_data = |
| platform_get_drvdata(pdev); |
| long value; |
| int ret = size; |
| |
| if (((strict_strtoul(buf, 10, &value)) < 0) || |
| (value != MOTION)) { |
| KDEBUG(M4SH_ERROR, "M4SH stillmode invalid value: %ld. Only " |
| "%d is allowed\n", value, MOTION); |
| return -EINVAL; |
| } |
| |
| if (value != stillmode_client_data->state) |
| return m4sensorhub_stillmode_exit(); |
| |
| return ret; |
| } |
| |
| static ssize_t m4_stillmode_get_timeout(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct stillmode_client *stillmode_client_data = |
| platform_get_drvdata(pdev); |
| |
| return sprintf(buf, "%d \n", stillmode_client_data->timeout); |
| } |
| |
| static ssize_t m4_stillmode_set_timeout(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct stillmode_client *stillmode_client_data = |
| platform_get_drvdata(pdev); |
| long value; |
| int ret; |
| |
| if (((strict_strtoul(buf, 10, &value)) < 0) || |
| (value < 0) || (value > USHRT_MAX)) { |
| KDEBUG(M4SH_ERROR, "M4SH stillmode invalid timeout: %ld\n", |
| value); |
| return -EINVAL; |
| } |
| |
| KDEBUG(M4SH_DEBUG, "%s() setting timeout to %ld\n", __func__, value); |
| |
| ret = stillmode_set_timeout(stillmode_client_data, value); |
| |
| return ((ret == 0) ? size : ret); |
| } |
| |
| static DEVICE_ATTR(state, 0664, m4_stillmode_getstate, |
| m4_stillmode_setstate); |
| static DEVICE_ATTR(timeout, 0664, m4_stillmode_get_timeout, |
| m4_stillmode_set_timeout); |
| |
| static int stillmode_driver_init(struct init_calldata *p_arg) |
| { |
| int ret; |
| struct m4sensorhub_data *m4sensorhub = p_arg->p_m4sensorhub_data; |
| |
| ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_STILL_DETECTED, |
| m4_handle_stillmode_irq, |
| g_stillmode_data, 1); |
| |
| if (ret < 0) { |
| KDEBUG(M4SH_ERROR, "Error registering still mode IRQ: %d\n", |
| ret); |
| return ret; |
| } |
| |
| ret = m4sensorhub_irq_enable(m4sensorhub, M4SH_IRQ_STILL_DETECTED); |
| if (ret < 0) { |
| KDEBUG(M4SH_ERROR, "Error enabling still mode int: %d\n", |
| ret); |
| goto unregister_still_irq; |
| } |
| |
| ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_MOTION_DETECTED, |
| m4_handle_stillmode_irq, |
| g_stillmode_data, 1); |
| |
| if (ret < 0) { |
| KDEBUG(M4SH_ERROR, "Error registering moving mode IRQ: %d\n", |
| ret); |
| goto disable_still_irq; |
| } |
| |
| ret = m4sensorhub_irq_enable(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); |
| if (ret < 0) { |
| KDEBUG(M4SH_ERROR, "Error enabling moving mode int: %d\n", |
| ret); |
| goto unregister_moving_irq; |
| } |
| |
| /* initialize timer on M4 */ |
| m4sensorhub_stillmode_exit(); |
| return ret; |
| |
| unregister_moving_irq: |
| m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); |
| disable_still_irq: |
| m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_STILL_DETECTED); |
| unregister_still_irq: |
| m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_STILL_DETECTED); |
| return ret; |
| } |
| |
| static int stillmode_client_probe(struct platform_device *pdev) |
| { |
| int ret = -1; |
| struct stillmode_client *stillmode_client_data; |
| struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); |
| |
| if (!m4sensorhub) |
| return -EFAULT; |
| |
| stillmode_client_data = kzalloc(sizeof(*stillmode_client_data), |
| GFP_KERNEL); |
| if (!stillmode_client_data) |
| return -ENOMEM; |
| |
| g_stillmode_data = stillmode_client_data; |
| stillmode_client_data->m4sensorhub = m4sensorhub; |
| platform_set_drvdata(pdev, stillmode_client_data); |
| stillmode_client_data->state = MOTION; |
| stillmode_client_data->timeout = STILLMODE_DEFAULT_TIMEOUT; |
| |
| stillmode_client_data->input_dev = input_allocate_device(); |
| if (!stillmode_client_data->input_dev) { |
| ret = -ENOMEM; |
| KDEBUG(M4SH_ERROR, "%s: input device allocate failed: %d\n", |
| __func__, ret); |
| goto free_memory; |
| } |
| |
| stillmode_client_data->input_dev->name = STILLMODE_CLIENT_DRIVER_NAME; |
| set_bit(EV_SW, stillmode_client_data->input_dev->evbit); |
| set_bit(SW_STILL_MODE, stillmode_client_data->input_dev->swbit); |
| |
| if (input_register_device(stillmode_client_data->input_dev)) { |
| KDEBUG(M4SH_ERROR, "%s: input device register failed\n", |
| __func__); |
| input_free_device(stillmode_client_data->input_dev); |
| goto free_memory; |
| } |
| |
| INIT_WORK(&stillmode_client_data->queued_work, |
| m4sensorhub_stillmode_work); |
| |
| ret = m4sensorhub_register_initcall(stillmode_driver_init, |
| stillmode_client_data); |
| if (ret < 0) { |
| KDEBUG(M4SH_ERROR, "Unable to register init function " |
| "for stillmode client = %d\n", ret); |
| goto destroy_wakelock; |
| } |
| |
| if (device_create_file(&pdev->dev, &dev_attr_state)) { |
| KDEBUG(M4SH_ERROR, "Error creating stillmode sys entry\n"); |
| ret = -1; |
| goto unregister_initcall; |
| } |
| |
| if (device_create_file(&pdev->dev, &dev_attr_timeout)) { |
| KDEBUG(M4SH_ERROR, "Error creating timeout sys entry\n"); |
| ret = -1; |
| goto remove_stillmode_sysfs; |
| } |
| |
| KDEBUG(M4SH_INFO, "Initialized %s driver\n", |
| STILLMODE_CLIENT_DRIVER_NAME); |
| |
| return 0; |
| |
| remove_stillmode_sysfs: |
| device_remove_file(&pdev->dev, &dev_attr_state); |
| unregister_initcall: |
| m4sensorhub_unregister_initcall(stillmode_driver_init); |
| destroy_wakelock: |
| input_unregister_device(stillmode_client_data->input_dev); |
| free_memory: |
| platform_set_drvdata(pdev, NULL); |
| stillmode_client_data->m4sensorhub = NULL; |
| kfree(stillmode_client_data); |
| g_stillmode_data = NULL; |
| |
| return ret; |
| } |
| |
| static int __exit stillmode_client_remove(struct platform_device *pdev) |
| { |
| struct stillmode_client *stillmode_client_data = |
| platform_get_drvdata(pdev); |
| struct m4sensorhub_data *m4sensorhub = |
| stillmode_client_data->m4sensorhub; |
| |
| device_remove_file(&pdev->dev, &dev_attr_timeout); |
| device_remove_file(&pdev->dev, &dev_attr_state); |
| m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); |
| m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); |
| m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_STILL_DETECTED); |
| m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_STILL_DETECTED); |
| m4sensorhub_unregister_initcall(stillmode_driver_init); |
| input_unregister_device(stillmode_client_data->input_dev); |
| platform_set_drvdata(pdev, NULL); |
| stillmode_client_data->m4sensorhub = NULL; |
| kfree(stillmode_client_data); |
| g_stillmode_data = NULL; |
| |
| return 0; |
| } |
| |
| static void stillmode_client_shutdown(struct platform_device *pdev) |
| { |
| return; |
| } |
| #ifdef CONFIG_PM |
| static int stillmode_client_suspend(struct platform_device *pdev, |
| pm_message_t message) |
| { |
| return 0; |
| } |
| |
| static int stillmode_client_resume(struct platform_device *pdev) |
| { |
| return 0; |
| } |
| #else |
| #define stillmode_client_suspend NULL |
| #define stillmode_client_resume NULL |
| #endif |
| |
| |
| static struct of_device_id m4stillmode_match_tbl[] = { |
| { .compatible = "mot,m4stillmode" }, |
| {}, |
| }; |
| |
| static struct platform_driver stillmode_client_driver = { |
| .probe = stillmode_client_probe, |
| .remove = __exit_p(stillmode_client_remove), |
| .shutdown = stillmode_client_shutdown, |
| .suspend = stillmode_client_suspend, |
| .resume = stillmode_client_resume, |
| .driver = { |
| .name = STILLMODE_CLIENT_DRIVER_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(m4stillmode_match_tbl), |
| }, |
| }; |
| |
| static int __init stillmode_client_init(void) |
| { |
| return platform_driver_register(&stillmode_client_driver); |
| } |
| |
| static void __exit stillmode_client_exit(void) |
| { |
| platform_driver_unregister(&stillmode_client_driver); |
| } |
| |
| module_init(stillmode_client_init); |
| module_exit(stillmode_client_exit); |
| |
| MODULE_ALIAS("platform:stillmode_client"); |
| MODULE_DESCRIPTION("M4 sensorhub still mode client driver"); |
| MODULE_AUTHOR("Motorola"); |
| MODULE_LICENSE("GPL"); |
| |