[PATCH 2/2] iio: magnetometer: add ti tmag5273 driver

Gerald Loacker
2022-11-15T07:37:51Z

Add support for TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor.
Additionally to temperature and magnetic X, Y and Z-axes the angle and
magnitude are reported.
The sensor is operating in continuous measurement mode and changes to sleep
mode if not used for 5 seconds.

Datasheet: https://www.ti.com/lit/gpn/tmag5273

Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
---
MAINTAINERS | 1 +
drivers/iio/magnetometer/Kconfig | 12 +
drivers/iio/magnetometer/Makefile | 2 +
drivers/iio/magnetometer/tmag5273.c | 809 ++++++++++++++++++++++++++++
4 files changed, 824 insertions(+)
create mode 100644 drivers/iio/magnetometer/tmag5273.c

diff --git a/MAINTAINERS b/MAINTAINERS
index ea7acec52f8b..9d20b5780051 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20618,6 +20618,7 @@ M: Gerald Loacker <gerald.loacker@wolfvision.net>
L: linux-iio@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/iio/magnetometer/ti,tmag5273.yaml
+F: drivers/iio/magnetometer/tmag5273.c

TI TRF7970A NFC DRIVER
M: Mark Greer <mgreer@animalcreek.com>
diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig
index b91fc5e6a26e..467819335588 100644
--- a/drivers/iio/magnetometer/Kconfig
+++ b/drivers/iio/magnetometer/Kconfig
@@ -208,6 +208,18 @@ config SENSORS_RM3100_SPI
To compile this driver as a module, choose M here: the module
will be called rm3100-spi.

+config TI_TMAG5273
+ tristate "TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y here to add support for the TI TMAG5273 Low-Power
+ Linear 3D Hall-Effect Sensor.
+
+ This driver can also be compiled as a module.
+ To compile this driver as a module, choose M here: the module
+ will be called tmag5273.
+
config YAMAHA_YAS530
tristate "Yamaha YAS530 family of 3-Axis Magnetometers (I2C)"
depends on I2C
diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile
index b9f45b7fafc3..b1c784ea71c8 100644
--- a/drivers/iio/magnetometer/Makefile
+++ b/drivers/iio/magnetometer/Makefile
@@ -29,4 +29,6 @@ obj-$(CONFIG_SENSORS_RM3100) += rm3100-core.o
obj-$(CONFIG_SENSORS_RM3100_I2C) += rm3100-i2c.o
obj-$(CONFIG_SENSORS_RM3100_SPI) += rm3100-spi.o

