zephyr/drivers/sensor/ccs811/ccs811.c
Peter A. Bigot 463bec1749 drivers: sensor: ccs811: bypass APP_START when not in boot mode
The CCS811 can be in either boot mode (after powerup) or application
mode, and these modes have distinct register maps that share only some
content.  The register written to switch from boot mode to application
mode is not available in application mode, so don't write to it in that
case.

Also respect the required timeout between APP_START and next I2C
operation.

Signed-off-by: Peter A. Bigot <pab@pabigot.com>
2020-01-14 15:44:09 -06:00

316 lines
7.4 KiB
C

/*
* Copyright (c) 2018 Peter Bigot Consulting, LLC
* Copyright (c) 2018 Linaro Ltd.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <device.h>
#include <drivers/gpio.h>
#include <drivers/i2c.h>
#include <kernel.h>
#include <sys/byteorder.h>
#include <sys/util.h>
#include <drivers/sensor.h>
#include <sys/__assert.h>
#include <logging/log.h>
#include "ccs811.h"
LOG_MODULE_REGISTER(CCS811, CONFIG_SENSOR_LOG_LEVEL);
#ifdef DT_INST_0_AMS_CCS811_WAKE_GPIOS_CONTROLLER
static void set_wake(struct ccs811_data *drv_data, bool enable)
{
/* Always active-low */
gpio_pin_write(drv_data->gpio, DT_INST_0_AMS_CCS811_WAKE_GPIOS_PIN, !enable);
if (enable) {
k_busy_wait(50); /* t_WAKE = 50 us */
} else {
k_busy_wait(20); /* t_DWAKE = 20 us */
}
}
#else
#define set_wake(...)
#endif
static int ccs811_sample_fetch(struct device *dev, enum sensor_channel chan)
{
struct ccs811_data *drv_data = dev->driver_data;
int tries;
u16_t buf[4];
u8_t status;
int rv = -EIO;
/* Check data ready flag for the measurement interval */
#ifdef CONFIG_CCS811_DRIVE_MODE_1
tries = 11;
#elif defined(CONFIG_CCS811_DRIVE_MODE_2)
tries = 101;
#elif defined(CONFIG_CCS811_DRIVE_MODE_3)
tries = 601;
#endif
while (tries-- > 0) {
set_wake(drv_data, true);
if (i2c_reg_read_byte(drv_data->i2c, DT_INST_0_AMS_CCS811_BASE_ADDRESS,
CCS811_REG_STATUS, &status) < 0) {
LOG_ERR("Failed to read Status register");
goto out;
}
if ((status & CCS811_STATUS_DATA_READY) || tries == 0) {
break;
}
set_wake(drv_data, false);
k_sleep(K_MSEC(100));
}
if (!(status & CCS811_STATUS_DATA_READY)) {
LOG_ERR("Sensor data not available");
goto out;
}
if (i2c_burst_read(drv_data->i2c, DT_INST_0_AMS_CCS811_BASE_ADDRESS,
CCS811_REG_ALG_RESULT_DATA, (u8_t *)buf, 8) < 0) {
LOG_ERR("Failed to read conversion data.");
goto out;
}
drv_data->co2 = sys_be16_to_cpu(buf[0]);
drv_data->voc = sys_be16_to_cpu(buf[1]);
drv_data->status = buf[2] & 0xff;
drv_data->error = buf[2] >> 8;
drv_data->resistance = sys_be16_to_cpu(buf[3]);
rv = 0;
out:
set_wake(drv_data, false);
return rv;
}
static int ccs811_channel_get(struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
struct ccs811_data *drv_data = dev->driver_data;
u32_t uval;
switch (chan) {
case SENSOR_CHAN_CO2:
val->val1 = drv_data->co2;
val->val2 = 0;
break;
case SENSOR_CHAN_VOC:
val->val1 = drv_data->voc;
val->val2 = 0;
break;
case SENSOR_CHAN_VOLTAGE:
/*
* Raw ADC readings are contained in least significant 10 bits
*/
uval = (drv_data->resistance & CCS811_VOLTAGE_MASK)
* CCS811_VOLTAGE_SCALE;
val->val1 = uval / 1000000U;
val->val2 = uval % 1000000;
break;
case SENSOR_CHAN_CURRENT:
/*
* Current readings are contained in most
* significant 6 bits in microAmps
*/
uval = drv_data->resistance >> 10;
val->val1 = uval / 1000000U;
val->val2 = uval % 1000000;
break;
default:
return -ENOTSUP;
}
return 0;
}
static const struct sensor_driver_api ccs811_driver_api = {
.sample_fetch = ccs811_sample_fetch,
.channel_get = ccs811_channel_get,
};
static int switch_to_app_mode(struct device *i2c)
{
u8_t status, buf;
LOG_DBG("Switching to Application mode...");
if (i2c_reg_read_byte(i2c, DT_INST_0_AMS_CCS811_BASE_ADDRESS,
CCS811_REG_STATUS, &status) < 0) {
LOG_ERR("Failed to read Status register");
return -EIO;
}
/* Check for the application firmware */
if (!(status & CCS811_STATUS_APP_VALID)) {
LOG_ERR("No Application firmware loaded");
return -EINVAL;
}
/* Check if already in application mode */
if (status & CCS811_STATUS_FW_MODE) {
LOG_DBG("CCS811 Already in application mode");
return 0;
}
buf = CCS811_REG_APP_START;
/* Set the device to application mode */
if (i2c_write(i2c, &buf, 1, DT_INST_0_AMS_CCS811_BASE_ADDRESS) < 0) {
LOG_ERR("Failed to set Application mode");
return -EIO;
}
k_sleep(1); /* t_APP_START */
if (i2c_reg_read_byte(i2c, DT_INST_0_AMS_CCS811_BASE_ADDRESS,
CCS811_REG_STATUS, &status) < 0) {
LOG_ERR("Failed to read Status register");
return -EIO;
}
/* Check for application mode */
if (!(status & CCS811_STATUS_FW_MODE)) {
LOG_ERR("Failed to start Application firmware");
return -EINVAL;
}
LOG_DBG("CCS811 Application firmware started!");
return 0;
}
int ccs811_init(struct device *dev)
{
struct ccs811_data *drv_data = dev->driver_data;
int ret;
u8_t hw_id, status;
drv_data->i2c = device_get_binding(DT_INST_0_AMS_CCS811_BUS_NAME);
if (drv_data->i2c == NULL) {
LOG_ERR("Failed to get pointer to %s device!",
DT_INST_0_AMS_CCS811_BUS_NAME);
return -EINVAL;
}
struct device *gpio = NULL;
(void)gpio;
#ifdef DT_INST_0_AMS_CCS811_WAKE_GPIOS_CONTROLLER
gpio = device_get_binding(DT_INST_0_AMS_CCS811_WAKE_GPIOS_CONTROLLER);
if (gpio == NULL) {
LOG_ERR("Failed to get pointer to WAKE device: %s",
DT_INST_0_AMS_CCS811_WAKE_GPIOS_CONTROLLER);
return -EINVAL;
}
drv_data->gpio = gpio;
#endif
#ifdef DT_INST_0_AMS_CCS811_RESET_GPIOS_CONTROLLER
gpio = device_get_binding(DT_INST_0_AMS_CCS811_RESET_GPIOS_CONTROLLER);
if (gpio == NULL) {
LOG_ERR("Failed to get pointer to RESET device: %s",
DT_INST_0_AMS_CCS811_RESET_GPIOS_CONTROLLER);
return -EINVAL;
}
if (drv_data->gpio == NULL) {
drv_data->gpio = gpio;
} else if (drv_data->gpio != gpio) {
LOG_ERR("Crossing GPIO devices not supported");
return -EINVAL;
}
#endif
if (drv_data->gpio) {
#ifdef DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN
gpio_pin_configure(drv_data->gpio, DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN,
GPIO_DIR_OUT);
gpio_pin_write(drv_data->gpio, DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN, 1);
k_sleep(K_MSEC(1));
#endif
/*
* Wakeup pin should be pulled low before initiating
* any I2C transfer. If it has been tied to GND by
* default, skip this part.
*/
#ifdef DT_INST_0_AMS_CCS811_WAKE_GPIOS_PIN
gpio_pin_configure(drv_data->gpio, DT_INST_0_AMS_CCS811_WAKE_GPIOS_PIN,
GPIO_DIR_OUT);
set_wake(drv_data, true);
k_sleep(K_MSEC(1));
#endif
}
/* Switch device to application mode */
ret = switch_to_app_mode(drv_data->i2c);
if (ret) {
goto out;
}
/* Check Hardware ID */
if (i2c_reg_read_byte(drv_data->i2c, DT_INST_0_AMS_CCS811_BASE_ADDRESS,
CCS811_REG_HW_ID, &hw_id) < 0) {
LOG_ERR("Failed to read Hardware ID register");
ret = -EIO;
goto out;
}
if (hw_id != CCS881_HW_ID) {
LOG_ERR("Hardware ID mismatch!");
ret = -EINVAL;
goto out;
}
/* Configure measurement mode */
u8_t meas_mode = 0;
#ifdef CONFIG_CCS811_DRIVE_MODE_1
meas_mode = 0x10;
#elif defined(CONFIG_CCS811_DRIVE_MODE_2)
meas_mode = 0x20;
#elif defined(CONFIG_CCS811_DRIVE_MODE_3)
meas_mode = 0x30;
#endif
if (i2c_reg_write_byte(drv_data->i2c, DT_INST_0_AMS_CCS811_BASE_ADDRESS,
CCS811_REG_MEAS_MODE,
meas_mode) < 0) {
LOG_ERR("Failed to set Measurement mode");
ret = -EIO;
goto out;
}
/* Check for error */
if (i2c_reg_read_byte(drv_data->i2c, DT_INST_0_AMS_CCS811_BASE_ADDRESS,
CCS811_REG_STATUS, &status) < 0) {
LOG_ERR("Failed to read Status register");
ret = -EIO;
goto out;
}
if (status & CCS811_STATUS_ERROR) {
LOG_ERR("Error occurred during sensor configuration");
ret = -EINVAL;
goto out;
}
out:
set_wake(drv_data, false);
return ret;
}
static struct ccs811_data ccs811_driver;
DEVICE_AND_API_INIT(ccs811, DT_INST_0_AMS_CCS811_LABEL, ccs811_init, &ccs811_driver,
NULL, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY,
&ccs811_driver_api);