diff mbox

[11/11] misc: Add slave driver for bma085 pressure sensor

Message ID 1309486707-1658-11-git-send-email-nroyer@invensense.com (mailing list archive)
State New, archived
Headers show

Commit Message

Nathan Royer July 1, 2011, 2:18 a.m. UTC
Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/Kconfig           |    1 +
 drivers/misc/inv_mpu/Makefile          |    1 +
 drivers/misc/inv_mpu/pressure/Kconfig  |   20 ++
 drivers/misc/inv_mpu/pressure/Makefile |    8 +
 drivers/misc/inv_mpu/pressure/bma085.c |  367 ++++++++++++++++++++++++++++++++
 5 files changed, 397 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/pressure/Kconfig
 create mode 100644 drivers/misc/inv_mpu/pressure/Makefile
 create mode 100644 drivers/misc/inv_mpu/pressure/bma085.c

Comments

Alan Cox July 1, 2011, 7:56 a.m. UTC | #1
The slave devices all appear to be generic i2c interface hardware. I
don't see why they are separate special slave devices to your driver,
they should simply be i²c drivers so they can be used when those
devices are found without the mpu3050.

Alan
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jean Delvare July 1, 2011, 8:47 a.m. UTC | #2
On Fri, 1 Jul 2011 08:56:04 +0100, Alan Cox wrote:
> The slave devices all appear to be generic i2c interface hardware. I
> don't see why they are separate special slave devices to your driver,
> they should simply be i²c drivers so they can be used when those
> devices are found without the mpu3050.

Seconded. As a matter of fact, we already have a driver for the AK8975
magnetometer:
drivers/staging/iio/magnetometer/ak8975.c
Jonathan Cameron July 1, 2011, 9:05 a.m. UTC | #3
Hang on, so your motion processing unit takes in a pressure sensor?
Interesting....

Manuel, am I imagining things or did you have a driver for one of these?

