drivers: sensors: bmi08x: add initial support for bmi08x

This adds support for the bosch bmi085 and bmi088. This also includes
support for data sync mode.

Signed-off-by: Ryan McClelland <ryanmcclelland@meta.com>
This commit is contained in:
Ryan McClelland 2022-11-10 23:04:47 -08:00 committed by Maureen Helm
commit f1a992c87a
22 changed files with 3029 additions and 0 deletions

View file

@ -18,6 +18,7 @@ add_subdirectory_ifdef(CONFIG_BMC150_MAGN bmc150_magn)
add_subdirectory_ifdef(CONFIG_BME280 bme280)
add_subdirectory_ifdef(CONFIG_BME680 bme680)
add_subdirectory_ifdef(CONFIG_BMG160 bmg160)
add_subdirectory_ifdef(CONFIG_BMI08X bmi08x)
add_subdirectory_ifdef(CONFIG_BMI160 bmi160)
add_subdirectory_ifdef(CONFIG_BMI270 bmi270)
add_subdirectory_ifdef(CONFIG_BMI323 bmi323)

View file

@ -74,6 +74,7 @@ source "drivers/sensor/bmc150_magn/Kconfig"
source "drivers/sensor/bme280/Kconfig"
source "drivers/sensor/bme680/Kconfig"
source "drivers/sensor/bmg160/Kconfig"
source "drivers/sensor/bmi08x/Kconfig"
source "drivers/sensor/bmi160/Kconfig"
source "drivers/sensor/bmi270/Kconfig"
source "drivers/sensor/bmi323/Kconfig"

View file

@ -0,0 +1,9 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_BMI08X bmi08x_accel.c)
zephyr_library_sources_ifdef(CONFIG_BMI08X bmi08x_gyro.c)
zephyr_library_sources_ifdef(CONFIG_BMI08X bmi08x.c)
zephyr_library_sources_ifdef(CONFIG_BMI08X_ACCEL_TRIGGER bmi08x_accel_trigger.c)
zephyr_library_sources_ifdef(CONFIG_BMI08X_GYRO_TRIGGER bmi08x_gyro_trigger.c)

View file

@ -0,0 +1,95 @@
# Bosch BMI08X inertial measurement configuration options
# Copyright (c) 2022 Meta Platforms, Inc. and its affiliates
# SPDX-License-Identifier: Apache-2.0
menuconfig BMI08X
bool "Bosch BMI08X inertial measurement unit"
default y
depends on DT_HAS_BOSCH_BMI08X_ACCEL_ENABLED || DT_HAS_BOSCH_BMI08X_GYRO_ENABLED
select I2C if $(dt_compat_on_bus,$(DT_COMPAT_BOSCH_BMI08X_ACCEL),i2c) \
|| $(dt_compat_on_bus,$(DT_COMPAT_BOSCH_BMI08X_GYRO),i2c)
select SPI if $(dt_compat_on_bus,$(DT_COMPAT_BOSCH_BMI08X_ACCEL),spi) \
|| $(dt_compat_on_bus,$(DT_COMPAT_BOSCH_BMI08X_GYRO),spi)
help
Enable Bosch BMI08X inertial measurement unit that provides acceleration
and angular rate measurements.
if BMI08X
choice BMI08X_ACCEL_TRIGGER_MODE
prompt "Accelerometer trigger mode"
default BMI08X_ACCEL_TRIGGER_GLOBAL_THREAD
help
Specify the type of triggering to be used by the driver.
config BMI08X_ACCEL_TRIGGER_NONE
bool "No trigger"
config BMI08X_ACCEL_TRIGGER_GLOBAL_THREAD
bool "Use global thread"
select BMI08X_ACCEL_TRIGGER
config BMI08X_ACCEL_TRIGGER_OWN_THREAD
bool "Use own thread"
select BMI08X_ACCEL_TRIGGER
endchoice
config BMI08X_ACCEL_TRIGGER
bool
config BMI08X_ACCEL_THREAD_PRIORITY
int "Accelerometer own thread priority"
depends on BMI08X_ACCEL_TRIGGER_OWN_THREAD
default 10
help
The priority of the thread used for handling interrupts.
config BMI08X_ACCEL_THREAD_STACK_SIZE
int "Accelerometer own thread stack size"
depends on BMI08X_ACCEL_TRIGGER_OWN_THREAD
default 1536
help
The thread stack size.
choice BMI08X_GYRO_TRIGGER_MODE
prompt "Gyroscope trigger mode"
default BMI08X_GYRO_TRIGGER_NONE
default BMI08X_GYRO_TRIGGER_GLOBAL_THREAD
help
Specify the type of triggering to be used by the driver.
config BMI08X_GYRO_TRIGGER_NONE
bool "No trigger"
config BMI08X_GYRO_TRIGGER_GLOBAL_THREAD
bool "Use global thread"
select BMI08X_GYRO_TRIGGER
config BMI08X_GYRO_TRIGGER_OWN_THREAD
bool "Use own thread"
select BMI08X_GYRO_TRIGGER
endchoice
config BMI08X_GYRO_TRIGGER
bool
config BMI08X_GYRO_THREAD_PRIORITY
int "Own thread priority"
depends on BMI08X_GYRO_TRIGGER_OWN_THREAD
default 10
help
The priority of the thread used for handling interrupts.
config BMI08X_GYRO_THREAD_STACK_SIZE
int "Own thread stack size"
depends on BMI08X_GYRO_TRIGGER_OWN_THREAD
default 1536
help
The thread stack size.
config BMI08X_I2C_WRITE_BURST_SIZE
int "Maximum length of single i2c write"
default 16
endif # BMI08X

View file

@ -0,0 +1,74 @@
/* Bosch BMI08X inertial measurement unit driver
*
* Copyright (c) 2022 Meta Platforms, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include "bmi08x.h"
/*
* Output data rate map with allowed frequencies:
* freq = freq_int + freq_milli / 1000
*
* Since we don't need a finer frequency resolution than milliHz, use uint16_t
* to save some flash.
*/
static const struct {
uint16_t freq_int;
uint16_t freq_milli; /* User should convert to uHz before setting the
* SENSOR_ATTR_SAMPLING_FREQUENCY attribute.
*/
} bmi08x_odr_map[] = {
{0, 0}, {0, 780}, {1, 562}, {3, 120}, {6, 250}, {12, 500}, {25, 0},
{50, 0}, {100, 0}, {200, 0}, {400, 0}, {800, 0}, {1600, 0}, {3200, 0},
};
int bmi08x_freq_to_odr_val(uint16_t freq_int, uint16_t freq_milli)
{
size_t i;
/* An ODR of 0 Hz is not allowed */
if (freq_int == 0U && freq_milli == 0U) {
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(bmi08x_odr_map); i++) {
if (freq_int < bmi08x_odr_map[i].freq_int ||
(freq_int == bmi08x_odr_map[i].freq_int &&
freq_milli <= bmi08x_odr_map[i].freq_milli)) {
return i;
}
}
return -EINVAL;
}
int32_t bmi08x_range_to_reg_val(uint16_t range, const struct bmi08x_range *range_map,
uint16_t range_map_size)
{
int i;
for (i = 0; i < range_map_size; i++) {
if (range <= range_map[i].range) {
return range_map[i].reg_val;
}
}
return -EINVAL;
}
int32_t bmi08x_reg_val_to_range(uint8_t reg_val, const struct bmi08x_range *range_map,
uint16_t range_map_size)
{
int i;
for (i = 0; i < range_map_size; i++) {
if (reg_val == range_map[i].reg_val) {
return range_map[i].range;
}
}
return -EINVAL;
}

View file

