diff options
Diffstat (limited to 'drivers/misc/inv_mpu/mpu-dev.c')
-rw-r--r-- | drivers/misc/inv_mpu/mpu-dev.c | 1311 |
1 files changed, 1311 insertions, 0 deletions
diff --git a/drivers/misc/inv_mpu/mpu-dev.c b/drivers/misc/inv_mpu/mpu-dev.c new file mode 100644 index 0000000..26622b8 --- /dev/null +++ b/drivers/misc/inv_mpu/mpu-dev.c @@ -0,0 +1,1311 @@ +/* + $License: + Copyright (C) 2011 InvenSense Corporation, All Rights Reserved. + + 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, see <http://www.gnu.org/licenses/>. + $ + */ +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/signal.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/version.h> +#include <linux/pm.h> +#include <linux/mutex.h> +#include <linux/suspend.h> +#include <linux/poll.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include "mpuirq.h" +#include "slaveirq.h" +#include "mlsl.h" +#include "mldl_cfg.h" +#include <linux/mpu.h> + + +/* Platform data for the MPU */ +struct mpu_private_data { + struct miscdevice dev; + struct i2c_client *client; + struct mldl_cfg mldl_cfg; + + struct mutex mutex; + wait_queue_head_t mpu_event_wait; + struct completion completion; + struct timer_list timeout; + struct notifier_block nb; + struct mpuirq_data mpu_pm_event; + int response_timeout; /* In seconds */ + unsigned long event; + int pid; + struct module *slave_modules[EXT_SLAVE_NUM_TYPES]; +}; + +struct mpu_private_data *mpu_private_data; + +static void mpu_pm_timeout(u_long data) +{ + struct mpu_private_data *mpu = (struct mpu_private_data *)data; + struct i2c_client *client = mpu->client; + dev_dbg(&client->adapter->dev, "%s\n", __func__); + complete(&mpu->completion); +} + +static int mpu_pm_notifier_callback(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct mpu_private_data *mpu = + container_of(nb, struct mpu_private_data, nb); + struct i2c_client *client = mpu->client; + dev_dbg(&client->adapter->dev, "%s: %ld\n", __func__, event); + + /* Prevent the file handle from being closed before we initialize + the completion event */ + mutex_lock(&mpu->mutex); + if (!(mpu->pid) || + (event != PM_SUSPEND_PREPARE && event != PM_POST_SUSPEND)) { + mutex_unlock(&mpu->mutex); + return NOTIFY_OK; + } + + if (event == PM_SUSPEND_PREPARE) + mpu->event = MPU_PM_EVENT_SUSPEND_PREPARE; + if (event == PM_POST_SUSPEND) + mpu->event = MPU_PM_EVENT_POST_SUSPEND; + + mpu->mpu_pm_event.irqtime = ktime_to_ns(ktime_get()); + mpu->mpu_pm_event.interruptcount++; + mpu->mpu_pm_event.data_type = MPUIRQ_DATA_TYPE_PM_EVENT; + mpu->mpu_pm_event.data = mpu->event; + + if (mpu->response_timeout > 0) { + mpu->timeout.expires = jiffies + mpu->response_timeout * HZ; + add_timer(&mpu->timeout); + } + INIT_COMPLETION(mpu->completion); + mutex_unlock(&mpu->mutex); + + wake_up_interruptible(&mpu->mpu_event_wait); + wait_for_completion(&mpu->completion); + del_timer_sync(&mpu->timeout); + dev_dbg(&client->adapter->dev, "%s: %ld DONE\n", __func__, event); + return NOTIFY_OK; +} + +static int mpu_dev_open(struct inode *inode, struct file *file) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + int result; + int ii; + dev_dbg(&client->adapter->dev, "%s\n", __func__); + dev_dbg(&client->adapter->dev, "current->pid %d\n", current->pid); + + result = mutex_lock_interruptible(&mpu->mutex); + if (mpu->pid) { + mutex_unlock(&mpu->mutex); + return -EBUSY; + } + mpu->pid = current->pid; + + /* Reset the sensors to the default */ + if (result) { + dev_err(&client->adapter->dev, + "%s: mutex_lock_interruptible returned %d\n", + __func__, result); + return result; + } + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) + __module_get(mpu->slave_modules[ii]); + + mldl_cfg->requested_sensors = INV_THREE_AXIS_GYRO; + if (mldl_cfg->accel && mldl_cfg->accel->resume) + mldl_cfg->requested_sensors |= INV_THREE_AXIS_ACCEL; + + if (mldl_cfg->compass && mldl_cfg->compass->resume) + mldl_cfg->requested_sensors |= INV_THREE_AXIS_COMPASS; + + if (mldl_cfg->pressure && mldl_cfg->pressure->resume) + mldl_cfg->requested_sensors |= INV_THREE_AXIS_PRESSURE; + mutex_unlock(&mpu->mutex); + return 0; +} + +/* close function - called when the "file" /dev/mpu is closed in userspace */ +static int mpu_release(struct inode *inode, struct file *file) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + int result = 0; + int ii; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + mutex_lock(&mpu->mutex); + mldl_cfg->requested_sensors = 0; + result = inv_mpu_suspend(mldl_cfg, client->adapter, + accel_adapter, compass_adapter, + pressure_adapter, INV_ALL_SENSORS); + mpu->pid = 0; + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) + module_put(mpu->slave_modules[ii]); + + mutex_unlock(&mpu->mutex); + complete(&mpu->completion); + dev_dbg(&client->adapter->dev, "mpu_release\n"); + + return result; +} + +/* read function called when from /dev/mpu is read. Read from the FIFO */ +static ssize_t mpu_read(struct file *file, + char __user *buf, size_t count, loff_t *offset) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + size_t len = sizeof(mpu->mpu_pm_event) + sizeof(unsigned long); + int err; + + if (!mpu->event && (!(file->f_flags & O_NONBLOCK))) + wait_event_interruptible(mpu->mpu_event_wait, mpu->event); + + if (!mpu->event || !buf + || count < sizeof(mpu->mpu_pm_event)) + return 0; + + err = copy_to_user(buf, &mpu->mpu_pm_event, sizeof(mpu->mpu_pm_event)); + if (err) { + dev_err(&client->adapter->dev, + "Copy to user returned %d\n", err); + return -EFAULT; + } + mpu->event = 0; + return len; +} + +static unsigned int mpu_poll(struct file *file, struct poll_table_struct *poll) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + int mask = 0; + + poll_wait(file, &mpu->mpu_event_wait, poll); + if (mpu->event) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +static int +mpu_dev_ioctl_set_mpu_pdata(struct i2c_client *client, unsigned long arg) +{ + int ii; + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mpu_platform_data *pdata = mpu->mldl_cfg.pdata; + struct mpu_platform_data local_pdata; + + if (copy_from_user(&local_pdata, (unsigned char __user *)arg, + sizeof(local_pdata))) + return -EFAULT; + + pdata->int_config = local_pdata.int_config; + for (ii = 0; ii < ARRAY_SIZE(pdata->orientation); ii++) + pdata->orientation[ii] = local_pdata.orientation[ii]; + pdata->level_shifter = local_pdata.level_shifter; + + pdata->accel.address = local_pdata.accel.address; + for (ii = 0; ii < ARRAY_SIZE(pdata->accel.orientation); ii++) + pdata->accel.orientation[ii] = + local_pdata.accel.orientation[ii]; + + pdata->compass.address = local_pdata.compass.address; + for (ii = 0; ii < ARRAY_SIZE(pdata->compass.orientation); ii++) + pdata->compass.orientation[ii] = + local_pdata.compass.orientation[ii]; + + pdata->pressure.address = local_pdata.pressure.address; + for (ii = 0; ii < ARRAY_SIZE(pdata->pressure.orientation); ii++) + pdata->pressure.orientation[ii] = + local_pdata.pressure.orientation[ii]; + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + return 0; +} + +static int +mpu_dev_ioctl_set_mpu_config(struct i2c_client *client, unsigned long arg) +{ + int ii; + int result = 0; + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct mldl_cfg *temp_mldl_cfg; + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + temp_mldl_cfg = kmalloc(offsetof(struct mldl_cfg, silicon_revision), + GFP_KERNEL); + if (!temp_mldl_cfg) + return -ENOMEM; + + /* + * User space is not allowed to modify accel compass pressure or + * pdata structs, as well as silicon_revision product_id or trim + */ + if (copy_from_user(temp_mldl_cfg, (struct mldl_cfg __user *)arg, + offsetof(struct mldl_cfg, silicon_revision))) { + result = -EFAULT; + goto out; + } + + if (mldl_cfg->gyro_is_suspended) { + if (mldl_cfg->addr != temp_mldl_cfg->addr) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->int_config != temp_mldl_cfg->int_config) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->ext_sync != temp_mldl_cfg->ext_sync) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->full_scale != temp_mldl_cfg->full_scale) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->lpf != temp_mldl_cfg->lpf) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->clk_src != temp_mldl_cfg->clk_src) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->divider != temp_mldl_cfg->divider) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->dmp_enable != temp_mldl_cfg->dmp_enable) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->fifo_enable != temp_mldl_cfg->fifo_enable) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->dmp_cfg1 != temp_mldl_cfg->dmp_cfg1) + mldl_cfg->gyro_needs_reset = TRUE; + + if (mldl_cfg->dmp_cfg2 != temp_mldl_cfg->dmp_cfg2) + mldl_cfg->gyro_needs_reset = TRUE; + + for (ii = 0; ii < GYRO_NUM_AXES; ii++) + if (mldl_cfg->offset_tc[ii] != + temp_mldl_cfg->offset_tc[ii]) + mldl_cfg->gyro_needs_reset = TRUE; + + for (ii = 0; ii < GYRO_NUM_AXES; ii++) + if (mldl_cfg->offset[ii] != temp_mldl_cfg->offset[ii]) + mldl_cfg->gyro_needs_reset = TRUE; + + if (memcmp(mldl_cfg->ram, temp_mldl_cfg->ram, + MPU_MEM_NUM_RAM_BANKS * MPU_MEM_BANK_SIZE * + sizeof(unsigned char))) + mldl_cfg->gyro_needs_reset = TRUE; + } + + memcpy(mldl_cfg, temp_mldl_cfg, + offsetof(struct mldl_cfg, silicon_revision)); + + out: + kfree(temp_mldl_cfg); + return result; +} + +static int +mpu_dev_ioctl_get_mpu_config(struct i2c_client *client, + struct mldl_cfg __user *arg) +{ + /* Have to be careful as there are 3 pointers in the mldl_cfg + * structure */ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct mldl_cfg *local_mldl_cfg; + int retval = 0; + + local_mldl_cfg = kmalloc(sizeof(struct mldl_cfg), GFP_KERNEL); + if (!local_mldl_cfg) + return -ENOMEM; + + retval = + copy_from_user(local_mldl_cfg, arg, sizeof(*arg)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on arg\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + + /* Fill in the accel, compass, pressure and pdata pointers */ + if (mldl_cfg->accel) { + retval = copy_to_user((void __user *)local_mldl_cfg->accel, + mldl_cfg->accel, + sizeof(*mldl_cfg->accel)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on accel\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + } + + if (mldl_cfg->compass) { + retval = copy_to_user((void __user *)local_mldl_cfg->compass, + mldl_cfg->compass, + sizeof(*mldl_cfg->compass)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on compass\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + } + + if (mldl_cfg->pressure) { + retval = copy_to_user((void __user *)local_mldl_cfg->pressure, + mldl_cfg->pressure, + sizeof(*mldl_cfg->pressure)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on pressure\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + } + + if (mldl_cfg->pdata) { + retval = copy_to_user((void __user *)local_mldl_cfg->pdata, + mldl_cfg->pdata, + sizeof(*mldl_cfg->pdata)); + if (retval) { + dev_err(&client->adapter->dev, + "%s|%s:%d: EFAULT on pdata\n", + __FILE__, __func__, __LINE__); + retval = -EFAULT; + goto out; + } + } + + /* Do not modify the accel, compass, pressure and pdata pointers */ + retval = copy_to_user(arg, mldl_cfg, offsetof(struct mldl_cfg, accel)); + + if (retval) + retval = -EFAULT; + out: + kfree(local_mldl_cfg); + return retval; +} + +/** + * slave_config() - Pass a requested slave configuration to the slave sensor + * + * @adapter the adaptor to use to communicate with the slave + * @mldl_cfg the mldl configuration structuer + * @slave pointer to the slave descriptor + * @usr_config The configuration to pass to the slave sensor + * + * returns 0 or non-zero error code + */ +static int slave_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + void *slave_adapter, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + if ((!slave) || (!slave->config)) + return -ENODEV; + + retval = copy_from_user(&config, usr_config, sizeof(config)); + if (retval) + return -EFAULT; + + if (config.len && config.data) { + void *data; + data = kmalloc(config.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)config.data, config.len); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + config.data = data; + } + retval = inv_mpu_slave_config(mldl_cfg, gyro_adapter, slave_adapter, + &config, slave, pdata); + kfree(config.data); + return retval; +} + +/** + * slave_get_config() - Get requested slave configuration from the slave sensor + * + * @adapter the adaptor to use to communicate with the slave + * @mldl_cfg the mldl configuration structuer + * @slave pointer to the slave descriptor + * @usr_config The configuration for the slave to fill out + * + * returns 0 or non-zero error code + */ +static int slave_get_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + void *slave_adapter, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + void *user_data; + if (!(slave) || !(slave->get_config)) + return -ENODEV; + + retval = copy_from_user(&config, usr_config, sizeof(config)); + if (retval) + return -EFAULT; + + user_data = config.data; + if (config.len && config.data) { + void *data; + data = kmalloc(config.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)config.data, config.len); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + config.data = data; + } + retval = inv_mpu_get_slave_config(mldl_cfg, gyro_adapter, + slave_adapter, &config, slave, pdata); + if (retval) { + kfree(config.data); + return retval; + } + retval = copy_to_user((unsigned char __user *)user_data, + config.data, config.len); + kfree(config.data); + return retval; +} + +static int inv_slave_read(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + void *slave_adapter, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + void __user *usr_data) +{ + int retval; + unsigned char *data; + data = kzalloc(slave->read_len, GFP_KERNEL); + if (!data) + return -EFAULT; + + retval = inv_mpu_slave_read(mldl_cfg, gyro_adapter, slave_adapter, + slave, pdata, data); + + if ((!retval) && + (copy_to_user((unsigned char __user *)usr_data, + data, slave->read_len))) + retval = -EFAULT; + + kfree(data); + return retval; +} + +static int mpu_handle_mlsl(void *sl_handle, + unsigned char addr, + unsigned int cmd, + struct mpu_read_write __user *usr_msg) +{ + int retval = 0; + struct mpu_read_write msg; + unsigned char *user_data; + retval = copy_from_user(&msg, usr_msg, sizeof(msg)); + if (retval) + return -EFAULT; + + user_data = msg.data; + if (msg.length && msg.data) { + unsigned char *data; + data = kmalloc(msg.length, GFP_KERNEL); + if (!data) + return -ENOMEM; + + retval = copy_from_user(data, + (void __user *)msg.data, msg.length); + if (retval) { + retval = -EFAULT; + kfree(data); + return retval; + } + msg.data = data; + } else { + return -EPERM; + } + + switch (cmd) { + case MPU_READ: + retval = inv_serial_read(sl_handle, addr, + msg.address, msg.length, msg.data); + break; + case MPU_WRITE: + retval = inv_serial_write(sl_handle, addr, + msg.length, msg.data); + break; + case MPU_READ_MEM: + retval = inv_serial_read_mem(sl_handle, addr, + msg.address, msg.length, msg.data); + break; + case MPU_WRITE_MEM: + retval = inv_serial_write_mem(sl_handle, addr, + msg.address, msg.length, + msg.data); + break; + case MPU_READ_FIFO: + retval = inv_serial_read_fifo(sl_handle, addr, + msg.length, msg.data); + break; + case MPU_WRITE_FIFO: + retval = inv_serial_write_fifo(sl_handle, addr, + msg.length, msg.data); + break; + + }; + if (retval) { + dev_err(&((struct i2c_adapter *)sl_handle)->dev, + "%s: i2c %d error %d\n", + __func__, cmd, retval); + kfree(msg.data); + return retval; + } + retval = copy_to_user((unsigned char __user *)user_data, + msg.data, msg.length); + kfree(msg.data); + return retval; +} + +/* ioctl - I/O control */ +static long mpu_dev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct mpu_private_data *mpu = + container_of(file->private_data, struct mpu_private_data, dev); + struct i2c_client *client = mpu->client; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + int retval = 0; + struct i2c_adapter *gyro_adapter; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + + gyro_adapter = client->adapter; + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + retval = mutex_lock_interruptible(&mpu->mutex); + if (retval) { + dev_err(&client->adapter->dev, + "%s: mutex_lock_interruptible returned %d\n", + __func__, retval); + return retval; + } + + switch (cmd) { + case MPU_SET_MPU_CONFIG: + retval = mpu_dev_ioctl_set_mpu_config(client, arg); + break; + case MPU_SET_PLATFORM_DATA: + retval = mpu_dev_ioctl_set_mpu_pdata(client, arg); + break; + case MPU_GET_MPU_CONFIG: + retval = mpu_dev_ioctl_get_mpu_config(client, + (struct mldl_cfg __user *)arg); + break; + case MPU_READ: + case MPU_WRITE: + case MPU_READ_MEM: + case MPU_WRITE_MEM: + case MPU_READ_FIFO: + case MPU_WRITE_FIFO: + retval = mpu_handle_mlsl(gyro_adapter, mldl_cfg->addr, cmd, + (struct mpu_read_write __user *)arg); + break; + case MPU_CONFIG_ACCEL: + retval = slave_config(mldl_cfg, + gyro_adapter, + accel_adapter, + mldl_cfg->accel, + &mldl_cfg->pdata->accel, + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_COMPASS: + retval = slave_config(mldl_cfg, + gyro_adapter, + compass_adapter, + mldl_cfg->compass, + &mldl_cfg->pdata->compass, + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_PRESSURE: + retval = slave_config(mldl_cfg, + gyro_adapter, + pressure_adapter, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure, + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_ACCEL: + retval = slave_get_config(mldl_cfg, + gyro_adapter, + accel_adapter, + mldl_cfg->accel, + &mldl_cfg->pdata->accel, + (struct ext_slave_config __user *) + arg); + break; + case MPU_GET_CONFIG_COMPASS: + retval = slave_get_config(mldl_cfg, + gyro_adapter, + compass_adapter, + mldl_cfg->compass, + &mldl_cfg->pdata->compass, + (struct ext_slave_config __user *) + arg); + break; + case MPU_GET_CONFIG_PRESSURE: + retval = slave_get_config(mldl_cfg, + gyro_adapter, + pressure_adapter, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure, + (struct ext_slave_config __user *) + arg); + break; + case MPU_SUSPEND: + retval = inv_mpu_suspend(mldl_cfg, + gyro_adapter, + accel_adapter, + compass_adapter, + pressure_adapter, + (~(mldl_cfg->requested_sensors)) + & INV_ALL_SENSORS); + break; + case MPU_RESUME: + retval = inv_mpu_resume(mldl_cfg, + gyro_adapter, + accel_adapter, + compass_adapter, + pressure_adapter, + mldl_cfg->requested_sensors); + break; + case MPU_PM_EVENT_HANDLED: + dev_dbg(&client->adapter->dev, "%s: %d\n", __func__, cmd); + complete(&mpu->completion); + break; + case MPU_READ_ACCEL: + retval = inv_slave_read(mldl_cfg, + gyro_adapter, + accel_adapter, + mldl_cfg->accel, + &mldl_cfg->pdata->accel, + (unsigned char __user *)arg); + break; + case MPU_READ_COMPASS: + retval = inv_slave_read(mldl_cfg, + gyro_adapter, + compass_adapter, + mldl_cfg->compass, + &mldl_cfg->pdata->compass, + (unsigned char __user *)arg); + break; + case MPU_READ_PRESSURE: + retval = inv_slave_read(mldl_cfg, + gyro_adapter, + pressure_adapter, + mldl_cfg->pressure, + &mldl_cfg->pdata->pressure, + (unsigned char __user *)arg); + break; + default: + dev_err(&client->adapter->dev, + "%s: Unknown cmd %x, arg %lu: MIN %x MAX %x\n", + __func__, cmd, arg, + MPU_SET_MPU_CONFIG, MPU_SET_MPU_CONFIG); + retval = -EINVAL; + }; + + mutex_unlock(&mpu->mutex); + return retval; +} + +void mpu_shutdown(struct i2c_client *client) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + mutex_lock(&mpu->mutex); + (void)inv_mpu_suspend(mldl_cfg, client->adapter, + accel_adapter, compass_adapter, pressure_adapter, + INV_ALL_SENSORS); + mutex_unlock(&mpu->mutex); + dev_dbg(&client->adapter->dev, "%s\n", __func__); +} + +int mpu_dev_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + mutex_lock(&mpu->mutex); + if (!mldl_cfg->ignore_system_suspend) { + dev_dbg(&client->adapter->dev, + "%s: suspending on event %d\n", __func__, mesg.event); + (void)inv_mpu_suspend(mldl_cfg, client->adapter, + accel_adapter, compass_adapter, + pressure_adapter, INV_ALL_SENSORS); + } else { + dev_dbg(&client->adapter->dev, + "%s: Already suspended %d\n", __func__, mesg.event); + } + mutex_unlock(&mpu->mutex); + return 0; +} + +int mpu_dev_resume(struct i2c_client *client) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + mutex_lock(&mpu->mutex); + if (mpu->pid && !mldl_cfg->ignore_system_suspend) { + (void)inv_mpu_resume(mldl_cfg, client->adapter, + accel_adapter, + compass_adapter, + pressure_adapter, + mldl_cfg->requested_sensors); + dev_dbg(&client->adapter->dev, + "%s for pid %d\n", __func__, mpu->pid); + } + mutex_unlock(&mpu->mutex); + return 0; +} + +/* define which file operations are supported */ +static const struct file_operations mpu_fops = { + .owner = THIS_MODULE, + .read = mpu_read, + .poll = mpu_poll, + .unlocked_ioctl = mpu_dev_ioctl, + .open = mpu_dev_open, + .release = mpu_release, +}; + +int inv_mpu_register_slave(struct module *slave_module, + struct i2c_client *slave_client, + struct ext_slave_platform_data *slave_pdata, + struct ext_slave_descr *(*get_slave_descr)(void)) +{ + struct mpu_private_data *mpu = mpu_private_data; + struct mldl_cfg *mldl_cfg; + struct mpu_platform_data *pdata; + struct ext_slave_descr *slave_descr; + int result = 0; + + if (!slave_client || !slave_pdata || !get_slave_descr) + return -EINVAL; + + if (!mpu) { + dev_err(&slave_client->adapter->dev, + "%s: Null mpu_private_data\n", __func__); + return -EINVAL; + } + mldl_cfg = &mpu->mldl_cfg; + pdata = mldl_cfg->pdata; + + slave_descr = get_slave_descr(); + if (!slave_descr) { + dev_err(&slave_client->adapter->dev, + "%s: Null ext_slave_descr\n", __func__); + return -EINVAL; + } + + mutex_lock(&mpu->mutex); + if (mpu->pid) { + mutex_unlock(&mpu->mutex); + return -EBUSY; + } + + mpu->slave_modules[slave_descr->type] = slave_module; + + switch (slave_descr->type) { + case EXT_SLAVE_TYPE_ACCELEROMETER: + if (pdata->accel.get_slave_descr) { + result = -EBUSY; + break; + } + + pdata->accel.address = slave_client->addr; + pdata->accel.irq = slave_client->irq; + pdata->accel.adapt_num = i2c_adapter_id(slave_client->adapter); + + if (pdata->accel.irq > 0) { + dev_info(&slave_client->adapter->dev, + "Installing Accel irq using %d\n", + pdata->accel.irq); + result = slaveirq_init(slave_client->adapter, + &pdata->accel, "accelirq"); + if (result) + break; + } else { + dev_WARN(&slave_client->adapter->dev, + "Accel irq not assigned\n"); + } + + if (slave_descr->init) { + result = slave_descr->init(slave_client->adapter, + slave_descr, + &pdata->accel); + if (result) { + dev_err(&slave_client->adapter->dev, + "Accel init failed %d\n", result); + if (pdata->accel.irq > 0) + slaveirq_exit(&pdata->accel); + break; + } + } + + pdata->accel.get_slave_descr = get_slave_descr; + mldl_cfg->accel = slave_descr; + dev_info(&slave_client->adapter->dev, + "%s: +%s\n", MPU_NAME, mldl_cfg->accel->name); + break; + case EXT_SLAVE_TYPE_COMPASS: + if (pdata->compass.get_slave_descr) { + result = -EBUSY; + break; + } + + pdata->compass.address = slave_client->addr; + pdata->compass.irq = slave_client->irq; + pdata->compass.adapt_num = + i2c_adapter_id(slave_client->adapter); + if (pdata->compass.irq > 0) { + dev_info(&slave_client->adapter->dev, + "Installing Compass irq using %d\n", + pdata->compass.irq); + result = slaveirq_init(slave_client->adapter, + &pdata->compass, + "compassirq"); + if (result) + break; + } else { + dev_warn(&slave_client->adapter->dev, + "Compass irq not assigned\n"); + } + + if (slave_descr->init) { + result = slave_descr->init(slave_client->adapter, + slave_descr, + &pdata->compass); + if (result) { + dev_err(&slave_client->adapter->dev, + "Compass init failed %d\n", result); + if (pdata->compass.irq > 0) + slaveirq_exit(&pdata->compass); + break; + } + } + + pdata->compass.get_slave_descr = get_slave_descr; + mldl_cfg->compass = pdata->compass.get_slave_descr(); + dev_info(&slave_client->adapter->dev, + "%s: +%s\n", MPU_NAME, + mldl_cfg->compass->name); + break; + case EXT_SLAVE_TYPE_PRESSURE: + if (pdata->pressure.get_slave_descr) { + result = -EBUSY; + break; + } + + pdata->pressure.address = slave_client->addr; + pdata->pressure.irq = slave_client->irq; + pdata->pressure.adapt_num = + i2c_adapter_id(slave_client->adapter); + if (pdata->pressure.irq > 0) { + dev_info(&slave_client->adapter->dev, + "Installing Pressure irq using %d\n", + pdata->pressure.irq); + result = slaveirq_init(slave_client->adapter, + &pdata->pressure, + "pressureirq"); + if (result) + break; + } else { + dev_warn(&slave_client->adapter->dev, + "Pressure irq not assigned\n"); + } + + if (slave_descr->init) { + result = slave_descr->init(slave_client->adapter, + slave_descr, + &pdata->pressure); + if (result) { + dev_err(&slave_client->adapter->dev, + "Pressure init failed %d\n", result); + if (pdata->pressure.irq > 0) + slaveirq_exit(&pdata->pressure); + break; + } + } + + pdata->pressure.get_slave_descr = get_slave_descr; + mldl_cfg->pressure = pdata->pressure.get_slave_descr(); + dev_info(&slave_client->adapter->dev, + "%s: +%s\n", MPU_NAME, + mldl_cfg->pressure->name); + break; + default: + dev_err(&slave_client->adapter->dev, + "Invalid slave type %d\n", slave_descr->type); + result = -EINVAL; + break; + }; + + mutex_unlock(&mpu->mutex); + return result; +} +EXPORT_SYMBOL(inv_mpu_register_slave); + +void inv_mpu_unregister_slave(struct i2c_client *slave_client, + struct ext_slave_platform_data *slave_pdata, + struct ext_slave_descr *(*get_slave_descr)(void)) +{ + struct mpu_private_data *mpu = mpu_private_data; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct mpu_platform_data *pdata; + struct ext_slave_descr *slave_descr; + int result; + + dev_info(&slave_client->adapter->dev, "%s\n", __func__); + + if (!slave_client || !slave_pdata || !get_slave_descr) + return; + + slave_descr = get_slave_descr(); + if (!slave_descr) + return; + + pdata = mldl_cfg->pdata; + if (!pdata) + return; + + mutex_lock(&mpu->mutex); + + if (slave_descr->exit) { + result = slave_descr->exit(slave_client->adapter, + slave_descr, + slave_pdata); + if (INV_SUCCESS != result) + MPL_LOGE("Accel exit failed %d\n", result); + } + + if (slave_pdata->irq) + slaveirq_exit(slave_pdata); + + switch (slave_descr->type) { + case EXT_SLAVE_TYPE_ACCELEROMETER: + mldl_cfg->accel = NULL; + pdata->accel.get_slave_descr = NULL; + break; + case EXT_SLAVE_TYPE_COMPASS: + mldl_cfg->compass = NULL; + pdata->compass.get_slave_descr = NULL; + break; + case EXT_SLAVE_TYPE_PRESSURE: + mldl_cfg->pressure = NULL; + pdata->pressure.get_slave_descr = NULL; + break; + default: + break; + }; + mpu->slave_modules[slave_descr->type] = NULL; + mutex_unlock(&mpu->mutex); +} +EXPORT_SYMBOL(inv_mpu_unregister_slave); + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static const struct i2c_device_id mpu_id[] = { + {"mpu3050", 0}, + {"mpu6050", 0}, + {"mpu6050_no_accel", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mpu_id); + +int mpu_probe(struct i2c_client *client, const struct i2c_device_id *devid) +{ + struct mpu_platform_data *pdata; + struct mpu_private_data *mpu; + struct mldl_cfg *mldl_cfg; + int res = 0; + struct i2c_adapter *accel_adapter = NULL; + struct i2c_adapter *compass_adapter = NULL; + struct i2c_adapter *pressure_adapter = NULL; + int ii = 0; + + dev_info(&client->adapter->dev, "%s: %d\n", __func__, ii++); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + res = -ENODEV; + goto out_check_functionality_failed; + } + + mpu = kzalloc(sizeof(struct mpu_private_data), GFP_KERNEL); + if (!mpu) { + res = -ENOMEM; + goto out_alloc_data_failed; + } + mpu_private_data = mpu; + i2c_set_clientdata(client, mpu); + mpu->client = client; + mldl_cfg = &mpu->mldl_cfg; + + init_waitqueue_head(&mpu->mpu_event_wait); + + mutex_init(&mpu->mutex); + init_completion(&mpu->completion); + + mpu->response_timeout = 60; /* Seconds */ + mpu->timeout.function = mpu_pm_timeout; + mpu->timeout.data = (u_long) mpu; + init_timer(&mpu->timeout); + + mpu->nb.notifier_call = mpu_pm_notifier_callback; + mpu->nb.priority = 0; + register_pm_notifier(&mpu->nb); + + pdata = (struct mpu_platform_data *)client->dev.platform_data; + if (!pdata) { + dev_WARN(&client->adapter->dev, + "Missing platform data for mpu\n"); + } + mldl_cfg->pdata = pdata; + + mldl_cfg->addr = client->addr; + res = inv_mpu_open(&mpu->mldl_cfg, client->adapter, + accel_adapter, compass_adapter, pressure_adapter); + + if (res) { + dev_err(&client->adapter->dev, + "Unable to open %s %d\n", MPU_NAME, res); + res = -ENODEV; + goto out_whoami_failed; + } + + mpu->dev.minor = MISC_DYNAMIC_MINOR; + mpu->dev.name = "mpu"; /* Same for both 3050 and 6000 */ + mpu->dev.fops = &mpu_fops; + res = misc_register(&mpu->dev); + if (res < 0) { + dev_err(&client->adapter->dev, + "ERROR: misc_register returned %d\n", res); + goto out_misc_register_failed; + } + + if (client->irq) { + dev_info(&client->adapter->dev, + "Installing irq using %d\n", client->irq); + res = mpuirq_init(client, mldl_cfg); + if (res) + goto out_mpuirq_failed; + } else { + dev_WARN(&client->adapter->dev, + "Missing %s IRQ\n", MPU_NAME); + } + + return res; + + out_mpuirq_failed: + misc_deregister(&mpu->dev); + out_misc_register_failed: + inv_mpu_close(&mpu->mldl_cfg, client->adapter, + accel_adapter, compass_adapter, pressure_adapter); + out_whoami_failed: + unregister_pm_notifier(&mpu->nb); + kfree(mpu); + mpu_private_data = NULL; + out_alloc_data_failed: + out_check_functionality_failed: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, res); + return res; + +} + +static int mpu_remove(struct i2c_client *client) +{ + struct mpu_private_data *mpu = i2c_get_clientdata(client); + struct i2c_adapter *accel_adapter; + struct i2c_adapter *compass_adapter; + struct i2c_adapter *pressure_adapter; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct mpu_platform_data *pdata = mldl_cfg->pdata; + + accel_adapter = i2c_get_adapter(mldl_cfg->pdata->accel.adapt_num); + compass_adapter = i2c_get_adapter(mldl_cfg->pdata->compass.adapt_num); + pressure_adapter = i2c_get_adapter(mldl_cfg->pdata->pressure.adapt_num); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_close(mldl_cfg, client->adapter, + accel_adapter, compass_adapter, pressure_adapter); + + if (client->irq) + mpuirq_exit(); + + if (pdata && pdata->pressure.get_slave_descr && pdata->pressure.irq) { + slaveirq_exit(&pdata->pressure); + pdata->pressure.get_slave_descr = NULL; + } + + if (pdata && pdata->compass.get_slave_descr && pdata->compass.irq) { + slaveirq_exit(&pdata->compass); + pdata->compass.get_slave_descr = NULL; + } + + if (pdata && pdata->accel.get_slave_descr && pdata->accel.irq) { + slaveirq_exit(&pdata->accel); + pdata->accel.get_slave_descr = NULL; + } + + unregister_pm_notifier(&mpu->nb); + misc_deregister(&mpu->dev); + kfree(mpu); + + return 0; +} + +static struct i2c_driver mpu_driver = { + .class = I2C_CLASS_HWMON, + .probe = mpu_probe, + .remove = mpu_remove, + .id_table = mpu_id, + .driver = { + .owner = THIS_MODULE, + .name = MPU_NAME, + }, + .address_list = normal_i2c, + .shutdown = mpu_shutdown, /* optional */ + .suspend = mpu_dev_suspend, /* optional */ + .resume = mpu_dev_resume, /* optional */ + +}; + +static int __init mpu_init(void) +{ + int res = i2c_add_driver(&mpu_driver); + pr_info("%s: Probe name %s\n", __func__, MPU_NAME); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit mpu_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&mpu_driver); +} + +module_init(mpu_init); +module_exit(mpu_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("User space character device interface for MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS(MPU_NAME); |