Happy to take barometers in IIO.
> Signed-off-by: Nathan Royer <nroyer@invensense.com>
> ---
>  drivers/misc/inv_mpu/Kconfig           |    1 +
>  drivers/misc/inv_mpu/Makefile          |    1 +
>  drivers/misc/inv_mpu/pressure/Kconfig  |   20 ++
>  drivers/misc/inv_mpu/pressure/Makefile |    8 +
>  drivers/misc/inv_mpu/pressure/bma085.c |  367 ++++++++++++++++++++++++++++++++
>  5 files changed, 397 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/misc/inv_mpu/pressure/Kconfig
>  create mode 100644 drivers/misc/inv_mpu/pressure/Makefile
>  create mode 100644 drivers/misc/inv_mpu/pressure/bma085.c
> 
> diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig
> index 7d9f2c3..ea7d754 100644
> --- a/drivers/misc/inv_mpu/Kconfig
> +++ b/drivers/misc/inv_mpu/Kconfig
> @@ -57,5 +57,6 @@ endchoice
>  
>  source "drivers/misc/inv_mpu/accel/Kconfig"
>  source "drivers/misc/inv_mpu/compass/Kconfig"
> +source "drivers/misc/inv_mpu/pressure/Kconfig"
>  
>  endif #INV_SENSORS
> diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile
> index dc77a1b..506b8f5 100644
> --- a/drivers/misc/inv_mpu/Makefile
> +++ b/drivers/misc/inv_mpu/Makefile
> @@ -18,3 +18,4 @@ EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
>  
>  obj-y			+= accel/
>  obj-y			+= compass/
> +obj-y			+= pressure/
> diff --git a/drivers/misc/inv_mpu/pressure/Kconfig b/drivers/misc/inv_mpu/pressure/Kconfig
> new file mode 100644
> index 0000000..f1c021e
> --- /dev/null
> +++ b/drivers/misc/inv_mpu/pressure/Kconfig
> @@ -0,0 +1,20 @@
> +menuconfig: INV_SENSORS_PRESSURE
> +	bool "Pressure Sensor Slaves"
> +	depends on INV_SENSORS
> +	default y
> +	help
> +	  Select y to see a list of supported pressure sensors that can be
> +	  integrated with the MPUxxxx set of motion processors.
> +
> +if INV_SENSORS_PRESSURE
> +
> +config MPU_SENSORS_BMA085
> +	tristate "Bosch BMA085"
> +	help
> +	  This enables support for the Bosch bma085 pressure sensor
> +	  This support is for integration with the MPU3050 or MPU6050 gyroscope
> +          device driver.  Only one accelerometer can be registered at a time.
> +	  Specifying more that one accelerometer in the board file will result
> +	  in runtime errors.
That's a seriously weird message for a pressure sensor...
> +
> +endif
> diff --git a/drivers/misc/inv_mpu/pressure/Makefile b/drivers/misc/inv_mpu/pressure/Makefile
> new file mode 100644
> index 0000000..595923d
> --- /dev/null
> +++ b/drivers/misc/inv_mpu/pressure/Makefile
> @@ -0,0 +1,8 @@
> +#
> +# Pressure Slaves to MPUxxxx
> +#
> +obj-$(CONFIG_MPU_SENSORS_BMA085) += inv_mpu_bma085.o
> +inv_mpu_bma085-objs +=	bma085.o
> +
> +EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
> +EXTRA_CFLAGS += -D__C99_DESIGNATED_INITIALIZER
> diff --git a/drivers/misc/inv_mpu/pressure/bma085.c b/drivers/misc/inv_mpu/pressure/bma085.c
> new file mode 100644
> index 0000000..696d2b6
> --- /dev/null
> +++ b/drivers/misc/inv_mpu/pressure/bma085.c
> @@ -0,0 +1,367 @@
> +/*
> +	$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/>.
> +	$
> + */
> +
> +/**
> + *  @defgroup   ACCELDL (Motion Library - Pressure Driver Layer)
> + *  @brief      Provides the interface to setup and handle a pressure
> + *              connected to the secondary I2C interface of the gyroscope.
> + *
> + *  @{
> + *      @file   bma085.c
> + *      @brief  Pressure setup and handling methods.
> + */
> +
> +/* ------------------ */
> +/* - Include Files. - */
> +/* ------------------ */
> +
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/kernel.h>
> +#include <linux/errno.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include "mpu-dev.h"
> +
> +#include <linux/mpu.h>
> +#include "mlsl.h"
> +#include "log.h"
> +
> +/*
> + * this structure holds all device specific calibration parameters
> + */
> +struct bmp085_calibration_param_t {
> +	short ac1;
> +	short ac2;
> +	short ac3;
> +	unsigned short ac4;
> +	unsigned short ac5;
> +	unsigned short ac6;
> +	short b1;
> +	short b2;
> +	short mb;
> +	short mc;
> +	short md;
> +	long param_b5;
> +};
> +
> +struct bmp085_calibration_param_t cal_param;
> +
> +#define PRESSURE_BMA085_PARAM_MG      3038        /* calibration parameter */
> +#define PRESSURE_BMA085_PARAM_MH     -7357        /* calibration parameter */
> +#define PRESSURE_BMA085_PARAM_MI      3791        /* calibration parameter */
> +
> +/*********************************************
> + *    Pressure Initialization Functions
> + *********************************************/
> +
> +static int bma085_suspend(void *mlsl_handle,
> +			  struct ext_slave_descr *slave,
> +			  struct ext_slave_platform_data *pdata)
> +{
> +	int result = INV_SUCCESS;
> +	return result;
> +}
> +
> +#define PRESSURE_BMA085_PROM_START_ADDR  (0xAA)
> +#define PRESSURE_BMA085_PROM_DATA_LEN    (22)
> +#define PRESSURE_BMP085_CTRL_MEAS_REG    (0xF4)
> +/* temperature measurent */
> +#define PRESSURE_BMP085_T_MEAS           (0x2E)
> +/* pressure measurement; oversampling_setting */
> +#define PRESSURE_BMP085_P_MEAS_OSS_0     (0x34)
> +#define PRESSURE_BMP085_P_MEAS_OSS_1     (0x74)
> +#define PRESSURE_BMP085_P_MEAS_OSS_2     (0xB4)
> +#define PRESSURE_BMP085_P_MEAS_OSS_3     (0xF4)
> +#define PRESSURE_BMP085_ADC_OUT_MSB_REG  (0xF6)
> +#define PRESSURE_BMP085_ADC_OUT_LSB_REG  (0xF7)
> +
> +static int bma085_resume(void *mlsl_handle,
> +			 struct ext_slave_descr *slave,
> +			 struct ext_slave_platform_data *pdata)
> +{
> +	int result;
> +	unsigned char data[PRESSURE_BMA085_PROM_DATA_LEN];
> +
> +	result =
> +	    inv_serial_read(mlsl_handle, pdata->address,
> +			   PRESSURE_BMA085_PROM_START_ADDR,
> +			   PRESSURE_BMA085_PROM_DATA_LEN, data);
> +	if (result) {
> +		LOG_RESULT_LOCATION(result);
> +		return result;
> +	}
> +
> +	/* parameters AC1-AC6 */
> +	cal_param.ac1 = (data[0] << 8) | data[1];
> +	cal_param.ac2 = (data[2] << 8) | data[3];
> +	cal_param.ac3 = (data[4] << 8) | data[5];
> +	cal_param.ac4 = (data[6] << 8) | data[7];
> +	cal_param.ac5 = (data[8] << 8) | data[9];
> +	cal_param.ac6 = (data[10] << 8) | data[11];
> +
> +	/* parameters B1,B2 */
> +	cal_param.b1 = (data[12] << 8) | data[13];
> +	cal_param.b2 = (data[14] << 8) | data[15];
> +
> +	/* parameters MB,MC,MD */
> +	cal_param.mb = (data[16] << 8) | data[17];
> +	cal_param.mc = (data[18] << 8) | data[19];
> +	cal_param.md = (data[20] << 8) | data[21];
> +
> +	return result;
> +}
> +
> +static int bma085_read(void *mlsl_handle,
> +		       struct ext_slave_descr *slave,
> +		       struct ext_slave_platform_data *pdata,
> +		       unsigned char *data)
> +{
> +	int result;
> +	long pressure, x1, x2, x3, b3, b6;
> +	unsigned long b4, b7;
> +	unsigned long up;
> +	unsigned short ut;
> +	short oversampling_setting = 0;
> +	short temperature;
> +	long divisor;
> +
> +	/* get temprature */
> +	result = inv_serial_single_write(mlsl_handle, pdata->address,
> +				       PRESSURE_BMP085_CTRL_MEAS_REG,
> +				       PRESSURE_BMP085_T_MEAS);
> +	msleep(5);
> +	result =
> +	    inv_serial_read(mlsl_handle, pdata->address,
> +			   PRESSURE_BMP085_ADC_OUT_MSB_REG, 2,
> +			   (unsigned char *)data);
> +	if (result) {
> +		LOG_RESULT_LOCATION(result);
> +		return result;
> +	}
> +	ut = (data[0] << 8) | data[1];
> +
> +	x1 = (((long) ut - (long)cal_param.ac6) * (long)cal_param.ac5) >> 15;
> +	divisor = x1 + cal_param.md;
> +	if (!divisor)
> +		return INV_ERROR_DIVIDE_BY_ZERO;
> +
> +	x2 = ((long)cal_param.mc << 11) / (x1 + cal_param.md);
> +	cal_param.param_b5 = x1 + x2;
> +	/* temperature in 0.1 degree C */
> +	temperature = (short)((cal_param.param_b5 + 8) >> 4);
> +
> +	/* get pressure */
> +	result = inv_serial_single_write(mlsl_handle, pdata->address,
> +				       PRESSURE_BMP085_CTRL_MEAS_REG,
> +				       PRESSURE_BMP085_P_MEAS_OSS_0);
> +	msleep(5);
> +	result =
> +	    inv_serial_read(mlsl_handle, pdata->address,
> +			   PRESSURE_BMP085_ADC_OUT_MSB_REG, 2,
> +			   (unsigned char *)data);
> +	if (result) {
> +		LOG_RESULT_LOCATION(result);
> +		return result;
> +	}
> +	up = (((unsigned long) data[0] << 8) | ((unsigned long) data[1]));
> +
> +	b6 = cal_param.param_b5 - 4000;
> +	/* calculate B3 */
> +	x1 = (b6*b6) >> 12;
> +	x1 *= cal_param.b2;
> +	x1 >>= 11;
> +
> +	x2 = (cal_param.ac2*b6);
> +	x2 >>= 11;
> +
> +	x3 = x1 + x2;
> +
> +	b3 = (((((long)cal_param.ac1) * 4 + x3)
> +	    << oversampling_setting) + 2) >> 2;
> +
> +	/* calculate B4 */
> +	x1 = (cal_param.ac3 * b6) >> 13;
> +	x2 = (cal_param.b1 * ((b6*b6) >> 12)) >> 16;
> +	x3 = ((x1 + x2) + 2) >> 2;
> +	b4 = (cal_param.ac4 * (unsigned long) (x3 + 32768)) >> 15;
> +	if (!b4)
> +		return INV_ERROR;
> +
> +	b7 = ((unsigned long)(up - b3) * (50000>>oversampling_setting));
> +	if (b7 < 0x80000000)
> +		pressure = (b7 << 1) / b4;
> +	else
> +		pressure = (b7 / b4) << 1;
> +
> +	x1 = pressure >> 8;
> +	x1 *= x1;
> +	x1 = (x1 * PRESSURE_BMA085_PARAM_MG) >> 16;
> +	x2 = (pressure * PRESSURE_BMA085_PARAM_MH) >> 16;
> +	/* pressure in Pa */
> +	pressure += (x1 + x2 + PRESSURE_BMA085_PARAM_MI) >> 4;
> +
> +	data[0] = (unsigned char)(pressure >> 16);
> +	data[1] = (unsigned char)(pressure >> 8);
> +	data[2] = (unsigned char)(pressure & 0xFF);
> +
> +	return result;
> +}
> +
> +static struct ext_slave_descr bma085_descr = {
> +	.init             = NULL,
> +	.exit             = NULL,
> +	.suspend          = bma085_suspend,
> +	.resume           = bma085_resume,
> +	.read             = bma085_read,
> +	.config           = NULL,
> +	.get_config       = NULL,
> +	.name             = "bma085",
> +	.type             = EXT_SLAVE_TYPE_PRESSURE,
> +	.id               = PRESSURE_ID_BMA085,
> +	.read_reg         = 0xF6,
> +	.read_len         = 3,
> +	.endian           = EXT_SLAVE_BIG_ENDIAN,
> +	.range            = {0, 0},
> +};
> +
> +static
> +struct ext_slave_descr *bma085_get_slave_descr(void)
> +{
> +	return &bma085_descr;
> +}
> +
> +/* Platform data for the MPU */
> +struct bma085_mod_private_data {
> +	struct i2c_client *client;
> +	struct ext_slave_platform_data *pdata;
> +};
> +
> +static unsigned short normal_i2c[] = { I2C_CLIENT_END };
> +
> +static int bma085_mod_probe(struct i2c_client *client,
> +			   const struct i2c_device_id *devid)
> +{
> +	struct ext_slave_platform_data *pdata;
> +	struct bma085_mod_private_data *private_data;
> +	int result = 0;
> +
> +	dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name);
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> +		result = -ENODEV;
> +		goto out_no_free;
> +	}
> +
> +	pdata = client->dev.platform_data;
> +	if (!pdata) {
> +		dev_err(&client->adapter->dev,
> +			"Missing platform data for slave %s\n", devid->name);
> +		result = -EFAULT;
> +		goto out_no_free;
> +	}
> +
> +	private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
> +	if (!private_data) {
> +		result = -ENOMEM;
> +		goto out_no_free;
> +	}
> +
> +	i2c_set_clientdata(client, private_data);
> +	private_data->client = client;
> +	private_data->pdata = pdata;
> +
> +	result = inv_mpu_register_slave(THIS_MODULE, client, pdata,
> +					bma085_get_slave_descr);
> +	if (result) {
> +		dev_err(&client->adapter->dev,
> +			"Slave registration failed: %s, %d\n",
> +			devid->name, result);
> +		goto out_free_memory;
> +	}
> +
> +	return result;
> +
> +out_free_memory:
> +	kfree(private_data);
> +out_no_free:
> +	dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
> +	return result;
> +
> +}
> +
> +static int bma085_mod_remove(struct i2c_client *client)
> +{
> +	struct bma085_mod_private_data *private_data =
> +		i2c_get_clientdata(client);
> +
> +	dev_dbg(&client->adapter->dev, "%s\n", __func__);
> +
> +	inv_mpu_unregister_slave(client, private_data->pdata,
> +				bma085_get_slave_descr);
> +
> +	kfree(private_data);
> +	return 0;
> +}
> +
> +static const struct i2c_device_id bma085_mod_id[] = {
> +	{ "bma085", PRESSURE_ID_BMA085 },
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, bma085_mod_id);
> +
> +static struct i2c_driver bma085_mod_driver = {
> +	.class = I2C_CLASS_HWMON,
> +	.probe = bma085_mod_probe,
> +	.remove = bma085_mod_remove,
> +	.id_table = bma085_mod_id,
> +	.driver = {
> +		   .owner = THIS_MODULE,
> +		   .name = "bma085_mod",
> +		   },
> +	.address_list = normal_i2c,
> +};
> +
> +static int __init bma085_mod_init(void)
> +{
> +	int res = i2c_add_driver(&bma085_mod_driver);
> +	pr_info("%s: Probe name %s\n", __func__, "bma085_mod");
> +	if (res)
> +		pr_err("%s failed\n", __func__);
> +	return res;
> +}
> +
> +static void __exit bma085_mod_exit(void)
> +{
> +	pr_info("%s\n", __func__);
> +	i2c_del_driver(&bma085_mod_driver);
> +}
> +
> +module_init(bma085_mod_init);
> +module_exit(bma085_mod_exit);
> +
> +MODULE_AUTHOR("Invensense Corporation");
> +MODULE_DESCRIPTION("Driver to integrate BMA085 sensor with the MPU");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("bma085_mod");
> +/**
> + *  @}
> +**/

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Manuel Stahl July 1, 2011, 10:35 a.m. UTC | #4
On 01.07.2011 11:05, Jonathan Cameron wrote:
> Hang on, so your motion processing unit takes in a pressure sensor?
> Interesting....
>
> Manuel, am I imagining things or did you have a driver for one of these?
Yes, indeed we have one for bmp085. I have to update it for the latest 
API, but these are things I'm going to do during the next 2 month.

