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