From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/iio/magnetometer/Kconfig | 334 +++++ drivers/iio/magnetometer/Makefile | 45 + drivers/iio/magnetometer/af8133j.c | 527 ++++++++ drivers/iio/magnetometer/ak8974.c | 1042 ++++++++++++++++ drivers/iio/magnetometer/ak8975.c | 1175 ++++++++++++++++++ drivers/iio/magnetometer/als31300.c | 490 ++++++++ drivers/iio/magnetometer/bmc150_magn.c | 1035 ++++++++++++++++ drivers/iio/magnetometer/bmc150_magn.h | 12 + drivers/iio/magnetometer/bmc150_magn_i2c.c | 72 ++ drivers/iio/magnetometer/bmc150_magn_spi.c | 56 + drivers/iio/magnetometer/hid-sensor-magn-3d.c | 568 +++++++++ drivers/iio/magnetometer/hmc5843.h | 56 + drivers/iio/magnetometer/hmc5843_core.c | 688 +++++++++++ drivers/iio/magnetometer/hmc5843_i2c.c | 106 ++ drivers/iio/magnetometer/hmc5843_spi.c | 100 ++ drivers/iio/magnetometer/mag3110.c | 647 ++++++++++ drivers/iio/magnetometer/mmc35240.c | 582 +++++++++ drivers/iio/magnetometer/mmc5633.c | 585 +++++++++ drivers/iio/magnetometer/mmc5983.c | 347 ++++++ drivers/iio/magnetometer/qmc5883l.c | 516 ++++++++ drivers/iio/magnetometer/qmc6308.c | 601 +++++++++ drivers/iio/magnetometer/rm3100-core.c | 616 ++++++++++ drivers/iio/magnetometer/rm3100-i2c.c | 55 + drivers/iio/magnetometer/rm3100-spi.c | 64 + drivers/iio/magnetometer/rm3100.h | 17 + drivers/iio/magnetometer/si7210.c | 438 +++++++ drivers/iio/magnetometer/st_magn.h | 39 + drivers/iio/magnetometer/st_magn_buffer.c | 46 + drivers/iio/magnetometer/st_magn_core.c | 665 ++++++++++ drivers/iio/magnetometer/st_magn_i2c.c | 121 ++ drivers/iio/magnetometer/st_magn_spi.c | 113 ++ drivers/iio/magnetometer/tlv493d.c | 525 ++++++++ drivers/iio/magnetometer/tmag5273.c | 735 +++++++++++ drivers/iio/magnetometer/yamaha-yas530.c | 1620 +++++++++++++++++++++++++ 34 files changed, 14638 insertions(+) create mode 100644 drivers/iio/magnetometer/Kconfig create mode 100644 drivers/iio/magnetometer/Makefile create mode 100644 drivers/iio/magnetometer/af8133j.c create mode 100644 drivers/iio/magnetometer/ak8974.c create mode 100644 drivers/iio/magnetometer/ak8975.c create mode 100644 drivers/iio/magnetometer/als31300.c create mode 100644 drivers/iio/magnetometer/bmc150_magn.c create mode 100644 drivers/iio/magnetometer/bmc150_magn.h create mode 100644 drivers/iio/magnetometer/bmc150_magn_i2c.c create mode 100644 drivers/iio/magnetometer/bmc150_magn_spi.c create mode 100644 drivers/iio/magnetometer/hid-sensor-magn-3d.c create mode 100644 drivers/iio/magnetometer/hmc5843.h create mode 100644 drivers/iio/magnetometer/hmc5843_core.c create mode 100644 drivers/iio/magnetometer/hmc5843_i2c.c create mode 100644 drivers/iio/magnetometer/hmc5843_spi.c create mode 100644 drivers/iio/magnetometer/mag3110.c create mode 100644 drivers/iio/magnetometer/mmc35240.c create mode 100644 drivers/iio/magnetometer/mmc5633.c create mode 100644 drivers/iio/magnetometer/mmc5983.c create mode 100644 drivers/iio/magnetometer/qmc5883l.c create mode 100644 drivers/iio/magnetometer/qmc6308.c create mode 100644 drivers/iio/magnetometer/rm3100-core.c create mode 100644 drivers/iio/magnetometer/rm3100-i2c.c create mode 100644 drivers/iio/magnetometer/rm3100-spi.c create mode 100644 drivers/iio/magnetometer/rm3100.h create mode 100644 drivers/iio/magnetometer/si7210.c create mode 100644 drivers/iio/magnetometer/st_magn.h create mode 100644 drivers/iio/magnetometer/st_magn_buffer.c create mode 100644 drivers/iio/magnetometer/st_magn_core.c create mode 100644 drivers/iio/magnetometer/st_magn_i2c.c create mode 100644 drivers/iio/magnetometer/st_magn_spi.c create mode 100644 drivers/iio/magnetometer/tlv493d.c create mode 100644 drivers/iio/magnetometer/tmag5273.c create mode 100644 drivers/iio/magnetometer/yamaha-yas530.c (limited to 'drivers/iio/magnetometer') diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig new file mode 100644 index 000000000..d359003b2 --- /dev/null +++ b/drivers/iio/magnetometer/Kconfig @@ -0,0 +1,334 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Magnetometer sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Magnetometer sensors" + +config AF8133J + tristate "Voltafield AF8133J 3-Axis Magnetometer" + depends on I2C + depends on OF + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Voltafield AF8133J I2C-based + 3-axis magnetometer chip. + + To compile this driver as a module, choose M here: the module + will be called af8133j. + +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, AK09916 or AK09918 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 ALS31300 + tristate "Allegro MicroSystems ALS31300 3-D Linear Hall Effect Sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Allegro MicroSystems + ALS31300 Hall Effect Sensor through its I2C interface. + + To compile this driver as a module, choose M here: the + module will be called als31300. + +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 Magnetometer 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 MMC5633 + tristate "MEMSIC MMC5633 3-axis magnetic sensor" + select REGMAP_I2C + select REGMAP_I3C if I3C + depends on I3C_OR_I2C + help + Say yes here to build support for the MEMSIC MMC5633 3-axis + magnetic sensor. + + To compile this driver as a module, choose M here: the module + will be called mmc5633 + +config MMC5983 + tristate "MEMSIC MMC5983 3-axis magnetic sensor" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for the MEMSIC MMC5983 3-axis + magnetic sensor. + + To compile this driver as a module, choose M here: the module + will be called mmc5983 + +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: + LSM303C, 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 INFINEON_TLV493D + tristate "Infineon TLV493D Low-Power 3D Magnetic Sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to add support for the Infineon TLV493D-A1B6 Low- + Power 3D Magnetic Sensor. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called tlv493d. + +config QMC5883L + tristate "QST QMC5883L 3-Axis Magnetic Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to add support driver for QST QMC5883L 3-Axis + Magnetic Sensor. + + To compile this driver as a module, choose M here: the + module will be called qmc5883l. + +config QMC6308 + tristate "QST QMC6308 3-Axis Magnetic Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to add support for the QST QMC6308 3-Axis + Magnetic Sensor. + + To compile this driver as a module, choose M here: the + module will be called qmc6308. + +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 SI7210 + tristate "SI7210 Hall effect sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to add support for the SI7210 Hall effect sensor. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called si7210. + +config TI_TMAG5273 + tristate "TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to add support for the TI TMAG5273 Low-Power + Linear 3D Hall-Effect Sensor. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called tmag5273. + +config YAMAHA_YAS530 + tristate "Yamaha YAS530 family of 3-Axis Magnetometers (I2C)" + depends on I2C + 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..8f2205975 --- /dev/null +++ b/drivers/iio/magnetometer/Makefile @@ -0,0 +1,45 @@ +# 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_AF8133J) += af8133j.o +obj-$(CONFIG_AK8974) += ak8974.o +obj-$(CONFIG_AK8975) += ak8975.o +obj-$(CONFIG_ALS31300) += als31300.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_MMC5633) += mmc5633.o +obj-$(CONFIG_MMC5983) += mmc5983.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_INFINEON_TLV493D) += tlv493d.o + +obj-$(CONFIG_QMC5883L) += qmc5883l.o +obj-$(CONFIG_QMC6308) += qmc6308.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_SI7210) += si7210.o + +obj-$(CONFIG_TI_TMAG5273) += tmag5273.o + +obj-$(CONFIG_YAMAHA_YAS530) += yamaha-yas530.o diff --git a/drivers/iio/magnetometer/af8133j.c b/drivers/iio/magnetometer/af8133j.c new file mode 100644 index 000000000..352f53eda --- /dev/null +++ b/drivers/iio/magnetometer/af8133j.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * af8133j.c - Voltafield AF8133J magnetometer driver + * + * Copyright 2021 Icenowy Zheng + * Copyright 2024 Ondřej Jirman + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AF8133J_REG_OUT 0x03 +#define AF8133J_REG_PCODE 0x00 +#define AF8133J_REG_PCODE_VAL 0x5e +#define AF8133J_REG_STATUS 0x02 +#define AF8133J_REG_STATUS_ACQ BIT(0) +#define AF8133J_REG_STATE 0x0a +#define AF8133J_REG_STATE_STBY 0x00 +#define AF8133J_REG_STATE_WORK 0x01 +#define AF8133J_REG_RANGE 0x0b +#define AF8133J_REG_RANGE_22G 0x12 +#define AF8133J_REG_RANGE_12G 0x34 +#define AF8133J_REG_SWR 0x11 +#define AF8133J_REG_SWR_PERFORM 0x81 + +static const char * const af8133j_supply_names[] = { + "avdd", + "dvdd", +}; + +struct af8133j_data { + struct i2c_client *client; + struct regmap *regmap; + /* + * Protect device internal state between starting a measurement + * and reading the result. + */ + struct mutex mutex; + struct iio_mount_matrix orientation; + + struct gpio_desc *reset_gpiod; + struct regulator_bulk_data supplies[ARRAY_SIZE(af8133j_supply_names)]; + + u8 range; +}; + +enum af8133j_axis { + AXIS_X = 0, + AXIS_Y, + AXIS_Z, +}; + +static struct iio_mount_matrix * +af8133j_get_mount_matrix(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct af8133j_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info af8133j_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, af8133j_get_mount_matrix), + { } +}; + +#define AF8133J_CHANNEL(_si, _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_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = af8133j_ext_info, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec af8133j_channels[] = { + AF8133J_CHANNEL(0, X), + AF8133J_CHANNEL(1, Y), + AF8133J_CHANNEL(2, Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static int af8133j_product_check(struct af8133j_data *data) +{ + struct device *dev = &data->client->dev; + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, AF8133J_REG_PCODE, &val); + if (ret) { + dev_err(dev, "Error reading product code (%d)\n", ret); + return ret; + } + + if (val != AF8133J_REG_PCODE_VAL) { + dev_warn(dev, "Invalid product code (0x%02x)\n", val); + return 0; /* Allow unknown ID so fallback compatibles work */ + } + + return 0; +} + +static int af8133j_reset(struct af8133j_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + + if (data->reset_gpiod) { + /* If we have GPIO reset line, use it */ + gpiod_set_value_cansleep(data->reset_gpiod, 1); + udelay(10); + gpiod_set_value_cansleep(data->reset_gpiod, 0); + } else { + /* Otherwise use software reset */ + ret = regmap_write(data->regmap, AF8133J_REG_SWR, + AF8133J_REG_SWR_PERFORM); + if (ret) { + dev_err(dev, "Failed to reset the chip\n"); + return ret; + } + } + + /* Wait for reset to finish */ + usleep_range(1000, 1100); + + /* Restore range setting */ + if (data->range == AF8133J_REG_RANGE_22G) { + ret = regmap_write(data->regmap, AF8133J_REG_RANGE, data->range); + if (ret) + return ret; + } + + return 0; +} + +static void af8133j_power_down(struct af8133j_data *data) +{ + gpiod_set_value_cansleep(data->reset_gpiod, 1); + regulator_bulk_disable(ARRAY_SIZE(data->supplies), data->supplies); +} + +static int af8133j_power_up(struct af8133j_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), data->supplies); + if (ret) { + dev_err(dev, "Could not enable regulators\n"); + return ret; + } + + gpiod_set_value_cansleep(data->reset_gpiod, 0); + + /* Wait for power on reset */ + usleep_range(15000, 16000); + + ret = af8133j_reset(data); + if (ret) { + af8133j_power_down(data); + return ret; + } + + return 0; +} + +static int af8133j_take_measurement(struct af8133j_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_write(data->regmap, + AF8133J_REG_STATE, AF8133J_REG_STATE_WORK); + if (ret) + return ret; + + /* The datasheet says "Mesaure Time <1.5ms" */ + ret = regmap_read_poll_timeout(data->regmap, AF8133J_REG_STATUS, val, + val & AF8133J_REG_STATUS_ACQ, + 500, 1500); + if (ret) + return ret; + + ret = regmap_write(data->regmap, + AF8133J_REG_STATE, AF8133J_REG_STATE_STBY); + if (ret) + return ret; + + return 0; +} + +static int af8133j_read_measurement(struct af8133j_data *data, __le16 buf[3]) +{ + struct device *dev = &data->client->dev; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + /* + * Ignore EACCES because that happens when RPM is disabled + * during system sleep, while userspace leave eg. hrtimer + * trigger attached and IIO core keeps trying to do measurements. + */ + if (ret != -EACCES) + dev_err(dev, "Failed to power on (%d)\n", ret); + return ret; + } + + scoped_guard(mutex, &data->mutex) { + ret = af8133j_take_measurement(data); + if (ret) + goto out_rpm_put; + + ret = regmap_bulk_read(data->regmap, AF8133J_REG_OUT, + buf, sizeof(__le16) * 3); + } + +out_rpm_put: + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static const int af8133j_scales[][2] = { + [0] = { 0, 366210 }, /* 12 gauss */ + [1] = { 0, 671386 }, /* 22 gauss */ +}; + +static int af8133j_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct af8133j_data *data = iio_priv(indio_dev); + __le16 buf[3]; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = af8133j_read_measurement(data, buf); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(buf[chan->address]), + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + + if (data->range == AF8133J_REG_RANGE_12G) + *val2 = af8133j_scales[0][1]; + else + *val2 = af8133j_scales[1][1]; + + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int af8133j_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)af8133j_scales; + *length = ARRAY_SIZE(af8133j_scales) * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int af8133j_set_scale(struct af8133j_data *data, + unsigned int val, unsigned int val2) +{ + struct device *dev = &data->client->dev; + u8 range; + int ret = 0; + + if (af8133j_scales[0][0] == val && af8133j_scales[0][1] == val2) + range = AF8133J_REG_RANGE_12G; + else if (af8133j_scales[1][0] == val && af8133j_scales[1][1] == val2) + range = AF8133J_REG_RANGE_22G; + else + return -EINVAL; + + pm_runtime_disable(dev); + + /* + * When suspended, just store the new range to data->range to be + * applied later during power up. + */ + if (!pm_runtime_status_suspended(dev)) { + scoped_guard(mutex, &data->mutex) + ret = regmap_write(data->regmap, + AF8133J_REG_RANGE, range); + } + + pm_runtime_enable(dev); + + data->range = range; + return ret; +} + +static int af8133j_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct af8133j_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return af8133j_set_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int af8133j_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static const struct iio_info af8133j_info = { + .read_raw = af8133j_read_raw, + .read_avail = af8133j_read_avail, + .write_raw = af8133j_write_raw, + .write_raw_get_fmt = af8133j_write_raw_get_fmt, +}; + +static irqreturn_t af8133j_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct af8133j_data *data = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + struct { + __le16 values[3]; + aligned_s64 timestamp; + } sample = { }; + int ret; + + ret = af8133j_read_measurement(data, sample.values); + if (ret) + goto out_done; + + iio_push_to_buffers_with_ts(indio_dev, &sample, sizeof(sample), + timestamp); + +out_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct regmap_config af8133j_regmap_config = { + .name = "af8133j_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = AF8133J_REG_SWR, +}; + +static void af8133j_power_down_action(void *ptr) +{ + struct af8133j_data *data = ptr; + + if (!pm_runtime_status_suspended(&data->client->dev)) + af8133j_power_down(data); +} + +static int af8133j_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct af8133j_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret, i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &af8133j_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "regmap initialization failed\n"); + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + data->range = AF8133J_REG_RANGE_12G; + mutex_init(&data->mutex); + + data->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(data->reset_gpiod)) + return dev_err_probe(dev, PTR_ERR(data->reset_gpiod), + "Failed to get reset gpio\n"); + + for (i = 0; i < ARRAY_SIZE(af8133j_supply_names); i++) + data->supplies[i].supply = af8133j_supply_names[i]; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), + data->supplies); + if (ret) + return ret; + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return dev_err_probe(dev, ret, "Failed to read mount matrix\n"); + + ret = af8133j_power_up(data); + if (ret) + return ret; + + pm_runtime_set_active(dev); + + ret = devm_add_action_or_reset(dev, af8133j_power_down_action, data); + if (ret) + return ret; + + ret = af8133j_product_check(data); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 500); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_put_autosuspend(dev); + + indio_dev->info = &af8133j_info; + indio_dev->name = "af8133j"; + indio_dev->channels = af8133j_channels; + indio_dev->num_channels = ARRAY_SIZE(af8133j_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &af8133j_trigger_handler, NULL); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to setup iio triggered buffer\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device"); + + return 0; +} + +static int af8133j_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct af8133j_data *data = iio_priv(indio_dev); + + af8133j_power_down(data); + + return 0; +} + +static int af8133j_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct af8133j_data *data = iio_priv(indio_dev); + + return af8133j_power_up(data); +} + +static const struct dev_pm_ops af8133j_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + RUNTIME_PM_OPS(af8133j_runtime_suspend, af8133j_runtime_resume, NULL) +}; + +static const struct of_device_id af8133j_of_match[] = { + { .compatible = "voltafield,af8133j", }, + { } +}; +MODULE_DEVICE_TABLE(of, af8133j_of_match); + +static const struct i2c_device_id af8133j_id[] = { + { .name = "af8133j" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, af8133j_id); + +static struct i2c_driver af8133j_driver = { + .driver = { + .name = "af8133j", + .of_match_table = af8133j_of_match, + .pm = pm_ptr(&af8133j_pm_ops), + }, + .probe = af8133j_probe, + .id_table = af8133j_id, +}; + +module_i2c_driver(af8133j_driver); + +MODULE_AUTHOR("Icenowy Zheng "); +MODULE_AUTHOR("Ondřej Jirman "); +MODULE_DESCRIPTION("Voltafield AF8133J magnetic sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/ak8974.c b/drivers/iio/magnetometer/ak8974.c new file mode 100644 index 000000000..375023db2 --- /dev/null +++ b/drivers/iio/magnetometer/ak8974.c @@ -0,0 +1,1042 @@ +// 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 + * Author: Linus Walleij + */ +#include +#include +#include +#include +#include /* For irq_get_irq_data() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * 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]; + aligned_s64 ts; + } 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_set_bits(ak8974->map, AK8974_CTRL3, 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; + } + + return regmap_bulk_read(ak8974->map, AK8974_DATA_X, result, 6); +} + +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_set_bits(ak8974->map, AK8974_CTRL3, 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[] = "XYZ"; + static const char pgaxis[] = "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 following 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_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_ts(indio_dev, &ak8974->scan, sizeof(ak8974->scan), + iio_get_time_ns(indio_dev)); + + out_unlock: + mutex_unlock(&ak8974->lock); + 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) +{ + 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 = irq_get_trigger_type(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) + 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[] = { + { .name = "ami305" }, + { .name = "ami306" }, + { .name = "ak8974" }, + { .name = "hscdtd008a" }, + { } +}; +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..8b0c07f82 --- /dev/null +++ b/drivers/iio/magnetometer/ak8975.c @@ -0,0 +1,1175 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +/* + * 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 AK09918_DEVICE_ID 0x0C +#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 + +/* + * 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 { + AK8975, + AK8963, + AK09911, + AK09912, + AK09916, + AK09918, +}; + +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[] = { + [AK8975] = { + .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}, + }, + [AK8963] = { + .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}, + }, + [AK09911] = { + .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}, + }, + [AK09912] = { + .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}, + }, + [AK09916] = { + .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}, + }, + [AK09918] = { + /* ak09918 is register compatible with ak09912 this is for avoid + * unknown id messages. + */ + .type = AK09918, + .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]; + aligned_s64 ts; + } 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. + */ + fsleep(500); + + 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(const struct ak8975_data *data, + enum asahi_compass_chipset type) +{ + struct i2c_client *client = data->client; + u8 wia_val[2]; + int ret; + + /* + * Signature for each device: + * Device | WIA1 | WIA2 + * AK09918 | DEVICE_ID_| AK09918_DEVICE_ID + * 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, + sizeof(wia_val), + wia_val); + if (ret < 0) { + dev_err(&client->dev, "Error reading WIA\n"); + return ret; + } + if (ret != sizeof(wia_val)) { + dev_err(&client->dev, "Error reading WIA\n"); + return -EIO; + } + + 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; + case AK09918: + if (wia_val[1] == AK09918_DEVICE_ID) + return 0; + break; + } + + dev_info(&client->dev, "Device ID %x is unknown.\n", wia_val[1]); + /* + * Let driver to probe on unknown id for support more register + * compatible variants. + */ + return 0; +} + +/* + * 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 at least 100us */ + fsleep(100); + + 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 irq; + int ret; + + 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); + + ret = devm_request_irq(&client->dev, irq, ak8975_irq_handler, + IRQF_TRIGGER_RISING, + dev_name(&client->dev), data); + if (ret) + return ret; + + data->eoc_irq = irq; + + return 0; +} + +/* + * Perform some start-of-day setup, including reading the asa calibration + * values and caching them. + */ +static int ak8975_setup(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + 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], + sizeof(data->asa), + data->asa); + if (ret < 0) { + dev_err(&client->dev, "Not able to read asa data\n"); + return ret; + } + if (ret != sizeof(data->asa)) { + dev_err(&client->dev, "Error reading asa data\n"); + return -EIO; + } + + /* 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, + unsigned int poll_ms, + unsigned int timeout_ms) +{ + struct i2c_client *client = data->client; + int ret; + int val; + + /* Wait for the conversion to complete. */ + ret = readx_poll_timeout(gpiod_get_value, data->eoc_gpiod, val, val != 0, + poll_ms * USEC_PER_MSEC, + timeout_ms * USEC_PER_MSEC); + if (ret) + return ret; + if (val < 0) { + dev_err(&client->dev, "Error in reading GPIOD\n"); + return val; + } + + 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, + unsigned int poll_ms, + unsigned int timeout_ms) +{ + struct i2c_client *client = data->client; + int ret; + int val; + + /* Wait for the conversion to complete. */ + ret = read_poll_timeout(i2c_smbus_read_byte_data, val, val != 0, + poll_ms * USEC_PER_MSEC, + timeout_ms * USEC_PER_MSEC, + true, + client, data->def->ctrl_regs[ST1]); + if (ret) + return ret; + if (val < 0) + dev_err(&client->dev, "Error in reading ST1\n"); + + return val; +} + +/* Returns 0 if the end of conversion interrupt occurred or -ETIMEDOUT otherwise */ +static int wait_conversion_complete_interrupt(struct ak8975_data *data, + unsigned int timeout_ms) +{ + int ret; + + ret = wait_event_timeout(data->data_ready_queue, + test_bit(0, &data->flags), + msecs_to_jiffies(timeout_ms)); + clear_bit(0, &data->flags); + + return ret > 0 ? 0 : -ETIMEDOUT; +} + +static int ak8975_start_read_axis(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + /* Set up the device for taking a sample. */ + 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, 100); + else if (data->eoc_gpiod) + ret = wait_conversion_complete_gpio(data, 10, 500); + else + ret = wait_conversion_complete_polled(data, 10, 500); + if (ret < 0) + return ret; + + /* Return with zero if the data is ready. */ + return !data->def->ctrl_regs[ST1_DRDY]; +} + +/* 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; + int ret; + + pm_runtime_get_sync(&data->client->dev); + + mutex_lock(&data->lock); + + ret = ak8975_start_read_axis(data); + 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; + if (ret != sizeof(rval)) { + ret = -EIO; + goto exit; + } + + /* Read out ST2 for release lock on measurement data. */ + ret = i2c_smbus_read_byte_data(client, data->def->ctrl_regs[ST2]); + if (ret < 0) { + dev_err(&client->dev, "Error in reading ST2\n"); + goto exit; + } + + 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); + ret = -EINVAL; + goto exit; + } + + mutex_unlock(&data->lock); + + pm_runtime_put_autosuspend(&data->client->dev); + + /* Swap bytes and convert to valid range. */ + *val = clamp_t(s16, le16_to_cpu(rval), -def->range, def->range); + + return IIO_VAL_INT; + +exit: + mutex_unlock(&data->lock); + pm_runtime_put_autosuspend(&data->client->dev); + 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 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); + 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], + sizeof(fval), + (u8 *)fval); + if (ret < 0) + goto unlock; + if (ret != sizeof(fval)) + 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_ts(indio_dev, &data->scan, sizeof(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_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ak8975_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + return pm_runtime_resume_and_get(dev); +} + +static int ak8975_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ak8975_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_buffer_setup_ops ak8975_buffer_setup_ops = { + .preenable = ak8975_buffer_preenable, + .postdisable = ak8975_buffer_postdisable, +}; +static int ak8975_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct ak8975_data *data; + struct iio_dev *indio_dev; + struct gpio_desc *eoc_gpiod; + struct gpio_desc *reset_gpiod; + const char *name = NULL; + int ret; + + /* + * 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); + 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; + + ret = iio_read_mount_matrix(&client->dev, &data->orientation); + if (ret) + return ret; + + /* id will be NULL when enumerated via ACPI */ + data->def = i2c_get_match_data(client); + if (!data->def) + return -ENODEV; + + /* If enumerated via firmware node, fix the ABI */ + if (dev_fwnode(&client->dev)) + name = dev_name(&client->dev); + else + name = id->name; + + /* 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); + + ret = ak8975_power_on(data); + if (ret) + return ret; + + ret = ak8975_who_i_am(data, data->def->type); + if (ret) { + 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. */ + ret = ak8975_setup(data); + if (ret) { + 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; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, ak8975_handle_trigger, + &ak8975_buffer_setup_ops); + if (ret) { + dev_err(&client->dev, "triggered buffer setup failed\n"); + goto power_off; + } + + ret = iio_device_register(indio_dev); + if (ret) { + 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 ret; +} + +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 acpi_device_id ak_acpi_match[] = { + {"AK8963", (kernel_ulong_t)&ak_def_array[AK8963] }, + {"AK8975", (kernel_ulong_t)&ak_def_array[AK8975] }, + {"AK009911", (kernel_ulong_t)&ak_def_array[AK09911] }, + {"AK09911", (kernel_ulong_t)&ak_def_array[AK09911] }, + {"AK09912", (kernel_ulong_t)&ak_def_array[AK09912] }, + {"AKM9911", (kernel_ulong_t)&ak_def_array[AK09911] }, + {"INVN6500", (kernel_ulong_t)&ak_def_array[AK8963] }, + { } +}; +MODULE_DEVICE_TABLE(acpi, ak_acpi_match); + +static const struct i2c_device_id ak8975_id[] = { + { .name = "AK8963", .driver_data = (kernel_ulong_t)&ak_def_array[AK8963] }, + { .name = "ak8963", .driver_data = (kernel_ulong_t)&ak_def_array[AK8963] }, + { .name = "ak8975", .driver_data = (kernel_ulong_t)&ak_def_array[AK8975] }, + { .name = "ak09911", .driver_data = (kernel_ulong_t)&ak_def_array[AK09911] }, + { .name = "ak09912", .driver_data = (kernel_ulong_t)&ak_def_array[AK09912] }, + { .name = "ak09916", .driver_data = (kernel_ulong_t)&ak_def_array[AK09916] }, + { .name = "ak09918", .driver_data = (kernel_ulong_t)&ak_def_array[AK09918] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak8975_id); + +static const struct of_device_id ak8975_of_match[] = { + { .compatible = "asahi-kasei,ak8975", .data = &ak_def_array[AK8975] }, + { .compatible = "ak8975", .data = &ak_def_array[AK8975] }, + { .compatible = "asahi-kasei,ak8963", .data = &ak_def_array[AK8963] }, + { .compatible = "ak8963", .data = &ak_def_array[AK8963] }, + { .compatible = "asahi-kasei,ak09911", .data = &ak_def_array[AK09911] }, + { .compatible = "ak09911", .data = &ak_def_array[AK09911] }, + { .compatible = "asahi-kasei,ak09912", .data = &ak_def_array[AK09912] }, + { .compatible = "ak09912", .data = &ak_def_array[AK09912] }, + { .compatible = "asahi-kasei,ak09916", .data = &ak_def_array[AK09916] }, + { .compatible = "asahi-kasei,ak09918", .data = &ak_def_array[AK09918] }, + { } +}; +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 "); +MODULE_DESCRIPTION("AK8975 magnetometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/als31300.c b/drivers/iio/magnetometer/als31300.c new file mode 100644 index 000000000..2a2677428 --- /dev/null +++ b/drivers/iio/magnetometer/als31300.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Allegro MicroSystems ALS31300 3-D Linear Hall Effect Sensor + * + * Copyright (c) 2024 Linaro Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * The Allegro MicroSystems ALS31300 has an EEPROM space to configure how + * the device works and how the interrupt line behaves. + * Only the default setup with external trigger is supported. + * + * While the bindings supports declaring an interrupt line, those + * events are not supported. + * + * It should be possible to adapt the driver to the current + * device EEPROM setup at runtime. + */ + +#define ALS31300_EEPROM_CONFIG 0x02 +#define ALS31300_EEPROM_INTERRUPT 0x03 +#define ALS31300_EEPROM_CUSTOMER_1 0x0d +#define ALS31300_EEPROM_CUSTOMER_2 0x0e +#define ALS31300_EEPROM_CUSTOMER_3 0x0f +#define ALS31300_VOL_MODE 0x27 +#define ALS31300_VOL_MODE_LPDCM GENMASK(6, 4) +#define ALS31300_LPDCM_INACTIVE_0_5_MS 0 +#define ALS31300_LPDCM_INACTIVE_1_0_MS 1 +#define ALS31300_LPDCM_INACTIVE_5_0_MS 2 +#define ALS31300_LPDCM_INACTIVE_10_0_MS 3 +#define ALS31300_LPDCM_INACTIVE_50_0_MS 4 +#define ALS31300_LPDCM_INACTIVE_100_0_MS 5 +#define ALS31300_LPDCM_INACTIVE_500_0_MS 6 +#define ALS31300_LPDCM_INACTIVE_1000_0_MS 7 +#define ALS31300_VOL_MODE_SLEEP GENMASK(1, 0) +#define ALS31300_VOL_MODE_ACTIVE_MODE 0 +#define ALS31300_VOL_MODE_SLEEP_MODE 1 +#define ALS31300_VOL_MODE_LPDCM_MODE 2 +#define ALS31300_VOL_MSB 0x28 +#define ALS31300_VOL_MSB_TEMPERATURE GENMASK(5, 0) +#define ALS31300_VOL_MSB_INTERRUPT BIT(6) +#define ALS31300_VOL_MSB_NEW_DATA BIT(7) +#define ALS31300_VOL_MSB_Z_AXIS GENMASK(15, 8) +#define ALS31300_VOL_MSB_Y_AXIS GENMASK(23, 16) +#define ALS31300_VOL_MSB_X_AXIS GENMASK(31, 24) +#define ALS31300_VOL_LSB 0x29 +#define ALS31300_VOL_LSB_TEMPERATURE GENMASK(5, 0) +#define ALS31300_VOL_LSB_HALL_STATUS GENMASK(7, 7) +#define ALS31300_VOL_LSB_Z_AXIS GENMASK(11, 8) +#define ALS31300_VOL_LSB_Y_AXIS GENMASK(15, 12) +#define ALS31300_VOL_LSB_X_AXIS GENMASK(19, 16) +#define ALS31300_VOL_LSB_INTERRUPT_WRITE BIT(20) +#define ALS31300_CUSTOMER_ACCESS 0x35 + +#define ALS31300_DATA_X_GET(b) \ + sign_extend32(FIELD_GET(ALS31300_VOL_MSB_X_AXIS, b[0]) << 4 | \ + FIELD_GET(ALS31300_VOL_LSB_X_AXIS, b[1]), 11) +#define ALS31300_DATA_Y_GET(b) \ + sign_extend32(FIELD_GET(ALS31300_VOL_MSB_Y_AXIS, b[0]) << 4 | \ + FIELD_GET(ALS31300_VOL_LSB_Y_AXIS, b[1]), 11) +#define ALS31300_DATA_Z_GET(b) \ + sign_extend32(FIELD_GET(ALS31300_VOL_MSB_Z_AXIS, b[0]) << 4 | \ + FIELD_GET(ALS31300_VOL_LSB_Z_AXIS, b[1]), 11) +#define ALS31300_TEMPERATURE_GET(b) \ + (FIELD_GET(ALS31300_VOL_MSB_TEMPERATURE, b[0]) << 6 | \ + FIELD_GET(ALS31300_VOL_LSB_TEMPERATURE, b[1])) + +enum als31300_channels { + TEMPERATURE = 0, + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +struct als31300_variant_info { + u8 sensitivity; +}; + +struct als31300_data { + struct device *dev; + /* protects power on/off the device and access HW */ + struct mutex mutex; + const struct als31300_variant_info *variant_info; + struct regmap *map; +}; + +/* The whole measure is split into 2x32-bit registers, we need to read them both at once */ +static int als31300_get_measure(struct als31300_data *data, + u16 *t, s16 *x, s16 *y, s16 *z) +{ + u32 buf[2]; + int ret, err; + + guard(mutex)(&data->mutex); + + ret = pm_runtime_resume_and_get(data->dev); + if (ret) + return ret; + + /* + * Loop until data is valid, new data should have the + * ALS31300_VOL_MSB_NEW_DATA bit set to 1. + * Max update rate is 2KHz, wait up to 1ms. + */ + ret = read_poll_timeout(regmap_bulk_read, err, + err || FIELD_GET(ALS31300_VOL_MSB_NEW_DATA, buf[0]), + 20, USEC_PER_MSEC, false, + data->map, ALS31300_VOL_MSB, buf, ARRAY_SIZE(buf)); + /* Bail out on read_poll_timeout() error */ + if (ret) + goto out; + + /* Bail out on regmap_bulk_read() error */ + if (err) { + dev_err(data->dev, "read data failed, error %d\n", ret); + ret = err; + goto out; + } + + *t = ALS31300_TEMPERATURE_GET(buf); + *x = ALS31300_DATA_X_GET(buf); + *y = ALS31300_DATA_Y_GET(buf); + *z = ALS31300_DATA_Z_GET(buf); + +out: + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int als31300_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct als31300_data *data = iio_priv(indio_dev); + s16 x, y, z; + u16 t; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = als31300_get_measure(data, &t, &x, &y, &z); + if (ret) + return ret; + + switch (chan->address) { + case TEMPERATURE: + *val = t; + return IIO_VAL_INT; + case AXIS_X: + *val = x; + return IIO_VAL_INT; + case AXIS_Y: + *val = y; + return IIO_VAL_INT; + case AXIS_Z: + *val = z; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* + * Fractional part of: + * 1000 * 302 * (value - 1708) + * temp = ---------------------------- + * 4096 + * to convert temperature in millicelcius. + */ + *val = MILLI * 302; + *val2 = 4096; + return IIO_VAL_FRACTIONAL; + case IIO_MAGN: + /* + * Devices are configured in factory + * with different sensitivities: + * - 500 GAUSS <-> 4 LSB/Gauss + * - 1000 GAUSS <-> 2 LSB/Gauss + * - 2000 GAUSS <-> 1 LSB/Gauss + * with translates by a division of the returned + * value to get Gauss value. + * The sensitivity cannot be read at runtime + * so the value depends on the model compatible + * or device id. + */ + *val = 1; + *val2 = data->variant_info->sensitivity; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = -1708; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t als31300_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct als31300_data *data = iio_priv(indio_dev); + struct { + u16 temperature; + s16 channels[3]; + aligned_s64 timestamp; + } scan; + s16 x, y, z; + int ret; + u16 t; + + ret = als31300_get_measure(data, &t, &x, &y, &z); + if (ret) + goto trigger_out; + + scan.temperature = t; + scan.channels[0] = x; + scan.channels[1] = y; + scan.channels[2] = z; + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); + +trigger_out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define ALS31300_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), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec als31300_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = TEMPERATURE, + .scan_index = TEMPERATURE, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + ALS31300_AXIS_CHANNEL(X, AXIS_X), + ALS31300_AXIS_CHANNEL(Y, AXIS_Y), + ALS31300_AXIS_CHANNEL(Z, AXIS_Z), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_info als31300_info = { + .read_raw = als31300_read_raw, +}; + +static int als31300_set_operating_mode(struct als31300_data *data, + unsigned int val) +{ + int ret; + + ret = regmap_update_bits(data->map, ALS31300_VOL_MODE, + ALS31300_VOL_MODE_SLEEP, val); + if (ret) { + dev_err(data->dev, "failed to set operating mode (%pe)\n", ERR_PTR(ret)); + return ret; + } + + /* The time it takes to exit sleep mode is equivalent to Power-On Delay Time */ + if (val == ALS31300_VOL_MODE_ACTIVE_MODE) + fsleep(600); + + return 0; +} + +static void als31300_power_down(void *data) +{ + als31300_set_operating_mode(data, ALS31300_VOL_MODE_SLEEP_MODE); +} + +static const struct iio_buffer_setup_ops als31300_setup_ops = {}; + +static const unsigned long als31300_scan_masks[] = { GENMASK(3, 0), 0 }; + +static bool als31300_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == ALS31300_VOL_MSB || reg == ALS31300_VOL_LSB; +} + +static const struct regmap_config als31300_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = ALS31300_CUSTOMER_ACCESS, + .volatile_reg = als31300_volatile_reg, +}; + +static int als31300_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct als31300_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + i2c_set_clientdata(i2c, indio_dev); + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + data->variant_info = i2c_get_match_data(i2c); + if (!data->variant_info) + return -EINVAL; + + data->map = devm_regmap_init_i2c(i2c, &als31300_regmap_config); + if (IS_ERR(data->map)) + return dev_err_probe(dev, PTR_ERR(data->map), + "failed to allocate register map\n"); + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + ret = als31300_set_operating_mode(data, ALS31300_VOL_MODE_ACTIVE_MODE); + if (ret) + return dev_err_probe(dev, ret, "failed to power on device\n"); + + ret = devm_add_action_or_reset(dev, als31300_power_down, data); + if (ret) + return ret; + + indio_dev->info = &als31300_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = i2c->name; + indio_dev->channels = als31300_channels; + indio_dev->num_channels = ARRAY_SIZE(als31300_channels); + indio_dev->available_scan_masks = als31300_scan_masks; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + als31300_trigger_handler, + &als31300_setup_ops); + if (ret < 0) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = pm_runtime_set_active(dev); + if (ret < 0) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, 200); + pm_runtime_use_autosuspend(dev); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "device register failed\n"); + + return 0; +} + +static int als31300_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct als31300_data *data = iio_priv(indio_dev); + + return als31300_set_operating_mode(data, ALS31300_VOL_MODE_SLEEP_MODE); +} + +static int als31300_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct als31300_data *data = iio_priv(indio_dev); + + return als31300_set_operating_mode(data, ALS31300_VOL_MODE_ACTIVE_MODE); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(als31300_pm_ops, + als31300_runtime_suspend, als31300_runtime_resume, + NULL); + +static const struct als31300_variant_info al31300_variant_500 = { + .sensitivity = 4, +}; + +static const struct als31300_variant_info al31300_variant_1000 = { + .sensitivity = 2, +}; + +static const struct als31300_variant_info al31300_variant_2000 = { + .sensitivity = 1, +}; + +static const struct i2c_device_id als31300_id[] = { + { + .name = "als31300-500", + .driver_data = (kernel_ulong_t)&al31300_variant_500, + }, + { + .name = "als31300-1000", + .driver_data = (kernel_ulong_t)&al31300_variant_1000, + }, + { + .name = "als31300-2000", + .driver_data = (kernel_ulong_t)&al31300_variant_2000, + }, + { } +}; +MODULE_DEVICE_TABLE(i2c, als31300_id); + +static const struct of_device_id als31300_of_match[] = { + { + .compatible = "allegromicro,als31300-500", + .data = &al31300_variant_500, + }, + { + .compatible = "allegromicro,als31300-1000", + .data = &al31300_variant_1000, + }, + { + .compatible = "allegromicro,als31300-2000", + .data = &al31300_variant_2000, + }, + { } +}; +MODULE_DEVICE_TABLE(of, als31300_of_match); + +static struct i2c_driver als31300_driver = { + .driver = { + .name = "als31300", + .of_match_table = als31300_of_match, + .pm = pm_ptr(&als31300_pm_ops), + }, + .probe = als31300_probe, + .id_table = als31300_id, +}; +module_i2c_driver(als31300_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ALS31300 3-D Linear Hall Effect Driver"); +MODULE_AUTHOR("Neil Armstrong "); diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c new file mode 100644 index 000000000..32dff9cdd --- /dev/null +++ b/drivers/iio/magnetometer/bmc150_magn.c @@ -0,0 +1,1035 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "bmc150_magn.h" + +#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]; + aligned_s64 timestamp; + } 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, + FIELD_PREP(BMC150_MAGN_MASK_OPMODE, + BMC150_MAGN_MODE_SLEEP)); + case BMC150_MAGN_POWER_MODE_NORMAL: + return regmap_update_bits(data->regmap, + BMC150_MAGN_REG_OPMODE_ODR, + BMC150_MAGN_MASK_OPMODE, + FIELD_PREP(BMC150_MAGN_MASK_OPMODE, + BMC150_MAGN_MODE_NORMAL)); + } + + return -EINVAL; +} + +static int bmc150_magn_set_power_mode_locked(struct bmc150_magn_data *data, + enum bmc150_magn_power_modes mode) +{ + guard(mutex)(&data->mutex); + return bmc150_magn_set_power_mode(data, mode, true); +} + +static int bmc150_magn_set_power_state(struct bmc150_magn_data *data, bool on) +{ + int ret = 0; + + if (on) + ret = pm_runtime_resume_and_get(data->dev); + else + pm_runtime_put_autosuspend(data->dev); + if (ret < 0) { + dev_err(data->dev, + "failed to change power state to %d\n", on); + return ret; + } + + 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, ®_val); + if (ret < 0) + return ret; + odr_val = FIELD_GET(BMC150_MAGN_MASK_ODR, reg_val); + + 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) +{ + u8 i; + + for (i = 0; i < ARRAY_SIZE(bmc150_magn_samp_freq_table); i++) { + if (bmc150_magn_samp_freq_table[i].freq != val) + continue; + + return regmap_update_bits(data->regmap, + BMC150_MAGN_REG_OPMODE_ODR, + BMC150_MAGN_MASK_ODR, + FIELD_PREP(BMC150_MAGN_MASK_ODR, + bmc150_magn_samp_freq_table[i].reg_val)); + } + + 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, + ®_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, + ®_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; + + guard(mutex)(&data->mutex); + + ret = bmc150_magn_set_power_state(data, true); + if (ret < 0) + return ret; + + ret = bmc150_magn_read_xyz(data, values); + if (ret < 0) { + bmc150_magn_set_power_state(data, false); + return ret; + } + *val = values[chan->scan_index]; + + ret = bmc150_magn_set_power_state(data, false); + if (ret < 0) + return ret; + + 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; + guard(mutex)(&data->mutex); + return bmc150_magn_set_odr(data, val); + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->channel2) { + case IIO_MOD_X: + case IIO_MOD_Y: { + if (val < 1 || val > 511) + return -EINVAL; + guard(mutex)(&data->mutex); + ret = bmc150_magn_set_max_odr(data, val, 0, 0); + if (ret < 0) + return ret; + return regmap_update_bits(data->regmap, + BMC150_MAGN_REG_REP_XY, + BMC150_MAGN_REG_REP_DATAMASK, + BMC150_MAGN_REPXY_TO_REGVAL(val)); + } + case IIO_MOD_Z: { + if (val < 1 || val > 256) + return -EINVAL; + guard(mutex)(&data->mutex); + ret = bmc150_magn_set_max_odr(data, 0, val, 0); + if (ret < 0) + return ret; + return regmap_update_bits(data->regmap, + BMC150_MAGN_REG_REP_Z, + BMC150_MAGN_REG_REP_DATAMASK, + BMC150_MAGN_REPZ_TO_REGVAL(val)); + } + 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_ts(indio_dev, &data->scan, sizeof(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. + */ + fsleep(5 * USEC_PER_MSEC); + + 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; + + guard(mutex)(&data->mutex); + ret = bmc150_magn_reset_intr(data); + 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; + + guard(mutex)(&data->mutex); + + if (state == data->dready_trigger_on) + return 0; + + ret = regmap_update_bits(data->regmap, BMC150_MAGN_REG_INT_DRDY, + BMC150_MAGN_MASK_DRDY_EN, + FIELD_PREP(BMC150_MAGN_MASK_DRDY_EN, state)); + if (ret < 0) + return ret; + + data->dready_trigger_on = state; + + if (state) { + ret = bmc150_magn_reset_intr(data); + if (ret < 0) + return ret; + } + + return 0; +} + +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, +}; + +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; + + 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_irq(irq, iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "bmc150_magn_event", 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); + + bmc150_magn_set_power_mode_locked(data, BMC150_MAGN_POWER_MODE_SUSPEND); + + 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; + + ret = bmc150_magn_set_power_mode_locked(data, + BMC150_MAGN_POWER_MODE_SLEEP); + 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); + + return bmc150_magn_set_power_mode_locked(data, + BMC150_MAGN_POWER_MODE_SLEEP); +} + +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); + + return bmc150_magn_set_power_mode_locked(data, + BMC150_MAGN_POWER_MODE_NORMAL); +} +#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 "); +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..28f2bee21 --- /dev/null +++ b/drivers/iio/magnetometer/bmc150_magn_i2c.c @@ -0,0 +1,72 @@ +// 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 +#include +#include +#include + +#include "bmc150_magn.h" + +static int bmc150_magn_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + 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 i2c_device_id bmc150_magn_i2c_id[] = { + { .name = "bmc150_magn" }, + { .name = "bmc156_magn" }, + { .name = "bmm150_magn" }, + { } +}; +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, + .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 +#include +#include + +#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[] = { + { .name = "bmc150_magn" }, + { .name = "bmc156_magn" }, + { .name = "bmm150_magn" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmc150_magn_spi_id); + +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 = { + .name = "bmc150_magn_spi", + }, +}; +module_spi_driver(bmc150_magn_spi_driver); + +MODULE_AUTHOR("Daniel Baluta +#include +#include +#include +#include +#include +#include +#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) +}; + +/* 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, + u32 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, + u32 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) + *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, + u32 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]; + _channels[*chan_count].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->magn[i].size), + .storagebits = BITS_PER_TYPE(u32), + }; + } + (*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) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + static char *name = "magn_3d"; + struct iio_dev *indio_dev; + struct magn_3d_state *magn_state; + 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) + 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; + } + + 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_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D); +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 void hid_magn_3d_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct magn_3d_state *magn_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D); + hid_sensor_remove_trigger(indio_dev, &magn_state->magn_flux_attributes); +} + +static const struct platform_device_id hid_magn_3d_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200083", + }, + { } +}; +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 "); +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..7a3faf7ff --- /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 + */ + +#ifndef HMC5843_CORE_H +#define HMC5843_CORE_H + +#include +#include + +#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_ts() + */ +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]; + aligned_s64 timestamp; + } 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..fc16ebd31 --- /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 + * Acknowledgment: Jonathan Cameron for valuable inputs. + * Support for HMC5883 and HMC5883L by Peter Meerwald . + * Split to multiple files by Josef Gajdusek - 2014 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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_ts(indio_dev, &data->scan, sizeof(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 "); +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..4c454b005 --- /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 + */ + +#include +#include +#include +#include +#include + +#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 = i2c_client_get_device_id(cli); + 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[] = { + { .name = "hmc5843", .driver_data = HMC5843_ID }, + { .name = "hmc5883", .driver_data = HMC5883_ID }, + { .name = "hmc5883l", .driver_data = HMC5883L_ID }, + { .name = "hmc5983", .driver_data = 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 "); +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..29dee5dce --- /dev/null +++ b/drivers/iio/magnetometer/hmc5843_spi.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPI driver for hmc5983 + * + * Copyright (C) Josef Gajdusek + */ + +#include +#include +#include + +#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; + + spi->mode = SPI_MODE_3; + spi->max_speed_hz = 8000000; + 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, HMC5983_ID, "hmc5983"); +} + +static void hmc5843_spi_remove(struct spi_device *spi) +{ + hmc5843_common_remove(&spi->dev); +} + +static const struct spi_device_id hmc5843_id[] = { + { .name = "hmc5983" }, + { } +}; +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 "); +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..479a12ece --- /dev/null +++ b/drivers/iio/magnetometer/mag3110.c @@ -0,0 +1,647 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mag3110.c - Support for Freescale MAG3110 magnetometer sensor + * + * Copyright (c) 2013 Peter Meerwald + * + * (7-bit I2C slave address 0x0e) + * + * TODO: irq, user offset, oversampling, continuous mode + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + aligned_s64 ts; + } 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; + + guard(mutex)(&data->lock); + ret = mag3110_request(data); + if (ret < 0) + return ret; + return i2c_smbus_read_i2c_block_data(data->client, MAG3110_OUT_X, + 3 * sizeof(__be16), (u8 *) buf); +} + +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; + + guard(mutex)(&data->lock); + + is_active = mag3110_is_active(data); + if (is_active < 0) + return is_active; + + /* config can only be changed when in standby */ + if (is_active > 0) { + ret = mag3110_standby(data); + if (ret < 0) + return ret; + } + + /* + * 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) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, reg, val); + if (ret < 0) + return ret; + + if (is_active > 0) { + ret = mag3110_active(data); + if (ret < 0) + return ret; + } + + return 0; +} + +static int __mag3110_read_info_raw(struct mag3110_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + __be16 buffer[3]; + int ret; + + switch (chan->type) { + case IIO_MAGN: /* in 0.1 uT / LSB */ + ret = mag3110_read(data, buffer); + if (ret < 0) + return ret; + *val = sign_extend32(be16_to_cpu(buffer[chan->scan_index]), + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + + case IIO_TEMP: { /* in 1 C / LSB */ + guard(mutex)(&data->lock); + ret = mag3110_request(data); + if (ret < 0) + return ret; + ret = i2c_smbus_read_byte_data(data->client, + MAG3110_DIE_TEMP); + if (ret < 0) + return ret; + *val = sign_extend32(ret, chan->scan_type.realbits - 1); + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +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); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __mag3110_read_info_raw(data, chan, val); + iio_device_release_direct(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 mag3110_data *data, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int rate; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rate = mag3110_get_samp_freq_index(data, val, val2); + if (rate < 0) + return -EINVAL; + + 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; + + return mag3110_change_config(data, MAG3110_CTRL_REG1, + data->ctrl_reg1); + + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -10000 || val > 10000) + return -EINVAL; + + return i2c_smbus_write_word_swapped(data->client, + MAG3110_OFF_X + 2 * chan->scan_index, val << 1); + default: + 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 ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __mag3110_write_raw(data, chan, val, val2, mask); + iio_device_release_direct(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_ts(indio_dev, &data->scan, sizeof(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 = i2c_client_get_device_id(client); + 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[] = { + { .name = "mag3110" }, + { } +}; +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 "); +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..6f40cba4b --- /dev/null +++ b/drivers/iio/magnetometer/mmc35240.c @@ -0,0 +1,582 @@ +// 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 +#include +#include +#include +#include +#include + +#include +#include + +#define MMC35240_DRV_NAME "mmc35240" + +#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_set_bits(data->regmap, MMC35240_REG_CTRL0, + 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_set_bits(data->regmap, MMC35240_REG_CTRL0, 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, ®_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, + ®_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, ®); + 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", + + .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) +{ + 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[] = { + { .name = "mmc35240" }, + { } +}; +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 = mmc35240_acpi_match, + }, + .probe = mmc35240_probe, + .id_table = mmc35240_id, +}; + +module_i2c_driver(mmc35240_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("MEMSIC MMC35240 magnetic sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/mmc5633.c b/drivers/iio/magnetometer/mmc5633.c new file mode 100644 index 000000000..f4dae7ba1 --- /dev/null +++ b/drivers/iio/magnetometer/mmc5633.c @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MMC5633 - MEMSIC 3-axis Magnetic Sensor + * + * Copyright (c) 2015, Intel Corporation. + * Copyright (c) 2025, NXP + * + * IIO driver for MMC5633, base on mmc35240.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MMC5633_REG_XOUT0 0x00 +#define MMC5633_REG_XOUT1 0x01 +#define MMC5633_REG_YOUT0 0x02 +#define MMC5633_REG_YOUT1 0x03 +#define MMC5633_REG_ZOUT0 0x04 +#define MMC5633_REG_ZOUT1 0x05 +#define MMC5633_REG_XOUT2 0x06 +#define MMC5633_REG_YOUT2 0x07 +#define MMC5633_REG_ZOUT2 0x08 +#define MMC5633_REG_TOUT 0x09 + +#define MMC5633_REG_STATUS1 0x18 +#define MMC5633_REG_STATUS0 0x19 +#define MMC5633_REG_CTRL0 0x1b +#define MMC5633_REG_CTRL1 0x1c +#define MMC5633_REG_CTRL2 0x1d + +#define MMC5633_REG_ID 0x39 + +#define MMC5633_STATUS1_MEAS_T_DONE_BIT BIT(7) +#define MMC5633_STATUS1_MEAS_M_DONE_BIT BIT(6) + +#define MMC5633_CTRL0_CMM_FREQ_EN BIT(7) +#define MMC5633_CTRL0_AUTO_ST_EN BIT(6) +#define MMC5633_CTRL0_AUTO_SR_EN BIT(5) +#define MMC5633_CTRL0_RESET BIT(4) +#define MMC5633_CTRL0_SET BIT(3) +#define MMC5633_CTRL0_MEAS_T BIT(1) +#define MMC5633_CTRL0_MEAS_M BIT(0) + +#define MMC5633_CTRL1_BW_MASK GENMASK(1, 0) + +#define MMC5633_WAIT_SET_RESET_US (1 * USEC_PER_MSEC) + +#define MMC5633_HDR_CTRL0_MEAS_M 0x01 +#define MMC5633_HDR_CTRL0_MEAS_T 0x03 +#define MMC5633_HDR_CTRL0_SET 0x05 +#define MMC5633_HDR_CTRL0_RESET 0x07 + +enum mmc5633_axis { + MMC5633_AXIS_X, + MMC5633_AXIS_Y, + MMC5633_AXIS_Z, + MMC5633_TEMPERATURE, +}; + +struct mmc5633_data { + struct regmap *regmap; + struct i3c_device *i3cdev; + struct mutex mutex; /* protect to finish one whole measurement */ +}; + +static int mmc5633_samp_freq[][2] = { + { 1, 200000 }, + { 2, 0 }, + { 3, 500000 }, + { 6, 600000 }, +}; + +#define MMC5633_CHANNEL(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .address = MMC5633_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 mmc5633_channels[] = { + MMC5633_CHANNEL(X), + MMC5633_CHANNEL(Y), + MMC5633_CHANNEL(Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = MMC5633_TEMPERATURE, + }, +}; + +static int mmc5633_get_samp_freq_index(struct mmc5633_data *data, + int val, int val2) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mmc5633_samp_freq); i++) + if (mmc5633_samp_freq[i][0] == val && + mmc5633_samp_freq[i][1] == val2) + return i; + return -EINVAL; +} + +static int mmc5633_init(struct mmc5633_data *data) +{ + unsigned int reg_id; + int ret; + + ret = regmap_read(data->regmap, MMC5633_REG_ID, ®_id); + if (ret) + return dev_err_probe(regmap_get_device(data->regmap), ret, + "Error reading product id\n"); + + /* + * Make sure we restore sensor characteristics, by doing + * a SET/RESET sequence, the axis polarity being naturally + * aligned after RESET. + */ + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, MMC5633_CTRL0_SET); + if (ret) + return ret; + + /* + * Minimum time interval between SET or RESET to other operations is + * 1ms according to Operating Timing Diagram in datasheet. + */ + fsleep(MMC5633_WAIT_SET_RESET_US); + + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, MMC5633_CTRL0_RESET); + if (ret) + return ret; + + /* set default sampling frequency */ + return regmap_update_bits(data->regmap, MMC5633_REG_CTRL1, + MMC5633_CTRL1_BW_MASK, + FIELD_PREP(MMC5633_CTRL1_BW_MASK, 0)); +} + +static int mmc5633_take_measurement(struct mmc5633_data *data, int address) +{ + unsigned int reg_status, val; + int ret; + + val = (address == MMC5633_TEMPERATURE) ? MMC5633_CTRL0_MEAS_T : MMC5633_CTRL0_MEAS_M; + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, val); + if (ret < 0) + return ret; + + val = (address == MMC5633_TEMPERATURE) ? + MMC5633_STATUS1_MEAS_T_DONE_BIT : MMC5633_STATUS1_MEAS_M_DONE_BIT; + ret = regmap_read_poll_timeout(data->regmap, MMC5633_REG_STATUS1, reg_status, + reg_status & val, + 10 * USEC_PER_MSEC, + 100 * 10 * USEC_PER_MSEC); + if (ret) { + dev_err(regmap_get_device(data->regmap), "data not ready\n"); + return ret; + } + + return 0; +} + +static bool mmc5633_is_support_hdr(struct mmc5633_data *data) +{ + if (!data->i3cdev) + return false; + + return i3c_device_get_supported_xfer_mode(data->i3cdev) & BIT(I3C_HDR_DDR); +} + +static int mmc5633_read_measurement(struct mmc5633_data *data, int address, void *buf, size_t sz) +{ + struct device *dev = regmap_get_device(data->regmap); + u8 data_cmd[2], status[2]; + unsigned int val, ready; + int ret; + + if (mmc5633_is_support_hdr(data)) { + struct i3c_xfer xfers_wr_cmd[] = { + { + .cmd = 0x3b, + .len = 2, + .data.out = data_cmd, + } + }; + struct i3c_xfer xfers_rd_sta_cmd[] = { + { + .cmd = 0x23 | BIT(7), /* RDSTA CMD */ + .len = 2, + .data.in = status, + }, + }; + struct i3c_xfer xfers_rd_data_cmd[] = { + { + .cmd = 0x22 | BIT(7), /* RDLONG CMD */ + .len = sz, + .data.in = buf, + }, + }; + + data_cmd[0] = 0; + data_cmd[1] = (address == MMC5633_TEMPERATURE) ? + MMC5633_HDR_CTRL0_MEAS_T : MMC5633_HDR_CTRL0_MEAS_M; + + ret = i3c_device_do_xfers(data->i3cdev, xfers_wr_cmd, + ARRAY_SIZE(xfers_wr_cmd), I3C_HDR_DDR); + if (ret < 0) + return ret; + + ready = (address == MMC5633_TEMPERATURE) ? + MMC5633_STATUS1_MEAS_T_DONE_BIT : MMC5633_STATUS1_MEAS_M_DONE_BIT; + ret = read_poll_timeout(i3c_device_do_xfers, val, + val || (status[0] & ready), + 10 * USEC_PER_MSEC, + 100 * 10 * USEC_PER_MSEC, 0, + data->i3cdev, xfers_rd_sta_cmd, + ARRAY_SIZE(xfers_rd_sta_cmd), I3C_HDR_DDR); + if (ret) { + dev_err(dev, "data not ready\n"); + return ret; + } + if (val) { + dev_err(dev, "i3c transfer error\n"); + return val; + } + return i3c_device_do_xfers(data->i3cdev, xfers_rd_data_cmd, + ARRAY_SIZE(xfers_rd_data_cmd), I3C_HDR_DDR); + } + + /* Fallback to use SDR/I2C mode */ + ret = mmc5633_take_measurement(data, address); + if (ret < 0) + return ret; + + if (address == MMC5633_TEMPERATURE) + /* + * Put tempeature to last byte of buff to align HDR case. + * I3C will early terminate data read if previous data is not + * available. + */ + return regmap_bulk_read(data->regmap, MMC5633_REG_TOUT, buf + sz - 1, 1); + + return regmap_bulk_read(data->regmap, MMC5633_REG_XOUT0, buf, sz); +} + +/* X,Y,Z 3 channels, each channel has 3 byte and TEMP */ +#define MMC5633_ALL_SIZE (3 * 3 + 1) + +static int mmc5633_get_raw(struct mmc5633_data *data, int index, unsigned char *buf, int *val) +{ + if (index == MMC5633_TEMPERATURE) { + *val = buf[MMC5633_ALL_SIZE - 1]; + return 0; + } + /* + * X[19..12] X[11..4] Y[19..12] Y[11..4] Z[19..12] Z[11..4] X[3..0] Y[3..0] Z[3..0] + */ + *val = get_unaligned_be16(buf + 2 * index) << 4; + *val |= buf[index + 6] >> 4; + + return 0; +} + +static int mmc5633_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mmc5633_data *data = iio_priv(indio_dev); + char buf[MMC5633_ALL_SIZE]; + unsigned int reg, i; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + scoped_guard(mutex, &data->mutex) { + ret = mmc5633_read_measurement(data, chan->address, buf, MMC5633_ALL_SIZE); + if (ret < 0) + return ret; + } + + ret = mmc5633_get_raw(data, chan->address, buf, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_MAGN) { + *val = 0; + *val2 = 62500; + } else { + *val = 0; + *val2 = 800000000; /* 0.8C */ + } + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = -75; + return IIO_VAL_INT; + } + return -EINVAL; + case IIO_CHAN_INFO_SAMP_FREQ: + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, MMC5633_REG_CTRL1, ®); + if (ret < 0) + return ret; + } + + i = FIELD_GET(MMC5633_CTRL1_BW_MASK, reg); + if (i >= ARRAY_SIZE(mmc5633_samp_freq)) + return -EINVAL; + + *val = mmc5633_samp_freq[i][0]; + *val2 = mmc5633_samp_freq[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mmc5633_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct mmc5633_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + ret = mmc5633_get_samp_freq_index(data, val, val2); + if (ret < 0) + return ret; + + guard(mutex)(&data->mutex); + + return regmap_update_bits(data->regmap, MMC5633_REG_CTRL1, + MMC5633_CTRL1_BW_MASK, + FIELD_PREP(MMC5633_CTRL1_BW_MASK, ret)); + } + default: + return -EINVAL; + } +} + +static int mmc5633_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)mmc5633_samp_freq; + *length = ARRAY_SIZE(mmc5633_samp_freq) * 2; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info mmc5633_info = { + .read_raw = mmc5633_read_raw, + .write_raw = mmc5633_write_raw, + .read_avail = mmc5633_read_avail, +}; + +static bool mmc5633_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_CTRL0: + case MMC5633_REG_CTRL1: + return true; + default: + return false; + } +} + +static bool mmc5633_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_XOUT0: + case MMC5633_REG_XOUT1: + case MMC5633_REG_YOUT0: + case MMC5633_REG_YOUT1: + case MMC5633_REG_ZOUT0: + case MMC5633_REG_ZOUT1: + case MMC5633_REG_XOUT2: + case MMC5633_REG_YOUT2: + case MMC5633_REG_ZOUT2: + case MMC5633_REG_TOUT: + case MMC5633_REG_STATUS1: + case MMC5633_REG_ID: + return true; + default: + return false; + } +} + +static bool mmc5633_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_CTRL0: + case MMC5633_REG_CTRL1: + return false; + default: + return true; + } +} + +static const struct reg_default mmc5633_reg_defaults[] = { + { MMC5633_REG_CTRL0, 0x00 }, + { MMC5633_REG_CTRL1, 0x00 }, +}; + +static const struct regmap_config mmc5633_regmap_config = { + .name = "mmc5633_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = MMC5633_REG_ID, + .cache_type = REGCACHE_MAPLE, + + .writeable_reg = mmc5633_is_writeable_reg, + .readable_reg = mmc5633_is_readable_reg, + .volatile_reg = mmc5633_is_volatile_reg, + + .reg_defaults = mmc5633_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(mmc5633_reg_defaults), +}; + +static int mmc5633_common_probe(struct regmap *regmap, char *name, + struct i3c_device *i3cdev) +{ + struct device *dev = regmap_get_device(regmap); + struct mmc5633_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->regmap = regmap; + data->i3cdev = i3cdev; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + indio_dev->info = &mmc5633_info; + indio_dev->name = name; + indio_dev->channels = mmc5633_channels; + indio_dev->num_channels = ARRAY_SIZE(mmc5633_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = mmc5633_init(data); + if (ret < 0) + return dev_err_probe(dev, ret, "mmc5633 chip init failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int mmc5633_suspend(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + + regcache_cache_only(regmap, true); + + return 0; +} + +static int mmc5633_resume(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + int ret; + + regcache_mark_dirty(regmap); + ret = regcache_sync_region(regmap, MMC5633_REG_CTRL0, MMC5633_REG_CTRL1); + if (ret) + dev_err(dev, "Failed to restore control registers\n"); + + regcache_cache_only(regmap, false); + + return 0; +} + +static int mmc5633_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &mmc5633_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n"); + + return mmc5633_common_probe(regmap, client->name, NULL); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mmc5633_pm_ops, mmc5633_suspend, mmc5633_resume); + +static const struct of_device_id mmc5633_of_match[] = { + { .compatible = "memsic,mmc5603" }, + { .compatible = "memsic,mmc5633" }, + { } +}; +MODULE_DEVICE_TABLE(of, mmc5633_of_match); + +static const struct i2c_device_id mmc5633_i2c_id[] = { + { .name = "mmc5603" }, + { .name = "mmc5633" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mmc5633_i2c_id); + +static struct i2c_driver mmc5633_i2c_driver = { + .driver = { + .name = "mmc5633_i2c", + .of_match_table = mmc5633_of_match, + .pm = pm_sleep_ptr(&mmc5633_pm_ops), + }, + .probe = mmc5633_i2c_probe, + .id_table = mmc5633_i2c_id, +}; + +static const struct i3c_device_id mmc5633_i3c_ids[] = { + I3C_DEVICE(0x0251, 0x0000, NULL), + { } +}; +MODULE_DEVICE_TABLE(i3c, mmc5633_i3c_ids); + +static int mmc5633_i3c_probe(struct i3c_device *i3cdev) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + struct regmap *regmap; + char *name; + + name = devm_kasprintf(dev, GFP_KERNEL, "mmc5633_%s", dev_name(dev)); + if (!name) + return -ENOMEM; + + regmap = devm_regmap_init_i3c(i3cdev, &mmc5633_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to register i3c regmap\n"); + + return mmc5633_common_probe(regmap, name, i3cdev); +} + +static struct i3c_driver mmc5633_i3c_driver = { + .driver = { + .name = "mmc5633_i3c", + }, + .probe = mmc5633_i3c_probe, + .id_table = mmc5633_i3c_ids, +}; +module_i3c_i2c_driver(mmc5633_i3c_driver, &mmc5633_i2c_driver) + +MODULE_AUTHOR("Frank Li "); +MODULE_DESCRIPTION("MEMSIC MMC5633 magnetic sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/mmc5983.c b/drivers/iio/magnetometer/mmc5983.c new file mode 100644 index 000000000..d10a6288e --- /dev/null +++ b/drivers/iio/magnetometer/mmc5983.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MMC5983 - MEMSIC 3-axis Magnetic Sensor + * + * Copyright (c) 2026, Vlad Kulikov + * + * IIO driver for MMC5983 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MMC5983_REG_XOUT0 0x00 +#define MMC5983_REG_XOUT1 0x01 +#define MMC5983_REG_YOUT0 0x02 +#define MMC5983_REG_YOUT1 0x03 +#define MMC5983_REG_ZOUT0 0x04 +#define MMC5983_REG_ZOUT1 0x05 +#define MMC5983_REG_XYZOUT2 0x06 + +#define MMC5983_REG_STATUS 0x08 + +#define MMC5983_REG_CTRL0 0x09 +#define MMC5983_REG_CTRL1 0x0A +#define MMC5983_REG_CTRL2 0x0B +#define MMC5983_REG_CTRL3 0x0C + +#define MMC5983_REG_ID 0x2F + +#define MMC5983_PRODUCT_ID 0x30 + +#define MMC5983_STATUS_MEAS_M_DONE_BIT BIT(0) +#define MMC5983_STATUS_OTP_RD_DONE_BIT BIT(4) + +#define MMC5983_CTRL0_TM_M_BIT BIT(0) +#define MMC5983_CTRL0_SET_BIT BIT(3) +#define MMC5983_CTRL0_RESET_BIT BIT(4) +#define MMC5983_CTRL0_OTP_RD_BIT BIT(6) + +#define MMC5983_CTRL1_SW_RST_BIT BIT(7) + +enum mmc5983_axis { + MMC5983_AXIS_X, + MMC5983_AXIS_Y, + MMC5983_AXIS_Z, +}; + +struct mmc5983_data { + struct regmap *regmap; + /* Protects chip access during SET/RESET measurement sequence */ + struct mutex mutex; +}; + +#define MMC5983_CHANNEL(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = MMC5983_AXIS_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mmc5983_channels[] = { + MMC5983_CHANNEL(X), + MMC5983_CHANNEL(Y), + MMC5983_CHANNEL(Z), +}; + +static int mmc5983_pulse_coil(struct mmc5983_data *data, unsigned int coil_bit) +{ + int ret; + + ret = regmap_write(data->regmap, MMC5983_REG_CTRL0, coil_bit); + if (ret) + return ret; + + /* + * Datasheet page 15: SET/RESET coil pulse is 500 ns. + * Vendor sample code waits 500 us before the next operation. + */ + fsleep(500); + + return 0; +} + +static int mmc5983_take_measurement(struct mmc5983_data *data, int m[3]) +{ + unsigned int status; + u8 buf[7]; + int ret; + + ret = regmap_write(data->regmap, MMC5983_REG_CTRL0, + MMC5983_CTRL0_TM_M_BIT); + if (ret) + return ret; + + /* + * Datasheet page 15: measurement time is 8 ms at BW=00 (default, + * slowest setting). Use a 50 ms timeout for margin. + */ + ret = regmap_read_poll_timeout(data->regmap, MMC5983_REG_STATUS, + status, + status & MMC5983_STATUS_MEAS_M_DONE_BIT, + 10 * USEC_PER_MSEC, + 50 * USEC_PER_MSEC); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, MMC5983_REG_XOUT0, buf, + sizeof(buf)); + if (ret) + return ret; + + m[0] = (buf[0] << 10) | (buf[1] << 2) | ((buf[6] >> 6) & 0x3); + m[1] = (buf[2] << 10) | (buf[3] << 2) | ((buf[6] >> 4) & 0x3); + m[2] = (buf[4] << 10) | (buf[5] << 2) | ((buf[6] >> 2) & 0x3); + + return 0; +} + +static int mmc5983_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct mmc5983_data *data = iio_priv(indio_dev); + int m1[3], m2[3]; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + guard(mutex)(&data->mutex); + + /* SET: magnetize sensor elements in forward direction */ + ret = mmc5983_pulse_coil(data, MMC5983_CTRL0_SET_BIT); + if (ret) + return ret; + + ret = mmc5983_take_measurement(data, m1); + if (ret) + return ret; + + /* RESET: magnetize sensor elements in reverse direction */ + ret = mmc5983_pulse_coil(data, MMC5983_CTRL0_RESET_BIT); + if (ret) + return ret; + + ret = mmc5983_take_measurement(data, m2); + if (ret) + return ret; + + *val = (m1[chan->address] - m2[chan->address]) / 2; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 61035; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info mmc5983_info = { + .read_raw = mmc5983_read_raw, +}; + +static bool mmc5983_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5983_REG_CTRL0: + case MMC5983_REG_CTRL1: + case MMC5983_REG_CTRL2: + case MMC5983_REG_CTRL3: + return true; + default: + return false; + } +} + +static bool mmc5983_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5983_REG_XOUT0: + case MMC5983_REG_XOUT1: + case MMC5983_REG_YOUT0: + case MMC5983_REG_YOUT1: + case MMC5983_REG_ZOUT0: + case MMC5983_REG_ZOUT1: + case MMC5983_REG_XYZOUT2: + case MMC5983_REG_STATUS: + case MMC5983_REG_CTRL0: + case MMC5983_REG_CTRL1: + case MMC5983_REG_CTRL2: + case MMC5983_REG_CTRL3: + case MMC5983_REG_ID: + return true; + default: + return false; + } +} + +static bool mmc5983_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5983_REG_XOUT0: + case MMC5983_REG_XOUT1: + case MMC5983_REG_YOUT0: + case MMC5983_REG_YOUT1: + case MMC5983_REG_ZOUT0: + case MMC5983_REG_ZOUT1: + case MMC5983_REG_XYZOUT2: + case MMC5983_REG_STATUS: + case MMC5983_REG_CTRL0: + case MMC5983_REG_CTRL1: + return true; + default: + return false; + } +} + +static const struct regmap_config mmc5983_regmap_config = { + .name = "mmc5983_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = MMC5983_REG_ID, + .writeable_reg = mmc5983_is_writeable_reg, + .readable_reg = mmc5983_is_readable_reg, + .volatile_reg = mmc5983_is_volatile_reg, +}; + +static int mmc5983_init(struct mmc5983_data *data) +{ + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(regmap); + unsigned int reg_id, status; + int ret; + + ret = regmap_read(regmap, MMC5983_REG_ID, ®_id); + if (ret) + return dev_err_probe(dev, ret, "Error reading product id\n"); + + if (reg_id != MMC5983_PRODUCT_ID) + dev_info(dev, "unexpected product id 0x%02x\n", reg_id); + + ret = regmap_write(regmap, MMC5983_REG_CTRL1, MMC5983_CTRL1_SW_RST_BIT); + if (ret) + return ret; + + /* Datasheet page 15: power-on time after SW_RST is 10 ms */ + fsleep(10 * USEC_PER_MSEC); + + ret = regmap_write(regmap, MMC5983_REG_CTRL0, MMC5983_CTRL0_OTP_RD_BIT); + if (ret) + return ret; + + /* + * Datasheet page 15: OTP read completes and self-clears. No separate + * OTP refresh timeout is specified, so use the 10 ms power-on time as + * a conservative upper bound. + */ + ret = regmap_read_poll_timeout(regmap, MMC5983_REG_STATUS, status, + status & MMC5983_STATUS_OTP_RD_DONE_BIT, + USEC_PER_MSEC, + 10 * USEC_PER_MSEC); + if (ret) + return ret; + + /* SET: magnetize sensor elements in forward direction */ + ret = mmc5983_pulse_coil(data, MMC5983_CTRL0_SET_BIT); + if (ret) + return ret; + + /* RESET: magnetize sensor elements in reverse direction */ + return mmc5983_pulse_coil(data, MMC5983_CTRL0_RESET_BIT); +} + +static int mmc5983_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct mmc5983_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); + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + data->regmap = devm_regmap_init_i2c(i2c, &mmc5983_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "failed to allocate register map\n"); + + indio_dev->info = &mmc5983_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = "mmc5983"; + indio_dev->channels = mmc5983_channels; + indio_dev->num_channels = ARRAY_SIZE(mmc5983_channels); + + ret = mmc5983_init(data); + if (ret) + return dev_err_probe(dev, ret, "mmc5983 chip init failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id mmc5983_of_match[] = { + { .compatible = "memsic,mmc5983" }, + { } +}; +MODULE_DEVICE_TABLE(of, mmc5983_of_match); + +static const struct i2c_device_id mmc5983_id[] = { + { .name = "mmc5983" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mmc5983_id); + +static struct i2c_driver mmc5983_driver = { + .driver = { + .name = "mmc5983", + .of_match_table = mmc5983_of_match, + }, + .probe = mmc5983_probe, + .id_table = mmc5983_id, +}; +module_i2c_driver(mmc5983_driver); + +MODULE_AUTHOR("Vladislav Kulikov "); +MODULE_DESCRIPTION("MEMSIC MMC5983 magnetic sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/qmc5883l.c b/drivers/iio/magnetometer/qmc5883l.c new file mode 100644 index 000000000..c3469715d --- /dev/null +++ b/drivers/iio/magnetometer/qmc5883l.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * Support for QST QMC5883L 3-Axis Magnetic Sensor on I2C bus. + * + * Copyright (C) 2026 Siratul Islam + * + * Datasheet available at + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define QMC5883L_REG_X_LSB 0x00 +#define QMC5883L_REG_STATUS1 0x06 +#define QMC5883L_REG_CTRL1 0x09 +#define QMC5883L_REG_CTRL2 0x0A +#define QMC5883L_REG_SET_RESET 0x0B +#define QMC5883L_REG_ID 0x0D + +#define QMC5883L_CHIP_ID 0xFF + +#define QMC5883L_MODE_MASK GENMASK(1, 0) +#define QMC5883L_ODR_MASK GENMASK(3, 2) +#define QMC5883L_RNG_MASK GENMASK(5, 4) +#define QMC5883L_OSR_MASK GENMASK(7, 6) + +#define QMC5883L_MODE_STANDBY 0x00 +#define QMC5883L_MODE_CONT 0x01 + +#define QMC5883L_ODR_10HZ 0x00 +#define QMC5883L_ODR_50HZ 0x01 +#define QMC5883L_ODR_100HZ 0x02 +#define QMC5883L_ODR_200HZ 0x03 + +#define QMC5883L_RNG_2G 0x00 +#define QMC5883L_RNG_8G 0x01 + +#define QMC5883L_OSR_512 0x00 +#define QMC5883L_OSR_256 0x01 +#define QMC5883L_OSR_128 0x02 +#define QMC5883L_OSR_64 0x03 + +#define QMC5883L_STATUS_DRDY BIT(0) +#define QMC5883L_STATUS_OVL BIT(1) + +#define QMC5883L_SET_RESET_VAL BIT(0) +#define QMC5883L_INT_DISABLE BIT(0) +#define QMC5883L_SOFT_RESET BIT(7) + +#define QMC5883L_PORT_US 350 + +struct qmc5883l_data { + struct regmap *regmap; + /* + * Protect data->range/odr/osr. + * Protect poll and read during measurement. + */ + struct mutex mutex; + u8 range; + u8 odr; + u8 osr; +}; + +enum qmc5883l_chan { + QMC5883L_AXIS_X, + QMC5883L_AXIS_Y, + QMC5883L_AXIS_Z, +}; + +static const int qmc5883l_odr_avail[] = { 10, 50, 100, 200 }; + +static const int qmc5883l_osr_avail[] = { 512, 256, 128, 64 }; + +static const int qmc5883l_scales[][2] = { + [QMC5883L_RNG_2G] = { 0, 83333 }, + [QMC5883L_RNG_8G] = { 0, 333333 }, +}; + +static int qmc5883l_take_measurement(struct iio_dev *indio_dev, int index, + int *val) +{ + struct qmc5883l_data *data = iio_priv(indio_dev); + struct regmap *map = data->regmap; + unsigned int status; + __le16 buf[3]; + int ret; + + guard(mutex) (&data->mutex); + + /* 50ms headroom over the slowest ODR (10Hz) */ + ret = regmap_read_poll_timeout(map, QMC5883L_REG_STATUS1, + status, (status & QMC5883L_STATUS_DRDY), + 2 * USEC_PER_MSEC, 150 * USEC_PER_MSEC); + if (ret) + return ret; + + ret = regmap_bulk_read(map, QMC5883L_REG_X_LSB, buf, sizeof(buf)); + if (ret) + return ret; + + if (status & QMC5883L_STATUS_OVL) + return -ERANGE; + + *val = (s16)le16_to_cpu(buf[index]); + + return 0; +} + +static int qmc5883l_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct qmc5883l_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = qmc5883l_take_measurement(indio_dev, chan->address, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: { + guard(mutex)(&data->mutex); + + *val = qmc5883l_scales[data->range][0]; + *val2 = qmc5883l_scales[data->range][1]; + + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&data->mutex); + + switch (data->odr) { + case QMC5883L_ODR_200HZ: + *val = 200; + break; + case QMC5883L_ODR_100HZ: + *val = 100; + break; + case QMC5883L_ODR_50HZ: + *val = 50; + break; + case QMC5883L_ODR_10HZ: + *val = 10; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&data->mutex); + + switch (data->osr) { + case QMC5883L_OSR_64: + *val = 64; + break; + case QMC5883L_OSR_128: + *val = 128; + break; + case QMC5883L_OSR_256: + *val = 256; + break; + case QMC5883L_OSR_512: + *val = 512; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int qmc5883l_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct qmc5883l_data *data = iio_priv(indio_dev); + u8 rng, osr, odr; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: { + if (val != 0) + return -EINVAL; + + if (val2 == qmc5883l_scales[QMC5883L_RNG_2G][1]) + rng = QMC5883L_RNG_2G; + else if (val2 == qmc5883l_scales[QMC5883L_RNG_8G][1]) + rng = QMC5883L_RNG_8G; + else + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, + QMC5883L_RNG_MASK, + FIELD_PREP(QMC5883L_RNG_MASK, rng)); + if (ret) + return ret; + + data->range = rng; + + return 0; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + switch (val) { + case 200: + odr = QMC5883L_ODR_200HZ; + break; + case 100: + odr = QMC5883L_ODR_100HZ; + break; + case 50: + odr = QMC5883L_ODR_50HZ; + break; + case 10: + odr = QMC5883L_ODR_10HZ; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, + QMC5883L_ODR_MASK, + FIELD_PREP(QMC5883L_ODR_MASK, odr)); + if (ret) + return ret; + + data->odr = odr; + + return 0; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + switch (val) { + case 64: + osr = QMC5883L_OSR_64; + break; + case 128: + osr = QMC5883L_OSR_128; + break; + case 256: + osr = QMC5883L_OSR_256; + break; + case 512: + osr = QMC5883L_OSR_512; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, + QMC5883L_OSR_MASK, + FIELD_PREP(QMC5883L_OSR_MASK, osr)); + if (ret) + return ret; + + data->osr = osr; + + return 0; + } + default: + return -EINVAL; + } +} + +static int qmc5883l_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = qmc5883l_odr_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc5883l_odr_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = qmc5883l_osr_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc5883l_osr_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)qmc5883l_scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(qmc5883l_scales) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int qmc5883l_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT; + } +} + +static const struct iio_info qmc5883l_info = { + .read_raw = qmc5883l_read_raw, + .write_raw = qmc5883l_write_raw, + .read_avail = qmc5883l_read_avail, + .write_raw_get_fmt = qmc5883l_write_raw_get_fmt, +}; + +static int qmc5883l_init(struct qmc5883l_data *data) +{ + struct regmap *map = data->regmap; + unsigned int reg; + int ret; + + ret = regmap_read(map, QMC5883L_REG_ID, ®); + if (ret) + return ret; + + /* Not failing because rev 1.0 had this register reserved */ + if (reg != QMC5883L_CHIP_ID) + dev_warn(regmap_get_device(map), + "Unknown chip id: 0x%02x, continuing\n", reg); + + ret = regmap_write(map, QMC5883L_REG_CTRL2, QMC5883L_SOFT_RESET); + if (ret) + return ret; + + /* Use POR completion time as a conservative bound */ + fsleep(QMC5883L_PORT_US); + + /* DRDY pin not used in this version of the driver */ + ret = regmap_write(map, QMC5883L_REG_CTRL2, QMC5883L_INT_DISABLE); + if (ret) + return ret; + + ret = regmap_write(map, QMC5883L_REG_SET_RESET, QMC5883L_SET_RESET_VAL); + if (ret) + return ret; + + data->odr = QMC5883L_ODR_50HZ; + data->range = QMC5883L_RNG_2G; + data->osr = QMC5883L_OSR_64; + + return regmap_write(map, QMC5883L_REG_CTRL1, + FIELD_PREP(QMC5883L_MODE_MASK, QMC5883L_MODE_CONT) | + FIELD_PREP(QMC5883L_ODR_MASK, data->odr) | + FIELD_PREP(QMC5883L_RNG_MASK, data->range) | + FIELD_PREP(QMC5883L_OSR_MASK, data->osr)); +} + +static void qmc5883l_power_down_action(void *priv) +{ + struct qmc5883l_data *data = priv; + + regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, + QMC5883L_MODE_MASK, + FIELD_PREP(QMC5883L_MODE_MASK, QMC5883L_MODE_STANDBY)); +} + +static bool qmc5883l_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg <= QMC5883L_REG_STATUS1; +} + +static bool qmc5883l_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case QMC5883L_REG_CTRL1: + case QMC5883L_REG_CTRL2: + case QMC5883L_REG_SET_RESET: + return true; + default: + return false; + } +} + +static const struct regmap_config qmc5883l_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = QMC5883L_REG_ID, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = qmc5883l_volatile_reg, + .writeable_reg = qmc5883l_writable_reg, +}; + +#define QMC5883L_CHANNEL(_axis) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = QMC5883L_AXIS_##_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) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + } + +static const struct iio_chan_spec qmc5883l_channels[] = { + QMC5883L_CHANNEL(X), + QMC5883L_CHANNEL(Y), + QMC5883L_CHANNEL(Z), +}; + +static int qmc5883l_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct qmc5883l_data *data; + struct iio_dev *indio_dev; + struct regmap *map; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + map = devm_regmap_init_i2c(client, &qmc5883l_regmap_config); + if (IS_ERR(map)) + return dev_err_probe(dev, PTR_ERR(map), + "regmap initialization failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable VDD regulator\n"); + + ret = devm_regulator_get_enable(dev, "vddio"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable VDDIO regulator\n"); + + /* POR completion time max per datasheet Table 7 */ + fsleep(QMC5883L_PORT_US); + + data = iio_priv(indio_dev); + data->regmap = map; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + indio_dev->name = "qmc5883l"; + indio_dev->info = &qmc5883l_info; + indio_dev->channels = qmc5883l_channels; + indio_dev->num_channels = ARRAY_SIZE(qmc5883l_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = qmc5883l_init(data); + if (ret) + return dev_err_probe(dev, ret, "qmc5883l init failed\n"); + + ret = devm_add_action_or_reset(dev, qmc5883l_power_down_action, data); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id qmc5883l_match[] = { + { .compatible = "qstcorp,qmc5883l" }, + { } +}; +MODULE_DEVICE_TABLE(of, qmc5883l_match); + +static const struct i2c_device_id qmc5883l_id[] = { + { .name = "qmc5883l" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, qmc5883l_id); + +static struct i2c_driver qmc5883l_driver = { + .driver = { + .name = "qmc5883l", + .of_match_table = qmc5883l_match, + }, + .id_table = qmc5883l_id, + .probe = qmc5883l_probe, +}; +module_i2c_driver(qmc5883l_driver); + +MODULE_DESCRIPTION("QST QMC5883L 3-Axis Magnetic Sensor driver"); +MODULE_AUTHOR("Siratul Islam "); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/iio/magnetometer/qmc6308.c b/drivers/iio/magnetometer/qmc6308.c new file mode 100644 index 000000000..72348cd9e --- /dev/null +++ b/drivers/iio/magnetometer/qmc6308.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for QST QMC6308 3-Axis Magnetic Sensor on I2C bus. + * + * Copyright (C) 2026 Jorijn van der Graaf + * + * Datasheet available at + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define QMC6308_REG_ID 0x00 +#define QMC6308_REG_X_LSB 0x01 +#define QMC6308_REG_STATUS 0x09 +#define QMC6308_REG_CTRL1 0x0A +#define QMC6308_REG_CTRL2 0x0B +#define QMC6308_REG_CTRL3 0x0D +#define QMC6308_REG_CTRL4 0x29 + +#define QMC6308_CHIP_ID 0x80 + +/* Control register 1 */ +#define QMC6308_MODE_MASK GENMASK(1, 0) +#define QMC6308_ODR_MASK GENMASK(3, 2) +#define QMC6308_OSR1_MASK GENMASK(5, 4) +#define QMC6308_OSR2_MASK GENMASK(7, 6) + +#define QMC6308_MODE_SUSPEND 0x00 +#define QMC6308_MODE_NORMAL 0x01 + +#define QMC6308_ODR_10HZ 0x00 +#define QMC6308_ODR_50HZ 0x01 +#define QMC6308_ODR_100HZ 0x02 +#define QMC6308_ODR_200HZ 0x03 + +#define QMC6308_OSR1_8 0x00 +#define QMC6308_OSR1_4 0x01 +#define QMC6308_OSR1_2 0x02 +#define QMC6308_OSR1_1 0x03 + +/* Control register 2 */ +#define QMC6308_SET_RESET_MASK GENMASK(1, 0) +#define QMC6308_RNG_MASK GENMASK(3, 2) +#define QMC6308_SELF_TEST BIT(6) +#define QMC6308_SOFT_RST BIT(7) + +#define QMC6308_SET_RESET_ON 0x00 + +#define QMC6308_RNG_30G 0x00 +#define QMC6308_RNG_12G 0x01 +#define QMC6308_RNG_8G 0x02 +#define QMC6308_RNG_2G 0x03 + +/* Status register */ +#define QMC6308_STATUS_DRDY BIT(0) +#define QMC6308_STATUS_OVFL BIT(1) + +/* Power-on completion time (datasheet Table 7) */ +#define QMC6308_POR_US 250 + +#define QMC6308_AUTOSUSPEND_DELAY_MS 500 + +struct qmc6308_data { + struct regmap *regmap; + /* Protect data->range/odr/osr and serialize measurements */ + struct mutex mutex; + struct iio_mount_matrix orientation; + u8 range; + u8 odr; + u8 osr; +}; + +enum qmc6308_axis { + QMC6308_AXIS_X, + QMC6308_AXIS_Y, + QMC6308_AXIS_Z, +}; + +static const int qmc6308_odr_avail[] = { + [QMC6308_ODR_10HZ] = 10, + [QMC6308_ODR_50HZ] = 50, + [QMC6308_ODR_100HZ] = 100, + [QMC6308_ODR_200HZ] = 200, +}; + +static const int qmc6308_osr1_avail[] = { + [QMC6308_OSR1_8] = 8, + [QMC6308_OSR1_4] = 4, + [QMC6308_OSR1_2] = 2, + [QMC6308_OSR1_1] = 1, +}; + +/* + * Sensitivity is 1000/2500/3750/15000 LSB/Gauss for the + * +-30/12/8/2 Gauss ranges respectively. + */ +static const int qmc6308_scales[][2] = { + [QMC6308_RNG_30G] = { 0, 1000000 }, + [QMC6308_RNG_12G] = { 0, 400000 }, + [QMC6308_RNG_8G] = { 0, 266667 }, + [QMC6308_RNG_2G] = { 0, 66667 }, +}; + +static int qmc6308_set_mode(struct qmc6308_data *data, unsigned int mode) +{ + return regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, + QMC6308_MODE_MASK, + FIELD_PREP(QMC6308_MODE_MASK, mode)); +} + +static int qmc6308_take_measurement(struct iio_dev *indio_dev, int index, + int *val) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + unsigned int status; + __le16 buf[3]; + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(dev, "Failed to power on (%d)\n", ret); + return ret; + } + + guard(mutex)(&data->mutex); + + /* + * Reading the status register clears DRDY, which is why the poll + * and the data read stay under one mutex hold. A runtime resume + * clears DRDY too, so a sample converted before the last suspend + * is never returned here. + * + * The timeout is 50ms of headroom over the slowest ODR (10Hz). + */ + ret = regmap_read_poll_timeout(data->regmap, QMC6308_REG_STATUS, + status, (status & QMC6308_STATUS_DRDY), + 2 * USEC_PER_MSEC, + 150 * USEC_PER_MSEC); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, QMC6308_REG_X_LSB, buf, + sizeof(buf)); + if (ret) + return ret; + + if (status & QMC6308_STATUS_OVFL) + return -ERANGE; + + *val = (s16)le16_to_cpu(buf[index]); + + return 0; +} + +static int qmc6308_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = qmc6308_take_measurement(indio_dev, chan->address, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: { + guard(mutex)(&data->mutex); + + *val = qmc6308_scales[data->range][0]; + *val2 = qmc6308_scales[data->range][1]; + + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&data->mutex); + + *val = qmc6308_odr_avail[data->odr]; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&data->mutex); + + *val = qmc6308_osr1_avail[data->osr]; + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int qmc6308_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + unsigned int status; + unsigned int i; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: { + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(qmc6308_scales); i++) { + if (val2 == qmc6308_scales[i][1]) + break; + } + if (i == ARRAY_SIZE(qmc6308_scales)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL2, + QMC6308_RNG_MASK, + FIELD_PREP(QMC6308_RNG_MASK, i)); + if (ret) + return ret; + + data->range = i; + + /* + * The data registers still hold (and DRDY still + * advertises) a sample converted at the previous range; + * discard it so that a read does not pair old-range data + * with the new scale. A conversion already in flight may + * still complete at the old range, so this narrows the + * window rather than closing it. The range change itself + * took effect, so only log a failure here: an error + * would mislead userspace about an effective write. + */ + ret = regmap_read(data->regmap, QMC6308_REG_STATUS, + &status); + if (ret) + dev_warn(regmap_get_device(data->regmap), + "Failed to discard stale sample (%d)\n", ret); + + return 0; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + for (i = 0; i < ARRAY_SIZE(qmc6308_odr_avail); i++) { + if (val == qmc6308_odr_avail[i]) + break; + } + if (i == ARRAY_SIZE(qmc6308_odr_avail)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, + QMC6308_ODR_MASK, + FIELD_PREP(QMC6308_ODR_MASK, i)); + if (ret) + return ret; + + data->odr = i; + + return 0; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + for (i = 0; i < ARRAY_SIZE(qmc6308_osr1_avail); i++) { + if (val == qmc6308_osr1_avail[i]) + break; + } + if (i == ARRAY_SIZE(qmc6308_osr1_avail)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, + QMC6308_OSR1_MASK, + FIELD_PREP(QMC6308_OSR1_MASK, i)); + if (ret) + return ret; + + data->osr = i; + + return 0; + } + default: + return -EINVAL; + } +} + +static int qmc6308_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = qmc6308_odr_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc6308_odr_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = qmc6308_osr1_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc6308_osr1_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)qmc6308_scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(qmc6308_scales) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int qmc6308_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT; + } +} + +static const struct iio_mount_matrix * +qmc6308_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info qmc6308_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, qmc6308_get_mount_matrix), + { } +}; + +static const struct iio_info qmc6308_info = { + .read_raw = qmc6308_read_raw, + .write_raw = qmc6308_write_raw, + .read_avail = qmc6308_read_avail, + .write_raw_get_fmt = qmc6308_write_raw_get_fmt, +}; + +static int qmc6308_init(struct qmc6308_data *data) +{ + struct regmap *map = data->regmap; + unsigned int reg; + int ret; + + ret = regmap_read(map, QMC6308_REG_ID, ®); + if (ret) + return ret; + + /* Allow unknown IDs so that fallback compatibles work */ + if (reg != QMC6308_CHIP_ID) + dev_warn(regmap_get_device(map), + "Unknown chip id: 0x%02x, continuing\n", reg); + + /* The SOFT_RST bit is not auto-cleared and must be written back 0 */ + ret = regmap_write(map, QMC6308_REG_CTRL2, QMC6308_SOFT_RST); + if (ret) + return ret; + + /* + * The datasheet gives no soft-reset completion figure; reuse the + * power-on time as a conservative bound. + */ + fsleep(QMC6308_POR_US); + + data->range = QMC6308_RNG_30G; + data->odr = QMC6308_ODR_50HZ; + data->osr = QMC6308_OSR1_8; + + ret = regmap_write(map, QMC6308_REG_CTRL2, + FIELD_PREP(QMC6308_SET_RESET_MASK, + QMC6308_SET_RESET_ON) | + FIELD_PREP(QMC6308_RNG_MASK, data->range)); + if (ret) + return ret; + + /* OSR2 (second-stage filter) set to its power-on default of 0 */ + return regmap_write(map, QMC6308_REG_CTRL1, + FIELD_PREP(QMC6308_MODE_MASK, + QMC6308_MODE_NORMAL) | + FIELD_PREP(QMC6308_ODR_MASK, data->odr) | + FIELD_PREP(QMC6308_OSR1_MASK, data->osr) | + FIELD_PREP(QMC6308_OSR2_MASK, 0)); +} + +static void qmc6308_power_down_action(void *priv) +{ + struct qmc6308_data *data = priv; + + if (!pm_runtime_status_suspended(regmap_get_device(data->regmap))) + qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); +} + +static bool qmc6308_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg >= QMC6308_REG_X_LSB && reg <= QMC6308_REG_STATUS; +} + +static bool qmc6308_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case QMC6308_REG_CTRL1: + case QMC6308_REG_CTRL2: + case QMC6308_REG_CTRL3: + case QMC6308_REG_CTRL4: + return true; + default: + return false; + } +} + +static const struct regmap_config qmc6308_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = QMC6308_REG_CTRL4, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = qmc6308_volatile_reg, + .writeable_reg = qmc6308_writable_reg, +}; + +#define QMC6308_CHANNEL(_axis) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = QMC6308_AXIS_##_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) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .ext_info = qmc6308_ext_info, \ + } + +static const struct iio_chan_spec qmc6308_channels[] = { + QMC6308_CHANNEL(X), + QMC6308_CHANNEL(Y), + QMC6308_CHANNEL(Z), +}; + +static int qmc6308_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct qmc6308_data *data; + struct iio_dev *indio_dev; + struct regmap *map; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + map = devm_regmap_init_i2c(client, &qmc6308_regmap_config); + if (IS_ERR(map)) + return dev_err_probe(dev, PTR_ERR(map), + "regmap initialization failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable VDD regulator\n"); + + fsleep(QMC6308_POR_US); + + data = iio_priv(indio_dev); + data->regmap = map; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read mount matrix\n"); + + indio_dev->name = "qmc6308"; + indio_dev->info = &qmc6308_info; + indio_dev->channels = qmc6308_channels; + indio_dev->num_channels = ARRAY_SIZE(qmc6308_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = qmc6308_init(data); + if (ret) + return dev_err_probe(dev, ret, "qmc6308 init failed\n"); + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, qmc6308_power_down_action, data); + if (ret) + return ret; + + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, QMC6308_AUTOSUSPEND_DELAY_MS); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int qmc6308_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct qmc6308_data *data = iio_priv(indio_dev); + + return qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); +} + +static int qmc6308_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct qmc6308_data *data = iio_priv(indio_dev); + unsigned int status; + int ret; + + ret = qmc6308_set_mode(data, QMC6308_MODE_NORMAL); + if (ret) + return ret; + + /* + * DRDY may still be set for a sample converted before the last + * suspend; reading the status register clears it so the next + * measurement waits for fresh data. + */ + ret = regmap_read(data->regmap, QMC6308_REG_STATUS, &status); + if (ret) { + /* Best effort to leave the chip in a consistent state */ + qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); + } + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(qmc6308_pm_ops, qmc6308_runtime_suspend, + qmc6308_runtime_resume, NULL); + +static const struct of_device_id qmc6308_match[] = { + { .compatible = "qstcorp,qmc6308" }, + { } +}; +MODULE_DEVICE_TABLE(of, qmc6308_match); + +static const struct i2c_device_id qmc6308_id[] = { + { .name = "qmc6308" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, qmc6308_id); + +static struct i2c_driver qmc6308_driver = { + .driver = { + .name = "qmc6308", + .of_match_table = qmc6308_match, + .pm = pm_ptr(&qmc6308_pm_ops), + }, + .id_table = qmc6308_id, + .probe = qmc6308_probe, +}; +module_i2c_driver(qmc6308_driver); + +MODULE_DESCRIPTION("QST QMC6308 3-Axis Magnetic Sensor driver"); +MODULE_AUTHOR("Jorijn van der Graaf "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/rm3100-core.c b/drivers/iio/magnetometer/rm3100-core.c new file mode 100644 index 000000000..46fce2978 --- /dev/null +++ b/drivers/iio/magnetometer/rm3100-core.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PNI RM3100 3-axis geomagnetic sensor driver core. + * + * Copyright (C) 2018 Song Qiang + * + * User Manual available at + * + * + * TODO: event generation, pm. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#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; + + guard(mutex)(&data->lock); + + ret = regmap_write(regmap, RM3100_REG_POLL, BIT(4 + idx)); + if (ret < 0) + return ret; + + ret = rm3100_wait_measurement(data); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(regmap, RM3100_REG_MX2 + 3 * idx, buffer, 3); + if (ret < 0) + return ret; + + *val = sign_extend32(get_unaligned_be24(&buffer[0]), 23); + + return IIO_VAL_INT; +} + +#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; + + guard(mutex)(&data->lock); + + ret = regmap_read(data->regmap, RM3100_REG_TMRC, &tmp); + 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; + + guard(mutex)(&data->lock); + + /* All cycle count registers use the same value. */ + ret = regmap_read(regmap, RM3100_REG_CC_X, &cycle_count); + if (ret < 0) + return ret; + + 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) + return -EINVAL; + + ret = regmap_write(regmap, RM3100_REG_TMRC, i + RM3100_TMRC_OFFSET); + if (ret < 0) + return ret; + + /* Checking if cycle count registers need changing. */ + if (val == 600 && cycle_count == 200) { + ret = rm3100_set_cycle_count(data, 100); + if (ret < 0) + return ret; + } else if (val != 600 && cycle_count == 100) { + ret = rm3100_set_cycle_count(data, 200); + if (ret < 0) + return ret; + } + + if (iio_buffer_enabled(indio_dev)) { + /* Writing TMRC registers requires CMM reset. */ + ret = regmap_write(regmap, RM3100_REG_CMM, 0); + if (ret < 0) + return ret; + ret = regmap_write(data->regmap, RM3100_REG_CMM, + (*indio_dev->active_scan_mask & 0x7) << + RM3100_CMM_AXIS_SHIFT | RM3100_CMM_START); + if (ret < 0) + return ret; + } + + data->conversion_time = rm3100_samp_rates[i][2] * 2; + return 0; +} + +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: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = rm3100_read_mag(data, chan->scan_index, val); + iio_device_release_direct(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, +}; + +/** + * rm3100_regmap_bulk_read_locked() - Wrapper around regmap_bulk_read() with a mutex + * + * @data: Data structure containing regmap and mutex + * @reg: First register to be read from, passed to regmap_bulk_read() + * @val: Pointer to store read value, in native register size for device, + * passed to regmap_bulk_read() + * @val_count: Number of registers to read, passed to regmap_bulk_read() + * + * Intended for use only in rm3100_trigger_handler(). + * + * Return: + * A value of zero on success, a negative errno in error cases. + */ +static int rm3100_regmap_bulk_read_locked(struct rm3100_data *data, unsigned int reg, + void *val, size_t val_count) +{ + guard(mutex)(&data->lock); + return regmap_bulk_read(data->regmap, reg, val, val_count); +} + +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 = iio_get_masklength(indio_dev); + struct rm3100_data *data = iio_priv(indio_dev); + int ret, i, bit; + + switch (scan_mask) { + case BIT(0) | BIT(1) | BIT(2): + ret = rm3100_regmap_bulk_read_locked(data, RM3100_REG_MX2, + data->buffer, 9); + 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 = rm3100_regmap_bulk_read_locked(data, RM3100_REG_MX2, + data->buffer, 6); + if (ret < 0) + goto done; + memmove(data->buffer + 4, data->buffer + 3, 3); + break; + case BIT(1) | BIT(2): + ret = rm3100_regmap_bulk_read_locked(data, RM3100_REG_MY2, + data->buffer, 6); + if (ret < 0) + goto done; + memmove(data->buffer + 4, data->buffer + 3, 3); + break; + case BIT(0) | BIT(2): + ret = rm3100_regmap_bulk_read_locked(data, RM3100_REG_MX2, + data->buffer, 9); + 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 = rm3100_regmap_bulk_read_locked(data, + RM3100_REG_MX2 + 3 * bit, + data->buffer, 3); + if (ret < 0) + goto done; + } + } + /* + * 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_ts(indio_dev, data->buffer, sizeof(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; + int samp_rate_index; + + 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) + 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; + + samp_rate_index = tmp - RM3100_TMRC_OFFSET; + if (samp_rate_index < 0 || samp_rate_index >= RM3100_SAMP_NUM) { + dev_err(dev, "The value read from RM3100_REG_TMRC is invalid!\n"); + return -EINVAL; + } + /* Initializing max wait time, which is double conversion time. */ + data->conversion_time = rm3100_samp_rates[samp_rate_index][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 "); +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..a09a271b6 --- /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 + * + * i2c slave address: 0x20 + SA1 << 1 + SA0. + */ + +#include +#include + +#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 = rm3100_probe, +}; +module_i2c_driver(rm3100_driver); + +MODULE_AUTHOR("Song Qiang "); +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..2f60a41c0 --- /dev/null +++ b/drivers/iio/magnetometer/rm3100-spi.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for PNI RM3100 3-axis geomagnetic sensor on a spi bus. + * + * Copyright (C) 2018 Song Qiang + */ + +#include +#include + +#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; + 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 "); +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 + */ + +#ifndef RM3100_CORE_H +#define RM3100_CORE_H + +#include + +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/si7210.c b/drivers/iio/magnetometer/si7210.c new file mode 100644 index 000000000..e9670d671 --- /dev/null +++ b/drivers/iio/magnetometer/si7210.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Silicon Labs Si7210 Hall Effect sensor driver + * + * Copyright (c) 2024 Antoni Pokusinski + * + * Datasheet: + * https://www.silabs.com/documents/public/data-sheets/si7210-datasheet.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Registers offsets and masks */ +#define SI7210_REG_DSPSIGM 0xC1 +#define SI7210_REG_DSPSIGL 0xC2 + +#define SI7210_MASK_DSPSIGSEL GENMASK(2, 0) +#define SI7210_REG_DSPSIGSEL 0xC3 + +#define SI7210_MASK_STOP BIT(1) +#define SI7210_MASK_ONEBURST BIT(2) +#define SI7210_REG_POWER_CTRL 0xC4 + +#define SI7210_MASK_ARAUTOINC BIT(0) +#define SI7210_REG_ARAUTOINC 0xC5 + +#define SI7210_REG_A0 0xCA +#define SI7210_REG_A1 0xCB +#define SI7210_REG_A2 0xCC +#define SI7210_REG_A3 0xCE +#define SI7210_REG_A4 0xCF +#define SI7210_REG_A5 0xD0 + +#define SI7210_REG_OTP_ADDR 0xE1 +#define SI7210_REG_OTP_DATA 0xE2 + +#define SI7210_MASK_OTP_READ_EN BIT(1) +#define SI7210_REG_OTP_CTRL 0xE3 + +/* OTP data registers offsets */ +#define SI7210_OTPREG_TMP_OFF 0x1D +#define SI7210_OTPREG_TMP_GAIN 0x1E + +#define SI7210_OTPREG_A0_20 0x21 +#define SI7210_OTPREG_A1_20 0x22 +#define SI7210_OTPREG_A2_20 0x23 +#define SI7210_OTPREG_A3_20 0x24 +#define SI7210_OTPREG_A4_20 0x25 +#define SI7210_OTPREG_A5_20 0x26 + +#define SI7210_OTPREG_A0_200 0x27 +#define SI7210_OTPREG_A1_200 0x28 +#define SI7210_OTPREG_A2_200 0x29 +#define SI7210_OTPREG_A3_200 0x2A +#define SI7210_OTPREG_A4_200 0x2B +#define SI7210_OTPREG_A5_200 0x2C + +#define A_REGS_COUNT 6 + +static const unsigned int a20_otp_regs[A_REGS_COUNT] = { + SI7210_OTPREG_A0_20, SI7210_OTPREG_A1_20, SI7210_OTPREG_A2_20, + SI7210_OTPREG_A3_20, SI7210_OTPREG_A4_20, SI7210_OTPREG_A5_20, +}; + +static const unsigned int a200_otp_regs[A_REGS_COUNT] = { + SI7210_OTPREG_A0_200, SI7210_OTPREG_A1_200, SI7210_OTPREG_A2_200, + SI7210_OTPREG_A3_200, SI7210_OTPREG_A4_200, SI7210_OTPREG_A5_200, +}; + +static const struct regmap_range si7210_read_reg_ranges[] = { + regmap_reg_range(SI7210_REG_DSPSIGM, SI7210_REG_ARAUTOINC), + regmap_reg_range(SI7210_REG_A0, SI7210_REG_A2), + regmap_reg_range(SI7210_REG_A3, SI7210_REG_A5), + regmap_reg_range(SI7210_REG_OTP_ADDR, SI7210_REG_OTP_CTRL), +}; + +static const struct regmap_access_table si7210_readable_regs = { + .yes_ranges = si7210_read_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si7210_read_reg_ranges), +}; + +static const struct regmap_range si7210_write_reg_ranges[] = { + regmap_reg_range(SI7210_REG_DSPSIGSEL, SI7210_REG_ARAUTOINC), + regmap_reg_range(SI7210_REG_A0, SI7210_REG_A2), + regmap_reg_range(SI7210_REG_A3, SI7210_REG_A5), + regmap_reg_range(SI7210_REG_OTP_ADDR, SI7210_REG_OTP_CTRL), +}; + +static const struct regmap_access_table si7210_writeable_regs = { + .yes_ranges = si7210_write_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si7210_write_reg_ranges), +}; + +static const struct regmap_range si7210_volatile_reg_ranges[] = { + regmap_reg_range(SI7210_REG_DSPSIGM, SI7210_REG_DSPSIGL), + regmap_reg_range(SI7210_REG_POWER_CTRL, SI7210_REG_POWER_CTRL), +}; + +static const struct regmap_access_table si7210_volatile_regs = { + .yes_ranges = si7210_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si7210_volatile_reg_ranges), +}; + +static const struct regmap_config si7210_regmap_conf = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SI7210_REG_OTP_CTRL, + + .rd_table = &si7210_readable_regs, + .wr_table = &si7210_writeable_regs, + .volatile_table = &si7210_volatile_regs, +}; + +struct si7210_data { + struct regmap *regmap; + struct i2c_client *client; + struct mutex fetch_lock; /* lock for a single measurement fetch */ + unsigned int vdd_uV; + s8 temp_offset; + s8 temp_gain; + s8 scale_20_a[A_REGS_COUNT]; + s8 scale_200_a[A_REGS_COUNT]; + u8 curr_scale; +}; + +static const struct iio_chan_spec si7210_channels[] = { + { + .type = IIO_MAGN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + }, { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static int si7210_fetch_measurement(struct si7210_data *data, + struct iio_chan_spec const *chan, + u16 *buf) +{ + u8 dspsigsel = chan->type == IIO_MAGN ? 0 : 1; + int ret; + __be16 result; + + guard(mutex)(&data->fetch_lock); + + ret = regmap_update_bits(data->regmap, SI7210_REG_DSPSIGSEL, + SI7210_MASK_DSPSIGSEL, dspsigsel); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, SI7210_REG_POWER_CTRL, + SI7210_MASK_ONEBURST | SI7210_MASK_STOP, + SI7210_MASK_ONEBURST & ~SI7210_MASK_STOP); + if (ret) + return ret; + + /* + * Read the contents of the + * registers containing the result: DSPSIGM, DSPSIGL + */ + ret = regmap_bulk_read(data->regmap, SI7210_REG_DSPSIGM, + &result, sizeof(result)); + if (ret) + return ret; + + *buf = be16_to_cpu(result); + + return 0; +} + +static int si7210_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct si7210_data *data = iio_priv(indio_dev); + long long temp; + u16 dspsig; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = si7210_fetch_measurement(data, chan, &dspsig); + if (ret) + return ret; + + *val = dspsig & GENMASK(14, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (data->curr_scale == 20) + *val2 = 12500; + else /* data->curr_scale == 200 */ + *val2 = 125000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = -16384; + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + ret = si7210_fetch_measurement(data, chan, &dspsig); + if (ret) + return ret; + + /* temp = 32 * Dspsigm[6:0] + (Dspsigl[7:0] >> 3) */ + temp = FIELD_GET(GENMASK(14, 3), dspsig); + temp = div_s64(-383 * temp * temp, 100) + 160940 * temp - 279800000; + temp *= (1 + (data->temp_gain / 2048)); + temp += (int)(MICRO / 16) * data->temp_offset; + + /* temp -= 0.222 * VDD */ + temp -= 222 * (data->vdd_uV / MILLI); + + *val = div_s64(temp, MILLI); + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int si7210_set_scale(struct si7210_data *data, unsigned int scale) +{ + s8 *a_otp_values; + int ret; + + if (scale == 20) + a_otp_values = data->scale_20_a; + else if (scale == 200) + a_otp_values = data->scale_200_a; + else + return -EINVAL; + + guard(mutex)(&data->fetch_lock); + + /* Write the registers 0xCA - 0xCC */ + ret = regmap_bulk_write(data->regmap, SI7210_REG_A0, a_otp_values, 3); + if (ret) + return ret; + + /* Write the registers 0xCE - 0xD0 */ + ret = regmap_bulk_write(data->regmap, SI7210_REG_A3, &a_otp_values[3], 3); + if (ret) + return ret; + + data->curr_scale = scale; + + return 0; +} + +static int si7210_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct si7210_data *data = iio_priv(indio_dev); + unsigned int scale; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val == 0 && val2 == 12500) + scale = 20; + else if (val == 0 && val2 == 125000) + scale = 200; + else + return -EINVAL; + + return si7210_set_scale(data, scale); + default: + return -EINVAL; + } +} + +static int si7210_read_otpreg_val(struct si7210_data *data, unsigned int otpreg, u8 *val) +{ + int ret; + unsigned int otpdata; + + ret = regmap_write(data->regmap, SI7210_REG_OTP_ADDR, otpreg); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, SI7210_REG_OTP_CTRL, + SI7210_MASK_OTP_READ_EN, SI7210_MASK_OTP_READ_EN); + if (ret) + return ret; + + ret = regmap_read(data->regmap, SI7210_REG_OTP_DATA, &otpdata); + if (ret) + return ret; + + *val = otpdata; + + return 0; +} + +/* + * According to the datasheet, the primary method to wake up a + * device is to send an empty write. However this is not feasible + * using the current API so we use the other method i.e. read a single + * byte. The device should respond with 0xFF. + */ +static int si7210_device_wake(struct si7210_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte(data->client); + if (ret < 0) + return ret; + + if (ret != 0xFF) + return -EIO; + + return 0; +} + +static int si7210_device_init(struct si7210_data *data) +{ + int ret; + unsigned int i; + + ret = si7210_device_wake(data); + if (ret) + return ret; + + fsleep(1000); + + ret = si7210_read_otpreg_val(data, SI7210_OTPREG_TMP_GAIN, &data->temp_gain); + if (ret) + return ret; + + ret = si7210_read_otpreg_val(data, SI7210_OTPREG_TMP_OFF, &data->temp_offset); + if (ret) + return ret; + + for (i = 0; i < A_REGS_COUNT; i++) { + ret = si7210_read_otpreg_val(data, a20_otp_regs[i], &data->scale_20_a[i]); + if (ret) + return ret; + } + + for (i = 0; i < A_REGS_COUNT; i++) { + ret = si7210_read_otpreg_val(data, a200_otp_regs[i], &data->scale_200_a[i]); + if (ret) + return ret; + } + + ret = regmap_update_bits(data->regmap, SI7210_REG_ARAUTOINC, + SI7210_MASK_ARAUTOINC, SI7210_MASK_ARAUTOINC); + if (ret) + return ret; + + return si7210_set_scale(data, 20); +} + +static const struct iio_info si7210_info = { + .read_raw = si7210_read_raw, + .write_raw = si7210_write_raw, +}; + +static int si7210_probe(struct i2c_client *client) +{ + struct si7210_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->client = client; + + ret = devm_mutex_init(&client->dev, &data->fetch_lock); + if (ret) + return ret; + + data->regmap = devm_regmap_init_i2c(client, &si7210_regmap_conf); + if (IS_ERR(data->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(data->regmap), + "failed to register regmap\n"); + + ret = devm_regulator_get_enable_read_voltage(&client->dev, "vdd"); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to get vdd regulator\n"); + data->vdd_uV = ret; + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &si7210_info; + indio_dev->channels = si7210_channels; + indio_dev->num_channels = ARRAY_SIZE(si7210_channels); + + ret = si7210_device_init(data); + if (ret) + return dev_err_probe(&client->dev, ret, + "device initialization failed\n"); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si7210_id[] = { + { .name = "si7210" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si7210_id); + +static const struct of_device_id si7210_dt_ids[] = { + { .compatible = "silabs,si7210" }, + { } +}; +MODULE_DEVICE_TABLE(of, si7210_dt_ids); + +static struct i2c_driver si7210_driver = { + .driver = { + .name = "si7210", + .of_match_table = si7210_dt_ids, + }, + .probe = si7210_probe, + .id_table = si7210_id, +}; +module_i2c_driver(si7210_driver); + +MODULE_AUTHOR("Antoni Pokusinski "); +MODULE_DESCRIPTION("Silicon Labs Si7210 Hall Effect sensor I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/st_magn.h b/drivers/iio/magnetometer/st_magn.h new file mode 100644 index 000000000..89945984d --- /dev/null +++ b/drivers/iio/magnetometer/st_magn.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + * v. 1.0.0 + */ + +#ifndef ST_MAGN_H +#define ST_MAGN_H + +#include +#include + +#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" +#define LSM303C_MAGN_DEV_NAME "lsm303c_magn" + +#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 + */ + +#include +#include +#include +#include +#include + +#include +#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..7644bd046 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_core.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#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, + [2] = LSM303C_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, + [1] = LSM303D_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, +}; + +/* LIS2MDL only supports DRDY on INT1 */ +static const struct st_sensors_platform_data alt_magn_pdata = { + .drdy_int_pin = 1, +}; + +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) { + if (mdata->sensor_settings->drdy_irq.int2.mask) + pdata = (struct st_sensors_platform_data *)&default_magn_pdata; + else + pdata = (struct st_sensors_platform_data *)&alt_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 "); +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..de6601681 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_i2c.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include +#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, + }, + { + .compatible = "st,lsm303c-magn", + .data = LSM303C_MAGN_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_magn_of_match); + +static int st_magn_i2c_probe(struct i2c_client *client) +{ + 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[] = { + { .name = LSM303DLH_MAGN_DEV_NAME }, + { .name = LSM303DLHC_MAGN_DEV_NAME }, + { .name = LSM303DLM_MAGN_DEV_NAME }, + { .name = LIS3MDL_MAGN_DEV_NAME }, + { .name = LSM303AGR_MAGN_DEV_NAME }, + { .name = LIS2MDL_MAGN_DEV_NAME }, + { .name = LSM9DS1_MAGN_DEV_NAME }, + { .name = IIS2MDC_MAGN_DEV_NAME }, + { .name = LSM303C_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 "); +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..af4642b6c --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_spi.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include +#include "st_magn.h" + +/* + * For new single-chip sensors use as compatible string. + * For old single-chip devices keep -magn to maintain + * compatibility + * For multi-chip devices, use -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, + }, + { + .compatible = "st,lsm303c-magn", + .data = LSM303C_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[] = { + { .name = LIS3MDL_MAGN_DEV_NAME }, + { .name = LSM303AGR_MAGN_DEV_NAME }, + { .name = LIS2MDL_MAGN_DEV_NAME }, + { .name = LSM9DS1_MAGN_DEV_NAME }, + { .name = IIS2MDC_MAGN_DEV_NAME }, + { .name = LSM303C_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 "); +MODULE_DESCRIPTION("STMicroelectronics magnetometers spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/magnetometer/tlv493d.c b/drivers/iio/magnetometer/tlv493d.c new file mode 100644 index 000000000..03415cc2c --- /dev/null +++ b/drivers/iio/magnetometer/tlv493d.c @@ -0,0 +1,525 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Infineon TLV493D Low-Power 3D Magnetic Sensor + * + * Copyright (C) 2025 Dixit Parmar + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * TLV493D sensor I2C communication note: + * + * The sensor supports only direct byte-stream write starting from the + * register address 0x0. So for any modification to be made to any write + * registers, it must be written starting from the register address 0x0. + * I2C write operation should not contain the register address in the I2C + * frame, it should contain only raw byte stream for the write registers. + * I2C Frame: |S|SlaveAddr Wr|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp| + * + * Same as the write operation, reading from the sensor registers is also + * performed starting from the register address 0x0 for as many bytes as + * need to be read. + * I2C read operation should not contain the register address in the I2C frame. + * I2C Frame: |S|SlaveAddr Rd|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp| + */ + +#define TLV493D_RD_REG_BX 0x00 +#define TLV493D_RD_REG_BY 0x01 +#define TLV493D_RD_REG_BZ 0x02 +#define TLV493D_RD_REG_TEMP 0x03 +#define TLV493D_RD_REG_BX2 0x04 +#define TLV493D_RD_REG_BZ2 0x05 +#define TLV493D_RD_REG_TEMP2 0x06 +#define TLV493D_RD_REG_RES1 0x07 +#define TLV493D_RD_REG_RES2 0x08 +#define TLV493D_RD_REG_RES3 0x09 +#define TLV493D_RD_REG_MAX 0x0a + +#define TLV493D_WR_REG_MODE1 0x01 +#define TLV493D_WR_REG_MODE2 0x03 +#define TLV493D_WR_REG_MAX 0x04 + +#define TLV493D_BX_MAG_X_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BX2_MAG_X_AXIS_LSB GENMASK(7, 4) +#define TLV493D_BY_MAG_Y_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BX2_MAG_Y_AXIS_LSB GENMASK(3, 0) +#define TLV493D_BZ_MAG_Z_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BZ2_MAG_Z_AXIS_LSB GENMASK(3, 0) +#define TLV493D_TEMP_TEMP_MSB GENMASK(7, 4) +#define TLV493D_TEMP2_TEMP_LSB GENMASK(7, 0) +#define TLV493D_TEMP_CHANNEL GENMASK(1, 0) +#define TLV493D_MODE1_MOD_LOWFAST GENMASK(1, 0) +#define TLV493D_MODE2_LP_PERIOD BIT(6) +#define TLV493D_RD_REG_RES1_WR_MASK GENMASK(4, 3) +#define TLV493D_RD_REG_RES2_WR_MASK GENMASK(7, 0) +#define TLV493D_RD_REG_RES3_WR_MASK GENMASK(4, 0) + +enum tlv493d_channels { + TLV493D_AXIS_X, + TLV493D_AXIS_Y, + TLV493D_AXIS_Z, + TLV493D_TEMPERATURE, +}; + +enum tlv493d_op_mode { + TLV493D_OP_MODE_POWERDOWN, + TLV493D_OP_MODE_FAST, + TLV493D_OP_MODE_LOWPOWER, + TLV493D_OP_MODE_ULTRA_LOWPOWER, + TLV493D_OP_MODE_MASTERCONTROLLED, +}; + +struct tlv493d_data { + struct i2c_client *client; + /* protects from simultaneous sensor access and register readings */ + struct mutex lock; + enum tlv493d_op_mode mode; + u8 wr_regs[TLV493D_WR_REG_MAX]; +}; + +/* + * Different mode has different measurement sampling time, this time is + * used in deriving the sleep and timeout while reading the data from + * sensor in polling. + * Power-down mode: No measurement. + * Fast mode: Freq:3.3 KHz. Measurement time:305 usec. + * Low-power mode: Freq:100 Hz. Measurement time:10 msec. + * Ultra low-power mode: Freq:10 Hz. Measurement time:100 msec. + * Master controlled mode: Freq:3.3 Khz. Measurement time:305 usec. + */ +static const u32 tlv493d_sample_rate_us[] = { + [TLV493D_OP_MODE_POWERDOWN] = 0, + [TLV493D_OP_MODE_FAST] = 305, + [TLV493D_OP_MODE_LOWPOWER] = 10 * USEC_PER_MSEC, + [TLV493D_OP_MODE_ULTRA_LOWPOWER] = 100 * USEC_PER_MSEC, + [TLV493D_OP_MODE_MASTERCONTROLLED] = 305, +}; + +static int tlv493d_write_all_regs(struct tlv493d_data *data) +{ + int ret; + struct device *dev = &data->client->dev; + + ret = i2c_master_send(data->client, data->wr_regs, ARRAY_SIZE(data->wr_regs)); + if (ret < 0) { + dev_err(dev, "i2c write registers failed, error: %d\n", ret); + return ret; + } + + return 0; +} + +static int tlv493d_set_operating_mode(struct tlv493d_data *data, enum tlv493d_op_mode mode) +{ + u8 *mode1_cfg = &data->wr_regs[TLV493D_WR_REG_MODE1]; + u8 *mode2_cfg = &data->wr_regs[TLV493D_WR_REG_MODE2]; + + switch (mode) { + case TLV493D_OP_MODE_POWERDOWN: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 0); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_FAST: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 1); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_LOWPOWER: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 1); + break; + + case TLV493D_OP_MODE_ULTRA_LOWPOWER: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_MASTERCONTROLLED: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 3); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + } + + return tlv493d_write_all_regs(data); +} + +static s16 tlv493d_get_channel_data(u8 *b, enum tlv493d_channels ch) +{ + u16 val; + + switch (ch) { + case TLV493D_AXIS_X: + val = FIELD_GET(TLV493D_BX_MAG_X_AXIS_MSB, b[TLV493D_RD_REG_BX]) << 4 | + FIELD_GET(TLV493D_BX2_MAG_X_AXIS_LSB, b[TLV493D_RD_REG_BX2]); + break; + case TLV493D_AXIS_Y: + val = FIELD_GET(TLV493D_BY_MAG_Y_AXIS_MSB, b[TLV493D_RD_REG_BY]) << 4 | + FIELD_GET(TLV493D_BX2_MAG_Y_AXIS_LSB, b[TLV493D_RD_REG_BX2]); + break; + case TLV493D_AXIS_Z: + val = FIELD_GET(TLV493D_BZ_MAG_Z_AXIS_MSB, b[TLV493D_RD_REG_BZ]) << 4 | + FIELD_GET(TLV493D_BZ2_MAG_Z_AXIS_LSB, b[TLV493D_RD_REG_BZ2]); + break; + case TLV493D_TEMPERATURE: + val = FIELD_GET(TLV493D_TEMP_TEMP_MSB, b[TLV493D_RD_REG_TEMP]) << 8 | + FIELD_GET(TLV493D_TEMP2_TEMP_LSB, b[TLV493D_RD_REG_TEMP2]); + break; + } + + return sign_extend32(val, 11); +} + +static int tlv493d_get_measurements(struct tlv493d_data *data, s16 *x, s16 *y, + s16 *z, s16 *t) +{ + u8 buff[7] = {}; + int err, ret; + struct device *dev = &data->client->dev; + u32 sleep_us = tlv493d_sample_rate_us[data->mode]; + + guard(mutex)(&data->lock); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* + * Poll until data is valid. + * For a valid data TLV493D_TEMP_CHANNEL bit of TLV493D_RD_REG_TEMP + * should be set to 0. The sampling time depends on the sensor mode. + * Poll 3x the time of the sampling time. + */ + ret = read_poll_timeout(i2c_master_recv, err, + err || !FIELD_GET(TLV493D_TEMP_CHANNEL, buff[TLV493D_RD_REG_TEMP]), + sleep_us, 3 * sleep_us, false, data->client, buff, + ARRAY_SIZE(buff)); + if (ret) { + dev_err(dev, "i2c read poll timeout, error:%d\n", ret); + goto out_put_autosuspend; + } + if (err < 0) { + dev_err(dev, "i2c read data failed, error:%d\n", err); + ret = err; + goto out_put_autosuspend; + } + + *x = tlv493d_get_channel_data(buff, TLV493D_AXIS_X); + *y = tlv493d_get_channel_data(buff, TLV493D_AXIS_Y); + *z = tlv493d_get_channel_data(buff, TLV493D_AXIS_Z); + *t = tlv493d_get_channel_data(buff, TLV493D_TEMPERATURE); + +out_put_autosuspend: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int tlv493d_init(struct tlv493d_data *data) +{ + int ret; + u8 buff[TLV493D_RD_REG_MAX]; + struct device *dev = &data->client->dev; + + /* + * The sensor initialization requires below steps to be followed, + * 1. Power-up sensor. + * 2. Read and store read-registers map (0x0-0x9). + * 3. Copy values from read reserved registers to write reserved fields + * (0x0-0x3). + * 4. Set operating mode. + * 5. Write to all registers. + */ + ret = i2c_master_recv(data->client, buff, ARRAY_SIZE(buff)); + if (ret < 0) + return dev_err_probe(dev, ret, "i2c read failed\n"); + + /* Write register 0x0 is reserved. Does not require to be updated.*/ + data->wr_regs[0] = 0; + data->wr_regs[1] = buff[TLV493D_RD_REG_RES1] & TLV493D_RD_REG_RES1_WR_MASK; + data->wr_regs[2] = buff[TLV493D_RD_REG_RES2] & TLV493D_RD_REG_RES2_WR_MASK; + data->wr_regs[3] = buff[TLV493D_RD_REG_RES3] & TLV493D_RD_REG_RES3_WR_MASK; + + ret = tlv493d_set_operating_mode(data, data->mode); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set operating mode\n"); + + return 0; +} + +static int tlv493d_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct tlv493d_data *data = iio_priv(indio_dev); + s16 x, y, z, t; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = tlv493d_get_measurements(data, &x, &y, &z, &t); + if (ret) + return ret; + + switch (chan->address) { + case TLV493D_AXIS_X: + *val = x; + return IIO_VAL_INT; + case TLV493D_AXIS_Y: + *val = y; + return IIO_VAL_INT; + case TLV493D_AXIS_Z: + *val = z; + return IIO_VAL_INT; + case TLV493D_TEMPERATURE: + *val = t; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + /* + * Magnetic field scale: 0.0098 mTesla (i.e. 9.8 µT) + * Magnetic field in Gauss: mT * 10 = 0.098. + */ + *val = 98; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + /* + * Temperature scale: 1.1 °C per LSB, expressed as 1100 m°C + * Returned as integer for IIO core to apply: + * temp = (raw + offset) * scale + */ + *val = 1100; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* + * Temperature offset includes sensor-specific raw offset + * plus compensation for +25°C bias in formula. + * offset = -raw_offset + (25000 / 1100) + * -340 + 22.72 = -317.28 + */ + *val = -31728; + *val2 = 100; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t tlv493d_trigger_handler(int irq, void *ptr) +{ + int ret; + s16 x, y, z, t; + struct iio_poll_func *pf = ptr; + struct iio_dev *indio_dev = pf->indio_dev; + struct tlv493d_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + struct { + s16 channels[3]; + s16 temperature; + aligned_s64 timestamp; + } scan; + + ret = tlv493d_get_measurements(data, &x, &y, &z, &t); + if (ret) { + dev_err(dev, "failed to read sensor data\n"); + goto out_trigger_notify; + } + + scan.channels[0] = x; + scan.channels[1] = y; + scan.channels[2] = z; + scan.temperature = t; + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); + +out_trigger_notify: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define TLV493D_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), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec tlv493d_channels[] = { + TLV493D_AXIS_CHANNEL(X, TLV493D_AXIS_X), + TLV493D_AXIS_CHANNEL(Y, TLV493D_AXIS_Y), + TLV493D_AXIS_CHANNEL(Z, TLV493D_AXIS_Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = TLV493D_TEMPERATURE, + .scan_index = TLV493D_TEMPERATURE, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_info tlv493d_info = { + .read_raw = tlv493d_read_raw, +}; + +static const unsigned long tlv493d_scan_masks[] = { GENMASK(3, 0), 0 }; + +static int tlv493d_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct tlv493d_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + /* + * Setting Sensor default operating mode to Master-Controlled mode since + * it performs measurement cycle only on-request and stays in Power-Down + * state until next cycle is initiated. + */ + data->mode = TLV493D_OP_MODE_MASTERCONTROLLED; + ret = tlv493d_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize\n"); + + indio_dev->info = &tlv493d_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = client->name; + indio_dev->channels = tlv493d_channels; + indio_dev->num_channels = ARRAY_SIZE(tlv493d_channels); + indio_dev->available_scan_masks = tlv493d_scan_masks; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + tlv493d_trigger_handler, + NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, 500); + pm_runtime_use_autosuspend(dev); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "iio device register failed\n"); + + return 0; +} + +static int tlv493d_runtime_suspend(struct device *dev) +{ + struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev)); + + return tlv493d_set_operating_mode(data, TLV493D_OP_MODE_POWERDOWN); +} + +static int tlv493d_runtime_resume(struct device *dev) +{ + struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev)); + + return tlv493d_set_operating_mode(data, data->mode); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tlv493d_pm_ops, tlv493d_runtime_suspend, + tlv493d_runtime_resume, NULL); + +static const struct i2c_device_id tlv493d_id[] = { + { .name = "tlv493d" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tlv493d_id); + +static const struct of_device_id tlv493d_of_match[] = { + { .compatible = "infineon,tlv493d-a1b6" }, + { } +}; +MODULE_DEVICE_TABLE(of, tlv493d_of_match); + +static struct i2c_driver tlv493d_driver = { + .driver = { + .name = "tlv493d", + .of_match_table = tlv493d_of_match, + .pm = pm_ptr(&tlv493d_pm_ops), + }, + .probe = tlv493d_probe, + .id_table = tlv493d_id, +}; +module_i2c_driver(tlv493d_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Infineon TLV493D Low-Power 3D Magnetic Sensor"); +MODULE_AUTHOR("Dixit Parmar "); diff --git a/drivers/iio/magnetometer/tmag5273.c b/drivers/iio/magnetometer/tmag5273.c new file mode 100644 index 000000000..155294b66 --- /dev/null +++ b/drivers/iio/magnetometer/tmag5273.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor + * + * Copyright (C) 2022 WolfVision GmbH + * + * Author: Gerald Loacker + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TMAG5273_DEVICE_CONFIG_1 0x00 +#define TMAG5273_DEVICE_CONFIG_2 0x01 +#define TMAG5273_SENSOR_CONFIG_1 0x02 +#define TMAG5273_SENSOR_CONFIG_2 0x03 +#define TMAG5273_X_THR_CONFIG 0x04 +#define TMAG5273_Y_THR_CONFIG 0x05 +#define TMAG5273_Z_THR_CONFIG 0x06 +#define TMAG5273_T_CONFIG 0x07 +#define TMAG5273_INT_CONFIG_1 0x08 +#define TMAG5273_MAG_GAIN_CONFIG 0x09 +#define TMAG5273_MAG_OFFSET_CONFIG_1 0x0A +#define TMAG5273_MAG_OFFSET_CONFIG_2 0x0B +#define TMAG5273_I2C_ADDRESS 0x0C +#define TMAG5273_DEVICE_ID 0x0D +#define TMAG5273_MANUFACTURER_ID_LSB 0x0E +#define TMAG5273_MANUFACTURER_ID_MSB 0x0F +#define TMAG5273_T_MSB_RESULT 0x10 +#define TMAG5273_T_LSB_RESULT 0x11 +#define TMAG5273_X_MSB_RESULT 0x12 +#define TMAG5273_X_LSB_RESULT 0x13 +#define TMAG5273_Y_MSB_RESULT 0x14 +#define TMAG5273_Y_LSB_RESULT 0x15 +#define TMAG5273_Z_MSB_RESULT 0x16 +#define TMAG5273_Z_LSB_RESULT 0x17 +#define TMAG5273_CONV_STATUS 0x18 +#define TMAG5273_ANGLE_RESULT_MSB 0x19 +#define TMAG5273_ANGLE_RESULT_LSB 0x1A +#define TMAG5273_MAGNITUDE_RESULT 0x1B +#define TMAG5273_DEVICE_STATUS 0x1C +#define TMAG5273_MAX_REG TMAG5273_DEVICE_STATUS + +#define TMAG5273_AUTOSLEEP_DELAY_MS 5000 +#define TMAG5273_MAX_AVERAGE 32 + +/* + * bits in the TMAG5273_MANUFACTURER_ID_LSB / MSB register + * 16-bit unique manufacturer ID 0x49 / 0x54 = "TI" + */ +#define TMAG5273_MANUFACTURER_ID 0x5449 + +/* bits in the TMAG5273_DEVICE_CONFIG_1 register */ +#define TMAG5273_AVG_MODE_MASK GENMASK(4, 2) +#define TMAG5273_AVG_1_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 0) +#define TMAG5273_AVG_2_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 1) +#define TMAG5273_AVG_4_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 2) +#define TMAG5273_AVG_8_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 3) +#define TMAG5273_AVG_16_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 4) +#define TMAG5273_AVG_32_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 5) + +/* bits in the TMAG5273_DEVICE_CONFIG_2 register */ +#define TMAG5273_OP_MODE_MASK GENMASK(1, 0) +#define TMAG5273_OP_MODE_STANDBY FIELD_PREP(TMAG5273_OP_MODE_MASK, 0) +#define TMAG5273_OP_MODE_SLEEP FIELD_PREP(TMAG5273_OP_MODE_MASK, 1) +#define TMAG5273_OP_MODE_CONT FIELD_PREP(TMAG5273_OP_MODE_MASK, 2) +#define TMAG5273_OP_MODE_WAKEUP FIELD_PREP(TMAG5273_OP_MODE_MASK, 3) + +/* bits in the TMAG5273_SENSOR_CONFIG_1 register */ +#define TMAG5273_MAG_CH_EN_MASK GENMASK(7, 4) +#define TMAG5273_MAG_CH_EN_X_Y_Z 7 + +/* bits in the TMAG5273_SENSOR_CONFIG_2 register */ +#define TMAG5273_Z_RANGE_MASK BIT(0) +#define TMAG5273_X_Y_RANGE_MASK BIT(1) +#define TMAG5273_ANGLE_EN_MASK GENMASK(3, 2) +#define TMAG5273_ANGLE_EN_OFF 0 +#define TMAG5273_ANGLE_EN_X_Y 1 +#define TMAG5273_ANGLE_EN_Y_Z 2 +#define TMAG5273_ANGLE_EN_X_Z 3 + +/* bits in the TMAG5273_T_CONFIG register */ +#define TMAG5273_T_CH_EN BIT(0) + +/* bits in the TMAG5273_DEVICE_ID register */ +#define TMAG5273_VERSION_MASK GENMASK(1, 0) + +/* bits in the TMAG5273_CONV_STATUS register */ +#define TMAG5273_CONV_STATUS_COMPLETE BIT(0) + +enum tmag5273_channels { + TEMPERATURE = 0, + AXIS_X, + AXIS_Y, + AXIS_Z, + ANGLE, + MAGNITUDE, +}; + +enum tmag5273_scale_index { + MAGN_RANGE_LOW = 0, + MAGN_RANGE_HIGH, + MAGN_RANGE_NUM +}; + +/* state container for the TMAG5273 driver */ +struct tmag5273_data { + struct device *dev; + unsigned int devid; + unsigned int version; + char name[16]; + unsigned int conv_avg; + enum tmag5273_scale_index scale_index; + unsigned int angle_measurement; + struct regmap *map; + + /* + * Locks the sensor for exclusive use during a measurement (which + * involves several register transactions so the regmap lock is not + * enough) so that measurements get serialized in a + * first-come-first-serve manner. + */ + struct mutex lock; +}; + +static const char *const tmag5273_angle_names[] = { "off", "x-y", "y-z", "x-z" }; + +/* + * Averaging enables additional sampling of the sensor data to reduce the noise + * effect, but also increases conversion time. + */ +static const unsigned int tmag5273_avg_table[] = { + 1, 2, 4, 8, 16, 32, +}; + +/* + * Magnetic resolution in Gauss for different TMAG5273 versions. + * Scale[Gauss] = Range[mT] * 1000 / 2^15 * 10, (1 mT = 10 Gauss) + * Only version 1 and 2 are valid, version 0 and 3 are reserved. + */ +static const struct iio_val_int_plus_micro tmag5273_scale[][MAGN_RANGE_NUM] = { + { { 0, 0 }, { 0, 0 } }, + { { 0, 12200 }, { 0, 24400 } }, + { { 0, 40600 }, { 0, 81200 } }, + { { 0, 0 }, { 0, 0 } }, +}; + +static int tmag5273_get_measure(struct tmag5273_data *data, s16 *t, s16 *x, + s16 *y, s16 *z, u16 *angle, u16 *magnitude) +{ + unsigned int status, val; + __be16 reg_data[4]; + int ret; + + mutex_lock(&data->lock); + + /* + * Max. conversion time is 2425 us in 32x averaging mode for all three + * channels. Since we are in continuous measurement mode, a measurement + * may already be there, so poll for completed measurement with + * timeout. + */ + ret = regmap_read_poll_timeout(data->map, TMAG5273_CONV_STATUS, status, + status & TMAG5273_CONV_STATUS_COMPLETE, + 100, 10000); + if (ret) { + dev_err(data->dev, "timeout waiting for measurement\n"); + goto out_unlock; + } + + ret = regmap_bulk_read(data->map, TMAG5273_T_MSB_RESULT, reg_data, + sizeof(reg_data)); + if (ret) + goto out_unlock; + *t = be16_to_cpu(reg_data[0]); + *x = be16_to_cpu(reg_data[1]); + *y = be16_to_cpu(reg_data[2]); + *z = be16_to_cpu(reg_data[3]); + + ret = regmap_bulk_read(data->map, TMAG5273_ANGLE_RESULT_MSB, + ®_data[0], sizeof(reg_data[0])); + if (ret) + goto out_unlock; + /* + * angle has 9 bits integer value and 4 bits fractional part + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * 0 0 0 a a a a a a a a a f f f f + */ + *angle = be16_to_cpu(reg_data[0]); + + ret = regmap_read(data->map, TMAG5273_MAGNITUDE_RESULT, &val); + if (ret < 0) + goto out_unlock; + *magnitude = val; + +out_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int tmag5273_write_osr(struct tmag5273_data *data, int val) +{ + int i; + + if (val == data->conv_avg) + return 0; + + for (i = 0; i < ARRAY_SIZE(tmag5273_avg_table); i++) { + if (tmag5273_avg_table[i] == val) + break; + } + if (i == ARRAY_SIZE(tmag5273_avg_table)) + return -EINVAL; + data->conv_avg = val; + + return regmap_update_bits(data->map, TMAG5273_DEVICE_CONFIG_1, + TMAG5273_AVG_MODE_MASK, + FIELD_PREP(TMAG5273_AVG_MODE_MASK, i)); +} + +static int tmag5273_write_scale(struct tmag5273_data *data, int scale_micro) +{ + u32 value; + int i; + + for (i = 0; i < ARRAY_SIZE(tmag5273_scale[0]); i++) { + if (tmag5273_scale[data->version][i].micro == scale_micro) + break; + } + if (i == ARRAY_SIZE(tmag5273_scale[0])) + return -EINVAL; + data->scale_index = i; + + if (data->scale_index == MAGN_RANGE_LOW) + value = 0; + else + value = TMAG5273_Z_RANGE_MASK | TMAG5273_X_Y_RANGE_MASK; + + return regmap_update_bits(data->map, TMAG5273_SENSOR_CONFIG_2, + TMAG5273_Z_RANGE_MASK | TMAG5273_X_Y_RANGE_MASK, value); +} + +static int tmag5273_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct tmag5273_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = tmag5273_avg_table; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(tmag5273_avg_table); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = (int *)tmag5273_scale[data->version]; + *length = ARRAY_SIZE(tmag5273_scale[data->version]) * + MAGN_RANGE_NUM; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int tmag5273_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct tmag5273_data *data = iio_priv(indio_dev); + s16 t, x, y, z; + u16 angle, magnitude; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = tmag5273_get_measure(data, &t, &x, &y, &z, &angle, &magnitude); + + pm_runtime_put_autosuspend(data->dev); + + if (ret) + return ret; + + switch (chan->address) { + case TEMPERATURE: + *val = t; + return IIO_VAL_INT; + case AXIS_X: + *val = x; + return IIO_VAL_INT; + case AXIS_Y: + *val = y; + return IIO_VAL_INT; + case AXIS_Z: + *val = z; + return IIO_VAL_INT; + case ANGLE: + *val = angle; + return IIO_VAL_INT; + case MAGNITUDE: + *val = magnitude; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* + * Convert device specific value to millicelsius. + * Resolution from the sensor is 60.1 LSB/celsius and + * the reference value at 25 celsius is 17508 LSBs. + */ + *val = 10000; + *val2 = 601; + return IIO_VAL_FRACTIONAL; + case IIO_MAGN: + /* Magnetic resolution in uT */ + *val = 0; + *val2 = tmag5273_scale[data->version] + [data->scale_index].micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ANGL: + /* + * Angle is in degrees and has four fractional bits, + * therefore use 1/16 * pi/180 to convert to radians. + */ + *val = 1000; + *val2 = 916732; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = -16005; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = data->conv_avg; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int tmag5273_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct tmag5273_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return tmag5273_write_osr(data, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + if (val) + return -EINVAL; + return tmag5273_write_scale(data, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +#define TMAG5273_AXIS_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec tmag5273_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = TEMPERATURE, + .scan_index = TEMPERATURE, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + TMAG5273_AXIS_CHANNEL(X, AXIS_X), + TMAG5273_AXIS_CHANNEL(Y, AXIS_Y), + TMAG5273_AXIS_CHANNEL(Z, AXIS_Z), + { + .type = IIO_ANGL, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .address = ANGLE, + .scan_index = ANGLE, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_DISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .address = MAGNITUDE, + .scan_index = MAGNITUDE, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(6), +}; + +static const struct iio_info tmag5273_info = { + .read_avail = tmag5273_read_avail, + .read_raw = tmag5273_read_raw, + .write_raw = tmag5273_write_raw, +}; + +static bool tmag5273_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg >= TMAG5273_T_MSB_RESULT && reg <= TMAG5273_MAGNITUDE_RESULT; +} + +static const struct regmap_config tmag5273_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TMAG5273_MAX_REG, + .volatile_reg = tmag5273_volatile_reg, +}; + +static int tmag5273_set_operating_mode(struct tmag5273_data *data, + unsigned int val) +{ + return regmap_write(data->map, TMAG5273_DEVICE_CONFIG_2, val); +} + +static void tmag5273_read_device_property(struct tmag5273_data *data) +{ + struct device *dev = data->dev; + int ret; + + data->angle_measurement = TMAG5273_ANGLE_EN_X_Y; + + ret = device_property_match_property_string(dev, "ti,angle-measurement", + tmag5273_angle_names, + ARRAY_SIZE(tmag5273_angle_names)); + if (ret >= 0) + data->angle_measurement = ret; +} + +static void tmag5273_wake_up(struct tmag5273_data *data) +{ + int val; + + /* Wake up the chip by sending a dummy I2C command */ + regmap_read(data->map, TMAG5273_DEVICE_ID, &val); + /* + * Time to go to stand-by mode from sleep mode is 50us + * typically, during this time no I2C access is possible. + */ + usleep_range(80, 200); +} + +static int tmag5273_chip_init(struct tmag5273_data *data) +{ + int ret; + + ret = regmap_write(data->map, TMAG5273_DEVICE_CONFIG_1, + TMAG5273_AVG_32_MODE); + if (ret) + return ret; + data->conv_avg = 32; + + ret = regmap_write(data->map, TMAG5273_DEVICE_CONFIG_2, + TMAG5273_OP_MODE_CONT); + if (ret) + return ret; + + ret = regmap_write(data->map, TMAG5273_SENSOR_CONFIG_1, + FIELD_PREP(TMAG5273_MAG_CH_EN_MASK, + TMAG5273_MAG_CH_EN_X_Y_Z)); + if (ret) + return ret; + + ret = regmap_write(data->map, TMAG5273_SENSOR_CONFIG_2, + FIELD_PREP(TMAG5273_ANGLE_EN_MASK, + data->angle_measurement)); + if (ret) + return ret; + data->scale_index = MAGN_RANGE_LOW; + + return regmap_write(data->map, TMAG5273_T_CONFIG, TMAG5273_T_CH_EN); +} + +static int tmag5273_check_device_id(struct tmag5273_data *data) +{ + __le16 devid; + int val, ret; + + ret = regmap_read(data->map, TMAG5273_DEVICE_ID, &val); + if (ret) + return dev_err_probe(data->dev, ret, "failed to power on device\n"); + data->version = FIELD_PREP(TMAG5273_VERSION_MASK, val); + + ret = regmap_bulk_read(data->map, TMAG5273_MANUFACTURER_ID_LSB, &devid, + sizeof(devid)); + if (ret) + return dev_err_probe(data->dev, ret, "failed to read device ID\n"); + data->devid = le16_to_cpu(devid); + + switch (data->devid) { + case TMAG5273_MANUFACTURER_ID: + /* + * The device name matches the orderable part number. 'x' stands + * for A, B, C or D devices, which have different I2C addresses. + * Versions 1 or 2 (0 and 3 is reserved) stands for different + * magnetic strengths. + */ + snprintf(data->name, sizeof(data->name), "tmag5273x%1u", data->version); + if (data->version < 1 || data->version > 2) + dev_warn(data->dev, "Unsupported device %s\n", data->name); + return 0; + default: + /* + * Only print warning in case of unknown device ID to allow + * fallback compatible in device tree. + */ + dev_warn(data->dev, "Unknown device ID 0x%x\n", data->devid); + return 0; + } +} + +static void tmag5273_power_down(void *data) +{ + tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_SLEEP); +} + +static int tmag5273_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct tmag5273_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + i2c_set_clientdata(i2c, indio_dev); + + data->map = devm_regmap_init_i2c(i2c, &tmag5273_regmap_config); + if (IS_ERR(data->map)) + return dev_err_probe(dev, PTR_ERR(data->map), + "failed to allocate register map\n"); + + mutex_init(&data->lock); + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + tmag5273_wake_up(data); + + ret = tmag5273_check_device_id(data); + if (ret) + return ret; + + ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_CONT); + if (ret) + return dev_err_probe(dev, ret, "failed to power on device\n"); + + /* + * Register powerdown deferred callback which suspends the chip + * after module unloaded. + * + * TMAG5273 should be in SUSPEND mode in the two cases: + * 1) When driver is loaded, but we do not have any data or + * configuration requests to it (we are solving it using + * autosuspend feature). + * 2) When driver is unloaded and device is not used (devm action is + * used in this case). + */ + ret = devm_add_action_or_reset(dev, tmag5273_power_down, data); + if (ret) + return ret; + + ret = pm_runtime_set_active(dev); + if (ret < 0) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, TMAG5273_AUTOSLEEP_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + tmag5273_read_device_property(data); + + ret = tmag5273_chip_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init device\n"); + + indio_dev->info = &tmag5273_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = data->name; + indio_dev->channels = tmag5273_channels; + indio_dev->num_channels = ARRAY_SIZE(tmag5273_channels); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "device register failed\n"); + + return 0; +} + +static int tmag5273_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tmag5273_data *data = iio_priv(indio_dev); + int ret; + + ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_SLEEP); + if (ret) + dev_err(dev, "failed to power off device (%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static int tmag5273_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tmag5273_data *data = iio_priv(indio_dev); + int ret; + + tmag5273_wake_up(data); + + ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_CONT); + if (ret) + dev_err(dev, "failed to power on device (%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tmag5273_pm_ops, + tmag5273_runtime_suspend, tmag5273_runtime_resume, + NULL); + +static const struct i2c_device_id tmag5273_id[] = { + { .name = "tmag5273" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmag5273_id); + +static const struct of_device_id tmag5273_of_match[] = { + { .compatible = "ti,tmag5273" }, + { } +}; +MODULE_DEVICE_TABLE(of, tmag5273_of_match); + +static struct i2c_driver tmag5273_driver = { + .driver = { + .name = "tmag5273", + .of_match_table = tmag5273_of_match, + .pm = pm_ptr(&tmag5273_pm_ops), + }, + .probe = tmag5273_probe, + .id_table = tmag5273_id, +}; +module_i2c_driver(tmag5273_driver); + +MODULE_DESCRIPTION("TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor driver"); +MODULE_AUTHOR("Gerald Loacker "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/yamaha-yas530.c b/drivers/iio/magnetometer/yamaha-yas530.c new file mode 100644 index 000000000..f9afe9a59 --- /dev/null +++ b/drivers/iio/magnetometer/yamaha-yas530.c @@ -0,0 +1,1620 @@ +// 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/* 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_label: product label used in Linux + * @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_label; + 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]; + aligned_s64 ts; + } 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 half_range = BIT(13); + 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] - half_range; + h[0] = (c->k * (128 * s[0] + c->a2 * s[1] + c->a3 * s[2])) / half_range; + h[1] = (c->k * (c->a4 * s[0] + c->a5 * s[1] + c->a6 * s[2])) / half_range; + h[2] = (c->k * (c->a7 * s[0] + c->a8 * s[1] + c->a9 * s[2])) / half_range; + for (i = 0; i < 3; i++) { + h[i] = clamp(h[i], -half_range, half_range - 1); + xy1y2[i] = h[i] + half_range; + } + } + + *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_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_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_ts(indio_dev, &yas5xx->scan, sizeof(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] = FIELD_GET_SIGNED(GENMASK(28, 23), val); + c->r[1] = FIELD_GET_SIGNED(GENMASK(20, 15), val); + c->r[2] = FIELD_GET_SIGNED(GENMASK(12, 7), val); + + 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] = FIELD_GET_SIGNED(GENMASK(30, 25), val); + c->r[1] = FIELD_GET_SIGNED(GENMASK(22, 17), val); + c->r[2] = FIELD_GET_SIGNED(GENMASK(14, 7), val); + + 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 + * roughly 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 */ + fsleep(YAS537_MAG_RCOIL_TIME_US); + + return 0; +} + +static const struct yas5xx_chip_info yas5xx_chip_info_tbl[] = { + [yas530] = { + .devid = YAS530_DEVICE_ID, + .product_label = "yas530", + .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_label = "yas532", + .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_label = "yas533", + .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_label = "yas537", + .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) +{ + 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; + + ret = devm_mutex_init(dev, &yas5xx->lock); + if (ret) + return ret; + + 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 */ + fsleep(31 * USEC_PER_MSEC); + + /* 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 = i2c_get_match_data(i2c); + 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, ci->product_label); + 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 = ci->product_label; + 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. + */ + fsleep(31 * USEC_PER_MSEC); + 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[] = { + { .name = "yas530", .driver_data = (kernel_ulong_t)&yas5xx_chip_info_tbl[yas530] }, + { .name = "yas532", .driver_data = (kernel_ulong_t)&yas5xx_chip_info_tbl[yas532] }, + { .name = "yas533", .driver_data = (kernel_ulong_t)&yas5xx_chip_info_tbl[yas533] }, + { .name = "yas537", .driver_data = (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"); -- cgit v1.3.1