Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Antoni Pokusinski 1857 100.00% 1 100.00%
Total 1857 1


// 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	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 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 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");

	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");