xref: /linux/drivers/iio/chemical/sen0322.c (revision c26f4fbd58375bd6ef74f95eb73d61762ad97c59)
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