mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Viral Patel <viral8854@gmail.com>
To: "Jonathan Cameron" <jic23@kernel.org>,
	"David Lechner" <dlechner@baylibre.com>,
	"Nuno Sá" <nuno.sa@analog.com>,
	"Andy Shevchenko" <andy@kernel.org>,
	"Rob Herring" <robh@kernel.org>,
	"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	 linux-kernel@vger.kernel.org, Viral Patel <viral8854@gmail.com>
Subject: [PATCH 2/2] iio: magnetometer: add Infineon TLE493D-W2B6 driver
Date: Thu, 05 Nov 2026 21:23:48 +0530	[thread overview]
Message-ID: <20261105-tle493d-v1-2-3246e91e6394@gmail.com> (raw)
In-Reply-To: <20261105-tle493d-v1-0-3246e91e6394@gmail.com>

Add a driver for the Infineon TLE493D-W2B6, a 3D Hall effect magnetic
sensor with 12-bit resolution per axis and an integrated temperature
sensor, accessed over I2C.

The sensor is run in master-controlled mode. Each measurement rewrites
the Config register with the "trigger after write" bits set in the
register address byte, which starts a single conversion, and then polls
registers 0x00 to 0x06 until Diag reports a completed conversion with a
new frame counter. Results with a fuse parity error, test mode flag or
bad bus parity are rejected. FP and CP are recomputed on every write of
MOD1 and Config, as a wrong FP makes the sensor release the bus until it
is reset.

The /INT output shares a pin with SCL, so the interrupt is disabled and
so is clock stretching. The sensor's reset sequence relies on
zero-length messages which many I2C adapters reject, so it is sent on a
best-effort basis during probe.

Raw I2C transfers are used rather than regmap: once PR is set a plain
read always starts at register 0x00, and the register address byte of a
write carries trigger bits.

The X, Y and Z channels and the temperature channel are available
through sysfs and through a triggered buffer with a software timestamp.
Wake-up, self-test, low-power and fast modes, sampling frequency control
and the short range are not supported yet.

This is not added to the tlv493d driver: apart from the X/Y/Z bit
packing, the two parts differ in write format, read protocol after
reset, register map and parity scheme.

Based on the TLE493D-W2B6 User Manual v1.11 and Datasheet v1.2.

Datasheet: https://www.infineon.com/assets/row/public/documents/30/49/infineon-tle493d-w2b6-datasheet-en.pdf
Signed-off-by: Viral Patel <viral8854@gmail.com>
---
 MAINTAINERS                        |   1 +
 drivers/iio/magnetometer/Kconfig   |  13 +
 drivers/iio/magnetometer/Makefile  |   1 +
 drivers/iio/magnetometer/tle493d.c | 552 +++++++++++++++++++++++++++++++++++++
 4 files changed, 567 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c8916e1286d3..5282c0039abb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13115,6 +13115,7 @@ M:	Viral Patel <viral8854@gmail.com>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/iio/magnetometer/infineon,tle493d-w2b6.yaml
+F:	drivers/iio/magnetometer/tle493d.c
 
 INFINEON TLV493D Driver
 M:	Dixit Parmar <dixitparmar19@gmail.com>
diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig
index d359003b248e..bc26fc4d78e2 100644
--- a/drivers/iio/magnetometer/Kconfig
+++ b/drivers/iio/magnetometer/Kconfig
@@ -196,6 +196,19 @@ config IIO_ST_MAGN_SPI_3AXIS
 	  To compile this driver as a module, choose M here. The module
 	  will be called st_magn_spi.
 
+config INFINEON_TLE493D
+	tristate "Infineon TLE493D-W2B6 3D Magnetic Sensor"
+	depends on I2C
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  Say Y here to add support for the Infineon TLE493D-W2B6 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 tle493d.
+
 config INFINEON_TLV493D
 	tristate "Infineon TLV493D Low-Power 3D Magnetic Sensor"
 	depends on I2C
diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile
index 8f2205975c1b..31df6a49459c 100644
--- a/drivers/iio/magnetometer/Makefile
+++ b/drivers/iio/magnetometer/Makefile
@@ -25,6 +25,7 @@ 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_TLE493D)		+= tle493d.o
 obj-$(CONFIG_INFINEON_TLV493D)		+= tlv493d.o
 
 obj-$(CONFIG_QMC5883L)			+= qmc5883l.o
diff --git a/drivers/iio/magnetometer/tle493d.c b/drivers/iio/magnetometer/tle493d.c
new file mode 100644
index 000000000000..232f7680a02b
--- /dev/null
+++ b/drivers/iio/magnetometer/tle493d.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Infineon TLE493D-W2B6 3D Magnetic Sensor
+ *
+ * Copyright (C) 2026 Viral Patel <viral8854@gmail.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/irqreturn.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+/*
+ * The sensor is accessed with raw I2C transfers instead of regmap: once PR
+ * is set, a plain read always starts at register 0x00, and the register
+ * address byte of a write also carries trigger bits that start an ADC
+ * conversion. Neither fits the regmap I2C model.
+ */
+
+#define TLE493D_REG_BX		0x00
+#define TLE493D_REG_BY		0x01
+#define TLE493D_REG_BZ		0x02
+#define TLE493D_REG_TEMP	0x03
+#define TLE493D_REG_BX2		0x04
+#define TLE493D_REG_TEMP2	0x05
+#define TLE493D_REG_DIAG	0x06
+#define TLE493D_REG_XL		0x07
+#define TLE493D_REG_WU		0x0d
+#define TLE493D_REG_TMODE	0x0e
+#define TLE493D_REG_TPHASE	0x0f
+#define TLE493D_REG_CONFIG	0x10
+#define TLE493D_REG_MOD1	0x11
+#define TLE493D_REG_MOD2	0x13
+#define TLE493D_REG_VER		0x16
+#define TLE493D_NUM_REGS	0x17
+
+/* Measurement result and Diag, registers 0x00 to 0x06 */
+#define TLE493D_DATA_LEN	7
+
+#define TLE493D_BX2_X_LSB	GENMASK(7, 4)
+#define TLE493D_BX2_Y_LSB	GENMASK(3, 0)
+#define TLE493D_TEMP2_TEMP_LSB	GENMASK(7, 6)
+#define TLE493D_TEMP2_ID	GENMASK(5, 4)
+#define TLE493D_TEMP2_Z_LSB	GENMASK(3, 0)
+
+#define TLE493D_DIAG_P		BIT(7)
+#define TLE493D_DIAG_FF		BIT(6)
+#define TLE493D_DIAG_T		BIT(4)
+#define TLE493D_DIAG_PD3	BIT(3)
+#define TLE493D_DIAG_PD0	BIT(2)
+#define TLE493D_DIAG_FRM	GENMASK(1, 0)
+
+#define TLE493D_WU_WA		BIT(7)
+#define TLE493D_TMODE_TST	GENMASK(7, 6)
+#define TLE493D_TPHASE_PH	GENMASK(7, 6)
+
+#define TLE493D_CONFIG_CP	BIT(0)
+
+#define TLE493D_MOD1_FP		BIT(7)
+#define TLE493D_MOD1_IICADR	GENMASK(6, 5)
+#define TLE493D_MOD1_PR		BIT(4)
+#define TLE493D_MOD1_CA		BIT(3)
+#define TLE493D_MOD1_INT	BIT(2)
+#define TLE493D_MOD1_MODE	GENMASK(1, 0)
+#define TLE493D_MOD1_MODE_MASTER	1
+
+#define TLE493D_MOD2_PRD	GENMASK(7, 5)
+
+#define TLE493D_VER_HWV		GENMASK(3, 0)
+#define TLE493D_VER_HWV_B21	9
+
+/* Register address byte of a write frame */
+#define TLE493D_ADDR_TRIG	GENMASK(7, 5)
+#define TLE493D_ADDR_TRIG_WRITE	1
+#define TLE493D_ADDR_REG	GENMASK(4, 0)
+
+#define TLE493D_RESET_DELAY_US	30
+/* A full range conversion takes about 175 us */
+#define TLE493D_POLL_US		100
+#define TLE493D_TIMEOUT_US	1000
+
+enum tle493d_channels {
+	TLE493D_AXIS_X,
+	TLE493D_AXIS_Y,
+	TLE493D_AXIS_Z,
+	TLE493D_TEMPERATURE,
+};
+
+/* I2C addresses selected by IICadr, i.e. the default of variants A0 to A3 */
+static const u8 tle493d_i2c_addrs[] = { 0x35, 0x22, 0x78, 0x44 };
+
+struct tle493d_data {
+	struct i2c_client *client;
+	/* Serialises measurements and protects the register cache */
+	struct mutex lock;
+	u8 regs[TLE493D_NUM_REGS];
+};
+
+/* FP makes MOD1 together with MOD2.PRD odd parity */
+static u8 tle493d_mod1_fp(u8 mod1, u8 mod2)
+{
+	unsigned int ones;
+
+	mod1 &= ~TLE493D_MOD1_FP;
+	ones = hweight8(mod1) + hweight8(mod2 & TLE493D_MOD2_PRD);
+	if (!(ones & 1))
+		mod1 |= TLE493D_MOD1_FP;
+
+	return mod1;
+}
+
+/* CP makes registers 0x07 to 0x10 odd parity, ignoring WA, TST and PH */
+static u8 tle493d_config_cp(const u8 *regs, u8 config)
+{
+	unsigned int ones = 0;
+	unsigned int i;
+
+	for (i = TLE493D_REG_XL; i < TLE493D_REG_WU; i++)
+		ones += hweight8(regs[i]);
+	ones += hweight8(regs[TLE493D_REG_WU] & ~TLE493D_WU_WA);
+	ones += hweight8(regs[TLE493D_REG_TMODE] & ~TLE493D_TMODE_TST);
+	ones += hweight8(regs[TLE493D_REG_TPHASE] & ~TLE493D_TPHASE_PH);
+
+	config &= ~TLE493D_CONFIG_CP;
+	ones += hweight8(config);
+	if (!(ones & 1))
+		config |= TLE493D_CONFIG_CP;
+
+	return config;
+}
+
+/* Registers 0x00 to 0x05 together with the P bit have odd parity */
+static bool tle493d_data_parity_ok(const u8 *buf)
+{
+	unsigned int ones = hweight8(buf[TLE493D_REG_DIAG] & TLE493D_DIAG_P);
+	unsigned int i;
+
+	for (i = TLE493D_REG_BX; i < TLE493D_REG_DIAG; i++)
+		ones += hweight8(buf[i]);
+
+	return ones & 1;
+}
+
+static int tle493d_read(struct tle493d_data *data, u8 *buf, int len)
+{
+	int ret;
+
+	ret = i2c_master_recv(data->client, buf, len);
+	if (ret < 0)
+		return ret;
+	if (ret != len)
+		return -EIO;
+
+	return 0;
+}
+
+/*
+ * Write one register, updating its parity bit first. Only MOD1 and Config
+ * are written by this driver. The sensor drops off the bus until reset if
+ * MOD1 is written with a wrong FP.
+ */
+static int tle493d_write(struct tle493d_data *data, u8 reg, u8 val, u8 trig)
+{
+	u8 buf[2];
+	int ret;
+
+	switch (reg) {
+	case TLE493D_REG_MOD1:
+		val = tle493d_mod1_fp(val, data->regs[TLE493D_REG_MOD2]);
+		break;
+	case TLE493D_REG_CONFIG:
+		val = tle493d_config_cp(data->regs, val);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	buf[0] = FIELD_PREP(TLE493D_ADDR_TRIG, trig) |
+		 FIELD_PREP(TLE493D_ADDR_REG, reg);
+	buf[1] = val;
+
+	ret = i2c_master_send(data->client, buf, sizeof(buf));
+	if (ret < 0)
+		return ret;
+	if (ret != sizeof(buf))
+		return -EIO;
+
+	data->regs[reg] = val;
+
+	return 0;
+}
+
+static bool tle493d_conversion_done(const u8 *buf, u8 last_frm)
+{
+	u8 diag = buf[TLE493D_REG_DIAG];
+
+	return (diag & TLE493D_DIAG_PD0) && (diag & TLE493D_DIAG_PD3) &&
+	       FIELD_GET(TLE493D_DIAG_FRM, diag) != last_frm;
+}
+
+static int tle493d_measure(struct tle493d_data *data, u8 *buf)
+{
+	u8 last_frm = FIELD_GET(TLE493D_DIAG_FRM, data->regs[TLE493D_REG_DIAG]);
+	u8 diag;
+	int ret, err;
+
+	/* Rewriting Config with the write trigger starts a single conversion */
+	ret = tle493d_write(data, TLE493D_REG_CONFIG,
+			    data->regs[TLE493D_REG_CONFIG], TLE493D_ADDR_TRIG_WRITE);
+	if (ret)
+		return ret;
+
+	ret = read_poll_timeout(tle493d_read, err,
+				err || tle493d_conversion_done(buf, last_frm),
+				TLE493D_POLL_US, TLE493D_TIMEOUT_US, true,
+				data, buf, TLE493D_DATA_LEN);
+	if (ret)
+		return ret;
+	if (err)
+		return err;
+
+	diag = buf[TLE493D_REG_DIAG];
+	data->regs[TLE493D_REG_DIAG] = diag;
+
+	if (!(diag & TLE493D_DIAG_FF) || (diag & TLE493D_DIAG_T) ||
+	    !tle493d_data_parity_ok(buf))
+		return -EIO;
+
+	return 0;
+}
+
+static int tle493d_get_channel_data(const u8 *buf, unsigned int ch)
+{
+	u32 val;
+
+	switch (ch) {
+	case TLE493D_AXIS_X:
+		val = buf[TLE493D_REG_BX] << 4 |
+		      FIELD_GET(TLE493D_BX2_X_LSB, buf[TLE493D_REG_BX2]);
+		return sign_extend32(val, 11);
+	case TLE493D_AXIS_Y:
+		val = buf[TLE493D_REG_BY] << 4 |
+		      FIELD_GET(TLE493D_BX2_Y_LSB, buf[TLE493D_REG_BX2]);
+		return sign_extend32(val, 11);
+	case TLE493D_AXIS_Z:
+		val = buf[TLE493D_REG_BZ] << 4 |
+		      FIELD_GET(TLE493D_TEMP2_Z_LSB, buf[TLE493D_REG_TEMP2]);
+		return sign_extend32(val, 11);
+	case TLE493D_TEMPERATURE:
+		/* 12-bit value whose two LSBs are always zero */
+		return buf[TLE493D_REG_TEMP] << 4 |
+		       FIELD_GET(TLE493D_TEMP2_TEMP_LSB, buf[TLE493D_REG_TEMP2]) << 2;
+	default:
+		return 0;
+	}
+}
+
+static int tle493d_read_raw(struct iio_dev *indio_dev,
+			    const struct iio_chan_spec *chan, int *val,
+			    int *val2, long mask)
+{
+	struct tle493d_data *data = iio_priv(indio_dev);
+	u8 buf[TLE493D_DATA_LEN];
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		scoped_guard(mutex, &data->lock) {
+			ret = tle493d_measure(data, buf);
+			if (ret)
+				return ret;
+		}
+		*val = tle493d_get_channel_data(buf, chan->address);
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->type) {
+		case IIO_MAGN:
+			/* 7.7 LSB/mT in full range, i.e. 10 / 7.7 Gauss per LSB */
+			*val = 100;
+			*val2 = 77;
+			return IIO_VAL_FRACTIONAL;
+		case IIO_TEMP:
+			/* 0.24 K per LSB of the 12-bit value */
+			*val = 240;
+			return IIO_VAL_INT;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_OFFSET:
+		switch (chan->type) {
+		case IIO_TEMP:
+			/*
+			 * The 12-bit value is 1180 at 25 degC:
+			 * offset = -1180 + 25000 / 240 = -6455 / 6
+			 */
+			*val = -6455;
+			*val2 = 6;
+			return IIO_VAL_FRACTIONAL;
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static irqreturn_t tle493d_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct tle493d_data *data = iio_priv(indio_dev);
+	u8 buf[TLE493D_DATA_LEN];
+	struct {
+		s16 magn[3];
+		u16 temp;
+		aligned_s64 timestamp;
+	} scan = { };
+	int ret;
+
+	scoped_guard(mutex, &data->lock)
+		ret = tle493d_measure(data, buf);
+	if (ret)
+		goto out;
+
+	scan.magn[0] = tle493d_get_channel_data(buf, TLE493D_AXIS_X);
+	scan.magn[1] = tle493d_get_channel_data(buf, TLE493D_AXIS_Y);
+	scan.magn[2] = tle493d_get_channel_data(buf, TLE493D_AXIS_Z);
+	scan.temp = tle493d_get_channel_data(buf, TLE493D_TEMPERATURE);
+
+	iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp);
+
+out:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+#define TLE493D_AXIS_CHANNEL(axis)					\
+	{								\
+		.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),	\
+		.address = TLE493D_AXIS_##axis,				\
+		.scan_index = TLE493D_AXIS_##axis,			\
+		.scan_type = {						\
+			.sign = 's',					\
+			.realbits = 12,					\
+			.storagebits = 16,				\
+			.endianness = IIO_CPU,				\
+		},							\
+	}
+
+static const struct iio_chan_spec tle493d_channels[] = {
+	TLE493D_AXIS_CHANNEL(X),
+	TLE493D_AXIS_CHANNEL(Y),
+	TLE493D_AXIS_CHANNEL(Z),
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_OFFSET),
+		.address = TLE493D_TEMPERATURE,
+		.scan_index = TLE493D_TEMPERATURE,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 12,
+			.storagebits = 16,
+			.endianness = IIO_CPU,
+		},
+	},
+	IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const unsigned long tle493d_scan_masks[] = { GENMASK(3, 0), 0 };
+
+static const struct iio_info tle493d_info = {
+	.read_raw = tle493d_read_raw,
+};
+
+/*
+ * The reset sequence is four separate frames whose first bytes are 0xff,
+ * 0xff, 0x00 and 0x00, i.e. zero-length reads from address 0x7f followed by
+ * zero-length writes to address 0x00. Not every adapter can send zero-length
+ * messages, so the reset is best effort.
+ */
+static void tle493d_reset(struct tle493d_data *data)
+{
+	struct device *dev = &data->client->dev;
+	struct i2c_msg msgs[] = {
+		{ .addr = 0x7f, .flags = I2C_M_RD },
+		{ .addr = 0x7f, .flags = I2C_M_RD },
+		{ .addr = 0x00 },
+		{ .addr = 0x00 },
+	};
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < ARRAY_SIZE(msgs); i++) {
+		/* The reset frames are not acknowledged, so ignore NACKs */
+		ret = i2c_transfer(data->client->adapter, &msgs[i], 1);
+		if (ret == -EOPNOTSUPP) {
+			dev_dbg(dev, "adapter cannot send the reset sequence\n");
+			return;
+		}
+	}
+
+	fsleep(TLE493D_RESET_DELAY_US);
+}
+
+static int tle493d_init(struct tle493d_data *data, unsigned int iicadr)
+{
+	struct device *dev = &data->client->dev;
+	unsigned int hwv, id;
+	u8 mod1;
+	int ret;
+
+	/*
+	 * After reset the sensor uses a 2-byte read protocol which plain I2C
+	 * reads cannot handle, so MOD1 has to be written before any read. MOD2
+	 * is not known yet and FP is computed with its reset value, PRD = 0.
+	 * Clock stretching and the interrupt are disabled because /INT shares
+	 * the SCL pin.
+	 */
+	mod1 = FIELD_PREP(TLE493D_MOD1_IICADR, iicadr) | TLE493D_MOD1_PR |
+	       TLE493D_MOD1_CA | TLE493D_MOD1_INT |
+	       FIELD_PREP(TLE493D_MOD1_MODE, TLE493D_MOD1_MODE_MASTER);
+	ret = tle493d_write(data, TLE493D_REG_MOD1, mod1, 0);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to write MOD1\n");
+
+	ret = tle493d_read(data, data->regs, TLE493D_NUM_REGS);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to read registers\n");
+
+	hwv = FIELD_GET(TLE493D_VER_HWV, data->regs[TLE493D_REG_VER]);
+	if (hwv != TLE493D_VER_HWV_B21)
+		dev_info(dev, "unknown hardware version %u\n", hwv);
+
+	id = FIELD_GET(TLE493D_TEMP2_ID, data->regs[TLE493D_REG_TEMP2]);
+	if (id != iicadr)
+		dev_info(dev, "unexpected ID %u, expected %u\n", id, iicadr);
+
+	/*
+	 * Full range, temperature and Bz enabled, no trigger via Config. This
+	 * also sets a valid CP, which is not the case after reset.
+	 */
+	ret = tle493d_write(data, TLE493D_REG_CONFIG, 0, 0);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to write Config\n");
+
+	return 0;
+}
+
+static int tle493d_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct tle493d_data *data;
+	struct iio_dev *indio_dev;
+	unsigned int iicadr;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+		return dev_err_probe(dev, -EOPNOTSUPP, "plain I2C transfers not supported\n");
+
+	for (iicadr = 0; iicadr < ARRAY_SIZE(tle493d_i2c_addrs); iicadr++) {
+		if (client->addr == tle493d_i2c_addrs[iicadr])
+			break;
+	}
+	if (iicadr == ARRAY_SIZE(tle493d_i2c_addrs))
+		return dev_err_probe(dev, -EINVAL, "unsupported I2C address 0x%02x\n",
+				     client->addr);
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	data->client = client;
+
+	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 vdd regulator\n");
+
+	tle493d_reset(data);
+
+	ret = tle493d_init(data, iicadr);
+	if (ret)
+		return ret;
+
+	indio_dev->name = "tle493d";
+	indio_dev->info = &tle493d_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = tle493d_channels;
+	indio_dev->num_channels = ARRAY_SIZE(tle493d_channels);
+	indio_dev->available_scan_masks = tle493d_scan_masks;
+
+	ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+					      iio_pollfunc_store_time,
+					      tle493d_trigger_handler, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to set up triggered buffer\n");
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct i2c_device_id tle493d_id[] = {
+	{ .name = "tle493d-w2b6" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tle493d_id);
+
+static const struct of_device_id tle493d_of_match[] = {
+	{ .compatible = "infineon,tle493d-w2b6" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tle493d_of_match);
+
+static struct i2c_driver tle493d_driver = {
+	.driver = {
+		.name = "tle493d",
+		.of_match_table = tle493d_of_match,
+	},
+	.probe = tle493d_probe,
+	.id_table = tle493d_id,
+};
+module_i2c_driver(tle493d_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Infineon TLE493D-W2B6 3D Magnetic Sensor driver");
+MODULE_AUTHOR("Viral Patel <viral8854@gmail.com>");

-- 
2.43.0


      parent reply	other threads:[~2026-11-05 15:54 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05 15:53 [PATCH 0/2] iio: magnetometer: add Infineon TLE493D-W2B6 support Viral Patel
2026-11-05 15:53 ` [PATCH 1/2] dt-bindings: iio: magnetometer: add Infineon TLE493D-W2B6 Viral Patel
2026-11-05 15:53 ` Viral Patel [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261105-tle493d-v1-2-3246e91e6394@gmail.com \
    --to=viral8854@gmail.com \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nuno.sa@analog.com \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®