1*d524b3e0STóth János // SPDX-License-Identifier: GPL-2.0
2*d524b3e0STóth János /*
3*d524b3e0STóth János * Driver for the DFRobot SEN0322 oxygen sensor.
4*d524b3e0STóth János *
5*d524b3e0STóth János * Datasheet:
6*d524b3e0STóth János * https://wiki.dfrobot.com/Gravity_I2C_Oxygen_Sensor_SKU_SEN0322
7*d524b3e0STóth János *
8*d524b3e0STóth János * Possible I2C slave addresses:
9*d524b3e0STóth János * 0x70
10*d524b3e0STóth János * 0x71
11*d524b3e0STóth János * 0x72
12*d524b3e0STóth János * 0x73
13*d524b3e0STóth János *
14*d524b3e0STóth János * Copyright (C) 2025 Tóth János <gomba007@gmail.com>
15*d524b3e0STóth János */
16*d524b3e0STóth János
17*d524b3e0STóth János #include <linux/i2c.h>
18*d524b3e0STóth János #include <linux/regmap.h>
19*d524b3e0STóth János
20*d524b3e0STóth János #include <linux/iio/iio.h>
21*d524b3e0STóth János
22*d524b3e0STóth János #define SEN0322_REG_DATA 0x03
23*d524b3e0STóth János #define SEN0322_REG_COEFF 0x0A
24*d524b3e0STóth János
25*d524b3e0STóth János struct sen0322 {
26*d524b3e0STóth János struct regmap *regmap;
27*d524b3e0STóth János };
28*d524b3e0STóth János
sen0322_read_data(struct sen0322 * sen0322)29*d524b3e0STóth János static int sen0322_read_data(struct sen0322 *sen0322)
30*d524b3e0STóth János {
31*d524b3e0STóth János u8 data[3] = { };
32*d524b3e0STóth János int ret;
33*d524b3e0STóth János
34*d524b3e0STóth János ret = regmap_bulk_read(sen0322->regmap, SEN0322_REG_DATA, data,
35*d524b3e0STóth János sizeof(data));
36*d524b3e0STóth János if (ret < 0)
37*d524b3e0STóth János return ret;
38*d524b3e0STóth János
39*d524b3e0STóth János /*
40*d524b3e0STóth János * The actual value in the registers is:
41*d524b3e0STóth János * val = data[0] + data[1] / 10 + data[2] / 100
42*d524b3e0STóth János * but it is multiplied by 100 here to avoid floating-point math
43*d524b3e0STóth János * and the scale is divided by 100 to compensate this.
44*d524b3e0STóth János */
45*d524b3e0STóth János return data[0] * 100 + data[1] * 10 + data[2];
46*d524b3e0STóth János }
47*d524b3e0STóth János
sen0322_read_scale(struct sen0322 * sen0322,int * num,int * den)48*d524b3e0STóth János static int sen0322_read_scale(struct sen0322 *sen0322, int *num, int *den)
49*d524b3e0STóth János {
50*d524b3e0STóth János u32 val;
51*d524b3e0STóth János int ret;
52*d524b3e0STóth János
53*d524b3e0STóth János ret = regmap_read(sen0322->regmap, SEN0322_REG_COEFF, &val);
54*d524b3e0STóth János if (ret < 0)
55*d524b3e0STóth János return ret;
56*d524b3e0STóth János
57*d524b3e0STóth János if (val) {
58*d524b3e0STóth János *num = val;
59*d524b3e0STóth János *den = 100000; /* Coeff is scaled by 1000 at calibration. */
60*d524b3e0STóth János } else { /* The device is not calibrated, using the factory-defaults. */
61*d524b3e0STóth János *num = 209; /* Oxygen content in the atmosphere is 20.9%. */
62*d524b3e0STóth János *den = 120000; /* Output of the sensor at 20.9% is 120 uA. */
63*d524b3e0STóth János }
64*d524b3e0STóth János
65*d524b3e0STóth János dev_dbg(regmap_get_device(sen0322->regmap), "scale: %d/%d\n",
66*d524b3e0STóth János *num, *den);
67*d524b3e0STóth János
68*d524b3e0STóth János return 0;
69*d524b3e0STóth János }
70*d524b3e0STóth János
sen0322_read_raw(struct iio_dev * iio_dev,const struct iio_chan_spec * chan,int * val,int * val2,long mask)71*d524b3e0STóth János static int sen0322_read_raw(struct iio_dev *iio_dev,
72*d524b3e0STóth János const struct iio_chan_spec *chan,
73*d524b3e0STóth János int *val, int *val2, long mask)
74*d524b3e0STóth János {
75*d524b3e0STóth János struct sen0322 *sen0322 = iio_priv(iio_dev);
76*d524b3e0STóth János int ret;
77*d524b3e0STóth János
78*d524b3e0STóth János if (chan->type != IIO_CONCENTRATION)
79*d524b3e0STóth János return -EINVAL;
80*d524b3e0STóth János
81*d524b3e0STóth János switch (mask) {
82*d524b3e0STóth János case IIO_CHAN_INFO_RAW:
83*d524b3e0STóth János ret = sen0322_read_data(sen0322);
84*d524b3e0STóth János if (ret < 0)
85*d524b3e0STóth János return ret;
86*d524b3e0STóth János
87*d524b3e0STóth János *val = ret;
88*d524b3e0STóth János return IIO_VAL_INT;
89*d524b3e0STóth János
90*d524b3e0STóth János case IIO_CHAN_INFO_SCALE:
91*d524b3e0STóth János ret = sen0322_read_scale(sen0322, val, val2);
92*d524b3e0STóth János if (ret < 0)
93*d524b3e0STóth János return ret;
94*d524b3e0STóth János
95*d524b3e0STóth János return IIO_VAL_FRACTIONAL;
96*d524b3e0STóth János
97*d524b3e0STóth János default:
98*d524b3e0STóth János return -EINVAL;
99*d524b3e0STóth János }
100*d524b3e0STóth János }
101*d524b3e0STóth János
102*d524b3e0STóth János static const struct iio_info sen0322_info = {
103*d524b3e0STóth János .read_raw = sen0322_read_raw,
104*d524b3e0STóth János };
105*d524b3e0STóth János
106*d524b3e0STóth János static const struct regmap_config sen0322_regmap_conf = {
107*d524b3e0STóth János .reg_bits = 8,
108*d524b3e0STóth János .val_bits = 8,
109*d524b3e0STóth János };
110*d524b3e0STóth János
111*d524b3e0STóth János static const struct iio_chan_spec sen0322_channel = {
112*d524b3e0STóth János .type = IIO_CONCENTRATION,
113*d524b3e0STóth János .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
114*d524b3e0STóth János BIT(IIO_CHAN_INFO_SCALE),
115*d524b3e0STóth János };
116*d524b3e0STóth János
sen0322_probe(struct i2c_client * client)117*d524b3e0STóth János static int sen0322_probe(struct i2c_client *client)
118*d524b3e0STóth János {
119*d524b3e0STóth János struct sen0322 *sen0322;
120*d524b3e0STóth János struct iio_dev *iio_dev;
121*d524b3e0STóth János
122*d524b3e0STóth János if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
123*d524b3e0STóth János return -ENODEV;
124*d524b3e0STóth János
125*d524b3e0STóth János iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*sen0322));
126*d524b3e0STóth János if (!iio_dev)
127*d524b3e0STóth János return -ENOMEM;
128*d524b3e0STóth János
129*d524b3e0STóth János sen0322 = iio_priv(iio_dev);
130*d524b3e0STóth János
131*d524b3e0STóth János sen0322->regmap = devm_regmap_init_i2c(client, &sen0322_regmap_conf);
132*d524b3e0STóth János if (IS_ERR(sen0322->regmap))
133*d524b3e0STóth János return PTR_ERR(sen0322->regmap);
134*d524b3e0STóth János
135*d524b3e0STóth János iio_dev->info = &sen0322_info;
136*d524b3e0STóth János iio_dev->name = "sen0322";
137*d524b3e0STóth János iio_dev->channels = &sen0322_channel;
138*d524b3e0STóth János iio_dev->num_channels = 1;
139*d524b3e0STóth János iio_dev->modes = INDIO_DIRECT_MODE;
140*d524b3e0STóth János
141*d524b3e0STóth János return devm_iio_device_register(&client->dev, iio_dev);
142*d524b3e0STóth János }
143*d524b3e0STóth János
144*d524b3e0STóth János static const struct of_device_id sen0322_of_match[] = {
145*d524b3e0STóth János { .compatible = "dfrobot,sen0322" },
146*d524b3e0STóth János { }
147*d524b3e0STóth János };
148*d524b3e0STóth János MODULE_DEVICE_TABLE(of, sen0322_of_match);
149*d524b3e0STóth János
150*d524b3e0STóth János static struct i2c_driver sen0322_driver = {
151*d524b3e0STóth János .driver = {
152*d524b3e0STóth János .name = "sen0322",
153*d524b3e0STóth János .of_match_table = sen0322_of_match,
154*d524b3e0STóth János },
155*d524b3e0STóth János .probe = sen0322_probe,
156*d524b3e0STóth János };
157*d524b3e0STóth János module_i2c_driver(sen0322_driver);
158*d524b3e0STóth János
159*d524b3e0STóth János MODULE_AUTHOR("Tóth János <gomba007@gmail.com>");
160*d524b3e0STóth János MODULE_LICENSE("GPL");
161*d524b3e0STóth János MODULE_DESCRIPTION("SEN0322 oxygen sensor driver");
162