diff options
Diffstat (limited to 'drivers/misc/m4sensorhub_download.c')
| -rw-r--r-- | drivers/misc/m4sensorhub_download.c | 501 | 
1 files changed, 501 insertions, 0 deletions
| diff --git a/drivers/misc/m4sensorhub_download.c b/drivers/misc/m4sensorhub_download.c new file mode 100644 index 00000000000..7ad1aae47b5 --- /dev/null +++ b/drivers/misc/m4sensorhub_download.c @@ -0,0 +1,501 @@ +/* + *  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/types.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/proc_fs.h> +#include <linux/input.h> +#include <linux/uaccess.h> +#include <linux/m4sensorhub.h> +#include <linux/m4sensorhub_client_ioctl.h> +#include <linux/m4sensorhub/MemMapDownload.h> +#include <linux/wait.h> +#include <linux/slab.h> + +#define DOWNLOAD_CLIENT_DRIVER_NAME	"m4sensorhub_download" +#define M4_SENSOR_DL_MAX_RETRY_CNT	3 +#define M4_SENSOR_DL_MAX_RET_SIZE	8 +#define M4_SENSOR_DL_MIN_INPUT_SIZE	\ +	(sizeof(struct m4sh_download_packet) - M4_SENSOR_DL_MAX_PACKET_SIZE) + +enum { +	i2c_reg_end, +	i2c_reg_read, +	i2c_reg_write, +	i2c_reg_wait, +}; + +struct i2c_reg_sequence { +	int direct; +	int reg; +}; + +struct download_client { +	struct m4sensorhub_data *m4sensorhub; +}; + +struct download_client *misc_download_data; +static wait_queue_head_t download_wq; +static atomic_t m4_dlcmd_resp_ready; +static atomic_t download_client_entry; + +static struct i2c_reg_sequence seq_m4dlm_get_checksum[] = { +	{i2c_reg_write, M4SH_REG_DOWNLOAD_FILENAME}, +	{i2c_reg_write, M4SH_REG_DOWNLOAD_COMMAND}, +	{i2c_reg_read, M4SH_REG_DOWNLOAD_CHECKSUM}, +	{i2c_reg_read, M4SH_REG_DOWNLOAD_STATUS}, +	{i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_open_file[] = { +	{i2c_reg_write, M4SH_REG_DOWNLOAD_FILENAME}, +	{i2c_reg_write, M4SH_REG_DOWNLOAD_COMMAND}, +	{i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, +	{i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_close_file[] = { +	{i2c_reg_write, M4SH_REG_DOWNLOAD_COMMAND}, +	{i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, +	{i2c_reg_read, M4SH_REG_DOWNLOAD_CHECKSUM}, +	{i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_delete_file[] = { +	{i2c_reg_write, M4SH_REG_DOWNLOAD_FILENAME}, +	{i2c_reg_write, M4SH_REG_DOWNLOAD_COMMAND}, +	{i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, +	{i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_write_file[] = { +	{i2c_reg_write, M4SH_REG_DOWNLOAD_PACKET}, +	{i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, +	{i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_write_size_file[] = { +	{i2c_reg_write, M4SH_REG_DOWNLOAD_SIZE}, +	{i2c_reg_write, M4SH_REG_DOWNLOAD_PACKET}, +	{i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, +	{i2c_reg_end, i2c_reg_end} +}; + +static int download_client_open(struct inode *inode, struct file *file) +{ +	int err = atomic_inc_return(&download_client_entry); +	if (err == 1) { +		err = nonseekable_open(inode, file); +		if (err >= 0) { +			file->private_data = misc_download_data; +			return 0; +		} +	} else +		err = -EBUSY; + +	atomic_dec_return(&download_client_entry); +	KDEBUG(M4SH_ERROR, "%s: failed, err=%d\n", __func__, -err); +	return err; +} + +static int download_client_close(struct inode *inode, struct file *file) +{ +	int entry = atomic_dec_return(&download_client_entry); +	file->private_data = NULL; +	KDEBUG(M4SH_DEBUG, "%s: entry = %d\n", __func__, entry); +	return 0; +} + +static void m4_handle_download_irq(enum m4sensorhub_irqs int_event, +					void *download_data) +{ +	atomic_set(&m4_dlcmd_resp_ready, true); +	wake_up_interruptible(&download_wq); +} + +static inline void wait_m4_cmd_executed(void) +{ +	wait_event_interruptible(download_wq, \ +			(atomic_read(&m4_dlcmd_resp_ready))); +	atomic_set(&m4_dlcmd_resp_ready, false); +} + +static char *m4dlm_i2c_reg_seq_getptr( +	int reg, struct m4sh_download_packet *dl_packet) +{ +	switch (reg) { +	case M4SH_REG_DOWNLOAD_COMMAND: +		return (char *)(&(dl_packet->command)); +	case M4SH_REG_DOWNLOAD_STATUS: +		return (char *)(&(dl_packet->status)); +	case M4SH_REG_DOWNLOAD_SIZE: +		return (char *)(&(dl_packet->size)); +	case M4SH_REG_DOWNLOAD_CHECKSUM: +		return (char *)(&(dl_packet->checksum)); +	case M4SH_REG_DOWNLOAD_FILENAME: +		return (char *)(dl_packet->filename); +	case M4SH_REG_DOWNLOAD_PACKET: +		return (char *)(dl_packet->buffer); +	} +	KDEBUG(M4SH_ERROR, "%s Invaild i2c reg %d\n", __func__, reg); +	return NULL; +} + +static int m4dlm_i2c_reg_seq_process( +	struct m4sensorhub_data *m4sensorhub, +	struct m4sh_download_packet *dl_packet, +	struct i2c_reg_sequence *i2c_reg_seq) +{ +	int ret; +	for (; i2c_reg_seq->direct != i2c_reg_end; i2c_reg_seq++) { +		/*we don't need retry for I2C read/write as +		 *m4sensorhub_reg_write/read already had retry mechanism +		 */ +		switch (i2c_reg_seq->direct) { +		case i2c_reg_write: +			ret = m4sensorhub_reg_write(m4sensorhub, +				i2c_reg_seq->reg, +				m4dlm_i2c_reg_seq_getptr(\ +					i2c_reg_seq->reg, dl_packet), +				m4sh_no_mask); +			break; +		case i2c_reg_wait: +			/*Wait for IRQ answered*/ +			wait_m4_cmd_executed(); +			/*fallback to read status*/ +		case i2c_reg_read: +			ret = m4sensorhub_reg_read(m4sensorhub, +				i2c_reg_seq->reg, +				m4dlm_i2c_reg_seq_getptr(\ +					i2c_reg_seq->reg, dl_packet) +				); +			break; +		default: +			/*should be fault*/ +			KDEBUG(M4SH_ERROR, "%s: Invaild I2C direct %d\n", \ +						__func__, i2c_reg_seq->direct); +			return -ENOEXEC; +		} +		if (ret != m4sensorhub_reg_getsize(\ +				m4sensorhub, i2c_reg_seq->reg)) { +			KDEBUG(M4SH_ERROR, "%s: Process I2C [%d-%d] failed!\n", +					__func__, i2c_reg_seq->direct, +					 i2c_reg_seq->reg); +			return -EIO; +		} +	} +	return 0; +} + +static long download_client_ioctl( +	struct file *filp, +	unsigned int cmd, unsigned long arg) +{ +	void __user *argp = (void __user *)arg; + +	struct download_client *download_data = filp->private_data; +	struct i2c_reg_sequence *i2c_req_seq = NULL; +	int ret = -EINVAL, retry; +	static struct m4sh_download_packet packet; +	static unsigned short packet_size; + +	switch (cmd) { +	case M4_SENSOR_IOCTL_DL_SEND_PACKET: +		if (copy_from_user(&packet, argp, M4_SENSOR_DL_MIN_INPUT_SIZE)) +			return -EFAULT; + +		KDEBUG(M4SH_INFO, "%s cmd = %d\n", __func__, packet.command); + +		switch (packet.command) { +		case M4_SENSOR_DL_CMD_GET_CHECKSUM: +			i2c_req_seq = seq_m4dlm_get_checksum; +			break; +		case M4_SENSOR_DL_CMD_OPEN_FILE: +			packet_size = 0; +			i2c_req_seq = seq_m4dlm_open_file; +			break; +		case M4_SENSOR_DL_CMD_DELETE_FILE: +			i2c_req_seq = seq_m4dlm_delete_file; +			break; +		case M4_SENSOR_DL_CMD_CLOSE_FILE: +			i2c_req_seq = seq_m4dlm_close_file; +			break; +		case M4_SENSOR_DL_CMD_WRITE_FILE: +			if (!packet.size || \ +				(packet.size > M4_SENSOR_DL_MAX_PACKET_SIZE)) { +				packet.status = M4_SENSOR_DL_ERROR_INVALID_SIZE; +				/*we only copy packet data before filename*/ +				if (copy_to_user(argp, &packet, \ +					M4_SENSOR_DL_MAX_RET_SIZE)) +					return -EFAULT; +				return 0; +			} +			if (copy_from_user(&packet, argp, sizeof(packet))) +				return -EFAULT; +			if (packet.size != packet_size) { +				i2c_req_seq = seq_m4dlm_write_size_file; +				packet_size = packet.size; +			} else +				i2c_req_seq = seq_m4dlm_write_file; +			break; +		default: +			/*should be wrong command received*/ +			KDEBUG(M4SH_ERROR, "%s Invaild packet cmd %d\n", \ +						__func__, packet.command); +			return  -EINVAL; +		} +		for (retry = 0; retry++ < M4_SENSOR_DL_MAX_RETRY_CNT; ) { +			ret = m4dlm_i2c_reg_seq_process(\ +					download_data->m4sensorhub, +					&packet, i2c_req_seq); +			/*only retry if M4 has internal error*/ +			if (!ret) { +				switch (packet.status) { +				case M4_SENSOR_DL_ERROR_SEND_CMD: +				case M4_SENSOR_DL_ERROR_DATA_CHECKSUM: +				/*something wrong and we need retry*/ +				KDEBUG(M4SH_ERROR, \ +					"Tried %d times for packet cmd %d\n", \ +					retry, packet.command); +					continue; +				} +			} +			break; /*exit retry loop*/ +		} +		if (!ret) { +			/*we only copy packet data before filename for return*/ +			if (copy_to_user(argp, &packet, \ +					M4_SENSOR_DL_MAX_RET_SIZE)) +				return -EFAULT; +		} +		break; +	default: +		KDEBUG(M4SH_ERROR, "%s Invaild ioctl cmd %d\n", __func__, cmd); +		break; +	} +	return ret; +} + +static ssize_t download_get_loglevel(struct device *dev, +	struct device_attribute *attr, char *buf) +{ +	int loglevel; +	struct platform_device *pdev = to_platform_device(dev); +	struct download_client *download_client_data = +		platform_get_drvdata(pdev); + +	m4sensorhub_reg_read(download_client_data->m4sensorhub, +		M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); +	loglevel = get_log_level(loglevel, DOWNLOAD_MASK_BIT_1); +	return sprintf(buf, "%d\n", loglevel); +} + +static ssize_t download_set_loglevel(struct device *dev, +	struct device_attribute *attr, +	const char *buf, size_t size) +{ +	unsigned long level; +	unsigned int mask = 0; +	struct platform_device *pdev = to_platform_device(dev); +	struct download_client *download_client_data = +		platform_get_drvdata(pdev); + +	if ((strict_strtoul(buf, 10, &level)) < 0) +		return -EINVAL; +	if (level > M4_MAX_LOG_LEVEL) { +		KDEBUG(M4SH_ERROR, " Invalid log level - %d\n", (int)level); +		return -EINVAL; +	} +	mask = (1 << DOWNLOAD_MASK_BIT_1) | (1 << DOWNLOAD_MASK_BIT_2); +	level = (level << DOWNLOAD_MASK_BIT_1); +	return m4sensorhub_reg_write(download_client_data->m4sensorhub, +		M4SH_REG_LOG_LOGENABLE, (char *)&level, (unsigned char *)&mask); +} + +static DEVICE_ATTR(LogLevel, 0664, \ +		download_get_loglevel, download_set_loglevel); + +static const struct file_operations download_client_fops = { +	.owner = THIS_MODULE, +	.unlocked_ioctl = download_client_ioctl, +	.open  = download_client_open, +	.release = download_client_close, +}; + +static struct miscdevice download_client_miscdrv = { +	.minor = MISC_DYNAMIC_MINOR, +	.name  = DOWNLOAD_CLIENT_DRIVER_NAME, +	.fops = &download_client_fops, +}; + +static int download_client_probe(struct platform_device *pdev) +{ +	int ret = -1; +	struct download_client *download_client_data; +	struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + +	if (!m4sensorhub) { +		printk(KERN_WARNING "m4sensorhub is null\n"); +		return -EFAULT; +	} + +	download_client_data = +		kzalloc(sizeof(*download_client_data), GFP_KERNEL); +	if (!download_client_data) +		return -ENOMEM; + +	download_client_data->m4sensorhub = m4sensorhub; +	platform_set_drvdata(pdev, download_client_data); + +	ret = misc_register(&download_client_miscdrv); +	if (ret < 0) { +		KDEBUG(M4SH_ERROR, "Error registering %s driver\n", +				 DOWNLOAD_CLIENT_DRIVER_NAME); +		goto free_memory; +	} +	misc_download_data = download_client_data; +	ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_DLCMD_RESP_READY, +					m4_handle_download_irq, +					download_client_data); +	if (ret < 0) { +		KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", +				M4SH_IRQ_DLCMD_RESP_READY, ret); +		goto unregister_misc_device; +	} + +	ret = m4sensorhub_irq_enable(m4sensorhub, M4SH_IRQ_DLCMD_RESP_READY); +	if (ret < 0) { +		KDEBUG(M4SH_ERROR, "Error enable irq %d (%d)\n", +				M4SH_IRQ_DLCMD_RESP_READY, ret); +		goto unregister_irq; +	} + +	if (device_create_file(&pdev->dev, &dev_attr_LogLevel)) { +		KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", +				DOWNLOAD_CLIENT_DRIVER_NAME); +		ret = -1; +		goto disable_irq; +	} + +	init_waitqueue_head(&download_wq); +	atomic_set(&m4_dlcmd_resp_ready, false); +	atomic_set(&download_client_entry, 0); + +	KDEBUG(M4SH_INFO, "Initialized %s driver\n", +		DOWNLOAD_CLIENT_DRIVER_NAME); +	return 0; + +disable_irq: +	m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_DLCMD_RESP_READY); +unregister_irq: +	m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_DLCMD_RESP_READY); +unregister_misc_device: +	misc_download_data = NULL; +	misc_deregister(&download_client_miscdrv); +free_memory: +	platform_set_drvdata(pdev, NULL); +	download_client_data->m4sensorhub = NULL; +	kfree(download_client_data); +	download_client_data = NULL; +	return ret; +} + +static int __exit download_client_remove(struct platform_device *pdev) +{ +	struct download_client *download_client_data = +		platform_get_drvdata(pdev); + +	device_remove_file(&pdev->dev, &dev_attr_LogLevel); +	m4sensorhub_irq_disable(download_client_data->m4sensorhub, +				M4SH_IRQ_DLCMD_RESP_READY); +	m4sensorhub_irq_unregister(download_client_data->m4sensorhub, +				M4SH_IRQ_DLCMD_RESP_READY); +	misc_download_data = NULL; +	misc_deregister(&download_client_miscdrv); +	platform_set_drvdata(pdev, NULL); +	download_client_data->m4sensorhub = NULL; +	kfree(download_client_data); +	download_client_data = NULL; +	return 0; +} + +static void download_client_shutdown(struct platform_device *pdev) +{ +} + +#ifdef CONFIG_PM + +static int download_client_suspend(struct platform_device *pdev, +				pm_message_t message) +{ +	return 0; +} + +static int download_client_resume(struct platform_device *pdev) +{ +	return 0; +} + +#else +#define download_client_suspend NULL +#define download_client_resume  NULL +#endif + +static struct of_device_id m4download_match_tbl[] = { +	{ .compatible = "mot,m4download" }, +	{}, +}; + +static struct platform_driver download_client_driver = { +	.probe		= download_client_probe, +	.remove		= __exit_p(download_client_remove), +	.shutdown	= download_client_shutdown, +	.suspend	= download_client_suspend, +	.resume		= download_client_resume, +		.driver		= { +		.name	= DOWNLOAD_CLIENT_DRIVER_NAME, +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(m4download_match_tbl), +	}, +}; + +static int __init download_client_init(void) +{ +	return platform_driver_register(&download_client_driver); +} + +static void __exit download_client_exit(void) +{ +	platform_driver_unregister(&download_client_driver); +} + +module_init(download_client_init); +module_exit(download_client_exit); + +MODULE_ALIAS("platform:download_client"); +MODULE_DESCRIPTION("M4 Sensor Hub driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); + |