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 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 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 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 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