/* sensor_lsm9ds0_mfd.c - Driver for LSM9DS0 accelerometer, magnetometer
 * and temperature (MFD) sensor driver
 */

/*
 * Copyright (c) 2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT st_lsm9ds0_mfd

#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>

#include "lsm9ds0_mfd.h"

LOG_MODULE_REGISTER(LSM9DS0_MFD, CONFIG_SENSOR_LOG_LEVEL);

static inline int lsm9ds0_mfd_reboot_memory(const struct device *dev)
{
	const struct lsm9ds0_mfd_config *config = dev->config;

	if (i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG0_XM,
				   LSM9DS0_MFD_MASK_CTRL_REG0_XM_BOOT,
				   1 << LSM9DS0_MFD_SHIFT_CTRL_REG0_XM_BOOT) < 0) {
		return -EIO;
	}

	k_busy_wait(USEC_PER_MSEC * 50U);

	return 0;
}

#if !defined(LSM9DS0_MFD_ACCEL_DISABLED)
static inline int lsm9ds0_mfd_accel_set_odr_raw(const struct device *dev,
						uint8_t odr)
{
	const struct lsm9ds0_mfd_config *config = dev->config;

	return i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG1_XM,
				      LSM9DS0_MFD_MASK_CTRL_REG1_XM_AODR,
				      odr << LSM9DS0_MFD_SHIFT_CTRL_REG1_XM_AODR);
}

#if defined(CONFIG_LSM9DS0_MFD_ACCEL_SAMPLING_RATE_RUNTIME)
static const struct {
	int freq_int;
	int freq_micro;
} lsm9ds0_mfd_accel_odr_map[] = { {0, 0},
				  {3, 125000},
				  {6, 250000},
				  {12, 500000},
				  {25, 0},
				  {50, 0},
				  {100, 0},
				  {200, 0},
				  {400, 0},
				  {800, 0},
				  {1600, 0} };

static int lsm9ds0_mfd_accel_set_odr(const struct device *dev,
				     const struct sensor_value *val)
{
	uint8_t i;

	for (i = 0U; i < ARRAY_SIZE(lsm9ds0_mfd_accel_odr_map); ++i) {
		if (val->val1 < lsm9ds0_mfd_accel_odr_map[i].freq_int ||
		    (val->val1 == lsm9ds0_mfd_accel_odr_map[i].freq_int &&
		     val->val2 <= lsm9ds0_mfd_accel_odr_map[i].freq_micro)) {
			return lsm9ds0_mfd_accel_set_odr_raw(dev, i);
		}
	}

	return -ENOTSUP;
}
#endif

static inline int lsm9ds0_mfd_accel_set_fs_raw(const struct device *dev,
					       uint8_t fs)
{
	const struct lsm9ds0_mfd_config *config = dev->config;

	if (i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG2_XM,
				   LSM9DS0_MFD_MASK_CTRL_REG2_XM_AFS,
				   fs << LSM9DS0_MFD_SHIFT_CTRL_REG2_XM_AFS) < 0) {
		return -EIO;
	}

#if defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_RUNTIME)
	data->accel_fs = fs;
#endif

	return 0;
}

#if defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_RUNTIME)
static const struct {
	int fs;
} lsm9ds0_mfd_accel_fs_map[] = { {2},
				 {4},
				 {6},
				 {8},
				 {16} };

static int lsm9ds0_mfd_accel_set_fs(const struct device *dev, int val)
{
	uint8_t i;

	for (i = 0U; i < ARRAY_SIZE(lsm9ds0_mfd_accel_fs_map); ++i) {
		if (val <= lsm9ds0_mfd_accel_fs_map[i].fs) {
			return lsm9ds0_mfd_accel_set_fs_raw(dev, i);
		}
	}

	return -ENOTSUP;
}
#endif
#endif

#if !defined(LSM9DS0_MFD_MAGN_DISABLED)
static inline int lsm9ds0_mfd_magn_set_odr_raw(const struct device *dev,
					       uint8_t odr)
{
	const struct lsm9ds0_mfd_config *config = dev->config;

	return i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG5_XM,
				      LSM9DS0_MFD_MASK_CTRL_REG5_XM_M_ODR,
				      odr << LSM9DS0_MFD_SHIFT_CTRL_REG5_XM_M_ODR);
}

#if defined(CONFIG_LSM9DS0_MFD_MAGN_SAMPLING_RATE_RUNTIME)
static const struct {
	int freq_int;
	int freq_micro;
} lsm9ds0_mfd_magn_odr_map[] = { {0, 0},
				 {3, 125000},
				 {6, 250000},
				 {12, 500000},
				 {25, 0},
				 {50, 0},
				 {100, 0} };

static int lsm9ds0_mfd_magn_set_odr(const struct device *dev,
				    const struct sensor_value *val)
{
	uint8_t i;

	for (i = 0U; i < ARRAY_SIZE(lsm9ds0_mfd_accel_odr_map); ++i) {
		if (val->val1 < lsm9ds0_mfd_accel_odr_map[i].freq_int ||
		    (val->val1 == lsm9ds0_mfd_accel_odr_map[i].freq_int &&
		     val->val2 <= lsm9ds0_mfd_accel_odr_map[i].freq_micro)) {
			return lsm9ds0_mfd_magn_set_odr_raw(dev, i);
		}
	}

	return -ENOTSUP;
}
#endif

static inline int lsm9ds0_mfd_magn_set_fs_raw(const struct device *dev,
					      uint8_t fs)
{
	const struct lsm9ds0_mfd_config *config = dev->config;

	if (i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG6_XM,
				   LSM9DS0_MFD_MASK_CTRL_REG6_XM_MFS,
				   fs << LSM9DS0_MFD_SHIFT_CTRL_REG6_XM_MFS) < 0) {
		return -EIO;
	}

#if defined(CONFIG_LSM9DS0_MFD_MAGN_FULL_SCALE_RUNTIME)
	data->magn_fs = fs;
#endif

	return 0;
}

#if defined(CONFIG_LSM9DS0_MFD_MAGN_FULL_SCALE_RUNTIME)
static const struct {
	int fs;
} lsm9ds0_mfd_magn_fs_map[] = { {2},
				{4},
				{8},
				{12} };

static int lsm9ds0_mfd_magn_set_fs(const struct device *dev,
				   const struct sensor_value *val)
{
	uint8_t i;

	for (i = 0U; i < ARRAY_SIZE(lsm9ds0_mfd_magn_fs_map); ++i) {
		if (val->val1 <= lsm9ds0_mfd_magn_fs_map[i].fs) {
			return lsm9ds0_mfd_magn_set_fs_raw(dev, i);
		}
	}

	return -ENOTSUP;
}
#endif
#endif

#if !defined(LSM9DS0_MFD_ACCEL_DISABLED)
static inline int lsm9ds0_mfd_sample_fetch_accel(const struct device *dev)
{
	struct lsm9ds0_mfd_data *data = dev->data;
	const struct lsm9ds0_mfd_config *config = dev->config;
	uint8_t out_l, out_h;

#if defined(CONFIG_LSM9DS0_MFD_ACCEL_ENABLE_X)
	if (i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_X_L_A, &out_l) < 0 ||
	    i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_X_H_A, &out_h) < 0) {
		LOG_DBG("failed to read accel sample (X axis)");
		return -EIO;
	}

	data->sample_accel_x = (int16_t)((uint16_t)(out_l) |
			       ((uint16_t)(out_h) << 8));
#endif

#if defined(CONFIG_LSM9DS0_MFD_ACCEL_ENABLE_Y)
	if (i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_Y_L_A, &out_l) < 0 ||
	    i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_Y_H_A, &out_h) < 0) {
		LOG_DBG("failed to read accel sample (Y axis)");
		return -EIO;
	}

	data->sample_accel_y = (int16_t)((uint16_t)(out_l) |
			       ((uint16_t)(out_h) << 8));
#endif

#if defined(CONFIG_LSM9DS0_MFD_ACCEL_ENABLE_Z)
	if (i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_Z_L_A, &out_l) < 0 ||
	    i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_Z_H_A, &out_h) < 0) {
		LOG_DBG("failed to read accel sample (Z axis)");
		return -EIO;
	}

	data->sample_accel_z = (int16_t)((uint16_t)(out_l) |
			       ((uint16_t)(out_h) << 8));
#endif

#if defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_RUNTIME)
	data->sample_accel_fs = data->accel_fs;
#endif

	return 0;
}
#endif

#if !defined(LSM9DS0_MFD_MAGN_DISABLED)
static inline int lsm9ds0_mfd_sample_fetch_magn(const struct device *dev)
{
	struct lsm9ds0_mfd_data *data = dev->data;
	const struct lsm9ds0_mfd_config *config = dev->config;
	uint8_t out_l, out_h;

	if (i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_X_L_M, &out_l) < 0 ||
	    i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_X_H_M, &out_h) < 0) {
		LOG_DBG("failed to read magn sample (X axis)");
		return -EIO;
	}

	data->sample_magn_x = (int16_t)((uint16_t)(out_l) |
			      ((uint16_t)(out_h) << 8));

	if (i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_Y_L_M, &out_l) < 0 ||
	    i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_Y_H_M, &out_h) < 0) {
		LOG_DBG("failed to read magn sample (Y axis)");
		return -EIO;
	}

	data->sample_magn_y = (int16_t)((uint16_t)(out_l) |
			      ((uint16_t)(out_h) << 8));

	if (i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_Z_L_M, &out_l) < 0 ||
	    i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_Z_H_M, &out_h) < 0) {
		LOG_DBG("failed to read magn sample (Z axis)");
		return -EIO;
	}

	data->sample_magn_z = (int16_t)((uint16_t)(out_l) |
			      ((uint16_t)(out_h) << 8));

#if defined(CONFIG_LSM9DS0_MFD_MAGN_FULL_SCALE_RUNTIME)
	data->sample_magn_fs = data->magn_fs;
#endif

	return 0;
}
#endif

#if !defined(LSM9DS0_MFD_TEMP_DISABLED)
static inline int lsm9ds0_mfd_sample_fetch_temp(const struct device *dev)
{
	struct lsm9ds0_mfd_data *data = dev->data;
	const struct lsm9ds0_mfd_config *config = dev->config;
	uint8_t out_l, out_h;

	if (i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_TEMP_L_XM, &out_l) < 0 ||
	    i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_OUT_TEMP_H_XM, &out_h) < 0) {
		LOG_DBG("failed to read temperature sample\n");
		return -EIO;
	}

	data->sample_temp = (int16_t)((uint16_t)(out_l) |
			    ((uint16_t)(out_h) << 8));

	return 0;
}
#endif

static inline int lsm9ds0_mfd_sample_fetch_all(const struct device *dev)
{
#if !defined(LSM9DS0_MFD_ACCEL_DISABLED)
	if (lsm9ds0_mfd_sample_fetch_accel(dev) < 0) {
		return -EIO;
	}
#endif

#if !defined(LSM9DS0_MFD_MAGN_DISABLED)
	if (lsm9ds0_mfd_sample_fetch_magn(dev) < 0) {
		return -EIO;
	}
#endif

#if !defined(LSM9DS0_MFD_TEMP_DISABLED)
	if (lsm9ds0_mfd_sample_fetch_temp(dev) < 0) {
		return -EIO;
	}
#endif

	return 0;
}

static int lsm9ds0_mfd_sample_fetch(const struct device *dev,
				    enum sensor_channel chan)
{
	switch (chan) {
#if !defined(LSM9DS0_MFD_ACCEL_DISABLED)
	case SENSOR_CHAN_ACCEL_XYZ:
		return lsm9ds0_mfd_sample_fetch_accel(dev);
#endif
#if !defined(LSM9DS0_MFD_MAGN_DISABLED)
	case SENSOR_CHAN_MAGN_XYZ:
		return lsm9ds0_mfd_sample_fetch_magn(dev);
#endif
#if !defined(LSM9DS0_MFD_TEMP_DISABLED)
	case SENSOR_CHAN_DIE_TEMP:
		return lsm9ds0_mfd_sample_fetch_temp(dev);
#endif
	case SENSOR_CHAN_ALL:
		return lsm9ds0_mfd_sample_fetch_all(dev);
	default:
		return -EINVAL;
	}

	return 0;
}

#if !defined(LSM9DS0_MFD_ACCEL_DISABLED)
static inline void lsm9ds0_mfd_convert_accel(struct sensor_value *val,
					     int raw_val,
					     float scale)
{
	double dval;

	dval = (double)(raw_val) * (double)scale;
	val->val1 = (int32_t)dval;
	val->val2 = ((int32_t)(dval * 1000000)) % 1000000;
}

static inline int lsm9ds0_mfd_get_accel_channel(enum sensor_channel chan,
						struct sensor_value *val,
						struct lsm9ds0_mfd_data *data,
						float scale)
{
	switch (chan) {
	case SENSOR_CHAN_ACCEL_X:
		lsm9ds0_mfd_convert_accel(val, data->sample_accel_x, scale);
		break;
	case SENSOR_CHAN_ACCEL_Y:
		lsm9ds0_mfd_convert_accel(val, data->sample_accel_y, scale);
		break;
	case SENSOR_CHAN_ACCEL_Z:
		lsm9ds0_mfd_convert_accel(val, data->sample_accel_z, scale);
		break;
	case SENSOR_CHAN_ACCEL_XYZ:
		lsm9ds0_mfd_convert_accel(val, data->sample_accel_x, scale);
		lsm9ds0_mfd_convert_accel(val + 1, data->sample_accel_y, scale);
		lsm9ds0_mfd_convert_accel(val + 2, data->sample_accel_z, scale);
		break;
	default:
		return -ENOTSUP;
	}

	return 0;
}

static inline int lsm9ds0_mfd_get_accel(const struct device *dev,
					enum sensor_channel chan,
					struct sensor_value *val)
{
	struct lsm9ds0_mfd_data *data = dev->data;

#if defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_RUNTIME)
	switch (data->sample_accel_fs) {
	case 0:
		return lsm9ds0_mfd_get_accel_channel(chan, val, data,
						     2.0 * 9.807 / 32767.0);
	case 1:
		return lsm9ds0_mfd_get_accel_channel(chan, val, data,
						     4.0 * 9.807 / 32767.0);
	case 2:
		return lsm9ds0_mfd_get_accel_channel(chan, val, data,
						     6.0 * 9.807 / 32767.0);
	case 3:
		return lsm9ds0_mfd_get_accel_channel(chan, val, data,
						     8.0 * 9.807 / 32767.0);
	case 4:
		return lsm9ds0_mfd_get_accel_channel(chan, val, data,
						     16.0 * 9.807 / 32767.0);
	default:
		return -ENOTSUP;
	}
#elif defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_2)
	return lsm9ds0_mfd_get_accel_channel(chan, val, data,
					     2.0 * 9.807 / 32767.0);
#elif defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_4)
	return lsm9ds0_mfd_get_accel_channel(chan, val, data,
					     4.0 * 9.807 / 32767.0);
#elif defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_6)
	return lsm9ds0_mfd_get_accel_channel(chan, val, data,
					     6.0 * 9.807 / 32767.0);
#elif defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_8)
	return lsm9ds0_mfd_get_accel_channel(chan, val, data,
					     8.0 * 9.807 / 32767.0);
#elif defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_16)
	return lsm9ds0_mfd_get_accel_channel(chan, val, data,
					     16.0 * 9.807 / 32767.0);
#endif

	return 0;
}
#endif

#if !defined(LSM9DS0_MFD_MAGN_DISABLED)
static inline void lsm9ds0_mfd_convert_magn(struct sensor_value *val,
					    int raw_val,
					    float scale)
{
	double dval;

	dval = (double)(raw_val) * (double)scale;
	val->val1 = (int32_t)dval;
	val->val2 = ((int32_t)(dval * 1000000)) % 1000000;
}

static inline int lsm9ds0_mfd_get_magn_channel(enum sensor_channel chan,
					       struct sensor_value *val,
					       struct lsm9ds0_mfd_data *data,
					       float scale)
{
	switch (chan) {
	case SENSOR_CHAN_MAGN_X:
		lsm9ds0_mfd_convert_magn(val, data->sample_magn_x, scale);
		break;
	case SENSOR_CHAN_MAGN_Y:
		lsm9ds0_mfd_convert_magn(val, data->sample_magn_y, scale);
		break;
	case SENSOR_CHAN_MAGN_Z:
		lsm9ds0_mfd_convert_magn(val, data->sample_magn_z, scale);
		break;
	case SENSOR_CHAN_MAGN_XYZ:
		lsm9ds0_mfd_convert_magn(val, data->sample_magn_x, scale);
		lsm9ds0_mfd_convert_magn(val + 1, data->sample_magn_y, scale);
		lsm9ds0_mfd_convert_magn(val + 2, data->sample_magn_z, scale);
		break;
	default:
		return -ENOTSUP;
	}

	return 0;
}

static inline int lsm9ds0_mfd_get_magn(const struct device *dev,
				       enum sensor_channel chan,
				       struct sensor_value *val)
{
	struct lsm9ds0_mfd_data *data = dev->data;

#if defined(CONFIG_LSM9DS0_MFD_MAGN_FULL_SCALE_RUNTIME)
	switch (data->sample_magn_fs) {
	case 0:
		return lsm9ds0_mfd_get_magn_channel(chan, val, data,
						    2.0 / 32767.0);
	case 1:
		return lsm9ds0_mfd_get_magn_channel(chan, val, data,
						    4.0 / 32767.0);
	case 2:
		return lsm9ds0_mfd_get_magn_channel(chan, val, data,
						    8.0 / 32767.0);
	case 3:
		return lsm9ds0_mfd_get_magn_channel(chan, val, data,
						    12.0 / 32767.0);
	default:
		return -ENOTSUP;
	}
#elif defined(CONFIG_LSM9DS0_MFD_MAGN_FULL_SCALE_2)
	return lsm9ds0_mfd_get_magn_channel(chan, val, data, 2.0 / 32767.0);
#elif defined(CONFIG_LSM9DS0_MFD_MAGN_FULL_SCALE_4)
	return lsm9ds0_mfd_get_magn_channel(chan, val, data, 4.0 / 32767.0);
#elif defined(CONFIG_LSM9DS0_MFD_MAGN_FULL_SCALE_8)
	return lsm9ds0_mfd_get_magn_channel(chan, val, data, 8.0 / 32767.0);
#elif defined(CONFIG_LSM9DS0_MFD_MAGN_FULL_SCALE_12)
	return lsm9ds0_mfd_get_magn_channel(chan, val, data, 12.0 / 32767.0);
#endif

	return 0;
}
#endif

static int lsm9ds0_mfd_channel_get(const struct device *dev,
				   enum sensor_channel chan,
				   struct sensor_value *val)
{
#if !defined(LSM9DS0_MFD_TEMP_DISABLED)
	struct lsm9ds0_mfd_data *data = dev->data;
#endif

	switch (chan) {
#if !defined(LSM9DS0_MFD_ACCEL_DISABLED)
	case SENSOR_CHAN_ACCEL_X:
	case SENSOR_CHAN_ACCEL_Y:
	case SENSOR_CHAN_ACCEL_Z:
	case SENSOR_CHAN_ACCEL_XYZ:
		return lsm9ds0_mfd_get_accel(dev, chan, val);
#endif
#if !defined(LSM9DS0_MFD_MAGN_DISABLED)
	case SENSOR_CHAN_MAGN_X:
	case SENSOR_CHAN_MAGN_Y:
	case SENSOR_CHAN_MAGN_Z:
	case SENSOR_CHAN_MAGN_XYZ:
		return lsm9ds0_mfd_get_magn(dev, chan, val);
#endif
#if !defined(LSM9DS0_MFD_TEMP_DISABLED)
	case SENSOR_CHAN_DIE_TEMP:
		val->val1 = data->sample_temp;
		val->val2 = 0;
		return 0;
#endif
	default:
		return -ENOTSUP;
	}
}

#if defined(LSM9DS0_MFD_ATTR_SET_ACCEL)
static inline int lsm9ds0_mfd_attr_set_accel(const struct device *dev,
					     enum sensor_attribute attr,
					     const struct sensor_value *val)
{
	switch (attr) {
#if defined(CONFIG_LSM9DS0_MFD_ACCEL_SAMPLING_RATE_RUNTIME)
	case SENSOR_ATTR_SAMPLING_FREQUENCY:
		return lsm9ds0_mfd_accel_set_odr(dev, val);
#endif
#if defined(CONFIG_LSM9DS0_MFD_ACCEL_FULL_SCALE_RUNTIME)
	case SENSOR_ATTR_FULL_SCALE:
		return lsm9ds0_mfd_accel_set_fs(dev, sensor_ms2_to_g(val));
#endif
	default:
		return -ENOTSUP;
	}

	return 0;
}
#endif

#if defined(LSM9DS0_MFD_ATTR_SET_MAGN)
static inline int lsm9ds0_mfd_attr_set_magn(const struct device *dev,
					     enum sensor_attribute attr,
					     const struct sensor_value *val)
{
	switch (attr) {
#if defined(CONFIG_LSM9DS0_MFD_MAGN_SAMPLING_RATE_RUNTIME)
	case SENSOR_ATTR_SAMPLING_FREQUENCY:
		return lsm9ds0_mfd_magn_set_odr(dev, val);
#endif
#if defined(CONFIG_LSM9DS0_MFD_MAGN_FULL_SCALE_RUNTIME)
	case SENSOR_ATTR_FULL_SCALE:
		return lsm9ds0_mfd_magn_set_fs(dev, val);
#endif
	default:
		return -ENOTSUP;
	}

	return 0;
}
#endif

#if defined(LSM9DS0_MFD_ATTR_SET)
static int lsm9ds0_mfd_attr_set(const struct device *dev,
				enum sensor_channel chan,
				enum sensor_attribute attr,
				const struct sensor_value *val)
{

	switch (chan) {
#if defined(LSM9DS0_MFD_ATTR_SET_ACCEL)
	case SENSOR_CHAN_ACCEL_X:
	case SENSOR_CHAN_ACCEL_Y:
	case SENSOR_CHAN_ACCEL_Z:
	case SENSOR_CHAN_ACCEL_XYZ:
		return lsm9ds0_mfd_attr_set_accel(dev, attr, val);
#endif
#if defined(LSM9DS0_MFD_ATTR_SET_MAGN)
	case SENSOR_CHAN_MAGN_X:
	case SENSOR_CHAN_MAGN_Y:
	case SENSOR_CHAN_MAGN_Z:
	case SENSOR_CHAN_MAGN_XYZ:
		return lsm9ds0_mfd_attr_set_magn(dev, attr, val);
#endif
	default:
		return -ENOTSUP;
	}

	return 0;
}
#endif

static const struct sensor_driver_api lsm9ds0_mfd_api_funcs = {
	.sample_fetch = lsm9ds0_mfd_sample_fetch,
	.channel_get = lsm9ds0_mfd_channel_get,
#if defined(LSM9DS0_MFD_ATTR_SET)
	.attr_set = lsm9ds0_mfd_attr_set,
#endif
};

static int lsm9ds0_mfd_init_chip(const struct device *dev)
{
	const struct lsm9ds0_mfd_config *config = dev->config;
	uint8_t chip_id;

	if (lsm9ds0_mfd_reboot_memory(dev) < 0) {
		LOG_DBG("failed to reset device");
		return -EIO;
	}

	if (i2c_reg_read_byte_dt(&config->i2c, LSM9DS0_MFD_REG_WHO_AM_I_XM, &chip_id) < 0) {
		LOG_DBG("failed reading chip id");
		return -EIO;
	}

	if (chip_id != LSM9DS0_MFD_VAL_WHO_AM_I_XM) {
		LOG_DBG("invalid chip id 0x%x", chip_id);
		return -EIO;
	}

	LOG_DBG("chip id 0x%x", chip_id);

#if !defined(LSM9DS0_MFD_ACCEL_DISABLED)
	if (i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG1_XM,
				   LSM9DS0_MFD_MASK_CTRL_REG1_XM_BDU |
				   LSM9DS0_MFD_MASK_CTRL_REG1_XM_AODR,
				   (1 << LSM9DS0_MFD_SHIFT_CTRL_REG1_XM_BDU) |
				   (LSM9DS0_MFD_ACCEL_DEFAULT_AODR <<
				   LSM9DS0_MFD_SHIFT_CTRL_REG1_XM_AODR))) {
		LOG_DBG("failed to set AODR and BDU");
		return -EIO;
	}

	if (lsm9ds0_mfd_accel_set_fs_raw(dev, LSM9DS0_MFD_ACCEL_DEFAULT_FS)) {
		LOG_DBG("failed to set accelerometer full-scale");
		return -EIO;
	}

	if (i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG1_XM,
				   LSM9DS0_MFD_MASK_CTRL_REG1_XM_AXEN |
				   LSM9DS0_MFD_MASK_CTRL_REG1_XM_AYEN |
				   LSM9DS0_MFD_MASK_CTRL_REG1_XM_AZEN,
				   (LSM9DS0_MFD_ACCEL_ENABLE_X <<
				   LSM9DS0_MFD_SHIFT_CTRL_REG1_XM_AXEN) |
				   (LSM9DS0_MFD_ACCEL_ENABLE_Y <<
				   LSM9DS0_MFD_SHIFT_CTRL_REG1_XM_AYEN) |
				   (LSM9DS0_MFD_ACCEL_ENABLE_Z <<
				   LSM9DS0_MFD_SHIFT_CTRL_REG1_XM_AZEN)) < 0) {
		LOG_DBG("failed to set accelerometer axis enable bits\n");
		return -EIO;
	}

#elif !defined(LSM9DS0_MFD_MAGN_DISABLED)
	if (i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG1_XM,
				   LSM9DS0_MFD_MASK_CTRL_REG1_XM_BDU,
				   1 << LSM9DS0_MFD_SHIFT_CTRL_REG1_XM_BDU) < 0) {
		LOG_DBG("failed to set BDU\n");
		return -EIO;
	}
#endif

#if !defined(LSM9DS0_MFD_MAGN_DISABLED)
	if (i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG7_XM,
				   LSM9DS0_MFD_MASK_CTRL_REG7_XM_MD,
				   (0 << LSM9DS0_MFD_SHIFT_CTRL_REG7_XM_MD)) < 0) {
		LOG_DBG("failed to power on magnetometer");
		return -EIO;
	}

	if (lsm9ds0_mfd_magn_set_odr_raw(dev, LSM9DS0_MFD_MAGN_DEFAULT_M_ODR)) {
		LOG_DBG("failed to set magnetometer sampling rate");
		return -EIO;
	}

	if (lsm9ds0_mfd_magn_set_fs_raw(dev, LSM9DS0_MFD_MAGN_DEFAULT_FS)) {
		LOG_DBG("failed to set magnetometer full-scale");
		return -EIO;
	}
#endif

#if !defined(LSM9DS0_MFD_TEMP_DISABLED)
	if (i2c_reg_update_byte_dt(&config->i2c, LSM9DS0_MFD_REG_CTRL_REG5_XM,
				   LSM9DS0_MFD_MASK_CTRL_REG5_XM_TEMP_EN,
				   1 << LSM9DS0_MFD_SHIFT_CTRL_REG5_XM_TEMP_EN) < 0) {
		LOG_DBG("failed to power on temperature sensor");
		return -EIO;
	}
#endif

	return 0;
}

int lsm9ds0_mfd_init(const struct device *dev)
{
	const struct lsm9ds0_mfd_config * const config = dev->config;

	if (!device_is_ready(config->i2c.bus)) {
		LOG_ERR("Bus device is not ready");
		return -ENODEV;
	}

	if (lsm9ds0_mfd_init_chip(dev) < 0) {
		LOG_DBG("failed to initialize chip");
		return -EIO;
	}

	return 0;
}

#define LSM9DS0_MFD_DEFINE(inst)								\
	static struct lsm9ds0_mfd_data lsm9ds0_mfd_data_##inst;					\
												\
	static const struct lsm9ds0_mfd_config lsm9ds0_mfd_config_##inst = {			\
		.i2c = I2C_DT_SPEC_INST_GET(inst),						\
	};											\
												\
	SENSOR_DEVICE_DT_INST_DEFINE(inst, lsm9ds0_mfd_init, NULL,				\
			      &lsm9ds0_mfd_data_##inst, &lsm9ds0_mfd_config_##inst, POST_KERNEL,\
			      CONFIG_SENSOR_INIT_PRIORITY, &lsm9ds0_mfd_api_funcs);		\

DT_INST_FOREACH_STATUS_OKAY(LSM9DS0_MFD_DEFINE)