Regards,
Chris Wolfe July 1, 2011, 2:28 p.m. UTC | #5
On Fri, Jul 1, 2011 at 12:56 AM, Alan Cox <alan@linux.intel.com> wrote:
> The slave devices all appear to be generic i2c interface hardware. I
> don't see why they are separate special slave devices to your driver,
> they should simply be i²c drivers so they can be used when those
> devices are found without the mpu3050.

Just to field this quickly, since I've been playing with the hardware
involved. There are actually two different things handled by the
"slave devices".

The MPU-3050 (and some similar InvenSense parts) provide a secondary
I2C bus. By default this is passed through to the I2C bus on which the
3050 is installed, but the MPU can also detach the extra bus and act
as an I2C master on it. A slave device is connected on this extra bus,
configured normally by the kernel, and then taken over by the MPU
(which will periodically do a block read of a configured address).

The second case of a slave device is simply some other device that the
driver reads from, and passes the data to the MPU for processing.

Part of the reason I got started on the KXTF9 driver was to try and
understand how a normal kernel driver could cope with the "here and
gone again" behavior of being on the MPU's secondary bus.

The other note that may not have been made explicit yet is that the
3050 will happily provide unprocessed gyro and slave data without
firmware loaded. The firmware sets up some additional processing
capabilities of the chip, which are used by InvenSense's user-space
products.