@ -0,0 +1,593 @@
/* Bosch BMI08X inertial measurement unit header
*
* Copyright (c) 2022 Meta Platforms, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_BMI08X_H_
#define ZEPHYR_DRIVERS_SENSOR_BMI08X_H_
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/sys/util.h>
/* Accel Chip Id register */
#define BMI08X_REG_ACCEL_CHIP_ID 0x00
/* Accel Error condition register */
#define BMI08X_REG_ACCEL_ERR 0x02
/* Accel Status flag register */
#define BMI08X_REG_ACCEL_STATUS 0x03
/* Accel X LSB data register */
#define BMI08X_REG_ACCEL_X_LSB 0x12
/* Accel X MSB data register */
#define BMI08X_REG_ACCEL_X_MSB 0x13
/* Accel Y LSB data register */
#define BMI08X_REG_ACCEL_Y_LSB 0x14
/* Accel Y MSB data register */
#define BMI08X_REG_ACCEL_Y_MSB 0x15
/* Accel Z LSB data register */
#define BMI08X_REG_ACCEL_Z_LSB 0x16
/* Accel Z MSB data register */
#define BMI08X_REG_ACCEL_Z_MSB 0x17
/* Sensor time byte 0 register */
#define BMI08X_REG_ACCEL_SENSORTIME_0 0x18
/* Sensor time byte 1 register */
#define BMI08X_REG_ACCEL_SENSORTIME_1 0x19
/* Sensor time byte 2 register */
#define BMI08X_REG_ACCEL_SENSORTIME_2 0x1A
/* Accel Interrupt status0 register */
#define BMI08X_REG_ACCEL_INT_STAT_0 0x1C
/* Accel Interrupt status1 register */
#define BMI08X_REG_ACCEL_INT_STAT_1 0x1D
/* Accel general purpose register 0*/
#define BMI08X_REG_ACCEL_GP_0 0x1E
/* Sensor temperature MSB data register */
#define BMI08X_REG_TEMP_MSB 0x22
/* Sensor temperature LSB data register */
#define BMI08X_REG_TEMP_LSB 0x23
/* Accel general purpose register 4*/
#define BMI08X_REG_ACCEL_GP_4 0x27
/* Accel Internal status register */
#define BMI08X_REG_ACCEL_INTERNAL_STAT 0x2A
/* Accel configuration register */
#define BMI08X_REG_ACCEL_CONF 0x40
/* Accel range setting register */
#define BMI08X_REG_ACCEL_RANGE 0x41
/* Accel Interrupt pin 1 configuration register */
#define BMI08X_REG_ACCEL_INT1_IO_CONF 0x53
/* Accel Interrupt pin 2 configuration register */
#define BMI08X_REG_ACCEL_INT2_IO_CONF 0x54
/* Accel Interrupt latch configuration register */
#define BMI08X_REG_ACCEL_INT_LATCH_CONF 0x55
/* Accel Interrupt pin1 mapping register */
#define BMI08X_REG_ACCEL_INT1_MAP 0x56
/* Accel Interrupt pin2 mapping register */
#define BMI08X_REG_ACCEL_INT2_MAP 0x57
/* Accel Interrupt map register */
#define BMI08X_REG_ACCEL_INT1_INT2_MAP_DATA 0x58
/* Accel Init control register */
#define BMI08X_REG_ACCEL_INIT_CTRL 0x59
/* Accel Self test register */
#define BMI08X_REG_ACCEL_SELF_TEST 0x6D
/* Accel Power mode configuration register */
#define BMI08X_REG_ACCEL_PWR_CONF 0x7C
/* Accel Power control (switch on or off register */
#define BMI08X_REG_ACCEL_PWR_CTRL 0x7D
/* Accel Soft reset register */
#define BMI08X_REG_ACCEL_SOFTRESET 0x7E
/* BMI085 Accel unique chip identifier */
#define BMI085_ACCEL_CHIP_ID 0x1F
/* BMI088 Accel unique chip identifier */
#define BMI088_ACCEL_CHIP_ID 0x1E
/* Feature Config related Registers */
#define BMI08X_ACCEL_RESERVED_5B_REG 0x5B
#define BMI08X_ACCEL_RESERVED_5C_REG 0x5C
#define BMI08X_ACCEL_FEATURE_CFG_REG 0x5E
/* Interrupt masks */
#define BMI08X_ACCEL_DATA_READY_INT 0x80
/* Accel Bandwidth */
#define BMI08X_ACCEL_BW_OSR4 0x00
#define BMI08X_ACCEL_BW_OSR2 0x01
#define BMI08X_ACCEL_BW_NORMAL 0x02
/* BMI085 Accel Range */
#define BMI085_ACCEL_RANGE_2G 0x00
#define BMI085_ACCEL_RANGE_4G 0x01
#define BMI085_ACCEL_RANGE_8G 0x02
#define BMI085_ACCEL_RANGE_16G 0x03
/**\name BMI088 Accel Range */
#define BMI088_ACCEL_RANGE_3G 0x00
#define BMI088_ACCEL_RANGE_6G 0x01
#define BMI088_ACCEL_RANGE_12G 0x02
#define BMI088_ACCEL_RANGE_24G 0x03
/* Accel Output data rate */
#define BMI08X_ACCEL_ODR_12_5_HZ 0x05
#define BMI08X_ACCEL_ODR_25_HZ 0x06
#define BMI08X_ACCEL_ODR_50_HZ 0x07
#define BMI08X_ACCEL_ODR_100_HZ 0x08
#define BMI08X_ACCEL_ODR_200_HZ 0x09
#define BMI08X_ACCEL_ODR_400_HZ 0x0A
#define BMI08X_ACCEL_ODR_800_HZ 0x0B
#define BMI08X_ACCEL_ODR_1600_HZ 0x0C
/* Accel Init Ctrl */
#define BMI08X_ACCEL_INIT_CTRL_DISABLE 0x00
#define BMI08X_ACCEL_INIT_CTRL_ENABLE 0x01
/* Accel Self test */
#define BMI08X_ACCEL_SWITCH_OFF_SELF_TEST 0x00
#define BMI08X_ACCEL_POSITIVE_SELF_TEST 0x0D
#define BMI08X_ACCEL_NEGATIVE_SELF_TEST 0x09
/* Accel Power mode */
#define BMI08X_ACCEL_PM_ACTIVE 0x00
#define BMI08X_ACCEL_PM_SUSPEND 0x03
/* Accel Power control settings */
#define BMI08X_ACCEL_POWER_DISABLE 0x00
#define BMI08X_ACCEL_POWER_ENABLE 0x04
/* Accel internal interrupt pin mapping */
#define BMI08X_ACCEL_INTA_DISABLE 0x00
#define BMI08X_ACCEL_INTA_ENABLE 0x01
#define BMI08X_ACCEL_INTB_DISABLE 0x00
#define BMI08X_ACCEL_INTB_ENABLE 0x02
#define BMI08X_ACCEL_INTC_DISABLE 0x00
#define BMI08X_ACCEL_INTC_ENABLE 0x04
/* Accel Soft reset delay */
#define BMI08X_ACCEL_SOFTRESET_DELAY_MS 1
/* Mask definitions for ACCEL_ERR_REG register */
#define BMI08X_FATAL_ERR_MASK 0x01
#define BMI08X_ERR_CODE_MASK 0x1C
/* Position definitions for ACCEL_ERR_REG register */
#define BMI08X_CMD_ERR_POS 1
#define BMI08X_ERR_CODE_POS 2
/* Mask definition for ACCEL_STATUS_REG register */
#define BMI08X_ACCEL_STATUS_MASK 0x80
/* Position definitions for ACCEL_STATUS_REG */
#define BMI08X_ACCEL_STATUS_POS 7
/* Mask definitions for odr, bandwidth and range */
#define BMI08X_ACCEL_ODR_MASK 0x0F
#define BMI08X_ACCEL_BW_MASK 0x70
#define BMI08X_ACCEL_RANGE_MASK 0x03
/* Position definitions for odr, bandwidth and range */
#define BMI08X_ACCEL_BW_POS 4
/* Mask definitions for INT1_IO_CONF register */
#define BMI08X_ACCEL_INT_EDGE_MASK 0x01
#define BMI08X_ACCEL_INT_LVL_MASK 0x02
#define BMI08X_ACCEL_INT_OD_MASK 0x04
#define BMI08X_ACCEL_INT_IO_MASK 0x08
#define BMI08X_ACCEL_INT_IN_MASK 0x10
/* Position definitions for INT1_IO_CONF register */
#define BMI08X_ACCEL_INT_EDGE_POS 0
#define BMI08X_ACCEL_INT_LVL_POS 1
#define BMI08X_ACCEL_INT_OD_POS 2
#define BMI08X_ACCEL_INT_IO_POS 3
#define BMI08X_ACCEL_INT_IN_POS 4
/* Mask definitions for INT1/INT2 mapping register */
#define BMI08X_ACCEL_MAP_INTA_MASK 0x01
/* Mask definitions for INT1/INT2 mapping register */
#define BMI08X_ACCEL_MAP_INTA_POS 0x00
/* Mask definitions for INT1_INT2_MAP_DATA register */
#define BMI08X_ACCEL_INT1_DRDY_MASK 0x04
#define BMI08X_ACCEL_INT2_DRDY_MASK 0x40
/* Position definitions for INT1_INT2_MAP_DATA register */
#define BMI08X_ACCEL_INT1_DRDY_POS 2
#define BMI08X_ACCEL_INT2_DRDY_POS 6
/* Asic Initialization value */
#define BMI08X_ASIC_INITIALIZED 0x01
#define BMI08X_TEMP_OFFSET 32
/*************************** BMI08 Gyroscope Macros *****************************/
/** Register map */
/* Gyro registers */
/* Gyro Chip Id register */
#define BMI08X_REG_GYRO_CHIP_ID 0x00
/* Gyro X LSB data register */
#define BMI08X_REG_GYRO_X_LSB 0x02
/* Gyro X MSB data register */
#define BMI08X_REG_GYRO_X_MSB 0x03
/* Gyro Y LSB data register */
#define BMI08X_REG_GYRO_Y_LSB 0x04
/* Gyro Y MSB data register */
#define BMI08X_REG_GYRO_Y_MSB 0x05
/* Gyro Z LSB data register */
#define BMI08X_REG_GYRO_Z_LSB 0x06
/* Gyro Z MSB data register */
#define BMI08X_REG_GYRO_Z_MSB 0x07
/* Gyro Interrupt status register */
#define BMI08X_REG_GYRO_INT_STAT_1 0x0A
/* Gyro Range register */
#define BMI08X_REG_GYRO_RANGE 0x0F
/* Gyro Bandwidth register */
#define BMI08X_REG_GYRO_BANDWIDTH 0x10
/* Gyro Power register */
#define BMI08X_REG_GYRO_LPM1 0x11
/* Gyro Soft reset register */
#define BMI08X_REG_GYRO_SOFTRESET 0x14
/* Gyro Interrupt control register */
#define BMI08X_REG_GYRO_INT_CTRL 0x15
/* Gyro Interrupt Pin configuration register */
#define BMI08X_REG_GYRO_INT3_INT4_IO_CONF 0x16
/* Gyro Interrupt Map register */
#define BMI08X_REG_GYRO_INT3_INT4_IO_MAP 0x18
/* Gyro Self test register */
#define BMI08X_REG_GYRO_SELF_TEST 0x3C
/* Gyro unique chip identifier */
#define BMI08X_GYRO_CHIP_ID 0x0F
/* Gyro Range */
#define BMI08X_GYRO_RANGE_2000_DPS 0x00
#define BMI08X_GYRO_RANGE_1000_DPS 0x01
#define BMI08X_GYRO_RANGE_500_DPS 0x02
#define BMI08X_GYRO_RANGE_250_DPS 0x03
#define BMI08X_GYRO_RANGE_125_DPS 0x04
/* Gyro Output data rate and bandwidth */
#define BMI08X_GYRO_BW_532_ODR_2000_HZ 0x00
#define BMI08X_GYRO_BW_230_ODR_2000_HZ 0x01
#define BMI08X_GYRO_BW_116_ODR_1000_HZ 0x02
#define BMI08X_GYRO_BW_47_ODR_400_HZ 0x03
#define BMI08X_GYRO_BW_23_ODR_200_HZ 0x04
#define BMI08X_GYRO_BW_12_ODR_100_HZ 0x05
#define BMI08X_GYRO_BW_64_ODR_200_HZ 0x06
#define BMI08X_GYRO_BW_32_ODR_100_HZ 0x07
#define BMI08X_GYRO_ODR_RESET_VAL 0x80
/* Gyro Power mode */
#define BMI08X_GYRO_PM_NORMAL 0x00
#define BMI08X_GYRO_PM_DEEP_SUSPEND 0x20
#define BMI08X_GYRO_PM_SUSPEND 0x80
/* Gyro data ready interrupt enable value */
#define BMI08X_GYRO_DRDY_INT_DISABLE_VAL 0x00
#define BMI08X_GYRO_DRDY_INT_ENABLE_VAL 0x80
/* Gyro data ready map values */
#define BMI08X_GYRO_MAP_DRDY_TO_INT3 0x01
#define BMI08X_GYRO_MAP_DRDY_TO_INT4 0x80
#define BMI08X_GYRO_MAP_DRDY_TO_BOTH_INT3_INT4 0x81
/* Gyro Soft reset delay */
#define BMI08X_GYRO_SOFTRESET_DELAY 30
/* Gyro power mode config delay */
#define BMI08X_GYRO_POWER_MODE_CONFIG_DELAY 30
/** Mask definitions for range, bandwidth and power */
#define BMI08X_GYRO_RANGE_MASK 0x07
#define BMI08X_GYRO_BW_MASK 0x0F
#define BMI08X_GYRO_POWER_MASK 0xA0
/** Position definitions for range, bandwidth and power */
#define BMI08X_GYRO_POWER_POS 5
/* Mask definitions for BMI08X_GYRO_INT_CTRL_REG register */
#define BMI08X_GYRO_DATA_EN_MASK 0x80
/* Position definitions for BMI08X_GYRO_INT_CTRL_REG register */
#define BMI08X_GYRO_DATA_EN_POS 7
/* Mask definitions for BMI08X_GYRO_INT3_INT4_IO_CONF_REG register */
#define BMI08X_GYRO_INT3_LVL_MASK 0x01
#define BMI08X_GYRO_INT3_OD_MASK 0x02
#define BMI08X_GYRO_INT4_LVL_MASK 0x04
#define BMI08X_GYRO_INT4_OD_MASK 0x08
/* Position definitions for BMI08X_GYRO_INT3_INT4_IO_CONF_REG register */
#define BMI08X_GYRO_INT3_OD_POS 1
#define BMI08X_GYRO_INT4_LVL_POS 2
#define BMI08X_GYRO_INT4_OD_POS 3
/* Mask definitions for BMI08X_GYRO_INT_EN_REG register */
#define BMI08X_GYRO_INT_EN_MASK 0x80
/* Position definitions for BMI08X_GYRO_INT_EN_REG register */
#define BMI08X_GYRO_INT_EN_POS 7
/* Mask definitions for BMI088_GYRO_INT_MAP_REG register */
#define BMI08X_GYRO_INT3_MAP_MASK 0x01
#define BMI08X_GYRO_INT4_MAP_MASK 0x80
/* Position definitions for BMI088_GYRO_INT_MAP_REG register */
#define BMI08X_GYRO_INT3_MAP_POS 0
#define BMI08X_GYRO_INT4_MAP_POS 7
/* Mask definitions for BMI088_GYRO_INT_MAP_REG register */
#define BMI088_GYRO_INT3_MAP_MASK 0x01
#define BMI088_GYRO_INT4_MAP_MASK 0x80
/* Position definitions for BMI088_GYRO_INT_MAP_REG register */
#define BMI088_GYRO_INT3_MAP_POS 0
#define BMI088_GYRO_INT4_MAP_POS 7
/* Mask definitions for GYRO_SELF_TEST register */
#define BMI08X_GYRO_SELF_TEST_EN_MASK 0x01
#define BMI08X_GYRO_SELF_TEST_RDY_MASK 0x02
#define BMI08X_GYRO_SELF_TEST_RESULT_MASK 0x04
#define BMI08X_GYRO_SELF_TEST_FUNCTION_MASK 0x08
/* Position definitions for GYRO_SELF_TEST register */
#define BMI08X_GYRO_SELF_TEST_RDY_POS 1
#define BMI08X_GYRO_SELF_TEST_RESULT_POS 2
#define BMI08X_GYRO_SELF_TEST_FUNCTION_POS 3
/*************************** Common Macros for both Accel and Gyro *****************************/
/** Soft reset Value */
#define BMI08X_SOFT_RESET_CMD 0xB6
/* Constant values macros */
#define BMI08X_SENSOR_DATA_SYNC_TIME_MS 1
#define BMI08X_DELAY_BETWEEN_WRITES_MS 1
#define BMI08X_SELF_TEST_DELAY_MS 3
#define BMI08X_POWER_CONFIG_DELAY 5
#define BMI08X_SENSOR_SETTLE_TIME_MS 30
#define BMI08X_SELF_TEST_DATA_READ_MS 50
#define BMI08X_ASIC_INIT_TIME_MS 150
/* allowed ODR values */
enum bmi08x_odr {
BMI08X_ODR_25_2,
BMI08X_ODR_25,
BMI08X_ODR_50,
BMI08X_ODR_100,
BMI08X_ODR_200,
BMI08X_ODR_400,
BMI08X_ODR_800,
BMI08X_ODR_1600,
};
/* Range values for accelerometer */
#define BMI08X_ACC_RANGE_2G_3G 0x0
#define BMI08X_ACC_RANGE_4G_6G 0x1
#define BMI08X_ACC_RANGE_8G_12G 0x2
#define BMI08X_ACC_RANGE_16G_24G 0x3
/* Range values for gyro */
#define BMI08X_GYR_RANGE_2000DPS 0
#define BMI08X_GYR_RANGE_1000DPS 1
#define BMI08X_GYR_RANGE_500DPS 2
#define BMI08X_GYR_RANGE_250DPS 3
#define BMI08X_GYR_RANGE_125DPS 4
#define BMI08X_ACC_SCALE(range_g) ((2 * range_g * SENSOR_G) / 65536LL)
#define BMI08X_GYR_SCALE(range_dps) ((2 * range_dps * SENSOR_PI) / 180LL / 65536LL)
/* report of data sync is selected */
#define BMI08X_ACCEL_DATA_SYNC_EN(inst) DT_NODE_HAS_STATUS(DT_INST_PHANDLE(inst, data_sync), okay)
/* Macro used for compile time optimization to compile in/out code used for data-sync
* if at least 1 bmi08x has data-sync enabled
*/
#define ACCEL_HELPER(inst) BMI08X_ACCEL_DATA_SYNC_EN(inst) ||
#define BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC DT_INST_FOREACH_STATUS_OKAY(ACCEL_HELPER) 0
#define GYRO_HELPER(inst) DT_INST_PROP(inst, data_sync) ||
#define BMI08X_GYRO_ANY_INST_HAS_DATA_SYNC DT_INST_FOREACH_STATUS_OKAY(GYRO_HELPER) 0
struct bmi08x_range {
uint16_t range;
uint8_t reg_val;
};
union bmi08x_bus {
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi)
struct spi_dt_spec spi;
#endif
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
struct i2c_dt_spec i2c;
#endif
};
struct bmi08x_accel_bus_io {
int (*check)(const union bmi08x_bus *bus);
int (*bus_init)(const struct device *dev);
int (*transceive)(const struct device *dev, uint8_t reg, bool write, void *data,
size_t length);
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
int (*write_config_file)(const struct device *dev);
#endif
};
struct bmi08x_gyro_bus_io {
int (*check)(const union bmi08x_bus *bus);
int (*transceive)(const struct device *dev, uint8_t reg, bool write, void *data,
size_t length);
};
struct bmi08x_accel_config {
union bmi08x_bus bus;
const struct bmi08x_accel_bus_io *api;
#if defined(CONFIG_BMI08X_ACCEL_TRIGGER)
struct gpio_dt_spec int_gpio;
#endif
#if defined(CONFIG_BMI08X_ACCEL_TRIGGER) || BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
uint8_t int1_map;
uint8_t int2_map;
uint8_t int1_conf_io;
uint8_t int2_conf_io;
#endif
uint8_t accel_hz;
uint8_t accel_fs;
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
uint8_t data_sync;
#endif
};
struct bmi08x_gyro_config {
union bmi08x_bus bus;
const struct bmi08x_gyro_bus_io *api;
#if defined(CONFIG_BMI08X_GYRO_TRIGGER)
struct gpio_dt_spec int_gpio;
#endif
#if defined(CONFIG_BMI08X_GYRO_TRIGGER) || BMI08X_GYRO_ANY_INST_HAS_DATA_SYNC
uint8_t int3_4_map;
uint8_t int3_4_conf_io;
#endif
uint8_t gyro_hz;
uint8_t gyro_fs;
};
struct bmi08x_accel_data {
#if defined(CONFIG_BMI08X_ACCEL_TRIGGER)
struct gpio_callback gpio_cb;
#endif
uint16_t acc_sample[3];
uint16_t scale; /* micro m/s^2/lsb */
#if defined(CONFIG_BMI08X_ACCEL_TRIGGER_OWN_THREAD)
K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_BMI08X_ACCEL_THREAD_STACK_SIZE);
struct k_thread thread;
struct k_sem sem;
#elif defined(CONFIG_BMI08X_ACCEL_TRIGGER_GLOBAL_THREAD)
struct k_work work;
const struct device *dev;
#endif
#ifdef CONFIG_BMI08X_ACCEL_TRIGGER
sensor_trigger_handler_t handler_drdy_acc;
const struct sensor_trigger *drdy_trig_acc;
#endif /* CONFIG_BMI08X_ACCEL_TRIGGER */
uint8_t accel_chip_id;
};
struct bmi08x_gyro_data {
#if defined(CONFIG_BMI08X_GYRO_TRIGGER)
struct gpio_callback gpio_cb;
#endif
uint16_t gyr_sample[3];
uint16_t scale; /* micro radians/s/lsb */
#if defined(CONFIG_BMI08X_GYRO_TRIGGER_OWN_THREAD)
K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_BMI08X_GYRO_THREAD_STACK_SIZE);
struct k_thread thread;
struct k_sem sem;
#elif defined(CONFIG_BMI08X_GYRO_TRIGGER_GLOBAL_THREAD)
struct k_work work;
const struct device *dev;
#endif
#ifdef CONFIG_BMI08X_GYRO_TRIGGER
sensor_trigger_handler_t handler_drdy_gyr;
const struct sensor_trigger *drdy_trig_gyr;
#endif /* CONFIG_BMI08X_GYRO_TRIGGER */
};
/* common functions for accel and gyro */
int bmi08x_freq_to_odr_val(uint16_t freq_int, uint16_t freq_milli);
int32_t bmi08x_range_to_reg_val(uint16_t range, const struct bmi08x_range *range_map,
uint16_t range_map_size);
int32_t bmi08x_reg_val_to_range(uint8_t reg_val, const struct bmi08x_range *range_map,
uint16_t range_map_size);
int bmi08x_accel_read(const struct device *dev, uint8_t reg_addr, uint8_t *data, uint8_t len);
int bmi08x_accel_write(const struct device *dev, uint8_t reg_addr, uint8_t *data, uint16_t len);
int bmi08x_accel_byte_read(const struct device *dev, uint8_t reg_addr, uint8_t *byte);
int bmi08x_accel_byte_write(const struct device *dev, uint8_t reg_addr, uint8_t byte);
int bmi08x_accel_word_write(const struct device *dev, uint8_t reg_addr, uint16_t word);
int bmi08x_accel_reg_field_update(const struct device *dev, uint8_t reg_addr, uint8_t pos,
uint8_t mask, uint8_t val);
static inline int bmi08x_accel_reg_update(const struct device *dev, uint8_t reg_addr, uint8_t mask,
uint8_t val)
{
return bmi08x_accel_reg_field_update(dev, reg_addr, 0, mask, val);
}
int bmi08x_gyro_read(const struct device *dev, uint8_t reg_addr, uint8_t *data, uint8_t len);
int bmi08x_gyro_byte_read(const struct device *dev, uint8_t reg_addr, uint8_t *byte);
int bmi08x_gyro_byte_write(const struct device *dev, uint8_t reg_addr, uint8_t byte);
int bmi08x_gyro_word_write(const struct device *dev, uint8_t reg_addr, uint16_t word);
int bmi08x_gyro_reg_field_update(const struct device *dev, uint8_t reg_addr, uint8_t pos,
uint8_t mask, uint8_t val);
static inline int bmi08x_gyro_reg_update(const struct device *dev, uint8_t reg_addr, uint8_t mask,
uint8_t val)
{
return bmi08x_gyro_reg_field_update(dev, reg_addr, 0, mask, val);
}
int bmi08x_acc_trigger_mode_init(const struct device *dev);
int bmi08x_trigger_set_acc(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
int bmi08x_acc_slope_config(const struct device *dev, enum sensor_attribute attr,
const struct sensor_value *val);
int32_t bmi08x_acc_reg_val_to_range(uint8_t reg_val);
int bmi08x_gyr_trigger_mode_init(const struct device *dev);
int bmi08x_trigger_set_gyr(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
int bmi08x_gyr_slope_config(const struct device *dev, enum sensor_attribute attr,
const struct sensor_value *val);
int32_t bmi08x_gyr_reg_val_to_range(uint8_t reg_val);
#endif /* ZEPHYR_DRIVERS_SENSOR_BMI08X_H_ */

View file

@ -0,0 +1,812 @@
/* Bosch BMI08X inertial measurement unit driver
*
* Copyright (c) 2022 Meta Platforms, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/byteorder.h>
#define DT_DRV_COMPAT bosch_bmi08x_accel
#include "bmi08x.h"
#include "bmi08x_config_file.h"
LOG_MODULE_REGISTER(BMI08X_ACCEL, CONFIG_SENSOR_LOG_LEVEL);
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
static int bmi08x_accel_transceive_i2c(const struct device *dev, uint8_t reg, bool write,
void *data, size_t length)
{
const struct bmi08x_accel_config *bmi08x = dev->config;
if (!write) {
return i2c_write_read_dt(&bmi08x->bus.i2c, &reg, 1, data, length);
}
if (length > CONFIG_BMI08X_I2C_WRITE_BURST_SIZE) {
return -EINVAL;
}
uint8_t buf[1 + CONFIG_BMI08X_I2C_WRITE_BURST_SIZE];
buf[0] = reg;
memcpy(&buf[1], data, length);
return i2c_write_dt(&bmi08x->bus.i2c, buf, 1 + length);
}
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
static int bmi08x_stream_transfer_write_i2c(const struct device *dev, uint16_t index,
const uint8_t *stream_data, uint16_t stream_length)
{
uint8_t asic_msb = (uint8_t)((index / 2) >> 4);
uint8_t asic_lsb = ((index / 2) & 0x0F);
int ret;
ret = bmi08x_accel_byte_write(dev, BMI08X_ACCEL_RESERVED_5B_REG, asic_lsb);
if (ret != 0) {
LOG_ERR("Cannot write index");
return ret;
}
ret = bmi08x_accel_byte_write(dev, BMI08X_ACCEL_RESERVED_5C_REG, asic_msb);
if (ret != 0) {
LOG_ERR("Cannot write index");
return ret;
}
ret = bmi08x_accel_write(dev, BMI08X_ACCEL_FEATURE_CFG_REG, (uint8_t *)stream_data,
stream_length);
if (ret != 0) {
LOG_ERR("Cannot write configuration for accelerometer.");
return ret;
}
return ret;
}
static int bmi08x_write_config_file_i2c(const struct device *dev)
{
const uint8_t *data = bmi08x_config_file;
uint16_t length = sizeof(bmi08x_config_file);
uint16_t index = 0;
int ret = 0;
while (length != 0) {
uint16_t len1 = length;
if (len1 > CONFIG_BMI08X_I2C_WRITE_BURST_SIZE) {
len1 = CONFIG_BMI08X_I2C_WRITE_BURST_SIZE;
}
ret = bmi08x_stream_transfer_write_i2c(dev, index, data, len1);
if (ret != 0) {
return ret;
}
index += len1;
data += len1;
length -= len1;
}
return ret;
}
#endif
static int bmi08x_bus_check_i2c(const union bmi08x_bus *bus)
{
return i2c_is_ready_dt(&bus->i2c) ? 0 : -ENODEV;
}
static const struct bmi08x_accel_bus_io bmi08x_i2c_api = {.check = bmi08x_bus_check_i2c,
.transceive = bmi08x_accel_transceive_i2c,
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
.write_config_file =
bmi08x_write_config_file_i2c
#endif
};
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi)
static int bmi08x_accel_transceive_spi(const struct device *dev, uint8_t reg, bool write,
void *data, size_t length)
{
const struct bmi08x_accel_config *bmi08x = dev->config;
const struct spi_buf tx_buf[2] = {{.buf = &reg, .len = 1}, {.buf = data, .len = length}};
const struct spi_buf_set tx = {.buffers = tx_buf, .count = write ? 2 : 1};
if (!write) {
uint16_t dummy;
const struct spi_buf rx_buf[2] = {{.buf = &dummy, .len = 2},
{.buf = data, .len = length}};
const struct spi_buf_set rx = {.buffers = rx_buf, .count = 2};
return spi_transceive_dt(&bmi08x->bus.spi, &tx, &rx);
}
return spi_write_dt(&bmi08x->bus.spi, &tx);
}
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
static int bmi08x_write_config_file_spi(const struct device *dev)
{
int ret;
ret = bmi08x_accel_byte_write(dev, BMI08X_ACCEL_RESERVED_5B_REG, 0);
if (ret < 0) {
LOG_ERR("Cannot write index");
return ret;
}
ret = bmi08x_accel_byte_write(dev, BMI08X_ACCEL_RESERVED_5C_REG, 0);
if (ret < 0) {
LOG_ERR("Cannot write index");
return ret;
}
/* write config file */
ret = bmi08x_accel_write(dev, BMI08X_ACCEL_FEATURE_CFG_REG, (uint8_t *)bmi08x_config_file,
sizeof(bmi08x_config_file));
if (ret < 0) {
LOG_ERR("Cannot write configuration for accelerometer.");
return ret;
}
return ret;
}
#endif
static int bmi08x_bus_check_spi(const union bmi08x_bus *bus)
{
return spi_is_ready_dt(&bus->spi) ? 0 : -ENODEV;
}
static int bmi08x_bus_init_spi(const struct device *dev)
{
uint8_t val;
int ret;
/* do a dummy read from 0x7F to activate SPI */
ret = bmi08x_accel_byte_read(dev, 0x7F, &val);
if (ret < 0) {
LOG_ERR("Cannot read from 0x7F..");
return ret;
}
k_usleep(100);
return ret;
}
static const struct bmi08x_accel_bus_io bmi08x_spi_api = {.check = bmi08x_bus_check_spi,
.bus_init = bmi08x_bus_init_spi,
.transceive = bmi08x_accel_transceive_spi,
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
.write_config_file =
bmi08x_write_config_file_spi
#endif
};
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) */
static inline int bmi08x_bus_check(const struct device *dev)
{
const struct bmi08x_accel_config *config = dev->config;
return config->api->check(&config->bus);
}
static inline int bmi08x_bus_init(const struct device *dev)
{
const struct bmi08x_accel_config *config = dev->config;
/* optional, only needed to initialize SPI according to datasheet */
if (config->api->bus_init) {
return config->api->bus_init(dev);
}
return 0;
}
static int bmi08x_accel_transceive(const struct device *dev, uint8_t reg, bool write, void *data,
size_t length)
{
const struct bmi08x_accel_config *config = dev->config;
return config->api->transceive(dev, reg, write, data, length);
}
int bmi08x_accel_read(const struct device *dev, uint8_t reg_addr, uint8_t *data, uint8_t len)
{
return bmi08x_accel_transceive(dev, reg_addr | BIT(7), false, data, len);
}
int bmi08x_accel_write(const struct device *dev, uint8_t reg_addr, uint8_t *data, uint16_t len)
{
return bmi08x_accel_transceive(dev, reg_addr, true, data, len);
}
int bmi08x_accel_byte_read(const struct device *dev, uint8_t reg_addr, uint8_t *byte)
{
return bmi08x_accel_transceive(dev, reg_addr | BIT(7), false, byte, 1);
}
static int bmi08x_accel_word_read(const struct device *dev, uint8_t reg_addr, uint16_t *word)
{
int ret;
ret = bmi08x_accel_transceive(dev, reg_addr | BIT(7), false, word, 2);
if (ret != 0) {
return ret;
}
*word = sys_le16_to_cpu(*word);
return ret;
}
int bmi08x_accel_byte_write(const struct device *dev, uint8_t reg_addr, uint8_t byte)
{
return bmi08x_accel_transceive(dev, reg_addr & 0x7F, true, &byte, 1);
}
int bmi08x_accel_word_write(const struct device *dev, uint8_t reg_addr, uint16_t word)
{
uint8_t tx_word[2] = {(uint8_t)(word & 0xff), (uint8_t)(word >> 8)};
return bmi08x_accel_transceive(dev, reg_addr & 0x7F, true, tx_word, 2);
}
int bmi08x_accel_reg_field_update(const struct device *dev, uint8_t reg_addr, uint8_t pos,
uint8_t mask, uint8_t val)
{
uint8_t old_val;
int ret;
ret = bmi08x_accel_byte_read(dev, reg_addr, &old_val);
if (ret < 0) {
return ret;
}
return bmi08x_accel_byte_write(dev, reg_addr, (old_val & ~mask) | ((val << pos) & mask));
}
static int bmi08x_acc_odr_set(const struct device *dev, uint16_t freq_int, uint16_t freq_milli)
{
int odr = bmi08x_freq_to_odr_val(freq_int, freq_milli);
if (odr < BMI08X_ACCEL_ODR_12_5_HZ) {
return odr;
}
return bmi08x_accel_reg_field_update(dev, BMI08X_REG_ACCEL_CONF, 0, BMI08X_ACCEL_ODR_MASK,
(uint8_t)odr);
}
static const struct bmi08x_range bmi085_acc_range_map[] = {
{2, BMI085_ACCEL_RANGE_2G},
{4, BMI085_ACCEL_RANGE_4G},
{8, BMI085_ACCEL_RANGE_8G},
{16, BMI085_ACCEL_RANGE_16G},
};
#define BMI085_ACC_RANGE_MAP_SIZE ARRAY_SIZE(bmi085_acc_range_map)
static const struct bmi08x_range bmi088_acc_range_map[] = {
{3, BMI088_ACCEL_RANGE_3G},
{6, BMI088_ACCEL_RANGE_6G},
{12, BMI088_ACCEL_RANGE_12G},
{24, BMI088_ACCEL_RANGE_24G},
};
#define BMI088_ACC_RANGE_MAP_SIZE ARRAY_SIZE(bmi088_acc_range_map)
static int bmi08x_acc_range_set(const struct device *dev, int32_t range)
{
struct bmi08x_accel_data *data = dev->data;
int32_t reg_val = -1;
int ret;
if (data->accel_chip_id == BMI085_ACCEL_CHIP_ID) {
reg_val = bmi08x_range_to_reg_val(range, bmi085_acc_range_map,
BMI085_ACC_RANGE_MAP_SIZE);
} else if (data->accel_chip_id == BMI088_ACCEL_CHIP_ID) {
reg_val = bmi08x_range_to_reg_val(range, bmi088_acc_range_map,
BMI088_ACC_RANGE_MAP_SIZE);
} else {
return -ENODEV;
}
if (reg_val < 0) {
return reg_val;
}
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_RANGE, reg_val & 0xff);
if (ret < 0) {
return ret;
}
data->scale = BMI08X_ACC_SCALE(range);
return ret;
}
static int bmi08x_acc_config(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
switch (attr) {
case SENSOR_ATTR_FULL_SCALE:
return bmi08x_acc_range_set(dev, sensor_ms2_to_g(val));
case SENSOR_ATTR_SAMPLING_FREQUENCY:
return bmi08x_acc_odr_set(dev, val->val1, val->val2 / 1000);
default:
LOG_ERR("Accel attribute not supported.");
return -ENOTSUP;
}
}
static int bmi08x_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
#ifdef CONFIG_PM_DEVICE
enum pm_device_state state;
(void)pm_device_state_get(dev, &state);
if (state != PM_DEVICE_STATE_ACTIVE) {
return -EBUSY;
}
#endif
switch (chan) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
return bmi08x_acc_config(dev, chan, attr, val);
default:
LOG_ERR("attr_set() not supported on this channel.");
return -ENOTSUP;
}
}
static int bmi08x_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
struct bmi08x_accel_data *data = dev->data;
size_t i;
int ret;
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_ACCEL_XYZ) {
LOG_ERR("Unsupported sensor channel");
return -ENOTSUP;
}
#ifdef CONFIG_PM_DEVICE
enum pm_device_state state;
(void)pm_device_state_get(dev, &state);
if (state != PM_DEVICE_STATE_ACTIVE) {
return -EBUSY;
}
#endif
pm_device_busy_set(dev);
ret = bmi08x_accel_read(dev, BMI08X_REG_ACCEL_X_LSB, (uint8_t *)data->acc_sample,
sizeof(data->acc_sample));
if (ret < 0) {
pm_device_busy_clear(dev);
return ret;
}
/* convert samples to cpu endianness */
for (i = 0; i < ARRAY_SIZE(data->acc_sample); i++) {
data->acc_sample[i] = sys_le16_to_cpu(data->acc_sample[i]);
}
pm_device_busy_clear(dev);
return ret;
}
static void bmi08x_to_fixed_point(int16_t raw_val, uint16_t scale, struct sensor_value *val)
{
int32_t converted_val;
/*
* maximum converted value we can get is: max(raw_val) * max(scale)
* max(raw_val) = +/- 2^15
* max(scale) = 4785
* max(converted_val) = 156794880 which is less than 2^31
*/
converted_val = raw_val * scale;
val->val1 = converted_val / 1000000;
val->val2 = converted_val % 1000000;
}
static void bmi08x_channel_convert(enum sensor_channel chan, uint16_t scale, uint16_t *raw_xyz,
struct sensor_value *val)
{
int i;
uint8_t ofs_start, ofs_stop;
switch (chan) {
case SENSOR_CHAN_ACCEL_X:
ofs_start = ofs_stop = 0U;
break;
case SENSOR_CHAN_ACCEL_Y:
ofs_start = ofs_stop = 1U;
break;
case SENSOR_CHAN_ACCEL_Z:
ofs_start = ofs_stop = 2U;
break;
default:
ofs_start = 0U;
ofs_stop = 2U;
break;
}
for (i = ofs_start; i <= ofs_stop; i++, val++) {
bmi08x_to_fixed_point(raw_xyz[i], scale, val);
}
}
static inline void bmi08x_acc_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct bmi08x_accel_data *data = dev->data;
bmi08x_channel_convert(chan, data->scale, data->acc_sample, val);
}
static int bmi08x_temp_channel_get(const struct device *dev, struct sensor_value *val)
{
uint16_t temp_raw = 0U;
int32_t temp_micro = 0;
int ret;
ret = bmi08x_accel_word_read(dev, BMI08X_REG_TEMP_MSB, &temp_raw);
if (ret < 0) {
return ret;
}
/* the scale is 1/2^5/LSB = 31250 micro degrees */
temp_micro = BMI08X_TEMP_OFFSET * 1000000ULL + temp_raw * 31250ULL;
val->val1 = temp_micro / 1000000ULL;
val->val2 = temp_micro % 1000000ULL;
return ret;
}
static int bmi08x_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
#ifdef CONFIG_PM_DEVICE
enum pm_device_state state;
(void)pm_device_state_get(dev, &state);
if (state != PM_DEVICE_STATE_ACTIVE) {
return -EBUSY;
}
#endif
switch ((int16_t)chan) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
bmi08x_acc_channel_get(dev, chan, val);
return 0;
case SENSOR_CHAN_DIE_TEMP:
return bmi08x_temp_channel_get(dev, val);
default:
LOG_ERR("Channel not supported.");
return -ENOTSUP;
}
return 0;
}
#ifdef CONFIG_PM_DEVICE
static int bmi08x_accel_pm_action(const struct device *dev, enum pm_device_action action)
{
uint8_t conf_reg_val;
uint8_t ctrl_reg_val;
int ret;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
conf_reg_val = BMI08X_ACCEL_PM_ACTIVE;
ctrl_reg_val = BMI08X_ACCEL_POWER_ENABLE;
break;
case PM_DEVICE_ACTION_SUSPEND:
conf_reg_val = BMI08X_ACCEL_PM_SUSPEND;
ctrl_reg_val = BMI08X_ACCEL_POWER_DISABLE;
break;
default:
return -ENOTSUP;
}
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_PWR_CONF, conf_reg_val);
if (ret < 0) {
LOG_ERR("Failed to set conf power mode");
return ret;
}
k_msleep(BMI08X_POWER_CONFIG_DELAY);
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_PWR_CTRL, ctrl_reg_val);
if (ret < 0) {
LOG_ERR("Failed to set ctrl power mode");
return ret;
}
k_msleep(BMI08X_POWER_CONFIG_DELAY);
return ret;
}
#endif /* CONFIG_PM_DEVICE */
static const struct sensor_driver_api bmi08x_api = {
.attr_set = bmi08x_attr_set,
#ifdef CONFIG_BMI08X_ACCEL_TRIGGER
.trigger_set = bmi08x_trigger_set_acc,
#endif
.sample_fetch = bmi08x_sample_fetch,
.channel_get = bmi08x_channel_get,
};
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
static int bmi08x_apply_sync_binary_config(const struct device *dev)
{
const struct bmi08x_accel_config *config = dev->config;
int ret;
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_PWR_CONF, BMI08X_ACCEL_PM_ACTIVE);
if (ret < 0) {
LOG_ERR("Cannot deactivate advanced power save mode.");
return ret;
}
/* required when switching power modes */
k_msleep(BMI08X_POWER_CONFIG_DELAY);
/* deactivate accel, otherwise post processing can not be enabled safely */
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_PWR_CTRL, BMI08X_ACCEL_POWER_DISABLE);
if (ret < 0) {
LOG_ERR("Cannot deactivate accel.");
return ret;
}
/* required when switching power modes */
k_msleep(BMI08X_POWER_CONFIG_DELAY);
/* disable config loading */
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_INIT_CTRL,
BMI08X_ACCEL_INIT_CTRL_DISABLE);
if (ret < 0) {
LOG_ERR("Cannot disable config loading.");
return ret;
}
if (config->api->write_config_file(dev) != 0) {
LOG_ERR("Cannot write configuration for accelerometer.");
return -EIO;
}
k_msleep(5U);
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_INIT_CTRL,
BMI08X_ACCEL_INIT_CTRL_ENABLE);
if (ret < 0) {
LOG_ERR("Cannot write configuration for accelerometer.");
return ret;
}
k_msleep(BMI08X_ASIC_INIT_TIME_MS);
/* check config initialization status */
uint8_t val;
ret = bmi08x_accel_byte_read(dev, BMI08X_REG_ACCEL_INTERNAL_STAT, &val);
if (ret < 0) {
LOG_ERR("Cannot write configuration for accelerometer.");
return ret;
}
if (val != 1) {
LOG_ERR("Configuration stream error.");
return -EIO;
}
/* write feature configuration */
uint8_t fdata[8];
ret = bmi08x_accel_read(dev, BMI08X_ACCEL_FEATURE_CFG_REG, fdata, 6);
if (ret < 0) {
LOG_ERR("Cannot read configuration for accelerometer.");
return ret;
}
fdata[4] = config->data_sync;
fdata[5] = 0x00;
ret = bmi08x_accel_write(dev, BMI08X_ACCEL_FEATURE_CFG_REG, fdata, 6);
if (ret < 0) {
LOG_ERR("Cannot write configuration for accelerometer.");
return ret;
}
k_msleep(100U);
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_PWR_CTRL, BMI08X_ACCEL_POWER_ENABLE);
if (ret < 0) {
LOG_ERR("Cannot activate accel.");
return ret;
}
/* required when switching power modes */
k_msleep(BMI08X_POWER_CONFIG_DELAY);
return ret;
}
#endif
int bmi08x_accel_init(const struct device *dev)
{
const struct bmi08x_accel_config *config = dev->config;
struct bmi08x_accel_data *data = dev->data;
uint8_t val = 0U;
int ret;
ret = bmi08x_bus_check(dev);
if (ret < 0) {
LOG_ERR("Bus not ready for '%s'", dev->name);
return ret;
}
/* reboot the chip */
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_SOFTRESET, BMI08X_SOFT_RESET_CMD);
if (ret < 0) {
LOG_ERR("Cannot reboot chip.");
return ret;
}
k_msleep(BMI08X_ACCEL_SOFTRESET_DELAY_MS);
ret = bmi08x_bus_init(dev);
if (ret < 0) {
LOG_ERR("Can't initialize bus for %s", dev->name);
return ret;
}
ret = bmi08x_accel_byte_read(dev, BMI08X_REG_ACCEL_CHIP_ID, &val);
if (ret < 0) {
LOG_ERR("Failed to read chip id.");
return ret;
}
if ((val != BMI085_ACCEL_CHIP_ID) && (val != BMI088_ACCEL_CHIP_ID)) {
LOG_ERR("Unsupported chip detected (0x%02x)!", val);
return -ENODEV;
}
data->accel_chip_id = val;
/* enable power */
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_PWR_CONF, BMI08X_ACCEL_PM_ACTIVE);
if (ret < 0) {
LOG_ERR("Failed to set conf power mode");
return ret;
}
k_msleep(BMI08X_POWER_CONFIG_DELAY);
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_PWR_CTRL, BMI08X_ACCEL_POWER_ENABLE);
if (ret < 0) {
LOG_ERR("Failed to set ctrl power mode");
return ret;
}
k_msleep(BMI08X_POWER_CONFIG_DELAY);
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
if (config->data_sync != 0) {
ret = bmi08x_apply_sync_binary_config(dev);
if (ret < 0) {
return ret;
}
}
#endif
/* set accelerometer default range, divide by two because the dts contains both bmi085 and
* bmi088 valid values even values in the enum are for the bmi085 and odd values are for the
* bmi088
*/
ret = bmi08x_acc_range_set(dev, config->accel_fs);
if (ret < 0) {
LOG_ERR("Cannot set default range for accelerometer.");
return ret;
}
/* set accelerometer default odr */
/* add 5 to offset from the dts enum */
ret = bmi08x_accel_reg_field_update(dev, BMI08X_REG_ACCEL_CONF, 0, BMI08X_ACCEL_ODR_MASK,
config->accel_hz);
if (ret < 0) {
LOG_ERR("Failed to set accel's default ODR.");
return ret;
}
#ifdef CONFIG_BMI08X_ACCEL_TRIGGER
ret = bmi08x_acc_trigger_mode_init(dev);
if (ret < 0) {
LOG_ERR("Cannot set up trigger mode.");
return ret;
}
#endif
return ret;
}
#define BMI08X_CONFIG_SPI(inst) \
.bus.spi = SPI_DT_SPEC_INST_GET( \
inst, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 2),
#define BMI08X_CONFIG_I2C(inst) .bus.i2c = I2C_DT_SPEC_INST_GET(inst),
#define BMI08X_ACCEL_TRIG(inst) \
.int1_map = DT_INST_PROP(inst, int1_map_io), .int2_map = DT_INST_PROP(inst, int2_map_io), \
.int1_conf_io = DT_INST_PROP(inst, int1_conf_io), \
.int2_conf_io = DT_INST_PROP(inst, int2_conf_io),
/* verify the bmi08x-accel is paired with a bmi08x-gyro */
#define BMI08X_VERIFY_DATA_SYNC(inst) \
BUILD_ASSERT(DT_NODE_HAS_COMPAT(DT_INST_PHANDLE(inst, data_sync), bosch_bmi08x_gyro) != 0, \
"bmi08x-accel data sync not paired with a bmi08x-gyro")
/*
* verify data sync odr, the only valid odr combinitions with the gyro are
* (gyro-hz == "400_47" and accel-hz == "400") or (gyro-hz == "1000_116" and accel-hz == "800")
* or ((gyro-hz == "2000_230" or gyro-hz == "2000_532") and accel-hz == "1600")
*/
#define BMI08X_GYRO_ODR(inst) DT_ENUM_IDX(DT_INST_PHANDLE(inst, data_sync), gyro_hz)
#define BMI08X_ACCEL_ODR(inst) DT_INST_ENUM_IDX(inst, accel_hz)
/* As the dts uses strings to define the definition, ints must be used for comparision */
#define BMI08X_VERIFY_DATA_SYNC_ODR(inst) \
BUILD_ASSERT((BMI08X_GYRO_ODR(inst) == 3 && BMI08X_ACCEL_ODR(inst) == 5) || \
(BMI08X_GYRO_ODR(inst) == 2 && BMI08X_ACCEL_ODR(inst) == 6) || \
((BMI08X_GYRO_ODR(inst) == 1 || BMI08X_GYRO_ODR(inst) == 0) && \
BMI08X_ACCEL_ODR(inst) == 7), \
"Invalid gyro and accel odr for data-sync")
/* Assert if the gyro does not have data-sync enabled */
#define BMI08X_VERIFY_GYRO_DATA_SYNC_EN(inst) \
BUILD_ASSERT(DT_PROP(DT_INST_PHANDLE(inst, data_sync), data_sync), \
"paired bmi08x-gyro does not have data-sync enabled")
/* infer the data-sync value from the gyro and accel odr 2000=1, 1000=2, 400=3, otherwise it is 0 if
* it is not enabled. the build_assert should prevent any invalid values when it is enabled
*/
#define BMI08X_DATA_SYNC_REG_VAL(inst) \
(BMI08X_GYRO_ODR(inst) == 3 && BMI08X_ACCEL_ODR(inst) == 5) ? 3 \
: (BMI08X_GYRO_ODR(inst) == 2 && BMI08X_ACCEL_ODR(inst) == 6) ? 2 \
: ((BMI08X_GYRO_ODR(inst) == 1 || BMI08X_GYRO_ODR(inst) == 0) && \
BMI08X_ACCEL_ODR(inst) == 7) \
? 1 \
: 0
/* define the .data_sync in the driver config */
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
/* if another bmi08x as the data sync enabled, and one doesn't, it will get the value of 0 and won't
* have the config file sent over
*/
#define BMI08X_DATA_SYNC_REG(inst) \
.data_sync = COND_CODE_1(BMI08X_ACCEL_DATA_SYNC_EN(inst), \
(BMI08X_DATA_SYNC_REG_VAL(inst)), (0)),
#define BMI08X_ACCEL_TRIGGER_PINS(inst) BMI08X_ACCEL_TRIG(inst)
#else
#define BMI08X_DATA_SYNC_REG(inst)
#define BMI08X_ACCEL_TRIGGER_PINS(inst) \
IF_ENABLED(CONFIG_BMI08X_ACCEL_TRIGGER, (BMI08X_ACCEL_TRIG(inst)))
#endif
#define BMI08X_CREATE_INST(inst) \
\
IF_ENABLED(BMI08X_ACCEL_DATA_SYNC_EN(inst), (BMI08X_VERIFY_DATA_SYNC(inst);)) \
IF_ENABLED(BMI08X_ACCEL_DATA_SYNC_EN(inst), (BMI08X_VERIFY_DATA_SYNC_ODR(inst);)) \
IF_ENABLED(BMI08X_ACCEL_DATA_SYNC_EN(inst), (BMI08X_VERIFY_GYRO_DATA_SYNC_EN(inst);)) \
\
static struct bmi08x_accel_data bmi08x_drv_##inst; \
\
static const struct bmi08x_accel_config bmi08x_config_##inst = { \
COND_CODE_1(DT_INST_ON_BUS(inst, spi), (BMI08X_CONFIG_SPI(inst)), \
(BMI08X_CONFIG_I2C(inst))) \
.api = COND_CODE_1(DT_INST_ON_BUS(inst, spi), (&bmi08x_spi_api), \
(&bmi08x_i2c_api)), \
IF_ENABLED(CONFIG_BMI08X_ACCEL_TRIGGER, \
(.int_gpio = GPIO_DT_SPEC_INST_GET(inst, int_gpios),)) \
BMI08X_ACCEL_TRIGGER_PINS(inst) \
.accel_hz = DT_INST_ENUM_IDX(inst, accel_hz) + 5, \
.accel_fs = DT_INST_PROP(inst, accel_fs), BMI08X_DATA_SYNC_REG(inst)}; \
\
PM_DEVICE_DT_INST_DEFINE(inst, bmi08x_accel_pm_action); \
SENSOR_DEVICE_DT_INST_DEFINE(inst, bmi08x_accel_init, PM_DEVICE_DT_INST_GET(inst), \
&bmi08x_drv_##inst, &bmi08x_config_##inst, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &bmi08x_api);
/* Create the struct device for every status "okay" node in the devicetree. */
DT_INST_FOREACH_STATUS_OKAY(BMI08X_CREATE_INST)

View file

@ -0,0 +1,171 @@
/* Bosch BMI08X inertial measurement unit driver, trigger implementation
*
* Copyright (c) 2022 Meta Platforms, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device.h>
#include <zephyr/kernel.h>
#define DT_DRV_COMPAT bosch_bmi08x_accel
#include "bmi08x.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(BMI08X_ACCEL, CONFIG_SENSOR_LOG_LEVEL);
static void bmi08x_handle_drdy_acc(const struct device *dev)
{
struct bmi08x_accel_data *data = dev->data;
#ifdef CONFIG_PM_DEVICE
enum pm_device_state state;
(void)pm_device_state_get(dev, &state);
if (state != PM_DEVICE_STATE_ACTIVE) {
return;
}
#endif
if (data->handler_drdy_acc) {
data->handler_drdy_acc(dev, data->drdy_trig_acc);
}
}
static void bmi08x_handle_interrupts_acc(void *arg)
{
const struct device *dev = (const struct device *)arg;
bmi08x_handle_drdy_acc(dev);
}
#ifdef CONFIG_BMI08X_ACCEL_TRIGGER_OWN_THREAD
static void bmi08x_acc_thread_main(void *arg1, void *unused1, void *unused2)
{
k_thread_name_set(NULL, "bmi08x_acc_trig");
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
const struct device *dev = (const struct device *)arg1;
struct bmi08x_accel_data *data = dev->data;
while (1) {
k_sem_take(&data->sem, K_FOREVER);
bmi08x_handle_interrupts_acc((void *)dev);
}
}
#endif
#ifdef CONFIG_BMI08X_ACCEL_TRIGGER_GLOBAL_THREAD
static void bmi08x_acc_work_handler(struct k_work *work)
{
struct bmi08x_accel_data *data = CONTAINER_OF(work, struct bmi08x_accel_data, work);
bmi08x_handle_interrupts_acc((void *)data->dev);
}
#endif
static void bmi08x_acc_gpio_callback(const struct device *port, struct gpio_callback *cb,
uint32_t pin)
{
struct bmi08x_accel_data *data = CONTAINER_OF(cb, struct bmi08x_accel_data, gpio_cb);
ARG_UNUSED(port);
ARG_UNUSED(pin);
#if defined(CONFIG_BMI08X_ACCEL_TRIGGER_OWN_THREAD)
k_sem_give(&data->sem);
#elif defined(CONFIG_BMI08X_ACCEL_TRIGGER_GLOBAL_THREAD)
k_work_submit(&data->work);
#endif
}
int bmi08x_trigger_set_acc(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
struct bmi08x_accel_data *data = dev->data;
if ((trig->chan == SENSOR_CHAN_ACCEL_XYZ) && (trig->type == SENSOR_TRIG_DATA_READY)) {
data->handler_drdy_acc = handler;
data->drdy_trig_acc = trig;
return 0;
}
return -ENOTSUP;
}
int bmi08x_acc_trigger_mode_init(const struct device *dev)
{
struct bmi08x_accel_data *data = dev->data;
const struct bmi08x_accel_config *cfg = dev->config;
int ret = 0;
if (!gpio_is_ready_dt(&cfg->int_gpio)) {
LOG_ERR("GPIO device not ready");
return -ENODEV;
}
#if defined(CONFIG_BMI08X_ACCEL_TRIGGER_OWN_THREAD)
k_sem_init(&data->sem, 0, K_SEM_MAX_LIMIT);
k_thread_create(&data->thread, data->thread_stack,
CONFIG_BMI08X_ACCEL_THREAD_STACK_SIZE, bmi08x_acc_thread_main, (void *)dev,
NULL, NULL, K_PRIO_COOP(CONFIG_BMI08X_ACCEL_THREAD_PRIORITY), 0, K_NO_WAIT);
#elif defined(CONFIG_BMI08X_ACCEL_TRIGGER_GLOBAL_THREAD)
data->work.handler = bmi08x_acc_work_handler;
data->dev = dev;
#endif
#if BMI08X_ACCEL_ANY_INST_HAS_DATA_SYNC
if (config->data_sync != 0) {
/* set accel ints */
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_INT1_MAP, cfg->int1_map);
if (ret < 0) {
LOG_ERR("Failed to map interrupts.");
return ret;
}
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_INT2_MAP, cfg->int2_map);
if (ret < 0) {
LOG_ERR("Failed to map interrupts.");
return ret;
}
} else
#endif
{
uint8_t map_data = ((cfg->int2_map << BMI08X_ACCEL_INT2_DRDY_POS) |
(cfg->int1_map << BMI08X_ACCEL_INT1_DRDY_POS));
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_INT1_INT2_MAP_DATA, map_data);
if (ret < 0) {
LOG_ERR("Failed to map interrupts.");
return ret;
}
}
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_INT1_IO_CONF, cfg->int1_conf_io);
if (ret < 0) {
LOG_ERR("Failed to map interrupts.");
return ret;
}
ret = bmi08x_accel_byte_write(dev, BMI08X_REG_ACCEL_INT2_IO_CONF, cfg->int2_conf_io);
if (ret < 0) {
LOG_ERR("Failed to map interrupts.");
return ret;
}
gpio_pin_configure_dt(&cfg->int_gpio, GPIO_INPUT);
gpio_init_callback(&data->gpio_cb, bmi08x_acc_gpio_callback, BIT(cfg->int_gpio.pin));
ret = gpio_add_callback(cfg->int_gpio.port, &data->gpio_cb);
if (ret < 0) {
LOG_ERR("Failed to set gpio callback.");
return ret;
}
gpio_pin_interrupt_configure_dt(&cfg->int_gpio, GPIO_INT_EDGE_TO_ACTIVE);
return ret;
}

