summaryrefslogtreecommitdiff
path: root/drivers/misc/m4sensorhub_download.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/m4sensorhub_download.c')
-rw-r--r--drivers/misc/m4sensorhub_download.c501
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");
+