Commit a7a1c60b authored by George Mois's avatar George Mois Committed by Jonathan Cameron
Browse files

drivers: iio: accel: adxl312 and adxl314 support

ADXL312 and ADXL314 are small, thin, low power, 3-axis accelerometers
with high resolution (13-bit) measurement up to +/-12 g and +/- 200 g
respectively.

Implement support for ADXL312 and ADXL314 by extending the ADXL313
driver.

Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL312.pdf
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL314.pdf


Signed-off-by: default avatarGeorge Mois <george.mois@analog.com>
Link: https://lore.kernel.org/r/20220905132018.364900-2-george.mois@analog.com


Signed-off-by: default avatarJonathan Cameron <Jonathan.Cameron@huawei.com>
parent abf90541
Loading
Loading
Loading
Loading
+33 −2
Original line number Diff line number Diff line
@@ -8,6 +8,8 @@
#ifndef _ADXL313_H_
#define _ADXL313_H_

#include <linux/iio/iio.h>

/* ADXL313 register definitions */
#define ADXL313_REG_DEVID0		0x00
#define ADXL313_REG_DEVID1		0x01
@@ -26,6 +28,7 @@
#define ADXL313_REG_FIFO_STATUS		0x39

#define ADXL313_DEVID0			0xAD
#define ADXL313_DEVID0_ADXL312_314	0xE5
#define ADXL313_DEVID1			0x1D
#define ADXL313_PARTID			0xCB
#define ADXL313_SOFT_RESET		0x52
@@ -37,18 +40,46 @@
#define ADXL313_MEASUREMENT_MODE	BIT(3)

#define ADXL313_RANGE_MSK		GENMASK(1, 0)
#define ADXL313_RANGE_4G		3
#define ADXL313_RANGE_MAX		3

#define ADXL313_FULL_RES		BIT(3)
#define ADXL313_SPI_3WIRE		BIT(6)
#define ADXL313_I2C_DISABLE		BIT(6)

extern const struct regmap_access_table adxl312_readable_regs_table;
extern const struct regmap_access_table adxl313_readable_regs_table;
extern const struct regmap_access_table adxl314_readable_regs_table;

extern const struct regmap_access_table adxl312_writable_regs_table;
extern const struct regmap_access_table adxl313_writable_regs_table;
extern const struct regmap_access_table adxl314_writable_regs_table;

enum adxl313_device_type {
	ADXL312,
	ADXL313,
	ADXL314,
};

struct adxl313_data {
	struct regmap	*regmap;
	const struct adxl313_chip_info *chip_info;
	struct mutex	lock; /* lock to protect transf_buf */
	__le16		transf_buf __aligned(IIO_DMA_MINALIGN);
};

struct adxl313_chip_info {
	const char			*name;
	enum adxl313_device_type	type;
	int				scale_factor;
	bool				variable_range;
	bool				soft_reset;
	int (*check_id)(struct device *dev, struct adxl313_data *data);
};

extern const struct adxl313_chip_info adxl31x_chip_info[];

int adxl313_core_probe(struct device *dev,
		       struct regmap *regmap,
		       const char *name,
		       const struct adxl313_chip_info *chip_info,
		       int (*setup)(struct device *, struct regmap *));
#endif /* _ADXL313_H_ */
+147 −55
Original line number Diff line number Diff line
@@ -8,12 +8,18 @@
 */

#include <linux/bitfield.h>
#include <linux/iio/iio.h>
#include <linux/module.h>
#include <linux/regmap.h>

#include "adxl313.h"

static const struct regmap_range adxl312_readable_reg_range[] = {
	regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_DEVID0),
	regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
	regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL),
	regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_FIFO_STATUS),
};

static const struct regmap_range adxl313_readable_reg_range[] = {
	regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_XID),
	regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET),
@@ -22,12 +28,109 @@ static const struct regmap_range adxl313_readable_reg_range[] = {
	regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_FIFO_STATUS),
};

const struct regmap_access_table adxl312_readable_regs_table = {
	.yes_ranges = adxl312_readable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl312_readable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl312_readable_regs_table, IIO_ADXL313);