View file

@ -0,0 +1,424 @@
/* Bosch BMI08X inertial measurement unit header
*
* Copyright (c) 2022 Bosch Sensortec GmbH.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef BMI08X_BMI08X_CONFIG_FILE_H_
#define BMI08X_BMI08X_CONFIG_FILE_H_
/* Source : https://github.com/BoschSensortec/BMI08x-Sensor-API/blob/bmi08x_v1.5.8/bmi08a.c#L69 */
const uint8_t bmi08x_config_file[] = {
0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x48, 0xb4, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x6d,
0xb4, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0xd4, 0xb3, 0x80, 0x2e, 0xb0, 0xb3, 0x80, 0x2e,
0x12, 0xb4, 0x50, 0x39, 0x21, 0x2e, 0xb0, 0xf0, 0x10, 0x30, 0x21, 0x2e, 0x16, 0xf0, 0x80,
0x2e, 0xfe, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45,
0x79, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x03, 0xb1, 0x12, 0x24, 0x67, 0x00, 0x90, 0x42,
0x81, 0x42, 0xba, 0x82, 0xd1, 0x7f, 0xe2, 0x7f, 0x98, 0x2e, 0x3c, 0xb0, 0xd1, 0x6f, 0x00,
0x2e, 0x41, 0x40, 0x40, 0xb2, 0x4c, 0x2f, 0xe1, 0x6f, 0x44, 0x86, 0x03, 0x2e, 0x67, 0x00,
0xc2, 0x40, 0xf7, 0x86, 0x4a, 0x04, 0xc1, 0x42, 0x00, 0x2e, 0xc2, 0x40, 0x80, 0xac, 0x01,
0x2f, 0x23, 0x2e, 0x63, 0x00, 0xd1, 0x40, 0xd2, 0x40, 0x0a, 0x0f, 0x01, 0x2f, 0x40, 0xac,
0x02, 0x2f, 0x01, 0x30, 0x23, 0x2e, 0x63, 0x00, 0xfe, 0x82, 0xc2, 0x40, 0x43, 0x40, 0xd3,
0x04, 0x46, 0x84, 0xc3, 0x7f, 0x85, 0x86, 0xc5, 0x82, 0x45, 0x80, 0x44, 0x40, 0xc1, 0x6f,
0xc3, 0x40, 0xe0, 0x7f, 0xd1, 0x7f, 0x00, 0x2e, 0x82, 0x40, 0x98, 0x2e, 0x00, 0xb0, 0xe1,
0x6f, 0x72, 0x84, 0x40, 0x42, 0x85, 0x86, 0xc5, 0x82, 0x45, 0x80, 0x44, 0x40, 0xc3, 0x40,
0xd1, 0x6f, 0xe0, 0x7f, 0x00, 0x2e, 0x82, 0x40, 0x98, 0x2e, 0x00, 0xb0, 0xe1, 0x6f, 0x72,
0x84, 0x40, 0x42, 0x85, 0x86, 0xc5, 0x82, 0x45, 0x80, 0x44, 0x40, 0xc3, 0x40, 0xd1, 0x6f,
0xe0, 0x7f, 0x00, 0x2e, 0x82, 0x40, 0x98, 0x2e, 0x00, 0xb0, 0xe1, 0x6f, 0x00, 0x2e, 0x40,
0x42, 0x98, 0x2e, 0xa5, 0xb0, 0x11, 0x30, 0x23, 0x2e, 0x5e, 0xf0, 0xfb, 0x6f, 0xc0, 0x5f,
0xb8, 0x2e, 0xaa, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0xed,
0x8f, 0xd9, 0x31, 0x00, 0x00, 0xc6, 0x01, 0x8c, 0x03, 0xc6, 0x01, 0x00, 0x00, 0x00, 0x00,
0x0e, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xb0, 0x50, 0xf0, 0x7f, 0x00, 0x2e, 0x01, 0x2e, 0x01, 0x01, 0x02, 0xbc,
0x0f, 0xb8, 0xe0, 0x7f, 0x00, 0x2e, 0x01, 0x2e, 0x01, 0x01, 0x01, 0xbc, 0x0f, 0xb8, 0xd0,
0x7f, 0x00, 0x2e, 0x01, 0x2e, 0x01, 0x01, 0x0f, 0xb8, 0xc0, 0x7f, 0x02, 0x30, 0xe6, 0x6f,
0xd4, 0x6f, 0xc3, 0x6f, 0x80, 0x91, 0x04, 0x2f, 0x00, 0x91, 0x02, 0x2f, 0xc0, 0xb2, 0x90,
0x2e, 0xf6, 0x01, 0xf0, 0x6f, 0x0b, 0x2e, 0x24, 0x00, 0x01, 0x82, 0x40, 0x91, 0x14, 0x2f,
0x41, 0x87, 0x27, 0x2e, 0x24, 0x00, 0x00, 0x40, 0x21, 0x2e, 0x1b, 0x00, 0x53, 0x40, 0x10,
0x24, 0x1c, 0x00, 0x13, 0x42, 0x00, 0x2e, 0x41, 0x40, 0x01, 0x42, 0x25, 0x2e, 0x18, 0x00,
0x25, 0x2e, 0x19, 0x00, 0x25, 0x2e, 0x1e, 0x00, 0x50, 0x5f, 0xb8, 0x2e, 0x0b, 0x2e, 0x00,
0x01, 0xd5, 0xbe, 0xd5, 0xba, 0xb5, 0x7f, 0x00, 0x2e, 0x0b, 0x2e, 0x01, 0x01, 0xd3, 0xbe,
0xd3, 0xba, 0xa5, 0x7f, 0x00, 0x2e, 0x0b, 0x2e, 0x00, 0x01, 0xd4, 0xbe, 0xdf, 0xba, 0x95,
0x7f, 0x00, 0x2e, 0x95, 0x6f, 0x0f, 0x2e, 0x1a, 0x00, 0x3d, 0x1a, 0x05, 0x2f, 0x25, 0x2e,
0x18, 0x00, 0x25, 0x2e, 0x19, 0x00, 0x2b, 0x2e, 0x1a, 0x00, 0x80, 0x91, 0x01, 0x2f, 0x06,
0x30, 0x07, 0x2d, 0x06, 0x40, 0x0f, 0x2e, 0x1b, 0x00, 0xb7, 0x05, 0x80, 0xa9, 0xd6, 0x05,
0xb7, 0x23, 0x86, 0x7f, 0x00, 0x91, 0x01, 0x2f, 0x04, 0x30, 0x07, 0x2d, 0x44, 0x40, 0x0d,
0x2e, 0x1c, 0x00, 0x26, 0x05, 0x00, 0xa9, 0x94, 0x05, 0x26, 0x23, 0x74, 0x7f, 0xc0, 0x90,
0x01, 0x2f, 0x00, 0x2e, 0x09, 0x2d, 0x02, 0x86, 0x00, 0x2e, 0xc3, 0x40, 0x09, 0x2e, 0x1d,
0x00, 0xdc, 0x04, 0xc0, 0xa8, 0x93, 0x04, 0x9a, 0x22, 0x62, 0x7f, 0x12, 0x30, 0x84, 0x6f,
0xb3, 0x6f, 0x63, 0x0f, 0x14, 0x30, 0x08, 0x2f, 0x74, 0x6f, 0x63, 0x0f, 0x14, 0x30, 0x04,
0x2f, 0x64, 0x6f, 0x63, 0x0f, 0x14, 0x30, 0x00, 0x2f, 0x04, 0x30, 0x54, 0x7f, 0x40, 0x91,
0x0b, 0x2e, 0x18, 0x00, 0x54, 0x6f, 0xa3, 0x6f, 0x6a, 0x29, 0x1d, 0x2f, 0x00, 0x91, 0x06,
0x30, 0x14, 0x24, 0x1c, 0x00, 0x0d, 0x2f, 0x2d, 0x2e, 0x18, 0x00, 0x05, 0x2e, 0x19, 0x00,
0x81, 0x84, 0x25, 0x2e, 0x19, 0x00, 0x05, 0x2e, 0x19, 0x00, 0x53, 0x0e, 0x2b, 0x2f, 0x2d,
0x2e, 0x1e, 0x00, 0x29, 0x2d, 0x2b, 0x2e, 0x18, 0x00, 0x2d, 0x2e, 0x19, 0x00, 0x0b, 0x2e,
0x18, 0x00, 0x6b, 0x0e, 0x20, 0x2f, 0x25, 0x2e, 0x1e, 0x00, 0x1e, 0x2d, 0x00, 0xb3, 0x05,
0x2f, 0x02, 0x30, 0x25, 0x2e, 0x18, 0x00, 0x25, 0x2e, 0x1e, 0x00, 0x08, 0x2d, 0x2b, 0x2e,
0x18, 0x00, 0x09, 0x2e, 0x18, 0x00, 0x63, 0x0e, 0x01, 0x2f, 0x25, 0x2e, 0x1e, 0x00, 0x02,
0x40, 0x25, 0x2e, 0x1b, 0x00, 0x31, 0x25, 0x00, 0x2e, 0xd5, 0x40, 0x12, 0x24, 0x1c, 0x00,
0x42, 0x25, 0x95, 0x42, 0x00, 0x2e, 0xc3, 0x40, 0x83, 0x42, 0x00, 0x2e, 0x05, 0x2e, 0x1e,
0x00, 0x80, 0xb2, 0x0d, 0x2f, 0x00, 0x40, 0x21, 0x2e, 0x1b, 0x00, 0x50, 0x40, 0x10, 0x43,
0x00, 0x2e, 0x40, 0x40, 0x00, 0x43, 0x20, 0x30, 0x21, 0x2e, 0x5e, 0xf0, 0x02, 0x2d, 0x25,
0x2e, 0x24, 0x00, 0x50, 0x5f, 0xb8, 0x2e, 0x40, 0x30, 0x21, 0x2e, 0xba, 0xf0, 0xb8, 0x2e,
0x80, 0x2e, 0x18, 0x00, 0x70, 0x50, 0xf4, 0x7f, 0xe3, 0x7f, 0xd2, 0x7f, 0xc1, 0x7f, 0x12,
0x30, 0x03, 0x2e, 0x66, 0x00, 0x91, 0x14, 0x92, 0x7f, 0x00, 0x31, 0xc4, 0x6f, 0x95, 0x6f,
0xe3, 0x6f, 0xa5, 0x0f, 0x70, 0x84, 0x01, 0x04, 0x14, 0x2f, 0xd5, 0x6f, 0x00, 0xa9, 0x01,
0x2f, 0xa5, 0x7f, 0x21, 0x2d, 0xdd, 0x04, 0xb3, 0x7f, 0x40, 0xb2, 0xb3, 0x6f, 0x1c, 0x18,
0x06, 0x2f, 0x50, 0xa0, 0x01, 0x2f, 0xba, 0x11, 0x03, 0x2d, 0x71, 0x12, 0xb8, 0x14, 0x8a,
0x0b, 0x6e, 0x00, 0xa1, 0x7f, 0x11, 0x2d, 0xf7, 0x6f, 0xfb, 0x05, 0xb7, 0x7f, 0x25, 0x05,
0xb5, 0x6f, 0x2c, 0x18, 0x40, 0xb2, 0x06, 0x2f, 0x50, 0xa0, 0x01, 0x2f, 0xba, 0x11, 0x03,
0x2d, 0x71, 0x12, 0xb8, 0x14, 0x8a, 0x0b, 0x5e, 0x00, 0xa1, 0x7f, 0x00, 0x2e, 0xa0, 0x6f,
0x90, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0x02, 0x01, 0x8e, 0xbc, 0x01, 0x2e, 0x62, 0x00, 0x9e,
0xb8, 0x01, 0x1a, 0x5f, 0x2f, 0x01, 0x2e, 0x02, 0x01, 0x0e, 0xbc, 0x0e, 0xb8, 0x21, 0x2e,
0x62, 0x00, 0x03, 0x2e, 0x62, 0x00, 0x43, 0xb2, 0x10, 0x24, 0x65, 0x00, 0x3c, 0x2f, 0x42,
0xb2, 0x22, 0x2f, 0x41, 0xb2, 0x06, 0x2f, 0x01, 0x30, 0x11, 0x42, 0x01, 0x42, 0x3e, 0x80,
0x00, 0x2e, 0x01, 0x42, 0xb8, 0x2e, 0x03, 0x2e, 0x9d, 0x00, 0x5f, 0x90, 0x62, 0x30, 0x11,
0x24, 0x81, 0x00, 0x07, 0x2f, 0x30, 0x25, 0x34, 0x37, 0xd4, 0x42, 0xc2, 0x42, 0xfe, 0x86,
0x00, 0x2e, 0xc1, 0x42, 0x00, 0x2e, 0x07, 0x2e, 0x9d, 0x00, 0xde, 0x90, 0x35, 0x2f, 0x63,
0x36, 0x13, 0x42, 0x02, 0x42, 0x3e, 0x80, 0x00, 0x2e, 0x01, 0x42, 0xb8, 0x2e, 0x03, 0x2e,
0x9d, 0x00, 0x5f, 0xb2, 0x52, 0x30, 0x21, 0x32, 0x0a, 0x2f, 0x07, 0x2e, 0x9d, 0x00, 0xde,
0x90, 0x24, 0x2f, 0x23, 0x31, 0x13, 0x42, 0x02, 0x42, 0x3e, 0x80, 0x00, 0x2e, 0x01, 0x42,
0xb8, 0x2e, 0x03, 0x32, 0x13, 0x42, 0x02, 0x42, 0x3e, 0x80, 0x00, 0x2e, 0x01, 0x42, 0xb8,
0x2e, 0x03, 0x2e, 0x9d, 0x00, 0x5f, 0xb2, 0x42, 0x30, 0x11, 0x31, 0x0a, 0x2f, 0x07, 0x2e,
0x9d, 0x00, 0xde, 0x90, 0x0c, 0x2f, 0xa3, 0x30, 0x13, 0x42, 0x02, 0x42, 0x3e, 0x80, 0x00,
0x2e, 0x01, 0x42, 0xb8, 0x2e, 0x63, 0x31, 0x13, 0x42, 0x02, 0x42, 0x3e, 0x80, 0x00, 0x2e,
0x01, 0x42, 0xb8, 0x2e, 0x10, 0x24, 0x78, 0x00, 0x11, 0x24, 0x52, 0xf0, 0x12, 0x40, 0x52,
0x42, 0x28, 0xb5, 0x52, 0x42, 0x00, 0x2e, 0x12, 0x40, 0x42, 0x42, 0x42, 0x82, 0x00, 0x40,
0x50, 0x42, 0x08, 0xb4, 0x40, 0x42, 0x7e, 0x80, 0xa8, 0xb4, 0x01, 0x42, 0xb8, 0x2e, 0x12,
0x24, 0x71, 0x00, 0x90, 0x40, 0x84, 0x82, 0x20, 0x50, 0x50, 0x42, 0x77, 0x80, 0x82, 0x40,
0x42, 0x42, 0xfb, 0x7f, 0x05, 0x82, 0x00, 0x40, 0x40, 0x42, 0x7c, 0x80, 0x05, 0x82, 0x00,
0x40, 0x40, 0x42, 0x7c, 0x80, 0x05, 0x82, 0x00, 0x40, 0x40, 0x42, 0x77, 0x84, 0x00, 0x2e,
0x90, 0x40, 0x84, 0x82, 0x82, 0x40, 0x50, 0x42, 0x77, 0x80, 0x42, 0x42, 0x05, 0x82, 0x00,
0x40, 0x40, 0x42, 0x7c, 0x80, 0x05, 0x82, 0x00, 0x40, 0x40, 0x42, 0x7c, 0x80, 0x05, 0x82,
0x00, 0x40, 0x40, 0x42, 0x7c, 0x82, 0xe1, 0x7f, 0x98, 0x2e, 0x03, 0xb1, 0xe2, 0x6f, 0x00,
0x2e, 0x90, 0x42, 0x81, 0x42, 0xbc, 0x82, 0x10, 0x24, 0x33, 0xf0, 0x23, 0x40, 0x02, 0x40,
0xb8, 0xbd, 0x9a, 0x0a, 0x03, 0x80, 0x52, 0x42, 0x00, 0x2e, 0x23, 0x40, 0x02, 0x40, 0xb8,
0xbd, 0x9a, 0x0a, 0x03, 0x80, 0x52, 0x42, 0x00, 0x2e, 0x22, 0x40, 0x00, 0x40, 0x28, 0xbd,
0x10, 0x0a, 0x40, 0x42, 0x00, 0x2e, 0xfb, 0x6f, 0xe0, 0x5f, 0xb8, 0x2e, 0x11, 0x24, 0x28,
0xf0, 0x50, 0x50, 0x60, 0x40, 0xf0, 0x7f, 0x51, 0x25, 0x60, 0x40, 0xe0, 0x7f, 0x00, 0x2e,
0x41, 0x40, 0xd1, 0x7f, 0x00, 0x2e, 0xe2, 0x6f, 0xd0, 0x6f, 0x00, 0xb2, 0xf3, 0x6f, 0xa8,
0xb8, 0x28, 0xbe, 0x59, 0x0a, 0x20, 0x0a, 0x01, 0x2f, 0xb0, 0x5f, 0xb8, 0x2e, 0x45, 0x41,
0xc5, 0x7f, 0x00, 0x2e, 0xc5, 0x6f, 0x40, 0x91, 0x09, 0x2f, 0x05, 0x2e, 0x28, 0xf0, 0xb2,
0x7f, 0x00, 0x2e, 0xb2, 0x6f, 0x1a, 0x1a, 0x07, 0x2f, 0xf0, 0x3f, 0x13, 0x25, 0x05, 0x2d,
0x15, 0x1a, 0x02, 0x2f, 0x10, 0x24, 0xff, 0x00, 0x20, 0x0a, 0xb0, 0x5f, 0xb8, 0x2e, 0x01,
0x2e, 0x03, 0x01, 0x8f, 0xbc, 0x01, 0x2e, 0x1f, 0x00, 0x9f, 0xb8, 0x01, 0x1a, 0x12, 0x2f,
0x01, 0x2e, 0x03, 0x01, 0x0f, 0xbc, 0x0f, 0xb8, 0x21, 0x2e, 0x1f, 0x00, 0x11, 0x30, 0x05,
0x2e, 0x1f, 0x00, 0x51, 0x08, 0xd2, 0x3f, 0x01, 0x2e, 0x07, 0xf0, 0x02, 0x08, 0x91, 0xbc,
0x01, 0x0a, 0x21, 0x2e, 0x07, 0xf0, 0xb8, 0x2e, 0xb8, 0x2e, 0x50, 0x50, 0xf2, 0x7f, 0xe1,
0x7f, 0x01, 0x30, 0xd1, 0x7f, 0xc1, 0x7f, 0x10, 0x24, 0x91, 0x04, 0xf2, 0x6f, 0xe3, 0x6f,
0x1c, 0x2d, 0xc4, 0x6f, 0x9c, 0x01, 0xd5, 0x6f, 0x86, 0x41, 0x6e, 0x0d, 0xd5, 0x7f, 0xb1,
0x7f, 0x0e, 0x2d, 0xd6, 0x6f, 0xef, 0xba, 0x61, 0xbf, 0x40, 0x91, 0x01, 0x2f, 0xd6, 0x7f,
0x03, 0x2d, 0x70, 0x0d, 0xd5, 0x7f, 0x00, 0x2e, 0xb5, 0x6f, 0x41, 0x8b, 0xb5, 0x7f, 0x00,
0x2e, 0xb5, 0x6f, 0x50, 0xa3, 0xee, 0x2f, 0x01, 0x89, 0xc4, 0x7f, 0x00, 0x2e, 0xc4, 0x6f,
0x62, 0x0e, 0xe0, 0x2f, 0xd0, 0x6f, 0xb0, 0x5f, 0xb8, 0x2e, 0x90, 0x50, 0xd3, 0x7f, 0xfb,
0x7f, 0xc2, 0x7f, 0xb1, 0x7f, 0x11, 0x30, 0xa1, 0x7f, 0x1a, 0x25, 0xc2, 0x6f, 0x72, 0x7f,
0x77, 0x82, 0xb2, 0x6f, 0x82, 0x7f, 0xe2, 0x7f, 0x22, 0x30, 0x98, 0x2e, 0x4d, 0xb1, 0x90,
0x7f, 0x00, 0x2e, 0xe1, 0x6f, 0xd0, 0x6f, 0x41, 0x16, 0x92, 0x6f, 0x01, 0x08, 0x51, 0x08,
0x48, 0x1a, 0x01, 0x2f, 0x01, 0x30, 0xa1, 0x7f, 0x00, 0x2e, 0xfb, 0x6f, 0xa0, 0x6f, 0x70,
0x5f, 0xb8, 0x2e, 0x70, 0x50, 0xe3, 0x7f, 0xfb, 0x7f, 0xc1, 0x7f, 0xd2, 0x7f, 0x1a, 0x25,
0xc0, 0x6f, 0xd2, 0x6f, 0x90, 0x7f, 0xa2, 0x7f, 0x79, 0x82, 0xe2, 0x6f, 0xb2, 0x7f, 0x32,
0x30, 0x98, 0x2e, 0x4d, 0xb1, 0xfb, 0x6f, 0x90, 0x5f, 0xb8, 0x2e, 0xb0, 0x50, 0xa4, 0x7f,
0xfb, 0x7f, 0x93, 0x7f, 0x82, 0x7f, 0x71, 0x7f, 0x00, 0x30, 0xa1, 0x6f, 0xe1, 0x7f, 0x40,
0x42, 0x00, 0x2e, 0x92, 0x6f, 0x83, 0x6f, 0x71, 0x6f, 0xd3, 0x7f, 0xc2, 0x7f, 0xb1, 0x7f,
0x98, 0x2e, 0x7a, 0xb1, 0x60, 0x7f, 0x00, 0x2e, 0x60, 0x6f, 0x01, 0xb2, 0x1e, 0x2f, 0xb1,
0x6f, 0xf2, 0x30, 0x8a, 0x08, 0x13, 0x24, 0xf0, 0x00, 0x4b, 0x08, 0x24, 0xbd, 0x94, 0xb8,
0x51, 0x0a, 0x51, 0x7f, 0x00, 0x2e, 0x51, 0x6f, 0x81, 0x16, 0xd3, 0x6f, 0x9a, 0x08, 0xc4,
0x6f, 0xe1, 0x08, 0xe1, 0x6f, 0x93, 0x0a, 0x42, 0x42, 0x12, 0x24, 0x00, 0xff, 0x43, 0x40,
0x9a, 0x08, 0x14, 0x24, 0xff, 0x00, 0xdc, 0x08, 0xb8, 0xbd, 0x28, 0xb9, 0x9a, 0x0a, 0x42,
0x42, 0x00, 0x2e, 0xfb, 0x6f, 0x50, 0x5f, 0xb8, 0x2e, 0x70, 0x50, 0xd4, 0x7f, 0xfb, 0x7f,
0xc3, 0x7f, 0xa1, 0x7f, 0xb2, 0x7f, 0x02, 0x30, 0x92, 0x7f, 0x00, 0x2e, 0x05, 0x2e, 0x20,
0x00, 0xc4, 0x6f, 0x80, 0xb2, 0x0c, 0x2f, 0x81, 0x90, 0x1a, 0x2f, 0xd2, 0x6f, 0x07, 0x2e,
0x21, 0x00, 0xa1, 0x32, 0x98, 0x2e, 0xaf, 0xb1, 0x02, 0x30, 0x25, 0x2e, 0x20, 0x00, 0x92,
0x7f, 0x10, 0x2d, 0xa1, 0x6f, 0xb2, 0x6f, 0xe4, 0x7f, 0xa3, 0x32, 0x98, 0x2e, 0x9c, 0xb1,
0x21, 0x2e, 0x21, 0x00, 0xe2, 0x6f, 0x03, 0x2e, 0x21, 0x00, 0x81, 0x42, 0x12, 0x30, 0x25,
0x2e, 0x20, 0x00, 0x92, 0x7f, 0x00, 0x2e, 0xfb, 0x6f, 0x90, 0x6f, 0x90, 0x5f, 0xb8, 0x2e,
0x10, 0x50, 0x00, 0x30, 0xf0, 0x7f, 0x12, 0x24, 0x86, 0x00, 0x1b, 0x2d, 0xf1, 0x6f, 0xd1,
0x00, 0x00, 0x2e, 0xc0, 0x42, 0xbc, 0x84, 0xd1, 0x00, 0x00, 0x2e, 0xc0, 0x42, 0x8c, 0x84,
0xd1, 0x00, 0x00, 0x2e, 0xc0, 0x42, 0xbc, 0x84, 0xd1, 0x00, 0x00, 0x2e, 0xc0, 0x42, 0x8c,
0x84, 0xd1, 0x00, 0x00, 0x2e, 0xc0, 0x42, 0xbc, 0x84, 0xd1, 0x00, 0x00, 0x2e, 0xc0, 0x42,
0x41, 0x82, 0xf1, 0x7f, 0xb4, 0x84, 0xf1, 0x6f, 0x43, 0xa2, 0xe1, 0x2f, 0xf0, 0x5f, 0xb8,
0x2e, 0xc0, 0x50, 0x92, 0x7f, 0xfb, 0x7f, 0x81, 0x7f, 0x00, 0x30, 0x60, 0x7f, 0x70, 0x7f,
0x50, 0x7f, 0x00, 0x2e, 0x03, 0x2e, 0x04, 0x01, 0x9d, 0xbc, 0x9e, 0xb8, 0x41, 0x7f, 0x00,
0x2e, 0x42, 0x6f, 0x52, 0x7f, 0xe2, 0x7f, 0x00, 0x2e, 0x83, 0x6f, 0xc4, 0x82, 0xd3, 0x7f,
0x0c, 0x2d, 0x55, 0x6f, 0x7f, 0x89, 0xdc, 0x01, 0x9d, 0x01, 0xcb, 0x41, 0x8b, 0x43, 0xcc,
0x01, 0x4d, 0x01, 0xc7, 0x41, 0x47, 0x43, 0x54, 0x7f, 0x00, 0x2e, 0x54, 0x6f, 0x00, 0xab,
0xf0, 0x2f, 0x9b, 0x6f, 0x8a, 0x00, 0x4b, 0x42, 0xc2, 0x7f, 0xb1, 0x7f, 0x50, 0x7f, 0x7c,
0x80, 0xa0, 0x7f, 0x13, 0x24, 0x09, 0x01, 0x3f, 0x2d, 0x50, 0x6f, 0x18, 0x01, 0xc8, 0x84,
0xc8, 0x00, 0x50, 0x00, 0x05, 0x41, 0xc7, 0x40, 0x44, 0x40, 0x61, 0x6f, 0x73, 0x6f, 0x2f,
0x18, 0x00, 0xb3, 0x0b, 0x2f, 0x10, 0xa1, 0x03, 0x2f, 0x30, 0x89, 0xbc, 0x11, 0xce, 0x17,
0x06, 0x2d, 0x74, 0x13, 0x06, 0x31, 0xb4, 0x05, 0xbe, 0x15, 0xfc, 0x11, 0xae, 0x0b, 0x4e,
0x00, 0xdf, 0x02, 0x61, 0x7f, 0x73, 0x7f, 0xb4, 0x84, 0x01, 0x82, 0xd1, 0x00, 0x88, 0x80,
0xa2, 0x6f, 0x11, 0x01, 0x81, 0x00, 0xc3, 0x40, 0x05, 0x41, 0x84, 0x40, 0x1d, 0x18, 0x72,
0x6f, 0x00, 0xb3, 0x63, 0x6f, 0x0b, 0x2f, 0x10, 0xa1, 0x03, 0x2f, 0x30, 0x89, 0xbc, 0x11,
0xce, 0x17, 0x06, 0x2d, 0x74, 0x13, 0x06, 0x31, 0xb4, 0x05, 0xbe, 0x15, 0xfc, 0x11, 0xae,
0x0b, 0xde, 0x04, 0x97, 0x06, 0x63, 0x7f, 0x72, 0x7f, 0x51, 0x7f, 0x3c, 0x86, 0xb1, 0x6f,
0xe2, 0x6f, 0x50, 0x6f, 0x42, 0x0e, 0xbc, 0x2f, 0xe0, 0x6f, 0xc8, 0x82, 0x98, 0x00, 0x48,
0x00, 0xc0, 0x6f, 0x83, 0x40, 0x04, 0x40, 0x42, 0x40, 0x61, 0x6f, 0x70, 0x6f, 0x80, 0xb2,
0x1c, 0x18, 0x0b, 0x2f, 0x90, 0xa0, 0x03, 0x2f, 0xb0, 0x84, 0xba, 0x11, 0xce, 0x17, 0x06,
0x2d, 0x03, 0x31, 0xda, 0x04, 0xfb, 0x14, 0x32, 0x13, 0xfa, 0x11, 0xa3, 0x0b, 0x4e, 0x00,
0x07, 0x02, 0x61, 0x7f, 0x70, 0x7f, 0x00, 0x2e, 0x72, 0x6f, 0x80, 0xa8, 0x60, 0x6f, 0xd1,
0x6f, 0x13, 0x2f, 0x80, 0x90, 0x03, 0x2f, 0x13, 0x24, 0xff, 0x7f, 0x43, 0x0f, 0x0d, 0x2f,
0xbf, 0xa0, 0x07, 0x2f, 0xbf, 0x90, 0x03, 0x2f, 0x12, 0x24, 0x00, 0x80, 0x42, 0x0e, 0x01,
0x2f, 0x40, 0x42, 0x07, 0x2d, 0x10, 0x24, 0x00, 0x80, 0x40, 0x42, 0x03, 0x2d, 0x10, 0x24,
0xff, 0x7f, 0x40, 0x42, 0x00, 0x2e, 0xfb, 0x6f, 0x40, 0x5f, 0x40, 0x40, 0xb8, 0x2e, 0x11,
0x24, 0x7b, 0x00, 0x30, 0x50, 0x10, 0x30, 0x50, 0x42, 0xfb, 0x7f, 0x10, 0x24, 0x33, 0xf0,
0x23, 0x40, 0x02, 0x40, 0xb8, 0xbd, 0x9a, 0x0a, 0x03, 0x80, 0x52, 0x42, 0x00, 0x2e, 0x23,
0x40, 0x02, 0x40, 0xb8, 0xbd, 0x9a, 0x0a, 0x03, 0x80, 0x52, 0x42, 0x00, 0x2e, 0x23, 0x40,
0x02, 0x40, 0xb8, 0xbd, 0x9a, 0x0a, 0x3c, 0x80, 0x42, 0x42, 0x7e, 0x84, 0xe0, 0x7f, 0x86,
0x82, 0xd1, 0x7f, 0x00, 0x2e, 0x82, 0x40, 0x98, 0x2e, 0x40, 0xb2, 0xd1, 0x6f, 0x7d, 0x82,
0x00, 0x2e, 0x40, 0x42, 0x7e, 0x80, 0x0d, 0x82, 0x02, 0x40, 0xd1, 0x7f, 0x98, 0x2e, 0x40,
0xb2, 0xd1, 0x6f, 0x76, 0x82, 0x00, 0x2e, 0x40, 0x42, 0x7e, 0x80, 0x14, 0x82, 0x02, 0x40,
0xd1, 0x7f, 0x98, 0x2e, 0x40, 0xb2, 0xd1, 0x6f, 0x6f, 0x82, 0x00, 0x2e, 0x40, 0x42, 0x7e,
0x80, 0xe1, 0x6f, 0x12, 0x40, 0x52, 0x42, 0x28, 0xb5, 0x52, 0x42, 0x00, 0x2e, 0x12, 0x40,
0x52, 0x42, 0x28, 0xb5, 0x52, 0x42, 0x00, 0x2e, 0x00, 0x40, 0x50, 0x42, 0x08, 0xb4, 0x40,
0x42, 0x00, 0x2e, 0xfb, 0x6f, 0xd0, 0x5f, 0xb8, 0x2e, 0x10, 0x50, 0x01, 0x2e, 0x55, 0xf0,
0xf0, 0x7f, 0x00, 0x2e, 0xf0, 0x6f, 0x21, 0x2e, 0x55, 0xf0, 0xf0, 0x5f, 0xb8, 0x2e, 0x20,
0x50, 0x00, 0x30, 0xe0, 0x7f, 0xfb, 0x7f, 0x11, 0x24, 0xb1, 0xf0, 0x42, 0x40, 0x43, 0x30,
0x93, 0x0a, 0x42, 0x42, 0x58, 0x82, 0x12, 0x24, 0xaf, 0x00, 0x62, 0x42, 0x12, 0x24, 0xff,
0x00, 0x42, 0x42, 0x69, 0x82, 0x72, 0x3c, 0x43, 0x40, 0x9a, 0x08, 0x83, 0x32, 0x93, 0x0a,
0x42, 0x42, 0x42, 0x82, 0x02, 0x3f, 0x43, 0x40, 0x9a, 0x08, 0x52, 0x42, 0x0b, 0x31, 0x4b,
0x42, 0x7e, 0x82, 0x72, 0x31, 0x42, 0x42, 0x00, 0x2e, 0x03, 0x2e, 0x40, 0xf0, 0x5f, 0xb2,
0x03, 0x2f, 0x03, 0x2e, 0x40, 0xf0, 0x5e, 0x90, 0x27, 0x2f, 0x11, 0x24, 0x00, 0x02, 0x12,
0x24, 0x05, 0x80, 0x13, 0x24, 0xff, 0xb7, 0x1b, 0x24, 0x00, 0xb0, 0x04, 0x30, 0x05, 0x30,
0x56, 0x32, 0x6e, 0x1a, 0x00, 0x2f, 0x25, 0x36, 0x69, 0x1a, 0x01, 0x2f, 0x5b, 0x25, 0x00,
0x2e, 0x56, 0x41, 0x26, 0x0d, 0x06, 0x30, 0xcf, 0xbb, 0x41, 0xbe, 0xc0, 0x91, 0x01, 0x2f,
0x00, 0x2e, 0x01, 0x2d, 0x22, 0x0d, 0x81, 0x8d, 0x90, 0xa1, 0xf5, 0x2f, 0xeb, 0x0e, 0xe8,
0x2f, 0x01, 0x2e, 0x25, 0x00, 0x20, 0x1a, 0x05, 0x2f, 0x20, 0x30, 0xe0, 0x7f, 0x03, 0x2d,
0x30, 0x30, 0xe0, 0x7f, 0x00, 0x2e, 0xe0, 0x6f, 0x00, 0xb2, 0x06, 0x2f, 0x21, 0x2e, 0x59,
0xf0, 0x98, 0x2e, 0x43, 0xb3, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0xfb, 0x6f, 0xe0, 0x5f,
0xb8, 0x2e, 0xa0, 0x50, 0x80, 0x7f, 0xe7, 0x7f, 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0xa2,
0x7f, 0x91, 0x7f, 0xf6, 0x7f, 0x7b, 0x7f, 0x00, 0x2e, 0x01, 0x2e, 0x43, 0xf0, 0x08, 0xbc,
0x0f, 0xb8, 0x60, 0x7f, 0x00, 0x2e, 0x60, 0x6f, 0x00, 0xb2, 0x01, 0x2f, 0x98, 0x2e, 0xb9,
0xb0, 0x40, 0x30, 0x21, 0x2e, 0xb8, 0xf0, 0xf6, 0x6f, 0x91, 0x6f, 0xa2, 0x6f, 0xb3, 0x6f,
0xc4, 0x6f, 0xd5, 0x6f, 0xe7, 0x6f, 0x7b, 0x6f, 0x80, 0x6f, 0x60, 0x5f, 0xc8, 0x2e, 0xa0,
0x50, 0x80, 0x7f, 0xe7, 0x7f, 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0xa2, 0x7f, 0x91, 0x7f,
0xf6, 0x7f, 0x7b, 0x7f, 0x00, 0x2e, 0x01, 0x2e, 0x29, 0xf0, 0x08, 0xbc, 0x0f, 0xb8, 0x60,
0x7f, 0x00, 0x2e, 0x60, 0x6f, 0x01, 0x90, 0x1b, 0x2f, 0x01, 0x2e, 0x02, 0x01, 0x0e, 0xbc,
0x0e, 0xb8, 0x00, 0x90, 0x05, 0x2f, 0x01, 0x2e, 0x04, 0x01, 0x0f, 0xbc, 0x0f, 0xb8, 0x01,
0xb2, 0x0d, 0x2f, 0x01, 0x2e, 0x7b, 0x00, 0x01, 0x90, 0x04, 0x2f, 0x98, 0x2e, 0x1a, 0xb2,
0x00, 0x30, 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0x37, 0xf0, 0x21, 0x2e, 0x37, 0xf0, 0x02,
0x2d, 0x98, 0x2e, 0xf3, 0xb2, 0x80, 0x30, 0x21, 0x2e, 0xb8, 0xf0, 0xf6, 0x6f, 0x91, 0x6f,
0xa2, 0x6f, 0xb3, 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0xe7, 0x6f, 0x7b, 0x6f, 0x80, 0x6f, 0x60,
0x5f, 0xc8, 0x2e, 0x60, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x36, 0x30, 0x0f, 0x2e, 0x01, 0xf0,
0xfe, 0xbf, 0xfe, 0xbb, 0xb7, 0x05, 0xa6, 0x7f, 0xd3, 0x7f, 0xc4, 0x7f, 0xb5, 0x7f, 0x14,
0x24, 0x89, 0xf0, 0x3f, 0x8b, 0x03, 0x41, 0x44, 0x41, 0xb8, 0xbd, 0x9c, 0x0b, 0xa3, 0x6f,
0x14, 0x24, 0x9a, 0x00, 0xb3, 0x11, 0x43, 0x8b, 0x16, 0x43, 0x00, 0x2e, 0x67, 0x41, 0x46,
0x41, 0xf8, 0xbf, 0xbe, 0x0b, 0xb3, 0x11, 0x16, 0x43, 0x43, 0x8d, 0x00, 0x2e, 0xa5, 0x41,
0x86, 0x41, 0xd8, 0xbe, 0x6e, 0x0b, 0xeb, 0x10, 0x03, 0x43, 0x13, 0x30, 0x27, 0x2e, 0x22,
0x00, 0x03, 0x31, 0x27, 0x2e, 0xb8, 0xf0, 0xf6, 0x6f, 0xe7, 0x6f, 0xc4, 0x6f, 0xb5, 0x6f,
0xd3, 0x6f, 0xa0, 0x5f, 0xc8, 0x2e, 0xa0, 0x50, 0x80, 0x7f, 0x91, 0x7f, 0xe7, 0x7f, 0xd5,
0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0xa2, 0x7f, 0xf6, 0x7f, 0x7b, 0x7f, 0x00, 0x2e, 0x01, 0x2e,
0xb9, 0xf0, 0x60, 0x7f, 0x10, 0x30, 0x61, 0x6f, 0x08, 0x08, 0x00, 0xb2, 0x01, 0x2f, 0x98,
0x2e, 0x9e, 0x00, 0x10, 0x30, 0x21, 0x2e, 0xb9, 0xf0, 0x21, 0x2e, 0x5f, 0xf0, 0xf6, 0x6f,
0x91, 0x6f, 0xa2, 0x6f, 0xb3, 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0xe7, 0x6f, 0x7b, 0x6f, 0x80,
0x6f, 0x60, 0x5f, 0xc8, 0x2e, 0x20, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x56, 0x32, 0x0f, 0x2e,
0x58, 0xf0, 0x7e, 0x1a, 0x02, 0x2f, 0x16, 0x30, 0x2d, 0x2e, 0x23, 0x00, 0x16, 0x24, 0x80,
0x00, 0x2d, 0x2e, 0xb9, 0xf0, 0xe7, 0x6f, 0xf6, 0x6f, 0xe0, 0x5f, 0xc8, 0x2e, 0x30, 0x50,
0x04, 0x30, 0xd4, 0x7f, 0xe4, 0x7f, 0xfb, 0x7f, 0x13, 0x24, 0x26, 0xf0, 0xc2, 0x40, 0xc1,
0x86, 0xe4, 0x7f, 0xd2, 0x7f, 0x00, 0x2e, 0xd1, 0x40, 0x18, 0xbc, 0x18, 0xba, 0xd2, 0x6f,
0xe1, 0x6f, 0x90, 0x0a, 0x0c, 0x0b, 0xd2, 0x7f, 0xe4, 0x7f, 0x00, 0x2e, 0xe4, 0x6f, 0xc3,
0x40, 0xe3, 0x0a, 0xdb, 0x6f, 0xdb, 0x7f, 0xe3, 0x7f, 0x13, 0x24, 0x15, 0x01, 0xe2, 0x6f,
0x09, 0x2e, 0x16, 0x01, 0xd1, 0x6f, 0x98, 0x2e, 0xea, 0xb1, 0x21, 0x2e, 0x58, 0xf0, 0x98,
0x2e, 0x43, 0xb3, 0xfb, 0x6f, 0xd0, 0x5f, 0xb8, 0x2e, 0x98, 0x2e, 0x4d, 0xb3, 0x20, 0x26,
0x98, 0x2e, 0xfa, 0x01, 0x98, 0x2e, 0xf5, 0xb4, 0x98, 0x2e, 0xf1, 0xb4, 0x98, 0x2e, 0xde,
0xb4, 0x98, 0x2e, 0xf9, 0xb4, 0x01, 0x2e, 0x40, 0xf0, 0x21, 0x2e, 0x9d, 0x00, 0x10, 0x30,
0x21, 0x2e, 0x59, 0xf0, 0x98, 0x2e, 0x43, 0xb3, 0x21, 0x30, 0x10, 0x24, 0x9a, 0x00, 0x00,
0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x05, 0x2e, 0x22, 0x00, 0x80, 0xb2, 0x02, 0x30, 0x05, 0x2f,
0x23, 0x2e, 0x5f, 0xf0, 0x25, 0x2e, 0x22, 0x00, 0x98, 0x2e, 0x17, 0x01, 0x98, 0x2e, 0x31,
0xb1, 0x01, 0x2e, 0x23, 0x00, 0x01, 0x90, 0x00, 0x30, 0xe7, 0x2f, 0x21, 0x2e, 0x23, 0x00,
0x98, 0x2e, 0x80, 0xb4, 0xe3, 0x2d, 0x80, 0x30, 0x21, 0x2e, 0xba, 0xf0, 0x10, 0x24, 0x80,
0x00, 0x03, 0x2e, 0x06, 0xf0, 0x08, 0x0a, 0x21, 0x2e, 0x06, 0xf0, 0x00, 0x3e, 0x03, 0x2e,
0x06, 0xf0, 0x08, 0x08, 0x51, 0x30, 0x01, 0x0a, 0x21, 0x2e, 0x06, 0xf0, 0xb8, 0x2e, 0x00,
0x31, 0x21, 0x2e, 0xba, 0xf0, 0xb8, 0x2e, 0x10, 0x30, 0x21, 0x2e, 0xbb, 0xf0, 0xb8, 0x2e,
0x10, 0x24, 0x80, 0x00, 0x21, 0x2e, 0xbb, 0xf0, 0xb8, 0x2e, 0x1a, 0x24, 0x26, 0x00, 0x80,
0x2e, 0xab, 0xb4, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e,
0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80,
0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00,
0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18,
0x00, 0x80, 0x2e, 0x18, 0x00, 0x80, 0x2e, 0x18, 0x00};
#endif /* BMI08X_BMI08X_CONFIG_FILE_H_ */

