summaryrefslogtreecommitdiffstats
path: root/drivers/iio/magnetometer
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/iio/magnetometer/Kconfig226
-rw-r--r--drivers/iio/magnetometer/Makefile32
-rw-r--r--drivers/iio/magnetometer/ak8974.c1059
-rw-r--r--drivers/iio/magnetometer/ak8975.c1121
-rw-r--r--drivers/iio/magnetometer/bmc150_magn.c1084
-rw-r--r--drivers/iio/magnetometer/bmc150_magn.h12
-rw-r--r--drivers/iio/magnetometer/bmc150_magn_i2c.c83
-rw-r--r--drivers/iio/magnetometer/bmc150_magn_spi.c67
-rw-r--r--drivers/iio/magnetometer/hid-sensor-magn-3d.c586
-rw-r--r--drivers/iio/magnetometer/hmc5843.h56
-rw-r--r--drivers/iio/magnetometer/hmc5843_core.c688
-rw-r--r--drivers/iio/magnetometer/hmc5843_i2c.c106
-rw-r--r--drivers/iio/magnetometer/hmc5843_spi.c103
-rw-r--r--drivers/iio/magnetometer/mag3110.c652
-rw-r--r--drivers/iio/magnetometer/mmc35240.c587
-rw-r--r--drivers/iio/magnetometer/rm3100-core.c605
-rw-r--r--drivers/iio/magnetometer/rm3100-i2c.c55
-rw-r--r--drivers/iio/magnetometer/rm3100-spi.c65
-rw-r--r--drivers/iio/magnetometer/rm3100.h17
-rw-r--r--drivers/iio/magnetometer/st_magn.h38
-rw-r--r--drivers/iio/magnetometer/st_magn_buffer.c46
-rw-r--r--drivers/iio/magnetometer/st_magn_core.c654
-rw-r--r--drivers/iio/magnetometer/st_magn_i2c.c118
-rw-r--r--drivers/iio/magnetometer/st_magn_spi.c109
-rw-r--r--drivers/iio/magnetometer/yamaha-yas530.c1616
25 files changed, 9785 insertions, 0 deletions
diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig
new file mode 100644
index 000000000..b91fc5e6a
--- /dev/null
+++ b/drivers/iio/magnetometer/Kconfig
@@ -0,0 +1,226 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Magnetometer sensors
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Magnetometer sensors"
+
+config AK8974
+ tristate "Asahi Kasei AK8974 3-Axis Magnetometer"
+ depends on I2C
+ select REGMAP_I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for Asahi Kasei AK8974, AMI305 or
+ AMI306 I2C-based 3-axis magnetometer chips.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ak8974.
+
+config AK8975
+ tristate "Asahi Kasei AK8975 3-Axis Magnetometer"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for Asahi Kasei AK8975, AK8963,
+ AK09911, AK09912 or AK09916 3-Axis Magnetometer.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ak8975.
+
+config AK09911
+ tristate "Asahi Kasei AK09911 3-axis Compass"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ select AK8975
+ help
+ Deprecated: AK09911 is now supported by AK8975 driver.
+
+config BMC150_MAGN
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config BMC150_MAGN_I2C
+ tristate "Bosch BMC150 I2C Magnetometer Driver"
+ depends on I2C
+ select BMC150_MAGN
+ select REGMAP_I2C
+ help
+ Say yes here to build support for the BMC150 magnetometer with
+ I2C interface.
+
+ This is a combo module with both accelerometer and magnetometer.
+ This driver is only implementing magnetometer part, which has
+ its own address and register map.
+
+ This driver also supports I2C Bosch BMC156 and BMM150 chips.
+ To compile this driver as a module, choose M here: the module will be
+ called bmc150_magn_i2c.
+
+config BMC150_MAGN_SPI
+ tristate "Bosch BMC150 SPI Magnetometer Driver"
+ depends on SPI
+ select BMC150_MAGN
+ select REGMAP_SPI
+ help
+ Say yes here to build support for the BMC150 magnetometer with
+ SPI interface.
+
+ This is a combo module with both accelerometer and magnetometer.
+ This driver is only implementing magnetometer part, which has
+ its own address and register map.
+
+ This driver also supports SPI Bosch BMC156 and BMM150 chips.
+ To compile this driver as a module, choose M here: the module will be
+ called bmc150_magn_spi.
+
+config MAG3110
+ tristate "Freescale MAG3110 3-Axis Magnetometer"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for the Freescale MAG3110 3-Axis
+ magnetometer.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mag3110.
+
+config HID_SENSOR_MAGNETOMETER_3D
+ depends on HID_SENSOR_HUB
+ select IIO_BUFFER
+ select HID_SENSOR_IIO_COMMON
+ select HID_SENSOR_IIO_TRIGGER
+ tristate "HID Magenetometer 3D"
+ help
+ Say yes here to build support for the HID SENSOR
+ Magnetometer 3D.
+
+config MMC35240
+ tristate "MEMSIC MMC35240 3-axis magnetic sensor"
+ select REGMAP_I2C
+ depends on I2C
+ help
+ Say yes here to build support for the MEMSIC MMC35240 3-axis
+ magnetic sensor.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mmc35240.
+
+config IIO_ST_MAGN_3AXIS
+ tristate "STMicroelectronics magnetometers 3-Axis Driver"
+ depends on (I2C || SPI_MASTER) && SYSFS
+ select IIO_ST_SENSORS_CORE
+ select IIO_TRIGGERED_BUFFER if (IIO_BUFFER)
+ help
+ Say yes here to build support for STMicroelectronics magnetometers:
+ LSM303DLHC, LSM303DLM, LIS3MDL.
+
+ Also need to enable at least one of I2C and SPI interface drivers
+ below.
+
+config IIO_ST_MAGN_I2C_3AXIS
+ tristate "STMicroelectronics magnetometers 3-Axis I2C Interface"
+ depends on I2C && IIO_ST_MAGN_3AXIS
+ default I2C && IIO_ST_MAGN_3AXIS
+ select IIO_ST_SENSORS_I2C
+ help
+ Build support for STMicroelectronics magnetometers I2C interface.
+
+ To compile this driver as a module, choose M here. The module
+ will be called st_magn_i2c.
+
+config IIO_ST_MAGN_SPI_3AXIS
+ tristate "STMicroelectronics magnetometers 3-Axis SPI Interface"
+ depends on SPI_MASTER && IIO_ST_MAGN_3AXIS
+ default SPI_MASTER && IIO_ST_MAGN_3AXIS
+ select IIO_ST_SENSORS_SPI
+ help
+ Build support for STMicroelectronics magnetometers SPI interface.
+
+ To compile this driver as a module, choose M here. The module
+ will be called st_magn_spi.
+
+config SENSORS_HMC5843
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config SENSORS_HMC5843_I2C
+ tristate "Honeywell HMC5843/5883/5883L 3-Axis Magnetometer (I2C)"
+ depends on I2C
+ select SENSORS_HMC5843
+ select REGMAP_I2C
+ help
+ Say Y here to add support for the Honeywell HMC5843, HMC5883 and
+ HMC5883L 3-Axis Magnetometer (digital compass).
+
+ This driver can also be compiled as a set of modules.
+ If so, these modules will be created:
+ - hmc5843_core (core functions)
+ - hmc5843_i2c (support for HMC5843, HMC5883, HMC5883L and HMC5983)
+
+config SENSORS_HMC5843_SPI
+ tristate "Honeywell HMC5983 3-Axis Magnetometer (SPI)"
+ depends on SPI_MASTER
+ select SENSORS_HMC5843
+ select REGMAP_SPI
+ help
+ Say Y here to add support for the Honeywell HMC5983 3-Axis Magnetometer
+ (digital compass).
+
+ This driver can also be compiled as a set of modules.
+ If so, these modules will be created:
+ - hmc5843_core (core functions)
+ - hmc5843_spi (support for HMC5983)
+
+config SENSORS_RM3100
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config SENSORS_RM3100_I2C
+ tristate "PNI RM3100 3-Axis Magnetometer (I2C)"
+ depends on I2C
+ select SENSORS_RM3100
+ select REGMAP_I2C
+ help
+ Say Y here to add support for the PNI RM3100 3-Axis Magnetometer.
+
+ This driver can also be compiled as a module.
+ To compile this driver as a module, choose M here: the module
+ will be called rm3100-i2c.
+
+config SENSORS_RM3100_SPI
+ tristate "PNI RM3100 3-Axis Magnetometer (SPI)"
+ depends on SPI_MASTER
+ select SENSORS_RM3100
+ select REGMAP_SPI
+ help
+ Say Y here to add support for the PNI RM3100 3-Axis Magnetometer.
+
+ This driver can also be compiled as a module.
+ To compile this driver as a module, choose M here: the module
+ will be called rm3100-spi.
+
+config YAMAHA_YAS530
+ tristate "Yamaha YAS530 family of 3-Axis Magnetometers (I2C)"
+ depends on I2C
+ select REGMAP_I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say Y here to add support for the Yamaha YAS530 series of
+ 3-Axis Magnetometers. YAS530, YAS532, YAS533 and YAS537 are
+ supported.
+
+ This driver can also be compiled as a module.
+ To compile this driver as a module, choose M here: the module
+ will be called yamaha-yas.
+
+endmenu
diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile
new file mode 100644
index 000000000..b9f45b7fa
--- /dev/null
+++ b/drivers/iio/magnetometer/Makefile
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for industrial I/O Magnetometer sensor drivers
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_AK8974) += ak8974.o
+obj-$(CONFIG_AK8975) += ak8975.o
+obj-$(CONFIG_BMC150_MAGN) += bmc150_magn.o
+obj-$(CONFIG_BMC150_MAGN_I2C) += bmc150_magn_i2c.o
+obj-$(CONFIG_BMC150_MAGN_SPI) += bmc150_magn_spi.o
+
+obj-$(CONFIG_MAG3110) += mag3110.o
+obj-$(CONFIG_HID_SENSOR_MAGNETOMETER_3D) += hid-sensor-magn-3d.o
+obj-$(CONFIG_MMC35240) += mmc35240.o
+
+obj-$(CONFIG_IIO_ST_MAGN_3AXIS) += st_magn.o
+st_magn-y := st_magn_core.o
+st_magn-$(CONFIG_IIO_BUFFER) += st_magn_buffer.o
+
+obj-$(CONFIG_IIO_ST_MAGN_I2C_3AXIS) += st_magn_i2c.o
+obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o
+
+obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o
+obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o
+obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o
+
+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_YAMAHA_YAS530) += yamaha-yas530.o
diff --git a/drivers/iio/magnetometer/ak8974.c b/drivers/iio/magnetometer/ak8974.c
new file mode 100644
index 000000000..7ec9ab3be
--- /dev/null
+++ b/drivers/iio/magnetometer/ak8974.c
@@ -0,0 +1,1059 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Asahi Kasei EMD Corporation AK8974
+ * and Aichi Steel AMI305 magnetometer chips.
+ * Based on a patch from Samu Onkalo and the AK8975 IIO driver.
+ *
+ * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ * Copyright (c) 2010 NVIDIA Corporation.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * Author: Samu Onkalo <samu.p.onkalo@nokia.com>
+ * Author: Linus Walleij <linus.walleij@linaro.org>
+ */
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h> /* For irq_get_irq_data() */
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/random.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+/*
+ * 16-bit registers are little-endian. LSB is at the address defined below
+ * and MSB is at the next higher address.
+ */
+
+/* These registers are common for AK8974 and AMI30x */
+#define AK8974_SELFTEST 0x0C
+#define AK8974_SELFTEST_IDLE 0x55
+#define AK8974_SELFTEST_OK 0xAA
+
+#define AK8974_INFO 0x0D
+
+#define AK8974_WHOAMI 0x0F
+#define AK8974_WHOAMI_VALUE_AMI306 0x46
+#define AK8974_WHOAMI_VALUE_AMI305 0x47
+#define AK8974_WHOAMI_VALUE_AK8974 0x48
+#define AK8974_WHOAMI_VALUE_HSCDTD008A 0x49
+
+#define AK8974_DATA_X 0x10
+#define AK8974_DATA_Y 0x12
+#define AK8974_DATA_Z 0x14
+#define AK8974_INT_SRC 0x16
+#define AK8974_STATUS 0x18
+#define AK8974_INT_CLEAR 0x1A
+#define AK8974_CTRL1 0x1B
+#define AK8974_CTRL2 0x1C
+#define AK8974_CTRL3 0x1D
+#define AK8974_INT_CTRL 0x1E
+#define AK8974_INT_THRES 0x26 /* Absolute any axis value threshold */
+#define AK8974_PRESET 0x30
+
+/* AK8974-specific offsets */
+#define AK8974_OFFSET_X 0x20
+#define AK8974_OFFSET_Y 0x22
+#define AK8974_OFFSET_Z 0x24
+/* AMI305-specific offsets */
+#define AMI305_OFFSET_X 0x6C
+#define AMI305_OFFSET_Y 0x72
+#define AMI305_OFFSET_Z 0x78
+
+/* Different temperature registers */
+#define AK8974_TEMP 0x31
+#define AMI305_TEMP 0x60
+
+/* AMI306-specific control register */
+#define AMI306_CTRL4 0x5C
+
+/* AMI306 factory calibration data */
+
+/* fine axis sensitivity */
+#define AMI306_FINEOUTPUT_X 0x90
+#define AMI306_FINEOUTPUT_Y 0x92
+#define AMI306_FINEOUTPUT_Z 0x94
+
+/* axis sensitivity */
+#define AMI306_SENS_X 0x96
+#define AMI306_SENS_Y 0x98
+#define AMI306_SENS_Z 0x9A
+
+/* axis cross-interference */
+#define AMI306_GAIN_PARA_XZ 0x9C
+#define AMI306_GAIN_PARA_XY 0x9D
+#define AMI306_GAIN_PARA_YZ 0x9E
+#define AMI306_GAIN_PARA_YX 0x9F
+#define AMI306_GAIN_PARA_ZY 0xA0
+#define AMI306_GAIN_PARA_ZX 0xA1
+
+/* offset at ZERO magnetic field */
+#define AMI306_OFFZERO_X 0xF8
+#define AMI306_OFFZERO_Y 0xFA
+#define AMI306_OFFZERO_Z 0xFC
+
+
+#define AK8974_INT_X_HIGH BIT(7) /* Axis over +threshold */
+#define AK8974_INT_Y_HIGH BIT(6)
+#define AK8974_INT_Z_HIGH BIT(5)
+#define AK8974_INT_X_LOW BIT(4) /* Axis below -threshold */
+#define AK8974_INT_Y_LOW BIT(3)
+#define AK8974_INT_Z_LOW BIT(2)
+#define AK8974_INT_RANGE BIT(1) /* Range overflow (any axis) */
+
+#define AK8974_STATUS_DRDY BIT(6) /* Data ready */
+#define AK8974_STATUS_OVERRUN BIT(5) /* Data overrun */
+#define AK8974_STATUS_INT BIT(4) /* Interrupt occurred */
+
+#define AK8974_CTRL1_POWER BIT(7) /* 0 = standby; 1 = active */
+#define AK8974_CTRL1_RATE BIT(4) /* 0 = 10 Hz; 1 = 20 Hz */
+#define AK8974_CTRL1_FORCE_EN BIT(1) /* 0 = normal; 1 = force */
+#define AK8974_CTRL1_MODE2 BIT(0) /* 0 */
+
+#define AK8974_CTRL2_INT_EN BIT(4) /* 1 = enable interrupts */
+#define AK8974_CTRL2_DRDY_EN BIT(3) /* 1 = enable data ready signal */
+#define AK8974_CTRL2_DRDY_POL BIT(2) /* 1 = data ready active high */
+#define AK8974_CTRL2_RESDEF (AK8974_CTRL2_DRDY_POL)
+
+#define AK8974_CTRL3_RESET BIT(7) /* Software reset */
+#define AK8974_CTRL3_FORCE BIT(6) /* Start forced measurement */
+#define AK8974_CTRL3_SELFTEST BIT(4) /* Set selftest register */
+#define AK8974_CTRL3_RESDEF 0x00
+
+#define AK8974_INT_CTRL_XEN BIT(7) /* Enable interrupt for this axis */
+#define AK8974_INT_CTRL_YEN BIT(6)
+#define AK8974_INT_CTRL_ZEN BIT(5)
+#define AK8974_INT_CTRL_XYZEN (BIT(7)|BIT(6)|BIT(5))
+#define AK8974_INT_CTRL_POL BIT(3) /* 0 = active low; 1 = active high */
+#define AK8974_INT_CTRL_PULSE BIT(1) /* 0 = latched; 1 = pulse (50 usec) */
+#define AK8974_INT_CTRL_RESDEF (AK8974_INT_CTRL_XYZEN | AK8974_INT_CTRL_POL)
+
+/* HSCDTD008A-specific control register */
+#define HSCDTD008A_CTRL4 0x1E
+#define HSCDTD008A_CTRL4_MMD BIT(7) /* must be set to 1 */
+#define HSCDTD008A_CTRL4_RANGE BIT(4) /* 0 = 14-bit output; 1 = 15-bit output */
+#define HSCDTD008A_CTRL4_RESDEF (HSCDTD008A_CTRL4_MMD | HSCDTD008A_CTRL4_RANGE)
+
+/* The AMI305 has elaborate FW version and serial number registers */
+#define AMI305_VER 0xE8
+#define AMI305_SN 0xEA
+
+#define AK8974_MAX_RANGE 2048
+
+#define AK8974_POWERON_DELAY 50
+#define AK8974_ACTIVATE_DELAY 1
+#define AK8974_SELFTEST_DELAY 1
+/*
+ * Set the autosuspend to two orders of magnitude larger than the poweron
+ * delay to make sane reasonable power tradeoff savings (5 seconds in
+ * this case).
+ */
+#define AK8974_AUTOSUSPEND_DELAY 5000
+
+#define AK8974_MEASTIME 3
+
+#define AK8974_PWR_ON 1
+#define AK8974_PWR_OFF 0
+
+/**
+ * struct ak8974 - state container for the AK8974 driver
+ * @i2c: parent I2C client
+ * @orientation: mounting matrix, flipped axis etc
+ * @map: regmap to access the AK8974 registers over I2C
+ * @regs: the avdd and dvdd power regulators
+ * @name: the name of the part
+ * @variant: the whoami ID value (for selecting code paths)
+ * @lock: locks the magnetometer for exclusive use during a measurement
+ * @drdy_irq: uses the DRDY IRQ line
+ * @drdy_complete: completion for DRDY
+ * @drdy_active_low: the DRDY IRQ is active low
+ * @scan: timestamps
+ */
+struct ak8974 {
+ struct i2c_client *i2c;
+ struct iio_mount_matrix orientation;
+ struct regmap *map;
+ struct regulator_bulk_data regs[2];
+ const char *name;
+ u8 variant;
+ struct mutex lock;
+ bool drdy_irq;
+ struct completion drdy_complete;
+ bool drdy_active_low;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ __le16 channels[3];
+ s64 ts __aligned(8);
+ } scan;
+};
+
+static const char ak8974_reg_avdd[] = "avdd";
+static const char ak8974_reg_dvdd[] = "dvdd";
+
+static int ak8974_get_u16_val(struct ak8974 *ak8974, u8 reg, u16 *val)
+{
+ int ret;
+ __le16 bulk;
+
+ ret = regmap_bulk_read(ak8974->map, reg, &bulk, 2);
+ if (ret)
+ return ret;
+ *val = le16_to_cpu(bulk);
+
+ return 0;
+}
+
+static int ak8974_set_u16_val(struct ak8974 *ak8974, u8 reg, u16 val)
+{
+ __le16 bulk = cpu_to_le16(val);
+
+ return regmap_bulk_write(ak8974->map, reg, &bulk, 2);
+}
+
+static int ak8974_set_power(struct ak8974 *ak8974, bool mode)
+{
+ int ret;
+ u8 val;
+
+ val = mode ? AK8974_CTRL1_POWER : 0;
+ val |= AK8974_CTRL1_FORCE_EN;
+ ret = regmap_write(ak8974->map, AK8974_CTRL1, val);
+ if (ret < 0)
+ return ret;
+
+ if (mode)
+ msleep(AK8974_ACTIVATE_DELAY);
+
+ return 0;
+}
+
+static int ak8974_reset(struct ak8974 *ak8974)
+{
+ int ret;
+
+ /* Power on to get register access. Sets CTRL1 reg to reset state */
+ ret = ak8974_set_power(ak8974, AK8974_PWR_ON);
+ if (ret)
+ return ret;
+ ret = regmap_write(ak8974->map, AK8974_CTRL2, AK8974_CTRL2_RESDEF);
+ if (ret)
+ return ret;
+ ret = regmap_write(ak8974->map, AK8974_CTRL3, AK8974_CTRL3_RESDEF);
+ if (ret)
+ return ret;
+ if (ak8974->variant != AK8974_WHOAMI_VALUE_HSCDTD008A) {
+ ret = regmap_write(ak8974->map, AK8974_INT_CTRL,
+ AK8974_INT_CTRL_RESDEF);
+ if (ret)
+ return ret;
+ } else {
+ ret = regmap_write(ak8974->map, HSCDTD008A_CTRL4,
+ HSCDTD008A_CTRL4_RESDEF);
+ if (ret)
+ return ret;
+ }
+
+ /* After reset, power off is default state */
+ return ak8974_set_power(ak8974, AK8974_PWR_OFF);
+}
+
+static int ak8974_configure(struct ak8974 *ak8974)
+{
+ int ret;
+
+ ret = regmap_write(ak8974->map, AK8974_CTRL2, AK8974_CTRL2_DRDY_EN |
+ AK8974_CTRL2_INT_EN);
+ if (ret)
+ return ret;
+ ret = regmap_write(ak8974->map, AK8974_CTRL3, 0);
+ if (ret)
+ return ret;
+ if (ak8974->variant == AK8974_WHOAMI_VALUE_AMI306) {
+ /* magic from datasheet: set high-speed measurement mode */
+ ret = ak8974_set_u16_val(ak8974, AMI306_CTRL4, 0xA07E);
+ if (ret)
+ return ret;
+ }
+ if (ak8974->variant == AK8974_WHOAMI_VALUE_HSCDTD008A)
+ return 0;
+ ret = regmap_write(ak8974->map, AK8974_INT_CTRL, AK8974_INT_CTRL_POL);
+ if (ret)
+ return ret;
+
+ return regmap_write(ak8974->map, AK8974_PRESET, 0);
+}
+
+static int ak8974_trigmeas(struct ak8974 *ak8974)
+{
+ unsigned int clear;
+ u8 mask;
+ u8 val;
+ int ret;
+
+ /* Clear any previous measurement overflow status */
+ ret = regmap_read(ak8974->map, AK8974_INT_CLEAR, &clear);
+ if (ret)
+ return ret;
+
+ /* If we have a DRDY IRQ line, use it */
+ if (ak8974->drdy_irq) {
+ mask = AK8974_CTRL2_INT_EN |
+ AK8974_CTRL2_DRDY_EN |
+ AK8974_CTRL2_DRDY_POL;
+ val = AK8974_CTRL2_DRDY_EN;
+
+ if (!ak8974->drdy_active_low)
+ val |= AK8974_CTRL2_DRDY_POL;
+
+ init_completion(&ak8974->drdy_complete);
+ ret = regmap_update_bits(ak8974->map, AK8974_CTRL2,
+ mask, val);
+ if (ret)
+ return ret;
+ }
+
+ /* Force a measurement */
+ return regmap_update_bits(ak8974->map,
+ AK8974_CTRL3,
+ AK8974_CTRL3_FORCE,
+ AK8974_CTRL3_FORCE);
+}
+
+static int ak8974_await_drdy(struct ak8974 *ak8974)
+{
+ int timeout = 2;
+ unsigned int val;
+ int ret;
+
+ if (ak8974->drdy_irq) {
+ ret = wait_for_completion_timeout(&ak8974->drdy_complete,
+ 1 + msecs_to_jiffies(1000));
+ if (!ret) {
+ dev_err(&ak8974->i2c->dev,
+ "timeout waiting for DRDY IRQ\n");
+ return -ETIMEDOUT;
+ }
+ return 0;
+ }
+
+ /* Default delay-based poll loop */
+ do {
+ msleep(AK8974_MEASTIME);
+ ret = regmap_read(ak8974->map, AK8974_STATUS, &val);
+ if (ret < 0)
+ return ret;
+ if (val & AK8974_STATUS_DRDY)
+ return 0;
+ } while (--timeout);
+
+ dev_err(&ak8974->i2c->dev, "timeout waiting for DRDY\n");
+ return -ETIMEDOUT;
+}
+
+static int ak8974_getresult(struct ak8974 *ak8974, __le16 *result)
+{
+ unsigned int src;
+ int ret;
+
+ ret = ak8974_await_drdy(ak8974);
+ if (ret)
+ return ret;
+ ret = regmap_read(ak8974->map, AK8974_INT_SRC, &src);
+ if (ret < 0)
+ return ret;
+
+ /* Out of range overflow! Strong magnet close? */
+ if (src & AK8974_INT_RANGE) {
+ dev_err(&ak8974->i2c->dev,
+ "range overflow in sensor\n");
+ return -ERANGE;
+ }
+
+ ret = regmap_bulk_read(ak8974->map, AK8974_DATA_X, result, 6);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static irqreturn_t ak8974_drdy_irq(int irq, void *d)
+{
+ struct ak8974 *ak8974 = d;
+
+ if (!ak8974->drdy_irq)
+ return IRQ_NONE;
+
+ /* TODO: timestamp here to get good measurement stamps */
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t ak8974_drdy_irq_thread(int irq, void *d)
+{
+ struct ak8974 *ak8974 = d;
+ unsigned int val;
+ int ret;
+
+ /* Check if this was a DRDY from us */
+ ret = regmap_read(ak8974->map, AK8974_STATUS, &val);
+ if (ret < 0) {
+ dev_err(&ak8974->i2c->dev, "error reading DRDY status\n");
+ return IRQ_HANDLED;
+ }
+ if (val & AK8974_STATUS_DRDY) {
+ /* Yes this was our IRQ */
+ complete(&ak8974->drdy_complete);
+ return IRQ_HANDLED;
+ }
+
+ /* We may be on a shared IRQ, let the next client check */
+ return IRQ_NONE;
+}
+
+static int ak8974_selftest(struct ak8974 *ak8974)
+{
+ struct device *dev = &ak8974->i2c->dev;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(ak8974->map, AK8974_SELFTEST, &val);
+ if (ret)
+ return ret;
+ if (val != AK8974_SELFTEST_IDLE) {
+ dev_err(dev, "selftest not idle before test\n");
+ return -EIO;
+ }
+
+ /* Trigger self-test */
+ ret = regmap_update_bits(ak8974->map,
+ AK8974_CTRL3,
+ AK8974_CTRL3_SELFTEST,
+ AK8974_CTRL3_SELFTEST);
+ if (ret) {
+ dev_err(dev, "could not write CTRL3\n");
+ return ret;
+ }
+
+ msleep(AK8974_SELFTEST_DELAY);
+
+ ret = regmap_read(ak8974->map, AK8974_SELFTEST, &val);
+ if (ret)
+ return ret;
+ if (val != AK8974_SELFTEST_OK) {
+ dev_err(dev, "selftest result NOT OK (%02x)\n", val);
+ return -EIO;
+ }
+
+ ret = regmap_read(ak8974->map, AK8974_SELFTEST, &val);
+ if (ret)
+ return ret;
+ if (val != AK8974_SELFTEST_IDLE) {
+ dev_err(dev, "selftest not idle after test (%02x)\n", val);
+ return -EIO;
+ }
+ dev_dbg(dev, "passed self-test\n");
+
+ return 0;
+}
+
+static void ak8974_read_calib_data(struct ak8974 *ak8974, unsigned int reg,
+ __le16 *tab, size_t tab_size)
+{
+ int ret = regmap_bulk_read(ak8974->map, reg, tab, tab_size);
+ if (ret) {
+ memset(tab, 0xFF, tab_size);
+ dev_warn(&ak8974->i2c->dev,
+ "can't read calibration data (regs %u..%zu): %d\n",
+ reg, reg + tab_size - 1, ret);
+ } else {
+ add_device_randomness(tab, tab_size);
+ }
+}
+
+static int ak8974_detect(struct ak8974 *ak8974)
+{
+ unsigned int whoami;
+ const char *name;
+ int ret;
+ unsigned int fw;
+ u16 sn;
+
+ ret = regmap_read(ak8974->map, AK8974_WHOAMI, &whoami);
+ if (ret)
+ return ret;
+
+ name = "ami305";
+
+ switch (whoami) {
+ case AK8974_WHOAMI_VALUE_AMI306:
+ name = "ami306";
+ fallthrough;
+ case AK8974_WHOAMI_VALUE_AMI305:
+ ret = regmap_read(ak8974->map, AMI305_VER, &fw);
+ if (ret)
+ return ret;
+ fw &= 0x7f; /* only bits 0 thru 6 valid */
+ ret = ak8974_get_u16_val(ak8974, AMI305_SN, &sn);
+ if (ret)
+ return ret;
+ add_device_randomness(&sn, sizeof(sn));
+ dev_info(&ak8974->i2c->dev,
+ "detected %s, FW ver %02x, S/N: %04x\n",
+ name, fw, sn);
+ break;
+ case AK8974_WHOAMI_VALUE_AK8974:
+ name = "ak8974";
+ dev_info(&ak8974->i2c->dev, "detected AK8974\n");
+ break;
+ case AK8974_WHOAMI_VALUE_HSCDTD008A:
+ name = "hscdtd008a";
+ dev_info(&ak8974->i2c->dev, "detected hscdtd008a\n");
+ break;
+ default:
+ dev_err(&ak8974->i2c->dev, "unsupported device (%02x) ",
+ whoami);
+ return -ENODEV;
+ }
+
+ ak8974->name = name;
+ ak8974->variant = whoami;
+
+ if (whoami == AK8974_WHOAMI_VALUE_AMI306) {
+ __le16 fab_data1[9], fab_data2[3];
+ int i;
+
+ ak8974_read_calib_data(ak8974, AMI306_FINEOUTPUT_X,
+ fab_data1, sizeof(fab_data1));
+ ak8974_read_calib_data(ak8974, AMI306_OFFZERO_X,
+ fab_data2, sizeof(fab_data2));
+
+ for (i = 0; i < 3; ++i) {
+ static const char axis[3] = "XYZ";
+ static const char pgaxis[6] = "ZYZXYX";
+ unsigned offz = le16_to_cpu(fab_data2[i]) & 0x7F;
+ unsigned fine = le16_to_cpu(fab_data1[i]);
+ unsigned sens = le16_to_cpu(fab_data1[i + 3]);
+ unsigned pgain1 = le16_to_cpu(fab_data1[i + 6]);
+ unsigned pgain2 = pgain1 >> 8;
+
+ pgain1 &= 0xFF;
+
+ dev_info(&ak8974->i2c->dev,
+ "factory calibration for axis %c: offz=%u sens=%u fine=%u pga%c=%u pga%c=%u\n",
+ axis[i], offz, sens, fine, pgaxis[i * 2],
+ pgain1, pgaxis[i * 2 + 1], pgain2);
+ }
+ }
+
+ return 0;
+}
+
+static int ak8974_measure_channel(struct ak8974 *ak8974, unsigned long address,
+ int *val)
+{
+ __le16 hw_values[3];
+ int ret;
+
+ pm_runtime_get_sync(&ak8974->i2c->dev);
+ mutex_lock(&ak8974->lock);
+
+ /*
+ * We read all axes and discard all but one, for optimized
+ * reading, use the triggered buffer.
+ */
+ ret = ak8974_trigmeas(ak8974);
+ if (ret)
+ goto out_unlock;
+ ret = ak8974_getresult(ak8974, hw_values);
+ if (ret)
+ goto out_unlock;
+ /*
+ * This explicit cast to (s16) is necessary as the measurement
+ * is done in 2's complement with positive and negative values.
+ * The follwing assignment to *val will then convert the signed
+ * s16 value to a signed int value.
+ */
+ *val = (s16)le16_to_cpu(hw_values[address]);
+out_unlock:
+ mutex_unlock(&ak8974->lock);
+ pm_runtime_mark_last_busy(&ak8974->i2c->dev);
+ pm_runtime_put_autosuspend(&ak8974->i2c->dev);
+
+ return ret;
+}
+
+static int ak8974_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2,
+ long mask)
+{
+ struct ak8974 *ak8974 = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->address > 2) {
+ dev_err(&ak8974->i2c->dev, "faulty channel address\n");
+ return -EIO;
+ }
+ ret = ak8974_measure_channel(ak8974, chan->address, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (ak8974->variant) {
+ case AK8974_WHOAMI_VALUE_AMI306:
+ case AK8974_WHOAMI_VALUE_AMI305:
+ /*
+ * The datasheet for AMI305 and AMI306, page 6
+ * specifies the range of the sensor to be
+ * +/- 12 Gauss.
+ */
+ *val = 12;
+ /*
+ * 12 bits are used, +/- 2^11
+ * [ -2048 .. 2047 ] (manual page 20)
+ * [ 0xf800 .. 0x07ff ]
+ */
+ *val2 = 11;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case AK8974_WHOAMI_VALUE_HSCDTD008A:
+ /*
+ * The datasheet for HSCDTF008A, page 3 specifies the
+ * range of the sensor as +/- 2.4 mT per axis, which
+ * corresponds to +/- 2400 uT = +/- 24 Gauss.
+ */
+ *val = 24;
+ /*
+ * 15 bits are used (set up in CTRL4), +/- 2^14
+ * [ -16384 .. 16383 ] (manual page 24)
+ * [ 0xc000 .. 0x3fff ]
+ */
+ *val2 = 14;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ /* GUESSING +/- 12 Gauss */
+ *val = 12;
+ /* GUESSING 12 bits ADC +/- 2^11 */
+ *val2 = 11;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ }
+ break;
+ default:
+ /* Unknown request */
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static void ak8974_fill_buffer(struct iio_dev *indio_dev)
+{
+ struct ak8974 *ak8974 = iio_priv(indio_dev);
+ int ret;
+
+ pm_runtime_get_sync(&ak8974->i2c->dev);
+ mutex_lock(&ak8974->lock);
+
+ ret = ak8974_trigmeas(ak8974);
+ if (ret) {
+ dev_err(&ak8974->i2c->dev, "error triggering measure\n");
+ goto out_unlock;
+ }
+ ret = ak8974_getresult(ak8974, ak8974->scan.channels);
+ if (ret) {
+ dev_err(&ak8974->i2c->dev, "error getting measures\n");
+ goto out_unlock;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &ak8974->scan,
+ iio_get_time_ns(indio_dev));
+
+ out_unlock:
+ mutex_unlock(&ak8974->lock);
+ pm_runtime_mark_last_busy(&ak8974->i2c->dev);
+ pm_runtime_put_autosuspend(&ak8974->i2c->dev);
+}
+
+static irqreturn_t ak8974_handle_trigger(int irq, void *p)
+{
+ const struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+
+ ak8974_fill_buffer(indio_dev);
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_mount_matrix *
+ak8974_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ak8974 *ak8974 = iio_priv(indio_dev);
+
+ return &ak8974->orientation;
+}
+
+static const struct iio_chan_spec_ext_info ak8974_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, ak8974_get_mount_matrix),
+ { },
+};
+
+#define AK8974_AXIS_CHANNEL(axis, index, bits) \
+ { \
+ .type = IIO_MAGN, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .ext_info = ak8974_ext_info, \
+ .address = index, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = bits, \
+ .storagebits = 16, \
+ .endianness = IIO_LE \
+ }, \
+ }
+
+/*
+ * We have no datasheet for the AK8974 but we guess that its
+ * ADC is 12 bits. The AMI305 and AMI306 certainly has 12bit
+ * ADC.
+ */
+static const struct iio_chan_spec ak8974_12_bits_channels[] = {
+ AK8974_AXIS_CHANNEL(X, 0, 12),
+ AK8974_AXIS_CHANNEL(Y, 1, 12),
+ AK8974_AXIS_CHANNEL(Z, 2, 12),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+/*
+ * The HSCDTD008A has 15 bits resolution the way we set it up
+ * in CTRL4.
+ */
+static const struct iio_chan_spec ak8974_15_bits_channels[] = {
+ AK8974_AXIS_CHANNEL(X, 0, 15),
+ AK8974_AXIS_CHANNEL(Y, 1, 15),
+ AK8974_AXIS_CHANNEL(Z, 2, 15),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const unsigned long ak8974_scan_masks[] = { 0x7, 0 };
+
+static const struct iio_info ak8974_info = {
+ .read_raw = &ak8974_read_raw,
+};
+
+static bool ak8974_writeable_reg(struct device *dev, unsigned int reg)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct iio_dev *indio_dev = i2c_get_clientdata(i2c);
+ struct ak8974 *ak8974 = iio_priv(indio_dev);
+
+ switch (reg) {
+ case AK8974_CTRL1:
+ case AK8974_CTRL2:
+ case AK8974_CTRL3:
+ case AK8974_INT_CTRL:
+ case AK8974_INT_THRES:
+ case AK8974_INT_THRES + 1:
+ return true;
+ case AK8974_PRESET:
+ case AK8974_PRESET + 1:
+ return ak8974->variant != AK8974_WHOAMI_VALUE_HSCDTD008A;
+ case AK8974_OFFSET_X:
+ case AK8974_OFFSET_X + 1:
+ case AK8974_OFFSET_Y:
+ case AK8974_OFFSET_Y + 1:
+ case AK8974_OFFSET_Z:
+ case AK8974_OFFSET_Z + 1:
+ return ak8974->variant == AK8974_WHOAMI_VALUE_AK8974 ||
+ ak8974->variant == AK8974_WHOAMI_VALUE_HSCDTD008A;
+ case AMI305_OFFSET_X:
+ case AMI305_OFFSET_X + 1:
+ case AMI305_OFFSET_Y:
+ case AMI305_OFFSET_Y + 1:
+ case AMI305_OFFSET_Z:
+ case AMI305_OFFSET_Z + 1:
+ return ak8974->variant == AK8974_WHOAMI_VALUE_AMI305 ||
+ ak8974->variant == AK8974_WHOAMI_VALUE_AMI306;
+ case AMI306_CTRL4:
+ case AMI306_CTRL4 + 1:
+ return ak8974->variant == AK8974_WHOAMI_VALUE_AMI306;
+ default:
+ return false;
+ }
+}
+
+static bool ak8974_precious_reg(struct device *dev, unsigned int reg)
+{
+ return reg == AK8974_INT_CLEAR;
+}
+
+static const struct regmap_config ak8974_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .writeable_reg = ak8974_writeable_reg,
+ .precious_reg = ak8974_precious_reg,
+};
+
+static int ak8974_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct iio_dev *indio_dev;
+ struct ak8974 *ak8974;
+ unsigned long irq_trig;
+ int irq = i2c->irq;
+ int ret;
+
+ /* Register with IIO */
+ indio_dev = devm_iio_device_alloc(&i2c->dev, sizeof(*ak8974));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ ak8974 = iio_priv(indio_dev);
+ i2c_set_clientdata(i2c, indio_dev);
+ ak8974->i2c = i2c;
+ mutex_init(&ak8974->lock);
+
+ ret = iio_read_mount_matrix(&i2c->dev, &ak8974->orientation);
+ if (ret)
+ return ret;
+
+ ak8974->regs[0].supply = ak8974_reg_avdd;
+ ak8974->regs[1].supply = ak8974_reg_dvdd;
+
+ ret = devm_regulator_bulk_get(&i2c->dev,
+ ARRAY_SIZE(ak8974->regs),
+ ak8974->regs);
+ if (ret < 0)
+ return dev_err_probe(&i2c->dev, ret, "cannot get regulators\n");
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak8974->regs), ak8974->regs);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "cannot enable regulators\n");
+ return ret;
+ }
+
+ /* Take runtime PM online */
+ pm_runtime_get_noresume(&i2c->dev);
+ pm_runtime_set_active(&i2c->dev);
+ pm_runtime_enable(&i2c->dev);
+
+ ak8974->map = devm_regmap_init_i2c(i2c, &ak8974_regmap_config);
+ if (IS_ERR(ak8974->map)) {
+ dev_err(&i2c->dev, "failed to allocate register map\n");
+ pm_runtime_put_noidle(&i2c->dev);
+ pm_runtime_disable(&i2c->dev);
+ return PTR_ERR(ak8974->map);
+ }
+
+ ret = ak8974_set_power(ak8974, AK8974_PWR_ON);
+ if (ret) {
+ dev_err(&i2c->dev, "could not power on\n");
+ goto disable_pm;
+ }
+
+ ret = ak8974_detect(ak8974);
+ if (ret) {
+ dev_err(&i2c->dev, "neither AK8974 nor AMI30x found\n");
+ goto disable_pm;
+ }
+
+ ret = ak8974_selftest(ak8974);
+ if (ret)
+ dev_err(&i2c->dev, "selftest failed (continuing anyway)\n");
+
+ ret = ak8974_reset(ak8974);
+ if (ret) {
+ dev_err(&i2c->dev, "AK8974 reset failed\n");
+ goto disable_pm;
+ }
+
+ switch (ak8974->variant) {
+ case AK8974_WHOAMI_VALUE_AMI306:
+ case AK8974_WHOAMI_VALUE_AMI305:
+ indio_dev->channels = ak8974_12_bits_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ak8974_12_bits_channels);
+ break;
+ case AK8974_WHOAMI_VALUE_HSCDTD008A:
+ indio_dev->channels = ak8974_15_bits_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ak8974_15_bits_channels);
+ break;
+ default:
+ indio_dev->channels = ak8974_12_bits_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ak8974_12_bits_channels);
+ break;
+ }
+ indio_dev->info = &ak8974_info;
+ indio_dev->available_scan_masks = ak8974_scan_masks;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->name = ak8974->name;
+
+ ret = iio_triggered_buffer_setup(indio_dev, NULL,
+ ak8974_handle_trigger,
+ NULL);
+ if (ret) {
+ dev_err(&i2c->dev, "triggered buffer setup failed\n");
+ goto disable_pm;
+ }
+
+ /* If we have a valid DRDY IRQ, make use of it */
+ if (irq > 0) {
+ irq_trig = irqd_get_trigger_type(irq_get_irq_data(irq));
+ if (irq_trig == IRQF_TRIGGER_RISING) {
+ dev_info(&i2c->dev, "enable rising edge DRDY IRQ\n");
+ } else if (irq_trig == IRQF_TRIGGER_FALLING) {
+ ak8974->drdy_active_low = true;
+ dev_info(&i2c->dev, "enable falling edge DRDY IRQ\n");
+ } else {
+ irq_trig = IRQF_TRIGGER_RISING;
+ }
+ irq_trig |= IRQF_ONESHOT;
+ irq_trig |= IRQF_SHARED;
+
+ ret = devm_request_threaded_irq(&i2c->dev,
+ irq,
+ ak8974_drdy_irq,
+ ak8974_drdy_irq_thread,
+ irq_trig,
+ ak8974->name,
+ ak8974);
+ if (ret) {
+ dev_err(&i2c->dev, "unable to request DRDY IRQ "
+ "- proceeding without IRQ\n");
+ goto no_irq;
+ }
+ ak8974->drdy_irq = true;
+ }
+
+no_irq:
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&i2c->dev, "device register failed\n");
+ goto cleanup_buffer;
+ }
+
+ pm_runtime_set_autosuspend_delay(&i2c->dev,
+ AK8974_AUTOSUSPEND_DELAY);
+ pm_runtime_use_autosuspend(&i2c->dev);
+ pm_runtime_put(&i2c->dev);
+
+ return 0;
+
+cleanup_buffer:
+ iio_triggered_buffer_cleanup(indio_dev);
+disable_pm:
+ pm_runtime_put_noidle(&i2c->dev);
+ pm_runtime_disable(&i2c->dev);
+ ak8974_set_power(ak8974, AK8974_PWR_OFF);
+ regulator_bulk_disable(ARRAY_SIZE(ak8974->regs), ak8974->regs);
+
+ return ret;
+}
+
+static void ak8974_remove(struct i2c_client *i2c)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(i2c);
+ struct ak8974 *ak8974 = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+ pm_runtime_get_sync(&i2c->dev);
+ pm_runtime_put_noidle(&i2c->dev);
+ pm_runtime_disable(&i2c->dev);
+ ak8974_set_power(ak8974, AK8974_PWR_OFF);
+ regulator_bulk_disable(ARRAY_SIZE(ak8974->regs), ak8974->regs);
+}
+
+static int ak8974_runtime_suspend(struct device *dev)
+{
+ struct ak8974 *ak8974 =
+ iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ ak8974_set_power(ak8974, AK8974_PWR_OFF);
+ regulator_bulk_disable(ARRAY_SIZE(ak8974->regs), ak8974->regs);
+
+ return 0;
+}
+
+static int ak8974_runtime_resume(struct device *dev)
+{
+ struct ak8974 *ak8974 =
+ iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak8974->regs), ak8974->regs);
+ if (ret)
+ return ret;
+ msleep(AK8974_POWERON_DELAY);
+ ret = ak8974_set_power(ak8974, AK8974_PWR_ON);
+ if (ret)
+ goto out_regulator_disable;
+
+ ret = ak8974_configure(ak8974);
+ if (ret)
+ goto out_disable_power;
+
+ return 0;
+
+out_disable_power:
+ ak8974_set_power(ak8974, AK8974_PWR_OFF);
+out_regulator_disable:
+ regulator_bulk_disable(ARRAY_SIZE(ak8974->regs), ak8974->regs);
+
+ return ret;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(ak8974_dev_pm_ops, ak8974_runtime_suspend,
+ ak8974_runtime_resume, NULL);
+
+static const struct i2c_device_id ak8974_id[] = {
+ {"ami305", 0 },
+ {"ami306", 0 },
+ {"ak8974", 0 },
+ {"hscdtd008a", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ak8974_id);
+
+static const struct of_device_id ak8974_of_match[] = {
+ { .compatible = "asahi-kasei,ak8974", },
+ { .compatible = "alps,hscdtd008a", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ak8974_of_match);
+
+static struct i2c_driver ak8974_driver = {
+ .driver = {
+ .name = "ak8974",
+ .pm = pm_ptr(&ak8974_dev_pm_ops),
+ .of_match_table = ak8974_of_match,
+ },
+ .probe = ak8974_probe,
+ .remove = ak8974_remove,
+ .id_table = ak8974_id,
+};
+module_i2c_driver(ak8974_driver);
+
+MODULE_DESCRIPTION("AK8974 and AMI30x 3-axis magnetometer driver");
+MODULE_AUTHOR("Samu Onkalo");
+MODULE_AUTHOR("Linus Walleij");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c
new file mode 100644
index 000000000..caf03a2a9
--- /dev/null
+++ b/drivers/iio/magnetometer/ak8975.c
@@ -0,0 +1,1121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A sensor driver for the magnetometer AK8975.
+ *
+ * Magnetic compass sensor driver for monitoring magnetic flux information.
+ *
+ * Copyright (c) 2010, NVIDIA Corporation.
+ */
+
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+/*
+ * Register definitions, as well as various shifts and masks to get at the
+ * individual fields of the registers.
+ */
+#define AK8975_REG_WIA 0x00
+#define AK8975_DEVICE_ID 0x48
+
+#define AK8975_REG_INFO 0x01
+
+#define AK8975_REG_ST1 0x02
+#define AK8975_REG_ST1_DRDY_SHIFT 0
+#define AK8975_REG_ST1_DRDY_MASK (1 << AK8975_REG_ST1_DRDY_SHIFT)
+
+#define AK8975_REG_HXL 0x03
+#define AK8975_REG_HXH 0x04
+#define AK8975_REG_HYL 0x05
+#define AK8975_REG_HYH 0x06
+#define AK8975_REG_HZL 0x07
+#define AK8975_REG_HZH 0x08
+#define AK8975_REG_ST2 0x09
+#define AK8975_REG_ST2_DERR_SHIFT 2
+#define AK8975_REG_ST2_DERR_MASK (1 << AK8975_REG_ST2_DERR_SHIFT)
+
+#define AK8975_REG_ST2_HOFL_SHIFT 3
+#define AK8975_REG_ST2_HOFL_MASK (1 << AK8975_REG_ST2_HOFL_SHIFT)
+
+#define AK8975_REG_CNTL 0x0A
+#define AK8975_REG_CNTL_MODE_SHIFT 0
+#define AK8975_REG_CNTL_MODE_MASK (0xF << AK8975_REG_CNTL_MODE_SHIFT)
+#define AK8975_REG_CNTL_MODE_POWER_DOWN 0x00
+#define AK8975_REG_CNTL_MODE_ONCE 0x01
+#define AK8975_REG_CNTL_MODE_SELF_TEST 0x08
+#define AK8975_REG_CNTL_MODE_FUSE_ROM 0x0F
+
+#define AK8975_REG_RSVC 0x0B
+#define AK8975_REG_ASTC 0x0C
+#define AK8975_REG_TS1 0x0D
+#define AK8975_REG_TS2 0x0E
+#define AK8975_REG_I2CDIS 0x0F
+#define AK8975_REG_ASAX 0x10
+#define AK8975_REG_ASAY 0x11
+#define AK8975_REG_ASAZ 0x12
+
+#define AK8975_MAX_REGS AK8975_REG_ASAZ
+
+/*
+ * AK09912 Register definitions
+ */
+#define AK09912_REG_WIA1 0x00
+#define AK09912_REG_WIA2 0x01
+#define AK09916_DEVICE_ID 0x09
+#define AK09912_DEVICE_ID 0x04
+#define AK09911_DEVICE_ID 0x05
+
+#define AK09911_REG_INFO1 0x02
+#define AK09911_REG_INFO2 0x03
+
+#define AK09912_REG_ST1 0x10
+
+#define AK09912_REG_ST1_DRDY_SHIFT 0
+#define AK09912_REG_ST1_DRDY_MASK (1 << AK09912_REG_ST1_DRDY_SHIFT)
+
+#define AK09912_REG_HXL 0x11
+#define AK09912_REG_HXH 0x12
+#define AK09912_REG_HYL 0x13
+#define AK09912_REG_HYH 0x14
+#define AK09912_REG_HZL 0x15
+#define AK09912_REG_HZH 0x16
+#define AK09912_REG_TMPS 0x17
+
+#define AK09912_REG_ST2 0x18
+#define AK09912_REG_ST2_HOFL_SHIFT 3
+#define AK09912_REG_ST2_HOFL_MASK (1 << AK09912_REG_ST2_HOFL_SHIFT)
+
+#define AK09912_REG_CNTL1 0x30
+
+#define AK09912_REG_CNTL2 0x31
+#define AK09912_REG_CNTL_MODE_POWER_DOWN 0x00
+#define AK09912_REG_CNTL_MODE_ONCE 0x01
+#define AK09912_REG_CNTL_MODE_SELF_TEST 0x10
+#define AK09912_REG_CNTL_MODE_FUSE_ROM 0x1F
+#define AK09912_REG_CNTL2_MODE_SHIFT 0
+#define AK09912_REG_CNTL2_MODE_MASK (0x1F << AK09912_REG_CNTL2_MODE_SHIFT)
+
+#define AK09912_REG_CNTL3 0x32
+
+#define AK09912_REG_TS1 0x33
+#define AK09912_REG_TS2 0x34
+#define AK09912_REG_TS3 0x35
+#define AK09912_REG_I2CDIS 0x36
+#define AK09912_REG_TS4 0x37
+
+#define AK09912_REG_ASAX 0x60
+#define AK09912_REG_ASAY 0x61
+#define AK09912_REG_ASAZ 0x62
+
+#define AK09912_MAX_REGS AK09912_REG_ASAZ
+
+/*
+ * Miscellaneous values.
+ */
+#define AK8975_MAX_CONVERSION_TIMEOUT 500
+#define AK8975_CONVERSION_DONE_POLL_TIME 10
+#define AK8975_DATA_READY_TIMEOUT ((100*HZ)/1000)
+
+/*
+ * Precalculate scale factor (in Gauss units) for each axis and
+ * store in the device data.
+ *
+ * This scale factor is axis-dependent, and is derived from 3 calibration
+ * factors ASA(x), ASA(y), and ASA(z).
+ *
+ * These ASA values are read from the sensor device at start of day, and
+ * cached in the device context struct.
+ *
+ * Adjusting the flux value with the sensitivity adjustment value should be
+ * done via the following formula:
+ *
+ * Hadj = H * ( ( ( (ASA-128)*0.5 ) / 128 ) + 1 )
+ * where H is the raw value, ASA is the sensitivity adjustment, and Hadj
+ * is the resultant adjusted value.
+ *
+ * We reduce the formula to:
+ *
+ * Hadj = H * (ASA + 128) / 256
+ *
+ * H is in the range of -4096 to 4095. The magnetometer has a range of
+ * +-1229uT. To go from the raw value to uT is:
+ *
+ * HuT = H * 1229/4096, or roughly, 3/10.
+ *
+ * Since 1uT = 0.01 gauss, our final scale factor becomes:
+ *
+ * Hadj = H * ((ASA + 128) / 256) * 3/10 * 1/100
+ * Hadj = H * ((ASA + 128) * 0.003) / 256
+ *
+ * Since ASA doesn't change, we cache the resultant scale factor into the
+ * device context in ak8975_setup().
+ *
+ * Given we use IIO_VAL_INT_PLUS_MICRO bit when displaying the scale, we
+ * multiply the stored scale value by 1e6.
+ */
+static long ak8975_raw_to_gauss(u16 data)
+{
+ return (((long)data + 128) * 3000) / 256;
+}
+
+/*
+ * For AK8963 and AK09911, same calculation, but the device is less sensitive:
+ *
+ * H is in the range of +-8190. The magnetometer has a range of
+ * +-4912uT. To go from the raw value to uT is:
+ *
+ * HuT = H * 4912/8190, or roughly, 6/10, instead of 3/10.
+ */
+
+static long ak8963_09911_raw_to_gauss(u16 data)
+{
+ return (((long)data + 128) * 6000) / 256;
+}
+
+/*
+ * For AK09912, same calculation, except the device is more sensitive:
+ *
+ * H is in the range of -32752 to 32752. The magnetometer has a range of
+ * +-4912uT. To go from the raw value to uT is:
+ *
+ * HuT = H * 4912/32752, or roughly, 3/20, instead of 3/10.
+ */
+static long ak09912_raw_to_gauss(u16 data)
+{
+ return (((long)data + 128) * 1500) / 256;
+}
+
+/* Compatible Asahi Kasei Compass parts */
+enum asahi_compass_chipset {
+ AKXXXX = 0,
+ AK8975,
+ AK8963,
+ AK09911,
+ AK09912,
+ AK09916,
+};
+
+enum ak_ctrl_reg_addr {
+ ST1,
+ ST2,
+ CNTL,
+ ASA_BASE,
+ MAX_REGS,
+ REGS_END,
+};
+
+enum ak_ctrl_reg_mask {
+ ST1_DRDY,
+ ST2_HOFL,
+ ST2_DERR,
+ CNTL_MODE,
+ MASK_END,
+};
+
+enum ak_ctrl_mode {
+ POWER_DOWN,
+ MODE_ONCE,
+ SELF_TEST,
+ FUSE_ROM,
+ MODE_END,
+};
+
+struct ak_def {
+ enum asahi_compass_chipset type;
+ long (*raw_to_gauss)(u16 data);
+ u16 range;
+ u8 ctrl_regs[REGS_END];
+ u8 ctrl_masks[MASK_END];
+ u8 ctrl_modes[MODE_END];
+ u8 data_regs[3];
+};
+
+static const struct ak_def ak_def_array[] = {
+ {
+ .type = AK8975,
+ .raw_to_gauss = ak8975_raw_to_gauss,
+ .range = 4096,
+ .ctrl_regs = {
+ AK8975_REG_ST1,
+ AK8975_REG_ST2,
+ AK8975_REG_CNTL,
+ AK8975_REG_ASAX,
+ AK8975_MAX_REGS},
+ .ctrl_masks = {
+ AK8975_REG_ST1_DRDY_MASK,
+ AK8975_REG_ST2_HOFL_MASK,
+ AK8975_REG_ST2_DERR_MASK,
+ AK8975_REG_CNTL_MODE_MASK},
+ .ctrl_modes = {
+ AK8975_REG_CNTL_MODE_POWER_DOWN,
+ AK8975_REG_CNTL_MODE_ONCE,
+ AK8975_REG_CNTL_MODE_SELF_TEST,
+ AK8975_REG_CNTL_MODE_FUSE_ROM},
+ .data_regs = {
+ AK8975_REG_HXL,
+ AK8975_REG_HYL,
+ AK8975_REG_HZL},
+ },
+ {
+ .type = AK8963,
+ .raw_to_gauss = ak8963_09911_raw_to_gauss,
+ .range = 8190,
+ .ctrl_regs = {
+ AK8975_REG_ST1,
+ AK8975_REG_ST2,
+ AK8975_REG_CNTL,
+ AK8975_REG_ASAX,
+ AK8975_MAX_REGS},
+ .ctrl_masks = {
+ AK8975_REG_ST1_DRDY_MASK,
+ AK8975_REG_ST2_HOFL_MASK,
+ 0,
+ AK8975_REG_CNTL_MODE_MASK},
+ .ctrl_modes = {
+ AK8975_REG_CNTL_MODE_POWER_DOWN,
+ AK8975_REG_CNTL_MODE_ONCE,
+ AK8975_REG_CNTL_MODE_SELF_TEST,
+ AK8975_REG_CNTL_MODE_FUSE_ROM},
+ .data_regs = {
+ AK8975_REG_HXL,
+ AK8975_REG_HYL,
+ AK8975_REG_HZL},
+ },
+ {
+ .type = AK09911,
+ .raw_to_gauss = ak8963_09911_raw_to_gauss,
+ .range = 8192,
+ .ctrl_regs = {
+ AK09912_REG_ST1,
+ AK09912_REG_ST2,
+ AK09912_REG_CNTL2,
+ AK09912_REG_ASAX,
+ AK09912_MAX_REGS},
+ .ctrl_masks = {
+ AK09912_REG_ST1_DRDY_MASK,
+ AK09912_REG_ST2_HOFL_MASK,
+ 0,
+ AK09912_REG_CNTL2_MODE_MASK},
+ .ctrl_modes = {
+ AK09912_REG_CNTL_MODE_POWER_DOWN,
+ AK09912_REG_CNTL_MODE_ONCE,
+ AK09912_REG_CNTL_MODE_SELF_TEST,
+ AK09912_REG_CNTL_MODE_FUSE_ROM},
+ .data_regs = {
+ AK09912_REG_HXL,
+ AK09912_REG_HYL,
+ AK09912_REG_HZL},
+ },
+ {
+ .type = AK09912,
+ .raw_to_gauss = ak09912_raw_to_gauss,
+ .range = 32752,
+ .ctrl_regs = {
+ AK09912_REG_ST1,
+ AK09912_REG_ST2,
+ AK09912_REG_CNTL2,
+ AK09912_REG_ASAX,
+ AK09912_MAX_REGS},
+ .ctrl_masks = {
+ AK09912_REG_ST1_DRDY_MASK,
+ AK09912_REG_ST2_HOFL_MASK,
+ 0,
+ AK09912_REG_CNTL2_MODE_MASK},
+ .ctrl_modes = {
+ AK09912_REG_CNTL_MODE_POWER_DOWN,
+ AK09912_REG_CNTL_MODE_ONCE,
+ AK09912_REG_CNTL_MODE_SELF_TEST,
+ AK09912_REG_CNTL_MODE_FUSE_ROM},
+ .data_regs = {
+ AK09912_REG_HXL,
+ AK09912_REG_HYL,
+ AK09912_REG_HZL},
+ },
+ {
+ .type = AK09916,
+ .raw_to_gauss = ak09912_raw_to_gauss,
+ .range = 32752,
+ .ctrl_regs = {
+ AK09912_REG_ST1,
+ AK09912_REG_ST2,
+ AK09912_REG_CNTL2,
+ AK09912_REG_ASAX,
+ AK09912_MAX_REGS},
+ .ctrl_masks = {
+ AK09912_REG_ST1_DRDY_MASK,
+ AK09912_REG_ST2_HOFL_MASK,
+ 0,
+ AK09912_REG_CNTL2_MODE_MASK},
+ .ctrl_modes = {
+ AK09912_REG_CNTL_MODE_POWER_DOWN,
+ AK09912_REG_CNTL_MODE_ONCE,
+ AK09912_REG_CNTL_MODE_SELF_TEST,
+ AK09912_REG_CNTL_MODE_FUSE_ROM},
+ .data_regs = {
+ AK09912_REG_HXL,
+ AK09912_REG_HYL,
+ AK09912_REG_HZL},
+ }
+};
+
+/*
+ * Per-instance context data for the device.
+ */
+struct ak8975_data {
+ struct i2c_client *client;
+ const struct ak_def *def;
+ struct mutex lock;
+ u8 asa[3];
+ long raw_to_gauss[3];
+ struct gpio_desc *eoc_gpiod;
+ struct gpio_desc *reset_gpiod;
+ int eoc_irq;
+ wait_queue_head_t data_ready_queue;
+ unsigned long flags;
+ u8 cntl_cache;
+ struct iio_mount_matrix orientation;
+ struct regulator *vdd;
+ struct regulator *vid;
+
+ /* Ensure natural alignment of timestamp */
+ struct {
+ s16 channels[3];
+ s64 ts __aligned(8);
+ } scan;
+};
+
+/* Enable attached power regulator if any. */
+static int ak8975_power_on(const struct ak8975_data *data)
+{
+ int ret;
+
+ ret = regulator_enable(data->vdd);
+ if (ret) {
+ dev_warn(&data->client->dev,
+ "Failed to enable specified Vdd supply\n");
+ return ret;
+ }
+ ret = regulator_enable(data->vid);
+ if (ret) {
+ dev_warn(&data->client->dev,
+ "Failed to enable specified Vid supply\n");
+ regulator_disable(data->vdd);
+ return ret;
+ }
+
+ gpiod_set_value_cansleep(data->reset_gpiod, 0);
+
+ /*
+ * According to the datasheet the power supply rise time is 200us
+ * and the minimum wait time before mode setting is 100us, in
+ * total 300us. Add some margin and say minimum 500us here.
+ */
+ usleep_range(500, 1000);
+ return 0;
+}
+
+/* Disable attached power regulator if any. */
+static void ak8975_power_off(const struct ak8975_data *data)
+{
+ gpiod_set_value_cansleep(data->reset_gpiod, 1);
+
+ regulator_disable(data->vid);
+ regulator_disable(data->vdd);
+}
+
+/*
+ * Return 0 if the i2c device is the one we expect.
+ * return a negative error number otherwise
+ */
+static int ak8975_who_i_am(struct i2c_client *client,
+ enum asahi_compass_chipset type)
+{
+ u8 wia_val[2];
+ int ret;
+
+ /*
+ * Signature for each device:
+ * Device | WIA1 | WIA2
+ * AK09916 | DEVICE_ID_| AK09916_DEVICE_ID
+ * AK09912 | DEVICE_ID | AK09912_DEVICE_ID
+ * AK09911 | DEVICE_ID | AK09911_DEVICE_ID
+ * AK8975 | DEVICE_ID | NA
+ * AK8963 | DEVICE_ID | NA
+ */
+ ret = i2c_smbus_read_i2c_block_data_or_emulated(
+ client, AK09912_REG_WIA1, 2, wia_val);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error reading WIA\n");
+ return ret;
+ }
+
+ if (wia_val[0] != AK8975_DEVICE_ID)
+ return -ENODEV;
+
+ switch (type) {
+ case AK8975:
+ case AK8963:
+ return 0;
+ case AK09911:
+ if (wia_val[1] == AK09911_DEVICE_ID)
+ return 0;
+ break;
+ case AK09912:
+ if (wia_val[1] == AK09912_DEVICE_ID)
+ return 0;
+ break;
+ case AK09916:
+ if (wia_val[1] == AK09916_DEVICE_ID)
+ return 0;
+ break;
+ default:
+ dev_err(&client->dev, "Type %d unknown\n", type);
+ }
+ return -ENODEV;
+}
+
+/*
+ * Helper function to write to CNTL register.
+ */
+static int ak8975_set_mode(struct ak8975_data *data, enum ak_ctrl_mode mode)
+{
+ u8 regval;
+ int ret;
+
+ regval = (data->cntl_cache & ~data->def->ctrl_masks[CNTL_MODE]) |
+ data->def->ctrl_modes[mode];
+ ret = i2c_smbus_write_byte_data(data->client,
+ data->def->ctrl_regs[CNTL], regval);
+ if (ret < 0) {
+ return ret;
+ }
+ data->cntl_cache = regval;
+ /* After mode change wait atleast 100us */
+ usleep_range(100, 500);
+
+ return 0;
+}
+
+/*
+ * Handle data ready irq
+ */
+static irqreturn_t ak8975_irq_handler(int irq, void *data)
+{
+ struct ak8975_data *ak8975 = data;
+
+ set_bit(0, &ak8975->flags);
+ wake_up(&ak8975->data_ready_queue);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Install data ready interrupt handler
+ */
+static int ak8975_setup_irq(struct ak8975_data *data)
+{
+ struct i2c_client *client = data->client;
+ int rc;
+ int irq;
+
+ init_waitqueue_head(&data->data_ready_queue);
+ clear_bit(0, &data->flags);
+ if (client->irq)
+ irq = client->irq;
+ else
+ irq = gpiod_to_irq(data->eoc_gpiod);
+
+ rc = devm_request_irq(&client->dev, irq, ak8975_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ dev_name(&client->dev), data);
+ if (rc < 0) {
+ dev_err(&client->dev, "irq %d request failed: %d\n", irq, rc);
+ return rc;
+ }
+
+ data->eoc_irq = irq;
+
+ return rc;
+}
+
+
+/*
+ * Perform some start-of-day setup, including reading the asa calibration
+ * values and caching them.
+ */
+static int ak8975_setup(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct ak8975_data *data = iio_priv(indio_dev);
+ int ret;
+
+ /* Write the fused rom access mode. */
+ ret = ak8975_set_mode(data, FUSE_ROM);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error in setting fuse access mode\n");
+ return ret;
+ }
+
+ /* Get asa data and store in the device data. */
+ ret = i2c_smbus_read_i2c_block_data_or_emulated(
+ client, data->def->ctrl_regs[ASA_BASE],
+ 3, data->asa);
+ if (ret < 0) {
+ dev_err(&client->dev, "Not able to read asa data\n");
+ return ret;
+ }
+
+ /* After reading fuse ROM data set power-down mode */
+ ret = ak8975_set_mode(data, POWER_DOWN);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error in setting power-down mode\n");
+ return ret;
+ }
+
+ if (data->eoc_gpiod || client->irq > 0) {
+ ret = ak8975_setup_irq(data);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Error setting data ready interrupt\n");
+ return ret;
+ }
+ }
+
+ data->raw_to_gauss[0] = data->def->raw_to_gauss(data->asa[0]);
+ data->raw_to_gauss[1] = data->def->raw_to_gauss(data->asa[1]);
+ data->raw_to_gauss[2] = data->def->raw_to_gauss(data->asa[2]);
+
+ return 0;
+}
+
+static int wait_conversion_complete_gpio(struct ak8975_data *data)
+{
+ struct i2c_client *client = data->client;
+ u32 timeout_ms = AK8975_MAX_CONVERSION_TIMEOUT;
+ int ret;
+
+ /* Wait for the conversion to complete. */
+ while (timeout_ms) {
+ msleep(AK8975_CONVERSION_DONE_POLL_TIME);
+ if (gpiod_get_value(data->eoc_gpiod))
+ break;
+ timeout_ms -= AK8975_CONVERSION_DONE_POLL_TIME;
+ }
+ if (!timeout_ms) {
+ dev_err(&client->dev, "Conversion timeout happened\n");
+ return -EINVAL;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, data->def->ctrl_regs[ST1]);
+ if (ret < 0)
+ dev_err(&client->dev, "Error in reading ST1\n");
+
+ return ret;
+}
+
+static int wait_conversion_complete_polled(struct ak8975_data *data)
+{
+ struct i2c_client *client = data->client;
+ u8 read_status;
+ u32 timeout_ms = AK8975_MAX_CONVERSION_TIMEOUT;
+ int ret;
+
+ /* Wait for the conversion to complete. */
+ while (timeout_ms) {
+ msleep(AK8975_CONVERSION_DONE_POLL_TIME);
+ ret = i2c_smbus_read_byte_data(client,
+ data->def->ctrl_regs[ST1]);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error in reading ST1\n");
+ return ret;
+ }
+ read_status = ret;
+ if (read_status)
+ break;
+ timeout_ms -= AK8975_CONVERSION_DONE_POLL_TIME;
+ }
+ if (!timeout_ms) {
+ dev_err(&client->dev, "Conversion timeout happened\n");
+ return -EINVAL;
+ }
+
+ return read_status;
+}
+
+/* Returns 0 if the end of conversion interrupt occured or -ETIME otherwise */
+static int wait_conversion_complete_interrupt(struct ak8975_data *data)
+{
+ int ret;
+
+ ret = wait_event_timeout(data->data_ready_queue,
+ test_bit(0, &data->flags),
+ AK8975_DATA_READY_TIMEOUT);
+ clear_bit(0, &data->flags);
+
+ return ret > 0 ? 0 : -ETIME;
+}
+
+static int ak8975_start_read_axis(struct ak8975_data *data,
+ const struct i2c_client *client)
+{
+ /* Set up the device for taking a sample. */
+ int ret = ak8975_set_mode(data, MODE_ONCE);
+
+ if (ret < 0) {
+ dev_err(&client->dev, "Error in setting operating mode\n");
+ return ret;
+ }
+
+ /* Wait for the conversion to complete. */
+ if (data->eoc_irq)
+ ret = wait_conversion_complete_interrupt(data);
+ else if (data->eoc_gpiod)
+ ret = wait_conversion_complete_gpio(data);
+ else
+ ret = wait_conversion_complete_polled(data);
+ if (ret < 0)
+ return ret;
+
+ /* This will be executed only for non-interrupt based waiting case */
+ if (ret & data->def->ctrl_masks[ST1_DRDY]) {
+ ret = i2c_smbus_read_byte_data(client,
+ data->def->ctrl_regs[ST2]);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error in reading ST2\n");
+ return ret;
+ }
+ if (ret & (data->def->ctrl_masks[ST2_DERR] |
+ data->def->ctrl_masks[ST2_HOFL])) {
+ dev_err(&client->dev, "ST2 status error 0x%x\n", ret);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* Retrieve raw flux value for one of the x, y, or z axis. */
+static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
+{
+ struct ak8975_data *data = iio_priv(indio_dev);
+ const struct i2c_client *client = data->client;
+ const struct ak_def *def = data->def;
+ __le16 rval;
+ u16 buff;
+ int ret;
+
+ pm_runtime_get_sync(&data->client->dev);
+
+ mutex_lock(&data->lock);
+
+ ret = ak8975_start_read_axis(data, client);
+ if (ret)
+ goto exit;
+
+ ret = i2c_smbus_read_i2c_block_data_or_emulated(
+ client, def->data_regs[index],
+ sizeof(rval), (u8*)&rval);
+ if (ret < 0)
+ goto exit;
+
+ mutex_unlock(&data->lock);
+
+ pm_runtime_mark_last_busy(&data->client->dev);
+ pm_runtime_put_autosuspend(&data->client->dev);
+
+ /* Swap bytes and convert to valid range. */
+ buff = le16_to_cpu(rval);
+ *val = clamp_t(s16, buff, -def->range, def->range);
+ return IIO_VAL_INT;
+
+exit:
+ mutex_unlock(&data->lock);
+ dev_err(&client->dev, "Error in reading axis\n");
+ return ret;
+}
+
+static int ak8975_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2,
+ long mask)
+{
+ struct ak8975_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return ak8975_read_axis(indio_dev, chan->address, val);
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = data->raw_to_gauss[chan->address];
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+ return -EINVAL;
+}
+
+static const struct iio_mount_matrix *
+ak8975_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ak8975_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info ak8975_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, ak8975_get_mount_matrix),
+ { }
+};
+
+#define AK8975_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), \
+ .address = index, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU \
+ }, \
+ .ext_info = ak8975_ext_info, \
+ }
+
+static const struct iio_chan_spec ak8975_channels[] = {
+ AK8975_CHANNEL(X, 0), AK8975_CHANNEL(Y, 1), AK8975_CHANNEL(Z, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const unsigned long ak8975_scan_masks[] = { 0x7, 0 };
+
+static const struct iio_info ak8975_info = {
+ .read_raw = &ak8975_read_raw,
+};
+
+static const struct acpi_device_id ak_acpi_match[] = {
+ {"AK8975", AK8975},
+ {"AK8963", AK8963},
+ {"INVN6500", AK8963},
+ {"AK009911", AK09911},
+ {"AK09911", AK09911},
+ {"AKM9911", AK09911},
+ {"AK09912", AK09912},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, ak_acpi_match);
+
+static void ak8975_fill_buffer(struct iio_dev *indio_dev)
+{
+ struct ak8975_data *data = iio_priv(indio_dev);
+ const struct i2c_client *client = data->client;
+ const struct ak_def *def = data->def;
+ int ret;
+ __le16 fval[3];
+
+ mutex_lock(&data->lock);
+
+ ret = ak8975_start_read_axis(data, client);
+ if (ret)
+ goto unlock;
+
+ /*
+ * For each axis, read the flux value from the appropriate register
+ * (the register is specified in the iio device attributes).
+ */
+ ret = i2c_smbus_read_i2c_block_data_or_emulated(client,
+ def->data_regs[0],
+ 3 * sizeof(fval[0]),
+ (u8 *)fval);
+ if (ret < 0)
+ goto unlock;
+
+ mutex_unlock(&data->lock);
+
+ /* Clamp to valid range. */
+ data->scan.channels[0] = clamp_t(s16, le16_to_cpu(fval[0]), -def->range, def->range);
+ data->scan.channels[1] = clamp_t(s16, le16_to_cpu(fval[1]), -def->range, def->range);
+ data->scan.channels[2] = clamp_t(s16, le16_to_cpu(fval[2]), -def->range, def->range);
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_get_time_ns(indio_dev));
+
+ return;
+
+unlock:
+ mutex_unlock(&data->lock);
+ dev_err(&client->dev, "Error in reading axes block\n");
+}
+
+static irqreturn_t ak8975_handle_trigger(int irq, void *p)
+{
+ const struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+
+ ak8975_fill_buffer(indio_dev);
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+static int ak8975_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ak8975_data *data;
+ struct iio_dev *indio_dev;
+ struct gpio_desc *eoc_gpiod;
+ struct gpio_desc *reset_gpiod;
+ const void *match;
+ unsigned int i;
+ int err;
+ enum asahi_compass_chipset chipset;
+ const char *name = NULL;
+
+ /*
+ * Grab and set up the supplied GPIO.
+ * We may not have a GPIO based IRQ to scan, that is fine, we will
+ * poll if so.
+ */
+ eoc_gpiod = devm_gpiod_get_optional(&client->dev, NULL, GPIOD_IN);
+ if (IS_ERR(eoc_gpiod))
+ return PTR_ERR(eoc_gpiod);
+ if (eoc_gpiod)
+ gpiod_set_consumer_name(eoc_gpiod, "ak_8975");
+
+ /*
+ * According to AK09911 datasheet, if reset GPIO is provided then
+ * deassert reset on ak8975_power_on() and assert reset on
+ * ak8975_power_off().
+ */
+ reset_gpiod = devm_gpiod_get_optional(&client->dev,
+ "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset_gpiod))
+ return PTR_ERR(reset_gpiod);
+
+ /* Register with IIO */
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+
+ data->client = client;
+ data->eoc_gpiod = eoc_gpiod;
+ data->reset_gpiod = reset_gpiod;
+ data->eoc_irq = 0;
+
+ err = iio_read_mount_matrix(&client->dev, &data->orientation);
+ if (err)
+ return err;
+
+ /* id will be NULL when enumerated via ACPI */
+ match = device_get_match_data(&client->dev);
+ if (match) {
+ chipset = (uintptr_t)match;
+ name = dev_name(&client->dev);
+ } else if (id) {
+ chipset = (enum asahi_compass_chipset)(id->driver_data);
+ name = id->name;
+ } else
+ return -ENOSYS;
+
+ for (i = 0; i < ARRAY_SIZE(ak_def_array); i++)
+ if (ak_def_array[i].type == chipset)
+ break;
+
+ if (i == ARRAY_SIZE(ak_def_array)) {
+ dev_err(&client->dev, "AKM device type unsupported: %d\n",
+ chipset);
+ return -ENODEV;
+ }
+
+ data->def = &ak_def_array[i];
+
+ /* Fetch the regulators */
+ data->vdd = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(data->vdd))
+ return PTR_ERR(data->vdd);
+ data->vid = devm_regulator_get(&client->dev, "vid");
+ if (IS_ERR(data->vid))
+ return PTR_ERR(data->vid);
+
+ err = ak8975_power_on(data);
+ if (err)
+ return err;
+
+ err = ak8975_who_i_am(client, data->def->type);
+ if (err < 0) {
+ dev_err(&client->dev, "Unexpected device\n");
+ goto power_off;
+ }
+ dev_dbg(&client->dev, "Asahi compass chip %s\n", name);
+
+ /* Perform some basic start-of-day setup of the device. */
+ err = ak8975_setup(client);
+ if (err < 0) {
+ dev_err(&client->dev, "%s initialization fails\n", name);
+ goto power_off;
+ }
+
+ mutex_init(&data->lock);
+ indio_dev->channels = ak8975_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ak8975_channels);
+ indio_dev->info = &ak8975_info;
+ indio_dev->available_scan_masks = ak8975_scan_masks;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->name = name;
+
+ err = iio_triggered_buffer_setup(indio_dev, NULL, ak8975_handle_trigger,
+ NULL);
+ if (err) {
+ dev_err(&client->dev, "triggered buffer setup failed\n");
+ goto power_off;
+ }
+
+ err = iio_device_register(indio_dev);
+ if (err) {
+ dev_err(&client->dev, "device register failed\n");
+ goto cleanup_buffer;
+ }
+
+ /* Enable runtime PM */
+ pm_runtime_get_noresume(&client->dev);
+ pm_runtime_set_active(&client->dev);
+ pm_runtime_enable(&client->dev);
+ /*
+ * The device comes online in 500us, so add two orders of magnitude
+ * of delay before autosuspending: 50 ms.
+ */
+ pm_runtime_set_autosuspend_delay(&client->dev, 50);
+ pm_runtime_use_autosuspend(&client->dev);
+ pm_runtime_put(&client->dev);
+
+ return 0;
+
+cleanup_buffer:
+ iio_triggered_buffer_cleanup(indio_dev);
+power_off:
+ ak8975_power_off(data);
+ return err;
+}
+
+static void ak8975_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct ak8975_data *data = iio_priv(indio_dev);
+
+ pm_runtime_get_sync(&client->dev);
+ pm_runtime_put_noidle(&client->dev);
+ pm_runtime_disable(&client->dev);
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+ ak8975_set_mode(data, POWER_DOWN);
+ ak8975_power_off(data);
+}
+
+static int ak8975_runtime_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct ak8975_data *data = iio_priv(indio_dev);
+ int ret;
+
+ /* Set the device in power down if it wasn't already */
+ ret = ak8975_set_mode(data, POWER_DOWN);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error in setting power-down mode\n");
+ return ret;
+ }
+ /* Next cut the regulators */
+ ak8975_power_off(data);
+
+ return 0;
+}
+
+static int ak8975_runtime_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct ak8975_data *data = iio_priv(indio_dev);
+ int ret;
+
+ /* Take up the regulators */
+ ak8975_power_on(data);
+ /*
+ * We come up in powered down mode, the reading routines will
+ * put us in the mode to read values later.
+ */
+ ret = ak8975_set_mode(data, POWER_DOWN);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error in setting power-down mode\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(ak8975_dev_pm_ops, ak8975_runtime_suspend,
+ ak8975_runtime_resume, NULL);
+
+static const struct i2c_device_id ak8975_id[] = {
+ {"ak8975", AK8975},
+ {"ak8963", AK8963},
+ {"AK8963", AK8963},
+ {"ak09911", AK09911},
+ {"ak09912", AK09912},
+ {"ak09916", AK09916},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, ak8975_id);
+
+static const struct of_device_id ak8975_of_match[] = {
+ { .compatible = "asahi-kasei,ak8975", },
+ { .compatible = "ak8975", },
+ { .compatible = "asahi-kasei,ak8963", },
+ { .compatible = "ak8963", },
+ { .compatible = "asahi-kasei,ak09911", },
+ { .compatible = "ak09911", },
+ { .compatible = "asahi-kasei,ak09912", },
+ { .compatible = "ak09912", },
+ { .compatible = "asahi-kasei,ak09916", },
+ { .compatible = "ak09916", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ak8975_of_match);
+
+static struct i2c_driver ak8975_driver = {
+ .driver = {
+ .name = "ak8975",
+ .pm = pm_ptr(&ak8975_dev_pm_ops),
+ .of_match_table = ak8975_of_match,
+ .acpi_match_table = ak_acpi_match,
+ },
+ .probe = ak8975_probe,
+ .remove = ak8975_remove,
+ .id_table = ak8975_id,
+};
+module_i2c_driver(ak8975_driver);
+
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_DESCRIPTION("AK8975 magnetometer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c
new file mode 100644
index 000000000..06d5a1ef1
--- /dev/null
+++ b/drivers/iio/magnetometer/bmc150_magn.c
@@ -0,0 +1,1084 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Bosch BMC150 three-axis magnetic field sensor driver
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This code is based on bmm050_api.c authored by contact@bosch.sensortec.com:
+ *
+ * (C) Copyright 2011~2014 Bosch Sensortec GmbH All Rights Reserved
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include "bmc150_magn.h"
+
+#define BMC150_MAGN_DRV_NAME "bmc150_magn"
+#define BMC150_MAGN_IRQ_NAME "bmc150_magn_event"
+
+#define BMC150_MAGN_REG_CHIP_ID 0x40
+#define BMC150_MAGN_CHIP_ID_VAL 0x32
+
+#define BMC150_MAGN_REG_X_L 0x42
+#define BMC150_MAGN_REG_X_M 0x43
+#define BMC150_MAGN_REG_Y_L 0x44
+#define BMC150_MAGN_REG_Y_M 0x45
+#define BMC150_MAGN_SHIFT_XY_L 3
+#define BMC150_MAGN_REG_Z_L 0x46
+#define BMC150_MAGN_REG_Z_M 0x47
+#define BMC150_MAGN_SHIFT_Z_L 1
+#define BMC150_MAGN_REG_RHALL_L 0x48
+#define BMC150_MAGN_REG_RHALL_M 0x49
+#define BMC150_MAGN_SHIFT_RHALL_L 2
+
+#define BMC150_MAGN_REG_INT_STATUS 0x4A
+
+#define BMC150_MAGN_REG_POWER 0x4B
+#define BMC150_MAGN_MASK_POWER_CTL BIT(0)
+
+#define BMC150_MAGN_REG_OPMODE_ODR 0x4C
+#define BMC150_MAGN_MASK_OPMODE GENMASK(2, 1)
+#define BMC150_MAGN_SHIFT_OPMODE 1
+#define BMC150_MAGN_MODE_NORMAL 0x00
+#define BMC150_MAGN_MODE_FORCED 0x01
+#define BMC150_MAGN_MODE_SLEEP 0x03
+#define BMC150_MAGN_MASK_ODR GENMASK(5, 3)
+#define BMC150_MAGN_SHIFT_ODR 3
+
+#define BMC150_MAGN_REG_INT 0x4D
+
+#define BMC150_MAGN_REG_INT_DRDY 0x4E
+#define BMC150_MAGN_MASK_DRDY_EN BIT(7)
+#define BMC150_MAGN_SHIFT_DRDY_EN 7
+#define BMC150_MAGN_MASK_DRDY_INT3 BIT(6)
+#define BMC150_MAGN_MASK_DRDY_Z_EN BIT(5)
+#define BMC150_MAGN_MASK_DRDY_Y_EN BIT(4)
+#define BMC150_MAGN_MASK_DRDY_X_EN BIT(3)
+#define BMC150_MAGN_MASK_DRDY_DR_POLARITY BIT(2)
+#define BMC150_MAGN_MASK_DRDY_LATCHING BIT(1)
+#define BMC150_MAGN_MASK_DRDY_INT3_POLARITY BIT(0)
+
+#define BMC150_MAGN_REG_LOW_THRESH 0x4F
+#define BMC150_MAGN_REG_HIGH_THRESH 0x50
+#define BMC150_MAGN_REG_REP_XY 0x51
+#define BMC150_MAGN_REG_REP_Z 0x52
+#define BMC150_MAGN_REG_REP_DATAMASK GENMASK(7, 0)
+
+#define BMC150_MAGN_REG_TRIM_START 0x5D
+#define BMC150_MAGN_REG_TRIM_END 0x71
+
+#define BMC150_MAGN_XY_OVERFLOW_VAL -4096
+#define BMC150_MAGN_Z_OVERFLOW_VAL -16384
+
+/* Time from SUSPEND to SLEEP */
+#define BMC150_MAGN_START_UP_TIME_MS 3
+
+#define BMC150_MAGN_AUTO_SUSPEND_DELAY_MS 2000
+
+#define BMC150_MAGN_REGVAL_TO_REPXY(regval) (((regval) * 2) + 1)
+#define BMC150_MAGN_REGVAL_TO_REPZ(regval) ((regval) + 1)
+#define BMC150_MAGN_REPXY_TO_REGVAL(rep) (((rep) - 1) / 2)
+#define BMC150_MAGN_REPZ_TO_REGVAL(rep) ((rep) - 1)
+
+enum bmc150_magn_axis {
+ AXIS_X,
+ AXIS_Y,
+ AXIS_Z,
+ RHALL,
+ AXIS_XYZ_MAX = RHALL,
+ AXIS_XYZR_MAX,
+};
+
+enum bmc150_magn_power_modes {
+ BMC150_MAGN_POWER_MODE_SUSPEND,
+ BMC150_MAGN_POWER_MODE_SLEEP,
+ BMC150_MAGN_POWER_MODE_NORMAL,
+};
+
+struct bmc150_magn_trim_regs {
+ s8 x1;
+ s8 y1;
+ __le16 reserved1;
+ u8 reserved2;
+ __le16 z4;
+ s8 x2;
+ s8 y2;
+ __le16 reserved3;
+ __le16 z2;
+ __le16 z1;
+ __le16 xyz1;
+ __le16 z3;
+ s8 xy2;
+ u8 xy1;
+} __packed;
+
+struct bmc150_magn_data {
+ struct device *dev;
+ /*
+ * 1. Protect this structure.
+ * 2. Serialize sequences that power on/off the device and access HW.
+ */
+ struct mutex mutex;
+ struct regmap *regmap;
+ struct regulator_bulk_data regulators[2];
+ struct iio_mount_matrix orientation;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ s32 chans[3];
+ s64 timestamp __aligned(8);
+ } scan;
+ struct iio_trigger *dready_trig;
+ bool dready_trigger_on;
+ int max_odr;
+ int irq;
+};
+
+static const struct {
+ int freq;
+ u8 reg_val;
+} bmc150_magn_samp_freq_table[] = { {2, 0x01},
+ {6, 0x02},
+ {8, 0x03},
+ {10, 0x00},
+ {15, 0x04},
+ {20, 0x05},
+ {25, 0x06},
+ {30, 0x07} };
+
+enum bmc150_magn_presets {
+ LOW_POWER_PRESET,
+ REGULAR_PRESET,
+ ENHANCED_REGULAR_PRESET,
+ HIGH_ACCURACY_PRESET
+};
+
+static const struct bmc150_magn_preset {
+ u8 rep_xy;
+ u8 rep_z;
+ u8 odr;
+} bmc150_magn_presets_table[] = {
+ [LOW_POWER_PRESET] = {3, 3, 10},
+ [REGULAR_PRESET] = {9, 15, 10},
+ [ENHANCED_REGULAR_PRESET] = {15, 27, 10},
+ [HIGH_ACCURACY_PRESET] = {47, 83, 20},
+};
+
+#define BMC150_MAGN_DEFAULT_PRESET REGULAR_PRESET
+
+static bool bmc150_magn_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BMC150_MAGN_REG_POWER:
+ case BMC150_MAGN_REG_OPMODE_ODR:
+ case BMC150_MAGN_REG_INT:
+ case BMC150_MAGN_REG_INT_DRDY:
+ case BMC150_MAGN_REG_LOW_THRESH:
+ case BMC150_MAGN_REG_HIGH_THRESH:
+ case BMC150_MAGN_REG_REP_XY:
+ case BMC150_MAGN_REG_REP_Z:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool bmc150_magn_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BMC150_MAGN_REG_X_L:
+ case BMC150_MAGN_REG_X_M:
+ case BMC150_MAGN_REG_Y_L:
+ case BMC150_MAGN_REG_Y_M:
+ case BMC150_MAGN_REG_Z_L:
+ case BMC150_MAGN_REG_Z_M:
+ case BMC150_MAGN_REG_RHALL_L:
+ case BMC150_MAGN_REG_RHALL_M:
+ case BMC150_MAGN_REG_INT_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+const struct regmap_config bmc150_magn_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = BMC150_MAGN_REG_TRIM_END,
+ .cache_type = REGCACHE_RBTREE,
+
+ .writeable_reg = bmc150_magn_is_writeable_reg,
+ .volatile_reg = bmc150_magn_is_volatile_reg,
+};
+EXPORT_SYMBOL_NS(bmc150_magn_regmap_config, IIO_BMC150_MAGN);
+
+static int bmc150_magn_set_power_mode(struct bmc150_magn_data *data,
+ enum bmc150_magn_power_modes mode,
+ bool state)
+{
+ int ret;
+
+ switch (mode) {
+ case BMC150_MAGN_POWER_MODE_SUSPEND:
+ ret = regmap_update_bits(data->regmap, BMC150_MAGN_REG_POWER,
+ BMC150_MAGN_MASK_POWER_CTL, !state);
+ if (ret < 0)
+ return ret;
+ usleep_range(BMC150_MAGN_START_UP_TIME_MS * 1000, 20000);
+ return 0;
+ case BMC150_MAGN_POWER_MODE_SLEEP:
+ return regmap_update_bits(data->regmap,
+ BMC150_MAGN_REG_OPMODE_ODR,
+ BMC150_MAGN_MASK_OPMODE,
+ BMC150_MAGN_MODE_SLEEP <<
+ BMC150_MAGN_SHIFT_OPMODE);
+ case BMC150_MAGN_POWER_MODE_NORMAL:
+ return regmap_update_bits(data->regmap,
+ BMC150_MAGN_REG_OPMODE_ODR,
+ BMC150_MAGN_MASK_OPMODE,
+ BMC150_MAGN_MODE_NORMAL <<
+ BMC150_MAGN_SHIFT_OPMODE);
+ }
+
+ return -EINVAL;
+}
+
+static int bmc150_magn_set_power_state(struct bmc150_magn_data *data, bool on)
+{
+#ifdef CONFIG_PM
+ int ret;
+
+ if (on) {
+ ret = pm_runtime_resume_and_get(data->dev);
+ } else {
+ pm_runtime_mark_last_busy(data->dev);
+ ret = pm_runtime_put_autosuspend(data->dev);
+ }
+
+ if (ret < 0) {
+ dev_err(data->dev,
+ "failed to change power state to %d\n", on);
+ return ret;
+ }
+#endif
+
+ return 0;
+}
+
+static int bmc150_magn_get_odr(struct bmc150_magn_data *data, int *val)
+{
+ int ret, reg_val;
+ u8 i, odr_val;
+
+ ret = regmap_read(data->regmap, BMC150_MAGN_REG_OPMODE_ODR, &reg_val);
+ if (ret < 0)
+ return ret;
+ odr_val = (reg_val & BMC150_MAGN_MASK_ODR) >> BMC150_MAGN_SHIFT_ODR;
+
+ for (i = 0; i < ARRAY_SIZE(bmc150_magn_samp_freq_table); i++)
+ if (bmc150_magn_samp_freq_table[i].reg_val == odr_val) {
+ *val = bmc150_magn_samp_freq_table[i].freq;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int bmc150_magn_set_odr(struct bmc150_magn_data *data, int val)
+{
+ int ret;
+ u8 i;
+
+ for (i = 0; i < ARRAY_SIZE(bmc150_magn_samp_freq_table); i++) {
+ if (bmc150_magn_samp_freq_table[i].freq == val) {
+ ret = regmap_update_bits(data->regmap,
+ BMC150_MAGN_REG_OPMODE_ODR,
+ BMC150_MAGN_MASK_ODR,
+ bmc150_magn_samp_freq_table[i].
+ reg_val <<
+ BMC150_MAGN_SHIFT_ODR);
+ if (ret < 0)
+ return ret;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int bmc150_magn_set_max_odr(struct bmc150_magn_data *data, int rep_xy,
+ int rep_z, int odr)
+{
+ int ret, reg_val, max_odr;
+
+ if (rep_xy <= 0) {
+ ret = regmap_read(data->regmap, BMC150_MAGN_REG_REP_XY,
+ &reg_val);
+ if (ret < 0)
+ return ret;
+ rep_xy = BMC150_MAGN_REGVAL_TO_REPXY(reg_val);
+ }
+ if (rep_z <= 0) {
+ ret = regmap_read(data->regmap, BMC150_MAGN_REG_REP_Z,
+ &reg_val);
+ if (ret < 0)
+ return ret;
+ rep_z = BMC150_MAGN_REGVAL_TO_REPZ(reg_val);
+ }
+ if (odr <= 0) {
+ ret = bmc150_magn_get_odr(data, &odr);
+ if (ret < 0)
+ return ret;
+ }
+ /* the maximum selectable read-out frequency from datasheet */
+ max_odr = 1000000 / (145 * rep_xy + 500 * rep_z + 980);
+ if (odr > max_odr) {
+ dev_err(data->dev,
+ "Can't set oversampling with sampling freq %d\n",
+ odr);
+ return -EINVAL;
+ }
+ data->max_odr = max_odr;
+
+ return 0;
+}
+
+static s32 bmc150_magn_compensate_x(struct bmc150_magn_trim_regs *tregs, s16 x,
+ u16 rhall)
+{
+ s16 val;
+ u16 xyz1 = le16_to_cpu(tregs->xyz1);
+
+ if (x == BMC150_MAGN_XY_OVERFLOW_VAL)
+ return S32_MIN;
+
+ if (!rhall)
+ rhall = xyz1;
+
+ val = ((s16)(((u16)((((s32)xyz1) << 14) / rhall)) - ((u16)0x4000)));
+ val = ((s16)((((s32)x) * ((((((((s32)tregs->xy2) * ((((s32)val) *
+ ((s32)val)) >> 7)) + (((s32)val) *
+ ((s32)(((s16)tregs->xy1) << 7)))) >> 9) + ((s32)0x100000)) *
+ ((s32)(((s16)tregs->x2) + ((s16)0xA0)))) >> 12)) >> 13)) +
+ (((s16)tregs->x1) << 3);
+
+ return (s32)val;
+}
+
+static s32 bmc150_magn_compensate_y(struct bmc150_magn_trim_regs *tregs, s16 y,
+ u16 rhall)
+{
+ s16 val;
+ u16 xyz1 = le16_to_cpu(tregs->xyz1);
+
+ if (y == BMC150_MAGN_XY_OVERFLOW_VAL)
+ return S32_MIN;
+
+ if (!rhall)
+ rhall = xyz1;
+
+ val = ((s16)(((u16)((((s32)xyz1) << 14) / rhall)) - ((u16)0x4000)));
+ val = ((s16)((((s32)y) * ((((((((s32)tregs->xy2) * ((((s32)val) *
+ ((s32)val)) >> 7)) + (((s32)val) *
+ ((s32)(((s16)tregs->xy1) << 7)))) >> 9) + ((s32)0x100000)) *
+ ((s32)(((s16)tregs->y2) + ((s16)0xA0)))) >> 12)) >> 13)) +
+ (((s16)tregs->y1) << 3);
+
+ return (s32)val;
+}
+
+static s32 bmc150_magn_compensate_z(struct bmc150_magn_trim_regs *tregs, s16 z,
+ u16 rhall)
+{
+ s32 val;
+ u16 xyz1 = le16_to_cpu(tregs->xyz1);
+ u16 z1 = le16_to_cpu(tregs->z1);
+ s16 z2 = le16_to_cpu(tregs->z2);
+ s16 z3 = le16_to_cpu(tregs->z3);
+ s16 z4 = le16_to_cpu(tregs->z4);
+
+ if (z == BMC150_MAGN_Z_OVERFLOW_VAL)
+ return S32_MIN;
+
+ val = (((((s32)(z - z4)) << 15) - ((((s32)z3) * ((s32)(((s16)rhall) -
+ ((s16)xyz1)))) >> 2)) / (z2 + ((s16)(((((s32)z1) *
+ ((((s16)rhall) << 1))) + (1 << 15)) >> 16))));
+
+ return val;
+}
+
+static int bmc150_magn_read_xyz(struct bmc150_magn_data *data, s32 *buffer)
+{
+ int ret;
+ __le16 values[AXIS_XYZR_MAX];
+ s16 raw_x, raw_y, raw_z;
+ u16 rhall;
+ struct bmc150_magn_trim_regs tregs;
+
+ ret = regmap_bulk_read(data->regmap, BMC150_MAGN_REG_X_L,
+ values, sizeof(values));
+ if (ret < 0)
+ return ret;
+
+ raw_x = (s16)le16_to_cpu(values[AXIS_X]) >> BMC150_MAGN_SHIFT_XY_L;
+ raw_y = (s16)le16_to_cpu(values[AXIS_Y]) >> BMC150_MAGN_SHIFT_XY_L;
+ raw_z = (s16)le16_to_cpu(values[AXIS_Z]) >> BMC150_MAGN_SHIFT_Z_L;
+ rhall = le16_to_cpu(values[RHALL]) >> BMC150_MAGN_SHIFT_RHALL_L;
+
+ ret = regmap_bulk_read(data->regmap, BMC150_MAGN_REG_TRIM_START,
+ &tregs, sizeof(tregs));
+ if (ret < 0)
+ return ret;
+
+ buffer[AXIS_X] = bmc150_magn_compensate_x(&tregs, raw_x, rhall);
+ buffer[AXIS_Y] = bmc150_magn_compensate_y(&tregs, raw_y, rhall);
+ buffer[AXIS_Z] = bmc150_magn_compensate_z(&tregs, raw_z, rhall);
+
+ return 0;
+}
+
+static int bmc150_magn_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+ int ret, tmp;
+ s32 values[AXIS_XYZ_MAX];
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (iio_buffer_enabled(indio_dev))
+ return -EBUSY;
+ mutex_lock(&data->mutex);
+
+ ret = bmc150_magn_set_power_state(data, true);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+
+ ret = bmc150_magn_read_xyz(data, values);
+ if (ret < 0) {
+ bmc150_magn_set_power_state(data, false);
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ *val = values[chan->scan_index];
+
+ ret = bmc150_magn_set_power_state(data, false);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+
+ mutex_unlock(&data->mutex);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /*
+ * The API/driver performs an off-chip temperature
+ * compensation and outputs x/y/z magnetic field data in
+ * 16 LSB/uT to the upper application layer.
+ */
+ *val = 0;
+ *val2 = 625;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = bmc150_magn_get_odr(data, val);
+ if (ret < 0)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ case IIO_MOD_Y:
+ ret = regmap_read(data->regmap, BMC150_MAGN_REG_REP_XY,
+ &tmp);
+ if (ret < 0)
+ return ret;
+ *val = BMC150_MAGN_REGVAL_TO_REPXY(tmp);
+ return IIO_VAL_INT;
+ case IIO_MOD_Z:
+ ret = regmap_read(data->regmap, BMC150_MAGN_REG_REP_Z,
+ &tmp);
+ if (ret < 0)
+ return ret;
+ *val = BMC150_MAGN_REGVAL_TO_REPZ(tmp);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmc150_magn_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (val > data->max_odr)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = bmc150_magn_set_odr(data, val);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ case IIO_MOD_Y:
+ if (val < 1 || val > 511)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = bmc150_magn_set_max_odr(data, val, 0, 0);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ ret = regmap_update_bits(data->regmap,
+ BMC150_MAGN_REG_REP_XY,
+ BMC150_MAGN_REG_REP_DATAMASK,
+ BMC150_MAGN_REPXY_TO_REGVAL
+ (val));
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_MOD_Z:
+ if (val < 1 || val > 256)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = bmc150_magn_set_max_odr(data, 0, val, 0);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ ret = regmap_update_bits(data->regmap,
+ BMC150_MAGN_REG_REP_Z,
+ BMC150_MAGN_REG_REP_DATAMASK,
+ BMC150_MAGN_REPZ_TO_REGVAL
+ (val));
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t bmc150_magn_show_samp_freq_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+ size_t len = 0;
+ u8 i;
+
+ for (i = 0; i < ARRAY_SIZE(bmc150_magn_samp_freq_table); i++) {
+ if (bmc150_magn_samp_freq_table[i].freq > data->max_odr)
+ break;
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
+ bmc150_magn_samp_freq_table[i].freq);
+ }
+ /* replace last space with a newline */
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static const struct iio_mount_matrix *
+bmc150_magn_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info bmc150_magn_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bmc150_magn_get_mount_matrix),
+ { }
+};
+
+static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(bmc150_magn_show_samp_freq_avail);
+
+static struct attribute *bmc150_magn_attributes[] = {
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group bmc150_magn_attrs_group = {
+ .attrs = bmc150_magn_attributes,
+};
+
+#define BMC150_MAGN_CHANNEL(_axis) { \
+ .type = IIO_MAGN, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = AXIS_##_axis, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ .endianness = IIO_LE \
+ }, \
+ .ext_info = bmc150_magn_ext_info, \
+}
+
+static const struct iio_chan_spec bmc150_magn_channels[] = {
+ BMC150_MAGN_CHANNEL(X),
+ BMC150_MAGN_CHANNEL(Y),
+ BMC150_MAGN_CHANNEL(Z),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct iio_info bmc150_magn_info = {
+ .attrs = &bmc150_magn_attrs_group,
+ .read_raw = bmc150_magn_read_raw,
+ .write_raw = bmc150_magn_write_raw,
+};
+
+static const unsigned long bmc150_magn_scan_masks[] = {
+ BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z),
+ 0};
+
+static irqreturn_t bmc150_magn_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = bmc150_magn_read_xyz(data, data->scan.chans);
+ if (ret < 0)
+ goto err;
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ pf->timestamp);
+
+err:
+ mutex_unlock(&data->mutex);
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int bmc150_magn_init(struct bmc150_magn_data *data)
+{
+ int ret, chip_id;
+ struct bmc150_magn_preset preset;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(data->regulators),
+ data->regulators);
+ if (ret < 0) {
+ dev_err(data->dev, "Failed to enable regulators: %d\n", ret);
+ return ret;
+ }
+ /*
+ * 3ms power-on time according to datasheet, let's better
+ * be safe than sorry and set this delay to 5ms.
+ */
+ msleep(5);
+
+ ret = bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SUSPEND,
+ false);
+ if (ret < 0) {
+ dev_err(data->dev,
+ "Failed to bring up device from suspend mode\n");
+ goto err_regulator_disable;
+ }
+
+ ret = regmap_read(data->regmap, BMC150_MAGN_REG_CHIP_ID, &chip_id);
+ if (ret < 0) {
+ dev_err(data->dev, "Failed reading chip id\n");
+ goto err_poweroff;
+ }
+ if (chip_id != BMC150_MAGN_CHIP_ID_VAL) {
+ dev_err(data->dev, "Invalid chip id 0x%x\n", chip_id);
+ ret = -ENODEV;
+ goto err_poweroff;
+ }
+ dev_dbg(data->dev, "Chip id %x\n", chip_id);
+
+ preset = bmc150_magn_presets_table[BMC150_MAGN_DEFAULT_PRESET];
+ ret = bmc150_magn_set_odr(data, preset.odr);
+ if (ret < 0) {
+ dev_err(data->dev, "Failed to set ODR to %d\n",
+ preset.odr);
+ goto err_poweroff;
+ }
+
+ ret = regmap_write(data->regmap, BMC150_MAGN_REG_REP_XY,
+ BMC150_MAGN_REPXY_TO_REGVAL(preset.rep_xy));
+ if (ret < 0) {
+ dev_err(data->dev, "Failed to set REP XY to %d\n",
+ preset.rep_xy);
+ goto err_poweroff;
+ }
+
+ ret = regmap_write(data->regmap, BMC150_MAGN_REG_REP_Z,
+ BMC150_MAGN_REPZ_TO_REGVAL(preset.rep_z));
+ if (ret < 0) {
+ dev_err(data->dev, "Failed to set REP Z to %d\n",
+ preset.rep_z);
+ goto err_poweroff;
+ }
+
+ ret = bmc150_magn_set_max_odr(data, preset.rep_xy, preset.rep_z,
+ preset.odr);
+ if (ret < 0)
+ goto err_poweroff;
+
+ ret = bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_NORMAL,
+ true);
+ if (ret < 0) {
+ dev_err(data->dev, "Failed to power on device\n");
+ goto err_poweroff;
+ }
+
+ return 0;
+
+err_poweroff:
+ bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SUSPEND, true);
+err_regulator_disable:
+ regulator_bulk_disable(ARRAY_SIZE(data->regulators), data->regulators);
+ return ret;
+}
+
+static int bmc150_magn_reset_intr(struct bmc150_magn_data *data)
+{
+ int tmp;
+
+ /*
+ * Data Ready (DRDY) is always cleared after
+ * readout of data registers ends.
+ */
+ return regmap_read(data->regmap, BMC150_MAGN_REG_X_L, &tmp);
+}
+
+static void bmc150_magn_trig_reen(struct iio_trigger *trig)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (!data->dready_trigger_on)
+ return;
+
+ mutex_lock(&data->mutex);
+ ret = bmc150_magn_reset_intr(data);
+ mutex_unlock(&data->mutex);
+ if (ret)
+ dev_err(data->dev, "Failed to reset interrupt\n");
+}
+
+static int bmc150_magn_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+ int ret = 0;
+
+ mutex_lock(&data->mutex);
+ if (state == data->dready_trigger_on)
+ goto err_unlock;
+
+ ret = regmap_update_bits(data->regmap, BMC150_MAGN_REG_INT_DRDY,
+ BMC150_MAGN_MASK_DRDY_EN,
+ state << BMC150_MAGN_SHIFT_DRDY_EN);
+ if (ret < 0)
+ goto err_unlock;
+
+ data->dready_trigger_on = state;
+
+ if (state) {
+ ret = bmc150_magn_reset_intr(data);
+ if (ret < 0)
+ goto err_unlock;
+ }
+ mutex_unlock(&data->mutex);
+
+ return 0;
+
+err_unlock:
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static const struct iio_trigger_ops bmc150_magn_trigger_ops = {
+ .set_trigger_state = bmc150_magn_data_rdy_trigger_set_state,
+ .reenable = bmc150_magn_trig_reen,
+};
+
+static int bmc150_magn_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+
+ return bmc150_magn_set_power_state(data, true);
+}
+
+static int bmc150_magn_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+
+ return bmc150_magn_set_power_state(data, false);
+}
+
+static const struct iio_buffer_setup_ops bmc150_magn_buffer_setup_ops = {
+ .preenable = bmc150_magn_buffer_preenable,
+ .postdisable = bmc150_magn_buffer_postdisable,
+};
+
+static const char *bmc150_magn_match_acpi_device(struct device *dev)
+{
+ const struct acpi_device_id *id;
+
+ id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!id)
+ return NULL;
+
+ return dev_name(dev);
+}
+
+int bmc150_magn_probe(struct device *dev, struct regmap *regmap,
+ int irq, const char *name)
+{
+ struct bmc150_magn_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);
+ dev_set_drvdata(dev, indio_dev);
+ data->regmap = regmap;
+ data->irq = irq;
+ data->dev = dev;
+
+ data->regulators[0].supply = "vdd";
+ data->regulators[1].supply = "vddio";
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->regulators),
+ data->regulators);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get regulators\n");
+
+ ret = iio_read_mount_matrix(dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ if (!name && ACPI_HANDLE(dev))
+ name = bmc150_magn_match_acpi_device(dev);
+
+ mutex_init(&data->mutex);
+
+ ret = bmc150_magn_init(data);
+ if (ret < 0)
+ return ret;
+
+ indio_dev->channels = bmc150_magn_channels;
+ indio_dev->num_channels = ARRAY_SIZE(bmc150_magn_channels);
+ indio_dev->available_scan_masks = bmc150_magn_scan_masks;
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &bmc150_magn_info;
+
+ if (irq > 0) {
+ data->dready_trig = devm_iio_trigger_alloc(dev,
+ "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->dready_trig) {
+ ret = -ENOMEM;
+ dev_err(dev, "iio trigger alloc failed\n");
+ goto err_poweroff;
+ }
+
+ data->dready_trig->ops = &bmc150_magn_trigger_ops;
+ iio_trigger_set_drvdata(data->dready_trig, indio_dev);
+ ret = iio_trigger_register(data->dready_trig);
+ if (ret) {
+ dev_err(dev, "iio trigger register failed\n");
+ goto err_poweroff;
+ }
+
+ ret = request_threaded_irq(irq,
+ iio_trigger_generic_data_rdy_poll,
+ NULL,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ BMC150_MAGN_IRQ_NAME,
+ data->dready_trig);
+ if (ret < 0) {
+ dev_err(dev, "request irq %d failed\n", irq);
+ goto err_trigger_unregister;
+ }
+ }
+
+ ret = iio_triggered_buffer_setup(indio_dev,
+ iio_pollfunc_store_time,
+ bmc150_magn_trigger_handler,
+ &bmc150_magn_buffer_setup_ops);
+ if (ret < 0) {
+ dev_err(dev, "iio triggered buffer setup failed\n");
+ goto err_free_irq;
+ }
+
+ ret = pm_runtime_set_active(dev);
+ if (ret)
+ goto err_buffer_cleanup;
+
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev,
+ BMC150_MAGN_AUTO_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(dev, "unable to register iio device\n");
+ goto err_pm_cleanup;
+ }
+
+ dev_dbg(dev, "Registered device %s\n", name);
+ return 0;
+
+err_pm_cleanup:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+err_buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+err_free_irq:
+ if (irq > 0)
+ free_irq(irq, data->dready_trig);
+err_trigger_unregister:
+ if (data->dready_trig)
+ iio_trigger_unregister(data->dready_trig);
+err_poweroff:
+ bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SUSPEND, true);
+ return ret;
+}
+EXPORT_SYMBOL_NS(bmc150_magn_probe, IIO_BMC150_MAGN);
+
+void bmc150_magn_remove(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+
+ iio_triggered_buffer_cleanup(indio_dev);
+
+ if (data->irq > 0)
+ free_irq(data->irq, data->dready_trig);
+
+ if (data->dready_trig)
+ iio_trigger_unregister(data->dready_trig);
+
+ mutex_lock(&data->mutex);
+ bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SUSPEND, true);
+ mutex_unlock(&data->mutex);
+
+ regulator_bulk_disable(ARRAY_SIZE(data->regulators), data->regulators);
+}
+EXPORT_SYMBOL_NS(bmc150_magn_remove, IIO_BMC150_MAGN);
+
+#ifdef CONFIG_PM
+static int bmc150_magn_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SLEEP,
+ true);
+ mutex_unlock(&data->mutex);
+ if (ret < 0) {
+ dev_err(dev, "powering off device failed\n");
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * Should be called with data->mutex held.
+ */
+static int bmc150_magn_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+
+ return bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_NORMAL,
+ true);
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int bmc150_magn_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SLEEP,
+ true);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int bmc150_magn_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmc150_magn_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_NORMAL,
+ true);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+#endif
+
+const struct dev_pm_ops bmc150_magn_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(bmc150_magn_suspend, bmc150_magn_resume)
+ SET_RUNTIME_PM_OPS(bmc150_magn_runtime_suspend,
+ bmc150_magn_runtime_resume, NULL)
+};
+EXPORT_SYMBOL_NS(bmc150_magn_pm_ops, IIO_BMC150_MAGN);
+
+MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BMC150 magnetometer core driver");
diff --git a/drivers/iio/magnetometer/bmc150_magn.h b/drivers/iio/magnetometer/bmc150_magn.h
new file mode 100644
index 000000000..98c086d10
--- /dev/null
+++ b/drivers/iio/magnetometer/bmc150_magn.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BMC150_MAGN_H_
+#define _BMC150_MAGN_H_
+
+extern const struct regmap_config bmc150_magn_regmap_config;
+extern const struct dev_pm_ops bmc150_magn_pm_ops;
+
+int bmc150_magn_probe(struct device *dev, struct regmap *regmap, int irq,
+ const char *name);
+void bmc150_magn_remove(struct device *dev);
+
+#endif /* _BMC150_MAGN_H_ */
diff --git a/drivers/iio/magnetometer/bmc150_magn_i2c.c b/drivers/iio/magnetometer/bmc150_magn_i2c.c
new file mode 100644
index 000000000..570deaa87
--- /dev/null
+++ b/drivers/iio/magnetometer/bmc150_magn_i2c.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 3-axis magnetometer driver supporting following I2C Bosch-Sensortec chips:
+ * - BMC150
+ * - BMC156
+ * - BMM150
+ *
+ * Copyright (c) 2016, Intel Corporation.
+ */
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/regmap.h>
+
+#include "bmc150_magn.h"
+
+static int bmc150_magn_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct regmap *regmap;
+ const char *name = NULL;
+
+ regmap = devm_regmap_init_i2c(client, &bmc150_magn_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "Failed to initialize i2c regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ if (id)
+ name = id->name;
+
+ return bmc150_magn_probe(&client->dev, regmap, client->irq, name);
+}
+
+static void bmc150_magn_i2c_remove(struct i2c_client *client)
+{
+ bmc150_magn_remove(&client->dev);
+}
+
+static const struct acpi_device_id bmc150_magn_acpi_match[] = {
+ {"BMC150B", 0},
+ {"BMC156B", 0},
+ {"BMM150B", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, bmc150_magn_acpi_match);
+
+static const struct i2c_device_id bmc150_magn_i2c_id[] = {
+ {"bmc150_magn", 0},
+ {"bmc156_magn", 0},
+ {"bmm150_magn", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, bmc150_magn_i2c_id);
+
+static const struct of_device_id bmc150_magn_of_match[] = {
+ { .compatible = "bosch,bmc150_magn" },
+ { .compatible = "bosch,bmc156_magn" },
+ { .compatible = "bosch,bmm150_magn" }, /* deprecated compatible */
+ { .compatible = "bosch,bmm150" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bmc150_magn_of_match);
+
+static struct i2c_driver bmc150_magn_driver = {
+ .driver = {
+ .name = "bmc150_magn_i2c",
+ .of_match_table = bmc150_magn_of_match,
+ .acpi_match_table = ACPI_PTR(bmc150_magn_acpi_match),
+ .pm = &bmc150_magn_pm_ops,
+ },
+ .probe = bmc150_magn_i2c_probe,
+ .remove = bmc150_magn_i2c_remove,
+ .id_table = bmc150_magn_i2c_id,
+};
+module_i2c_driver(bmc150_magn_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BMC150 I2C magnetometer driver");
+MODULE_IMPORT_NS(IIO_BMC150_MAGN);
diff --git a/drivers/iio/magnetometer/bmc150_magn_spi.c b/drivers/iio/magnetometer/bmc150_magn_spi.c
new file mode 100644
index 000000000..882987721
--- /dev/null
+++ b/drivers/iio/magnetometer/bmc150_magn_spi.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 3-axis magnetometer driver support following SPI Bosch-Sensortec chips:
+ * - BMC150
+ * - BMC156
+ * - BMM150
+ *
+ * Copyright (c) 2016, Intel Corporation.
+ */
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/spi/spi.h>
+#include <linux/acpi.h>
+#include <linux/regmap.h>
+
+#include "bmc150_magn.h"
+
+static int bmc150_magn_spi_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+ const struct spi_device_id *id = spi_get_device_id(spi);
+
+ regmap = devm_regmap_init_spi(spi, &bmc150_magn_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&spi->dev, "Failed to register spi regmap: %pe\n",
+ regmap);
+ return PTR_ERR(regmap);
+ }
+ return bmc150_magn_probe(&spi->dev, regmap, spi->irq, id->name);
+}
+
+static void bmc150_magn_spi_remove(struct spi_device *spi)
+{
+ bmc150_magn_remove(&spi->dev);
+}
+
+static const struct spi_device_id bmc150_magn_spi_id[] = {
+ {"bmc150_magn", 0},
+ {"bmc156_magn", 0},
+ {"bmm150_magn", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, bmc150_magn_spi_id);
+
+static const struct acpi_device_id bmc150_magn_acpi_match[] = {
+ {"BMC150B", 0},
+ {"BMC156B", 0},
+ {"BMM150B", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, bmc150_magn_acpi_match);
+
+static struct spi_driver bmc150_magn_spi_driver = {
+ .probe = bmc150_magn_spi_probe,
+ .remove = bmc150_magn_spi_remove,
+ .id_table = bmc150_magn_spi_id,
+ .driver = {
+ .acpi_match_table = ACPI_PTR(bmc150_magn_acpi_match),
+ .name = "bmc150_magn_spi",
+ },
+};
+module_spi_driver(bmc150_magn_spi_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com");
+MODULE_DESCRIPTION("BMC150 magnetometer SPI driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_BMC150_MAGN);
diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
new file mode 100644
index 000000000..e85a3a8ee
--- /dev/null
+++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
@@ -0,0 +1,586 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HID Sensors Driver
+ * Copyright (c) 2012, Intel Corporation.
+ */
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/hid-sensor-hub.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include "../common/hid-sensors/hid-sensor-trigger.h"
+
+enum magn_3d_channel {
+ CHANNEL_SCAN_INDEX_X,
+ CHANNEL_SCAN_INDEX_Y,
+ CHANNEL_SCAN_INDEX_Z,
+ CHANNEL_SCAN_INDEX_NORTH_MAGN_TILT_COMP,
+ CHANNEL_SCAN_INDEX_NORTH_TRUE_TILT_COMP,
+ CHANNEL_SCAN_INDEX_NORTH_MAGN,
+ CHANNEL_SCAN_INDEX_NORTH_TRUE,
+ CHANNEL_SCAN_INDEX_TIMESTAMP,
+ MAGN_3D_CHANNEL_MAX,
+};
+
+struct common_attributes {
+ int scale_pre_decml;
+ int scale_post_decml;
+ int scale_precision;
+ int value_offset;
+};
+
+struct magn_3d_state {
+ struct hid_sensor_hub_callbacks callbacks;
+ struct hid_sensor_common magn_flux_attributes;
+ struct hid_sensor_common rot_attributes;
+ struct hid_sensor_hub_attribute_info magn[MAGN_3D_CHANNEL_MAX];
+
+ /* dynamically sized array to hold sensor values */
+ u32 *iio_vals;
+ /* array of pointers to sensor value */
+ u32 *magn_val_addr[MAGN_3D_CHANNEL_MAX];
+
+ struct common_attributes magn_flux_attr;
+ struct common_attributes rot_attr;
+ s64 timestamp;
+};
+
+static const u32 magn_3d_addresses[MAGN_3D_CHANNEL_MAX] = {
+ HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_X_AXIS,
+ HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Y_AXIS,
+ HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Z_AXIS,
+ HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH,
+ HID_USAGE_SENSOR_ORIENT_COMP_TRUE_NORTH,
+ HID_USAGE_SENSOR_ORIENT_MAGN_NORTH,
+ HID_USAGE_SENSOR_ORIENT_TRUE_NORTH,
+ HID_USAGE_SENSOR_TIME_TIMESTAMP,
+};
+
+static const u32 magn_3d_sensitivity_addresses[] = {
+ HID_USAGE_SENSOR_DATA_ORIENTATION,
+ HID_USAGE_SENSOR_ORIENT_MAGN_FLUX,
+};
+
+/* Channel definitions */
+static const struct iio_chan_spec magn_3d_channels[] = {
+ {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_X,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ }, {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_Y,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ }, {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_Z,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ }, {
+ .type = IIO_ROT,
+ .modified = 1,
+ .channel2 = IIO_MOD_NORTH_MAGN_TILT_COMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ }, {
+ .type = IIO_ROT,
+ .modified = 1,
+ .channel2 = IIO_MOD_NORTH_TRUE_TILT_COMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ }, {
+ .type = IIO_ROT,
+ .modified = 1,
+ .channel2 = IIO_MOD_NORTH_MAGN,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ }, {
+ .type = IIO_ROT,
+ .modified = 1,
+ .channel2 = IIO_MOD_NORTH_TRUE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(7)
+};
+
+/* Adjust channel real bits based on report descriptor */
+static void magn_3d_adjust_channel_bit_mask(struct iio_chan_spec *channels,
+ int channel, int size)
+{
+ channels[channel].scan_type.sign = 's';
+ /* Real storage bits will change based on the report desc. */
+ channels[channel].scan_type.realbits = size * 8;
+ /* Maximum size of a sample to capture is u32 */
+ channels[channel].scan_type.storagebits = sizeof(u32) * 8;
+}
+
+/* Channel read_raw handler */
+static int magn_3d_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2,
+ long mask)
+{
+ struct magn_3d_state *magn_state = iio_priv(indio_dev);
+ int report_id = -1;
+ u32 address;
+ int ret_type;
+ s32 min;
+
+ *val = 0;
+ *val2 = 0;
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ hid_sensor_power_state(&magn_state->magn_flux_attributes, true);
+ report_id = magn_state->magn[chan->address].report_id;
+ min = magn_state->magn[chan->address].logical_minimum;
+ address = magn_3d_addresses[chan->address];
+ if (report_id >= 0)
+ *val = sensor_hub_input_attr_get_raw_value(
+ magn_state->magn_flux_attributes.hsdev,
+ HID_USAGE_SENSOR_COMPASS_3D, address,
+ report_id,
+ SENSOR_HUB_SYNC,
+ min < 0);
+ else {
+ *val = 0;
+ hid_sensor_power_state(
+ &magn_state->magn_flux_attributes,
+ false);
+ return -EINVAL;
+ }
+ hid_sensor_power_state(&magn_state->magn_flux_attributes,
+ false);
+ ret_type = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_MAGN:
+ *val = magn_state->magn_flux_attr.scale_pre_decml;
+ *val2 = magn_state->magn_flux_attr.scale_post_decml;
+ ret_type = magn_state->magn_flux_attr.scale_precision;
+ break;
+ case IIO_ROT:
+ *val = magn_state->rot_attr.scale_pre_decml;
+ *val2 = magn_state->rot_attr.scale_post_decml;
+ ret_type = magn_state->rot_attr.scale_precision;
+ break;
+ default:
+ ret_type = -EINVAL;
+ }
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_MAGN:
+ *val = magn_state->magn_flux_attr.value_offset;
+ ret_type = IIO_VAL_INT;
+ break;
+ case IIO_ROT:
+ *val = magn_state->rot_attr.value_offset;
+ ret_type = IIO_VAL_INT;
+ break;
+ default:
+ ret_type = -EINVAL;
+ }
+ break;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret_type = hid_sensor_read_samp_freq_value(
+ &magn_state->magn_flux_attributes, val, val2);
+ break;
+ case IIO_CHAN_INFO_HYSTERESIS:
+ switch (chan->type) {
+ case IIO_MAGN:
+ ret_type = hid_sensor_read_raw_hyst_value(
+ &magn_state->magn_flux_attributes, val, val2);
+ break;
+ case IIO_ROT:
+ ret_type = hid_sensor_read_raw_hyst_value(
+ &magn_state->rot_attributes, val, val2);
+ break;
+ default:
+ ret_type = -EINVAL;
+ }
+ break;
+ default:
+ ret_type = -EINVAL;
+ break;
+ }
+
+ return ret_type;
+}
+
+/* Channel write_raw handler */
+static int magn_3d_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct magn_3d_state *magn_state = iio_priv(indio_dev);
+ int ret = 0;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = hid_sensor_write_samp_freq_value(
+ &magn_state->magn_flux_attributes, val, val2);
+ break;
+ case IIO_CHAN_INFO_HYSTERESIS:
+ switch (chan->type) {
+ case IIO_MAGN:
+ ret = hid_sensor_write_raw_hyst_value(
+ &magn_state->magn_flux_attributes, val, val2);
+ break;
+ case IIO_ROT:
+ ret = hid_sensor_write_raw_hyst_value(
+ &magn_state->rot_attributes, val, val2);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct iio_info magn_3d_info = {
+ .read_raw = &magn_3d_read_raw,
+ .write_raw = &magn_3d_write_raw,
+};
+
+/* Callback handler to send event after all samples are received and captured */
+static int magn_3d_proc_event(struct hid_sensor_hub_device *hsdev,
+ unsigned usage_id,
+ void *priv)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(priv);
+ struct magn_3d_state *magn_state = iio_priv(indio_dev);
+
+ dev_dbg(&indio_dev->dev, "magn_3d_proc_event\n");
+ if (atomic_read(&magn_state->magn_flux_attributes.data_ready)) {
+ if (!magn_state->timestamp)
+ magn_state->timestamp = iio_get_time_ns(indio_dev);
+
+ iio_push_to_buffers_with_timestamp(indio_dev,
+ magn_state->iio_vals,
+ magn_state->timestamp);
+ magn_state->timestamp = 0;
+ }
+
+ return 0;
+}
+
+/* Capture samples in local storage */
+static int magn_3d_capture_sample(struct hid_sensor_hub_device *hsdev,
+ unsigned usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(priv);
+ struct magn_3d_state *magn_state = iio_priv(indio_dev);
+ int offset;
+ int ret = 0;
+ u32 *iio_val = NULL;
+
+ switch (usage_id) {
+ case HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_X_AXIS:
+ case HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Y_AXIS:
+ case HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Z_AXIS:
+ offset = (usage_id - HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_X_AXIS)
+ + CHANNEL_SCAN_INDEX_X;
+ break;
+ case HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH:
+ case HID_USAGE_SENSOR_ORIENT_COMP_TRUE_NORTH:
+ case HID_USAGE_SENSOR_ORIENT_MAGN_NORTH:
+ case HID_USAGE_SENSOR_ORIENT_TRUE_NORTH:
+ offset = (usage_id - HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH)
+ + CHANNEL_SCAN_INDEX_NORTH_MAGN_TILT_COMP;
+ break;
+ case HID_USAGE_SENSOR_TIME_TIMESTAMP:
+ magn_state->timestamp =
+ hid_sensor_convert_timestamp(&magn_state->magn_flux_attributes,
+ *(s64 *)raw_data);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+
+ iio_val = magn_state->magn_val_addr[offset];
+
+ if (iio_val != NULL)
+ *iio_val = *((u32 *)raw_data);
+ else
+ ret = -EINVAL;
+
+ return ret;
+}
+
+/* Parse report which is specific to an usage id*/
+static int magn_3d_parse_report(struct platform_device *pdev,
+ struct hid_sensor_hub_device *hsdev,
+ struct iio_chan_spec **channels,
+ int *chan_count,
+ unsigned usage_id,
+ struct magn_3d_state *st)
+{
+ int i;
+ int attr_count = 0;
+ struct iio_chan_spec *_channels;
+
+ /* Scan for each usage attribute supported */
+ for (i = 0; i < MAGN_3D_CHANNEL_MAX; i++) {
+ int status;
+ u32 address = magn_3d_addresses[i];
+
+ /* Check if usage attribute exists in the sensor hub device */
+ status = sensor_hub_input_get_attribute_info(hsdev,
+ HID_INPUT_REPORT,
+ usage_id,
+ address,
+ &(st->magn[i]));
+ if (!status)
+ attr_count++;
+ }
+
+ if (attr_count <= 0) {
+ dev_err(&pdev->dev,
+ "failed to find any supported usage attributes in report\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(&pdev->dev, "magn_3d Found %d usage attributes\n",
+ attr_count);
+ dev_dbg(&pdev->dev, "magn_3d X: %x:%x Y: %x:%x Z: %x:%x\n",
+ st->magn[0].index,
+ st->magn[0].report_id,
+ st->magn[1].index, st->magn[1].report_id,
+ st->magn[2].index, st->magn[2].report_id);
+
+ /* Setup IIO channel array */
+ _channels = devm_kcalloc(&pdev->dev, attr_count,
+ sizeof(struct iio_chan_spec),
+ GFP_KERNEL);
+ if (!_channels) {
+ dev_err(&pdev->dev,
+ "failed to allocate space for iio channels\n");
+ return -ENOMEM;
+ }
+
+ /* attr_count include timestamp channel, and the iio_vals should be aligned to 8byte */
+ st->iio_vals = devm_kcalloc(&pdev->dev,
+ ((attr_count + 1) % 2 + (attr_count + 1) / 2) * 2,
+ sizeof(u32), GFP_KERNEL);
+ if (!st->iio_vals) {
+ dev_err(&pdev->dev,
+ "failed to allocate space for iio values array\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0, *chan_count = 0;
+ i < MAGN_3D_CHANNEL_MAX && *chan_count < attr_count;
+ i++){
+ if (st->magn[i].index >= 0) {
+ /* Setup IIO channel struct */
+ (_channels[*chan_count]) = magn_3d_channels[i];
+ (_channels[*chan_count]).scan_index = *chan_count;
+ (_channels[*chan_count]).address = i;
+
+ if (i != CHANNEL_SCAN_INDEX_TIMESTAMP) {
+ /* Set magn_val_addr to iio value address */
+ st->magn_val_addr[i] = &st->iio_vals[*chan_count];
+ magn_3d_adjust_channel_bit_mask(_channels,
+ *chan_count,
+ st->magn[i].size);
+ }
+ (*chan_count)++;
+ }
+ }
+
+ if (*chan_count <= 0) {
+ dev_err(&pdev->dev,
+ "failed to find any magnetic channels setup\n");
+ return -EINVAL;
+ }
+
+ *channels = _channels;
+
+ dev_dbg(&pdev->dev, "magn_3d Setup %d IIO channels\n",
+ *chan_count);
+
+ st->magn_flux_attr.scale_precision = hid_sensor_format_scale(
+ HID_USAGE_SENSOR_COMPASS_3D,
+ &st->magn[CHANNEL_SCAN_INDEX_X],
+ &st->magn_flux_attr.scale_pre_decml,
+ &st->magn_flux_attr.scale_post_decml);
+ st->rot_attr.scale_precision
+ = hid_sensor_format_scale(
+ HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH,
+ &st->magn[CHANNEL_SCAN_INDEX_NORTH_MAGN_TILT_COMP],
+ &st->rot_attr.scale_pre_decml,
+ &st->rot_attr.scale_post_decml);
+
+ if (st->rot_attributes.sensitivity.index < 0) {
+ sensor_hub_input_get_attribute_info(hsdev,
+ HID_FEATURE_REPORT, usage_id,
+ HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
+ HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH,
+ &st->rot_attributes.sensitivity);
+ dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n",
+ st->rot_attributes.sensitivity.index,
+ st->rot_attributes.sensitivity.report_id);
+ }
+
+ return 0;
+}
+
+/* Function to initialize the processing for usage id */
+static int hid_magn_3d_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ static char *name = "magn_3d";
+ struct iio_dev *indio_dev;
+ struct magn_3d_state *magn_state;
+ struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+ struct iio_chan_spec *channels;
+ int chan_count = 0;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev,
+ sizeof(struct magn_3d_state));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ magn_state = iio_priv(indio_dev);
+ magn_state->magn_flux_attributes.hsdev = hsdev;
+ magn_state->magn_flux_attributes.pdev = pdev;
+
+ ret = hid_sensor_parse_common_attributes(hsdev,
+ HID_USAGE_SENSOR_COMPASS_3D,
+ &magn_state->magn_flux_attributes,
+ magn_3d_sensitivity_addresses,
+ ARRAY_SIZE(magn_3d_sensitivity_addresses));
+ if (ret) {
+ dev_err(&pdev->dev, "failed to setup common attributes\n");
+ return ret;
+ }
+ magn_state->rot_attributes = magn_state->magn_flux_attributes;
+ /* sensitivity of rot_attribute is not the same as magn_flux_attributes */
+ magn_state->rot_attributes.sensitivity.index = -1;
+
+ ret = magn_3d_parse_report(pdev, hsdev,
+ &channels, &chan_count,
+ HID_USAGE_SENSOR_COMPASS_3D, magn_state);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to parse report\n");
+ return ret;
+ }
+
+ indio_dev->channels = channels;
+ indio_dev->num_channels = chan_count;
+ indio_dev->info = &magn_3d_info;
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ atomic_set(&magn_state->magn_flux_attributes.data_ready, 0);
+
+ ret = hid_sensor_setup_trigger(indio_dev, name,
+ &magn_state->magn_flux_attributes);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "trigger setup failed\n");
+ return ret;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_trigger;
+ }
+
+ magn_state->callbacks.send_event = magn_3d_proc_event;
+ magn_state->callbacks.capture_sample = magn_3d_capture_sample;
+ magn_state->callbacks.pdev = pdev;
+ ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D,
+ &magn_state->callbacks);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "callback reg failed\n");
+ goto error_iio_unreg;
+ }
+
+ return ret;
+
+error_iio_unreg:
+ iio_device_unregister(indio_dev);
+error_remove_trigger:
+ hid_sensor_remove_trigger(indio_dev, &magn_state->magn_flux_attributes);
+ return ret;
+}
+
+/* Function to deinitialize the processing for usage id */
+static int hid_magn_3d_remove(struct platform_device *pdev)
+{
+ struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct magn_3d_state *magn_state = iio_priv(indio_dev);
+
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D);
+ iio_device_unregister(indio_dev);
+ hid_sensor_remove_trigger(indio_dev, &magn_state->magn_flux_attributes);
+
+ return 0;
+}
+
+static const struct platform_device_id hid_magn_3d_ids[] = {
+ {
+ /* Format: HID-SENSOR-usage_id_in_hex_lowercase */
+ .name = "HID-SENSOR-200083",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, hid_magn_3d_ids);
+
+static struct platform_driver hid_magn_3d_platform_driver = {
+ .id_table = hid_magn_3d_ids,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .pm = &hid_sensor_pm_ops,
+ },
+ .probe = hid_magn_3d_probe,
+ .remove = hid_magn_3d_remove,
+};
+module_platform_driver(hid_magn_3d_platform_driver);
+
+MODULE_DESCRIPTION("HID Sensor Magnetometer 3D");
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_HID);
diff --git a/drivers/iio/magnetometer/hmc5843.h b/drivers/iio/magnetometer/hmc5843.h
new file mode 100644
index 000000000..60fbb5431
--- /dev/null
+++ b/drivers/iio/magnetometer/hmc5843.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Header file for hmc5843 driver
+ *
+ * Split from hmc5843.c
+ * Copyright (C) Josef Gajdusek <atx@atx.name>
+ */
+
+#ifndef HMC5843_CORE_H
+#define HMC5843_CORE_H
+
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+
+#define HMC5843_CONFIG_REG_A 0x00
+#define HMC5843_CONFIG_REG_B 0x01
+#define HMC5843_MODE_REG 0x02
+#define HMC5843_DATA_OUT_MSB_REGS 0x03
+#define HMC5843_STATUS_REG 0x09
+#define HMC5843_ID_REG 0x0a
+#define HMC5843_ID_END 0x0c
+
+enum hmc5843_ids {
+ HMC5843_ID,
+ HMC5883_ID,
+ HMC5883L_ID,
+ HMC5983_ID,
+};
+
+/**
+ * struct hmc5843_data - device specific data
+ * @dev: actual device
+ * @lock: update and read regmap data
+ * @regmap: hardware access register maps
+ * @variant: describe chip variants
+ * @scan: buffer to pack data for passing to
+ * iio_push_to_buffers_with_timestamp()
+ */
+struct hmc5843_data {
+ struct device *dev;
+ struct mutex lock;
+ struct regmap *regmap;
+ const struct hmc5843_chip_info *variant;
+ struct iio_mount_matrix orientation;
+ struct {
+ __be16 chans[3];
+ s64 timestamp __aligned(8);
+ } scan;
+};
+
+int hmc5843_common_probe(struct device *dev, struct regmap *regmap,
+ enum hmc5843_ids id, const char *name);
+void hmc5843_common_remove(struct device *dev);
+
+extern const struct dev_pm_ops hmc5843_pm_ops;
+#endif /* HMC5843_CORE_H */
diff --git a/drivers/iio/magnetometer/hmc5843_core.c b/drivers/iio/magnetometer/hmc5843_core.c
new file mode 100644
index 000000000..c5521d61d
--- /dev/null
+++ b/drivers/iio/magnetometer/hmc5843_core.c
@@ -0,0 +1,688 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Device driver for the HMC5843 multi-chip module designed
+ * for low field magnetic sensing.
+ *
+ * Copyright (C) 2010 Texas Instruments
+ *
+ * Author: Shubhrajyoti Datta <shubhrajyoti@ti.com>
+ * Acknowledgment: Jonathan Cameron <jic23@kernel.org> for valuable inputs.
+ * Support for HMC5883 and HMC5883L by Peter Meerwald <pmeerw@pmeerw.net>.
+ * Split to multiple files by Josef Gajdusek <atx@atx.name> - 2014
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/delay.h>
+
+#include "hmc5843.h"
+
+/*
+ * Range gain settings in (+-)Ga
+ * Beware: HMC5843 and HMC5883 have different recommended sensor field
+ * ranges; default corresponds to +-1.0 Ga and +-1.3 Ga, respectively
+ */
+#define HMC5843_RANGE_GAIN_OFFSET 0x05
+#define HMC5843_RANGE_GAIN_DEFAULT 0x01
+#define HMC5843_RANGE_GAIN_MASK 0xe0
+
+/* Device status */
+#define HMC5843_DATA_READY 0x01
+#define HMC5843_DATA_OUTPUT_LOCK 0x02
+
+/* Mode register configuration */
+#define HMC5843_MODE_CONVERSION_CONTINUOUS 0x00
+#define HMC5843_MODE_CONVERSION_SINGLE 0x01
+#define HMC5843_MODE_IDLE 0x02
+#define HMC5843_MODE_SLEEP 0x03
+#define HMC5843_MODE_MASK 0x03
+
+/*
+ * HMC5843: Minimum data output rate
+ * HMC5883: Typical data output rate
+ */
+#define HMC5843_RATE_OFFSET 0x02
+#define HMC5843_RATE_DEFAULT 0x04
+#define HMC5843_RATE_MASK 0x1c
+
+/* Device measurement configuration */
+#define HMC5843_MEAS_CONF_NORMAL 0x00
+#define HMC5843_MEAS_CONF_POSITIVE_BIAS 0x01
+#define HMC5843_MEAS_CONF_NEGATIVE_BIAS 0x02
+#define HMC5843_MEAS_CONF_MASK 0x03
+
+/*
+ * API for setting the measurement configuration to
+ * Normal, Positive bias and Negative bias
+ *
+ * From the datasheet:
+ * 0 - Normal measurement configuration (default): In normal measurement
+ * configuration the device follows normal measurement flow. Pins BP
+ * and BN are left floating and high impedance.
+ *
+ * 1 - Positive bias configuration: In positive bias configuration, a
+ * positive current is forced across the resistive load on pins BP
+ * and BN.
+ *
+ * 2 - Negative bias configuration. In negative bias configuration, a
+ * negative current is forced across the resistive load on pins BP
+ * and BN.
+ *
+ * 3 - Only available on HMC5983. Magnetic sensor is disabled.
+ * Temperature sensor is enabled.
+ */
+
+static const char *const hmc5843_meas_conf_modes[] = {"normal", "positivebias",
+ "negativebias"};
+
+static const char *const hmc5983_meas_conf_modes[] = {"normal", "positivebias",
+ "negativebias",
+ "disabled"};
+/* Scaling factors: 10000000/Gain */
+static const int hmc5843_regval_to_nanoscale[] = {
+ 6173, 7692, 10309, 12821, 18868, 21739, 25641, 35714
+};
+
+static const int hmc5883_regval_to_nanoscale[] = {
+ 7812, 9766, 13021, 16287, 24096, 27701, 32573, 45662
+};
+
+static const int hmc5883l_regval_to_nanoscale[] = {
+ 7299, 9174, 12195, 15152, 22727, 25641, 30303, 43478
+};
+
+/*
+ * From the datasheet:
+ * Value | HMC5843 | HMC5883/HMC5883L
+ * | Data output rate (Hz) | Data output rate (Hz)
+ * 0 | 0.5 | 0.75
+ * 1 | 1 | 1.5
+ * 2 | 2 | 3
+ * 3 | 5 | 7.5
+ * 4 | 10 (default) | 15
+ * 5 | 20 | 30
+ * 6 | 50 | 75
+ * 7 | Not used | Not used
+ */
+static const int hmc5843_regval_to_samp_freq[][2] = {
+ {0, 500000}, {1, 0}, {2, 0}, {5, 0}, {10, 0}, {20, 0}, {50, 0}
+};
+
+static const int hmc5883_regval_to_samp_freq[][2] = {
+ {0, 750000}, {1, 500000}, {3, 0}, {7, 500000}, {15, 0}, {30, 0},
+ {75, 0}
+};
+
+static const int hmc5983_regval_to_samp_freq[][2] = {
+ {0, 750000}, {1, 500000}, {3, 0}, {7, 500000}, {15, 0}, {30, 0},
+ {75, 0}, {220, 0}
+};
+
+/* Describe chip variants */
+struct hmc5843_chip_info {
+ const struct iio_chan_spec *channels;
+ const int (*regval_to_samp_freq)[2];
+ const int n_regval_to_samp_freq;
+ const int *regval_to_nanoscale;
+ const int n_regval_to_nanoscale;
+};
+
+/* The lower two bits contain the current conversion mode */
+static s32 hmc5843_set_mode(struct hmc5843_data *data, u8 operating_mode)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = regmap_update_bits(data->regmap, HMC5843_MODE_REG,
+ HMC5843_MODE_MASK, operating_mode);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int hmc5843_wait_measurement(struct hmc5843_data *data)
+{
+ int tries = 150;
+ unsigned int val;
+ int ret;
+
+ while (tries-- > 0) {
+ ret = regmap_read(data->regmap, HMC5843_STATUS_REG, &val);
+ if (ret < 0)
+ return ret;
+ if (val & HMC5843_DATA_READY)
+ break;
+ msleep(20);
+ }
+
+ if (tries < 0) {
+ dev_err(data->dev, "data not ready\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/* Return the measurement value from the specified channel */
+static int hmc5843_read_measurement(struct hmc5843_data *data,
+ int idx, int *val)
+{
+ __be16 values[3];
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = hmc5843_wait_measurement(data);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+ ret = regmap_bulk_read(data->regmap, HMC5843_DATA_OUT_MSB_REGS,
+ values, sizeof(values));
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(be16_to_cpu(values[idx]), 15);
+ return IIO_VAL_INT;
+}
+
+static int hmc5843_set_meas_conf(struct hmc5843_data *data, u8 meas_conf)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_A,
+ HMC5843_MEAS_CONF_MASK, meas_conf);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static
+int hmc5843_show_measurement_configuration(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct hmc5843_data *data = iio_priv(indio_dev);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_A, &val);
+ if (ret)
+ return ret;
+
+ return val & HMC5843_MEAS_CONF_MASK;
+}
+
+static
+int hmc5843_set_measurement_configuration(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int meas_conf)
+{
+ struct hmc5843_data *data = iio_priv(indio_dev);
+
+ return hmc5843_set_meas_conf(data, meas_conf);
+}
+
+static const struct iio_mount_matrix *
+hmc5843_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct hmc5843_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_enum hmc5843_meas_conf_enum = {
+ .items = hmc5843_meas_conf_modes,
+ .num_items = ARRAY_SIZE(hmc5843_meas_conf_modes),
+ .get = hmc5843_show_measurement_configuration,
+ .set = hmc5843_set_measurement_configuration,
+};
+
+static const struct iio_chan_spec_ext_info hmc5843_ext_info[] = {
+ IIO_ENUM("meas_conf", IIO_SHARED_BY_TYPE, &hmc5843_meas_conf_enum),
+ IIO_ENUM_AVAILABLE("meas_conf", IIO_SHARED_BY_TYPE, &hmc5843_meas_conf_enum),
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, hmc5843_get_mount_matrix),
+ { }
+};
+
+static const struct iio_enum hmc5983_meas_conf_enum = {
+ .items = hmc5983_meas_conf_modes,
+ .num_items = ARRAY_SIZE(hmc5983_meas_conf_modes),
+ .get = hmc5843_show_measurement_configuration,
+ .set = hmc5843_set_measurement_configuration,
+};
+
+static const struct iio_chan_spec_ext_info hmc5983_ext_info[] = {
+ IIO_ENUM("meas_conf", IIO_SHARED_BY_TYPE, &hmc5983_meas_conf_enum),
+ IIO_ENUM_AVAILABLE("meas_conf", IIO_SHARED_BY_TYPE, &hmc5983_meas_conf_enum),
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, hmc5843_get_mount_matrix),
+ { }
+};
+
+static
+ssize_t hmc5843_show_samp_freq_avail(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev));
+ size_t len = 0;
+ int i;
+
+ for (i = 0; i < data->variant->n_regval_to_samp_freq; i++)
+ len += scnprintf(buf + len, PAGE_SIZE - len,
+ "%d.%d ", data->variant->regval_to_samp_freq[i][0],
+ data->variant->regval_to_samp_freq[i][1]);
+
+ /* replace trailing space by newline */
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(hmc5843_show_samp_freq_avail);
+
+static int hmc5843_set_samp_freq(struct hmc5843_data *data, u8 rate)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_A,
+ HMC5843_RATE_MASK,
+ rate << HMC5843_RATE_OFFSET);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int hmc5843_get_samp_freq_index(struct hmc5843_data *data,
+ int val, int val2)
+{
+ int i;
+
+ for (i = 0; i < data->variant->n_regval_to_samp_freq; i++)
+ if (val == data->variant->regval_to_samp_freq[i][0] &&
+ val2 == data->variant->regval_to_samp_freq[i][1])
+ return i;
+
+ return -EINVAL;
+}
+
+static int hmc5843_set_range_gain(struct hmc5843_data *data, u8 range)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_B,
+ HMC5843_RANGE_GAIN_MASK,
+ range << HMC5843_RANGE_GAIN_OFFSET);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static ssize_t hmc5843_show_scale_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev));
+
+ size_t len = 0;
+ int i;
+
+ for (i = 0; i < data->variant->n_regval_to_nanoscale; i++)
+ len += scnprintf(buf + len, PAGE_SIZE - len,
+ "0.%09d ", data->variant->regval_to_nanoscale[i]);
+
+ /* replace trailing space by newline */
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(scale_available, S_IRUGO,
+ hmc5843_show_scale_avail, NULL, 0);
+
+static int hmc5843_get_scale_index(struct hmc5843_data *data, int val, int val2)
+{
+ int i;
+
+ if (val)
+ return -EINVAL;
+
+ for (i = 0; i < data->variant->n_regval_to_nanoscale; i++)
+ if (val2 == data->variant->regval_to_nanoscale[i])
+ return i;
+
+ return -EINVAL;
+}
+
+static int hmc5843_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct hmc5843_data *data = iio_priv(indio_dev);
+ unsigned int rval;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return hmc5843_read_measurement(data, chan->scan_index, val);
+ case IIO_CHAN_INFO_SCALE:
+ ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_B, &rval);
+ if (ret < 0)
+ return ret;
+ rval >>= HMC5843_RANGE_GAIN_OFFSET;
+ *val = 0;
+ *val2 = data->variant->regval_to_nanoscale[rval];
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_A, &rval);
+ if (ret < 0)
+ return ret;
+ rval >>= HMC5843_RATE_OFFSET;
+ *val = data->variant->regval_to_samp_freq[rval][0];
+ *val2 = data->variant->regval_to_samp_freq[rval][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+ return -EINVAL;
+}
+
+static int hmc5843_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct hmc5843_data *data = iio_priv(indio_dev);
+ int rate, range;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ rate = hmc5843_get_samp_freq_index(data, val, val2);
+ if (rate < 0)
+ return -EINVAL;
+
+ return hmc5843_set_samp_freq(data, rate);
+ case IIO_CHAN_INFO_SCALE:
+ range = hmc5843_get_scale_index(data, val, val2);
+ if (range < 0)
+ return -EINVAL;
+
+ return hmc5843_set_range_gain(data, range);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hmc5843_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static irqreturn_t hmc5843_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct hmc5843_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = hmc5843_wait_measurement(data);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ goto done;
+ }
+
+ ret = regmap_bulk_read(data->regmap, HMC5843_DATA_OUT_MSB_REGS,
+ data->scan.chans, sizeof(data->scan.chans));
+
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto done;
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_get_time_ns(indio_dev));
+
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+#define HMC5843_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 = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+ .ext_info = hmc5843_ext_info, \
+ }
+
+#define HMC5983_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 = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+ .ext_info = hmc5983_ext_info, \
+ }
+
+static const struct iio_chan_spec hmc5843_channels[] = {
+ HMC5843_CHANNEL(X, 0),
+ HMC5843_CHANNEL(Y, 1),
+ HMC5843_CHANNEL(Z, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+/* Beware: Y and Z are exchanged on HMC5883 and 5983 */
+static const struct iio_chan_spec hmc5883_channels[] = {
+ HMC5843_CHANNEL(X, 0),
+ HMC5843_CHANNEL(Z, 1),
+ HMC5843_CHANNEL(Y, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct iio_chan_spec hmc5983_channels[] = {
+ HMC5983_CHANNEL(X, 0),
+ HMC5983_CHANNEL(Z, 1),
+ HMC5983_CHANNEL(Y, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static struct attribute *hmc5843_attributes[] = {
+ &iio_dev_attr_scale_available.dev_attr.attr,
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group hmc5843_group = {
+ .attrs = hmc5843_attributes,
+};
+
+static const struct hmc5843_chip_info hmc5843_chip_info_tbl[] = {
+ [HMC5843_ID] = {
+ .channels = hmc5843_channels,
+ .regval_to_samp_freq = hmc5843_regval_to_samp_freq,
+ .n_regval_to_samp_freq =
+ ARRAY_SIZE(hmc5843_regval_to_samp_freq),
+ .regval_to_nanoscale = hmc5843_regval_to_nanoscale,
+ .n_regval_to_nanoscale =
+ ARRAY_SIZE(hmc5843_regval_to_nanoscale),
+ },
+ [HMC5883_ID] = {
+ .channels = hmc5883_channels,
+ .regval_to_samp_freq = hmc5883_regval_to_samp_freq,
+ .n_regval_to_samp_freq =
+ ARRAY_SIZE(hmc5883_regval_to_samp_freq),
+ .regval_to_nanoscale = hmc5883_regval_to_nanoscale,
+ .n_regval_to_nanoscale =
+ ARRAY_SIZE(hmc5883_regval_to_nanoscale),
+ },
+ [HMC5883L_ID] = {
+ .channels = hmc5883_channels,
+ .regval_to_samp_freq = hmc5883_regval_to_samp_freq,
+ .n_regval_to_samp_freq =
+ ARRAY_SIZE(hmc5883_regval_to_samp_freq),
+ .regval_to_nanoscale = hmc5883l_regval_to_nanoscale,
+ .n_regval_to_nanoscale =
+ ARRAY_SIZE(hmc5883l_regval_to_nanoscale),
+ },
+ [HMC5983_ID] = {
+ .channels = hmc5983_channels,
+ .regval_to_samp_freq = hmc5983_regval_to_samp_freq,
+ .n_regval_to_samp_freq =
+ ARRAY_SIZE(hmc5983_regval_to_samp_freq),
+ .regval_to_nanoscale = hmc5883l_regval_to_nanoscale,
+ .n_regval_to_nanoscale =
+ ARRAY_SIZE(hmc5883l_regval_to_nanoscale),
+ }
+};
+
+static int hmc5843_init(struct hmc5843_data *data)
+{
+ int ret;
+ u8 id[3];
+
+ ret = regmap_bulk_read(data->regmap, HMC5843_ID_REG,
+ id, ARRAY_SIZE(id));
+ if (ret < 0)
+ return ret;
+ if (id[0] != 'H' || id[1] != '4' || id[2] != '3') {
+ dev_err(data->dev, "no HMC5843/5883/5883L/5983 sensor\n");
+ return -ENODEV;
+ }
+
+ ret = hmc5843_set_meas_conf(data, HMC5843_MEAS_CONF_NORMAL);
+ if (ret < 0)
+ return ret;
+ ret = hmc5843_set_samp_freq(data, HMC5843_RATE_DEFAULT);
+ if (ret < 0)
+ return ret;
+ ret = hmc5843_set_range_gain(data, HMC5843_RANGE_GAIN_DEFAULT);
+ if (ret < 0)
+ return ret;
+ return hmc5843_set_mode(data, HMC5843_MODE_CONVERSION_CONTINUOUS);
+}
+
+static const struct iio_info hmc5843_info = {
+ .attrs = &hmc5843_group,
+ .read_raw = &hmc5843_read_raw,
+ .write_raw = &hmc5843_write_raw,
+ .write_raw_get_fmt = &hmc5843_write_raw_get_fmt,
+};
+
+static const unsigned long hmc5843_scan_masks[] = {0x7, 0};
+
+static int hmc5843_common_suspend(struct device *dev)
+{
+ return hmc5843_set_mode(iio_priv(dev_get_drvdata(dev)),
+ HMC5843_MODE_SLEEP);
+}
+
+static int hmc5843_common_resume(struct device *dev)
+{
+ return hmc5843_set_mode(iio_priv(dev_get_drvdata(dev)),
+ HMC5843_MODE_CONVERSION_CONTINUOUS);
+}
+EXPORT_NS_SIMPLE_DEV_PM_OPS(hmc5843_pm_ops, hmc5843_common_suspend,
+ hmc5843_common_resume, IIO_HMC5843);
+
+int hmc5843_common_probe(struct device *dev, struct regmap *regmap,
+ enum hmc5843_ids id, const char *name)
+{
+ struct hmc5843_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, indio_dev);
+
+ /* default settings at probe */
+ data = iio_priv(indio_dev);
+ data->dev = dev;
+ data->regmap = regmap;
+ data->variant = &hmc5843_chip_info_tbl[id];
+ mutex_init(&data->lock);
+
+ ret = iio_read_mount_matrix(dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ indio_dev->name = name;
+ indio_dev->info = &hmc5843_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = data->variant->channels;
+ indio_dev->num_channels = 4;
+ indio_dev->available_scan_masks = hmc5843_scan_masks;
+
+ ret = hmc5843_init(data);
+ if (ret < 0)
+ return ret;
+
+ ret = iio_triggered_buffer_setup(indio_dev, NULL,
+ hmc5843_trigger_handler, NULL);
+ if (ret < 0)
+ goto buffer_setup_err;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0)
+ goto buffer_cleanup;
+
+ return 0;
+
+buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+buffer_setup_err:
+ hmc5843_set_mode(iio_priv(indio_dev), HMC5843_MODE_SLEEP);
+ return ret;
+}
+EXPORT_SYMBOL_NS(hmc5843_common_probe, IIO_HMC5843);
+
+void hmc5843_common_remove(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+
+ /* sleep mode to save power */
+ hmc5843_set_mode(iio_priv(indio_dev), HMC5843_MODE_SLEEP);
+}
+EXPORT_SYMBOL_NS(hmc5843_common_remove, IIO_HMC5843);
+
+MODULE_AUTHOR("Shubhrajyoti Datta <shubhrajyoti@ti.com>");
+MODULE_DESCRIPTION("HMC5843/5883/5883L/5983 core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/magnetometer/hmc5843_i2c.c b/drivers/iio/magnetometer/hmc5843_i2c.c
new file mode 100644
index 000000000..18a13dd51
--- /dev/null
+++ b/drivers/iio/magnetometer/hmc5843_i2c.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * i2c driver for hmc5843/5843/5883/5883l/5983
+ *
+ * Split from hmc5843.c
+ * Copyright (C) Josef Gajdusek <atx@atx.name>
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include "hmc5843.h"
+
+static const struct regmap_range hmc5843_readable_ranges[] = {
+ regmap_reg_range(0, HMC5843_ID_END),
+};
+
+static const struct regmap_access_table hmc5843_readable_table = {
+ .yes_ranges = hmc5843_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(hmc5843_readable_ranges),
+};
+
+static const struct regmap_range hmc5843_writable_ranges[] = {
+ regmap_reg_range(0, HMC5843_MODE_REG),
+};
+
+static const struct regmap_access_table hmc5843_writable_table = {
+ .yes_ranges = hmc5843_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(hmc5843_writable_ranges),
+};
+
+static const struct regmap_range hmc5843_volatile_ranges[] = {
+ regmap_reg_range(HMC5843_DATA_OUT_MSB_REGS, HMC5843_STATUS_REG),
+};
+
+static const struct regmap_access_table hmc5843_volatile_table = {
+ .yes_ranges = hmc5843_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(hmc5843_volatile_ranges),
+};
+
+static const struct regmap_config hmc5843_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .rd_table = &hmc5843_readable_table,
+ .wr_table = &hmc5843_writable_table,
+ .volatile_table = &hmc5843_volatile_table,
+
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int hmc5843_i2c_probe(struct i2c_client *cli,
+ const struct i2c_device_id *id)
+{
+ struct regmap *regmap = devm_regmap_init_i2c(cli,
+ &hmc5843_i2c_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return hmc5843_common_probe(&cli->dev,
+ regmap,
+ id->driver_data, id->name);
+}
+
+static void hmc5843_i2c_remove(struct i2c_client *client)
+{
+ hmc5843_common_remove(&client->dev);
+}
+
+static const struct i2c_device_id hmc5843_id[] = {
+ { "hmc5843", HMC5843_ID },
+ { "hmc5883", HMC5883_ID },
+ { "hmc5883l", HMC5883L_ID },
+ { "hmc5983", HMC5983_ID },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, hmc5843_id);
+
+static const struct of_device_id hmc5843_of_match[] = {
+ { .compatible = "honeywell,hmc5843", .data = (void *)HMC5843_ID },
+ { .compatible = "honeywell,hmc5883", .data = (void *)HMC5883_ID },
+ { .compatible = "honeywell,hmc5883l", .data = (void *)HMC5883L_ID },
+ { .compatible = "honeywell,hmc5983", .data = (void *)HMC5983_ID },
+ {}
+};
+MODULE_DEVICE_TABLE(of, hmc5843_of_match);
+
+static struct i2c_driver hmc5843_driver = {
+ .driver = {
+ .name = "hmc5843",
+ .pm = pm_sleep_ptr(&hmc5843_pm_ops),
+ .of_match_table = hmc5843_of_match,
+ },
+ .id_table = hmc5843_id,
+ .probe = hmc5843_i2c_probe,
+ .remove = hmc5843_i2c_remove,
+};
+module_i2c_driver(hmc5843_driver);
+
+MODULE_AUTHOR("Josef Gajdusek <atx@atx.name>");
+MODULE_DESCRIPTION("HMC5843/5883/5883L/5983 i2c driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_HMC5843);
diff --git a/drivers/iio/magnetometer/hmc5843_spi.c b/drivers/iio/magnetometer/hmc5843_spi.c
new file mode 100644
index 000000000..c42d2e2a6
--- /dev/null
+++ b/drivers/iio/magnetometer/hmc5843_spi.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SPI driver for hmc5983
+ *
+ * Copyright (C) Josef Gajdusek <atx@atx.name>
+ */
+
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/iio/iio.h>
+
+#include "hmc5843.h"
+
+static const struct regmap_range hmc5843_readable_ranges[] = {
+ regmap_reg_range(0, HMC5843_ID_END),
+};
+
+static const struct regmap_access_table hmc5843_readable_table = {
+ .yes_ranges = hmc5843_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(hmc5843_readable_ranges),
+};
+
+static const struct regmap_range hmc5843_writable_ranges[] = {
+ regmap_reg_range(0, HMC5843_MODE_REG),
+};
+
+static const struct regmap_access_table hmc5843_writable_table = {
+ .yes_ranges = hmc5843_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(hmc5843_writable_ranges),
+};
+
+static const struct regmap_range hmc5843_volatile_ranges[] = {
+ regmap_reg_range(HMC5843_DATA_OUT_MSB_REGS, HMC5843_STATUS_REG),
+};
+
+static const struct regmap_access_table hmc5843_volatile_table = {
+ .yes_ranges = hmc5843_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(hmc5843_volatile_ranges),
+};
+
+static const struct regmap_config hmc5843_spi_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .rd_table = &hmc5843_readable_table,
+ .wr_table = &hmc5843_writable_table,
+ .volatile_table = &hmc5843_volatile_table,
+
+ /* Autoincrement address pointer */
+ .read_flag_mask = 0xc0,
+
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int hmc5843_spi_probe(struct spi_device *spi)
+{
+ int ret;
+ struct regmap *regmap;
+ const struct spi_device_id *id = spi_get_device_id(spi);
+
+ spi->mode = SPI_MODE_3;
+ spi->max_speed_hz = 8000000;
+ spi->bits_per_word = 8;
+ ret = spi_setup(spi);
+ if (ret)
+ return ret;
+
+ regmap = devm_regmap_init_spi(spi, &hmc5843_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return hmc5843_common_probe(&spi->dev,
+ regmap,
+ id->driver_data, id->name);
+}
+
+static void hmc5843_spi_remove(struct spi_device *spi)
+{
+ hmc5843_common_remove(&spi->dev);
+}
+
+static const struct spi_device_id hmc5843_id[] = {
+ { "hmc5983", HMC5983_ID },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, hmc5843_id);
+
+static struct spi_driver hmc5843_driver = {
+ .driver = {
+ .name = "hmc5843",
+ .pm = pm_sleep_ptr(&hmc5843_pm_ops),
+ },
+ .id_table = hmc5843_id,
+ .probe = hmc5843_spi_probe,
+ .remove = hmc5843_spi_remove,
+};
+
+module_spi_driver(hmc5843_driver);
+
+MODULE_AUTHOR("Josef Gajdusek <atx@atx.name>");
+MODULE_DESCRIPTION("HMC5983 SPI driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_HMC5843);
diff --git a/drivers/iio/magnetometer/mag3110.c b/drivers/iio/magnetometer/mag3110.c
new file mode 100644
index 000000000..b870ad803
--- /dev/null
+++ b/drivers/iio/magnetometer/mag3110.c
@@ -0,0 +1,652 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * mag3110.c - Support for Freescale MAG3110 magnetometer sensor
+ *
+ * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * (7-bit I2C slave address 0x0e)
+ *
+ * TODO: irq, user offset, oversampling, continuous mode
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+
+#define MAG3110_STATUS 0x00
+#define MAG3110_OUT_X 0x01 /* MSB first */
+#define MAG3110_OUT_Y 0x03
+#define MAG3110_OUT_Z 0x05
+#define MAG3110_WHO_AM_I 0x07
+#define MAG3110_SYSMOD 0x08
+#define MAG3110_OFF_X 0x09 /* MSB first */
+#define MAG3110_OFF_Y 0x0b
+#define MAG3110_OFF_Z 0x0d
+#define MAG3110_DIE_TEMP 0x0f
+#define MAG3110_CTRL_REG1 0x10
+#define MAG3110_CTRL_REG2 0x11
+
+#define MAG3110_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0))
+
+#define MAG3110_CTRL_DR_MASK (BIT(7) | BIT(6) | BIT(5))
+#define MAG3110_CTRL_DR_SHIFT 5
+#define MAG3110_CTRL_DR_DEFAULT 0
+
+#define MAG3110_SYSMOD_MODE_MASK GENMASK(1, 0)
+
+#define MAG3110_CTRL_TM BIT(1) /* trigger single measurement */
+#define MAG3110_CTRL_AC BIT(0) /* continuous measurements */
+
+#define MAG3110_CTRL_AUTO_MRST_EN BIT(7) /* magnetic auto-reset */
+#define MAG3110_CTRL_RAW BIT(5) /* measurements not user-offset corrected */
+
+#define MAG3110_DEVICE_ID 0xc4
+
+/* Each client has this additional data */
+struct mag3110_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ u8 ctrl_reg1;
+ int sleep_val;
+ struct regulator *vdd_reg;
+ struct regulator *vddio_reg;
+ /* Ensure natural alignment of timestamp */
+ struct {
+ __be16 channels[3];
+ u8 temperature;
+ s64 ts __aligned(8);
+ } scan;
+};
+
+static int mag3110_request(struct mag3110_data *data)
+{
+ int ret, tries = 150;
+
+ if ((data->ctrl_reg1 & MAG3110_CTRL_AC) == 0) {
+ /* trigger measurement */
+ ret = i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1,
+ data->ctrl_reg1 | MAG3110_CTRL_TM);
+ if (ret < 0)
+ return ret;
+ }
+
+ while (tries-- > 0) {
+ ret = i2c_smbus_read_byte_data(data->client, MAG3110_STATUS);
+ if (ret < 0)
+ return ret;
+ /* wait for data ready */
+ if ((ret & MAG3110_STATUS_DRDY) == MAG3110_STATUS_DRDY)
+ break;
+
+ if (data->sleep_val <= 20)
+ usleep_range(data->sleep_val * 250, data->sleep_val * 500);
+ else
+ msleep(20);
+ }
+
+ if (tries < 0) {
+ dev_err(&data->client->dev, "data not ready\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mag3110_read(struct mag3110_data *data, __be16 buf[3])
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = mag3110_request(data);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+ ret = i2c_smbus_read_i2c_block_data(data->client,
+ MAG3110_OUT_X, 3 * sizeof(__be16), (u8 *) buf);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static ssize_t mag3110_show_int_plus_micros(char *buf,
+ const int (*vals)[2], int n)
+{
+ size_t len = 0;
+
+ while (n-- > 0)
+ len += scnprintf(buf + len, PAGE_SIZE - len,
+ "%d.%06d ", vals[n][0], vals[n][1]);
+
+ /* replace trailing space by newline */
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static int mag3110_get_int_plus_micros_index(const int (*vals)[2], int n,
+ int val, int val2)
+{
+ while (n-- > 0)
+ if (val == vals[n][0] && val2 == vals[n][1])
+ return n;
+
+ return -EINVAL;
+}
+
+static const int mag3110_samp_freq[8][2] = {
+ {80, 0}, {40, 0}, {20, 0}, {10, 0}, {5, 0}, {2, 500000},
+ {1, 250000}, {0, 625000}
+};
+
+static ssize_t mag3110_show_samp_freq_avail(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return mag3110_show_int_plus_micros(buf, mag3110_samp_freq, 8);
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(mag3110_show_samp_freq_avail);
+
+static int mag3110_get_samp_freq_index(struct mag3110_data *data,
+ int val, int val2)
+{
+ return mag3110_get_int_plus_micros_index(mag3110_samp_freq, 8, val,
+ val2);
+}
+
+static int mag3110_calculate_sleep(struct mag3110_data *data)
+{
+ int ret, i = data->ctrl_reg1 >> MAG3110_CTRL_DR_SHIFT;
+
+ if (mag3110_samp_freq[i][0] > 0)
+ ret = 1000 / mag3110_samp_freq[i][0];
+ else
+ ret = 1000;
+
+ return ret == 0 ? 1 : ret;
+}
+
+static int mag3110_standby(struct mag3110_data *data)
+{
+ return i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1,
+ data->ctrl_reg1 & ~MAG3110_CTRL_AC);
+}
+
+static int mag3110_wait_standby(struct mag3110_data *data)
+{
+ int ret, tries = 30;
+
+ /*
+ * Takes up to 1/ODR to come out of active mode into stby
+ * Longest expected period is 12.5seconds.
+ * We'll sleep for 500ms between checks
+ */
+ while (tries-- > 0) {
+ ret = i2c_smbus_read_byte_data(data->client, MAG3110_SYSMOD);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "i2c error\n");
+ return ret;
+ }
+ /* wait for standby */
+ if ((ret & MAG3110_SYSMOD_MODE_MASK) == 0)
+ break;
+
+ msleep_interruptible(500);
+ }
+
+ if (tries < 0) {
+ dev_err(&data->client->dev, "device not entering standby mode\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mag3110_active(struct mag3110_data *data)
+{
+ return i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1,
+ data->ctrl_reg1);
+}
+
+/* returns >0 if active, 0 if in standby and <0 on error */
+static int mag3110_is_active(struct mag3110_data *data)
+{
+ int reg;
+
+ reg = i2c_smbus_read_byte_data(data->client, MAG3110_CTRL_REG1);
+ if (reg < 0)
+ return reg;
+
+ return reg & MAG3110_CTRL_AC;
+}
+
+static int mag3110_change_config(struct mag3110_data *data, u8 reg, u8 val)
+{
+ int ret;
+ int is_active;
+
+ mutex_lock(&data->lock);
+
+ is_active = mag3110_is_active(data);
+ if (is_active < 0) {
+ ret = is_active;
+ goto fail;
+ }
+
+ /* config can only be changed when in standby */
+ if (is_active > 0) {
+ ret = mag3110_standby(data);
+ if (ret < 0)
+ goto fail;
+ }
+
+ /*
+ * After coming out of active we must wait for the part
+ * to transition to STBY. This can take up to 1 /ODR to occur
+ */
+ ret = mag3110_wait_standby(data);
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_smbus_write_byte_data(data->client, reg, val);
+ if (ret < 0)
+ goto fail;
+
+ if (is_active > 0) {
+ ret = mag3110_active(data);
+ if (ret < 0)
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int mag3110_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mag3110_data *data = iio_priv(indio_dev);
+ __be16 buffer[3];
+ int i, ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ switch (chan->type) {
+ case IIO_MAGN: /* in 0.1 uT / LSB */
+ ret = mag3110_read(data, buffer);
+ if (ret < 0)
+ goto release;
+ *val = sign_extend32(
+ be16_to_cpu(buffer[chan->scan_index]),
+ chan->scan_type.realbits - 1);
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_TEMP: /* in 1 C / LSB */
+ mutex_lock(&data->lock);
+ ret = mag3110_request(data);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ goto release;
+ }
+ ret = i2c_smbus_read_byte_data(data->client,
+ MAG3110_DIE_TEMP);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto release;
+ *val = sign_extend32(ret,
+ chan->scan_type.realbits - 1);
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+release:
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_MAGN:
+ *val = 0;
+ *val2 = 1000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ *val = 1000;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ i = data->ctrl_reg1 >> MAG3110_CTRL_DR_SHIFT;
+ *val = mag3110_samp_freq[i][0];
+ *val2 = mag3110_samp_freq[i][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ ret = i2c_smbus_read_word_swapped(data->client,
+ MAG3110_OFF_X + 2 * chan->scan_index);
+ if (ret < 0)
+ return ret;
+ *val = sign_extend32(ret >> 1, 14);
+ return IIO_VAL_INT;
+ }
+ return -EINVAL;
+}
+
+static int mag3110_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mag3110_data *data = iio_priv(indio_dev);
+ int rate, ret;
+
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ rate = mag3110_get_samp_freq_index(data, val, val2);
+ if (rate < 0) {
+ ret = -EINVAL;
+ break;
+ }
+ data->ctrl_reg1 &= 0xff & ~MAG3110_CTRL_DR_MASK
+ & ~MAG3110_CTRL_AC;
+ data->ctrl_reg1 |= rate << MAG3110_CTRL_DR_SHIFT;
+ data->sleep_val = mag3110_calculate_sleep(data);
+ if (data->sleep_val < 40)
+ data->ctrl_reg1 |= MAG3110_CTRL_AC;
+
+ ret = mag3110_change_config(data, MAG3110_CTRL_REG1,
+ data->ctrl_reg1);
+ break;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ if (val < -10000 || val > 10000) {
+ ret = -EINVAL;
+ break;
+ }
+ ret = i2c_smbus_write_word_swapped(data->client,
+ MAG3110_OFF_X + 2 * chan->scan_index, val << 1);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+}
+
+static irqreturn_t mag3110_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct mag3110_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = mag3110_read(data, data->scan.channels);
+ if (ret < 0)
+ goto done;
+
+ if (test_bit(3, indio_dev->active_scan_mask)) {
+ ret = i2c_smbus_read_byte_data(data->client,
+ MAG3110_DIE_TEMP);
+ if (ret < 0)
+ goto done;
+ data->scan.temperature = ret;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_get_time_ns(indio_dev));
+
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+#define MAG3110_CHANNEL(axis, idx) { \
+ .type = IIO_MAGN, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = idx, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+}
+
+static const struct iio_chan_spec mag3110_channels[] = {
+ MAG3110_CHANNEL(X, 0),
+ MAG3110_CHANNEL(Y, 1),
+ MAG3110_CHANNEL(Z, 2),
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = 3,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 8,
+ .storagebits = 8,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static struct attribute *mag3110_attributes[] = {
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group mag3110_group = {
+ .attrs = mag3110_attributes,
+};
+
+static const struct iio_info mag3110_info = {
+ .attrs = &mag3110_group,
+ .read_raw = &mag3110_read_raw,
+ .write_raw = &mag3110_write_raw,
+};
+
+static const unsigned long mag3110_scan_masks[] = {0x7, 0xf, 0};
+
+static int mag3110_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mag3110_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+
+ data->vdd_reg = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(data->vdd_reg))
+ return dev_err_probe(&client->dev, PTR_ERR(data->vdd_reg),
+ "failed to get VDD regulator!\n");
+
+ data->vddio_reg = devm_regulator_get(&client->dev, "vddio");
+ if (IS_ERR(data->vddio_reg))
+ return dev_err_probe(&client->dev, PTR_ERR(data->vddio_reg),
+ "failed to get VDDIO regulator!\n");
+
+ ret = regulator_enable(data->vdd_reg);
+ if (ret) {
+ dev_err(&client->dev, "failed to enable VDD regulator!\n");
+ return ret;
+ }
+
+ ret = regulator_enable(data->vddio_reg);
+ if (ret) {
+ dev_err(&client->dev, "failed to enable VDDIO regulator!\n");
+ goto disable_regulator_vdd;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, MAG3110_WHO_AM_I);
+ if (ret < 0)
+ goto disable_regulators;
+ if (ret != MAG3110_DEVICE_ID) {
+ ret = -ENODEV;
+ goto disable_regulators;
+ }
+
+ data->client = client;
+ mutex_init(&data->lock);
+
+ i2c_set_clientdata(client, indio_dev);
+ indio_dev->info = &mag3110_info;
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mag3110_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mag3110_channels);
+ indio_dev->available_scan_masks = mag3110_scan_masks;
+
+ data->ctrl_reg1 = MAG3110_CTRL_DR_DEFAULT << MAG3110_CTRL_DR_SHIFT;
+ data->sleep_val = mag3110_calculate_sleep(data);
+ if (data->sleep_val < 40)
+ data->ctrl_reg1 |= MAG3110_CTRL_AC;
+
+ ret = mag3110_change_config(data, MAG3110_CTRL_REG1, data->ctrl_reg1);
+ if (ret < 0)
+ goto disable_regulators;
+
+ ret = i2c_smbus_write_byte_data(client, MAG3110_CTRL_REG2,
+ MAG3110_CTRL_AUTO_MRST_EN);
+ if (ret < 0)
+ goto standby_on_error;
+
+ ret = iio_triggered_buffer_setup(indio_dev, NULL,
+ mag3110_trigger_handler, NULL);
+ if (ret < 0)
+ goto standby_on_error;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0)
+ goto buffer_cleanup;
+ return 0;
+
+buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+standby_on_error:
+ mag3110_standby(iio_priv(indio_dev));
+disable_regulators:
+ regulator_disable(data->vddio_reg);
+disable_regulator_vdd:
+ regulator_disable(data->vdd_reg);
+
+ return ret;
+}
+
+static void mag3110_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct mag3110_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+ mag3110_standby(iio_priv(indio_dev));
+ regulator_disable(data->vddio_reg);
+ regulator_disable(data->vdd_reg);
+}
+
+static int mag3110_suspend(struct device *dev)
+{
+ struct mag3110_data *data = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+ int ret;
+
+ ret = mag3110_standby(iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev))));
+ if (ret)
+ return ret;
+
+ ret = regulator_disable(data->vddio_reg);
+ if (ret) {
+ dev_err(dev, "failed to disable VDDIO regulator\n");
+ return ret;
+ }
+
+ ret = regulator_disable(data->vdd_reg);
+ if (ret) {
+ dev_err(dev, "failed to disable VDD regulator\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mag3110_resume(struct device *dev)
+{
+ struct mag3110_data *data = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+ int ret;
+
+ ret = regulator_enable(data->vdd_reg);
+ if (ret) {
+ dev_err(dev, "failed to enable VDD regulator\n");
+ return ret;
+ }
+
+ ret = regulator_enable(data->vddio_reg);
+ if (ret) {
+ dev_err(dev, "failed to enable VDDIO regulator\n");
+ regulator_disable(data->vdd_reg);
+ return ret;
+ }
+
+ return i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1,
+ data->ctrl_reg1);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(mag3110_pm_ops, mag3110_suspend,
+ mag3110_resume);
+
+static const struct i2c_device_id mag3110_id[] = {
+ { "mag3110", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mag3110_id);
+
+static const struct of_device_id mag3110_of_match[] = {
+ { .compatible = "fsl,mag3110" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mag3110_of_match);
+
+static struct i2c_driver mag3110_driver = {
+ .driver = {
+ .name = "mag3110",
+ .of_match_table = mag3110_of_match,
+ .pm = pm_sleep_ptr(&mag3110_pm_ops),
+ },
+ .probe = mag3110_probe,
+ .remove = mag3110_remove,
+ .id_table = mag3110_id,
+};
+module_i2c_driver(mag3110_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("Freescale MAG3110 magnetometer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/magnetometer/mmc35240.c b/drivers/iio/magnetometer/mmc35240.c
new file mode 100644
index 000000000..186edfcda
--- /dev/null
+++ b/drivers/iio/magnetometer/mmc35240.c
@@ -0,0 +1,587 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MMC35240 - MEMSIC 3-axis Magnetic Sensor
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * IIO driver for MMC35240 (7-bit I2C slave address 0x30).
+ *
+ * TODO: offset, ACPI, continuous measurement mode, PM
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/acpi.h>
+#include <linux/pm.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define MMC35240_DRV_NAME "mmc35240"
+#define MMC35240_REGMAP_NAME "mmc35240_regmap"
+
+#define MMC35240_REG_XOUT_L 0x00
+#define MMC35240_REG_XOUT_H 0x01
+#define MMC35240_REG_YOUT_L 0x02
+#define MMC35240_REG_YOUT_H 0x03
+#define MMC35240_REG_ZOUT_L 0x04
+#define MMC35240_REG_ZOUT_H 0x05
+
+#define MMC35240_REG_STATUS 0x06
+#define MMC35240_REG_CTRL0 0x07
+#define MMC35240_REG_CTRL1 0x08
+
+#define MMC35240_REG_ID 0x20
+
+#define MMC35240_STATUS_MEAS_DONE_BIT BIT(0)
+
+#define MMC35240_CTRL0_REFILL_BIT BIT(7)
+#define MMC35240_CTRL0_RESET_BIT BIT(6)
+#define MMC35240_CTRL0_SET_BIT BIT(5)
+#define MMC35240_CTRL0_CMM_BIT BIT(1)
+#define MMC35240_CTRL0_TM_BIT BIT(0)
+
+/* output resolution bits */
+#define MMC35240_CTRL1_BW0_BIT BIT(0)
+#define MMC35240_CTRL1_BW1_BIT BIT(1)
+
+#define MMC35240_CTRL1_BW_MASK (MMC35240_CTRL1_BW0_BIT | \
+ MMC35240_CTRL1_BW1_BIT)
+#define MMC35240_CTRL1_BW_SHIFT 0
+
+#define MMC35240_WAIT_CHARGE_PUMP 50000 /* us */
+#define MMC35240_WAIT_SET_RESET 1000 /* us */
+
+/*
+ * Memsic OTP process code piece is put here for reference:
+ *
+ * #define OTP_CONVERT(REG) ((float)((REG) >=32 ? (32 - (REG)) : (REG)) * 0.006
+ * 1) For X axis, the COEFFICIENT is always 1.
+ * 2) For Y axis, the COEFFICIENT is as below:
+ * f_OTP_matrix[4] = OTP_CONVERT(((reg_data[1] & 0x03) << 4) |
+ * (reg_data[2] >> 4)) + 1.0;
+ * 3) For Z axis, the COEFFICIENT is as below:
+ * f_OTP_matrix[8] = (OTP_CONVERT(reg_data[3] & 0x3f) + 1) * 1.35;
+ * We implemented the OTP logic into driver.
+ */
+
+/* scale = 1000 here for Y otp */
+#define MMC35240_OTP_CONVERT_Y(REG) (((REG) >= 32 ? (32 - (REG)) : (REG)) * 6)
+
+/* 0.6 * 1.35 = 0.81, scale 10000 for Z otp */
+#define MMC35240_OTP_CONVERT_Z(REG) (((REG) >= 32 ? (32 - (REG)) : (REG)) * 81)
+
+#define MMC35240_X_COEFF(x) (x)
+#define MMC35240_Y_COEFF(y) (y + 1000)
+#define MMC35240_Z_COEFF(z) (z + 13500)
+
+#define MMC35240_OTP_START_ADDR 0x1B
+
+enum mmc35240_resolution {
+ MMC35240_16_BITS_SLOW = 0, /* 7.92 ms */
+ MMC35240_16_BITS_FAST, /* 4.08 ms */
+ MMC35240_14_BITS, /* 2.16 ms */
+ MMC35240_12_BITS, /* 1.20 ms */
+};
+
+enum mmc35240_axis {
+ AXIS_X = 0,
+ AXIS_Y,
+ AXIS_Z,
+};
+
+static const struct {
+ int sens[3]; /* sensitivity per X, Y, Z axis */
+ int nfo; /* null field output */
+} mmc35240_props_table[] = {
+ /* 16 bits, 125Hz ODR */
+ {
+ {1024, 1024, 1024},
+ 32768,
+ },
+ /* 16 bits, 250Hz ODR */
+ {
+ {1024, 1024, 770},
+ 32768,
+ },
+ /* 14 bits, 450Hz ODR */
+ {
+ {256, 256, 193},
+ 8192,
+ },
+ /* 12 bits, 800Hz ODR */
+ {
+ {64, 64, 48},
+ 2048,
+ },
+};
+
+struct mmc35240_data {
+ struct i2c_client *client;
+ struct mutex mutex;
+ struct regmap *regmap;
+ enum mmc35240_resolution res;
+
+ /* OTP compensation */
+ int axis_coef[3];
+ int axis_scale[3];
+};
+
+static const struct {
+ int val;
+ int val2;
+} mmc35240_samp_freq[] = { {1, 500000},
+ {13, 0},
+ {25, 0},
+ {50, 0} };
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("1.5 13 25 50");
+
+#define MMC35240_CHANNEL(_axis) { \
+ .type = IIO_MAGN, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_ ## _axis, \
+ .address = AXIS_ ## _axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec mmc35240_channels[] = {
+ MMC35240_CHANNEL(X),
+ MMC35240_CHANNEL(Y),
+ MMC35240_CHANNEL(Z),
+};
+
+static struct attribute *mmc35240_attributes[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group mmc35240_attribute_group = {
+ .attrs = mmc35240_attributes,
+};
+
+static int mmc35240_get_samp_freq_index(struct mmc35240_data *data,
+ int val, int val2)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mmc35240_samp_freq); i++)
+ if (mmc35240_samp_freq[i].val == val &&
+ mmc35240_samp_freq[i].val2 == val2)
+ return i;
+ return -EINVAL;
+}
+
+static int mmc35240_hw_set(struct mmc35240_data *data, bool set)
+{
+ int ret;
+ u8 coil_bit;
+
+ /*
+ * Recharge the capacitor at VCAP pin, requested to be issued
+ * before a SET/RESET command.
+ */
+ ret = regmap_update_bits(data->regmap, MMC35240_REG_CTRL0,
+ MMC35240_CTRL0_REFILL_BIT,
+ MMC35240_CTRL0_REFILL_BIT);
+ if (ret < 0)
+ return ret;
+ usleep_range(MMC35240_WAIT_CHARGE_PUMP, MMC35240_WAIT_CHARGE_PUMP + 1);
+
+ if (set)
+ coil_bit = MMC35240_CTRL0_SET_BIT;
+ else
+ coil_bit = MMC35240_CTRL0_RESET_BIT;
+
+ return regmap_update_bits(data->regmap, MMC35240_REG_CTRL0,
+ coil_bit, coil_bit);
+
+}
+
+static int mmc35240_init(struct mmc35240_data *data)
+{
+ int ret, y_convert, z_convert;
+ unsigned int reg_id;
+ u8 otp_data[6];
+
+ ret = regmap_read(data->regmap, MMC35240_REG_ID, &reg_id);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading product id\n");
+ return ret;
+ }
+
+ dev_dbg(&data->client->dev, "MMC35240 chip id %x\n", reg_id);
+
+ /*
+ * make sure we restore sensor characteristics, by doing
+ * a SET/RESET sequence, the axis polarity being naturally
+ * aligned after RESET
+ */
+ ret = mmc35240_hw_set(data, true);
+ if (ret < 0)
+ return ret;
+ usleep_range(MMC35240_WAIT_SET_RESET, MMC35240_WAIT_SET_RESET + 1);
+
+ ret = mmc35240_hw_set(data, false);
+ if (ret < 0)
+ return ret;
+
+ /* set default sampling frequency */
+ ret = regmap_update_bits(data->regmap, MMC35240_REG_CTRL1,
+ MMC35240_CTRL1_BW_MASK,
+ data->res << MMC35240_CTRL1_BW_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_bulk_read(data->regmap, MMC35240_OTP_START_ADDR,
+ otp_data, sizeof(otp_data));
+ if (ret < 0)
+ return ret;
+
+ y_convert = MMC35240_OTP_CONVERT_Y(((otp_data[1] & 0x03) << 4) |
+ (otp_data[2] >> 4));
+ z_convert = MMC35240_OTP_CONVERT_Z(otp_data[3] & 0x3f);
+
+ data->axis_coef[0] = MMC35240_X_COEFF(1);
+ data->axis_coef[1] = MMC35240_Y_COEFF(y_convert);
+ data->axis_coef[2] = MMC35240_Z_COEFF(z_convert);
+
+ data->axis_scale[0] = 1;
+ data->axis_scale[1] = 1000;
+ data->axis_scale[2] = 10000;
+
+ return 0;
+}
+
+static int mmc35240_take_measurement(struct mmc35240_data *data)
+{
+ int ret, tries = 100;
+ unsigned int reg_status;
+
+ ret = regmap_write(data->regmap, MMC35240_REG_CTRL0,
+ MMC35240_CTRL0_TM_BIT);
+ if (ret < 0)
+ return ret;
+
+ while (tries-- > 0) {
+ ret = regmap_read(data->regmap, MMC35240_REG_STATUS,
+ &reg_status);
+ if (ret < 0)
+ return ret;
+ if (reg_status & MMC35240_STATUS_MEAS_DONE_BIT)
+ break;
+ /* minimum wait time to complete measurement is 10 ms */
+ usleep_range(10000, 11000);
+ }
+
+ if (tries < 0) {
+ dev_err(&data->client->dev, "data not ready\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mmc35240_read_measurement(struct mmc35240_data *data, __le16 buf[3])
+{
+ int ret;
+
+ ret = mmc35240_take_measurement(data);
+ if (ret < 0)
+ return ret;
+
+ return regmap_bulk_read(data->regmap, MMC35240_REG_XOUT_L, buf,
+ 3 * sizeof(__le16));
+}
+
+/**
+ * mmc35240_raw_to_mgauss - convert raw readings to milli gauss. Also apply
+ * compensation for output value.
+ *
+ * @data: device private data
+ * @index: axis index for which we want the conversion
+ * @buf: raw data to be converted, 2 bytes in little endian format
+ * @val: compensated output reading (unit is milli gauss)
+ *
+ * Returns: 0 in case of success, -EINVAL when @index is not valid
+ */
+static int mmc35240_raw_to_mgauss(struct mmc35240_data *data, int index,
+ __le16 buf[], int *val)
+{
+ int raw[3];
+ int sens[3];
+ int nfo;
+
+ raw[AXIS_X] = le16_to_cpu(buf[AXIS_X]);
+ raw[AXIS_Y] = le16_to_cpu(buf[AXIS_Y]);
+ raw[AXIS_Z] = le16_to_cpu(buf[AXIS_Z]);
+
+ sens[AXIS_X] = mmc35240_props_table[data->res].sens[AXIS_X];
+ sens[AXIS_Y] = mmc35240_props_table[data->res].sens[AXIS_Y];
+ sens[AXIS_Z] = mmc35240_props_table[data->res].sens[AXIS_Z];
+
+ nfo = mmc35240_props_table[data->res].nfo;
+
+ switch (index) {
+ case AXIS_X:
+ *val = (raw[AXIS_X] - nfo) * 1000 / sens[AXIS_X];
+ break;
+ case AXIS_Y:
+ *val = (raw[AXIS_Y] - nfo) * 1000 / sens[AXIS_Y] -
+ (raw[AXIS_Z] - nfo) * 1000 / sens[AXIS_Z];
+ break;
+ case AXIS_Z:
+ *val = (raw[AXIS_Y] - nfo) * 1000 / sens[AXIS_Y] +
+ (raw[AXIS_Z] - nfo) * 1000 / sens[AXIS_Z];
+ break;
+ default:
+ return -EINVAL;
+ }
+ /* apply OTP compensation */
+ *val = (*val) * data->axis_coef[index] / data->axis_scale[index];
+
+ return 0;
+}
+
+static int mmc35240_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct mmc35240_data *data = iio_priv(indio_dev);
+ int ret, i;
+ unsigned int reg;
+ __le16 buf[3];
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&data->mutex);
+ ret = mmc35240_read_measurement(data, buf);
+ mutex_unlock(&data->mutex);
+ if (ret < 0)
+ return ret;
+ ret = mmc35240_raw_to_mgauss(data, chan->address, buf, val);
+ if (ret < 0)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = 1000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ mutex_lock(&data->mutex);
+ ret = regmap_read(data->regmap, MMC35240_REG_CTRL1, &reg);
+ mutex_unlock(&data->mutex);
+ if (ret < 0)
+ return ret;
+
+ i = (reg & MMC35240_CTRL1_BW_MASK) >> MMC35240_CTRL1_BW_SHIFT;
+ if (i < 0 || i >= ARRAY_SIZE(mmc35240_samp_freq))
+ return -EINVAL;
+
+ *val = mmc35240_samp_freq[i].val;
+ *val2 = mmc35240_samp_freq[i].val2;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mmc35240_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct mmc35240_data *data = iio_priv(indio_dev);
+ int i, ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ i = mmc35240_get_samp_freq_index(data, val, val2);
+ if (i < 0)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = regmap_update_bits(data->regmap, MMC35240_REG_CTRL1,
+ MMC35240_CTRL1_BW_MASK,
+ i << MMC35240_CTRL1_BW_SHIFT);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info mmc35240_info = {
+ .read_raw = mmc35240_read_raw,
+ .write_raw = mmc35240_write_raw,
+ .attrs = &mmc35240_attribute_group,
+};
+
+static bool mmc35240_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MMC35240_REG_CTRL0:
+ case MMC35240_REG_CTRL1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mmc35240_is_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MMC35240_REG_XOUT_L:
+ case MMC35240_REG_XOUT_H:
+ case MMC35240_REG_YOUT_L:
+ case MMC35240_REG_YOUT_H:
+ case MMC35240_REG_ZOUT_L:
+ case MMC35240_REG_ZOUT_H:
+ case MMC35240_REG_STATUS:
+ case MMC35240_REG_ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mmc35240_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MMC35240_REG_CTRL0:
+ case MMC35240_REG_CTRL1:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct reg_default mmc35240_reg_defaults[] = {
+ { MMC35240_REG_CTRL0, 0x00 },
+ { MMC35240_REG_CTRL1, 0x00 },
+};
+
+static const struct regmap_config mmc35240_regmap_config = {
+ .name = MMC35240_REGMAP_NAME,
+
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = MMC35240_REG_ID,
+ .cache_type = REGCACHE_FLAT,
+
+ .writeable_reg = mmc35240_is_writeable_reg,
+ .readable_reg = mmc35240_is_readable_reg,
+ .volatile_reg = mmc35240_is_volatile_reg,
+
+ .reg_defaults = mmc35240_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(mmc35240_reg_defaults),
+};
+
+static int mmc35240_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mmc35240_data *data;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(client, &mmc35240_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "regmap initialization failed\n");
+ return PTR_ERR(regmap);
+ }
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ data->regmap = regmap;
+ data->res = MMC35240_16_BITS_SLOW;
+
+ mutex_init(&data->mutex);
+
+ indio_dev->info = &mmc35240_info;
+ indio_dev->name = MMC35240_DRV_NAME;
+ indio_dev->channels = mmc35240_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mmc35240_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = mmc35240_init(data);
+ if (ret < 0) {
+ dev_err(&client->dev, "mmc35240 chip init failed\n");
+ return ret;
+ }
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int mmc35240_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mmc35240_data *data = iio_priv(indio_dev);
+
+ regcache_cache_only(data->regmap, true);
+
+ return 0;
+}
+
+static int mmc35240_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mmc35240_data *data = iio_priv(indio_dev);
+ int ret;
+
+ regcache_mark_dirty(data->regmap);
+ ret = regcache_sync_region(data->regmap, MMC35240_REG_CTRL0,
+ MMC35240_REG_CTRL1);
+ if (ret < 0)
+ dev_err(dev, "Failed to restore control registers\n");
+
+ regcache_cache_only(data->regmap, false);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(mmc35240_pm_ops, mmc35240_suspend,
+ mmc35240_resume);
+
+static const struct of_device_id mmc35240_of_match[] = {
+ { .compatible = "memsic,mmc35240", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mmc35240_of_match);
+
+static const struct acpi_device_id mmc35240_acpi_match[] = {
+ {"MMC35240", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, mmc35240_acpi_match);
+
+static const struct i2c_device_id mmc35240_id[] = {
+ {"mmc35240", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, mmc35240_id);
+
+static struct i2c_driver mmc35240_driver = {
+ .driver = {
+ .name = MMC35240_DRV_NAME,
+ .of_match_table = mmc35240_of_match,
+ .pm = pm_sleep_ptr(&mmc35240_pm_ops),
+ .acpi_match_table = ACPI_PTR(mmc35240_acpi_match),
+ },
+ .probe = mmc35240_probe,
+ .id_table = mmc35240_id,
+};
+
+module_i2c_driver(mmc35240_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
+MODULE_DESCRIPTION("MEMSIC MMC35240 magnetic sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/magnetometer/rm3100-core.c b/drivers/iio/magnetometer/rm3100-core.c
new file mode 100644
index 000000000..699382044
--- /dev/null
+++ b/drivers/iio/magnetometer/rm3100-core.c
@@ -0,0 +1,605 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PNI RM3100 3-axis geomagnetic sensor driver core.
+ *
+ * Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
+ *
+ * User Manual available at
+ * <https://www.pnicorp.com/download/rm3100-user-manual/>
+ *
+ * TODO: event generation, pm.
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#include <asm/unaligned.h>
+
+#include "rm3100.h"
+
+/* Cycle Count Registers. */
+#define RM3100_REG_CC_X 0x05
+#define RM3100_REG_CC_Y 0x07
+#define RM3100_REG_CC_Z 0x09
+
+/* Poll Measurement Mode register. */
+#define RM3100_REG_POLL 0x00
+#define RM3100_POLL_X BIT(4)
+#define RM3100_POLL_Y BIT(5)
+#define RM3100_POLL_Z BIT(6)
+
+/* Continuous Measurement Mode register. */
+#define RM3100_REG_CMM 0x01
+#define RM3100_CMM_START BIT(0)
+#define RM3100_CMM_X BIT(4)
+#define RM3100_CMM_Y BIT(5)
+#define RM3100_CMM_Z BIT(6)
+
+/* TiMe Rate Configuration register. */
+#define RM3100_REG_TMRC 0x0B
+#define RM3100_TMRC_OFFSET 0x92
+
+/* Result Status register. */
+#define RM3100_REG_STATUS 0x34
+#define RM3100_STATUS_DRDY BIT(7)
+
+/* Measurement result registers. */
+#define RM3100_REG_MX2 0x24
+#define RM3100_REG_MY2 0x27
+#define RM3100_REG_MZ2 0x2a
+
+#define RM3100_W_REG_START RM3100_REG_POLL
+#define RM3100_W_REG_END RM3100_REG_TMRC
+#define RM3100_R_REG_START RM3100_REG_POLL
+#define RM3100_R_REG_END RM3100_REG_STATUS
+#define RM3100_V_REG_START RM3100_REG_POLL
+#define RM3100_V_REG_END RM3100_REG_STATUS
+
+/*
+ * This is computed by hand, is the sum of channel storage bits and padding
+ * bits, which is 4+4+4+12=24 in here.
+ */
+#define RM3100_SCAN_BYTES 24
+
+#define RM3100_CMM_AXIS_SHIFT 4
+
+struct rm3100_data {
+ struct regmap *regmap;
+ struct completion measuring_done;
+ bool use_interrupt;
+ int conversion_time;
+ int scale;
+ /* Ensure naturally aligned timestamp */
+ u8 buffer[RM3100_SCAN_BYTES] __aligned(8);
+ struct iio_trigger *drdy_trig;
+
+ /*
+ * This lock is for protecting the consistency of series of i2c
+ * operations, that is, to make sure a measurement process will
+ * not be interrupted by a set frequency operation, which should
+ * be taken where a series of i2c operation starts, released where
+ * the operation ends.
+ */
+ struct mutex lock;
+};
+
+static const struct regmap_range rm3100_readable_ranges[] = {
+ regmap_reg_range(RM3100_R_REG_START, RM3100_R_REG_END),
+};
+
+const struct regmap_access_table rm3100_readable_table = {
+ .yes_ranges = rm3100_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(rm3100_readable_ranges),
+};
+EXPORT_SYMBOL_NS_GPL(rm3100_readable_table, IIO_RM3100);
+
+static const struct regmap_range rm3100_writable_ranges[] = {
+ regmap_reg_range(RM3100_W_REG_START, RM3100_W_REG_END),
+};
+
+const struct regmap_access_table rm3100_writable_table = {
+ .yes_ranges = rm3100_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(rm3100_writable_ranges),
+};
+EXPORT_SYMBOL_NS_GPL(rm3100_writable_table, IIO_RM3100);
+
+static const struct regmap_range rm3100_volatile_ranges[] = {
+ regmap_reg_range(RM3100_V_REG_START, RM3100_V_REG_END),
+};
+
+const struct regmap_access_table rm3100_volatile_table = {
+ .yes_ranges = rm3100_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(rm3100_volatile_ranges),
+};
+EXPORT_SYMBOL_NS_GPL(rm3100_volatile_table, IIO_RM3100);
+
+static irqreturn_t rm3100_thread_fn(int irq, void *d)
+{
+ struct iio_dev *indio_dev = d;
+ struct rm3100_data *data = iio_priv(indio_dev);
+
+ /*
+ * Write operation to any register or read operation
+ * to first byte of results will clear the interrupt.
+ */
+ regmap_write(data->regmap, RM3100_REG_POLL, 0);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rm3100_irq_handler(int irq, void *d)
+{
+ struct iio_dev *indio_dev = d;
+ struct rm3100_data *data = iio_priv(indio_dev);
+
+ if (!iio_buffer_enabled(indio_dev))
+ complete(&data->measuring_done);
+ else
+ iio_trigger_poll(data->drdy_trig);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static int rm3100_wait_measurement(struct rm3100_data *data)
+{
+ struct regmap *regmap = data->regmap;
+ unsigned int val;
+ int tries = 20;
+ int ret;
+
+ /*
+ * A read cycle of 400kbits i2c bus is about 20us, plus the time
+ * used for scheduling, a read cycle of fast mode of this device
+ * can reach 1.7ms, it may be possible for data to arrive just
+ * after we check the RM3100_REG_STATUS. In this case, irq_handler is
+ * called before measuring_done is reinitialized, it will wait
+ * forever for data that has already been ready.
+ * Reinitialize measuring_done before looking up makes sure we
+ * will always capture interrupt no matter when it happens.
+ */
+ if (data->use_interrupt)
+ reinit_completion(&data->measuring_done);
+
+ ret = regmap_read(regmap, RM3100_REG_STATUS, &val);
+ if (ret < 0)
+ return ret;
+
+ if ((val & RM3100_STATUS_DRDY) != RM3100_STATUS_DRDY) {
+ if (data->use_interrupt) {
+ ret = wait_for_completion_timeout(&data->measuring_done,
+ msecs_to_jiffies(data->conversion_time));
+ if (!ret)
+ return -ETIMEDOUT;
+ } else {
+ do {
+ usleep_range(1000, 5000);
+
+ ret = regmap_read(regmap, RM3100_REG_STATUS,
+ &val);
+ if (ret < 0)
+ return ret;
+
+ if (val & RM3100_STATUS_DRDY)
+ break;
+ } while (--tries);
+ if (!tries)
+ return -ETIMEDOUT;
+ }
+ }
+ return 0;
+}
+
+static int rm3100_read_mag(struct rm3100_data *data, int idx, int *val)
+{
+ struct regmap *regmap = data->regmap;
+ u8 buffer[3];
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = regmap_write(regmap, RM3100_REG_POLL, BIT(4 + idx));
+ if (ret < 0)
+ goto unlock_return;
+
+ ret = rm3100_wait_measurement(data);
+ if (ret < 0)
+ goto unlock_return;
+
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2 + 3 * idx, buffer, 3);
+ if (ret < 0)
+ goto unlock_return;
+ mutex_unlock(&data->lock);
+
+ *val = sign_extend32(get_unaligned_be24(&buffer[0]), 23);
+
+ return IIO_VAL_INT;
+
+unlock_return:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+#define RM3100_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 = 's', \
+ .realbits = 24, \
+ .storagebits = 32, \
+ .shift = 8, \
+ .endianness = IIO_BE, \
+ }, \
+ }
+
+static const struct iio_chan_spec rm3100_channels[] = {
+ RM3100_CHANNEL(X, 0),
+ RM3100_CHANNEL(Y, 1),
+ RM3100_CHANNEL(Z, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
+ "600 300 150 75 37 18 9 4.5 2.3 1.2 0.6 0.3 0.015 0.075"
+);
+
+static struct attribute *rm3100_attributes[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group rm3100_attribute_group = {
+ .attrs = rm3100_attributes,
+};
+
+#define RM3100_SAMP_NUM 14
+
+/*
+ * Frequency : rm3100_samp_rates[][0].rm3100_samp_rates[][1]Hz.
+ * Time between reading: rm3100_sam_rates[][2]ms.
+ * The first one is actually 1.7ms.
+ */
+static const int rm3100_samp_rates[RM3100_SAMP_NUM][3] = {
+ {600, 0, 2}, {300, 0, 3}, {150, 0, 7}, {75, 0, 13}, {37, 0, 27},
+ {18, 0, 55}, {9, 0, 110}, {4, 500000, 220}, {2, 300000, 440},
+ {1, 200000, 800}, {0, 600000, 1600}, {0, 300000, 3300},
+ {0, 15000, 6700}, {0, 75000, 13000}
+};
+
+static int rm3100_get_samp_freq(struct rm3100_data *data, int *val, int *val2)
+{
+ unsigned int tmp;
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = regmap_read(data->regmap, RM3100_REG_TMRC, &tmp);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+ *val = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][0];
+ *val2 = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int rm3100_set_cycle_count(struct rm3100_data *data, int val)
+{
+ int ret;
+ u8 i;
+
+ for (i = 0; i < 3; i++) {
+ ret = regmap_write(data->regmap, RM3100_REG_CC_X + 2 * i, val);
+ if (ret < 0)
+ return ret;
+ }
+
+ /*
+ * The scale of this sensor depends on the cycle count value, these
+ * three values are corresponding to the cycle count value 50, 100,
+ * 200. scale = output / gain * 10^4.
+ */
+ switch (val) {
+ case 50:
+ data->scale = 500;
+ break;
+ case 100:
+ data->scale = 263;
+ break;
+ /*
+ * case 200:
+ * This function will never be called by users' code, so here we
+ * assume that it will never get a wrong parameter.
+ */
+ default:
+ data->scale = 133;
+ }
+
+ return 0;
+}
+
+static int rm3100_set_samp_freq(struct iio_dev *indio_dev, int val, int val2)
+{
+ struct rm3100_data *data = iio_priv(indio_dev);
+ struct regmap *regmap = data->regmap;
+ unsigned int cycle_count;
+ int ret;
+ int i;
+
+ mutex_lock(&data->lock);
+ /* All cycle count registers use the same value. */
+ ret = regmap_read(regmap, RM3100_REG_CC_X, &cycle_count);
+ if (ret < 0)
+ goto unlock_return;
+
+ for (i = 0; i < RM3100_SAMP_NUM; i++) {
+ if (val == rm3100_samp_rates[i][0] &&
+ val2 == rm3100_samp_rates[i][1])
+ break;
+ }
+ if (i == RM3100_SAMP_NUM) {
+ ret = -EINVAL;
+ goto unlock_return;
+ }
+
+ ret = regmap_write(regmap, RM3100_REG_TMRC, i + RM3100_TMRC_OFFSET);
+ if (ret < 0)
+ goto unlock_return;
+
+ /* Checking if cycle count registers need changing. */
+ if (val == 600 && cycle_count == 200) {
+ ret = rm3100_set_cycle_count(data, 100);
+ if (ret < 0)
+ goto unlock_return;
+ } else if (val != 600 && cycle_count == 100) {
+ ret = rm3100_set_cycle_count(data, 200);
+ if (ret < 0)
+ goto unlock_return;
+ }
+
+ if (iio_buffer_enabled(indio_dev)) {
+ /* Writing TMRC registers requires CMM reset. */
+ ret = regmap_write(regmap, RM3100_REG_CMM, 0);
+ if (ret < 0)
+ goto unlock_return;
+ ret = regmap_write(data->regmap, RM3100_REG_CMM,
+ (*indio_dev->active_scan_mask & 0x7) <<
+ RM3100_CMM_AXIS_SHIFT | RM3100_CMM_START);
+ if (ret < 0)
+ goto unlock_return;
+ }
+ mutex_unlock(&data->lock);
+
+ data->conversion_time = rm3100_samp_rates[i][2] * 2;
+ return 0;
+
+unlock_return:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int rm3100_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct rm3100_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ ret = rm3100_read_mag(data, chan->scan_index, val);
+ iio_device_release_direct_mode(indio_dev);
+
+ return ret;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = data->scale;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return rm3100_get_samp_freq(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int rm3100_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return rm3100_set_samp_freq(indio_dev, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info rm3100_info = {
+ .attrs = &rm3100_attribute_group,
+ .read_raw = rm3100_read_raw,
+ .write_raw = rm3100_write_raw,
+};
+
+static int rm3100_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct rm3100_data *data = iio_priv(indio_dev);
+
+ /* Starting channels enabled. */
+ return regmap_write(data->regmap, RM3100_REG_CMM,
+ (*indio_dev->active_scan_mask & 0x7) << RM3100_CMM_AXIS_SHIFT |
+ RM3100_CMM_START);
+}
+
+static int rm3100_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct rm3100_data *data = iio_priv(indio_dev);
+
+ return regmap_write(data->regmap, RM3100_REG_CMM, 0);
+}
+
+static const struct iio_buffer_setup_ops rm3100_buffer_ops = {
+ .preenable = rm3100_buffer_preenable,
+ .postdisable = rm3100_buffer_postdisable,
+};
+
+static irqreturn_t rm3100_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ unsigned long scan_mask = *indio_dev->active_scan_mask;
+ unsigned int mask_len = indio_dev->masklength;
+ struct rm3100_data *data = iio_priv(indio_dev);
+ struct regmap *regmap = data->regmap;
+ int ret, i, bit;
+
+ mutex_lock(&data->lock);
+ switch (scan_mask) {
+ case BIT(0) | BIT(1) | BIT(2):
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2, data->buffer, 9);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto done;
+ /* Convert XXXYYYZZZxxx to XXXxYYYxZZZx. x for paddings. */
+ for (i = 2; i > 0; i--)
+ memmove(data->buffer + i * 4, data->buffer + i * 3, 3);
+ break;
+ case BIT(0) | BIT(1):
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2, data->buffer, 6);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto done;
+ memmove(data->buffer + 4, data->buffer + 3, 3);
+ break;
+ case BIT(1) | BIT(2):
+ ret = regmap_bulk_read(regmap, RM3100_REG_MY2, data->buffer, 6);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto done;
+ memmove(data->buffer + 4, data->buffer + 3, 3);
+ break;
+ case BIT(0) | BIT(2):
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2, data->buffer, 9);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto done;
+ memmove(data->buffer + 4, data->buffer + 6, 3);
+ break;
+ default:
+ for_each_set_bit(bit, &scan_mask, mask_len) {
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2 + 3 * bit,
+ data->buffer, 3);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ goto done;
+ }
+ }
+ mutex_unlock(&data->lock);
+ }
+ /*
+ * Always using the same buffer so that we wouldn't need to set the
+ * paddings to 0 in case of leaking any data.
+ */
+ iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
+ pf->timestamp);
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+int rm3100_common_probe(struct device *dev, struct regmap *regmap, int irq)
+{
+ struct iio_dev *indio_dev;
+ struct rm3100_data *data;
+ unsigned int tmp;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->regmap = regmap;
+
+ mutex_init(&data->lock);
+
+ indio_dev->name = "rm3100";
+ indio_dev->info = &rm3100_info;
+ indio_dev->channels = rm3100_channels;
+ indio_dev->num_channels = ARRAY_SIZE(rm3100_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ if (!irq)
+ data->use_interrupt = false;
+ else {
+ data->use_interrupt = true;
+
+ init_completion(&data->measuring_done);
+ ret = devm_request_threaded_irq(dev,
+ irq,
+ rm3100_irq_handler,
+ rm3100_thread_fn,
+ IRQF_TRIGGER_HIGH |
+ IRQF_ONESHOT,
+ indio_dev->name,
+ indio_dev);
+ if (ret < 0) {
+ dev_err(dev, "request irq line failed.\n");
+ return ret;
+ }
+
+ data->drdy_trig = devm_iio_trigger_alloc(dev, "%s-drdy%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->drdy_trig)
+ return -ENOMEM;
+
+ ret = devm_iio_trigger_register(dev, data->drdy_trig);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ rm3100_trigger_handler,
+ &rm3100_buffer_ops);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, RM3100_REG_TMRC, &tmp);
+ if (ret < 0)
+ return ret;
+ /* Initializing max wait time, which is double conversion time. */
+ data->conversion_time = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][2]
+ * 2;
+
+ /* Cycle count values may not be what we want. */
+ if ((tmp - RM3100_TMRC_OFFSET) == 0)
+ rm3100_set_cycle_count(data, 100);
+ else
+ rm3100_set_cycle_count(data, 200);
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(rm3100_common_probe, IIO_RM3100);
+
+MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
+MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer i2c driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/magnetometer/rm3100-i2c.c b/drivers/iio/magnetometer/rm3100-i2c.c
new file mode 100644
index 000000000..ba669ab71
--- /dev/null
+++ b/drivers/iio/magnetometer/rm3100-i2c.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for PNI RM3100 3-axis geomagnetic sensor on a i2c bus.
+ *
+ * Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
+ *
+ * i2c slave address: 0x20 + SA1 << 1 + SA0.
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+#include "rm3100.h"
+
+static const struct regmap_config rm3100_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .rd_table = &rm3100_readable_table,
+ .wr_table = &rm3100_writable_table,
+ .volatile_table = &rm3100_volatile_table,
+
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int rm3100_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_i2c(client, &rm3100_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return rm3100_common_probe(&client->dev, regmap, client->irq);
+}
+
+static const struct of_device_id rm3100_dt_match[] = {
+ { .compatible = "pni,rm3100", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rm3100_dt_match);
+
+static struct i2c_driver rm3100_driver = {
+ .driver = {
+ .name = "rm3100-i2c",
+ .of_match_table = rm3100_dt_match,
+ },
+ .probe_new = rm3100_probe,
+};
+module_i2c_driver(rm3100_driver);
+
+MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
+MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer i2c driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_RM3100);
diff --git a/drivers/iio/magnetometer/rm3100-spi.c b/drivers/iio/magnetometer/rm3100-spi.c
new file mode 100644
index 000000000..76dc9b66c
--- /dev/null
+++ b/drivers/iio/magnetometer/rm3100-spi.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for PNI RM3100 3-axis geomagnetic sensor on a spi bus.
+ *
+ * Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include "rm3100.h"
+
+static const struct regmap_config rm3100_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .rd_table = &rm3100_readable_table,
+ .wr_table = &rm3100_writable_table,
+ .volatile_table = &rm3100_volatile_table,
+
+ .read_flag_mask = 0x80,
+
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int rm3100_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+ int ret;
+
+ /* Actually this device supports both mode 0 and mode 3. */
+ spi->mode = SPI_MODE_0;
+ /* Data rates cannot exceed 1Mbits. */
+ spi->max_speed_hz = 1000000;
+ spi->bits_per_word = 8;
+ ret = spi_setup(spi);
+ if (ret)
+ return ret;
+
+ regmap = devm_regmap_init_spi(spi, &rm3100_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return rm3100_common_probe(&spi->dev, regmap, spi->irq);
+}
+
+static const struct of_device_id rm3100_dt_match[] = {
+ { .compatible = "pni,rm3100", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rm3100_dt_match);
+
+static struct spi_driver rm3100_driver = {
+ .driver = {
+ .name = "rm3100-spi",
+ .of_match_table = rm3100_dt_match,
+ },
+ .probe = rm3100_probe,
+};
+module_spi_driver(rm3100_driver);
+
+MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
+MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer spi driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_RM3100);
diff --git a/drivers/iio/magnetometer/rm3100.h b/drivers/iio/magnetometer/rm3100.h
new file mode 100644
index 000000000..c3508218b
--- /dev/null
+++ b/drivers/iio/magnetometer/rm3100.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
+ */
+
+#ifndef RM3100_CORE_H
+#define RM3100_CORE_H
+
+#include <linux/regmap.h>
+
+extern const struct regmap_access_table rm3100_readable_table;
+extern const struct regmap_access_table rm3100_writable_table;
+extern const struct regmap_access_table rm3100_volatile_table;
+
+int rm3100_common_probe(struct device *dev, struct regmap *regmap, int irq);
+
+#endif /* RM3100_CORE_H */
diff --git a/drivers/iio/magnetometer/st_magn.h b/drivers/iio/magnetometer/st_magn.h
new file mode 100644
index 000000000..785b7f7b8
--- /dev/null
+++ b/drivers/iio/magnetometer/st_magn.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * STMicroelectronics magnetometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ * v. 1.0.0
+ */
+
+#ifndef ST_MAGN_H
+#define ST_MAGN_H
+
+#include <linux/types.h>
+#include <linux/iio/common/st_sensors.h>
+
+#define LSM303DLH_MAGN_DEV_NAME "lsm303dlh_magn"
+#define LSM303DLHC_MAGN_DEV_NAME "lsm303dlhc_magn"
+#define LSM303DLM_MAGN_DEV_NAME "lsm303dlm_magn"
+#define LIS3MDL_MAGN_DEV_NAME "lis3mdl"
+#define LSM303AGR_MAGN_DEV_NAME "lsm303agr_magn"
+#define LIS2MDL_MAGN_DEV_NAME "lis2mdl"
+#define LSM9DS1_MAGN_DEV_NAME "lsm9ds1_magn"
+#define IIS2MDC_MAGN_DEV_NAME "iis2mdc"
+
+#ifdef CONFIG_IIO_BUFFER
+int st_magn_allocate_ring(struct iio_dev *indio_dev);
+int st_magn_trig_set_state(struct iio_trigger *trig, bool state);
+#define ST_MAGN_TRIGGER_SET_STATE (&st_magn_trig_set_state)
+#else /* CONFIG_IIO_BUFFER */
+static inline int st_magn_allocate_ring(struct iio_dev *indio_dev)
+{
+ return 0;
+}
+#define ST_MAGN_TRIGGER_SET_STATE NULL
+#endif /* CONFIG_IIO_BUFFER */
+
+#endif /* ST_MAGN_H */
diff --git a/drivers/iio/magnetometer/st_magn_buffer.c b/drivers/iio/magnetometer/st_magn_buffer.c
new file mode 100644
index 000000000..79987f42e
--- /dev/null
+++ b/drivers/iio/magnetometer/st_magn_buffer.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * STMicroelectronics magnetometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include <linux/iio/common/st_sensors.h>
+#include "st_magn.h"
+
+int st_magn_trig_set_state(struct iio_trigger *trig, bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+
+ return st_sensors_set_dataready_irq(indio_dev, state);
+}
+
+static int st_magn_buffer_postenable(struct iio_dev *indio_dev)
+{
+ return st_sensors_set_enable(indio_dev, true);
+}
+
+static int st_magn_buffer_predisable(struct iio_dev *indio_dev)
+{
+ return st_sensors_set_enable(indio_dev, false);
+}
+
+static const struct iio_buffer_setup_ops st_magn_buffer_setup_ops = {
+ .postenable = &st_magn_buffer_postenable,
+ .predisable = &st_magn_buffer_predisable,
+};
+
+int st_magn_allocate_ring(struct iio_dev *indio_dev)
+{
+ return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev,
+ NULL, &st_sensors_trigger_handler, &st_magn_buffer_setup_ops);
+}
+
diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c
new file mode 100644
index 000000000..e2fd233b3
--- /dev/null
+++ b/drivers/iio/magnetometer/st_magn_core.c
@@ -0,0 +1,654 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * STMicroelectronics magnetometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+
+#include <linux/iio/common/st_sensors.h>
+#include "st_magn.h"
+
+#define ST_MAGN_NUMBER_DATA_CHANNELS 3
+
+/* DEFAULT VALUE FOR SENSORS */
+#define ST_MAGN_DEFAULT_OUT_X_H_ADDR 0x03
+#define ST_MAGN_DEFAULT_OUT_Y_H_ADDR 0x07
+#define ST_MAGN_DEFAULT_OUT_Z_H_ADDR 0x05
+
+/* FULLSCALE */
+#define ST_MAGN_FS_AVL_1300MG 1300
+#define ST_MAGN_FS_AVL_1900MG 1900
+#define ST_MAGN_FS_AVL_2000MG 2000
+#define ST_MAGN_FS_AVL_2500MG 2500
+#define ST_MAGN_FS_AVL_4000MG 4000
+#define ST_MAGN_FS_AVL_4700MG 4700
+#define ST_MAGN_FS_AVL_5600MG 5600
+#define ST_MAGN_FS_AVL_8000MG 8000
+#define ST_MAGN_FS_AVL_8100MG 8100
+#define ST_MAGN_FS_AVL_12000MG 12000
+#define ST_MAGN_FS_AVL_15000MG 15000
+#define ST_MAGN_FS_AVL_16000MG 16000
+
+/* Special L addresses for Sensor 2 */
+#define ST_MAGN_2_OUT_X_L_ADDR 0x28
+#define ST_MAGN_2_OUT_Y_L_ADDR 0x2a
+#define ST_MAGN_2_OUT_Z_L_ADDR 0x2c
+
+/* Special L addresses for sensor 3 */
+#define ST_MAGN_3_OUT_X_L_ADDR 0x68
+#define ST_MAGN_3_OUT_Y_L_ADDR 0x6a
+#define ST_MAGN_3_OUT_Z_L_ADDR 0x6c
+
+/* Special L addresses for sensor 4 */
+#define ST_MAGN_4_OUT_X_L_ADDR 0x08
+#define ST_MAGN_4_OUT_Y_L_ADDR 0x0a
+#define ST_MAGN_4_OUT_Z_L_ADDR 0x0c
+
+static const struct iio_mount_matrix *
+st_magn_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct st_sensor_data *mdata = iio_priv(indio_dev);
+
+ return &mdata->mount_matrix;
+}
+
+static const struct iio_chan_spec_ext_info st_magn_mount_matrix_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, st_magn_get_mount_matrix),
+ { }
+};
+
+static const struct iio_chan_spec st_magn_16bit_channels[] = {
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_BE, 16, 16,
+ ST_MAGN_DEFAULT_OUT_X_H_ADDR,
+ st_magn_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_BE, 16, 16,
+ ST_MAGN_DEFAULT_OUT_Y_H_ADDR,
+ st_magn_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_BE, 16, 16,
+ ST_MAGN_DEFAULT_OUT_Z_H_ADDR,
+ st_magn_mount_matrix_ext_info),
+ IIO_CHAN_SOFT_TIMESTAMP(3)
+};
+
+static const struct iio_chan_spec st_magn_2_16bit_channels[] = {
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16,
+ ST_MAGN_2_OUT_X_L_ADDR,
+ st_magn_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16,
+ ST_MAGN_2_OUT_Y_L_ADDR,
+ st_magn_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16,
+ ST_MAGN_2_OUT_Z_L_ADDR,
+ st_magn_mount_matrix_ext_info),
+ IIO_CHAN_SOFT_TIMESTAMP(3)
+};
+
+static const struct iio_chan_spec st_magn_3_16bit_channels[] = {
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16,
+ ST_MAGN_3_OUT_X_L_ADDR,
+ st_magn_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16,
+ ST_MAGN_3_OUT_Y_L_ADDR,
+ st_magn_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16,
+ ST_MAGN_3_OUT_Z_L_ADDR,
+ st_magn_mount_matrix_ext_info),
+ IIO_CHAN_SOFT_TIMESTAMP(3)
+};
+
+static const struct iio_chan_spec st_magn_4_16bit_channels[] = {
+ ST_SENSORS_LSM_CHANNELS(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16,
+ ST_MAGN_4_OUT_X_L_ADDR),
+ ST_SENSORS_LSM_CHANNELS(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16,
+ ST_MAGN_4_OUT_Y_L_ADDR),
+ ST_SENSORS_LSM_CHANNELS(IIO_MAGN,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16,
+ ST_MAGN_4_OUT_Z_L_ADDR),
+ IIO_CHAN_SOFT_TIMESTAMP(3)
+};
+
+static const struct st_sensor_settings st_magn_sensors_settings[] = {
+ {
+ .wai = 0, /* This sensor has no valid WhoAmI report 0 */
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LSM303DLH_MAGN_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_magn_16bit_channels,
+ .odr = {
+ .addr = 0x00,
+ .mask = 0x1c,
+ .odr_avl = {
+ { .hz = 1, .value = 0x00 },
+ { .hz = 2, .value = 0x01 },
+ { .hz = 3, .value = 0x02 },
+ { .hz = 8, .value = 0x03 },
+ { .hz = 15, .value = 0x04 },
+ { .hz = 30, .value = 0x05 },
+ { .hz = 75, .value = 0x06 },
+ /* 220 Hz, 0x07 reportedly exist */
+ },
+ },
+ .pw = {
+ .addr = 0x02,
+ .mask = 0x03,
+ .value_on = 0x00,
+ .value_off = 0x03,
+ },
+ .fs = {
+ .addr = 0x01,
+ .mask = 0xe0,
+ .fs_avl = {
+ [0] = {
+ .num = ST_MAGN_FS_AVL_1300MG,
+ .value = 0x01,
+ .gain = 1100,
+ .gain2 = 980,
+ },
+ [1] = {
+ .num = ST_MAGN_FS_AVL_1900MG,
+ .value = 0x02,
+ .gain = 855,
+ .gain2 = 760,
+ },
+ [2] = {
+ .num = ST_MAGN_FS_AVL_2500MG,
+ .value = 0x03,
+ .gain = 670,
+ .gain2 = 600,
+ },
+ [3] = {
+ .num = ST_MAGN_FS_AVL_4000MG,
+ .value = 0x04,
+ .gain = 450,
+ .gain2 = 400,
+ },
+ [4] = {
+ .num = ST_MAGN_FS_AVL_4700MG,
+ .value = 0x05,
+ .gain = 400,
+ .gain2 = 355,
+ },
+ [5] = {
+ .num = ST_MAGN_FS_AVL_5600MG,
+ .value = 0x06,
+ .gain = 330,
+ .gain2 = 295,
+ },
+ [6] = {
+ .num = ST_MAGN_FS_AVL_8100MG,
+ .value = 0x07,
+ .gain = 230,
+ .gain2 = 205,
+ },
+ },
+ },
+ .multi_read_bit = false,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x3c,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LSM303DLHC_MAGN_DEV_NAME,
+ [1] = LSM303DLM_MAGN_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_magn_16bit_channels,
+ .odr = {
+ .addr = 0x00,
+ .mask = 0x1c,
+ .odr_avl = {
+ { .hz = 1, .value = 0x00 },
+ { .hz = 2, .value = 0x01 },
+ { .hz = 3, .value = 0x02 },
+ { .hz = 8, .value = 0x03 },
+ { .hz = 15, .value = 0x04 },
+ { .hz = 30, .value = 0x05 },
+ { .hz = 75, .value = 0x06 },
+ { .hz = 220, .value = 0x07 },
+ },
+ },
+ .pw = {
+ .addr = 0x02,
+ .mask = 0x03,
+ .value_on = 0x00,
+ .value_off = 0x03,
+ },
+ .fs = {
+ .addr = 0x01,
+ .mask = 0xe0,
+ .fs_avl = {
+ [0] = {
+ .num = ST_MAGN_FS_AVL_1300MG,
+ .value = 0x01,
+ .gain = 909,
+ .gain2 = 1020,
+ },
+ [1] = {
+ .num = ST_MAGN_FS_AVL_1900MG,
+ .value = 0x02,
+ .gain = 1169,
+ .gain2 = 1315,
+ },
+ [2] = {
+ .num = ST_MAGN_FS_AVL_2500MG,
+ .value = 0x03,
+ .gain = 1492,
+ .gain2 = 1666,
+ },
+ [3] = {
+ .num = ST_MAGN_FS_AVL_4000MG,
+ .value = 0x04,
+ .gain = 2222,
+ .gain2 = 2500,
+ },
+ [4] = {
+ .num = ST_MAGN_FS_AVL_4700MG,
+ .value = 0x05,
+ .gain = 2500,
+ .gain2 = 2816,
+ },
+ [5] = {
+ .num = ST_MAGN_FS_AVL_5600MG,
+ .value = 0x06,
+ .gain = 3030,
+ .gain2 = 3389,
+ },
+ [6] = {
+ .num = ST_MAGN_FS_AVL_8100MG,
+ .value = 0x07,
+ .gain = 4347,
+ .gain2 = 4878,
+ },
+ },
+ },
+ .multi_read_bit = false,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x3d,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS3MDL_MAGN_DEV_NAME,
+ [1] = LSM9DS1_MAGN_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_magn_2_16bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0x1c,
+ .odr_avl = {
+ { .hz = 1, .value = 0x00 },
+ { .hz = 2, .value = 0x01 },
+ { .hz = 3, .value = 0x02 },
+ { .hz = 5, .value = 0x03 },
+ { .hz = 10, .value = 0x04 },
+ { .hz = 20, .value = 0x05 },
+ { .hz = 40, .value = 0x06 },
+ { .hz = 80, .value = 0x07 },
+ },
+ },
+ .pw = {
+ .addr = 0x22,
+ .mask = 0x03,
+ .value_on = 0x00,
+ .value_off = 0x03,
+ },
+ .fs = {
+ .addr = 0x21,
+ .mask = 0x60,
+ .fs_avl = {
+ [0] = {
+ .num = ST_MAGN_FS_AVL_4000MG,
+ .value = 0x00,
+ .gain = 146,
+ },
+ [1] = {
+ .num = ST_MAGN_FS_AVL_8000MG,
+ .value = 0x01,
+ .gain = 292,
+ },
+ [2] = {
+ .num = ST_MAGN_FS_AVL_12000MG,
+ .value = 0x02,
+ .gain = 438,
+ },
+ [3] = {
+ .num = ST_MAGN_FS_AVL_16000MG,
+ .value = 0x03,
+ .gain = 584,
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x24,
+ .mask = 0x40,
+ },
+ .drdy_irq = {
+ /* drdy line is routed drdy pin */
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x22,
+ .value = BIT(2),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x40,
+ .wai_addr = 0x4f,
+ .sensors_supported = {
+ [0] = LSM303AGR_MAGN_DEV_NAME,
+ [1] = LIS2MDL_MAGN_DEV_NAME,
+ [2] = IIS2MDC_MAGN_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_magn_3_16bit_channels,
+ .odr = {
+ .addr = 0x60,
+ .mask = 0x0c,
+ .odr_avl = {
+ { .hz = 10, .value = 0x00 },
+ { .hz = 20, .value = 0x01 },
+ { .hz = 50, .value = 0x02 },
+ { .hz = 100, .value = 0x03 },
+ },
+ },
+ .pw = {
+ .addr = 0x60,
+ .mask = 0x03,
+ .value_on = 0x00,
+ .value_off = 0x03,
+ },
+ .fs = {
+ .fs_avl = {
+ [0] = {
+ .num = ST_MAGN_FS_AVL_15000MG,
+ .gain = 1500,
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x62,
+ .mask = 0x10,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x62,
+ .mask = 0x01,
+ },
+ .stat_drdy = {
+ .addr = 0x67,
+ .mask = 0x07,
+ },
+ },
+ .multi_read_bit = false,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x49,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LSM9DS0_IMU_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_magn_4_16bit_channels,
+ .odr = {
+ .addr = 0x24,
+ .mask = GENMASK(4, 2),
+ .odr_avl = {
+ { 3, 0x00, },
+ { 6, 0x01, },
+ { 12, 0x02, },
+ { 25, 0x03, },
+ { 50, 0x04, },
+ { 100, 0x05, },
+ },
+ },
+ .pw = {
+ .addr = 0x26,
+ .mask = GENMASK(1, 0),
+ .value_on = 0x00,
+ .value_off = 0x03,
+ },
+ .fs = {
+ .addr = 0x25,
+ .mask = GENMASK(6, 5),
+ .fs_avl = {
+ [0] = {
+ .num = ST_MAGN_FS_AVL_2000MG,
+ .value = 0x00,
+ .gain = 73,
+ },
+ [1] = {
+ .num = ST_MAGN_FS_AVL_4000MG,
+ .value = 0x01,
+ .gain = 146,
+ },
+ [2] = {
+ .num = ST_MAGN_FS_AVL_8000MG,
+ .value = 0x02,
+ .gain = 292,
+ },
+ [3] = {
+ .num = ST_MAGN_FS_AVL_12000MG,
+ .value = 0x03,
+ .gain = 438,
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x20,
+ .mask = BIT(3),
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = BIT(1),
+ },
+ .int2 = {
+ .addr = 0x23,
+ .mask = BIT(2),
+ },
+ .stat_drdy = {
+ .addr = 0x07,
+ .mask = GENMASK(2, 0),
+ },
+ },
+ .sim = {
+ .addr = 0x21,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+};
+
+/* Default magn DRDY is available on INT2 pin */
+static const struct st_sensors_platform_data default_magn_pdata = {
+ .drdy_int_pin = 2,
+};
+
+static int st_magn_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *ch, int *val,
+ int *val2, long mask)
+{
+ int err;
+ struct st_sensor_data *mdata = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ err = st_sensors_read_info_raw(indio_dev, ch, val);
+ if (err < 0)
+ goto read_error;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ if ((ch->scan_index == ST_SENSORS_SCAN_Z) &&
+ (mdata->current_fullscale->gain2 != 0))
+ *val2 = mdata->current_fullscale->gain2;
+ else
+ *val2 = mdata->current_fullscale->gain;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = mdata->odr;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+
+read_error:
+ return err;
+}
+
+static int st_magn_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val, int val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return st_sensors_set_fullscale_by_gain(indio_dev, val2);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (val2)
+ return -EINVAL;
+
+ return st_sensors_set_odr(indio_dev, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL();
+static ST_SENSORS_DEV_ATTR_SCALE_AVAIL(in_magn_scale_available);
+
+static struct attribute *st_magn_attributes[] = {
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ &iio_dev_attr_in_magn_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group st_magn_attribute_group = {
+ .attrs = st_magn_attributes,
+};
+
+static const struct iio_info magn_info = {
+ .attrs = &st_magn_attribute_group,
+ .read_raw = &st_magn_read_raw,
+ .write_raw = &st_magn_write_raw,
+ .debugfs_reg_access = &st_sensors_debugfs_reg_access,
+};
+
+#ifdef CONFIG_IIO_TRIGGER
+static const struct iio_trigger_ops st_magn_trigger_ops = {
+ .set_trigger_state = ST_MAGN_TRIGGER_SET_STATE,
+ .validate_device = st_sensors_validate_device,
+};
+#define ST_MAGN_TRIGGER_OPS (&st_magn_trigger_ops)
+#else
+#define ST_MAGN_TRIGGER_OPS NULL
+#endif
+
+/*
+ * st_magn_get_settings() - get sensor settings from device name
+ * @name: device name buffer reference.
+ *
+ * Return: valid reference on success, NULL otherwise.
+ */
+const struct st_sensor_settings *st_magn_get_settings(const char *name)
+{
+ int index = st_sensors_get_settings_index(name,
+ st_magn_sensors_settings,
+ ARRAY_SIZE(st_magn_sensors_settings));
+ if (index < 0)
+ return NULL;
+
+ return &st_magn_sensors_settings[index];
+}
+EXPORT_SYMBOL_NS(st_magn_get_settings, IIO_ST_SENSORS);
+
+int st_magn_common_probe(struct iio_dev *indio_dev)
+{
+ struct st_sensor_data *mdata = iio_priv(indio_dev);
+ struct device *parent = indio_dev->dev.parent;
+ struct st_sensors_platform_data *pdata = dev_get_platdata(parent);
+ int err;
+
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &magn_info;
+
+ err = st_sensors_verify_id(indio_dev);
+ if (err < 0)
+ return err;
+
+ mdata->num_data_channels = ST_MAGN_NUMBER_DATA_CHANNELS;
+ indio_dev->channels = mdata->sensor_settings->ch;
+ indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS;
+
+ err = iio_read_mount_matrix(parent, &mdata->mount_matrix);
+ if (err)
+ return err;
+
+ mdata->current_fullscale = &mdata->sensor_settings->fs.fs_avl[0];
+ mdata->odr = mdata->sensor_settings->odr.odr_avl[0].hz;
+
+ if (!pdata)
+ pdata = (struct st_sensors_platform_data *)&default_magn_pdata;
+
+ err = st_sensors_init_sensor(indio_dev, pdata);
+ if (err < 0)
+ return err;
+
+ err = st_magn_allocate_ring(indio_dev);
+ if (err < 0)
+ return err;
+
+ if (mdata->irq > 0) {
+ err = st_sensors_allocate_trigger(indio_dev,
+ ST_MAGN_TRIGGER_OPS);
+ if (err < 0)
+ return err;
+ }
+
+ return devm_iio_device_register(parent, indio_dev);
+}
+EXPORT_SYMBOL_NS(st_magn_common_probe, IIO_ST_SENSORS);
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics magnetometers driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_ST_SENSORS);
diff --git a/drivers/iio/magnetometer/st_magn_i2c.c b/drivers/iio/magnetometer/st_magn_i2c.c
new file mode 100644
index 000000000..c5d8c303d
--- /dev/null
+++ b/drivers/iio/magnetometer/st_magn_i2c.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * STMicroelectronics magnetometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+
+#include <linux/iio/common/st_sensors.h>
+#include <linux/iio/common/st_sensors_i2c.h>
+#include "st_magn.h"
+
+static const struct of_device_id st_magn_of_match[] = {
+ {
+ .compatible = "st,lsm303dlh-magn",
+ .data = LSM303DLH_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303dlhc-magn",
+ .data = LSM303DLHC_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303dlm-magn",
+ .data = LSM303DLM_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3mdl-magn",
+ .data = LIS3MDL_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303agr-magn",
+ .data = LSM303AGR_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2mdl",
+ .data = LIS2MDL_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm9ds1-magn",
+ .data = LSM9DS1_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,iis2mdc",
+ .data = IIS2MDC_MAGN_DEV_NAME,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, st_magn_of_match);
+
+static int st_magn_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct st_sensor_settings *settings;
+ struct st_sensor_data *mdata;
+ struct iio_dev *indio_dev;
+ int err;
+
+ st_sensors_dev_name_probe(&client->dev, client->name, sizeof(client->name));
+
+ settings = st_magn_get_settings(client->name);
+ if (!settings) {
+ dev_err(&client->dev, "device name %s not recognized.\n",
+ client->name);
+ return -ENODEV;
+ }
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mdata));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ mdata = iio_priv(indio_dev);
+ mdata->sensor_settings = (struct st_sensor_settings *)settings;
+
+ err = st_sensors_i2c_configure(indio_dev, client);
+ if (err < 0)
+ return err;
+
+ err = st_sensors_power_enable(indio_dev);
+ if (err)
+ return err;
+
+ return st_magn_common_probe(indio_dev);
+}
+
+static const struct i2c_device_id st_magn_id_table[] = {
+ { LSM303DLH_MAGN_DEV_NAME },
+ { LSM303DLHC_MAGN_DEV_NAME },
+ { LSM303DLM_MAGN_DEV_NAME },
+ { LIS3MDL_MAGN_DEV_NAME },
+ { LSM303AGR_MAGN_DEV_NAME },
+ { LIS2MDL_MAGN_DEV_NAME },
+ { LSM9DS1_MAGN_DEV_NAME },
+ { IIS2MDC_MAGN_DEV_NAME },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, st_magn_id_table);
+
+static struct i2c_driver st_magn_driver = {
+ .driver = {
+ .name = "st-magn-i2c",
+ .of_match_table = st_magn_of_match,
+ },
+ .probe = st_magn_i2c_probe,
+ .id_table = st_magn_id_table,
+};
+module_i2c_driver(st_magn_driver);
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics magnetometers i2c driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_ST_SENSORS);
diff --git a/drivers/iio/magnetometer/st_magn_spi.c b/drivers/iio/magnetometer/st_magn_spi.c
new file mode 100644
index 000000000..6ddc43185
--- /dev/null
+++ b/drivers/iio/magnetometer/st_magn_spi.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * STMicroelectronics magnetometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/spi/spi.h>
+#include <linux/iio/iio.h>
+
+#include <linux/iio/common/st_sensors.h>
+#include <linux/iio/common/st_sensors_spi.h>
+#include "st_magn.h"
+
+/*
+ * For new single-chip sensors use <device_name> as compatible string.
+ * For old single-chip devices keep <device_name>-magn to maintain
+ * compatibility
+ * For multi-chip devices, use <device_name>-magn to distinguish which
+ * capability is being used
+ */
+static const struct of_device_id st_magn_of_match[] = {
+ {
+ .compatible = "st,lis3mdl-magn",
+ .data = LIS3MDL_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303agr-magn",
+ .data = LSM303AGR_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2mdl",
+ .data = LIS2MDL_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm9ds1-magn",
+ .data = LSM9DS1_MAGN_DEV_NAME,
+ },
+ {
+ .compatible = "st,iis2mdc",
+ .data = IIS2MDC_MAGN_DEV_NAME,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, st_magn_of_match);
+
+static int st_magn_spi_probe(struct spi_device *spi)
+{
+ const struct st_sensor_settings *settings;
+ struct st_sensor_data *mdata;
+ struct iio_dev *indio_dev;
+ int err;
+
+ st_sensors_dev_name_probe(&spi->dev, spi->modalias, sizeof(spi->modalias));
+
+ settings = st_magn_get_settings(spi->modalias);
+ if (!settings) {
+ dev_err(&spi->dev, "device name %s not recognized.\n",
+ spi->modalias);
+ return -ENODEV;
+ }
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*mdata));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ mdata = iio_priv(indio_dev);
+ mdata->sensor_settings = (struct st_sensor_settings *)settings;
+
+ err = st_sensors_spi_configure(indio_dev, spi);
+ if (err < 0)
+ return err;
+
+ err = st_sensors_power_enable(indio_dev);
+ if (err)
+ return err;
+
+ return st_magn_common_probe(indio_dev);
+}
+
+static const struct spi_device_id st_magn_id_table[] = {
+ { LIS3MDL_MAGN_DEV_NAME },
+ { LSM303AGR_MAGN_DEV_NAME },
+ { LIS2MDL_MAGN_DEV_NAME },
+ { LSM9DS1_MAGN_DEV_NAME },
+ { IIS2MDC_MAGN_DEV_NAME },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, st_magn_id_table);
+
+static struct spi_driver st_magn_driver = {
+ .driver = {
+ .name = "st-magn-spi",
+ .of_match_table = st_magn_of_match,
+ },
+ .probe = st_magn_spi_probe,
+ .id_table = st_magn_id_table,
+};
+module_spi_driver(st_magn_driver);
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics magnetometers spi driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_ST_SENSORS);
diff --git a/drivers/iio/magnetometer/yamaha-yas530.c b/drivers/iio/magnetometer/yamaha-yas530.c
new file mode 100644
index 000000000..801c760fe
--- /dev/null
+++ b/drivers/iio/magnetometer/yamaha-yas530.c
@@ -0,0 +1,1616 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Yamaha YAS magnetic sensors, often used in Samsung
+ * mobile phones. While all are not yet handled because of lacking
+ * hardware, expand this driver to handle the different variants:
+ *
+ * YAS530 MS-3E (2011 Samsung Galaxy S Advance)
+ * YAS532 MS-3R (2011 Samsung Galaxy S4)
+ * YAS533 MS-3F (Vivo 1633, 1707, V3, Y21L)
+ * (YAS534 is a magnetic switch, not handled)
+ * YAS535 MS-6C
+ * YAS536 MS-3W
+ * YAS537 MS-3T (2015 Samsung Galaxy S6, Note 5, Galaxy S7)
+ * YAS539 MS-3S (2018 Samsung Galaxy A7 SM-A750FN)
+ *
+ * Code functions found in the MPU3050 YAS530 and YAS532 drivers
+ * named "inv_compass" in the Tegra Android kernel tree.
+ * Copyright (C) 2012 InvenSense Corporation
+ *
+ * Code functions for YAS537 based on Yamaha Android kernel driver.
+ * Copyright (c) 2014 Yamaha Corporation
+ *
+ * Author: Linus Walleij <linus.walleij@linaro.org>
+ */
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/random.h>
+#include <linux/units.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include <asm/unaligned.h>
+
+/* Commonly used registers */
+#define YAS5XX_DEVICE_ID 0x80
+#define YAS5XX_MEASURE_DATA 0xB0
+
+/* These registers are used by YAS530, YAS532 and YAS533 */
+#define YAS530_ACTUATE_INIT_COIL 0x81
+#define YAS530_MEASURE 0x82
+#define YAS530_CONFIG 0x83
+#define YAS530_MEASURE_INTERVAL 0x84
+#define YAS530_OFFSET_X 0x85 /* [-31 .. 31] */
+#define YAS530_OFFSET_Y1 0x86 /* [-31 .. 31] */
+#define YAS530_OFFSET_Y2 0x87 /* [-31 .. 31] */
+#define YAS530_TEST1 0x88
+#define YAS530_TEST2 0x89
+#define YAS530_CAL 0x90
+
+/* Registers used by YAS537 */
+#define YAS537_MEASURE 0x81 /* Originally YAS537_REG_CMDR */
+#define YAS537_CONFIG 0x82 /* Originally YAS537_REG_CONFR */
+#define YAS537_MEASURE_INTERVAL 0x83 /* Originally YAS537_REG_INTRVLR */
+#define YAS537_OFFSET_X 0x84 /* Originally YAS537_REG_OXR */
+#define YAS537_OFFSET_Y1 0x85 /* Originally YAS537_REG_OY1R */
+#define YAS537_OFFSET_Y2 0x86 /* Originally YAS537_REG_OY2R */
+#define YAS537_AVR 0x87
+#define YAS537_HCK 0x88
+#define YAS537_LCK 0x89
+#define YAS537_SRST 0x90
+#define YAS537_ADCCAL 0x91
+#define YAS537_MTC 0x93
+#define YAS537_OC 0x9E
+#define YAS537_TRM 0x9F
+#define YAS537_CAL 0xC0
+
+/* Bits in the YAS5xx config register */
+#define YAS5XX_CONFIG_INTON BIT(0) /* Interrupt on? */
+#define YAS5XX_CONFIG_INTHACT BIT(1) /* Interrupt active high? */
+#define YAS5XX_CONFIG_CCK_MASK GENMASK(4, 2)
+#define YAS5XX_CONFIG_CCK_SHIFT 2
+
+/* Bits in the measure command register */
+#define YAS5XX_MEASURE_START BIT(0)
+#define YAS5XX_MEASURE_LDTC BIT(1)
+#define YAS5XX_MEASURE_FORS BIT(2)
+#define YAS5XX_MEASURE_DLYMES BIT(4)
+#define YAS5XX_MEASURE_CONT BIT(5)
+
+/* Bits in the measure data register */
+#define YAS5XX_MEASURE_DATA_BUSY BIT(7)
+
+#define YAS530_DEVICE_ID 0x01 /* YAS530 (MS-3E) */
+#define YAS530_VERSION_A 0 /* YAS530 (MS-3E A) */
+#define YAS530_VERSION_B 1 /* YAS530B (MS-3E B) */
+#define YAS530_VERSION_A_COEF 380
+#define YAS530_VERSION_B_COEF 550
+#define YAS530_DATA_BITS 12
+#define YAS530_DATA_CENTER BIT(YAS530_DATA_BITS - 1)
+#define YAS530_DATA_OVERFLOW (BIT(YAS530_DATA_BITS) - 1)
+
+#define YAS532_DEVICE_ID 0x02 /* YAS532/YAS533 (MS-3R/F) */
+#define YAS532_VERSION_AB 0 /* YAS532/533 AB (MS-3R/F AB) */
+#define YAS532_VERSION_AC 1 /* YAS532/533 AC (MS-3R/F AC) */
+#define YAS532_VERSION_AB_COEF 1800
+#define YAS532_VERSION_AC_COEF_X 850
+#define YAS532_VERSION_AC_COEF_Y1 750
+#define YAS532_VERSION_AC_COEF_Y2 750
+#define YAS532_DATA_BITS 13
+#define YAS532_DATA_CENTER BIT(YAS532_DATA_BITS - 1)
+#define YAS532_DATA_OVERFLOW (BIT(YAS532_DATA_BITS) - 1)
+
+#define YAS537_DEVICE_ID 0x07 /* YAS537 (MS-3T) */
+#define YAS537_VERSION_0 0 /* Version naming unknown */
+#define YAS537_VERSION_1 1 /* Version naming unknown */
+#define YAS537_MAG_AVERAGE_32_MASK GENMASK(6, 4)
+#define YAS537_MEASURE_TIME_WORST_US 1500
+#define YAS537_DEFAULT_SENSOR_DELAY_MS 50
+#define YAS537_MAG_RCOIL_TIME_US 65
+#define YAS537_MTC3_MASK_PREP GENMASK(7, 0)
+#define YAS537_MTC3_MASK_GET GENMASK(7, 5)
+#define YAS537_MTC3_ADD_BIT BIT(4)
+#define YAS537_HCK_MASK_PREP GENMASK(4, 0)
+#define YAS537_HCK_MASK_GET GENMASK(7, 4)
+#define YAS537_LCK_MASK_PREP GENMASK(4, 0)
+#define YAS537_LCK_MASK_GET GENMASK(3, 0)
+#define YAS537_OC_MASK_GET GENMASK(5, 0)
+
+/* Turn off device regulators etc after 5 seconds of inactivity */
+#define YAS5XX_AUTOSUSPEND_DELAY_MS 5000
+
+enum chip_ids {
+ yas530,
+ yas532,
+ yas533,
+ yas537,
+};
+
+static const int yas530_volatile_reg[] = {
+ YAS530_ACTUATE_INIT_COIL,
+ YAS530_MEASURE,
+};
+
+static const int yas537_volatile_reg[] = {
+ YAS537_MEASURE,
+};
+
+struct yas5xx_calibration {
+ /* Linearization calibration x, y1, y2 */
+ s32 r[3];
+ u32 f[3];
+ /* Temperature compensation calibration */
+ s16 Cx, Cy1, Cy2;
+ /* Misc calibration coefficients */
+ s8 a2, a3, a4, a6, a7, a8;
+ s16 a5, a9;
+ u8 k;
+ /* clock divider */
+ u8 dck;
+};
+
+struct yas5xx;
+
+/**
+ * struct yas5xx_chip_info - device-specific data and function pointers
+ * @devid: device ID number
+ * @product_name: product name of the YAS variant
+ * @version_names: version letters or namings
+ * @volatile_reg: device-specific volatile registers
+ * @volatile_reg_qty: quantity of device-specific volatile registers
+ * @scaling_val2: scaling value for IIO_CHAN_INFO_SCALE
+ * @t_ref: number of counts at reference temperature 20 °C
+ * @min_temp_x10: starting point of temperature counting in 1/10:s degrees Celsius
+ * @get_measure: function pointer to get a measurement
+ * @get_calibration_data: function pointer to get calibration data
+ * @dump_calibration: function pointer to dump calibration for debugging
+ * @measure_offsets: function pointer to measure the offsets
+ * @power_on: function pointer to power-on procedure
+ *
+ * The "t_ref" value for YAS532/533 is known from the Android driver.
+ * For YAS530 and YAS537 it was approximately measured.
+ *
+ * The temperatures "min_temp_x10" are derived from the temperature resolutions
+ * given in the data sheets.
+ */
+struct yas5xx_chip_info {
+ unsigned int devid;
+ const char *product_name;
+ const char *version_names[2];
+ const int *volatile_reg;
+ int volatile_reg_qty;
+ u32 scaling_val2;
+ u16 t_ref;
+ s16 min_temp_x10;
+ int (*get_measure)(struct yas5xx *yas5xx, s32 *to, s32 *xo, s32 *yo, s32 *zo);
+ int (*get_calibration_data)(struct yas5xx *yas5xx);
+ void (*dump_calibration)(struct yas5xx *yas5xx);
+ int (*measure_offsets)(struct yas5xx *yas5xx);
+ int (*power_on)(struct yas5xx *yas5xx);
+};
+
+/**
+ * struct yas5xx - state container for the YAS5xx driver
+ * @dev: parent device pointer
+ * @chip_info: device-specific data and function pointers
+ * @version: device version
+ * @calibration: calibration settings from the OTP storage
+ * @hard_offsets: offsets for each axis measured with initcoil actuated
+ * @orientation: mounting matrix, flipped axis etc
+ * @map: regmap to access the YAX5xx registers over I2C
+ * @regs: the vdd and vddio power regulators
+ * @reset: optional GPIO line used for handling RESET
+ * @lock: locks the magnetometer 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
+ * @scan: naturally aligned measurements
+ */
+struct yas5xx {
+ struct device *dev;
+ const struct yas5xx_chip_info *chip_info;
+ unsigned int version;
+ struct yas5xx_calibration calibration;
+ s8 hard_offsets[3];
+ struct iio_mount_matrix orientation;
+ struct regmap *map;
+ struct regulator_bulk_data regs[2];
+ struct gpio_desc *reset;
+ struct mutex lock;
+ /*
+ * The scanout is 4 x 32 bits in CPU endianness.
+ * Ensure timestamp is naturally aligned
+ */
+ struct {
+ s32 channels[4];
+ s64 ts __aligned(8);
+ } scan;
+};
+
+/* On YAS530 the x, y1 and y2 values are 12 bits */
+static u16 yas530_extract_axis(u8 *data)
+{
+ u16 val;
+
+ /*
+ * These are the bits used in a 16bit word:
+ * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+ * x x x x x x x x x x x x
+ */
+ val = get_unaligned_be16(&data[0]);
+ val = FIELD_GET(GENMASK(14, 3), val);
+ return val;
+}
+
+/* On YAS532 the x, y1 and y2 values are 13 bits */
+static u16 yas532_extract_axis(u8 *data)
+{
+ u16 val;
+
+ /*
+ * These are the bits used in a 16bit word:
+ * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+ * x x x x x x x x x x x x x
+ */
+ val = get_unaligned_be16(&data[0]);
+ val = FIELD_GET(GENMASK(14, 2), val);
+ return val;
+}
+
+/**
+ * yas530_measure() - Make a measure from the hardware
+ * @yas5xx: The device state
+ * @t: the raw temperature measurement
+ * @x: the raw x axis measurement
+ * @y1: the y1 axis measurement
+ * @y2: the y2 axis measurement
+ * @return: 0 on success or error code
+ *
+ * Used by YAS530, YAS532 and YAS533.
+ */
+static int yas530_measure(struct yas5xx *yas5xx, u16 *t, u16 *x, u16 *y1, u16 *y2)
+{
+ const struct yas5xx_chip_info *ci = yas5xx->chip_info;
+ unsigned int busy;
+ u8 data[8];
+ int ret;
+ u16 val;
+
+ mutex_lock(&yas5xx->lock);
+ ret = regmap_write(yas5xx->map, YAS530_MEASURE, YAS5XX_MEASURE_START);
+ if (ret < 0)
+ goto out_unlock;
+
+ /*
+ * Typical time to measure 1500 us, max 2000 us so wait min 500 us
+ * and at most 20000 us (one magnitude more than the datsheet max)
+ * before timeout.
+ */
+ ret = regmap_read_poll_timeout(yas5xx->map, YAS5XX_MEASURE_DATA, busy,
+ !(busy & YAS5XX_MEASURE_DATA_BUSY),
+ 500, 20000);
+ if (ret) {
+ dev_err(yas5xx->dev, "timeout waiting for measurement\n");
+ goto out_unlock;
+ }
+
+ ret = regmap_bulk_read(yas5xx->map, YAS5XX_MEASURE_DATA,
+ data, sizeof(data));
+ if (ret)
+ goto out_unlock;
+
+ mutex_unlock(&yas5xx->lock);
+
+ switch (ci->devid) {
+ case YAS530_DEVICE_ID:
+ /*
+ * The t value is 9 bits in big endian format
+ * These are the bits used in a 16bit word:
+ * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+ * x x x x x x x x x
+ */
+ val = get_unaligned_be16(&data[0]);
+ val = FIELD_GET(GENMASK(14, 6), val);
+ *t = val;
+ *x = yas530_extract_axis(&data[2]);
+ *y1 = yas530_extract_axis(&data[4]);
+ *y2 = yas530_extract_axis(&data[6]);
+ break;
+ case YAS532_DEVICE_ID:
+ /*
+ * The t value is 10 bits in big endian format
+ * These are the bits used in a 16bit word:
+ * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+ * x x x x x x x x x x
+ */
+ val = get_unaligned_be16(&data[0]);
+ val = FIELD_GET(GENMASK(14, 5), val);
+ *t = val;
+ *x = yas532_extract_axis(&data[2]);
+ *y1 = yas532_extract_axis(&data[4]);
+ *y2 = yas532_extract_axis(&data[6]);
+ break;
+ default:
+ dev_err(yas5xx->dev, "unknown data format\n");
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+
+out_unlock:
+ mutex_unlock(&yas5xx->lock);
+ return ret;
+}
+
+/**
+ * yas537_measure() - Make a measure from the hardware
+ * @yas5xx: The device state
+ * @t: the raw temperature measurement
+ * @x: the raw x axis measurement
+ * @y1: the y1 axis measurement
+ * @y2: the y2 axis measurement
+ * @return: 0 on success or error code
+ */
+static int yas537_measure(struct yas5xx *yas5xx, u16 *t, u16 *x, u16 *y1, u16 *y2)
+{
+ struct yas5xx_calibration *c = &yas5xx->calibration;
+ unsigned int busy;
+ u8 data[8];
+ u16 xy1y2[3];
+ s32 h[3], s[3];
+ int i, ret;
+
+ mutex_lock(&yas5xx->lock);
+
+ /* Contrary to YAS530/532, also a "cont" bit is set, meaning unknown */
+ ret = regmap_write(yas5xx->map, YAS537_MEASURE, YAS5XX_MEASURE_START |
+ YAS5XX_MEASURE_CONT);
+ if (ret < 0)
+ goto out_unlock;
+
+ /* Use same timeout like YAS530/532 but the bit is in data row 2 */
+ ret = regmap_read_poll_timeout(yas5xx->map, YAS5XX_MEASURE_DATA + 2, busy,
+ !(busy & YAS5XX_MEASURE_DATA_BUSY),
+ 500, 20000);
+ if (ret) {
+ dev_err(yas5xx->dev, "timeout waiting for measurement\n");
+ goto out_unlock;
+ }
+
+ ret = regmap_bulk_read(yas5xx->map, YAS5XX_MEASURE_DATA,
+ data, sizeof(data));
+ if (ret)
+ goto out_unlock;
+
+ mutex_unlock(&yas5xx->lock);
+
+ *t = get_unaligned_be16(&data[0]);
+ xy1y2[0] = FIELD_GET(GENMASK(13, 0), get_unaligned_be16(&data[2]));
+ xy1y2[1] = get_unaligned_be16(&data[4]);
+ xy1y2[2] = get_unaligned_be16(&data[6]);
+
+ /* The second version of YAS537 needs to include calibration coefficients */
+ if (yas5xx->version == YAS537_VERSION_1) {
+ for (i = 0; i < 3; i++)
+ s[i] = xy1y2[i] - BIT(13);
+ h[0] = (c->k * (128 * s[0] + c->a2 * s[1] + c->a3 * s[2])) / BIT(13);
+ h[1] = (c->k * (c->a4 * s[0] + c->a5 * s[1] + c->a6 * s[2])) / BIT(13);
+ h[2] = (c->k * (c->a7 * s[0] + c->a8 * s[1] + c->a9 * s[2])) / BIT(13);
+ for (i = 0; i < 3; i++) {
+ clamp_val(h[i], -BIT(13), BIT(13) - 1);
+ xy1y2[i] = h[i] + BIT(13);
+ }
+ }
+
+ *x = xy1y2[0];
+ *y1 = xy1y2[1];
+ *y2 = xy1y2[2];
+
+ return 0;
+
+out_unlock:
+ mutex_unlock(&yas5xx->lock);
+ return ret;
+}
+
+/* Used by YAS530, YAS532 and YAS533 */
+static s32 yas530_linearize(struct yas5xx *yas5xx, u16 val, int axis)
+{
+ const struct yas5xx_chip_info *ci = yas5xx->chip_info;
+ struct yas5xx_calibration *c = &yas5xx->calibration;
+ static const s32 yas532ac_coef[] = {
+ YAS532_VERSION_AC_COEF_X,
+ YAS532_VERSION_AC_COEF_Y1,
+ YAS532_VERSION_AC_COEF_Y2,
+ };
+ s32 coef;
+
+ /* Select coefficients */
+ switch (ci->devid) {
+ case YAS530_DEVICE_ID:
+ if (yas5xx->version == YAS530_VERSION_A)
+ coef = YAS530_VERSION_A_COEF;
+ else
+ coef = YAS530_VERSION_B_COEF;
+ break;
+ case YAS532_DEVICE_ID:
+ if (yas5xx->version == YAS532_VERSION_AB)
+ coef = YAS532_VERSION_AB_COEF;
+ else
+ /* Elaborate coefficients */
+ coef = yas532ac_coef[axis];
+ break;
+ default:
+ dev_err(yas5xx->dev, "unknown device type\n");
+ return val;
+ }
+ /*
+ * Linearization formula:
+ *
+ * x' = x - (3721 + 50 * f) + (xoffset - r) * c
+ *
+ * Where f and r are calibration values, c is a per-device
+ * and sometimes per-axis coefficient.
+ */
+ return val - (3721 + 50 * c->f[axis]) +
+ (yas5xx->hard_offsets[axis] - c->r[axis]) * coef;
+}
+
+static s32 yas5xx_calc_temperature(struct yas5xx *yas5xx, u16 t)
+{
+ const struct yas5xx_chip_info *ci = yas5xx->chip_info;
+ s32 to;
+ u16 t_ref;
+ s16 min_temp_x10;
+ int ref_temp_x10;
+
+ t_ref = ci->t_ref;
+ min_temp_x10 = ci->min_temp_x10;
+ ref_temp_x10 = 200;
+
+ to = (min_temp_x10 + ((ref_temp_x10 - min_temp_x10) * t / t_ref)) * 100;
+ return to;
+}
+
+/**
+ * yas530_get_measure() - Measure a sample of all axis and process
+ * @yas5xx: The device state
+ * @to: Temperature out
+ * @xo: X axis out
+ * @yo: Y axis out
+ * @zo: Z axis out
+ * @return: 0 on success or error code
+ *
+ * Used by YAS530, YAS532 and YAS533.
+ */
+static int yas530_get_measure(struct yas5xx *yas5xx, s32 *to, s32 *xo, s32 *yo, s32 *zo)
+{
+ const struct yas5xx_chip_info *ci = yas5xx->chip_info;
+ struct yas5xx_calibration *c = &yas5xx->calibration;
+ u16 t_ref, t_comp, t, x, y1, y2;
+ /* These are signed x, signed y1 etc */
+ s32 sx, sy1, sy2, sy, sz;
+ int ret;
+
+ /* We first get raw data that needs to be translated to [x,y,z] */
+ ret = yas530_measure(yas5xx, &t, &x, &y1, &y2);
+ if (ret)
+ return ret;
+
+ /* Do some linearization if available */
+ sx = yas530_linearize(yas5xx, x, 0);
+ sy1 = yas530_linearize(yas5xx, y1, 1);
+ sy2 = yas530_linearize(yas5xx, y2, 2);
+
+ /*
+ * Set the temperature for compensation (unit: counts):
+ * YAS532/YAS533 version AC uses the temperature deviation as a
+ * multiplier. YAS530 and YAS532 version AB use solely the t value.
+ */
+ t_ref = ci->t_ref;
+ if (ci->devid == YAS532_DEVICE_ID &&
+ yas5xx->version == YAS532_VERSION_AC) {
+ t_comp = t - t_ref;
+ } else {
+ t_comp = t;
+ }
+
+ /*
+ * Temperature compensation for x, y1, y2 respectively:
+ *
+ * Cx * t_comp
+ * x' = x - -----------
+ * 100
+ */
+ sx = sx - (c->Cx * t_comp) / 100;
+ sy1 = sy1 - (c->Cy1 * t_comp) / 100;
+ sy2 = sy2 - (c->Cy2 * t_comp) / 100;
+
+ /*
+ * Break y1 and y2 into y and z, y1 and y2 are apparently encoding
+ * y and z.
+ */
+ sy = sy1 - sy2;
+ sz = -sy1 - sy2;
+
+ /* Calculate temperature readout */
+ *to = yas5xx_calc_temperature(yas5xx, t);
+
+ /*
+ * Calibrate [x,y,z] with some formulas like this:
+ *
+ * 100 * x + a_2 * y + a_3 * z
+ * x' = k * ---------------------------
+ * 10
+ *
+ * a_4 * x + a_5 * y + a_6 * z
+ * y' = k * ---------------------------
+ * 10
+ *
+ * a_7 * x + a_8 * y + a_9 * z
+ * z' = k * ---------------------------
+ * 10
+ */
+ *xo = c->k * ((100 * sx + c->a2 * sy + c->a3 * sz) / 10);
+ *yo = c->k * ((c->a4 * sx + c->a5 * sy + c->a6 * sz) / 10);
+ *zo = c->k * ((c->a7 * sx + c->a8 * sy + c->a9 * sz) / 10);
+
+ return 0;
+}
+
+/**
+ * yas537_get_measure() - Measure a sample of all axis and process
+ * @yas5xx: The device state
+ * @to: Temperature out
+ * @xo: X axis out
+ * @yo: Y axis out
+ * @zo: Z axis out
+ * @return: 0 on success or error code
+ */
+static int yas537_get_measure(struct yas5xx *yas5xx, s32 *to, s32 *xo, s32 *yo, s32 *zo)
+{
+ u16 t, x, y1, y2;
+ int ret;
+
+ /* We first get raw data that needs to be translated to [x,y,z] */
+ ret = yas537_measure(yas5xx, &t, &x, &y1, &y2);
+ if (ret)
+ return ret;
+
+ /* Calculate temperature readout */
+ *to = yas5xx_calc_temperature(yas5xx, t);
+
+ /*
+ * Unfortunately, no linearization or temperature compensation formulas
+ * are known for YAS537.
+ */
+
+ /* Calculate x, y, z from x, y1, y2 */
+ *xo = (x - BIT(13)) * 300;
+ *yo = (y1 - y2) * 1732 / 10;
+ *zo = (-y1 - y2 + BIT(14)) * 300;
+
+ return 0;
+}
+
+static int yas5xx_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2,
+ long mask)
+{
+ struct yas5xx *yas5xx = iio_priv(indio_dev);
+ const struct yas5xx_chip_info *ci = yas5xx->chip_info;
+ s32 t, x, y, z;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ case IIO_CHAN_INFO_RAW:
+ pm_runtime_get_sync(yas5xx->dev);
+ ret = ci->get_measure(yas5xx, &t, &x, &y, &z);
+ pm_runtime_mark_last_busy(yas5xx->dev);
+ pm_runtime_put_autosuspend(yas5xx->dev);
+ if (ret)
+ return ret;
+ switch (chan->address) {
+ case 0:
+ *val = t;
+ break;
+ case 1:
+ *val = x;
+ break;
+ case 2:
+ *val = y;
+ break;
+ case 3:
+ *val = z;
+ break;
+ default:
+ dev_err(yas5xx->dev, "unknown channel\n");
+ return -EINVAL;
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1;
+ *val2 = ci->scaling_val2;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ /* Unknown request */
+ return -EINVAL;
+ }
+}
+
+static void yas5xx_fill_buffer(struct iio_dev *indio_dev)
+{
+ struct yas5xx *yas5xx = iio_priv(indio_dev);
+ const struct yas5xx_chip_info *ci = yas5xx->chip_info;
+ s32 t, x, y, z;
+ int ret;
+
+ pm_runtime_get_sync(yas5xx->dev);
+ ret = ci->get_measure(yas5xx, &t, &x, &y, &z);
+ pm_runtime_mark_last_busy(yas5xx->dev);
+ pm_runtime_put_autosuspend(yas5xx->dev);
+ if (ret) {
+ dev_err(yas5xx->dev, "error refilling buffer\n");
+ return;
+ }
+ yas5xx->scan.channels[0] = t;
+ yas5xx->scan.channels[1] = x;
+ yas5xx->scan.channels[2] = y;
+ yas5xx->scan.channels[3] = z;
+ iio_push_to_buffers_with_timestamp(indio_dev, &yas5xx->scan,
+ iio_get_time_ns(indio_dev));
+}
+
+static irqreturn_t yas5xx_handle_trigger(int irq, void *p)
+{
+ const struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+
+ yas5xx_fill_buffer(indio_dev);
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+
+static const struct iio_mount_matrix *
+yas5xx_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct yas5xx *yas5xx = iio_priv(indio_dev);
+
+ return &yas5xx->orientation;
+}
+
+static const struct iio_chan_spec_ext_info yas5xx_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, yas5xx_get_mount_matrix),
+ { }
+};
+
+#define YAS5XX_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), \
+ .ext_info = yas5xx_ext_info, \
+ .address = index, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ .endianness = IIO_CPU, \
+ }, \
+ }
+
+static const struct iio_chan_spec yas5xx_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .address = 0,
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 32,
+ .storagebits = 32,
+ .endianness = IIO_CPU,
+ },
+ },
+ YAS5XX_AXIS_CHANNEL(X, 1),
+ YAS5XX_AXIS_CHANNEL(Y, 2),
+ YAS5XX_AXIS_CHANNEL(Z, 3),
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const unsigned long yas5xx_scan_masks[] = { GENMASK(3, 0), 0 };
+
+static const struct iio_info yas5xx_info = {
+ .read_raw = &yas5xx_read_raw,
+};
+
+static bool yas5xx_volatile_reg(struct device *dev, unsigned int reg)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct yas5xx *yas5xx = iio_priv(indio_dev);
+ const struct yas5xx_chip_info *ci = yas5xx->chip_info;
+ int reg_qty;
+ int i;
+
+ if (reg >= YAS5XX_MEASURE_DATA && reg < YAS5XX_MEASURE_DATA + 8)
+ return true;
+
+ /*
+ * YAS versions share different registers on the same address,
+ * need to differentiate.
+ */
+ reg_qty = ci->volatile_reg_qty;
+ for (i = 0; i < reg_qty; i++) {
+ if (reg == ci->volatile_reg[i])
+ return true;
+ }
+
+ return false;
+}
+
+/* TODO: enable regmap cache, using mark dirty and sync at runtime resume */
+static const struct regmap_config yas5xx_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .volatile_reg = yas5xx_volatile_reg,
+};
+
+/**
+ * yas530_extract_calibration() - extracts the a2-a9 and k calibration
+ * @data: the bitfield to use
+ * @c: the calibration to populate
+ *
+ * Used by YAS530, YAS532 and YAS533.
+ */
+static void yas530_extract_calibration(u8 *data, struct yas5xx_calibration *c)
+{
+ u64 val = get_unaligned_be64(data);
+
+ /*
+ * Bitfield layout for the axis calibration data, for factor
+ * a2 = 2 etc, k = k, c = clock divider
+ *
+ * n 7 6 5 4 3 2 1 0
+ * 0 [ 2 2 2 2 2 2 3 3 ] bits 63 .. 56
+ * 1 [ 3 3 4 4 4 4 4 4 ] bits 55 .. 48
+ * 2 [ 5 5 5 5 5 5 6 6 ] bits 47 .. 40
+ * 3 [ 6 6 6 6 7 7 7 7 ] bits 39 .. 32
+ * 4 [ 7 7 7 8 8 8 8 8 ] bits 31 .. 24
+ * 5 [ 8 9 9 9 9 9 9 9 ] bits 23 .. 16
+ * 6 [ 9 k k k k k c c ] bits 15 .. 8
+ * 7 [ c x x x x x x x ] bits 7 .. 0
+ */
+ c->a2 = FIELD_GET(GENMASK_ULL(63, 58), val) - 32;
+ c->a3 = FIELD_GET(GENMASK_ULL(57, 54), val) - 8;
+ c->a4 = FIELD_GET(GENMASK_ULL(53, 48), val) - 32;
+ c->a5 = FIELD_GET(GENMASK_ULL(47, 42), val) + 38;
+ c->a6 = FIELD_GET(GENMASK_ULL(41, 36), val) - 32;
+ c->a7 = FIELD_GET(GENMASK_ULL(35, 29), val) - 64;
+ c->a8 = FIELD_GET(GENMASK_ULL(28, 23), val) - 32;
+ c->a9 = FIELD_GET(GENMASK_ULL(22, 15), val);
+ c->k = FIELD_GET(GENMASK_ULL(14, 10), val) + 10;
+ c->dck = FIELD_GET(GENMASK_ULL(9, 7), val);
+}
+
+static int yas530_get_calibration_data(struct yas5xx *yas5xx)
+{
+ struct yas5xx_calibration *c = &yas5xx->calibration;
+ u8 data[16];
+ u32 val;
+ int ret;
+
+ /* Dummy read, first read is ALWAYS wrong */
+ ret = regmap_bulk_read(yas5xx->map, YAS530_CAL, data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /* Actual calibration readout */
+ ret = regmap_bulk_read(yas5xx->map, YAS530_CAL, data, sizeof(data));
+ if (ret)
+ return ret;
+ dev_dbg(yas5xx->dev, "calibration data: %16ph\n", data);
+
+ /* Contribute calibration data to the input pool for kernel entropy */
+ add_device_randomness(data, sizeof(data));
+
+ /* Extract version */
+ yas5xx->version = data[15] & GENMASK(1, 0);
+
+ /* Extract the calibration from the bitfield */
+ c->Cx = data[0] * 6 - 768;
+ c->Cy1 = data[1] * 6 - 768;
+ c->Cy2 = data[2] * 6 - 768;
+ yas530_extract_calibration(&data[3], c);
+
+ /*
+ * Extract linearization:
+ * Linearization layout in the 32 bits at byte 11:
+ * The r factors are 6 bit values where bit 5 is the sign
+ *
+ * n 7 6 5 4 3 2 1 0
+ * 0 [ xx xx xx r0 r0 r0 r0 r0 ] bits 31 .. 24
+ * 1 [ r0 f0 f0 r1 r1 r1 r1 r1 ] bits 23 .. 16
+ * 2 [ r1 f1 f1 r2 r2 r2 r2 r2 ] bits 15 .. 8
+ * 3 [ r2 f2 f2 xx xx xx xx xx ] bits 7 .. 0
+ */
+ val = get_unaligned_be32(&data[11]);
+ c->f[0] = FIELD_GET(GENMASK(22, 21), val);
+ c->f[1] = FIELD_GET(GENMASK(14, 13), val);
+ c->f[2] = FIELD_GET(GENMASK(6, 5), val);
+ c->r[0] = sign_extend32(FIELD_GET(GENMASK(28, 23), val), 5);
+ c->r[1] = sign_extend32(FIELD_GET(GENMASK(20, 15), val), 5);
+ c->r[2] = sign_extend32(FIELD_GET(GENMASK(12, 7), val), 5);
+
+ return 0;
+}
+
+static int yas532_get_calibration_data(struct yas5xx *yas5xx)
+{
+ struct yas5xx_calibration *c = &yas5xx->calibration;
+ u8 data[14];
+ u32 val;
+ int ret;
+
+ /* Dummy read, first read is ALWAYS wrong */
+ ret = regmap_bulk_read(yas5xx->map, YAS530_CAL, data, sizeof(data));
+ if (ret)
+ return ret;
+ /* Actual calibration readout */
+ ret = regmap_bulk_read(yas5xx->map, YAS530_CAL, data, sizeof(data));
+ if (ret)
+ return ret;
+ dev_dbg(yas5xx->dev, "calibration data: %14ph\n", data);
+
+ /* Sanity check, is this all zeroes? */
+ if (!memchr_inv(data, 0x00, 13) && !(data[13] & BIT(7)))
+ dev_warn(yas5xx->dev, "calibration is blank!\n");
+
+ /* Contribute calibration data to the input pool for kernel entropy */
+ add_device_randomness(data, sizeof(data));
+
+ /* Only one bit of version info reserved here as far as we know */
+ yas5xx->version = data[13] & BIT(0);
+
+ /* Extract calibration from the bitfield */
+ c->Cx = data[0] * 10 - 1280;
+ c->Cy1 = data[1] * 10 - 1280;
+ c->Cy2 = data[2] * 10 - 1280;
+ yas530_extract_calibration(&data[3], c);
+
+ /*
+ * Extract linearization:
+ * Linearization layout in the 32 bits at byte 10:
+ * The r factors are 6 bit values where bit 5 is the sign
+ *
+ * n 7 6 5 4 3 2 1 0
+ * 0 [ xx r0 r0 r0 r0 r0 r0 f0 ] bits 31 .. 24
+ * 1 [ f0 r1 r1 r1 r1 r1 r1 f1 ] bits 23 .. 16
+ * 2 [ f1 r2 r2 r2 r2 r2 r2 f2 ] bits 15 .. 8
+ * 3 [ f2 xx xx xx xx xx xx xx ] bits 7 .. 0
+ */
+ val = get_unaligned_be32(&data[10]);
+ c->f[0] = FIELD_GET(GENMASK(24, 23), val);
+ c->f[1] = FIELD_GET(GENMASK(16, 15), val);
+ c->f[2] = FIELD_GET(GENMASK(8, 7), val);
+ c->r[0] = sign_extend32(FIELD_GET(GENMASK(30, 25), val), 5);
+ c->r[1] = sign_extend32(FIELD_GET(GENMASK(22, 17), val), 5);
+ c->r[2] = sign_extend32(FIELD_GET(GENMASK(14, 7), val), 5);
+
+ return 0;
+}
+
+static int yas537_get_calibration_data(struct yas5xx *yas5xx)
+{
+ struct yas5xx_calibration *c = &yas5xx->calibration;
+ u8 data[17];
+ u32 val1, val2, val3, val4;
+ int i, ret;
+
+ /* Writing SRST register */
+ ret = regmap_write(yas5xx->map, YAS537_SRST, BIT(1));
+ if (ret)
+ return ret;
+
+ /* Calibration readout, YAS537 needs one readout only */
+ ret = regmap_bulk_read(yas5xx->map, YAS537_CAL, data, sizeof(data));
+ if (ret)
+ return ret;
+ dev_dbg(yas5xx->dev, "calibration data: %17ph\n", data);
+
+ /* Sanity check, is this all zeroes? */
+ if (!memchr_inv(data, 0x00, 16) && !FIELD_GET(GENMASK(5, 0), data[16]))
+ dev_warn(yas5xx->dev, "calibration is blank!\n");
+
+ /* Contribute calibration data to the input pool for kernel entropy */
+ add_device_randomness(data, sizeof(data));
+
+ /* Extract version information */
+ yas5xx->version = FIELD_GET(GENMASK(7, 6), data[16]);
+
+ /* There are two versions of YAS537 behaving differently */
+ switch (yas5xx->version) {
+ case YAS537_VERSION_0:
+ /*
+ * The first version simply writes data back into registers:
+ *
+ * data[0] YAS537_MTC 0x93
+ * data[1] 0x94
+ * data[2] 0x95
+ * data[3] 0x96
+ * data[4] 0x97
+ * data[5] 0x98
+ * data[6] 0x99
+ * data[7] 0x9a
+ * data[8] 0x9b
+ * data[9] 0x9c
+ * data[10] 0x9d
+ * data[11] YAS537_OC 0x9e
+ *
+ * data[12] YAS537_OFFSET_X 0x84
+ * data[13] YAS537_OFFSET_Y1 0x85
+ * data[14] YAS537_OFFSET_Y2 0x86
+ *
+ * data[15] YAS537_HCK 0x88
+ * data[16] YAS537_LCK 0x89
+ */
+ for (i = 0; i < 12; i++) {
+ ret = regmap_write(yas5xx->map, YAS537_MTC + i,
+ data[i]);
+ if (ret)
+ return ret;
+ }
+ for (i = 0; i < 3; i++) {
+ ret = regmap_write(yas5xx->map, YAS537_OFFSET_X + i,
+ data[i + 12]);
+ if (ret)
+ return ret;
+ yas5xx->hard_offsets[i] = data[i + 12];
+ }
+ for (i = 0; i < 2; i++) {
+ ret = regmap_write(yas5xx->map, YAS537_HCK + i,
+ data[i + 15]);
+ if (ret)
+ return ret;
+ }
+ break;
+ case YAS537_VERSION_1:
+ /*
+ * The second version writes some data into registers but also
+ * extracts calibration coefficients.
+ *
+ * Registers being written:
+ *
+ * data[0] YAS537_MTC 0x93
+ * data[1] YAS537_MTC+1 0x94
+ * data[2] YAS537_MTC+2 0x95
+ * data[3] YAS537_MTC+3 (partially) 0x96
+ *
+ * data[12] YAS537_OFFSET_X 0x84
+ * data[13] YAS537_OFFSET_Y1 0x85
+ * data[14] YAS537_OFFSET_Y2 0x86
+ *
+ * data[15] YAS537_HCK (partially) 0x88
+ * YAS537_LCK (partially) 0x89
+ * data[16] YAS537_OC (partially) 0x9e
+ */
+ for (i = 0; i < 3; i++) {
+ ret = regmap_write(yas5xx->map, YAS537_MTC + i,
+ data[i]);
+ if (ret)
+ return ret;
+ }
+ for (i = 0; i < 3; i++) {
+ ret = regmap_write(yas5xx->map, YAS537_OFFSET_X + i,
+ data[i + 12]);
+ if (ret)
+ return ret;
+ yas5xx->hard_offsets[i] = data[i + 12];
+ }
+ /*
+ * Visualization of partially taken data:
+ *
+ * data[3] n 7 6 5 4 3 2 1 0
+ * YAS537_MTC+3 x x x 1 0 0 0 0
+ *
+ * data[15] n 7 6 5 4 3 2 1 0
+ * YAS537_HCK x x x x 0
+ *
+ * data[15] n 7 6 5 4 3 2 1 0
+ * YAS537_LCK x x x x 0
+ *
+ * data[16] n 7 6 5 4 3 2 1 0
+ * YAS537_OC x x x x x x
+ */
+ ret = regmap_write(yas5xx->map, YAS537_MTC + 3,
+ FIELD_PREP(YAS537_MTC3_MASK_PREP,
+ FIELD_GET(YAS537_MTC3_MASK_GET, data[3])) |
+ YAS537_MTC3_ADD_BIT);
+ if (ret)
+ return ret;
+ ret = regmap_write(yas5xx->map, YAS537_HCK,
+ FIELD_PREP(YAS537_HCK_MASK_PREP,
+ FIELD_GET(YAS537_HCK_MASK_GET, data[15])));
+ if (ret)
+ return ret;
+ ret = regmap_write(yas5xx->map, YAS537_LCK,
+ FIELD_PREP(YAS537_LCK_MASK_PREP,
+ FIELD_GET(YAS537_LCK_MASK_GET, data[15])));
+ if (ret)
+ return ret;
+ ret = regmap_write(yas5xx->map, YAS537_OC,
+ FIELD_GET(YAS537_OC_MASK_GET, data[16]));
+ if (ret)
+ return ret;
+ /*
+ * For data extraction, build some blocks. Four 32-bit blocks
+ * look appropriate.
+ *
+ * n 7 6 5 4 3 2 1 0
+ * data[0] 0 [ Cx Cx Cx Cx Cx Cx Cx Cx ] bits 31 .. 24
+ * data[1] 1 [ Cx C1 C1 C1 C1 C1 C1 C1 ] bits 23 .. 16
+ * data[2] 2 [ C1 C1 C2 C2 C2 C2 C2 C2 ] bits 15 .. 8
+ * data[3] 3 [ C2 C2 C2 ] bits 7 .. 0
+ *
+ * n 7 6 5 4 3 2 1 0
+ * data[3] 0 [ a2 a2 a2 a2 a2 ] bits 31 .. 24
+ * data[4] 1 [ a2 a2 a3 a3 a3 a3 a3 a3 ] bits 23 .. 16
+ * data[5] 2 [ a3 a4 a4 a4 a4 a4 a4 a4 ] bits 15 .. 8
+ * data[6] 3 [ a4 ] bits 7 .. 0
+ *
+ * n 7 6 5 4 3 2 1 0
+ * data[6] 0 [ a5 a5 a5 a5 a5 a5 a5 ] bits 31 .. 24
+ * data[7] 1 [ a5 a5 a6 a6 a6 a6 a6 a6 ] bits 23 .. 16
+ * data[8] 2 [ a6 a7 a7 a7 a7 a7 a7 a7 ] bits 15 .. 8
+ * data[9] 3 [ a7 ] bits 7 .. 0
+ *
+ * n 7 6 5 4 3 2 1 0
+ * data[9] 0 [ a8 a8 a8 a8 a8 a8 a8 ] bits 31 .. 24
+ * data[10] 1 [ a9 a9 a9 a9 a9 a9 a9 a9 ] bits 23 .. 16
+ * data[11] 2 [ a9 k k k k k k k ] bits 15 .. 8
+ * data[12] 3 [ ] bits 7 .. 0
+ */
+ val1 = get_unaligned_be32(&data[0]);
+ val2 = get_unaligned_be32(&data[3]);
+ val3 = get_unaligned_be32(&data[6]);
+ val4 = get_unaligned_be32(&data[9]);
+ /* Extract calibration coefficients and modify */
+ c->Cx = FIELD_GET(GENMASK(31, 23), val1) - 256;
+ c->Cy1 = FIELD_GET(GENMASK(22, 14), val1) - 256;
+ c->Cy2 = FIELD_GET(GENMASK(13, 5), val1) - 256;
+ c->a2 = FIELD_GET(GENMASK(28, 22), val2) - 64;
+ c->a3 = FIELD_GET(GENMASK(21, 15), val2) - 64;
+ c->a4 = FIELD_GET(GENMASK(14, 7), val2) - 128;
+ c->a5 = FIELD_GET(GENMASK(30, 22), val3) - 112;
+ c->a6 = FIELD_GET(GENMASK(21, 15), val3) - 64;
+ c->a7 = FIELD_GET(GENMASK(14, 7), val3) - 128;
+ c->a8 = FIELD_GET(GENMASK(30, 24), val4) - 64;
+ c->a9 = FIELD_GET(GENMASK(23, 15), val4) - 112;
+ c->k = FIELD_GET(GENMASK(14, 8), val4);
+ break;
+ default:
+ dev_err(yas5xx->dev, "unknown version of YAS537\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Used by YAS530, YAS532 and YAS533 */
+static void yas530_dump_calibration(struct yas5xx *yas5xx)
+{
+ struct yas5xx_calibration *c = &yas5xx->calibration;
+
+ dev_dbg(yas5xx->dev, "f[] = [%d, %d, %d]\n",
+ c->f[0], c->f[1], c->f[2]);
+ dev_dbg(yas5xx->dev, "r[] = [%d, %d, %d]\n",
+ c->r[0], c->r[1], c->r[2]);
+ dev_dbg(yas5xx->dev, "Cx = %d\n", c->Cx);
+ dev_dbg(yas5xx->dev, "Cy1 = %d\n", c->Cy1);
+ dev_dbg(yas5xx->dev, "Cy2 = %d\n", c->Cy2);
+ dev_dbg(yas5xx->dev, "a2 = %d\n", c->a2);
+ dev_dbg(yas5xx->dev, "a3 = %d\n", c->a3);
+ dev_dbg(yas5xx->dev, "a4 = %d\n", c->a4);
+ dev_dbg(yas5xx->dev, "a5 = %d\n", c->a5);
+ dev_dbg(yas5xx->dev, "a6 = %d\n", c->a6);
+ dev_dbg(yas5xx->dev, "a7 = %d\n", c->a7);
+ dev_dbg(yas5xx->dev, "a8 = %d\n", c->a8);
+ dev_dbg(yas5xx->dev, "a9 = %d\n", c->a9);
+ dev_dbg(yas5xx->dev, "k = %d\n", c->k);
+ dev_dbg(yas5xx->dev, "dck = %d\n", c->dck);
+}
+
+static void yas537_dump_calibration(struct yas5xx *yas5xx)
+{
+ struct yas5xx_calibration *c = &yas5xx->calibration;
+
+ if (yas5xx->version == YAS537_VERSION_1) {
+ dev_dbg(yas5xx->dev, "Cx = %d\n", c->Cx);
+ dev_dbg(yas5xx->dev, "Cy1 = %d\n", c->Cy1);
+ dev_dbg(yas5xx->dev, "Cy2 = %d\n", c->Cy2);
+ dev_dbg(yas5xx->dev, "a2 = %d\n", c->a2);
+ dev_dbg(yas5xx->dev, "a3 = %d\n", c->a3);
+ dev_dbg(yas5xx->dev, "a4 = %d\n", c->a4);
+ dev_dbg(yas5xx->dev, "a5 = %d\n", c->a5);
+ dev_dbg(yas5xx->dev, "a6 = %d\n", c->a6);
+ dev_dbg(yas5xx->dev, "a7 = %d\n", c->a7);
+ dev_dbg(yas5xx->dev, "a8 = %d\n", c->a8);
+ dev_dbg(yas5xx->dev, "a9 = %d\n", c->a9);
+ dev_dbg(yas5xx->dev, "k = %d\n", c->k);
+ }
+}
+
+/* Used by YAS530, YAS532 and YAS533 */
+static int yas530_set_offsets(struct yas5xx *yas5xx, s8 ox, s8 oy1, s8 oy2)
+{
+ int ret;
+
+ ret = regmap_write(yas5xx->map, YAS530_OFFSET_X, ox);
+ if (ret)
+ return ret;
+ ret = regmap_write(yas5xx->map, YAS530_OFFSET_Y1, oy1);
+ if (ret)
+ return ret;
+ return regmap_write(yas5xx->map, YAS530_OFFSET_Y2, oy2);
+}
+
+/* Used by YAS530, YAS532 and YAS533 */
+static s8 yas530_adjust_offset(s8 old, int bit, u16 center, u16 measure)
+{
+ if (measure > center)
+ return old + BIT(bit);
+ if (measure < center)
+ return old - BIT(bit);
+ return old;
+}
+
+/* Used by YAS530, YAS532 and YAS533 */
+static int yas530_measure_offsets(struct yas5xx *yas5xx)
+{
+ const struct yas5xx_chip_info *ci = yas5xx->chip_info;
+ int ret;
+ u16 center;
+ u16 t, x, y1, y2;
+ s8 ox, oy1, oy2;
+ int i;
+
+ /* Actuate the init coil and measure offsets */
+ ret = regmap_write(yas5xx->map, YAS530_ACTUATE_INIT_COIL, 0);
+ if (ret)
+ return ret;
+
+ /* When the initcoil is active this should be around the center */
+ switch (ci->devid) {
+ case YAS530_DEVICE_ID:
+ center = YAS530_DATA_CENTER;
+ break;
+ case YAS532_DEVICE_ID:
+ center = YAS532_DATA_CENTER;
+ break;
+ default:
+ dev_err(yas5xx->dev, "unknown device type\n");
+ return -EINVAL;
+ }
+
+ /*
+ * We set offsets in the interval +-31 by iterating
+ * +-16, +-8, +-4, +-2, +-1 adjusting the offsets each
+ * time, then writing the final offsets into the
+ * registers.
+ *
+ * NOTE: these offsets are NOT in the same unit or magnitude
+ * as the values for [x, y1, y2]. The value is +/-31
+ * but the effect on the raw values is much larger.
+ * The effect of the offset is to bring the measure
+ * rougly to the center.
+ */
+ ox = 0;
+ oy1 = 0;
+ oy2 = 0;
+
+ for (i = 4; i >= 0; i--) {
+ ret = yas530_set_offsets(yas5xx, ox, oy1, oy2);
+ if (ret)
+ return ret;
+
+ ret = yas530_measure(yas5xx, &t, &x, &y1, &y2);
+ if (ret)
+ return ret;
+ dev_dbg(yas5xx->dev, "measurement %d: x=%d, y1=%d, y2=%d\n",
+ 5-i, x, y1, y2);
+
+ ox = yas530_adjust_offset(ox, i, center, x);
+ oy1 = yas530_adjust_offset(oy1, i, center, y1);
+ oy2 = yas530_adjust_offset(oy2, i, center, y2);
+ }
+
+ /* Needed for calibration algorithm */
+ yas5xx->hard_offsets[0] = ox;
+ yas5xx->hard_offsets[1] = oy1;
+ yas5xx->hard_offsets[2] = oy2;
+ ret = yas530_set_offsets(yas5xx, ox, oy1, oy2);
+ if (ret)
+ return ret;
+
+ dev_info(yas5xx->dev, "discovered hard offsets: x=%d, y1=%d, y2=%d\n",
+ ox, oy1, oy2);
+ return 0;
+}
+
+/* Used by YAS530, YAS532 and YAS533 */
+static int yas530_power_on(struct yas5xx *yas5xx)
+{
+ unsigned int val;
+ int ret;
+
+ /* Zero the test registers */
+ ret = regmap_write(yas5xx->map, YAS530_TEST1, 0);
+ if (ret)
+ return ret;
+ ret = regmap_write(yas5xx->map, YAS530_TEST2, 0);
+ if (ret)
+ return ret;
+
+ /* Set up for no interrupts, calibrated clock divider */
+ val = FIELD_PREP(YAS5XX_CONFIG_CCK_MASK, yas5xx->calibration.dck);
+ ret = regmap_write(yas5xx->map, YAS530_CONFIG, val);
+ if (ret)
+ return ret;
+
+ /* Measure interval 0 (back-to-back?) */
+ return regmap_write(yas5xx->map, YAS530_MEASURE_INTERVAL, 0);
+}
+
+static int yas537_power_on(struct yas5xx *yas5xx)
+{
+ __be16 buf;
+ int ret;
+ u8 intrvl;
+
+ /* Writing ADCCAL and TRM registers */
+ buf = cpu_to_be16(GENMASK(9, 3));
+ ret = regmap_bulk_write(yas5xx->map, YAS537_ADCCAL, &buf, sizeof(buf));
+ if (ret)
+ return ret;
+ ret = regmap_write(yas5xx->map, YAS537_TRM, GENMASK(7, 0));
+ if (ret)
+ return ret;
+
+ /* The interval value is static in regular operation */
+ intrvl = (YAS537_DEFAULT_SENSOR_DELAY_MS * MILLI
+ - YAS537_MEASURE_TIME_WORST_US) / 4100;
+ ret = regmap_write(yas5xx->map, YAS537_MEASURE_INTERVAL, intrvl);
+ if (ret)
+ return ret;
+
+ /* The average value is also static in regular operation */
+ ret = regmap_write(yas5xx->map, YAS537_AVR, YAS537_MAG_AVERAGE_32_MASK);
+ if (ret)
+ return ret;
+
+ /* Perform the "rcoil" part but skip the "last_after_rcoil" read */
+ ret = regmap_write(yas5xx->map, YAS537_CONFIG, BIT(3));
+ if (ret)
+ return ret;
+
+ /* Wait until the coil has ramped up */
+ usleep_range(YAS537_MAG_RCOIL_TIME_US, YAS537_MAG_RCOIL_TIME_US + 100);
+
+ return 0;
+}
+
+static const struct yas5xx_chip_info yas5xx_chip_info_tbl[] = {
+ [yas530] = {
+ .devid = YAS530_DEVICE_ID,
+ .product_name = "YAS530 MS-3E",
+ .version_names = { "A", "B" },
+ .volatile_reg = yas530_volatile_reg,
+ .volatile_reg_qty = ARRAY_SIZE(yas530_volatile_reg),
+ .scaling_val2 = 100000000, /* picotesla to Gauss */
+ .t_ref = 182, /* counts */
+ .min_temp_x10 = -620, /* 1/10:s degrees Celsius */
+ .get_measure = yas530_get_measure,
+ .get_calibration_data = yas530_get_calibration_data,
+ .dump_calibration = yas530_dump_calibration,
+ .measure_offsets = yas530_measure_offsets,
+ .power_on = yas530_power_on,
+ },
+ [yas532] = {
+ .devid = YAS532_DEVICE_ID,
+ .product_name = "YAS532 MS-3R",
+ .version_names = { "AB", "AC" },
+ .volatile_reg = yas530_volatile_reg,
+ .volatile_reg_qty = ARRAY_SIZE(yas530_volatile_reg),
+ .scaling_val2 = 100000, /* nanotesla to Gauss */
+ .t_ref = 390, /* counts */
+ .min_temp_x10 = -500, /* 1/10:s degrees Celsius */
+ .get_measure = yas530_get_measure,
+ .get_calibration_data = yas532_get_calibration_data,
+ .dump_calibration = yas530_dump_calibration,
+ .measure_offsets = yas530_measure_offsets,
+ .power_on = yas530_power_on,
+ },
+ [yas533] = {
+ .devid = YAS532_DEVICE_ID,
+ .product_name = "YAS533 MS-3F",
+ .version_names = { "AB", "AC" },
+ .volatile_reg = yas530_volatile_reg,
+ .volatile_reg_qty = ARRAY_SIZE(yas530_volatile_reg),
+ .scaling_val2 = 100000, /* nanotesla to Gauss */
+ .t_ref = 390, /* counts */
+ .min_temp_x10 = -500, /* 1/10:s degrees Celsius */
+ .get_measure = yas530_get_measure,
+ .get_calibration_data = yas532_get_calibration_data,
+ .dump_calibration = yas530_dump_calibration,
+ .measure_offsets = yas530_measure_offsets,
+ .power_on = yas530_power_on,
+ },
+ [yas537] = {
+ .devid = YAS537_DEVICE_ID,
+ .product_name = "YAS537 MS-3T",
+ .version_names = { "v0", "v1" }, /* version naming unknown */
+ .volatile_reg = yas537_volatile_reg,
+ .volatile_reg_qty = ARRAY_SIZE(yas537_volatile_reg),
+ .scaling_val2 = 100000, /* nanotesla to Gauss */
+ .t_ref = 8120, /* counts */
+ .min_temp_x10 = -3860, /* 1/10:s degrees Celsius */
+ .get_measure = yas537_get_measure,
+ .get_calibration_data = yas537_get_calibration_data,
+ .dump_calibration = yas537_dump_calibration,
+ /* .measure_offets is not needed for yas537 */
+ .power_on = yas537_power_on,
+ },
+};
+
+static int yas5xx_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct iio_dev *indio_dev;
+ struct device *dev = &i2c->dev;
+ struct yas5xx *yas5xx;
+ const struct yas5xx_chip_info *ci;
+ int id_check;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*yas5xx));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ yas5xx = iio_priv(indio_dev);
+ i2c_set_clientdata(i2c, indio_dev);
+ yas5xx->dev = dev;
+ mutex_init(&yas5xx->lock);
+
+ ret = iio_read_mount_matrix(dev, &yas5xx->orientation);
+ if (ret)
+ return ret;
+
+ yas5xx->regs[0].supply = "vdd";
+ yas5xx->regs[1].supply = "iovdd";
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(yas5xx->regs),
+ yas5xx->regs);
+ if (ret)
+ return dev_err_probe(dev, ret, "cannot get regulators\n");
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs);
+ if (ret)
+ return dev_err_probe(dev, ret, "cannot enable regulators\n");
+
+ /* See comment in runtime resume callback */
+ usleep_range(31000, 40000);
+
+ /* This will take the device out of reset if need be */
+ yas5xx->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(yas5xx->reset)) {
+ ret = dev_err_probe(dev, PTR_ERR(yas5xx->reset), "failed to get reset line\n");
+ goto reg_off;
+ }
+
+ yas5xx->map = devm_regmap_init_i2c(i2c, &yas5xx_regmap_config);
+ if (IS_ERR(yas5xx->map)) {
+ ret = dev_err_probe(dev, PTR_ERR(yas5xx->map), "failed to allocate register map\n");
+ goto assert_reset;
+ }
+
+ ci = device_get_match_data(dev);
+ if (!ci)
+ ci = (const struct yas5xx_chip_info *)id->driver_data;
+ yas5xx->chip_info = ci;
+
+ ret = regmap_read(yas5xx->map, YAS5XX_DEVICE_ID, &id_check);
+ if (ret)
+ goto assert_reset;
+
+ if (id_check != ci->devid) {
+ ret = dev_err_probe(dev, -ENODEV,
+ "device ID %02x doesn't match %s\n",
+ id_check, id->name);
+ goto assert_reset;
+ }
+
+ ret = ci->get_calibration_data(yas5xx);
+ if (ret)
+ goto assert_reset;
+
+ dev_info(dev, "detected %s %s\n", ci->product_name,
+ ci->version_names[yas5xx->version]);
+
+ ci->dump_calibration(yas5xx);
+
+ ret = ci->power_on(yas5xx);
+ if (ret)
+ goto assert_reset;
+
+ if (ci->measure_offsets) {
+ ret = ci->measure_offsets(yas5xx);
+ if (ret)
+ goto assert_reset;
+ }
+
+ indio_dev->info = &yas5xx_info;
+ indio_dev->available_scan_masks = yas5xx_scan_masks;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->name = id->name;
+ indio_dev->channels = yas5xx_channels;
+ indio_dev->num_channels = ARRAY_SIZE(yas5xx_channels);
+
+ ret = iio_triggered_buffer_setup(indio_dev, NULL,
+ yas5xx_handle_trigger,
+ NULL);
+ if (ret) {
+ dev_err_probe(dev, ret, "triggered buffer setup failed\n");
+ goto assert_reset;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "device register failed\n");
+ goto cleanup_buffer;
+ }
+
+ /* Take runtime PM online */
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ pm_runtime_set_autosuspend_delay(dev, YAS5XX_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_put(dev);
+
+ return 0;
+
+cleanup_buffer:
+ iio_triggered_buffer_cleanup(indio_dev);
+assert_reset:
+ gpiod_set_value_cansleep(yas5xx->reset, 1);
+reg_off:
+ regulator_bulk_disable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs);
+
+ return ret;
+}
+
+static void yas5xx_remove(struct i2c_client *i2c)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(i2c);
+ struct yas5xx *yas5xx = iio_priv(indio_dev);
+ struct device *dev = &i2c->dev;
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+ /*
+ * Now we can't get any more reads from the device, which would
+ * also call pm_runtime* functions and race with our disable
+ * code. Disable PM runtime in orderly fashion and power down.
+ */
+ pm_runtime_get_sync(dev);
+ pm_runtime_put_noidle(dev);
+ pm_runtime_disable(dev);
+ gpiod_set_value_cansleep(yas5xx->reset, 1);
+ regulator_bulk_disable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs);
+}
+
+static int yas5xx_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct yas5xx *yas5xx = iio_priv(indio_dev);
+
+ gpiod_set_value_cansleep(yas5xx->reset, 1);
+ regulator_bulk_disable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs);
+
+ return 0;
+}
+
+static int yas5xx_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct yas5xx *yas5xx = iio_priv(indio_dev);
+ const struct yas5xx_chip_info *ci = yas5xx->chip_info;
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs);
+ if (ret) {
+ dev_err(dev, "cannot enable regulators\n");
+ return ret;
+ }
+
+ /*
+ * The YAS530 datasheet says TVSKW is up to 30 ms, after that 1 ms
+ * for all voltages to settle. The YAS532 is 10ms then 4ms for the
+ * I2C to come online. Let's keep it safe and put this at 31ms.
+ */
+ usleep_range(31000, 40000);
+ gpiod_set_value_cansleep(yas5xx->reset, 0);
+
+ ret = ci->power_on(yas5xx);
+ if (ret) {
+ dev_err(dev, "cannot power on\n");
+ goto out_reset;
+ }
+
+ return 0;
+
+out_reset:
+ gpiod_set_value_cansleep(yas5xx->reset, 1);
+ regulator_bulk_disable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs);
+
+ return ret;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(yas5xx_dev_pm_ops, yas5xx_runtime_suspend,
+ yas5xx_runtime_resume, NULL);
+
+static const struct i2c_device_id yas5xx_id[] = {
+ {"yas530", (kernel_ulong_t)&yas5xx_chip_info_tbl[yas530] },
+ {"yas532", (kernel_ulong_t)&yas5xx_chip_info_tbl[yas532] },
+ {"yas533", (kernel_ulong_t)&yas5xx_chip_info_tbl[yas533] },
+ {"yas537", (kernel_ulong_t)&yas5xx_chip_info_tbl[yas537] },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, yas5xx_id);
+
+static const struct of_device_id yas5xx_of_match[] = {
+ { .compatible = "yamaha,yas530", &yas5xx_chip_info_tbl[yas530] },
+ { .compatible = "yamaha,yas532", &yas5xx_chip_info_tbl[yas532] },
+ { .compatible = "yamaha,yas533", &yas5xx_chip_info_tbl[yas533] },
+ { .compatible = "yamaha,yas537", &yas5xx_chip_info_tbl[yas537] },
+ {}
+};
+MODULE_DEVICE_TABLE(of, yas5xx_of_match);
+
+static struct i2c_driver yas5xx_driver = {
+ .driver = {
+ .name = "yas5xx",
+ .of_match_table = yas5xx_of_match,
+ .pm = pm_ptr(&yas5xx_dev_pm_ops),
+ },
+ .probe = yas5xx_probe,
+ .remove = yas5xx_remove,
+ .id_table = yas5xx_id,
+};
+module_i2c_driver(yas5xx_driver);
+
+MODULE_DESCRIPTION("Yamaha YAS53x 3-axis magnetometer driver");
+MODULE_AUTHOR("Linus Walleij");
+MODULE_LICENSE("GPL v2");