const struct regmap_access_table adxl313_readable_regs_table = {
	.yes_ranges = adxl313_readable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl313_readable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl313_readable_regs_table, IIO_ADXL313);

const struct regmap_access_table adxl314_readable_regs_table = {
	.yes_ranges = adxl312_readable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl312_readable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl314_readable_regs_table, IIO_ADXL313);

static int adxl312_check_id(struct device *dev,
			    struct adxl313_data *data)
{
	unsigned int regval;
	int ret;

	ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, &regval);
	if (ret)
		return ret;

	if (regval != ADXL313_DEVID0_ADXL312_314)
		dev_warn(dev, "Invalid manufacturer ID: %#02x\n", regval);

	return 0;
}

static int adxl313_check_id(struct device *dev,
			    struct adxl313_data *data)
{
	unsigned int regval;
	int ret;

	ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, &regval);
	if (ret)
		return ret;

	if (regval != ADXL313_DEVID0)
		dev_warn(dev, "Invalid manufacturer ID: 0x%02x\n", regval);

	/* Check DEVID1 and PARTID */
	if (regval == ADXL313_DEVID0) {
		ret = regmap_read(data->regmap, ADXL313_REG_DEVID1, &regval);
		if (ret)
			return ret;

		if (regval != ADXL313_DEVID1)
			dev_warn(dev, "Invalid mems ID: 0x%02x\n", regval);

		ret = regmap_read(data->regmap, ADXL313_REG_PARTID, &regval);
		if (ret)
			return ret;

		if (regval != ADXL313_PARTID)
			dev_warn(dev, "Invalid device ID: 0x%02x\n", regval);
	}

	return 0;
}

const struct adxl313_chip_info adxl31x_chip_info[] = {
	[ADXL312] = {
		.name = "adxl312",
		.type = ADXL312,
		.scale_factor = 28425072,
		.variable_range = true,
		.soft_reset = false,
		.check_id = &adxl312_check_id,
	},
	[ADXL313] = {
		.name = "adxl313",
		.type = ADXL313,
		.scale_factor = 9576806,
		.variable_range = true,
		.soft_reset = true,
		.check_id = &adxl313_check_id,
	},
	[ADXL314] = {
		.name = "adxl314",
		.type = ADXL314,
		.scale_factor = 478858719,
		.variable_range = false,
		.soft_reset = false,
		.check_id = &adxl312_check_id,
	},
};
EXPORT_SYMBOL_NS_GPL(adxl31x_chip_info, IIO_ADXL313);

static const struct regmap_range adxl312_writable_reg_range[] = {
	regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
	regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL),
	regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_INT_MAP),
	regmap_reg_range(ADXL313_REG_DATA_FORMAT, ADXL313_REG_DATA_FORMAT),
	regmap_reg_range(ADXL313_REG_FIFO_CTL, ADXL313_REG_FIFO_CTL),
};

static const struct regmap_range adxl313_writable_reg_range[] = {
	regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET),
	regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
@@ -37,17 +140,23 @@ static const struct regmap_range adxl313_writable_reg_range[] = {
	regmap_reg_range(ADXL313_REG_FIFO_CTL, ADXL313_REG_FIFO_CTL),
};

const struct regmap_access_table adxl312_writable_regs_table = {
	.yes_ranges = adxl312_writable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl312_writable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl312_writable_regs_table, IIO_ADXL313);

const struct regmap_access_table adxl313_writable_regs_table = {
	.yes_ranges = adxl313_writable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl313_writable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl313_writable_regs_table, IIO_ADXL313);

