// SPDX-License-Identifier: GPL-2.0-only /* * Support for QST QMC6308 3-Axis Magnetic Sensor on I2C bus. * * Copyright (C) 2026 Jorijn van der Graaf * * Datasheet available at * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define QMC6308_REG_ID 0x00 #define QMC6308_REG_X_LSB 0x01 #define QMC6308_REG_STATUS 0x09 #define QMC6308_REG_CTRL1 0x0A #define QMC6308_REG_CTRL2 0x0B #define QMC6308_REG_CTRL3 0x0D #define QMC6308_REG_CTRL4 0x29 #define QMC6308_CHIP_ID 0x80 /* Control register 1 */ #define QMC6308_MODE_MASK GENMASK(1, 0) #define QMC6308_ODR_MASK GENMASK(3, 2) #define QMC6308_OSR1_MASK GENMASK(5, 4) #define QMC6308_OSR2_MASK GENMASK(7, 6) #define QMC6308_MODE_SUSPEND 0x00 #define QMC6308_MODE_NORMAL 0x01 #define QMC6308_ODR_10HZ 0x00 #define QMC6308_ODR_50HZ 0x01 #define QMC6308_ODR_100HZ 0x02 #define QMC6308_ODR_200HZ 0x03 #define QMC6308_OSR1_8 0x00 #define QMC6308_OSR1_4 0x01 #define QMC6308_OSR1_2 0x02 #define QMC6308_OSR1_1 0x03 /* Control register 2 */ #define QMC6308_SET_RESET_MASK GENMASK(1, 0) #define QMC6308_RNG_MASK GENMASK(3, 2) #define QMC6308_SELF_TEST BIT(6) #define QMC6308_SOFT_RST BIT(7) #define QMC6308_SET_RESET_ON 0x00 #define QMC6308_RNG_30G 0x00 #define QMC6308_RNG_12G 0x01 #define QMC6308_RNG_8G 0x02 #define QMC6308_RNG_2G 0x03 /* Status register */ #define QMC6308_STATUS_DRDY BIT(0) #define QMC6308_STATUS_OVFL BIT(1) /* Power-on completion time (datasheet Table 7) */ #define QMC6308_POR_US 250 #define QMC6308_AUTOSUSPEND_DELAY_MS 500 struct qmc6308_data { struct regmap *regmap; /* Protect data->range/odr/osr and serialize measurements */ struct mutex mutex; struct iio_mount_matrix orientation; u8 range; u8 odr; u8 osr; }; enum qmc6308_axis { QMC6308_AXIS_X, QMC6308_AXIS_Y, QMC6308_AXIS_Z, }; static const int qmc6308_odr_avail[] = { [QMC6308_ODR_10HZ] = 10, [QMC6308_ODR_50HZ] = 50, [QMC6308_ODR_100HZ] = 100, [QMC6308_ODR_200HZ] = 200, }; static const int qmc6308_osr1_avail[] = { [QMC6308_OSR1_8] = 8, [QMC6308_OSR1_4] = 4, [QMC6308_OSR1_2] = 2, [QMC6308_OSR1_1] = 1, }; /* * Sensitivity is 1000/2500/3750/15000 LSB/Gauss for the * +-30/12/8/2 Gauss ranges respectively. */ static const int qmc6308_scales[][2] = { [QMC6308_RNG_30G] = { 0, 1000000 }, [QMC6308_RNG_12G] = { 0, 400000 }, [QMC6308_RNG_8G] = { 0, 266667 }, [QMC6308_RNG_2G] = { 0, 66667 }, }; static int qmc6308_set_mode(struct qmc6308_data *data, unsigned int mode) { return regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, QMC6308_MODE_MASK, FIELD_PREP(QMC6308_MODE_MASK, mode)); } static int qmc6308_take_measurement(struct iio_dev *indio_dev, int index, int *val) { struct qmc6308_data *data = iio_priv(indio_dev); struct device *dev = regmap_get_device(data->regmap); unsigned int status; __le16 buf[3]; int ret; PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); ret = PM_RUNTIME_ACQUIRE_ERR(&pm); if (ret) { dev_err(dev, "Failed to power on (%d)\n", ret); return ret; } guard(mutex)(&data->mutex); /* * Reading the status register clears DRDY, which is why the poll * and the data read stay under one mutex hold. A runtime resume * clears DRDY too, so a sample converted before the last suspend * is never returned here. * * The timeout is 50ms of headroom over the slowest ODR (10Hz). */ ret = regmap_read_poll_timeout(data->regmap, QMC6308_REG_STATUS, status, (status & QMC6308_STATUS_DRDY), 2 * USEC_PER_MSEC, 150 * USEC_PER_MSEC); if (ret) return ret; ret = regmap_bulk_read(data->regmap, QMC6308_REG_X_LSB, buf, sizeof(buf)); if (ret) return ret; if (status & QMC6308_STATUS_OVFL) return -ERANGE; *val = (s16)le16_to_cpu(buf[index]); return 0; } static int qmc6308_read_raw(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, int *val, int *val2, long mask) { struct qmc6308_data *data = iio_priv(indio_dev); int ret; switch (mask) { case IIO_CHAN_INFO_RAW: ret = qmc6308_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 = qmc6308_scales[data->range][0]; *val2 = qmc6308_scales[data->range][1]; return IIO_VAL_INT_PLUS_NANO; } case IIO_CHAN_INFO_SAMP_FREQ: { guard(mutex)(&data->mutex); *val = qmc6308_odr_avail[data->odr]; return IIO_VAL_INT; } case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { guard(mutex)(&data->mutex); *val = qmc6308_osr1_avail[data->osr]; return IIO_VAL_INT; } default: return -EINVAL; } } static int qmc6308_write_raw(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, int val, int val2, long mask) { struct qmc6308_data *data = iio_priv(indio_dev); unsigned int status; unsigned int i; int ret; switch (mask) { case IIO_CHAN_INFO_SCALE: { if (val != 0) return -EINVAL; for (i = 0; i < ARRAY_SIZE(qmc6308_scales); i++) { if (val2 == qmc6308_scales[i][1]) break; } if (i == ARRAY_SIZE(qmc6308_scales)) return -EINVAL; guard(mutex)(&data->mutex); ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL2, QMC6308_RNG_MASK, FIELD_PREP(QMC6308_RNG_MASK, i)); if (ret) return ret; data->range = i; /* * The data registers still hold (and DRDY still * advertises) a sample converted at the previous range; * discard it so that a read does not pair old-range data * with the new scale. A conversion already in flight may * still complete at the old range, so this narrows the * window rather than closing it. The range change itself * took effect, so only log a failure here: an error * would mislead userspace about an effective write. */ ret = regmap_read(data->regmap, QMC6308_REG_STATUS, &status); if (ret) dev_warn(regmap_get_device(data->regmap), "Failed to discard stale sample (%d)\n", ret); return 0; } case IIO_CHAN_INFO_SAMP_FREQ: { for (i = 0; i < ARRAY_SIZE(qmc6308_odr_avail); i++) { if (val == qmc6308_odr_avail[i]) break; } if (i == ARRAY_SIZE(qmc6308_odr_avail)) return -EINVAL; guard(mutex)(&data->mutex); ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, QMC6308_ODR_MASK, FIELD_PREP(QMC6308_ODR_MASK, i)); if (ret) return ret; data->odr = i; return 0; } case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { for (i = 0; i < ARRAY_SIZE(qmc6308_osr1_avail); i++) { if (val == qmc6308_osr1_avail[i]) break; } if (i == ARRAY_SIZE(qmc6308_osr1_avail)) return -EINVAL; guard(mutex)(&data->mutex); ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, QMC6308_OSR1_MASK, FIELD_PREP(QMC6308_OSR1_MASK, i)); if (ret) return ret; data->osr = i; return 0; } default: return -EINVAL; } } static int qmc6308_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 = qmc6308_odr_avail; *type = IIO_VAL_INT; *length = ARRAY_SIZE(qmc6308_odr_avail); return IIO_AVAIL_LIST; case IIO_CHAN_INFO_OVERSAMPLING_RATIO: *vals = qmc6308_osr1_avail; *type = IIO_VAL_INT; *length = ARRAY_SIZE(qmc6308_osr1_avail); return IIO_AVAIL_LIST; case IIO_CHAN_INFO_SCALE: *vals = (const int *)qmc6308_scales; *type = IIO_VAL_INT_PLUS_NANO; *length = ARRAY_SIZE(qmc6308_scales) * 2; return IIO_AVAIL_LIST; default: return -EINVAL; } } static int qmc6308_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_mount_matrix * qmc6308_get_mount_matrix(const struct iio_dev *indio_dev, const struct iio_chan_spec *chan) { struct qmc6308_data *data = iio_priv(indio_dev); return &data->orientation; } static const struct iio_chan_spec_ext_info qmc6308_ext_info[] = { IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, qmc6308_get_mount_matrix), { } }; static const struct iio_info qmc6308_info = { .read_raw = qmc6308_read_raw, .write_raw = qmc6308_write_raw, .read_avail = qmc6308_read_avail, .write_raw_get_fmt = qmc6308_write_raw_get_fmt, }; static int qmc6308_init(struct qmc6308_data *data) { struct regmap *map = data->regmap; unsigned int reg; int ret; ret = regmap_read(map, QMC6308_REG_ID, ®); if (ret) return ret; /* Allow unknown IDs so that fallback compatibles work */ if (reg != QMC6308_CHIP_ID) dev_warn(regmap_get_device(map), "Unknown chip id: 0x%02x, continuing\n", reg); /* The SOFT_RST bit is not auto-cleared and must be written back 0 */ ret = regmap_write(map, QMC6308_REG_CTRL2, QMC6308_SOFT_RST); if (ret) return ret; /* * The datasheet gives no soft-reset completion figure; reuse the * power-on time as a conservative bound. */ fsleep(QMC6308_POR_US); data->range = QMC6308_RNG_30G; data->odr = QMC6308_ODR_50HZ; data->osr = QMC6308_OSR1_8; ret = regmap_write(map, QMC6308_REG_CTRL2, FIELD_PREP(QMC6308_SET_RESET_MASK, QMC6308_SET_RESET_ON) | FIELD_PREP(QMC6308_RNG_MASK, data->range)); if (ret) return ret; /* OSR2 (second-stage filter) set to its power-on default of 0 */ return regmap_write(map, QMC6308_REG_CTRL1, FIELD_PREP(QMC6308_MODE_MASK, QMC6308_MODE_NORMAL) | FIELD_PREP(QMC6308_ODR_MASK, data->odr) | FIELD_PREP(QMC6308_OSR1_MASK, data->osr) | FIELD_PREP(QMC6308_OSR2_MASK, 0)); } static void qmc6308_power_down_action(void *priv) { struct qmc6308_data *data = priv; if (!pm_runtime_status_suspended(regmap_get_device(data->regmap))) qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); } static bool qmc6308_volatile_reg(struct device *dev, unsigned int reg) { return reg >= QMC6308_REG_X_LSB && reg <= QMC6308_REG_STATUS; } static bool qmc6308_writable_reg(struct device *dev, unsigned int reg) { switch (reg) { case QMC6308_REG_CTRL1: case QMC6308_REG_CTRL2: case QMC6308_REG_CTRL3: case QMC6308_REG_CTRL4: return true; default: return false; } } static const struct regmap_config qmc6308_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = QMC6308_REG_CTRL4, .cache_type = REGCACHE_MAPLE, .volatile_reg = qmc6308_volatile_reg, .writeable_reg = qmc6308_writable_reg, }; #define QMC6308_CHANNEL(_axis) \ { \ .type = IIO_MAGN, \ .modified = 1, \ .channel2 = IIO_MOD_##_axis, \ .address = QMC6308_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), \ .ext_info = qmc6308_ext_info, \ } static const struct iio_chan_spec qmc6308_channels[] = { QMC6308_CHANNEL(X), QMC6308_CHANNEL(Y), QMC6308_CHANNEL(Z), }; static int qmc6308_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct qmc6308_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; i2c_set_clientdata(client, indio_dev); map = devm_regmap_init_i2c(client, &qmc6308_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"); fsleep(QMC6308_POR_US); data = iio_priv(indio_dev); data->regmap = map; ret = devm_mutex_init(dev, &data->mutex); if (ret) return ret; ret = iio_read_mount_matrix(dev, &data->orientation); if (ret) return dev_err_probe(dev, ret, "Failed to read mount matrix\n"); indio_dev->name = "qmc6308"; indio_dev->info = &qmc6308_info; indio_dev->channels = qmc6308_channels; indio_dev->num_channels = ARRAY_SIZE(qmc6308_channels); indio_dev->modes = INDIO_DIRECT_MODE; ret = qmc6308_init(data); if (ret) return dev_err_probe(dev, ret, "qmc6308 init failed\n"); ret = pm_runtime_set_active(dev); if (ret) return ret; ret = devm_add_action_or_reset(dev, qmc6308_power_down_action, data); if (ret) return ret; pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, QMC6308_AUTOSUSPEND_DELAY_MS); ret = devm_pm_runtime_enable(dev); if (ret) return ret; return devm_iio_device_register(dev, indio_dev); } static int qmc6308_runtime_suspend(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct qmc6308_data *data = iio_priv(indio_dev); return qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); } static int qmc6308_runtime_resume(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct qmc6308_data *data = iio_priv(indio_dev); unsigned int status; int ret; ret = qmc6308_set_mode(data, QMC6308_MODE_NORMAL); if (ret) return ret; /* * DRDY may still be set for a sample converted before the last * suspend; reading the status register clears it so the next * measurement waits for fresh data. */ ret = regmap_read(data->regmap, QMC6308_REG_STATUS, &status); if (ret) { /* Best effort to leave the chip in a consistent state */ qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); } return ret; } static DEFINE_RUNTIME_DEV_PM_OPS(qmc6308_pm_ops, qmc6308_runtime_suspend, qmc6308_runtime_resume, NULL); static const struct of_device_id qmc6308_match[] = { { .compatible = "qstcorp,qmc6308" }, { } }; MODULE_DEVICE_TABLE(of, qmc6308_match); static const struct i2c_device_id qmc6308_id[] = { { .name = "qmc6308" }, { } }; MODULE_DEVICE_TABLE(i2c, qmc6308_id); static struct i2c_driver qmc6308_driver = { .driver = { .name = "qmc6308", .of_match_table = qmc6308_match, .pm = pm_ptr(&qmc6308_pm_ops), }, .id_table = qmc6308_id, .probe = qmc6308_probe, }; module_i2c_driver(qmc6308_driver); MODULE_DESCRIPTION("QST QMC6308 3-Axis Magnetic Sensor driver"); MODULE_AUTHOR("Jorijn van der Graaf "); MODULE_LICENSE("GPL");