Chris

[Sorry about gmail. I'm on the road and hope it doesn't mangle things
too badly.]
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alan Cox July 1, 2011, 2:41 p.m. UTC | #6
> Just to field this quickly, since I've been playing with the hardware

(Ditto)

> Part of the reason I got started on the KXTF9 driver was to try and
> understand how a normal kernel driver could cope with the "here and
> gone again" behavior of being on the MPU's secondary bus.

It's a hot pluggable bus - no different to an i2c bus on a hot pluggable
device. 

I would guess however that if you knew the device was going to be used for
the mpu3050 only you'd not add the "slave" devices in your platform data
in the first place as well so they didn't bounce in and out and you'd
probably teach the mpu3050 code to not create the bus if it was then
going to destroy it again a moment later.

> The other note that may not have been made explicit yet is that the
> 3050 will happily provide unprocessed gyro and slave data without
> firmware loaded. The firmware sets up some additional processing
> capabilities of the chip, which are used by InvenSense's user-space
> products.

Yes - and the current mpu3050 driver provides just these interfaces.

Of the other stuff the slave drivers seem to be no problem as standard
i²c bus devices, the MPU mode may depend upon whether there is open
documentation, applying the standards we apply to everything else. That
is "can someone write their own tools to use that interface based upon the
documentation/source code examples available".

