From patchwork Fri Jul 1 02:18:19 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nathan Royer X-Patchwork-Id: 934262 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.4) with ESMTP id p612LDXQ013662 for ; Fri, 1 Jul 2011 02:21:14 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754539Ab1GACTX (ORCPT ); Thu, 30 Jun 2011 22:19:23 -0400 Received: from na3sys009aog115.obsmtp.com ([74.125.149.238]:54643 "HELO na3sys009aog115.obsmtp.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with SMTP id S1754485Ab1GACTS (ORCPT ); Thu, 30 Jun 2011 22:19:18 -0400 Received: from mail-iy0-f180.google.com ([209.85.210.180]) (using TLSv1) by na3sys009aob115.postini.com ([74.125.148.12]) with SMTP ID DSNKTg0upSbUEu5luJpgSPMsNHnxQe0tK/Zb@postini.com; Thu, 30 Jun 2011 19:19:18 PDT Received: by mail-iy0-f180.google.com with SMTP id 42so4712750iyh.25 for ; Thu, 30 Jun 2011 19:19:17 -0700 (PDT) Received: by 10.43.50.73 with SMTP id vd9mr2774526icb.472.1309486757287; Thu, 30 Jun 2011 19:19:17 -0700 (PDT) Received: from localhost.localdomain ([209.119.66.34]) by mx.google.com with ESMTPS id j2sm2780359icu.22.2011.06.30.19.19.13 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 30 Jun 2011 19:19:16 -0700 (PDT) From: Nathan Royer To: Nathan Royer Cc: Andrew Morton , Greg Kroah-Hartman , Jonathan Cameron , Jiri Kosina , Alan Cox , Jean Delvare , linux-kernel@vger.kernel.org, linux-input@vger.kernel.org Subject: [PATCH 03/11] misc: mpu3050 /dev/mpu implementation. Date: Thu, 30 Jun 2011 19:18:19 -0700 Message-Id: <1309486707-1658-3-git-send-email-nroyer@invensense.com> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1309486707-1658-1-git-send-email-nroyer@invensense.com> References: <1309486707-1658-1-git-send-email-nroyer@invensense.com> Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Fri, 01 Jul 2011 02:21:14 +0000 (UTC) Implements i2c probing, receives registration of slaves, handles file operations, and power management. Signed-off-by: Nathan Royer --- drivers/misc/inv_mpu/mpu-dev.c | 1247 ++++++++++++++++++++++++++++++++++++++++ drivers/misc/inv_mpu/mpu-dev.h | 36 ++ 2 files changed, 1283 insertions(+), 0 deletions(-) create mode 100644 drivers/misc/inv_mpu/mpu-dev.c create mode 100644 drivers/misc/inv_mpu/mpu-dev.h diff --git a/drivers/misc/inv_mpu/mpu-dev.c b/drivers/misc/inv_mpu/mpu-dev.c new file mode 100644 index 0000000..977631d --- /dev/null +++ b/drivers/misc/inv_mpu/mpu-dev.c @@ -0,0 +1,1247 @@ +/* + $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 . + $ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "mpuirq.h" +#include "slaveirq.h" +#include "mlsl.h" +#include "mldl_cfg.h" +#include + + +/* Platform data for the MPU */ +struct mpu_private_data { + struct miscdevice dev; + struct i2c_client *client; + + /* mldl_cfg data */ + struct mldl_cfg mldl_cfg; + struct mpu_ram mpu_ram; + struct mpu_gyro_cfg mpu_gyro_cfg; + struct mpu_offsets mpu_offsets; + struct mpu_chip_info mpu_chip_info; + struct inv_mpu_cfg inv_mpu_cfg; + struct inv_mpu_state inv_mpu_state; + + 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; + struct timeval event_time; + 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; + } + + do_gettimeofday(&event_time); + mpu->mpu_pm_event.interruptcount++; + mpu->mpu_pm_event.irqtime = + (((long long)event_time.tv_sec) << 32) + event_time.tv_usec; + mpu->mpu_pm_event.data_type = MPUIRQ_DATA_TYPE_PM_EVENT; + mpu->mpu_pm_event.data = mpu->event; + + if (event == PM_SUSPEND_PREPARE) + mpu->event = MPU_PM_EVENT_SUSPEND_PREPARE; + if (event == PM_POST_SUSPEND) + mpu->event = MPU_PM_EVENT_POST_SUSPEND; + + 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; + 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]); + + 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; + int result = 0; + int ii; + struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + mutex_lock(&mpu->mutex); + mldl_cfg->inv_mpu_cfg->requested_sensors = 0; + result = inv_mpu_suspend(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + 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) + sizeof(unsigned long)) + 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_get_ext_slave_platform_data( + struct i2c_client *client, + struct ext_slave_platform_data __user *arg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct ext_slave_platform_data *pdata_slave; + struct ext_slave_platform_data local_pdata_slave; + + if (copy_from_user(&local_pdata_slave, arg, sizeof(local_pdata_slave))) + return -EFAULT; + + if (local_pdata_slave.type >= EXT_SLAVE_NUM_TYPES) + return -EINVAL; + + pdata_slave = mpu->mldl_cfg.pdata_slave[local_pdata_slave.type]; + /* All but private data and irq_data */ + if (!pdata_slave) + return -ENODEV; + if (copy_to_user(arg, pdata_slave, sizeof(*pdata_slave))) + return -EFAULT; + return 0; +} + +static int mpu_dev_ioctl_get_mpu_platform_data( + struct i2c_client *client, + struct mpu_platform_data __user *arg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct mpu_platform_data *pdata = mpu->mldl_cfg.pdata; + + if (copy_to_user(arg, pdata, sizeof(pdata))) + return -EFAULT; + return 0; +} + +static int mpu_dev_ioctl_get_ext_slave_descr( + struct i2c_client *client, + struct ext_slave_descr __user *arg) +{ + struct mpu_private_data *mpu = + (struct mpu_private_data *)i2c_get_clientdata(client); + struct ext_slave_descr *slave; + struct ext_slave_descr local_slave; + + if (copy_from_user(&local_slave, arg, sizeof(local_slave))) + return -EFAULT; + + if (local_slave.type >= EXT_SLAVE_NUM_TYPES) + return -EINVAL; + + slave = mpu->mldl_cfg.slave[local_slave.type]; + /* All but private data and irq_data */ + if (!slave) + return -ENODEV; + if (copy_to_user(arg, slave, sizeof(*slave))) + return -EFAULT; + return 0; +} + + +/** + * 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 inv_mpu_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + + 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 = gyro_config(gyro_adapter, mldl_cfg, &config); + kfree(config.data); + return retval; +} + +static int inv_mpu_get_config(struct mldl_cfg *mldl_cfg, + void *gyro_adapter, + struct ext_slave_config __user *usr_config) +{ + int retval = 0; + struct ext_slave_config config; + void *user_data; + + 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 = gyro_get_config(gyro_adapter, mldl_cfg, &config); + if (!retval) + retval = copy_to_user((unsigned char __user *)user_data, + config.data, config.len); + kfree(config.data); + return retval; +} + +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; +} + +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 *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_descr **slave = mldl_cfg->slave; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + 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_GET_EXT_SLAVE_PLATFORM_DATA: + retval = mpu_dev_ioctl_get_ext_slave_platform_data( + client, + (struct ext_slave_platform_data __user *)arg); + break; + case MPU_GET_MPU_PLATFORM_DATA: + retval = mpu_dev_ioctl_get_mpu_platform_data( + client, + (struct mpu_platform_data __user *)arg); + break; + case MPU_GET_EXT_SLAVE_DESCR: + retval = mpu_dev_ioctl_get_ext_slave_descr( + client, + (struct ext_slave_descr __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( + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + mldl_cfg->mpu_chip_info->addr, cmd, + (struct mpu_read_write __user *)arg); + break; + case MPU_CONFIG_GYRO: + retval = inv_mpu_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_ACCEL: + retval = slave_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave[EXT_SLAVE_TYPE_ACCEL], + pdata_slave[EXT_SLAVE_TYPE_ACCEL], + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_COMPASS: + retval = slave_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave[EXT_SLAVE_TYPE_COMPASS], + pdata_slave[EXT_SLAVE_TYPE_COMPASS], + (struct ext_slave_config __user *)arg); + break; + case MPU_CONFIG_PRESSURE: + retval = slave_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + slave[EXT_SLAVE_TYPE_PRESSURE], + pdata_slave[EXT_SLAVE_TYPE_PRESSURE], + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_GYRO: + retval = inv_mpu_get_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_ACCEL: + retval = slave_get_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave[EXT_SLAVE_TYPE_ACCEL], + pdata_slave[EXT_SLAVE_TYPE_ACCEL], + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_COMPASS: + retval = slave_get_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave[EXT_SLAVE_TYPE_COMPASS], + pdata_slave[EXT_SLAVE_TYPE_COMPASS], + (struct ext_slave_config __user *)arg); + break; + case MPU_GET_CONFIG_PRESSURE: + retval = slave_get_config( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + slave[EXT_SLAVE_TYPE_PRESSURE], + pdata_slave[EXT_SLAVE_TYPE_PRESSURE], + (struct ext_slave_config __user *)arg); + break; + case MPU_SUSPEND: + retval = inv_mpu_suspend( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + arg); + break; + case MPU_RESUME: + retval = inv_mpu_resume( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + arg); + 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, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave[EXT_SLAVE_TYPE_ACCEL], + pdata_slave[EXT_SLAVE_TYPE_ACCEL], + (unsigned char __user *)arg); + break; + case MPU_READ_COMPASS: + retval = inv_slave_read( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave[EXT_SLAVE_TYPE_COMPASS], + pdata_slave[EXT_SLAVE_TYPE_COMPASS], + (unsigned char __user *)arg); + break; + case MPU_READ_PRESSURE: + retval = inv_slave_read( + mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + slave[EXT_SLAVE_TYPE_PRESSURE], + pdata_slave[EXT_SLAVE_TYPE_PRESSURE], + (unsigned char __user *)arg); + break; + case MPU_GET_REQUESTED_SENSORS: + if (copy_to_user( + (__u32 __user *)arg, + &mldl_cfg->inv_mpu_cfg->requested_sensors, + sizeof(mldl_cfg->inv_mpu_cfg->requested_sensors))) + retval = -EFAULT; + break; + case MPU_SET_REQUESTED_SENSORS: + mldl_cfg->inv_mpu_cfg->requested_sensors = arg; + break; + case MPU_GET_IGNORE_SYSTEM_SUSPEND: + if (copy_to_user( + (unsigned char __user *)arg, + &mldl_cfg->inv_mpu_cfg->ignore_system_suspend, + sizeof(mldl_cfg->inv_mpu_cfg->ignore_system_suspend))) + retval = -EFAULT; + break; + case MPU_SET_IGNORE_SYSTEM_SUSPEND: + mldl_cfg->inv_mpu_cfg->ignore_system_suspend = arg; + break; + case MPU_GET_MLDL_STATUS: + if (copy_to_user( + (unsigned char __user *)arg, + &mldl_cfg->inv_mpu_state->status, + sizeof(mldl_cfg->inv_mpu_state->status))) + retval = -EFAULT; + break; + case MPU_GET_I2C_SLAVES_ENABLED: + if (copy_to_user( + (unsigned char __user *)arg, + &mldl_cfg->inv_mpu_state->i2c_slaves_enabled, + sizeof(mldl_cfg->inv_mpu_state->i2c_slaves_enabled))) + retval = -EFAULT; + break; + default: + dev_err(&client->adapter->dev, + "%s: Unknown cmd %x, arg %lu\n", + __func__, cmd, arg); + retval = -EINVAL; + }; + + mutex_unlock(&mpu->mutex); + dev_dbg(&client->adapter->dev, "%s: %08x, %08lx, %d\n", + __func__, cmd, arg, retval); + + if (retval > 0) + retval = -retval; + + 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 *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + mutex_lock(&mpu->mutex); + (void)inv_mpu_suspend(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + 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 *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + mutex_lock(&mpu->mutex); + if (!mldl_cfg->inv_mpu_cfg->ignore_system_suspend) { + dev_dbg(&client->adapter->dev, + "%s: suspending on event %d\n", __func__, mesg.event); + (void)inv_mpu_suspend(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + 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 *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + + mutex_lock(&mpu->mutex); + if (mpu->pid && !mldl_cfg->inv_mpu_cfg->ignore_system_suspend) { + (void)inv_mpu_resume(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE], + mldl_cfg->inv_mpu_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 ext_slave_descr *slave_descr; + struct ext_slave_platform_data **pdata_slave; + char *irq_name; + 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_slave = mldl_cfg->pdata_slave; + + 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; + + if (pdata_slave[slave_descr->type]) { + result = -EBUSY; + goto out_unlock_mutex; + } + + pdata_slave[slave_descr->type] = slave_pdata; + pdata_slave[slave_descr->type]->address = slave_client->addr; + pdata_slave[slave_descr->type]->irq = slave_client->irq; + pdata_slave[slave_descr->type]->adapt_num = + i2c_adapter_id(slave_client->adapter); + + switch (slave_descr->type) { + case EXT_SLAVE_TYPE_ACCEL: + irq_name = "accelirq"; + break; + case EXT_SLAVE_TYPE_COMPASS: + irq_name = "compassirq"; + break; + case EXT_SLAVE_TYPE_PRESSURE: + irq_name = "pressureirq"; + break; + default: + irq_name = "none"; + }; + + if (pdata_slave[slave_descr->type]->irq > 0) { + dev_info(&slave_client->adapter->dev, + "Installing %s irq using %d\n", + irq_name, + pdata_slave[slave_descr->type]->irq); + result = slaveirq_init(slave_client->adapter, + pdata_slave[slave_descr->type], irq_name); + if (result) + goto out_unlock_mutex; + } 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_slave[slave_descr->type]); + if (result) { + dev_err(&slave_client->adapter->dev, + "Accel init failed %d\n", result); + goto out_slaveirq_exit; + } + } + + mldl_cfg->slave[slave_descr->type] = slave_descr; + + dev_info(&slave_client->adapter->dev, + "%s: +%s Type %d: Addr: %2x IRQ: %2d, Adapt: %2d\n", + __func__, + mldl_cfg->slave[slave_descr->type]->name, + slave_descr->type, + pdata_slave[slave_descr->type]->address, + pdata_slave[slave_descr->type]->irq, + pdata_slave[slave_descr->type]->adapt_num); + goto out_unlock_mutex; + +out_slaveirq_exit: + if (pdata_slave[slave_descr->type]->irq > 0) + slaveirq_exit(pdata_slave[slave_descr->type]); +out_unlock_mutex: + 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 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; + + mutex_lock(&mpu->mutex); + + if (slave_descr->exit) { + result = slave_descr->exit(slave_client->adapter, + slave_descr, + slave_pdata); + if (result) + dev_err(&slave_client->adapter->dev, + "Accel exit failed %d\n", result); + } + + if (slave_pdata->irq) + slaveirq_exit(slave_pdata); + + mldl_cfg->slave[slave_descr->type] = NULL; + mldl_cfg->pdata_slave[slave_descr->type] = NULL; + 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; + 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; + } + mldl_cfg = &mpu->mldl_cfg; + mldl_cfg->mpu_ram = &mpu->mpu_ram; + mldl_cfg->mpu_gyro_cfg = &mpu->mpu_gyro_cfg; + mldl_cfg->mpu_offsets = &mpu->mpu_offsets; + mldl_cfg->mpu_chip_info = &mpu->mpu_chip_info; + mldl_cfg->inv_mpu_cfg = &mpu->inv_mpu_cfg; + mldl_cfg->inv_mpu_state = &mpu->inv_mpu_state; + + mldl_cfg->mpu_ram->length = MPU_MEM_NUM_RAM_BANKS * MPU_MEM_BANK_SIZE; + mldl_cfg->mpu_ram->ram = kzalloc(mldl_cfg->mpu_ram->length, GFP_KERNEL); + if (!mldl_cfg->mpu_ram->ram) { + res = -ENOMEM; + goto out_alloc_ram_failed; + } + mpu_private_data = mpu; + i2c_set_clientdata(client, mpu); + mpu->client = client; + + 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; + res = register_pm_notifier(&mpu->nb); + if (res) { + dev_err(&client->adapter->dev, + "Unable to register pm_notifier %d\n", res); + goto out_register_pm_notifier_failed; + } + + 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->mpu_chip_info->addr = client->addr; + res = inv_mpu_open(&mpu->mldl_cfg, client->adapter, NULL, NULL, NULL); + + 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"; + 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, NULL, NULL, NULL); +out_whoami_failed: + unregister_pm_notifier(&mpu->nb); +out_register_pm_notifier_failed: + kfree(mldl_cfg->mpu_ram->ram); + mpu_private_data = NULL; +out_alloc_ram_failed: + kfree(mpu); +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 *slave_adapter[EXT_SLAVE_NUM_TYPES]; + struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg; + struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave; + int ii; + + for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) { + if (!pdata_slave[ii]) + slave_adapter[ii] = NULL; + else + slave_adapter[ii] = + i2c_get_adapter(pdata_slave[ii]->adapt_num); + } + + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter; + dev_dbg(&client->adapter->dev, "%s\n", __func__); + + inv_mpu_close(mldl_cfg, + slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE], + slave_adapter[EXT_SLAVE_TYPE_ACCEL], + slave_adapter[EXT_SLAVE_TYPE_COMPASS], + slave_adapter[EXT_SLAVE_TYPE_PRESSURE]); + + if (client->irq) + mpuirq_exit(); + + for (ii = EXT_SLAVE_TYPE_ACCEL - 1; ii >= 0; ii--) { + if (pdata_slave[ii] && + pdata_slave[ii]->irq) { + slaveirq_exit(pdata_slave[ii]); + } + } + + 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); diff --git a/drivers/misc/inv_mpu/mpu-dev.h b/drivers/misc/inv_mpu/mpu-dev.h new file mode 100644 index 0000000..b6a4fcf --- /dev/null +++ b/drivers/misc/inv_mpu/mpu-dev.h @@ -0,0 +1,36 @@ +/* + $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 . + $ + */ + + +#ifndef __MPU_DEV_H__ +#define __MPU_DEV_H__ + +#include +#include +#include + +int inv_mpu_register_slave(struct module *slave_module, + struct i2c_client *client, + struct ext_slave_platform_data *pdata, + struct ext_slave_descr *(*slave_descr)(void)); + +void inv_mpu_unregister_slave(struct i2c_client *client, + struct ext_slave_platform_data *pdata, + struct ext_slave_descr *(*slave_descr)(void)); +#endif