From 5d9adcd918ff6c4b852d6821d5154dec99e9ff26 Mon Sep 17 00:00:00 2001 From: Maximilian Deubel Date: Fri, 24 Jan 2025 13:39:56 +0100 Subject: [PATCH] drivers: bmm350 Add release-ready BMM350. This driver will be added to Zephyr soon, but with this, customers can already try it out on their Thingy:91 X devices. Signed-off-by: Maximilian Deubel --- drivers/sensor/CMakeLists.txt | 1 + drivers/sensor/Kconfig | 1 + drivers/sensor/bmm350/CMakeLists.txt | 8 + drivers/sensor/bmm350/Kconfig | 60 ++ drivers/sensor/bmm350/bmm350.c | 969 ++++++++++++++++++ drivers/sensor/bmm350/bmm350.h | 544 ++++++++++ drivers/sensor/bmm350/bmm350_i2c.c | 33 + drivers/sensor/bmm350/bmm350_trigger.c | 169 +++ dts/bindings/sensor/bosch,bmm350-i2c.yaml | 11 + dts/bindings/sensor/bosch,bmm350.yaml | 12 + samples/sensor/bmm350/CMakeLists.txt | 12 + samples/sensor/bmm350/README.rst | 69 ++ samples/sensor/bmm350/app.overlay | 20 + .../boards/thingy91x_nrf9151_ns.overlay | 3 + samples/sensor/bmm350/prj.conf | 7 + samples/sensor/bmm350/sample.yaml | 9 + samples/sensor/bmm350/src/main.c | 90 ++ 17 files changed, 2018 insertions(+) create mode 100644 drivers/sensor/bmm350/CMakeLists.txt create mode 100644 drivers/sensor/bmm350/Kconfig create mode 100644 drivers/sensor/bmm350/bmm350.c create mode 100644 drivers/sensor/bmm350/bmm350.h create mode 100644 drivers/sensor/bmm350/bmm350_i2c.c create mode 100644 drivers/sensor/bmm350/bmm350_trigger.c create mode 100644 dts/bindings/sensor/bosch,bmm350-i2c.yaml create mode 100644 dts/bindings/sensor/bosch,bmm350.yaml create mode 100644 samples/sensor/bmm350/CMakeLists.txt create mode 100644 samples/sensor/bmm350/README.rst create mode 100644 samples/sensor/bmm350/app.overlay create mode 100644 samples/sensor/bmm350/boards/thingy91x_nrf9151_ns.overlay create mode 100644 samples/sensor/bmm350/prj.conf create mode 100644 samples/sensor/bmm350/sample.yaml create mode 100644 samples/sensor/bmm350/src/main.c diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index 7bb0c4eaea7e..7df2b44c3052 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory_ifdef(CONFIG_SENSOR_STUB sensor_stub) add_subdirectory_ifdef(CONFIG_PMW3360 pmw3360) add_subdirectory_ifdef(CONFIG_PAW3212 paw3212) add_subdirectory_ifdef(CONFIG_BME68X_IAQ bme68x_iaq) +add_subdirectory_ifdef(CONFIG_BMM350 bmm350) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index f4bcb7038ac2..c574e12f9611 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -17,5 +17,6 @@ rsource "sensor_stub/Kconfig" rsource "pmw3360/Kconfig" rsource "paw3212/Kconfig" rsource "bme68x_iaq/Kconfig" +rsource "bmm350/Kconfig" endif # SENSOR diff --git a/drivers/sensor/bmm350/CMakeLists.txt b/drivers/sensor/bmm350/CMakeLists.txt new file mode 100644 index 000000000000..23ab34009ff8 --- /dev/null +++ b/drivers/sensor/bmm350/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Bosch Sensortec GmbH + +# SPDX-License-Identifier: Apache-2.0 + + +zephyr_library() +zephyr_library_sources(bmm350.c bmm350_i2c.c) +zephyr_library_sources_ifdef(CONFIG_BMM350_TRIGGER bmm350_trigger.c) diff --git a/drivers/sensor/bmm350/Kconfig b/drivers/sensor/bmm350/Kconfig new file mode 100644 index 000000000000..7ec3224552c4 --- /dev/null +++ b/drivers/sensor/bmm350/Kconfig @@ -0,0 +1,60 @@ +# BMM350 Geomagnetic sensor configuration options + +# Copyright (c) 2024 Bosch Sensortec GmbH + +# SPDX-License-Identifier: Apache-2.0 + + +menuconfig BMM350 + bool "BMM350 I2C Geomagnetic Chip" + default y + depends on DT_HAS_BOSCH_BMM350_ENABLED + select I2C + help + Enable driver for BMM350 I2C-based Geomagnetic sensor. +if BMM350 + +choice BMM350_TRIGGER_MODE + prompt "Trigger mode" + default BMM350_TRIGGER_NONE + help + Specify the type of triggering to be used by the driver. + +config BMM350_TRIGGER_NONE + bool "No trigger" + +config BMM350_TRIGGER_GLOBAL_THREAD + bool "Use global thread" + select BMM350_TRIGGER + +config BMM350_TRIGGER_OWN_THREAD + bool "Use own thread" + select BMM350_TRIGGER + +config BMM350_TRIGGER_DIRECT + bool "Use IRQ handler" + select BMM350_TRIGGER +endchoice + +config BMM350_TRIGGER + bool + +config BMM350_SAMPLING_RATE_RUNTIME + bool "Dynamic sampling rate" + help + Enable alteration of sampling rate attribute at runtime. +config BMM350_THREAD_PRIORITY + int "Own thread priority" + depends on BMM350_TRIGGER_OWN_THREAD + default 10 + help + Priority of the thread used by the driver to handle interrupts. + +config BMM350_THREAD_STACK_SIZE + int "Own thread stack size" + depends on BMM350_TRIGGER_OWN_THREAD + default 1024 + help + Stack size of thread used by the driver to handle interrupts. + +endif # BMM350 diff --git a/drivers/sensor/bmm350/bmm350.c b/drivers/sensor/bmm350/bmm350.c new file mode 100644 index 000000000000..83340ed430b2 --- /dev/null +++ b/drivers/sensor/bmm350/bmm350.c @@ -0,0 +1,969 @@ +/* + * Copyright (c) 2024 Bosch Sensortec GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Bus-specific functionality for BMM350s accessed via I2C. + * version 1.0.0 + */ + +#include +#include "bmm350.h" +/*lint -e10 -e551 -e752 -e413*/ +LOG_MODULE_REGISTER(BMM350, CONFIG_SENSOR_LOG_LEVEL); +/*lint -e10 -e551 -e752 -e413*/ + +static inline int bmm350_bus_check(const struct device *dev) +{ + const struct bmm350_config *cfg = dev->config; + + return cfg->bus_io->check(&cfg->bus); +} + +static inline int bmm350_reg_read(const struct device *dev, + uint8_t start, uint8_t *buf, int size) +{ + const struct bmm350_config *cfg = dev->config; + + return cfg->bus_io->read(&cfg->bus, start, buf, size); +} + +int bmm350_reg_write(const struct device *dev, uint8_t reg, + uint8_t val) +{ + const struct bmm350_config *cfg = dev->config; + + return cfg->bus_io->write(&cfg->bus, reg, val); +} + +static int8_t bmm350_read_otp_word(const struct device *dev, uint8_t addr, uint16_t *lsb_msb) +{ + int8_t ret = 0; + uint8_t tx_buf = 0; + uint8_t rx_buf[3] = {0x00}; + uint8_t otp_status = 0; + uint8_t otp_err = BMM350_OTP_STATUS_NO_ERROR; + uint8_t lsb = 0, msb = 0; + + if (lsb_msb) { + /* Set OTP command at specified address */ + tx_buf = BMM350_OTP_CMD_DIR_READ | (addr & BMM350_OTP_WORD_ADDR_MSK); + ret = bmm350_reg_write(dev, BMM350_REG_OTP_CMD_REG, tx_buf); + if (ret) { + LOG_ERR("i2c xfer failed! read addr = 0x%02x, ret = %d\n", tx_buf, ret); + return ret; + } + + do { + /* Get OTP status */ + ret += bmm350_reg_read(dev, BMM350_REG_OTP_STATUS_REG, &rx_buf[0], 3); + otp_status = rx_buf[2]; + otp_err = BMM350_OTP_STATUS_ERROR(otp_status); + if (otp_err != BMM350_OTP_STATUS_NO_ERROR) { + break; + } + } while ((!(otp_status & BMM350_OTP_STATUS_CMD_DONE)) && (ret == BMM350_OK)); + + if (otp_err != BMM350_OTP_STATUS_NO_ERROR) + { + switch (otp_err) + { + case BMM350_OTP_STATUS_BOOT_ERR: + ret = BMM350_E_OTP_BOOT; + break; + case BMM350_OTP_STATUS_PAGE_RD_ERR: + ret = BMM350_E_OTP_PAGE_RD; + break; + case BMM350_OTP_STATUS_PAGE_PRG_ERR: + ret = BMM350_E_OTP_PAGE_PRG; + break; + case BMM350_OTP_STATUS_SIGN_ERR: + ret = BMM350_E_OTP_SIGN; + break; + case BMM350_OTP_STATUS_INV_CMD_ERR: + ret = BMM350_E_OTP_INV_CMD; + break; + default: + ret = BMM350_E_OTP_UNDEFINED; + break; + } + return ret; + } + + /* Get OTP L/MSB data */ + ret += bmm350_reg_read(dev, BMM350_REG_OTP_DATA_MSB_REG, &rx_buf[0], 3); + msb = rx_buf[2]; + ret += bmm350_reg_read(dev, BMM350_REG_OTP_DATA_LSB_REG, &rx_buf[0], 3); + lsb = rx_buf[2]; + *lsb_msb = ((uint16_t)(msb << 8) | lsb) & 0xFFFF; + } + + return ret; +} + +static int32_t fix_sign(uint32_t inval, int8_t number_of_bits) +{ + int32_t ret = 0; + int32_t power = 0; + + switch ((enum bmm350_signed_bit)number_of_bits) { + case BMM350_SIGNED_8_BIT: + power = 128; /* 2^7 */ + break; + case BMM350_SIGNED_12_BIT: + power = 2048; /* 2^11 */ + break; + case BMM350_SIGNED_16_BIT: + power = 32768; /* 2^15 */ + break; + case BMM350_SIGNED_21_BIT: + power = 1048576; /* 2^20 */ + break; + case BMM350_SIGNED_24_BIT: + power = 8388608; /* 2^23 */ + break; + default: + power = 0; + break; + } + + ret = (int32_t)inval; + if (ret >= power) { + ret = ret - (power * 2); + } + + return ret; +} + +static void bmm350_update_mag_off_sens(struct bmm350_data *data) +{ + uint16_t off_x_lsb_msb, off_y_lsb_msb, off_z_lsb_msb, t_off = 0; + uint8_t sens_x, sens_y, sens_z, t_sens = 0; + uint8_t tco_x, tco_y, tco_z = 0; + uint8_t tcs_x, tcs_y, tcs_z = 0; + uint8_t cross_x_y, cross_y_x, cross_z_x, cross_z_y = 0; + + off_x_lsb_msb = data->otp_data[BMM350_MAG_OFFSET_X] & 0x0FFF; + off_y_lsb_msb = ((data->otp_data[BMM350_MAG_OFFSET_X] & 0xF000) >> 4) + + (data->otp_data[BMM350_MAG_OFFSET_Y] & BMM350_LSB_MASK); + off_z_lsb_msb = (data->otp_data[BMM350_MAG_OFFSET_Y] & 0x0F00) + + (data->otp_data[BMM350_MAG_OFFSET_Z] & BMM350_LSB_MASK); + t_off = data->otp_data[BMM350_TEMP_OFF_SENS] & BMM350_LSB_MASK; + +#ifdef BMM350_USE_FIXED_POINT + data->mag_comp.dut_offset_coef.offset_x = fix_sign(off_x_lsb_msb, BMM350_SIGNED_12_BIT); + data->mag_comp.dut_offset_coef.offset_y = fix_sign(off_y_lsb_msb, BMM350_SIGNED_12_BIT); + data->mag_comp.dut_offset_coef.offset_z = fix_sign(off_z_lsb_msb, BMM350_SIGNED_12_BIT); + data->mag_comp.dut_offset_coef.t_offs = fix_sign(t_off, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.dut_offset_coef.t_offs = data->mag_comp.dut_offset_coef.t_offs / 5; +#endif + + sens_x = (data->otp_data[BMM350_MAG_SENS_X] & BMM350_MSB_MASK) >> 8; + sens_y = (data->otp_data[BMM350_MAG_SENS_Y] & BMM350_LSB_MASK); + sens_z = (data->otp_data[BMM350_MAG_SENS_Z] & BMM350_MSB_MASK) >> 8; + t_sens = (data->otp_data[BMM350_TEMP_OFF_SENS] & BMM350_MSB_MASK) >> 8; + +#ifdef BMM350_USE_FIXED_POINT + data->mag_comp.dut_sensit_coef.sens_x = fix_sign(sens_x, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.dut_sensit_coef.sens_y = fix_sign(sens_y, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.dut_sensit_coef.sens_z = fix_sign(sens_z, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.dut_sensit_coef.t_sens = fix_sign(t_sens, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + + data->mag_comp.dut_sensit_coef.sens_x = (data->mag_comp.dut_sensit_coef.sens_x / 256); + data->mag_comp.dut_sensit_coef.sens_y = (data->mag_comp.dut_sensit_coef.sens_y / 256); + data->mag_comp.dut_sensit_coef.sens_z = (data->mag_comp.dut_sensit_coef.sens_z / 256); + data->mag_comp.dut_sensit_coef.t_sens = (data->mag_comp.dut_sensit_coef.t_sens / 512); +#endif + + tco_x = (data->otp_data[BMM350_MAG_TCO_X] & BMM350_LSB_MASK); + tco_y = (data->otp_data[BMM350_MAG_TCO_Y] & BMM350_LSB_MASK); + tco_z = (data->otp_data[BMM350_MAG_TCO_Z] & BMM350_LSB_MASK); + +#ifdef BMM350_USE_FIXED_POINT + data->mag_comp.dut_tco.tco_x = fix_sign(tco_x, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.dut_tco.tco_y = fix_sign(tco_y, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.dut_tco.tco_z = fix_sign(tco_z, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + + data->mag_comp.dut_tco.tco_x = (data->mag_comp.dut_tco.tco_x / 32); + data->mag_comp.dut_tco.tco_y = (data->mag_comp.dut_tco.tco_y / 32); + data->mag_comp.dut_tco.tco_z = (data->mag_comp.dut_tco.tco_z / 32); +#endif + + tcs_x = (data->otp_data[BMM350_MAG_TCS_X] & BMM350_MSB_MASK) >> 8; + tcs_y = (data->otp_data[BMM350_MAG_TCS_Y] & BMM350_MSB_MASK) >> 8; + tcs_z = (data->otp_data[BMM350_MAG_TCS_Z] & BMM350_MSB_MASK) >> 8; + +#ifdef BMM350_USE_FIXED_POINT + data->mag_comp.dut_tcs.tcs_x = fix_sign(tcs_x, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.dut_tcs.tcs_y = fix_sign(tcs_y, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.dut_tcs.tcs_z = fix_sign(tcs_z, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + + data->mag_comp.dut_tcs.tcs_x = (data->mag_comp.dut_tcs.tcs_x / 16384); + data->mag_comp.dut_tcs.tcs_y = (data->mag_comp.dut_tcs.tcs_y / 16384); + data->mag_comp.dut_tcs.tcs_z = (data->mag_comp.dut_tcs.tcs_z / 16384); +#endif + +#ifdef BMM350_USE_FIXED_POINT + data->mag_comp.dut_t0 = (fix_sign(data->otp_data[BMM350_MAG_DUT_T_0], BMM350_SIGNED_16_BIT) / 512) + 23; +#endif + + cross_x_y = (data->otp_data[BMM350_CROSS_X_Y] & BMM350_LSB_MASK); + cross_y_x = (data->otp_data[BMM350_CROSS_Y_X] & BMM350_MSB_MASK) >> 8; + cross_z_x = (data->otp_data[BMM350_CROSS_Z_X] & BMM350_LSB_MASK); + cross_z_y = (data->otp_data[BMM350_CROSS_Z_Y] & BMM350_MSB_MASK) >> 8; + +#ifdef BMM350_USE_FIXED_POINT + data->mag_comp.cross_axis.cross_x_y = fix_sign(cross_x_y, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.cross_axis.cross_y_x = fix_sign(cross_y_x, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.cross_axis.cross_z_x = fix_sign(cross_z_x, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + data->mag_comp.cross_axis.cross_z_y = fix_sign(cross_z_y, BMM350_SIGNED_8_BIT) * BMM350_MAG_COMP_COEFF_SCALING; + + data->mag_comp.cross_axis.cross_x_y = (data->mag_comp.cross_axis.cross_x_y / 800); + data->mag_comp.cross_axis.cross_y_x = (data->mag_comp.cross_axis.cross_y_x / 800); + data->mag_comp.cross_axis.cross_z_x = (data->mag_comp.cross_axis.cross_z_x / 800); + data->mag_comp.cross_axis.cross_z_y = (data->mag_comp.cross_axis.cross_z_y / 800); +#endif +} + + + +static int bmm350_otp_dump_after_boot(const struct device *dev) +{ + struct bmm350_data *data = dev->data; + int ret = 0; + uint16_t otp_word = 0; + + for (uint8_t idx = 0; idx < BMM350_OTP_DATA_LENGTH; idx++) { + ret = bmm350_read_otp_word(dev, idx, &otp_word); + data->otp_data[idx] = otp_word; + } + + data->var_id = (data->otp_data[30] & 0x7f00) >> 9; + /* Set the default auto bit reset configuration */ + data->enable_auto_br = ((data->var_id > BMM350_CURRENT_SHUTTLE_VARIANT_ID) ? BMM350_DISABLE : BMM350_ENABLE); + + LOG_DBG("bmm350 Find the var id %d\n", data->var_id); + /* Update magnetometer offset and sensitivity data. */ + bmm350_update_mag_off_sens(data); + + if (ret) { + LOG_ERR("i2c xfer failed, ret = %d\n", ret); + } + + return ret; +} + +/*! + * @brief This API gets the PMU command status 0 value + */ +static int8_t bmm350_get_pmu_cmd_status_0( + struct bmm350_pmu_cmd_status_0 *pmu_cmd_stat_0, const struct device *dev) +{ + /* Variable to store the function result */ + int8_t ret; + uint8_t rx_buf[3] = {0x00}; + + if (pmu_cmd_stat_0 != NULL) { + /* Get PMU command status 0 data */ + ret = bmm350_reg_read(dev, BMM350_REG_PMU_CMD_STATUS_0, &rx_buf[0], 3); + LOG_DBG("pmu cmd status 0:0x%x\n", rx_buf[2]); + if (ret == BMM350_OK) { + pmu_cmd_stat_0->pmu_cmd_busy = BMM350_GET_BITS_POS_0(rx_buf[2], BMM350_PMU_CMD_BUSY); + pmu_cmd_stat_0->odr_ovwr = BMM350_GET_BITS(rx_buf[2], BMM350_ODR_OVWR); + pmu_cmd_stat_0->avr_ovwr = BMM350_GET_BITS(rx_buf[2], BMM350_AVG_OVWR); + pmu_cmd_stat_0->pwr_mode_is_normal = BMM350_GET_BITS(rx_buf[2], BMM350_PWR_MODE_IS_NORMAL); + pmu_cmd_stat_0->cmd_is_illegal = BMM350_GET_BITS(rx_buf[2], BMM350_CMD_IS_ILLEGAL); + pmu_cmd_stat_0->pmu_cmd_value = BMM350_GET_BITS(rx_buf[2], BMM350_PMU_CMD_VALUE); + } + } + else { + ret = BMM350_E_NULL_PTR; + } + return ret; +} + +/*! + * @brief This internal API is used to switch from suspend mode to normal mode or forced mode. + */ +static int8_t set_powermode(enum bmm350_power_modes powermode, const struct device *dev) +{ + /* Variable to store the function result */ + int8_t ret = 0; + uint8_t rx_buf[3] = {0x00}; + uint8_t reg_data = powermode; + + /* Array to store suspend to forced mode delay */ + uint32_t sus_to_forced_mode[4] = { + BMM350_SUS_TO_FORCEDMODE_NO_AVG_DELAY, BMM350_SUS_TO_FORCEDMODE_AVG_2_DELAY, BMM350_SUS_TO_FORCEDMODE_AVG_4_DELAY, + BMM350_SUS_TO_FORCEDMODE_AVG_8_DELAY }; + + /* Array to store suspend to forced mode fast delay */ + uint32_t sus_to_forced_mode_fast[4] = { + BMM350_SUS_TO_FORCEDMODE_FAST_NO_AVG_DELAY, BMM350_SUS_TO_FORCEDMODE_FAST_AVG_2_DELAY, + BMM350_SUS_TO_FORCEDMODE_FAST_AVG_4_DELAY, BMM350_SUS_TO_FORCEDMODE_FAST_AVG_8_DELAY }; + + uint8_t avg = 0; + uint32_t delay_us = 0; + + if (ret == BMM350_OK) { + /* Get average configuration */ + ret = bmm350_reg_read(dev, BMM350_REG_PMU_CMD_AGGR_SET, &rx_buf[0], 3); + /* Mask the average value */ + avg = ((rx_buf[2] & BMM350_AVG_MSK) >> BMM350_AVG_POS); + if (ret == BMM350_OK) { + /* Get average configuration */ + if (powermode == BMM350_NORMAL_MODE) { + delay_us = BMM350_SUSPEND_TO_NORMAL_DELAY; + } + + /* Check if desired power mode is forced mode */ + if (powermode == BMM350_FORCED_MODE) { + /* Store delay based on averaging mode */ + delay_us = sus_to_forced_mode[avg]; + } + + /* Check if desired power mode is forced mode fast */ + if (powermode == BMM350_FORCED_MODE_FAST) { + /* Store delay based on averaging mode */ + delay_us = sus_to_forced_mode_fast[avg]; + } + + /* Set PMU command configuration to desired power mode */ + ret = bmm350_reg_write(dev, BMM350_REG_PMU_CMD, reg_data); + k_usleep(delay_us); + + } + } + + LOG_DBG("pmu cmd agget set powermode %d\n", powermode); + + return ret; +} + +/*! + * @brief This API is used to set the power mode of the sensor + */ +static int8_t bmm350_set_powermode(enum bmm350_power_modes powermode, const struct device *dev) +{ + /* Variable to store the function result */ + int8_t ret = 0; + uint8_t rx_buf[3] = {0x00}; + + if (ret == BMM350_OK) { + ret = bmm350_reg_read(dev, BMM350_REG_PMU_CMD, &rx_buf[0], 3); + if (ret == BMM350_OK) { + if (rx_buf[2] > BMM350_PMU_CMD_NM_TC) { + ret = BMM350_E_INVALID_CONFIG; + } + + if ((ret == BMM350_OK) && + ((rx_buf[2] == BMM350_PMU_CMD_NM) || (rx_buf[2] == BMM350_PMU_CMD_UPD_OAE))) { + /* Set PMU command configuration */ + ret = bmm350_reg_write(dev, BMM350_REG_PMU_CMD, BMM350_PMU_CMD_SUS); + } + + if (ret == BMM350_OK) { + ret = set_powermode(powermode, dev); + } + } + } + + return ret; +} + +/*! + * used to perform the magnetic reset of the sensor + * which is necessary after a field shock ( 400mT field applied to sensor ) + */ +static int8_t bmm350_magnetic_reset_and_wait(const struct device *dev) +{ + /* Variable to store the function result */ + int8_t ret = 0; + struct bmm350_pmu_cmd_status_0 pmu_cmd_stat_0 = { 0 }; + uint8_t restore_normal = BMM350_DISABLE; + + /* Read PMU CMD status */ + ret = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); + LOG_DBG("get status result 0:%d\n", ret); + + /* Check the powermode is normal before performing magnetic reset */ + if ((ret == BMM350_OK) && (pmu_cmd_stat_0.pwr_mode_is_normal == BMM350_ENABLE)) { + restore_normal = BMM350_ENABLE; + /* Reset can only be triggered in suspend */ + ret = bmm350_set_powermode(BMM350_SUSPEND_MODE, dev); + LOG_DBG("set power mode 0:%d\n", ret); + } + if (ret == BMM350_OK) { + /* Set BR to PMU_CMD register */ + ret = bmm350_reg_write(dev, BMM350_REG_PMU_CMD, BMM350_PMU_CMD_BR); + k_usleep(BMM350_BR_DELAY); + } + + if (ret == BMM350_OK) { + /* Verify if PMU_CMD_STATUS_0 register has BR set */ + ret = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); + LOG_DBG("get status result 1:%d\n", ret); + if ((ret == BMM350_OK) && (pmu_cmd_stat_0.pmu_cmd_value != BMM350_PMU_CMD_STATUS_0_BR)) { + ret = BMM350_E_PMU_CMD_VALUE; + } + } + + if (ret == BMM350_OK) { + /* Set FGR to PMU_CMD register */ + ret = bmm350_reg_write(dev, BMM350_REG_PMU_CMD, BMM350_PMU_CMD_FGR); + k_usleep(BMM350_FGR_DELAY); + } + if (ret == BMM350_OK) { + /* Verify if PMU_CMD_STATUS_0 register has FGR set */ + ret = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); + LOG_DBG("get status result 2:%d\n", ret); + + if ((ret == BMM350_OK) && (pmu_cmd_stat_0.pmu_cmd_value != BMM350_PMU_CMD_STATUS_0_FGR)) + ret = BMM350_E_PMU_CMD_VALUE; + } + + if ((ret == BMM350_OK) && (restore_normal == BMM350_ENABLE)) { + ret = bmm350_set_powermode(BMM350_NORMAL_MODE, dev); + LOG_DBG("set power mode 1:%d\n", ret); + } else { + if (ret == BMM350_OK) { + /* Reset PMU_CMD register */ + ret =bmm350_reg_write(dev, BMM350_REG_PMU_CMD, 0x00); + } + } + return ret; +} +/*! + * @brief This API is used to read uncompensated mag and temperature data. + */ +int8_t bmm350_read_uncomp_mag_temp_data(struct bmm350_raw_mag_data *raw_data, const struct device *dev) +{ + struct bmm350_data *data = dev->data; + int8_t rslt = BMM350_OK; + uint8_t mag_data[14] = { 0 }; + uint32_t raw_mag_x, raw_mag_y, raw_mag_z, raw_temp; + + if (raw_data != NULL) { + /* Get uncompensated mag data */ + rslt = bmm350_reg_read(dev, BMM350_REG_MAG_X_XLSB, mag_data, 14); + + if (rslt == BMM350_OK) { + raw_mag_x = (uint32_t)mag_data[2] + ((uint32_t)mag_data[3] << 8) + ((uint32_t)mag_data[4] << 16); + raw_mag_y = (uint32_t)mag_data[5] + ((uint32_t)mag_data[6] << 8) + ((uint32_t)mag_data[7] << 16); + raw_mag_z = (uint32_t)mag_data[8] + ((uint32_t)mag_data[9] << 8) + ((uint32_t)mag_data[10] << 16); + raw_temp = (uint32_t)mag_data[11] + ((uint32_t)mag_data[12] << 8) + ((uint32_t)mag_data[13] << 16); + + if ((data->axis_en & BMM350_EN_X_MSK) == BMM350_DISABLE) { + raw_data->raw_xdata = BMM350_DISABLE; + } + else { + raw_data->raw_xdata = fix_sign(raw_mag_x, BMM350_SIGNED_24_BIT); + } + + if ((data->axis_en & BMM350_EN_Y_MSK) == BMM350_DISABLE) { + raw_data->raw_ydata = BMM350_DISABLE; + } + else { + raw_data->raw_ydata = fix_sign(raw_mag_y, BMM350_SIGNED_24_BIT); + } + + if ((data->axis_en & BMM350_EN_Z_MSK) == BMM350_DISABLE) { + raw_data->raw_zdata = BMM350_DISABLE; + } + else { + raw_data->raw_zdata = fix_sign(raw_mag_z, BMM350_SIGNED_24_BIT); + } + + raw_data->raw_data_t = fix_sign(raw_temp, BMM350_SIGNED_24_BIT); + } + } + else { + rslt = BMM350_E_NULL_PTR; + } + + return rslt; +} +static int8_t read_out_raw_data(int32_t *out_data, const struct device *dev) +{ + int8_t rslt = BMM350_OK; + int32_t temp = 0; + struct bmm350_raw_mag_data raw_data = { 0 }; + + if (out_data != NULL) { + rslt = bmm350_read_uncomp_mag_temp_data(&raw_data, dev); + + if (rslt == BMM350_OK) { + /* Convert mag lsb to uT and temp lsb to degC */ + out_data[0] = ((raw_data.raw_xdata * BMM350_LSB_TO_UT_XY_COEFF) / BMM350_LSB_TO_UT_COEFF_DIV); + out_data[1] = ((raw_data.raw_ydata * BMM350_LSB_TO_UT_XY_COEFF) / BMM350_LSB_TO_UT_COEFF_DIV); + out_data[2] = ((raw_data.raw_zdata * BMM350_LSB_TO_UT_Z_COEFF) / BMM350_LSB_TO_UT_COEFF_DIV); + out_data[3] = ((raw_data.raw_data_t * BMM350_LSB_TO_UT_TEMP_COEFF) / BMM350_LSB_TO_UT_COEFF_DIV); + + if (out_data[3] > 0) + temp = (out_data[3] - (2549 / 100)); + else if (out_data[3] < 0) + temp = (out_data[3] + (2549 / 100)); + else + temp = out_data[3]; + + out_data[3] = temp; + } + } + else { + rslt = BMM350_E_NULL_PTR; + } + + return rslt; +} + + +int8_t bmm350_get_compensated_mag_xyz_temp_data_fixed( + struct bmm350_mag_temp_data *mag_temp_data, + const struct device *dev) +{ + struct bmm350_data *data = dev->data; + int8_t rslt = BMM350_OK; + uint8_t indx; + int32_t out_data[4] = { 0 }; + int32_t dut_offset_coef[3], dut_sensit_coef[3], dut_tco[3], dut_tcs[3]; + int32_t cr_ax_comp_x, cr_ax_comp_y, cr_ax_comp_z; + + if (mag_temp_data != NULL) { + /* Reads raw magnetic x,y and z axis along with temperature */ + rslt = read_out_raw_data(out_data, dev); + + if (rslt == BMM350_OK) { + /* Apply compensation to temperature reading */ + out_data[3] = + (((BMM350_MAG_COMP_COEFF_SCALING + data->mag_comp.dut_sensit_coef.t_sens) * out_data[3]) + + data->mag_comp.dut_offset_coef.t_offs) / BMM350_MAG_COMP_COEFF_SCALING; + + /* Store magnetic compensation structure to an array */ + dut_offset_coef[0] = data->mag_comp.dut_offset_coef.offset_x; + dut_offset_coef[1] = data->mag_comp.dut_offset_coef.offset_y; + dut_offset_coef[2] = data->mag_comp.dut_offset_coef.offset_z; + + dut_sensit_coef[0] = data->mag_comp.dut_sensit_coef.sens_x; + dut_sensit_coef[1] = data->mag_comp.dut_sensit_coef.sens_y; + dut_sensit_coef[2] = data->mag_comp.dut_sensit_coef.sens_z; + + dut_tco[0] = data->mag_comp.dut_tco.tco_x; + dut_tco[1] = data->mag_comp.dut_tco.tco_y; + dut_tco[2] = data->mag_comp.dut_tco.tco_z; + + dut_tcs[0] = data->mag_comp.dut_tcs.tcs_x; + dut_tcs[1] = data->mag_comp.dut_tcs.tcs_y; + dut_tcs[2] = data->mag_comp.dut_tcs.tcs_z; + + /* Compensate raw magnetic data */ + for (indx = 0; indx < 3; indx++) { + out_data[indx] = (out_data[indx] * (BMM350_MAG_COMP_COEFF_SCALING + dut_sensit_coef[indx])) / + BMM350_MAG_COMP_COEFF_SCALING; + out_data[indx] = (out_data[indx] + dut_offset_coef[indx]); + out_data[indx] = + ((out_data[indx] * BMM350_MAG_COMP_COEFF_SCALING) + + (dut_tco[indx] * (out_data[3] - data->mag_comp.dut_t0))) / BMM350_MAG_COMP_COEFF_SCALING; + out_data[indx] = (out_data[indx] * BMM350_MAG_COMP_COEFF_SCALING) / + (BMM350_MAG_COMP_COEFF_SCALING + + (dut_tcs[indx] * (out_data[3] - data->mag_comp.dut_t0))); + } + + cr_ax_comp_x = + ((((out_data[0] * BMM350_MAG_COMP_COEFF_SCALING) - (data->mag_comp.cross_axis.cross_x_y * out_data[1])) * + BMM350_MAG_COMP_COEFF_SCALING) / + ((BMM350_MAG_COMP_COEFF_SCALING * BMM350_MAG_COMP_COEFF_SCALING) - + (data->mag_comp.cross_axis.cross_y_x * data->mag_comp.cross_axis.cross_x_y))); + + cr_ax_comp_y = + ((((out_data[1] * BMM350_MAG_COMP_COEFF_SCALING) - (data->mag_comp.cross_axis.cross_y_x * out_data[0])) * + BMM350_MAG_COMP_COEFF_SCALING) / + ((BMM350_MAG_COMP_COEFF_SCALING * BMM350_MAG_COMP_COEFF_SCALING) - + (data->mag_comp.cross_axis.cross_y_x * data->mag_comp.cross_axis.cross_x_y))); + + cr_ax_comp_z = + (out_data[2] + + (((out_data[0] * + ((data->mag_comp.cross_axis.cross_y_x * data->mag_comp.cross_axis.cross_z_y) - + (data->mag_comp.cross_axis.cross_z_x * BMM350_MAG_COMP_COEFF_SCALING))) - + (out_data[1] * + ((data->mag_comp.cross_axis.cross_z_y * BMM350_MAG_COMP_COEFF_SCALING) - + (data->mag_comp.cross_axis.cross_x_y * data->mag_comp.cross_axis.cross_z_x))))) / + (((BMM350_MAG_COMP_COEFF_SCALING * BMM350_MAG_COMP_COEFF_SCALING) - + data->mag_comp.cross_axis.cross_y_x * + data->mag_comp.cross_axis.cross_x_y))); + + out_data[0] = (int32_t)cr_ax_comp_x; + out_data[1] = (int32_t)cr_ax_comp_y; + out_data[2] = (int32_t)cr_ax_comp_z; + } + LOG_DBG("mag data %d %d %d\n", out_data[0], out_data[1], out_data[2]); + + if (rslt == BMM350_OK) { + if ((data->axis_en & BMM350_EN_X_MSK) == BMM350_DISABLE) { + mag_temp_data->x = BMM350_DISABLE; + } + else { + mag_temp_data->x = out_data[0]; + } + + if ((data->axis_en & BMM350_EN_Y_MSK) == BMM350_DISABLE) { + mag_temp_data->y = BMM350_DISABLE; + } + else { + mag_temp_data->y = out_data[1]; + } + + if ((data->axis_en & BMM350_EN_Z_MSK) == BMM350_DISABLE) { + mag_temp_data->z = BMM350_DISABLE; + } + else { + mag_temp_data->z = out_data[2]; + } + mag_temp_data->temperature = out_data[3]; + } + } + else + { + rslt = BMM350_E_NULL_PTR; + } + return rslt; +} + +static int bmm350_sample_fetch(const struct device *dev, + enum sensor_channel chan) +{ + struct bmm350_data *drv_data = dev->data; + struct bmm350_mag_temp_data mag_temp_data; + + if (bmm350_get_compensated_mag_xyz_temp_data_fixed(&mag_temp_data, dev) < 0) { + LOG_ERR("failed to read sample"); + return -EIO; + } + drv_data->mag_temp_data.x = mag_temp_data.x; + drv_data->mag_temp_data.y = mag_temp_data.y; + drv_data->mag_temp_data.z = mag_temp_data.z; + return 0; +} + +/* + * ut change to Gauss + */ +static void bmm350_convert(struct sensor_value *val, int raw_val) +{ + val->val1 = raw_val / 100; + val->val2 = raw_val % 100 ; +} + +static int bmm350_channel_get(const struct device *dev, + enum sensor_channel chan, + struct sensor_value *val) +{ + struct bmm350_data *drv_data = dev->data; + + switch (chan) { + case SENSOR_CHAN_MAGN_X: + bmm350_convert(val, drv_data->mag_temp_data.x); + break; + case SENSOR_CHAN_MAGN_Y: + bmm350_convert(val, drv_data->mag_temp_data.y); + break; + case SENSOR_CHAN_MAGN_Z: + bmm350_convert(val, drv_data->mag_temp_data.z); + break; + case SENSOR_CHAN_MAGN_XYZ: + bmm350_convert(val, drv_data->mag_temp_data.x); + bmm350_convert(val + 1, drv_data->mag_temp_data.y); + bmm350_convert(val + 2, drv_data->mag_temp_data.z); + break; + default: + return -ENOTSUP; + } + + return 0; +} +static uint8_t acc_odr_to_reg(const struct sensor_value *val) +{ + double odr = sensor_value_to_double((struct sensor_value *) val); + + uint8_t reg = BMM350_DATA_RATE_100HZ; + + if ((odr >= 0.78125) && (odr <= 1.5625)) { + reg = BMM350_DATA_RATE_1_5625HZ; + } else if ((odr > 1.5625) && (odr <= 3.125)) { + reg = BMM350_DATA_RATE_3_125HZ; + } else if ((odr > 3.125) && (odr <= 6.25)) { + reg = BMM350_DATA_RATE_6_25HZ; + } else if ((odr > 6.25) && (odr <= 12.5)) { + reg = BMM350_DATA_RATE_12_5HZ; + } else if ((odr > 12.5) && (odr <= 25.0)) { + reg = BMM350_DATA_RATE_25HZ; + } else if ((odr > 25.0) && (odr <= 50.0)) { + reg = BMM350_DATA_RATE_50HZ; + } else if ((odr > 50.0) && (odr <= 100.0)) { + reg = BMM350_DATA_RATE_100HZ; + } else if ((odr > 100.0) && (odr <= 200.0)) { + reg = BMM350_DATA_RATE_200HZ; + } else if ((odr > 200.0) && (odr <= 400.0)) { + reg = BMM350_DATA_RATE_400HZ; + } else if (odr > 400.0) { + reg = BMM350_DATA_RATE_400HZ; + } + return reg; +} +/*! + * @brief This API sets the ODR and averaging factor. + */ +int8_t bmm350_set_odr_performance(enum bmm350_data_rates odr, + enum bmm350_performance_parameters performance, + const struct device *dev) +{ + /* Variable to store the function result */ + int8_t rslt = 0x00; + /* Variable to get PMU command */ + uint8_t reg_data = 0; + enum bmm350_performance_parameters performance_fix = performance; + + if (rslt == BMM350_OK) { + /* Reduce the performance setting when too high for the chosen ODR */ + if ((odr == BMM350_DATA_RATE_400HZ) && (performance >= BMM350_AVERAGING_2)) { + performance_fix = BMM350_NO_AVERAGING; + } + else if ((odr == BMM350_DATA_RATE_200HZ) && (performance >= BMM350_AVERAGING_4)) { + performance_fix = BMM350_AVERAGING_2; + } + else if ((odr == BMM350_DATA_RATE_100HZ) && (performance >= BMM350_AVERAGING_8)) { + performance_fix = BMM350_AVERAGING_4; + } + + /* ODR is an enum taking the generated constants from the register map */ + reg_data = ((uint8_t)odr & BMM350_ODR_MSK); + /* AVG / performance is an enum taking the generated constants from the register map */ + reg_data = BMM350_SET_BITS(reg_data, BMM350_AVG, (uint8_t)performance_fix); + /* Set PMU command configurations for ODR and performance */ + rslt = bmm350_reg_write(dev, BMM350_REG_PMU_CMD_AGGR_SET, reg_data); + LOG_DBG("odr index %d odr_reg_data 0x%x",odr, reg_data); + + if (rslt == BMM350_OK) { + /* Set PMU command configurations to update odr and average */ + reg_data = BMM350_PMU_CMD_UPD_OAE; + /* Set PMU command configuration */ + rslt = bmm350_reg_write(dev,BMM350_REG_PMU_CMD, reg_data); + if (rslt == BMM350_OK) { + k_usleep(BMM350_UPD_OAE_DELAY); + } + } + } + + return rslt; +} +static int set_mag_odr_osr(const struct device *dev, const struct sensor_value *odr, + const struct sensor_value *osr) +{ + uint8_t odr_bits = 0x00; + + if (odr) { + odr_bits = acc_odr_to_reg(odr); + if (bmm350_set_odr_performance((enum bmm350_data_rates)odr_bits, BMM350_AVERAGING_2, dev) < 0) { + LOG_ERR("bmm350_set_odr_performance failed"); + return -EIO; + } + } + if (osr) { + if (osr->val1 == 0) { + if (bmm350_set_powermode(BMM350_SUSPEND_MODE, dev) < 0) { + LOG_ERR("bmm350_set_powermode suspend failed"); + return -EIO; + } + + } + else { + if (bmm350_set_powermode(BMM350_NORMAL_MODE, dev) < 0) { + LOG_ERR("bmm350_set_powermode normal failed"); + return -EIO; + } + } + } + return 0; +} + +static int bmm350_attr_set(const struct device *dev, + enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + switch (attr) { + case SENSOR_ATTR_SAMPLING_FREQUENCY: + if (set_mag_odr_osr(dev, val, NULL) < 0) { + return -EIO; + } + break; + case SENSOR_ATTR_OVERSAMPLING: + if (set_mag_odr_osr(dev, NULL, val) < 0) { + return -EIO; + } + break; + default: + return -EINVAL; + } + + return 0; +} +static const struct sensor_driver_api bmm350_api_funcs = { + .attr_set = bmm350_attr_set, + .sample_fetch = bmm350_sample_fetch, + .channel_get = bmm350_channel_get, +#ifdef CONFIG_BMM350_TRIGGER + .trigger_set = bmm350_trigger_set, +#endif +}; + + +static int bmm350_init_chip(const struct device *dev) +{ + struct bmm350_pmu_cmd_status_0 pmu_cmd_stat_0 = { 0 }; + /* Variable to store soft-reset command */ + uint8_t soft_reset; + uint8_t rx_buf[3] = {0x00}; + uint8_t chip_id[3] = {0x00}; + int ret = 0; + /* Read chip ID (can only be read in sleep mode)*/ + if (bmm350_reg_read(dev, BMM350_REG_CHIP_ID, &chip_id[0], 3) < 0) { + LOG_ERR("failed reading chip id"); + goto err_poweroff; + } + if (chip_id[2] != BMM350_CHIP_ID) { + LOG_ERR("invalid chip id 0x%x", chip_id[2]); + goto err_poweroff; + } + /* Soft-reset */ + soft_reset = BMM350_CMD_SOFTRESET; + + /* Set the command in the command register */ + ret = bmm350_reg_write(dev, BMM350_REG_CMD, soft_reset); + k_usleep(BMM350_SOFT_RESET_DELAY); + /* Read chip ID (can only be read in sleep mode)*/ + if (bmm350_reg_read(dev, BMM350_REG_CHIP_ID, &chip_id[0], 3) < 0) { + LOG_ERR("failed reading chip id"); + goto err_poweroff; + } + if (chip_id[2] != BMM350_CHIP_ID) { + LOG_ERR("invalid chip id 0x%x", chip_id[2]); + goto err_poweroff; + } + ret = bmm350_otp_dump_after_boot(dev); + LOG_DBG("bmm350 chip_id 0x%x otp dump after boot %d\n", chip_id[2], ret); + + if (bmm350_reg_write(dev, BMM350_REG_OTP_CMD_REG, BMM350_OTP_CMD_PWR_OFF_OTP) < 0) { + LOG_ERR("failed to set REP"); + goto err_poweroff; + } + + ret += bmm350_magnetic_reset_and_wait(dev); + + LOG_DBG("bmm350 setup result %d\n", ret); + + ret += bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); + ret += bmm350_reg_read(dev, BMM350_REG_ERR_REG, &rx_buf[0], 3); + if (ret != 0) { + LOG_ERR("bmm350_init_chip %d", ret); + } + + return 0; + +err_poweroff: + ret = bmm350_set_powermode(BMM350_SUSPEND_MODE, dev); + if (ret != 0) { + return -EIO; + } + return -EIO; +} + +#ifdef CONFIG_PM_DEVICE +static int pm_action(const struct device *dev, enum pm_device_action action) +{ + int ret; + + switch (action) { + case PM_DEVICE_ACTION_RESUME: + ret = bmm350_set_powermode(BMM350_NORMAL_MODE, dev); + if (ret != 0) { + LOG_ERR("failed to enter normal mode: %d", ret); + } + break; + case PM_DEVICE_ACTION_SUSPEND: + ret = bmm350_set_powermode(BMM350_SUSPEND_MODE, dev); + if (ret != 0) { + LOG_ERR("failed to enter suspend mode: %d", ret); + } + break; + default: + return -ENOTSUP; + } + + return ret; +} +#endif + +static int bmm350_init(const struct device *dev) +{ + int err = 0; + struct bmm350_data *data = dev->data; + + err = bmm350_bus_check(dev); + if (err < 0) { + LOG_ERR("bus check failed: %d", err); + return err; + } + + if (bmm350_init_chip(dev) < 0) { + LOG_ERR("failed to initialize chip"); + return -EIO; + } + +#ifdef CONFIG_BMM350_TRIGGER + if (bmm350_trigger_mode_init(dev) < 0) { + LOG_ERR("Cannot set up trigger mode."); + return -EINVAL; + } +#endif + + /* Assign axis_en with all axis enabled (BMM350_EN_XYZ_MSK) */ + data->axis_en = BMM350_EN_XYZ_MSK; + + return 0; +} + +/* Initializes a struct bmm350_config for an instance on an I2C bus. */ +#define BMM350_CONFIG_I2C(inst) \ + .bus.i2c = I2C_DT_SPEC_INST_GET(inst), \ + .bus_io = &bmm350_bus_io_i2c, +#ifdef CONFIG_BMM350_TRIGGER +#define BMM350_INT_CFG(inst) \ + .drdy_int = GPIO_DT_SPEC_INST_GET(inst, drdy_gpios), +#else +#define BMM350_INT_CFG(inst) +#endif + +#define BMM350_DEFINE(inst) \ + static struct bmm350_data bmm350_data_##inst; \ + static const struct bmm350_config bmm350_config_##inst = { \ + .bus.i2c = I2C_DT_SPEC_INST_GET(inst), \ + .bus_io = &bmm350_bus_io_i2c, \ + BMM350_INT_CFG(inst) \ + }; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, pm_action); \ + \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, \ + bmm350_init, \ + PM_DEVICE_DT_INST_GET(inst), \ + &bmm350_data_##inst, \ + &bmm350_config_##inst, \ + POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, \ + &bmm350_api_funcs); + +/* Create the struct device for every status "okay" node in the devicetree. */ +DT_INST_FOREACH_STATUS_OKAY(BMM350_DEFINE) diff --git a/drivers/sensor/bmm350/bmm350.h b/drivers/sensor/bmm350/bmm350.h new file mode 100644 index 000000000000..b7e7db3b0c59 --- /dev/null +++ b/drivers/sensor/bmm350/bmm350.h @@ -0,0 +1,544 @@ +/* + * Copyright (c) 2024 Bosch Sensortec GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Bus-specific functionality for BMM350s accessed via I2C. + */ + +#ifndef ZEPHYR_DRIVERS_SENSOR_BMM350_BMM350_H_ +#define ZEPHYR_DRIVERS_SENSOR_BMM350_BMM350_H_ + +#include +#include +#include +#include +#include + +#define DT_DRV_COMPAT bosch_bmm350 + + +#define BMM350_BUS_I2C DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) + +struct bmm350_bus { + struct i2c_dt_spec i2c; +}; + +typedef int (*bmm350_bus_check_fn)(const struct bmm350_bus *bus); +typedef int (*bmm350_reg_read_fn)(const struct bmm350_bus *bus, + uint8_t start, uint8_t *buf, int size); +typedef int (*bmm350_reg_write_fn)(const struct bmm350_bus *bus, + uint8_t reg, uint8_t val); + +struct bmm350_bus_io { + bmm350_bus_check_fn check; + bmm350_reg_read_fn read; + bmm350_reg_write_fn write; +}; + +extern const struct bmm350_bus_io bmm350_bus_io_i2c; + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define DT_DRV_COMPAT bosch_bmm350 +#define BMM350_USE_FIXED_POINT 1 +#define BMM350_OK (0) +#define BMM350_DISABLE UINT8_C(0x0) +#define BMM350_ENABLE UINT8_C(0x1) + +#define BMM350_REG_CHIP_ID UINT8_C(0x00) +#define BMM350_REG_REV_ID UINT8_C(0x01) +#define BMM350_REG_ERR_REG UINT8_C(0x02) +#define BMM350_REG_PAD_CTRL UINT8_C(0x03) +#define BMM350_REG_PMU_CMD_AGGR_SET UINT8_C(0x04) +#define BMM350_REG_PMU_CMD_AXIS_EN UINT8_C(0x05) +#define BMM350_REG_PMU_CMD UINT8_C(0x06) +#define BMM350_REG_PMU_CMD_STATUS_0 UINT8_C(0x07) +#define BMM350_REG_PMU_CMD_STATUS_1 UINT8_C(0x08) +#define BMM350_REG_I3C_ERR UINT8_C(0x09) +#define BMM350_REG_I2C_WDT_SET UINT8_C(0x0A) +#define BMM350_REG_TRSDCR_REV_ID UINT8_C(0x0D) +#define BMM350_REG_TC_SYNC_TU UINT8_C(0x21) +#define BMM350_REG_TC_SYNC_ODR UINT8_C(0x22) +#define BMM350_REG_TC_SYNC_TPH_1 UINT8_C(0x23) +#define BMM350_REG_TC_SYNC_TPH_2 UINT8_C(0x24) +#define BMM350_REG_TC_SYNC_DT UINT8_C(0x25) +#define BMM350_REG_TC_SYNC_ST_0 UINT8_C(0x26) +#define BMM350_REG_TC_SYNC_ST_1 UINT8_C(0x27) +#define BMM350_REG_TC_SYNC_ST_2 UINT8_C(0x28) +#define BMM350_REG_TC_SYNC_STATUS UINT8_C(0x29) +#define BMM350_REG_INT_CTRL UINT8_C(0x2E) +#define BMM350_REG_INT_CTRL_IBI UINT8_C(0x2F) +#define BMM350_REG_INT_STATUS UINT8_C(0x30) +#define BMM350_REG_MAG_X_XLSB UINT8_C(0x31) +#define BMM350_REG_MAG_X_LSB UINT8_C(0x32) +#define BMM350_REG_MAG_X_MSB UINT8_C(0x33) +#define BMM350_REG_MAG_Y_XLSB UINT8_C(0x34) +#define BMM350_REG_MAG_Y_LSB UINT8_C(0x35) +#define BMM350_REG_MAG_Y_MSB UINT8_C(0x36) +#define BMM350_REG_MAG_Z_XLSB UINT8_C(0x37) +#define BMM350_REG_MAG_Z_LSB UINT8_C(0x38) +#define BMM350_REG_MAG_Z_MSB UINT8_C(0x39) +#define BMM350_REG_TEMP_XLSB UINT8_C(0x3A) +#define BMM350_REG_TEMP_LSB UINT8_C(0x3B) +#define BMM350_REG_TEMP_MSB UINT8_C(0x3C) +#define BMM350_REG_SENSORTIME_XLSB UINT8_C(0x3D) +#define BMM350_REG_SENSORTIME_LSB UINT8_C(0x3E) +#define BMM350_REG_SENSORTIME_MSB UINT8_C(0x3F) +#define BMM350_REG_OTP_CMD_REG UINT8_C(0x50) +#define BMM350_REG_OTP_DATA_MSB_REG UINT8_C(0x52) +#define BMM350_REG_OTP_DATA_LSB_REG UINT8_C(0x53) +#define BMM350_REG_OTP_STATUS_REG UINT8_C(0x55) +#define BMM350_REG_TMR_SELFTEST_USER UINT8_C(0x60) +#define BMM350_REG_CTRL_USER UINT8_C(0x61) +#define BMM350_REG_CMD UINT8_C(0x7E) + +/************************* Sensor Shuttle Variant **************************/ +#define BMM350_LEGACY_SHUTTLE_VARIANT_ID UINT8_C(0x10) +#define BMM350_CURRENT_SHUTTLE_VARIANT_ID UINT8_C(0x11) + +/********************* Sensor interface success code **********************/ +#define BMM350_INTF_RET_SUCCESS INT8_C(0) + +/* default value */ +#define BMM350_CHIP_ID UINT8_C(0x33) +#define BMM350_REV_ID UINT8_C(0x00) +#define BMM350_OTP_CMD_DIR_READ UINT8_C(0x20) +#define BMM350_OTP_WORD_ADDR_MSK UINT8_C(0x1F) +#define BMM350_OTP_STATUS_ERROR_MSK UINT8_C(0xE0) +#define BMM350_OTP_STATUS_ERROR(val) (val & BMM350_OTP_STATUS_ERROR_MSK) +#define BMM350_OTP_STATUS_NO_ERROR UINT8_C(0x00) +#define BMM350_OTP_STATUS_BOOT_ERR (0x20) +#define BMM350_OTP_STATUS_PAGE_RD_ERR (0x40) +#define BMM350_OTP_STATUS_PAGE_PRG_ERR (0x60) +#define BMM350_OTP_STATUS_SIGN_ERR (0x80) +#define BMM350_OTP_STATUS_INV_CMD_ERR (0xA0) +#define BMM350_OTP_STATUS_CMD_DONE UINT8_C(0x01) +#define BMM350_CMD_SOFTRESET UINT8_C(0xB6) + +/****************************** OTP indices ***************************/ +#define BMM350_TEMP_OFF_SENS UINT8_C(0x0D) +#define BMM350_MAG_OFFSET_X UINT8_C(0x0E) +#define BMM350_MAG_OFFSET_Y UINT8_C(0x0F) +#define BMM350_MAG_OFFSET_Z UINT8_C(0x10) + +#define BMM350_MAG_SENS_X UINT8_C(0x10) +#define BMM350_MAG_SENS_Y UINT8_C(0x11) +#define BMM350_MAG_SENS_Z UINT8_C(0x11) + +#define BMM350_MAG_TCO_X UINT8_C(0x12) +#define BMM350_MAG_TCO_Y UINT8_C(0x13) +#define BMM350_MAG_TCO_Z UINT8_C(0x14) + +#define BMM350_MAG_TCS_X UINT8_C(0x12) +#define BMM350_MAG_TCS_Y UINT8_C(0x13) +#define BMM350_MAG_TCS_Z UINT8_C(0x14) + +#define BMM350_MAG_DUT_T_0 UINT8_C(0x18) + +#define BMM350_CROSS_X_Y UINT8_C(0x15) +#define BMM350_CROSS_Y_X UINT8_C(0x15) +#define BMM350_CROSS_Z_X UINT8_C(0x16) +#define BMM350_CROSS_Z_Y UINT8_C(0x16) + +/**************************** Signed bit macros **********************/ +enum bmm350_signed_bit { + BMM350_SIGNED_8_BIT = 8, + BMM350_SIGNED_12_BIT = 12, + BMM350_SIGNED_16_BIT = 16, + BMM350_SIGNED_21_BIT = 21, + BMM350_SIGNED_24_BIT = 24 +}; + +/********************* Power modes *************************/ +#define BMM350_PMU_CMD_SUS 0x00 +#define BMM350_PMU_CMD_NM 0x01 +#define BMM350_PMU_CMD_UPD_OAE UINT8_C(0x02) +#define BMM350_PMU_CMD_FM 0x03 +#define BMM350_PMU_CMD_FM_FAST 0x04 +#define BMM350_PMU_CMD_FGR UINT8_C(0x05) +#define BMM350_PMU_CMD_FGR_FAST UINT8_C(0x06) +#define BMM350_PMU_CMD_BR UINT8_C(0x07) +#define BMM350_PMU_CMD_BR_FAST UINT8_C(0x08) +#define BMM350_PMU_CMD_ENABLE_XYZ UINT8_C(0x70) +#define BMM350_PMU_STATUS_0 UINT8_C(0x00) + +/********************* Error message*************************/ +#define BMM350_E_NULL_PTR INT8_C(-1) +#define BMM350_E_COM_FAIL INT8_C(-2) +#define BMM350_E_DEV_NOT_FOUND INT8_C(-3) +#define BMM350_E_INVALID_CONFIG INT8_C(-4) +#define BMM350_E_BAD_PAD_DRIVE INT8_C(-5) +#define BMM350_E_RESET_UNFINISHED INT8_C(-6) +#define BMM350_E_INVALID_INPUT INT8_C(-7) +#define BMM350_E_SELF_TEST_INVALID_AXIS INT8_C(-8) +#define BMM350_E_OTP_BOOT INT8_C(-9) +#define BMM350_E_OTP_PAGE_RD INT8_C(-10) +#define BMM350_E_OTP_PAGE_PRG INT8_C(-11) +#define BMM350_E_OTP_SIGN INT8_C(-12) +#define BMM350_E_OTP_INV_CMD INT8_C(-13) +#define BMM350_E_OTP_UNDEFINED INT8_C(-14) +#define BMM350_E_ALL_AXIS_DISABLED INT8_C(-15) +#define BMM350_E_PMU_CMD_VALUE INT8_C(-16) + +/**************************** PMU command status 0 macros **********************/ +#define BMM350_PMU_CMD_STATUS_0_SUS UINT8_C(0x00) +#define BMM350_PMU_CMD_STATUS_0_NM UINT8_C(0x01) +#define BMM350_PMU_CMD_STATUS_0_UPD_OAE UINT8_C(0x02) +#define BMM350_PMU_CMD_STATUS_0_FM UINT8_C(0x03) +#define BMM350_PMU_CMD_STATUS_0_FM_FAST UINT8_C(0x04) +#define BMM350_PMU_CMD_STATUS_0_FGR UINT8_C(0x05) +#define BMM350_PMU_CMD_STATUS_0_FGR_FAST UINT8_C(0x06) +#define BMM350_PMU_CMD_STATUS_0_BR UINT8_C(0x07) +#define BMM350_PMU_CMD_STATUS_0_BR_FAST UINT8_C(0x07) + +/*********************** Macros for bit masking ***************************/ +#define BMM350_AVG_MSK (0x30) +#define BMM350_AVG_POS UINT8_C(0x04) +#define BMM350_PMU_CMD_BUSY_MSK UINT8_C(0x01) +#define BMM350_PMU_CMD_BUSY_POS UINT8_C(0x00) +#define BMM350_ODR_OVWR_MSK UINT8_C(0x02) +#define BMM350_ODR_OVWR_POS UINT8_C(0x01) +#define BMM350_AVG_OVWR_MSK UINT8_C(0x04) +#define BMM350_AVG_OVWR_POS UINT8_C(0x02) +#define BMM350_PWR_MODE_IS_NORMAL_MSK UINT8_C(0x08) +#define BMM350_PWR_MODE_IS_NORMAL_POS UINT8_C(0x03) +#define BMM350_CMD_IS_ILLEGAL_MSK UINT8_C(0x10) +#define BMM350_CMD_IS_ILLEGAL_POS UINT8_C(0x04) +#define BMM350_PMU_CMD_VALUE_MSK UINT8_C(0xE0) +#define BMM350_PMU_CMD_VALUE_POS UINT8_C(0x05) + +/**************************** Self-test macros **********************/ +#define BMM350_SELF_TEST_DISABLE UINT8_C(0x00) +#define BMM350_SELF_TEST_POS_X UINT8_C(0x0D) +#define BMM350_SELF_TEST_NEG_X UINT8_C(0x0B) +#define BMM350_SELF_TEST_POS_Y UINT8_C(0x15) +#define BMM350_SELF_TEST_NEG_Y UINT8_C(0x13) + +/************************* Sensor delay time settings in microseconds **************************/ +#define BMM350_SOFT_RESET_DELAY UINT32_C(24000) +#define BMM350_MAGNETIC_RESET_DELAY UINT32_C(40000) +#define BMM350_START_UP_TIME_FROM_POR UINT32_C(3000) + +#define BMM350_GOTO_SUSPEND_DELAY UINT32_C(6000) +#define BMM350_SUSPEND_TO_NORMAL_DELAY UINT32_C(38000) + +#define BMM350_SUS_TO_FORCEDMODE_NO_AVG_DELAY (15000) +#define BMM350_SUS_TO_FORCEDMODE_AVG_2_DELAY (17000) +#define BMM350_SUS_TO_FORCEDMODE_AVG_4_DELAY (20000) +#define BMM350_SUS_TO_FORCEDMODE_AVG_8_DELAY (28000) + +#define BMM350_SUS_TO_FORCEDMODE_FAST_NO_AVG_DELAY (4000) +#define BMM350_SUS_TO_FORCEDMODE_FAST_AVG_2_DELAY (5000) +#define BMM350_SUS_TO_FORCEDMODE_FAST_AVG_4_DELAY (9000) +#define BMM350_SUS_TO_FORCEDMODE_FAST_AVG_8_DELAY (16000) + +#define BMM350_PMU_CMD_NM_TC UINT8_C(0x09) +#define BMM350_OTP_DATA_LENGTH UINT8_C(32) +#define BMM350_READ_BUFFER_LENGTH UINT8_C(127) +#define BMM350_MAG_TEMP_DATA_LEN UINT8_C(12) +#define BMM350_OTP_CMD_PWR_OFF_OTP UINT8_C(0x80) +#define BMM350_UPD_OAE_DELAY UINT16_C(1000) + +#define BMM350_BR_DELAY UINT16_C(14000) +#define BMM350_FGR_DELAY UINT16_C(18000) +#define BMM350_SOFT_RESET_DELAY UINT32_C(24000) + +#define BMM350_LSB_MASK UINT16_C(0x00FF) +#define BMM350_MSB_MASK UINT16_C(0xFF00) + +#ifdef BMM350_USE_FIXED_POINT +#define BMM350_LSB_TO_UT_XY_COEFF 71 +#define BMM350_LSB_TO_UT_Z_COEFF 72 +#define BMM350_LSB_TO_UT_TEMP_COEFF 10 +#define BMM350_LSB_TO_UT_COEFF_DIV 10000 +#define BMM350_MAG_COMP_COEFF_SCALING 1000 +#endif + +#ifdef BMM350_USE_FIXED_POINT +#define BMM350_SENS_CORR_Y 1 +#define BMM350_TCS_CORR_Z 1 +#else +#define BMM350_SENS_CORR_Y (0.01f) +#define BMM350_TCS_CORR_Z (0.0001f) +#endif + +#define BMM350_EN_X_MSK UINT8_C(0x01) +#define BMM350_EN_X_POS UINT8_C(0x0) +#define BMM350_EN_Y_MSK UINT8_C(0x02) +#define BMM350_EN_Y_POS UINT8_C(0x1) +#define BMM350_EN_Z_MSK UINT8_C(0x04) +#define BMM350_EN_Z_POS UINT8_C(0x2) +#define BMM350_EN_XYZ_MSK UINT8_C(0x7) +#define BMM350_EN_XYZ_POS UINT8_C(0x0) +/************************ Averaging macros **********************/ +#define BMM350_AVG_NO_AVG 0x0 +#define BMM350_AVG_2 0x1 +#define BMM350_AVG_4 0x2 +#define BMM350_AVG_8 0x3 +/******************************* ODR **************************/ +#define BMM350_ODR_400HZ UINT8_C(0x2) +#define BMM350_ODR_200HZ UINT8_C(0x3) +#define BMM350_ODR_100HZ UINT8_C(0x4) +#define BMM350_ODR_50HZ UINT8_C(0x5) +#define BMM350_ODR_25HZ UINT8_C(0x6) +#define BMM350_ODR_12_5HZ UINT8_C(0x7) +#define BMM350_ODR_6_25HZ UINT8_C(0x8) +#define BMM350_ODR_3_125HZ UINT8_C(0x9) +#define BMM350_ODR_1_5625HZ UINT8_C(0xA) +#define BMM350_ODR_MSK UINT8_C(0xf) +#define BMM350_ODR_POS UINT8_C(0x0) +#define BMM350_DATA_READY_INT_CTRL UINT8_C(0x8e) + +/* Macro to SET and GET BITS of a register*/ +#define BMM350_SET_BITS(reg_data, bitname, data) \ + ((reg_data & ~(bitname##_MSK)) | \ + ((data << bitname##_POS) & bitname##_MSK)) + +#define BMM350_GET_BITS(reg_data, bitname) ((reg_data & (bitname##_MSK)) >> (bitname##_POS)) + +#define BMM350_GET_BITS_POS_0(reg_data, bitname) (reg_data & (bitname##_MSK)) + +#define BMM350_SET_BITS_POS_0(reg_data, bitname, data) \ + ((reg_data & ~(bitname##_MSK)) | \ + (data & bitname##_MSK)) + +enum bmm350_power_modes { + BMM350_SUSPEND_MODE = BMM350_PMU_CMD_SUS, + BMM350_NORMAL_MODE = BMM350_PMU_CMD_NM, + BMM350_FORCED_MODE = BMM350_PMU_CMD_FM, + BMM350_FORCED_MODE_FAST = BMM350_PMU_CMD_FM_FAST +}; + +enum bmm350_data_rates { + BMM350_DATA_RATE_400HZ = 2, //BMM350_ODR_400HZ + BMM350_DATA_RATE_200HZ = 3, //BMM350_ODR_200HZ + BMM350_DATA_RATE_100HZ = 4, //BMM350_ODR_100HZ + BMM350_DATA_RATE_50HZ = 5, //BMM350_ODR_50HZ + BMM350_DATA_RATE_25HZ = 6, //BMM350_ODR_25HZ + BMM350_DATA_RATE_12_5HZ = 7, //BMM350_ODR_12_5HZ + BMM350_DATA_RATE_6_25HZ = 8, //BMM350_ODR_6_25HZ + BMM350_DATA_RATE_3_125HZ = 9, //BMM350_ODR_3_125HZ + BMM350_DATA_RATE_1_5625HZ = 10 //BMM350_ODR_1_5625HZ +}; +enum bmm350_performance_parameters { + BMM350_NO_AVERAGING = BMM350_AVG_NO_AVG, + BMM350_AVERAGING_2 = BMM350_AVG_2, + BMM350_AVERAGING_4 = BMM350_AVG_4, + BMM350_AVERAGING_8 = BMM350_AVG_8, + /*lint -e849*/ + BMM350_ULTRALOWNOISE = BMM350_AVG_8, + BMM350_LOWNOISE = BMM350_AVG_4, + BMM350_REGULARPOWER = BMM350_AVG_2, + BMM350_LOWPOWER = BMM350_AVG_NO_AVG +}; + +/*! + * @brief bmm350 compensated magnetometer data and temperature data + */ +struct bmm350_mag_temp_data +{ + /*! Compensated mag X data */ + int32_t x; + + /*! Compensated mag Y data */ + int32_t y; + + /*! Compensated mag Z data */ + int32_t z; + + /*! Temperature */ + int32_t temperature; +}; + +/*! + * @brief bmm350 magnetometer dut offset coefficient structure + */ +struct bmm350_dut_offset_coef +{ + /*! Temperature offset */ + int32_t t_offs; + + /*! Offset x-axis */ + int32_t offset_x; + + /*! Offset y-axis */ + int32_t offset_y; + + /*! Offset z-axis */ + int32_t offset_z; +}; + +/*! + * @brief bmm350 magnetometer dut sensitivity coefficient structure + */ +struct bmm350_dut_sensit_coef +{ + /*! Temperature sensitivity */ + int32_t t_sens; + + /*! Sensitivity x-axis */ + int32_t sens_x; + + /*! Sensitivity y-axis */ + int32_t sens_y; + + /*! Sensitivity z-axis */ + int32_t sens_z; +}; + +/*! + * @brief bmm350 magnetometer dut tco structure + */ +struct bmm350_dut_tco +{ + int32_t tco_x; + int32_t tco_y; + int32_t tco_z; +}; + +/*! + * @brief bmm350 magnetometer dut tcs structure + */ +struct bmm350_dut_tcs +{ + int32_t tcs_x; + int32_t tcs_y; + int32_t tcs_z; +}; + +/*! + * @brief bmm350 magnetometer cross axis compensation structure + */ +struct bmm350_cross_axis +{ + int32_t cross_x_y; + int32_t cross_y_x; + int32_t cross_z_x; + int32_t cross_z_y; +}; +struct mag_compensate { + /*! Structure to store dut offset coefficient */ + struct bmm350_dut_offset_coef dut_offset_coef; + + /*! Structure to store dut sensitivity coefficient */ + struct bmm350_dut_sensit_coef dut_sensit_coef; + + /*! Structure to store dut tco */ + struct bmm350_dut_tco dut_tco; + + /*! Structure to store dut tcs */ + struct bmm350_dut_tcs dut_tcs; + + /*! Initialize T0_reading parameter */ + int32_t dut_t0; + + /*! Structure to define cross axis compensation */ + struct bmm350_cross_axis cross_axis; +}; + + +struct bmm350_pmu_cmd_status_0 +{ + /*! The previous PMU CMD is still in processing */ + uint8_t pmu_cmd_busy; + + /*! The previous PMU_CMD_AGGR_SET.odr has been overwritten */ + uint8_t odr_ovwr; + + /*! The previous PMU_CMD_AGGR_SET.avg has been overwritten */ + uint8_t avr_ovwr; + + /*! The chip is in normal power mode */ + uint8_t pwr_mode_is_normal; + + /*! CMD value is not allowed */ + uint8_t cmd_is_illegal; + + /*! Stores the latest PMU_CMD code processed */ + uint8_t pmu_cmd_value; +}; + +/*! + * @brief bmm350 un-compensated (raw) magnetometer data, signed integer + */ +struct bmm350_raw_mag_data +{ + /*! Raw mag X data */ + int32_t raw_xdata; + + /*! Raw mag Y data */ + int32_t raw_ydata; + + /*! Raw mag Z data */ + int32_t raw_zdata; + + /*! Raw mag temperature value */ + int32_t raw_data_t; +}; + +struct bmm350_config { + struct bmm350_bus bus; + const struct bmm350_bus_io *bus_io; +#ifdef CONFIG_BMM350_TRIGGER + struct gpio_dt_spec drdy_int; +#endif +}; + +struct bmm350_data { + + /*! Variable to store status of axes enabled */ + uint8_t axis_en; + struct mag_compensate mag_comp; + /*! Array to store OTP data */ + uint16_t otp_data[BMM350_OTP_DATA_LENGTH]; + /*! Variant ID */ + uint8_t var_id; + /*! Variable to enable/disable xy bit reset */ + uint8_t enable_auto_br; +struct bmm350_mag_temp_data mag_temp_data; + +#ifdef CONFIG_BMM350_TRIGGER + struct gpio_callback gpio_cb; +#endif + +#ifdef CONFIG_BMM350_TRIGGER_OWN_THREAD + struct k_sem sem; +#endif + +#ifdef CONFIG_BMM350_TRIGGER_GLOBAL_THREAD + struct k_work work; +#endif + +#if defined(CONFIG_BMM350_TRIGGER_GLOBAL_THREAD) || \ + defined(CONFIG_BMM350_TRIGGER_DIRECT) + const struct device *dev; +#endif + +#ifdef CONFIG_BMM350_TRIGGER + const struct sensor_trigger *drdy_trigger; + sensor_trigger_handler_t drdy_handler; +#endif /* CONFIG_BMM350_TRIGGER */ +}; + +int bmm350_trigger_mode_init(const struct device *dev); + +int bmm350_trigger_set(const struct device *dev, + const struct sensor_trigger *trig, + sensor_trigger_handler_t handler); +int bmm350_reg_write(const struct device *dev, + uint8_t reg, + uint8_t val); +#endif /* __SENSOR_BMM350_H__ */ diff --git a/drivers/sensor/bmm350/bmm350_i2c.c b/drivers/sensor/bmm350/bmm350_i2c.c new file mode 100644 index 000000000000..da871772cb1f --- /dev/null +++ b/drivers/sensor/bmm350/bmm350_i2c.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Bosch Sensortec GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include "bmm350.h" + + +static int bmm350_bus_check_i2c(const struct bmm350_bus *bus) +{ + return i2c_is_ready_dt(&bus->i2c) ? 0 : -ENODEV; +} + +static int bmm350_reg_read_i2c(const struct bmm350_bus *bus, + uint8_t start, uint8_t *buf, int size) +{ + return i2c_burst_read_dt(&bus->i2c, start, buf, size); +} + +static int bmm350_reg_write_i2c(const struct bmm350_bus *bus, + uint8_t reg, uint8_t val) +{ + return i2c_reg_write_byte_dt(&bus->i2c, reg, val); +} + +const struct bmm350_bus_io bmm350_bus_io_i2c = { + .check = bmm350_bus_check_i2c, + .read = bmm350_reg_read_i2c, + .write = bmm350_reg_write_i2c, +}; + diff --git a/drivers/sensor/bmm350/bmm350_trigger.c b/drivers/sensor/bmm350/bmm350_trigger.c new file mode 100644 index 000000000000..db51f8a8de59 --- /dev/null +++ b/drivers/sensor/bmm350/bmm350_trigger.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2024 Bosch Sensortec GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Bus-specific functionality for BMM350s accessed via I2C. + */ +#include +#include +#include + +#include "bmm350.h" +/*lint -e26 -e10 -e551 -e752 -e413*/ +LOG_MODULE_DECLARE(BMM350, CONFIG_SENSOR_LOG_LEVEL); +/*lint -e26 -e10 -e551 -e752 -e413*/ + +static void bmm350_handle_interrupts(const void *arg) +{ + const struct device *dev = (const struct device *)arg; + struct bmm350_data *data = dev->data; + + if (data->drdy_handler) { + data->drdy_handler(dev, data->drdy_trigger); + } +} + +#ifdef CONFIG_BMM350_TRIGGER_OWN_THREAD +static K_THREAD_STACK_DEFINE(bmm350_thread_stack, + CONFIG_BMM350_THREAD_STACK_SIZE); +static struct k_thread bmm350_thread; + +static void bmm350_thread_main(void *arg1, void *unused1, void *unused2) +{ + ARG_UNUSED(unused1); + ARG_UNUSED(unused2); + const struct device *dev = (const struct device *)arg1; + struct bmm350_data *data = dev->data; + + while (1) { + k_sem_take(&data->sem, K_FOREVER); + bmm350_handle_interrupts(dev); + } +} +#endif + +#ifdef CONFIG_BMM350_TRIGGER_GLOBAL_THREAD +static void bmm350_work_handler(struct k_work *work) +{ + /*lint -e26 -e10 -e124 -e40 -e413 -e30 -e578 -e514 -e516*/ + struct bmm350_data *data = CONTAINER_OF(work, + struct bmm350_data, + work); + /*lint +e26 +e10 +e124 +e40 +e413 +e30 +e578 +e514 +e516*/ + + bmm350_handle_interrupts(data->dev); +} +#endif + +static void bmm350_gpio_callback(const struct device *port, + struct gpio_callback *cb, + uint32_t pin) +{ + /*lint -e26 -e10 -e124 -e40 -e413 -e30 -e578 -e514 -e516*/ + struct bmm350_data *data = CONTAINER_OF(cb, + struct bmm350_data, + gpio_cb); + /*lint +e26 +e10 +e124 +e40 +e413 +e30 +e578 +e514 +e516*/ + ARG_UNUSED(port); + ARG_UNUSED(pin); + +#if defined(CONFIG_BMM350_TRIGGER_OWN_THREAD) + k_sem_give(&data->sem); +#elif defined(CONFIG_BMM350_TRIGGER_GLOBAL_THREAD) + k_work_submit(&data->work); +#elif defined(CONFIG_BMM350_TRIGGER_DIRECT) + bmm350_handle_interrupts(data->dev); +#endif +} + +int bmm350_trigger_set( + const struct device *dev, + const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) +{ + struct bmm350_data *data = dev->data; + int ret = 0; + LOG_INF("bmm350_trigger_set"); + +#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 + + if (trig->type != SENSOR_TRIG_DATA_READY) { + return -ENOTSUP; + } + + data->drdy_trigger = trig; + data->drdy_handler = handler; + + /* Set PMU command configuration */ + ret = bmm350_reg_write(dev, BMM350_REG_INT_CTRL, BMM350_DATA_READY_INT_CTRL); + if (ret < 0) { + return ret; + } + return 0; +} + +int bmm350_trigger_mode_init(const struct device *dev) +{ + struct bmm350_data *data = dev->data; + const struct bmm350_config *cfg = dev->config; + int ret; + + if (!device_is_ready(cfg->drdy_int.port)) { + LOG_ERR("INT device is not ready"); + return -ENODEV; + } + +#if defined(CONFIG_BMM350_TRIGGER_OWN_THREAD) + k_sem_init(&data->sem, 0, 1); + k_thread_create( + &bmm350_thread, + bmm350_thread_stack, + CONFIG_BMM350_THREAD_STACK_SIZE, + bmm350_thread_main, + (void *)dev, + NULL, + NULL, + K_PRIO_COOP(CONFIG_BMM350_THREAD_PRIORITY), + 0, + K_NO_WAIT); +#elif defined(CONFIG_BMM350_TRIGGER_GLOBAL_THREAD) + k_work_init(&data->work, bmm350_work_handler); +#endif + +#if defined(CONFIG_BMM350_TRIGGER_GLOBAL_THREAD) || \ + defined(CONFIG_BMM350_TRIGGER_DIRECT) + data->dev = dev; +#endif + + ret = gpio_pin_configure_dt(&cfg->drdy_int, GPIO_INPUT); + if (ret < 0) { + return ret; + } + + gpio_init_callback(&data->gpio_cb, + bmm350_gpio_callback, + BIT(cfg->drdy_int.pin)); + + ret = gpio_add_callback(cfg->drdy_int.port, &data->gpio_cb); + if (ret < 0) { + return ret; + } + + ret = gpio_pin_interrupt_configure_dt(&cfg->drdy_int, + GPIO_INT_EDGE_TO_ACTIVE); + if (ret < 0) { + return ret; + } + + return 0; +} diff --git a/dts/bindings/sensor/bosch,bmm350-i2c.yaml b/dts/bindings/sensor/bosch,bmm350-i2c.yaml new file mode 100644 index 000000000000..35434f69b54c --- /dev/null +++ b/dts/bindings/sensor/bosch,bmm350-i2c.yaml @@ -0,0 +1,11 @@ +# Copyright (c) 2024, Bosch Sensortec GmbH + +# SPDX-License-Identifier: Apache-2.0 + +description: | + Bosch BMM350 Geomagnetic sensor. See more info at: + https://www.bosch-sensortec.com/products/motion-sensors/magnetometers/bmm350/ + +compatible: "bosch,bmm350" + +include: [i2c-device.yaml, "bosch,bmm350.yaml"] diff --git a/dts/bindings/sensor/bosch,bmm350.yaml b/dts/bindings/sensor/bosch,bmm350.yaml new file mode 100644 index 000000000000..35206be2d2ed --- /dev/null +++ b/dts/bindings/sensor/bosch,bmm350.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2024, Bosch Sensortec GmbH + +# SPDX-License-Identifier: Apache-2.0 + +include: sensor-device.yaml + +properties: + drdy-gpios: + type: phandle-array + description: | + This property specifies the connection for data ready pin. + The polarity default is active high when sensor data is ready. diff --git a/samples/sensor/bmm350/CMakeLists.txt b/samples/sensor/bmm350/CMakeLists.txt new file mode 100644 index 000000000000..f9808f9c3438 --- /dev/null +++ b/samples/sensor/bmm350/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright (c) 2021 Bosch Sensortec GmbH +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(NONE) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/sensor/bmm350/README.rst b/samples/sensor/bmm350/README.rst new file mode 100644 index 000000000000..b7b6049d178e --- /dev/null +++ b/samples/sensor/bmm350/README.rst @@ -0,0 +1,69 @@ +.. _BMM350: + +BMM350: Magnetometers +######################################## + +Description +*********** + +This sample application configures the Magnetometers to +measure data at 25Hz. The result is written to the console. + +References +********** + + - BMM350: https://www.bosch-sensortec.com/products/motion-sensors/magnetometers/bmm350/ + +Wiring +******* + +This sample uses the BMM350 sensor controlled using the I2C interface. +Connect Supply: **VDD**, **VDDIO**, **GND** and Interface: **SDA**, **SCL**. +The supply voltage can be in the 1.8V to 3.6V range. +Depending on the baseboard used, the **SDA** and **SCL** lines require Pull-Up +resistors. + +Building and Running +******************** + +This project outputs sensor data to the console. It requires a BMM350 +sensor. It should work with any platform featuring a I2C peripheral interface. +It does not work on QEMU. +In this example below the :ref:`nrf52840dk_nrf52840` board is used. + + +.. zephyr-app-commands:: + :zephyr-app: samples/sensor/bmm350 + :board: nrf52840dk_nrf52840 + :goals: build flash + +Sample Output +============= + +.. code-block:: console + +disable enable CONFIG_BMM350_TRIGGER_OWN_THREAD + and CONFIG_BMM350_TRIGGER_GLOBAL_THREAD +Polling TIME(ms) 6913 MX: -0.070000; MY: 0.120000; MZ: 0.400000; +Polling TIME(ms) 6960 MX: -0.070000; MY: 0.120000; MZ: 0.400000; +Polling TIME(ms) 7008 MX: -0.080000; MY: 0.120000; MZ: 0.400000; +Polling TIME(ms) 7055 MX: -0.080000; MY: 0.120000; MZ: 0.400000; +Polling TIME(ms) 7103 MX: -0.080000; MY: 0.120000; MZ: 0.400000; +Polling TIME(ms) 7150 MX: -0.080000; MY: 0.120000; MZ: 0.400000; +Polling TIME(ms) 7198 MX: -0.080000; MY: 0.120000; MZ: 0.400000; +Polling TIME(ms) 7245 MX: -0.080000; MY: 0.120000; MZ: 0.400000; +Polling TIME(ms) 7293 MX: -0.080000; MY: 0.110000; MZ: 0.400000; +Polling TIME(ms) 7340 MX: -0.080000; MY: 0.120000; MZ: 0.400000; + +enable CONFIG_BMM350_TRIGGER_OWN_THREAD + or CONFIG_BMM350_TRIGGER_GLOBAL_THREAD +Data_ready TIME(ms) 4165 MX: -0.080000; MY: 0.120000; MZ: 0.400000; +Data_ready TIME(ms) 4205 MX: -0.070000; MY: 0.120000; MZ: 0.400000; +Data_ready TIME(ms) 4245 MX: -0.070000; MY: 0.120000; MZ: 0.400000; +Data_ready TIME(ms) 4285 MX: -0.070000; MY: 0.120000; MZ: 0.400000; +Data_ready TIME(ms) 4325 MX: -0.070000; MY: 0.120000; MZ: 0.400000; +Data_ready TIME(ms) 4364 MX: -0.070000; MY: 0.120000; MZ: 0.400000; +Data_ready TIME(ms) 4404 MX: -0.070000; MY: 0.120000; MZ: 0.400000; +Data_ready TIME(ms) 4444 MX: -0.070000; MY: 0.120000; MZ: 0.400000; +Data_ready TIME(ms) 4484 MX: -0.080000; MY: 0.120000; MZ: 0.400000; +Data_ready TIME(ms) 4524 MX: -0.070000; MY: 0.120000; MZ: 0.400000; \ No newline at end of file diff --git a/samples/sensor/bmm350/app.overlay b/samples/sensor/bmm350/app.overlay new file mode 100644 index 000000000000..12e935e0742c --- /dev/null +++ b/samples/sensor/bmm350/app.overlay @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Bosch Sensortec GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +//this i2c config can be used successfully +&arduino_i2c { + compatible = "nordic,nrf-twim"; + zephyr,concat-buf-size = <257>; + status = "okay"; + + bmm350@14 { + compatible = "bosch,bmm350"; + drdy-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; + status = "okay"; + label = "BMM350"; + reg = <0x14>; + }; +}; diff --git a/samples/sensor/bmm350/boards/thingy91x_nrf9151_ns.overlay b/samples/sensor/bmm350/boards/thingy91x_nrf9151_ns.overlay new file mode 100644 index 000000000000..b8e0a8c87ddd --- /dev/null +++ b/samples/sensor/bmm350/boards/thingy91x_nrf9151_ns.overlay @@ -0,0 +1,3 @@ +&magnetometer { + status = "okay"; +}; diff --git a/samples/sensor/bmm350/prj.conf b/samples/sensor/bmm350/prj.conf new file mode 100644 index 000000000000..a21b985d0118 --- /dev/null +++ b/samples/sensor/bmm350/prj.conf @@ -0,0 +1,7 @@ +CONFIG_STDOUT_CONSOLE=y +CONFIG_I2C=y +CONFIG_SENSOR=y +CONFIG_LOG=y +CONFIG_CBPRINTF_FP_SUPPORT=y +CONFIG_BMM350=y +#CONFIG_BMM350_TRIGGER_GLOBAL_THREAD=y diff --git a/samples/sensor/bmm350/sample.yaml b/samples/sensor/bmm350/sample.yaml new file mode 100644 index 000000000000..e017667c1272 --- /dev/null +++ b/samples/sensor/bmm350/sample.yaml @@ -0,0 +1,9 @@ +sample: + name: BMM350 Sensor sample +tests: + sample.sensor.bmm350: + harness: sensor + tags: + - samples + - sensor + depends_on: arduino_i2c diff --git a/samples/sensor/bmm350/src/main.c b/samples/sensor/bmm350/src/main.c new file mode 100644 index 000000000000..94e3def0f44b --- /dev/null +++ b/samples/sensor/bmm350/src/main.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 Bosch Sensortec GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#if (CONFIG_BMM350_TRIGGER_GLOBAL_THREAD || CONFIG_BMM350_TRIGGER_OWN_THREAD) +static void trigger_handler(const struct device *dev, + const struct sensor_trigger *trig) +{ + struct sensor_value mag[3]; + + int64_t current_time = k_uptime_get(); + + sensor_sample_fetch(dev); + + sensor_channel_get(dev, SENSOR_CHAN_MAGN_XYZ, mag); + + float report_mag[3] = {0.0}; + report_mag[0] = (float)mag[0].val1 + (float)mag[0].val2 / 100.0; + report_mag[1] = (float)mag[1].val1 + (float)mag[1].val2 / 100.0; + report_mag[2] = (float)mag[2].val1 + (float)mag[2].val2 / 100.0; + printf("Data_ready TIME(ms) %lld MX: %f MY: %f MZ: %f \n", + current_time, report_mag[0], report_mag[1], report_mag[2]); +} +#endif + +int main(void) +{ + const struct device *const dev = DEVICE_DT_GET_ONE(bosch_bmm350); + struct sensor_value sampling_freq, oversampling; + + if (!device_is_ready(dev)) { + printf("Device %s is not ready\n", dev->name); + return 0; + } + + + printf("Device %p name is %s\n", dev, dev->name); + + /* Setting scale in G, due to loss of precision if the SI unit m/s^2 + * is used + */ + sampling_freq.val1 = 25; /* Hz from 0.78 to 400*/ + sampling_freq.val2 = 0; /*not use*/ + oversampling.val1 = 1; /*1 Normal mode 0suspend mode*/ + oversampling.val2 = 0; /*not use*/ + sensor_attr_set(dev, SENSOR_CHAN_MAGN_XYZ, + SENSOR_ATTR_SAMPLING_FREQUENCY, + &sampling_freq); + /* Set sampling frequency last as this also sets the appropriate + * power mode. If already sampling, change to 0.0Hz before changing + * other attributes + */ + sensor_attr_set(dev, SENSOR_CHAN_MAGN_XYZ, SENSOR_ATTR_OVERSAMPLING, + &oversampling); + + +#if (CONFIG_BMM350_TRIGGER_GLOBAL_THREAD || CONFIG_BMM350_TRIGGER_OWN_THREAD) + struct sensor_trigger trig; + trig.type = SENSOR_TRIG_DATA_READY; + trig.chan = SENSOR_CHAN_MAGN_XYZ; + + if (sensor_trigger_set(dev, &trig, trigger_handler) != 0) { + printf("Could not set sensor type and channel\n"); + return 0; + } +#else + while (1) { + struct sensor_value mag[3]; + /* 10ms period, 100Hz Sampling frequency */ + k_sleep(K_MSEC(1000 / sampling_freq.val1)); + int64_t current_time = k_uptime_get(); + sensor_sample_fetch(dev); + sensor_channel_get(dev, SENSOR_CHAN_MAGN_XYZ, mag); + double report_mag[3] = {0.0}; + report_mag[0] = mag[0].val1 + mag[0].val2 / 100.0; + report_mag[1] = mag[1].val1 + mag[1].val2 / 100.0; + report_mag[2] = mag[2].val1 + mag[2].val2 / 100.0; + printf("Polling TIME(ms) %lld MX: %f MY: %f MZ: %f \n", + current_time, report_mag[0], report_mag[1], report_mag[2]); + } +#endif + return 0; +}