Eg for the ektf driver which is pending the interface is all documented
and provides rectangles of pressure data but you'll have to go write
your own gesture recognizer parts if you want to do clever stuff with it.

Alan
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Chris Wolfe July 1, 2011, 3:52 p.m. UTC | #7
On Fri, Jul 1, 2011 at 7:41 AM, Alan Cox <alan@lxorguk.ukuu.org.uk> wrote:
>> Part of the reason I got started on the KXTF9 driver was to try and
>> understand how a normal kernel driver could cope with the "here and
>> gone again" behavior of being on the MPU's secondary bus.
>
> It's a hot pluggable bus - no different to an i2c bus on a hot pluggable
> device.

Can such a hot pluggable device maintain state while "unplugged"?

e.g. if the system is going to suspend, we need to pause the MPU,
switch it back to pass-through mode, then send commands to suspend the
slave on that bus. I took that to mean we wanted the secondary bus to
have a normal device on it, which is still considered plugged in, but
temporarily inaccessible from the CPU.

> I would guess however that if you knew the device was going to be used for
> the mpu3050 only you'd not add the "slave" devices in your platform data
> in the first place as well so they didn't bounce in and out and you'd
> probably teach the mpu3050 code to not create the bus if it was then
> going to destroy it again a moment later.

Something needs to initialize and configure the slaved devices (and
suspend/resume/shutdown/etc). I'm not sure where this happens if not
A) special-purpose code in the mpu3050 driver for each possible slave;
or B) normal drivers and platform data which cope with B1) the kernel
relaying their data to the MPU and B2) being disconnected from the CPU
while the MPU reads from the device directly.

>> The other note that may not have been made explicit yet is that the
>> 3050 will happily provide unprocessed gyro and slave data without
>> firmware loaded. The firmware sets up some additional processing
>> capabilities of the chip, which are used by InvenSense's user-space
>> products.
>
> Yes - and the current mpu3050 driver provides just these interfaces.

Ah, great. Sorry for missing them. In scanning the ioctls, it looked
like dev-i2c was still a similar level of abstraction :)

Chris
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jean Delvare July 1, 2011, 4:09 p.m. UTC | #8
On Fri, 1 Jul 2011 15:41:50 +0100, Alan Cox wrote:
> > Just to field this quickly, since I've been playing with the hardware
> 
> (Ditto)
> 
> > Part of the reason I got started on the KXTF9 driver was to try and
> > understand how a normal kernel driver could cope with the "here and
> > gone again" behavior of being on the MPU's secondary bus.
> 
> It's a hot pluggable bus - no different to an i2c bus on a hot pluggable
> device. 
> 
> I would guess however that if you knew the device was going to be used for
> the mpu3050 only you'd not add the "slave" devices in your platform data
> in the first place as well so they didn't bounce in and out and you'd
> probably teach the mpu3050 code to not create the bus if it was then
> going to destroy it again a moment later.

I didn't look in the code in details, but I see no reason to destroy
buses. This is a standard multi-master case, where a given I2C bus
segment can be driven by two masters. If both the host I2C controller
and the MPU3050 master are fully I2C compliant, it should be no problem
to let both coexist. If you really need to handle mutual exclusion
(e.g. the MPU3050 implementation forces you to do so), you can leverage
the i2c multiplexing support for this, which is available since kernel
2.6.36. See drivers/i2c/muxes/pca9541.c for an example implementation.
Alan Cox July 1, 2011, 5 p.m. UTC | #9
> > It's a hot pluggable bus - no different to an i2c bus on a hot pluggable
> > device.
> 
> Can such a hot pluggable device maintain state while "unplugged"?

The hardware or the driver ?

As far as the OS is concerned it would have gone away.

> e.g. if the system is going to suspend, we need to pause the MPU,
> switch it back to pass-through mode, then send commands to suspend the
> slave on that bus. I took that to mean we wanted the secondary bus to
> have a normal device on it, which is still considered plugged in, but
> temporarily inaccessible from the CPU.

Thats a bit messier but we'd still want them to be normal i²c drivers
when on the slave bus and being driven by Linux. Possibly you end up with
a little helper to send i²c commands directly to the bus  in the "smart"
mode.

> A) special-purpose code in the mpu3050 driver for each possible slave;
> or B) normal drivers and platform data which cope with B1) the kernel
> relaying their data to the MPU and B2) being disconnected from the CPU
> while the MPU reads from the device directly.

