1 /*
2 * Copyright (c) 2018 Peter Bigot Consulting, LLC
3 * Copyright (c) 2018 Linaro Ltd.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #define DT_DRV_COMPAT ams_ccs811
9
10 #include <zephyr/device.h>
11 #include <zephyr/drivers/gpio.h>
12 #include <zephyr/drivers/i2c.h>
13 #include <zephyr/kernel.h>
14 #include <zephyr/sys/byteorder.h>
15 #include <zephyr/sys/util.h>
16 #include <zephyr/drivers/sensor.h>
17 #include <zephyr/sys/__assert.h>
18 #include <zephyr/logging/log.h>
19
20 #include "ccs811.h"
21
22 LOG_MODULE_REGISTER(CCS811, CONFIG_SENSOR_LOG_LEVEL);
23
set_wake(const struct device * dev,bool enable)24 static void set_wake(const struct device *dev, bool enable)
25 {
26 const struct ccs811_config *config = dev->config;
27
28 gpio_pin_set_dt(&config->wake_gpio, enable);
29 if (enable) {
30 k_busy_wait(50); /* t_WAKE = 50 us */
31 } else {
32 k_busy_wait(20); /* t_DWAKE = 20 us */
33 }
34 }
35
36 /* Get STATUS register in low 8 bits, and if ERROR is set put ERROR_ID
37 * in bits 8..15. These registers are available in both boot and
38 * application mode.
39 */
fetch_status(const struct device * dev)40 static int fetch_status(const struct device *dev)
41 {
42 const struct ccs811_config *config = dev->config;
43 uint8_t status;
44 int rv;
45
46 if (i2c_reg_read_byte_dt(&config->i2c, CCS811_REG_STATUS, &status) < 0) {
47 LOG_ERR("Failed to read Status register");
48 return -EIO;
49 }
50
51 rv = status;
52 if (status & CCS811_STATUS_ERROR) {
53 uint8_t error_id;
54
55 if (i2c_reg_read_byte_dt(&config->i2c, CCS811_REG_ERROR_ID, &error_id) < 0) {
56 LOG_ERR("Failed to read ERROR_ID register");
57 return -EIO;
58 }
59
60 rv |= (error_id << 8);
61 }
62
63 return rv;
64 }
65
error_from_status(int status)66 static inline uint8_t error_from_status(int status)
67 {
68 return status >> 8;
69 }
70
ccs811_result(const struct device * dev)71 const struct ccs811_result_type *ccs811_result(const struct device *dev)
72 {
73 struct ccs811_data *drv_data = dev->data;
74
75 return &drv_data->result;
76 }
77
ccs811_configver_fetch(const struct device * dev,struct ccs811_configver_type * ptr)78 int ccs811_configver_fetch(const struct device *dev,
79 struct ccs811_configver_type *ptr)
80 {
81 struct ccs811_data *drv_data = dev->data;
82 const struct ccs811_config *config = dev->config;
83 uint8_t cmd;
84 int rc;
85
86 if (!ptr) {
87 return -EINVAL;
88 }
89
90 set_wake(dev, true);
91 cmd = CCS811_REG_HW_VERSION;
92 rc = i2c_write_read_dt(&config->i2c, &cmd, sizeof(cmd), &ptr->hw_version,
93 sizeof(ptr->hw_version));
94 if (rc == 0) {
95 cmd = CCS811_REG_FW_BOOT_VERSION;
96 rc = i2c_write_read_dt(&config->i2c, &cmd, sizeof(cmd),
97 (uint8_t *)&ptr->fw_boot_version,
98 sizeof(ptr->fw_boot_version));
99 ptr->fw_boot_version = sys_be16_to_cpu(ptr->fw_boot_version);
100 }
101
102 if (rc == 0) {
103 cmd = CCS811_REG_FW_APP_VERSION;
104 rc = i2c_write_read_dt(&config->i2c, &cmd, sizeof(cmd),
105 (uint8_t *)&ptr->fw_app_version,
106 sizeof(ptr->fw_app_version));
107 ptr->fw_app_version = sys_be16_to_cpu(ptr->fw_app_version);
108 }
109 if (rc == 0) {
110 LOG_INF("HW %x FW %x APP %x",
111 ptr->hw_version, ptr->fw_boot_version,
112 ptr->fw_app_version);
113 }
114
115 set_wake(dev, false);
116 ptr->mode = drv_data->mode & CCS811_MODE_MSK;
117
118 return rc;
119 }
120
ccs811_baseline_fetch(const struct device * dev)121 int ccs811_baseline_fetch(const struct device *dev)
122 {
123 const uint8_t cmd = CCS811_REG_BASELINE;
124 const struct ccs811_config *config = dev->config;
125 int rc;
126 uint16_t baseline;
127
128 set_wake(dev, true);
129
130 rc = i2c_write_read_dt(&config->i2c, &cmd, sizeof(cmd), (uint8_t *)&baseline,
131 sizeof(baseline));
132 set_wake(dev, false);
133 if (rc <= 0) {
134 rc = baseline;
135 }
136
137 return rc;
138 }
139
ccs811_baseline_update(const struct device * dev,uint16_t baseline)140 int ccs811_baseline_update(const struct device *dev,
141 uint16_t baseline)
142 {
143 const struct ccs811_config *config = dev->config;
144 uint8_t buf[1 + sizeof(baseline)];
145 int rc;
146
147 buf[0] = CCS811_REG_BASELINE;
148 memcpy(buf + 1, &baseline, sizeof(baseline));
149 set_wake(dev, true);
150 rc = i2c_write_dt(&config->i2c, buf, sizeof(buf));
151 set_wake(dev, false);
152 return rc;
153 }
154
ccs811_envdata_update(const struct device * dev,const struct sensor_value * temperature,const struct sensor_value * humidity)155 int ccs811_envdata_update(const struct device *dev,
156 const struct sensor_value *temperature,
157 const struct sensor_value *humidity)
158 {
159 const struct ccs811_config *config = dev->config;
160 int rc;
161 uint8_t buf[5] = { CCS811_REG_ENV_DATA };
162
163 /*
164 * Environment data are represented in a broken whole/fraction
165 * system that specified a 9-bit fractional part to represent
166 * milli-units. Since 1000 is greater than 512, the device
167 * actually only pays attention to the top bit, treating it as
168 * indicating 0.5. So we only write the first octet (7-bit
169 * while plus 1-bit half).
170 *
171 * Humidity is simple: scale it by two and round to the
172 * nearest half. Assume the fractional part is not
173 * negative.
174 */
175 if (humidity) {
176 int value = 2 * humidity->val1;
177
178 value += (250000 + humidity->val2) / 500000;
179 if (value < 0) {
180 value = 0;
181 } else if (value > (2 * 100)) {
182 value = 2 * 100;
183 }
184 LOG_DBG("HUM %d.%06d becomes %d",
185 humidity->val1, humidity->val2, value);
186 buf[1] = value;
187 } else {
188 buf[1] = 2 * 50;
189 }
190
191 /*
192 * Temperature is offset from -25 Cel. Values below minimum
193 * store as zero. Default is 25 Cel. Again we round to the
194 * nearest half, complicated by Zephyr's signed representation
195 * of the fractional part.
196 */
197 if (temperature) {
198 int value = 2 * temperature->val1;
199
200 if (temperature->val2 < 0) {
201 value += (250000 + temperature->val2) / 500000;
202 } else {
203 value += (-250000 + temperature->val2) / 500000;
204 }
205 if (value < (2 * -25)) {
206 value = 0;
207 } else {
208 value += 2 * 25;
209 }
210 LOG_DBG("TEMP %d.%06d becomes %d",
211 temperature->val1, temperature->val2, value);
212 buf[3] = value;
213 } else {
214 buf[3] = 2 * (25 + 25);
215 }
216
217 set_wake(dev, true);
218 rc = i2c_write_dt(&config->i2c, buf, sizeof(buf));
219 set_wake(dev, false);
220 return rc;
221 }
222
ccs811_sample_fetch(const struct device * dev,enum sensor_channel chan)223 static int ccs811_sample_fetch(const struct device *dev,
224 enum sensor_channel chan)
225 {
226 struct ccs811_data *drv_data = dev->data;
227 const struct ccs811_config *config = dev->config;
228 struct ccs811_result_type *rp = &drv_data->result;
229 const uint8_t cmd = CCS811_REG_ALG_RESULT_DATA;
230 int rc;
231 uint16_t buf[4] = { 0 };
232 unsigned int status;
233
234 set_wake(dev, true);
235 rc = i2c_write_read_dt(&config->i2c, &cmd, sizeof(cmd), (uint8_t *)buf, sizeof(buf));
236 set_wake(dev, false);
237 if (rc < 0) {
238 return -EIO;
239 }
240
241 rp->co2 = sys_be16_to_cpu(buf[0]);
242 rp->voc = sys_be16_to_cpu(buf[1]);
243 status = sys_le16_to_cpu(buf[2]); /* sic */
244 rp->status = status;
245 rp->error = error_from_status(status);
246 rp->raw = sys_be16_to_cpu(buf[3]);
247
248 /* APP FW 1.1 does not set DATA_READY, but it does set CO2 to
249 * zero while it's starting up. Assume a non-zero CO2 with
250 * old firmware is valid for the purposes of claiming the
251 * fetch was fresh.
252 */
253 if ((drv_data->app_fw_ver <= 0x11)
254 && (rp->co2 != 0)) {
255 status |= CCS811_STATUS_DATA_READY;
256 }
257 return (status & CCS811_STATUS_DATA_READY) ? 0 : -EAGAIN;
258 }
259
ccs811_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)260 static int ccs811_channel_get(const struct device *dev,
261 enum sensor_channel chan,
262 struct sensor_value *val)
263 {
264 struct ccs811_data *drv_data = dev->data;
265 const struct ccs811_result_type *rp = &drv_data->result;
266 uint32_t uval;
267
268 switch (chan) {
269 case SENSOR_CHAN_CO2:
270 val->val1 = rp->co2;
271 val->val2 = 0;
272
273 break;
274 case SENSOR_CHAN_VOC:
275 val->val1 = rp->voc;
276 val->val2 = 0;
277
278 break;
279 case SENSOR_CHAN_VOLTAGE:
280 /*
281 * Raw ADC readings are contained in least significant 10 bits
282 */
283 uval = ((rp->raw & CCS811_RAW_VOLTAGE_MSK)
284 >> CCS811_RAW_VOLTAGE_POS) * CCS811_RAW_VOLTAGE_SCALE;
285 val->val1 = uval / 1000000U;
286 val->val2 = uval % 1000000;
287
288 break;
289 case SENSOR_CHAN_CURRENT:
290 /*
291 * Current readings are contained in most
292 * significant 6 bits in microAmps
293 */
294 uval = ((rp->raw & CCS811_RAW_CURRENT_MSK)
295 >> CCS811_RAW_CURRENT_POS) * CCS811_RAW_CURRENT_SCALE;
296 val->val1 = uval / 1000000U;
297 val->val2 = uval % 1000000;
298
299 break;
300 default:
301 return -ENOTSUP;
302 }
303
304 return 0;
305 }
306
307 static const struct sensor_driver_api ccs811_driver_api = {
308 #ifdef CONFIG_CCS811_TRIGGER
309 .attr_set = ccs811_attr_set,
310 .trigger_set = ccs811_trigger_set,
311 #endif
312 .sample_fetch = ccs811_sample_fetch,
313 .channel_get = ccs811_channel_get,
314 };
315
switch_to_app_mode(const struct device * dev)316 static int switch_to_app_mode(const struct device *dev)
317 {
318 const struct ccs811_config *config = dev->config;
319 uint8_t buf;
320 int status;
321
322 LOG_DBG("Switching to Application mode...");
323
324 status = fetch_status(dev);
325 if (status < 0) {
326 return -EIO;
327 }
328
329 /* Check for the application firmware */
330 if (!(status & CCS811_STATUS_APP_VALID)) {
331 LOG_ERR("No Application firmware loaded");
332 return -EINVAL;
333 }
334
335 /* Check if already in application mode */
336 if (status & CCS811_STATUS_FW_MODE) {
337 LOG_DBG("CCS811 Already in application mode");
338 return 0;
339 }
340
341 buf = CCS811_REG_APP_START;
342 /* Set the device to application mode */
343 if (i2c_write_dt(&config->i2c, &buf, 1) < 0) {
344 LOG_ERR("Failed to set Application mode");
345 return -EIO;
346 }
347
348 k_msleep(1); /* t_APP_START */
349 status = fetch_status(dev);
350 if (status < 0) {
351 return -EIO;
352 }
353
354 /* Check for application mode */
355 if (!(status & CCS811_STATUS_FW_MODE)) {
356 LOG_ERR("Failed to start Application firmware");
357 return -EINVAL;
358 }
359
360 LOG_DBG("CCS811 Application firmware started!");
361
362 return 0;
363 }
364
365 #ifdef CONFIG_CCS811_TRIGGER
366
ccs811_mutate_meas_mode(const struct device * dev,uint8_t set,uint8_t clear)367 int ccs811_mutate_meas_mode(const struct device *dev,
368 uint8_t set,
369 uint8_t clear)
370 {
371 struct ccs811_data *drv_data = dev->data;
372 const struct ccs811_config *config = dev->config;
373 int rc = 0;
374 uint8_t mode = set | (drv_data->mode & ~clear);
375
376 /*
377 * Changing drive mode of a running system has preconditions.
378 * Only allow changing the interrupt generation.
379 */
380 if ((set | clear) & ~(CCS811_MODE_DATARDY | CCS811_MODE_THRESH)) {
381 return -EINVAL;
382 }
383
384 if (mode != drv_data->mode) {
385 set_wake(dev, true);
386 rc = i2c_reg_write_byte_dt(&config->i2c, CCS811_REG_MEAS_MODE, mode);
387 LOG_DBG("CCS811 meas mode change %02x to %02x got %d",
388 drv_data->mode, mode, rc);
389 if (rc < 0) {
390 LOG_ERR("Failed to set mode");
391 rc = -EIO;
392 } else {
393 drv_data->mode = mode;
394 rc = 0;
395 }
396
397 set_wake(dev, false);
398 }
399
400 return rc;
401 }
402
ccs811_set_thresholds(const struct device * dev)403 int ccs811_set_thresholds(const struct device *dev)
404 {
405 struct ccs811_data *drv_data = dev->data;
406 const struct ccs811_config *config = dev->config;
407 const uint8_t buf[5] = {
408 CCS811_REG_THRESHOLDS,
409 drv_data->co2_l2m >> 8,
410 drv_data->co2_l2m,
411 drv_data->co2_m2h >> 8,
412 drv_data->co2_m2h,
413 };
414 int rc;
415
416 set_wake(dev, true);
417 rc = i2c_write_dt(&config->i2c, buf, sizeof(buf));
418 set_wake(dev, false);
419 return rc;
420 }
421
422 #endif /* CONFIG_CCS811_TRIGGER */
423
ccs811_init(const struct device * dev)424 static int ccs811_init(const struct device *dev)
425 {
426 struct ccs811_data *drv_data = dev->data;
427 const struct ccs811_config *config = dev->config;
428 int ret = 0;
429 int status;
430 uint16_t fw_ver;
431 uint8_t cmd;
432 uint8_t hw_id;
433
434 if (!device_is_ready(config->i2c.bus)) {
435 LOG_ERR("I2C bus device not ready");
436 return -ENODEV;
437 }
438
439 if (config->wake_gpio.port) {
440 if (!gpio_is_ready_dt(&config->wake_gpio)) {
441 LOG_ERR("GPIO device not ready");
442 return -ENODEV;
443 }
444
445 /*
446 * Wakeup pin should be pulled low before initiating
447 * any I2C transfer. If it has been tied to GND by
448 * default, skip this part.
449 */
450 gpio_pin_configure_dt(&config->wake_gpio, GPIO_OUTPUT_INACTIVE);
451
452 set_wake(dev, true);
453 k_msleep(1);
454 }
455
456 if (config->reset_gpio.port) {
457 if (!gpio_is_ready_dt(&config->reset_gpio)) {
458 LOG_ERR("GPIO device not ready");
459 return -ENODEV;
460 }
461
462 gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_ACTIVE);
463
464 k_msleep(1);
465 }
466
467 if (config->irq_gpio.port) {
468 if (!gpio_is_ready_dt(&config->irq_gpio)) {
469 LOG_ERR("GPIO device not ready");
470 return -ENODEV;
471 }
472 }
473
474 k_msleep(20); /* t_START assuming recent power-on */
475
476 /* Reset the device. This saves having to deal with detecting
477 * and validating any errors or configuration inconsistencies
478 * after a reset that left the device running.
479 */
480 if (config->reset_gpio.port) {
481 gpio_pin_set_dt(&config->reset_gpio, 1);
482 k_busy_wait(15); /* t_RESET */
483 gpio_pin_set_dt(&config->reset_gpio, 0);
484 } else {
485 static uint8_t const reset_seq[] = {
486 0xFF, 0x11, 0xE5, 0x72, 0x8A,
487 };
488
489 if (i2c_write_dt(&config->i2c, reset_seq, sizeof(reset_seq)) < 0) {
490 LOG_ERR("Failed to issue SW reset");
491 ret = -EIO;
492 goto out;
493 }
494 }
495
496 k_msleep(2); /* t_START after reset */
497
498 /* Switch device to application mode */
499 ret = switch_to_app_mode(dev);
500 if (ret) {
501 goto out;
502 }
503
504 /* Check Hardware ID */
505 if (i2c_reg_read_byte_dt(&config->i2c, CCS811_REG_HW_ID, &hw_id) < 0) {
506 LOG_ERR("Failed to read Hardware ID register");
507 ret = -EIO;
508 goto out;
509 }
510
511 if (hw_id != CCS881_HW_ID) {
512 LOG_ERR("Hardware ID mismatch!");
513 ret = -EINVAL;
514 goto out;
515 }
516
517 /* Check application firmware version (first byte) */
518 cmd = CCS811_REG_FW_APP_VERSION;
519 if (i2c_write_read_dt(&config->i2c, &cmd, sizeof(cmd), &fw_ver, sizeof(fw_ver)) < 0) {
520 LOG_ERR("Failed to read App Firmware Version register");
521 ret = -EIO;
522 goto out;
523 }
524 fw_ver = sys_be16_to_cpu(fw_ver);
525 LOG_INF("App FW %04x", fw_ver);
526 drv_data->app_fw_ver = fw_ver >> 8U;
527
528 /* Configure measurement mode */
529 uint8_t meas_mode = CCS811_MODE_IDLE;
530 #ifdef CONFIG_CCS811_DRIVE_MODE_1
531 meas_mode = CCS811_MODE_IAQ_1SEC;
532 #elif defined(CONFIG_CCS811_DRIVE_MODE_2)
533 meas_mode = CCS811_MODE_IAQ_10SEC;
534 #elif defined(CONFIG_CCS811_DRIVE_MODE_3)
535 meas_mode = CCS811_MODE_IAQ_60SEC;
536 #elif defined(CONFIG_CCS811_DRIVE_MODE_4)
537 meas_mode = CCS811_MODE_IAQ_250MSEC;
538 #endif
539 if (i2c_reg_write_byte_dt(&config->i2c, CCS811_REG_MEAS_MODE, meas_mode) < 0) {
540 LOG_ERR("Failed to set Measurement mode");
541 ret = -EIO;
542 goto out;
543 }
544 drv_data->mode = meas_mode;
545
546 /* Check for error */
547 status = fetch_status(dev);
548 if (status < 0) {
549 ret = -EIO;
550 goto out;
551 }
552
553 if (status & CCS811_STATUS_ERROR) {
554 LOG_ERR("CCS811 Error %02x during sensor configuration",
555 error_from_status(status));
556 ret = -EINVAL;
557 goto out;
558 }
559
560 #ifdef CONFIG_CCS811_TRIGGER
561 if (config->irq_gpio.port) {
562 ret = ccs811_init_interrupt(dev);
563 LOG_DBG("CCS811 interrupt init got %d", ret);
564 }
565 #endif
566
567 out:
568 set_wake(dev, false);
569 return ret;
570 }
571
572 #define CCS811_DEFINE(inst) \
573 static struct ccs811_data ccs811_data_##inst; \
574 \
575 static const struct ccs811_config ccs811_config_##inst = { \
576 .i2c = I2C_DT_SPEC_INST_GET(inst), \
577 IF_ENABLED(CONFIG_CCS811_TRIGGER, \
578 (.irq_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, irq_gpios, { 0 }),)) \
579 .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, { 0 }), \
580 .wake_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, wake_gpios, { 0 }), \
581 }; \
582 \
583 SENSOR_DEVICE_DT_INST_DEFINE(0, ccs811_init, NULL, \
584 &ccs811_data_##inst, &ccs811_config_##inst, \
585 POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \
586 &ccs811_driver_api); \
587
588 DT_INST_FOREACH_STATUS_OKAY(CCS811_DEFINE)
589