summaryrefslogtreecommitdiff
path: root/drivers/iio/chemical/sen0322.c
blob: 96c6fc1203ad1ddced5f6e57e21e4237bbf0be7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// SPDX-License-Identifier: GPL-2.0
/*
 * Driver for the DFRobot SEN0322 oxygen sensor.
 *
 * Datasheet:
 *	https://wiki.dfrobot.com/Gravity_I2C_Oxygen_Sensor_SKU_SEN0322
 *
 * Possible I2C slave addresses:
 *	0x70
 *	0x71
 *	0x72
 *	0x73
 *
 * Copyright (C) 2025 Tóth János <gomba007@gmail.com>
 */

#include <linux/i2c.h>
#include <linux/regmap.h>

#include <linux/iio/iio.h>

#define SEN0322_REG_DATA	0x03
#define SEN0322_REG_COEFF	0x0A

struct sen0322 {
	struct regmap	*regmap;
};

static int sen0322_read_data(struct sen0322 *sen0322)
{
	u8 data[3] = { };
	int ret;

	ret = regmap_bulk_read(sen0322->regmap, SEN0322_REG_DATA, data,
			       sizeof(data));
	if (ret < 0)
		return ret;

	/*
	 * The actual value in the registers is:
	 *	val = data[0] + data[1] / 10 + data[2] / 100
	 * but it is multiplied by 100 here to avoid floating-point math
	 * and the scale is divided by 100 to compensate this.
	 */
	return data[0] * 100 + data[1] * 10 + data[2];
}

static int sen0322_read_scale(struct sen0322 *sen0322, int *num, int *den)
{
	u32 val;
	int ret;

	ret = regmap_read(sen0322->regmap, SEN0322_REG_COEFF, &val);
	if (ret < 0)
		return ret;

	if (val) {
		*num = val;
		*den = 100000;	/* Coeff is scaled by 1000 at calibration. */
	} else { /* The device is not calibrated, using the factory-defaults. */
		*num = 209;	/* Oxygen content in the atmosphere is 20.9%. */
		*den = 120000;	/* Output of the sensor at 20.9% is 120 uA. */
	}

	dev_dbg(regmap_get_device(sen0322->regmap), "scale: %d/%d\n",
		*num, *den);

	return 0;
}

static int sen0322_read_raw(struct iio_dev *iio_dev,
			    const struct iio_chan_spec *chan,
			    int *val, int *val2, long mask)
{
	struct sen0322 *sen0322 = iio_priv(iio_dev);
	int ret;

	if (chan->type != IIO_CONCENTRATION)
		return -EINVAL;

	switch (mask) {
	case IIO_CHAN_INFO_RAW:
		ret = sen0322_read_data(sen0322);
		if (ret < 0)
			return ret;

		*val = ret;
		return IIO_VAL_INT;

	case IIO_CHAN_INFO_SCALE:
		ret = sen0322_read_scale(sen0322, val, val2);
		if (ret < 0)
			return ret;

		return IIO_VAL_FRACTIONAL;

	default:
		return -EINVAL;
	}
}

static const struct iio_info sen0322_info = {
	.read_raw = sen0322_read_raw,
};

static const struct regmap_config sen0322_regmap_conf = {
	.reg_bits = 8,
	.val_bits = 8,
};

static const struct iio_chan_spec sen0322_channel = {
	.type = IIO_CONCENTRATION,
	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
			      BIT(IIO_CHAN_INFO_SCALE),
};

static int sen0322_probe(struct i2c_client *client)
{
	struct sen0322 *sen0322;
	struct iio_dev *iio_dev;

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
		return -ENODEV;

	iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*sen0322));
	if (!iio_dev)
		return -ENOMEM;

	sen0322 = iio_priv(iio_dev);

	sen0322->regmap = devm_regmap_init_i2c(client, &sen0322_regmap_conf);
	if (IS_ERR(sen0322->regmap))
		return PTR_ERR(sen0322->regmap);

	iio_dev->info = &sen0322_info;
	iio_dev->name = "sen0322";
	iio_dev->channels = &sen0322_channel;
	iio_dev->num_channels = 1;
	iio_dev->modes = INDIO_DIRECT_MODE;

	return devm_iio_device_register(&client->dev, iio_dev);
}

static const struct of_device_id sen0322_of_match[] = {
	{ .compatible = "dfrobot,sen0322" },
	{ }
};
MODULE_DEVICE_TABLE(of, sen0322_of_match);

static struct i2c_driver sen0322_driver = {
	.driver = {
		.name = "sen0322",
		.of_match_table = sen0322_of_match,
	},
	.probe = sen0322_probe,
};
module_i2c_driver(sen0322_driver);

MODULE_AUTHOR("Tóth János <gomba007@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SEN0322 oxygen sensor driver");