If it's operating as a normal i²c device then it'll behave like any
normal kernel device, and really ought to be using runtime pm anyway.

In "smart" mode its really up to the driver how it does it but two
versions of every device is not a good answer !

> 
> >> The other note that may not have been made explicit yet is that the
> >> 3050 will happily provide unprocessed gyro and slave data without
> >> firmware loaded. The firmware sets up some additional processing
> >> capabilities of the chip, which are used by InvenSense's user-space
> >> products.
> >
> > Yes - and the current mpu3050 driver provides just these interfaces.
> 
> Ah, great. Sorry for missing them. In scanning the ioctls, it looked
> like dev-i2c was still a similar level of abstraction :)

Sorry may not have been clear "just those" as in "just those which can be
used without loading firmware and running non-free stuff"

And really until that area is sorted it may be premature to worry about
the rest - if its going to need proprietary bits to use the smart mode,
then the kernel just needs an i2c slave bus implementation, a few drivers
(we have most listed already it seems), and we are done.

Alan
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Nathan Royer July 1, 2011, 5:56 p.m. UTC | #10
I've added an explanation of some of the high level bypass mode work which
is done in mldl_cfg.c in response that patch

[PATCH 05/11] misc: MPU3050 and slave device configuration.
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig
index 7d9f2c3..ea7d754 100644
--- a/drivers/misc/inv_mpu/Kconfig
+++ b/drivers/misc/inv_mpu/Kconfig
@@ -57,5 +57,6 @@  endchoice
 
 source "drivers/misc/inv_mpu/accel/Kconfig"
 source "drivers/misc/inv_mpu/compass/Kconfig"
+source "drivers/misc/inv_mpu/pressure/Kconfig"
 
 endif #INV_SENSORS
diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile
index dc77a1b..506b8f5 100644
--- a/drivers/misc/inv_mpu/Makefile
+++ b/drivers/misc/inv_mpu/Makefile
@@ -18,3 +18,4 @@  EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
 
 obj-y			+= accel/
 obj-y			+= compass/
