diff --git a/drivers/sensor/ccs811/CMakeLists.txt b/drivers/sensor/ccs811/CMakeLists.txt index b694cc78ff5..8e5a695f0ca 100644 --- a/drivers/sensor/ccs811/CMakeLists.txt +++ b/drivers/sensor/ccs811/CMakeLists.txt @@ -3,3 +3,4 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_CCS811 ccs811.c) +zephyr_library_sources_ifdef(CONFIG_CCS811_TRIGGER ccs811_trigger.c) diff --git a/drivers/sensor/ccs811/Kconfig b/drivers/sensor/ccs811/Kconfig index 0b160a72a1b..dc3844182d1 100644 --- a/drivers/sensor/ccs811/Kconfig +++ b/drivers/sensor/ccs811/Kconfig @@ -33,4 +33,42 @@ config CCS811_DRIVE_MODE_3 endchoice +config CCS811_TRIGGER + bool + +choice + prompt "Trigger mode" + default CCS811_TRIGGER_NONE + help + Specify the type of triggering to be used by the driver. + +config CCS811_TRIGGER_NONE + bool "No trigger" + +config CCS811_TRIGGER_GLOBAL_THREAD + bool "Use global thread" + depends on GPIO + select CCS811_TRIGGER + +config CCS811_TRIGGER_OWN_THREAD + bool "Use own thread" + depends on GPIO + select CCS811_TRIGGER + +endchoice + +config CCS811_THREAD_PRIORITY + int "Thread priority" + depends on CCS811_TRIGGER_OWN_THREAD + default 10 + help + Priority of thread used by the driver to handle interrupts. + +config CCS811_THREAD_STACK_SIZE + int "Thread stack size" + depends on CCS811_TRIGGER_OWN_THREAD + default 1024 + help + Stack size of thread used by the driver to handle interrupts. + endif # CCS811 diff --git a/drivers/sensor/ccs811/ccs811.c b/drivers/sensor/ccs811/ccs811.c index 9dd00df36be..69d94783c62 100644 --- a/drivers/sensor/ccs811/ccs811.c +++ b/drivers/sensor/ccs811/ccs811.c @@ -23,7 +23,7 @@ LOG_MODULE_REGISTER(CCS811, CONFIG_SENSOR_LOG_LEVEL); 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); + gpio_pin_write(drv_data->wake_gpio, DT_INST_0_AMS_CCS811_WAKE_GPIOS_PIN, !enable); if (enable) { k_busy_wait(50); /* t_WAKE = 50 us */ } else { @@ -170,6 +170,10 @@ static int ccs811_channel_get(struct device *dev, } static const struct sensor_driver_api ccs811_driver_api = { +#ifdef CONFIG_CCS811_TRIGGER + .attr_set = ccs811_attr_set, + .trigger_set = ccs811_trigger_set, +#endif .sample_fetch = ccs811_sample_fetch, .channel_get = ccs811_channel_get, }; @@ -222,6 +226,65 @@ static int switch_to_app_mode(struct device *i2c) return 0; } +#ifdef CONFIG_CCS811_TRIGGER + +int ccs811_mutate_meas_mode(struct device *dev, + u8_t set, + u8_t clear) +{ + struct ccs811_data *drv_data = dev->driver_data; + int rc = 0; + u8_t mode = set | (drv_data->mode & ~clear); + + /* + * Changing drive mode of a running system has preconditions. + * Only allow changing the interrupt generation. + */ + if ((set | clear) & ~(CCS811_MODE_DATARDY | CCS811_MODE_THRESH)) { + return -EINVAL; + } + + if (mode != drv_data->mode) { + set_wake(drv_data, true); + rc = i2c_reg_write_byte(drv_data->i2c, DT_INST_0_AMS_CCS811_BASE_ADDRESS, + CCS811_REG_MEAS_MODE, + mode); + LOG_DBG("CCS811 meas mode change %02x to %02x got %d", + drv_data->mode, mode, rc); + if (rc < 0) { + LOG_ERR("Failed to set mode"); + rc = -EIO; + } else { + drv_data->mode = mode; + rc = 0; + } + + set_wake(drv_data, false); + } + + return rc; +} + +int ccs811_set_thresholds(struct device *dev) +{ + struct ccs811_data *drv_data = dev->driver_data; + const u8_t buf[5] = { + CCS811_REG_THRESHOLDS, + drv_data->co2_l2m >> 8, + drv_data->co2_l2m, + drv_data->co2_m2h >> 8, + drv_data->co2_m2h, + }; + int rc; + + set_wake(drv_data, true); + rc = i2c_write(drv_data->i2c, buf, sizeof(buf), DT_INST_0_AMS_CCS811_BASE_ADDRESS); + set_wake(drv_data, false); + return rc; +} + +#endif /* CONFIG_CCS811_TRIGGER */ + int ccs811_init(struct device *dev) { struct ccs811_data *drv_data = dev->driver_data; @@ -229,6 +292,7 @@ int ccs811_init(struct device *dev) u8_t hw_id; int status; + *drv_data = (struct ccs811_data){ 0 }; 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!", @@ -236,65 +300,56 @@ int ccs811_init(struct device *dev) 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) { + drv_data->wake_gpio = device_get_binding(DT_INST_0_AMS_CCS811_WAKE_GPIOS_CONTROLLER); + if (drv_data->wake_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; + /* + * Wakeup pin should be pulled low before initiating + * any I2C transfer. If it has been tied to GND by + * default, skip this part. + */ + gpio_pin_configure(drv_data->wake_gpio, DT_INST_0_AMS_CCS811_WAKE_GPIOS_PIN, + GPIO_DIR_OUT); + + set_wake(drv_data, true); + k_sleep(1); #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) { + drv_data->reset_gpio = device_get_binding(DT_INST_0_AMS_CCS811_RESET_GPIOS_CONTROLLER); + if (drv_data->reset_gpio == NULL) { LOG_ERR("Failed to get pointer to RESET device: %s", DT_INST_0_AMS_CCS811_RESET_GPIOS_CONTROLLER); return -EINVAL; } + gpio_pin_configure(drv_data->reset_gpio, DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN, + GPIO_DIR_OUT); + gpio_pin_write(drv_data->reset_gpio, DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN, 1); - if (drv_data->gpio == NULL) { - drv_data->gpio = gpio; - } else if (drv_data->gpio != gpio) { - LOG_ERR("Crossing GPIO devices not supported"); + k_sleep(1); +#endif + +#ifdef DT_INST_0_AMS_CCS811_IRQ_GPIOS_CONTROLLER + drv_data->int_gpio = device_get_binding(DT_INST_0_AMS_CCS811_IRQ_GPIOS_CONTROLLER); + if (drv_data->int_gpio == NULL) { + LOG_ERR("Failed to get pointer to INT device: %s", + DT_INST_0_AMS_CCS811_IRQ_GPIOS_CONTROLLER); 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 - } - /* Reset the device. This saves having to deal with detecting * and validating any errors or configuration inconsistencies * after a reset that left the device running. */ #ifdef DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN - gpio_pin_write(drv_data->gpio, DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN, 0); - k_busy_wait(15); /* t_RESET */ - gpio_pin_write(drv_data->gpio, DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN, 1); + gpio_pin_write(drv_data->reset_gpio, DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN, 0); + k_busy_wait(15); /* t_RESET */ + gpio_pin_write(drv_data->reset_gpio, DT_INST_0_AMS_CCS811_RESET_GPIOS_PIN, 1); #else { static u8_t const reset_seq[] = { @@ -347,6 +402,7 @@ int ccs811_init(struct device *dev) ret = -EIO; goto out; } + drv_data->mode = meas_mode; /* Check for error */ status = fetch_status(drv_data->i2c); @@ -362,6 +418,11 @@ int ccs811_init(struct device *dev) goto out; } +#ifdef CONFIG_CCS811_TRIGGER + ret = ccs811_init_interrupt(dev); + LOG_DBG("CCS811 interrupt init got %d", ret); +#endif + out: set_wake(drv_data, false); return ret; diff --git a/drivers/sensor/ccs811/ccs811.h b/drivers/sensor/ccs811/ccs811.h index 3def5b0744f..30390141ab2 100644 --- a/drivers/sensor/ccs811/ccs811.h +++ b/drivers/sensor/ccs811/ccs811.h @@ -17,6 +17,7 @@ #define CCS811_REG_MEAS_MODE 0x01 #define CCS811_REG_ALG_RESULT_DATA 0x02 #define CCS811_REG_RAW_DATA 0x03 +#define CCS811_REG_THRESHOLDS 0x10 #define CCS811_REG_HW_ID 0x20 #define CCS811_REG_HW_VERSION 0x21 #define CCS811_REG_HW_VERSION_MASK 0xF0 @@ -38,19 +39,73 @@ #define CCS811_MODE_IAQ_10SEC 0x20 #define CCS811_MODE_IAQ_60SEC 0x30 #define CCS811_MODE_RAW_DATA 0x40 +#define CCS811_MODE_DATARDY 0x08 +#define CCS811_MODE_THRESH 0x04 #define CCS811_VOLTAGE_SCALE 1613 #define CCS811_VOLTAGE_MASK 0x3FF +#define CCS811_CO2_MIN_PPM 400 +#define CCS811_CO2_MAX_PPM 32767 + struct ccs811_data { struct device *i2c; - struct device *gpio; +#ifdef DT_INST_0_AMS_CCS811_IRQ_GPIOS_CONTROLLER + struct device *int_gpio; +#ifdef CONFIG_CCS811_TRIGGER + /* + * DATARDY is configured through SENSOR_CHAN_ALL. + * THRESH would be configured through SENSOR_CHAN_CO2. + */ + struct gpio_callback gpio_cb; + sensor_trigger_handler_t handler; + struct sensor_trigger trigger; +#if defined(CONFIG_CCS811_TRIGGER_OWN_THREAD) + K_THREAD_STACK_MEMBER(thread_stack, CONFIG_CCS811_THREAD_STACK_SIZE); + struct k_sem gpio_sem; + struct k_thread thread; +#elif defined(CONFIG_CCS811_TRIGGER_GLOBAL_THREAD) + struct k_work work; + struct device *dev; +#endif + u16_t co2_l2m; + u16_t co2_m2h; +#endif /* CONFIG_CCS811_TRIGGER */ +#endif +#ifdef DT_INST_0_AMS_CCS811_RESET_GPIOS_CONTROLLER + struct device *reset_gpio; +#endif +#ifdef DT_INST_0_AMS_CCS811_WAKE_GPIOS_CONTROLLER + struct device *wake_gpio; +#endif u16_t co2; u16_t voc; + u16_t resistance; u8_t status; u8_t error; - u16_t resistance; + u8_t mode; }; -#endif /* _SENSOR_CCS811_ */ +#ifdef CONFIG_CCS811_TRIGGER + +int ccs811_mutate_meas_mode(struct device *dev, + u8_t set, + u8_t clear); + +int ccs811_set_thresholds(struct device *dev); + +int ccs811_attr_set(struct device *dev, + enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val); + +int ccs811_trigger_set(struct device *dev, + const struct sensor_trigger *trig, + sensor_trigger_handler_t handler); + +int ccs811_init_interrupt(struct device *dev); + +#endif /* CONFIG_CCS811_TRIGGER */ + +#endif /* _SENSOR_CCS811_ */ diff --git a/drivers/sensor/ccs811/ccs811_trigger.c b/drivers/sensor/ccs811/ccs811_trigger.c new file mode 100644 index 00000000000..c8b9ba3c440 --- /dev/null +++ b/drivers/sensor/ccs811/ccs811_trigger.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2018 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ccs811.h" + +#define LOG_LEVEL CONFIG_SENSOR_LOG_LEVEL +#include +LOG_MODULE_DECLARE(CCS811); + +int ccs811_attr_set(struct device *dev, + enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *thr) +{ + struct ccs811_data *drv_data = dev->driver_data; + int rc; + + if (chan != SENSOR_CHAN_CO2) { + rc = -ENOTSUP; + } else if (attr == SENSOR_ATTR_LOWER_THRESH) { + rc = -EINVAL; + if ((thr->val1 >= CCS811_CO2_MIN_PPM) + && (thr->val1 <= CCS811_CO2_MAX_PPM)) { + drv_data->co2_l2m = thr->val1; + rc = 0; + } + } else if (attr == SENSOR_ATTR_UPPER_THRESH) { + rc = -EINVAL; + if ((thr->val1 >= CCS811_CO2_MIN_PPM) + && (thr->val1 <= CCS811_CO2_MAX_PPM)) { + drv_data->co2_m2h = thr->val1; + rc = 0; + } + } else { + rc = -ENOTSUP; + } + return rc; +} + +static void gpio_callback(struct device *dev, + struct gpio_callback *cb, + u32_t pins) +{ + struct ccs811_data *drv_data = + CONTAINER_OF(cb, struct ccs811_data, gpio_cb); + + ARG_UNUSED(pins); + + gpio_pin_disable_callback(dev, DT_INST_0_AMS_CCS811_IRQ_GPIOS_PIN); + +#if defined(CONFIG_CCS811_TRIGGER_OWN_THREAD) + k_sem_give(&drv_data->gpio_sem); +#elif defined(CONFIG_CCS811_TRIGGER_GLOBAL_THREAD) + k_work_submit(&drv_data->work); +#else +#error Unhandled trigger configuration +#endif +} + +static void thread_cb(void *arg) +{ + struct device *dev = arg; + struct ccs811_data *drv_data = dev->driver_data; + + if (drv_data->handler != NULL) { + drv_data->handler(dev, &drv_data->trigger); + } + + gpio_pin_enable_callback(drv_data->int_gpio, DT_INST_0_AMS_CCS811_IRQ_GPIOS_PIN); +} + +#ifdef CONFIG_CCS811_TRIGGER_OWN_THREAD +static void datardy_thread(int dev_ptr, int unused) +{ + struct device *dev = INT_TO_POINTER(dev_ptr); + struct ccs811_data *drv_data = dev->driver_data; + + ARG_UNUSED(unused); + + while (1) { + k_sem_take(&drv_data->gpio_sem, K_FOREVER); + thread_cb(dev); + } +} +#elif defined(CONFIG_CCS811_TRIGGER_GLOBAL_THREAD) +static void work_cb(struct k_work *work) +{ + struct ccs811_data *drv_data = + CONTAINER_OF(work, struct ccs811_data, work); + + thread_cb(drv_data->dev); +} +#else +#error Unhandled trigger configuration +#endif + +int ccs811_trigger_set(struct device *dev, + const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) +{ + struct ccs811_data *drv_data = dev->driver_data; + u8_t drdy_thresh = CCS811_MODE_THRESH | CCS811_MODE_DATARDY; + int rc; + + LOG_DBG("CCS811 trigger set"); + gpio_pin_disable_callback(drv_data->int_gpio, DT_INST_0_AMS_CCS811_IRQ_GPIOS_PIN); + if (trig->type == SENSOR_TRIG_DATA_READY) { + rc = ccs811_mutate_meas_mode(dev, CCS811_MODE_DATARDY, + CCS811_MODE_THRESH); + } else if (trig->type == SENSOR_TRIG_THRESHOLD) { + rc = -EINVAL; + if ((drv_data->co2_l2m >= CCS811_CO2_MIN_PPM) + && (drv_data->co2_l2m <= CCS811_CO2_MAX_PPM) + && (drv_data->co2_m2h >= CCS811_CO2_MIN_PPM) + && (drv_data->co2_m2h <= CCS811_CO2_MAX_PPM) + && (drv_data->co2_l2m <= drv_data->co2_m2h)) { + rc = ccs811_set_thresholds(dev); + } + if (rc == 0) { + rc = ccs811_mutate_meas_mode(dev, drdy_thresh, 0); + } + } else { + rc = -ENOTSUP; + } + + if (rc == 0) { + drv_data->handler = handler; + drv_data->trigger = *trig; + gpio_pin_enable_callback(drv_data->int_gpio, + DT_INST_0_AMS_CCS811_IRQ_GPIOS_PIN); + } else { + (void)ccs811_mutate_meas_mode(dev, 0, drdy_thresh); + } + + return rc; +} + +int ccs811_init_interrupt(struct device *dev) +{ + struct ccs811_data *drv_data = dev->driver_data; + +#ifndef DT_INST_0_AMS_CCS811_IRQ_GPIOS_PIN + return -EINVAL; +#endif + gpio_pin_configure(drv_data->int_gpio, DT_INST_0_AMS_CCS811_IRQ_GPIOS_PIN, + GPIO_DIR_IN | GPIO_INT | GPIO_INT_LEVEL | + GPIO_INT_ACTIVE_LOW | GPIO_PUD_PULL_UP | + GPIO_INT_DEBOUNCE); + + gpio_init_callback(&drv_data->gpio_cb, gpio_callback, + BIT(DT_INST_0_AMS_CCS811_IRQ_GPIOS_PIN)); + + if (gpio_add_callback(drv_data->int_gpio, &drv_data->gpio_cb) < 0) { + LOG_DBG("Failed to set gpio callback!"); + return -EIO; + } + +#if defined(CONFIG_CCS811_TRIGGER_OWN_THREAD) + k_sem_init(&drv_data->gpio_sem, 0, UINT_MAX); + + k_thread_create(&drv_data->thread, drv_data->thread_stack, + CONFIG_CCS811_THREAD_STACK_SIZE, + (k_thread_entry_t)datardy_thread, dev, + 0, NULL, K_PRIO_COOP(CONFIG_CCS811_THREAD_PRIORITY), + 0, 0); +#elif defined(CONFIG_CCS811_TRIGGER_GLOBAL_THREAD) + drv_data->work.handler = work_cb; + drv_data->dev = dev; +#else +#error Unhandled trigger configuration +#endif + return 0; +}