diff options
Diffstat (limited to 'drivers/misc/m4sensorhub_stillmode.c')
| -rw-r--r-- | drivers/misc/m4sensorhub_stillmode.c | 425 | 
1 files changed, 425 insertions, 0 deletions
| diff --git a/drivers/misc/m4sensorhub_stillmode.c b/drivers/misc/m4sensorhub_stillmode.c new file mode 100644 index 00000000000..3fa61219245 --- /dev/null +++ b/drivers/misc/m4sensorhub_stillmode.c @@ -0,0 +1,425 @@ +/* + *  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 wake_lock wakelock; +	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); + +		/* Hold a 500ms wakelock to let data get to KineticManager */ +		wake_lock_timeout(&stillmode_client_data->wakelock, 0.5 * HZ); + +		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_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; +	} + +	wake_lock_init(&stillmode_client_data->wakelock, WAKE_LOCK_SUSPEND, +		       STILLMODE_CLIENT_DRIVER_NAME); + +	INIT_WORK(&stillmode_client_data->queued_work, +		  m4sensorhub_stillmode_work); + +	ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_STILL_DETECTED, +						m4_handle_stillmode_irq, +						stillmode_client_data); +	if (ret < 0) { +		KDEBUG(M4SH_ERROR, "Error registering still mode IRQ: " +			"%d\n", ret); +		goto destroy_wakelock; +	} +	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, +						stillmode_client_data); +	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; +	} + +	if (device_create_file(&pdev->dev, &dev_attr_state)) { +		KDEBUG(M4SH_ERROR, "Error creating stillmode sys entry\n"); +		ret = -1; +		goto disable_moving_irq; +	} + +	if (device_create_file(&pdev->dev, &dev_attr_timeout)) { +		KDEBUG(M4SH_ERROR, "Error creating timeout sys entry\n"); +		ret = -1; +		goto remove_stillmode_sysfs; +	} + +	/* initialize timer on M4 */ +	m4sensorhub_stillmode_exit(); + +	KDEBUG(M4SH_INFO, "Initialized %s driver\n", +		STILLMODE_CLIENT_DRIVER_NAME); + +	return 0; + +remove_stillmode_sysfs: +	device_remove_file(&pdev->dev, &dev_attr_state); +disable_moving_irq: +	m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); +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); +destroy_wakelock: +	wake_lock_destroy(&stillmode_client_data->wakelock); +	input_unregister_device(stillmode_client_data->input_dev); +free_memory: +	platform_set_drvdata(pdev, NULL); +	m4sensorhub->pdev->stillmode_exit = 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); +	wake_lock_destroy(&stillmode_client_data->wakelock); +	input_unregister_device(stillmode_client_data->input_dev); +	platform_set_drvdata(pdev, NULL); +	m4sensorhub->pdev->stillmode_exit = 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"); + |