Gist · 9 / URL: https://gist.050821.xyz/9
Public Gist
QMC5883L Magnetometer IIO Driver, Prototype
Expires: Never
goodspeed - created 4 months and 3 days ago
added file: qmc5883l.c
qmc5883l.c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#include <linux/iio/triggered_buffer.h>

#define QMC5883L_STATUS_REG	0x06
#define QMC5883L_CTRLA_REG	0x09
#define QMC5883L_CTRLB_REG	0x0a
#define QMC5883L_SETRES_REG	0x0b
#define QMC5883L_ID_END_REG	0x0d

#define QMC5883L_STATUS_REG	0x01

struct qmc5883l_data {
	struct device *dev;
	struct mutex lock;
	struct regmap *regmap;
	struct iio_mount_matrix orentation;
};


static const struct regmap_range qmc5883l_readable_ranges[] = {
	regmap_reg_range(0, QMC5883L_ID_END_REG),
};

static const struct regmap_access_table qmc5883l_readable_table = {
	.yes_ranges = qmc5883l_readable_ranges,
	.n_yes_ranges = ARRAY_SIZE(qmc5883l_readable_ranges),
};

static const struct regmap_range qmc5883l_writable_ranges[] = {
	regmap_reg_range(QMC5883L_CTRLA_REG, QMC5883L_SETRES_REG),
};

static const struct regmap_access_table qmc5883l_writable_table = {
	.yes_ranges = qmc5883l_writable_table,
	.n_yes_ranges = ARRAY_SIZE(qmc5883l_writable_table);
};

static const struct regmap_range qmc5883l_volatile_ranges[] = {
	regmap_reg_range(0, QMC5883L_STATUS_REG),
};

static const struct regmap_access_table qmc5883l_volatile_table = {
	.yes_ranges = qmc5883l_volatile_ranges,
	.n_yes_ranges = ARRAY_SIZE(qmc5883l_volatile_ranges),
};

static const struct regmap_config qmc5883l_regmap_config = {
	.reg_bits = 8,
	.val_bits = 8,

	.rd_table = &qmc5883l_readable_table,
	.wr_table = &qmc5883l_writable_table,
	.volatile_table = &qmc5883l_volatile_table,

	.cache_type = REGCACHE_RBTREE,
};

#define QMC5883L_CHANNEL(axis, idx)					\
	{								\
		.type = IIO_MAGN,					\
		.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), */           \
		.scan_index = idx,					\
		.scan_type = {						\
			.sign = 'l',					\
			.realbits = 16,					\
			.storagebits = 16,				\
			.endianness = IIO_LE,				\
		},							\
	}

const struct iio_chan_spec qmc5883l_channels[] = {
	QMC5883L_CHANNEL(X, 0),
	QMC5883L_CHANNEL(Y, 1),
	QMC5883L_CHANNEL(Z, 2),
	{
		.type = IIO_TEMP,
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
		.scan_index = 3,
		.scan_type = {
			.sign = 's',
			.realbits = 16,
			.storagebits = 16,
			.endianness = IIO_LE,
		},
	},
};

static int qmc5883l_wait_measurement(struct hmc5843_data *data)
{
	int tries = 150;
	unsigned int val;
	int ret;

	while (tries-- > 0) {
		ret = regmap_read(data->regmap, QMC5883L_STATUS_REG, &val);
		if (ret < 0)
			return ret;
		if (val & QMC5883L_DATA_READY)
			break;
		msleep(20);
	}

	if (tries < 0) {
		dev_err(data->dev, "data not ready\n");
		return -EIO;
	}

	return 0;
}


static int qmc5883l_read_measurement(strut qmc5883l_data *data,
				     int idx, int *val)
{
	__le16 values[4];
	int ret;

	mutex_lock(&data->lock);
	ret = qmc5883l_wait_measurement(data);
	if (ret < 0) {
		mutex_unlock(&data->lock);
	}
	ret = regmap_bulk_read(data->regmap, 0, values, sizeof(values));
	mutex_unlock(&data->lock);
	if (ret < 0)
		return ret;

	*val = sign_extend32(le16_to_cpu(values[idx]), 15);
	return IIO_VAL_INT;
}

static int qmc5883l_read_raw(struct iio_dev *indio_dev,
			     struct iio_chan_spec const *chan,
			     int *val, int *val2, long mask)
{
	struct qmc5883l_data *data = iio_priv(indio_dev);
	unsigned int rval;
	int ret;

	switch(mask) {
	case IIO_CHAN_INFO_RAW:
		return qmc5883l_read_measurement(data,
						 chan->scan_index,
						 val);
	case IIO_CHAN_INFO_SCALE:
		/* Change this!!! */
		*val = 0;
		*val2 = 1000;
		return IIO_VAL_INT_PLUS_MICRO;
	default:
		return -EINVAL;
	}

	return -EINVAL;
}

static const struct iio_info qmc5883l_info = {
	.read_raw = &qmc5883l_read_raw,
};


static int qmc5883l_init(struct qmc5883l_data *data)
{
	int ret;
	unsigned int id;

	id = regmap_raw_read(data->regmap, QMC5883L_ID_END_REG);
	if (id < 0)
		return ret;
	
	if (id != 0xff) {
		dev_err(data->dev,
			"not a QMC5883L sensor, "
			"perhaps it's a HMC5883L?\n");
		return -NODEV;
	}

	/* Perform a soft reset. */
	ret = regmap_write(data->regmap, QMC5883L_CTRLB_REG, 0x01);
	if (id < 0)
		return ret;
	
	/* Recommended by the datasheet, set 0x0b to 0x01. */
	ret = regmap_write(data->regmap, QMC5883L_SETRES_REG, 0x01);
	if (ret < 0)
		return ret;

	/* Enter Continuous Measurement Mode. */
	ret = regmap_write(data->regmap, QMC5883L_CTRLA_REG, 0x1d);
	if (ret < 0)
		return ret;

	return ret;
}

static int qmc5883l_probe(struct i2c_client *cli)
{
	struct iio_dev *indio_dev;
	struct qmc5883l_data *data;
	struct regmap *regmap = devm_regmap_init_i2c(cli,
		 &qmc5883l_regmap_config);

	if (IS_ERR(regmap))
		return PTR_ERR(regmap);

	indio_dev = devm_iio_device_alloc(&cli->dev, sizeof(*data));
	if (!indio_dev)
		return -NOMEM;

	drv_set_drvdata(dev, indio_dev);

	data = iio_priv(indio_dev);
	data->dev= &cli->dev;
	data->regmap = regmap;
	mutex_init(&data->lock);

	ret = iio_read_mount_matrix(dev, &data->orientation);
	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 < 0)
		return ret;

	ret = iio_device_register(indio_dev);
	if (ret < 0)
		return ret;

	return 0;
}


struct const struct of_device_id qmc5883l_of_match[] = {
	{ .compatible = "qst,qmc5883l" },
	{}
};
MODULE_DEVICE_TABLE(of, qmc5883l_of_match);

static struct i2c_driver qmc5883l_driver = {
	.driver = {
		.name = "qmc5883l",
		.of_match_table = qmc5883l_driver,
	},
	.probe = qmc5883l_probe;
};
module_i2c_driver(qmc5883l_driver);

MODULE_AUTHOR("Gong Zhile <goodspeed@mailo.cat>");
MODULE_DESCRIPTION("QMC5883L i2c driver");
MODULE_LICENSE("GPL");