| // SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause | |
| /* | |
| * Support for QST QMC5883L 3-Axis Magnetic Sensor on I2C bus. | |
| * | |
| * Copyright (C) 2026 Siratul Islam <siratul.islam@linux.dev> | |
| * | |
| * Datasheet available at | |
| * <https://www.qstcorp.com/upload/pdf/202512/13-52-04%20QMC5883L%20Datasheet%20Rev.%20B.pdf> | |
| */ | |
| #include <linux/array_size.h> | |
| #include <linux/bitfield.h> | |
| #include <linux/bits.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/delay.h> | |
| #include <linux/dev_printk.h> | |
| #include <linux/err.h> | |
| #include <linux/i2c.h> | |
| #include <linux/module.h> | |
| #include <linux/mod_devicetable.h> | |
| #include <linux/regmap.h> | |
| #include <linux/regulator/consumer.h> | |
| #include <linux/time.h> | |
| #include <linux/types.h> | |
| #include <asm/byteorder.h> | |
| #include <linux/iio/iio.h> | |
| #define QMC5883L_REG_X_LSB 0x00 | |
| #define QMC5883L_REG_STATUS1 0x06 | |
| #define QMC5883L_REG_CTRL1 0x09 | |
| #define QMC5883L_REG_CTRL2 0x0A | |
| #define QMC5883L_REG_SET_RESET 0x0B | |
| #define QMC5883L_REG_ID 0x0D | |
| #define QMC5883L_CHIP_ID 0xFF | |
| #define QMC5883L_MODE_MASK GENMASK(1, 0) | |
| #define QMC5883L_ODR_MASK GENMASK(3, 2) | |
| #define QMC5883L_RNG_MASK GENMASK(5, 4) | |
| #define QMC5883L_OSR_MASK GENMASK(7, 6) | |
| #define QMC5883L_MODE_STANDBY 0x00 | |
| #define QMC5883L_MODE_CONT 0x01 | |
| #define QMC5883L_ODR_10HZ 0x00 | |
| #define QMC5883L_ODR_50HZ 0x01 | |
| #define QMC5883L_ODR_100HZ 0x02 | |
| #define QMC5883L_ODR_200HZ 0x03 | |
| #define QMC5883L_RNG_2G 0x00 | |
| #define QMC5883L_RNG_8G 0x01 | |
| #define QMC5883L_OSR_512 0x00 | |
| #define QMC5883L_OSR_256 0x01 | |
| #define QMC5883L_OSR_128 0x02 | |
| #define QMC5883L_OSR_64 0x03 | |
| #define QMC5883L_STATUS_DRDY BIT(0) | |
| #define QMC5883L_STATUS_OVL BIT(1) | |
| #define QMC5883L_SET_RESET_VAL BIT(0) | |
| #define QMC5883L_INT_DISABLE BIT(0) | |
| #define QMC5883L_SOFT_RESET BIT(7) | |
| #define QMC5883L_PORT_US 350 | |
| struct qmc5883l_data { | |
| struct regmap *regmap; | |
| /* | |
| * Protect data->range/odr/osr. | |
| * Protect poll and read during measurement. | |
| */ | |
| struct mutex mutex; | |
| u8 range; | |
| u8 odr; | |
| u8 osr; | |
| }; | |
| enum qmc5883l_chan { | |
| QMC5883L_AXIS_X, | |
| QMC5883L_AXIS_Y, | |
| QMC5883L_AXIS_Z, | |
| }; | |
| static const int qmc5883l_odr_avail[] = { 10, 50, 100, 200 }; | |
| static const int qmc5883l_osr_avail[] = { 512, 256, 128, 64 }; | |
| static const int qmc5883l_scales[][2] = { | |
| [QMC5883L_RNG_2G] = { 0, 83333 }, | |
| [QMC5883L_RNG_8G] = { 0, 333333 }, | |
| }; | |
| static int qmc5883l_take_measurement(struct iio_dev *indio_dev, int index, | |
| int *val) | |
| { | |
| struct qmc5883l_data *data = iio_priv(indio_dev); | |
| struct regmap *map = data->regmap; | |
| unsigned int status; | |
| __le16 buf[3]; | |
| int ret; | |
| guard(mutex) (&data->mutex); | |
| /* 50ms headroom over the slowest ODR (10Hz) */ | |
| ret = regmap_read_poll_timeout(map, QMC5883L_REG_STATUS1, | |
| status, (status & QMC5883L_STATUS_DRDY), | |
| 2 * USEC_PER_MSEC, 150 * USEC_PER_MSEC); | |
| if (ret) | |
| return ret; | |
| ret = regmap_bulk_read(map, QMC5883L_REG_X_LSB, buf, sizeof(buf)); | |
| if (ret) | |
| return ret; | |
| if (status & QMC5883L_STATUS_OVL) | |
| return -ERANGE; | |
| *val = (s16)le16_to_cpu(buf[index]); | |
| return 0; | |
| } | |
| static int qmc5883l_read_raw(struct iio_dev *indio_dev, | |
| const struct iio_chan_spec *chan, | |
| int *val, int *val2, long mask) | |
| { | |
| struct qmc5883l_data *data = iio_priv(indio_dev); | |
| int ret; | |
| switch (mask) { | |
| case IIO_CHAN_INFO_RAW: | |
| ret = qmc5883l_take_measurement(indio_dev, chan->address, val); | |
| if (ret) | |
| return ret; | |
| return IIO_VAL_INT; | |
| case IIO_CHAN_INFO_SCALE: { | |
| guard(mutex)(&data->mutex); | |
| *val = qmc5883l_scales[data->range][0]; | |
| *val2 = qmc5883l_scales[data->range][1]; | |
| return IIO_VAL_INT_PLUS_NANO; | |
| } | |
| case IIO_CHAN_INFO_SAMP_FREQ: { | |
| guard(mutex)(&data->mutex); | |
| switch (data->odr) { | |
| case QMC5883L_ODR_200HZ: | |
| *val = 200; | |
| break; | |
| case QMC5883L_ODR_100HZ: | |
| *val = 100; | |
| break; | |
| case QMC5883L_ODR_50HZ: | |
| *val = 50; | |
| break; | |
| case QMC5883L_ODR_10HZ: | |
| *val = 10; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return IIO_VAL_INT; | |
| } | |
| case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { | |
| guard(mutex)(&data->mutex); | |
| switch (data->osr) { | |
| case QMC5883L_OSR_64: | |
| *val = 64; | |
| break; | |
| case QMC5883L_OSR_128: | |
| *val = 128; | |
| break; | |
| case QMC5883L_OSR_256: | |
| *val = 256; | |
| break; | |
| case QMC5883L_OSR_512: | |
| *val = 512; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return IIO_VAL_INT; | |
| } | |
| default: | |
| return -EINVAL; | |
| } | |
| } | |
| static int qmc5883l_write_raw(struct iio_dev *indio_dev, | |
| const struct iio_chan_spec *chan, | |
| int val, int val2, long mask) | |
| { | |
| struct qmc5883l_data *data = iio_priv(indio_dev); | |
| u8 rng, osr, odr; | |
| int ret; | |
| switch (mask) { | |
| case IIO_CHAN_INFO_SCALE: { | |
| if (val != 0) | |
| return -EINVAL; | |
| if (val2 == qmc5883l_scales[QMC5883L_RNG_2G][1]) | |
| rng = QMC5883L_RNG_2G; | |
| else if (val2 == qmc5883l_scales[QMC5883L_RNG_8G][1]) | |
| rng = QMC5883L_RNG_8G; | |
| else | |
| return -EINVAL; | |
| guard(mutex)(&data->mutex); | |
| ret = regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, | |
| QMC5883L_RNG_MASK, | |
| FIELD_PREP(QMC5883L_RNG_MASK, rng)); | |
| if (ret) | |
| return ret; | |
| data->range = rng; | |
| return 0; | |
| } | |
| case IIO_CHAN_INFO_SAMP_FREQ: { | |
| switch (val) { | |
| case 200: | |
| odr = QMC5883L_ODR_200HZ; | |
| break; | |
| case 100: | |
| odr = QMC5883L_ODR_100HZ; | |
| break; | |
| case 50: | |
| odr = QMC5883L_ODR_50HZ; | |
| break; | |
| case 10: | |
| odr = QMC5883L_ODR_10HZ; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| guard(mutex)(&data->mutex); | |
| ret = regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, | |
| QMC5883L_ODR_MASK, | |
| FIELD_PREP(QMC5883L_ODR_MASK, odr)); | |
| if (ret) | |
| return ret; | |
| data->odr = odr; | |
| return 0; | |
| } | |
| case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { | |
| switch (val) { | |
| case 64: | |
| osr = QMC5883L_OSR_64; | |
| break; | |
| case 128: | |
| osr = QMC5883L_OSR_128; | |
| break; | |
| case 256: | |
| osr = QMC5883L_OSR_256; | |
| break; | |
| case 512: | |
| osr = QMC5883L_OSR_512; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| guard(mutex)(&data->mutex); | |
| ret = regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, | |
| QMC5883L_OSR_MASK, | |
| FIELD_PREP(QMC5883L_OSR_MASK, osr)); | |
| if (ret) | |
| return ret; | |
| data->osr = osr; | |
| return 0; | |
| } | |
| default: | |
| return -EINVAL; | |
| } | |
| } | |
| static int qmc5883l_read_avail(struct iio_dev *indio_dev, | |
| struct iio_chan_spec const *chan, | |
| const int **vals, int *type, int *length, | |
| long mask) | |
| { | |
| switch (mask) { | |
| case IIO_CHAN_INFO_SAMP_FREQ: | |
| *vals = qmc5883l_odr_avail; | |
| *type = IIO_VAL_INT; | |
| *length = ARRAY_SIZE(qmc5883l_odr_avail); | |
| return IIO_AVAIL_LIST; | |
| case IIO_CHAN_INFO_OVERSAMPLING_RATIO: | |
| *vals = qmc5883l_osr_avail; | |
| *type = IIO_VAL_INT; | |
| *length = ARRAY_SIZE(qmc5883l_osr_avail); | |
| return IIO_AVAIL_LIST; | |
| case IIO_CHAN_INFO_SCALE: | |
| *vals = (const int *)qmc5883l_scales; | |
| *type = IIO_VAL_INT_PLUS_NANO; | |
| *length = ARRAY_SIZE(qmc5883l_scales) * 2; | |
| return IIO_AVAIL_LIST; | |
| default: | |
| return -EINVAL; | |
| } | |
| } | |
| static int qmc5883l_write_raw_get_fmt(struct iio_dev *indio_dev, | |
| struct iio_chan_spec const *chan, | |
| long mask) | |
| { | |
| switch (mask) { | |
| case IIO_CHAN_INFO_SCALE: | |
| return IIO_VAL_INT_PLUS_NANO; | |
| default: | |
| return IIO_VAL_INT; | |
| } | |
| } | |
| static const struct iio_info qmc5883l_info = { | |
| .read_raw = qmc5883l_read_raw, | |
| .write_raw = qmc5883l_write_raw, | |
| .read_avail = qmc5883l_read_avail, | |
| .write_raw_get_fmt = qmc5883l_write_raw_get_fmt, | |
| }; | |
| static int qmc5883l_init(struct qmc5883l_data *data) | |
| { | |
| struct regmap *map = data->regmap; | |
| unsigned int reg; | |
| int ret; | |
| ret = regmap_read(map, QMC5883L_REG_ID, ®); | |
| if (ret) | |
| return ret; | |
| /* Not failing because rev 1.0 had this register reserved */ | |
| if (reg != QMC5883L_CHIP_ID) | |
| dev_warn(regmap_get_device(map), | |
| "Unknown chip id: 0x%02x, continuing\n", reg); | |
| ret = regmap_write(map, QMC5883L_REG_CTRL2, QMC5883L_SOFT_RESET); | |
| if (ret) | |
| return ret; | |
| /* Use POR completion time as a conservative bound */ | |
| fsleep(QMC5883L_PORT_US); | |
| /* DRDY pin not used in this version of the driver */ | |
| ret = regmap_write(map, QMC5883L_REG_CTRL2, QMC5883L_INT_DISABLE); | |
| if (ret) | |
| return ret; | |
| ret = regmap_write(map, QMC5883L_REG_SET_RESET, QMC5883L_SET_RESET_VAL); | |
| if (ret) | |
| return ret; | |
| data->odr = QMC5883L_ODR_50HZ; | |
| data->range = QMC5883L_RNG_2G; | |
| data->osr = QMC5883L_OSR_64; | |
| return regmap_write(map, QMC5883L_REG_CTRL1, | |
| FIELD_PREP(QMC5883L_MODE_MASK, QMC5883L_MODE_CONT) | | |
| FIELD_PREP(QMC5883L_ODR_MASK, data->odr) | | |
| FIELD_PREP(QMC5883L_RNG_MASK, data->range) | | |
| FIELD_PREP(QMC5883L_OSR_MASK, data->osr)); | |
| } | |
| static void qmc5883l_power_down_action(void *priv) | |
| { | |
| struct qmc5883l_data *data = priv; | |
| regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, | |
| QMC5883L_MODE_MASK, | |
| FIELD_PREP(QMC5883L_MODE_MASK, QMC5883L_MODE_STANDBY)); | |
| } | |
| static bool qmc5883l_volatile_reg(struct device *dev, unsigned int reg) | |
| { | |
| return reg <= QMC5883L_REG_STATUS1; | |
| } | |
| static bool qmc5883l_writable_reg(struct device *dev, unsigned int reg) | |
| { | |
| switch (reg) { | |
| case QMC5883L_REG_CTRL1: | |
| case QMC5883L_REG_CTRL2: | |
| case QMC5883L_REG_SET_RESET: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| static const struct regmap_config qmc5883l_regmap_config = { | |
| .reg_bits = 8, | |
| .val_bits = 8, | |
| .max_register = QMC5883L_REG_ID, | |
| .cache_type = REGCACHE_MAPLE, | |
| .volatile_reg = qmc5883l_volatile_reg, | |
| .writeable_reg = qmc5883l_writable_reg, | |
| }; | |
| #define QMC5883L_CHANNEL(_axis) \ | |
| { \ | |
| .type = IIO_MAGN, \ | |
| .modified = 1, \ | |
| .channel2 = IIO_MOD_##_axis, \ | |
| .address = QMC5883L_AXIS_##_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) | \ | |
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ | |
| .info_mask_shared_by_type_available = \ | |
| BIT(IIO_CHAN_INFO_SCALE) | \ | |
| BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ | |
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ | |
| } | |
| static const struct iio_chan_spec qmc5883l_channels[] = { | |
| QMC5883L_CHANNEL(X), | |
| QMC5883L_CHANNEL(Y), | |
| QMC5883L_CHANNEL(Z), | |
| }; | |
| static int qmc5883l_probe(struct i2c_client *client) | |
| { | |
| struct device *dev = &client->dev; | |
| struct qmc5883l_data *data; | |
| struct iio_dev *indio_dev; | |
| struct regmap *map; | |
| int ret; | |
| indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); | |
| if (!indio_dev) | |
| return -ENOMEM; | |
| map = devm_regmap_init_i2c(client, &qmc5883l_regmap_config); | |
| if (IS_ERR(map)) | |
| return dev_err_probe(dev, PTR_ERR(map), | |
| "regmap initialization failed\n"); | |
| ret = devm_regulator_get_enable(dev, "vdd"); | |
| if (ret) | |
| return dev_err_probe(dev, ret, | |
| "Failed to enable VDD regulator\n"); | |
| ret = devm_regulator_get_enable(dev, "vddio"); | |
| if (ret) | |
| return dev_err_probe(dev, ret, | |
| "Failed to enable VDDIO regulator\n"); | |
| /* POR completion time max per datasheet Table 7 */ | |
| fsleep(QMC5883L_PORT_US); | |
| data = iio_priv(indio_dev); | |
| data->regmap = map; | |
| ret = devm_mutex_init(dev, &data->mutex); | |
| if (ret) | |
| return ret; | |
| indio_dev->name = "qmc5883l"; | |
| indio_dev->info = &qmc5883l_info; | |
| indio_dev->channels = qmc5883l_channels; | |
| indio_dev->num_channels = ARRAY_SIZE(qmc5883l_channels); | |
| indio_dev->modes = INDIO_DIRECT_MODE; | |
| ret = qmc5883l_init(data); | |
| if (ret) | |
| return dev_err_probe(dev, ret, "qmc5883l init failed\n"); | |
| ret = devm_add_action_or_reset(dev, qmc5883l_power_down_action, data); | |
| if (ret) | |
| return ret; | |
| return devm_iio_device_register(dev, indio_dev); | |
| } | |
| static const struct of_device_id qmc5883l_match[] = { | |
| { .compatible = "qstcorp,qmc5883l" }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(of, qmc5883l_match); | |
| static const struct i2c_device_id qmc5883l_id[] = { | |
| { .name = "qmc5883l" }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(i2c, qmc5883l_id); | |
| static struct i2c_driver qmc5883l_driver = { | |
| .driver = { | |
| .name = "qmc5883l", | |
| .of_match_table = qmc5883l_match, | |
| }, | |
| .id_table = qmc5883l_id, | |
| .probe = qmc5883l_probe, | |
| }; | |
| module_i2c_driver(qmc5883l_driver); | |
| MODULE_DESCRIPTION("QST QMC5883L 3-Axis Magnetic Sensor driver"); | |
| MODULE_AUTHOR("Siratul Islam <siratul.islam@linux.dev>"); | |
| MODULE_LICENSE("Dual BSD/GPL"); |