blob: bdd75c33677c559e44b48a08b97ee9d66c306dea [file] [log] [blame]
/*
* 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;
};
static 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 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_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_DLCMD_RESP_READY,
m4_handle_download_irq,
misc_download_data, 0);
if (ret < 0) {
KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n",
M4SH_IRQ_DLCMD_RESP_READY, ret);
return ret;
}
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 exit;
}
return ret;
exit:
m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_DLCMD_RESP_READY);
return ret;
}
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_register_initcall(download_driver_init,
download_client_data);
if (ret < 0) {
KDEBUG(M4SH_ERROR, "Unable to register init function "
"for download client = %d\n", ret);
goto unregister_misc_device;
}
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;
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);
m4sensorhub_irq_disable(download_client_data->m4sensorhub,
M4SH_IRQ_DLCMD_RESP_READY);
m4sensorhub_irq_unregister(download_client_data->m4sensorhub,
M4SH_IRQ_DLCMD_RESP_READY);
m4sensorhub_unregister_initcall(download_driver_init);
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");