From e5159320a951742e891f523d1b4f56feb7d073b5 Mon Sep 17 00:00:00 2001 From: Hanna Mofid Date: Wed, 5 Jul 2023 21:15:22 +0000 Subject: [PATCH] drivers: Add bmi088 driver Add bmi088 driver code which implements accelerometer and gyroscope part of the IMU. Bug 4190630 Change-Id: I653e928e594b67df61e3de35359b87436a80b60d Signed-off-by: Hanna Mofid Signed-off-by: Gautham Srinivasan Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2931645 Reviewed-by: Jonathan Hunter GVS: Gerrit_Virtual_Submit --- drivers/Makefile | 3 + drivers/bmi088/Makefile | 5 + drivers/bmi088/bmi088_core.c | 1706 ++++++++++++++++++++++++++++++++++ drivers/bmi088/bmi088_iio.c | 614 ++++++++++++ drivers/bmi088/bmi_iio.h | 48 + 5 files changed, 2376 insertions(+) create mode 100644 drivers/bmi088/Makefile create mode 100644 drivers/bmi088/bmi088_core.c create mode 100644 drivers/bmi088/bmi088_iio.c create mode 100644 drivers/bmi088/bmi_iio.h diff --git a/drivers/Makefile b/drivers/Makefile index 864d2a18..779db3e3 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -77,3 +77,6 @@ obj-m += staging/platform/tegra/gte/ obj-m += staging/platform/tegra/gte_test/ obj-m += nv-virtio/ obj-m += usb/ +ifdef CONFIG_I2C +obj-m += bmi088/ +endif diff --git a/drivers/bmi088/Makefile b/drivers/bmi088/Makefile new file mode 100644 index 00000000..25d57f95 --- /dev/null +++ b/drivers/bmi088/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. + +bmi088-objs := bmi088_core.o bmi088_iio.o +obj-m += bmi088.o diff --git a/drivers/bmi088/bmi088_core.c b/drivers/bmi088/bmi088_core.c new file mode 100644 index 00000000..3d6a75e8 --- /dev/null +++ b/drivers/bmi088/bmi088_core.c @@ -0,0 +1,1706 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +/* Device tree example: + * + * bmi088@69 { + * compatible = "bmi,bmi088"; + * reg = <0x69>; // <-- Must be gyroscope I2C address + * accel_i2c_addr = <0x19>; // Must be specified + * accel_irq_gpio = <&tegra_gpio TEGRA_GPIO(BB, 0) GPIO_ACTIVE_HIGH>; + * gyro_irq_gpio = <&tegra_gpio TEGRA_GPIO(BB, 1) GPIO_ACTIVE_HIGH>; + * accel_matrix = [01 00 00 00 01 00 00 00 01]; + * gyro_matrix = [01 00 00 00 01 00 00 00 01]; + * }; + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bmi_iio.h" + +#define BMI_NAME "bmi088" + +#define BMI_ACC_SOFTRESET_DELAY_MS (50) +#define BMI_GYR_SOFTRESET_DELAY_MS (50) +#define BMI_ACC_PM_DELAY_MS (5) +#define BMI_GYR_PM_DELAY_MS (30) +#define BMI_HW_DELAY_POR_MS (10) +#define BMI_HW_DELAY_DEV_ON_US (2) +#define BMI_HW_DELAY_DEV_OFF_US (1000) +#define BMI_REG_ACC_CHIP_ID (0x00) +#define BMI_REG_ACC_ERR_REG (0x02) +#define BMI_REG_ACC_STATUS (0x03) +#define BMI_REG_ACC_DATA (0x12) +#define BMI_REG_SENSORTIME_2 (0x1A) +#define BMI_REG_ACC_INT_STAT_1 (0x1D) +#define BMI_REG_TEMP_MSB (0x22) +#define BMI_REG_FIFO_DATA (0x26) +#define BMI_REG_ACC_CONF (0x40) +#define BMI_REG_ACC_CONF_BWP_POR (0xA0) +#define BMI_REG_ACC_CONF_BWP_MSK (0xF0) +#define BMI_REG_ACC_RANGE (0x41) +#define BMI_REG_FIFO_DOWNS (0x45) +#define BMI_REG_ACC_FIFO_CFG_0 (0x48) +#define BMI_REG_ACC_FIFO_CFG_1 (0x49) +#define BMI_REG_INT1_IO_CTRL (0x53) +#define BMI_REG_INT2_IO_CTRL (0x54) +#define BMI_REG_ACCEL_INIT_CTRL (0x59) +#define BMI_REG_INTX_IO_CTRL_OUT_EN (0x08) +#define BMI_REG_INTX_IO_CTRL_ACTV_HI (0x02) +#define BMI_REG_INT_MAP_DATA (0x58) +#define BMI_INT1_OUT_ACTIVE_HIGH (0x0A) +#define BMI_INT1_DTRDY (0x04) +#define BMI_REG_ACC_PWR_CONF (0x7C) +#define BMI_REG_ACC_PWR_CONF_ACTV (0x00) +#define BMI_REG_ACC_PWR_CONF_SUSP (0x03) +#define BMI_REG_ACC_PWR_CTRL (0x7D) +#define BMI_REG_ACC_PWR_CTRL_OFF (0x00) +#define BMI_REG_ACC_PWR_CTRL_ON (0x04) +#define BMI_REG_ACC_SOFTRESET (0x7E) +#define BMI_REG_ACC_SOFTRESET_FIFO (0xB0) +#define BMI_REG_ACC_SOFTRESET_EXE (0xB6) +/* HW registers gyroscope */ +#define BMI_REG_GYR_CHIP_ID (0x00) +#define BMI_REG_GYR_Z_MSB (0x07) +#define BMI_REG_GYR_DATA (0x02) +#define BMI_REG_GYR_INT_STAT_1 (0x0A) +#define BMI_REG_FIFO_STATUS (0x0E) +#define BMI_REG_GYR_RANGE (0x0F) +#define BMI_REG_GYR_BW (0x10) +#define BMI_REG_GYR_LPM1 (0x11) +#define BMI_REG_GYR_LPM1_NORM (0x00) +#define BMI_REG_GYR_LPM1_DEEP (0x20) +#define BMI_REG_GYR_LPM1_SUSP (0x80) +#define BMI_REG_GYR_SOFTRESET (0x14) +#define BMI_REG_GYR_SOFTRESET_EXE (0xB6) +#define BMI_REG_GYR_INT_CTRL (0x15) +#define BMI_REG_GYR_INT_CTRL_DIS (0x00) +#define BMI_REG_GYR_INT_CTRL_DATA_EN (0x80) +#define BMI_REG_INT_3_4_IO_CONF (0x16) +#define BMI_REG_INT_3_4_IO_CONF_3_HI (0x01) +#define BMI_REG_INT_3_4_IO_CONF_4_HI (0x04) +#define BMI_REG_INT_3_4_IO_MAP (0x18) +#define BMI_REG_INT_3_4_IO_MAP_INT3 (0x01) +#define BMI_REG_INT_3_ACTIVE_HIGH (0x01) +#define BMI_REG_FIFO_EXT_INT_S (0x34) +#define BMI_REG_GYR_SELF_TEST (0x3C) +#define BMI_REG_GYR_FIFO_CFG_1 (0x3E) + +#define BMI_AXIS_N (3) +#define BMI_IMU_DATA (6) + +/* hardware devices */ +#define BMI_HW_ACC (0) +#define BMI_HW_GYR (1) +#define BMI_HW_N (2) + +#define BMI_PART_BMI088 (0) + +static const struct i2c_device_id bmi_i2c_device_ids[] = { + { BMI_NAME, BMI_PART_BMI088 }, + {}, +}; + +static char *gte_hw_str_t194 = "nvidia,tegra194-gte-aon"; +static char *gte_hw_str_t234 = "nvidia,tegra234-gte-aon"; +static struct device_node *gte_nd; + +struct bmi_gte_irq { + struct tegra_gte_ev_desc *gte; + const char *dev_name; + int gpio; + int irq; + u64 irq_ts; + u64 irq_ts_old; +}; + +struct bmi_reg_rd { + u8 reg_lo; + u8 reg_hi; +}; + +static struct bmi_reg_rd bmi_reg_rds_acc[] = { + { + .reg_lo = BMI_REG_ACC_CHIP_ID, + .reg_hi = BMI_REG_ACC_STATUS, + }, + { + .reg_lo = BMI_REG_ACC_DATA, + .reg_hi = BMI_REG_SENSORTIME_2, + }, + { + .reg_lo = BMI_REG_ACC_INT_STAT_1, + .reg_hi = BMI_REG_ACC_INT_STAT_1, + }, + { + .reg_lo = BMI_REG_TEMP_MSB, + .reg_hi = BMI_REG_FIFO_DATA, + }, + { + .reg_lo = BMI_REG_ACC_CONF, + .reg_hi = BMI_REG_ACC_RANGE, + }, + { + .reg_lo = BMI_REG_FIFO_DOWNS, + .reg_hi = BMI_REG_ACC_FIFO_CFG_1, + }, + { + .reg_lo = BMI_REG_INT1_IO_CTRL, + .reg_hi = BMI_REG_INT2_IO_CTRL, + }, + { + .reg_lo = BMI_REG_INT_MAP_DATA, + .reg_hi = BMI_REG_INT_MAP_DATA, + }, + { + .reg_lo = BMI_REG_ACC_PWR_CONF, + .reg_hi = BMI_REG_ACC_SOFTRESET, + }, +}; + +static struct bmi_reg_rd bmi_reg_rds_gyr[] = { + { + .reg_lo = BMI_REG_GYR_CHIP_ID, + .reg_hi = BMI_REG_GYR_Z_MSB, + }, + { + .reg_lo = BMI_REG_GYR_INT_STAT_1, + .reg_hi = BMI_REG_GYR_INT_STAT_1, + }, + { + .reg_lo = BMI_REG_FIFO_STATUS, + .reg_hi = BMI_REG_GYR_LPM1, + }, + { + .reg_lo = BMI_REG_GYR_SOFTRESET, + .reg_hi = BMI_REG_INT_3_4_IO_CONF, + }, + { + .reg_lo = BMI_REG_INT_3_4_IO_MAP, + .reg_hi = BMI_REG_INT_3_4_IO_MAP, + }, + { + .reg_lo = BMI_REG_FIFO_EXT_INT_S, + .reg_hi = BMI_REG_FIFO_EXT_INT_S, + }, + { + .reg_lo = BMI_REG_GYR_SELF_TEST, + .reg_hi = BMI_REG_GYR_FIFO_CFG_1, + }, +}; + +static struct sensor_cfg bmi_snsr_cfgs[] = { + { + .name = "accelerometer", + .snsr_id = BMI_HW_ACC, + .ch_n = BMI_AXIS_N, + .part = BMI_NAME, + .max_range = { + .ival = 0, /* default = +/-3g */ + }, + .delay_us_max = 80000, + /* default matrix to get the attribute */ + .matrix[0] = 1, + .matrix[4] = 1, + .matrix[8] = 1, + .float_significance = IIO_VAL_INT_PLUS_MICRO, + }, + { + .name = "gyroscope", + .snsr_id = BMI_HW_GYR, + .ch_n = BMI_AXIS_N, + .max_range = { + .ival = 0, /* default = +/-2000dps */ + }, + .delay_us_max = 10000, + .matrix[0] = 1, + .matrix[4] = 1, + .matrix[8] = 1, + .float_significance = IIO_VAL_INT_PLUS_MICRO, + }, +}; + +struct bmi_rr { + struct bmi_float max_range; + struct bmi_float resolution; +}; + +static struct bmi_rr bmi_rr_acc_bmi088[] = { +/* all accelerometer values are in g's (9.80665 m/s2) fval = NANO scale */ + { + .max_range = { + .ival = 29, + .fval = 419950000, + }, + .resolution = { + .ival = 0, + .fval = 897, + }, + }, + { + .max_range = { + .ival = 58, + .fval = 839900000, + }, + .resolution = { + .ival = 0, + .fval = 1795, + }, + }, + { + .max_range = { + .ival = 117, + .fval = 679800000, + }, + .resolution = { + .ival = 0, + .fval = 3591, + }, + }, + { + .max_range = { + .ival = 235, + .fval = 359600000, + }, + .resolution = { + .ival = 0, + .fval = 7182, + }, + }, +}; + +static struct bmi_rr bmi_rr_gyr[] = { +/* rad / sec fval = NANO scale */ + { + .max_range = { + .ival = 34, + .fval = 906585040, + }, + .resolution = { + .ival = 0, + .fval = 1065, + }, + }, + { + .max_range = { + .ival = 17, + .fval = 453292520, + }, + .resolution = { + .ival = 0, + .fval = 532, + }, + }, + { + .max_range = { + .ival = 8, + .fval = 726646260, + }, + .resolution = { + .ival = 0, + .fval = 266, + }, + }, + { + .max_range = { + .ival = 4, + .fval = 363323130, + }, + .resolution = { + .ival = 0, + .fval = 133, + }, + }, + { + .max_range = { + .ival = 2, + .fval = 181661565, + }, + .resolution = { + .ival = 0, + .fval = 66, + }, + }, +}; + +struct bmi_rrs { + struct bmi_rr *rr; + unsigned int rr_0n; +}; + +static struct bmi_rrs bmi_rrs_acc[] = { + { + .rr = bmi_rr_acc_bmi088, + .rr_0n = ARRAY_SIZE(bmi_rr_acc_bmi088) - 1, + }, +}; + +static struct bmi_rrs bmi_rrs_gyr[] = { + { + .rr = bmi_rr_gyr, + .rr_0n = ARRAY_SIZE(bmi_rr_gyr) - 1, + }, +}; + +struct bmi_state; +static int bmi_acc_able(struct bmi_state *st, int en, bool fast); +static int bmi_acc_batch(struct bmi_state *st, unsigned int period_us, + bool range); +static int bmi_acc_softreset(struct bmi_state *st, unsigned int hw); +static int bmi_acc_pm(struct bmi_state *st, unsigned int hw, int able); +static unsigned long bmi_acc_irqflags(struct bmi_state *st); +static int bmi_gyr_able(struct bmi_state *st, int en, bool fast); +static int bmi_gyr_batch(struct bmi_state *st, unsigned int period_us, + bool range); +static int bmi_gyr_softreset(struct bmi_state *st, unsigned int hw); +static int bmi_gyr_pm(struct bmi_state *st, unsigned int hw, int able); +static unsigned long bmi_gyr_irqflags(struct bmi_state *st); + +struct bmi_hw { + struct bmi_reg_rd *reg_rds; + struct bmi_rrs *rrs; + unsigned int reg_rds_n; + unsigned int rrs_0n; + int (*fn_able)(struct bmi_state *st, int en, bool fast); + int (*fn_batch)(struct bmi_state *st, unsigned int period_us, + bool range); + int (*fn_softreset)(struct bmi_state *st, unsigned int hw); + int (*fn_pm)(struct bmi_state *st, unsigned int hw, int able); + unsigned long (*fn_irqflags)(struct bmi_state *st); +}; + +static struct bmi_hw bmi_hws[] = { + { + .reg_rds = bmi_reg_rds_acc, + .rrs = bmi_rrs_acc, + .reg_rds_n = ARRAY_SIZE(bmi_reg_rds_acc), + .rrs_0n = ARRAY_SIZE(bmi_rrs_acc) - 1, + .fn_able = &bmi_acc_able, + .fn_batch = &bmi_acc_batch, + .fn_softreset = &bmi_acc_softreset, + .fn_pm = &bmi_acc_pm, + .fn_irqflags = &bmi_acc_irqflags, + }, + { + .reg_rds = bmi_reg_rds_gyr, + .rrs = bmi_rrs_gyr, + .reg_rds_n = ARRAY_SIZE(bmi_reg_rds_gyr), + .rrs_0n = ARRAY_SIZE(bmi_rrs_gyr) - 1, + .fn_able = &bmi_gyr_able, + .fn_batch = &bmi_gyr_batch, + .fn_softreset = &bmi_gyr_softreset, + .fn_pm = &bmi_gyr_pm, + .fn_irqflags = &bmi_gyr_irqflags, + }, +}; + +struct bmi_snsr { + struct iio_dev *bmi_iio; + struct bmi_rrs *rrs; + struct sensor_cfg cfg; + unsigned int usr_cfg; + unsigned int period_us; +}; + +struct bmi_state { + struct i2c_client *i2c; + struct bmi_snsr snsrs[BMI_HW_N]; + struct bmi_gte_irq gis[BMI_HW_N]; + bool iio_init_done[BMI_HW_N]; + unsigned int part; + unsigned int sts; + unsigned int errs_bus[BMI_HW_N]; + unsigned int err_ts_thread[BMI_HW_N]; + unsigned int sam_dropped[BMI_HW_N]; + unsigned int enabled; + unsigned int suspend_en_st; + unsigned int hw_n; + unsigned int hw_en; + s64 ts_hw[BMI_HW_N]; + u8 ra_0x53; + u8 ra_0x54; + u8 ra_0x58; + u8 rg_0x16; + u8 rg_0x18; + u16 i2c_addrs[BMI_HW_N]; +}; + +static inline s64 get_ktime_timestamp(void) +{ + struct timespec64 ts; + + ktime_get_ts64(&ts); + + return timespec64_to_ns(&ts); +} + +static int bmi_i2c_rd(struct bmi_state *st, unsigned int hw, + u8 reg, u16 len, void *buf) +{ + struct i2c_msg msg[2]; + int ret = -ENODEV; + s64 ts; + + if (st->i2c_addrs[hw]) { + msg[0].addr = st->i2c_addrs[hw]; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = ® + msg[1].addr = st->i2c_addrs[hw]; + msg[1].flags = I2C_M_RD; + msg[1].len = len; + msg[1].buf = buf; + ts = st->ts_hw[hw]; + if (st->hw_en & (1 << hw)) + ts += (BMI_HW_DELAY_DEV_ON_US * 1000); + else + ts += (BMI_HW_DELAY_DEV_OFF_US * 1000); + ts -= get_ktime_timestamp(); + if (ts > 0) { + ts /= 1000; + ts++; + udelay(ts); + } + ret = i2c_transfer(st->i2c->adapter, msg, 2); + st->ts_hw[hw] = get_ktime_timestamp(); + if (ret != 2) { + st->errs_bus[hw]++; + ret = -EIO; + } else { + ret = 0; + } + } + + return ret; +} + +static int bmi_i2c_w(struct bmi_state *st, unsigned int hw, + u16 len, u8 *buf) +{ + struct i2c_msg msg; + int ret; + s64 ts; + + if (st->i2c_addrs[hw]) { + msg.addr = st->i2c_addrs[hw]; + msg.flags = 0; + msg.len = len; + msg.buf = buf; + ts = st->ts_hw[hw]; + if (st->hw_en & (1 << hw)) + ts += (BMI_HW_DELAY_DEV_ON_US * 1000); + else + ts += (BMI_HW_DELAY_DEV_OFF_US * 1000); + ts -= get_ktime_timestamp(); + if (ts > 0) { + ts /= 1000; /* ns => us */ + ts++; + udelay(ts); + } + ret = i2c_transfer(st->i2c->adapter, &msg, 1); + st->ts_hw[hw] = get_ktime_timestamp(); + if (ret != 1) { + st->errs_bus[hw]++; + ret = -EIO; + } else { + ret = 0; + } + } else { + ret = -ENODEV; + } + + return ret; +} + +static int bmi_i2c_wr(struct bmi_state *st, unsigned int hw, u8 reg, u8 val) +{ + int ret; + u8 buf[2]; + + buf[0] = reg; + buf[1] = val; + ret = bmi_i2c_w(st, hw, sizeof(buf), buf); + if (ret) + dev_err(&st->i2c->dev, "ERR: 0x%02X=>0x%02X\n", val, reg); + + return ret; +} + +static void bmi_gte_exit_gpio(struct bmi_gte_irq *ngi, unsigned int n) +{ + unsigned int i; + + for (i = 0; i < n; i++) { + if (ngi[i].gpio >= 0) + gpio_free(ngi[i].gpio); + } +} + +static int bmi_gte_init_gpio2irq(struct device *dev, struct bmi_gte_irq *ngi, + unsigned int n) +{ + unsigned int i; + unsigned int prev; + int ret; + + for (i = 0; i < n; i++) { + if (!gpio_is_valid(ngi[i].gpio) || + gpio_request(ngi[i].gpio, ngi[i].dev_name)) { + ret = -EPROBE_DEFER; + if (!i) { + goto out_no_prev; + } else { + prev = i - 1; + goto out; + } + } + + ret = gpio_direction_input(ngi[i].gpio); + if (ret < 0) { + dev_err(dev, "%s gpio_dir_input(%d) ERR:%d\n", + ngi[i].dev_name, ngi[i].gpio, ret); + ret = -ENODEV; + if (!i) + prev = i; + else + prev = i - 1; + + goto out; + } + + ret = gpio_to_irq(ngi[i].gpio); + if (ret <= 0) { + dev_err(dev, "%s gpio_to_irq(%d) ERR:%d\n", + ngi[i].dev_name, ngi[i].gpio, ret); + ret = -ENODEV; + if (!i) + prev = i; + else + prev = i - 1; + + goto out; + } + + ngi[i].irq = ret; + ret = 0; + } + + return ret; + +out: + bmi_gte_exit_gpio(ngi, prev); + +out_no_prev: + + return ret; +} + +static int bmi_gte_ts(struct bmi_gte_irq *ngi) +{ + struct tegra_gte_ev_desc *desc = (struct tegra_gte_ev_desc *)ngi->gte; + struct tegra_gte_ev_detail dtl; + int ret; + + ret = tegra_gte_retrieve_event(desc, &dtl); + if (!ret) + ngi->irq_ts = dtl.ts_ns; + + return ret; +} + +static inline int bmi_gte_deinit(struct bmi_gte_irq *ngi) +{ + int ret = 0; + + if (ngi->gte) { + ret = tegra_gte_unregister_event(ngi->gte); + ngi->gte = NULL; + } + + return ret; +} + +static void bmi_gte_gpio_exit(struct bmi_state *st, unsigned int n) +{ + unsigned int i; + struct bmi_gte_irq *ngi = st->gis; + + if (gte_nd) { + of_node_put(gte_nd); + gte_nd = NULL; + } + + for (i = 0; i < n; i++) { + bmi_gte_deinit(&ngi[i]); + if (ngi[i].gpio >= 0) + gpio_free(ngi[i].gpio); + } +} + +static int bmi_gte_init(struct bmi_state *st, unsigned int id) +{ + int ret = 0; + struct bmi_gte_irq *ngi = st->gis; + + if (!ngi[id].gte) { + ngi[id].gte = tegra_gte_register_event(gte_nd, ngi[id].gpio); + if (!ngi[id].gte) + ret = -ENODEV; + } + + return ret; +} + +static int bmi_setup_gpio(struct device *dev, struct bmi_state *st, + unsigned int n) +{ + unsigned int i; + struct bmi_gte_irq *ngi = st->gis; + + for (i = 0; i < n; i++) + ngi[i].irq = -1; + + return bmi_gte_init_gpio2irq(dev, ngi, n); +} + +static int bmi_pm(struct bmi_state *st, int snsr_id, bool en) +{ + unsigned int i; + int ret = 0; + + if (en) { + if (!st->hw_en) + /* first time power on, wait for power on reset time */ + mdelay(BMI_HW_DELAY_POR_MS); + + if (snsr_id < 0) { + for (i = 0; i < st->hw_n; i++) + ret |= bmi_hws[i].fn_pm(st, i, 1); + } else { + if (snsr_id >= st->hw_n) + return -ENODEV; + + ret = bmi_hws[snsr_id].fn_pm(st, snsr_id, 1); + } + } else { + if (snsr_id < 0) { + for (i = 0; i < st->hw_n; i++) { + ret |= bmi_hws[i].fn_pm(st, i, 0); + st->enabled &= ~(1 << i); + } + } else { + if (snsr_id >= st->hw_n) + return -ENODEV; + + dev_dbg(&st->i2c->dev, "turning off:%d\n", snsr_id); + ret = bmi_hws[snsr_id].fn_pm(st, snsr_id, 0); + st->enabled &= ~(1 << snsr_id); + } + } + + if (ret) { + if (snsr_id < 0) + dev_err(&st->i2c->dev, "ALL pm_en=%x ERR=%d\n", + en, ret); + else + dev_err(&st->i2c->dev, "%s pm_en=%x ERR=%d\n", + st->snsrs[snsr_id].cfg.name, en, ret); + } + + return ret; +} + +struct bmi_odr { + unsigned int period_us; + u8 hw; + unsigned int odr_hz; + unsigned int nodr_hz_mant; +}; + +static unsigned int bmi_odr_i(struct bmi_odr *odrs, unsigned int odrs_n, + unsigned int period_us) +{ + unsigned int i; + + for (i = 0; i < odrs_n; i++) { + if (period_us >= odrs[i].period_us) + break; + } + + return i; +} + +static struct bmi_odr bmi_odrs_acc[] = { + { 80000, 0x05, 12, 500000 }, + { 40000, 0x06, 25, 0 }, + { 20000, 0x07, 50, 0 }, + { 10000, 0x08, 100, 0 }, + { 5000, 0x09, 200, 0 }, + { 2500, 0x0A, 400, 0 }, + { 1250, 0x0B, 800, 0 }, + { 625, 0x0C, 1600, 0 }, +}; + +/* Configures ODR and range */ +static int bmi_acc_batch(struct bmi_state *st, unsigned int period_us, + bool range) +{ + u8 val; + unsigned int odr_i; + int ret = 0; + + odr_i = bmi_odr_i(bmi_odrs_acc, ARRAY_SIZE(bmi_odrs_acc), period_us); + + val = bmi_odrs_acc[odr_i].hw; + val |= BMI_REG_ACC_CONF_BWP_POR; + ret = bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_ACC_CONF, val); + if (!ret) + st->snsrs[BMI_HW_ACC].period_us = bmi_odrs_acc[odr_i].period_us; + + if (range) + ret |= bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_ACC_RANGE, + (u8)st->snsrs[BMI_HW_ACC].usr_cfg); + + return ret; +} + +/* Maps and set/reset the data ready interrupt */ +static int bmi_acc_able(struct bmi_state *st, int en, bool fast) +{ + int ret = 0; + + if (!en) + return bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_INT_MAP_DATA, 0x0); + + if (!fast) { + ret = bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_INT1_IO_CTRL, + st->ra_0x53); + ret |= bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_INT2_IO_CTRL, + st->ra_0x54); + } + + ret |= bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_INT_MAP_DATA, st->ra_0x58); + + return ret; +} + +static int bmi_acc_softreset(struct bmi_state *st, unsigned int hw) +{ + int ret; + + ret = bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_ACC_SOFTRESET, + BMI_REG_ACC_SOFTRESET_EXE); + mdelay(BMI_ACC_SOFTRESET_DELAY_MS); + + st->hw_en &= ~(1 << hw); + st->enabled &= ~(1 << hw); + + return ret; +} + +static int bmi_acc_pm(struct bmi_state *st, unsigned int hw, int able) +{ + int ret; + u8 pwr_conf; + u8 pwr_on_off; + + if (able) { + pwr_conf = BMI_REG_ACC_PWR_CONF_ACTV; + pwr_on_off = BMI_REG_ACC_PWR_CTRL_ON; + st->hw_en |= (1 << hw); + } else { + pwr_conf = BMI_REG_ACC_PWR_CONF_SUSP; + pwr_on_off = BMI_REG_ACC_PWR_CTRL_OFF; + st->hw_en &= ~(1 << hw); + } + + ret = bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_ACC_PWR_CONF, + pwr_conf); + + mdelay(BMI_ACC_PM_DELAY_MS); + + + ret |= bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_ACC_PWR_CTRL, + pwr_on_off); + + mdelay(BMI_ACC_PM_DELAY_MS); + + if (ret) + st->hw_en &= ~(1 << hw); + + return ret; +} + +static unsigned long bmi_acc_irqflags(struct bmi_state *st) +{ + unsigned long irqflags = IRQF_ONESHOT; + u8 int_io_conf; + + if (st->ra_0x53 & BMI_REG_INTX_IO_CTRL_OUT_EN) + int_io_conf = st->ra_0x53; + else + int_io_conf = st->ra_0x54; + + if (int_io_conf & BMI_REG_INTX_IO_CTRL_ACTV_HI) + irqflags |= IRQF_TRIGGER_RISING; + else + irqflags |= IRQF_TRIGGER_FALLING; + + return irqflags; +} + +static struct bmi_odr bmi_odrs_gyr[] = { + { 10000, 0x05, 100, 0 }, + { 5000, 0x04, 200, 0 }, + { 2500, 0x03, 400, 0 }, + { 1000, 0x02, 1000, 0 }, + { 500, 0x01, 2000, 0 }, +}; + +static int bmi_gyr_batch(struct bmi_state *st, unsigned int period_us, + bool range) +{ + u8 val; + unsigned int odr_i; + int ret = 0; + + odr_i = bmi_odr_i(bmi_odrs_gyr, ARRAY_SIZE(bmi_odrs_gyr), period_us); + + val = bmi_odrs_gyr[odr_i].hw; + ret = bmi_i2c_wr(st, BMI_HW_GYR, BMI_REG_GYR_BW, val); + if (!ret) + st->snsrs[BMI_HW_GYR].period_us = bmi_odrs_gyr[odr_i].period_us; + + if (range) { + val = st->snsrs[BMI_HW_GYR].usr_cfg; + ret |= bmi_i2c_wr(st, BMI_HW_GYR, BMI_REG_GYR_RANGE, val); + } + + return ret; +} + +/* Map and set/reset data ready interrupt */ +static int bmi_gyr_able(struct bmi_state *st, int en, bool fast) +{ + int ret = 0; + + if (!en) + return bmi_i2c_wr(st, BMI_HW_GYR, BMI_REG_GYR_INT_CTRL, + BMI_REG_GYR_INT_CTRL_DIS); + + if (!fast) { + ret = bmi_i2c_wr(st, BMI_HW_GYR, BMI_REG_INT_3_4_IO_CONF, + st->rg_0x16); + ret |= bmi_i2c_wr(st, BMI_HW_GYR, BMI_REG_INT_3_4_IO_MAP, + st->rg_0x18); + } + + ret |= bmi_i2c_wr(st, BMI_HW_GYR, BMI_REG_GYR_INT_CTRL, + BMI_REG_GYR_INT_CTRL_DATA_EN); + + return ret; +} + +static int bmi_gyr_softreset(struct bmi_state *st, unsigned int hw) +{ + int ret; + + ret = bmi_i2c_wr(st, BMI_HW_GYR, BMI_REG_GYR_SOFTRESET, + BMI_REG_GYR_SOFTRESET_EXE); + mdelay(BMI_GYR_SOFTRESET_DELAY_MS); + + st->hw_en &= ~(1 << hw); + st->enabled &= ~(1 << hw); + + return ret; +} + +static int bmi_gyr_pm(struct bmi_state *st, unsigned int hw, int able) +{ + int ret; + u8 val; + + if (able) { + val = BMI_REG_GYR_LPM1_NORM; + st->hw_en |= (1 << hw); + } else { + val = BMI_REG_GYR_LPM1_SUSP; + st->hw_en &= ~(1 << hw); + } + + ret = bmi_i2c_wr(st, BMI_HW_GYR, BMI_REG_GYR_LPM1, val); + if (ret) + st->hw_en &= ~(1 << hw); + + mdelay(BMI_GYR_PM_DELAY_MS); + + return ret; +} + +static unsigned long bmi_gyr_irqflags(struct bmi_state *st) +{ + unsigned long irqflags = IRQF_ONESHOT; + + if (st->rg_0x18 & BMI_REG_INT_3_4_IO_MAP_INT3) { + if (st->rg_0x16 & BMI_REG_INT_3_4_IO_CONF_3_HI) + irqflags |= IRQF_TRIGGER_RISING; + else + irqflags |= IRQF_TRIGGER_FALLING; + } else { /* BMI_REG_INT_3_4_IO_MAP_INT4 */ + if (st->rg_0x16 & BMI_REG_INT_3_4_IO_CONF_4_HI) + irqflags |= IRQF_TRIGGER_RISING; + else + irqflags |= IRQF_TRIGGER_FALLING; + } + + return irqflags; +} + +static irqreturn_t bmi_irq_thread(int irq, void *dev_id) +{ + struct bmi_state *st = (struct bmi_state *)dev_id; + unsigned int hw; + int ret; + u8 reg; + u8 sample[BMI_IMU_DATA]; + int cnt = 0; + u64 ts_old; + + if (irq == st->gis[BMI_HW_GYR].irq) { + hw = BMI_HW_GYR; + reg = BMI_REG_GYR_DATA; + } else { + hw = BMI_HW_ACC; + reg = BMI_REG_ACC_DATA; + } + + /* Disbale data ready interrupt before we read out data */ + ret = bmi_hws[hw].fn_able(st, 0, true); + if (unlikely(ret)) { + dev_err_ratelimited(&st->i2c->dev, + "can't disable sensor: %d\n", hw); + goto err; + } + + ts_old = st->gis[hw].irq_ts_old; + + /* + * There is a possibility that data ready IRQ may have caused GTE to + * store the timestamps by the time this thread got a chance to run + * and disable IRQ especially for the high data rate, in that case, + * drain the GTE till it returns error and use last timestamp to + * associate the data to be read. + */ + while (bmi_gte_ts(&st->gis[hw]) == 0) + cnt++; + + /* Means we failed to get the ts in the first go */ + if (!st->gis[hw].irq_ts && !cnt) { + dev_dbg(&st->i2c->dev, "sample dropped, gte get ts failed\n"); + st->sam_dropped[hw]++; + goto out; + } + + /* + * If ts is same as old or 0, something is seriously wrong, + * re-register with gte + */ + if ((st->gis[hw].irq_ts_old == st->gis[hw].irq_ts) || + (!st->gis[hw].irq_ts && cnt)) { + dev_dbg(&st->i2c->dev, + "ts issue for: %d, ts old: %llu, new: %llu\n", + hw, st->gis[hw].irq_ts_old, st->gis[hw].irq_ts); + + st->err_ts_thread[hw]++; + st->sam_dropped[hw]++; + dev_dbg(&st->i2c->dev, "sample dropped due to ts issues\n"); + + /* Re-register with GTE */ + bmi_gte_deinit(&st->gis[hw]); + ret = bmi_gte_init(st, hw); + if (ret) { + dev_err_ratelimited(&st->i2c->dev, + "GTE re-registration failed: %d\n", + hw); + goto err; + } + + goto out; + } + +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_lock(&st->snsrs[hw].bmi_iio->mlock); +#endif + + ret = bmi_i2c_rd(st, hw, reg, sizeof(sample), sample); + +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&st->snsrs[hw].bmi_iio->mlock); +#endif + + if (!ret) { + bmi_iio_push_buf(st->snsrs[hw].bmi_iio, sample, + st->gis[hw].irq_ts); + st->gis[hw].irq_ts_old = st->gis[hw].irq_ts; + } + + dev_dbg(&st->i2c->dev, "%d, ts= %lld, ts_old=%lld\n", + hw, st->gis[hw].irq_ts, ts_old); + +out: + st->gis[hw].irq_ts = 0; + bmi_hws[hw].fn_able(st, 1, true); + +err: + return IRQ_HANDLED; +} + +static int bmi_period(struct bmi_state *st, int snsr_id, bool range) +{ + + if (snsr_id >= st->hw_n) + return -ENODEV; + + return bmi_hws[snsr_id].fn_batch(st, st->snsrs[snsr_id].period_us, + range); +} + +static int bmi_enable(void *client, int snsr_id, int enable, bool is_gte) +{ + struct bmi_state *st = (struct bmi_state *)client; + int ret; + + if (snsr_id >= st->hw_n) + return -ENODEV; + + if (enable < 0) + return (st->enabled & (1 << snsr_id)); + + if (enable) { + if (is_gte) { + ret = bmi_gte_init(st, snsr_id); + if (ret) + return ret; + } + + enable = st->enabled | (1 << snsr_id); + ret = bmi_pm(st, snsr_id, true); + if (ret < 0) { + if (is_gte) + bmi_gte_deinit(&st->gis[snsr_id]); + + return ret; + } + + ret = bmi_period(st, snsr_id, true); + ret |= bmi_hws[snsr_id].fn_able(st, 1, false); + if (!ret) { + st->enabled = enable; + return ret; + } + } + + if (is_gte) + bmi_gte_deinit(&st->gis[snsr_id]); + + ret = bmi_hws[snsr_id].fn_able(st, 0, false); + ret |= bmi_pm(st, snsr_id, false); + + return ret; +} + +static inline struct bmi_odr *bmi_find_odrs(struct bmi_state *st, int snsr_id, + unsigned int *sz) +{ + struct bmi_odr *odr; + + if (!st || !sz || (snsr_id >= st->hw_n)) + return NULL; + + if (snsr_id == BMI_HW_GYR) { + odr = bmi_odrs_gyr; + *sz = ARRAY_SIZE(bmi_odrs_gyr); + } else if (snsr_id == BMI_HW_ACC) { + odr = bmi_odrs_acc; + *sz = ARRAY_SIZE(bmi_odrs_acc); + } else { + return NULL; + } + + return odr; +} + +static int bmi_read_odrs(struct bmi_state *st, int snsr_id, int *val, + int *val2) +{ + struct bmi_odr *odr; + unsigned int o_sz; + unsigned int index; + + if (!st || (snsr_id >= st->hw_n)) + return -EINVAL; + + odr = bmi_find_odrs(st, snsr_id, &o_sz); + if (!odr) + return -EINVAL; + + index = bmi_odr_i(odr, o_sz, st->snsrs[snsr_id].period_us); + if (index >= o_sz) + return -EINVAL; + + *val = odr[index].odr_hz; + *val2 = odr[index].nodr_hz_mant; + + return index; +} + +static int bmi_find_freq(struct bmi_odr *odr, unsigned int o_sz, + int val, int val2) +{ + unsigned int i; + + for (i = 0; i < o_sz; i++) { + if (val == odr[i].odr_hz && val2 == odr[i].nodr_hz_mant) + break; + } + + if (i >= o_sz) + return -EINVAL; + + return i; +} + +static int bmi_freq_write(void *client, int snsr_id, int val, int val2) +{ + struct bmi_state *st = (struct bmi_state *)client; + int odr_i; + struct bmi_odr *odr; + unsigned int o_sz, old_period; + int ret = 0; + + if (!st || (snsr_id >= st->hw_n)) + return -EINVAL; + + odr = bmi_find_odrs(st, snsr_id, &o_sz); + if (!odr) + return -EINVAL; + + odr_i = bmi_find_freq(odr, o_sz, val, val2); + if (odr_i < 0) + return -EINVAL; + + old_period = st->snsrs[snsr_id].period_us; + + st->snsrs[snsr_id].period_us = odr[odr_i].period_us; + + ret = bmi_period(st, snsr_id, false); + if (ret) + st->snsrs[snsr_id].period_us = old_period; + + return ret; +} + +static int bmi_freq_read(void *client, int snsr_id, int *val, int *val2) +{ + struct bmi_state *st = (struct bmi_state *)client; + int odr_i; + int ret = 0; + + if (!val || !val2 || !st) + return -EINVAL; + + odr_i = bmi_read_odrs(st, snsr_id, val, val2); + + if (unlikely(odr_i < 0)) + return -EINVAL; + + return ret; +} + +static int bmi_max_range(void *client, int snsr_id, int max_range) +{ + struct bmi_state *st = (struct bmi_state *)client; + unsigned int i = max_range; + + if (st->enabled & (1 << snsr_id)) + /* can't change settings on the fly (disable device first) */ + return -EBUSY; + + if (st->snsrs[snsr_id].rrs) { + if (i > st->snsrs[snsr_id].rrs->rr_0n) + /* clamp to highest setting */ + i = st->snsrs[snsr_id].rrs->rr_0n; + st->snsrs[snsr_id].usr_cfg = i; + st->snsrs[snsr_id].cfg.max_range.ival = + st->snsrs[snsr_id].rrs->rr[i].max_range.ival; + st->snsrs[snsr_id].cfg.max_range.fval = + st->snsrs[snsr_id].rrs->rr[i].max_range.fval; + st->snsrs[snsr_id].cfg.scale.ival = + st->snsrs[snsr_id].rrs->rr[i].resolution.ival; + st->snsrs[snsr_id].cfg.scale.fval = + st->snsrs[snsr_id].rrs->rr[i].resolution.fval; + + } + + return 0; +} + +static int bmi_scale_write(void *client, int snsr_id, int val, int val2) +{ + struct bmi_state *st = (struct bmi_state *)client; + int ret = 0; + int i = 0; + + if (!st || (snsr_id >= st->hw_n)) + return -ENODEV; + + for (; i <= st->snsrs[snsr_id].rrs->rr_0n; i++) { + if (st->snsrs[snsr_id].rrs->rr[i].resolution.ival == val && + st->snsrs[snsr_id].rrs->rr[i].resolution.fval == val2) + break; + } + if (i > st->snsrs[snsr_id].rrs->rr_0n) + return -EINVAL; + + if (snsr_id == BMI_HW_GYR) + ret = bmi_i2c_wr(st, BMI_HW_GYR, BMI_REG_GYR_RANGE, i); + else if (snsr_id == BMI_HW_ACC) + ret = bmi_i2c_wr(st, BMI_HW_ACC, BMI_REG_ACC_RANGE, i); + else + return -ENODEV; + + if (!ret) { + st->snsrs[snsr_id].usr_cfg = i; + st->snsrs[snsr_id].cfg.max_range.ival = + st->snsrs[snsr_id].rrs->rr[i].max_range.ival; + st->snsrs[snsr_id].cfg.max_range.fval = + st->snsrs[snsr_id].rrs->rr[i].max_range.fval; + st->snsrs[snsr_id].cfg.scale.ival = + st->snsrs[snsr_id].rrs->rr[i].resolution.ival; + st->snsrs[snsr_id].cfg.scale.fval = + st->snsrs[snsr_id].rrs->rr[i].resolution.fval; + } + + return ret; +} + +static int bmi_read_err(void *client, int snsr_id, char *buf) +{ + ssize_t t = 0; + struct bmi_state *st = (struct bmi_state *)client; + + if (snsr_id >= st->hw_n) + return -ENODEV; + + t += snprintf(buf, PAGE_SIZE, "%s:\n", st->snsrs[snsr_id].cfg.name); + t += snprintf(buf + t, PAGE_SIZE - t, + "I2C Bus Errors:%u\n", st->errs_bus[snsr_id]); + t += snprintf(buf + t, PAGE_SIZE - t, + "GTE Timestamp Errors:%u\n", st->err_ts_thread[snsr_id]); + t += snprintf(buf + t, PAGE_SIZE - t, + "Sample dropped:%u\n", st->sam_dropped[snsr_id]); + + return t; +} + +static int bmi_get_data(void *client, int snsr_id, int axis, int *val) +{ + struct bmi_state *st = (struct bmi_state *)client; + u8 reg; + __le16 sample; + int ret = 0; + + if (snsr_id >= st->hw_n) + return -ENODEV; + + if (snsr_id == BMI_HW_ACC) + reg = BMI_REG_ACC_DATA; + else + reg = BMI_REG_GYR_DATA; + + reg += (axis - IIO_MOD_X) * sizeof(__le16); + + ret = bmi_i2c_rd(st, snsr_id, reg, sizeof(__le16), &sample); + if (!ret) + *val = sign_extend32(le16_to_cpu(sample), 15); + + return ret; +} + +static int bmi_regs(void *client, int snsr_id, char *buf) +{ + struct bmi_state *st = (struct bmi_state *)client; + struct bmi_reg_rd *reg_rd; + ssize_t t; + u8 reg; + u8 val; + unsigned int i; + int ret; + + if (snsr_id >= st->hw_n) + return -ENODEV; + + t = snprintf(buf, PAGE_SIZE, "register:value\n"); + reg_rd = bmi_hws[snsr_id].reg_rds; + for (i = 0; i < bmi_hws[snsr_id].reg_rds_n; i++) { + for (reg = reg_rd[i].reg_lo; reg <= reg_rd[i].reg_hi; reg++) { + ret = bmi_i2c_rd(st, snsr_id, reg, 1, &val); + if (ret) + t += snprintf(buf + t, PAGE_SIZE - t, + "0x%02X=ERR\n", i); + else + t += snprintf(buf + t, PAGE_SIZE - t, + "0x%02X=0x%02X\n", reg, val); + } + } + + return t; +} + +static struct iio_fn_dev bmi_fn_dev = { + .enable = bmi_enable, + .regs = bmi_regs, + .freq_read = bmi_freq_read, + .freq_write = bmi_freq_write, + .scale_write = bmi_scale_write, + .read_err = bmi_read_err, + .get_data = bmi_get_data, +}; + +static int __maybe_unused bmi_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bmi_state *st = i2c_get_clientdata(client); + unsigned int i; + unsigned int old_en_st; + int ret = 0; + int temp_ret = 0; + + st->sts |= BMI_STS_SUSPEND; + st->suspend_en_st = 0; + + for (i = 0; i < st->hw_n; i++) { +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_lock(&st->snsrs[i].bmi_iio->mlock); +#endif + /* check if sensor is enabled to begin with */ + old_en_st = bmi_enable(st, st->snsrs[i].cfg.snsr_id, -1, + false); + if (old_en_st) { + temp_ret = bmi_enable(st, st->snsrs[i].cfg.snsr_id, 0, + false); + if (!temp_ret) + st->suspend_en_st |= old_en_st; + + ret |= temp_ret; + } +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&st->snsrs[i].bmi_iio->mlock); +#endif + } + + return ret; +} + +static int __maybe_unused bmi_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bmi_state *st = i2c_get_clientdata(client); + unsigned int i; + int ret = 0; + + for (i = 0; i < st->hw_n; i++) { +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_lock(&st->snsrs[i].bmi_iio->mlock); +#endif + if (st->suspend_en_st & (1 << st->snsrs[i].cfg.snsr_id)) + ret |= bmi_enable(st, st->snsrs[i].cfg.snsr_id, 1, + false); +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&st->snsrs[i].bmi_iio->mlock); +#endif + } + + st->sts &= ~BMI_STS_SUSPEND; + + return ret; +} + +static SIMPLE_DEV_PM_OPS(bmi_pm_ops, bmi_suspend, bmi_resume); + +static void bmi_shutdown(struct i2c_client *client) +{ + struct bmi_state *st = i2c_get_clientdata(client); + unsigned int i; + + st->sts |= BMI_STS_SHUTDOWN; + for (i = 0; i < st->hw_n; i++) { + if (st->iio_init_done[i]) +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_lock(&st->snsrs[i].bmi_iio->mlock); +#endif + + if (bmi_enable(st, st->snsrs[i].cfg.snsr_id, -1, false)) + bmi_enable(st, st->snsrs[i].cfg.snsr_id, 0, false); + + if (st->iio_init_done[i]) { +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&st->snsrs[i].bmi_iio->mlock); +#endif + } + } +} + +static void bmi_remove(void *data) +{ + struct i2c_client *client = data; + struct bmi_state *st = i2c_get_clientdata(client); + int i; + + if (st != NULL) { + bmi_shutdown(client); + bmi_gte_gpio_exit(st, BMI_HW_N); + for (i = 0; i < st->hw_n; i++) { + if (st->iio_init_done[i]) + bmi_iio_remove(st->snsrs[i].bmi_iio); + } + } + + dev_info(&client->dev, "removed\n"); +} + +static int bmi_of_dt(struct bmi_state *st, struct device_node *dn) +{ + u32 val32 = 0; + const char *charp; + int lenp; + + if (!dn) + return 0; + + if (!st->i2c_addrs[BMI_HW_ACC]) { + if (!of_property_read_u32(dn, "accel_i2c_addr", &val32)) + st->i2c_addrs[BMI_HW_ACC] = val32; + else + return -ENODEV; + } + + st->gis[BMI_HW_ACC].gpio = of_get_named_gpio(dn, "accel_irq_gpio", 0); + st->gis[BMI_HW_GYR].gpio = of_get_named_gpio(dn, "gyro_irq_gpio", 0); + + if (!of_property_read_u32(dn, "accel_reg_0x53", &val32)) + st->ra_0x53 = (u8)val32; + if (!of_property_read_u32(dn, "accel_reg_0x54", &val32)) + st->ra_0x54 = (u8)val32; + if (!of_property_read_u32(dn, "accel_reg_0x58", &val32)) + st->ra_0x58 = (u8)val32; + + if (!of_property_read_u32(dn, "gyro_reg_0x16", &val32)) + st->rg_0x16 = (u8)val32; + if (!of_property_read_u32(dn, "gyro_reg_0x18", &val32)) + st->rg_0x18 = (u8)val32; + + charp = of_get_property(dn, "accel_matrix", &lenp); + if (charp && lenp == sizeof(st->snsrs[BMI_HW_ACC].cfg.matrix)) + memcpy(&st->snsrs[BMI_HW_ACC].cfg.matrix, charp, lenp); + + charp = of_get_property(dn, "gyro_matrix", &lenp); + if (charp && lenp == sizeof(st->snsrs[BMI_HW_GYR].cfg.matrix)) + memcpy(&st->snsrs[BMI_HW_GYR].cfg.matrix, charp, lenp); + + return 0; +} + +static int bmi_reset_all(struct bmi_state *st) +{ + int ret = 0; + + ret = bmi_hws[BMI_HW_ACC].fn_softreset(st, BMI_HW_ACC); + ret |= bmi_hws[BMI_HW_GYR].fn_softreset(st, BMI_HW_GYR); + + return ret; +} + +static int bmi_init(struct bmi_state *st, const struct i2c_device_id *id) +{ + unsigned long irqflags; + unsigned int i; + int ret; + + if (id == NULL) + return -EINVAL; + + /* driver specific defaults */ + for (i = 0; i < BMI_HW_N; i++) + st->gis[i].gpio = -1; + + st->ra_0x53 = BMI_INT1_OUT_ACTIVE_HIGH; + st->ra_0x54 = 0x00; + st->ra_0x58 = BMI_INT1_DTRDY; + st->rg_0x16 = BMI_REG_INT_3_ACTIVE_HIGH; + st->rg_0x18 = BMI_REG_INT_3_4_IO_MAP_INT3; + st->hw_n = BMI_HW_N; + st->i2c_addrs[BMI_HW_ACC] = 0; + st->hw_en = 0; + st->enabled = 0; + + ret = bmi_of_dt(st, st->i2c->dev.of_node); + if (ret) { + dev_err(&st->i2c->dev, "of_dt ERR\n"); + return ret; + } + + /* + * Only interrupt mode is supported as we want hardware timestamps + * from GTE. + */ + if (st->gis[BMI_HW_ACC].gpio < 0 || st->gis[BMI_HW_GYR].gpio < 0) + return -EINVAL; + + st->part = id->driver_data; + st->i2c_addrs[BMI_HW_GYR] = st->i2c->addr; + ret = bmi_reset_all(st); + if (ret) { + dev_err(&st->i2c->dev, "softreset failed\n"); + return ret; + } + + bmi_fn_dev.sts = &st->sts; + + /* + * 0 - Accl + * 1 - Gyro + */ + for (i = 0; i < BMI_HW_N; i++) { + memcpy(&st->snsrs[i].cfg, &bmi_snsr_cfgs[i], + sizeof(st->snsrs[i].cfg)); + + ret = bmi_08x_iio_init(&st->snsrs[i].bmi_iio, st, + &st->i2c->dev, &bmi_fn_dev, + &st->snsrs[i].cfg); + if (ret) + return -ENODEV; + + st->snsrs[i].cfg.snsr_id = i; + st->snsrs[i].cfg.part = bmi_i2c_device_ids[st->part].name; + st->snsrs[i].rrs = &bmi_hws[i].rrs[st->part]; + bmi_max_range(st, i, st->snsrs[i].cfg.max_range.ival); + st->gis[i].dev_name = st->snsrs[i].cfg.name; + st->gis[i].gte = NULL; + st->iio_init_done[i] = true; + } + + ret = bmi_setup_gpio(&st->i2c->dev, st, st->hw_n); + if (ret < 0) + return ret; + + for (i = 0; i < st->hw_n; i++) { + if (bmi_hws[i].fn_irqflags) { + irqflags = bmi_hws[i].fn_irqflags(st); + ret = devm_request_threaded_irq(&st->i2c->dev, + st->gis[i].irq, + NULL, + bmi_irq_thread, + irqflags, + st->gis[i].dev_name, + st); + if (ret) { + dev_err(&st->i2c->dev, + "req_threaded_irq ERR %d\n", ret); + return ret; + } + } + } + + /* + * default rate to slowest speed, this gets reflected in register + * during buffer enable time. + */ + for (i = 0; i < st->hw_n; i++) + st->snsrs[i].period_us = st->snsrs[i].cfg.delay_us_max; + + gte_nd = of_find_compatible_node(NULL, NULL, gte_hw_str_t194); + if (!gte_nd) + gte_nd = of_find_compatible_node(NULL, NULL, gte_hw_str_t234); + + if (!gte_nd) { + dev_err(&st->i2c->dev, "Failed to find GTE node\n"); + return -ENODEV; + } + + return ret; +} + + +#if KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE +static int bmi_probe(struct i2c_client *client) +#else +static int bmi_probe(struct i2c_client *client, const struct i2c_device_id *id) +#endif +{ + struct bmi_state *st; + int ret; + + st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL); + if (st == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, st); + st->i2c = client; +#if KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE + ret = bmi_init(st, NULL); +#else + ret = bmi_init(st, id); +#endif + if (ret) { + bmi_remove(client); + return ret; + } + + ret = devm_add_action_or_reset(&client->dev, bmi_remove, client); + if (ret) + return ret; + + dev_dbg(&client->dev, "done\n"); + + return ret; +} + +MODULE_DEVICE_TABLE(i2c, bmi_i2c_device_ids); + +static const struct of_device_id bmi_of_match[] = { + { .compatible = "bmi,bmi088", }, + {} +}; + +MODULE_DEVICE_TABLE(of, bmi_of_match); + +static struct i2c_driver bmi_driver = { + .class = I2C_CLASS_HWMON, + .probe = bmi_probe, + .driver = { + .name = BMI_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(bmi_of_match), + .pm = &bmi_pm_ops, + }, + .id_table = bmi_i2c_device_ids, +}; + +module_i2c_driver(bmi_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMI088 I2C driver"); +MODULE_AUTHOR("NVIDIA Corporation"); + diff --git a/drivers/bmi088/bmi088_iio.c b/drivers/bmi088/bmi088_iio.c new file mode 100644 index 00000000..225531ca --- /dev/null +++ b/drivers/bmi088/bmi088_iio.c @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bmi_iio.h" + +enum BMI_ATTR { + BMI_ATTR_PART, + BMI_ATTR_MATRIX, + BMI_ATTR_DEV_STATE, + BMI_ATTR_DEV_ERR, + BMI_ATTR_DUMP_REGS, +}; + +static ssize_t bmi_iio_attr_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t bmi_iio_attr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +static IIO_DEVICE_ATTR(part, 0444, + bmi_iio_attr_show, NULL, BMI_ATTR_PART); +static IIO_DEVICE_ATTR(dev_state, 0444, + bmi_iio_attr_show, NULL, BMI_ATTR_DEV_STATE); +static IIO_DEVICE_ATTR(dev_err, 0444, + bmi_iio_attr_show, NULL, BMI_ATTR_DEV_ERR); +static IIO_DEVICE_ATTR(dump_regs, 0444, + bmi_iio_attr_show, NULL, BMI_ATTR_DUMP_REGS); + +static IIO_DEVICE_ATTR(mount_matrix, 0444, + bmi_iio_attr_show, bmi_iio_attr_store, BMI_ATTR_MATRIX); +static +IIO_CONST_ATTR(in_accel_sampling_frequency_available, + "12.5 25 50 100 200 400 800 1600"); +static +IIO_CONST_ATTR(in_anglvel_sampling_frequency_available, + "100 200 400 1000 2000"); + +static +IIO_CONST_ATTR(in_accel_scale_available, + "0.000897 0.001795 0.003591 0.007182"); +static +IIO_CONST_ATTR(in_anglvel_scale_available, + "0.001065 0.000532 0.000266 0.000133 0.000066"); + +static struct attribute *bmi_iio_attrs[] = { + &iio_dev_attr_part.dev_attr.attr, + &iio_dev_attr_mount_matrix.dev_attr.attr, + &iio_dev_attr_dev_state.dev_attr.attr, + &iio_dev_attr_dev_err.dev_attr.attr, + &iio_dev_attr_dump_regs.dev_attr.attr, +}; + +static struct attribute *bmi_iio_accel_attrs[] = { + &iio_const_attr_in_accel_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + NULL +}; + +static struct attribute *bmi_iio_gyro_attrs[] = { + &iio_const_attr_in_anglvel_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_anglvel_scale_available.dev_attr.attr, + NULL +}; + +/* array size is same for accl and gyro */ +#define ATTR_SZ_DEVICE ARRAY_SIZE(bmi_iio_gyro_attrs) + +enum bmi088_accel_scan_axis { + BMI088_SCAN_ACCEL_X, + BMI088_SCAN_ACCEL_Y, + BMI088_SCAN_ACCEL_Z, + BMI088_SCAN_ACCEL_TS, +}; + +enum bmi088_gyro_scan_axis { + BMI088_SCAN_GYRO_X, + BMI088_SCAN_GYRO_Y, + BMI088_SCAN_GYRO_Z, + BMI088_SCAN_GYRO_TS, +}; + +#define NUM_CHANNELS 3 + +#define BMI088_CHANNEL(_type, _axis, _index) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +#define IIO_CHAN_HW_TIMESTAMP(_si) { \ + .type = IIO_TIMESTAMP, \ + .channel = -1, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 64, \ + .storagebits = 64, \ + }, \ +} + +static const struct iio_chan_spec bmi088_acc_channels[] = { + BMI088_CHANNEL(IIO_ACCEL, X, BMI088_SCAN_ACCEL_X), + BMI088_CHANNEL(IIO_ACCEL, Y, BMI088_SCAN_ACCEL_Y), + BMI088_CHANNEL(IIO_ACCEL, Z, BMI088_SCAN_ACCEL_Z), + IIO_CHAN_HW_TIMESTAMP(BMI088_SCAN_ACCEL_TS), +}; + +static const struct iio_chan_spec bmi088_gyro_channels[] = { + BMI088_CHANNEL(IIO_ANGL_VEL, X, BMI088_SCAN_GYRO_X), + BMI088_CHANNEL(IIO_ANGL_VEL, Y, BMI088_SCAN_GYRO_Y), + BMI088_CHANNEL(IIO_ANGL_VEL, Z, BMI088_SCAN_GYRO_Z), + IIO_CHAN_HW_TIMESTAMP(BMI088_SCAN_GYRO_TS), +}; + +struct bmi_iio_state { + void *client; + struct device *dev; + struct iio_fn_dev *fn_dev; + struct sensor_cfg *cfg; + struct iio_chan_spec *chs; + struct attribute *attrs[ARRAY_SIZE(bmi_iio_attrs) + ATTR_SZ_DEVICE]; + struct attribute_group attr_group; + struct iio_info info; + u64 ts; + + struct { + s16 chan[NUM_CHANNELS]; + u64 timestamp __aligned(8); + } data; +}; + +int bmi_iio_push_buf(struct iio_dev *indio_dev, unsigned char *data, u64 ts) +{ + struct bmi_iio_state *st; + int ret = 0; + int bit; + __le16 *sample = (__le16 *)data; + + if (!indio_dev || !data) + return -EINVAL; + + st = iio_priv(indio_dev); + if (!st) + return -EINVAL; + + memset(&st->data, 0, sizeof(st->data)); + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + st->data.chan[bit] = sample[bit]; + } + + if (iio_buffer_enabled(indio_dev)) { + ret = iio_push_to_buffers_with_timestamp(indio_dev, &st->data, ts); + if (!ret) { + if (ts) + st->ts = ts; + } + } + + if (ts) { + if (unlikely((ts - st->ts) < 0)) { + dev_err(st->dev, "%s ts_diff=%lld\n", + st->cfg->name, ts - st->ts); + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(bmi_iio_push_buf); + +static int bmi_iio_enable(struct iio_dev *indio_dev, bool en) +{ + struct bmi_iio_state *st = iio_priv(indio_dev); + int enable = 0; + int bit; + + if (!en) + return st->fn_dev->enable(st->client, st->cfg->snsr_id, + 0, true); + + if (indio_dev->num_channels > 1) { + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) + enable |= 1 << bit; + } else { + enable = 1; + } + + + return st->fn_dev->enable(st->client, st->cfg->snsr_id, enable, true); +} + +static ssize_t bmi_iio_attr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmi_iio_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + char *str; + s8 matrix[9]; + unsigned int new; + int ret; + + if (!indio_dev || !st || !this_attr) + return -EINVAL; + +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_lock(&indio_dev->mlock); +#endif + + if (*st->fn_dev->sts & (BMI_STS_SHUTDOWN | BMI_STS_SUSPEND)) { +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&indio_dev->mlock); +#endif + return -EPERM; + } + + switch (this_attr->address) { + case BMI_ATTR_MATRIX: + for (new = 0; new < sizeof(matrix); new++) { + str = strsep((char **)&buf, " \0"); + if (str != NULL) { + ret = kstrtos8(str, 10, &matrix[new]); + if (ret) + break; + + if (matrix[new] < -1 || matrix[new] > 1) { + ret = -EINVAL; + break; + } + } else { + ret = -EINVAL; + break; + } + } + + if (new == sizeof(matrix)) + memcpy(st->cfg->matrix, matrix, + sizeof(st->cfg->matrix)); + break; + default: + ret = -EINVAL; + } + +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&indio_dev->mlock); +#endif + + return ret; +} + +static ssize_t bmi_iio_attr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmi_iio_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + ssize_t t = 0; + unsigned int i; + + if (!indio_dev || !st || !this_attr) + return -EINVAL; + + switch (this_attr->address) { + case BMI_ATTR_PART: + return snprintf(buf, PAGE_SIZE, "%s %s\n", + st->cfg->part, st->cfg->name); + + case BMI_ATTR_MATRIX: +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_lock(&indio_dev->mlock); +#endif + for (i = 0; i < 8; i++) + t += snprintf(buf + t, PAGE_SIZE - t, "%hhd,", + st->cfg->matrix[i]); + t += snprintf(buf + t, PAGE_SIZE - t, "%hhd\n", + st->cfg->matrix[i]); +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&indio_dev->mlock); +#endif + + return t; + + case BMI_ATTR_DEV_ERR: + if (!st->fn_dev->read_err) + return -EINVAL; + + return st->fn_dev->read_err(st->client, st->cfg->snsr_id, buf); + + case BMI_ATTR_DEV_STATE: + return snprintf(buf, PAGE_SIZE, "dev state=%u\n", + *st->fn_dev->sts); + + case BMI_ATTR_DUMP_REGS: + if (!st->fn_dev->regs) + return -EINVAL; + + return st->fn_dev->regs(st->client, st->cfg->snsr_id, buf); + + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int bmi_iio_attr(struct iio_dev *indio_dev, const char *name) +{ + struct bmi_iio_state *st; + + if (!indio_dev || !name) + return 0; + + st = iio_priv(indio_dev); + + memcpy(st->attrs, bmi_iio_attrs, sizeof(bmi_iio_attrs)); + if (!strcmp(st->cfg->name, "accelerometer")) + memcpy(&st->attrs[ARRAY_SIZE(bmi_iio_attrs)], + bmi_iio_accel_attrs, sizeof(bmi_iio_accel_attrs)); + else if (!strcmp(st->cfg->name, "gyroscope")) + memcpy(&st->attrs[ARRAY_SIZE(bmi_iio_attrs)], + bmi_iio_gyro_attrs, sizeof(bmi_iio_gyro_attrs)); + else + return 0; + + st->attr_group.attrs = st->attrs; + + return 0; +} + +static inline int bmi_iio_check_enable(struct bmi_iio_state *st) +{ + if (!st->fn_dev->enable) + return -EINVAL; + + return st->fn_dev->enable(st->client, st->cfg->snsr_id, -1, false); +} + +static int bmi_iio_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct bmi_iio_state *st = iio_priv(indio_dev); + int ret; + +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_lock(&indio_dev->mlock); +#endif + if (*st->fn_dev->sts & (BMI_STS_SHUTDOWN | BMI_STS_SUSPEND)) { +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&indio_dev->mlock); +#endif + return -EPERM; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = bmi_iio_check_enable(st); + if (ret < 0) + goto out; + + if (!st->fn_dev->get_data) { + ret = -EINVAL; + goto out; + } + + ret = st->fn_dev->get_data(st->client, st->cfg->snsr_id, + chan->channel2, val); + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + if (!st->fn_dev->freq_read) { + ret = -EINVAL; + goto out; + } + + ret = st->fn_dev->freq_read(st->client, st->cfg->snsr_id, + val, val2); + if (!ret) + ret = st->cfg->float_significance; + break; + case IIO_CHAN_INFO_SCALE: + *val = st->cfg->scale.ival; + *val2 = st->cfg->scale.fval; + + ret = st->cfg->float_significance; + break; + default: + ret = -EINVAL; + break; + } + +out: +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&indio_dev->mlock); +#endif + + return ret; +} + +static int bmi_iio_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct bmi_iio_state *st = iio_priv(indio_dev); + int ret = 0; + +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_lock(&indio_dev->mlock); +#endif + if (*st->fn_dev->sts & (BMI_STS_SHUTDOWN | BMI_STS_SUSPEND)) { +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&indio_dev->mlock); +#endif + return -EPERM; + } + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (!st->fn_dev->freq_write) { + ret = -EINVAL; + goto out; + } + + ret = st->fn_dev->freq_write(st->client, st->cfg->snsr_id, + val, val2); + break; + + case IIO_CHAN_INFO_SCALE: + if (!st->fn_dev->scale_write) { + ret = -EINVAL; + goto out; + } + + ret = st->fn_dev->scale_write(st->client, st->cfg->snsr_id, + val, val2); + break; + + default: + ret = -EINVAL; + break; + } + +out: +#if KERNEL_VERSION(6, 2, 0) >= LINUX_VERSION_CODE + mutex_unlock(&indio_dev->mlock); +#endif + + return ret; +} + +static int bmi_iio_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bmi_iio_state *st = iio_priv(indio_dev); + + if (*st->fn_dev->sts & (BMI_STS_SHUTDOWN | BMI_STS_SUSPEND)) + return -EINVAL; + + return 0; +} + +static int bmi_iio_buffer_postenable(struct iio_dev *indio_dev) +{ + return bmi_iio_enable(indio_dev, true); +} + +static int bmi_iio_buffer_postdisable(struct iio_dev *indio_dev) +{ + return bmi_iio_enable(indio_dev, false); +} + +static const struct iio_buffer_setup_ops bmi_iio_buffer_setup_ops = { + .preenable = &bmi_iio_buffer_preenable, + .postenable = &bmi_iio_buffer_postenable, + .postdisable = &bmi_iio_buffer_postdisable, +}; + +void bmi_iio_remove(struct iio_dev *indio_dev) +{ + (void)indio_dev; +} +EXPORT_SYMBOL_GPL(bmi_iio_remove); + +static int __bmi_08x_iio_init(struct iio_dev *indio_dev, + struct bmi_iio_state *st) +{ + struct iio_buffer *buffer; + int ret; + + ret = bmi_iio_attr(indio_dev, st->cfg->name); + if (ret) { + dev_err(st->dev, "bmi_iio_attr ERR=%d\n", ret); + return ret; + } + + if (!strcmp(st->cfg->name, "accelerometer")) { + indio_dev->channels = bmi088_acc_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi088_acc_channels); + } else if (!strcmp(st->cfg->name, "gyroscope")) { + indio_dev->channels = bmi088_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi088_gyro_channels); + } else { + return -EINVAL; + } + + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->dev.parent = st->dev; + indio_dev->name = st->cfg->name; + st->info.attrs = &st->attr_group; + st->info.read_raw = &bmi_iio_read_raw; + st->info.write_raw = &bmi_iio_write_raw; + indio_dev->info = &st->info; + indio_dev->setup_ops = &bmi_iio_buffer_setup_ops; + buffer = iio_kfifo_allocate(); + if (!buffer) { + dev_err(st->dev, "iio_kfifo_allocate ERR\n"); + return -ENOMEM; + } + + iio_device_attach_buffer(indio_dev, buffer); + + ret = devm_iio_device_register(st->dev, indio_dev); + if (ret) { + dev_err(st->dev, "iio_device_register ERR\n"); + return ret; + } + + return 0; +} + +int bmi_08x_iio_init(struct iio_dev **handle, void *dev_client, + struct device *dev, struct iio_fn_dev *fn_dev, + struct sensor_cfg *snsr_cfg) +{ + struct iio_dev *indio_dev; + struct bmi_iio_state *st; + int ret; + + if (!snsr_cfg) + return -ENODEV; + + if (snsr_cfg->snsr_id < 0) { + /* device has been disabled */ + if (snsr_cfg->name) + dev_info(dev, "%s disabled\n", snsr_cfg->name); + else + dev_info(dev, "device disabled\n"); + + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) { + dev_err(dev, "iio_device_alloc ERR\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + st->client = dev_client; + st->dev = dev; + st->fn_dev = fn_dev; + + st->cfg = snsr_cfg; + ret = __bmi_08x_iio_init(indio_dev, st); + if (ret) { + if (st->cfg->name) + dev_err(st->dev, "%s snsr_id=%d EXIT ERR=%d\n", + st->cfg->name, st->cfg->snsr_id, ret); + else + dev_err(st->dev, "snsr_id=%d EXIT ERR=%d\n", + st->cfg->snsr_id, ret); + + return ret; + } + + *handle = indio_dev; + + dev_info(st->dev, "iio %s done\n", st->cfg->name); + + return ret; +} +EXPORT_SYMBOL_GPL(bmi_08x_iio_init); + +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_DESCRIPTION("BMI088 IIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/bmi088/bmi_iio.h b/drivers/bmi088/bmi_iio.h new file mode 100644 index 00000000..cb85e610 --- /dev/null +++ b/drivers/bmi088/bmi_iio.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + + +#ifndef _BMI_IIO_H_ +#define _BMI_IIO_H_ + +#include +#include + +#define BMI_STS_SHUTDOWN (1 << 0) +#define BMI_STS_SUSPEND (1 << 1) + +struct bmi_float { + int ival; + int fval; +}; + +struct sensor_cfg { + const char *name; + int snsr_id; + unsigned int ch_n; + const char *part; + struct bmi_float max_range; + int delay_us_max; + signed char matrix[9]; + unsigned int float_significance; + struct bmi_float scale; +}; + +struct iio_fn_dev { + unsigned int *sts; + int (*enable)(void *client, int snsr_id, int enable, bool is_gte); + int (*freq_read)(void *client, int snsr_id, int *val, int *val2); + int (*freq_write)(void *client, int snsr_id, int val, int val2); + int (*scale_write)(void *client, int snsr_id, int val, int val2); + int (*regs)(void *client, int snsr_id, char *buf); + int (*read_err)(void *client, int snsr_id, char *buf); + int (*get_data)(void *client, int snsr_id, int axis, int *val); +}; + +void bmi_iio_remove(struct iio_dev *indio_dev); +int bmi_iio_push_buf(struct iio_dev *indio_dev, unsigned char *data, u64 ts); +int bmi_08x_iio_init(struct iio_dev **handle, void *dev_client, + struct device *dev, struct iio_fn_dev *fn_dev, + struct sensor_cfg *snsr_cfg); + +#endif