From: Jonathan Cameron <jic23@kernel.org>
To: Viral Patel <viral8854@gmail.com>
Cc: "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>,
linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH 2/2] iio: magnetometer: add Infineon TLE493D-W2B6 driver
Date: Fri, 6 Nov 2026 03:04:19 +0000 [thread overview]
Message-ID: <20261106030419.23ca9b26@jic23-hlaptop> (raw)
In-Reply-To: <20261105-tle493d-v1-2-3246e91e6394@gmail.com>
On Thu, 05 Nov 2026 21:23:48 +0530
Viral Patel <viral8854@gmail.com> wrote:
> 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>
Seems sashiko isn't done with this one yet (at time of writing)
so please take a look to see if it finds anything useful
https://sashiko.dev/#/patchset/20261105-tle493d-v1-0-3246e91e6394%40gmail.com
This hardware has a creative interface so I may have
asked some questions that reflect my general state of
confusion induced by that.
A few things inline,
> 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
>
> +
> +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) &&
Use FIELD_GET() to extract the 1 bit values as well.
Ends up easier to read than the mix of & and &&
> + 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;
Why do this after setting data->regs[TLE493_REG_DIAG]?
If this function is returning an error I'd assume we
wouldn't use that value anyway
> +
> + 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) {
I'd be tempted to just hold the lock a bit long and do the simpler
case IIO_CHAN_INFO_RAW: {
guard()...
...
return IIO_VAL_INT;
}
> + ret = tle493d_measure(data, buf);
> + if (ret)
> + return ret;
> + }
> + *val = tle493d_get_channel_data(buf, chan->address);
> + return IIO_VAL_INT;
...
> +
> +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)
Generally avoid any use of cleanup.h magic in a function with
a goto. This one isn't a bug as such but it is considered bad
practice. Just use mutex_lock/unlock()
> + 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;
> +}
> +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.
Can we detect if the controller supports it? Feels like it should have
an I2C_FUNC_xxx to enable discovery of this.
Is it same as SMBUS Quick command? If so use that.
> + */
> +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++) {
for (unsigned int i = 0; ...
> + /* 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])
This match is unusual. I don't think we should be failing
probe on seeing a device on a different i2c address given we
are happy to accept a mismatch from internal device ID as long
as it is one of this list. However it seems the index is
written to the device. Can you add a few comments on what
this iicadr is for.
> + 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);
> +}
>
prev parent reply other threads:[~2026-11-06 3:04 UTC|newest]
Thread overview: 5+ 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-06 2:36 ` Jonathan Cameron
2026-11-05 15:53 ` [PATCH 2/2] iio: magnetometer: add Infineon TLE493D-W2B6 driver Viral Patel
2026-11-06 3:04 ` Jonathan Cameron [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=20261106030419.23ca9b26@jic23-hlaptop \
--to=jic23@kernel.org \
--cc=andy@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dlechner@baylibre.com \
--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 \
--cc=viral8854@gmail.com \
/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®