Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2018 Linaro Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <device.h>
#include <gpio.h>
#include <i2c.h>
#include <kernel.h>
#include <misc/byteorder.h>
#include <misc/util.h>
#include <sensor.h>
#include <misc/__assert.h>
#include <logging/log.h>

#include "ccs811.h"

#define LOG_LEVEL CONFIG_SENSOR_LOG_LEVEL
LOG_MODULE_REGISTER(CCS811);

static int ccs811_sample_fetch(struct device *dev, enum sensor_channel chan)
{
	struct ccs811_data *drv_data = dev->driver_data;
	int tries = 11;
	u16_t buf[4];
	u8_t status;

	/* Check data ready flag for the measurement interval of 1 seconds */
	while (tries-- > 0) {
		if (i2c_reg_read_byte(drv_data->i2c, DT_AMS_CCS811_0_BASE_ADDRESS,
				      CCS811_REG_STATUS, &status) < 0) {
			LOG_ERR("Failed to read Status register");
			return -EIO;
		}

		if ((status & CCS811_STATUS_DATA_READY) || tries == 0) {
			break;
		}

		k_sleep(100);
	}

	if (!(status & CCS811_STATUS_DATA_READY)) {
		LOG_ERR("Sensor data not available");
		return -EIO;
	}

	if (i2c_burst_read(drv_data->i2c, DT_AMS_CCS811_0_BASE_ADDRESS,
			   CCS811_REG_ALG_RESULT_DATA, (u8_t *)buf, 8) < 0) {
		LOG_ERR("Failed to read conversion data.");
		return -EIO;
	}

	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]);

	return 0;
}

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_AMS_CCS811_0_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;
	}

	buf = CCS811_REG_APP_START;
	/* Set the device to application mode */
	if (i2c_write(i2c, &buf, 1, DT_AMS_CCS811_0_BASE_ADDRESS) < 0) {
		LOG_ERR("Failed to set Application mode");
		return -EIO;
	}

	if (i2c_reg_read_byte(i2c, DT_AMS_CCS811_0_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_AMS_CCS811_0_BUS_NAME);
	if (drv_data->i2c == NULL) {
		LOG_ERR("Failed to get pointer to %s device!",
			    DT_AMS_CCS811_0_BUS_NAME);
		return -EINVAL;
	}

#if defined(CONFIG_CCS811_GPIO_WAKEUP) || defined(CONFIG_CCS811_GPIO_RESET)
	drv_data->gpio = device_get_binding(CONFIG_CCS811_GPIO_DEV_NAME);
	if (drv_data->gpio == NULL) {
		LOG_ERR("Failed to get pointer to %s device!",
			    CONFIG_CCS811_GPIO_DEV_NAME);
		return -EINVAL;
	}
#endif

#ifdef CONFIG_CCS811_GPIO_RESET
	gpio_pin_configure(drv_data->gpio, CONFIG_CCS811_GPIO_RESET_PIN_NUM,
			   GPIO_DIR_OUT);
	gpio_pin_write(drv_data->gpio, CONFIG_CCS811_GPIO_RESET_PIN_NUM, 1);

	k_sleep(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 CONFIG_CCS811_GPIO_WAKEUP
	gpio_pin_configure(drv_data->gpio, CONFIG_CCS811_GPIO_WAKEUP_PIN_NUM,
			   GPIO_DIR_OUT);
	gpio_pin_write(drv_data->gpio, CONFIG_CCS811_GPIO_WAKEUP_PIN_NUM, 0);

	k_sleep(1);
#endif

	/* Switch device to application mode */
	ret = switch_to_app_mode(drv_data->i2c);
	if (ret) {
		return ret;
	}

	/* Check Hardware ID */
	if (i2c_reg_read_byte(drv_data->i2c, DT_AMS_CCS811_0_BASE_ADDRESS,
			      CCS811_REG_HW_ID, &hw_id) < 0) {
		LOG_ERR("Failed to read Hardware ID register");
		return -EIO;
	}

	if (hw_id != CCS881_HW_ID) {
		LOG_ERR("Hardware ID mismatch!");
		return -EINVAL;
	}

	/* Set Measurement mode for 1 second */
	if (i2c_reg_write_byte(drv_data->i2c, DT_AMS_CCS811_0_BASE_ADDRESS,
			       CCS811_REG_MEAS_MODE,
			       CCS811_MODE_IAQ_1SEC) < 0) {
		LOG_ERR("Failed to set Measurement mode");
		return -EIO;
	}

	/* Check for error */
	if (i2c_reg_read_byte(drv_data->i2c, DT_AMS_CCS811_0_BASE_ADDRESS,
			      CCS811_REG_STATUS, &status) < 0) {
		LOG_ERR("Failed to read Status register");
		return -EIO;
	}

	if (status & CCS811_STATUS_ERROR) {
		LOG_ERR("Error occurred during sensor configuration");
		return -EINVAL;
	}

	return 0;
}

static struct ccs811_data ccs811_driver;

DEVICE_AND_API_INIT(ccs811, DT_AMS_CCS811_0_LABEL, ccs811_init, &ccs811_driver,
		    NULL, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY,
		    &ccs811_driver_api);