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