| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Driver for the Infineon TLV493D Low-Power 3D Magnetic Sensor |
| * |
| * Copyright (C) 2025 Dixit Parmar <dixitparmar19@gmail.com> |
| */ |
| |
| #include <linux/array_size.h> |
| #include <linux/bits.h> |
| #include <linux/bitfield.h> |
| #include <linux/cleanup.h> |
| #include <linux/delay.h> |
| #include <linux/dev_printk.h> |
| #include <linux/i2c.h> |
| #include <linux/iopoll.h> |
| #include <linux/module.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/pm.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/types.h> |
| #include <linux/units.h> |
| |
| #include <linux/iio/buffer.h> |
| #include <linux/iio/iio.h> |
| #include <linux/iio/trigger_consumer.h> |
| #include <linux/iio/triggered_buffer.h> |
| |
| /* |
| * 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]) >> 4; |
| 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[] = { |
| { "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 <dixitparmar19@gmail.com>"); |