| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * FXOS8700 - NXP IMU (accelerometer plus magnetometer) | 
 |  * | 
 |  * IIO core driver for FXOS8700, with support for I2C/SPI busses | 
 |  * | 
 |  * TODO: Buffer, trigger, and IRQ support | 
 |  */ | 
 | #include <linux/module.h> | 
 | #include <linux/regmap.h> | 
 | #include <linux/acpi.h> | 
 | #include <linux/bitops.h> | 
 | #include <linux/bitfield.h> | 
 |  | 
 | #include <linux/iio/iio.h> | 
 | #include <linux/iio/sysfs.h> | 
 |  | 
 | #include "fxos8700.h" | 
 |  | 
 | /* Register Definitions */ | 
 | #define FXOS8700_STATUS             0x00 | 
 | #define FXOS8700_OUT_X_MSB          0x01 | 
 | #define FXOS8700_OUT_X_LSB          0x02 | 
 | #define FXOS8700_OUT_Y_MSB          0x03 | 
 | #define FXOS8700_OUT_Y_LSB          0x04 | 
 | #define FXOS8700_OUT_Z_MSB          0x05 | 
 | #define FXOS8700_OUT_Z_LSB          0x06 | 
 | #define FXOS8700_F_SETUP            0x09 | 
 | #define FXOS8700_TRIG_CFG           0x0a | 
 | #define FXOS8700_SYSMOD             0x0b | 
 | #define FXOS8700_INT_SOURCE         0x0c | 
 | #define FXOS8700_WHO_AM_I           0x0d | 
 | #define FXOS8700_XYZ_DATA_CFG       0x0e | 
 | #define FXOS8700_HP_FILTER_CUTOFF   0x0f | 
 | #define FXOS8700_PL_STATUS          0x10 | 
 | #define FXOS8700_PL_CFG             0x11 | 
 | #define FXOS8700_PL_COUNT           0x12 | 
 | #define FXOS8700_PL_BF_ZCOMP        0x13 | 
 | #define FXOS8700_PL_THS_REG         0x14 | 
 | #define FXOS8700_A_FFMT_CFG         0x15 | 
 | #define FXOS8700_A_FFMT_SRC         0x16 | 
 | #define FXOS8700_A_FFMT_THS         0x17 | 
 | #define FXOS8700_A_FFMT_COUNT       0x18 | 
 | #define FXOS8700_TRANSIENT_CFG      0x1d | 
 | #define FXOS8700_TRANSIENT_SRC      0x1e | 
 | #define FXOS8700_TRANSIENT_THS      0x1f | 
 | #define FXOS8700_TRANSIENT_COUNT    0x20 | 
 | #define FXOS8700_PULSE_CFG          0x21 | 
 | #define FXOS8700_PULSE_SRC          0x22 | 
 | #define FXOS8700_PULSE_THSX         0x23 | 
 | #define FXOS8700_PULSE_THSY         0x24 | 
 | #define FXOS8700_PULSE_THSZ         0x25 | 
 | #define FXOS8700_PULSE_TMLT         0x26 | 
 | #define FXOS8700_PULSE_LTCY         0x27 | 
 | #define FXOS8700_PULSE_WIND         0x28 | 
 | #define FXOS8700_ASLP_COUNT         0x29 | 
 | #define FXOS8700_CTRL_REG1          0x2a | 
 | #define FXOS8700_CTRL_REG2          0x2b | 
 | #define FXOS8700_CTRL_REG3          0x2c | 
 | #define FXOS8700_CTRL_REG4          0x2d | 
 | #define FXOS8700_CTRL_REG5          0x2e | 
 | #define FXOS8700_OFF_X              0x2f | 
 | #define FXOS8700_OFF_Y              0x30 | 
 | #define FXOS8700_OFF_Z              0x31 | 
 | #define FXOS8700_M_DR_STATUS        0x32 | 
 | #define FXOS8700_M_OUT_X_MSB        0x33 | 
 | #define FXOS8700_M_OUT_X_LSB        0x34 | 
 | #define FXOS8700_M_OUT_Y_MSB        0x35 | 
 | #define FXOS8700_M_OUT_Y_LSB        0x36 | 
 | #define FXOS8700_M_OUT_Z_MSB        0x37 | 
 | #define FXOS8700_M_OUT_Z_LSB        0x38 | 
 | #define FXOS8700_CMP_X_MSB          0x39 | 
 | #define FXOS8700_CMP_X_LSB          0x3a | 
 | #define FXOS8700_CMP_Y_MSB          0x3b | 
 | #define FXOS8700_CMP_Y_LSB          0x3c | 
 | #define FXOS8700_CMP_Z_MSB          0x3d | 
 | #define FXOS8700_CMP_Z_LSB          0x3e | 
 | #define FXOS8700_M_OFF_X_MSB        0x3f | 
 | #define FXOS8700_M_OFF_X_LSB        0x40 | 
 | #define FXOS8700_M_OFF_Y_MSB        0x41 | 
 | #define FXOS8700_M_OFF_Y_LSB        0x42 | 
 | #define FXOS8700_M_OFF_Z_MSB        0x43 | 
 | #define FXOS8700_M_OFF_Z_LSB        0x44 | 
 | #define FXOS8700_MAX_X_MSB          0x45 | 
 | #define FXOS8700_MAX_X_LSB          0x46 | 
 | #define FXOS8700_MAX_Y_MSB          0x47 | 
 | #define FXOS8700_MAX_Y_LSB          0x48 | 
 | #define FXOS8700_MAX_Z_MSB          0x49 | 
 | #define FXOS8700_MAX_Z_LSB          0x4a | 
 | #define FXOS8700_MIN_X_MSB          0x4b | 
 | #define FXOS8700_MIN_X_LSB          0x4c | 
 | #define FXOS8700_MIN_Y_MSB          0x4d | 
 | #define FXOS8700_MIN_Y_LSB          0x4e | 
 | #define FXOS8700_MIN_Z_MSB          0x4f | 
 | #define FXOS8700_MIN_Z_LSB          0x50 | 
 | #define FXOS8700_TEMP               0x51 | 
 | #define FXOS8700_M_THS_CFG          0x52 | 
 | #define FXOS8700_M_THS_SRC          0x53 | 
 | #define FXOS8700_M_THS_X_MSB        0x54 | 
 | #define FXOS8700_M_THS_X_LSB        0x55 | 
 | #define FXOS8700_M_THS_Y_MSB        0x56 | 
 | #define FXOS8700_M_THS_Y_LSB        0x57 | 
 | #define FXOS8700_M_THS_Z_MSB        0x58 | 
 | #define FXOS8700_M_THS_Z_LSB        0x59 | 
 | #define FXOS8700_M_THS_COUNT        0x5a | 
 | #define FXOS8700_M_CTRL_REG1        0x5b | 
 | #define FXOS8700_M_CTRL_REG2        0x5c | 
 | #define FXOS8700_M_CTRL_REG3        0x5d | 
 | #define FXOS8700_M_INT_SRC          0x5e | 
 | #define FXOS8700_A_VECM_CFG         0x5f | 
 | #define FXOS8700_A_VECM_THS_MSB     0x60 | 
 | #define FXOS8700_A_VECM_THS_LSB     0x61 | 
 | #define FXOS8700_A_VECM_CNT         0x62 | 
 | #define FXOS8700_A_VECM_INITX_MSB   0x63 | 
 | #define FXOS8700_A_VECM_INITX_LSB   0x64 | 
 | #define FXOS8700_A_VECM_INITY_MSB   0x65 | 
 | #define FXOS8700_A_VECM_INITY_LSB   0x66 | 
 | #define FXOS8700_A_VECM_INITZ_MSB   0x67 | 
 | #define FXOS8700_A_VECM_INITZ_LSB   0x68 | 
 | #define FXOS8700_M_VECM_CFG         0x69 | 
 | #define FXOS8700_M_VECM_THS_MSB     0x6a | 
 | #define FXOS8700_M_VECM_THS_LSB     0x6b | 
 | #define FXOS8700_M_VECM_CNT         0x6c | 
 | #define FXOS8700_M_VECM_INITX_MSB   0x6d | 
 | #define FXOS8700_M_VECM_INITX_LSB   0x6e | 
 | #define FXOS8700_M_VECM_INITY_MSB   0x6f | 
 | #define FXOS8700_M_VECM_INITY_LSB   0x70 | 
 | #define FXOS8700_M_VECM_INITZ_MSB   0x71 | 
 | #define FXOS8700_M_VECM_INITZ_LSB   0x72 | 
 | #define FXOS8700_A_FFMT_THS_X_MSB   0x73 | 
 | #define FXOS8700_A_FFMT_THS_X_LSB   0x74 | 
 | #define FXOS8700_A_FFMT_THS_Y_MSB   0x75 | 
 | #define FXOS8700_A_FFMT_THS_Y_LSB   0x76 | 
 | #define FXOS8700_A_FFMT_THS_Z_MSB   0x77 | 
 | #define FXOS8700_A_FFMT_THS_Z_LSB   0x78 | 
 | #define FXOS8700_A_TRAN_INIT_MSB    0x79 | 
 | #define FXOS8700_A_TRAN_INIT_LSB_X  0x7a | 
 | #define FXOS8700_A_TRAN_INIT_LSB_Y  0x7b | 
 | #define FXOS8700_A_TRAN_INIT_LSB_Z  0x7d | 
 | #define FXOS8700_TM_NVM_LOCK        0x7e | 
 | #define FXOS8700_NVM_DATA0_35       0x80 | 
 | #define FXOS8700_NVM_DATA_BNK3      0xa4 | 
 | #define FXOS8700_NVM_DATA_BNK2      0xa5 | 
 | #define FXOS8700_NVM_DATA_BNK1      0xa6 | 
 | #define FXOS8700_NVM_DATA_BNK0      0xa7 | 
 |  | 
 | /* Bit definitions for FXOS8700_CTRL_REG1 */ | 
 | #define FXOS8700_CTRL_ODR_MAX       0x00 | 
 | #define FXOS8700_CTRL_ODR_MSK       GENMASK(5, 3) | 
 |  | 
 | /* Bit definitions for FXOS8700_M_CTRL_REG1 */ | 
 | #define FXOS8700_HMS_MASK           GENMASK(1, 0) | 
 | #define FXOS8700_OS_MASK            GENMASK(4, 2) | 
 |  | 
 | /* Bit definitions for FXOS8700_M_CTRL_REG2 */ | 
 | #define FXOS8700_MAXMIN_RST         BIT(2) | 
 | #define FXOS8700_MAXMIN_DIS_THS     BIT(3) | 
 | #define FXOS8700_MAXMIN_DIS         BIT(4) | 
 |  | 
 | #define FXOS8700_ACTIVE             0x01 | 
 | #define FXOS8700_ACTIVE_MIN_USLEEP  4000 /* from table 6 in datasheet */ | 
 |  | 
 | #define FXOS8700_DEVICE_ID          0xC7 | 
 | #define FXOS8700_PRE_DEVICE_ID      0xC4 | 
 | #define FXOS8700_DATA_BUF_SIZE      3 | 
 |  | 
 | struct fxos8700_data { | 
 | 	struct regmap *regmap; | 
 | 	struct iio_trigger *trig; | 
 | 	__be16 buf[FXOS8700_DATA_BUF_SIZE] ____cacheline_aligned; | 
 | }; | 
 |  | 
 | /* Regmap info */ | 
 | static const struct regmap_range read_range[] = { | 
 | 	{ | 
 | 		.range_min = FXOS8700_STATUS, | 
 | 		.range_max = FXOS8700_A_FFMT_COUNT, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_TRANSIENT_CFG, | 
 | 		.range_max = FXOS8700_A_FFMT_THS_Z_LSB, | 
 | 	}, | 
 | }; | 
 |  | 
 | static const struct regmap_range write_range[] = { | 
 | 	{ | 
 | 		.range_min = FXOS8700_F_SETUP, | 
 | 		.range_max = FXOS8700_TRIG_CFG, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_XYZ_DATA_CFG, | 
 | 		.range_max = FXOS8700_HP_FILTER_CUTOFF, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_PL_CFG, | 
 | 		.range_max = FXOS8700_A_FFMT_CFG, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_A_FFMT_THS, | 
 | 		.range_max = FXOS8700_TRANSIENT_CFG, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_TRANSIENT_THS, | 
 | 		.range_max = FXOS8700_PULSE_CFG, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_PULSE_THSX, | 
 | 		.range_max = FXOS8700_OFF_Z, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_M_OFF_X_MSB, | 
 | 		.range_max = FXOS8700_M_OFF_Z_LSB, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_M_THS_CFG, | 
 | 		.range_max = FXOS8700_M_THS_CFG, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_M_THS_X_MSB, | 
 | 		.range_max = FXOS8700_M_CTRL_REG3, | 
 | 	}, { | 
 | 		.range_min = FXOS8700_A_VECM_CFG, | 
 | 		.range_max = FXOS8700_A_FFMT_THS_Z_LSB, | 
 | 	}, | 
 | }; | 
 |  | 
 | static const struct regmap_access_table driver_read_table = { | 
 | 	.yes_ranges =   read_range, | 
 | 	.n_yes_ranges = ARRAY_SIZE(read_range), | 
 | }; | 
 |  | 
 | static const struct regmap_access_table driver_write_table = { | 
 | 	.yes_ranges =   write_range, | 
 | 	.n_yes_ranges = ARRAY_SIZE(write_range), | 
 | }; | 
 |  | 
 | const struct regmap_config fxos8700_regmap_config = { | 
 | 	.reg_bits = 8, | 
 | 	.val_bits = 8, | 
 | 	.max_register = FXOS8700_NVM_DATA_BNK0, | 
 | 	.rd_table = &driver_read_table, | 
 | 	.wr_table = &driver_write_table, | 
 | }; | 
 | EXPORT_SYMBOL(fxos8700_regmap_config); | 
 |  | 
 | #define FXOS8700_CHANNEL(_type, _axis) {			\ | 
 | 	.type = _type,						\ | 
 | 	.modified = 1,						\ | 
 | 	.channel2 = IIO_MOD_##_axis,				\ | 
 | 	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\ | 
 | 	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |  \ | 
 | 		BIT(IIO_CHAN_INFO_SAMP_FREQ),			\ | 
 | } | 
 |  | 
 | enum fxos8700_accel_scale_bits { | 
 | 	MODE_2G = 0, | 
 | 	MODE_4G, | 
 | 	MODE_8G, | 
 | }; | 
 |  | 
 | /* scan indexes follow DATA register order */ | 
 | enum fxos8700_scan_axis { | 
 | 	FXOS8700_SCAN_ACCEL_X = 0, | 
 | 	FXOS8700_SCAN_ACCEL_Y, | 
 | 	FXOS8700_SCAN_ACCEL_Z, | 
 | 	FXOS8700_SCAN_MAGN_X, | 
 | 	FXOS8700_SCAN_MAGN_Y, | 
 | 	FXOS8700_SCAN_MAGN_Z, | 
 | 	FXOS8700_SCAN_RHALL, | 
 | 	FXOS8700_SCAN_TIMESTAMP, | 
 | }; | 
 |  | 
 | enum fxos8700_sensor { | 
 | 	FXOS8700_ACCEL	= 0, | 
 | 	FXOS8700_MAGN, | 
 | 	FXOS8700_NUM_SENSORS /* must be last */ | 
 | }; | 
 |  | 
 | enum fxos8700_int_pin { | 
 | 	FXOS8700_PIN_INT1, | 
 | 	FXOS8700_PIN_INT2 | 
 | }; | 
 |  | 
 | struct fxos8700_scale { | 
 | 	u8 bits; | 
 | 	int uscale; | 
 | }; | 
 |  | 
 | struct fxos8700_odr { | 
 | 	u8 bits; | 
 | 	int odr; | 
 | 	int uodr; | 
 | }; | 
 |  | 
 | static const struct fxos8700_scale fxos8700_accel_scale[] = { | 
 | 	{ MODE_2G, 244}, | 
 | 	{ MODE_4G, 488}, | 
 | 	{ MODE_8G, 976}, | 
 | }; | 
 |  | 
 | /* | 
 |  * Accellerometer and magnetometer have the same ODR options, set in the | 
 |  * CTRL_REG1 register. ODR is halved when using both sensors at once in | 
 |  * hybrid mode. | 
 |  */ | 
 | static const struct fxos8700_odr fxos8700_odr[] = { | 
 | 	{0x00, 800, 0}, | 
 | 	{0x01, 400, 0}, | 
 | 	{0x02, 200, 0}, | 
 | 	{0x03, 100, 0}, | 
 | 	{0x04, 50, 0}, | 
 | 	{0x05, 12, 500000}, | 
 | 	{0x06, 6, 250000}, | 
 | 	{0x07, 1, 562500}, | 
 | }; | 
 |  | 
 | static const struct iio_chan_spec fxos8700_channels[] = { | 
 | 	FXOS8700_CHANNEL(IIO_ACCEL, X), | 
 | 	FXOS8700_CHANNEL(IIO_ACCEL, Y), | 
 | 	FXOS8700_CHANNEL(IIO_ACCEL, Z), | 
 | 	FXOS8700_CHANNEL(IIO_MAGN, X), | 
 | 	FXOS8700_CHANNEL(IIO_MAGN, Y), | 
 | 	FXOS8700_CHANNEL(IIO_MAGN, Z), | 
 | 	IIO_CHAN_SOFT_TIMESTAMP(FXOS8700_SCAN_TIMESTAMP), | 
 | }; | 
 |  | 
 | static enum fxos8700_sensor fxos8700_to_sensor(enum iio_chan_type iio_type) | 
 | { | 
 | 	switch (iio_type) { | 
 | 	case IIO_ACCEL: | 
 | 		return FXOS8700_ACCEL; | 
 | 	case IIO_MAGN: | 
 | 		return FXOS8700_MAGN; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | } | 
 |  | 
 | static int fxos8700_set_active_mode(struct fxos8700_data *data, | 
 | 				    enum fxos8700_sensor t, bool mode) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, mode); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	usleep_range(FXOS8700_ACTIVE_MIN_USLEEP, | 
 | 		     FXOS8700_ACTIVE_MIN_USLEEP + 1000); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int fxos8700_set_scale(struct fxos8700_data *data, | 
 | 			      enum fxos8700_sensor t, int uscale) | 
 | { | 
 | 	int i, ret, val; | 
 | 	bool active_mode; | 
 | 	static const int scale_num = ARRAY_SIZE(fxos8700_accel_scale); | 
 | 	struct device *dev = regmap_get_device(data->regmap); | 
 |  | 
 | 	if (t == FXOS8700_MAGN) { | 
 | 		dev_err(dev, "Magnetometer scale is locked at 0.001Gs\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * When device is in active mode, it failed to set an ACCEL | 
 | 	 * full-scale range(2g/4g/8g) in FXOS8700_XYZ_DATA_CFG. | 
 | 	 * This is not align with the datasheet, but it is a fxos8700 | 
 | 	 * chip behavier. Set the device in standby mode before setting | 
 | 	 * an ACCEL full-scale range. | 
 | 	 */ | 
 | 	ret = regmap_read(data->regmap, FXOS8700_CTRL_REG1, &val); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	active_mode = val & FXOS8700_ACTIVE; | 
 | 	if (active_mode) { | 
 | 		ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, | 
 | 				   val & ~FXOS8700_ACTIVE); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < scale_num; i++) | 
 | 		if (fxos8700_accel_scale[i].uscale == uscale) | 
 | 			break; | 
 |  | 
 | 	if (i == scale_num) | 
 | 		return -EINVAL; | 
 |  | 
 | 	ret = regmap_write(data->regmap, FXOS8700_XYZ_DATA_CFG, | 
 | 			    fxos8700_accel_scale[i].bits); | 
 | 	if (ret) | 
 | 		return ret; | 
 | 	return regmap_write(data->regmap, FXOS8700_CTRL_REG1, | 
 | 				  active_mode); | 
 | } | 
 |  | 
 | static int fxos8700_get_scale(struct fxos8700_data *data, | 
 | 			      enum fxos8700_sensor t, int *uscale) | 
 | { | 
 | 	int i, ret, val; | 
 | 	static const int scale_num = ARRAY_SIZE(fxos8700_accel_scale); | 
 |  | 
 | 	if (t == FXOS8700_MAGN) { | 
 | 		*uscale = 1000; /* Magnetometer is locked at 0.001Gs */ | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	ret = regmap_read(data->regmap, FXOS8700_XYZ_DATA_CFG, &val); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	for (i = 0; i < scale_num; i++) { | 
 | 		if (fxos8700_accel_scale[i].bits == (val & 0x3)) { | 
 | 			*uscale = fxos8700_accel_scale[i].uscale; | 
 | 			return 0; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static int fxos8700_get_data(struct fxos8700_data *data, int chan_type, | 
 | 			     int axis, int *val) | 
 | { | 
 | 	u8 base, reg; | 
 | 	s16 tmp; | 
 | 	int ret; | 
 |  | 
 | 	/* | 
 | 	 * Different register base addresses varies with channel types. | 
 | 	 * This bug hasn't been noticed before because using an enum is | 
 | 	 * really hard to read. Use an a switch statement to take over that. | 
 | 	 */ | 
 | 	switch (chan_type) { | 
 | 	case IIO_ACCEL: | 
 | 		base = FXOS8700_OUT_X_MSB; | 
 | 		break; | 
 | 	case IIO_MAGN: | 
 | 		base = FXOS8700_M_OUT_X_MSB; | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Block read 6 bytes of device output registers to avoid data loss */ | 
 | 	ret = regmap_bulk_read(data->regmap, base, data->buf, | 
 | 			       sizeof(data->buf)); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	/* Convert axis to buffer index */ | 
 | 	reg = axis - IIO_MOD_X; | 
 |  | 
 | 	/* | 
 | 	 * Convert to native endianness. The accel data and magn data | 
 | 	 * are signed, so a forced type conversion is needed. | 
 | 	 */ | 
 | 	tmp = be16_to_cpu(data->buf[reg]); | 
 |  | 
 | 	/* | 
 | 	 * ACCEL output data registers contain the X-axis, Y-axis, and Z-axis | 
 | 	 * 14-bit left-justified sample data and MAGN output data registers | 
 | 	 * contain the X-axis, Y-axis, and Z-axis 16-bit sample data. Apply | 
 | 	 * a signed 2 bits right shift to the readback raw data from ACCEL | 
 | 	 * output data register and keep that from MAGN sensor as the origin. | 
 | 	 * Value should be extended to 32 bit. | 
 | 	 */ | 
 | 	switch (chan_type) { | 
 | 	case IIO_ACCEL: | 
 | 		tmp = tmp >> 2; | 
 | 		break; | 
 | 	case IIO_MAGN: | 
 | 		/* Nothing to do */ | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Convert to native endianness */ | 
 | 	*val = sign_extend32(tmp, 15); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int fxos8700_set_odr(struct fxos8700_data *data, enum fxos8700_sensor t, | 
 | 			    int odr, int uodr) | 
 | { | 
 | 	int i, ret, val; | 
 | 	bool active_mode; | 
 | 	static const int odr_num = ARRAY_SIZE(fxos8700_odr); | 
 |  | 
 | 	ret = regmap_read(data->regmap, FXOS8700_CTRL_REG1, &val); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	active_mode = val & FXOS8700_ACTIVE; | 
 |  | 
 | 	if (active_mode) { | 
 | 		/* | 
 | 		 * The device must be in standby mode to change any of the | 
 | 		 * other fields within CTRL_REG1 | 
 | 		 */ | 
 | 		ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, | 
 | 				   val & ~FXOS8700_ACTIVE); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < odr_num; i++) | 
 | 		if (fxos8700_odr[i].odr == odr && fxos8700_odr[i].uodr == uodr) | 
 | 			break; | 
 |  | 
 | 	if (i >= odr_num) | 
 | 		return -EINVAL; | 
 |  | 
 | 	val &= ~FXOS8700_CTRL_ODR_MSK; | 
 | 	val |= FIELD_PREP(FXOS8700_CTRL_ODR_MSK, fxos8700_odr[i].bits) | FXOS8700_ACTIVE; | 
 | 	return regmap_write(data->regmap, FXOS8700_CTRL_REG1, val); | 
 | } | 
 |  | 
 | static int fxos8700_get_odr(struct fxos8700_data *data, enum fxos8700_sensor t, | 
 | 			    int *odr, int *uodr) | 
 | { | 
 | 	int i, val, ret; | 
 | 	static const int odr_num = ARRAY_SIZE(fxos8700_odr); | 
 |  | 
 | 	ret = regmap_read(data->regmap, FXOS8700_CTRL_REG1, &val); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	val = FIELD_GET(FXOS8700_CTRL_ODR_MSK, val); | 
 |  | 
 | 	for (i = 0; i < odr_num; i++) | 
 | 		if (val == fxos8700_odr[i].bits) | 
 | 			break; | 
 |  | 
 | 	if (i >= odr_num) | 
 | 		return -EINVAL; | 
 |  | 
 | 	*odr = fxos8700_odr[i].odr; | 
 | 	*uodr = fxos8700_odr[i].uodr; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int fxos8700_read_raw(struct iio_dev *indio_dev, | 
 | 			     struct iio_chan_spec const *chan, | 
 | 			     int *val, int *val2, long mask) | 
 | { | 
 | 	int ret; | 
 | 	struct fxos8700_data *data = iio_priv(indio_dev); | 
 |  | 
 | 	switch (mask) { | 
 | 	case IIO_CHAN_INFO_RAW: | 
 | 		ret = fxos8700_get_data(data, chan->type, chan->channel2, val); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 		return IIO_VAL_INT; | 
 | 	case IIO_CHAN_INFO_SCALE: | 
 | 		*val = 0; | 
 | 		ret = fxos8700_get_scale(data, fxos8700_to_sensor(chan->type), | 
 | 					 val2); | 
 | 		return ret ? ret : IIO_VAL_INT_PLUS_MICRO; | 
 | 	case IIO_CHAN_INFO_SAMP_FREQ: | 
 | 		ret = fxos8700_get_odr(data, fxos8700_to_sensor(chan->type), | 
 | 				       val, val2); | 
 | 		return ret ? ret : IIO_VAL_INT_PLUS_MICRO; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | } | 
 |  | 
 | static int fxos8700_write_raw(struct iio_dev *indio_dev, | 
 | 			      struct iio_chan_spec const *chan, | 
 | 			      int val, int val2, long mask) | 
 | { | 
 | 	struct fxos8700_data *data = iio_priv(indio_dev); | 
 |  | 
 | 	switch (mask) { | 
 | 	case IIO_CHAN_INFO_SCALE: | 
 | 		return fxos8700_set_scale(data, fxos8700_to_sensor(chan->type), | 
 | 					  val2); | 
 | 	case IIO_CHAN_INFO_SAMP_FREQ: | 
 | 		return fxos8700_set_odr(data, fxos8700_to_sensor(chan->type), | 
 | 					val, val2); | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | } | 
 |  | 
 | static IIO_CONST_ATTR(in_accel_sampling_frequency_available, | 
 | 		      "1.5625 6.25 12.5 50 100 200 400 800"); | 
 | static IIO_CONST_ATTR(in_magn_sampling_frequency_available, | 
 | 		      "1.5625 6.25 12.5 50 100 200 400 800"); | 
 | static IIO_CONST_ATTR(in_accel_scale_available, "0.000244 0.000488 0.000976"); | 
 | static IIO_CONST_ATTR(in_magn_scale_available, "0.001000"); | 
 |  | 
 | static struct attribute *fxos8700_attrs[] = { | 
 | 	&iio_const_attr_in_accel_sampling_frequency_available.dev_attr.attr, | 
 | 	&iio_const_attr_in_magn_sampling_frequency_available.dev_attr.attr, | 
 | 	&iio_const_attr_in_accel_scale_available.dev_attr.attr, | 
 | 	&iio_const_attr_in_magn_scale_available.dev_attr.attr, | 
 | 	NULL, | 
 | }; | 
 |  | 
 | static const struct attribute_group fxos8700_attrs_group = { | 
 | 	.attrs = fxos8700_attrs, | 
 | }; | 
 |  | 
 | static const struct iio_info fxos8700_info = { | 
 | 	.read_raw = fxos8700_read_raw, | 
 | 	.write_raw = fxos8700_write_raw, | 
 | 	.attrs = &fxos8700_attrs_group, | 
 | }; | 
 |  | 
 | static int fxos8700_chip_init(struct fxos8700_data *data, bool use_spi) | 
 | { | 
 | 	int ret; | 
 | 	unsigned int val; | 
 | 	struct device *dev = regmap_get_device(data->regmap); | 
 |  | 
 | 	ret = regmap_read(data->regmap, FXOS8700_WHO_AM_I, &val); | 
 | 	if (ret) { | 
 | 		dev_err(dev, "Error reading chip id\n"); | 
 | 		return ret; | 
 | 	} | 
 | 	if (val != FXOS8700_DEVICE_ID && val != FXOS8700_PRE_DEVICE_ID) { | 
 | 		dev_err(dev, "Wrong chip id, got %x expected %x or %x\n", | 
 | 			val, FXOS8700_DEVICE_ID, FXOS8700_PRE_DEVICE_ID); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	ret = fxos8700_set_active_mode(data, FXOS8700_ACCEL, true); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = fxos8700_set_active_mode(data, FXOS8700_MAGN, true); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	/* | 
 | 	 * The device must be in standby mode to change any of the other fields | 
 | 	 * within CTRL_REG1 | 
 | 	 */ | 
 | 	ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, 0x00); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	/* Set max oversample ratio (OSR) and both devices active */ | 
 | 	ret = regmap_write(data->regmap, FXOS8700_M_CTRL_REG1, | 
 | 			   FXOS8700_HMS_MASK | FXOS8700_OS_MASK); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	/* Disable and rst min/max measurements & threshold */ | 
 | 	ret = regmap_write(data->regmap, FXOS8700_M_CTRL_REG2, | 
 | 			   FXOS8700_MAXMIN_RST | FXOS8700_MAXMIN_DIS_THS | | 
 | 			   FXOS8700_MAXMIN_DIS); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	/* | 
 | 	 * Set max full-scale range (+/-8G) for ACCEL sensor in chip | 
 | 	 * initialization then activate the device. | 
 | 	 */ | 
 | 	ret = regmap_write(data->regmap, FXOS8700_XYZ_DATA_CFG, MODE_8G); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	/* Max ODR (800Hz individual or 400Hz hybrid), active mode */ | 
 | 	return regmap_update_bits(data->regmap, FXOS8700_CTRL_REG1, | 
 | 				FXOS8700_CTRL_ODR_MSK | FXOS8700_ACTIVE, | 
 | 				FIELD_PREP(FXOS8700_CTRL_ODR_MSK, FXOS8700_CTRL_ODR_MAX) | | 
 | 				FXOS8700_ACTIVE); | 
 | } | 
 |  | 
 | static void fxos8700_chip_uninit(void *data) | 
 | { | 
 | 	struct fxos8700_data *fxos8700_data = data; | 
 |  | 
 | 	fxos8700_set_active_mode(fxos8700_data, FXOS8700_ACCEL, false); | 
 | 	fxos8700_set_active_mode(fxos8700_data, FXOS8700_MAGN, false); | 
 | } | 
 |  | 
 | int fxos8700_core_probe(struct device *dev, struct regmap *regmap, | 
 | 			const char *name, bool use_spi) | 
 | { | 
 | 	struct iio_dev *indio_dev; | 
 | 	struct fxos8700_data *data; | 
 | 	int ret; | 
 |  | 
 | 	indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); | 
 | 	if (!indio_dev) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	data = iio_priv(indio_dev); | 
 | 	dev_set_drvdata(dev, indio_dev); | 
 | 	data->regmap = regmap; | 
 |  | 
 | 	ret = fxos8700_chip_init(data, use_spi); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = devm_add_action_or_reset(dev, fxos8700_chip_uninit, data); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	indio_dev->channels = fxos8700_channels; | 
 | 	indio_dev->num_channels = ARRAY_SIZE(fxos8700_channels); | 
 | 	indio_dev->name = name ? name : "fxos8700"; | 
 | 	indio_dev->modes = INDIO_DIRECT_MODE; | 
 | 	indio_dev->info = &fxos8700_info; | 
 |  | 
 | 	return devm_iio_device_register(dev, indio_dev); | 
 | } | 
 | EXPORT_SYMBOL_GPL(fxos8700_core_probe); | 
 |  | 
 | MODULE_AUTHOR("Robert Jones <[email protected]>"); | 
 | MODULE_DESCRIPTION("FXOS8700 6-Axis Acc and Mag Combo Sensor driver"); | 
 | MODULE_LICENSE("GPL v2"); |