/** * Copyright (c) 2025 Bosch Sensortec GmbH. All rights reserved. * * BSD-3-Clause * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @file bmm350.c * @date 2025-10-30 * @version v1.10.0 * */ /*************************** Header files *******************************/ #include "bmm350.h" #ifdef __KERNEL__ #include #include #else #include #include #endif /******************************* Macros ********************************/ /********************** Static function declarations ************************/ /*! * @brief This internal API is used to validate the device pointer for * null conditions. * * @param[in] dev : Structure instance of bmm350_dev. * * @return Result of API execution status * @retval = 0 -> Success * @retval < 0 -> Error */ static int8_t null_ptr_check(const struct bmm350_dev *dev); /*! * @brief This internal API is used to update magnetometer offset and sensitivity data. * * @param[in] dev : Structure instance of bmm350_dev. * * @return void */ static void update_mag_off_sens(struct bmm350_dev *dev); /*! * @brief This internal API converts the raw data from the IC data registers to signed integer * * @param[in] inval : Unsigned data from data registers * @param[in number_of_bits : Width of data register * * @return Conversion to signed integer */ static int32_t fix_sign(uint32_t inval, int8_t number_of_bits); /*! * @brief This internal API is used to read OTP word * * @param[in] addr : Stores OTP address * @param[in, out] lsb_msb : Pointer to store OTP word * @param[in, out] dev : Structure instance of bmm350_dev. * * @return Result of API execution status * @retval = 0 -> Success * @retval < 0 -> Error */ static int8_t read_otp_word(uint8_t addr, uint16_t *lsb_msb, struct bmm350_dev *dev); /*! * @brief This internal API is used to read raw magnetic x,y and z axis data along with temperature. * * @param[out] out_data : Pointer variable to store mag and temperature data. * @param[in, out] dev : Structure instance of bmm350_dev. * * @return Result of API execution status * @retval = 0 -> Success * @retval < 0 -> Error */ #ifdef BMM350_USE_FIXED_POINT static int8_t read_out_raw_data(fixed_t *out_data, struct bmm350_dev *dev); #else static int8_t read_out_raw_data(float *out_data, struct bmm350_dev *dev); #endif #ifndef BMM350_USE_FIXED_POINT /*! * @brief This internal API is used to convert raw mag lsb data to uT and raw temperature data to degC. * * @param[in,out] lsb_to_ut_degc : Float variable to store converted value of mag lsb in micro tesla(uT) and * temperature data in degC. * * @return void */ static void update_default_coefiecents(float *lsb_to_ut_degc); #endif /*! * @brief This internal API is used to read OTP data after boot in user mode. * * @param[in, out] dev : Structure instance of bmm350_dev. * * @return Result of API execution status * @retval = 0 -> Success * @retval < 0 -> Error */ static int8_t otp_dump_after_boot(struct bmm350_dev *dev); /*! * @brief This internal API is used for self-test entry configuration * * @param[in, out] dev : Structure instance of bmm350_dev. * * @return Result of API execution status * @retval = 0 -> Success * @retval < 0 -> Error */ static int8_t self_test_entry_config(struct bmm350_dev *dev); /*! * @brief This internal API is used to test self-test for X and Y axis * * @param[in, out] out_data : Structure instance of bmm350_self_test. * @param[in, out] dev : Structure instance of bmm350_dev. * * @return Result of API execution status * @retval = 0 -> Success * @retval < 0 -> Error */ static int8_t self_test_xy_axis(struct bmm350_self_test *out_data, struct bmm350_dev *dev); /*! * @brief This internal API is used to set self-test configurations. * * @param[in] st_cmd : Variable to store self-test command. * @param[in] pmu_cmd : Variable to store PMU command. * @param[in, out] out_data : Structure instance of bmm350_self_test. * @param[in, out] dev : Structure instance of bmm350_dev. * * @return Result of API execution status * @retval = 0 -> Success * @retval < 0 -> Error */ static int8_t self_test_config(uint8_t st_cmd, uint8_t pmu_cmd, struct bmm350_self_test *out_data, struct bmm350_dev *dev); /*! * @brief This internal API is used to set powermode. * * @param[in] powermode : Variable to set new powermode. * @param[in, out] dev : Structure instance of bmm350_dev. * * @return Result of API execution status * @retval = 0 -> Success * @retval < 0 -> Error */ static int8_t set_powermode(enum bmm350_power_modes powermode, struct bmm350_dev *dev); /********************** Global function definitions ************************/ /*! * @brief This API gives the release version details of the BMM350 SensorAPI. */ int8_t bmm350_api_version(struct bmm350_version *api_version) { /* Variable to store the function result */ int8_t rslt = BMM350_OK; /* Check for null pointer in the device structure */ if (api_version == NULL) { /* Device structure pointer is not valid */ rslt = BMM350_E_NULL_PTR; } else { /* Populate the version details in the structure */ api_version->major = BMM350_VER_MAJOR; api_version->minor = BMM350_VER_MINOR; api_version->bugfix = BMM350_VER_BUGFIX; } return rslt; } /*! * @brief This API is the entry point. Call this API before using other APIs. */ int8_t bmm350_init(struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Variable to get chip id */ uint8_t chip_id = BMM350_DISABLE; /* Variable to store the command to power-off the OTP */ uint8_t otp_cmd = BMM350_OTP_CMD_PWR_OFF_OTP; /* Variable to store soft-reset command */ uint8_t soft_reset; /* Check for null pointer in the device structure */ rslt = null_ptr_check(dev); /* Proceed if null check is fine */ if (rslt == BMM350_OK) { dev->chip_id = 0; /* Assign axis_en with all axis enabled (BMM350_EN_XYZ_MSK) */ dev->axis_en = BMM350_EN_XYZ_MSK; rslt = bmm350_delay_us(BMM350_START_UP_TIME_FROM_POR, dev); if (rslt == BMM350_OK) { /* Soft-reset */ soft_reset = BMM350_CMD_SOFTRESET; /* Set the command in the command register */ rslt = bmm350_set_regs(BMM350_REG_CMD, &soft_reset, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(BMM350_SOFT_RESET_DELAY, dev); } } if (rslt == BMM350_OK) { /* Chip ID of the sensor is read */ rslt = bmm350_get_regs(BMM350_REG_CHIP_ID, &chip_id, 1, dev); if (rslt == BMM350_OK) { /* Assign chip_id to dev->chip_id */ dev->chip_id = chip_id; } } /* Check for chip id validity */ if ((rslt == BMM350_OK) && (dev->chip_id == BMM350_CHIP_ID)) { /* Download OTP memory */ rslt = otp_dump_after_boot(dev); if (rslt == BMM350_OK) { /* Power off OTP */ rslt = bmm350_set_regs(BMM350_REG_OTP_CMD_REG, &otp_cmd, 1, dev); if (rslt == BMM350_OK) { if (dev->boot_done_status != BMM350_BOOT_NOT_DONE) { dev->boot_done_status = BMM350_BOOT_NOT_DONE; } rslt = bmm350_magnetic_reset_and_wait(dev); if (rslt == BMM350_OK) { dev->boot_done_status = BMM350_BOOT_DONE; } } } } else { rslt = BMM350_E_DEV_NOT_FOUND; } } return rslt; } /*! * @brief This API writes the given data to the register address * of the sensor. */ int8_t bmm350_set_regs(uint8_t reg_addr, const uint8_t *reg_data, uint16_t len, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Check for null pointer in the device structure */ rslt = null_ptr_check(dev); /* Proceed if null check is fine */ if ((rslt == BMM350_OK) && (reg_data != NULL) && (len != 0)) { /* Write the data to the reg_addr */ dev->intf_rslt = dev->write(reg_addr, reg_data, len, dev->intf_ptr); if (dev->intf_rslt != BMM350_INTF_RET_SUCCESS) { rslt = BMM350_E_COM_FAIL; } } else { rslt = BMM350_E_NULL_PTR; } return rslt; } /*! * @brief This API reads the data from the given register address of sensor. */ int8_t bmm350_get_regs(uint8_t reg_addr, uint8_t *reg_data, uint16_t len, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Variable to define temporary length */ uint16_t temp_len = len + BMM350_DUMMY_BYTES; /* Variable to define temporary buffer */ uint8_t temp_buf[BMM350_READ_BUFFER_LENGTH]; /* Variable to define loop */ uint16_t index = 0; /* Check for null pointer in the device structure */ rslt = null_ptr_check(dev); /* Proceed if null check is fine */ if ((rslt == BMM350_OK) && (reg_data != NULL)) { /* Read the data from the reg_addr */ dev->intf_rslt = dev->read(reg_addr, temp_buf, temp_len, dev->intf_ptr); if (dev->intf_rslt == BMM350_INTF_RET_SUCCESS) { /* Copy data after dummy byte indices */ while (index < len) { reg_data[index] = temp_buf[index + BMM350_DUMMY_BYTES]; index++; } } else { rslt = BMM350_E_COM_FAIL; } } else { rslt = BMM350_E_NULL_PTR; } return rslt; } /*! * @brief This function provides the delay for required time (Microsecond) as per the input provided in some of the * APIs. */ int8_t bmm350_delay_us(uint32_t period_us, const struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Check for null pointer in the device structure */ rslt = null_ptr_check(dev); if (rslt == BMM350_OK) { dev->delay_us(period_us, dev->intf_ptr); } return rslt; } /*! * @brief This API is used to perform soft-reset of the sensor * where all the registers are reset to their default values */ int8_t bmm350_soft_reset(struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t reg_data; /* Variable to store the command to power-off the OTP */ uint8_t otp_cmd = BMM350_OTP_CMD_PWR_OFF_OTP; /* Check for null pointer in the device structure */ rslt = null_ptr_check(dev); if (rslt == BMM350_OK) { reg_data = BMM350_CMD_SOFTRESET; /* Set the command in the command register */ rslt = bmm350_set_regs(BMM350_REG_CMD, ®_data, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(BMM350_SOFT_RESET_DELAY, dev); if (rslt == BMM350_OK) { /* Power off OTP */ rslt = bmm350_set_regs(BMM350_REG_OTP_CMD_REG, &otp_cmd, 1, dev); if (rslt == BMM350_OK) { if (dev->boot_done_status != BMM350_BOOT_NOT_DONE) { dev->boot_done_status = BMM350_BOOT_NOT_DONE; } rslt = bmm350_magnetic_reset_and_wait(dev); if (rslt == BMM350_OK) { dev->boot_done_status = BMM350_BOOT_DONE; } } } } } return rslt; } /*! * @brief This API is used to read the sensor time. * It converts the sensor time register values to the representative time value. * Returns the sensor time in ticks. */ int8_t bmm350_read_sensortime(uint32_t *seconds, uint32_t *nanoseconds, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; #ifdef BMM350_USE_FIXED_POINT uint32_t time; #else uint64_t time; #endif uint8_t reg_data[3]; if ((seconds != NULL) && (nanoseconds != NULL)) { /* Get sensor time raw data */ rslt = bmm350_get_regs(BMM350_REG_SENSORTIME_XLSB, reg_data, 3, dev); if (rslt == BMM350_OK) { time = (reg_data[0] + ((uint32_t)reg_data[1] << 8) + ((uint32_t)reg_data[2] << 16)); #ifdef BMM350_USE_FIXED_POINT fixed_t fixed_time = fixed_mul_A48_16((fixed_t)(time << FRAC_BITS), A48_16_0_0000390625); /* Converting nanoseconds to seconds by dividing the value with 10^6 */ *seconds = (uint32_t)(fixed_time >> F16_FRAC_BITS); /* Scaling the remainder nanoseconds in the decimal place */ *nanoseconds = ((fixed_time & 0XFFFF) * BMM350_SENSOR_TIME_NS_SCALING) >> F16_FRAC_BITS; #else /* 1 LSB is 39.0625us. Converting to nanoseconds */ time *= UINT64_C(390625); time /= UINT64_C(10); *seconds = (uint32_t)(time / UINT64_C(1000000000)); *nanoseconds = (uint32_t)(time - ((*seconds) * UINT64_C(1000000000))); #endif } } else { rslt = BMM350_E_NULL_PTR; } return rslt; } /*! * @brief This API is used to get the status flags of all interrupt * which is used to check for the assertion of interrupts */ int8_t bmm350_get_interrupt_status(uint8_t *drdy_status, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t int_status_reg; if (drdy_status != NULL) { /* Get the status of interrupt */ rslt = bmm350_get_regs(BMM350_REG_INT_STATUS, &int_status_reg, 1, dev); if (rslt == BMM350_OK) { /* Read the interrupt status */ (*drdy_status) = BMM350_GET_BITS(int_status_reg, BMM350_DRDY_DATA_REG); } } else { rslt = BMM350_E_NULL_PTR; } return rslt; } /*! * @brief This API is used to set the power mode of the sensor */ int8_t bmm350_set_powermode(enum bmm350_power_modes powermode, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t last_pwr_mode; uint8_t reg_data; /* Check for null pointer in the device structure */ rslt = null_ptr_check(dev); if (rslt == BMM350_OK) { rslt = bmm350_get_regs(BMM350_REG_PMU_CMD, &last_pwr_mode, 1, dev); if (rslt == BMM350_OK) { if (last_pwr_mode > BMM350_PMU_CMD_BR_FAST) { rslt = BMM350_E_INVALID_CONFIG; } if ((rslt == BMM350_OK) && ((last_pwr_mode == BMM350_PMU_CMD_NM) || (last_pwr_mode == BMM350_PMU_CMD_UPD_OAE))) { reg_data = BMM350_PMU_CMD_SUS; /* Set PMU command configuration */ rslt = bmm350_set_regs(BMM350_REG_PMU_CMD, ®_data, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(BMM350_GOTO_SUSPEND_DELAY, dev); } } if (rslt == BMM350_OK) { rslt = set_powermode(powermode, dev); } } } return rslt; } /*! * @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, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Variable to get PMU command */ uint8_t reg_data = 0; enum bmm350_performance_parameters performance_fix = performance; /* Check for null pointer in the device structure */ rslt = null_ptr_check(dev); 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_set_regs(BMM350_REG_PMU_CMD_AGGR_SET, ®_data, 1, dev); 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_set_regs(BMM350_REG_PMU_CMD, ®_data, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(BMM350_UPD_OAE_DELAY, dev); } } } return rslt; } /*! * @brief This API is used to enable or disable the magnetic * measurement of x,y,z axes */ int8_t bmm350_enable_axes(enum bmm350_x_axis_en_dis en_x, enum bmm350_y_axis_en_dis en_y, enum bmm350_z_axis_en_dis en_z, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Variable to store axis data */ uint8_t data; /* Check for null pointer in the device structure */ rslt = null_ptr_check(dev); if (rslt == BMM350_OK) { if ((en_x == BMM350_X_DIS) && (en_y == BMM350_Y_DIS) && (en_z == BMM350_Z_DIS)) { rslt = BMM350_E_ALL_AXIS_DISABLED; /* Assign axis_en with all axis disabled status */ dev->axis_en = BMM350_DISABLE; } else { data = (en_x & BMM350_EN_X_MSK); data = BMM350_SET_BITS(data, BMM350_EN_Y, en_y); data = BMM350_SET_BITS(data, BMM350_EN_Z, en_z); rslt = bmm350_set_regs(BMM350_REG_PMU_CMD_AXIS_EN, &data, 1, dev); if (rslt == BMM350_OK) { /* Assign axis_en with the axis selection done */ dev->axis_en = data; } } } return rslt; } /*! * @brief This API is used to enable or disable the data ready interrupt */ int8_t bmm350_enable_interrupt(enum bmm350_interrupt_enable_disable enable_disable, struct bmm350_dev *dev) { /* Variable to get interrupt control configuration */ uint8_t reg_data = 0; /* Variable to store the function result */ int8_t rslt; /* Get interrupt control configuration */ rslt = bmm350_get_regs(BMM350_REG_INT_CTRL, ®_data, 1, dev); if (rslt == BMM350_OK) { reg_data = BMM350_SET_BITS(reg_data, BMM350_DRDY_DATA_REG_EN, (uint8_t)enable_disable); /* Finally transfer the interrupt configurations */ rslt = bmm350_set_regs(BMM350_REG_INT_CTRL, ®_data, 1, dev); } return rslt; } /*! * @brief This API is used to configure the interrupt control settings */ int8_t bmm350_configure_interrupt(enum bmm350_intr_latch latching, enum bmm350_intr_polarity polarity, enum bmm350_intr_drive drivertype, enum bmm350_intr_map map_nomap, struct bmm350_dev *dev) { /* Variable to get interrupt control configuration */ uint8_t reg_data = 0; /* Variable to store the function result */ int8_t rslt; /* Get interrupt control configuration */ rslt = bmm350_get_regs(BMM350_REG_INT_CTRL, ®_data, 1, dev); if (rslt == BMM350_OK) { reg_data = BMM350_SET_BITS_POS_0(reg_data, BMM350_INT_MODE, latching); reg_data = BMM350_SET_BITS(reg_data, BMM350_INT_POL, polarity); reg_data = BMM350_SET_BITS(reg_data, BMM350_INT_OD, drivertype); reg_data = BMM350_SET_BITS(reg_data, BMM350_INT_OUTPUT_EN, map_nomap); /* Finally transfer the interrupt configurations */ rslt = bmm350_set_regs(BMM350_REG_INT_CTRL, ®_data, 1, dev); } return rslt; } /*! * @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, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t mag_data[12] = { 0 }; uint32_t raw_mag_x, raw_mag_y, raw_mag_z, raw_temp; if (raw_data != NULL) { /* Get uncompensated mag data */ rslt = bmm350_get_regs(BMM350_REG_MAG_X_XLSB, mag_data, BMM350_MAG_TEMP_DATA_LEN, dev); if (rslt == BMM350_OK) { raw_mag_x = (uint32_t)mag_data[0] + ((uint32_t)mag_data[1] << 8) + ((uint32_t)mag_data[2] << 16); raw_mag_y = (uint32_t)mag_data[3] + ((uint32_t)mag_data[4] << 8) + ((uint32_t)mag_data[5] << 16); raw_mag_z = (uint32_t)mag_data[6] + ((uint32_t)mag_data[7] << 8) + ((uint32_t)mag_data[8] << 16); raw_temp = (uint32_t)mag_data[9] + ((uint32_t)mag_data[10] << 8) + ((uint32_t)mag_data[11] << 16); if ((dev->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 ((dev->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 ((dev->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; } /*! * @brief This API sets the interrupt control IBI configurations to the sensor. */ int8_t bmm350_set_int_ctrl_ibi(enum bmm350_drdy_int_map_to_ibi en_dis, enum bmm350_clear_drdy_int_status_upon_ibi clear_on_ibi, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Variable to get interrupt control configuration */ uint8_t reg_data = 0; /* Get interrupt control configuration */ rslt = bmm350_get_regs(BMM350_REG_INT_CTRL_IBI, ®_data, 1, dev); if (rslt == BMM350_OK) { reg_data = BMM350_SET_BITS_POS_0(reg_data, BMM350_DRDY_INT_MAP_TO_IBI, en_dis); reg_data = BMM350_SET_BITS(reg_data, BMM350_CLEAR_DRDY_INT_STATUS_UPON_IBI, clear_on_ibi); /* Set the IBI control configuration */ rslt = bmm350_set_regs(BMM350_REG_INT_CTRL_IBI, ®_data, 1, dev); if (en_dis == BMM350_IBI_ENABLE) { /* Enable data ready interrupt if IBI is enabled */ rslt = bmm350_enable_interrupt(BMM350_ENABLE_INTERRUPT, dev); } } return rslt; } /*! * @brief This API is used to set the pad drive strength */ int8_t bmm350_set_pad_drive(uint8_t drive, struct bmm350_dev *dev) { uint8_t reg_data; /* Variable to store the function result */ int8_t rslt = BMM350_E_BAD_PAD_DRIVE; if (drive <= BMM350_PAD_DRIVE_STRONGEST) { reg_data = drive & BMM350_DRV_MSK; /* Set drive */ rslt = bmm350_set_regs(BMM350_REG_PAD_CTRL, ®_data, 1, dev); } return rslt; } /*! * @brief This API is used to perform the magnetic reset of the sensor * which is necessary after a field shock (400mT field applied to sensor). * It sends flux guide or bit reset to the device in suspend mode. */ int8_t bmm350_magnetic_reset_and_wait(struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t pmu_cmd = 0; struct bmm350_pmu_cmd_status_0 pmu_cmd_stat_0 = { 0 }; uint8_t restore_normal = BMM350_DISABLE; rslt = null_ptr_check(dev); if ((rslt == BMM350_OK) && (dev->mraw_override)) { rslt = dev->mraw_override(dev); } else { /* Read PMU CMD status */ rslt = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); /* Check the powermode is normal before performing magnetic reset */ if ((rslt == BMM350_OK) && (pmu_cmd_stat_0.pwr_mode_is_normal == BMM350_ENABLE)) { restore_normal = BMM350_ENABLE; /* Reset can only be triggered in suspend */ rslt = bmm350_set_powermode(BMM350_SUSPEND_MODE, dev); } if (rslt == BMM350_OK) { /* Set BR to PMU_CMD register */ pmu_cmd = BMM350_PMU_CMD_BR; rslt = bmm350_set_regs(BMM350_REG_PMU_CMD, &pmu_cmd, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(BMM350_BR_DELAY, dev); } } if (rslt == BMM350_OK) { /* Verify if PMU_CMD_STATUS_0 register has BR set */ rslt = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); if ((rslt == BMM350_OK) && (pmu_cmd_stat_0.pmu_cmd_value != BMM350_PMU_CMD_STATUS_0_BR)) { rslt = BMM350_E_PMU_CMD_VALUE; } } if (rslt == BMM350_OK) { /* Set FGR to PMU_CMD register */ pmu_cmd = BMM350_PMU_CMD_FGR; rslt = bmm350_set_regs(BMM350_REG_PMU_CMD, &pmu_cmd, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(BMM350_FGR_DELAY, dev); } } if (rslt == BMM350_OK) { /* Verify if PMU_CMD_STATUS_0 register has FGR set */ rslt = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); if ((rslt == BMM350_OK) && (pmu_cmd_stat_0.pmu_cmd_value != BMM350_PMU_CMD_STATUS_0_FGR)) { rslt = BMM350_E_PMU_CMD_VALUE; } } if ((rslt == BMM350_OK) && (restore_normal == BMM350_ENABLE)) { rslt = bmm350_set_powermode(BMM350_NORMAL_MODE, dev); } } return rslt; } #ifdef BMM350_USE_FIXED_POINT int8_t bmm350_get_compensated_mag_xyz_temp_data_fixed(struct bmm350_mag_temp_data *mag_temp_data, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t indx; fixed_t out_data[4] = { 0 }; fixed_t dut_offset_coef[3], dut_sensit_coef[3], dut_tco[3], dut_tcs[3]; fixed_t cr_ax_comp_x, cr_ax_comp_y, cr_ax_comp_z; fixed_t fact_1 = 0, fact_2 = 0; fixed_t temp_xy1 = 0, temp_xy2 = 0, temp_xy3 = 0, temp_z1 = 0, temp_z2 = 0; 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] = (fixed_mul_A48_16(((1 << F16_FRAC_BITS) + dev->mag_comp.dut_sensit_coef.t_sens), out_data[3]) + dev->mag_comp.dut_offset_coef.t_offs); /* Store magnetic compensation structure to an array */ dut_offset_coef[0] = dev->mag_comp.dut_offset_coef.offset_x; dut_offset_coef[1] = dev->mag_comp.dut_offset_coef.offset_y; dut_offset_coef[2] = dev->mag_comp.dut_offset_coef.offset_z; dut_sensit_coef[0] = dev->mag_comp.dut_sensit_coef.sens_x; dut_sensit_coef[1] = dev->mag_comp.dut_sensit_coef.sens_y; dut_sensit_coef[2] = dev->mag_comp.dut_sensit_coef.sens_z; dut_tco[0] = dev->mag_comp.dut_tco.tco_x; dut_tco[1] = dev->mag_comp.dut_tco.tco_y; dut_tco[2] = dev->mag_comp.dut_tco.tco_z; dut_tcs[0] = dev->mag_comp.dut_tcs.tcs_x; dut_tcs[1] = dev->mag_comp.dut_tcs.tcs_y; dut_tcs[2] = dev->mag_comp.dut_tcs.tcs_z; /* Compensate raw magnetic data */ for (indx = 0; indx < 3; indx++) { out_data[indx] = fixed_mul_A48_16(out_data[indx], fixed_add(FIXED_ONE, dut_sensit_coef[indx])); out_data[indx] = fixed_add(out_data[indx], dut_offset_coef[indx]); fact_1 = fixed_mul_A48_16(dut_tco[indx], (out_data[3] - dev->mag_comp.dut_t0)); out_data[indx] = fixed_add(out_data[indx], fact_1); fact_2 = fixed_add((1 << F16_FRAC_BITS), fixed_mul_A48_16(dut_tcs[indx], (out_data[3] - dev->mag_comp.dut_t0))); out_data[indx] = fixed_div(out_data[indx], fact_2); } temp_xy1 = (1 << F16_FRAC_BITS) - fixed_mul_A48_16(dev->mag_comp.cross_axis.cross_y_x, dev->mag_comp.cross_axis.cross_x_y); temp_xy2 = fixed_mul_A48_16(dev->mag_comp.cross_axis.cross_x_y, out_data[1]); cr_ax_comp_x = (fixed_div((out_data[0] - temp_xy2), temp_xy1)); temp_xy3 = fixed_mul_A48_16(dev->mag_comp.cross_axis.cross_y_x, out_data[0]); cr_ax_comp_y = fixed_div((out_data[1] - temp_xy3), temp_xy1); temp_z1 = fixed_mul_A48_16(dev->mag_comp.cross_axis.cross_y_x, dev->mag_comp.cross_axis.cross_z_y); temp_z2 = fixed_mul_A48_16(dev->mag_comp.cross_axis.cross_x_y, dev->mag_comp.cross_axis.cross_z_x); cr_ax_comp_z = out_data[2] + fixed_div((fixed_mul_A48_16(out_data[0], (temp_z1 - dev->mag_comp.cross_axis.cross_z_x))) - (fixed_mul_A48_16(out_data[1], (dev->mag_comp.cross_axis.cross_z_y - temp_z2))), temp_xy1); out_data[0] = cr_ax_comp_x; out_data[1] = cr_ax_comp_y; out_data[2] = cr_ax_comp_z; } if (rslt == BMM350_OK) { if ((dev->axis_en & BMM350_EN_X_MSK) == BMM350_DISABLE) { mag_temp_data->x = BMM350_DISABLE; } else { mag_temp_data->x = out_data[0]; } if ((dev->axis_en & BMM350_EN_Y_MSK) == BMM350_DISABLE) { mag_temp_data->y = BMM350_DISABLE; } else { mag_temp_data->y = out_data[1]; } if ((dev->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; } #else /*! * @brief This API is used to perform compensation for raw magnetometer and temperature data. */ int8_t bmm350_get_compensated_mag_xyz_temp_data(struct bmm350_mag_temp_data *mag_temp_data, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t indx; float out_data[4] = { 0.0f }; float dut_offset_coef[3], dut_sensit_coef[3], dut_tco[3], dut_tcs[3]; float 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] = (1 + dev->mag_comp.dut_sensit_coef.t_sens) * out_data[3] + dev->mag_comp.dut_offset_coef.t_offs; /* Store magnetic compensation structure to an array */ dut_offset_coef[0] = dev->mag_comp.dut_offset_coef.offset_x; dut_offset_coef[1] = dev->mag_comp.dut_offset_coef.offset_y; dut_offset_coef[2] = dev->mag_comp.dut_offset_coef.offset_z; dut_sensit_coef[0] = dev->mag_comp.dut_sensit_coef.sens_x; dut_sensit_coef[1] = dev->mag_comp.dut_sensit_coef.sens_y; dut_sensit_coef[2] = dev->mag_comp.dut_sensit_coef.sens_z; dut_tco[0] = dev->mag_comp.dut_tco.tco_x; dut_tco[1] = dev->mag_comp.dut_tco.tco_y; dut_tco[2] = dev->mag_comp.dut_tco.tco_z; dut_tcs[0] = dev->mag_comp.dut_tcs.tcs_x; dut_tcs[1] = dev->mag_comp.dut_tcs.tcs_y; dut_tcs[2] = dev->mag_comp.dut_tcs.tcs_z; /* Compensate raw magnetic data */ for (indx = 0; indx < 3; indx++) { out_data[indx] *= 1 + dut_sensit_coef[indx]; out_data[indx] += dut_offset_coef[indx]; out_data[indx] += dut_tco[indx] * (out_data[3] - dev->mag_comp.dut_t0); out_data[indx] /= 1 + dut_tcs[indx] * (out_data[3] - dev->mag_comp.dut_t0); } cr_ax_comp_x = (out_data[0] - dev->mag_comp.cross_axis.cross_x_y * out_data[1]) / (1 - dev->mag_comp.cross_axis.cross_y_x * dev->mag_comp.cross_axis.cross_x_y); cr_ax_comp_y = (out_data[1] - dev->mag_comp.cross_axis.cross_y_x * out_data[0]) / (1 - dev->mag_comp.cross_axis.cross_y_x * dev->mag_comp.cross_axis.cross_x_y); cr_ax_comp_z = (out_data[2] + (out_data[0] * (dev->mag_comp.cross_axis.cross_y_x * dev->mag_comp.cross_axis.cross_z_y - dev->mag_comp.cross_axis.cross_z_x) - out_data[1] * (dev->mag_comp.cross_axis.cross_z_y - dev->mag_comp.cross_axis.cross_x_y * dev->mag_comp.cross_axis.cross_z_x)) / (1 - dev->mag_comp.cross_axis.cross_y_x * dev->mag_comp.cross_axis.cross_x_y)); out_data[0] = cr_ax_comp_x; out_data[1] = cr_ax_comp_y; out_data[2] = cr_ax_comp_z; } if (rslt == BMM350_OK) { if ((dev->axis_en & BMM350_EN_X_MSK) == BMM350_DISABLE) { mag_temp_data->x = BMM350_DISABLE; } else { mag_temp_data->x = out_data[0]; } if ((dev->axis_en & BMM350_EN_Y_MSK) == BMM350_DISABLE) { mag_temp_data->y = BMM350_DISABLE; } else { mag_temp_data->y = out_data[1]; } if ((dev->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; } #endif /*! * @brief This function executes FGR and BR sequences to initialize TMR sensor and performs the user self-test. */ int8_t bmm350_perform_self_test(struct bmm350_self_test *out_data, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Variable to store last powermode */ uint8_t last_pwr_mode; if (out_data != NULL) { rslt = bmm350_get_regs(BMM350_REG_PMU_CMD, &last_pwr_mode, 1, dev); if (rslt == BMM350_OK) { /* Self-test entry configuration */ rslt = self_test_entry_config(dev); if (rslt == BMM350_OK) { /* Updates self-test values to structure */ rslt = self_test_xy_axis(out_data, dev); } } if (rslt == BMM350_OK) { /* Setup DUT: disable user self-test */ rslt = bmm350_set_tmr_selftest_user(BMM350_ST_IGEN_DIS, BMM350_ST_N_DIS, BMM350_ST_P_DIS, BMM350_IST_X_DIS, BMM350_IST_Y_DIS, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(1000, dev); } if (last_pwr_mode == BMM350_PMU_CMD_NM) { rslt = bmm350_set_powermode(BMM350_NORMAL_MODE, dev); } } } else { rslt = BMM350_E_NULL_PTR; } return rslt; } /*! * @brief This API sets the I2C watchdog timer configurations to the sensor. */ int8_t bmm350_set_i2c_wdt(enum bmm350_i2c_wdt_en i2c_wdt_en_dis, enum bmm350_i2c_wdt_sel i2c_wdt_sel, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t reg_data; /* Get I2C WDT configuration */ rslt = bmm350_get_regs(BMM350_REG_I2C_WDT_SET, ®_data, 1, dev); if (rslt == BMM350_OK) { reg_data = BMM350_SET_BITS_POS_0(reg_data, BMM350_I2C_WDT_EN, i2c_wdt_en_dis); reg_data = BMM350_SET_BITS(reg_data, BMM350_I2C_WDT_SEL, i2c_wdt_sel); /* Set I2C WDT configuration */ rslt = bmm350_set_regs(BMM350_REG_I2C_WDT_SET, ®_data, 1, dev); } return rslt; } /*! * @brief This API sets the TMR user self-test register */ int8_t bmm350_set_tmr_selftest_user(enum bmm350_st_igen_en st_igen_en_dis, enum bmm350_st_n st_n_en_dis, enum bmm350_st_p st_p_en_dis, enum bmm350_ist_en_x ist_x_en_dis, enum bmm350_ist_en_y ist_y_en_dis, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t reg_data; /* Get TMR self-test user configuration */ rslt = bmm350_get_regs(BMM350_REG_TMR_SELFTEST_USER, ®_data, 1, dev); if (rslt == BMM350_OK) { reg_data = BMM350_SET_BITS_POS_0(reg_data, BMM350_ST_IGEN_EN, st_igen_en_dis); reg_data = BMM350_SET_BITS(reg_data, BMM350_ST_N, st_n_en_dis); reg_data = BMM350_SET_BITS(reg_data, BMM350_ST_P, st_p_en_dis); reg_data = BMM350_SET_BITS(reg_data, BMM350_IST_EN_X, ist_x_en_dis); reg_data = BMM350_SET_BITS(reg_data, BMM350_IST_EN_Y, ist_y_en_dis); /* Set TMR self-test user configuration */ rslt = bmm350_set_regs(BMM350_REG_TMR_SELFTEST_USER, ®_data, 1, dev); } return rslt; } /*! * @brief This API sets the control user configurations to the sensor which forces the sensor timer to be always * running, even in suspend mode. */ int8_t bmm350_set_ctrl_user(enum bmm350_ctrl_user cfg_sens_tim_aon_en_dis, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t reg_data; /* Get control user configuration */ rslt = bmm350_get_regs(BMM350_REG_CTRL_USER, ®_data, 1, dev); if (rslt == BMM350_OK) { reg_data = BMM350_SET_BITS_POS_0(reg_data, BMM350_CFG_SENS_TIM_AON, cfg_sens_tim_aon_en_dis); /* Set control user configuration */ rslt = bmm350_set_regs(BMM350_REG_CTRL_USER, ®_data, 1, dev); } return rslt; } /*! * @brief This API gets the PMU command status 0 value */ int8_t bmm350_get_pmu_cmd_status_0(struct bmm350_pmu_cmd_status_0 *pmu_cmd_stat_0, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t reg_data; if (pmu_cmd_stat_0 != NULL) { /* Get PMU command status 0 data */ rslt = bmm350_get_regs(BMM350_REG_PMU_CMD_STATUS_0, ®_data, 1, dev); if (rslt == BMM350_OK) { pmu_cmd_stat_0->pmu_cmd_busy = BMM350_GET_BITS_POS_0(reg_data, BMM350_PMU_CMD_BUSY); pmu_cmd_stat_0->odr_ovwr = BMM350_GET_BITS(reg_data, BMM350_ODR_OVWR); pmu_cmd_stat_0->avr_ovwr = BMM350_GET_BITS(reg_data, BMM350_AVG_OVWR); pmu_cmd_stat_0->pwr_mode_is_normal = BMM350_GET_BITS(reg_data, BMM350_PWR_MODE_IS_NORMAL); pmu_cmd_stat_0->cmd_is_illegal = BMM350_GET_BITS(reg_data, BMM350_CMD_IS_ILLEGAL); pmu_cmd_stat_0->pmu_cmd_value = BMM350_GET_BITS(reg_data, BMM350_PMU_CMD_VALUE); } } else { rslt = BMM350_E_NULL_PTR; } return rslt; } #ifdef BMM350_USE_FIXED_POINT /*! * @brief This API is used to compute the square root in fixed point. * @param[in] inp : input, whose square root needs to be computed * * @return square root of the input */ uint16_t bmm350_fixed_point_sqrt(uint32_t inp) { /* Variable for computing the square root */ uint32_t root = 0; /* Reference for comparison with input square value, by default set to (2^30) */ uint32_t place = ((uint32_t)1 << 30); /* Condition check for optimization. For 0 and 1, skip the computation loop as the output and input * is same for these values*/ if (inp > 1) { /* Scale the reference to the scale of input square value by downscaling * the reference with the power of 2 */ while (place > inp) { place >>= 2; } /* Compute the square root value by narrowing down the subsequent LSB values * by iterative subtraction */ while (place != 0) { if (inp >= (root + place)) { inp -= root + place; root += (place << 1); } root = root >> 1; place = place >> 2; } } /* Return the square root */ return (uint16_t)root; } #endif /****************************************************************************/ /**\name INTERNAL APIs */ /*! * @brief This internal API is used to validate the device structure pointer for * null conditions. */ static int8_t null_ptr_check(const struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; if ((dev == NULL) || (dev->read == NULL) || (dev->write == NULL) || (dev->delay_us == NULL)) { /* Device structure pointer is not valid */ rslt = BMM350_E_NULL_PTR; } else { /* Device structure is fine */ rslt = BMM350_OK; } return rslt; } /*! * @brief This internal API converts the raw data from the IC data registers to signed integer */ static int32_t fix_sign(uint32_t inval, int8_t number_of_bits) { int32_t power = 0; int32_t retval; switch (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; } retval = (int32_t)inval; if (retval >= power) { retval = retval - (power * 2); } return retval; } /*! * @brief This internal API is used to read OTP word */ static int8_t read_otp_word(uint8_t addr, uint16_t *lsb_msb, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t otp_cmd, otp_status = 0, otp_err = BMM350_OTP_STATUS_NO_ERROR, lsb = 0, msb = 0; if (lsb_msb != NULL) { /* Set OTP command at specified address */ otp_cmd = BMM350_OTP_CMD_DIR_READ | (addr & BMM350_OTP_WORD_ADDR_MSK); rslt = bmm350_set_regs(BMM350_REG_OTP_CMD_REG, &otp_cmd, 1, dev); if (rslt == BMM350_OK) { do { rslt = bmm350_delay_us(300, dev); if (rslt == BMM350_OK) { /* Get OTP status */ rslt = bmm350_get_regs(BMM350_REG_OTP_STATUS_REG, &otp_status, 1, dev); 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)) && (rslt == BMM350_OK)); if (otp_err != BMM350_OTP_STATUS_NO_ERROR) { switch (otp_err) { case BMM350_OTP_STATUS_BOOT_ERR: rslt = BMM350_E_OTP_BOOT; break; case BMM350_OTP_STATUS_PAGE_RD_ERR: rslt = BMM350_E_OTP_PAGE_RD; break; case BMM350_OTP_STATUS_PAGE_PRG_ERR: rslt = BMM350_E_OTP_PAGE_PRG; break; case BMM350_OTP_STATUS_SIGN_ERR: rslt = BMM350_E_OTP_SIGN; break; case BMM350_OTP_STATUS_INV_CMD_ERR: rslt = BMM350_E_OTP_INV_CMD; break; default: rslt = BMM350_E_OTP_UNDEFINED; break; } } } if (rslt == BMM350_OK) { /* Get OTP MSB data */ rslt = bmm350_get_regs(BMM350_REG_OTP_DATA_MSB_REG, &msb, 1, dev); if (rslt == BMM350_OK) { /* Get OTP LSB data */ rslt = bmm350_get_regs(BMM350_REG_OTP_DATA_LSB_REG, &lsb, 1, dev); *lsb_msb = ((uint16_t)(msb << 8) | lsb) & 0xFFFF; } } } else { rslt = BMM350_E_NULL_PTR; } return rslt; } /*! * @brief This internal API is used to update magnetometer offset and sensitivity data. */ static void update_mag_off_sens(struct bmm350_dev *dev) { 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 = dev->otp_data[BMM350_MAG_OFFSET_X] & 0x0FFF; off_y_lsb_msb = ((dev->otp_data[BMM350_MAG_OFFSET_X] & 0xF000) >> 4) + (dev->otp_data[BMM350_MAG_OFFSET_Y] & BMM350_LSB_MASK); off_z_lsb_msb = (dev->otp_data[BMM350_MAG_OFFSET_Y] & 0x0F00) + (dev->otp_data[BMM350_MAG_OFFSET_Z] & BMM350_LSB_MASK); t_off = dev->otp_data[BMM350_TEMP_OFF_SENS] & BMM350_LSB_MASK; #ifdef BMM350_USE_FIXED_POINT dev->mag_comp.dut_offset_coef.offset_x = f16_from_int(fix_sign(off_x_lsb_msb, BMM350_SIGNED_12_BIT)); dev->mag_comp.dut_offset_coef.offset_y = f16_from_int(fix_sign(off_y_lsb_msb, BMM350_SIGNED_12_BIT)); dev->mag_comp.dut_offset_coef.offset_z = f16_from_int(fix_sign(off_z_lsb_msb, BMM350_SIGNED_12_BIT)); dev->mag_comp.dut_offset_coef.t_offs = fixed_mul_A48_16(f16_from_int(fix_sign(t_off, BMM350_SIGNED_8_BIT)), A48_16_0_2); #else dev->mag_comp.dut_offset_coef.offset_x = fix_sign(off_x_lsb_msb, BMM350_SIGNED_12_BIT); dev->mag_comp.dut_offset_coef.offset_y = fix_sign(off_y_lsb_msb, BMM350_SIGNED_12_BIT); dev->mag_comp.dut_offset_coef.offset_z = fix_sign(off_z_lsb_msb, BMM350_SIGNED_12_BIT); dev->mag_comp.dut_offset_coef.t_offs = fix_sign(t_off, BMM350_SIGNED_8_BIT) / 5.0f; #endif sens_x = (dev->otp_data[BMM350_MAG_SENS_X] & BMM350_MSB_MASK) >> 8; sens_y = (dev->otp_data[BMM350_MAG_SENS_Y] & BMM350_LSB_MASK); sens_z = (dev->otp_data[BMM350_MAG_SENS_Z] & BMM350_MSB_MASK) >> 8; t_sens = (dev->otp_data[BMM350_TEMP_OFF_SENS] & BMM350_MSB_MASK) >> 8; #ifdef BMM350_USE_FIXED_POINT dev->mag_comp.dut_sensit_coef.sens_x = fixed_mul_A48_16(f16_from_int(fix_sign(sens_x, BMM350_SIGNED_8_BIT)), A48_16_0_00390625); dev->mag_comp.dut_sensit_coef.sens_y = fixed_mul_A48_16(f16_from_int(fix_sign(sens_y, BMM350_SIGNED_8_BIT)), A48_16_0_00390625); dev->mag_comp.dut_sensit_coef.sens_z = fixed_mul_A48_16(f16_from_int(fix_sign(sens_z, BMM350_SIGNED_8_BIT)), A48_16_0_00390625); dev->mag_comp.dut_sensit_coef.t_sens = fixed_mul_A48_16(f16_from_int(fix_sign(t_sens, BMM350_SIGNED_8_BIT)), A48_16_0_001953125); #else dev->mag_comp.dut_sensit_coef.sens_x = fix_sign(sens_x, BMM350_SIGNED_8_BIT) / 256.0f; dev->mag_comp.dut_sensit_coef.sens_y = fix_sign(sens_y, BMM350_SIGNED_8_BIT) / 256.0f; dev->mag_comp.dut_sensit_coef.sens_z = fix_sign(sens_z, BMM350_SIGNED_8_BIT) / 256.0f; dev->mag_comp.dut_sensit_coef.t_sens = fix_sign(t_sens, BMM350_SIGNED_8_BIT) / 512.0f; #endif tco_x = (dev->otp_data[BMM350_MAG_TCO_X] & BMM350_LSB_MASK); tco_y = (dev->otp_data[BMM350_MAG_TCO_Y] & BMM350_LSB_MASK); tco_z = (dev->otp_data[BMM350_MAG_TCO_Z] & BMM350_LSB_MASK); #ifdef BMM350_USE_FIXED_POINT dev->mag_comp.dut_tco.tco_x = fixed_mul_A48_16(f16_from_int(fix_sign(tco_x, BMM350_SIGNED_8_BIT)), A48_16_0_03125); dev->mag_comp.dut_tco.tco_y = fixed_mul_A48_16(f16_from_int(fix_sign(tco_y, BMM350_SIGNED_8_BIT)), A48_16_0_03125); dev->mag_comp.dut_tco.tco_z = fixed_mul_A48_16(f16_from_int(fix_sign(tco_z, BMM350_SIGNED_8_BIT)), A48_16_0_03125); #else dev->mag_comp.dut_tco.tco_x = fix_sign(tco_x, BMM350_SIGNED_8_BIT) / 32.0f; dev->mag_comp.dut_tco.tco_y = fix_sign(tco_y, BMM350_SIGNED_8_BIT) / 32.0f; dev->mag_comp.dut_tco.tco_z = fix_sign(tco_z, BMM350_SIGNED_8_BIT) / 32.0f; #endif tcs_x = (dev->otp_data[BMM350_MAG_TCS_X] & BMM350_MSB_MASK) >> 8; tcs_y = (dev->otp_data[BMM350_MAG_TCS_Y] & BMM350_MSB_MASK) >> 8; tcs_z = (dev->otp_data[BMM350_MAG_TCS_Z] & BMM350_MSB_MASK) >> 8; #ifdef BMM350_USE_FIXED_POINT dev->mag_comp.dut_tcs.tcs_x = fixed_mul_A48_16(f16_from_int(fix_sign(tcs_x, BMM350_SIGNED_8_BIT)), A48_16_0_00006103515625); dev->mag_comp.dut_tcs.tcs_y = fixed_mul_A48_16(f16_from_int(fix_sign(tcs_y, BMM350_SIGNED_8_BIT)), A48_16_0_00006103515625); dev->mag_comp.dut_tcs.tcs_z = fixed_mul_A48_16(f16_from_int(fix_sign(tcs_z, BMM350_SIGNED_8_BIT)), A48_16_0_00006103515625); #else dev->mag_comp.dut_tcs.tcs_x = fix_sign(tcs_x, BMM350_SIGNED_8_BIT) / 16384.0f; dev->mag_comp.dut_tcs.tcs_y = fix_sign(tcs_y, BMM350_SIGNED_8_BIT) / 16384.0f; dev->mag_comp.dut_tcs.tcs_z = fix_sign(tcs_z, BMM350_SIGNED_8_BIT) / 16384.0f; #endif #ifdef BMM350_USE_FIXED_POINT dev->mag_comp.dut_t0 = fixed_add(fixed_mul_A48_16(f16_from_int(fix_sign(dev->otp_data[BMM350_MAG_DUT_T_0], BMM350_SIGNED_16_BIT)), A48_16_0_001953125), f16_from_int(23)); #else dev->mag_comp.dut_t0 = (fix_sign(dev->otp_data[BMM350_MAG_DUT_T_0], BMM350_SIGNED_16_BIT) / 512.0f) + 23.0f; #endif cross_x_y = (dev->otp_data[BMM350_CROSS_X_Y] & BMM350_LSB_MASK); cross_y_x = (dev->otp_data[BMM350_CROSS_Y_X] & BMM350_MSB_MASK) >> 8; cross_z_x = (dev->otp_data[BMM350_CROSS_Z_X] & BMM350_LSB_MASK); cross_z_y = (dev->otp_data[BMM350_CROSS_Z_Y] & BMM350_MSB_MASK) >> 8; #ifdef BMM350_USE_FIXED_POINT dev->mag_comp.cross_axis.cross_x_y = fixed_mul_A48_16(f16_from_int(fix_sign(cross_x_y, BMM350_SIGNED_8_BIT)), A48_16_0_00125); dev->mag_comp.cross_axis.cross_y_x = fixed_mul_A48_16(f16_from_int(fix_sign(cross_y_x, BMM350_SIGNED_8_BIT)), A48_16_0_00125); dev->mag_comp.cross_axis.cross_z_x = fixed_mul_A48_16(f16_from_int(fix_sign(cross_z_x, BMM350_SIGNED_8_BIT)), A48_16_0_00125); dev->mag_comp.cross_axis.cross_z_y = fixed_mul_A48_16(f16_from_int(fix_sign(cross_z_y, BMM350_SIGNED_8_BIT)), A48_16_0_00125); #else dev->mag_comp.cross_axis.cross_x_y = fix_sign(cross_x_y, BMM350_SIGNED_8_BIT) / 800.0f; dev->mag_comp.cross_axis.cross_y_x = fix_sign(cross_y_x, BMM350_SIGNED_8_BIT) / 800.0f; dev->mag_comp.cross_axis.cross_z_x = fix_sign(cross_z_x, BMM350_SIGNED_8_BIT) / 800.0f; dev->mag_comp.cross_axis.cross_z_y = fix_sign(cross_z_y, BMM350_SIGNED_8_BIT) / 800.0f; #endif } /*! * @brief This internal API is used to read raw magnetic x,y and z axis along with temperature */ #ifdef BMM350_USE_FIXED_POINT static int8_t read_out_raw_data(fixed_t *out_data, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; 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] = fixed_mul_A48_16(f16_from_int((int64_t)raw_data.raw_xdata), A48_16_0_007069979_X4); out_data[1] = fixed_mul_A48_16(f16_from_int((int64_t)raw_data.raw_ydata), A48_16_0_007069979_X4); out_data[2] = fixed_mul_A48_16(f16_from_int((int64_t)raw_data.raw_zdata), A48_16_0_007174964_X4); out_data[3] = fixed_mul_A48_16(f16_from_int((int64_t)raw_data.raw_data_t), A48_16_0_000981282_X4); out_data[0] = out_data[0] >> 2; /* Dividing by 4 using Right shift 2, as the coefficients are scaled by 4 */ out_data[1] = out_data[1] >> 2; /* Dividing by 4 using Right shift 2, as the coefficients are scaled by 4 */ out_data[2] = out_data[2] >> 2; /* Dividing by 4 using Right shift 2, as the coefficients are scaled by 4 */ out_data[3] = out_data[3] >> 2; /* Dividing by 4 using Right shift 2, as the coefficients are scaled by 4 */ out_data[3] = fixed_sub(out_data[3], A48_16_25_49); } } else { rslt = BMM350_E_NULL_PTR; } return rslt; } #else static int8_t read_out_raw_data(float *out_data, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Float variable to convert mag lsb to uT and temp lsb to degC */ float lsb_to_ut_degc[4]; 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 */ update_default_coefiecents(lsb_to_ut_degc); out_data[0] = (float)raw_data.raw_xdata * lsb_to_ut_degc[0]; out_data[1] = (float)raw_data.raw_ydata * lsb_to_ut_degc[1]; out_data[2] = (float)raw_data.raw_zdata * lsb_to_ut_degc[2]; out_data[3] = (float)raw_data.raw_data_t * lsb_to_ut_degc[3]; out_data[3] = (float)(out_data[3] - (1 * 25.49)); } } else { rslt = BMM350_E_NULL_PTR; } return rslt; } #endif #ifndef BMM350_USE_FIXED_POINT /*! * @brief This internal API is used to convert lsb to uT and degC. */ static void update_default_coefiecents(float *lsb_to_ut_degc) { float bxy_sens, bz_sens, temp_sens, ina_xy_gain_trgt, ina_z_gain_trgt, adc_gain, lut_gain; float power; bxy_sens = 14.55f; bz_sens = 9.0f; temp_sens = 0.00204f; ina_xy_gain_trgt = 19.46f; ina_z_gain_trgt = 31.0; adc_gain = 1 / 1.5f; lut_gain = 0.714607238769531f; power = (float)(1000000.0 / 1048576.0); lsb_to_ut_degc[0] = (power / (bxy_sens * ina_xy_gain_trgt * adc_gain * lut_gain)); lsb_to_ut_degc[1] = (power / (bxy_sens * ina_xy_gain_trgt * adc_gain * lut_gain)); lsb_to_ut_degc[2] = (power / (bz_sens * ina_z_gain_trgt * adc_gain * lut_gain)); lsb_to_ut_degc[3] = 1 / (temp_sens * adc_gain * lut_gain * 1048576); } #endif /*! * @brief This internal API is used to read OTP data after boot in user mode. */ static int8_t otp_dump_after_boot(struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint16_t otp_word = 0; uint8_t indx; for (indx = 0; indx < BMM350_OTP_DATA_LENGTH; indx++) { rslt = read_otp_word(indx, &otp_word, dev); dev->otp_data[indx] = otp_word; } dev->var_id = (dev->otp_data[30] & 0x7f00) >> 9; /* Set the default auto bit reset configuration */ dev->enable_auto_br = ((dev->var_id > BMM350_CURRENT_SHUTTLE_VARIANT_ID) ? BMM350_DISABLE : BMM350_ENABLE); /* Update magnetometer offset and sensitivity data. */ update_mag_off_sens(dev); return rslt; } /*! * @brief This internal API is used for self-test entry configuration */ static int8_t self_test_entry_config(struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Variable to store PMU command */ uint8_t cmd; /* Structure instance of PMU command status 0 */ struct bmm350_pmu_cmd_status_0 pmu_cmd_stat_0 = { 0 }; /* Set suspend mode */ cmd = BMM350_PMU_CMD_SUS; rslt = bmm350_set_regs(BMM350_REG_PMU_CMD, &cmd, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(30000, dev); } /* Read DUT outputs in FORCED mode */ if (rslt == BMM350_OK) { rslt = bmm350_set_odr_performance(BMM350_DATA_RATE_100HZ, BMM350_AVERAGING_2, dev); if (rslt == BMM350_OK) { /* Enable all axis */ rslt = bmm350_enable_axes(BMM350_X_EN, BMM350_Y_EN, BMM350_Z_EN, dev); } } /* Execute FGR with full CRST recharge */ cmd = BMM350_PMU_CMD_FGR; if (rslt == BMM350_OK) { rslt = bmm350_set_regs(BMM350_REG_PMU_CMD, &cmd, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(30000, dev); } } if (rslt == BMM350_OK) { rslt = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); if ((rslt == BMM350_OK) && (pmu_cmd_stat_0.pmu_cmd_value == BMM350_PMU_CMD_STATUS_0_FGR)) { /* Execute BR with full CRST recharge */ cmd = BMM350_PMU_CMD_BR_FAST; rslt = bmm350_set_regs(BMM350_REG_PMU_CMD, &cmd, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(4000, dev); } } } if (rslt == BMM350_OK) { rslt = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); } if ((rslt == BMM350_OK) && (pmu_cmd_stat_0.pmu_cmd_value == BMM350_PMU_CMD_STATUS_0_BR_FAST)) { cmd = BMM350_PMU_CMD_FM_FAST; rslt = bmm350_set_regs(BMM350_REG_PMU_CMD, &cmd, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(16000, dev); } if (rslt == BMM350_OK) { rslt = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); if ((rslt == BMM350_OK) && (pmu_cmd_stat_0.pmu_cmd_value == BMM350_PMU_CMD_STATUS_0_FM_FAST)) { rslt = bmm350_delay_us(10, dev); } } } return rslt; } /*! * @brief This internal API is used to test self-test for X and Y axis */ static int8_t self_test_xy_axis(struct bmm350_self_test *out_data, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; /* Set pmu command */ uint8_t cmd = BMM350_PMU_CMD_FM_FAST; /* Setup DUT: enable positive user self-test on x-axis */ rslt = self_test_config(BMM350_SELF_TEST_POS_X, cmd, out_data, dev); if (rslt == BMM350_OK) { /* Setup DUT: enable negative user self-test on x-axis */ rslt = self_test_config(BMM350_SELF_TEST_NEG_X, cmd, out_data, dev); if (rslt == BMM350_OK) { /* Setup DUT: enable positive user self-test on y-axis */ rslt = self_test_config(BMM350_SELF_TEST_POS_Y, cmd, out_data, dev); if (rslt == BMM350_OK) { /* Setup DUT: enable negative user self-test on y-axis */ rslt = self_test_config(BMM350_SELF_TEST_NEG_Y, cmd, out_data, dev); } } } return rslt; } /*! * @brief This internal API is used to set self-test configurations. */ static int8_t self_test_config(uint8_t st_cmd, uint8_t pmu_cmd, struct bmm350_self_test *out_data, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; #ifdef BMM350_USE_FIXED_POINT fixed_t out_ust[4]; #else float out_ust[4]; #endif struct bmm350_pmu_cmd_status_0 pmu_cmd_stat_0 = { 0 }; rslt = bmm350_set_regs(BMM350_REG_TMR_SELFTEST_USER, &st_cmd, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(1000, dev); } if (rslt == BMM350_OK) { rslt = bmm350_set_regs(BMM350_REG_PMU_CMD, &pmu_cmd, 1, dev); if (rslt == BMM350_OK) { rslt = bmm350_delay_us(6000, dev); if (rslt == BMM350_OK) { rslt = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, dev); } } } if ((rslt == BMM350_OK) && (pmu_cmd_stat_0.pmu_cmd_value == BMM350_PMU_CMD_STATUS_0_FM_FAST)) { /* Reads raw magnetic x and y axis */ rslt = read_out_raw_data(out_ust, dev); if (rslt == BMM350_OK) { /* Read DUT outputs in FORCED mode (XP_UST) */ if (st_cmd == BMM350_SELF_TEST_POS_X) { out_data->out_ust_xh = (out_ust[0]); } /* Read DUT outputs in FORCED mode (XN_UST) */ else if (st_cmd == BMM350_SELF_TEST_NEG_X) { out_data->out_ust_xl = (out_ust[0]); } /* Read DUT outputs in FORCED mode (YP_UST) */ else if (st_cmd == BMM350_SELF_TEST_POS_Y) { out_data->out_ust_yh = (out_ust[1]); } /* Read DUT outputs in FORCED mode (YN_UST) */ else if (st_cmd == BMM350_SELF_TEST_NEG_Y) { out_data->out_ust_yl = (out_ust[1]); /* As the self test sequence is completed here, compute self test results */ out_data->out_ust_x = out_data->out_ust_xh - out_data->out_ust_xl; out_data->out_ust_y = out_data->out_ust_yh - out_data->out_ust_yl; } else { /* Returns error if self-test axis is wrong */ rslt = BMM350_E_SELF_TEST_INVALID_AXIS; } } } return rslt; } /*! * @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, struct bmm350_dev *dev) { /* Variable to store the function result */ int8_t rslt; uint8_t reg_data = powermode; uint8_t get_avg; /* 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; rslt = null_ptr_check(dev); if (rslt == BMM350_OK) { /* Set PMU command configuration to desired power mode */ rslt = bmm350_set_regs(BMM350_REG_PMU_CMD, ®_data, 1, dev); if (rslt == BMM350_OK) { /* Get average configuration */ rslt = bmm350_get_regs(BMM350_REG_PMU_CMD_AGGR_SET, &get_avg, 1, dev); if (rslt == BMM350_OK) { /* Mask the average value */ avg = ((get_avg & BMM350_AVG_MSK) >> BMM350_AVG_POS); } } } if (rslt == BMM350_OK) { /* Check if desired power mode is normal mode */ 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]; } /* Perform delay based on power mode */ rslt = bmm350_delay_us(delay_us, dev); } return rslt; } #ifdef BMM350_USE_FIXED_POINT /* Add two U(16,16) fixed-point numbers */ fixed_t fixed_add(fixed_t a, fixed_t b) { return (int64_t)a + (int64_t)b; } fixed_t fixed_sub(fixed_t a, fixed_t b) { return a - b; } fixed_t fixed_mul_A48_16(fixed_t a, fixed_t b) { int sign = ((a < 0) ^ (b < 0)); /* track sign */ uint64_t ua = (a < 0) ? -a : a; /* abs(a) */ uint64_t ub = (b < 0) ? -b : b; /* abs(b) */ /* Split into 32-bit parts */ uint64_t a_lo = (uint32_t)ua; uint64_t a_hi = ua >> 32; uint64_t b_lo = (uint32_t)ub; uint64_t b_hi = ub >> 32; /* Partial products */ uint64_t lo_lo = a_lo * b_lo; /* 64-bit */ uint64_t lo_hi = a_lo * b_hi; uint64_t hi_lo = a_hi * b_lo; uint64_t hi_hi = a_hi * b_hi; /* Assemble 128-bit result */ uint64_t carry = (lo_lo >> 32) + (lo_hi & 0xFFFFFFFFULL) + (hi_lo & 0xFFFFFFFFULL); uint64_t low = (lo_lo & 0xFFFFFFFFULL) | (carry << 32); uint64_t high = hi_hi + (lo_hi >> 32) + (hi_lo >> 32) + (carry >> 32); /* Shift right by 16 (A48.16 scaling) */ uint64_t res = (high << (64 - 16)) | (low >> 16); return sign ? -(int64_t)res : (int64_t)res; } fixed_t fixed_div(fixed_t a, fixed_t b) { if (b == 0) { return (a >= 0) ? INT64_MAX : INT64_MIN; } int sign = ((a < 0) ^ (b < 0)); uint64_t ua = (a < 0) ? -a : a; uint64_t ub = (b < 0) ? -b : b; /* * Split shift to avoid 64-bit overflow * (a << 16) / b = ((a / b) << 16) + ((a % b) << 16) / b */ uint64_t q = ua / ub; uint64_t r = ua % ub; uint64_t result = (q << FRAC_BITS) + ((r << FRAC_BITS) / ub); return sign ? -(fixed_t)result : (fixed_t)result; } #endif