* [PATCH] iio: orientation: Add BNO055 9-axis Absolute Orientation Sensor driver @ 2016-07-25 23:18 navin patidar 2016-07-26 8:07 ` Peter Meerwald-Stadler 0 siblings, 1 reply; 6+ messages in thread From: navin patidar @ 2016-07-25 23:18 UTC (permalink / raw) To: linux-iio, jic23; +Cc: knaack.h, lars, pmeerw, linux-kernel, navin patidar BNO055 provides the following motion sensors data: * Gyroscope * Accelerometer * Magnetometer * Absolute Orientation (Quaternion) Signed-off-by: navin patidar <navin.patidar@gmail.com> --- drivers/iio/orientation/Kconfig | 10 + drivers/iio/orientation/Makefile | 1 + drivers/iio/orientation/bno055.c | 422 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 433 insertions(+) create mode 100644 drivers/iio/orientation/bno055.c diff --git a/drivers/iio/orientation/Kconfig b/drivers/iio/orientation/Kconfig index e3aa1e5..9ac21ee 100644 --- a/drivers/iio/orientation/Kconfig +++ b/drivers/iio/orientation/Kconfig @@ -28,4 +28,14 @@ config HID_SENSOR_DEVICE_ROTATION device rotation. The output of a device rotation sensor is presented using quaternion format. +config BNO055 + tristate "BOSCH BNO055 Absolute Orientation" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for BOSCH BNO55 9-axis absolute orientation sensor + driver connected via I2C. + This driver can also be built as a module. If so, the module + will be called bno055. + endmenu diff --git a/drivers/iio/orientation/Makefile b/drivers/iio/orientation/Makefile index 4734dab..7d00037 100644 --- a/drivers/iio/orientation/Makefile +++ b/drivers/iio/orientation/Makefile @@ -5,3 +5,4 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_HID_SENSOR_INCLINOMETER_3D) += hid-sensor-incl-3d.o obj-$(CONFIG_HID_SENSOR_DEVICE_ROTATION) += hid-sensor-rotation.o +obj-$(CONFIG_BNO055) += bno055.o diff --git a/drivers/iio/orientation/bno055.c b/drivers/iio/orientation/bno055.c new file mode 100644 index 0000000..48b9e93 --- /dev/null +++ b/drivers/iio/orientation/bno055.c @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Driver for Bosch Sensortec BNO055 digital motion sensor. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) "bno055: " fmt + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/acpi.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + + +#define BNO055_CHIP_ID 0xA0 +#define REG_MAG_RADIUS_MSB 0x6A + +/* BNO055 configuration registers */ +#define REG_PWR_MODE 0x3E +#define REG_OPR_MODE 0x3D +#define REG_UNIT_SEL 0x3B +#define REG_AXIS_MAP_SIGN 0x42 +#define REG_AXIS_MAP_CONFIG 0x41 +#define REG_UNIT_SEL 0x3B +#define REG_SYS_TRIGGER 0x3F +#define BNO055_REG_PAGE_ID 0x07 +#define BNO055_REG_ID 0x00 + +/* BNO055 status registers */ +#define SYS_STATUS 0x39 +#define INT_STATUS 0x37 +#define CALIB_STATUS 0x35 + +/* BNO055 data registers */ + +#define GRV_DATA_Z_MSB 0x33 +#define GRV_DATA_Z_LSB 0x32 +#define GRV_DATA_Y_MSB 0x31 +#define GRV_DATA_Y_LSB 0x30 +#define GRV_DATA_X_MSB 0x2F +#define GRV_DATA_X_LSB 0x2E + +#define LIA_DATA_Z_MSB 0x2D +#define LIA_DATA_Z_LSB 0x2C +#define LIA_DATA_Y_MSB 0x2B +#define LIA_DATA_Y_LSB 0x2A +#define LIA_DATA_X_MSB 0x29 +#define LIA_DATA_X_LSB 0x28 + +#define QUA_DATA_Z_MSB 0x27 +#define QUA_DATA_Z_LSB 0x26 +#define QUA_DATA_Y_MSB 0x25 +#define QUA_DATA_Y_LSB 0x24 +#define QUA_DATA_X_MSB 0x23 +#define QUA_DATA_X_LSB 0x22 +#define QUA_DATA_W_MSB 0x21 +#define QUA_DATA_W_LSB 0x20 + +#define EUL_PITCH_MSB 0x1F +#define EUL_PITCH_LSB 0x1E +#define EUL_ROLL_MSB 0x1D +#define EUL_ROLL_LSB 0x1C +#define EUL_HEADING_MSB 0x1B +#define EUL_HEADING_LSB 0x1A + +#define GYR_DATA_Z_MSB 0x19 +#define GYR_DATA_Z_LSB 0x18 +#define GYR_DATA_Y_MSB 0x17 +#define GYR_DATA_Y_LSB 0x16 +#define GYR_DATA_X_MSB 0x15 +#define GYR_DATA_X_LSB 0x14 + +#define MAG_DATA_Z_MSB 0x13 +#define MAG_DATA_Z_LSB 0x12 +#define MAG_DATA_Y_MSB 0x11 +#define MAG_DATA_Y_LSB 0x10 +#define MAG_DATA_X_MSB 0x0F +#define MAG_DATA_X_LSB 0x0E + +#define ACC_DATA_Z_MSB 0x0D +#define ACC_DATA_Z_LSB 0x0C +#define ACC_DATA_Y_MSB 0x0B +#define ACC_DATA_Y_LSB 0x0A +#define ACC_DATA_X_MSB 0x09 +#define ACC_DATA_X_LSB 0x08 + +/* operation modes */ +#define FUSION_NDOF_MODE 0x0C + +/* power modes */ +#define NORMAL_MODE 0x00 +#define LOW_POWER_MODE BIT(0) +#define SUSPEND_MODE BIT(1) + +#define PROPER_CALIBRATION 0xFF + +#define BASE_REG 0x08 +#define SENSOR_AXIS_TO_REG(sensor,axis) (BASE_REG + (sensor * 6) + (axis * 2)) + +#define BNO055_CHANNEL(_type, _axis) { \ + .type = IIO_##_type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +struct bno055_data { + struct i2c_client *client; + struct mutex lock; + struct regmap *regmap; +}; + +enum bno055_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +enum {ACC=0, MAG=1, GYR=2, EUL=3, QUA=4}; + +static const struct iio_chan_spec bno055_channels[] = { + BNO055_CHANNEL(ANGL_VEL, X), + BNO055_CHANNEL(ANGL_VEL, Y), + BNO055_CHANNEL(ANGL_VEL, Z), + BNO055_CHANNEL(MAGN, X), + BNO055_CHANNEL(MAGN, Y), + BNO055_CHANNEL(MAGN, Z), + BNO055_CHANNEL(ACCEL, X), + BNO055_CHANNEL(ACCEL, Y), + BNO055_CHANNEL(ACCEL, Z), + { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_QUATERNION, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static bool bno055_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_PWR_MODE: + case REG_OPR_MODE: + case REG_UNIT_SEL: + case REG_AXIS_MAP_SIGN: + case REG_AXIS_MAP_CONFIG: + case REG_SYS_TRIGGER: + return true; + default: + return false; + }; +} + +static bool bno055_is_volatile_reg(struct device *dev, unsigned int reg) +{ + if ((reg >= ACC_DATA_X_LSB) && (reg <= QUA_DATA_Z_MSB)) + return true; + else + return false; +} + +static const struct regmap_config bno055_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = REG_MAG_RADIUS_MSB, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bno055_is_writeable_reg, + .volatile_reg = bno055_is_volatile_reg, +}; + + +static int read_bno055(struct bno055_data *data, int sensor, int axis, int *val) +{ + int ret; + __le16 raw_val; + int reg; + + reg = SENSOR_AXIS_TO_REG(sensor, axis); + + ret = regmap_bulk_read(data->regmap, reg, &raw_val, + sizeof(raw_val)); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading axis %d\n", axis); + return ret; + } + + *val = sign_extend32(le16_to_cpu(raw_val), 15); + return IIO_VAL_INT; +} + +/* Read raw quaternion values W,X,Y and Z. + * Raw values needs to be divied by the 16384 to get the exact quaternion values. + */ + +static int read_rot(struct bno055_data *data, int *vals, int *val_len) +{ + int ret, i; + __le16 raw_val[4]; + + ret = regmap_bulk_read(data->regmap, QUA_DATA_W_LSB, &raw_val, + sizeof(raw_val)); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading Orientation \n"); + return ret; + } + + for (i = 0; i < 4; ++i) + vals[i] = sign_extend32(le16_to_cpu(raw_val[i]), 15); + + *val_len = 4; + return IIO_VAL_INT_MULTIPLE; +} + +static int bno055_read_raw_multi(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int size, + int *val, int *val2, long mask) +{ + int ret = -EINVAL; + struct bno055_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ROT: + ret = read_rot(data, val, val2); + break; + case IIO_ANGL_VEL: + /* Gyroscope unit degrees per second */ + /* Raw value needs to be divied by the 16 to get the exact value.*/ + ret = read_bno055(data, GYR, chan->scan_index, val); + break; + case IIO_ACCEL: + /* Accelerometer Unit m/s2 */ + /* Raw value needs to be divied by the 100 to get the exact value.*/ + ret = read_bno055(data, ACC, chan->scan_index, val); + break; + case IIO_MAGN: + /* Magnetometer Unit microTesla */ + /* Raw value needs to be divied by the 16 to get the exact value. */ + ret = read_bno055(data, MAG, chan->scan_index, val); + break; + default: + ret = -EINVAL; + break; + } + break; + + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_info bno055_info = { + .driver_module = THIS_MODULE, + .read_raw_multi = &bno055_read_raw_multi, +}; + + +static int bno055_chip_init(struct bno055_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, REG_PWR_MODE, + NORMAL_MODE); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to write power mode register\n"); + return ret; + } + + ret = regmap_write(data->regmap, REG_OPR_MODE, + FUSION_NDOF_MODE); + if (ret < 0) + dev_err(&data->client->dev, + "failed to write operation mode register\n"); + + return ret; +} + +static ssize_t show_calibration_status(struct device *dev, struct device_attribute *attr, + char *buf){ + struct bno055_data *data; + int ret; + unsigned int calib_stat; + + data = dev->driver_data; + + ret = regmap_read(data->regmap, CALIB_STATUS, &calib_stat); + + if (ret){ + dev_err(dev, "Failed to read calibration status.\n"); + return ret; + } + + if (calib_stat != PROPER_CALIBRATION) + dev_info(dev, "bad calibration. expected %x got %x\n", + PROPER_CALIBRATION, calib_stat); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", calib_stat); +} + +static DEVICE_ATTR(calib_status, 0444, show_calibration_status, NULL); + +static int bno055_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct iio_dev *indio_dev; + struct bno055_data *data; + unsigned int chip_id; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + mutex_init(&data->lock); + data->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->channels = bno055_channels; + indio_dev->num_channels = ARRAY_SIZE(bno055_channels); + indio_dev->info = &bno055_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + data->regmap = devm_regmap_init_i2c(client, &bno055_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "Failed to allocate register map.\n"); + return PTR_ERR(data->regmap); + } + + indio_dev->dev.driver_data = data; + i2c_set_clientdata(client, indio_dev); + + ret = regmap_read(data->regmap, BNO055_REG_ID, &chip_id); + if (ret < 0) + return ret; + if (chip_id != BNO055_CHIP_ID) { + dev_err(&client->dev, "bad chip id. expected %x got %x\n", + BNO055_CHIP_ID, chip_id); + return -EINVAL; + } + + ret = bno055_chip_init(data); + if (ret < 0) + return ret; + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret){ + dev_err(&client->dev, "failed to register IIO device.\n"); + return ret; + } + + ret = device_create_file(&indio_dev->dev, &dev_attr_calib_status); + if (ret){ + dev_err(&client->dev, "failed to create sysfs entry.\n"); + devm_iio_device_unregister(&client->dev, indio_dev); + return ret; + } + + return ret; +} + +static int bno055_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + device_remove_file(&indio_dev->dev, &dev_attr_calib_status); + devm_iio_device_unregister(&client->dev, indio_dev); + return 0; +} + +static const struct acpi_device_id bno055_acpi_match[] = { + {"bno055", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, bno055_acpi_match); + +static const struct i2c_device_id bno055_id[] = { + {"bno055", 0}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, bno055_id); + +static struct i2c_driver bno055_driver = { + .driver = { + .name = "bno055", + .acpi_match_table = ACPI_PTR(bno055_acpi_match), + }, + .probe = bno055_probe, + .remove = bno055_remove, + .id_table = bno055_id, +}; +module_i2c_driver(bno055_driver); + +MODULE_AUTHOR("navin patidar <navin.patidar@gmail.com>"); +MODULE_DESCRIPTION("Driver for Bosch Sensortec BNO055 orientation sensor"); +MODULE_LICENSE("GPL v2"); -- 1.9.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH] iio: orientation: Add BNO055 9-axis Absolute Orientation Sensor driver 2016-07-25 23:18 [PATCH] iio: orientation: Add BNO055 9-axis Absolute Orientation Sensor driver navin patidar @ 2016-07-26 8:07 ` Peter Meerwald-Stadler 2016-07-26 18:39 ` navin patidar 0 siblings, 1 reply; 6+ messages in thread From: Peter Meerwald-Stadler @ 2016-07-26 8:07 UTC (permalink / raw) To: navin patidar; +Cc: linux-iio, jic23, knaack.h, lars, linux-kernel > BNO055 provides the following motion sensors data: > > * Gyroscope > * Accelerometer > * Magnetometer > * Absolute Orientation (Quaternion) comments below > Signed-off-by: navin patidar <navin.patidar@gmail.com> > --- > drivers/iio/orientation/Kconfig | 10 + > drivers/iio/orientation/Makefile | 1 + > drivers/iio/orientation/bno055.c | 422 +++++++++++++++++++++++++++++++++++++++ > 3 files changed, 433 insertions(+) > create mode 100644 drivers/iio/orientation/bno055.c > > diff --git a/drivers/iio/orientation/Kconfig b/drivers/iio/orientation/Kconfig > index e3aa1e5..9ac21ee 100644 > --- a/drivers/iio/orientation/Kconfig > +++ b/drivers/iio/orientation/Kconfig > @@ -28,4 +28,14 @@ config HID_SENSOR_DEVICE_ROTATION > device rotation. The output of a device rotation sensor > is presented using quaternion format. > > +config BNO055 > + tristate "BOSCH BNO055 Absolute Orientation" > + depends on I2C > + select REGMAP_I2C > + help > + Say yes here to build support for BOSCH BNO55 9-axis absolute orientation sensor > + driver connected via I2C. > + This driver can also be built as a module. If so, the module > + will be called bno055. > + > endmenu > diff --git a/drivers/iio/orientation/Makefile b/drivers/iio/orientation/Makefile > index 4734dab..7d00037 100644 > --- a/drivers/iio/orientation/Makefile > +++ b/drivers/iio/orientation/Makefile > @@ -5,3 +5,4 @@ > # When adding new entries keep the list in alphabetical order > obj-$(CONFIG_HID_SENSOR_INCLINOMETER_3D) += hid-sensor-incl-3d.o > obj-$(CONFIG_HID_SENSOR_DEVICE_ROTATION) += hid-sensor-rotation.o > +obj-$(CONFIG_BNO055) += bno055.o > diff --git a/drivers/iio/orientation/bno055.c b/drivers/iio/orientation/bno055.c > new file mode 100644 > index 0000000..48b9e93 > --- /dev/null > +++ b/drivers/iio/orientation/bno055.c > @@ -0,0 +1,422 @@ > +/* > + * Copyright (c) 2016 Intel Corporation > + * > + * Driver for Bosch Sensortec BNO055 digital motion sensor. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * I find it useful to state the 7-bit i2c chip address here > + */ > + > +#define pr_fmt(fmt) "bno055: " fmt > + > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/acpi.h> > +#include <linux/regmap.h> > +#include <linux/iio/iio.h> > +#include <linux/iio/sysfs.h> > + drop one newline > + > +#define BNO055_CHIP_ID 0xA0 > +#define REG_MAG_RADIUS_MSB 0x6A please consistently prefix with BNO055_ > + > +/* BNO055 configuration registers */ > +#define REG_PWR_MODE 0x3E > +#define REG_OPR_MODE 0x3D > +#define REG_UNIT_SEL 0x3B > +#define REG_AXIS_MAP_SIGN 0x42 > +#define REG_AXIS_MAP_CONFIG 0x41 > +#define REG_UNIT_SEL 0x3B > +#define REG_SYS_TRIGGER 0x3F > +#define BNO055_REG_PAGE_ID 0x07 > +#define BNO055_REG_ID 0x00 > + > +/* BNO055 status registers */ > +#define SYS_STATUS 0x39 > +#define INT_STATUS 0x37 > +#define CALIB_STATUS 0x35 > + > +/* BNO055 data registers */ drop newline > + > +#define GRV_DATA_Z_MSB 0x33 > +#define GRV_DATA_Z_LSB 0x32 > +#define GRV_DATA_Y_MSB 0x31 > +#define GRV_DATA_Y_LSB 0x30 > +#define GRV_DATA_X_MSB 0x2F > +#define GRV_DATA_X_LSB 0x2E do we really need all those register #defines? they are not used and noone wants to read just the LSB... > + > +#define LIA_DATA_Z_MSB 0x2D > +#define LIA_DATA_Z_LSB 0x2C > +#define LIA_DATA_Y_MSB 0x2B > +#define LIA_DATA_Y_LSB 0x2A > +#define LIA_DATA_X_MSB 0x29 > +#define LIA_DATA_X_LSB 0x28 > + > +#define QUA_DATA_Z_MSB 0x27 > +#define QUA_DATA_Z_LSB 0x26 > +#define QUA_DATA_Y_MSB 0x25 > +#define QUA_DATA_Y_LSB 0x24 > +#define QUA_DATA_X_MSB 0x23 > +#define QUA_DATA_X_LSB 0x22 > +#define QUA_DATA_W_MSB 0x21 > +#define QUA_DATA_W_LSB 0x20 > + > +#define EUL_PITCH_MSB 0x1F > +#define EUL_PITCH_LSB 0x1E > +#define EUL_ROLL_MSB 0x1D > +#define EUL_ROLL_LSB 0x1C > +#define EUL_HEADING_MSB 0x1B > +#define EUL_HEADING_LSB 0x1A > + > +#define GYR_DATA_Z_MSB 0x19 > +#define GYR_DATA_Z_LSB 0x18 > +#define GYR_DATA_Y_MSB 0x17 > +#define GYR_DATA_Y_LSB 0x16 > +#define GYR_DATA_X_MSB 0x15 > +#define GYR_DATA_X_LSB 0x14 > + > +#define MAG_DATA_Z_MSB 0x13 > +#define MAG_DATA_Z_LSB 0x12 > +#define MAG_DATA_Y_MSB 0x11 > +#define MAG_DATA_Y_LSB 0x10 > +#define MAG_DATA_X_MSB 0x0F > +#define MAG_DATA_X_LSB 0x0E > + > +#define ACC_DATA_Z_MSB 0x0D > +#define ACC_DATA_Z_LSB 0x0C > +#define ACC_DATA_Y_MSB 0x0B > +#define ACC_DATA_Y_LSB 0x0A > +#define ACC_DATA_X_MSB 0x09 > +#define ACC_DATA_X_LSB 0x08 > + > +/* operation modes */ > +#define FUSION_NDOF_MODE 0x0C > + > +/* power modes */ > +#define NORMAL_MODE 0x00 > +#define LOW_POWER_MODE BIT(0) > +#define SUSPEND_MODE BIT(1) > + > +#define PROPER_CALIBRATION 0xFF > + > +#define BASE_REG 0x08 > +#define SENSOR_AXIS_TO_REG(sensor,axis) (BASE_REG + (sensor * 6) + (axis * 2)) > + > +#define BNO055_CHANNEL(_type, _axis) { \ > + .type = IIO_##_type, \ > + .modified = 1, \ > + .channel2 = IIO_MOD_##_axis, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ in what unit is the data returned; please check with IIO ABI documentation I think you will likely need _SCALE for some channels > + .scan_index = AXIS_##_axis, \ .scan_index is a continuous counter over ALL channels, not just for one channel type the driver does not support buffered reads, so not scan_type/scan_index is needed anyway > + .scan_type = { \ > + .sign = 's', \ > + .realbits = 16, \ > + .storagebits = 16, \ > + .endianness = IIO_LE, \ > + }, \ > +} > + > +struct bno055_data { > + struct i2c_client *client; > + struct mutex lock; > + struct regmap *regmap; > +}; > + > +enum bno055_axis { > + AXIS_X, > + AXIS_Y, > + AXIS_Z, > +}; > + > +enum {ACC=0, MAG=1, GYR=2, EUL=3, QUA=4}; prefix! > + > +static const struct iio_chan_spec bno055_channels[] = { > + BNO055_CHANNEL(ANGL_VEL, X), > + BNO055_CHANNEL(ANGL_VEL, Y), > + BNO055_CHANNEL(ANGL_VEL, Z), > + BNO055_CHANNEL(MAGN, X), > + BNO055_CHANNEL(MAGN, Y), > + BNO055_CHANNEL(MAGN, Z), > + BNO055_CHANNEL(ACCEL, X), > + BNO055_CHANNEL(ACCEL, Y), > + BNO055_CHANNEL(ACCEL, Z), > + { > + .type = IIO_ROT, > + .modified = 1, > + .channel2 = IIO_MOD_QUATERNION, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), > + } > +}; > + > +static bool bno055_is_writeable_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case REG_PWR_MODE: > + case REG_OPR_MODE: > + case REG_UNIT_SEL: > + case REG_AXIS_MAP_SIGN: > + case REG_AXIS_MAP_CONFIG: > + case REG_SYS_TRIGGER: > + return true; > + default: > + return false; > + }; > +} > + > +static bool bno055_is_volatile_reg(struct device *dev, unsigned int reg) > +{ > + if ((reg >= ACC_DATA_X_LSB) && (reg <= QUA_DATA_Z_MSB)) > + return true; > + else > + return false; > +} > + > +static const struct regmap_config bno055_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + > + .max_register = REG_MAG_RADIUS_MSB, > + .cache_type = REGCACHE_RBTREE, > + > + .writeable_reg = bno055_is_writeable_reg, > + .volatile_reg = bno055_is_volatile_reg, > +}; > + drop newline > + > +static int read_bno055(struct bno055_data *data, int sensor, int axis, int *val) bno055 prefix please > +{ > + int ret; > + __le16 raw_val; > + int reg; > + > + reg = SENSOR_AXIS_TO_REG(sensor, axis); you can use .address in channel spec for that > + > + ret = regmap_bulk_read(data->regmap, reg, &raw_val, > + sizeof(raw_val)); > + if (ret < 0) { > + dev_err(&data->client->dev, "Error reading axis %d\n", axis); > + return ret; > + } > + > + *val = sign_extend32(le16_to_cpu(raw_val), 15); > + return IIO_VAL_INT; > +} > + > +/* Read raw quaternion values W,X,Y and Z. > + * Raw values needs to be divied by the 16384 to get the exact quaternion values. > + */ > + > +static int read_rot(struct bno055_data *data, int *vals, int *val_len) > +{ > + int ret, i; > + __le16 raw_val[4]; > + > + ret = regmap_bulk_read(data->regmap, QUA_DATA_W_LSB, &raw_val, > + sizeof(raw_val)); > + if (ret < 0) { > + dev_err(&data->client->dev, "Error reading Orientation \n"); lowercase orientation delete space before \n > + return ret; > + } > + > + for (i = 0; i < 4; ++i) > + vals[i] = sign_extend32(le16_to_cpu(raw_val[i]), 15); > + > + *val_len = 4; > + return IIO_VAL_INT_MULTIPLE; > +} > + > +static int bno055_read_raw_multi(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int size, > + int *val, int *val2, long mask) > +{ > + int ret = -EINVAL; > + struct bno055_data *data = iio_priv(indio_dev); > + > + mutex_lock(&data->lock); > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + switch (chan->type) { > + case IIO_ROT: > + ret = read_rot(data, val, val2); > + break; > + case IIO_ANGL_VEL: > + /* Gyroscope unit degrees per second */ > + /* Raw value needs to be divied by the 16 to get the exact value.*/ comment style, not a proper multi-line comment use _SCALE, it is not about 'exact' but correct scaling/unit typo: divided wording: divided by 16 > + ret = read_bno055(data, GYR, chan->scan_index, val); > + break; > + case IIO_ACCEL: > + /* Accelerometer Unit m/s2 */ > + /* Raw value needs to be divied by the 100 to get the exact value.*/ > + ret = read_bno055(data, ACC, chan->scan_index, val); > + break; > + case IIO_MAGN: > + /* Magnetometer Unit microTesla */ > + /* Raw value needs to be divied by the 16 to get the exact value. */ > + ret = read_bno055(data, MAG, chan->scan_index, val); > + break; > + default: > + ret = -EINVAL; > + break; > + } > + break; > + > + default: > + ret = -EINVAL; > + break; > + } > + > + mutex_unlock(&data->lock); > + > + return ret; > +} > + > +static const struct iio_info bno055_info = { > + .driver_module = THIS_MODULE, > + .read_raw_multi = &bno055_read_raw_multi, > +}; > + drop newline > + > +static int bno055_chip_init(struct bno055_data *data) > +{ > + int ret; > + > + ret = regmap_write(data->regmap, REG_PWR_MODE, > + NORMAL_MODE); > + if (ret < 0) { > + dev_err(&data->client->dev, > + "failed to write power mode register\n"); > + return ret; > + } > + > + ret = regmap_write(data->regmap, REG_OPR_MODE, > + FUSION_NDOF_MODE); > + if (ret < 0) > + dev_err(&data->client->dev, > + "failed to write operation mode register\n"); > + > + return ret; > +} > + > +static ssize_t show_calibration_status(struct device *dev, struct device_attribute *attr, > + char *buf){ this is private API which needs to be documented > + struct bno055_data *data; > + int ret; > + unsigned int calib_stat; > + > + data = dev->driver_data; > + > + ret = regmap_read(data->regmap, CALIB_STATUS, &calib_stat); > + > + if (ret){ space before { > + dev_err(dev, "Failed to read calibration status.\n"); > + return ret; > + } > + > + if (calib_stat != PROPER_CALIBRATION) > + dev_info(dev, "bad calibration. expected %x got %x\n", two spaces before 'expected' > + PROPER_CALIBRATION, calib_stat); > + > + return scnprintf(buf, PAGE_SIZE, "0x%x\n", calib_stat); > +} > + > +static DEVICE_ATTR(calib_status, 0444, show_calibration_status, NULL); > + > +static int bno055_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + int ret; > + struct iio_dev *indio_dev; > + struct bno055_data *data; > + unsigned int chip_id; > + > + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); > + if (!indio_dev) > + return -ENOMEM; > + > + data = iio_priv(indio_dev); > + mutex_init(&data->lock); > + data->client = client; > + > + indio_dev->dev.parent = &client->dev; > + indio_dev->name = id->name; > + indio_dev->channels = bno055_channels; > + indio_dev->num_channels = ARRAY_SIZE(bno055_channels); > + indio_dev->info = &bno055_info; > + indio_dev->modes = INDIO_DIRECT_MODE; > + > + data->regmap = devm_regmap_init_i2c(client, &bno055_regmap_config); > + if (IS_ERR(data->regmap)) { > + dev_err(&client->dev, "Failed to allocate register map.\n"); > + return PTR_ERR(data->regmap); > + } > + > + indio_dev->dev.driver_data = data; > + i2c_set_clientdata(client, indio_dev); > + > + ret = regmap_read(data->regmap, BNO055_REG_ID, &chip_id); > + if (ret < 0) > + return ret; > + if (chip_id != BNO055_CHIP_ID) { > + dev_err(&client->dev, "bad chip id. expected %x got %x\n", > + BNO055_CHIP_ID, chip_id); > + return -EINVAL; > + } > + > + ret = bno055_chip_init(data); > + if (ret < 0) > + return ret; > + > + ret = devm_iio_device_register(&client->dev, indio_dev); > + if (ret){ space before { > + dev_err(&client->dev, "failed to register IIO device.\n"); > + return ret; > + } > + > + ret = device_create_file(&indio_dev->dev, &dev_attr_calib_status); needs to be done BEFORE devm_iio_device_register() IIO has specific mechanisms to create custom sysfs entries... > + if (ret){ space before { > + dev_err(&client->dev, "failed to create sysfs entry.\n"); > + devm_iio_device_unregister(&client->dev, indio_dev); no need to call devm_iio_device_unregister > + return ret; > + } > + > + return ret; > +} > + > +static int bno055_remove(struct i2c_client *client) > +{ > + struct iio_dev *indio_dev = i2c_get_clientdata(client); > + > + device_remove_file(&indio_dev->dev, &dev_attr_calib_status); > + devm_iio_device_unregister(&client->dev, indio_dev); > + return 0; > +} > + > +static const struct acpi_device_id bno055_acpi_match[] = { > + {"bno055", 0}, > + { }, > +}; > +MODULE_DEVICE_TABLE(acpi, bno055_acpi_match); > + > +static const struct i2c_device_id bno055_id[] = { > + {"bno055", 0}, > + { }, > +}; > +MODULE_DEVICE_TABLE(i2c, bno055_id); > + > +static struct i2c_driver bno055_driver = { > + .driver = { > + .name = "bno055", > + .acpi_match_table = ACPI_PTR(bno055_acpi_match), > + }, > + .probe = bno055_probe, > + .remove = bno055_remove, > + .id_table = bno055_id, > +}; > +module_i2c_driver(bno055_driver); > + > +MODULE_AUTHOR("navin patidar <navin.patidar@gmail.com>"); > +MODULE_DESCRIPTION("Driver for Bosch Sensortec BNO055 orientation sensor"); > +MODULE_LICENSE("GPL v2"); > -- Peter Meerwald-Stadler +43-664-2444418 (mobile) ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] iio: orientation: Add BNO055 9-axis Absolute Orientation Sensor driver 2016-07-26 8:07 ` Peter Meerwald-Stadler @ 2016-07-26 18:39 ` navin patidar 2016-07-26 18:46 ` Peter Meerwald-Stadler 0 siblings, 1 reply; 6+ messages in thread From: navin patidar @ 2016-07-26 18:39 UTC (permalink / raw) To: Peter Meerwald-Stadler; +Cc: linux-iio, jic23, knaack.h, lars, linux-kernel [-- Attachment #1: Type: text/plain, Size: 17575 bytes --] Hi Peter, Thanks for reviewing the patch. I will send the updated patch. regards, --navin-patidar On Tue, Jul 26, 2016 at 1:07 AM, Peter Meerwald-Stadler <pmeerw@pmeerw.net> wrote: > > > BNO055 provides the following motion sensors data: > > > > * Gyroscope > > * Accelerometer > > * Magnetometer > > * Absolute Orientation (Quaternion) > > comments below > > > Signed-off-by: navin patidar <navin.patidar@gmail.com> > > --- > > drivers/iio/orientation/Kconfig | 10 + > > drivers/iio/orientation/Makefile | 1 + > > drivers/iio/orientation/bno055.c | 422 > +++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 433 insertions(+) > > create mode 100644 drivers/iio/orientation/bno055.c > > > > diff --git a/drivers/iio/orientation/Kconfig > b/drivers/iio/orientation/Kconfig > > index e3aa1e5..9ac21ee 100644 > > --- a/drivers/iio/orientation/Kconfig > > +++ b/drivers/iio/orientation/Kconfig > > @@ -28,4 +28,14 @@ config HID_SENSOR_DEVICE_ROTATION > > device rotation. The output of a device rotation sensor > > is presented using quaternion format. > > > > +config BNO055 > > + tristate "BOSCH BNO055 Absolute Orientation" > > + depends on I2C > > + select REGMAP_I2C > > + help > > + Say yes here to build support for BOSCH BNO55 9-axis absolute > orientation sensor > > + driver connected via I2C. > > + This driver can also be built as a module. If so, the module > > + will be called bno055. > > + > > endmenu > > diff --git a/drivers/iio/orientation/Makefile > b/drivers/iio/orientation/Makefile > > index 4734dab..7d00037 100644 > > --- a/drivers/iio/orientation/Makefile > > +++ b/drivers/iio/orientation/Makefile > > @@ -5,3 +5,4 @@ > > # When adding new entries keep the list in alphabetical order > > obj-$(CONFIG_HID_SENSOR_INCLINOMETER_3D) += hid-sensor-incl-3d.o > > obj-$(CONFIG_HID_SENSOR_DEVICE_ROTATION) += hid-sensor-rotation.o > > +obj-$(CONFIG_BNO055) += bno055.o > > diff --git a/drivers/iio/orientation/bno055.c > b/drivers/iio/orientation/bno055.c > > new file mode 100644 > > index 0000000..48b9e93 > > --- /dev/null > > +++ b/drivers/iio/orientation/bno055.c > > @@ -0,0 +1,422 @@ > > +/* > > + * Copyright (c) 2016 Intel Corporation > > + * > > + * Driver for Bosch Sensortec BNO055 digital motion sensor. > > + * > > + * This program is free software; you can redistribute it and/or modify > > + * it under the terms of the GNU General Public License version 2 as > > + * published by the Free Software Foundation. > > + * > > I find it useful to state the 7-bit i2c chip address here > > > + */ > > + > > +#define pr_fmt(fmt) "bno055: " fmt > > + > > +#include <linux/module.h> > > +#include <linux/i2c.h> > > +#include <linux/acpi.h> > > +#include <linux/regmap.h> > > +#include <linux/iio/iio.h> > > +#include <linux/iio/sysfs.h> > > + > > drop one newline > > > + > > +#define BNO055_CHIP_ID 0xA0 > > +#define REG_MAG_RADIUS_MSB 0x6A > > please consistently prefix with BNO055_ > > > + > > +/* BNO055 configuration registers */ > > +#define REG_PWR_MODE 0x3E > > +#define REG_OPR_MODE 0x3D > > +#define REG_UNIT_SEL 0x3B > > +#define REG_AXIS_MAP_SIGN 0x42 > > +#define REG_AXIS_MAP_CONFIG 0x41 > > +#define REG_UNIT_SEL 0x3B > > +#define REG_SYS_TRIGGER 0x3F > > +#define BNO055_REG_PAGE_ID 0x07 > > +#define BNO055_REG_ID 0x00 > > + > > +/* BNO055 status registers */ > > +#define SYS_STATUS 0x39 > > +#define INT_STATUS 0x37 > > +#define CALIB_STATUS 0x35 > > + > > +/* BNO055 data registers */ > > drop newline > > > + > > +#define GRV_DATA_Z_MSB 0x33 > > +#define GRV_DATA_Z_LSB 0x32 > > +#define GRV_DATA_Y_MSB 0x31 > > +#define GRV_DATA_Y_LSB 0x30 > > +#define GRV_DATA_X_MSB 0x2F > > +#define GRV_DATA_X_LSB 0x2E > > do we really need all those register #defines? > they are not used and noone wants to read just the LSB... > > > + > > +#define LIA_DATA_Z_MSB 0x2D > > +#define LIA_DATA_Z_LSB 0x2C > > +#define LIA_DATA_Y_MSB 0x2B > > +#define LIA_DATA_Y_LSB 0x2A > > +#define LIA_DATA_X_MSB 0x29 > > +#define LIA_DATA_X_LSB 0x28 > > + > > +#define QUA_DATA_Z_MSB 0x27 > > +#define QUA_DATA_Z_LSB 0x26 > > +#define QUA_DATA_Y_MSB 0x25 > > +#define QUA_DATA_Y_LSB 0x24 > > +#define QUA_DATA_X_MSB 0x23 > > +#define QUA_DATA_X_LSB 0x22 > > +#define QUA_DATA_W_MSB 0x21 > > +#define QUA_DATA_W_LSB 0x20 > > + > > +#define EUL_PITCH_MSB 0x1F > > +#define EUL_PITCH_LSB 0x1E > > +#define EUL_ROLL_MSB 0x1D > > +#define EUL_ROLL_LSB 0x1C > > +#define EUL_HEADING_MSB 0x1B > > +#define EUL_HEADING_LSB 0x1A > > + > > +#define GYR_DATA_Z_MSB 0x19 > > +#define GYR_DATA_Z_LSB 0x18 > > +#define GYR_DATA_Y_MSB 0x17 > > +#define GYR_DATA_Y_LSB 0x16 > > +#define GYR_DATA_X_MSB 0x15 > > +#define GYR_DATA_X_LSB 0x14 > > + > > +#define MAG_DATA_Z_MSB 0x13 > > +#define MAG_DATA_Z_LSB 0x12 > > +#define MAG_DATA_Y_MSB 0x11 > > +#define MAG_DATA_Y_LSB 0x10 > > +#define MAG_DATA_X_MSB 0x0F > > +#define MAG_DATA_X_LSB 0x0E > > + > > +#define ACC_DATA_Z_MSB 0x0D > > +#define ACC_DATA_Z_LSB 0x0C > > +#define ACC_DATA_Y_MSB 0x0B > > +#define ACC_DATA_Y_LSB 0x0A > > +#define ACC_DATA_X_MSB 0x09 > > +#define ACC_DATA_X_LSB 0x08 > > + > > +/* operation modes */ > > +#define FUSION_NDOF_MODE 0x0C > > + > > +/* power modes */ > > +#define NORMAL_MODE 0x00 > > +#define LOW_POWER_MODE BIT(0) > > +#define SUSPEND_MODE BIT(1) > > + > > +#define PROPER_CALIBRATION 0xFF > > + > > +#define BASE_REG 0x08 > > +#define SENSOR_AXIS_TO_REG(sensor,axis) (BASE_REG + (sensor * 6) + > (axis * 2)) > > + > > +#define BNO055_CHANNEL(_type, _axis) { > \ > > + .type = IIO_##_type, \ > > + .modified = 1, \ > > + .channel2 = IIO_MOD_##_axis, \ > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ > > in what unit is the data returned; please check with IIO ABI documentation > I think you will likely need _SCALE for some channels > > > + .scan_index = AXIS_##_axis, \ > > .scan_index is a continuous counter over ALL channels, not just for one > channel type > > the driver does not support buffered reads, so not scan_type/scan_index is > needed anyway > > > + .scan_type = { \ > > + .sign = 's', \ > > + .realbits = 16, \ > > + .storagebits = 16, \ > > + .endianness = IIO_LE, \ > > + }, \ > > +} > > + > > +struct bno055_data { > > + struct i2c_client *client; > > + struct mutex lock; > > + struct regmap *regmap; > > +}; > > + > > +enum bno055_axis { > > + AXIS_X, > > + AXIS_Y, > > + AXIS_Z, > > +}; > > + > > +enum {ACC=0, MAG=1, GYR=2, EUL=3, QUA=4}; > > prefix! > > > + > > +static const struct iio_chan_spec bno055_channels[] = { > > + BNO055_CHANNEL(ANGL_VEL, X), > > + BNO055_CHANNEL(ANGL_VEL, Y), > > + BNO055_CHANNEL(ANGL_VEL, Z), > > + BNO055_CHANNEL(MAGN, X), > > + BNO055_CHANNEL(MAGN, Y), > > + BNO055_CHANNEL(MAGN, Z), > > + BNO055_CHANNEL(ACCEL, X), > > + BNO055_CHANNEL(ACCEL, Y), > > + BNO055_CHANNEL(ACCEL, Z), > > + { > > + .type = IIO_ROT, > > + .modified = 1, > > + .channel2 = IIO_MOD_QUATERNION, > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), > > + } > > +}; > > + > > +static bool bno055_is_writeable_reg(struct device *dev, unsigned int > reg) > > +{ > > + switch (reg) { > > + case REG_PWR_MODE: > > + case REG_OPR_MODE: > > + case REG_UNIT_SEL: > > + case REG_AXIS_MAP_SIGN: > > + case REG_AXIS_MAP_CONFIG: > > + case REG_SYS_TRIGGER: > > + return true; > > + default: > > + return false; > > + }; > > +} > > + > > +static bool bno055_is_volatile_reg(struct device *dev, unsigned int reg) > > +{ > > + if ((reg >= ACC_DATA_X_LSB) && (reg <= QUA_DATA_Z_MSB)) > > + return true; > > + else > > + return false; > > +} > > + > > +static const struct regmap_config bno055_regmap_config = { > > + .reg_bits = 8, > > + .val_bits = 8, > > + > > + .max_register = REG_MAG_RADIUS_MSB, > > + .cache_type = REGCACHE_RBTREE, > > + > > + .writeable_reg = bno055_is_writeable_reg, > > + .volatile_reg = bno055_is_volatile_reg, > > +}; > > + > > drop newline > > > + > > +static int read_bno055(struct bno055_data *data, int sensor, int axis, > int *val) > > bno055 prefix please > > > +{ > > + int ret; > > + __le16 raw_val; > > + int reg; > > + > > + reg = SENSOR_AXIS_TO_REG(sensor, axis); > > you can use .address in channel spec for that > > > + > > + ret = regmap_bulk_read(data->regmap, reg, &raw_val, > > + sizeof(raw_val)); > > + if (ret < 0) { > > + dev_err(&data->client->dev, "Error reading axis %d\n", > axis); > > + return ret; > > + } > > + > > + *val = sign_extend32(le16_to_cpu(raw_val), 15); > > + return IIO_VAL_INT; > > +} > > + > > +/* Read raw quaternion values W,X,Y and Z. > > + * Raw values needs to be divied by the 16384 to get the exact > quaternion values. > > + */ > > + > > +static int read_rot(struct bno055_data *data, int *vals, int *val_len) > > +{ > > + int ret, i; > > + __le16 raw_val[4]; > > + > > + ret = regmap_bulk_read(data->regmap, QUA_DATA_W_LSB, &raw_val, > > + sizeof(raw_val)); > > + if (ret < 0) { > > + dev_err(&data->client->dev, "Error reading Orientation > \n"); > > lowercase orientation > delete space before \n > > > + return ret; > > + } > > + > > + for (i = 0; i < 4; ++i) > > + vals[i] = sign_extend32(le16_to_cpu(raw_val[i]), 15); > > + > > + *val_len = 4; > > + return IIO_VAL_INT_MULTIPLE; > > +} > > + > > +static int bno055_read_raw_multi(struct iio_dev *indio_dev, > > + struct iio_chan_spec const *chan, int size, > > + int *val, int *val2, long mask) > > +{ > > + int ret = -EINVAL; > > + struct bno055_data *data = iio_priv(indio_dev); > > + > > + mutex_lock(&data->lock); > > + > > + switch (mask) { > > + case IIO_CHAN_INFO_RAW: > > + switch (chan->type) { > > + case IIO_ROT: > > + ret = read_rot(data, val, val2); > > + break; > > + case IIO_ANGL_VEL: > > + /* Gyroscope unit degrees per second */ > > + /* Raw value needs to be divied by the 16 to get > the exact value.*/ > > comment style, not a proper multi-line comment > use _SCALE, it is not about 'exact' but correct scaling/unit > typo: divided > wording: divided by 16 > > > + ret = read_bno055(data, GYR, chan->scan_index, > val); > > + break; > > + case IIO_ACCEL: > > + /* Accelerometer Unit m/s2 */ > > + /* Raw value needs to be divied by the 100 to get > the exact value.*/ > > + ret = read_bno055(data, ACC, chan->scan_index, > val); > > + break; > > + case IIO_MAGN: > > + /* Magnetometer Unit microTesla */ > > + /* Raw value needs to be divied by the 16 to get > the exact value. */ > > + ret = read_bno055(data, MAG, chan->scan_index, > val); > > + break; > > + default: > > + ret = -EINVAL; > > + break; > > + } > > + break; > > + > > + default: > > + ret = -EINVAL; > > + break; > > + } > > + > > + mutex_unlock(&data->lock); > > + > > + return ret; > > +} > > + > > +static const struct iio_info bno055_info = { > > + .driver_module = THIS_MODULE, > > + .read_raw_multi = &bno055_read_raw_multi, > > +}; > > + > > drop newline > > > + > > +static int bno055_chip_init(struct bno055_data *data) > > +{ > > + int ret; > > + > > + ret = regmap_write(data->regmap, REG_PWR_MODE, > > + NORMAL_MODE); > > + if (ret < 0) { > > + dev_err(&data->client->dev, > > + "failed to write power mode register\n"); > > + return ret; > > + } > > + > > + ret = regmap_write(data->regmap, REG_OPR_MODE, > > + FUSION_NDOF_MODE); > > + if (ret < 0) > > + dev_err(&data->client->dev, > > + "failed to write operation mode register\n"); > > + > > + return ret; > > +} > > + > > +static ssize_t show_calibration_status(struct device *dev, struct > device_attribute *attr, > > + char *buf){ > > this is private API which needs to be documented > > > + struct bno055_data *data; > > + int ret; > > + unsigned int calib_stat; > > + > > + data = dev->driver_data; > > + > > + ret = regmap_read(data->regmap, CALIB_STATUS, &calib_stat); > > + > > + if (ret){ > > space before { > > > + dev_err(dev, "Failed to read calibration status.\n"); > > + return ret; > > + } > > + > > + if (calib_stat != PROPER_CALIBRATION) > > + dev_info(dev, "bad calibration. expected %x got %x\n", > > two spaces before 'expected' > > > + PROPER_CALIBRATION, calib_stat); > > + > > + return scnprintf(buf, PAGE_SIZE, "0x%x\n", calib_stat); > > +} > > + > > +static DEVICE_ATTR(calib_status, 0444, show_calibration_status, NULL); > > + > > +static int bno055_probe(struct i2c_client *client, > > + const struct i2c_device_id *id) > > +{ > > + int ret; > > + struct iio_dev *indio_dev; > > + struct bno055_data *data; > > + unsigned int chip_id; > > + > > + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); > > + if (!indio_dev) > > + return -ENOMEM; > > + > > + data = iio_priv(indio_dev); > > + mutex_init(&data->lock); > > + data->client = client; > > + > > + indio_dev->dev.parent = &client->dev; > > + indio_dev->name = id->name; > > + indio_dev->channels = bno055_channels; > > + indio_dev->num_channels = ARRAY_SIZE(bno055_channels); > > + indio_dev->info = &bno055_info; > > + indio_dev->modes = INDIO_DIRECT_MODE; > > + > > + data->regmap = devm_regmap_init_i2c(client, &bno055_regmap_config); > > + if (IS_ERR(data->regmap)) { > > + dev_err(&client->dev, "Failed to allocate register > map.\n"); > > + return PTR_ERR(data->regmap); > > + } > > + > > + indio_dev->dev.driver_data = data; > > + i2c_set_clientdata(client, indio_dev); > > + > > + ret = regmap_read(data->regmap, BNO055_REG_ID, &chip_id); > > + if (ret < 0) > > + return ret; > > + if (chip_id != BNO055_CHIP_ID) { > > + dev_err(&client->dev, "bad chip id. expected %x got %x\n", > > + BNO055_CHIP_ID, chip_id); > > + return -EINVAL; > > + } > > + > > + ret = bno055_chip_init(data); > > + if (ret < 0) > > + return ret; > > + > > + ret = devm_iio_device_register(&client->dev, indio_dev); > > + if (ret){ > > space before { > > > + dev_err(&client->dev, "failed to register IIO device.\n"); > > + return ret; > > + } > > + > > + ret = device_create_file(&indio_dev->dev, &dev_attr_calib_status); > > needs to be done BEFORE devm_iio_device_register() > IIO has specific mechanisms to create custom sysfs entries... > > > + if (ret){ > > space before { > > > + dev_err(&client->dev, "failed to create sysfs entry.\n"); > > + devm_iio_device_unregister(&client->dev, indio_dev); > > no need to call devm_iio_device_unregister > > > + return ret; > > + } > > + > > + return ret; > > +} > > + > > +static int bno055_remove(struct i2c_client *client) > > +{ > > + struct iio_dev *indio_dev = i2c_get_clientdata(client); > > + > > + device_remove_file(&indio_dev->dev, &dev_attr_calib_status); > > + devm_iio_device_unregister(&client->dev, indio_dev); > > + return 0; > > +} > > + > > +static const struct acpi_device_id bno055_acpi_match[] = { > > + {"bno055", 0}, > > + { }, > > +}; > > +MODULE_DEVICE_TABLE(acpi, bno055_acpi_match); > > + > > +static const struct i2c_device_id bno055_id[] = { > > + {"bno055", 0}, > > + { }, > > +}; > > +MODULE_DEVICE_TABLE(i2c, bno055_id); > > + > > +static struct i2c_driver bno055_driver = { > > + .driver = { > > + .name = "bno055", > > + .acpi_match_table = ACPI_PTR(bno055_acpi_match), > > + }, > > + .probe = bno055_probe, > > + .remove = bno055_remove, > > + .id_table = bno055_id, > > +}; > > +module_i2c_driver(bno055_driver); > > + > > +MODULE_AUTHOR("navin patidar <navin.patidar@gmail.com>"); > > +MODULE_DESCRIPTION("Driver for Bosch Sensortec BNO055 orientation > sensor"); > > +MODULE_LICENSE("GPL v2"); > > > > -- > > Peter Meerwald-Stadler > +43-664-2444418 (mobile) > [-- Attachment #2: Type: text/html, Size: 23776 bytes --] ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] iio: orientation: Add BNO055 9-axis Absolute Orientation Sensor driver 2016-07-26 18:39 ` navin patidar @ 2016-07-26 18:46 ` Peter Meerwald-Stadler 2016-07-27 0:22 ` navin patidar 0 siblings, 1 reply; 6+ messages in thread From: Peter Meerwald-Stadler @ 2016-07-26 18:46 UTC (permalink / raw) To: navin patidar; +Cc: linux-iio, jic23, knaack.h, lars, linux-kernel, Vlad Dogaru Hallo Navin, > Thanks for reviewing the patch. I will send the updated patch. I am sorry to have overlooked a previous patch by Vlad Dogaru, see http://comments.gmane.org/gmane.linux.kernel.iio/24643, for the BNO055 there have been a number of issues, it has not been accepted yet I think our policy is first come, first served; I have compared the two patches, maybe you can collaborate on a common proposal? regards, p. > regards, > --navin-patidar > > On Tue, Jul 26, 2016 at 1:07 AM, Peter Meerwald-Stadler <pmeerw@pmeerw.net> > wrote: > > > > > > BNO055 provides the following motion sensors data: > > > > > > * Gyroscope > > > * Accelerometer > > > * Magnetometer > > > * Absolute Orientation (Quaternion) > > > > comments below > > > > > Signed-off-by: navin patidar <navin.patidar@gmail.com> > > > --- > > > drivers/iio/orientation/Kconfig | 10 + > > > drivers/iio/orientation/Makefile | 1 + > > > drivers/iio/orientation/bno055.c | 422 > > +++++++++++++++++++++++++++++++++++++++ > > > 3 files changed, 433 insertions(+) > > > create mode 100644 drivers/iio/orientation/bno055.c > > > > > > diff --git a/drivers/iio/orientation/Kconfig > > b/drivers/iio/orientation/Kconfig > > > index e3aa1e5..9ac21ee 100644 > > > --- a/drivers/iio/orientation/Kconfig > > > +++ b/drivers/iio/orientation/Kconfig > > > @@ -28,4 +28,14 @@ config HID_SENSOR_DEVICE_ROTATION > > > device rotation. The output of a device rotation sensor > > > is presented using quaternion format. > > > > > > +config BNO055 > > > + tristate "BOSCH BNO055 Absolute Orientation" > > > + depends on I2C > > > + select REGMAP_I2C > > > + help > > > + Say yes here to build support for BOSCH BNO55 9-axis absolute > > orientation sensor > > > + driver connected via I2C. > > > + This driver can also be built as a module. If so, the module > > > + will be called bno055. > > > + > > > endmenu > > > diff --git a/drivers/iio/orientation/Makefile > > b/drivers/iio/orientation/Makefile > > > index 4734dab..7d00037 100644 > > > --- a/drivers/iio/orientation/Makefile > > > +++ b/drivers/iio/orientation/Makefile > > > @@ -5,3 +5,4 @@ > > > # When adding new entries keep the list in alphabetical order > > > obj-$(CONFIG_HID_SENSOR_INCLINOMETER_3D) += hid-sensor-incl-3d.o > > > obj-$(CONFIG_HID_SENSOR_DEVICE_ROTATION) += hid-sensor-rotation.o > > > +obj-$(CONFIG_BNO055) += bno055.o > > > diff --git a/drivers/iio/orientation/bno055.c > > b/drivers/iio/orientation/bno055.c > > > new file mode 100644 > > > index 0000000..48b9e93 > > > --- /dev/null > > > +++ b/drivers/iio/orientation/bno055.c > > > @@ -0,0 +1,422 @@ > > > +/* > > > + * Copyright (c) 2016 Intel Corporation > > > + * > > > + * Driver for Bosch Sensortec BNO055 digital motion sensor. > > > + * > > > + * This program is free software; you can redistribute it and/or modify > > > + * it under the terms of the GNU General Public License version 2 as > > > + * published by the Free Software Foundation. > > > + * > > > > I find it useful to state the 7-bit i2c chip address here > > > > > + */ > > > + > > > +#define pr_fmt(fmt) "bno055: " fmt > > > + > > > +#include <linux/module.h> > > > +#include <linux/i2c.h> > > > +#include <linux/acpi.h> > > > +#include <linux/regmap.h> > > > +#include <linux/iio/iio.h> > > > +#include <linux/iio/sysfs.h> > > > + > > > > drop one newline > > > > > + > > > +#define BNO055_CHIP_ID 0xA0 > > > +#define REG_MAG_RADIUS_MSB 0x6A > > > > please consistently prefix with BNO055_ > > > > > + > > > +/* BNO055 configuration registers */ > > > +#define REG_PWR_MODE 0x3E > > > +#define REG_OPR_MODE 0x3D > > > +#define REG_UNIT_SEL 0x3B > > > +#define REG_AXIS_MAP_SIGN 0x42 > > > +#define REG_AXIS_MAP_CONFIG 0x41 > > > +#define REG_UNIT_SEL 0x3B > > > +#define REG_SYS_TRIGGER 0x3F > > > +#define BNO055_REG_PAGE_ID 0x07 > > > +#define BNO055_REG_ID 0x00 > > > + > > > +/* BNO055 status registers */ > > > +#define SYS_STATUS 0x39 > > > +#define INT_STATUS 0x37 > > > +#define CALIB_STATUS 0x35 > > > + > > > +/* BNO055 data registers */ > > > > drop newline > > > > > + > > > +#define GRV_DATA_Z_MSB 0x33 > > > +#define GRV_DATA_Z_LSB 0x32 > > > +#define GRV_DATA_Y_MSB 0x31 > > > +#define GRV_DATA_Y_LSB 0x30 > > > +#define GRV_DATA_X_MSB 0x2F > > > +#define GRV_DATA_X_LSB 0x2E > > > > do we really need all those register #defines? > > they are not used and noone wants to read just the LSB... > > > > > + > > > +#define LIA_DATA_Z_MSB 0x2D > > > +#define LIA_DATA_Z_LSB 0x2C > > > +#define LIA_DATA_Y_MSB 0x2B > > > +#define LIA_DATA_Y_LSB 0x2A > > > +#define LIA_DATA_X_MSB 0x29 > > > +#define LIA_DATA_X_LSB 0x28 > > > + > > > +#define QUA_DATA_Z_MSB 0x27 > > > +#define QUA_DATA_Z_LSB 0x26 > > > +#define QUA_DATA_Y_MSB 0x25 > > > +#define QUA_DATA_Y_LSB 0x24 > > > +#define QUA_DATA_X_MSB 0x23 > > > +#define QUA_DATA_X_LSB 0x22 > > > +#define QUA_DATA_W_MSB 0x21 > > > +#define QUA_DATA_W_LSB 0x20 > > > + > > > +#define EUL_PITCH_MSB 0x1F > > > +#define EUL_PITCH_LSB 0x1E > > > +#define EUL_ROLL_MSB 0x1D > > > +#define EUL_ROLL_LSB 0x1C > > > +#define EUL_HEADING_MSB 0x1B > > > +#define EUL_HEADING_LSB 0x1A > > > + > > > +#define GYR_DATA_Z_MSB 0x19 > > > +#define GYR_DATA_Z_LSB 0x18 > > > +#define GYR_DATA_Y_MSB 0x17 > > > +#define GYR_DATA_Y_LSB 0x16 > > > +#define GYR_DATA_X_MSB 0x15 > > > +#define GYR_DATA_X_LSB 0x14 > > > + > > > +#define MAG_DATA_Z_MSB 0x13 > > > +#define MAG_DATA_Z_LSB 0x12 > > > +#define MAG_DATA_Y_MSB 0x11 > > > +#define MAG_DATA_Y_LSB 0x10 > > > +#define MAG_DATA_X_MSB 0x0F > > > +#define MAG_DATA_X_LSB 0x0E > > > + > > > +#define ACC_DATA_Z_MSB 0x0D > > > +#define ACC_DATA_Z_LSB 0x0C > > > +#define ACC_DATA_Y_MSB 0x0B > > > +#define ACC_DATA_Y_LSB 0x0A > > > +#define ACC_DATA_X_MSB 0x09 > > > +#define ACC_DATA_X_LSB 0x08 > > > + > > > +/* operation modes */ > > > +#define FUSION_NDOF_MODE 0x0C > > > + > > > +/* power modes */ > > > +#define NORMAL_MODE 0x00 > > > +#define LOW_POWER_MODE BIT(0) > > > +#define SUSPEND_MODE BIT(1) > > > + > > > +#define PROPER_CALIBRATION 0xFF > > > + > > > +#define BASE_REG 0x08 > > > +#define SENSOR_AXIS_TO_REG(sensor,axis) (BASE_REG + (sensor * 6) + > > (axis * 2)) > > > + > > > +#define BNO055_CHANNEL(_type, _axis) { > > \ > > > + .type = IIO_##_type, \ > > > + .modified = 1, \ > > > + .channel2 = IIO_MOD_##_axis, \ > > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ > > > > in what unit is the data returned; please check with IIO ABI documentation > > I think you will likely need _SCALE for some channels > > > > > + .scan_index = AXIS_##_axis, \ > > > > .scan_index is a continuous counter over ALL channels, not just for one > > channel type > > > > the driver does not support buffered reads, so not scan_type/scan_index is > > needed anyway > > > > > + .scan_type = { \ > > > + .sign = 's', \ > > > + .realbits = 16, \ > > > + .storagebits = 16, \ > > > + .endianness = IIO_LE, \ > > > + }, \ > > > +} > > > + > > > +struct bno055_data { > > > + struct i2c_client *client; > > > + struct mutex lock; > > > + struct regmap *regmap; > > > +}; > > > + > > > +enum bno055_axis { > > > + AXIS_X, > > > + AXIS_Y, > > > + AXIS_Z, > > > +}; > > > + > > > +enum {ACC=0, MAG=1, GYR=2, EUL=3, QUA=4}; > > > > prefix! > > > > > + > > > +static const struct iio_chan_spec bno055_channels[] = { > > > + BNO055_CHANNEL(ANGL_VEL, X), > > > + BNO055_CHANNEL(ANGL_VEL, Y), > > > + BNO055_CHANNEL(ANGL_VEL, Z), > > > + BNO055_CHANNEL(MAGN, X), > > > + BNO055_CHANNEL(MAGN, Y), > > > + BNO055_CHANNEL(MAGN, Z), > > > + BNO055_CHANNEL(ACCEL, X), > > > + BNO055_CHANNEL(ACCEL, Y), > > > + BNO055_CHANNEL(ACCEL, Z), > > > + { > > > + .type = IIO_ROT, > > > + .modified = 1, > > > + .channel2 = IIO_MOD_QUATERNION, > > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), > > > + } > > > +}; > > > + > > > +static bool bno055_is_writeable_reg(struct device *dev, unsigned int > > reg) > > > +{ > > > + switch (reg) { > > > + case REG_PWR_MODE: > > > + case REG_OPR_MODE: > > > + case REG_UNIT_SEL: > > > + case REG_AXIS_MAP_SIGN: > > > + case REG_AXIS_MAP_CONFIG: > > > + case REG_SYS_TRIGGER: > > > + return true; > > > + default: > > > + return false; > > > + }; > > > +} > > > + > > > +static bool bno055_is_volatile_reg(struct device *dev, unsigned int reg) > > > +{ > > > + if ((reg >= ACC_DATA_X_LSB) && (reg <= QUA_DATA_Z_MSB)) > > > + return true; > > > + else > > > + return false; > > > +} > > > + > > > +static const struct regmap_config bno055_regmap_config = { > > > + .reg_bits = 8, > > > + .val_bits = 8, > > > + > > > + .max_register = REG_MAG_RADIUS_MSB, > > > + .cache_type = REGCACHE_RBTREE, > > > + > > > + .writeable_reg = bno055_is_writeable_reg, > > > + .volatile_reg = bno055_is_volatile_reg, > > > +}; > > > + > > > > drop newline > > > > > + > > > +static int read_bno055(struct bno055_data *data, int sensor, int axis, > > int *val) > > > > bno055 prefix please > > > > > +{ > > > + int ret; > > > + __le16 raw_val; > > > + int reg; > > > + > > > + reg = SENSOR_AXIS_TO_REG(sensor, axis); > > > > you can use .address in channel spec for that > > > > > + > > > + ret = regmap_bulk_read(data->regmap, reg, &raw_val, > > > + sizeof(raw_val)); > > > + if (ret < 0) { > > > + dev_err(&data->client->dev, "Error reading axis %d\n", > > axis); > > > + return ret; > > > + } > > > + > > > + *val = sign_extend32(le16_to_cpu(raw_val), 15); > > > + return IIO_VAL_INT; > > > +} > > > + > > > +/* Read raw quaternion values W,X,Y and Z. > > > + * Raw values needs to be divied by the 16384 to get the exact > > quaternion values. > > > + */ > > > + > > > +static int read_rot(struct bno055_data *data, int *vals, int *val_len) > > > +{ > > > + int ret, i; > > > + __le16 raw_val[4]; > > > + > > > + ret = regmap_bulk_read(data->regmap, QUA_DATA_W_LSB, &raw_val, > > > + sizeof(raw_val)); > > > + if (ret < 0) { > > > + dev_err(&data->client->dev, "Error reading Orientation > > \n"); > > > > lowercase orientation > > delete space before \n > > > > > + return ret; > > > + } > > > + > > > + for (i = 0; i < 4; ++i) > > > + vals[i] = sign_extend32(le16_to_cpu(raw_val[i]), 15); > > > + > > > + *val_len = 4; > > > + return IIO_VAL_INT_MULTIPLE; > > > +} > > > + > > > +static int bno055_read_raw_multi(struct iio_dev *indio_dev, > > > + struct iio_chan_spec const *chan, int size, > > > + int *val, int *val2, long mask) > > > +{ > > > + int ret = -EINVAL; > > > + struct bno055_data *data = iio_priv(indio_dev); > > > + > > > + mutex_lock(&data->lock); > > > + > > > + switch (mask) { > > > + case IIO_CHAN_INFO_RAW: > > > + switch (chan->type) { > > > + case IIO_ROT: > > > + ret = read_rot(data, val, val2); > > > + break; > > > + case IIO_ANGL_VEL: > > > + /* Gyroscope unit degrees per second */ > > > + /* Raw value needs to be divied by the 16 to get > > the exact value.*/ > > > > comment style, not a proper multi-line comment > > use _SCALE, it is not about 'exact' but correct scaling/unit > > typo: divided > > wording: divided by 16 > > > > > + ret = read_bno055(data, GYR, chan->scan_index, > > val); > > > + break; > > > + case IIO_ACCEL: > > > + /* Accelerometer Unit m/s2 */ > > > + /* Raw value needs to be divied by the 100 to get > > the exact value.*/ > > > + ret = read_bno055(data, ACC, chan->scan_index, > > val); > > > + break; > > > + case IIO_MAGN: > > > + /* Magnetometer Unit microTesla */ > > > + /* Raw value needs to be divied by the 16 to get > > the exact value. */ > > > + ret = read_bno055(data, MAG, chan->scan_index, > > val); > > > + break; > > > + default: > > > + ret = -EINVAL; > > > + break; > > > + } > > > + break; > > > + > > > + default: > > > + ret = -EINVAL; > > > + break; > > > + } > > > + > > > + mutex_unlock(&data->lock); > > > + > > > + return ret; > > > +} > > > + > > > +static const struct iio_info bno055_info = { > > > + .driver_module = THIS_MODULE, > > > + .read_raw_multi = &bno055_read_raw_multi, > > > +}; > > > + > > > > drop newline > > > > > + > > > +static int bno055_chip_init(struct bno055_data *data) > > > +{ > > > + int ret; > > > + > > > + ret = regmap_write(data->regmap, REG_PWR_MODE, > > > + NORMAL_MODE); > > > + if (ret < 0) { > > > + dev_err(&data->client->dev, > > > + "failed to write power mode register\n"); > > > + return ret; > > > + } > > > + > > > + ret = regmap_write(data->regmap, REG_OPR_MODE, > > > + FUSION_NDOF_MODE); > > > + if (ret < 0) > > > + dev_err(&data->client->dev, > > > + "failed to write operation mode register\n"); > > > + > > > + return ret; > > > +} > > > + > > > +static ssize_t show_calibration_status(struct device *dev, struct > > device_attribute *attr, > > > + char *buf){ > > > > this is private API which needs to be documented > > > > > + struct bno055_data *data; > > > + int ret; > > > + unsigned int calib_stat; > > > + > > > + data = dev->driver_data; > > > + > > > + ret = regmap_read(data->regmap, CALIB_STATUS, &calib_stat); > > > + > > > + if (ret){ > > > > space before { > > > > > + dev_err(dev, "Failed to read calibration status.\n"); > > > + return ret; > > > + } > > > + > > > + if (calib_stat != PROPER_CALIBRATION) > > > + dev_info(dev, "bad calibration. expected %x got %x\n", > > > > two spaces before 'expected' > > > > > + PROPER_CALIBRATION, calib_stat); > > > + > > > + return scnprintf(buf, PAGE_SIZE, "0x%x\n", calib_stat); > > > +} > > > + > > > +static DEVICE_ATTR(calib_status, 0444, show_calibration_status, NULL); > > > + > > > +static int bno055_probe(struct i2c_client *client, > > > + const struct i2c_device_id *id) > > > +{ > > > + int ret; > > > + struct iio_dev *indio_dev; > > > + struct bno055_data *data; > > > + unsigned int chip_id; > > > + > > > + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); > > > + if (!indio_dev) > > > + return -ENOMEM; > > > + > > > + data = iio_priv(indio_dev); > > > + mutex_init(&data->lock); > > > + data->client = client; > > > + > > > + indio_dev->dev.parent = &client->dev; > > > + indio_dev->name = id->name; > > > + indio_dev->channels = bno055_channels; > > > + indio_dev->num_channels = ARRAY_SIZE(bno055_channels); > > > + indio_dev->info = &bno055_info; > > > + indio_dev->modes = INDIO_DIRECT_MODE; > > > + > > > + data->regmap = devm_regmap_init_i2c(client, &bno055_regmap_config); > > > + if (IS_ERR(data->regmap)) { > > > + dev_err(&client->dev, "Failed to allocate register > > map.\n"); > > > + return PTR_ERR(data->regmap); > > > + } > > > + > > > + indio_dev->dev.driver_data = data; > > > + i2c_set_clientdata(client, indio_dev); > > > + > > > + ret = regmap_read(data->regmap, BNO055_REG_ID, &chip_id); > > > + if (ret < 0) > > > + return ret; > > > + if (chip_id != BNO055_CHIP_ID) { > > > + dev_err(&client->dev, "bad chip id. expected %x got %x\n", > > > + BNO055_CHIP_ID, chip_id); > > > + return -EINVAL; > > > + } > > > + > > > + ret = bno055_chip_init(data); > > > + if (ret < 0) > > > + return ret; > > > + > > > + ret = devm_iio_device_register(&client->dev, indio_dev); > > > + if (ret){ > > > > space before { > > > > > + dev_err(&client->dev, "failed to register IIO device.\n"); > > > + return ret; > > > + } > > > + > > > + ret = device_create_file(&indio_dev->dev, &dev_attr_calib_status); > > > > needs to be done BEFORE devm_iio_device_register() > > IIO has specific mechanisms to create custom sysfs entries... > > > > > + if (ret){ > > > > space before { > > > > > + dev_err(&client->dev, "failed to create sysfs entry.\n"); > > > + devm_iio_device_unregister(&client->dev, indio_dev); > > > > no need to call devm_iio_device_unregister > > > > > + return ret; > > > + } > > > + > > > + return ret; > > > +} > > > + > > > +static int bno055_remove(struct i2c_client *client) > > > +{ > > > + struct iio_dev *indio_dev = i2c_get_clientdata(client); > > > + > > > + device_remove_file(&indio_dev->dev, &dev_attr_calib_status); > > > + devm_iio_device_unregister(&client->dev, indio_dev); > > > + return 0; > > > +} > > > + > > > +static const struct acpi_device_id bno055_acpi_match[] = { > > > + {"bno055", 0}, > > > + { }, > > > +}; > > > +MODULE_DEVICE_TABLE(acpi, bno055_acpi_match); > > > + > > > +static const struct i2c_device_id bno055_id[] = { > > > + {"bno055", 0}, > > > + { }, > > > +}; > > > +MODULE_DEVICE_TABLE(i2c, bno055_id); > > > + > > > +static struct i2c_driver bno055_driver = { > > > + .driver = { > > > + .name = "bno055", > > > + .acpi_match_table = ACPI_PTR(bno055_acpi_match), > > > + }, > > > + .probe = bno055_probe, > > > + .remove = bno055_remove, > > > + .id_table = bno055_id, > > > +}; > > > +module_i2c_driver(bno055_driver); > > > + > > > +MODULE_AUTHOR("navin patidar <navin.patidar@gmail.com>"); > > > +MODULE_DESCRIPTION("Driver for Bosch Sensortec BNO055 orientation > > sensor"); > > > +MODULE_LICENSE("GPL v2"); > > > > > > > -- > > > > Peter Meerwald-Stadler > > +43-664-2444418 (mobile) > > > -- Peter Meerwald-Stadler +43-664-2444418 (mobile) ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] iio: orientation: Add BNO055 9-axis Absolute Orientation Sensor driver 2016-07-26 18:46 ` Peter Meerwald-Stadler @ 2016-07-27 0:22 ` navin patidar 2016-08-15 18:05 ` Jonathan Cameron 0 siblings, 1 reply; 6+ messages in thread From: navin patidar @ 2016-07-27 0:22 UTC (permalink / raw) To: Peter Meerwald-Stadler Cc: linux-iio, jic23, knaack.h, lars, linux-kernel, Vlad Dogaru Oh, I didn't know that Vlad already have submitted a patch for BNO055. My patch doesn't do any thing new so please drop my patch. regards, --navin-patidar On Tue, Jul 26, 2016 at 11:46 AM, Peter Meerwald-Stadler <pmeerw@pmeerw.net> wrote: > Hallo Navin, > >> Thanks for reviewing the patch. I will send the updated patch. > > I am sorry to have overlooked a previous patch by Vlad Dogaru, see > http://comments.gmane.org/gmane.linux.kernel.iio/24643, for the BNO055 > > there have been a number of issues, it has not been accepted yet > > I think our policy is first come, first served; I have compared the two > patches, maybe you can collaborate on a common proposal? > > regards, p. > >> regards, >> --navin-patidar >> >> On Tue, Jul 26, 2016 at 1:07 AM, Peter Meerwald-Stadler <pmeerw@pmeerw.net> >> wrote: >> >> > >> > > BNO055 provides the following motion sensors data: >> > > >> > > * Gyroscope >> > > * Accelerometer >> > > * Magnetometer >> > > * Absolute Orientation (Quaternion) >> > >> > comments below >> > >> > > Signed-off-by: navin patidar <navin.patidar@gmail.com> >> > > --- >> > > drivers/iio/orientation/Kconfig | 10 + >> > > drivers/iio/orientation/Makefile | 1 + >> > > drivers/iio/orientation/bno055.c | 422 >> > +++++++++++++++++++++++++++++++++++++++ >> > > 3 files changed, 433 insertions(+) >> > > create mode 100644 drivers/iio/orientation/bno055.c >> > > >> > > diff --git a/drivers/iio/orientation/Kconfig >> > b/drivers/iio/orientation/Kconfig >> > > index e3aa1e5..9ac21ee 100644 >> > > --- a/drivers/iio/orientation/Kconfig >> > > +++ b/drivers/iio/orientation/Kconfig >> > > @@ -28,4 +28,14 @@ config HID_SENSOR_DEVICE_ROTATION >> > > device rotation. The output of a device rotation sensor >> > > is presented using quaternion format. >> > > >> > > +config BNO055 >> > > + tristate "BOSCH BNO055 Absolute Orientation" >> > > + depends on I2C >> > > + select REGMAP_I2C >> > > + help >> > > + Say yes here to build support for BOSCH BNO55 9-axis absolute >> > orientation sensor >> > > + driver connected via I2C. >> > > + This driver can also be built as a module. If so, the module >> > > + will be called bno055. >> > > + >> > > endmenu >> > > diff --git a/drivers/iio/orientation/Makefile >> > b/drivers/iio/orientation/Makefile >> > > index 4734dab..7d00037 100644 >> > > --- a/drivers/iio/orientation/Makefile >> > > +++ b/drivers/iio/orientation/Makefile >> > > @@ -5,3 +5,4 @@ >> > > # When adding new entries keep the list in alphabetical order >> > > obj-$(CONFIG_HID_SENSOR_INCLINOMETER_3D) += hid-sensor-incl-3d.o >> > > obj-$(CONFIG_HID_SENSOR_DEVICE_ROTATION) += hid-sensor-rotation.o >> > > +obj-$(CONFIG_BNO055) += bno055.o >> > > diff --git a/drivers/iio/orientation/bno055.c >> > b/drivers/iio/orientation/bno055.c >> > > new file mode 100644 >> > > index 0000000..48b9e93 >> > > --- /dev/null >> > > +++ b/drivers/iio/orientation/bno055.c >> > > @@ -0,0 +1,422 @@ >> > > +/* >> > > + * Copyright (c) 2016 Intel Corporation >> > > + * >> > > + * Driver for Bosch Sensortec BNO055 digital motion sensor. >> > > + * >> > > + * This program is free software; you can redistribute it and/or modify >> > > + * it under the terms of the GNU General Public License version 2 as >> > > + * published by the Free Software Foundation. >> > > + * >> > >> > I find it useful to state the 7-bit i2c chip address here >> > >> > > + */ >> > > + >> > > +#define pr_fmt(fmt) "bno055: " fmt >> > > + >> > > +#include <linux/module.h> >> > > +#include <linux/i2c.h> >> > > +#include <linux/acpi.h> >> > > +#include <linux/regmap.h> >> > > +#include <linux/iio/iio.h> >> > > +#include <linux/iio/sysfs.h> >> > > + >> > >> > drop one newline >> > >> > > + >> > > +#define BNO055_CHIP_ID 0xA0 >> > > +#define REG_MAG_RADIUS_MSB 0x6A >> > >> > please consistently prefix with BNO055_ >> > >> > > + >> > > +/* BNO055 configuration registers */ >> > > +#define REG_PWR_MODE 0x3E >> > > +#define REG_OPR_MODE 0x3D >> > > +#define REG_UNIT_SEL 0x3B >> > > +#define REG_AXIS_MAP_SIGN 0x42 >> > > +#define REG_AXIS_MAP_CONFIG 0x41 >> > > +#define REG_UNIT_SEL 0x3B >> > > +#define REG_SYS_TRIGGER 0x3F >> > > +#define BNO055_REG_PAGE_ID 0x07 >> > > +#define BNO055_REG_ID 0x00 >> > > + >> > > +/* BNO055 status registers */ >> > > +#define SYS_STATUS 0x39 >> > > +#define INT_STATUS 0x37 >> > > +#define CALIB_STATUS 0x35 >> > > + >> > > +/* BNO055 data registers */ >> > >> > drop newline >> > >> > > + >> > > +#define GRV_DATA_Z_MSB 0x33 >> > > +#define GRV_DATA_Z_LSB 0x32 >> > > +#define GRV_DATA_Y_MSB 0x31 >> > > +#define GRV_DATA_Y_LSB 0x30 >> > > +#define GRV_DATA_X_MSB 0x2F >> > > +#define GRV_DATA_X_LSB 0x2E >> > >> > do we really need all those register #defines? >> > they are not used and noone wants to read just the LSB... >> > >> > > + >> > > +#define LIA_DATA_Z_MSB 0x2D >> > > +#define LIA_DATA_Z_LSB 0x2C >> > > +#define LIA_DATA_Y_MSB 0x2B >> > > +#define LIA_DATA_Y_LSB 0x2A >> > > +#define LIA_DATA_X_MSB 0x29 >> > > +#define LIA_DATA_X_LSB 0x28 >> > > + >> > > +#define QUA_DATA_Z_MSB 0x27 >> > > +#define QUA_DATA_Z_LSB 0x26 >> > > +#define QUA_DATA_Y_MSB 0x25 >> > > +#define QUA_DATA_Y_LSB 0x24 >> > > +#define QUA_DATA_X_MSB 0x23 >> > > +#define QUA_DATA_X_LSB 0x22 >> > > +#define QUA_DATA_W_MSB 0x21 >> > > +#define QUA_DATA_W_LSB 0x20 >> > > + >> > > +#define EUL_PITCH_MSB 0x1F >> > > +#define EUL_PITCH_LSB 0x1E >> > > +#define EUL_ROLL_MSB 0x1D >> > > +#define EUL_ROLL_LSB 0x1C >> > > +#define EUL_HEADING_MSB 0x1B >> > > +#define EUL_HEADING_LSB 0x1A >> > > + >> > > +#define GYR_DATA_Z_MSB 0x19 >> > > +#define GYR_DATA_Z_LSB 0x18 >> > > +#define GYR_DATA_Y_MSB 0x17 >> > > +#define GYR_DATA_Y_LSB 0x16 >> > > +#define GYR_DATA_X_MSB 0x15 >> > > +#define GYR_DATA_X_LSB 0x14 >> > > + >> > > +#define MAG_DATA_Z_MSB 0x13 >> > > +#define MAG_DATA_Z_LSB 0x12 >> > > +#define MAG_DATA_Y_MSB 0x11 >> > > +#define MAG_DATA_Y_LSB 0x10 >> > > +#define MAG_DATA_X_MSB 0x0F >> > > +#define MAG_DATA_X_LSB 0x0E >> > > + >> > > +#define ACC_DATA_Z_MSB 0x0D >> > > +#define ACC_DATA_Z_LSB 0x0C >> > > +#define ACC_DATA_Y_MSB 0x0B >> > > +#define ACC_DATA_Y_LSB 0x0A >> > > +#define ACC_DATA_X_MSB 0x09 >> > > +#define ACC_DATA_X_LSB 0x08 >> > > + >> > > +/* operation modes */ >> > > +#define FUSION_NDOF_MODE 0x0C >> > > + >> > > +/* power modes */ >> > > +#define NORMAL_MODE 0x00 >> > > +#define LOW_POWER_MODE BIT(0) >> > > +#define SUSPEND_MODE BIT(1) >> > > + >> > > +#define PROPER_CALIBRATION 0xFF >> > > + >> > > +#define BASE_REG 0x08 >> > > +#define SENSOR_AXIS_TO_REG(sensor,axis) (BASE_REG + (sensor * 6) + >> > (axis * 2)) >> > > + >> > > +#define BNO055_CHANNEL(_type, _axis) { >> > \ >> > > + .type = IIO_##_type, \ >> > > + .modified = 1, \ >> > > + .channel2 = IIO_MOD_##_axis, \ >> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ >> > >> > in what unit is the data returned; please check with IIO ABI documentation >> > I think you will likely need _SCALE for some channels >> > >> > > + .scan_index = AXIS_##_axis, \ >> > >> > .scan_index is a continuous counter over ALL channels, not just for one >> > channel type >> > >> > the driver does not support buffered reads, so not scan_type/scan_index is >> > needed anyway >> > >> > > + .scan_type = { \ >> > > + .sign = 's', \ >> > > + .realbits = 16, \ >> > > + .storagebits = 16, \ >> > > + .endianness = IIO_LE, \ >> > > + }, \ >> > > +} >> > > + >> > > +struct bno055_data { >> > > + struct i2c_client *client; >> > > + struct mutex lock; >> > > + struct regmap *regmap; >> > > +}; >> > > + >> > > +enum bno055_axis { >> > > + AXIS_X, >> > > + AXIS_Y, >> > > + AXIS_Z, >> > > +}; >> > > + >> > > +enum {ACC=0, MAG=1, GYR=2, EUL=3, QUA=4}; >> > >> > prefix! >> > >> > > + >> > > +static const struct iio_chan_spec bno055_channels[] = { >> > > + BNO055_CHANNEL(ANGL_VEL, X), >> > > + BNO055_CHANNEL(ANGL_VEL, Y), >> > > + BNO055_CHANNEL(ANGL_VEL, Z), >> > > + BNO055_CHANNEL(MAGN, X), >> > > + BNO055_CHANNEL(MAGN, Y), >> > > + BNO055_CHANNEL(MAGN, Z), >> > > + BNO055_CHANNEL(ACCEL, X), >> > > + BNO055_CHANNEL(ACCEL, Y), >> > > + BNO055_CHANNEL(ACCEL, Z), >> > > + { >> > > + .type = IIO_ROT, >> > > + .modified = 1, >> > > + .channel2 = IIO_MOD_QUATERNION, >> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), >> > > + } >> > > +}; >> > > + >> > > +static bool bno055_is_writeable_reg(struct device *dev, unsigned int >> > reg) >> > > +{ >> > > + switch (reg) { >> > > + case REG_PWR_MODE: >> > > + case REG_OPR_MODE: >> > > + case REG_UNIT_SEL: >> > > + case REG_AXIS_MAP_SIGN: >> > > + case REG_AXIS_MAP_CONFIG: >> > > + case REG_SYS_TRIGGER: >> > > + return true; >> > > + default: >> > > + return false; >> > > + }; >> > > +} >> > > + >> > > +static bool bno055_is_volatile_reg(struct device *dev, unsigned int reg) >> > > +{ >> > > + if ((reg >= ACC_DATA_X_LSB) && (reg <= QUA_DATA_Z_MSB)) >> > > + return true; >> > > + else >> > > + return false; >> > > +} >> > > + >> > > +static const struct regmap_config bno055_regmap_config = { >> > > + .reg_bits = 8, >> > > + .val_bits = 8, >> > > + >> > > + .max_register = REG_MAG_RADIUS_MSB, >> > > + .cache_type = REGCACHE_RBTREE, >> > > + >> > > + .writeable_reg = bno055_is_writeable_reg, >> > > + .volatile_reg = bno055_is_volatile_reg, >> > > +}; >> > > + >> > >> > drop newline >> > >> > > + >> > > +static int read_bno055(struct bno055_data *data, int sensor, int axis, >> > int *val) >> > >> > bno055 prefix please >> > >> > > +{ >> > > + int ret; >> > > + __le16 raw_val; >> > > + int reg; >> > > + >> > > + reg = SENSOR_AXIS_TO_REG(sensor, axis); >> > >> > you can use .address in channel spec for that >> > >> > > + >> > > + ret = regmap_bulk_read(data->regmap, reg, &raw_val, >> > > + sizeof(raw_val)); >> > > + if (ret < 0) { >> > > + dev_err(&data->client->dev, "Error reading axis %d\n", >> > axis); >> > > + return ret; >> > > + } >> > > + >> > > + *val = sign_extend32(le16_to_cpu(raw_val), 15); >> > > + return IIO_VAL_INT; >> > > +} >> > > + >> > > +/* Read raw quaternion values W,X,Y and Z. >> > > + * Raw values needs to be divied by the 16384 to get the exact >> > quaternion values. >> > > + */ >> > > + >> > > +static int read_rot(struct bno055_data *data, int *vals, int *val_len) >> > > +{ >> > > + int ret, i; >> > > + __le16 raw_val[4]; >> > > + >> > > + ret = regmap_bulk_read(data->regmap, QUA_DATA_W_LSB, &raw_val, >> > > + sizeof(raw_val)); >> > > + if (ret < 0) { >> > > + dev_err(&data->client->dev, "Error reading Orientation >> > \n"); >> > >> > lowercase orientation >> > delete space before \n >> > >> > > + return ret; >> > > + } >> > > + >> > > + for (i = 0; i < 4; ++i) >> > > + vals[i] = sign_extend32(le16_to_cpu(raw_val[i]), 15); >> > > + >> > > + *val_len = 4; >> > > + return IIO_VAL_INT_MULTIPLE; >> > > +} >> > > + >> > > +static int bno055_read_raw_multi(struct iio_dev *indio_dev, >> > > + struct iio_chan_spec const *chan, int size, >> > > + int *val, int *val2, long mask) >> > > +{ >> > > + int ret = -EINVAL; >> > > + struct bno055_data *data = iio_priv(indio_dev); >> > > + >> > > + mutex_lock(&data->lock); >> > > + >> > > + switch (mask) { >> > > + case IIO_CHAN_INFO_RAW: >> > > + switch (chan->type) { >> > > + case IIO_ROT: >> > > + ret = read_rot(data, val, val2); >> > > + break; >> > > + case IIO_ANGL_VEL: >> > > + /* Gyroscope unit degrees per second */ >> > > + /* Raw value needs to be divied by the 16 to get >> > the exact value.*/ >> > >> > comment style, not a proper multi-line comment >> > use _SCALE, it is not about 'exact' but correct scaling/unit >> > typo: divided >> > wording: divided by 16 >> > >> > > + ret = read_bno055(data, GYR, chan->scan_index, >> > val); >> > > + break; >> > > + case IIO_ACCEL: >> > > + /* Accelerometer Unit m/s2 */ >> > > + /* Raw value needs to be divied by the 100 to get >> > the exact value.*/ >> > > + ret = read_bno055(data, ACC, chan->scan_index, >> > val); >> > > + break; >> > > + case IIO_MAGN: >> > > + /* Magnetometer Unit microTesla */ >> > > + /* Raw value needs to be divied by the 16 to get >> > the exact value. */ >> > > + ret = read_bno055(data, MAG, chan->scan_index, >> > val); >> > > + break; >> > > + default: >> > > + ret = -EINVAL; >> > > + break; >> > > + } >> > > + break; >> > > + >> > > + default: >> > > + ret = -EINVAL; >> > > + break; >> > > + } >> > > + >> > > + mutex_unlock(&data->lock); >> > > + >> > > + return ret; >> > > +} >> > > + >> > > +static const struct iio_info bno055_info = { >> > > + .driver_module = THIS_MODULE, >> > > + .read_raw_multi = &bno055_read_raw_multi, >> > > +}; >> > > + >> > >> > drop newline >> > >> > > + >> > > +static int bno055_chip_init(struct bno055_data *data) >> > > +{ >> > > + int ret; >> > > + >> > > + ret = regmap_write(data->regmap, REG_PWR_MODE, >> > > + NORMAL_MODE); >> > > + if (ret < 0) { >> > > + dev_err(&data->client->dev, >> > > + "failed to write power mode register\n"); >> > > + return ret; >> > > + } >> > > + >> > > + ret = regmap_write(data->regmap, REG_OPR_MODE, >> > > + FUSION_NDOF_MODE); >> > > + if (ret < 0) >> > > + dev_err(&data->client->dev, >> > > + "failed to write operation mode register\n"); >> > > + >> > > + return ret; >> > > +} >> > > + >> > > +static ssize_t show_calibration_status(struct device *dev, struct >> > device_attribute *attr, >> > > + char *buf){ >> > >> > this is private API which needs to be documented >> > >> > > + struct bno055_data *data; >> > > + int ret; >> > > + unsigned int calib_stat; >> > > + >> > > + data = dev->driver_data; >> > > + >> > > + ret = regmap_read(data->regmap, CALIB_STATUS, &calib_stat); >> > > + >> > > + if (ret){ >> > >> > space before { >> > >> > > + dev_err(dev, "Failed to read calibration status.\n"); >> > > + return ret; >> > > + } >> > > + >> > > + if (calib_stat != PROPER_CALIBRATION) >> > > + dev_info(dev, "bad calibration. expected %x got %x\n", >> > >> > two spaces before 'expected' >> > >> > > + PROPER_CALIBRATION, calib_stat); >> > > + >> > > + return scnprintf(buf, PAGE_SIZE, "0x%x\n", calib_stat); >> > > +} >> > > + >> > > +static DEVICE_ATTR(calib_status, 0444, show_calibration_status, NULL); >> > > + >> > > +static int bno055_probe(struct i2c_client *client, >> > > + const struct i2c_device_id *id) >> > > +{ >> > > + int ret; >> > > + struct iio_dev *indio_dev; >> > > + struct bno055_data *data; >> > > + unsigned int chip_id; >> > > + >> > > + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); >> > > + if (!indio_dev) >> > > + return -ENOMEM; >> > > + >> > > + data = iio_priv(indio_dev); >> > > + mutex_init(&data->lock); >> > > + data->client = client; >> > > + >> > > + indio_dev->dev.parent = &client->dev; >> > > + indio_dev->name = id->name; >> > > + indio_dev->channels = bno055_channels; >> > > + indio_dev->num_channels = ARRAY_SIZE(bno055_channels); >> > > + indio_dev->info = &bno055_info; >> > > + indio_dev->modes = INDIO_DIRECT_MODE; >> > > + >> > > + data->regmap = devm_regmap_init_i2c(client, &bno055_regmap_config); >> > > + if (IS_ERR(data->regmap)) { >> > > + dev_err(&client->dev, "Failed to allocate register >> > map.\n"); >> > > + return PTR_ERR(data->regmap); >> > > + } >> > > + >> > > + indio_dev->dev.driver_data = data; >> > > + i2c_set_clientdata(client, indio_dev); >> > > + >> > > + ret = regmap_read(data->regmap, BNO055_REG_ID, &chip_id); >> > > + if (ret < 0) >> > > + return ret; >> > > + if (chip_id != BNO055_CHIP_ID) { >> > > + dev_err(&client->dev, "bad chip id. expected %x got %x\n", >> > > + BNO055_CHIP_ID, chip_id); >> > > + return -EINVAL; >> > > + } >> > > + >> > > + ret = bno055_chip_init(data); >> > > + if (ret < 0) >> > > + return ret; >> > > + >> > > + ret = devm_iio_device_register(&client->dev, indio_dev); >> > > + if (ret){ >> > >> > space before { >> > >> > > + dev_err(&client->dev, "failed to register IIO device.\n"); >> > > + return ret; >> > > + } >> > > + >> > > + ret = device_create_file(&indio_dev->dev, &dev_attr_calib_status); >> > >> > needs to be done BEFORE devm_iio_device_register() >> > IIO has specific mechanisms to create custom sysfs entries... >> > >> > > + if (ret){ >> > >> > space before { >> > >> > > + dev_err(&client->dev, "failed to create sysfs entry.\n"); >> > > + devm_iio_device_unregister(&client->dev, indio_dev); >> > >> > no need to call devm_iio_device_unregister >> > >> > > + return ret; >> > > + } >> > > + >> > > + return ret; >> > > +} >> > > + >> > > +static int bno055_remove(struct i2c_client *client) >> > > +{ >> > > + struct iio_dev *indio_dev = i2c_get_clientdata(client); >> > > + >> > > + device_remove_file(&indio_dev->dev, &dev_attr_calib_status); >> > > + devm_iio_device_unregister(&client->dev, indio_dev); >> > > + return 0; >> > > +} >> > > + >> > > +static const struct acpi_device_id bno055_acpi_match[] = { >> > > + {"bno055", 0}, >> > > + { }, >> > > +}; >> > > +MODULE_DEVICE_TABLE(acpi, bno055_acpi_match); >> > > + >> > > +static const struct i2c_device_id bno055_id[] = { >> > > + {"bno055", 0}, >> > > + { }, >> > > +}; >> > > +MODULE_DEVICE_TABLE(i2c, bno055_id); >> > > + >> > > +static struct i2c_driver bno055_driver = { >> > > + .driver = { >> > > + .name = "bno055", >> > > + .acpi_match_table = ACPI_PTR(bno055_acpi_match), >> > > + }, >> > > + .probe = bno055_probe, >> > > + .remove = bno055_remove, >> > > + .id_table = bno055_id, >> > > +}; >> > > +module_i2c_driver(bno055_driver); >> > > + >> > > +MODULE_AUTHOR("navin patidar <navin.patidar@gmail.com>"); >> > > +MODULE_DESCRIPTION("Driver for Bosch Sensortec BNO055 orientation >> > sensor"); >> > > +MODULE_LICENSE("GPL v2"); >> > > >> > >> > -- >> > >> > Peter Meerwald-Stadler >> > +43-664-2444418 (mobile) >> > >> > > -- > > Peter Meerwald-Stadler > +43-664-2444418 (mobile) ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] iio: orientation: Add BNO055 9-axis Absolute Orientation Sensor driver 2016-07-27 0:22 ` navin patidar @ 2016-08-15 18:05 ` Jonathan Cameron 0 siblings, 0 replies; 6+ messages in thread From: Jonathan Cameron @ 2016-08-15 18:05 UTC (permalink / raw) To: navin patidar, Peter Meerwald-Stadler Cc: linux-iio, knaack.h, lars, linux-kernel, Vlad Dogaru On 27/07/16 01:22, navin patidar wrote: > Oh, I didn't know that Vlad already have submitted a patch for BNO055. > My patch doesn't do any thing new so please drop my patch. > > regards, > --navin-patidar You have my utmost sympathies! Sadly this happens to us all from time to time. Jonathan > > On Tue, Jul 26, 2016 at 11:46 AM, Peter Meerwald-Stadler > <pmeerw@pmeerw.net> wrote: >> Hallo Navin, >> >>> Thanks for reviewing the patch. I will send the updated patch. >> >> I am sorry to have overlooked a previous patch by Vlad Dogaru, see >> http://comments.gmane.org/gmane.linux.kernel.iio/24643, for the BNO055 >> >> there have been a number of issues, it has not been accepted yet >> >> I think our policy is first come, first served; I have compared the two >> patches, maybe you can collaborate on a common proposal? >> >> regards, p. >> >>> regards, >>> --navin-patidar >>> >>> On Tue, Jul 26, 2016 at 1:07 AM, Peter Meerwald-Stadler <pmeerw@pmeerw.net> >>> wrote: >>> >>>> >>>>> BNO055 provides the following motion sensors data: >>>>> >>>>> * Gyroscope >>>>> * Accelerometer >>>>> * Magnetometer >>>>> * Absolute Orientation (Quaternion) >>>> >>>> comments below >>>> >>>>> Signed-off-by: navin patidar <navin.patidar@gmail.com> >>>>> --- >>>>> drivers/iio/orientation/Kconfig | 10 + >>>>> drivers/iio/orientation/Makefile | 1 + >>>>> drivers/iio/orientation/bno055.c | 422 >>>> +++++++++++++++++++++++++++++++++++++++ >>>>> 3 files changed, 433 insertions(+) >>>>> create mode 100644 drivers/iio/orientation/bno055.c >>>>> >>>>> diff --git a/drivers/iio/orientation/Kconfig >>>> b/drivers/iio/orientation/Kconfig >>>>> index e3aa1e5..9ac21ee 100644 >>>>> --- a/drivers/iio/orientation/Kconfig >>>>> +++ b/drivers/iio/orientation/Kconfig >>>>> @@ -28,4 +28,14 @@ config HID_SENSOR_DEVICE_ROTATION >>>>> device rotation. The output of a device rotation sensor >>>>> is presented using quaternion format. >>>>> >>>>> +config BNO055 >>>>> + tristate "BOSCH BNO055 Absolute Orientation" >>>>> + depends on I2C >>>>> + select REGMAP_I2C >>>>> + help >>>>> + Say yes here to build support for BOSCH BNO55 9-axis absolute >>>> orientation sensor >>>>> + driver connected via I2C. >>>>> + This driver can also be built as a module. If so, the module >>>>> + will be called bno055. >>>>> + >>>>> endmenu >>>>> diff --git a/drivers/iio/orientation/Makefile >>>> b/drivers/iio/orientation/Makefile >>>>> index 4734dab..7d00037 100644 >>>>> --- a/drivers/iio/orientation/Makefile >>>>> +++ b/drivers/iio/orientation/Makefile >>>>> @@ -5,3 +5,4 @@ >>>>> # When adding new entries keep the list in alphabetical order >>>>> obj-$(CONFIG_HID_SENSOR_INCLINOMETER_3D) += hid-sensor-incl-3d.o >>>>> obj-$(CONFIG_HID_SENSOR_DEVICE_ROTATION) += hid-sensor-rotation.o >>>>> +obj-$(CONFIG_BNO055) += bno055.o >>>>> diff --git a/drivers/iio/orientation/bno055.c >>>> b/drivers/iio/orientation/bno055.c >>>>> new file mode 100644 >>>>> index 0000000..48b9e93 >>>>> --- /dev/null >>>>> +++ b/drivers/iio/orientation/bno055.c >>>>> @@ -0,0 +1,422 @@ >>>>> +/* >>>>> + * Copyright (c) 2016 Intel Corporation >>>>> + * >>>>> + * Driver for Bosch Sensortec BNO055 digital motion sensor. >>>>> + * >>>>> + * This program is free software; you can redistribute it and/or modify >>>>> + * it under the terms of the GNU General Public License version 2 as >>>>> + * published by the Free Software Foundation. >>>>> + * >>>> >>>> I find it useful to state the 7-bit i2c chip address here >>>> >>>>> + */ >>>>> + >>>>> +#define pr_fmt(fmt) "bno055: " fmt >>>>> + >>>>> +#include <linux/module.h> >>>>> +#include <linux/i2c.h> >>>>> +#include <linux/acpi.h> >>>>> +#include <linux/regmap.h> >>>>> +#include <linux/iio/iio.h> >>>>> +#include <linux/iio/sysfs.h> >>>>> + >>>> >>>> drop one newline >>>> >>>>> + >>>>> +#define BNO055_CHIP_ID 0xA0 >>>>> +#define REG_MAG_RADIUS_MSB 0x6A >>>> >>>> please consistently prefix with BNO055_ >>>> >>>>> + >>>>> +/* BNO055 configuration registers */ >>>>> +#define REG_PWR_MODE 0x3E >>>>> +#define REG_OPR_MODE 0x3D >>>>> +#define REG_UNIT_SEL 0x3B >>>>> +#define REG_AXIS_MAP_SIGN 0x42 >>>>> +#define REG_AXIS_MAP_CONFIG 0x41 >>>>> +#define REG_UNIT_SEL 0x3B >>>>> +#define REG_SYS_TRIGGER 0x3F >>>>> +#define BNO055_REG_PAGE_ID 0x07 >>>>> +#define BNO055_REG_ID 0x00 >>>>> + >>>>> +/* BNO055 status registers */ >>>>> +#define SYS_STATUS 0x39 >>>>> +#define INT_STATUS 0x37 >>>>> +#define CALIB_STATUS 0x35 >>>>> + >>>>> +/* BNO055 data registers */ >>>> >>>> drop newline >>>> >>>>> + >>>>> +#define GRV_DATA_Z_MSB 0x33 >>>>> +#define GRV_DATA_Z_LSB 0x32 >>>>> +#define GRV_DATA_Y_MSB 0x31 >>>>> +#define GRV_DATA_Y_LSB 0x30 >>>>> +#define GRV_DATA_X_MSB 0x2F >>>>> +#define GRV_DATA_X_LSB 0x2E >>>> >>>> do we really need all those register #defines? >>>> they are not used and noone wants to read just the LSB... >>>> >>>>> + >>>>> +#define LIA_DATA_Z_MSB 0x2D >>>>> +#define LIA_DATA_Z_LSB 0x2C >>>>> +#define LIA_DATA_Y_MSB 0x2B >>>>> +#define LIA_DATA_Y_LSB 0x2A >>>>> +#define LIA_DATA_X_MSB 0x29 >>>>> +#define LIA_DATA_X_LSB 0x28 >>>>> + >>>>> +#define QUA_DATA_Z_MSB 0x27 >>>>> +#define QUA_DATA_Z_LSB 0x26 >>>>> +#define QUA_DATA_Y_MSB 0x25 >>>>> +#define QUA_DATA_Y_LSB 0x24 >>>>> +#define QUA_DATA_X_MSB 0x23 >>>>> +#define QUA_DATA_X_LSB 0x22 >>>>> +#define QUA_DATA_W_MSB 0x21 >>>>> +#define QUA_DATA_W_LSB 0x20 >>>>> + >>>>> +#define EUL_PITCH_MSB 0x1F >>>>> +#define EUL_PITCH_LSB 0x1E >>>>> +#define EUL_ROLL_MSB 0x1D >>>>> +#define EUL_ROLL_LSB 0x1C >>>>> +#define EUL_HEADING_MSB 0x1B >>>>> +#define EUL_HEADING_LSB 0x1A >>>>> + >>>>> +#define GYR_DATA_Z_MSB 0x19 >>>>> +#define GYR_DATA_Z_LSB 0x18 >>>>> +#define GYR_DATA_Y_MSB 0x17 >>>>> +#define GYR_DATA_Y_LSB 0x16 >>>>> +#define GYR_DATA_X_MSB 0x15 >>>>> +#define GYR_DATA_X_LSB 0x14 >>>>> + >>>>> +#define MAG_DATA_Z_MSB 0x13 >>>>> +#define MAG_DATA_Z_LSB 0x12 >>>>> +#define MAG_DATA_Y_MSB 0x11 >>>>> +#define MAG_DATA_Y_LSB 0x10 >>>>> +#define MAG_DATA_X_MSB 0x0F >>>>> +#define MAG_DATA_X_LSB 0x0E >>>>> + >>>>> +#define ACC_DATA_Z_MSB 0x0D >>>>> +#define ACC_DATA_Z_LSB 0x0C >>>>> +#define ACC_DATA_Y_MSB 0x0B >>>>> +#define ACC_DATA_Y_LSB 0x0A >>>>> +#define ACC_DATA_X_MSB 0x09 >>>>> +#define ACC_DATA_X_LSB 0x08 >>>>> + >>>>> +/* operation modes */ >>>>> +#define FUSION_NDOF_MODE 0x0C >>>>> + >>>>> +/* power modes */ >>>>> +#define NORMAL_MODE 0x00 >>>>> +#define LOW_POWER_MODE BIT(0) >>>>> +#define SUSPEND_MODE BIT(1) >>>>> + >>>>> +#define PROPER_CALIBRATION 0xFF >>>>> + >>>>> +#define BASE_REG 0x08 >>>>> +#define SENSOR_AXIS_TO_REG(sensor,axis) (BASE_REG + (sensor * 6) + >>>> (axis * 2)) >>>>> + >>>>> +#define BNO055_CHANNEL(_type, _axis) { >>>> \ >>>>> + .type = IIO_##_type, \ >>>>> + .modified = 1, \ >>>>> + .channel2 = IIO_MOD_##_axis, \ >>>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ >>>> >>>> in what unit is the data returned; please check with IIO ABI documentation >>>> I think you will likely need _SCALE for some channels >>>> >>>>> + .scan_index = AXIS_##_axis, \ >>>> >>>> .scan_index is a continuous counter over ALL channels, not just for one >>>> channel type >>>> >>>> the driver does not support buffered reads, so not scan_type/scan_index is >>>> needed anyway >>>> >>>>> + .scan_type = { \ >>>>> + .sign = 's', \ >>>>> + .realbits = 16, \ >>>>> + .storagebits = 16, \ >>>>> + .endianness = IIO_LE, \ >>>>> + }, \ >>>>> +} >>>>> + >>>>> +struct bno055_data { >>>>> + struct i2c_client *client; >>>>> + struct mutex lock; >>>>> + struct regmap *regmap; >>>>> +}; >>>>> + >>>>> +enum bno055_axis { >>>>> + AXIS_X, >>>>> + AXIS_Y, >>>>> + AXIS_Z, >>>>> +}; >>>>> + >>>>> +enum {ACC=0, MAG=1, GYR=2, EUL=3, QUA=4}; >>>> >>>> prefix! >>>> >>>>> + >>>>> +static const struct iio_chan_spec bno055_channels[] = { >>>>> + BNO055_CHANNEL(ANGL_VEL, X), >>>>> + BNO055_CHANNEL(ANGL_VEL, Y), >>>>> + BNO055_CHANNEL(ANGL_VEL, Z), >>>>> + BNO055_CHANNEL(MAGN, X), >>>>> + BNO055_CHANNEL(MAGN, Y), >>>>> + BNO055_CHANNEL(MAGN, Z), >>>>> + BNO055_CHANNEL(ACCEL, X), >>>>> + BNO055_CHANNEL(ACCEL, Y), >>>>> + BNO055_CHANNEL(ACCEL, Z), >>>>> + { >>>>> + .type = IIO_ROT, >>>>> + .modified = 1, >>>>> + .channel2 = IIO_MOD_QUATERNION, >>>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), >>>>> + } >>>>> +}; >>>>> + >>>>> +static bool bno055_is_writeable_reg(struct device *dev, unsigned int >>>> reg) >>>>> +{ >>>>> + switch (reg) { >>>>> + case REG_PWR_MODE: >>>>> + case REG_OPR_MODE: >>>>> + case REG_UNIT_SEL: >>>>> + case REG_AXIS_MAP_SIGN: >>>>> + case REG_AXIS_MAP_CONFIG: >>>>> + case REG_SYS_TRIGGER: >>>>> + return true; >>>>> + default: >>>>> + return false; >>>>> + }; >>>>> +} >>>>> + >>>>> +static bool bno055_is_volatile_reg(struct device *dev, unsigned int reg) >>>>> +{ >>>>> + if ((reg >= ACC_DATA_X_LSB) && (reg <= QUA_DATA_Z_MSB)) >>>>> + return true; >>>>> + else >>>>> + return false; >>>>> +} >>>>> + >>>>> +static const struct regmap_config bno055_regmap_config = { >>>>> + .reg_bits = 8, >>>>> + .val_bits = 8, >>>>> + >>>>> + .max_register = REG_MAG_RADIUS_MSB, >>>>> + .cache_type = REGCACHE_RBTREE, >>>>> + >>>>> + .writeable_reg = bno055_is_writeable_reg, >>>>> + .volatile_reg = bno055_is_volatile_reg, >>>>> +}; >>>>> + >>>> >>>> drop newline >>>> >>>>> + >>>>> +static int read_bno055(struct bno055_data *data, int sensor, int axis, >>>> int *val) >>>> >>>> bno055 prefix please >>>> >>>>> +{ >>>>> + int ret; >>>>> + __le16 raw_val; >>>>> + int reg; >>>>> + >>>>> + reg = SENSOR_AXIS_TO_REG(sensor, axis); >>>> >>>> you can use .address in channel spec for that >>>> >>>>> + >>>>> + ret = regmap_bulk_read(data->regmap, reg, &raw_val, >>>>> + sizeof(raw_val)); >>>>> + if (ret < 0) { >>>>> + dev_err(&data->client->dev, "Error reading axis %d\n", >>>> axis); >>>>> + return ret; >>>>> + } >>>>> + >>>>> + *val = sign_extend32(le16_to_cpu(raw_val), 15); >>>>> + return IIO_VAL_INT; >>>>> +} >>>>> + >>>>> +/* Read raw quaternion values W,X,Y and Z. >>>>> + * Raw values needs to be divied by the 16384 to get the exact >>>> quaternion values. >>>>> + */ >>>>> + >>>>> +static int read_rot(struct bno055_data *data, int *vals, int *val_len) >>>>> +{ >>>>> + int ret, i; >>>>> + __le16 raw_val[4]; >>>>> + >>>>> + ret = regmap_bulk_read(data->regmap, QUA_DATA_W_LSB, &raw_val, >>>>> + sizeof(raw_val)); >>>>> + if (ret < 0) { >>>>> + dev_err(&data->client->dev, "Error reading Orientation >>>> \n"); >>>> >>>> lowercase orientation >>>> delete space before \n >>>> >>>>> + return ret; >>>>> + } >>>>> + >>>>> + for (i = 0; i < 4; ++i) >>>>> + vals[i] = sign_extend32(le16_to_cpu(raw_val[i]), 15); >>>>> + >>>>> + *val_len = 4; >>>>> + return IIO_VAL_INT_MULTIPLE; >>>>> +} >>>>> + >>>>> +static int bno055_read_raw_multi(struct iio_dev *indio_dev, >>>>> + struct iio_chan_spec const *chan, int size, >>>>> + int *val, int *val2, long mask) >>>>> +{ >>>>> + int ret = -EINVAL; >>>>> + struct bno055_data *data = iio_priv(indio_dev); >>>>> + >>>>> + mutex_lock(&data->lock); >>>>> + >>>>> + switch (mask) { >>>>> + case IIO_CHAN_INFO_RAW: >>>>> + switch (chan->type) { >>>>> + case IIO_ROT: >>>>> + ret = read_rot(data, val, val2); >>>>> + break; >>>>> + case IIO_ANGL_VEL: >>>>> + /* Gyroscope unit degrees per second */ >>>>> + /* Raw value needs to be divied by the 16 to get >>>> the exact value.*/ >>>> >>>> comment style, not a proper multi-line comment >>>> use _SCALE, it is not about 'exact' but correct scaling/unit >>>> typo: divided >>>> wording: divided by 16 >>>> >>>>> + ret = read_bno055(data, GYR, chan->scan_index, >>>> val); >>>>> + break; >>>>> + case IIO_ACCEL: >>>>> + /* Accelerometer Unit m/s2 */ >>>>> + /* Raw value needs to be divied by the 100 to get >>>> the exact value.*/ >>>>> + ret = read_bno055(data, ACC, chan->scan_index, >>>> val); >>>>> + break; >>>>> + case IIO_MAGN: >>>>> + /* Magnetometer Unit microTesla */ >>>>> + /* Raw value needs to be divied by the 16 to get >>>> the exact value. */ >>>>> + ret = read_bno055(data, MAG, chan->scan_index, >>>> val); >>>>> + break; >>>>> + default: >>>>> + ret = -EINVAL; >>>>> + break; >>>>> + } >>>>> + break; >>>>> + >>>>> + default: >>>>> + ret = -EINVAL; >>>>> + break; >>>>> + } >>>>> + >>>>> + mutex_unlock(&data->lock); >>>>> + >>>>> + return ret; >>>>> +} >>>>> + >>>>> +static const struct iio_info bno055_info = { >>>>> + .driver_module = THIS_MODULE, >>>>> + .read_raw_multi = &bno055_read_raw_multi, >>>>> +}; >>>>> + >>>> >>>> drop newline >>>> >>>>> + >>>>> +static int bno055_chip_init(struct bno055_data *data) >>>>> +{ >>>>> + int ret; >>>>> + >>>>> + ret = regmap_write(data->regmap, REG_PWR_MODE, >>>>> + NORMAL_MODE); >>>>> + if (ret < 0) { >>>>> + dev_err(&data->client->dev, >>>>> + "failed to write power mode register\n"); >>>>> + return ret; >>>>> + } >>>>> + >>>>> + ret = regmap_write(data->regmap, REG_OPR_MODE, >>>>> + FUSION_NDOF_MODE); >>>>> + if (ret < 0) >>>>> + dev_err(&data->client->dev, >>>>> + "failed to write operation mode register\n"); >>>>> + >>>>> + return ret; >>>>> +} >>>>> + >>>>> +static ssize_t show_calibration_status(struct device *dev, struct >>>> device_attribute *attr, >>>>> + char *buf){ >>>> >>>> this is private API which needs to be documented >>>> >>>>> + struct bno055_data *data; >>>>> + int ret; >>>>> + unsigned int calib_stat; >>>>> + >>>>> + data = dev->driver_data; >>>>> + >>>>> + ret = regmap_read(data->regmap, CALIB_STATUS, &calib_stat); >>>>> + >>>>> + if (ret){ >>>> >>>> space before { >>>> >>>>> + dev_err(dev, "Failed to read calibration status.\n"); >>>>> + return ret; >>>>> + } >>>>> + >>>>> + if (calib_stat != PROPER_CALIBRATION) >>>>> + dev_info(dev, "bad calibration. expected %x got %x\n", >>>> >>>> two spaces before 'expected' >>>> >>>>> + PROPER_CALIBRATION, calib_stat); >>>>> + >>>>> + return scnprintf(buf, PAGE_SIZE, "0x%x\n", calib_stat); >>>>> +} >>>>> + >>>>> +static DEVICE_ATTR(calib_status, 0444, show_calibration_status, NULL); >>>>> + >>>>> +static int bno055_probe(struct i2c_client *client, >>>>> + const struct i2c_device_id *id) >>>>> +{ >>>>> + int ret; >>>>> + struct iio_dev *indio_dev; >>>>> + struct bno055_data *data; >>>>> + unsigned int chip_id; >>>>> + >>>>> + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); >>>>> + if (!indio_dev) >>>>> + return -ENOMEM; >>>>> + >>>>> + data = iio_priv(indio_dev); >>>>> + mutex_init(&data->lock); >>>>> + data->client = client; >>>>> + >>>>> + indio_dev->dev.parent = &client->dev; >>>>> + indio_dev->name = id->name; >>>>> + indio_dev->channels = bno055_channels; >>>>> + indio_dev->num_channels = ARRAY_SIZE(bno055_channels); >>>>> + indio_dev->info = &bno055_info; >>>>> + indio_dev->modes = INDIO_DIRECT_MODE; >>>>> + >>>>> + data->regmap = devm_regmap_init_i2c(client, &bno055_regmap_config); >>>>> + if (IS_ERR(data->regmap)) { >>>>> + dev_err(&client->dev, "Failed to allocate register >>>> map.\n"); >>>>> + return PTR_ERR(data->regmap); >>>>> + } >>>>> + >>>>> + indio_dev->dev.driver_data = data; >>>>> + i2c_set_clientdata(client, indio_dev); >>>>> + >>>>> + ret = regmap_read(data->regmap, BNO055_REG_ID, &chip_id); >>>>> + if (ret < 0) >>>>> + return ret; >>>>> + if (chip_id != BNO055_CHIP_ID) { >>>>> + dev_err(&client->dev, "bad chip id. expected %x got %x\n", >>>>> + BNO055_CHIP_ID, chip_id); >>>>> + return -EINVAL; >>>>> + } >>>>> + >>>>> + ret = bno055_chip_init(data); >>>>> + if (ret < 0) >>>>> + return ret; >>>>> + >>>>> + ret = devm_iio_device_register(&client->dev, indio_dev); >>>>> + if (ret){ >>>> >>>> space before { >>>> >>>>> + dev_err(&client->dev, "failed to register IIO device.\n"); >>>>> + return ret; >>>>> + } >>>>> + >>>>> + ret = device_create_file(&indio_dev->dev, &dev_attr_calib_status); >>>> >>>> needs to be done BEFORE devm_iio_device_register() >>>> IIO has specific mechanisms to create custom sysfs entries... >>>> >>>>> + if (ret){ >>>> >>>> space before { >>>> >>>>> + dev_err(&client->dev, "failed to create sysfs entry.\n"); >>>>> + devm_iio_device_unregister(&client->dev, indio_dev); >>>> >>>> no need to call devm_iio_device_unregister >>>> >>>>> + return ret; >>>>> + } >>>>> + >>>>> + return ret; >>>>> +} >>>>> + >>>>> +static int bno055_remove(struct i2c_client *client) >>>>> +{ >>>>> + struct iio_dev *indio_dev = i2c_get_clientdata(client); >>>>> + >>>>> + device_remove_file(&indio_dev->dev, &dev_attr_calib_status); >>>>> + devm_iio_device_unregister(&client->dev, indio_dev); >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static const struct acpi_device_id bno055_acpi_match[] = { >>>>> + {"bno055", 0}, >>>>> + { }, >>>>> +}; >>>>> +MODULE_DEVICE_TABLE(acpi, bno055_acpi_match); >>>>> + >>>>> +static const struct i2c_device_id bno055_id[] = { >>>>> + {"bno055", 0}, >>>>> + { }, >>>>> +}; >>>>> +MODULE_DEVICE_TABLE(i2c, bno055_id); >>>>> + >>>>> +static struct i2c_driver bno055_driver = { >>>>> + .driver = { >>>>> + .name = "bno055", >>>>> + .acpi_match_table = ACPI_PTR(bno055_acpi_match), >>>>> + }, >>>>> + .probe = bno055_probe, >>>>> + .remove = bno055_remove, >>>>> + .id_table = bno055_id, >>>>> +}; >>>>> +module_i2c_driver(bno055_driver); >>>>> + >>>>> +MODULE_AUTHOR("navin patidar <navin.patidar@gmail.com>"); >>>>> +MODULE_DESCRIPTION("Driver for Bosch Sensortec BNO055 orientation >>>> sensor"); >>>>> +MODULE_LICENSE("GPL v2"); >>>>> >>>> >>>> -- >>>> >>>> Peter Meerwald-Stadler >>>> +43-664-2444418 (mobile) >>>> >>> >> >> -- >> >> Peter Meerwald-Stadler >> +43-664-2444418 (mobile) ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2016-08-15 18:05 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2016-07-25 23:18 [PATCH] iio: orientation: Add BNO055 9-axis Absolute Orientation Sensor driver navin patidar 2016-07-26 8:07 ` Peter Meerwald-Stadler 2016-07-26 18:39 ` navin patidar 2016-07-26 18:46 ` Peter Meerwald-Stadler 2016-07-27 0:22 ` navin patidar 2016-08-15 18:05 ` Jonathan Cameron
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).