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"); + |