+obj-$(CONFIG_TI_TMAG5273) += tmag5273.o
+
obj-$(CONFIG_YAMAHA_YAS530) += yamaha-yas530.o
diff --git a/drivers/iio/magnetometer/tmag5273.c b/drivers/iio/magnetometer/tmag5273.c
new file mode 100644
index 000000000000..549ed1ddba61
--- /dev/null
+++ b/drivers/iio/magnetometer/tmag5273.c
@@ -0,0 +1,809 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor
+ *
+ * Copyright (C) 2022 WolfVision GmbH
+ *
+ * Author: Gerald Loacker <gerald.loacker@wolfvision.net>
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/pm_runtime.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#include <asm/unaligned.h>
+
+#define TMAG5273_DEVICE_CONFIG_1 0x00
+#define TMAG5273_DEVICE_CONFIG_2 0x01
+#define TMAG5273_SENSOR_CONFIG_1 0x02
+#define TMAG5273_SENSOR_CONFIG_2 0x03
+#define TMAG5273_X_THR_CONFIG 0x04
+#define TMAG5273_Y_THR_CONFIG 0x05
+#define TMAG5273_Z_THR_CONFIG 0x06
+#define TMAG5273_T_CONFIG 0x07
+#define TMAG5273_INT_CONFIG_1 0x08
+#define TMAG5273_MAG_GAIN_CONFIG 0x09
+#define TMAG5273_MAG_OFFSET_CONFIG_1 0x0A
+#define TMAG5273_MAG_OFFSET_CONFIG_2 0x0B
+#define TMAG5273_I2C_ADDRESS 0x0C
+#define TMAG5273_DEVICE_ID 0x0D
+#define TMAG5273_MANUFACTURER_ID_LSB 0x0E
+#define TMAG5273_MANUFACTURER_ID_MSB 0x0F
+#define TMAG5273_T_MSB_RESULT 0x10
+#define TMAG5273_T_LSB_RESULT 0x11
+#define TMAG5273_X_MSB_RESULT 0x12
+#define TMAG5273_X_LSB_RESULT 0x13
+#define TMAG5273_Y_MSB_RESULT 0x14
+#define TMAG5273_Y_LSB_RESULT 0x15
+#define TMAG5273_Z_MSB_RESULT 0x16
+#define TMAG5273_Z_LSB_RESULT 0x17
+#define TMAG5273_CONV_STATUS 0x18
+#define TMAG5273_ANGLE_RESULT_MSB 0x19
+#define TMAG5273_ANGLE_RESULT_LSB 0x1A
+#define TMAG5273_MAGNITUDE_RESULT 0x1B
+#define TMAG5273_DEVICE_STATUS 0x1C
+
+#define TMAG5273_MANUFACTURER_ID 0x5449
+
+#define TMAG5273_AUTOSLEEP_DELAY 5000
+
+/* Bits in the TMAG5273_DEVICE_CONFIG_1 register */
+#define TMAG5273_AVG_MODE_MASK GENMASK(4, 2)
+#define TMAG5273_AVG_1_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 0)
+#define TMAG5273_AVG_2_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 1)
+#define TMAG5273_AVG_4_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 2)
+#define TMAG5273_AVG_8_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 3)
+#define TMAG5273_AVG_16_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 4)
+#define TMAG5273_AVG_32_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 5)
+
+/* bits in the TMAG5273_DEVICE_CONFIG_2 register */
+#define TMAG5273_OP_MODE_MASK GENMASK(1, 0)
+#define TMAG5273_OP_MODE_STANDBY FIELD_PREP(TMAG5273_OP_MODE_MASK, 0)
+#define TMAG5273_OP_MODE_SLEEP FIELD_PREP(TMAG5273_OP_MODE_MASK, 1)
+#define TMAG5273_OP_MODE_CONT FIELD_PREP(TMAG5273_OP_MODE_MASK, 2)
+#define TMAG5273_OP_MODE_WAKEUP FIELD_PREP(TMAG5273_OP_MODE_MASK, 3)
+
+/* bits in the TMAG5273_SENSOR_CONFIG_1 register */
+#define TMAG5273_MAG_CH_EN_MASK GENMASK(7, 4)
+#define TMAG5273_MAG_CH_EN_X_Y_Z 0x07
+
+/* bits in the TMAG5273_SENSOR_CONFIG_2 register */
+#define TMAG5273_Z_RANGE_MASK BIT(0)
+#define TMAG5273_X_Y_RANGE_MASK BIT(1)
+#define TMAG5273_ANGLE_EN_MASK GENMASK(3, 2)
+#define TMAG5273_ANGLE_EN_X_Y 1
+#define TMAG5273_ANGLE_EN_Y_Z 2
+#define TMAG5273_ANGLE_EN_X_Z 3
+
+/* bits in the TMAG5273_T_CONFIG register */
+#define TMAG5273_T_CH_EN BIT(0)
+
+/* bits in the TMAG5273_DEVICE_ID register */
+#define TMAG5273_VERSION_MASK GENMASK(1, 0)
+
+/* bits in the TMAG5273_CONV_STATUS register */
+#define TMAG5273_CONV_STATUS_COMPLETE BIT(0)
+
+/* state container for the TMAG5273 driver */
+struct tmag5273_data {
+ struct device *dev;
+ unsigned int devid;
+ unsigned int version;
+ char name[16];
+ int conv_avg;
+ int max_avg;
+ int range;
+ u32 angle_en;
+ struct regmap *map;
+ struct regulator *vcc;
+ /* Locks the sensor for exclusive use during a measurement (which
+ * involves several register transactions so the regmap lock is not
+ * enough) so that measurements get serialized in a first-come-first-
+ * serve manner.
+ */
+ struct mutex lock;
+};
+
+static const struct {
+ int avg;
+ u8 reg_val;
+} tmag5273_avg_table[] = {
+ { 1, 0x00 }, { 2, 0x01 }, { 4, 0x02 },
+ { 8, 0x03 }, { 16, 0x04 }, { 32, 0x05 },
+};
+
+/*
+ * magnetic range in mT for different TMAG5273 versions
+ * only version 1 and 2 are valid, version 0 and 3 are reserved
+ */
+static const struct {
+ int range;
+ u8 reg_val;
+} tmag5273_range_table[4][2] = {
+ { { 0, 0 }, { 0, 3 } },
+ { { 40, 0 }, { 80, 3 } },
+ { { 133, 0 }, { 266, 3 } },
+ { { 0, 0 }, { 0, 3 } },
+};
+
+/*
+ * tmag5273_measure() - Make a measure from the hardware
+ * @tmag5273: The device state
+ * @t: the processed temperature measurement
+ * @x: the raw x axis measurement
+ * @y: the raw x axis measurement
+ * @z: the raw x axis measurement
+ * @angle: the calculated angle
+ * @magnitude: the calculated magnitude
+ * @return: 0 on success or error code
+ */
+static int tmag5273_measure(struct tmag5273_data *data, u16 *t, u16 *x, u16 *y,
+ u16 *z, u16 *angle, u16 *magnitude)
+{
+ unsigned int status;
+ u8 reg_data[12];
+ int ret;
+ u16 val;
+
+ mutex_lock(&data->lock);
+ ret = regmap_read(data->map, TMAG5273_CONV_STATUS, &status);
+ if (ret < 0)
+ goto out_unlock;
+
+ /*
+ * Conversion time is 2425 us in 32x averaging mode for all three
+ * channels. Since we are in continuous measurement mode, a measurement
+ * may already be there, so poll for completed measurement with
+ * timeout.
+ */
+ ret = regmap_read_poll_timeout(data->map, TMAG5273_CONV_STATUS, status,
+ status & TMAG5273_CONV_STATUS_COMPLETE,
+ 100, 10000);
+ if (ret) {
+ dev_err_probe(data->dev, ret, "timeout waiting for measurement\n");
+ goto out_unlock;
+ }
+
+ ret = regmap_bulk_read(data->map, TMAG5273_T_MSB_RESULT, reg_data,
+ sizeof(reg_data));
+ if (ret)
+ goto out_unlock;
+
+ ret = regmap_read(data->map, TMAG5273_CONV_STATUS, &status);
+ if (ret < 0)
+ goto out_unlock;
+
+ mutex_unlock(&data->lock);
+
+ *t = get_unaligned_be16(&reg_data[0]);
+ *x = get_unaligned_be16(&reg_data[2]);
+ *y = get_unaligned_be16(&reg_data[4]);
+ *z = get_unaligned_be16(&reg_data[6]);
+ /*
+ * angle has 9 bits integer value and 4 bits fractional part
+ * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+ * 0 0 0 a a a a a a a a a f f f f
+ */
+ val = get_unaligned_be16(&reg_data[9]);
+ *angle = FIELD_GET(GENMASK(12, 0), val);
+ *magnitude = reg_data[11];
+ return ret;
+
+out_unlock:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+/*
+ * tmag5273_get_measure() - Measure a sample of all axis and process
+ * @tmag5273: The device state
+ * @to: Temperature out
+ * @xo: X axis out
+ * @yo: Y axis out
+ * @zo: Z axis out
+ * @ao: Angle out
+ * @mo: Magnitude out
+ * @return: 0 on success or error code
+ */
+static int tmag5273_get_measure(struct tmag5273_data *data, s32 *to, s32 *xo,
+ s32 *yo, s32 *zo, u16 *ao, u16 *mo)
+{
+ u16 t, x, y, z, angle, magnitude;
+ int ret;
+
+ ret = tmag5273_measure(data, &t, &x, &y, &z, &angle, &magnitude);
+ if (ret)
+ return ret;
+
+ /*
+ * convert device specific value to millicelsius
+ * use multiply by 16639 and divide by 10000 to achieve divide by 60.1
+ * and convert to millicelsius
+ */
+ *to = (((s32)t - 17508) * 16639) / 1000 + 25000;
+ *xo = (s16)x;
+ *yo = (s16)y;
+ *zo = (s16)z;
+ *ao = angle;
+ *mo = magnitude;
+ return 0;
+}
+
+static int tmag5273_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, int *val,
+ int *val2, long mask)
+{
+ struct tmag5273_data *data = iio_priv(indio_dev);
+ s32 t, x, y, z;
+ u16 angle, magnitude;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ case IIO_CHAN_INFO_RAW:
+ ret = pm_runtime_resume_and_get(data->dev);
+ if (ret < 0)
+ return ret;
+ ret = tmag5273_get_measure(data, &t, &x, &y, &z, &angle,
+ &magnitude);
+ if (ret)
+ return ret;
+ pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+ switch (chan->scan_index) {
+ case 0:
+ *val = t;
+ break;
+ case 1:
+ *val = x;
+ break;
+ case 2:
+ *val = y;
+ break;
+ case 3:
+ *val = z;
+ break;
+ case 4:
+ *val = angle;
+ break;
+ case 5:
+ *val = magnitude;
+ break;
+ default:
+ return dev_err_probe(data->dev, -EINVAL, "unknown channel\n");
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_TEMP:
+ /* 1 LSB = 1 millidegree Celsius */
+ *val = 1;
+ return IIO_VAL_INT;
+ case IIO_MAGN:
+ /*
+ * The axis values are in stored in 2^15 / range LSB/mT.
+ * Since 1 mT = 10 Gauss, we need to multiply by 10 and
+ * divide by [range] to get Gauss from the raw value.
+ */
+ *val = data->range * 10;
+ *val2 = 32768;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_ANGL:
+ /*
+ * Angle is in degrees and has four fractional bits,
+ * therefore use 1/16 * pi/180 to convert to radiants.
+ */
+ *val = 1000;
+ *val2 = 916732;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_DISTANCE:
+ /* Magnitude is unscaled */
+ *val = 1;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ /* Unknown request */
+ return -EINVAL;
+ }
+}
+
+static ssize_t tmag5273_show_conv_avg(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct tmag5273_data *data = iio_priv(indio_dev);
+
+ return sprintf(buf, "%d\n", data->conv_avg);
+}
+
+static ssize_t tmag5273_write_conv_avg(struct tmag5273_data *data,
+ unsigned int val)
+{
+ int ret;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tmag5273_avg_table); i++) {
+ if (tmag5273_avg_table[i].avg == val)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(tmag5273_avg_table))
+ return -EINVAL;
+
+ ret = regmap_update_bits(data->map, TMAG5273_DEVICE_CONFIG_1,
+ TMAG5273_AVG_MODE_MASK,
+ tmag5273_avg_table[i].reg_val);
+ if (ret)
+ return ret;
+
+ data->conv_avg = val;
+
+ return 0;
+}
+
+static ssize_t tmag5273_store_conv_avg(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct tmag5273_data *data = iio_priv(indio_dev);
+ int ret;
+ int integer;
+
+ ret = kstrtoint(buf, 0, &integer);
+ if (ret)
+ return ret;
+
+ ret = tmag5273_write_conv_avg(data, integer);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(conv_avg, 0644, tmag5273_show_conv_avg,
+ tmag5273_store_conv_avg, 0);
+
+static ssize_t tmag5273_conv_avg_available(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct tmag5273_data *data = iio_priv(indio_dev);
+ ssize_t len = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tmag5273_avg_table); i++) {
+ if (tmag5273_avg_table[i].avg > data->max_avg)
+ break;
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
+ tmag5273_avg_table[i].avg);
+ }
+ /* replace last space with a newline */
+ if (len > 0)
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(conv_avg_available, 0444, tmag5273_conv_avg_available,
+ NULL, 0);
+
+static ssize_t tmag5273_show_range(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct tmag5273_data *data = iio_priv(indio_dev);
+
+ return sprintf(buf, "%d\n", data->range);
+}
+
+static ssize_t tmag5273_write_range(struct tmag5273_data *data,
+ unsigned int val)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(tmag5273_range_table[0]); i++) {
+ if (tmag5273_range_table[data->version][i].range == val)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(tmag5273_range_table[0]))
+ return -EINVAL;
+
+ ret = regmap_update_bits(data->map,
+ TMAG5273_SENSOR_CONFIG_2,
+ TMAG5273_Z_RANGE_MASK | TMAG5273_X_Y_RANGE_MASK,
+ tmag5273_range_table[data->version][i].reg_val);
+ if (ret)
+ return ret;
+
+ data->range = val;
+
+ return 0;
+}
+
+static ssize_t tmag5273_store_range(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct tmag5273_data *data = iio_priv(indio_dev);
+ int range, ret;
+
+ ret = kstrtoint(buf, 0, &range);
+ if (ret)
+ return ret;
+
+ ret = tmag5273_write_range(data, range);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(range, 0644, tmag5273_show_range,
+ tmag5273_store_range, 0);
+
+static ssize_t tmag5273_range_available(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct tmag5273_data *data = iio_priv(indio_dev);
+ ssize_t len = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tmag5273_range_table[0]); i++) {
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
+ tmag5273_range_table[data->version][i].range);
+ }
+ /* replace last space with a newline */
+ if (len > 0)
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(range_available, 0444, tmag5273_range_available, NULL,
+ 0);
+
+static struct attribute *tmag5273_attributes[] = {
+ &iio_dev_attr_conv_avg.dev_attr.attr,
+ &iio_dev_attr_conv_avg_available.dev_attr.attr,
+ &iio_dev_attr_range.dev_attr.attr,
+ &iio_dev_attr_range_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group tmag5273_attrs_group = {
+ .attrs = tmag5273_attributes,
+};
+
+#define TMAG5273_AXIS_CHANNEL(axis, index) \
+ { \
+ .type = IIO_MAGN, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ .endianness = IIO_CPU, \
+ }, \
+ }
+
+static const struct iio_chan_spec tmag5273_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 32,
+ .storagebits = 32,
+ .endianness = IIO_CPU,
+ },
+ },
+ TMAG5273_AXIS_CHANNEL(X, 1),
+ TMAG5273_AXIS_CHANNEL(Y, 2),
+ TMAG5273_AXIS_CHANNEL(Z, 3),
+ {
+ .type = IIO_ANGL,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = 4,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_CPU,
+ },
+ },
+ {
+ .type = IIO_DISTANCE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .scan_index = 5,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_CPU,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(6),
+};
+
+static const struct iio_info tmag5273_info = {
+ .attrs = &tmag5273_attrs_group,
+ .read_raw = &tmag5273_read_raw,
+};
+
+static bool tmag5273_volatile_reg(struct device *dev, unsigned int reg)
+{
+ return (reg >= TMAG5273_T_MSB_RESULT &&
+ reg <= TMAG5273_MAGNITUDE_RESULT);
+}
+
+static const struct regmap_config tmag5273_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .volatile_reg = tmag5273_volatile_reg,
+};
+
+static int tmag5273_set_operating_mode(struct tmag5273_data *data,
+ unsigned int val)
+{
+ return regmap_write(data->map, TMAG5273_DEVICE_CONFIG_2, val);
+}
+
+static int tmag5273_chip_init(struct tmag5273_data *data)
+{
+ int ret;
+
+ ret = regmap_write(data->map, TMAG5273_DEVICE_CONFIG_1,
+ TMAG5273_AVG_32_MODE);
+ if (ret)
+ return ret;
+ data->conv_avg = 32;
+ ret = regmap_write(data->map, TMAG5273_DEVICE_CONFIG_2,
+ TMAG5273_OP_MODE_CONT);
+ if (ret)
+ return ret;
+ ret = regmap_write(data->map, TMAG5273_SENSOR_CONFIG_1,
+ FIELD_PREP(TMAG5273_MAG_CH_EN_MASK,
+ TMAG5273_MAG_CH_EN_X_Y_Z));
+ if (ret)
+ return ret;
+ ret = regmap_write(data->map, TMAG5273_SENSOR_CONFIG_2,
+ FIELD_PREP(TMAG5273_ANGLE_EN_MASK, data->angle_en));
+ if (ret)
+ return ret;
+
+ data->range = tmag5273_range_table[data->version][0].range;
+ return regmap_write(data->map, TMAG5273_T_CONFIG, TMAG5273_T_CH_EN);
+}
+
+static int tmag5273_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct iio_dev *indio_dev;
+ struct device *dev = &i2c->dev;
+ struct device_node *node = dev->of_node;
+ struct tmag5273_data *data;
+ int val, ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(i2c, indio_dev);
+ data->dev = dev;
+ mutex_init(&data->lock);
+
+ data->vcc = devm_regulator_get(dev, "vcc");
+ if (IS_ERR(data->vcc))
+ return dev_err_probe(dev, PTR_ERR(data->vcc),
+ "failed to get VCC regulator\n");
+
+ /* Operating voltage 1.7V .. 3.6V according to datasheet */
+ ret = regulator_set_voltage(data->vcc, 1700000, 3600000);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to set VCC voltage\n");
+
+ ret = regulator_enable(data->vcc);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to enable VCC regulator\n");
+
+ /*
+ * Regulators have to ramp up with 3V/ms, additional time to go to
+ * stand-by mode is 270us typically. We give 1 ms to 2 ms time.
+ */
+ usleep_range(1000, 2000);
+
+ data->map = devm_regmap_init_i2c(i2c, &tmag5273_regmap_config);
+ if (IS_ERR(data->map)) {
+ ret = PTR_ERR(data->map);
+ dev_err_probe(dev, ret, "failed to allocate register map\n");
+ goto out_disable_vcc;
+ }
+
+ ret = regmap_read(data->map, TMAG5273_DEVICE_ID, &val);
+ if (ret) {
+ /*
+ * If we come from sleep with power already activated, the
+ * first I2C command wakes up the chip but will fail.
+ * Time to go to stand-by mode from sleep mode is 50us
+ * typically. During this time no I2C access is possible.
+ */
+ usleep_range(80, 200);
+
+ ret = regmap_read(data->map, TMAG5273_DEVICE_ID, &val);
+ if (ret)
+ goto out_disable_vcc;
+ }
+ data->version = FIELD_PREP(TMAG5273_VERSION_MASK, val);
+
+ ret = regmap_bulk_read(data->map, TMAG5273_MANUFACTURER_ID_LSB,
+ &data->devid, 2);
+ if (ret)
+ goto out_disable_vcc;
+
+ switch (data->devid) {
+ case TMAG5273_MANUFACTURER_ID:
+ strncpy(data->name, "TMAG5273", sizeof(data->name) - 2);
+ switch (data->version) {
+ case 1:
+ strncat(data->name, "x1", 2);
+ break;
+ case 2:
+ strncat(data->name, "x2", 2);
+ break;
+ default:
+ break;
+ }
+ dev_info(dev, "%s", data->name);
+ data->max_avg = 32;
+ break;
+ default:
+ ret = -ENODEV;
+ dev_err_probe(dev, ret, "unhandled device ID 0x%x\n", data->devid);
+ goto out_disable_vcc;
+ }
+
+ /*
+ * Angle-enable is optional and set to 1 (enable X-Y plane) by default,
+ * the value is modified only if a valid u32 value can be decoded.
+ */
+ data->angle_en = TMAG5273_ANGLE_EN_X_Y;
+ of_property_read_u32(node, "tmag5273,angle-enable", &data->angle_en);
+
+ ret = tmag5273_chip_init(data);
+ if (ret)
+ goto out_disable_vcc;
+
+ indio_dev->info = &tmag5273_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->name = data->name;
+ indio_dev->channels = tmag5273_channels;
+ indio_dev->num_channels = ARRAY_SIZE(tmag5273_channels);
+
+ pm_runtime_set_autosuspend_delay(dev, TMAG5273_AUTOSLEEP_DELAY);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_mark_last_busy(dev);
+ ret = pm_runtime_set_active(dev);
+ if (ret < 0)
+ goto out_disable_vcc;
+ pm_runtime_enable(dev);
+ pm_runtime_idle(dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "device register failed\n");
+ goto cleanup_runtime;
+ }
+
+ return 0;
+
+cleanup_runtime:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+out_disable_vcc:
+ tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_SLEEP);
+ regulator_disable(data->vcc);
+ return ret;
+}
+
+static void tmag5273_remove(struct i2c_client *i2c)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(i2c);
+ struct tmag5273_data *data = iio_priv(indio_dev);
+ struct device *dev = &i2c->dev;
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+
+ tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_SLEEP);
+ regulator_disable(data->vcc);
+}
+
+static int tmag5273_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct tmag5273_data *data = iio_priv(indio_dev);
+
+ tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_SLEEP);
+
+ return 0;
+}
+
+static int tmag5273_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct tmag5273_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_CONT);
+ if (ret) {
+ /*
+ * Time to go to stand-by mode from sleep mode is 50us
+ * typically. During this time no I2C access is possible.
+ */
+ usleep_range(80, 200);
+ ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_CONT);
+ }
+
+ return ret;
+}
+
+static const struct dev_pm_ops tmag5273_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ RUNTIME_PM_OPS(tmag5273_runtime_suspend, tmag5273_runtime_resume, NULL)
+};
+
+static const struct i2c_device_id tmag5273_id[] = {
+ {
+ "tmag5273",
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i2c, tmag5273_id);
+
+static const struct of_device_id tmag5273_of_match[] = {
+ {
+ .compatible = "ti,tmag5273",
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, tmag5273_of_match);
+
+static struct i2c_driver tmag5273_driver = {
+ .driver = {
+ .name = "tmag5273",
+ .of_match_table = tmag5273_of_match,
+ .pm = pm_ptr(&tmag5273_pm_ops),
+ },
+ .probe = tmag5273_probe,
+ .remove = tmag5273_remove,
+ .id_table = tmag5273_id,
+};
+module_i2c_driver(tmag5273_driver);
+
+MODULE_DESCRIPTION("TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor driver");
+MODULE_AUTHOR("Gerald Loacker <gerald.loacker@wolfvision.net>");
+MODULE_LICENSE("GPL");
--
2.37.2