> BNO055 provides the following motion sensors data: > > * Gyroscope > * Accelerometer > * Magnetometer > * Absolute Orientation (Quaternion) comments below > Signed-off-by: navin patidar <navin.patidar@xxxxxxxxx> > --- > 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@xxxxxxxxx>"); > +MODULE_DESCRIPTION("Driver for Bosch Sensortec BNO055 orientation sensor"); > +MODULE_LICENSE("GPL v2"); > -- Peter Meerwald-Stadler +43-664-2444418 (mobile) -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html