From 2c3c1048746a4622d8c89a29670120dc8fab93c4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:49:45 +0200 Subject: Adding upstream version 6.1.76. Signed-off-by: Daniel Baumann --- drivers/iio/imu/inv_mpu6050/Kconfig | 34 + drivers/iio/imu/inv_mpu6050/Makefile | 14 + drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c | 198 +++ drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c | 204 +++ drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h | 19 + drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 1778 +++++++++++++++++++++++++ drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c | 278 ++++ drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h | 474 +++++++ drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c | 365 +++++ drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h | 39 + drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 208 +++ drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c | 165 +++ drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c | 251 ++++ 13 files changed, 4027 insertions(+) create mode 100644 drivers/iio/imu/inv_mpu6050/Kconfig create mode 100644 drivers/iio/imu/inv_mpu6050/Makefile create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_core.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c (limited to 'drivers/iio/imu/inv_mpu6050') diff --git a/drivers/iio/imu/inv_mpu6050/Kconfig b/drivers/iio/imu/inv_mpu6050/Kconfig new file mode 100644 index 000000000..3636b1bc9 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# inv-mpu6050 drivers for Invensense MPU devices and combos +# + +config INV_MPU6050_IIO + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config INV_MPU6050_I2C + tristate "Invensense MPU6050 devices (I2C)" + depends on I2C + select I2C_MUX + select INV_MPU6050_IIO + select REGMAP_I2C + help + This driver supports the Invensense MPU6050/9150, + MPU6500/6515/6880/9250/9255, ICM20608(D)/20609/20689, ICM20602/ICM20690 + and IAM20680 motion tracking devices over I2C. + This driver can be built as a module. The module will be called + inv-mpu6050-i2c. + +config INV_MPU6050_SPI + tristate "Invensense MPU6050 devices (SPI)" + depends on SPI_MASTER + select INV_MPU6050_IIO + select REGMAP_SPI + help + This driver supports the Invensense MPU6000, + MPU6500/6515/6880/9250/9255, ICM20608(D)/20609/20689, ICM20602/ICM20690 + and IAM20680 motion tracking devices over SPI. + This driver can be built as a module. The module will be called + inv-mpu6050-spi. diff --git a/drivers/iio/imu/inv_mpu6050/Makefile b/drivers/iio/imu/inv_mpu6050/Makefile new file mode 100644 index 000000000..c103441a9 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for Invensense MPU6050 device. +# + +obj-$(CONFIG_INV_MPU6050_IIO) += inv-mpu6050.o +inv-mpu6050-y := inv_mpu_core.o inv_mpu_ring.o inv_mpu_trigger.o \ + inv_mpu_aux.o inv_mpu_magn.o + +obj-$(CONFIG_INV_MPU6050_I2C) += inv-mpu6050-i2c.o +inv-mpu6050-i2c-y := inv_mpu_i2c.o inv_mpu_acpi.o + +obj-$(CONFIG_INV_MPU6050_SPI) += inv-mpu6050-spi.o +inv-mpu6050-spi-y := inv_mpu_spi.o diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c new file mode 100644 index 000000000..f7bce428d --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * inv_mpu_acpi: ACPI processing for creating client devices + * Copyright (c) 2015, Intel Corporation. + */ + +#ifdef CONFIG_ACPI + +#include +#include +#include +#include +#include "inv_mpu_iio.h" + +enum inv_mpu_product_name { + INV_MPU_NOT_MATCHED, + INV_MPU_ASUS_T100TA, +}; + +static enum inv_mpu_product_name matched_product_name; + +static int __init asus_t100_matched(const struct dmi_system_id *d) +{ + matched_product_name = INV_MPU_ASUS_T100TA; + + return 0; +} + +static const struct dmi_system_id inv_mpu_dev_list[] = { + { + .callback = asus_t100_matched, + .ident = "Asus Transformer Book T100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC"), + DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"), + DMI_MATCH(DMI_PRODUCT_VERSION, "1.0"), + }, + }, + /* Add more matching tables here..*/ + {} +}; + +static int asus_acpi_get_sensor_info(struct acpi_device *adev, + struct i2c_client *client, + struct i2c_board_info *info) +{ + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + int i; + acpi_status status; + union acpi_object *cpm; + int ret; + + status = acpi_evaluate_object(adev->handle, "CNF0", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + cpm = buffer.pointer; + for (i = 0; i < cpm->package.count; ++i) { + union acpi_object *elem; + int j; + + elem = &cpm->package.elements[i]; + for (j = 0; j < elem->package.count; ++j) { + union acpi_object *sub_elem; + + sub_elem = &elem->package.elements[j]; + if (sub_elem->type == ACPI_TYPE_STRING) + strscpy(info->type, sub_elem->string.pointer, + sizeof(info->type)); + else if (sub_elem->type == ACPI_TYPE_INTEGER) { + if (sub_elem->integer.value != client->addr) { + info->addr = sub_elem->integer.value; + break; /* Not a MPU6500 primary */ + } + } + } + } + ret = cpm->package.count; + kfree(buffer.pointer); + + return ret; +} + +static int acpi_i2c_check_resource(struct acpi_resource *ares, void *data) +{ + struct acpi_resource_i2c_serialbus *sb; + u32 *addr = data; + + if (i2c_acpi_get_i2c_resource(ares, &sb)) { + if (*addr) + *addr |= (sb->slave_address << 16); + else + *addr = sb->slave_address; + } + + /* Tell the ACPI core that we already copied this address */ + return 1; +} + +static int inv_mpu_process_acpi_config(struct i2c_client *client, + unsigned short *primary_addr, + unsigned short *secondary_addr) +{ + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + const struct acpi_device_id *id; + u32 i2c_addr = 0; + LIST_HEAD(resources); + int ret; + + id = acpi_match_device(client->dev.driver->acpi_match_table, + &client->dev); + if (!id) + return -ENODEV; + + ret = acpi_dev_get_resources(adev, &resources, + acpi_i2c_check_resource, &i2c_addr); + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&resources); + *primary_addr = i2c_addr & 0x0000ffff; + *secondary_addr = (i2c_addr & 0xffff0000) >> 16; + + return 0; +} + +int inv_mpu_acpi_create_mux_client(struct i2c_client *client) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev)); + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + + st->mux_client = NULL; + if (adev) { + struct i2c_board_info info; + struct i2c_client *mux_client; + int ret = -1; + + memset(&info, 0, sizeof(info)); + + dmi_check_system(inv_mpu_dev_list); + switch (matched_product_name) { + case INV_MPU_ASUS_T100TA: + ret = asus_acpi_get_sensor_info(adev, client, + &info); + break; + /* Add more matched product processing here */ + default: + break; + } + + if (ret < 0) { + /* No matching DMI, so create device on INV6XX type */ + unsigned short primary, secondary; + + ret = inv_mpu_process_acpi_config(client, &primary, + &secondary); + if (!ret && secondary) { + char *name; + + info.addr = secondary; + strscpy(info.type, dev_name(&adev->dev), + sizeof(info.type)); + name = strchr(info.type, ':'); + if (name) + *name = '\0'; + strlcat(info.type, "-client", + sizeof(info.type)); + } else + return 0; /* no secondary addr, which is OK */ + } + mux_client = i2c_new_client_device(st->muxc->adapter[0], &info); + if (IS_ERR(mux_client)) + return PTR_ERR(mux_client); + st->mux_client = mux_client; + } + + return 0; +} + +void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev)); + + i2c_unregister_device(st->mux_client); +} +#else + +#include "inv_mpu_iio.h" + +int inv_mpu_acpi_create_mux_client(struct i2c_client *client) +{ + return 0; +} + +void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) +{ +} +#endif diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c new file mode 100644 index 000000000..7327e5723 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 TDK-InvenSense, Inc. + */ + +#include +#include +#include +#include + +#include "inv_mpu_aux.h" +#include "inv_mpu_iio.h" + +/* + * i2c master auxiliary bus transfer function. + * Requires the i2c operations to be correctly setup before. + */ +static int inv_mpu_i2c_master_xfer(const struct inv_mpu6050_state *st) +{ + /* use 50hz frequency for xfer */ + const unsigned int freq = 50; + const unsigned int period_ms = 1000 / freq; + uint8_t d; + unsigned int user_ctrl; + int ret; + + /* set sample rate */ + d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(freq); + ret = regmap_write(st->map, st->reg->sample_rate_div, d); + if (ret) + return ret; + + /* start i2c master */ + user_ctrl = st->chip_config.user_ctrl | INV_MPU6050_BIT_I2C_MST_EN; + ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl); + if (ret) + goto error_restore_rate; + + /* wait for xfer: 1 period + half-period margin */ + msleep(period_ms + period_ms / 2); + + /* stop i2c master */ + user_ctrl = st->chip_config.user_ctrl; + ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl); + if (ret) + goto error_stop_i2c; + + /* restore sample rate */ + d = st->chip_config.divider; + ret = regmap_write(st->map, st->reg->sample_rate_div, d); + if (ret) + goto error_restore_rate; + + return 0; + +error_stop_i2c: + regmap_write(st->map, st->reg->user_ctrl, st->chip_config.user_ctrl); +error_restore_rate: + regmap_write(st->map, st->reg->sample_rate_div, st->chip_config.divider); + return ret; +} + +/** + * inv_mpu_aux_init() - init i2c auxiliary bus + * @st: driver internal state + * + * Returns 0 on success, a negative error code otherwise. + */ +int inv_mpu_aux_init(const struct inv_mpu6050_state *st) +{ + unsigned int val; + int ret; + + /* configure i2c master */ + val = INV_MPU6050_BITS_I2C_MST_CLK_400KHZ | + INV_MPU6050_BIT_WAIT_FOR_ES; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_MST_CTRL, val); + if (ret) + return ret; + + /* configure i2c master delay */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV4_CTRL, 0); + if (ret) + return ret; + + val = INV_MPU6050_BIT_I2C_SLV0_DLY_EN | + INV_MPU6050_BIT_I2C_SLV1_DLY_EN | + INV_MPU6050_BIT_I2C_SLV2_DLY_EN | + INV_MPU6050_BIT_I2C_SLV3_DLY_EN | + INV_MPU6050_BIT_DELAY_ES_SHADOW; + return regmap_write(st->map, INV_MPU6050_REG_I2C_MST_DELAY_CTRL, val); +} + +/** + * inv_mpu_aux_read() - read register function for i2c auxiliary bus + * @st: driver internal state. + * @addr: chip i2c Address + * @reg: chip register address + * @val: buffer for storing read bytes + * @size: number of bytes to read + * + * Returns 0 on success, a negative error code otherwise. + */ +int inv_mpu_aux_read(const struct inv_mpu6050_state *st, uint8_t addr, + uint8_t reg, uint8_t *val, size_t size) +{ + unsigned int status; + int ret; + + if (size > 0x0F) + return -EINVAL; + + /* setup i2c SLV0 control: i2c addr, register, enable + size */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0), + INV_MPU6050_BIT_I2C_SLV_RNW | addr); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0), reg); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), + INV_MPU6050_BIT_SLV_EN | size); + if (ret) + return ret; + + /* do i2c xfer */ + ret = inv_mpu_i2c_master_xfer(st); + if (ret) + goto error_disable_i2c; + + /* disable i2c slave */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); + if (ret) + goto error_disable_i2c; + + /* check i2c status */ + ret = regmap_read(st->map, INV_MPU6050_REG_I2C_MST_STATUS, &status); + if (ret) + return ret; + if (status & INV_MPU6050_BIT_I2C_SLV0_NACK) + return -EIO; + + /* read data in registers */ + return regmap_bulk_read(st->map, INV_MPU6050_REG_EXT_SENS_DATA, + val, size); + +error_disable_i2c: + regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); + return ret; +} + +/** + * inv_mpu_aux_write() - write register function for i2c auxiliary bus + * @st: driver internal state. + * @addr: chip i2c Address + * @reg: chip register address + * @val: 1 byte value to write + * + * Returns 0 on success, a negative error code otherwise. + */ +int inv_mpu_aux_write(const struct inv_mpu6050_state *st, uint8_t addr, + uint8_t reg, uint8_t val) +{ + unsigned int status; + int ret; + + /* setup i2c SLV0 control: i2c addr, register, value, enable + size */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0), addr); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0), reg); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_DO(0), val); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), + INV_MPU6050_BIT_SLV_EN | 1); + if (ret) + return ret; + + /* do i2c xfer */ + ret = inv_mpu_i2c_master_xfer(st); + if (ret) + goto error_disable_i2c; + + /* disable i2c slave */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); + if (ret) + goto error_disable_i2c; + + /* check i2c status */ + ret = regmap_read(st->map, INV_MPU6050_REG_I2C_MST_STATUS, &status); + if (ret) + return ret; + if (status & INV_MPU6050_BIT_I2C_SLV0_NACK) + return -EIO; + + return 0; + +error_disable_i2c: + regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); + return ret; +} diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h new file mode 100644 index 000000000..b66997545 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 TDK-InvenSense, Inc. + */ + +#ifndef INV_MPU_AUX_H_ +#define INV_MPU_AUX_H_ + +#include "inv_mpu_iio.h" + +int inv_mpu_aux_init(const struct inv_mpu6050_state *st); + +int inv_mpu_aux_read(const struct inv_mpu6050_state *st, uint8_t addr, + uint8_t reg, uint8_t *val, size_t size); + +int inv_mpu_aux_write(const struct inv_mpu6050_state *st, uint8_t addr, + uint8_t reg, uint8_t val); + +#endif /* INV_MPU_AUX_H_ */ diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c new file mode 100644 index 000000000..19a1ef535 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -0,0 +1,1778 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "inv_mpu_iio.h" +#include "inv_mpu_magn.h" + +/* + * this is the gyro scale translated from dynamic range plus/minus + * {250, 500, 1000, 2000} to rad/s + */ +static const int gyro_scale_6050[] = {133090, 266181, 532362, 1064724}; + +/* + * this is the accel scale translated from dynamic range plus/minus + * {2, 4, 8, 16} to m/s^2 + */ +static const int accel_scale[] = {598, 1196, 2392, 4785}; + +static const struct inv_mpu6050_reg_map reg_set_icm20602 = { + .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, + .lpf = INV_MPU6050_REG_CONFIG, + .accel_lpf = INV_MPU6500_REG_ACCEL_CONFIG_2, + .user_ctrl = INV_MPU6050_REG_USER_CTRL, + .fifo_en = INV_MPU6050_REG_FIFO_EN, + .gyro_config = INV_MPU6050_REG_GYRO_CONFIG, + .accl_config = INV_MPU6050_REG_ACCEL_CONFIG, + .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H, + .fifo_r_w = INV_MPU6050_REG_FIFO_R_W, + .raw_gyro = INV_MPU6050_REG_RAW_GYRO, + .raw_accl = INV_MPU6050_REG_RAW_ACCEL, + .temperature = INV_MPU6050_REG_TEMPERATURE, + .int_enable = INV_MPU6050_REG_INT_ENABLE, + .int_status = INV_MPU6050_REG_INT_STATUS, + .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, + .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, + .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG, + .accl_offset = INV_MPU6500_REG_ACCEL_OFFSET, + .gyro_offset = INV_MPU6050_REG_GYRO_OFFSET, + .i2c_if = INV_ICM20602_REG_I2C_IF, +}; + +static const struct inv_mpu6050_reg_map reg_set_6500 = { + .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, + .lpf = INV_MPU6050_REG_CONFIG, + .accel_lpf = INV_MPU6500_REG_ACCEL_CONFIG_2, + .user_ctrl = INV_MPU6050_REG_USER_CTRL, + .fifo_en = INV_MPU6050_REG_FIFO_EN, + .gyro_config = INV_MPU6050_REG_GYRO_CONFIG, + .accl_config = INV_MPU6050_REG_ACCEL_CONFIG, + .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H, + .fifo_r_w = INV_MPU6050_REG_FIFO_R_W, + .raw_gyro = INV_MPU6050_REG_RAW_GYRO, + .raw_accl = INV_MPU6050_REG_RAW_ACCEL, + .temperature = INV_MPU6050_REG_TEMPERATURE, + .int_enable = INV_MPU6050_REG_INT_ENABLE, + .int_status = INV_MPU6050_REG_INT_STATUS, + .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, + .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, + .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG, + .accl_offset = INV_MPU6500_REG_ACCEL_OFFSET, + .gyro_offset = INV_MPU6050_REG_GYRO_OFFSET, + .i2c_if = 0, +}; + +static const struct inv_mpu6050_reg_map reg_set_6050 = { + .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, + .lpf = INV_MPU6050_REG_CONFIG, + .user_ctrl = INV_MPU6050_REG_USER_CTRL, + .fifo_en = INV_MPU6050_REG_FIFO_EN, + .gyro_config = INV_MPU6050_REG_GYRO_CONFIG, + .accl_config = INV_MPU6050_REG_ACCEL_CONFIG, + .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H, + .fifo_r_w = INV_MPU6050_REG_FIFO_R_W, + .raw_gyro = INV_MPU6050_REG_RAW_GYRO, + .raw_accl = INV_MPU6050_REG_RAW_ACCEL, + .temperature = INV_MPU6050_REG_TEMPERATURE, + .int_enable = INV_MPU6050_REG_INT_ENABLE, + .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, + .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, + .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG, + .accl_offset = INV_MPU6050_REG_ACCEL_OFFSET, + .gyro_offset = INV_MPU6050_REG_GYRO_OFFSET, + .i2c_if = 0, +}; + +static const struct inv_mpu6050_chip_config chip_config_6050 = { + .clk = INV_CLK_INTERNAL, + .fsr = INV_MPU6050_FSR_2000DPS, + .lpf = INV_MPU6050_FILTER_20HZ, + .divider = INV_MPU6050_FIFO_RATE_TO_DIVIDER(50), + .gyro_en = true, + .accl_en = true, + .temp_en = true, + .magn_en = false, + .gyro_fifo_enable = false, + .accl_fifo_enable = false, + .temp_fifo_enable = false, + .magn_fifo_enable = false, + .accl_fs = INV_MPU6050_FS_02G, + .user_ctrl = 0, +}; + +static const struct inv_mpu6050_chip_config chip_config_6500 = { + .clk = INV_CLK_PLL, + .fsr = INV_MPU6050_FSR_2000DPS, + .lpf = INV_MPU6050_FILTER_20HZ, + .divider = INV_MPU6050_FIFO_RATE_TO_DIVIDER(50), + .gyro_en = true, + .accl_en = true, + .temp_en = true, + .magn_en = false, + .gyro_fifo_enable = false, + .accl_fifo_enable = false, + .temp_fifo_enable = false, + .magn_fifo_enable = false, + .accl_fs = INV_MPU6050_FS_02G, + .user_ctrl = 0, +}; + +/* Indexed by enum inv_devices */ +static const struct inv_mpu6050_hw hw_info[] = { + { + .whoami = INV_MPU6050_WHOAMI_VALUE, + .name = "MPU6050", + .reg = ®_set_6050, + .config = &chip_config_6050, + .fifo_size = 1024, + .temp = {INV_MPU6050_TEMP_OFFSET, INV_MPU6050_TEMP_SCALE}, + .startup_time = {INV_MPU6050_GYRO_STARTUP_TIME, INV_MPU6050_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU6500_WHOAMI_VALUE, + .name = "MPU6500", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU6515_WHOAMI_VALUE, + .name = "MPU6515", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU6880_WHOAMI_VALUE, + .name = "MPU6880", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 4096, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU6000_WHOAMI_VALUE, + .name = "MPU6000", + .reg = ®_set_6050, + .config = &chip_config_6050, + .fifo_size = 1024, + .temp = {INV_MPU6050_TEMP_OFFSET, INV_MPU6050_TEMP_SCALE}, + .startup_time = {INV_MPU6050_GYRO_STARTUP_TIME, INV_MPU6050_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU9150_WHOAMI_VALUE, + .name = "MPU9150", + .reg = ®_set_6050, + .config = &chip_config_6050, + .fifo_size = 1024, + .temp = {INV_MPU6050_TEMP_OFFSET, INV_MPU6050_TEMP_SCALE}, + .startup_time = {INV_MPU6050_GYRO_STARTUP_TIME, INV_MPU6050_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU9250_WHOAMI_VALUE, + .name = "MPU9250", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU9255_WHOAMI_VALUE, + .name = "MPU9255", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20608_WHOAMI_VALUE, + .name = "ICM20608", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20608D_WHOAMI_VALUE, + .name = "ICM20608D", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20609_WHOAMI_VALUE, + .name = "ICM20609", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 4 * 1024, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20689_WHOAMI_VALUE, + .name = "ICM20689", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 4 * 1024, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20602_WHOAMI_VALUE, + .name = "ICM20602", + .reg = ®_set_icm20602, + .config = &chip_config_6500, + .fifo_size = 1008, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_ICM20602_GYRO_STARTUP_TIME, INV_ICM20602_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20690_WHOAMI_VALUE, + .name = "ICM20690", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 1024, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_ICM20690_GYRO_STARTUP_TIME, INV_ICM20690_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_IAM20680_WHOAMI_VALUE, + .name = "IAM20680", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, +}; + +static int inv_mpu6050_pwr_mgmt_1_write(struct inv_mpu6050_state *st, bool sleep, + int clock, int temp_dis) +{ + u8 val; + + if (clock < 0) + clock = st->chip_config.clk; + if (temp_dis < 0) + temp_dis = !st->chip_config.temp_en; + + val = clock & INV_MPU6050_BIT_CLK_MASK; + if (temp_dis) + val |= INV_MPU6050_BIT_TEMP_DIS; + if (sleep) + val |= INV_MPU6050_BIT_SLEEP; + + dev_dbg(regmap_get_device(st->map), "pwr_mgmt_1: 0x%x\n", val); + return regmap_write(st->map, st->reg->pwr_mgmt_1, val); +} + +static int inv_mpu6050_clock_switch(struct inv_mpu6050_state *st, + unsigned int clock) +{ + int ret; + + switch (st->chip_type) { + case INV_MPU6050: + case INV_MPU6000: + case INV_MPU9150: + /* old chips: switch clock manually */ + ret = inv_mpu6050_pwr_mgmt_1_write(st, false, clock, -1); + if (ret) + return ret; + st->chip_config.clk = clock; + break; + default: + /* automatic clock switching, nothing to do */ + break; + } + + return 0; +} + +int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, + unsigned int mask) +{ + unsigned int sleep; + u8 pwr_mgmt2, user_ctrl; + int ret; + + /* delete useless requests */ + if (mask & INV_MPU6050_SENSOR_ACCL && en == st->chip_config.accl_en) + mask &= ~INV_MPU6050_SENSOR_ACCL; + if (mask & INV_MPU6050_SENSOR_GYRO && en == st->chip_config.gyro_en) + mask &= ~INV_MPU6050_SENSOR_GYRO; + if (mask & INV_MPU6050_SENSOR_TEMP && en == st->chip_config.temp_en) + mask &= ~INV_MPU6050_SENSOR_TEMP; + if (mask & INV_MPU6050_SENSOR_MAGN && en == st->chip_config.magn_en) + mask &= ~INV_MPU6050_SENSOR_MAGN; + if (mask == 0) + return 0; + + /* turn on/off temperature sensor */ + if (mask & INV_MPU6050_SENSOR_TEMP) { + ret = inv_mpu6050_pwr_mgmt_1_write(st, false, -1, !en); + if (ret) + return ret; + st->chip_config.temp_en = en; + } + + /* update user_crtl for driving magnetometer */ + if (mask & INV_MPU6050_SENSOR_MAGN) { + user_ctrl = st->chip_config.user_ctrl; + if (en) + user_ctrl |= INV_MPU6050_BIT_I2C_MST_EN; + else + user_ctrl &= ~INV_MPU6050_BIT_I2C_MST_EN; + ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl); + if (ret) + return ret; + st->chip_config.user_ctrl = user_ctrl; + st->chip_config.magn_en = en; + } + + /* manage accel & gyro engines */ + if (mask & (INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_GYRO)) { + /* compute power management 2 current value */ + pwr_mgmt2 = 0; + if (!st->chip_config.accl_en) + pwr_mgmt2 |= INV_MPU6050_BIT_PWR_ACCL_STBY; + if (!st->chip_config.gyro_en) + pwr_mgmt2 |= INV_MPU6050_BIT_PWR_GYRO_STBY; + + /* update to new requested value */ + if (mask & INV_MPU6050_SENSOR_ACCL) { + if (en) + pwr_mgmt2 &= ~INV_MPU6050_BIT_PWR_ACCL_STBY; + else + pwr_mgmt2 |= INV_MPU6050_BIT_PWR_ACCL_STBY; + } + if (mask & INV_MPU6050_SENSOR_GYRO) { + if (en) + pwr_mgmt2 &= ~INV_MPU6050_BIT_PWR_GYRO_STBY; + else + pwr_mgmt2 |= INV_MPU6050_BIT_PWR_GYRO_STBY; + } + + /* switch clock to internal when turning gyro off */ + if (mask & INV_MPU6050_SENSOR_GYRO && !en) { + ret = inv_mpu6050_clock_switch(st, INV_CLK_INTERNAL); + if (ret) + return ret; + } + + /* update sensors engine */ + dev_dbg(regmap_get_device(st->map), "pwr_mgmt_2: 0x%x\n", + pwr_mgmt2); + ret = regmap_write(st->map, st->reg->pwr_mgmt_2, pwr_mgmt2); + if (ret) + return ret; + if (mask & INV_MPU6050_SENSOR_ACCL) + st->chip_config.accl_en = en; + if (mask & INV_MPU6050_SENSOR_GYRO) + st->chip_config.gyro_en = en; + + /* compute required time to have sensors stabilized */ + sleep = 0; + if (en) { + if (mask & INV_MPU6050_SENSOR_ACCL) { + if (sleep < st->hw->startup_time.accel) + sleep = st->hw->startup_time.accel; + } + if (mask & INV_MPU6050_SENSOR_GYRO) { + if (sleep < st->hw->startup_time.gyro) + sleep = st->hw->startup_time.gyro; + } + } else { + if (mask & INV_MPU6050_SENSOR_GYRO) { + if (sleep < INV_MPU6050_GYRO_DOWN_TIME) + sleep = INV_MPU6050_GYRO_DOWN_TIME; + } + } + if (sleep) + msleep(sleep); + + /* switch clock to PLL when turning gyro on */ + if (mask & INV_MPU6050_SENSOR_GYRO && en) { + ret = inv_mpu6050_clock_switch(st, INV_CLK_PLL); + if (ret) + return ret; + } + } + + return 0; +} + +static int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, + bool power_on) +{ + int result; + + result = inv_mpu6050_pwr_mgmt_1_write(st, !power_on, -1, -1); + if (result) + return result; + + if (power_on) + usleep_range(INV_MPU6050_REG_UP_TIME_MIN, + INV_MPU6050_REG_UP_TIME_MAX); + + return 0; +} + +static int inv_mpu6050_set_gyro_fsr(struct inv_mpu6050_state *st, + enum inv_mpu6050_fsr_e val) +{ + unsigned int gyro_shift; + u8 data; + + switch (st->chip_type) { + case INV_ICM20690: + gyro_shift = INV_ICM20690_GYRO_CONFIG_FSR_SHIFT; + break; + default: + gyro_shift = INV_MPU6050_GYRO_CONFIG_FSR_SHIFT; + break; + } + + data = val << gyro_shift; + return regmap_write(st->map, st->reg->gyro_config, data); +} + +/* + * inv_mpu6050_set_lpf_regs() - set low pass filter registers, chip dependent + * + * MPU60xx/MPU9150 use only 1 register for accelerometer + gyroscope + * MPU6500 and above have a dedicated register for accelerometer + */ +static int inv_mpu6050_set_lpf_regs(struct inv_mpu6050_state *st, + enum inv_mpu6050_filter_e val) +{ + int result; + + result = regmap_write(st->map, st->reg->lpf, val); + if (result) + return result; + + /* set accel lpf */ + switch (st->chip_type) { + case INV_MPU6050: + case INV_MPU6000: + case INV_MPU9150: + /* old chips, nothing to do */ + return 0; + case INV_ICM20689: + case INV_ICM20690: + /* set FIFO size to maximum value */ + val |= INV_ICM20689_BITS_FIFO_SIZE_MAX; + break; + default: + break; + } + + return regmap_write(st->map, st->reg->accel_lpf, val); +} + +/* + * inv_mpu6050_init_config() - Initialize hardware, disable FIFO. + * + * Initial configuration: + * FSR: ± 2000DPS + * DLPF: 20Hz + * FIFO rate: 50Hz + * Clock source: Gyro PLL + */ +static int inv_mpu6050_init_config(struct iio_dev *indio_dev) +{ + int result; + u8 d; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + result = inv_mpu6050_set_gyro_fsr(st, st->chip_config.fsr); + if (result) + return result; + + result = inv_mpu6050_set_lpf_regs(st, st->chip_config.lpf); + if (result) + return result; + + d = st->chip_config.divider; + result = regmap_write(st->map, st->reg->sample_rate_div, d); + if (result) + return result; + + d = (st->chip_config.accl_fs << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); + result = regmap_write(st->map, st->reg->accl_config, d); + if (result) + return result; + + result = regmap_write(st->map, st->reg->int_pin_cfg, st->irq_mask); + if (result) + return result; + + /* + * Internal chip period is 1ms (1kHz). + * Let's use at the beginning the theorical value before measuring + * with interrupt timestamps. + */ + st->chip_period = NSEC_PER_MSEC; + + /* magn chip init, noop if not present in the chip */ + result = inv_mpu_magn_probe(st); + if (result) + return result; + + return 0; +} + +static int inv_mpu6050_sensor_set(struct inv_mpu6050_state *st, int reg, + int axis, int val) +{ + int ind, result; + __be16 d = cpu_to_be16(val); + + ind = (axis - IIO_MOD_X) * 2; + result = regmap_bulk_write(st->map, reg + ind, &d, sizeof(d)); + if (result) + return -EINVAL; + + return 0; +} + +static int inv_mpu6050_sensor_show(struct inv_mpu6050_state *st, int reg, + int axis, int *val) +{ + int ind, result; + __be16 d; + + ind = (axis - IIO_MOD_X) * 2; + result = regmap_bulk_read(st->map, reg + ind, &d, sizeof(d)); + if (result) + return -EINVAL; + *val = (short)be16_to_cpup(&d); + + return IIO_VAL_INT; +} + +static int inv_mpu6050_read_channel_data(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + unsigned int freq_hz, period_us, min_sleep_us, max_sleep_us; + int result; + int ret; + + /* compute sample period */ + freq_hz = INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); + period_us = 1000000 / freq_hz; + + result = pm_runtime_resume_and_get(pdev); + if (result) + return result; + + switch (chan->type) { + case IIO_ANGL_VEL: + if (!st->chip_config.gyro_en) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_SENSOR_GYRO); + if (result) + goto error_power_off; + /* need to wait 2 periods to have first valid sample */ + min_sleep_us = 2 * period_us; + max_sleep_us = 2 * (period_us + period_us / 2); + usleep_range(min_sleep_us, max_sleep_us); + } + ret = inv_mpu6050_sensor_show(st, st->reg->raw_gyro, + chan->channel2, val); + break; + case IIO_ACCEL: + if (!st->chip_config.accl_en) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_SENSOR_ACCL); + if (result) + goto error_power_off; + /* wait 1 period for first sample availability */ + min_sleep_us = period_us; + max_sleep_us = period_us + period_us / 2; + usleep_range(min_sleep_us, max_sleep_us); + } + ret = inv_mpu6050_sensor_show(st, st->reg->raw_accl, + chan->channel2, val); + break; + case IIO_TEMP: + /* temperature sensor work only with accel and/or gyro */ + if (!st->chip_config.accl_en && !st->chip_config.gyro_en) { + result = -EBUSY; + goto error_power_off; + } + if (!st->chip_config.temp_en) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_SENSOR_TEMP); + if (result) + goto error_power_off; + /* wait 1 period for first sample availability */ + min_sleep_us = period_us; + max_sleep_us = period_us + period_us / 2; + usleep_range(min_sleep_us, max_sleep_us); + } + ret = inv_mpu6050_sensor_show(st, st->reg->temperature, + IIO_MOD_X, val); + break; + case IIO_MAGN: + if (!st->chip_config.magn_en) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_SENSOR_MAGN); + if (result) + goto error_power_off; + /* frequency is limited for magnetometer */ + if (freq_hz > INV_MPU_MAGN_FREQ_HZ_MAX) { + freq_hz = INV_MPU_MAGN_FREQ_HZ_MAX; + period_us = 1000000 / freq_hz; + } + /* need to wait 2 periods to have first valid sample */ + min_sleep_us = 2 * period_us; + max_sleep_us = 2 * (period_us + period_us / 2); + usleep_range(min_sleep_us, max_sleep_us); + } + ret = inv_mpu_magn_read(st, chan->channel2, val); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); + + return ret; + +error_power_off: + pm_runtime_put_autosuspend(pdev); + return result; +} + +static int +inv_mpu6050_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + mutex_lock(&st->lock); + ret = inv_mpu6050_read_channel_data(indio_dev, chan, val); + mutex_unlock(&st->lock); + iio_device_release_direct_mode(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + mutex_lock(&st->lock); + *val = 0; + *val2 = gyro_scale_6050[st->chip_config.fsr]; + mutex_unlock(&st->lock); + + return IIO_VAL_INT_PLUS_NANO; + case IIO_ACCEL: + mutex_lock(&st->lock); + *val = 0; + *val2 = accel_scale[st->chip_config.accl_fs]; + mutex_unlock(&st->lock); + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = st->hw->temp.scale / 1000000; + *val2 = st->hw->temp.scale % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + return inv_mpu_magn_get_scale(st, chan, val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = st->hw->temp.offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ANGL_VEL: + mutex_lock(&st->lock); + ret = inv_mpu6050_sensor_show(st, st->reg->gyro_offset, + chan->channel2, val); + mutex_unlock(&st->lock); + return ret; + case IIO_ACCEL: + mutex_lock(&st->lock); + ret = inv_mpu6050_sensor_show(st, st->reg->accl_offset, + chan->channel2, val); + mutex_unlock(&st->lock); + return ret; + + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int inv_mpu6050_write_gyro_scale(struct inv_mpu6050_state *st, int val, + int val2) +{ + int result, i; + + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(gyro_scale_6050); ++i) { + if (gyro_scale_6050[i] == val2) { + result = inv_mpu6050_set_gyro_fsr(st, i); + if (result) + return result; + + st->chip_config.fsr = i; + return 0; + } + } + + return -EINVAL; +} + +static int inv_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } + default: + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int inv_mpu6050_write_accel_scale(struct inv_mpu6050_state *st, int val, + int val2) +{ + int result, i; + u8 d; + + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(accel_scale); ++i) { + if (accel_scale[i] == val2) { + d = (i << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); + result = regmap_write(st->map, st->reg->accl_config, d); + if (result) + return result; + + st->chip_config.accl_fs = i; + return 0; + } + } + + return -EINVAL; +} + +static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + int result; + + /* + * we should only update scale when the chip is disabled, i.e. + * not running + */ + result = iio_device_claim_direct_mode(indio_dev); + if (result) + return result; + + mutex_lock(&st->lock); + result = pm_runtime_resume_and_get(pdev); + if (result) + goto error_write_raw_unlock; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + result = inv_mpu6050_write_gyro_scale(st, val, val2); + break; + case IIO_ACCEL: + result = inv_mpu6050_write_accel_scale(st, val, val2); + break; + default: + result = -EINVAL; + break; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ANGL_VEL: + result = inv_mpu6050_sensor_set(st, + st->reg->gyro_offset, + chan->channel2, val); + break; + case IIO_ACCEL: + result = inv_mpu6050_sensor_set(st, + st->reg->accl_offset, + chan->channel2, val); + break; + default: + result = -EINVAL; + break; + } + break; + default: + result = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); +error_write_raw_unlock: + mutex_unlock(&st->lock); + iio_device_release_direct_mode(indio_dev); + + return result; +} + +/* + * inv_mpu6050_set_lpf() - set low pass filer based on fifo rate. + * + * Based on the Nyquist principle, the bandwidth of the low + * pass filter must not exceed the signal sampling rate divided + * by 2, or there would be aliasing. + * This function basically search for the correct low pass + * parameters based on the fifo rate, e.g, sampling frequency. + * + * lpf is set automatically when setting sampling rate to avoid any aliases. + */ +static int inv_mpu6050_set_lpf(struct inv_mpu6050_state *st, int rate) +{ + static const int hz[] = {400, 200, 90, 40, 20, 10}; + static const int d[] = { + INV_MPU6050_FILTER_200HZ, INV_MPU6050_FILTER_100HZ, + INV_MPU6050_FILTER_45HZ, INV_MPU6050_FILTER_20HZ, + INV_MPU6050_FILTER_10HZ, INV_MPU6050_FILTER_5HZ + }; + int i, result; + u8 data; + + data = INV_MPU6050_FILTER_5HZ; + for (i = 0; i < ARRAY_SIZE(hz); ++i) { + if (rate >= hz[i]) { + data = d[i]; + break; + } + } + result = inv_mpu6050_set_lpf_regs(st, data); + if (result) + return result; + st->chip_config.lpf = data; + + return 0; +} + +/* + * inv_mpu6050_fifo_rate_store() - Set fifo rate. + */ +static ssize_t +inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int fifo_rate; + u8 d; + int result; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + + if (kstrtoint(buf, 10, &fifo_rate)) + return -EINVAL; + if (fifo_rate < INV_MPU6050_MIN_FIFO_RATE || + fifo_rate > INV_MPU6050_MAX_FIFO_RATE) + return -EINVAL; + + /* compute the chip sample rate divider */ + d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(fifo_rate); + /* compute back the fifo rate to handle truncation cases */ + fifo_rate = INV_MPU6050_DIVIDER_TO_FIFO_RATE(d); + + mutex_lock(&st->lock); + if (d == st->chip_config.divider) { + result = 0; + goto fifo_rate_fail_unlock; + } + result = pm_runtime_resume_and_get(pdev); + if (result) + goto fifo_rate_fail_unlock; + + result = regmap_write(st->map, st->reg->sample_rate_div, d); + if (result) + goto fifo_rate_fail_power_off; + st->chip_config.divider = d; + + result = inv_mpu6050_set_lpf(st, fifo_rate); + if (result) + goto fifo_rate_fail_power_off; + + /* update rate for magn, noop if not present in chip */ + result = inv_mpu_magn_set_rate(st, fifo_rate); + if (result) + goto fifo_rate_fail_power_off; + + pm_runtime_mark_last_busy(pdev); +fifo_rate_fail_power_off: + pm_runtime_put_autosuspend(pdev); +fifo_rate_fail_unlock: + mutex_unlock(&st->lock); + if (result) + return result; + + return count; +} + +/* + * inv_fifo_rate_show() - Get the current sampling rate. + */ +static ssize_t +inv_fifo_rate_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned fifo_rate; + + mutex_lock(&st->lock); + fifo_rate = INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); + mutex_unlock(&st->lock); + + return scnprintf(buf, PAGE_SIZE, "%u\n", fifo_rate); +} + +/* + * inv_attr_show() - calling this function will show current + * parameters. + * + * Deprecated in favor of IIO mounting matrix API. + * + * See inv_get_mount_matrix() + */ +static ssize_t inv_attr_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + s8 *m; + + switch (this_attr->address) { + /* + * In MPU6050, the two matrix are the same because gyro and accel + * are integrated in one chip + */ + case ATTR_GYRO_MATRIX: + case ATTR_ACCL_MATRIX: + m = st->plat_data.orientation; + + return scnprintf(buf, PAGE_SIZE, + "%d, %d, %d; %d, %d, %d; %d, %d, %d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + default: + return -EINVAL; + } +} + +/** + * inv_mpu6050_validate_trigger() - validate_trigger callback for invensense + * MPU6050 device. + * @indio_dev: The IIO device + * @trig: The new trigger + * + * Returns: 0 if the 'trig' matches the trigger registered by the MPU6050 + * device, -EINVAL otherwise. + */ +static int inv_mpu6050_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + if (st->trig != trig) + return -EINVAL; + + return 0; +} + +static const struct iio_mount_matrix * +inv_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct inv_mpu6050_state *data = iio_priv(indio_dev); + const struct iio_mount_matrix *matrix; + + if (chan->type == IIO_MAGN) + matrix = &data->magn_orient; + else + matrix = &data->orientation; + + return matrix; +} + +static const struct iio_chan_spec_ext_info inv_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, inv_get_mount_matrix), + { } +}; + +#define INV_MPU6050_CHAN(_type, _channel2, _index) \ + { \ + .type = _type, \ + .modified = 1, \ + .channel2 = _channel2, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = inv_ext_info, \ + } + +#define INV_MPU6050_TEMP_CHAN(_index) \ + { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_OFFSET) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec inv_mpu_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP), + + INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP), + + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), +}; + +#define INV_MPU6050_SCAN_MASK_3AXIS_ACCEL \ + (BIT(INV_MPU6050_SCAN_ACCL_X) \ + | BIT(INV_MPU6050_SCAN_ACCL_Y) \ + | BIT(INV_MPU6050_SCAN_ACCL_Z)) + +#define INV_MPU6050_SCAN_MASK_3AXIS_GYRO \ + (BIT(INV_MPU6050_SCAN_GYRO_X) \ + | BIT(INV_MPU6050_SCAN_GYRO_Y) \ + | BIT(INV_MPU6050_SCAN_GYRO_Z)) + +#define INV_MPU6050_SCAN_MASK_TEMP (BIT(INV_MPU6050_SCAN_TEMP)) + +static const unsigned long inv_mpu_scan_masks[] = { + /* 3-axis accel */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_TEMP, + /* 3-axis gyro */ + INV_MPU6050_SCAN_MASK_3AXIS_GYRO, + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis accel + gyro */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU6050_SCAN_MASK_TEMP, + 0, +}; + +#define INV_MPU9X50_MAGN_CHAN(_chan2, _bits, _index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = _chan2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _bits, \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = inv_ext_info, \ + } + +static const struct iio_chan_spec inv_mpu9150_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU9X50_SCAN_TIMESTAMP), + + INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP), + + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), + + /* Magnetometer resolution is 13 bits */ + INV_MPU9X50_MAGN_CHAN(IIO_MOD_X, 13, INV_MPU9X50_SCAN_MAGN_X), + INV_MPU9X50_MAGN_CHAN(IIO_MOD_Y, 13, INV_MPU9X50_SCAN_MAGN_Y), + INV_MPU9X50_MAGN_CHAN(IIO_MOD_Z, 13, INV_MPU9X50_SCAN_MAGN_Z), +}; + +static const struct iio_chan_spec inv_mpu9250_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU9X50_SCAN_TIMESTAMP), + + INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP), + + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), + + /* Magnetometer resolution is 16 bits */ + INV_MPU9X50_MAGN_CHAN(IIO_MOD_X, 16, INV_MPU9X50_SCAN_MAGN_X), + INV_MPU9X50_MAGN_CHAN(IIO_MOD_Y, 16, INV_MPU9X50_SCAN_MAGN_Y), + INV_MPU9X50_MAGN_CHAN(IIO_MOD_Z, 16, INV_MPU9X50_SCAN_MAGN_Z), +}; + +#define INV_MPU9X50_SCAN_MASK_3AXIS_MAGN \ + (BIT(INV_MPU9X50_SCAN_MAGN_X) \ + | BIT(INV_MPU9X50_SCAN_MAGN_Y) \ + | BIT(INV_MPU9X50_SCAN_MAGN_Z)) + +static const unsigned long inv_mpu9x50_scan_masks[] = { + /* 3-axis accel */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_TEMP, + /* 3-axis gyro */ + INV_MPU6050_SCAN_MASK_3AXIS_GYRO, + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU6050_SCAN_MASK_TEMP, + /* 3-axis magn */ + INV_MPU9X50_SCAN_MASK_3AXIS_MAGN, + INV_MPU9X50_SCAN_MASK_3AXIS_MAGN | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis accel + gyro */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis accel + magn */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN + | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis gyro + magn */ + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN, + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN + | INV_MPU6050_SCAN_MASK_TEMP, + /* 9-axis accel + gyro + magn */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN + | INV_MPU6050_SCAN_MASK_TEMP, + 0, +}; + +static const unsigned long inv_icm20602_scan_masks[] = { + /* 3-axis accel + temp (mandatory) */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_TEMP, + /* 3-axis gyro + temp (mandatory) */ + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis accel + gyro + temp (mandatory) */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU6050_SCAN_MASK_TEMP, + 0, +}; + +/* + * The user can choose any frequency between INV_MPU6050_MIN_FIFO_RATE and + * INV_MPU6050_MAX_FIFO_RATE, but only these frequencies are matched by the + * low-pass filter. Specifically, each of these sampling rates are about twice + * the bandwidth of a corresponding low-pass filter, which should eliminate + * aliasing following the Nyquist principle. By picking a frequency different + * from these, the user risks aliasing effects. + */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("10 20 50 100 200 500"); +static IIO_CONST_ATTR(in_anglvel_scale_available, + "0.000133090 0.000266181 0.000532362 0.001064724"); +static IIO_CONST_ATTR(in_accel_scale_available, + "0.000598 0.001196 0.002392 0.004785"); +static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, inv_fifo_rate_show, + inv_mpu6050_fifo_rate_store); + +/* Deprecated: kept for userspace backward compatibility. */ +static IIO_DEVICE_ATTR(in_gyro_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_GYRO_MATRIX); +static IIO_DEVICE_ATTR(in_accel_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_ACCL_MATRIX); + +static struct attribute *inv_attributes[] = { + &iio_dev_attr_in_gyro_matrix.dev_attr.attr, /* deprecated */ + &iio_dev_attr_in_accel_matrix.dev_attr.attr, /* deprecated */ + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + &iio_const_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group inv_attribute_group = { + .attrs = inv_attributes +}; + +static int inv_mpu6050_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + if (readval) + ret = regmap_read(st->map, reg, readval); + else + ret = regmap_write(st->map, reg, writeval); + mutex_unlock(&st->lock); + + return ret; +} + +static const struct iio_info mpu_info = { + .read_raw = &inv_mpu6050_read_raw, + .write_raw = &inv_mpu6050_write_raw, + .write_raw_get_fmt = &inv_write_raw_get_fmt, + .attrs = &inv_attribute_group, + .validate_trigger = inv_mpu6050_validate_trigger, + .debugfs_reg_access = &inv_mpu6050_reg_access, +}; + +/* + * inv_check_and_setup_chip() - check and setup chip. + */ +static int inv_check_and_setup_chip(struct inv_mpu6050_state *st) +{ + int result; + unsigned int regval, mask; + int i; + + st->hw = &hw_info[st->chip_type]; + st->reg = hw_info[st->chip_type].reg; + memcpy(&st->chip_config, hw_info[st->chip_type].config, + sizeof(st->chip_config)); + + /* check chip self-identification */ + result = regmap_read(st->map, INV_MPU6050_REG_WHOAMI, ®val); + if (result) + return result; + if (regval != st->hw->whoami) { + /* check whoami against all possible values */ + for (i = 0; i < INV_NUM_PARTS; ++i) { + if (regval == hw_info[i].whoami) { + dev_warn(regmap_get_device(st->map), + "whoami mismatch got 0x%02x (%s) expected 0x%02x (%s)\n", + regval, hw_info[i].name, + st->hw->whoami, st->hw->name); + break; + } + } + if (i >= INV_NUM_PARTS) { + dev_err(regmap_get_device(st->map), + "invalid whoami 0x%02x expected 0x%02x (%s)\n", + regval, st->hw->whoami, st->hw->name); + return -ENODEV; + } + } + + /* reset to make sure previous state are not there */ + result = regmap_write(st->map, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_H_RESET); + if (result) + return result; + msleep(INV_MPU6050_POWER_UP_TIME); + switch (st->chip_type) { + case INV_MPU6000: + case INV_MPU6500: + case INV_MPU6515: + case INV_MPU6880: + case INV_MPU9250: + case INV_MPU9255: + /* reset signal path (required for spi connection) */ + regval = INV_MPU6050_BIT_TEMP_RST | INV_MPU6050_BIT_ACCEL_RST | + INV_MPU6050_BIT_GYRO_RST; + result = regmap_write(st->map, INV_MPU6050_REG_SIGNAL_PATH_RESET, + regval); + if (result) + return result; + msleep(INV_MPU6050_POWER_UP_TIME); + break; + default: + break; + } + + /* + * Turn power on. After reset, the sleep bit could be on + * or off depending on the OTP settings. Turning power on + * make it in a definite state as well as making the hardware + * state align with the software state + */ + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + mask = INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_GYRO | + INV_MPU6050_SENSOR_TEMP | INV_MPU6050_SENSOR_MAGN; + result = inv_mpu6050_switch_engine(st, false, mask); + if (result) + goto error_power_off; + + return 0; + +error_power_off: + inv_mpu6050_set_power_itg(st, false); + return result; +} + +static int inv_mpu_core_enable_regulator_vddio(struct inv_mpu6050_state *st) +{ + int result; + + result = regulator_enable(st->vddio_supply); + if (result) { + dev_err(regmap_get_device(st->map), + "Failed to enable vddio regulator: %d\n", result); + } else { + /* Give the device a little bit of time to start up. */ + usleep_range(3000, 5000); + } + + return result; +} + +static int inv_mpu_core_disable_regulator_vddio(struct inv_mpu6050_state *st) +{ + int result; + + result = regulator_disable(st->vddio_supply); + if (result) + dev_err(regmap_get_device(st->map), + "Failed to disable vddio regulator: %d\n", result); + + return result; +} + +static void inv_mpu_core_disable_regulator_action(void *_data) +{ + struct inv_mpu6050_state *st = _data; + int result; + + result = regulator_disable(st->vdd_supply); + if (result) + dev_err(regmap_get_device(st->map), + "Failed to disable vdd regulator: %d\n", result); + + inv_mpu_core_disable_regulator_vddio(st); +} + +static void inv_mpu_pm_disable(void *data) +{ + struct device *dev = data; + + pm_runtime_disable(dev); +} + +int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, + int (*inv_mpu_bus_setup)(struct iio_dev *), int chip_type) +{ + struct inv_mpu6050_state *st; + struct iio_dev *indio_dev; + struct inv_mpu6050_platform_data *pdata; + struct device *dev = regmap_get_device(regmap); + int result; + struct irq_data *desc; + int irq_type; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + BUILD_BUG_ON(ARRAY_SIZE(hw_info) != INV_NUM_PARTS); + if (chip_type < 0 || chip_type >= INV_NUM_PARTS) { + dev_err(dev, "Bad invensense chip_type=%d name=%s\n", + chip_type, name); + return -ENODEV; + } + st = iio_priv(indio_dev); + mutex_init(&st->lock); + st->chip_type = chip_type; + st->irq = irq; + st->map = regmap; + + pdata = dev_get_platdata(dev); + if (!pdata) { + result = iio_read_mount_matrix(dev, &st->orientation); + if (result) { + dev_err(dev, "Failed to retrieve mounting matrix %d\n", + result); + return result; + } + } else { + st->plat_data = *pdata; + } + + if (irq > 0) { + desc = irq_get_irq_data(irq); + if (!desc) { + dev_err(dev, "Could not find IRQ %d\n", irq); + return -EINVAL; + } + + irq_type = irqd_get_trigger_type(desc); + if (!irq_type) + irq_type = IRQF_TRIGGER_RISING; + } else { + /* Doesn't really matter, use the default */ + irq_type = IRQF_TRIGGER_RISING; + } + + if (irq_type & IRQF_TRIGGER_RISING) // rising or both-edge + st->irq_mask = INV_MPU6050_ACTIVE_HIGH; + else if (irq_type == IRQF_TRIGGER_FALLING) + st->irq_mask = INV_MPU6050_ACTIVE_LOW; + else if (irq_type == IRQF_TRIGGER_HIGH) + st->irq_mask = INV_MPU6050_ACTIVE_HIGH | + INV_MPU6050_LATCH_INT_EN; + else if (irq_type == IRQF_TRIGGER_LOW) + st->irq_mask = INV_MPU6050_ACTIVE_LOW | + INV_MPU6050_LATCH_INT_EN; + else { + dev_err(dev, "Invalid interrupt type 0x%x specified\n", + irq_type); + return -EINVAL; + } + + st->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(st->vdd_supply)) + return dev_err_probe(dev, PTR_ERR(st->vdd_supply), + "Failed to get vdd regulator\n"); + + st->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(st->vddio_supply)) + return dev_err_probe(dev, PTR_ERR(st->vddio_supply), + "Failed to get vddio regulator\n"); + + result = regulator_enable(st->vdd_supply); + if (result) { + dev_err(dev, "Failed to enable vdd regulator: %d\n", result); + return result; + } + msleep(INV_MPU6050_POWER_UP_TIME); + + result = inv_mpu_core_enable_regulator_vddio(st); + if (result) { + regulator_disable(st->vdd_supply); + return result; + } + + result = devm_add_action_or_reset(dev, inv_mpu_core_disable_regulator_action, + st); + if (result) { + dev_err(dev, "Failed to setup regulator cleanup action %d\n", + result); + return result; + } + + /* fill magnetometer orientation */ + result = inv_mpu_magn_set_orient(st); + if (result) + return result; + + /* power is turned on inside check chip type*/ + result = inv_check_and_setup_chip(st); + if (result) + return result; + + result = inv_mpu6050_init_config(indio_dev); + if (result) { + dev_err(dev, "Could not initialize device.\n"); + goto error_power_off; + } + + dev_set_drvdata(dev, indio_dev); + /* name will be NULL when enumerated via ACPI */ + if (name) + indio_dev->name = name; + else + indio_dev->name = dev_name(dev); + + /* requires parent device set in indio_dev */ + if (inv_mpu_bus_setup) { + result = inv_mpu_bus_setup(indio_dev); + if (result) + goto error_power_off; + } + + /* chip init is done, turning on runtime power management */ + result = pm_runtime_set_active(dev); + if (result) + goto error_power_off; + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, INV_MPU6050_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + result = devm_add_action_or_reset(dev, inv_mpu_pm_disable, dev); + if (result) + return result; + + switch (chip_type) { + case INV_MPU9150: + indio_dev->channels = inv_mpu9150_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu9150_channels); + indio_dev->available_scan_masks = inv_mpu9x50_scan_masks; + break; + case INV_MPU9250: + case INV_MPU9255: + indio_dev->channels = inv_mpu9250_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu9250_channels); + indio_dev->available_scan_masks = inv_mpu9x50_scan_masks; + break; + case INV_ICM20602: + indio_dev->channels = inv_mpu_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); + indio_dev->available_scan_masks = inv_icm20602_scan_masks; + break; + default: + indio_dev->channels = inv_mpu_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); + indio_dev->available_scan_masks = inv_mpu_scan_masks; + break; + } + /* + * Use magnetometer inside the chip only if there is no i2c + * auxiliary device in use. Otherwise Going back to 6-axis only. + */ + if (st->magn_disabled) { + indio_dev->channels = inv_mpu_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); + indio_dev->available_scan_masks = inv_mpu_scan_masks; + } + + indio_dev->info = &mpu_info; + + if (irq > 0) { + /* + * The driver currently only supports buffered capture with its + * own trigger. So no IRQ, no trigger, no buffer + */ + result = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + inv_mpu6050_read_fifo, + NULL); + if (result) { + dev_err(dev, "configure buffer fail %d\n", result); + return result; + } + + result = inv_mpu6050_probe_trigger(indio_dev, irq_type); + if (result) { + dev_err(dev, "trigger probe fail %d\n", result); + return result; + } + } + + result = devm_iio_device_register(dev, indio_dev); + if (result) { + dev_err(dev, "IIO register fail %d\n", result); + return result; + } + + return 0; + +error_power_off: + inv_mpu6050_set_power_itg(st, false); + return result; +} +EXPORT_SYMBOL_GPL(inv_mpu_core_probe); + +static int __maybe_unused inv_mpu_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int result; + + mutex_lock(&st->lock); + result = inv_mpu_core_enable_regulator_vddio(st); + if (result) + goto out_unlock; + + result = inv_mpu6050_set_power_itg(st, true); + if (result) + goto out_unlock; + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + result = inv_mpu6050_switch_engine(st, true, st->suspended_sensors); + if (result) + goto out_unlock; + + if (iio_buffer_enabled(indio_dev)) + result = inv_mpu6050_prepare_fifo(st, true); + +out_unlock: + mutex_unlock(&st->lock); + + return result; +} + +static int __maybe_unused inv_mpu_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int result; + + mutex_lock(&st->lock); + + st->suspended_sensors = 0; + if (pm_runtime_suspended(dev)) { + result = 0; + goto out_unlock; + } + + if (iio_buffer_enabled(indio_dev)) { + result = inv_mpu6050_prepare_fifo(st, false); + if (result) + goto out_unlock; + } + + if (st->chip_config.accl_en) + st->suspended_sensors |= INV_MPU6050_SENSOR_ACCL; + if (st->chip_config.gyro_en) + st->suspended_sensors |= INV_MPU6050_SENSOR_GYRO; + if (st->chip_config.temp_en) + st->suspended_sensors |= INV_MPU6050_SENSOR_TEMP; + if (st->chip_config.magn_en) + st->suspended_sensors |= INV_MPU6050_SENSOR_MAGN; + result = inv_mpu6050_switch_engine(st, false, st->suspended_sensors); + if (result) + goto out_unlock; + + result = inv_mpu6050_set_power_itg(st, false); + if (result) + goto out_unlock; + + inv_mpu_core_disable_regulator_vddio(st); +out_unlock: + mutex_unlock(&st->lock); + + return result; +} + +static int __maybe_unused inv_mpu_runtime_suspend(struct device *dev) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(dev)); + unsigned int sensors; + int ret; + + mutex_lock(&st->lock); + + sensors = INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_GYRO | + INV_MPU6050_SENSOR_TEMP | INV_MPU6050_SENSOR_MAGN; + ret = inv_mpu6050_switch_engine(st, false, sensors); + if (ret) + goto out_unlock; + + ret = inv_mpu6050_set_power_itg(st, false); + if (ret) + goto out_unlock; + + inv_mpu_core_disable_regulator_vddio(st); + +out_unlock: + mutex_unlock(&st->lock); + return ret; +} + +static int __maybe_unused inv_mpu_runtime_resume(struct device *dev) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = inv_mpu_core_enable_regulator_vddio(st); + if (ret) + return ret; + + return inv_mpu6050_set_power_itg(st, true); +} + +const struct dev_pm_ops inv_mpu_pmops = { + SET_SYSTEM_SLEEP_PM_OPS(inv_mpu_suspend, inv_mpu_resume) + SET_RUNTIME_PM_OPS(inv_mpu_runtime_suspend, inv_mpu_runtime_resume, NULL) +}; +EXPORT_SYMBOL_GPL(inv_mpu_pmops); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device MPU6050 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c new file mode 100644 index 000000000..14255a918 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "inv_mpu_iio.h" + +static const struct regmap_config inv_mpu_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_mpu6050_select_bypass(struct i2c_mux_core *muxc, u32 chan_id) +{ + return 0; +} + +static bool inv_mpu_i2c_aux_bus(struct device *dev) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(dev)); + + switch (st->chip_type) { + case INV_ICM20608: + case INV_ICM20608D: + case INV_ICM20609: + case INV_ICM20689: + case INV_ICM20602: + case INV_IAM20680: + /* no i2c auxiliary bus on the chip */ + return false; + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + if (st->magn_disabled) + return true; + else + return false; + default: + return true; + } +} + +static int inv_mpu_i2c_aux_setup(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct fwnode_handle *mux_node; + int ret; + + /* + * MPU9xxx magnetometer support requires to disable i2c auxiliary bus. + * To ensure backward compatibility with existing setups, do not disable + * i2c auxiliary bus if it used. + * Check for i2c-gate node in devicetree and set magnetometer disabled. + * Only MPU6500 is supported by ACPI, no need to check. + */ + switch (st->chip_type) { + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + mux_node = device_get_named_child_node(dev, "i2c-gate"); + if (mux_node != NULL) { + st->magn_disabled = true; + dev_warn(dev, "disable internal use of magnetometer\n"); + } + fwnode_handle_put(mux_node); + break; + default: + break; + } + + /* enable i2c bypass when using i2c auxiliary bus */ + if (inv_mpu_i2c_aux_bus(dev)) { + ret = regmap_write(st->map, st->reg->int_pin_cfg, + st->irq_mask | INV_MPU6050_BIT_BYPASS_EN); + if (ret) + return ret; + } + + return 0; +} + +/** + * inv_mpu_probe() - probe function. + * @client: i2c client. + * @id: i2c device id. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_mpu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const void *match; + struct inv_mpu6050_state *st; + int result; + enum inv_devices chip_type; + struct regmap *regmap; + const char *name; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EOPNOTSUPP; + + match = device_get_match_data(&client->dev); + if (match) { + chip_type = (uintptr_t)match; + name = client->name; + } else if (id) { + chip_type = (enum inv_devices) + id->driver_data; + name = id->name; + } else { + return -ENOSYS; + } + + regmap = devm_regmap_init_i2c(client, &inv_mpu_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + result = inv_mpu_core_probe(regmap, client->irq, name, + inv_mpu_i2c_aux_setup, chip_type); + if (result < 0) + return result; + + st = iio_priv(dev_get_drvdata(&client->dev)); + if (inv_mpu_i2c_aux_bus(&client->dev)) { + /* declare i2c auxiliary bus */ + st->muxc = i2c_mux_alloc(client->adapter, &client->dev, + 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE, + inv_mpu6050_select_bypass, NULL); + if (!st->muxc) + return -ENOMEM; + st->muxc->priv = dev_get_drvdata(&client->dev); + result = i2c_mux_add_adapter(st->muxc, 0, 0, 0); + if (result) + return result; + result = inv_mpu_acpi_create_mux_client(client); + if (result) + goto out_del_mux; + } + + return 0; + +out_del_mux: + i2c_mux_del_adapters(st->muxc); + return result; +} + +static void inv_mpu_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + if (st->muxc) { + inv_mpu_acpi_delete_mux_client(client); + i2c_mux_del_adapters(st->muxc); + } +} + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_mpu_id[] = { + {"mpu6050", INV_MPU6050}, + {"mpu6500", INV_MPU6500}, + {"mpu6515", INV_MPU6515}, + {"mpu6880", INV_MPU6880}, + {"mpu9150", INV_MPU9150}, + {"mpu9250", INV_MPU9250}, + {"mpu9255", INV_MPU9255}, + {"icm20608", INV_ICM20608}, + {"icm20608d", INV_ICM20608D}, + {"icm20609", INV_ICM20609}, + {"icm20689", INV_ICM20689}, + {"icm20602", INV_ICM20602}, + {"icm20690", INV_ICM20690}, + {"iam20680", INV_IAM20680}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, inv_mpu_id); + +static const struct of_device_id inv_of_match[] = { + { + .compatible = "invensense,mpu6050", + .data = (void *)INV_MPU6050 + }, + { + .compatible = "invensense,mpu6500", + .data = (void *)INV_MPU6500 + }, + { + .compatible = "invensense,mpu6515", + .data = (void *)INV_MPU6515 + }, + { + .compatible = "invensense,mpu6880", + .data = (void *)INV_MPU6880 + }, + { + .compatible = "invensense,mpu9150", + .data = (void *)INV_MPU9150 + }, + { + .compatible = "invensense,mpu9250", + .data = (void *)INV_MPU9250 + }, + { + .compatible = "invensense,mpu9255", + .data = (void *)INV_MPU9255 + }, + { + .compatible = "invensense,icm20608", + .data = (void *)INV_ICM20608 + }, + { + .compatible = "invensense,icm20608d", + .data = (void *)INV_ICM20608D + }, + { + .compatible = "invensense,icm20609", + .data = (void *)INV_ICM20609 + }, + { + .compatible = "invensense,icm20689", + .data = (void *)INV_ICM20689 + }, + { + .compatible = "invensense,icm20602", + .data = (void *)INV_ICM20602 + }, + { + .compatible = "invensense,icm20690", + .data = (void *)INV_ICM20690 + }, + { + .compatible = "invensense,iam20680", + .data = (void *)INV_IAM20680 + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_of_match); + +static const struct acpi_device_id inv_acpi_match[] = { + {"INVN6500", INV_MPU6500}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, inv_acpi_match); + +static struct i2c_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .id_table = inv_mpu_id, + .driver = { + .of_match_table = inv_of_match, + .acpi_match_table = inv_acpi_match, + .name = "inv-mpu6050-i2c", + .pm = &inv_mpu_pmops, + }, +}; + +module_i2c_driver(inv_mpu_driver); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device MPU6050 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h new file mode 100644 index 000000000..94b54c501 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -0,0 +1,474 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#ifndef INV_MPU_IIO_H_ +#define INV_MPU_IIO_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct inv_mpu6050_reg_map - Notable registers. + * @sample_rate_div: Divider applied to gyro output rate. + * @lpf: Configures internal low pass filter. + * @accel_lpf: Configures accelerometer low pass filter. + * @user_ctrl: Enables/resets the FIFO. + * @fifo_en: Determines which data will appear in FIFO. + * @gyro_config: gyro config register. + * @accl_config: accel config register + * @fifo_count_h: Upper byte of FIFO count. + * @fifo_r_w: FIFO register. + * @raw_gyro: Address of first gyro register. + * @raw_accl: Address of first accel register. + * @temperature: temperature register + * @int_enable: Interrupt enable register. + * @int_status: Interrupt status register. + * @pwr_mgmt_1: Controls chip's power state and clock source. + * @pwr_mgmt_2: Controls power state of individual sensors. + * @int_pin_cfg; Controls interrupt pin configuration. + * @accl_offset: Controls the accelerometer calibration offset. + * @gyro_offset: Controls the gyroscope calibration offset. + * @i2c_if: Controls the i2c interface + */ +struct inv_mpu6050_reg_map { + u8 sample_rate_div; + u8 lpf; + u8 accel_lpf; + u8 user_ctrl; + u8 fifo_en; + u8 gyro_config; + u8 accl_config; + u8 fifo_count_h; + u8 fifo_r_w; + u8 raw_gyro; + u8 raw_accl; + u8 temperature; + u8 int_enable; + u8 int_status; + u8 pwr_mgmt_1; + u8 pwr_mgmt_2; + u8 int_pin_cfg; + u8 accl_offset; + u8 gyro_offset; + u8 i2c_if; +}; + +/*device enum */ +enum inv_devices { + INV_MPU6050, + INV_MPU6500, + INV_MPU6515, + INV_MPU6880, + INV_MPU6000, + INV_MPU9150, + INV_MPU9250, + INV_MPU9255, + INV_ICM20608, + INV_ICM20608D, + INV_ICM20609, + INV_ICM20689, + INV_ICM20602, + INV_ICM20690, + INV_IAM20680, + INV_NUM_PARTS +}; + +/* chip sensors mask: accelerometer, gyroscope, temperature, magnetometer */ +#define INV_MPU6050_SENSOR_ACCL BIT(0) +#define INV_MPU6050_SENSOR_GYRO BIT(1) +#define INV_MPU6050_SENSOR_TEMP BIT(2) +#define INV_MPU6050_SENSOR_MAGN BIT(3) + +/** + * struct inv_mpu6050_chip_config - Cached chip configuration data. + * @clk: selected chip clock + * @fsr: Full scale range. + * @lpf: Digital low pass filter frequency. + * @accl_fs: accel full scale range. + * @accl_en: accel engine enabled + * @gyro_en: gyro engine enabled + * @temp_en: temperature sensor enabled + * @magn_en: magn engine (i2c master) enabled + * @accl_fifo_enable: enable accel data output + * @gyro_fifo_enable: enable gyro data output + * @temp_fifo_enable: enable temp data output + * @magn_fifo_enable: enable magn data output + * @divider: chip sample rate divider (sample rate divider - 1) + */ +struct inv_mpu6050_chip_config { + unsigned int clk:3; + unsigned int fsr:2; + unsigned int lpf:3; + unsigned int accl_fs:2; + unsigned int accl_en:1; + unsigned int gyro_en:1; + unsigned int temp_en:1; + unsigned int magn_en:1; + unsigned int accl_fifo_enable:1; + unsigned int gyro_fifo_enable:1; + unsigned int temp_fifo_enable:1; + unsigned int magn_fifo_enable:1; + u8 divider; + u8 user_ctrl; +}; + +/* + * Maximum of 6 + 6 + 2 + 7 (for MPU9x50) = 21 round up to 24 and plus 8. + * May be less if fewer channels are enabled, as long as the timestamp + * remains 8 byte aligned + */ +#define INV_MPU6050_OUTPUT_DATA_SIZE 32 + +/** + * struct inv_mpu6050_hw - Other important hardware information. + * @whoami: Self identification byte from WHO_AM_I register + * @name: name of the chip. + * @reg: register map of the chip. + * @config: configuration of the chip. + * @fifo_size: size of the FIFO in bytes. + * @temp: offset and scale to apply to raw temperature. + */ +struct inv_mpu6050_hw { + u8 whoami; + u8 *name; + const struct inv_mpu6050_reg_map *reg; + const struct inv_mpu6050_chip_config *config; + size_t fifo_size; + struct { + int offset; + int scale; + } temp; + struct { + unsigned int accel; + unsigned int gyro; + } startup_time; +}; + +/* + * struct inv_mpu6050_state - Driver state variables. + * @lock: Chip access lock. + * @trig: IIO trigger. + * @chip_config: Cached attribute information. + * @reg: Map of important registers. + * @hw: Other hardware-specific information. + * @chip_type: chip type. + * @plat_data: platform data (deprecated in favor of @orientation). + * @orientation: sensor chip orientation relative to main hardware. + * @map regmap pointer. + * @irq interrupt number. + * @irq_mask the int_pin_cfg mask to configure interrupt type. + * @chip_period: chip internal period estimation (~1kHz). + * @it_timestamp: timestamp from previous interrupt. + * @data_timestamp: timestamp for next data sample. + * @vdd_supply: VDD voltage regulator for the chip. + * @vddio_supply I/O voltage regulator for the chip. + * @magn_disabled: magnetometer disabled for backward compatibility reason. + * @magn_raw_to_gauss: coefficient to convert mag raw value to Gauss. + * @magn_orient: magnetometer sensor chip orientation if available. + * @suspended_sensors: sensors mask of sensors turned off for suspend + * @data: dma safe buffer used for bulk reads. + */ +struct inv_mpu6050_state { + struct mutex lock; + struct iio_trigger *trig; + struct inv_mpu6050_chip_config chip_config; + const struct inv_mpu6050_reg_map *reg; + const struct inv_mpu6050_hw *hw; + enum inv_devices chip_type; + struct i2c_mux_core *muxc; + struct i2c_client *mux_client; + struct inv_mpu6050_platform_data plat_data; + struct iio_mount_matrix orientation; + struct regmap *map; + int irq; + u8 irq_mask; + unsigned skip_samples; + s64 chip_period; + s64 it_timestamp; + s64 data_timestamp; + struct regulator *vdd_supply; + struct regulator *vddio_supply; + bool magn_disabled; + s32 magn_raw_to_gauss[3]; + struct iio_mount_matrix magn_orient; + unsigned int suspended_sensors; + u8 data[INV_MPU6050_OUTPUT_DATA_SIZE] __aligned(IIO_DMA_MINALIGN); +}; + +/*register and associated bit definition*/ +#define INV_MPU6050_REG_ACCEL_OFFSET 0x06 +#define INV_MPU6050_REG_GYRO_OFFSET 0x13 + +#define INV_MPU6050_REG_SAMPLE_RATE_DIV 0x19 +#define INV_MPU6050_REG_CONFIG 0x1A +#define INV_MPU6050_REG_GYRO_CONFIG 0x1B +#define INV_MPU6050_REG_ACCEL_CONFIG 0x1C + +#define INV_MPU6050_REG_FIFO_EN 0x23 +#define INV_MPU6050_BIT_SLAVE_0 0x01 +#define INV_MPU6050_BIT_SLAVE_1 0x02 +#define INV_MPU6050_BIT_SLAVE_2 0x04 +#define INV_MPU6050_BIT_ACCEL_OUT 0x08 +#define INV_MPU6050_BITS_GYRO_OUT 0x70 +#define INV_MPU6050_BIT_TEMP_OUT 0x80 + +#define INV_MPU6050_REG_I2C_MST_CTRL 0x24 +#define INV_MPU6050_BITS_I2C_MST_CLK_400KHZ 0x0D +#define INV_MPU6050_BIT_I2C_MST_P_NSR 0x10 +#define INV_MPU6050_BIT_SLV3_FIFO_EN 0x20 +#define INV_MPU6050_BIT_WAIT_FOR_ES 0x40 +#define INV_MPU6050_BIT_MULT_MST_EN 0x80 + +/* control I2C slaves from 0 to 3 */ +#define INV_MPU6050_REG_I2C_SLV_ADDR(_x) (0x25 + 3 * (_x)) +#define INV_MPU6050_BIT_I2C_SLV_RNW 0x80 + +#define INV_MPU6050_REG_I2C_SLV_REG(_x) (0x26 + 3 * (_x)) + +#define INV_MPU6050_REG_I2C_SLV_CTRL(_x) (0x27 + 3 * (_x)) +#define INV_MPU6050_BIT_SLV_GRP 0x10 +#define INV_MPU6050_BIT_SLV_REG_DIS 0x20 +#define INV_MPU6050_BIT_SLV_BYTE_SW 0x40 +#define INV_MPU6050_BIT_SLV_EN 0x80 + +/* I2C master delay register */ +#define INV_MPU6050_REG_I2C_SLV4_CTRL 0x34 +#define INV_MPU6050_BITS_I2C_MST_DLY(_x) ((_x) & 0x1F) + +#define INV_MPU6050_REG_I2C_MST_STATUS 0x36 +#define INV_MPU6050_BIT_I2C_SLV0_NACK 0x01 +#define INV_MPU6050_BIT_I2C_SLV1_NACK 0x02 +#define INV_MPU6050_BIT_I2C_SLV2_NACK 0x04 +#define INV_MPU6050_BIT_I2C_SLV3_NACK 0x08 + +#define INV_MPU6050_REG_INT_ENABLE 0x38 +#define INV_MPU6050_BIT_DATA_RDY_EN 0x01 +#define INV_MPU6050_BIT_DMP_INT_EN 0x02 + +#define INV_MPU6050_REG_RAW_ACCEL 0x3B +#define INV_MPU6050_REG_TEMPERATURE 0x41 +#define INV_MPU6050_REG_RAW_GYRO 0x43 + +#define INV_MPU6050_REG_INT_STATUS 0x3A +#define INV_MPU6050_BIT_FIFO_OVERFLOW_INT 0x10 +#define INV_MPU6050_BIT_RAW_DATA_RDY_INT 0x01 + +#define INV_MPU6050_REG_EXT_SENS_DATA 0x49 + +/* I2C slaves data output from 0 to 3 */ +#define INV_MPU6050_REG_I2C_SLV_DO(_x) (0x63 + (_x)) + +#define INV_MPU6050_REG_I2C_MST_DELAY_CTRL 0x67 +#define INV_MPU6050_BIT_I2C_SLV0_DLY_EN 0x01 +#define INV_MPU6050_BIT_I2C_SLV1_DLY_EN 0x02 +#define INV_MPU6050_BIT_I2C_SLV2_DLY_EN 0x04 +#define INV_MPU6050_BIT_I2C_SLV3_DLY_EN 0x08 +#define INV_MPU6050_BIT_DELAY_ES_SHADOW 0x80 + +#define INV_MPU6050_REG_SIGNAL_PATH_RESET 0x68 +#define INV_MPU6050_BIT_TEMP_RST BIT(0) +#define INV_MPU6050_BIT_ACCEL_RST BIT(1) +#define INV_MPU6050_BIT_GYRO_RST BIT(2) + +#define INV_MPU6050_REG_USER_CTRL 0x6A +#define INV_MPU6050_BIT_SIG_COND_RST 0x01 +#define INV_MPU6050_BIT_FIFO_RST 0x04 +#define INV_MPU6050_BIT_DMP_RST 0x08 +#define INV_MPU6050_BIT_I2C_MST_EN 0x20 +#define INV_MPU6050_BIT_FIFO_EN 0x40 +#define INV_MPU6050_BIT_DMP_EN 0x80 +#define INV_MPU6050_BIT_I2C_IF_DIS 0x10 + +#define INV_MPU6050_REG_PWR_MGMT_1 0x6B +#define INV_MPU6050_BIT_H_RESET 0x80 +#define INV_MPU6050_BIT_SLEEP 0x40 +#define INV_MPU6050_BIT_TEMP_DIS 0x08 +#define INV_MPU6050_BIT_CLK_MASK 0x7 + +#define INV_MPU6050_REG_PWR_MGMT_2 0x6C +#define INV_MPU6050_BIT_PWR_ACCL_STBY 0x38 +#define INV_MPU6050_BIT_PWR_GYRO_STBY 0x07 + +/* ICM20602 register */ +#define INV_ICM20602_REG_I2C_IF 0x70 +#define INV_ICM20602_BIT_I2C_IF_DIS 0x40 + +#define INV_MPU6050_REG_FIFO_COUNT_H 0x72 +#define INV_MPU6050_REG_FIFO_R_W 0x74 + +#define INV_MPU6050_BYTES_PER_3AXIS_SENSOR 6 +#define INV_MPU6050_FIFO_COUNT_BYTE 2 + +/* MPU9X50 9-axis magnetometer */ +#define INV_MPU9X50_BYTES_MAGN 7 + +/* FIFO temperature sample size */ +#define INV_MPU6050_BYTES_PER_TEMP_SENSOR 2 + +/* mpu6500 registers */ +#define INV_MPU6500_REG_ACCEL_CONFIG_2 0x1D +#define INV_ICM20689_BITS_FIFO_SIZE_MAX 0xC0 +#define INV_MPU6500_REG_ACCEL_OFFSET 0x77 + +/* delay time in milliseconds */ +#define INV_MPU6050_POWER_UP_TIME 100 +#define INV_MPU6050_TEMP_UP_TIME 100 +#define INV_MPU6050_ACCEL_STARTUP_TIME 20 +#define INV_MPU6050_GYRO_STARTUP_TIME 60 +#define INV_MPU6050_GYRO_DOWN_TIME 150 +#define INV_MPU6050_SUSPEND_DELAY_MS 2000 + +#define INV_MPU6500_GYRO_STARTUP_TIME 70 +#define INV_MPU6500_ACCEL_STARTUP_TIME 30 + +#define INV_ICM20602_GYRO_STARTUP_TIME 100 +#define INV_ICM20602_ACCEL_STARTUP_TIME 20 + +#define INV_ICM20690_GYRO_STARTUP_TIME 80 +#define INV_ICM20690_ACCEL_STARTUP_TIME 10 + + +/* delay time in microseconds */ +#define INV_MPU6050_REG_UP_TIME_MIN 5000 +#define INV_MPU6050_REG_UP_TIME_MAX 10000 + +#define INV_MPU6050_TEMP_OFFSET 12420 +#define INV_MPU6050_TEMP_SCALE 2941176 +#define INV_MPU6050_MAX_GYRO_FS_PARAM 3 +#define INV_MPU6050_MAX_ACCL_FS_PARAM 3 +#define INV_MPU6050_THREE_AXIS 3 +#define INV_MPU6050_GYRO_CONFIG_FSR_SHIFT 3 +#define INV_ICM20690_GYRO_CONFIG_FSR_SHIFT 2 +#define INV_MPU6050_ACCL_CONFIG_FSR_SHIFT 3 + +#define INV_MPU6500_TEMP_OFFSET 7011 +#define INV_MPU6500_TEMP_SCALE 2995178 + +#define INV_ICM20608_TEMP_OFFSET 8170 +#define INV_ICM20608_TEMP_SCALE 3059976 + +#define INV_MPU6050_REG_INT_PIN_CFG 0x37 +#define INV_MPU6050_ACTIVE_HIGH 0x00 +#define INV_MPU6050_ACTIVE_LOW 0x80 +/* enable level triggering */ +#define INV_MPU6050_LATCH_INT_EN 0x20 +#define INV_MPU6050_BIT_BYPASS_EN 0x2 + +/* Allowed timestamp period jitter in percent */ +#define INV_MPU6050_TS_PERIOD_JITTER 4 + +/* init parameters */ +#define INV_MPU6050_MAX_FIFO_RATE 1000 +#define INV_MPU6050_MIN_FIFO_RATE 4 + +/* chip internal frequency: 1KHz */ +#define INV_MPU6050_INTERNAL_FREQ_HZ 1000 +/* return the frequency divider (chip sample rate divider + 1) */ +#define INV_MPU6050_FREQ_DIVIDER(st) \ + ((st)->chip_config.divider + 1) +/* chip sample rate divider to fifo rate */ +#define INV_MPU6050_FIFO_RATE_TO_DIVIDER(fifo_rate) \ + ((INV_MPU6050_INTERNAL_FREQ_HZ / (fifo_rate)) - 1) +#define INV_MPU6050_DIVIDER_TO_FIFO_RATE(divider) \ + (INV_MPU6050_INTERNAL_FREQ_HZ / ((divider) + 1)) + +#define INV_MPU6050_REG_WHOAMI 117 + +#define INV_MPU6000_WHOAMI_VALUE 0x68 +#define INV_MPU6050_WHOAMI_VALUE 0x68 +#define INV_MPU6500_WHOAMI_VALUE 0x70 +#define INV_MPU6880_WHOAMI_VALUE 0x78 +#define INV_MPU9150_WHOAMI_VALUE 0x68 +#define INV_MPU9250_WHOAMI_VALUE 0x71 +#define INV_MPU9255_WHOAMI_VALUE 0x73 +#define INV_MPU6515_WHOAMI_VALUE 0x74 +#define INV_ICM20608_WHOAMI_VALUE 0xAF +#define INV_ICM20608D_WHOAMI_VALUE 0xAE +#define INV_ICM20609_WHOAMI_VALUE 0xA6 +#define INV_ICM20689_WHOAMI_VALUE 0x98 +#define INV_ICM20602_WHOAMI_VALUE 0x12 +#define INV_ICM20690_WHOAMI_VALUE 0x20 +#define INV_IAM20680_WHOAMI_VALUE 0xA9 + +/* scan element definition for generic MPU6xxx devices */ +enum inv_mpu6050_scan { + INV_MPU6050_SCAN_ACCL_X, + INV_MPU6050_SCAN_ACCL_Y, + INV_MPU6050_SCAN_ACCL_Z, + INV_MPU6050_SCAN_TEMP, + INV_MPU6050_SCAN_GYRO_X, + INV_MPU6050_SCAN_GYRO_Y, + INV_MPU6050_SCAN_GYRO_Z, + INV_MPU6050_SCAN_TIMESTAMP, + + INV_MPU9X50_SCAN_MAGN_X = INV_MPU6050_SCAN_GYRO_Z + 1, + INV_MPU9X50_SCAN_MAGN_Y, + INV_MPU9X50_SCAN_MAGN_Z, + INV_MPU9X50_SCAN_TIMESTAMP, +}; + +enum inv_mpu6050_filter_e { + INV_MPU6050_FILTER_NOLPF2 = 0, + INV_MPU6050_FILTER_200HZ, + INV_MPU6050_FILTER_100HZ, + INV_MPU6050_FILTER_45HZ, + INV_MPU6050_FILTER_20HZ, + INV_MPU6050_FILTER_10HZ, + INV_MPU6050_FILTER_5HZ, + INV_MPU6050_FILTER_NOLPF, + NUM_MPU6050_FILTER +}; + +/* IIO attribute address */ +enum INV_MPU6050_IIO_ATTR_ADDR { + ATTR_GYRO_MATRIX, + ATTR_ACCL_MATRIX, +}; + +enum inv_mpu6050_accl_fs_e { + INV_MPU6050_FS_02G = 0, + INV_MPU6050_FS_04G, + INV_MPU6050_FS_08G, + INV_MPU6050_FS_16G, + NUM_ACCL_FSR +}; + +enum inv_mpu6050_fsr_e { + INV_MPU6050_FSR_250DPS = 0, + INV_MPU6050_FSR_500DPS, + INV_MPU6050_FSR_1000DPS, + INV_MPU6050_FSR_2000DPS, + NUM_MPU6050_FSR +}; + +enum inv_mpu6050_clock_sel_e { + INV_CLK_INTERNAL = 0, + INV_CLK_PLL, + NUM_CLK +}; + +irqreturn_t inv_mpu6050_read_fifo(int irq, void *p); +int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev, int irq_type); +int inv_mpu6050_prepare_fifo(struct inv_mpu6050_state *st, bool enable); +int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, + unsigned int mask); +int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 val); +int inv_mpu_acpi_create_mux_client(struct i2c_client *client); +void inv_mpu_acpi_delete_mux_client(struct i2c_client *client); +int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, + int (*inv_mpu_bus_setup)(struct iio_dev *), int chip_type); +extern const struct dev_pm_ops inv_mpu_pmops; + +#endif diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c new file mode 100644 index 000000000..6aee6c989 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 TDK-InvenSense, Inc. + */ + +#include +#include +#include + +#include "inv_mpu_aux.h" +#include "inv_mpu_iio.h" +#include "inv_mpu_magn.h" + +/* + * MPU9xxx magnetometer are AKM chips on I2C aux bus + * MPU9150 is AK8975 + * MPU9250 is AK8963 + */ +#define INV_MPU_MAGN_I2C_ADDR 0x0C + +#define INV_MPU_MAGN_REG_WIA 0x00 +#define INV_MPU_MAGN_BITS_WIA 0x48 + +#define INV_MPU_MAGN_REG_ST1 0x02 +#define INV_MPU_MAGN_BIT_DRDY 0x01 +#define INV_MPU_MAGN_BIT_DOR 0x02 + +#define INV_MPU_MAGN_REG_DATA 0x03 + +#define INV_MPU_MAGN_REG_ST2 0x09 +#define INV_MPU_MAGN_BIT_HOFL 0x08 +#define INV_MPU_MAGN_BIT_BITM 0x10 + +#define INV_MPU_MAGN_REG_CNTL1 0x0A +#define INV_MPU_MAGN_BITS_MODE_PWDN 0x00 +#define INV_MPU_MAGN_BITS_MODE_SINGLE 0x01 +#define INV_MPU_MAGN_BITS_MODE_FUSE 0x0F +#define INV_MPU9250_MAGN_BIT_OUTPUT_BIT 0x10 + +#define INV_MPU9250_MAGN_REG_CNTL2 0x0B +#define INV_MPU9250_MAGN_BIT_SRST 0x01 + +#define INV_MPU_MAGN_REG_ASAX 0x10 +#define INV_MPU_MAGN_REG_ASAY 0x11 +#define INV_MPU_MAGN_REG_ASAZ 0x12 + +static bool inv_magn_supported(const struct inv_mpu6050_state *st) +{ + switch (st->chip_type) { + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + return true; + default: + return false; + } +} + +/* init magnetometer chip */ +static int inv_magn_init(struct inv_mpu6050_state *st) +{ + uint8_t val; + uint8_t asa[3]; + int32_t sensitivity; + int ret; + + /* check whoami */ + ret = inv_mpu_aux_read(st, INV_MPU_MAGN_I2C_ADDR, INV_MPU_MAGN_REG_WIA, + &val, sizeof(val)); + if (ret) + return ret; + if (val != INV_MPU_MAGN_BITS_WIA) + return -ENODEV; + + /* software reset for MPU925x only */ + switch (st->chip_type) { + case INV_MPU9250: + case INV_MPU9255: + ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR, + INV_MPU9250_MAGN_REG_CNTL2, + INV_MPU9250_MAGN_BIT_SRST); + if (ret) + return ret; + break; + default: + break; + } + + /* read fuse ROM data */ + ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR, + INV_MPU_MAGN_REG_CNTL1, + INV_MPU_MAGN_BITS_MODE_FUSE); + if (ret) + return ret; + + ret = inv_mpu_aux_read(st, INV_MPU_MAGN_I2C_ADDR, INV_MPU_MAGN_REG_ASAX, + asa, sizeof(asa)); + if (ret) + return ret; + + /* switch back to power-down */ + ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR, + INV_MPU_MAGN_REG_CNTL1, + INV_MPU_MAGN_BITS_MODE_PWDN); + if (ret) + return ret; + + /* + * Sensor sentivity + * 1 uT = 0.01 G and value is in micron (1e6) + * sensitvity = x uT * 0.01 * 1e6 + */ + switch (st->chip_type) { + case INV_MPU9150: + /* sensor sensitivity is 0.3 uT */ + sensitivity = 3000; + break; + case INV_MPU9250: + case INV_MPU9255: + /* sensor sensitivity in 16 bits mode: 0.15 uT */ + sensitivity = 1500; + break; + default: + return -EINVAL; + } + + /* + * Sensitivity adjustement and scale to Gauss + * + * Hadj = H * (((ASA - 128) * 0.5 / 128) + 1) + * Factor simplification: + * Hadj = H * ((ASA + 128) / 256) + * + * raw_to_gauss = Hadj * sensitivity + */ + st->magn_raw_to_gauss[0] = (((int32_t)asa[0] + 128) * sensitivity) / 256; + st->magn_raw_to_gauss[1] = (((int32_t)asa[1] + 128) * sensitivity) / 256; + st->magn_raw_to_gauss[2] = (((int32_t)asa[2] + 128) * sensitivity) / 256; + + return 0; +} + +/** + * inv_mpu_magn_probe() - probe and setup magnetometer chip + * @st: driver internal state + * + * Returns 0 on success, a negative error code otherwise + * + * It is probing the chip and setting up all needed i2c transfers. + * Noop if there is no magnetometer in the chip. + */ +int inv_mpu_magn_probe(struct inv_mpu6050_state *st) +{ + uint8_t val; + int ret; + + /* quit if chip is not supported */ + if (!inv_magn_supported(st)) + return 0; + + /* configure i2c master aux port */ + ret = inv_mpu_aux_init(st); + if (ret) + return ret; + + /* check and init mag chip */ + ret = inv_magn_init(st); + if (ret) + return ret; + + /* + * configure mpu i2c master accesses + * i2c SLV0: read sensor data, 7 bytes data(6)-ST2 + * Byte swap data to store them in big-endian in impair address groups + */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0), + INV_MPU6050_BIT_I2C_SLV_RNW | INV_MPU_MAGN_I2C_ADDR); + if (ret) + return ret; + + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0), + INV_MPU_MAGN_REG_DATA); + if (ret) + return ret; + + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), + INV_MPU6050_BIT_SLV_EN | + INV_MPU6050_BIT_SLV_BYTE_SW | + INV_MPU6050_BIT_SLV_GRP | + INV_MPU9X50_BYTES_MAGN); + if (ret) + return ret; + + /* i2c SLV1: launch single measurement */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(1), + INV_MPU_MAGN_I2C_ADDR); + if (ret) + return ret; + + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(1), + INV_MPU_MAGN_REG_CNTL1); + if (ret) + return ret; + + /* add 16 bits mode for MPU925x */ + val = INV_MPU_MAGN_BITS_MODE_SINGLE; + switch (st->chip_type) { + case INV_MPU9250: + case INV_MPU9255: + val |= INV_MPU9250_MAGN_BIT_OUTPUT_BIT; + break; + default: + break; + } + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_DO(1), val); + if (ret) + return ret; + + return regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(1), + INV_MPU6050_BIT_SLV_EN | 1); +} + +/** + * inv_mpu_magn_set_rate() - set magnetometer sampling rate + * @st: driver internal state + * @fifo_rate: mpu set fifo rate + * + * Returns 0 on success, a negative error code otherwise + * + * Limit sampling frequency to the maximum value supported by the + * magnetometer chip. Resulting in duplicated data for higher frequencies. + * Noop if there is no magnetometer in the chip. + */ +int inv_mpu_magn_set_rate(const struct inv_mpu6050_state *st, int fifo_rate) +{ + uint8_t d; + + /* quit if chip is not supported */ + if (!inv_magn_supported(st)) + return 0; + + /* + * update i2c master delay to limit mag sampling to max frequency + * compute fifo_rate divider d: rate = fifo_rate / (d + 1) + */ + if (fifo_rate > INV_MPU_MAGN_FREQ_HZ_MAX) + d = fifo_rate / INV_MPU_MAGN_FREQ_HZ_MAX - 1; + else + d = 0; + + return regmap_write(st->map, INV_MPU6050_REG_I2C_SLV4_CTRL, d); +} + +/** + * inv_mpu_magn_set_orient() - fill magnetometer mounting matrix + * @st: driver internal state + * + * Returns 0 on success, a negative error code otherwise + * + * Fill magnetometer mounting matrix using the provided chip matrix. + */ +int inv_mpu_magn_set_orient(struct inv_mpu6050_state *st) +{ + struct device *dev = regmap_get_device(st->map); + const char *orient; + char *str; + int i; + + /* fill magnetometer orientation */ + switch (st->chip_type) { + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + /* x <- y */ + st->magn_orient.rotation[0] = st->orientation.rotation[3]; + st->magn_orient.rotation[1] = st->orientation.rotation[4]; + st->magn_orient.rotation[2] = st->orientation.rotation[5]; + /* y <- x */ + st->magn_orient.rotation[3] = st->orientation.rotation[0]; + st->magn_orient.rotation[4] = st->orientation.rotation[1]; + st->magn_orient.rotation[5] = st->orientation.rotation[2]; + /* z <- -z */ + for (i = 6; i < 9; ++i) { + orient = st->orientation.rotation[i]; + + /* + * The value is negated according to one of the following + * rules: + * + * 1) Drop leading minus. + * 2) Leave 0 as is. + * 3) Add leading minus. + */ + if (orient[0] == '-') + str = devm_kstrdup(dev, orient + 1, GFP_KERNEL); + else if (!strcmp(orient, "0")) + str = devm_kstrdup(dev, orient, GFP_KERNEL); + else + str = devm_kasprintf(dev, GFP_KERNEL, "-%s", orient); + if (!str) + return -ENOMEM; + + st->magn_orient.rotation[i] = str; + } + break; + default: + st->magn_orient = st->orientation; + break; + } + + return 0; +} + +/** + * inv_mpu_magn_read() - read magnetometer data + * @st: driver internal state + * @axis: IIO modifier axis value + * @val: store corresponding axis value + * + * Returns 0 on success, a negative error code otherwise + */ +int inv_mpu_magn_read(struct inv_mpu6050_state *st, int axis, int *val) +{ + unsigned int status; + __be16 data; + uint8_t addr; + int ret; + + /* quit if chip is not supported */ + if (!inv_magn_supported(st)) + return -ENODEV; + + /* Mag data: XH,XL,YH,YL,ZH,ZL */ + switch (axis) { + case IIO_MOD_X: + addr = 0; + break; + case IIO_MOD_Y: + addr = 2; + break; + case IIO_MOD_Z: + addr = 4; + break; + default: + return -EINVAL; + } + addr += INV_MPU6050_REG_EXT_SENS_DATA; + + /* check i2c status and read raw data */ + ret = regmap_read(st->map, INV_MPU6050_REG_I2C_MST_STATUS, &status); + if (ret) + return ret; + + if (status & INV_MPU6050_BIT_I2C_SLV0_NACK || + status & INV_MPU6050_BIT_I2C_SLV1_NACK) + return -EIO; + + ret = regmap_bulk_read(st->map, addr, &data, sizeof(data)); + if (ret) + return ret; + + *val = (int16_t)be16_to_cpu(data); + + return IIO_VAL_INT; +} diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h new file mode 100644 index 000000000..185c000c6 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 TDK-InvenSense, Inc. + */ + +#ifndef INV_MPU_MAGN_H_ +#define INV_MPU_MAGN_H_ + +#include + +#include "inv_mpu_iio.h" + +/* Magnetometer maximum frequency */ +#define INV_MPU_MAGN_FREQ_HZ_MAX 50 + +int inv_mpu_magn_probe(struct inv_mpu6050_state *st); + +/** + * inv_mpu_magn_get_scale() - get magnetometer scale value + * @st: driver internal state + * + * Returns IIO data format. + */ +static inline int inv_mpu_magn_get_scale(const struct inv_mpu6050_state *st, + const struct iio_chan_spec *chan, + int *val, int *val2) +{ + *val = 0; + *val2 = st->magn_raw_to_gauss[chan->address]; + return IIO_VAL_INT_PLUS_MICRO; +} + +int inv_mpu_magn_set_rate(const struct inv_mpu6050_state *st, int fifo_rate); + +int inv_mpu_magn_set_orient(struct inv_mpu6050_state *st); + +int inv_mpu_magn_read(struct inv_mpu6050_state *st, int axis, int *val); + +#endif /* INV_MPU_MAGN_H_ */ diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c new file mode 100644 index 000000000..45c37525c --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "inv_mpu_iio.h" + +/** + * inv_mpu6050_update_period() - Update chip internal period estimation + * + * @st: driver state + * @timestamp: the interrupt timestamp + * @nb: number of data set in the fifo + * + * This function uses interrupt timestamps to estimate the chip period and + * to choose the data timestamp to come. + */ +static void inv_mpu6050_update_period(struct inv_mpu6050_state *st, + s64 timestamp, size_t nb) +{ + /* Period boundaries for accepting timestamp */ + const s64 period_min = + (NSEC_PER_MSEC * (100 - INV_MPU6050_TS_PERIOD_JITTER)) / 100; + const s64 period_max = + (NSEC_PER_MSEC * (100 + INV_MPU6050_TS_PERIOD_JITTER)) / 100; + const s32 divider = INV_MPU6050_FREQ_DIVIDER(st); + s64 delta, interval; + bool use_it_timestamp = false; + + if (st->it_timestamp == 0) { + /* not initialized, forced to use it_timestamp */ + use_it_timestamp = true; + } else if (nb == 1) { + /* + * Validate the use of it timestamp by checking if interrupt + * has been delayed. + * nb > 1 means interrupt was delayed for more than 1 sample, + * so it's obviously not good. + * Compute the chip period between 2 interrupts for validating. + */ + delta = div_s64(timestamp - st->it_timestamp, divider); + if (delta > period_min && delta < period_max) { + /* update chip period and use it timestamp */ + st->chip_period = (st->chip_period + delta) / 2; + use_it_timestamp = true; + } + } + + if (use_it_timestamp) { + /* + * Manage case of multiple samples in the fifo (nb > 1): + * compute timestamp corresponding to the first sample using + * estimated chip period. + */ + interval = (nb - 1) * st->chip_period * divider; + st->data_timestamp = timestamp - interval; + } + + /* save it timestamp */ + st->it_timestamp = timestamp; +} + +/** + * inv_mpu6050_get_timestamp() - Return the current data timestamp + * + * @st: driver state + * @return: current data timestamp + * + * This function returns the current data timestamp and prepares for next one. + */ +static s64 inv_mpu6050_get_timestamp(struct inv_mpu6050_state *st) +{ + s64 ts; + + /* return current data timestamp and increment */ + ts = st->data_timestamp; + st->data_timestamp += st->chip_period * INV_MPU6050_FREQ_DIVIDER(st); + + return ts; +} + +static int inv_reset_fifo(struct iio_dev *indio_dev) +{ + int result; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + /* disable fifo and reenable it */ + inv_mpu6050_prepare_fifo(st, false); + result = inv_mpu6050_prepare_fifo(st, true); + if (result) + goto reset_fifo_fail; + + return 0; + +reset_fifo_fail: + dev_err(regmap_get_device(st->map), "reset fifo failed %d\n", result); + result = regmap_write(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN); + + return result; +} + +/* + * inv_mpu6050_read_fifo() - Transfer data from hardware FIFO to KFIFO. + */ +irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + size_t bytes_per_datum; + int result; + u16 fifo_count; + s64 timestamp; + int int_status; + size_t i, nb; + + mutex_lock(&st->lock); + + /* ack interrupt and check status */ + result = regmap_read(st->map, st->reg->int_status, &int_status); + if (result) { + dev_err(regmap_get_device(st->map), + "failed to ack interrupt\n"); + goto flush_fifo; + } + if (!(int_status & INV_MPU6050_BIT_RAW_DATA_RDY_INT)) + goto end_session; + + if (!(st->chip_config.accl_fifo_enable | + st->chip_config.gyro_fifo_enable | + st->chip_config.magn_fifo_enable)) + goto end_session; + bytes_per_datum = 0; + if (st->chip_config.accl_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; + + if (st->chip_config.gyro_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; + + if (st->chip_config.temp_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_TEMP_SENSOR; + + if (st->chip_config.magn_fifo_enable) + bytes_per_datum += INV_MPU9X50_BYTES_MAGN; + + /* + * read fifo_count register to know how many bytes are inside the FIFO + * right now + */ + result = regmap_bulk_read(st->map, st->reg->fifo_count_h, + st->data, INV_MPU6050_FIFO_COUNT_BYTE); + if (result) + goto end_session; + fifo_count = be16_to_cpup((__be16 *)&st->data[0]); + + /* + * Handle fifo overflow by resetting fifo. + * Reset if there is only 3 data set free remaining to mitigate + * possible delay between reading fifo count and fifo data. + */ + nb = 3 * bytes_per_datum; + if (fifo_count >= st->hw->fifo_size - nb) { + dev_warn(regmap_get_device(st->map), "fifo overflow reset\n"); + goto flush_fifo; + } + + /* compute and process all complete datum */ + nb = fifo_count / bytes_per_datum; + inv_mpu6050_update_period(st, pf->timestamp, nb); + for (i = 0; i < nb; ++i) { + result = regmap_noinc_read(st->map, st->reg->fifo_r_w, + st->data, bytes_per_datum); + if (result) + goto flush_fifo; + /* skip first samples if needed */ + if (st->skip_samples) { + st->skip_samples--; + continue; + } + timestamp = inv_mpu6050_get_timestamp(st); + iio_push_to_buffers_with_timestamp(indio_dev, st->data, timestamp); + } + +end_session: + mutex_unlock(&st->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; + +flush_fifo: + /* Flush HW and SW FIFOs. */ + inv_reset_fifo(indio_dev); + mutex_unlock(&st->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c new file mode 100644 index 000000000..e6107b0cc --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2015 Intel Corporation Inc. +*/ +#include +#include +#include +#include +#include +#include +#include "inv_mpu_iio.h" + +static const struct regmap_config inv_mpu_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_mpu_i2c_disable(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret = 0; + + if (st->reg->i2c_if) { + ret = regmap_write(st->map, st->reg->i2c_if, + INV_ICM20602_BIT_I2C_IF_DIS); + } else { + st->chip_config.user_ctrl |= INV_MPU6050_BIT_I2C_IF_DIS; + ret = regmap_write(st->map, st->reg->user_ctrl, + st->chip_config.user_ctrl); + } + + return ret; +} + +static int inv_mpu_probe(struct spi_device *spi) +{ + const void *match; + struct regmap *regmap; + const struct spi_device_id *spi_id; + const char *name = NULL; + enum inv_devices chip_type; + + if ((spi_id = spi_get_device_id(spi))) { + chip_type = (enum inv_devices)spi_id->driver_data; + name = spi_id->name; + } else if ((match = device_get_match_data(&spi->dev))) { + chip_type = (uintptr_t)match; + name = dev_name(&spi->dev); + } else { + return -ENODEV; + } + + regmap = devm_regmap_init_spi(spi, &inv_mpu_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + return inv_mpu_core_probe(regmap, spi->irq, name, + inv_mpu_i2c_disable, chip_type); +} + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct spi_device_id inv_mpu_id[] = { + {"mpu6000", INV_MPU6000}, + {"mpu6500", INV_MPU6500}, + {"mpu6515", INV_MPU6515}, + {"mpu6880", INV_MPU6880}, + {"mpu9250", INV_MPU9250}, + {"mpu9255", INV_MPU9255}, + {"icm20608", INV_ICM20608}, + {"icm20608d", INV_ICM20608D}, + {"icm20609", INV_ICM20609}, + {"icm20689", INV_ICM20689}, + {"icm20602", INV_ICM20602}, + {"icm20690", INV_ICM20690}, + {"iam20680", INV_IAM20680}, + {} +}; + +MODULE_DEVICE_TABLE(spi, inv_mpu_id); + +static const struct of_device_id inv_of_match[] = { + { + .compatible = "invensense,mpu6000", + .data = (void *)INV_MPU6000 + }, + { + .compatible = "invensense,mpu6500", + .data = (void *)INV_MPU6500 + }, + { + .compatible = "invensense,mpu6515", + .data = (void *)INV_MPU6515 + }, + { + .compatible = "invensense,mpu6880", + .data = (void *)INV_MPU6880 + }, + { + .compatible = "invensense,mpu9250", + .data = (void *)INV_MPU9250 + }, + { + .compatible = "invensense,mpu9255", + .data = (void *)INV_MPU9255 + }, + { + .compatible = "invensense,icm20608", + .data = (void *)INV_ICM20608 + }, + { + .compatible = "invensense,icm20608d", + .data = (void *)INV_ICM20608D + }, + { + .compatible = "invensense,icm20609", + .data = (void *)INV_ICM20609 + }, + { + .compatible = "invensense,icm20689", + .data = (void *)INV_ICM20689 + }, + { + .compatible = "invensense,icm20602", + .data = (void *)INV_ICM20602 + }, + { + .compatible = "invensense,icm20690", + .data = (void *)INV_ICM20690 + }, + { + .compatible = "invensense,iam20680", + .data = (void *)INV_IAM20680 + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_of_match); + +static const struct acpi_device_id inv_acpi_match[] = { + {"INVN6000", INV_MPU6000}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, inv_acpi_match); + +static struct spi_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .id_table = inv_mpu_id, + .driver = { + .of_match_table = inv_of_match, + .acpi_match_table = inv_acpi_match, + .name = "inv-mpu6000-spi", + .pm = &inv_mpu_pmops, + }, +}; + +module_spi_driver(inv_mpu_driver); + +MODULE_AUTHOR("Adriana Reus "); +MODULE_DESCRIPTION("Invensense device MPU6000 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c new file mode 100644 index 000000000..882546897 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#include +#include "inv_mpu_iio.h" + +static unsigned int inv_scan_query_mpu6050(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + unsigned int mask; + + /* + * If the MPU6050 is just used as a trigger, then the scan mask + * is not allocated so we simply enable the temperature channel + * as a dummy and bail out. + */ + if (!indio_dev->active_scan_mask) { + st->chip_config.temp_fifo_enable = true; + return INV_MPU6050_SENSOR_TEMP; + } + + st->chip_config.gyro_fifo_enable = + test_bit(INV_MPU6050_SCAN_GYRO_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Z, + indio_dev->active_scan_mask); + + st->chip_config.accl_fifo_enable = + test_bit(INV_MPU6050_SCAN_ACCL_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Z, + indio_dev->active_scan_mask); + + st->chip_config.temp_fifo_enable = + test_bit(INV_MPU6050_SCAN_TEMP, indio_dev->active_scan_mask); + + mask = 0; + if (st->chip_config.gyro_fifo_enable) + mask |= INV_MPU6050_SENSOR_GYRO; + if (st->chip_config.accl_fifo_enable) + mask |= INV_MPU6050_SENSOR_ACCL; + if (st->chip_config.temp_fifo_enable) + mask |= INV_MPU6050_SENSOR_TEMP; + + return mask; +} + +static unsigned int inv_scan_query_mpu9x50(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + unsigned int mask; + + mask = inv_scan_query_mpu6050(indio_dev); + + /* no magnetometer if i2c auxiliary bus is used */ + if (st->magn_disabled) + return mask; + + st->chip_config.magn_fifo_enable = + test_bit(INV_MPU9X50_SCAN_MAGN_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU9X50_SCAN_MAGN_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU9X50_SCAN_MAGN_Z, + indio_dev->active_scan_mask); + if (st->chip_config.magn_fifo_enable) + mask |= INV_MPU6050_SENSOR_MAGN; + + return mask; +} + +static unsigned int inv_scan_query(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + switch (st->chip_type) { + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + return inv_scan_query_mpu9x50(indio_dev); + default: + return inv_scan_query_mpu6050(indio_dev); + } +} + +static unsigned int inv_compute_skip_samples(const struct inv_mpu6050_state *st) +{ + unsigned int skip_samples = 0; + + /* mag first sample is always not ready, skip it */ + if (st->chip_config.magn_fifo_enable) + skip_samples = 1; + + return skip_samples; +} + +int inv_mpu6050_prepare_fifo(struct inv_mpu6050_state *st, bool enable) +{ + uint8_t d; + int ret; + + if (enable) { + st->it_timestamp = 0; + /* reset FIFO */ + d = st->chip_config.user_ctrl | INV_MPU6050_BIT_FIFO_RST; + ret = regmap_write(st->map, st->reg->user_ctrl, d); + if (ret) + return ret; + /* enable sensor output to FIFO */ + d = 0; + if (st->chip_config.gyro_fifo_enable) + d |= INV_MPU6050_BITS_GYRO_OUT; + if (st->chip_config.accl_fifo_enable) + d |= INV_MPU6050_BIT_ACCEL_OUT; + if (st->chip_config.temp_fifo_enable) + d |= INV_MPU6050_BIT_TEMP_OUT; + if (st->chip_config.magn_fifo_enable) + d |= INV_MPU6050_BIT_SLAVE_0; + ret = regmap_write(st->map, st->reg->fifo_en, d); + if (ret) + return ret; + /* enable FIFO reading */ + d = st->chip_config.user_ctrl | INV_MPU6050_BIT_FIFO_EN; + ret = regmap_write(st->map, st->reg->user_ctrl, d); + if (ret) + return ret; + /* enable interrupt */ + ret = regmap_write(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN); + } else { + ret = regmap_write(st->map, st->reg->int_enable, 0); + if (ret) + return ret; + ret = regmap_write(st->map, st->reg->fifo_en, 0); + if (ret) + return ret; + /* restore user_ctrl for disabling FIFO reading */ + ret = regmap_write(st->map, st->reg->user_ctrl, + st->chip_config.user_ctrl); + } + + return ret; +} + +/** + * inv_mpu6050_set_enable() - enable chip functions. + * @indio_dev: Device driver instance. + * @enable: enable/disable + */ +static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + unsigned int scan; + int result; + + if (enable) { + scan = inv_scan_query(indio_dev); + result = pm_runtime_resume_and_get(pdev); + if (result) + return result; + /* + * In case autosuspend didn't trigger, turn off first not + * required sensors. + */ + result = inv_mpu6050_switch_engine(st, false, ~scan); + if (result) + goto error_power_off; + result = inv_mpu6050_switch_engine(st, true, scan); + if (result) + goto error_power_off; + st->skip_samples = inv_compute_skip_samples(st); + result = inv_mpu6050_prepare_fifo(st, true); + if (result) + goto error_power_off; + } else { + result = inv_mpu6050_prepare_fifo(st, false); + if (result) + goto error_power_off; + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); + } + + return 0; + +error_power_off: + pm_runtime_put_autosuspend(pdev); + return result; +} + +/** + * inv_mpu_data_rdy_trigger_set_state() - set data ready interrupt state + * @trig: Trigger instance + * @state: Desired trigger state + */ +static int inv_mpu_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int result; + + mutex_lock(&st->lock); + result = inv_mpu6050_set_enable(indio_dev, state); + mutex_unlock(&st->lock); + + return result; +} + +static const struct iio_trigger_ops inv_mpu_trigger_ops = { + .set_trigger_state = &inv_mpu_data_rdy_trigger_set_state, +}; + +int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev, int irq_type) +{ + int ret; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + st->trig = devm_iio_trigger_alloc(&indio_dev->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + ret = devm_request_irq(&indio_dev->dev, st->irq, + &iio_trigger_generic_data_rdy_poll, + irq_type, + "inv_mpu", + st->trig); + if (ret) + return ret; + + st->trig->dev.parent = regmap_get_device(st->map); + st->trig->ops = &inv_mpu_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + + ret = devm_iio_trigger_register(&indio_dev->dev, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; +} -- cgit v1.2.3