View file

@ -0,0 +1,484 @@
/* Bosch BMI08X inertial measurement unit driver
*
* Copyright (c) 2022 Meta Platforms, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/byteorder.h>
#define DT_DRV_COMPAT bosch_bmi08x_gyro
#include "bmi08x.h"
LOG_MODULE_REGISTER(BMI08X_GYRO, CONFIG_SENSOR_LOG_LEVEL);
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
static int bmi08x_gyro_transceive_i2c(const struct device *dev, uint8_t reg, bool write, void *data,
size_t length)
{
const struct bmi08x_gyro_config *bmi08x = dev->config;
if (!write) {
return i2c_write_read_dt(&bmi08x->bus.i2c, &reg, 1, data, length);
}
if (length > CONFIG_BMI08X_I2C_WRITE_BURST_SIZE) {
return -EINVAL;
}
uint8_t buf[1 + CONFIG_BMI08X_I2C_WRITE_BURST_SIZE];
buf[0] = reg;
memcpy(&buf[1], data, length);
return i2c_write_dt(&bmi08x->bus.i2c, buf, 1 + length);
}
static int bmi08x_bus_check_i2c(const union bmi08x_bus *bus)
{
return i2c_is_ready_dt(&bus->i2c) ? 0 : -ENODEV;
}
static const struct bmi08x_gyro_bus_io bmi08x_i2c_api = {
.check = bmi08x_bus_check_i2c,
.transceive = bmi08x_gyro_transceive_i2c,
};
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi)
static int bmi08x_gyro_transceive_spi(const struct device *dev, uint8_t reg, bool write, void *data,
size_t length)
{
const struct bmi08x_gyro_config *bmi08x = dev->config;
const struct spi_buf tx_buf[2] = {{.buf = &reg, .len = 1}, {.buf = data, .len = length}};
const struct spi_buf_set tx = {.buffers = tx_buf, .count = write ? 2 : 1};
if (!write) {
uint16_t dummy;
const struct spi_buf rx_buf[2] = {{.buf = &dummy, .len = 1},
{.buf = data, .len = length}};
const struct spi_buf_set rx = {.buffers = rx_buf, .count = 2};
return spi_transceive_dt(&bmi08x->bus.spi, &tx, &rx);
}
return spi_write_dt(&bmi08x->bus.spi, &tx);
}
static int bmi08x_bus_check_spi(const union bmi08x_bus *bus)
{
return spi_is_ready_dt(&bus->spi) ? 0 : -ENODEV;
}
static const struct bmi08x_gyro_bus_io bmi08x_spi_api = {
.check = bmi08x_bus_check_spi,
.transceive = bmi08x_gyro_transceive_spi,
};
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) */
static inline int bmi08x_bus_check(const struct device *dev)
{
const struct bmi08x_gyro_config *config = dev->config;
return config->api->check(&config->bus);
}
static int bmi08x_gyro_transceive(const struct device *dev, uint8_t reg, bool write, void *data,
size_t length)
{
const struct bmi08x_gyro_config *cfg = dev->config;
return cfg->api->transceive(dev, reg, write, data, length);
}
int bmi08x_gyro_read(const struct device *dev, uint8_t reg_addr, uint8_t *data, uint8_t len)
{
return bmi08x_gyro_transceive(dev, reg_addr | BIT(7), false, data, len);
}
int bmi08x_gyro_byte_read(const struct device *dev, uint8_t reg_addr, uint8_t *byte)
{
return bmi08x_gyro_transceive(dev, reg_addr | BIT(7), false, byte, 1);
}
int bmi08x_gyro_byte_write(const struct device *dev, uint8_t reg_addr, uint8_t byte)
{
return bmi08x_gyro_transceive(dev, reg_addr & 0x7F, true, &byte, 1);
}
int bmi08x_gyro_word_write(const struct device *dev, uint8_t reg_addr, uint16_t word)
{
uint8_t tx_word[2] = {(uint8_t)(word & 0xff), (uint8_t)(word >> 8)};
return bmi08x_gyro_transceive(dev, reg_addr & 0x7F, true, tx_word, 2);
}
int bmi08x_gyro_reg_field_update(const struct device *dev, uint8_t reg_addr, uint8_t pos,
uint8_t mask, uint8_t val)
{
uint8_t old_val;
int ret;
ret = bmi08x_gyro_byte_read(dev, reg_addr, &old_val);
if (ret < 0) {
return ret;
}
return bmi08x_gyro_byte_write(dev, reg_addr, (old_val & ~mask) | ((val << pos) & mask));
}
static const struct bmi08x_range bmi08x_gyr_range_map[] = {
{125, BMI08X_GYR_RANGE_125DPS}, {250, BMI08X_GYR_RANGE_250DPS},
{500, BMI08X_GYR_RANGE_500DPS}, {1000, BMI08X_GYR_RANGE_1000DPS},
{2000, BMI08X_GYR_RANGE_2000DPS},
};
#define BMI08X_GYR_RANGE_MAP_SIZE ARRAY_SIZE(bmi08x_gyr_range_map)
int32_t bmi08x_gyr_reg_val_to_range(uint8_t reg_val)
{
return bmi08x_reg_val_to_range(reg_val, bmi08x_gyr_range_map, BMI08X_GYR_RANGE_MAP_SIZE);
}
static int bmi08x_gyr_odr_set(const struct device *dev, uint16_t freq_int, uint16_t freq_milli)
{
int odr = bmi08x_freq_to_odr_val(freq_int, freq_milli);
if (odr < 0) {
return odr;
}
if (odr < BMI08X_GYRO_BW_532_ODR_2000_HZ || odr > BMI08X_GYRO_BW_32_ODR_100_HZ) {
return -ENOTSUP;
}
return bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_BANDWIDTH, (uint8_t)odr);
}
static int bmi08x_gyr_range_set(const struct device *dev, uint16_t range)
{
struct bmi08x_gyro_data *bmi08x = dev->data;
int32_t reg_val =
bmi08x_range_to_reg_val(range, bmi08x_gyr_range_map, BMI08X_GYR_RANGE_MAP_SIZE);
int ret;
if (reg_val < 0) {
return reg_val;
}
ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_RANGE, reg_val);
if (ret < 0) {
return ret;
}
bmi08x->scale = BMI08X_GYR_SCALE(range);
return ret;
}
static int bmi08x_gyr_config(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
switch (attr) {
case SENSOR_ATTR_FULL_SCALE:
return bmi08x_gyr_range_set(dev, sensor_rad_to_degrees(val));
case SENSOR_ATTR_SAMPLING_FREQUENCY:
return bmi08x_gyr_odr_set(dev, val->val1, val->val2 / 1000);
default:
LOG_ERR("Gyro attribute not supported.");
return -ENOTSUP;
}
}
static int bmi08x_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
#ifdef CONFIG_PM_DEVICE
enum pm_device_state state;
(void)pm_device_state_get(dev, &state);
if (state != PM_DEVICE_STATE_ACTIVE) {
return -EBUSY;
}
#endif
switch (chan) {
case SENSOR_CHAN_GYRO_X:
case SENSOR_CHAN_GYRO_Y:
case SENSOR_CHAN_GYRO_Z:
case SENSOR_CHAN_GYRO_XYZ:
return bmi08x_gyr_config(dev, chan, attr, val);
default:
LOG_ERR("attr_set() not supported on this channel.");
return -ENOTSUP;
}
}
static int bmi08x_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
struct bmi08x_gyro_data *bmi08x = dev->data;
size_t i;
int ret;
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_GYRO_XYZ) {
LOG_ERR("Unsupported sensor channel");
return -ENOTSUP;
}
ret = bmi08x_gyro_read(dev, BMI08X_REG_GYRO_X_LSB, (uint8_t *)bmi08x->gyr_sample,
sizeof(bmi08x->gyr_sample));
if (ret < 0) {
return ret;
}
/* convert samples to cpu endianness */
for (i = 0; i < ARRAY_SIZE(bmi08x->gyr_sample); i++) {
bmi08x->gyr_sample[i] = sys_le16_to_cpu(bmi08x->gyr_sample[i]);
}
return ret;
}
static void bmi08x_to_fixed_point(int16_t raw_val, uint16_t scale, struct sensor_value *val)
{
int32_t converted_val;
/*
* maximum converted value we can get is: max(raw_val) * max(scale)
* max(raw_val) = +/- 2^15
* max(scale) = 4785
* max(converted_val) = 156794880 which is less than 2^31
*/
converted_val = raw_val * scale;
val->val1 = converted_val / 1000000;
val->val2 = converted_val % 1000000;
}
static void bmi08x_channel_convert(enum sensor_channel chan, uint16_t scale, uint16_t *raw_xyz,
struct sensor_value *val)
{
int i;
uint8_t ofs_start, ofs_stop;
switch (chan) {
case SENSOR_CHAN_GYRO_X:
ofs_start = ofs_stop = 0U;
break;
case SENSOR_CHAN_GYRO_Y:
ofs_start = ofs_stop = 1U;
break;
case SENSOR_CHAN_GYRO_Z:
ofs_start = ofs_stop = 2U;
break;
default:
ofs_start = 0U;
ofs_stop = 2U;
break;
}
for (i = ofs_start; i <= ofs_stop; i++, val++) {
bmi08x_to_fixed_point(raw_xyz[i], scale, val);
}
}
static inline void bmi08x_gyr_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct bmi08x_gyro_data *bmi08x = dev->data;
bmi08x_channel_convert(chan, bmi08x->scale, bmi08x->gyr_sample, val);
}
static int bmi08x_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
#ifdef CONFIG_PM_DEVICE
enum pm_device_state state;
(void)pm_device_state_get(dev, &state);
if (state != PM_DEVICE_STATE_ACTIVE) {
return -EBUSY;
}
#endif
switch ((int16_t)chan) {
case SENSOR_CHAN_GYRO_X:
case SENSOR_CHAN_GYRO_Y:
case SENSOR_CHAN_GYRO_Z:
case SENSOR_CHAN_GYRO_XYZ:
bmi08x_gyr_channel_get(dev, chan, val);
return 0;
default:
LOG_ERR("Channel not supported.");
return -ENOTSUP;
}
}
#ifdef CONFIG_PM_DEVICE
static int bmi08x_gyro_pm_action(const struct device *dev, enum pm_device_action action)
{
uint8_t reg_val;
int ret;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
reg_val = BMI08X_GYRO_PM_NORMAL;
break;
case PM_DEVICE_ACTION_SUSPEND:
reg_val = BMI08X_GYRO_PM_SUSPEND;
break;
default:
return -ENOTSUP;
}
ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_LPM1, reg_val);
if (ret < 0) {
LOG_ERR("Failed to set power mode");
return ret;
}
k_msleep(BMI08X_GYRO_POWER_MODE_CONFIG_DELAY);
return ret;
}
#endif /* CONFIG_PM_DEVICE */
static const struct sensor_driver_api bmi08x_api = {
.attr_set = bmi08x_attr_set,
#ifdef CONFIG_BMI08X_GYRO_TRIGGER
.trigger_set = bmi08x_trigger_set_gyr,
#endif
.sample_fetch = bmi08x_sample_fetch,
.channel_get = bmi08x_channel_get,
};
int bmi08x_gyro_init(const struct device *dev)
{
const struct bmi08x_gyro_config *config = dev->config;
uint8_t val = 0U;
int ret;
ret = bmi08x_bus_check(dev);
if (ret < 0) {
LOG_ERR("Bus not ready for '%s'", dev->name);
return ret;
}
/* reboot the chip */
ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_SOFTRESET, BMI08X_SOFT_RESET_CMD);
if (ret < 0) {
LOG_ERR("Cannot reboot chip.");
return ret;
}
k_msleep(BMI08X_GYRO_SOFTRESET_DELAY);
ret = bmi08x_gyro_byte_read(dev, BMI08X_REG_GYRO_CHIP_ID, &val);
if (ret < 0) {
LOG_ERR("Failed to read chip id.");
return ret;
}
if (val != BMI08X_GYRO_CHIP_ID) {
LOG_ERR("Unsupported chip detected (0x%02x)!", val);
return -ENODEV;
}
/* set gyro default range */
ret = bmi08x_gyr_range_set(dev, config->gyro_fs);
if (ret < 0) {
LOG_ERR("Cannot set default range for gyroscope.");
return ret;
}
/* set gyro default bandwidth */
ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_BANDWIDTH, config->gyro_hz);
if (ret < 0) {
LOG_ERR("Failed to set gyro's default ODR.");
return ret;
}
#ifdef CONFIG_BMI08X_GYRO_TRIGGER
ret = bmi08x_gyr_trigger_mode_init(dev);
if (ret < 0) {
LOG_ERR("Cannot set up trigger mode.");
return ret;
}
#endif
/* with BMI08X_DATA_SYNC set, it is expected that the INT3 or INT4 is wired to either INT1
* or INT2
*/
#if defined(CONFIG_BMI08X_GYRO_TRIGGER) || BMI08X_GYRO_ANY_INST_HAS_DATA_SYNC
/* set gyro ints */
/* set ints */
ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_INT_CTRL, 0x80);
if (ret < 0) {
LOG_ERR("Failed to map interrupts.");
return ret;
}
ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_INT3_INT4_IO_CONF,
config->int3_4_conf_io);
if (ret < 0) {
LOG_ERR("Failed to map interrupts.");
return ret;
}
ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_INT3_INT4_IO_MAP, config->int3_4_map);
if (ret < 0) {
LOG_ERR("Failed to map interrupts.");
return ret;
}
#endif
return ret;
}
#define BMI08X_CONFIG_SPI(inst) \
.bus.spi = SPI_DT_SPEC_INST_GET( \
inst, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 2),
#define BMI08X_CONFIG_I2C(inst) .bus.i2c = I2C_DT_SPEC_INST_GET(inst),
#define BMI08X_GYRO_TRIG(inst) \
.int3_4_map = DT_INST_PROP(inst, int3_4_map_io), \
.int3_4_conf_io = DT_INST_PROP(inst, int3_4_conf_io),
#if BMI08X_GYRO_ANY_INST_HAS_DATA_SYNC
/* the bmi08x-gyro should not have trigger mode with data-sync enabled */
BUILD_ASSERT(CONFIG_BMI08X_GYRO_TRIGGER_NONE,
"Only none trigger type allowed for bmi08x-gyro with data-sync enabled");
/* with data-sync, one of the int pins should be wired directory to the accel's int pins, their
* config should be defined
*/
#define BMI08X_GYRO_TRIGGER_PINS(inst) BMI08X_GYRO_TRIG(inst)
#else
#define BMI08X_GYRO_TRIGGER_PINS(inst) \
IF_ENABLED(CONFIG_BMI08X_GYRO_TRIGGER, (BMI08X_GYRO_TRIG(inst)))
#endif
#define BMI08X_CREATE_INST(inst) \
\
static struct bmi08x_gyro_data bmi08x_drv_##inst; \
\
static const struct bmi08x_gyro_config bmi08x_config_##inst = { \
COND_CODE_1(DT_INST_ON_BUS(inst, spi), (BMI08X_CONFIG_SPI(inst)), \
(BMI08X_CONFIG_I2C(inst))) \
.api = COND_CODE_1(DT_INST_ON_BUS(inst, spi), (&bmi08x_spi_api), \
(&bmi08x_i2c_api)), \
IF_ENABLED(CONFIG_BMI08X_GYRO_TRIGGER, \
(.int_gpio = GPIO_DT_SPEC_INST_GET(inst, int_gpios),)) \
.gyro_hz = DT_INST_ENUM_IDX(inst, gyro_hz), \
BMI08X_GYRO_TRIGGER_PINS(inst).gyro_fs = DT_INST_ENUM_IDX(inst, gyro_fs), \
}; \
\
PM_DEVICE_DT_INST_DEFINE(inst, bmi08x_gyro_pm_action); \
SENSOR_DEVICE_DT_INST_DEFINE(inst, bmi08x_gyro_init, PM_DEVICE_DT_INST_GET(inst), \
&bmi08x_drv_##inst, &bmi08x_config_##inst, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &bmi08x_api);
/* Create the struct device for every status "okay" node in the devicetree. */
DT_INST_FOREACH_STATUS_OKAY(BMI08X_CREATE_INST)

View file

@ -0,0 +1,135 @@
/* Bosch BMI08X inertial measurement unit driver, trigger implementation
*
* Copyright (c) 2022 Meta Platforms, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device.h>
#include <zephyr/kernel.h>
#define DT_DRV_COMPAT bosch_bmi08x_gyro
#include "bmi08x.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(BMI08X_GYRO, CONFIG_SENSOR_LOG_LEVEL);
static void bmi08x_handle_drdy_gyr(const struct device *dev)
{
struct bmi08x_gyro_data *data = dev->data;
#ifdef CONFIG_PM_DEVICE
enum pm_device_state state;
(void)pm_device_state_get(dev, &state);
if (state != PM_DEVICE_STATE_ACTIVE) {
return;
}
#endif
if (data->handler_drdy_gyr) {
data->handler_drdy_gyr(dev, data->drdy_trig_gyr);
}
}
static void bmi08x_handle_interrupts_gyr(void *arg)
{
const struct device *dev = (const struct device *)arg;
bmi08x_handle_drdy_gyr(dev);
}
#ifdef CONFIG_BMI08X_GYRO_TRIGGER_OWN_THREAD
static void bmi08x_gyr_thread_main(void *arg1, void *unused1, void *unused2)
{
k_thread_name_set(NULL, "bmi08x_gyr_trig");
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
const struct device *dev = (const struct device *)arg1;
struct bmi08x_gyro_data *data = dev->data;
while (1) {
k_sem_take(&data->sem, K_FOREVER);
bmi08x_handle_interrupts_gyr((void *)dev);
}
}
#endif
#ifdef CONFIG_BMI08X_GYRO_TRIGGER_GLOBAL_THREAD
static void bmi08x_gyr_work_handler(struct k_work *work)
{
struct bmi08x_gyro_data *data = CONTAINER_OF(work, struct bmi08x_gyro_data, work);
bmi08x_handle_interrupts_gyr((void *)data->dev);
}
#endif
static void bmi08x_gyr_gpio_callback(const struct device *port, struct gpio_callback *cb,
uint32_t pin)
{
struct bmi08x_gyro_data *data = CONTAINER_OF(cb, struct bmi08x_gyro_data, gpio_cb);
ARG_UNUSED(port);
ARG_UNUSED(pin);
#if defined(CONFIG_BMI08X_GYRO_TRIGGER_OWN_THREAD)
k_sem_give(&data->sem);
#elif defined(CONFIG_BMI08X_GYRO_TRIGGER_GLOBAL_THREAD)
k_work_submit(&data->work);
#endif
}
int bmi08x_trigger_set_gyr(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
struct bmi08x_gyro_data *data = dev->data;
if ((trig->chan == SENSOR_CHAN_GYRO_XYZ) && (trig->type == SENSOR_TRIG_DATA_READY)) {
data->handler_drdy_gyr = handler;
data->drdy_trig_gyr = trig;
return 0;
}
return -ENOTSUP;
}
int bmi08x_gyr_trigger_mode_init(const struct device *dev)
{
struct bmi08x_gyro_data *data = dev->data;
const struct bmi08x_gyro_config *cfg = dev->config;
int ret;
if (!gpio_is_ready_dt(&cfg->int_gpio)) {
LOG_ERR("GPIO device not ready");
return -ENODEV;
}
#if defined(CONFIG_BMI08X_GYRO_TRIGGER_OWN_THREAD)
k_sem_init(&data->sem, 0, K_SEM_MAX_LIMIT);
k_thread_create(&data->thread, data->thread_stack,
CONFIG_BMI08X_GYRO_THREAD_STACK_SIZE, bmi08x_gyr_thread_main, (void *)dev,
NULL, NULL, K_PRIO_COOP(CONFIG_BMI08X_GYRO_THREAD_PRIORITY), 0, K_NO_WAIT);
#elif defined(CONFIG_BMI08X_GYRO_TRIGGER_GLOBAL_THREAD)
data->work.handler = bmi08x_gyr_work_handler;
data->dev = dev;
#endif
gpio_pin_configure_dt(&cfg->int_gpio, GPIO_INPUT);
gpio_init_callback(&data->gpio_cb, bmi08x_gyr_gpio_callback, BIT(cfg->int_gpio.pin));
ret = gpio_add_callback(cfg->int_gpio.port, &data->gpio_cb);
if (ret < 0) {
LOG_ERR("Failed to set gpio callback.");
return ret;
}
gpio_pin_interrupt_configure_dt(&cfg->int_gpio, GPIO_INT_EDGE_TO_ACTIVE);
return ret;
}

View file

@ -0,0 +1,8 @@
# Copyright (c) 2022 Meta Platforms, Inc. and its affiliates.
# SPDX-License-Identifier: Apache-2.0
description: BMI08X Accel inertial measurement unit
compatible: "bosch,bmi08x-accel"
include: [i2c-device.yaml, "bosch,bmi08x-accel.yaml"]

View file

@ -0,0 +1,8 @@
# Copyright (c) 2022 Meta Platforms, Inc. and its affiliates.
# SPDX-License-Identifier: Apache-2.0
description: BMI08X Accel inertial measurement unit
compatible: "bosch,bmi08x-accel"
include: [spi-device.yaml, "bosch,bmi08x-accel.yaml"]

View file

@ -0,0 +1,84 @@
# Copyright (c) 2022 Meta Platforms, Inc. and its affiliates.
# SPDX-License-Identifier: Apache-2.0
description: BMI08X Accel inertial measurement unit
include: sensor-device.yaml
properties:
int-gpios:
type: phandle-array
description: |
This property specifies the connection for INT, because the
Zephyr driver maps all interrupts to INT. The signal defaults
to output low when produced by the sensor.
int1-map-io:
type: int
description: |
Bit[0]: Map Interrupt A to INT1, Accel Data Ready
Bit[1]: Map Interrupt B to INT1
Bit[2]: Map Interrupt C to INT1
int2-map-io:
type: int
description: |
Bit[0]: Map Interrupt A to INT2, Accel Data Ready
Bit[1]: Map Interrupt B to INT2
Bit[2]: Map Interrupt C to INT2
int1-conf-io:
type: int
description: |
Bit[0]: reserved
Bit[1]: if set to 1, INT1 is active high, otherwise it's active low
Bit[2]: if set to 1, INT1 is open-drain, otherwise it's push-pull
Bit[3]: if set to 1, enable INT1 as an output pin
Bit[4]: if set to 1, enable INT1 as an input pin
Bit[7:5] : reserved
int2-conf-io:
type: int
description: |
Bit[0]: reserved
Bit[1]: if set to 1, INT2 is active high, otherwise it's active low
Bit[2]: if set to 1, INT2 is open-drain, otherwise it's push-pull
Bit[3]: if set to 1, enable INT2 as an output pin
Bit[4]: if set to 1, enable INT2 as an input pin
Bit[7:5] : reserved
accel-hz:
type: string
required: true
description: |
Default frequency of accelerometer. (Unit - Hz)
enum:
- "12.5"
- "25"
- "50"
- "100"
- "200"
- "400"
- "800"
- "1600"
accel-fs:
type: int
required: true
description: |
Default full scale of accelerometer. (Unit - g)
enum:
- 2
- 3
- 4
- 6
- 8
- 12
- 16
- 24
data-sync:
type: phandle
description: |
Enables data sync if defined. This is to point to the bmi08x-gyro definition
that is within the same IC as the bmi08x-accel.

View file

@ -0,0 +1,8 @@
# Copyright (c) 2022 Meta Platforms, Inc. and its affiliates.
# SPDX-License-Identifier: Apache-2.0
description: BMI08X Gyro inertial measurement unit
compatible: "bosch,bmi08x-gyro"
include: [i2c-device.yaml, "bosch,bmi08x-gyro.yaml"]

View file

@ -0,0 +1,8 @@
# Copyright (c) 2022 Meta Platforms, Inc. and its affiliates.
# SPDX-License-Identifier: Apache-2.0
description: BMI08X Gyro inertial measurement unit
compatible: "bosch,bmi08x-gyro"
include: [spi-device.yaml, "bosch,bmi08x-gyro.yaml"]

View file

@ -0,0 +1,62 @@
# Copyright (c) 2022 Meta Platforms, Inc. and its affiliates.
# SPDX-License-Identifier: Apache-2.0
description: BMI08X Gyro inertial measurement unit
include: sensor-device.yaml
properties:
int-gpios:
type: phandle-array
description: |
This property specifies the connection for INT, because the
Zephyr driver maps all interrupts to INT. The signal defaults
to output low when produced by the sensor.
int3-4-map-io:
type: int
description: |
Bit[0] will map the data ready interrupt on INT3
Bit[2] will map the fifo interrupt on INT3
Bit[5] will map the fifo interrupt on INT4
Bit[7] will enable the data ready interrupt on INT4
int3-4-conf-io:
type: int
description: |
Bit[0]: if set to 1, INT3 is active high, otherwise it's active low
Bit[1]: if set to 1, INT3 is open-drain, otherwise it's push-pull
Bit[2]: if set to 1, INT4 is active high, otherwise it's active low
Bit[3]: if set to 1, INT4 is open-drain, otherwise it's push-pull
gyro-hz:
type: string
required: true
description: |
Default frequency of accelerometer. (Unit - Hz)
enum:
- "2000_532"
- "2000_230"
- "1000_116"
- "400_47"
- "200_23"
- "100_12"
- "200_64"
- "100_32"
gyro-fs:
type: int
required: true
description: |
Default full scale of accelerometer. (Unit - g)
enum:
- 2000
- 1000
- 500
- 250
- 125
data-sync:
type: boolean
description: |
Enables data sync if defined. Must be set if bmi08x-accel data-sync is set as well.

View file

@ -745,3 +745,25 @@ test_tcn75a: tcn75a@73 {
reg = <0x73>;
alert-gpios = <&test_gpio 0 0>;
};
test_i2c_bmi08x_accel: bmi08x@74 {
compatible = "bosch,bmi08x-accel";
reg = <0x74>;
int-gpios = <&test_gpio 0 0>;
int1-map-io = <0x01>;
int2-map-io = <0x00>;
int1-conf-io = <0x0A>;
int2-conf-io = <0x17>;
accel-hz = "800";
accel-fs = <4>;
};
test_i2c_bmi08x_gyro: bmi08x@75 {
compatible = "bosch,bmi08x-gyro";
reg = <0x75>;
int-gpios = <&test_gpio 0 0>;
int3-4-map-io = <0x01>;
int3-4-conf-io = <0x01>;
gyro-hz = "1000_116";
gyro-fs = <1000>;
};

View file

@ -6,6 +6,8 @@ CONFIG_AMG88XX_TRIGGER_GLOBAL_THREAD=y
CONFIG_APDS9960_TRIGGER_GLOBAL_THREAD=y
CONFIG_BMA280_TRIGGER_GLOBAL_THREAD=y
CONFIG_BMG160_TRIGGER_GLOBAL_THREAD=y
CONFIG_BMI08X_ACCEL_TRIGGER_GLOBAL_THREAD=y
CONFIG_BMI08X_GYRO_TRIGGER_GLOBAL_THREAD=y
CONFIG_BMI160_TRIGGER_GLOBAL_THREAD=y
CONFIG_BMI270_TRIGGER_GLOBAL_THREAD=y
CONFIG_BMP388_TRIGGER_GLOBAL_THREAD=y

View file

@ -6,6 +6,8 @@ CONFIG_AMG88XX_TRIGGER_NONE=y
CONFIG_APDS9960_TRIGGER_NONE=y
CONFIG_BMA280_TRIGGER_NONE=y
CONFIG_BMG160_TRIGGER_NONE=y
CONFIG_BMI08X_ACCEL_TRIGGER_NONE=y
CONFIG_BMI08X_GYRO_TRIGGER_NONE=y
CONFIG_BMI160_TRIGGER_NONE=y
CONFIG_BMI270_TRIGGER_NONE=y
CONFIG_BMP388_TRIGGER_NONE=y

View file

@ -5,6 +5,8 @@ CONFIG_ADXL372_TRIGGER_OWN_THREAD=y
CONFIG_AMG88XX_TRIGGER_OWN_THREAD=y
CONFIG_BMA280_TRIGGER_OWN_THREAD=y
CONFIG_BMG160_TRIGGER_OWN_THREAD=y
CONFIG_BMI08X_ACCEL_TRIGGER_OWN_THREAD=y
CONFIG_BMI08X_GYRO_TRIGGER_OWN_THREAD=y
CONFIG_BMI160_TRIGGER_OWN_THREAD=y
CONFIG_BMI270_TRIGGER_OWN_THREAD=y
CONFIG_BMP388_TRIGGER_OWN_THREAD=y

View file

@ -395,3 +395,27 @@ test_spi_lsm6dsv16x: lsm6dsv16x@30 {
spi-max-frequency = <0>;
irq-gpios = <&test_gpio 0 0>;
};
test_spi_bmi08x_accel: bmi08x@31 {
compatible = "bosch,bmi08x-accel";
reg = <0x31>;
spi-max-frequency = <0>;
int-gpios = <&test_gpio 0 0>;
int1-map-io = <0x01>;
int2-map-io = <0x00>;
int1-conf-io = <0x0A>;
int2-conf-io = <0x17>;
accel-hz = "800";
accel-fs = <4>;
};
test_spi_bmi08x_gyro: bmi08x@32 {
compatible = "bosch,bmi08x-gyro";
reg = <0x32>;
spi-max-frequency = <0>;
int-gpios = <&test_gpio 0 0>;
int3-4-map-io = <0x01>;
int3-4-conf-io = <0x01>;
gyro-hz = "1000_116";
gyro-fs = <1000>;
};