// SPDX-License-Identifier: GPL-2.0
/*
* Silicon Labs Si7210 Hall Effect sensor driver
*
* Copyright ( c ) 2024 Antoni Pokusinski < apokusinski01 @ gmail . com >
*
* Datasheet :
* https : //www.silabs.com/documents/public/data-sheets/si7210-datasheet.pdf
*/
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/math64.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/types.h>
#include <linux/units.h>
#include <asm /byteorder.h>
/* Registers offsets and masks */
#define SI7210_REG_DSPSIGM 0 xC1
#define SI7210_REG_DSPSIGL 0 xC2
#define SI7210_MASK_DSPSIGSEL GENMASK(2 , 0 )
#define SI7210_REG_DSPSIGSEL 0 xC3
#define SI7210_MASK_STOP BIT(1 )
#define SI7210_MASK_ONEBURST BIT(2 )
#define SI7210_REG_POWER_CTRL 0 xC4
#define SI7210_MASK_ARAUTOINC BIT(0 )
#define SI7210_REG_ARAUTOINC 0 xC5
#define SI7210_REG_A0 0 xCA
#define SI7210_REG_A1 0 xCB
#define SI7210_REG_A2 0 xCC
#define SI7210_REG_A3 0 xCE
#define SI7210_REG_A4 0 xCF
#define SI7210_REG_A5 0 xD0
#define SI7210_REG_OTP_ADDR 0 xE1
#define SI7210_REG_OTP_DATA 0 xE2
#define SI7210_MASK_OTP_READ_EN BIT(1 )
#define SI7210_REG_OTP_CTRL 0 xE3
/* OTP data registers offsets */
#define SI7210_OTPREG_TMP_OFF 0 x1D
#define SI7210_OTPREG_TMP_GAIN 0 x1E
#define SI7210_OTPREG_A0_20 0 x21
#define SI7210_OTPREG_A1_20 0 x22
#define SI7210_OTPREG_A2_20 0 x23
#define SI7210_OTPREG_A3_20 0 x24
#define SI7210_OTPREG_A4_20 0 x25
#define SI7210_OTPREG_A5_20 0 x26
#define SI7210_OTPREG_A0_200 0 x27
#define SI7210_OTPREG_A1_200 0 x28
#define SI7210_OTPREG_A2_200 0 x29
#define SI7210_OTPREG_A3_200 0 x2A
#define SI7210_OTPREG_A4_200 0 x2B
#define SI7210_OTPREG_A5_200 0 x2C
#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 regulator *vdd;
struct mutex fetch_lock; /* lock for a single measurement fetch */
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;
ret = regulator_get_voltage(data->vdd);
if (ret < 0 )
return ret;
/* temp -= 0.222 * VDD */
temp -= 222 * div_s64(ret, 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 0 xFF .
*/
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 != 0 xFF)
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" );
data->vdd = devm_regulator_get(&client->dev, "vdd" );
if (IS_ERR(data->vdd))
return dev_err_probe(&client->dev, PTR_ERR(data->vdd),
"failed to get VDD regulator\n" );
ret = regulator_enable(data->vdd);
if (ret)
return 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[] = {
{ "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 <apokusinski01@gmail.com>" );
MODULE_DESCRIPTION("Silicon Labs Si7210 Hall Effect sensor I2C driver" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=93 H=98 G=95
¤ Dauer der Verarbeitung: 0.2 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland