iio: magnetometer: add ti tmag5273 driver
authorGerald Loacker <gerald.loacker@wolfvision.net>
Thu, 1 Dec 2022 07:22:20 +0000 (08:22 +0100)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Wed, 28 Dec 2022 17:19:44 +0000 (17:19 +0000)
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>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20221201072220.402585-4-gerald.loacker@wolfvision.net
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
MAINTAINERS
drivers/iio/magnetometer/Kconfig
drivers/iio/magnetometer/Makefile
drivers/iio/magnetometer/tmag5273.c [new file with mode: 0644]

index 24d5d0e..86d8fac 100644 (file)
@@ -20912,6 +20912,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>
index b91fc5e..4678193 100644 (file)
@@ -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
index b9f45b7..b1c784e 100644 (file)
@@ -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 (file)
index 0000000..28bb7ef
--- /dev/null
@@ -0,0 +1,743 @@
+// 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/bitfield.h>
+#include <linux/bits.h>
+#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>
+
+#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_MAX_REG                TMAG5273_DEVICE_STATUS
+
+#define TMAG5273_AUTOSLEEP_DELAY_MS     5000
+#define TMAG5273_MAX_AVERAGE             32
+
+/*
+ * bits in the TMAG5273_MANUFACTURER_ID_LSB / MSB register
+ * 16-bit unique manufacturer ID 0x49 / 0x54 = "TI"
+ */
+#define TMAG5273_MANUFACTURER_ID        0x5449
+
+/* 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        7
+
+/* 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_OFF           0
+#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)
+
+enum tmag5273_channels {
+       TEMPERATURE = 0,
+       AXIS_X,
+       AXIS_Y,
+       AXIS_Z,
+       ANGLE,
+       MAGNITUDE,
+};
+
+enum tmag5273_scale_index {
+       MAGN_RANGE_LOW = 0,
+       MAGN_RANGE_HIGH,
+       MAGN_RANGE_NUM
+};
+
+/* state container for the TMAG5273 driver */
+struct tmag5273_data {
+       struct device *dev;
+       unsigned int devid;
+       unsigned int version;
+       char name[16];
+       unsigned int conv_avg;
+       unsigned int scale;
+       enum tmag5273_scale_index scale_index;
+       unsigned int angle_measurement;
+       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 char *const tmag5273_angle_names[] = { "off", "x-y", "y-z", "x-z" };
+
+/*
+ * Averaging enables additional sampling of the sensor data to reduce the noise
+ * effect, but also increases conversion time.
+ */
+static const unsigned int tmag5273_avg_table[] = {
+       1, 2, 4, 8, 16, 32,
+};
+
+/*
+ * Magnetic resolution in Gauss for different TMAG5273 versions.
+ * Scale[Gauss] = Range[mT] * 1000 / 2^15 * 10, (1 mT = 10 Gauss)
+ * Only version 1 and 2 are valid, version 0 and 3 are reserved.
+ */
+static const struct iio_val_int_plus_micro tmag5273_scale[][MAGN_RANGE_NUM] = {
+       { { 0,     0 }, { 0,     0 } },
+       { { 0, 12200 }, { 0, 24400 } },
+       { { 0, 40600 }, { 0, 81200 } },
+       { { 0,     0 }, { 0,     0 } },
+};
+
+static int tmag5273_get_measure(struct tmag5273_data *data, s16 *t, s16 *x,
+                               s16 *y, s16 *z, u16 *angle, u16 *magnitude)
+{
+       unsigned int status, val;
+       __be16 reg_data[4];
+       int ret;
+
+       mutex_lock(&data->lock);
+
+       /*
+        * Max. 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(data->dev, "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;
+       *t = be16_to_cpu(reg_data[0]);
+       *x = be16_to_cpu(reg_data[1]);
+       *y = be16_to_cpu(reg_data[2]);
+       *z = be16_to_cpu(reg_data[3]);
+
+       ret = regmap_bulk_read(data->map, TMAG5273_ANGLE_RESULT_MSB,
+                              &reg_data[0], sizeof(reg_data[0]));
+       if (ret)
+               goto out_unlock;
+       /*
+        * 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
+        */
+       *angle = be16_to_cpu(reg_data[0]);
+
+       ret = regmap_read(data->map, TMAG5273_MAGNITUDE_RESULT, &val);
+       if (ret < 0)
+               goto out_unlock;
+       *magnitude = val;
+
+out_unlock:
+       mutex_unlock(&data->lock);
+       return ret;
+}
+
+static int tmag5273_write_osr(struct tmag5273_data *data, int val)
+{
+       int i;
+
+       if (val == data->conv_avg)
+               return 0;
+
+       for (i = 0; i < ARRAY_SIZE(tmag5273_avg_table); i++) {
+               if (tmag5273_avg_table[i] == val)
+                       break;
+       }
+       if (i == ARRAY_SIZE(tmag5273_avg_table))
+               return -EINVAL;
+       data->conv_avg = val;
+
+       return regmap_update_bits(data->map, TMAG5273_DEVICE_CONFIG_1,
+                                 TMAG5273_AVG_MODE_MASK,
+                                 FIELD_PREP(TMAG5273_AVG_MODE_MASK, i));
+}
+
+static int tmag5273_write_scale(struct tmag5273_data *data, int scale_micro)
+{
+       u32 value;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(tmag5273_scale[0]); i++) {
+               if (tmag5273_scale[data->version][i].micro == scale_micro)
+                       break;
+       }
+       if (i == ARRAY_SIZE(tmag5273_scale[0]))
+               return -EINVAL;
+       data->scale_index = i;
+
+       if (data->scale_index == MAGN_RANGE_LOW)
+               value = 0;
+       else
+               value = TMAG5273_Z_RANGE_MASK | TMAG5273_X_Y_RANGE_MASK;
+
+       return regmap_update_bits(data->map, TMAG5273_SENSOR_CONFIG_2,
+                                 TMAG5273_Z_RANGE_MASK | TMAG5273_X_Y_RANGE_MASK, value);
+}
+
+static int tmag5273_read_avail(struct iio_dev *indio_dev,
+                              struct iio_chan_spec const *chan,
+                              const int **vals, int *type, int *length,
+                              long mask)
+{
+       struct tmag5273_data *data = iio_priv(indio_dev);
+
+       switch (mask) {
+       case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+               *vals = tmag5273_avg_table;
+               *type = IIO_VAL_INT;
+               *length = ARRAY_SIZE(tmag5273_avg_table);
+               return IIO_AVAIL_LIST;
+       case IIO_CHAN_INFO_SCALE:
+               switch (chan->type) {
+               case IIO_MAGN:
+                       *type = IIO_VAL_INT_PLUS_MICRO;
+                       *vals = (int *)tmag5273_scale[data->version];
+                       *length = ARRAY_SIZE(tmag5273_scale[data->version]) *
+                                 MAGN_RANGE_NUM;
+                       return IIO_AVAIL_LIST;
+               default:
+                       return -EINVAL;
+               }
+       default:
+               return -EINVAL;
+       }
+}
+
+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);
+       s16 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->address) {
+               case TEMPERATURE:
+                       *val = t;
+                       return IIO_VAL_INT;
+               case AXIS_X:
+                       *val = x;
+                       return IIO_VAL_INT;
+               case AXIS_Y:
+                       *val = y;
+                       return IIO_VAL_INT;
+               case AXIS_Z:
+                       *val = z;
+                       return IIO_VAL_INT;
+               case ANGLE:
+                       *val = angle;
+                       return IIO_VAL_INT;
+               case MAGNITUDE:
+                       *val = magnitude;
+                       return IIO_VAL_INT;
+               default:
+                       return -EINVAL;
+               }
+       case IIO_CHAN_INFO_SCALE:
+               switch (chan->type) {
+               case IIO_TEMP:
+                       /*
+                        * Convert device specific value to millicelsius.
+                        * Resolution from the sensor is 60.1 LSB/celsius and
+                        * the reference value at 25 celsius is 17508 LSBs.
+                        */
+                       *val = 10000;
+                       *val2 = 601;
+                       return IIO_VAL_FRACTIONAL;
+               case IIO_MAGN:
+                       /* Magnetic resolution in uT */
+                       *val = 0;
+                       *val2 = tmag5273_scale[data->version]
+                                             [data->scale_index].micro;
+                       return IIO_VAL_INT_PLUS_MICRO;
+               case IIO_ANGL:
+                       /*
+                        * Angle is in degrees and has four fractional bits,
+                        * therefore use 1/16 * pi/180 to convert to radians.
+                        */
+                       *val = 1000;
+                       *val2 = 916732;
+                       return IIO_VAL_FRACTIONAL;
+               default:
+                       return -EINVAL;
+               }
+       case IIO_CHAN_INFO_OFFSET:
+               switch (chan->type) {
+               case IIO_TEMP:
+                       *val = -266314;
+                       return IIO_VAL_INT;
+               default:
+                       return -EINVAL;
+               }
+       case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+               *val = data->conv_avg;
+               return IIO_VAL_INT;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int tmag5273_write_raw(struct iio_dev *indio_dev,
+                             struct iio_chan_spec const *chan, int val,
+                             int val2, long mask)
+{
+       struct tmag5273_data *data = iio_priv(indio_dev);
+
+       switch (mask) {
+       case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+               return tmag5273_write_osr(data, val);
+       case IIO_CHAN_INFO_SCALE:
+               switch (chan->type) {
+               case IIO_MAGN:
+                       if (val)
+                               return -EINVAL;
+                       return tmag5273_write_scale(data, val2);
+               default:
+                       return -EINVAL;
+               }
+       default:
+               return -EINVAL;
+       }
+}
+
+#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),              \
+               .info_mask_shared_by_type_available =                        \
+                                     BIT(IIO_CHAN_INFO_SCALE),              \
+               .info_mask_shared_by_all =                                   \
+                                     BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+               .info_mask_shared_by_all_available =                         \
+                                     BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+               .address = index,                                            \
+               .scan_index = index,                                         \
+               .scan_type = {                                               \
+                       .sign = 's',                                         \
+                       .realbits = 16,                                      \
+                       .storagebits = 16,                                   \
+                       .endianness = IIO_CPU,                               \
+               },                                                           \
+       }
+
+static const struct iio_chan_spec tmag5273_channels[] = {
+       {
+               .type = IIO_TEMP,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+                       BIT(IIO_CHAN_INFO_SCALE) |
+                       BIT(IIO_CHAN_INFO_OFFSET),
+               .address = TEMPERATURE,
+               .scan_index = TEMPERATURE,
+               .scan_type = {
+                       .sign = 'u',
+                       .realbits = 16,
+                       .storagebits = 16,
+                       .endianness = IIO_CPU,
+               },
+       },
+       TMAG5273_AXIS_CHANNEL(X, AXIS_X),
+       TMAG5273_AXIS_CHANNEL(Y, AXIS_Y),
+       TMAG5273_AXIS_CHANNEL(Z, AXIS_Z),
+       {
+               .type = IIO_ANGL,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+               .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+               .info_mask_shared_by_all =
+                                     BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+               .info_mask_shared_by_all_available =
+                                     BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+               .address = ANGLE,
+               .scan_index = ANGLE,
+               .scan_type = {
+                       .sign = 'u',
+                       .realbits = 16,
+                       .storagebits = 16,
+                       .endianness = IIO_CPU,
+               },
+       },
+       {
+               .type = IIO_DISTANCE,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+               .info_mask_shared_by_all =
+                                     BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+               .info_mask_shared_by_all_available =
+                                     BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+               .address = MAGNITUDE,
+               .scan_index = MAGNITUDE,
+               .scan_type = {
+                       .sign = 'u',
+                       .realbits = 16,
+                       .storagebits = 16,
+                       .endianness = IIO_CPU,
+               },
+       },
+       IIO_CHAN_SOFT_TIMESTAMP(6),
+};
+
+static const struct iio_info tmag5273_info = {
+       .read_avail = tmag5273_read_avail,
+       .read_raw = tmag5273_read_raw,
+       .write_raw = tmag5273_write_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 = TMAG5273_MAX_REG,
+       .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 void tmag5273_read_device_property(struct tmag5273_data *data)
+{
+       struct device *dev = data->dev;
+       const char *str;
+       int ret;
+
+       data->angle_measurement = TMAG5273_ANGLE_EN_X_Y;
+
+       ret = device_property_read_string(dev, "ti,angle-measurement", &str);
+       if (ret)
+               return;
+
+       ret = match_string(tmag5273_angle_names,
+                          ARRAY_SIZE(tmag5273_angle_names), str);
+       if (ret >= 0)
+               data->angle_measurement = ret;
+}
+
+static void tmag5273_wake_up(struct tmag5273_data *data)
+{
+       int val;
+
+       /* Wake up the chip by sending a dummy I2C command */
+       regmap_read(data->map, TMAG5273_DEVICE_ID, &val);
+       /*
+        * 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);
+}
+
+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_measurement));
+       if (ret)
+               return ret;
+       data->scale_index = MAGN_RANGE_LOW;
+
+       return regmap_write(data->map, TMAG5273_T_CONFIG, TMAG5273_T_CH_EN);
+}
+
+static int tmag5273_check_device_id(struct tmag5273_data *data)
+{
+       __le16 devid;
+       int val, ret;
+
+       ret = regmap_read(data->map, TMAG5273_DEVICE_ID, &val);
+       if (ret)
+               return dev_err_probe(data->dev, ret, "failed to power on device\n");
+       data->version = FIELD_PREP(TMAG5273_VERSION_MASK, val);
+
+       ret = regmap_bulk_read(data->map, TMAG5273_MANUFACTURER_ID_LSB, &devid,
+                              sizeof(devid));
+       if (ret)
+               return dev_err_probe(data->dev, ret, "failed to read device ID\n");
+       data->devid = le16_to_cpu(devid);
+
+       switch (data->devid) {
+       case TMAG5273_MANUFACTURER_ID:
+               /*
+                * The device name matches the orderable part number. 'x' stands
+                * for A, B, C or D devices, which have different I2C addresses.
+                * Versions 1 or 2 (0 and 3 is reserved) stands for different
+                * magnetic strengths.
+                */
+               snprintf(data->name, sizeof(data->name), "tmag5273x%1u", data->version);
+               if (data->version < 1 || data->version > 2)
+                       dev_warn(data->dev, "Unsupported device %s\n", data->name);
+               return 0;
+       default:
+               /*
+                * Only print warning in case of unknown device ID to allow
+                * fallback compatible in device tree.
+                */
+               dev_warn(data->dev, "Unknown device ID 0x%x\n", data->devid);
+               return 0;
+       }
+}
+
+static void tmag5273_power_down(void *data)
+{
+       tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_SLEEP);
+}
+
+static int tmag5273_probe(struct i2c_client *i2c)
+{
+       struct device *dev = &i2c->dev;
+       struct tmag5273_data *data;
+       struct iio_dev *indio_dev;
+       int ret;
+
+       indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+       if (!indio_dev)
+               return -ENOMEM;
+
+       data = iio_priv(indio_dev);
+       data->dev = dev;
+       i2c_set_clientdata(i2c, indio_dev);
+
+       data->map = devm_regmap_init_i2c(i2c, &tmag5273_regmap_config);
+       if (IS_ERR(data->map))
+               return dev_err_probe(dev, PTR_ERR(data->map),
+                                    "failed to allocate register map\n");
+
+       mutex_init(&data->lock);
+
+       ret = devm_regulator_get_enable(dev, "vcc");
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to enable regulator\n");
+
+       tmag5273_wake_up(data);
+
+       ret = tmag5273_check_device_id(data);
+       if (ret)
+               return ret;
+
+       ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_CONT);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to power on device\n");
+
+       /*
+        * Register powerdown deferred callback which suspends the chip
+        * after module unloaded.
+        *
+        * TMAG5273 should be in SUSPEND mode in the two cases:
+        * 1) When driver is loaded, but we do not have any data or
+        *    configuration requests to it (we are solving it using
+        *    autosuspend feature).
+        * 2) When driver is unloaded and device is not used (devm action is
+        *    used in this case).
+        */
+       ret = devm_add_action_or_reset(dev, tmag5273_power_down, data);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to add powerdown action\n");
+
+       ret = pm_runtime_set_active(dev);
+       if (ret < 0)
+               return ret;
+
+       ret = devm_pm_runtime_enable(dev);
+       if (ret)
+               return ret;
+
+       pm_runtime_get_noresume(dev);
+       pm_runtime_set_autosuspend_delay(dev, TMAG5273_AUTOSLEEP_DELAY_MS);
+       pm_runtime_use_autosuspend(dev);
+
+       tmag5273_read_device_property(data);
+
+       ret = tmag5273_chip_init(data);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to init device\n");
+
+       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_mark_last_busy(dev);
+       pm_runtime_put_autosuspend(dev);
+
+       ret = devm_iio_device_register(dev, indio_dev);
+       if (ret)
+               return dev_err_probe(dev, ret, "device register failed\n");
+
+       return 0;
+}
+
+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);
+       int ret;
+
+       ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_SLEEP);
+       if (ret)
+               dev_err(dev, "failed to power off device (%pe)\n", ERR_PTR(ret));
+
+       return ret;
+}
+
+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;
+
+       tmag5273_wake_up(data);
+
+       ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_CONT);
+       if (ret)
+               dev_err(dev, "failed to power on device (%pe)\n", ERR_PTR(ret));
+
+       return ret;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(tmag5273_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_new = tmag5273_probe,
+       .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");