/* bmm150.c - Driver for Bosch BMM150 Geomagnetic Sensor */ /* * Copyright (c) 2017 Intel Corporation * Copyright (c) 2023 FTP Technologies * * SPDX-License-Identifier: Apache-2.0 */ #include #include "bmm150.h" LOG_MODULE_REGISTER(BMM150, CONFIG_SENSOR_LOG_LEVEL); static const struct { int freq; uint8_t reg_val; } bmm150_samp_freq_table[] = { { 2, 0x01 }, { 6, 0x02 }, { 8, 0x03 }, { 10, 0x00 }, { 15, 0x04 }, { 20, 0x05 }, { 25, 0x06 }, { 30, 0x07 } }; static const struct bmm150_preset { uint8_t rep_xy; uint8_t rep_z; uint8_t odr; } bmm150_presets_table[] = { [BMM150_LOW_POWER_PRESET] = { 3, 3, 10 }, [BMM150_REGULAR_PRESET] = { 9, 15, 10 }, [BMM150_ENHANCED_REGULAR_PRESET] = { 15, 27, 10 }, [BMM150_HIGH_ACCURACY_PRESET] = { 47, 83, 20 } }; static inline int bmm150_bus_check(const struct device *dev) { const struct bmm150_config *cfg = dev->config; return cfg->bus_io->check(&cfg->bus); } static inline int bmm150_reg_read(const struct device *dev, uint8_t start, uint8_t *buf, int size) { const struct bmm150_config *cfg = dev->config; return cfg->bus_io->read(&cfg->bus, start, buf, size); } static inline int bmm150_reg_write(const struct device *dev, uint8_t reg, uint8_t val) { const struct bmm150_config *cfg = dev->config; return cfg->bus_io->write(&cfg->bus, reg, val); } int bmm150_reg_update_byte(const struct device *dev, uint8_t reg, uint8_t mask, uint8_t value) { int ret = 0; uint8_t old_value, new_value; ret = bmm150_reg_read(dev, reg, &old_value, 1); if (ret < 0) { goto failed; } new_value = (old_value & ~mask) | (value & mask); if (new_value == old_value) { return 0; } return bmm150_reg_write(dev, reg, new_value); failed: return ret; } /* Power control = 'bit' */ static int bmm150_power_control(const struct device *dev, uint8_t bit) { return bmm150_reg_update_byte(dev, BMM150_REG_POWER, BMM150_MASK_POWER_CTL, bit); } /* OpMode = 'mode' */ static int bmm150_opmode(const struct device *dev, uint8_t mode) { return bmm150_reg_update_byte(dev, BMM150_REG_OPMODE_ODR, BMM150_MASK_OPMODE, mode << BMM150_SHIFT_OPMODE); } static int bmm150_set_odr(const struct device *dev, uint8_t val) { uint8_t i; for (i = 0U; i < ARRAY_SIZE(bmm150_samp_freq_table); ++i) { if (val <= bmm150_samp_freq_table[i].freq) { return bmm150_reg_update_byte(dev, BMM150_REG_OPMODE_ODR, BMM150_MASK_ODR, (bmm150_samp_freq_table[i].reg_val << BMM150_SHIFT_ODR)); } } return -ENOTSUP; } #if defined(BMM150_SET_ATTR) static int bmm150_read_rep_xy(const struct device *dev) { struct bmm150_data *data = dev->driver->data; const struct bmm150_config *config = dev->config; uint8_t reg_val; if (bmm150_reg_read(dev, BMM150_REG_REP_XY, ®_val, 1) < 0) { return -EIO; } data->rep_xy = BMM150_REGVAL_TO_REPXY((uint8_t)(reg_val)); return 0; } static int bmm150_read_rep_z(const struct device *dev) { struct bmm150_data *data = dev->data; const struct bmm150_config *config = dev->config; uint8_t reg_val; if (bmm150_reg_read(dev, BMM150_REG_REP_Z, ®_val, 1) < 0) { return -EIO; } data->rep_z = BMM150_REGVAL_TO_REPZ((int)(reg_val)); return 0; } static int bmm150_compute_max_odr(const struct device *dev, int rep_xy, int rep_z, int *max_odr) { struct bmm150_data *data = dev->data; if (rep_xy == 0) { if (data->rep_xy <= 0) { if (bmm150_read_rep_xy(dev) < 0) { return -EIO; } } rep_xy = data->rep_xy; } if (rep_z == 0) { if (data->rep_z <= 0) { if (bmm150_read_rep_z(dev) < 0) { return -EIO; } } rep_z = data->rep_z; } /* Equation reference Datasheet 4.2.4 */ *max_odr = 1000000 / (145 * rep_xy + 500 * rep_z + 980); return 0; } #endif #if defined(BMM150_SET_ATTR_REP) static int bmm150_read_odr(const struct device *dev) { struct bmm150_data *data = dev->data; const struct bmm150_config *config = dev->config; uint8_t i, odr_val, reg_val; if (bmm150_reg_read(dev, BMM150_REG_OPMODE_ODR, ®_val, 1) < 0) { return -EIO; } odr_val = (reg_val & BMM150_MASK_ODR) >> BMM150_SHIFT_ODR; for (i = 0U; i < ARRAY_SIZE(bmm150_samp_freq_table); ++i) { if (bmm150_samp_freq_table[i].reg_val == odr_val) { data->odr = bmm150_samp_freq_table[i].freq; return 0; } } return -ENOTSUP; } #endif #if defined(CONFIG_BMM150_SAMPLING_REP_XY) static int bmm150_write_rep_xy(const struct device *dev, int val) { struct bmm150_data *data = dev->data; const struct bmm150_config *config = dev->config; if (bmm150_reg_update_byte(dev, BMM150_REG_REP_XY, BMM150_REG_REP_DATAMASK, BMM150_REPXY_TO_REGVAL(val)) < 0) { return -EIO; } data->rep_xy = val; return 0; } #endif #if defined(CONFIG_BMM150_SAMPLING_REP_Z) static int bmm150_write_rep_z(const struct device *dev, int val) { struct bmm150_data *data = dev->data; const struct bmm150_config *config = dev->config; if (bmm150_reg_update_byte(dev, BMM150_REG_REP_Z, BMM150_REG_REP_DATAMASK, BMM150_REPZ_TO_REGVAL(val)) < 0) { return -EIO; } data->rep_z = val; return 0; } #endif /* Reference Datasheet 4.3.2 */ static int32_t bmm150_compensate_xy(struct bmm150_trim_regs *tregs, int16_t xy, uint16_t rhall, bool is_x) { int8_t txy1, txy2; int16_t val; uint16_t prevalue; int32_t temp1, temp2, temp3; if (xy == BMM150_XY_OVERFLOW_VAL) { return INT32_MIN; } if (!rhall) { rhall = tregs->xyz1; } if (is_x) { txy1 = tregs->x1; txy2 = tregs->x2; } else { txy1 = tregs->y1; txy2 = tregs->y2; } prevalue = (uint16_t)((((int32_t)tregs->xyz1) << 14) / rhall); val = (int16_t)((prevalue) - ((uint16_t)0x4000)); temp1 = (((int32_t)tregs->xy2) * ((((int32_t)val) * ((int32_t)val)) >> 7)); temp2 = ((int32_t)val) * ((int32_t)(((int16_t)tregs->xy1) << 7)); temp3 = (((((temp1 + temp2) >> 9) + ((int32_t)0x100000)) * ((int32_t)(((int16_t)txy2) + ((int16_t)0xA0)))) >> 12); val = ((int16_t)((((int32_t)xy) * temp3) >> 13)) + (((int16_t)txy1) << 3); return (int32_t)val; } static int32_t bmm150_compensate_z(struct bmm150_trim_regs *tregs, int16_t z, uint16_t rhall) { int32_t val, temp1, temp2; int16_t temp3; if (z == BMM150_Z_OVERFLOW_VAL) { return INT32_MIN; } temp1 = (((int32_t)(z - tregs->z4)) << 15); temp2 = ((((int32_t)tregs->z3) * ((int32_t)(((int16_t)rhall) - ((int16_t)tregs->xyz1)))) >> 2); temp3 = ((int16_t)(((((int32_t)tregs->z1) * ((((int16_t)rhall) << 1))) + (1 << 15)) >> 16)); val = ((temp1 - temp2) / (tregs->z2 + temp3)); return val; } static int bmm150_sample_fetch(const struct device *dev, enum sensor_channel chan) { struct bmm150_data *drv_data = dev->data; uint16_t values[BMM150_AXIS_XYZR_MAX]; int16_t raw_x, raw_y, raw_z; uint16_t rhall; __ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_MAGN_XYZ); if (bmm150_reg_read(dev, BMM150_REG_X_L, (uint8_t *)values, sizeof(values)) < 0) { LOG_ERR("failed to read sample"); return -EIO; } raw_x = (int16_t)sys_le16_to_cpu(values[BMM150_AXIS_X]) >> BMM150_SHIFT_XY_L; raw_y = (int16_t)sys_le16_to_cpu(values[BMM150_AXIS_Y]) >> BMM150_SHIFT_XY_L; raw_z = (int16_t)sys_le16_to_cpu(values[BMM150_AXIS_Z]) >> BMM150_SHIFT_Z_L; rhall = sys_le16_to_cpu(values[BMM150_RHALL]) >> BMM150_SHIFT_RHALL_L; drv_data->sample_x = bmm150_compensate_xy(&drv_data->tregs, raw_x, rhall, true); drv_data->sample_y = bmm150_compensate_xy(&drv_data->tregs, raw_y, rhall, false); drv_data->sample_z = bmm150_compensate_z(&drv_data->tregs, raw_z, rhall); return 0; } /* * Datasheet specify raw units are 16 LSB/uT and this function converts it to * Gauss */ static void bmm150_convert(struct sensor_value *val, int raw_val) { /* val = raw_val / 1600 */ val->val1 = raw_val / 1600; val->val2 = ((int32_t)raw_val * (1000000 / 1600)) % 1000000; } static int bmm150_channel_get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val) { struct bmm150_data *drv_data = dev->data; switch (chan) { case SENSOR_CHAN_MAGN_X: bmm150_convert(val, drv_data->sample_x); break; case SENSOR_CHAN_MAGN_Y: bmm150_convert(val, drv_data->sample_y); break; case SENSOR_CHAN_MAGN_Z: bmm150_convert(val, drv_data->sample_z); break; case SENSOR_CHAN_MAGN_XYZ: bmm150_convert(val, drv_data->sample_x); bmm150_convert(val + 1, drv_data->sample_y); bmm150_convert(val + 2, drv_data->sample_z); break; default: return -ENOTSUP; } return 0; } #if defined(BMM150_SET_ATTR_REP) static inline int bmm150_attr_set_rep(const struct device *dev, enum sensor_channel chan, const struct sensor_value *val) { struct bmm150_data *data = dev->data; int max_odr; switch (chan) { #if defined(CONFIG_BMM150_SAMPLING_REP_XY) case SENSOR_CHAN_MAGN_X: case SENSOR_CHAN_MAGN_Y: if (val->val1 < 1 || val->val1 > 511) { return -EINVAL; } if (bmm150_compute_max_odr(dev, val->val1, 0, &max_odr) < 0) { return -EIO; } if (data->odr <= 0) { if (bmm150_read_odr(dev) < 0) { return -EIO; } } if (data->odr > max_odr) { return -EINVAL; } if (bmm150_write_rep_xy(dev, val->val1) < 0) { return -EIO; } break; #endif #if defined(CONFIG_BMM150_SAMPLING_REP_Z) case SENSOR_CHAN_MAGN_Z: if (val->val1 < 1 || val->val1 > 256) { return -EINVAL; } if (bmm150_compute_max_odr(dev, 0, val->val1, &max_odr) < 0) { return -EIO; } if (data->odr <= 0) { if (bmm150_read_odr(dev) < 0) { return -EIO; } } if (data->odr > max_odr) { return -EINVAL; } if (bmm150_write_rep_z(dev, val->val1) < 0) { return -EIO; } break; #endif default: return -EINVAL; } return 0; } #endif #if defined(BMM150_SET_ATTR) static int bmm150_attr_set(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr, const struct sensor_value *val) { struct bmm150_magn_data *data = dev->data; switch (attr) { #if defined(CONFIG_BMM150_SAMPLING_RATE_RUNTIME) case SENSOR_ATTR_SAMPLING_FREQUENCY: if (data->max_odr <= 0) { if (bmm150_compute_max_odr(dev, 0, 0, &data->max_odr) < 0) { return -EIO; } } if (data->max_odr < val->val1) { LOG_ERR("not supported with current oversampling"); return -ENOTSUP; } if (bmm150_set_odr(dev, (uint8_t)(val->val1)) < 0) { return -EIO; } break; #endif #if defined(BMM150_SET_ATTR_REP) case SENSOR_ATTR_OVERSAMPLING: bmm150_attr_set_rep(dev, chan, val); break; #endif default: return -EINVAL; } return 0; } #endif static const struct sensor_driver_api bmm150_api_funcs = { #if defined(BMM150_SET_ATTR) .attr_set = bmm150_attr_set, #endif .sample_fetch = bmm150_sample_fetch, .channel_get = bmm150_channel_get, #ifdef CONFIG_BMM150_TRIGGER .trigger_set = bmm150_trigger_set, #endif }; static int bmm150_full_por(const struct device *dev) { int ret; /* Ensure we are not in suspend mode so soft reset is not ignored */ ret = bmm150_power_control(dev, 1); if (ret != 0) { LOG_ERR("failed to ensure not in suspend mode: %d", ret); return ret; } k_sleep(BMM150_START_UP_TIME); /* Soft reset always brings the device into sleep mode */ ret = bmm150_reg_update_byte(dev, BMM150_REG_POWER, BMM150_MASK_SOFT_RESET, BMM150_SOFT_RESET); if (ret != 0) { LOG_ERR("failed soft reset: %d", ret); return ret; } /* * To perform full POR (after soft reset), bring the device into suspend * mode then back into sleep mode, see datasheet section 5.6 */ ret = bmm150_power_control(dev, 0); if (ret != 0) { LOG_ERR("failed to enter suspend mode: %d", ret); return ret; } k_sleep(BMM150_POR_TIME); /* Full POR - back into sleep mode */ ret = bmm150_power_control(dev, 1); if (ret != 0) { LOG_ERR("failed to go back into sleep mode: %d", ret); return ret; } k_sleep(BMM150_START_UP_TIME); return 0; } static int bmm150_init_chip(const struct device *dev) { struct bmm150_data *data = dev->data; struct bmm150_preset preset; uint8_t chip_id; if (bmm150_full_por(dev) != 0) { goto err_poweroff; } /* Read chip ID (can only be read in sleep mode)*/ if (bmm150_reg_read(dev, BMM150_REG_CHIP_ID, &chip_id, 1) < 0) { LOG_ERR("failed reading chip id"); goto err_poweroff; } if (chip_id != BMM150_CHIP_ID_VAL) { LOG_ERR("invalid chip id 0x%x", chip_id); goto err_poweroff; } /* Setting preset mode */ preset = bmm150_presets_table[BMM150_DEFAULT_PRESET]; if (bmm150_set_odr(dev, preset.odr) < 0) { LOG_ERR("failed to set ODR to %d", preset.odr); goto err_poweroff; } if (bmm150_reg_write(dev, BMM150_REG_REP_XY, BMM150_REPXY_TO_REGVAL(preset.rep_xy)) < 0) { LOG_ERR("failed to set REP XY to %d", preset.rep_xy); goto err_poweroff; } if (bmm150_reg_write(dev, BMM150_REG_REP_Z, BMM150_REPZ_TO_REGVAL(preset.rep_z)) < 0) { LOG_ERR("failed to set REP Z to %d", preset.rep_z); goto err_poweroff; } /* Set chip normal mode */ if (bmm150_opmode(dev, BMM150_MODE_NORMAL) < 0) { LOG_ERR("failed to enter normal mode"); } /* Reads the trim registers of the sensor */ if (bmm150_reg_read(dev, BMM150_REG_TRIM_START, (uint8_t *)&data->tregs, sizeof(data->tregs)) < 0) { LOG_ERR("failed to read trim regs"); goto err_poweroff; } data->rep_xy = 0; data->rep_z = 0; data->odr = 0; data->max_odr = 0; data->sample_x = 0; data->sample_y = 0; data->sample_z = 0; data->tregs.xyz1 = sys_le16_to_cpu(data->tregs.xyz1); data->tregs.z1 = sys_le16_to_cpu(data->tregs.z1); data->tregs.z2 = sys_le16_to_cpu(data->tregs.z2); data->tregs.z3 = sys_le16_to_cpu(data->tregs.z3); data->tregs.z4 = sys_le16_to_cpu(data->tregs.z4); return 0; err_poweroff: (void)bmm150_power_control(dev, 0); /* Suspend */ 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: /* Need to enter sleep mode before setting OpMode to normal */ ret = bmm150_power_control(dev, 1); if (ret != 0) { LOG_ERR("failed to enter sleep mode: %d", ret); } k_sleep(BMM150_START_UP_TIME); ret |= bmm150_opmode(dev, BMM150_MODE_NORMAL); if (ret != 0) { LOG_ERR("failed to enter normal mode: %d", ret); } break; case PM_DEVICE_ACTION_SUSPEND: ret = bmm150_power_control(dev, 0); /* Suspend */ if (ret != 0) { LOG_ERR("failed to enter suspend mode: %d", ret); } break; default: return -ENOTSUP; } return ret; } #endif static int bmm150_init(const struct device *dev) { int err = 0; err = bmm150_bus_check(dev); if (err < 0) { LOG_DBG("bus check failed: %d", err); return err; } if (bmm150_init_chip(dev) < 0) { LOG_ERR("failed to initialize chip"); return -EIO; } #ifdef CONFIG_BMM150_TRIGGER if (bmm150_trigger_mode_init(dev) < 0) { LOG_ERR("Cannot set up trigger mode."); return -EINVAL; } #endif return 0; } /* Initializes a struct bmm150_config for an instance on a SPI bus. */ #define BMM150_CONFIG_SPI(inst) \ .bus.spi = SPI_DT_SPEC_INST_GET(inst, BMM150_SPI_OPERATION, 0), \ .bus_io = &bmm150_bus_io_spi, /* Initializes a struct bmm150_config for an instance on an I2C bus. */ #define BMM150_CONFIG_I2C(inst) \ .bus.i2c = I2C_DT_SPEC_INST_GET(inst), \ .bus_io = &bmm150_bus_io_i2c, #define BMM150_BUS_CFG(inst) \ COND_CODE_1(DT_INST_ON_BUS(inst, i2c), \ (BMM150_CONFIG_I2C(inst)), \ (BMM150_CONFIG_SPI(inst))) #if defined(CONFIG_BMM150_TRIGGER) #define BMM150_INT_CFG(inst) \ .drdy_int = GPIO_DT_SPEC_INST_GET(inst, drdy_gpios), #else #define BMM150_INT_CFG(inst) #endif /* * Main instantiation macro, which selects the correct bus-specific * instantiation macros for the instance. */ #define BMM150_DEFINE(inst) \ static struct bmm150_data bmm150_data_##inst; \ static const struct bmm150_config bmm150_config_##inst = { \ BMM150_BUS_CFG(inst) \ BMM150_INT_CFG(inst) \ }; \ \ PM_DEVICE_DT_INST_DEFINE(inst, pm_action); \ \ SENSOR_DEVICE_DT_INST_DEFINE(inst, \ bmm150_init, \ PM_DEVICE_DT_INST_GET(inst), \ &bmm150_data_##inst, \ &bmm150_config_##inst, \ POST_KERNEL, \ CONFIG_SENSOR_INIT_PRIORITY, \ &bmm150_api_funcs); /* Create the struct device for every status "okay" node in the devicetree. */ DT_INST_FOREACH_STATUS_OKAY(BMM150_DEFINE)