diff options
author | Jean-Baptiste Maneyrol <jmaneyrol@invensense.com> | 2020-07-17 19:29:54 +0200 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-08-29 01:00:17 +0000 |
commit | b5613d0f007651f9b5adeb498150bf51672984f9 (patch) | |
tree | be7f1026a6e889fbe93e9d492d66ffff67d59964 | |
parent | 52315edd087c95ba400c90f7964eb11d180d02e0 (diff) | |
download | chrome-ec-b5613d0f007651f9b5adeb498150bf51672984f9.tar.gz |
driver: add ICM-426xx driver support
Add ICM-426xx accel/gyro driver code.
BUG=chromium:1117541
BRANCH=None
TEST=ectool motionsense fifo_read && tast run hardware.SensorRing
Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
Change-Id: I83fe48abc6aa9cde86576a777ac4272d90fac597
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2317888
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
Commit-Queue: Gwendal Grignou <gwendal@chromium.org>
Tested-by: Gwendal Grignou <gwendal@chromium.org>
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | driver/accelgyro_icm426xx.c | 969 | ||||
-rw-r--r-- | driver/accelgyro_icm426xx.h | 256 | ||||
-rw-r--r-- | driver/accelgyro_icm_common.c | 399 | ||||
-rw-r--r-- | driver/accelgyro_icm_common.h | 101 | ||||
-rw-r--r-- | driver/build.mk | 1 | ||||
-rw-r--r-- | include/config.h | 2 |
7 files changed, 1729 insertions, 0 deletions
diff --git a/common/build.mk b/common/build.mk index 21a268f507..f54f9ad5a8 100644 --- a/common/build.mk +++ b/common/build.mk @@ -14,6 +14,7 @@ common-y+=version.o printf.o queue.o queue_policies.o common-$(CONFIG_ACCELGYRO_BMI160)+=math_util.o common-$(CONFIG_ACCELGYRO_BMI260)+=math_util.o +common-$(CONFIG_ACCELGYRO_ICM426XX)+=math_util.o common-$(CONFIG_ACCELGYRO_LSM6DS0)+=math_util.o common-$(CONFIG_ACCELGYRO_LSM6DSM)+=math_util.o common-$(CONFIG_ACCELGYRO_LSM6DSO)+=math_util.o diff --git a/driver/accelgyro_icm426xx.c b/driver/accelgyro_icm426xx.c new file mode 100644 index 0000000000..27d590b96e --- /dev/null +++ b/driver/accelgyro_icm426xx.c @@ -0,0 +1,969 @@ +/* Copyright 2020 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * ICM-426xx accelerometer and gyroscope module for Chrome EC + * 3D digital accelerometer & 3D digital gyroscope + */ + +#include "accelgyro.h" +#include "console.h" +#include "driver/accelgyro_icm_common.h" +#include "driver/accelgyro_icm426xx.h" +#include "hwtimer.h" +#include "i2c.h" +#include "math_util.h" +#include "motion_sense.h" +#include "motion_sense_fifo.h" +#include "spi.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +#define CPUTS(outstr) cputs(CC_ACCEL, outstr) +#define CPRINTF(format, args...) cprintf(CC_ACCEL, format, ## args) +#define CPRINTS(format, args...) cprints(CC_ACCEL, format, ## args) + +STATIC_IF(CONFIG_ACCEL_FIFO) volatile uint32_t last_interrupt_timestamp; + +static int icm426xx_normalize(const struct motion_sensor_t *s, intv3_t v, + const uint8_t *raw) +{ + struct accelgyro_saved_data_t *data = ICM_GET_SAVED_DATA(s); + int i; + + /* sensor data is configured as little-endian */ + v[X] = (int16_t)UINT16_FROM_BYTE_ARRAY_LE(raw, 0); + v[Y] = (int16_t)UINT16_FROM_BYTE_ARRAY_LE(raw, 2); + v[Z] = (int16_t)UINT16_FROM_BYTE_ARRAY_LE(raw, 4); + + /* check if data is valid */ + if (v[X] == ICM426XX_INVALID_DATA && + v[Y] == ICM426XX_INVALID_DATA && + v[Z] == ICM426XX_INVALID_DATA) { + return EC_ERROR_INVAL; + } + + rotate(v, *s->rot_standard_ref, v); + + for (i = X; i <= Z; i++) + v[i] = SENSOR_APPLY_SCALE(v[i], data->scale[i]); + + return EC_SUCCESS; +} + +/* use FIFO threshold interrupt on INT1 */ +#define ICM426XX_FIFO_INT_EN ICM426XX_FIFO_THS_INT1_EN +#define ICM426XX_FIFO_INT_STATUS ICM426XX_FIFO_THS_INT + +static int __maybe_unused icm426xx_enable_fifo(const struct motion_sensor_t *s, + int enable) +{ + int val, ret; + + if (enable) { + /* enable FIFO interrupts */ + ret = icm_field_update8(s, ICM426XX_REG_INT_SOURCE0, + ICM426XX_FIFO_INT_EN, + ICM426XX_FIFO_INT_EN); + if (ret != EC_SUCCESS) + return ret; + + /* flush FIFO data */ + ret = icm_write8(s, ICM426XX_REG_SIGNAL_PATH_RESET, + ICM426XX_FIFO_FLUSH); + if (ret != EC_SUCCESS) + return ret; + + /* set FIFO in streaming mode */ + ret = icm_write8(s, ICM426XX_REG_FIFO_CONFIG, + ICM426XX_FIFO_MODE_STREAM); + if (ret != EC_SUCCESS) + return ret; + + /* workaround: first read of FIFO count is always 0 */ + ret = icm_read16(s, ICM426XX_REG_FIFO_COUNT, &val); + if (ret != EC_SUCCESS) + return ret; + } else { + /* set FIFO in bypass mode */ + ret = icm_write8(s, ICM426XX_REG_FIFO_CONFIG, + ICM426XX_FIFO_MODE_BYPASS); + if (ret != EC_SUCCESS) + return ret; + + /* flush FIFO data */ + ret = icm_write8(s, ICM426XX_REG_SIGNAL_PATH_RESET, + ICM426XX_FIFO_FLUSH); + if (ret != EC_SUCCESS) + return ret; + + /* disable FIFO interrupts */ + ret = icm_field_update8(s, ICM426XX_REG_INT_SOURCE0, + ICM426XX_FIFO_INT_EN, 0); + if (ret != EC_SUCCESS) + return ret; + } + + return EC_SUCCESS; +} + +static int __maybe_unused icm426xx_config_fifo(const struct motion_sensor_t *s, + int enable) +{ + struct icm_drv_data_t *st = ICM_GET_DATA(s); + int mask, val; + uint8_t old_fifo_en; + int ret; + + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + mask = ICM426XX_FIFO_ACCEL_EN; + break; + case MOTIONSENSE_TYPE_GYRO: + mask = ICM426XX_FIFO_GYRO_EN; + break; + default: + return EC_ERROR_INVAL; + } + /* temperature data has to be always present in the FIFO */ + mask |= ICM426XX_FIFO_TEMP_EN; + + val = enable ? mask : 0; + + mutex_lock(s->mutex); + + ret = icm_field_update8(s, ICM426XX_REG_FIFO_CONFIG1, mask, val); + if (ret != EC_SUCCESS) + goto out_unlock; + + old_fifo_en = st->fifo_en; + if (enable) + st->fifo_en |= BIT(s->type); + else + st->fifo_en &= ~BIT(s->type); + + if (!old_fifo_en && st->fifo_en) { + /* 1st sensor enabled => turn FIFO on */ + ret = icm426xx_enable_fifo(s, 1); + if (ret != EC_SUCCESS) + goto out_unlock; + } else if (old_fifo_en && !st->fifo_en) { + /* last sensor disabled => turn FIFO off */ + ret = icm426xx_enable_fifo(s, 0); + if (ret != EC_SUCCESS) + goto out_unlock; + } + +out_unlock: + mutex_unlock(s->mutex); + return ret; +} + +static void __maybe_unused icm426xx_push_fifo_data(struct motion_sensor_t *s, + const uint8_t *raw, uint32_t ts) +{ + intv3_t v; + struct ec_response_motion_sensor_data vect; + int ret; + + if (s == NULL) + return; + + ret = icm426xx_normalize(s, v, raw); + if (ret == EC_SUCCESS) { + vect.data[X] = v[X]; + vect.data[Y] = v[Y]; + vect.data[Z] = v[Z]; + vect.flags = 0; + vect.sensor_num = s - motion_sensors; + motion_sense_fifo_stage_data(&vect, s, 3, ts); + } +} + +static int __maybe_unused icm426xx_load_fifo(struct motion_sensor_t *s, + uint32_t ts) +{ + struct icm_drv_data_t *st = ICM_GET_DATA(s); + int count, i, size; + const uint8_t *accel, *gyro; + int ret; + + ret = icm_read16(s, ICM426XX_REG_FIFO_COUNT, &count); + if (ret != EC_SUCCESS) + return ret; + + if (count <= 0) + return EC_ERROR_INVAL; + + /* flush FIFO if buffer is not large enough */ + if (count > ICM_FIFO_BUFFER) { + CPRINTS("It should not happen, the EC is too slow for the ODR"); + ret = icm_write8(s, ICM426XX_REG_SIGNAL_PATH_RESET, + ICM426XX_FIFO_FLUSH); + if (ret != EC_SUCCESS) + return ret; + return EC_ERROR_OVERFLOW; + } + + ret = icm_read_n(s, ICM426XX_REG_FIFO_DATA, st->fifo_buffer, count); + if (ret != EC_SUCCESS) + return ret; + + for (i = 0; i < count; i += size) { + size = icm_fifo_decode_packet(&st->fifo_buffer[i], + &accel, &gyro); + /* exit if error or FIFO is empty */ + if (size <= 0) + return -size; + if (accel != NULL) + icm426xx_push_fifo_data(st->accel, accel, ts); + if (gyro != NULL) + icm426xx_push_fifo_data(st->gyro, gyro, ts); + } + + return EC_SUCCESS; +} + +#ifdef CONFIG_ACCEL_INTERRUPTS + +/** + * icm426xx_interrupt - called when the sensor activates the interrupt line. + * + * This is a "top half" interrupt handler, it just asks motion sense ask + * to schedule the "bottom half", ->icm426xx_irq_handler(). + */ +void icm426xx_interrupt(enum gpio_signal signal) +{ + if (IS_ENABLED(CONFIG_ACCEL_FIFO)) + last_interrupt_timestamp = __hw_clock_source_read(); + + task_set_event(TASK_ID_MOTIONSENSE, + CONFIG_ACCELGYRO_ICM426XX_INT_EVENT, 0); +} + +/** + * icm426xx_irq_handler - bottom half of the interrupt stack. + * Ran from the motion_sense task, finds the events that raised the interrupt. + */ +static int icm426xx_irq_handler(struct motion_sensor_t *s, uint32_t *event) +{ + int status; + int ret; + + if ((s->type != MOTIONSENSE_TYPE_ACCEL) || + (!(*event & CONFIG_ACCELGYRO_ICM426XX_INT_EVENT))) + return EC_ERROR_NOT_HANDLED; + + mutex_lock(s->mutex); + + /* read and clear interrupt status */ + ret = icm_read8(s, ICM426XX_REG_INT_STATUS, &status); + if (ret != EC_SUCCESS) + goto out_unlock; + + if (IS_ENABLED(CONFIG_ACCEL_FIFO)) { + if (status & ICM426XX_FIFO_INT_STATUS) { + ret = icm426xx_load_fifo(s, last_interrupt_timestamp); + if (ret == EC_SUCCESS) + motion_sense_fifo_commit_data(); + } + } + +out_unlock: + mutex_unlock(s->mutex); + return ret; +} + +static int icm426xx_config_interrupt(const struct motion_sensor_t *s) +{ + struct icm_drv_data_t *st = ICM_GET_DATA(s); + int val, ret; + + /* configure INT1 pin */ + val = ICM426XX_INT1_PUSH_PULL; + if (s->flags & MOTIONSENSE_FLAG_INT_ACTIVE_HIGH) + val |= ICM426XX_INT1_ACTIVE_HIGH; + + ret = icm_write8(s, ICM426XX_REG_INT_CONFIG, val); + if (ret != EC_SUCCESS) + return ret; + + /* deassert async reset for proper INT pin operation */ + ret = icm_field_update8(s, ICM426XX_REG_INT_CONFIG1, + ICM426XX_INT_ASYNC_RESET, 0); + if (ret != EC_SUCCESS) + return ret; + + if (IS_ENABLED(CONFIG_ACCEL_FIFO)) { + /* + * configure FIFO: + * - enable FIFO partial read + * - enable continuous watermark interrupt + * - disable all FIFO en bits + */ + val = ICM426XX_FIFO_PARTIAL_READ | ICM426XX_FIFO_WM_GT_TH; + ret = icm_field_update8(s, ICM426XX_REG_FIFO_CONFIG1, + GENMASK(6, 5) | ICM426XX_FIFO_EN_MASK, + val); + if (ret != EC_SUCCESS) + return ret; + + /* clear internal FIFO enable bits tracking */ + st->fifo_en = 0; + + /* set FIFO watermark to 1 data packet (8 bytes) */ + ret = icm_write16(s, ICM426XX_REG_FIFO_WATERMARK, 8); + if (ret != EC_SUCCESS) + return ret; + } + + return ret; +} + +#endif /* CONFIG_ACCEL_INTERRUPTS */ + +static int icm426xx_enable_sensor(const struct motion_sensor_t *s, int enable) +{ + unsigned int sleep; + uint8_t mask, val; + int ret; + + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + mask = ICM426XX_ACCEL_MODE_MASK; + if (enable) { + sleep = 20; + val = ICM426XX_ACCEL_MODE(ICM426XX_MODE_LOW_POWER); + } else { + sleep = 0; + val = ICM426XX_ACCEL_MODE(ICM426XX_MODE_OFF); + } + break; + case MOTIONSENSE_TYPE_GYRO: + mask = ICM426XX_GYRO_MODE_MASK; + if (enable) { + sleep = 60; + val = ICM426XX_GYRO_MODE(ICM426XX_MODE_LOW_NOISE); + } else { + sleep = 150; + val = ICM426XX_GYRO_MODE(ICM426XX_MODE_OFF); + } + break; + default: + return -EC_ERROR_INVAL; + } + + mutex_lock(s->mutex); + + ret = icm_field_update8(s, ICM426XX_REG_PWR_MGMT0, mask, val); + /* when turning sensor on block any register write for 200 us */ + if (ret == EC_SUCCESS && enable) + usleep(200); + + mutex_unlock(s->mutex); + + if (ret != EC_SUCCESS) + return ret; + + if (sleep) + msleep(sleep); + + return EC_SUCCESS; +} + +static int icm426xx_set_data_rate(const struct motion_sensor_t *s, int rate, + int rnd) +{ + struct accelgyro_saved_data_t *data = ICM_GET_SAVED_DATA(s); + int reg, ret, reg_val; + int normalized_rate; + int max_rate, min_rate; + + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + reg = ICM426XX_REG_ACCEL_CONFIG0; + min_rate = ICM426XX_ACCEL_MIN_FREQ; + max_rate = ICM426XX_ACCEL_MAX_FREQ; + break; + case MOTIONSENSE_TYPE_GYRO: + reg = ICM426XX_REG_GYRO_CONFIG0; + min_rate = ICM426XX_GYRO_MIN_FREQ; + max_rate = ICM426XX_GYRO_MAX_FREQ; + break; + default: + return EC_RES_INVALID_PARAM; + } + + /* normalize the rate */ + reg_val = ICM426XX_ODR_TO_REG(rate); + normalized_rate = ICM426XX_REG_TO_ODR(reg_val); + + /* round up the rate */ + if (rnd && (normalized_rate < rate)) { + reg_val = ICM426XX_ODR_REG_UP(reg_val); + normalized_rate = ICM426XX_REG_TO_ODR(reg_val); + } + + if (rate > 0) { + if ((normalized_rate < min_rate) || + (normalized_rate > max_rate)) + return EC_RES_INVALID_PARAM; + } + + if (rate == 0) { + /* disable data in FIFO */ + if (IS_ENABLED(CONFIG_ACCEL_FIFO)) + icm426xx_config_fifo(s, 0); + /* disable sensor */ + ret = icm426xx_enable_sensor(s, 0); + data->odr = 0; + return ret; + } else if (data->odr == 0) { + /* enable sensor */ + ret = icm426xx_enable_sensor(s, 1); + if (ret) + return ret; + } + + mutex_lock(s->mutex); + + ret = icm_field_update8(s, reg, ICM426XX_ODR_MASK, + ICM426XX_ODR(reg_val)); + if (ret != EC_SUCCESS) + goto out_unlock; + + data->odr = normalized_rate; + + mutex_unlock(s->mutex); + + /* enable data in FIFO */ + if (IS_ENABLED(CONFIG_ACCEL_FIFO)) + icm426xx_config_fifo(s, 1); + + return EC_SUCCESS; + +out_unlock: + mutex_unlock(s->mutex); + return ret; +} + +static int icm426xx_set_range(const struct motion_sensor_t *s, int range, + int rnd) +{ + struct accelgyro_saved_data_t *data = ICM_GET_SAVED_DATA(s); + int reg, ret, reg_val; + int newrange; + + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + reg = ICM426XX_REG_ACCEL_CONFIG0; + + reg_val = ICM426XX_ACCEL_FS_TO_REG(range); + newrange = ICM426XX_ACCEL_REG_TO_FS(reg_val); + + if (rnd && (newrange < range) && (reg_val > 0)) { + reg_val--; + newrange = ICM426XX_ACCEL_REG_TO_FS(reg_val); + } + + if (newrange > ICM426XX_ACCEL_FS_MAX_VAL) { + newrange = ICM426XX_ACCEL_FS_MAX_VAL; + reg_val = ICM426XX_ACCEL_FS_TO_REG(range); + } + + break; + case MOTIONSENSE_TYPE_GYRO: + reg = ICM426XX_REG_GYRO_CONFIG0; + + reg_val = ICM426XX_GYRO_FS_TO_REG(range); + newrange = ICM426XX_GYRO_REG_TO_FS(reg_val); + + if (rnd && (newrange < range) && (reg_val > 0)) { + reg_val--; + newrange = ICM426XX_GYRO_REG_TO_FS(reg_val); + } + + if (newrange > ICM426XX_GYRO_FS_MAX_VAL) { + newrange = ICM426XX_GYRO_FS_MAX_VAL; + reg_val = ICM426XX_GYRO_FS_TO_REG(newrange); + } + + break; + default: + return EC_RES_INVALID_PARAM; + } + + mutex_lock(s->mutex); + + ret = icm_field_update8(s, reg, ICM426XX_FS_MASK, + ICM426XX_FS_SEL(reg_val)); + if (ret == EC_SUCCESS) + data->range = newrange; + + mutex_unlock(s->mutex); + + return ret; +} + +static int icm426xx_get_hw_offset(const struct motion_sensor_t *s, + intv3_t offset) +{ + uint8_t raw[5]; + int i, ret; + + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + mutex_lock(s->mutex); + ret = icm_read_n(s, ICM426XX_REG_OFFSET_USER4, + raw, sizeof(raw)); + mutex_unlock(s->mutex); + if (ret != EC_SUCCESS) + return ret; + /* + * raw[0]: Accel X[11:8] | gyro Z[11:8] + * raw[1]: Accel X[0:7] + * raw[2]: Accel Y[7:0] + * raw[3]: Accel Z[11:8] | Accel Y[11:8] + * raw[4]: Accel Z[7:0] + */ + offset[X] = (((int)raw[0] << 4) & ~GENMASK(7, 0)) | raw[1]; + offset[Y] = (((int)raw[3] << 8) & ~GENMASK(7, 0)) | raw[2]; + offset[Z] = (((int)raw[3] << 4) & ~GENMASK(7, 0)) | raw[4]; + break; + case MOTIONSENSE_TYPE_GYRO: + mutex_lock(s->mutex); + ret = icm_read_n(s, ICM426XX_REG_OFFSET_USER0, + raw, sizeof(raw)); + mutex_unlock(s->mutex); + if (ret != EC_SUCCESS) + return ret; + /* + * raw[0]: Gyro X[7:0] + * raw[1]: Gyro Y[11:8] | Gyro X[11:8] + * raw[2]: Gyro Y[7:0] + * raw[3]: Gyro Z[7:0] + * raw[4]: Accel X[11:8] | gyro Z[11:8] + */ + offset[X] = (((int)raw[1] << 8) & ~GENMASK(7, 0)) | raw[0]; + offset[Y] = (((int)raw[1] << 4) & ~GENMASK(7, 0)) | raw[2]; + offset[Z] = (((int)raw[4] << 8) & ~GENMASK(7, 0)) | raw[3]; + break; + default: + return EC_ERROR_INVAL; + } + + /* Extend sign-bit of 12 bits signed values */ + for (i = X; i <= Z; ++i) + offset[i] = sign_extend(offset[i], 11); + + return EC_SUCCESS; +} + +static int icm426xx_set_hw_offset(const struct motion_sensor_t *s, + intv3_t offset) +{ + int i, val, ret; + + /* value is 12 bits signed maximum */ + for (i = X; i <= Z; ++i) { + if (offset[i] > 2047) + offset[i] = 2047; + else if (offset[i] < -2048) + offset[i] = -2048; + } + + mutex_lock(s->mutex); + + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + /* Accel X[11:8] | gyro Z[11:8] */ + val = (offset[X] >> 4) & GENMASK(7, 4); + ret = icm_field_update8(s, ICM426XX_REG_OFFSET_USER4, + GENMASK(7, 4), val); + if (ret != EC_SUCCESS) + goto out_unlock; + + /* Accel X[7:0] */ + val = offset[X] & GENMASK(7, 0); + ret = icm_write8(s, ICM426XX_REG_OFFSET_USER5, val); + if (ret != EC_SUCCESS) + goto out_unlock; + + /* Accel Y[7:0] */ + val = offset[Y] & GENMASK(7, 0); + ret = icm_write8(s, ICM426XX_REG_OFFSET_USER6, val); + if (ret != EC_SUCCESS) + goto out_unlock; + + /* Accel Z[11:8] | Accel Y[11:8] */ + val = ((offset[Z] >> 4) & GENMASK(7, 4)) | + ((offset[Y] >> 8) & GENMASK(3, 0)); + ret = icm_write8(s, ICM426XX_REG_OFFSET_USER7, val); + if (ret != EC_SUCCESS) + goto out_unlock; + + /* Accel Z[7:0] */ + val = offset[Z] & GENMASK(7, 0); + ret = icm_write8(s, ICM426XX_REG_OFFSET_USER8, val); + if (ret != EC_SUCCESS) + goto out_unlock; + break; + + case MOTIONSENSE_TYPE_GYRO: + /* Gyro X[7:0] */ + val = offset[X] & GENMASK(7, 0); + ret = icm_write8(s, ICM426XX_REG_OFFSET_USER0, val); + if (ret != EC_SUCCESS) + goto out_unlock; + + /* Gyro Y[11:8] | Gyro X[11:8] */ + val = ((offset[Y] >> 4) & GENMASK(7, 4)) | + ((offset[X] >> 8) & GENMASK(3, 0)); + ret = icm_write8(s, ICM426XX_REG_OFFSET_USER1, val); + if (ret != EC_SUCCESS) + goto out_unlock; + + /* Gyro Y[7:0] */ + val = offset[Y] & GENMASK(7, 0); + ret = icm_write8(s, ICM426XX_REG_OFFSET_USER2, val); + if (ret != EC_SUCCESS) + goto out_unlock; + + /* Gyro Z[7:0] */ + val = offset[Z] & GENMASK(7, 0); + ret = icm_write8(s, ICM426XX_REG_OFFSET_USER3, val); + if (ret != EC_SUCCESS) + goto out_unlock; + + /* Accel X[11:8] | gyro Z[11:8] */ + val = (offset[Z] >> 8) & GENMASK(3, 0); + ret = icm_field_update8(s, ICM426XX_REG_OFFSET_USER4, + GENMASK(3, 0), val); + if (ret != EC_SUCCESS) + goto out_unlock; + break; + + default: + ret = EC_ERROR_INVAL; + break; + } + +out_unlock: + mutex_unlock(s->mutex); + return ret; +} + +static int icm426xx_set_offset(const struct motion_sensor_t *s, + const int16_t *offset, int16_t temp) +{ + struct accelgyro_saved_data_t *data = ICM_GET_SAVED_DATA(s); + intv3_t v = { offset[X], offset[Y], offset[Z] }; + int range, div1, div2; + int i; + + /* unscale values and rotate back to chip frame */ + for (i = X; i <= Z; ++i) + v[i] = SENSOR_APPLY_DIV_SCALE(v[i], data->scale[i]); + rotate_inv(v, *s->rot_standard_ref, v); + + /* convert raw data to hardware offset units */ + range = icm_get_range(s); + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + /* hardware offset is 1/2048g by LSB */ + div1 = range * 2048; + div2 = 32768; + break; + case MOTIONSENSE_TYPE_GYRO: + /* hardware offset is 1/32dps by LSB */ + div1 = range * 32; + div2 = 32768; + break; + default: + return EC_ERROR_INVAL; + } + for (i = X; i <= Z; ++i) + v[i] = round_divide(v[i] * div1, div2); + + return icm426xx_set_hw_offset(s, v); +} + +static int icm426xx_get_offset(const struct motion_sensor_t *s, int16_t *offset, + int16_t *temp) +{ + struct accelgyro_saved_data_t *data = ICM_GET_SAVED_DATA(s); + intv3_t v; + int range, div1, div2; + int i, ret; + + ret = icm426xx_get_hw_offset(s, v); + if (ret != EC_SUCCESS) + return ret; + + /* transform offset to raw data */ + range = icm_get_range(s); + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + /* hardware offset is 1/2048g by LSB */ + div1 = 32768; + div2 = 2048 * range; + break; + case MOTIONSENSE_TYPE_GYRO: + /* hardware offset is 1/32dps by LSB */ + div1 = 32768; + div2 = 32 * range; + break; + default: + return EC_ERROR_INVAL; + } + for (i = X; i <= Z; ++i) + v[i] = round_divide(v[i] * div1, div2); + + rotate(v, *s->rot_standard_ref, v); + for (i = X; i <= Z; i++) + v[i] = SENSOR_APPLY_SCALE(v[i], data->scale[i]); + + offset[X] = v[X]; + offset[Y] = v[Y]; + offset[Z] = v[Z]; + *temp = EC_MOTION_SENSE_INVALID_CALIB_TEMP; + return EC_SUCCESS; +} + +static int icm426xx_read(const struct motion_sensor_t *s, intv3_t v) +{ + uint8_t raw[6]; + int reg, ret; + + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + reg = ICM426XX_REG_ACCEL_DATA_XYZ; + break; + case MOTIONSENSE_TYPE_GYRO: + reg = ICM426XX_REG_GYRO_DATA_XYZ; + break; + default: + return EC_ERROR_INVAL; + } + + /* read data registers */ + mutex_lock(s->mutex); + ret = icm_read_n(s, reg, raw, sizeof(raw)); + mutex_unlock(s->mutex); + if (ret != EC_SUCCESS) + return ret; + + ret = icm426xx_normalize(s, v, raw); + /* if data is invalid return the previous read data */ + if (ret != EC_SUCCESS) { + if (v != s->raw_xyz) + memcpy(v, s->raw_xyz, sizeof(s->raw_xyz)); + } + + return EC_SUCCESS; +} + +static int icm426xx_read_temp(const struct motion_sensor_t *s, int *temp_ptr) +{ + int val, ret; + + mutex_lock(s->mutex); + ret = icm_read16(s, ICM426XX_REG_TEMP_DATA, &val); + mutex_unlock(s->mutex); + if (ret != EC_SUCCESS) + return ret; + + /* ensure correct propagation of 16 bits sign bit */ + val = sign_extend(val, 15); + + if (val == ICM426XX_INVALID_DATA) + return EC_ERROR_NOT_POWERED; + + *temp_ptr = C_TO_K((val * 100) / 13248 + 25); + return EC_SUCCESS; +} + +static int icm426xx_init_config(const struct motion_sensor_t *s) +{ + uint8_t mask, val; + int ret; + + /* + * serial bus setup (i2c or spi) + * + * Do not check result for INTF_CONFIG6, since it can induce + * interferences on the bus. + */ + + ret = 0; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + icm_field_update8(s, ICM426XX_REG_INTF_CONFIG6, + ICM426XX_INTF_CONFIG6_MASK, + ICM426XX_I3C_EN | ICM426XX_I3C_SDR_EN | + ICM426XX_I3C_DDR_EN); + ret = icm_field_update8(s, ICM426XX_REG_INTF_CONFIG4, + ICM426XX_I3C_BUS_MODE, + ICM426XX_I3C_BUS_MODE); +#endif + } else { +#ifdef I2C_PORT_ACCEL + icm_field_update8(s, ICM426XX_REG_INTF_CONFIG6, + ICM426XX_INTF_CONFIG6_MASK, + ICM426XX_I3C_EN); + ret = icm_field_update8(s, ICM426XX_REG_INTF_CONFIG4, + ICM426XX_I3C_BUS_MODE, + 0); +#endif + } + if (ret) + return ret; + + ret = 0; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + ret = icm_field_update8(s, ICM426XX_REG_DRIVE_CONFIG, + ICM426XX_DRIVE_CONFIG_MASK, + ICM426XX_I2C_SLEW_RATE(ICM426XX_SLEW_RATE_20NS_60NS) | + ICM426XX_SPI_SLEW_RATE(ICM426XX_SLEW_RATE_INF_2NS)); +#endif + } else { +#ifdef I2C_PORT_ACCEL + ret = icm_field_update8(s, ICM426XX_REG_DRIVE_CONFIG, + ICM426XX_DRIVE_CONFIG_MASK, + ICM426XX_I2C_SLEW_RATE(ICM426XX_SLEW_RATE_12NS_36NS) | + ICM426XX_SPI_SLEW_RATE(ICM426XX_SLEW_RATE_12NS_36NS)); +#endif + } + if (ret) + return ret; + + /* + * Use invalid value in registers and FIFO. + * Data registers in little-endian format. + * Disable unused serial interface. + */ + mask = ICM426XX_DATA_CONF_MASK | ICM426XX_UI_SIFS_CFG_MASK; + val = 0; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + val |= ICM426XX_UI_SIFS_CFG_I2C_DIS; +#endif + } else { +#ifdef I2C_PORT_ACCEL + val |= ICM426XX_UI_SIFS_CFG_SPI_DIS; +#endif + } + + return icm_field_update8(s, ICM426XX_REG_INTF_CONFIG0, mask, val); +} + +static int icm426xx_init(const struct motion_sensor_t *s) +{ + struct icm_drv_data_t *st = ICM_GET_DATA(s); + struct accelgyro_saved_data_t *saved_data = ICM_GET_SAVED_DATA(s); + int mask, val; + int ret, i; + + mutex_lock(s->mutex); + + /* manually force register bank to 0 */ + st->bank = 0; + ret = icm_write8(s, ICM426XX_REG_BANK_SEL, ICM426XX_BANK_SEL(0)); + if (ret) + goto out_unlock; + + /* detect chip using whoami */ + ret = icm_read8(s, ICM426XX_REG_WHO_AM_I, &val); + if (ret) + goto out_unlock; + + if (val != ICM426XX_CHIP_ICM40608 && val != ICM426XX_CHIP_ICM42605) { + CPRINTS("Unknown WHO_AM_I: 0x%02x", val); + ret = EC_ERROR_ACCESS_DENIED; + goto out_unlock; + } + + /* first time init done only for 1st sensor (accel) */ + if (s->type == MOTIONSENSE_TYPE_ACCEL) { + /* reset the chip and verify it is ready */ + ret = icm_write8(s, ICM426XX_REG_DEVICE_CONFIG, + ICM426XX_SOFT_RESET_CONFIG); + if (ret) + goto out_unlock; + msleep(1); + + ret = icm_read8(s, ICM426XX_REG_INT_STATUS, &val); + if (ret) + goto out_unlock; + if (!(val & ICM426XX_RESET_DONE_INT)) { + ret = EC_ERROR_ACCESS_DENIED; + goto out_unlock; + } + + /* configure sensor */ + ret = icm426xx_init_config(s); + if (ret) + goto out_unlock; + +#ifdef CONFIG_ACCEL_INTERRUPTS + ret = icm426xx_config_interrupt(s); + if (ret) + goto out_unlock; +#endif + } + + for (i = X; i <= Z; i++) + saved_data->scale[i] = MOTION_SENSE_DEFAULT_SCALE; + + /* set sensor filter */ + switch (s->type) { + case MOTIONSENSE_TYPE_ACCEL: + mask = ICM426XX_ACCEL_UI_FILT_MASK; + val = ICM426XX_ACCEL_UI_FILT_BW(ICM426XX_FILTER_BW_AVG_16X); + if (IS_ENABLED(CONFIG_ACCEL_FIFO)) + st->accel = (struct motion_sensor_t *)s; + break; + case MOTIONSENSE_TYPE_GYRO: + mask = ICM426XX_GYRO_UI_FILT_MASK; + val = ICM426XX_GYRO_UI_FILT_BW(ICM426XX_FILTER_BW_ODR_DIV_2); + if (IS_ENABLED(CONFIG_ACCEL_FIFO)) + st->gyro = (struct motion_sensor_t *)s; + break; + default: + ret = EC_ERROR_INVAL; + goto out_unlock; + } + ret = icm_field_update8(s, ICM426XX_REG_GYRO_ACCEL_CONFIG0, mask, val); + if (ret != EC_SUCCESS) + goto out_unlock; + + mutex_unlock(s->mutex); + + return sensor_init_done(s); + +out_unlock: + mutex_unlock(s->mutex); + return ret; +} + +const struct accelgyro_drv icm426xx_drv = { + .init = icm426xx_init, + .read = icm426xx_read, + .read_temp = icm426xx_read_temp, + .set_range = icm426xx_set_range, + .get_range = icm_get_range, + .get_resolution = icm_get_resolution, + .set_data_rate = icm426xx_set_data_rate, + .get_data_rate = icm_get_data_rate, + .set_offset = icm426xx_set_offset, + .get_offset = icm426xx_get_offset, + .set_scale = icm_set_scale, + .get_scale = icm_get_scale, +#ifdef CONFIG_ACCEL_INTERRUPTS + .irq_handler = icm426xx_irq_handler, +#endif +}; diff --git a/driver/accelgyro_icm426xx.h b/driver/accelgyro_icm426xx.h new file mode 100644 index 0000000000..0b4e3aa14a --- /dev/null +++ b/driver/accelgyro_icm426xx.h @@ -0,0 +1,256 @@ +/* Copyright 2020 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* ICM-426xx accelerometer and gyroscope for Chrome EC */ + +#ifndef __CROS_EC_ACCELGYRO_ICM426XX_H +#define __CROS_EC_ACCELGYRO_ICM426XX_H + +#include "accelgyro.h" +#include "common.h" + +/* + * 7-bit address is 110100Xb. Where 'X' is determined + * by the logic level on pin AP_AD0. + */ +#define ICM426XX_ADDR0_FLAGS 0x68 +#define ICM426XX_ADDR1_FLAGS 0x69 + +/* Min and Max sampling frequency in mHz */ +#define ICM426XX_ACCEL_MIN_FREQ 3125 +#define ICM426XX_ACCEL_MAX_FREQ MOTION_MAX_SENSOR_FREQUENCY(500000, 100000) +#define ICM426XX_GYRO_MIN_FREQ 12500 +#define ICM426XX_GYRO_MAX_FREQ MOTION_MAX_SENSOR_FREQUENCY(4000000, 100000) + +/* Min and Max Accel FS in G */ +#define ICM426XX_ACCEL_FS_MIN_VAL 2 +#define ICM426XX_ACCEL_FS_MAX_VAL 16 + +/* Min and Max Gyro FS in dps */ +#define ICM426XX_GYRO_FS_MIN_VAL 125 +#define ICM426XX_GYRO_FS_MAX_VAL 2000 + +/* Reg value from Accel FS in G */ +#define ICM426XX_ACCEL_FS_TO_REG(_fs) ((_fs) < 2 ? 3 : \ + (_fs) > 16 ? 0 : \ + 3 - __fls((_fs) / 2)) + +/* Accel FSR in G from Reg value */ +#define ICM426XX_ACCEL_REG_TO_FS(_reg) ((1 << (3 - (_reg))) * 2) + +/* Reg value from Gyro FS in dps */ +#define ICM426XX_GYRO_FS_TO_REG(_fs) ((_fs) < 125 ? 4 : \ + (_fs) > 2000 ? 0 : \ + 4 - __fls((_fs) / 125)) + +/* Gyro FSR in dps from Reg value */ +#define ICM426XX_GYRO_REG_TO_FS(_reg) ((1 << (4 - (_reg))) * 125) + +/* Reg value from ODR in mHz */ +#define ICM426XX_ODR_TO_REG(_odr) ((_odr) <= 200000 ? \ + 13 - __fls((_odr) / 3125) : \ + (_odr) < 500000 ? 7 : \ + (_odr) < 1000000 ? 15 : \ + 6 - __fls((_odr) / 1000000)) + +/* ODR in mHz from Reg value */ +#define ICM426XX_REG_TO_ODR(_reg) ((_reg) == 15 ? 500000 : \ + (_reg) >= 7 ? \ + (1 << (13 - (_reg))) * 3125 : \ + (1 << (6 - (_reg))) * 1000000) + +/* Reg value for the next higher ODR */ +#define ICM426XX_ODR_REG_UP(_reg) ((_reg) == 15 ? 6 : \ + (_reg) == 7 ? 15 : \ + (_reg) - 1) + +/* + * Register addresses are virtual address on 16 bits. + * MSB is coding register bank and LSB real register address. + * ex: bank 4, register 1F => 0x041F + */ +#define ICM426XX_REG_DEVICE_CONFIG 0x0011 +#define ICM426XX_SOFT_RESET_CONFIG BIT(0) + +enum icm426xx_slew_rate { + ICM426XX_SLEW_RATE_20NS_60NS, + ICM426XX_SLEW_RATE_12NS_36NS, + ICM426XX_SLEW_RATE_6NS_18NS, + ICM426XX_SLEW_RATE_4NS_12NS, + ICM426XX_SLEW_RATE_2NS_6NS, + ICM426XX_SLEW_RATE_INF_2NS, +}; +#define ICM426XX_REG_DRIVE_CONFIG 0x0013 +#define ICM426XX_DRIVE_CONFIG_MASK GENMASK(5, 0) +#define ICM426XX_I2C_SLEW_RATE(_s) (((_s) & 0x07) << 3) +#define ICM426XX_SPI_SLEW_RATE(_s) ((_s) & 0x07) + +/* default int configuration is pulsed mode, open drain, and active low */ +#define ICM426XX_REG_INT_CONFIG 0x0014 +#define ICM426XX_INT2_LATCHED BIT(5) +#define ICM426XX_INT2_PUSH_PULL BIT(4) +#define ICM426XX_INT2_ACTIVE_HIGH BIT(3) +#define ICM426XX_INT1_LATCHED BIT(2) +#define ICM426XX_INT1_PUSH_PULL BIT(1) +#define ICM426XX_INT1_ACTIVE_HIGH BIT(0) + +#define ICM426XX_REG_FIFO_CONFIG 0x0016 +#define ICM426XX_FIFO_MODE_BYPASS (0x00 << 6) +#define ICM426XX_FIFO_MODE_STREAM (0x01 << 6) +#define ICM426XX_FIFO_MODE_STOP_FULL (0x02 << 6) + +/* data are 16 bits */ +#define ICM426XX_REG_TEMP_DATA 0x001D +/* X + Y + Z: 3 * 16 bits */ +#define ICM426XX_REG_ACCEL_DATA_XYZ 0x001F +#define ICM426XX_REG_GYRO_DATA_XYZ 0x0025 + +#define ICM426XX_INVALID_DATA -32768 + +#define ICM426XX_REG_INT_STATUS 0x002D +#define ICM426XX_UI_FSYNC_INT BIT(6) +#define ICM426XX_PLL_RDY_INT BIT(5) +#define ICM426XX_RESET_DONE_INT BIT(4) +#define ICM426XX_DATA_RDY_INT BIT(3) +#define ICM426XX_FIFO_THS_INT BIT(2) +#define ICM426XX_FIFO_FULL_INT BIT(1) +#define ICM426XX_AGC_RDY_INT BIT(0) + +/* FIFO count is 16 bits */ +#define ICM426XX_REG_FIFO_COUNT 0x002E +#define ICM426XX_REG_FIFO_DATA 0x0030 + +#define ICM426XX_REG_SIGNAL_PATH_RESET 0x004B +#define ICM426XX_ABORT_AND_RESET BIT(3) +#define ICM426XX_TMST_STROBE BIT(2) +#define ICM426XX_FIFO_FLUSH BIT(1) + +#define ICM426XX_REG_INTF_CONFIG0 0x004C +#define ICM426XX_DATA_CONF_MASK GENMASK(7, 4) +#define ICM426XX_FIFO_HOLD_LAST_DATA BIT(7) +#define ICM426XX_FIFO_COUNT_REC BIT(6) +#define ICM426XX_FIFO_COUNT_BE BIT(5) +#define ICM426XX_SENSOR_DATA_BE BIT(4) +#define ICM426XX_UI_SIFS_CFG_MASK GENMASK(1, 0) +#define ICM426XX_UI_SIFS_CFG_SPI_DIS 0x02 +#define ICM426XX_UI_SIFS_CFG_I2C_DIS 0x03 + +enum icm426xx_sensor_mode { + ICM426XX_MODE_OFF, + ICM426XX_MODE_STANDBY, + ICM426XX_MODE_LOW_POWER, + ICM426XX_MODE_LOW_NOISE, +}; +#define ICM426XX_REG_PWR_MGMT0 0x004E +#define ICM426XX_TEMP_DIS BIT(5) +#define ICM426XX_IDLE BIT(4) +#define ICM426XX_GYRO_MODE_MASK GENMASK(3, 2) +#define ICM426XX_GYRO_MODE(_m) (((_m) & 0x03) << 2) +#define ICM426XX_ACCEL_MODE_MASK GENMASK(1, 0) +#define ICM426XX_ACCEL_MODE(_m) ((_m) & 0x03) + +#define ICM426XX_REG_GYRO_CONFIG0 0x004F +#define ICM426XX_REG_ACCEL_CONFIG0 0x0050 +#define ICM426XX_FS_MASK GENMASK(7, 5) +#define ICM426XX_FS_SEL(_fs) (((_fs) & 0x07) << 5) +#define ICM426XX_ODR_MASK GENMASK(3, 0) +#define ICM426XX_ODR(_odr) ((_odr) & 0x0F) + +enum icm426xx_filter_bw { + /* low noise mode */ + ICM426XX_FILTER_BW_ODR_DIV_2 = 0, + + /* low power mode */ + ICM426XX_FILTER_BW_AVG_1X = 1, + ICM426XX_FILTER_BW_AVG_16X = 6, +}; + +#define ICM426XX_REG_GYRO_ACCEL_CONFIG0 0x0052 +#define ICM426XX_ACCEL_UI_FILT_MASK GENMASK(7, 4) +#define ICM426XX_ACCEL_UI_FILT_BW(_f) (((_f) & 0x0F) << 4) +#define ICM426XX_GYRO_UI_FILT_MASK GENMASK(3, 0) +#define ICM426XX_GYRO_UI_FILT_BW(_f) ((_f) & 0x0F) + +#define ICM426XX_REG_FIFO_CONFIG1 0x005F +#define ICM426XX_FIFO_PARTIAL_READ BIT(6) +#define ICM426XX_FIFO_WM_GT_TH BIT(5) +#define ICM426XX_FIFO_EN_MASK GENMASK(3, 0) +#define ICM426XX_FIFO_TMST_FSYNC_EN BIT(3) +#define ICM426XX_FIFO_TEMP_EN BIT(2) +#define ICM426XX_FIFO_GYRO_EN BIT(1) +#define ICM426XX_FIFO_ACCEL_EN BIT(0) + +/* FIFO watermark value is 16 bits little endian */ +#define ICM426XX_REG_FIFO_WATERMARK 0x0060 + +#define ICM426XX_REG_INT_CONFIG1 0x0064 +#define ICM426XX_INT_PULSE_DURATION BIT(6) +#define ICM426XX_INT_TDEASSERT_DIS BIT(5) +#define ICM426XX_INT_ASYNC_RESET BIT(4) + +#define ICM426XX_REG_INT_SOURCE0 0x0065 +#define ICM426XX_UI_FSYNC_INT1_EN BIT(6) +#define ICM426XX_PLL_RDY_INT1_EN BIT(5) +#define ICM426XX_RESET_DONE_INT1_EN BIT(4) +#define ICM426XX_UI_DRDY_INT1_EN BIT(3) +#define ICM426XX_FIFO_THS_INT1_EN BIT(2) +#define ICM426XX_FIFO_FULL_INT1_EN BIT(1) +#define ICM426XX_UI_AGC_RDY_INT1_EN BIT(0) + +#define ICM426XX_REG_INT_SOURCE3 0x0068 +#define ICM426XX_UI_FSYNC_INT2_EN BIT(6) +#define ICM426XX_PLL_RDY_INT2_EN BIT(5) +#define ICM426XX_RESET_DONE_INT2_EN BIT(4) +#define ICM426XX_UI_DRDY_INT2_EN BIT(3) +#define ICM426XX_FIFO_THS_INT2_EN BIT(2) +#define ICM426XX_FIFO_FULL_INT2_EN BIT(1) +#define ICM426XX_UI_AGC_RDY_INT2_EN BIT(0) + +#define ICM426XX_REG_WHO_AM_I 0x0075 +#define ICM426XX_CHIP_ICM40608 0x39 +#define ICM426XX_CHIP_ICM42605 0x42 + +#define ICM426XX_REG_BANK_SEL 0x0076 +#define ICM426XX_BANK_SEL(_b) ((_b) & 0x07) + +#define ICM426XX_REG_INTF_CONFIG4 0x017A +#define ICM426XX_I3C_BUS_MODE BIT(6) +#define ICM426XX_SPI_AP_4WIRE BIT(1) + +#define ICM426XX_REG_INTF_CONFIG5 0x017B +#define ICM426XX_PIN9_FUNC_INT2 (0x00 << 1) +#define ICM426XX_PIN9_FUNC_FSYNC (0x01 << 1) + +#define ICM426XX_REG_INTF_CONFIG6 0x017C +#define ICM426XX_INTF_CONFIG6_MASK GENMASK(4, 0) +#define ICM426XX_I3C_EN BIT(4) +#define ICM426XX_I3C_IBI_BYTE_EN BIT(3) +#define ICM426XX_I3C_IBI_EN BIT(2) +#define ICM426XX_I3C_DDR_EN BIT(1) +#define ICM426XX_I3C_SDR_EN BIT(0) + +#define ICM426XX_REG_INT_SOURCE8 0x044F +#define ICM426XX_FSYNC_IBI_EN BIT(5) +#define ICM426XX_PLL_RDY_IBI_EN BIT(4) +#define ICM426XX_UI_DRDY_IBI_EN BIT(3) +#define ICM426XX_FIFO_THS_IBI_EN BIT(2) +#define ICM426XX_FIFO_FULL_IBI_EN BIT(1) +#define ICM426XX_AGC_RDY_IBI_EN BIT(0) + +#define ICM426XX_REG_OFFSET_USER0 0x0477 +#define ICM426XX_REG_OFFSET_USER1 0x0478 +#define ICM426XX_REG_OFFSET_USER2 0x0479 +#define ICM426XX_REG_OFFSET_USER3 0x047A +#define ICM426XX_REG_OFFSET_USER4 0x047B +#define ICM426XX_REG_OFFSET_USER5 0x047C +#define ICM426XX_REG_OFFSET_USER6 0x047D +#define ICM426XX_REG_OFFSET_USER7 0x047E +#define ICM426XX_REG_OFFSET_USER8 0x047F + +extern const struct accelgyro_drv icm426xx_drv; + +void icm426xx_interrupt(enum gpio_signal signal); + +#endif /* __CROS_EC_ACCELGYRO_ICM426XX_H */ diff --git a/driver/accelgyro_icm_common.c b/driver/accelgyro_icm_common.c new file mode 100644 index 0000000000..03a736e76b --- /dev/null +++ b/driver/accelgyro_icm_common.c @@ -0,0 +1,399 @@ +/* Copyright 2020 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * ICM accelerometer and gyroscope module for Chrome EC + * 3D digital accelerometer & 3D digital gyroscope + */ + +#include "accelgyro.h" +#include "console.h" +#include "i2c.h" +#include "spi.h" +#include "driver/accelgyro_icm_common.h" +#include "driver/accelgyro_icm426xx.h" + +#define CPUTS(outstr) cputs(CC_ACCEL, outstr) +#define CPRINTF(format, args...) cprintf(CC_ACCEL, format, ## args) +#define CPRINTS(format, args...) cprints(CC_ACCEL, format, ## args) + +#ifdef CONFIG_SPI_ACCEL_PORT +static int icm_spi_raw_read(const int addr, const uint8_t reg, + uint8_t *data, const int len) +{ + uint8_t cmd = 0x80 | reg; + + return spi_transaction(&spi_devices[addr], &cmd, 1, data, len); +} + +static int icm_spi_raw_write(const int addr, const uint8_t reg, + const uint8_t *data, const int len) +{ + uint8_t cmd[3]; + int i; + + if (len > 2) + return EC_ERROR_UNIMPLEMENTED; + + cmd[0] = reg; + for (i = 0; i < len; ++i) + cmd[i + 1] = data[i]; + + return spi_transaction(&spi_devices[addr], cmd, len + 1, NULL, 0); +} +#endif + +static int icm_bank_sel(const struct motion_sensor_t *s, const int reg) +{ + struct icm_drv_data_t *st = ICM_GET_DATA(s); + uint8_t bank = ICM426XX_REG_GET_BANK(reg); + int ret; + + if (bank == st->bank) + return EC_SUCCESS; + + ret = EC_ERROR_UNIMPLEMENTED; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + ret = icm_spi_raw_write( + SLAVE_GET_SPI_ADDR(s->i2c_spi_addr_flags), + ICM426XX_REG_BANK_SEL, &bank, 1); +#endif + } else { +#ifdef I2C_PORT_ACCEL + ret = i2c_write8(s->port, s->i2c_spi_addr_flags, + ICM426XX_REG_BANK_SEL, bank); +#endif + } + + if (ret == EC_SUCCESS) + st->bank = bank; + + return ret; +} + +/** + * Read 8 bits register + */ +int icm_read8(const struct motion_sensor_t *s, const int reg, int *data_ptr) +{ + const uint8_t addr = ICM426XX_REG_GET_ADDR(reg); + int ret; + + ret = icm_bank_sel(s, reg); + if (ret != EC_SUCCESS) + return ret; + + ret = EC_ERROR_UNIMPLEMENTED; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + uint8_t val; + + ret = icm_spi_raw_read( + SLAVE_GET_SPI_ADDR(s->i2c_spi_addr_flags), + addr, &val, sizeof(val)); + if (ret == EC_SUCCESS) + *data_ptr = val; +#endif + } else { +#ifdef I2C_PORT_ACCEL + ret = i2c_read8(s->port, s->i2c_spi_addr_flags, addr, data_ptr); +#endif + } + + return ret; +} + +/** + * Write 8 bits register + */ +int icm_write8(const struct motion_sensor_t *s, const int reg, int data) +{ + const uint8_t addr = ICM426XX_REG_GET_ADDR(reg); + int ret; + + ret = icm_bank_sel(s, reg); + if (ret != EC_SUCCESS) + return ret; + + ret = EC_ERROR_UNIMPLEMENTED; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + uint8_t val = data; + + ret = icm_spi_raw_write( + LAVE_GET_SPI_ADDR(s->i2c_spi_addr_flags), + addr, &val, sizeof(val)); +#endif + } else { +#ifdef I2C_PORT_ACCEL + ret = i2c_write8(s->port, s->i2c_spi_addr_flags, addr, data); +#endif + } + + return ret; +} + +/** + * Read 16 bits register + */ +int icm_read16(const struct motion_sensor_t *s, const int reg, int *data_ptr) +{ + const uint8_t addr = ICM426XX_REG_GET_ADDR(reg); + int ret; + + ret = icm_bank_sel(s, reg); + if (ret != EC_SUCCESS) + return ret; + + ret = EC_ERROR_UNIMPLEMENTED; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + uint8_t val[2]; + + ret = icm_spi_raw_read( + LAVE_GET_SPI_ADDR(s->i2c_spi_addr_flags), + addr, val, sizeof(val)); + if (ret == EC_SUCCESS) { + if (I2C_IS_BIG_ENDIAN(s->i2c_spi_addr_flags)) + *data_ptr = ((int)val[0] << 8) | val[1]; + else + *data_ptr = ((int)val[1] << 8) | val[0]; + } +#endif + } else { +#ifdef I2C_PORT_ACCEL + ret = i2c_read16(s->port, s->i2c_spi_addr_flags, + addr, data_ptr); +#endif + } + + return ret; +} + +/** + * Write 16 bits register + */ +int icm_write16(const struct motion_sensor_t *s, const int reg, int data) +{ + const uint8_t addr = ICM426XX_REG_GET_ADDR(reg); + int ret; + + ret = icm_bank_sel(s, reg); + if (ret != EC_SUCCESS) + return ret; + + ret = EC_ERROR_UNIMPLEMENTED; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + uint8_t val[2]; + + if (I2C_IS_BIG_ENDIAN(s->i2c_spi_addr_flags)) { + val[0] = (data >> 8) & 0xFF; + val[1] = data & 0xFF; + } else { + val[0] = data & 0xFF; + val[1] = (data >> 8) & 0xFF; + } + ret = icm_spi_raw_write( + LAVE_GET_SPI_ADDR(s->i2c_spi_addr_flags), + addr, val, sizeof(val)); +#endif + } else { +#ifdef I2C_PORT_ACCEL + ret = i2c_write16(s->port, s->i2c_spi_addr_flags, addr, data); +#endif + } + + return ret; +} + +/** + * Read n bytes + */ +int icm_read_n(const struct motion_sensor_t *s, const int reg, + uint8_t *data_ptr, const int len) +{ + const uint8_t addr = ICM426XX_REG_GET_ADDR(reg); + int ret; + + ret = icm_bank_sel(s, reg); + if (ret != EC_SUCCESS) + return ret; + + ret = EC_ERROR_UNIMPLEMENTED; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + ret = icm_spi_raw_read( + SLAVE_GET_SPI_ADDR(s->i2c_spi_addr_flags), + addr, data_ptr, len); +#endif + } else { +#ifdef I2C_PORT_ACCEL + ret = i2c_read_block(s->port, s->i2c_spi_addr_flags, addr, + data_ptr, len); +#endif + } + + return ret; +} + +int icm_field_update8(const struct motion_sensor_t *s, const int reg, + const uint8_t field_mask, const uint8_t set_value) +{ + const uint8_t addr = ICM426XX_REG_GET_ADDR(reg); + int ret; + + ret = icm_bank_sel(s, reg); + if (ret != EC_SUCCESS) + return ret; + + ret = EC_ERROR_UNIMPLEMENTED; + if (SLAVE_IS_SPI(s->i2c_spi_addr_flags)) { +#ifdef CONFIG_SPI_ACCEL_PORT + uint8_t val; + + ret = icm_spi_raw_read( + SLAVE_GET_SPI_ADDR(s->i2c_spi_addr_flags), + addr, &val, sizeof(val)); + if (ret != EC_SUCCESS) + return ret; + + val = (val & (~field_mask)) | set_value; + + ret = icm_spi_raw_write( + SLAVE_GET_SPI_ADDR(s->i2c_spi_addr_flags), + addr, &val, sizeof(val)); +#endif + } else { +#ifdef I2C_PORT_ACCEL + ret = i2c_field_update8(s->port, s->i2c_spi_addr_flags, addr, + field_mask, set_value); +#endif + } + + return ret; +} + +int icm_get_resolution(const struct motion_sensor_t *s) +{ + return ICM_RESOLUTION; +} + +int icm_get_range(const struct motion_sensor_t *s) +{ + struct accelgyro_saved_data_t *data = ICM_GET_SAVED_DATA(s); + + return data->range; +} + +int icm_get_data_rate(const struct motion_sensor_t *s) +{ + struct accelgyro_saved_data_t *data = ICM_GET_SAVED_DATA(s); + + return data->odr; +} + +int icm_set_scale(const struct motion_sensor_t *s, const uint16_t *scale, + int16_t temp) +{ + struct accelgyro_saved_data_t *data = ICM_GET_SAVED_DATA(s); + + data->scale[X] = scale[X]; + data->scale[Y] = scale[Y]; + data->scale[Z] = scale[Z]; + return EC_SUCCESS; +} + +int icm_get_scale(const struct motion_sensor_t *s, uint16_t *scale, + int16_t *temp) +{ + struct accelgyro_saved_data_t *data = ICM_GET_SAVED_DATA(s); + + scale[X] = data->scale[X]; + scale[Y] = data->scale[Y]; + scale[Z] = data->scale[Z]; + *temp = EC_MOTION_SENSE_INVALID_CALIB_TEMP; + return EC_SUCCESS; +} + +/* FIFO header: 1 byte */ +#define ICM_FIFO_HEADER_MSG BIT(7) +#define ICM_FIFO_HEADER_ACCEL BIT(6) +#define ICM_FIFO_HEADER_GYRO BIT(5) +#define ICM_FIFO_HEADER_TMST_FSYNC GENMASK(3, 2) +#define ICM_FIFO_HEADER_ODR_ACCEL BIT(1) +#define ICM_FIFO_HEADER_ODR_GYRO BIT(0) + +/* FIFO data packet */ +struct icm_fifo_sensor_data { + int16_t x; + int16_t y; + int16_t z; +} __packed; + +struct icm_fifo_1sensor_packet { + uint8_t header; + struct icm_fifo_sensor_data data; + int8_t temp; +} __packed; +#define ICM_FIFO_1SENSOR_PACKET_SIZE 8 + +struct icm_fifo_2sensors_packet { + uint8_t header; + struct icm_fifo_sensor_data accel; + struct icm_fifo_sensor_data gyro; + int8_t temp; + uint16_t timestamp; +} __packed; +#define ICM_FIFO_2SENSORS_PACKET_SIZE 16 + +ssize_t icm_fifo_decode_packet(const void *packet, const uint8_t **accel, + const uint8_t **gyro) +{ + const struct icm_fifo_1sensor_packet *pack1 = packet; + const struct icm_fifo_2sensors_packet *pack2 = packet; + uint8_t header = *((const uint8_t *)packet); + + /* FIFO empty */ + if (header & ICM_FIFO_HEADER_MSG) { + if (accel != NULL) + *accel = NULL; + if (gyro != NULL) + *gyro = NULL; + return 0; + } + + /* accel + gyro */ + if ((header & ICM_FIFO_HEADER_ACCEL) && + (header & ICM_FIFO_HEADER_GYRO)) { + if (accel != NULL) + *accel = (uint8_t *)&pack2->accel; + if (gyro != NULL) + *gyro = (uint8_t *)&pack2->gyro; + return ICM_FIFO_2SENSORS_PACKET_SIZE; + } + + /* accel only */ + if (header & ICM_FIFO_HEADER_ACCEL) { + if (accel != NULL) + *accel = (uint8_t *)&pack1->data; + if (gyro != NULL) + *gyro = NULL; + return ICM_FIFO_1SENSOR_PACKET_SIZE; + } + + /* gyro only */ + if (header & ICM_FIFO_HEADER_GYRO) { + if (accel != NULL) + *accel = NULL; + if (gyro != NULL) + *gyro = (uint8_t *)&pack1->data; + return ICM_FIFO_1SENSOR_PACKET_SIZE; + } + + /* invalid packet if here */ + return -EC_ERROR_INVAL; +} diff --git a/driver/accelgyro_icm_common.h b/driver/accelgyro_icm_common.h new file mode 100644 index 0000000000..4d991ce341 --- /dev/null +++ b/driver/accelgyro_icm_common.h @@ -0,0 +1,101 @@ +/* Copyright 2020 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* ICM accelerometer and gyroscope common definitions for Chrome EC */ + +#ifndef __CROS_EC_ACCELGYRO_ICM_COMMON_H +#define __CROS_EC_ACCELGYRO_ICM_COMMON_H + +#include "accelgyro.h" + +#ifdef CONFIG_ACCEL_FIFO +/* reserve maximum 4 samples of 16 bytes */ +#define ICM_FIFO_BUFFER 64 +#else +#define ICM_FIFO_BUFFER 0 +#endif + +struct icm_drv_data_t { + struct accelgyro_saved_data_t saved_data[2]; + struct motion_sensor_t *accel; + struct motion_sensor_t *gyro; + uint8_t bank; + uint8_t fifo_en; + uint8_t fifo_buffer[ICM_FIFO_BUFFER] __aligned(sizeof(long)); +}; + +#define ICM_GET_DATA(_s) \ + ((struct icm_drv_data_t *)(_s)->drv_data) +#define ICM_GET_SAVED_DATA(_s) \ + (&ICM_GET_DATA(_s)->saved_data[(_s)->type]) + +/* + * Virtual register address is 16 bits: + * - 8 bits MSB coding bank number + * - 8 bits LSB coding physical address + */ +#define ICM426XX_REG_GET_BANK(_r) (((_r) & 0xFF00) >> 8) +#define ICM426XX_REG_GET_ADDR(_r) ((_r) & 0x00FF) + +/* Sensor resolution in number of bits */ +#define ICM_RESOLUTION 16 + +/** + * sign_extend - sign extend a standard int value using the given sign-bit + * @value: value to sign extend + * @index: 0 based bit index to sign bit + */ +static inline int sign_extend(int value, int index) +{ + int shift = (sizeof(int) * 8) - index - 1; + + return (int)(value << shift) >> shift; +} + +/** + * Read 8 bits register + */ +int icm_read8(const struct motion_sensor_t *s, const int reg, int *data_ptr); + +/** + * Write 8 bits register + */ +int icm_write8(const struct motion_sensor_t *s, const int reg, int data); + +/** + * Read 16 bits register + */ +int icm_read16(const struct motion_sensor_t *s, const int reg, int *data_ptr); + +/** + * Write 16 bits register + */ +int icm_write16(const struct motion_sensor_t *s, const int reg, int data); + +/** + * Read n bytes + */ +int icm_read_n(const struct motion_sensor_t *s, const int reg, + uint8_t *data_ptr, const int len); + +int icm_field_update8(const struct motion_sensor_t *s, const int reg, + const uint8_t field_mask, const uint8_t set_value); + +int icm_get_resolution(const struct motion_sensor_t *s); + +int icm_get_range(const struct motion_sensor_t *s); + +int icm_get_data_rate(const struct motion_sensor_t *s); + +int icm_set_scale(const struct motion_sensor_t *s, const uint16_t *scale, + int16_t temp); + +int icm_get_scale(const struct motion_sensor_t *s, uint16_t *scale, + int16_t *temp); + +ssize_t icm_fifo_decode_packet(const void *packet, const uint8_t **accel, + const uint8_t **gyro); + +#endif /* __CROS_EC_ACCELGYRO_ICM_COMMON_H */ diff --git a/driver/build.mk b/driver/build.mk index e443ccb7ea..d970bd142b 100644 --- a/driver/build.mk +++ b/driver/build.mk @@ -25,6 +25,7 @@ driver-$(CONFIG_SENSORHUB_LSM6DSM)+=sensorhub_lsm6dsm.o driver-$(CONFIG_SYNC)+=sync.o driver-$(CONFIG_ACCEL_LIS2DW_COMMON)+=accel_lis2dw12.o stm_mems_common.o driver-$(CONFIG_ACCEL_LIS2DS)+=accel_lis2ds.o stm_mems_common.o +driver-$(CONFIG_ACCELGYRO_ICM426XX)+=accelgyro_icm426xx.o accelgyro_icm_common.o # BC1.2 Charger Detection Devices driver-$(CONFIG_BC12_DETECT_MAX14637)+=bc12/max14637.o diff --git a/include/config.h b/include/config.h index 6e542a7a57..bcd6671516 100644 --- a/include/config.h +++ b/include/config.h @@ -109,6 +109,7 @@ #undef CONFIG_ACCELGYRO_BMI160 #undef CONFIG_ACCELGYRO_BMI260 +#undef CONFIG_ACCELGYRO_ICM426XX #undef CONFIG_ACCELGYRO_LSM6DS0 /* Use CONFIG_ACCELGYRO_LSM6DSM for LSM6DSL, LSM6DSM, and/or LSM6DS3 */ #undef CONFIG_ACCELGYRO_LSM6DSM @@ -313,6 +314,7 @@ */ #undef CONFIG_ACCELGYRO_BMI160_INT_EVENT #undef CONFIG_ACCELGYRO_BMI260_INT_EVENT +#undef CONFIG_ACCELGYRO_ICM426XX_INT_EVENT #undef CONFIG_ACCEL_LSM6DSM_INT_EVENT #undef CONFIG_ACCEL_LSM6DSO_INT_EVENT #undef CONFIG_ACCEL_LIS2DS_INT_EVENT |