sgp40.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * sgp40.c - Support for Sensirion SGP40 Gas Sensor
  4. *
  5. * Copyright (C) 2021 Andreas Klinger <[email protected]>
  6. *
  7. * I2C slave address: 0x59
  8. *
  9. * Datasheet can be found here:
  10. * https://www.sensirion.com/file/datasheet_sgp40
  11. *
  12. * There are two functionalities supported:
  13. *
  14. * 1) read raw logarithmic resistance value from sensor
  15. * --> useful to pass it to the algorithm of the sensor vendor for
  16. * measuring deteriorations and improvements of air quality.
  17. *
  18. * 2) calculate an estimated absolute voc index (0 - 500 index points) for
  19. * measuring the air quality.
  20. * For this purpose the value of the resistance for which the voc index
  21. * will be 250 can be set up using calibbias.
  22. *
  23. * Compensation values of relative humidity and temperature can be set up
  24. * by writing to the out values of temp and humidityrelative.
  25. */
  26. #include <linux/delay.h>
  27. #include <linux/crc8.h>
  28. #include <linux/module.h>
  29. #include <linux/mutex.h>
  30. #include <linux/i2c.h>
  31. #include <linux/iio/iio.h>
  32. /*
  33. * floating point calculation of voc is done as integer
  34. * where numbers are multiplied by 1 << SGP40_CALC_POWER
  35. */
  36. #define SGP40_CALC_POWER 14
  37. #define SGP40_CRC8_POLYNOMIAL 0x31
  38. #define SGP40_CRC8_INIT 0xff
  39. DECLARE_CRC8_TABLE(sgp40_crc8_table);
  40. struct sgp40_data {
  41. struct device *dev;
  42. struct i2c_client *client;
  43. int rht;
  44. int temp;
  45. int res_calibbias;
  46. /* Prevent concurrent access to rht, tmp, calibbias */
  47. struct mutex lock;
  48. };
  49. struct sgp40_tg_measure {
  50. u8 command[2];
  51. __be16 rht_ticks;
  52. u8 rht_crc;
  53. __be16 temp_ticks;
  54. u8 temp_crc;
  55. } __packed;
  56. struct sgp40_tg_result {
  57. __be16 res_ticks;
  58. u8 res_crc;
  59. } __packed;
  60. static const struct iio_chan_spec sgp40_channels[] = {
  61. {
  62. .type = IIO_CONCENTRATION,
  63. .channel2 = IIO_MOD_VOC,
  64. .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
  65. },
  66. {
  67. .type = IIO_RESISTANCE,
  68. .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
  69. BIT(IIO_CHAN_INFO_CALIBBIAS),
  70. },
  71. {
  72. .type = IIO_TEMP,
  73. .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
  74. .output = 1,
  75. },
  76. {
  77. .type = IIO_HUMIDITYRELATIVE,
  78. .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
  79. .output = 1,
  80. },
  81. };
  82. /*
  83. * taylor approximation of e^x:
  84. * y = 1 + x + x^2 / 2 + x^3 / 6 + x^4 / 24 + ... + x^n / n!
  85. *
  86. * Because we are calculating x real value multiplied by 2^power we get
  87. * an additional 2^power^n to divide for every element. For a reasonable
  88. * precision this would overflow after a few iterations. Therefore we
  89. * divide the x^n part whenever its about to overflow (xmax).
  90. */
  91. static u32 sgp40_exp(int exp, u32 power, u32 rounds)
  92. {
  93. u32 x, y, xp;
  94. u32 factorial, divider, xmax;
  95. int sign = 1;
  96. int i;
  97. if (exp == 0)
  98. return 1 << power;
  99. else if (exp < 0) {
  100. sign = -1;
  101. exp *= -1;
  102. }
  103. xmax = 0x7FFFFFFF / exp;
  104. x = exp;
  105. xp = 1;
  106. factorial = 1;
  107. y = 1 << power;
  108. divider = 0;
  109. for (i = 1; i <= rounds; i++) {
  110. xp *= x;
  111. factorial *= i;
  112. y += (xp >> divider) / factorial;
  113. divider += power;
  114. /* divide when next multiplication would overflow */
  115. if (xp >= xmax) {
  116. xp >>= power;
  117. divider -= power;
  118. }
  119. }
  120. if (sign == -1)
  121. return (1 << (power * 2)) / y;
  122. else
  123. return y;
  124. }
  125. static int sgp40_calc_voc(struct sgp40_data *data, u16 resistance_raw, int *voc)
  126. {
  127. int x;
  128. u32 exp = 0;
  129. /* we calculate as a multiple of 16384 (2^14) */
  130. mutex_lock(&data->lock);
  131. x = ((int)resistance_raw - data->res_calibbias) * 106;
  132. mutex_unlock(&data->lock);
  133. /* voc = 500 / (1 + e^x) */
  134. exp = sgp40_exp(x, SGP40_CALC_POWER, 18);
  135. *voc = 500 * ((1 << (SGP40_CALC_POWER * 2)) / ((1<<SGP40_CALC_POWER) + exp));
  136. dev_dbg(data->dev, "raw: %d res_calibbias: %d x: %d exp: %d voc: %d\n",
  137. resistance_raw, data->res_calibbias, x, exp, *voc);
  138. return 0;
  139. }
  140. static int sgp40_measure_resistance_raw(struct sgp40_data *data, u16 *resistance_raw)
  141. {
  142. int ret;
  143. struct i2c_client *client = data->client;
  144. u32 ticks;
  145. u16 ticks16;
  146. u8 crc;
  147. struct sgp40_tg_measure tg = {.command = {0x26, 0x0F}};
  148. struct sgp40_tg_result tgres;
  149. mutex_lock(&data->lock);
  150. ticks = (data->rht / 10) * 65535 / 10000;
  151. ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between 0 .. 100 %rH */
  152. tg.rht_ticks = cpu_to_be16(ticks16);
  153. tg.rht_crc = crc8(sgp40_crc8_table, (u8 *)&tg.rht_ticks, 2, SGP40_CRC8_INIT);
  154. ticks = ((data->temp + 45000) / 10 ) * 65535 / 17500;
  155. ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between -45 .. +130 °C */
  156. tg.temp_ticks = cpu_to_be16(ticks16);
  157. tg.temp_crc = crc8(sgp40_crc8_table, (u8 *)&tg.temp_ticks, 2, SGP40_CRC8_INIT);
  158. mutex_unlock(&data->lock);
  159. ret = i2c_master_send(client, (const char *)&tg, sizeof(tg));
  160. if (ret != sizeof(tg)) {
  161. dev_warn(data->dev, "i2c_master_send ret: %d sizeof: %zu\n", ret, sizeof(tg));
  162. return -EIO;
  163. }
  164. msleep(30);
  165. ret = i2c_master_recv(client, (u8 *)&tgres, sizeof(tgres));
  166. if (ret < 0)
  167. return ret;
  168. if (ret != sizeof(tgres)) {
  169. dev_warn(data->dev, "i2c_master_recv ret: %d sizeof: %zu\n", ret, sizeof(tgres));
  170. return -EIO;
  171. }
  172. crc = crc8(sgp40_crc8_table, (u8 *)&tgres.res_ticks, 2, SGP40_CRC8_INIT);
  173. if (crc != tgres.res_crc) {
  174. dev_err(data->dev, "CRC error while measure-raw\n");
  175. return -EIO;
  176. }
  177. *resistance_raw = be16_to_cpu(tgres.res_ticks);
  178. return 0;
  179. }
  180. static int sgp40_read_raw(struct iio_dev *indio_dev,
  181. struct iio_chan_spec const *chan, int *val,
  182. int *val2, long mask)
  183. {
  184. struct sgp40_data *data = iio_priv(indio_dev);
  185. int ret, voc;
  186. u16 resistance_raw;
  187. switch (mask) {
  188. case IIO_CHAN_INFO_RAW:
  189. switch (chan->type) {
  190. case IIO_RESISTANCE:
  191. ret = sgp40_measure_resistance_raw(data, &resistance_raw);
  192. if (ret)
  193. return ret;
  194. *val = resistance_raw;
  195. return IIO_VAL_INT;
  196. case IIO_TEMP:
  197. mutex_lock(&data->lock);
  198. *val = data->temp;
  199. mutex_unlock(&data->lock);
  200. return IIO_VAL_INT;
  201. case IIO_HUMIDITYRELATIVE:
  202. mutex_lock(&data->lock);
  203. *val = data->rht;
  204. mutex_unlock(&data->lock);
  205. return IIO_VAL_INT;
  206. default:
  207. return -EINVAL;
  208. }
  209. case IIO_CHAN_INFO_PROCESSED:
  210. ret = sgp40_measure_resistance_raw(data, &resistance_raw);
  211. if (ret)
  212. return ret;
  213. ret = sgp40_calc_voc(data, resistance_raw, &voc);
  214. if (ret)
  215. return ret;
  216. *val = voc / (1 << SGP40_CALC_POWER);
  217. /*
  218. * calculation should fit into integer, where:
  219. * voc <= (500 * 2^SGP40_CALC_POWER) = 8192000
  220. * (with SGP40_CALC_POWER = 14)
  221. */
  222. *val2 = ((voc % (1 << SGP40_CALC_POWER)) * 244) / (1 << (SGP40_CALC_POWER - 12));
  223. dev_dbg(data->dev, "voc: %d val: %d.%06d\n", voc, *val, *val2);
  224. return IIO_VAL_INT_PLUS_MICRO;
  225. case IIO_CHAN_INFO_CALIBBIAS:
  226. mutex_lock(&data->lock);
  227. *val = data->res_calibbias;
  228. mutex_unlock(&data->lock);
  229. return IIO_VAL_INT;
  230. default:
  231. return -EINVAL;
  232. }
  233. }
  234. static int sgp40_write_raw(struct iio_dev *indio_dev,
  235. struct iio_chan_spec const *chan, int val,
  236. int val2, long mask)
  237. {
  238. struct sgp40_data *data = iio_priv(indio_dev);
  239. switch (mask) {
  240. case IIO_CHAN_INFO_RAW:
  241. switch (chan->type) {
  242. case IIO_TEMP:
  243. if ((val < -45000) || (val > 130000))
  244. return -EINVAL;
  245. mutex_lock(&data->lock);
  246. data->temp = val;
  247. mutex_unlock(&data->lock);
  248. return 0;
  249. case IIO_HUMIDITYRELATIVE:
  250. if ((val < 0) || (val > 100000))
  251. return -EINVAL;
  252. mutex_lock(&data->lock);
  253. data->rht = val;
  254. mutex_unlock(&data->lock);
  255. return 0;
  256. default:
  257. return -EINVAL;
  258. }
  259. case IIO_CHAN_INFO_CALIBBIAS:
  260. if ((val < 20000) || (val > 52768))
  261. return -EINVAL;
  262. mutex_lock(&data->lock);
  263. data->res_calibbias = val;
  264. mutex_unlock(&data->lock);
  265. return 0;
  266. }
  267. return -EINVAL;
  268. }
  269. static const struct iio_info sgp40_info = {
  270. .read_raw = sgp40_read_raw,
  271. .write_raw = sgp40_write_raw,
  272. };
  273. static int sgp40_probe(struct i2c_client *client,
  274. const struct i2c_device_id *id)
  275. {
  276. struct device *dev = &client->dev;
  277. struct iio_dev *indio_dev;
  278. struct sgp40_data *data;
  279. int ret;
  280. indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
  281. if (!indio_dev)
  282. return -ENOMEM;
  283. data = iio_priv(indio_dev);
  284. data->client = client;
  285. data->dev = dev;
  286. crc8_populate_msb(sgp40_crc8_table, SGP40_CRC8_POLYNOMIAL);
  287. mutex_init(&data->lock);
  288. /* set default values */
  289. data->rht = 50000; /* 50 % */
  290. data->temp = 25000; /* 25 °C */
  291. data->res_calibbias = 30000; /* resistance raw value for voc index of 250 */
  292. indio_dev->info = &sgp40_info;
  293. indio_dev->name = id->name;
  294. indio_dev->modes = INDIO_DIRECT_MODE;
  295. indio_dev->channels = sgp40_channels;
  296. indio_dev->num_channels = ARRAY_SIZE(sgp40_channels);
  297. ret = devm_iio_device_register(dev, indio_dev);
  298. if (ret)
  299. dev_err(dev, "failed to register iio device\n");
  300. return ret;
  301. }
  302. static const struct i2c_device_id sgp40_id[] = {
  303. { "sgp40" },
  304. { }
  305. };
  306. MODULE_DEVICE_TABLE(i2c, sgp40_id);
  307. static const struct of_device_id sgp40_dt_ids[] = {
  308. { .compatible = "sensirion,sgp40" },
  309. { }
  310. };
  311. MODULE_DEVICE_TABLE(of, sgp40_dt_ids);
  312. static struct i2c_driver sgp40_driver = {
  313. .driver = {
  314. .name = "sgp40",
  315. .of_match_table = sgp40_dt_ids,
  316. },
  317. .probe = sgp40_probe,
  318. .id_table = sgp40_id,
  319. };
  320. module_i2c_driver(sgp40_driver);
  321. MODULE_AUTHOR("Andreas Klinger <[email protected]>");
  322. MODULE_DESCRIPTION("Sensirion SGP40 gas sensor");
  323. MODULE_LICENSE("GPL v2");