struct adxl313_data {
	struct regmap	*regmap;
	struct mutex	lock; /* lock to protect transf_buf */
	__le16		transf_buf __aligned(IIO_DMA_MINALIGN);
const struct regmap_access_table adxl314_writable_regs_table = {
	.yes_ranges = adxl312_writable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl312_writable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl314_writable_regs_table, IIO_ADXL313);

static const int adxl313_odr_freqs[][2] = {
	[0] = { 6, 250000 },
@@ -156,12 +265,10 @@ static int adxl313_read_raw(struct iio_dev *indio_dev,
		*val = sign_extend32(ret, chan->scan_type.realbits - 1);
		return IIO_VAL_INT;
	case IIO_CHAN_INFO_SCALE:
		/*
		 * Scale for any g range is given in datasheet as
		 * 1024 LSB/g = 0.0009765625 * 9.80665 = 0.009576806640625 m/s^2
		 */
		*val = 0;
		*val2 = 9576806;

		*val2 = data->chip_info->scale_factor;

		return IIO_VAL_INT_PLUS_NANO;
	case IIO_CHAN_INFO_CALIBBIAS:
		ret = regmap_read(data->regmap,
@@ -170,7 +277,7 @@ static int adxl313_read_raw(struct iio_dev *indio_dev,
			return ret;

		/*
		 * 8-bit resolution at +/- 0.5g, that is 4x accel data scale
		 * 8-bit resolution at minimum range, that is 4x accel data scale
		 * factor at full resolution
		 */
		*val = sign_extend32(regval, 7) * 4;
@@ -198,7 +305,7 @@ static int adxl313_write_raw(struct iio_dev *indio_dev,
	switch (mask) {
	case IIO_CHAN_INFO_CALIBBIAS:
		/*
		 * 8-bit resolution at +/- 0.5g, that is 4x accel data scale
		 * 8-bit resolution at minimum range, that is 4x accel data scale
		 * factor at full resolution
		 */
		if (clamp_val(val, -128 * 4, 127 * 4) != val)
@@ -223,14 +330,18 @@ static const struct iio_info adxl313_info = {
static int adxl313_setup(struct device *dev, struct adxl313_data *data,
			 int (*setup)(struct device *, struct regmap *))
{
	unsigned int regval;
	int ret;

	/* Ensures the device is in a consistent state after start up */
	/*
	 * If sw reset available, ensures the device is in a consistent
	 * state after start up
	 */
	if (data->chip_info->soft_reset) {
		ret = regmap_write(data->regmap, ADXL313_REG_SOFT_RESET,
				   ADXL313_SOFT_RESET);
		if (ret)
			return ret;
	}

	if (setup) {
		ret = setup(dev, data->regmap);
@@ -238,37 +349,15 @@ static int adxl313_setup(struct device *dev, struct adxl313_data *data,
			return ret;
	}

	ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, &regval);
	if (ret)
		return ret;

	if (regval != ADXL313_DEVID0) {
		dev_err(dev, "Invalid manufacturer ID: 0x%02x\n", regval);
		return -ENODEV;
	}

	ret = regmap_read(data->regmap, ADXL313_REG_DEVID1, &regval);
	ret = data->chip_info->check_id(dev, data);
	if (ret)
		return ret;

	if (regval != ADXL313_DEVID1) {
		dev_err(dev, "Invalid mems ID: 0x%02x\n", regval);
		return -ENODEV;
	}

	ret = regmap_read(data->regmap, ADXL313_REG_PARTID, &regval);
	if (ret)
		return ret;

	if (regval != ADXL313_PARTID) {
		dev_err(dev, "Invalid device ID: 0x%02x\n", regval);
		return -ENODEV;
	}

	/* Sets the range to +/- 4g */
	/* Sets the range to maximum, full resolution, if applicable */
	if (data->chip_info->variable_range) {
		ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT,
					 ADXL313_RANGE_MSK,
				 FIELD_PREP(ADXL313_RANGE_MSK, ADXL313_RANGE_4G));
					 FIELD_PREP(ADXL313_RANGE_MSK, ADXL313_RANGE_MAX));
		if (ret)
			return ret;

@@ -277,6 +366,7 @@ static int adxl313_setup(struct device *dev, struct adxl313_data *data,
					 ADXL313_FULL_RES, ADXL313_FULL_RES);
		if (ret)
			return ret;
	}

	/* Enables measurement mode */
	return regmap_update_bits(data->regmap, ADXL313_REG_POWER_CTL,
@@ -288,7 +378,7 @@ static int adxl313_setup(struct device *dev, struct adxl313_data *data,
 * adxl313_core_probe() - probe and setup for adxl313 accelerometer
 * @dev:	Driver model representation of the device
 * @regmap:	Register map of the device
 * @name:	Device name buffer reference
 * @chip_info:	Structure containing device specific data
 * @setup:	Setup routine to be executed right before the standard device
 *		setup, can also be set to NULL if not required
 *
@@ -296,7 +386,7 @@ static int adxl313_setup(struct device *dev, struct adxl313_data *data,
 */
int adxl313_core_probe(struct device *dev,
		       struct regmap *regmap,
		       const char *name,
		       const struct adxl313_chip_info *chip_info,
		       int (*setup)(struct device *, struct regmap *))
{
	struct adxl313_data *data;
@@ -309,9 +399,11 @@ int adxl313_core_probe(struct device *dev,

	data = iio_priv(indio_dev);
	data->regmap = regmap;
	data->chip_info = chip_info;

	mutex_init(&data->lock);

	indio_dev->name = name;
	indio_dev->name = chip_info->name;
	indio_dev->info = &adxl313_info;
	indio_dev->modes = INDIO_DIRECT_MODE;
	indio_dev->channels = adxl313_channels;
+52 −22
Original line number Diff line number Diff line
@@ -14,42 +14,72 @@

#include "adxl313.h"

static const struct regmap_config adxl313_i2c_regmap_config = {
static const struct regmap_config adxl31x_i2c_regmap_config[] = {
	[ADXL312] = {
		.reg_bits	= 8,
		.val_bits	= 8,
		.rd_table	= &adxl312_readable_regs_table,
		.wr_table	= &adxl312_writable_regs_table,
		.max_register	= 0x39,
	},
	[ADXL313] = {
		.reg_bits	= 8,
		.val_bits	= 8,
		.rd_table	= &adxl313_readable_regs_table,
		.wr_table	= &adxl313_writable_regs_table,
		.max_register	= 0x39,
	},
	[ADXL314] = {
		.reg_bits	= 8,
		.val_bits	= 8,
		.rd_table	= &adxl314_readable_regs_table,
		.wr_table	= &adxl314_writable_regs_table,
		.max_register	= 0x39,
	},
};

static int adxl313_i2c_probe(struct i2c_client *client)
{
	struct regmap *regmap;

	regmap = devm_regmap_init_i2c(client, &adxl313_i2c_regmap_config);
	if (IS_ERR(regmap)) {
		dev_err(&client->dev, "Error initializing i2c regmap: %ld\n",
			PTR_ERR(regmap));
		return PTR_ERR(regmap);
	}

	return adxl313_core_probe(&client->dev, regmap, client->name, NULL);
}

static const struct i2c_device_id adxl313_i2c_id[] = {
	{ "adxl313" },
	{ .name = "adxl312", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL312] },
	{ .name = "adxl313", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL312] },
	{ .name = "adxl314", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL312] },
	{ }
};

MODULE_DEVICE_TABLE(i2c, adxl313_i2c_id);

static const struct of_device_id adxl313_of_match[] = {
	{ .compatible = "adi,adxl313" },
	{ .compatible = "adi,adxl312", .data = &adxl31x_chip_info[ADXL312] },
	{ .compatible = "adi,adxl313", .data = &adxl31x_chip_info[ADXL313] },
	{ .compatible = "adi,adxl314", .data = &adxl31x_chip_info[ADXL314] },
	{ }
};

MODULE_DEVICE_TABLE(of, adxl313_of_match);

static int adxl313_i2c_probe(struct i2c_client *client)
{
	const struct adxl313_chip_info *chip_data;
	struct regmap *regmap;

	/*
	 * Retrieves device specific data as a pointer to a
	 * adxl313_chip_info structure
	 */
	chip_data = device_get_match_data(&client->dev);
	if (!chip_data)
		chip_data = (const struct adxl313_chip_info *)i2c_match_id(adxl313_i2c_id, client)->driver_data;

	regmap = devm_regmap_init_i2c(client,
				      &adxl31x_i2c_regmap_config[chip_data->type]);
	if (IS_ERR(regmap)) {
		dev_err(&client->dev, "Error initializing i2c regmap: %ld\n",
			PTR_ERR(regmap));
		return PTR_ERR(regmap);
	}

	return adxl313_core_probe(&client->dev, regmap, chip_data, NULL);
}

static struct i2c_driver adxl313_i2c_driver = {
	.driver = {
		.name	= "adxl313_i2c",
+49 −14
Original line number Diff line number Diff line
@@ -11,10 +11,21 @@
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <linux/property.h>

#include "adxl313.h"

static const struct regmap_config adxl313_spi_regmap_config = {
static const struct regmap_config adxl31x_spi_regmap_config[] = {
	[ADXL312] = {
		.reg_bits	= 8,
		.val_bits	= 8,
		.rd_table	= &adxl312_readable_regs_table,
		.wr_table	= &adxl312_writable_regs_table,
		.max_register	= 0x39,
		/* Setting bits 7 and 6 enables multiple-byte read */
		.read_flag_mask	= BIT(7) | BIT(6),
	},
	[ADXL313] = {
		.reg_bits	= 8,
		.val_bits	= 8,
		.rd_table	= &adxl313_readable_regs_table,
@@ -22,6 +33,16 @@ static const struct regmap_config adxl313_spi_regmap_config = {
		.max_register	= 0x39,
		/* Setting bits 7 and 6 enables multiple-byte read */
		.read_flag_mask	= BIT(7) | BIT(6),
	},
	[ADXL314] = {
		.reg_bits	= 8,
		.val_bits	= 8,
		.rd_table	= &adxl314_readable_regs_table,
		.wr_table	= &adxl314_writable_regs_table,
		.max_register	= 0x39,
		/* Setting bits 7 and 6 enables multiple-byte read */
		.read_flag_mask	= BIT(7) | BIT(6),
	},
};

static int adxl313_spi_setup(struct device *dev, struct regmap *regmap)
@@ -42,7 +63,7 @@ static int adxl313_spi_setup(struct device *dev, struct regmap *regmap)

static int adxl313_spi_probe(struct spi_device *spi)
{
	const struct spi_device_id *id = spi_get_device_id(spi);
	const struct adxl313_chip_info *chip_data;
	struct regmap *regmap;
	int ret;

@@ -51,26 +72,40 @@ static int adxl313_spi_probe(struct spi_device *spi)
	if (ret)
		return ret;

	regmap = devm_regmap_init_spi(spi, &adxl313_spi_regmap_config);
	/*
	 * Retrieves device specific data as a pointer to a
	 * adxl313_chip_info structure
	 */
	chip_data = device_get_match_data(&spi->dev);
	if (!chip_data)
		chip_data = (const struct adxl313_chip_info *)spi_get_device_id(spi)->driver_data;

	regmap = devm_regmap_init_spi(spi,
				      &adxl31x_spi_regmap_config[chip_data->type]);

	if (IS_ERR(regmap)) {
		dev_err(&spi->dev, "Error initializing spi regmap: %ld\n",
			PTR_ERR(regmap));
		return PTR_ERR(regmap);
	}

	return adxl313_core_probe(&spi->dev, regmap, id->name,
				  &adxl313_spi_setup);
	return adxl313_core_probe(&spi->dev, regmap,
				  chip_data, &adxl313_spi_setup);
}

static const struct spi_device_id adxl313_spi_id[] = {
	{ "adxl313" },
	{ .name = "adxl312", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL312] },
	{ .name = "adxl313", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL313] },
	{ .name = "adxl314", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL314] },
	{ }
};

MODULE_DEVICE_TABLE(spi, adxl313_spi_id);

static const struct of_device_id adxl313_of_match[] = {
	{ .compatible = "adi,adxl313" },
	{ .compatible = "adi,adxl312", .data = &adxl31x_chip_info[ADXL312] },
	{ .compatible = "adi,adxl313", .data = &adxl31x_chip_info[ADXL313] },
	{ .compatible = "adi,adxl314", .data = &adxl31x_chip_info[ADXL314] },
	{ }
};