+obj-y			+= pressure/
diff --git a/drivers/misc/inv_mpu/pressure/Kconfig b/drivers/misc/inv_mpu/pressure/Kconfig
new file mode 100644
index 0000000..f1c021e
--- /dev/null
+++ b/drivers/misc/inv_mpu/pressure/Kconfig
@@ -0,0 +1,20 @@ 
+menuconfig: INV_SENSORS_PRESSURE
+	bool "Pressure Sensor Slaves"
+	depends on INV_SENSORS
+	default y
+	help
+	  Select y to see a list of supported pressure sensors that can be
+	  integrated with the MPUxxxx set of motion processors.
+
+if INV_SENSORS_PRESSURE
+
+config MPU_SENSORS_BMA085
+	tristate "Bosch BMA085"
+	help
+	  This enables support for the Bosch bma085 pressure sensor
+	  This support is for integration with the MPU3050 or MPU6050 gyroscope
+          device driver.  Only one accelerometer can be registered at a time.
+	  Specifying more that one accelerometer in the board file will result
+	  in runtime errors.
+
+endif
diff --git a/drivers/misc/inv_mpu/pressure/Makefile b/drivers/misc/inv_mpu/pressure/Makefile
new file mode 100644
index 0000000..595923d
--- /dev/null
+++ b/drivers/misc/inv_mpu/pressure/Makefile
@@ -0,0 +1,8 @@ 
+#
+# Pressure Slaves to MPUxxxx
+#
+obj-$(CONFIG_MPU_SENSORS_BMA085) += inv_mpu_bma085.o
+inv_mpu_bma085-objs +=	bma085.o
+
+EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
+EXTRA_CFLAGS += -D__C99_DESIGNATED_INITIALIZER
diff --git a/drivers/misc/inv_mpu/pressure/bma085.c b/drivers/misc/inv_mpu/pressure/bma085.c
new file mode 100644
index 0000000..696d2b6
--- /dev/null
+++ b/drivers/misc/inv_mpu/pressure/bma085.c
@@ -0,0 +1,367 @@ 
+/*
+	$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/>.
+	$
+ */
+
+/**
+ *  @defgroup   ACCELDL (Motion Library - Pressure Driver Layer)
+ *  @brief      Provides the interface to setup and handle a pressure
+ *              connected to the secondary I2C interface of the gyroscope.
+ *
+ *  @{
+ *      @file   bma085.c
+ *      @brief  Pressure setup and handling methods.
+ */
+
+/* ------------------ */
+/* - Include Files. - */
+/* ------------------ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include "mpu-dev.h"
+
+#include <linux/mpu.h>
+#include "mlsl.h"
+#include "log.h"
+
+/*
+ * this structure holds all device specific calibration parameters
+ */
+struct bmp085_calibration_param_t {
+	short ac1;
+	short ac2;
+	short ac3;
+	unsigned short ac4;
+	unsigned short ac5;
+	unsigned short ac6;
+	short b1;
+	short b2;
+	short mb;
+	short mc;
+	short md;
+	long param_b5;
+};
+
+struct bmp085_calibration_param_t cal_param;
+
+#define PRESSURE_BMA085_PARAM_MG      3038        /* calibration parameter */
+#define PRESSURE_BMA085_PARAM_MH     -7357        /* calibration parameter */
+#define PRESSURE_BMA085_PARAM_MI      3791        /* calibration parameter */
+
+/*********************************************
+ *    Pressure Initialization Functions
+ *********************************************/
+
+static int bma085_suspend(void *mlsl_handle,
+			  struct ext_slave_descr *slave,
+			  struct ext_slave_platform_data *pdata)
+{
+	int result = INV_SUCCESS;
+	return result;
+}
+
+#define PRESSURE_BMA085_PROM_START_ADDR  (0xAA)
+#define PRESSURE_BMA085_PROM_DATA_LEN    (22)
+#define PRESSURE_BMP085_CTRL_MEAS_REG    (0xF4)
+/* temperature measurent */
+#define PRESSURE_BMP085_T_MEAS           (0x2E)
+/* pressure measurement; oversampling_setting */
+#define PRESSURE_BMP085_P_MEAS_OSS_0     (0x34)
+#define PRESSURE_BMP085_P_MEAS_OSS_1     (0x74)
+#define PRESSURE_BMP085_P_MEAS_OSS_2     (0xB4)
+#define PRESSURE_BMP085_P_MEAS_OSS_3     (0xF4)
+#define PRESSURE_BMP085_ADC_OUT_MSB_REG  (0xF6)
+#define PRESSURE_BMP085_ADC_OUT_LSB_REG  (0xF7)
+
+static int bma085_resume(void *mlsl_handle,
+			 struct ext_slave_descr *slave,
+			 struct ext_slave_platform_data *pdata)
+{
+	int result;
+	unsigned char data[PRESSURE_BMA085_PROM_DATA_LEN];
+
+	result =
+	    inv_serial_read(mlsl_handle, pdata->address,
+			   PRESSURE_BMA085_PROM_START_ADDR,
+			   PRESSURE_BMA085_PROM_DATA_LEN, data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* parameters AC1-AC6 */
+	cal_param.ac1 = (data[0] << 8) | data[1];
+	cal_param.ac2 = (data[2] << 8) | data[3];
+	cal_param.ac3 = (data[4] << 8) | data[5];
+	cal_param.ac4 = (data[6] << 8) | data[7];
+	cal_param.ac5 = (data[8] << 8) | data[9];
+	cal_param.ac6 = (data[10] << 8) | data[11];
+
+	/* parameters B1,B2 */
+	cal_param.b1 = (data[12] << 8) | data[13];
+	cal_param.b2 = (data[14] << 8) | data[15];
+
+	/* parameters MB,MC,MD */
+	cal_param.mb = (data[16] << 8) | data[17];
+	cal_param.mc = (data[18] << 8) | data[19];
+	cal_param.md = (data[20] << 8) | data[21];
+
+	return result;
+}
+
+static int bma085_read(void *mlsl_handle,
+		       struct ext_slave_descr *slave,
+		       struct ext_slave_platform_data *pdata,
+		       unsigned char *data)
+{
+	int result;
+	long pressure, x1, x2, x3, b3, b6;
+	unsigned long b4, b7;
+	unsigned long up;
+	unsigned short ut;
+	short oversampling_setting = 0;
+	short temperature;
+	long divisor;
+
+	/* get temprature */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+				       PRESSURE_BMP085_CTRL_MEAS_REG,
+				       PRESSURE_BMP085_T_MEAS);
+	msleep(5);
+	result =
+	    inv_serial_read(mlsl_handle, pdata->address,
+			   PRESSURE_BMP085_ADC_OUT_MSB_REG, 2,
+			   (unsigned char *)data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	ut = (data[0] << 8) | data[1];
+
+	x1 = (((long) ut - (long)cal_param.ac6) * (long)cal_param.ac5) >> 15;
+	divisor = x1 + cal_param.md;
+	if (!divisor)
+		return INV_ERROR_DIVIDE_BY_ZERO;
+
+	x2 = ((long)cal_param.mc << 11) / (x1 + cal_param.md);
+	cal_param.param_b5 = x1 + x2;
+	/* temperature in 0.1 degree C */
+	temperature = (short)((cal_param.param_b5 + 8) >> 4);
+
+	/* get pressure */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+				       PRESSURE_BMP085_CTRL_MEAS_REG,
+				       PRESSURE_BMP085_P_MEAS_OSS_0);
+	msleep(5);
+	result =
+	    inv_serial_read(mlsl_handle, pdata->address,
+			   PRESSURE_BMP085_ADC_OUT_MSB_REG, 2,
+			   (unsigned char *)data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	up = (((unsigned long) data[0] << 8) | ((unsigned long) data[1]));
+
+	b6 = cal_param.param_b5 - 4000;
+	/* calculate B3 */
+	x1 = (b6*b6) >> 12;
+	x1 *= cal_param.b2;
+	x1 >>= 11;
+
+	x2 = (cal_param.ac2*b6);
+	x2 >>= 11;
+
+	x3 = x1 + x2;
+
+	b3 = (((((long)cal_param.ac1) * 4 + x3)
+	    << oversampling_setting) + 2) >> 2;
+
+	/* calculate B4 */
+	x1 = (cal_param.ac3 * b6) >> 13;
+	x2 = (cal_param.b1 * ((b6*b6) >> 12)) >> 16;
+	x3 = ((x1 + x2) + 2) >> 2;
+	b4 = (cal_param.ac4 * (unsigned long) (x3 + 32768)) >> 15;
+	if (!b4)
+		return INV_ERROR;
+
+	b7 = ((unsigned long)(up - b3) * (50000>>oversampling_setting));
+	if (b7 < 0x80000000)
+		pressure = (b7 << 1) / b4;
+	else
+		pressure = (b7 / b4) << 1;
+
+	x1 = pressure >> 8;
+	x1 *= x1;
+	x1 = (x1 * PRESSURE_BMA085_PARAM_MG) >> 16;
+	x2 = (pressure * PRESSURE_BMA085_PARAM_MH) >> 16;
+	/* pressure in Pa */
+	pressure += (x1 + x2 + PRESSURE_BMA085_PARAM_MI) >> 4;
+
+	data[0] = (unsigned char)(pressure >> 16);
+	data[1] = (unsigned char)(pressure >> 8);
+	data[2] = (unsigned char)(pressure & 0xFF);
+
+	return result;
+}
+
+static struct ext_slave_descr bma085_descr = {
+	.init             = NULL,
+	.exit             = NULL,
+	.suspend          = bma085_suspend,
+	.resume           = bma085_resume,
+	.read             = bma085_read,
+	.config           = NULL,
+	.get_config       = NULL,
+	.name             = "bma085",
+	.type             = EXT_SLAVE_TYPE_PRESSURE,
+	.id               = PRESSURE_ID_BMA085,
+	.read_reg         = 0xF6,
+	.read_len         = 3,
+	.endian           = EXT_SLAVE_BIG_ENDIAN,
+	.range            = {0, 0},
+};
+
+static
+struct ext_slave_descr *bma085_get_slave_descr(void)
+{
+	return &bma085_descr;
+}
+
+/* Platform data for the MPU */
+struct bma085_mod_private_data {
+	struct i2c_client *client;
+	struct ext_slave_platform_data *pdata;
+};
+
+static unsigned short normal_i2c[] = { I2C_CLIENT_END };
+
+static int bma085_mod_probe(struct i2c_client *client,
+			   const struct i2c_device_id *devid)
+{
+	struct ext_slave_platform_data *pdata;
+	struct bma085_mod_private_data *private_data;
+	int result = 0;
+
+	dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name);
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		result = -ENODEV;
+		goto out_no_free;
+	}
+
+	pdata = client->dev.platform_data;
+	if (!pdata) {
+		dev_err(&client->adapter->dev,
+			"Missing platform data for slave %s\n", devid->name);
+		result = -EFAULT;
+		goto out_no_free;
+	}
+
+	private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
+	if (!private_data) {
+		result = -ENOMEM;
+		goto out_no_free;
+	}
+
+	i2c_set_clientdata(client, private_data);
+	private_data->client = client;
+	private_data->pdata = pdata;
+
+	result = inv_mpu_register_slave(THIS_MODULE, client, pdata,
+					bma085_get_slave_descr);
+	if (result) {
+		dev_err(&client->adapter->dev,
+			"Slave registration failed: %s, %d\n",
+			devid->name, result);
+		goto out_free_memory;
+	}
+
+	return result;
+
+out_free_memory:
+	kfree(private_data);
+out_no_free:
+	dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+	return result;
+
+}
+
+static int bma085_mod_remove(struct i2c_client *client)
+{
+	struct bma085_mod_private_data *private_data =
+		i2c_get_clientdata(client);
+
+	dev_dbg(&client->adapter->dev, "%s\n", __func__);
+
+	inv_mpu_unregister_slave(client, private_data->pdata,
+				bma085_get_slave_descr);
+
+	kfree(private_data);
+	return 0;
+}
+
+static const struct i2c_device_id bma085_mod_id[] = {
+	{ "bma085", PRESSURE_ID_BMA085 },
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, bma085_mod_id);
+
+static struct i2c_driver bma085_mod_driver = {
+	.class = I2C_CLASS_HWMON,
+	.probe = bma085_mod_probe,
+	.remove = bma085_mod_remove,
+	.id_table = bma085_mod_id,
+	.driver = {
+		   .owner = THIS_MODULE,
+		   .name = "bma085_mod",
+		   },
+	.address_list = normal_i2c,
+};
+
+static int __init bma085_mod_init(void)
+{
+	int res = i2c_add_driver(&bma085_mod_driver);
+	pr_info("%s: Probe name %s\n", __func__, "bma085_mod");
+	if (res)
+		pr_err("%s failed\n", __func__);
+	return res;
+}
+
+static void __exit bma085_mod_exit(void)
+{
+	pr_info("%s\n", __func__);
+	i2c_del_driver(&bma085_mod_driver);
+}
+
+module_init(bma085_mod_init);
+module_exit(bma085_mod_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Driver to integrate BMA085 sensor with the MPU");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("bma085_mod");
+/**
+ *  @}
+**/