diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index ffbef132a99..087d33ce224 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -113,6 +113,7 @@ add_subdirectory_ifdef(CONFIG_TEMP_KINETIS nxp_kinetis_temp) add_subdirectory_ifdef(CONFIG_TACH_XEC mchp_tach_xec) add_subdirectory_ifdef(CONFIG_WSEN_HIDS wsen_hids) add_subdirectory_ifdef(CONFIG_ITDS wsen_itds) +add_subdirectory_ifdef(CONFIG_WSEN_TIDS wsen_tids) add_subdirectory_ifdef(CONFIG_MCUX_ACMP mcux_acmp) add_subdirectory_ifdef(CONFIG_TACH_NPCX nuvoton_tach_npcx) add_subdirectory_ifdef(CONFIG_ADC_CMP_NPCX nuvoton_adc_cmp_npcx) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index f94aed76e9c..47c1ee1ee13 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -265,6 +265,8 @@ source "drivers/sensor/wsen_hids/Kconfig" source "drivers/sensor/wsen_itds/Kconfig" +source "drivers/sensor/wsen_tids/Kconfig" + source "drivers/sensor/mcux_acmp/Kconfig" source "drivers/sensor/nuvoton_tach_npcx/Kconfig" diff --git a/drivers/sensor/wsen_tids/CMakeLists.txt b/drivers/sensor/wsen_tids/CMakeLists.txt new file mode 100644 index 00000000000..52019917bd4 --- /dev/null +++ b/drivers/sensor/wsen_tids/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (c) 2022 Würth Elektronik eiSos GmbH & Co. KG +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(wsen_tids.c) +zephyr_library_sources_ifdef(CONFIG_WSEN_TIDS_TRIGGER wsen_tids_trigger.c) diff --git a/drivers/sensor/wsen_tids/Kconfig b/drivers/sensor/wsen_tids/Kconfig new file mode 100644 index 00000000000..764352fb7bc --- /dev/null +++ b/drivers/sensor/wsen_tids/Kconfig @@ -0,0 +1,53 @@ +# Copyright (c) 2022 Würth Elektronik eiSos GmbH & Co. KG +# SPDX-License-Identifier: Apache-2.0 + +menuconfig WSEN_TIDS + bool "WSEN-TIDS temperature sensor" + default y + depends on DT_HAS_WE_WSEN_TIDS_ENABLED + select I2C if $(dt_compat_on_bus,$(DT_COMPAT_WE_WSEN_TIDS),i2c) + select HAS_WESENSORS + help + Enable driver for the WSEN-TIDS I2C-based temperature sensor. + +if WSEN_TIDS + +choice WSEN_TIDS_TRIGGER_MODE + prompt "Trigger mode" + default WSEN_TIDS_TRIGGER_NONE + help + Specify the type of triggering to be used by the driver. + +config WSEN_TIDS_TRIGGER_NONE + bool "No trigger" + +config WSEN_TIDS_TRIGGER_GLOBAL_THREAD + bool "Use global thread" + depends on GPIO + select WSEN_TIDS_TRIGGER + +config WSEN_TIDS_TRIGGER_OWN_THREAD + bool "Use own thread" + depends on GPIO + select WSEN_TIDS_TRIGGER + +endchoice # WSEN_TIDS_TRIGGER_MODE + +config WSEN_TIDS_TRIGGER + bool + +config WSEN_TIDS_THREAD_PRIORITY + int "Thread priority" + depends on WSEN_TIDS_TRIGGER_OWN_THREAD + default 10 + help + Priority of thread used by the driver to handle interrupts. + +config WSEN_TIDS_THREAD_STACK_SIZE + int "Thread stack size" + depends on WSEN_TIDS_TRIGGER_OWN_THREAD + default 1024 + help + Stack size of thread used by the driver to handle interrupts. + +endif # WSEN_TIDS diff --git a/drivers/sensor/wsen_tids/wsen_tids.c b/drivers/sensor/wsen_tids/wsen_tids.c new file mode 100644 index 00000000000..ad397706e74 --- /dev/null +++ b/drivers/sensor/wsen_tids/wsen_tids.c @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2022 Würth Elektronik eiSos GmbH & Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT we_wsen_tids + +#include + +#include +#include +#include + +#include "wsen_tids.h" + +LOG_MODULE_REGISTER(WSEN_TIDS, CONFIG_SENSOR_LOG_LEVEL); + +/* + * List of supported output data rates. Index into this list is used as + * argument for TIDS_setOutputDataRate() + */ +static const int32_t tids_odr_list[] = { + 25, + 50, + 100, + 200, +}; + +static int tids_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct tids_data *data = dev->data; + int16_t raw_temperature; + + if ((chan != SENSOR_CHAN_ALL) && (chan != SENSOR_CHAN_AMBIENT_TEMP)) { + LOG_ERR("Fetching is not supported on channel %d.", chan); + return -EINVAL; + } + + if (TIDS_getRawTemperature(&data->sensor_interface, &raw_temperature) != WE_SUCCESS) { + LOG_ERR("Failed to fetch data sample"); + return -EIO; + } + + data->temperature = raw_temperature; + + return 0; +} + +static int tids_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct tids_data *data = dev->data; + + if (chan == SENSOR_CHAN_AMBIENT_TEMP) { + /* Convert temperature from 0.01 degrees Celsius to degrees Celsius */ + val->val1 = data->temperature / 100; + val->val2 = ((int32_t)data->temperature % 100) * (1000000 / 100); + } else { + return -ENOTSUP; + } + + return 0; +} + +/* Set output data rate. See tids_odr_list for allowed values. */ +static int tids_odr_set(const struct device *dev, const struct sensor_value *odr) +{ + struct tids_data *data = dev->data; + int odr_index; + + for (odr_index = 0; odr_index < ARRAY_SIZE(tids_odr_list); odr_index++) { + if (odr->val1 == tids_odr_list[odr_index] && odr->val2 == 0) { + break; + } + } + + if (odr_index == ARRAY_SIZE(tids_odr_list)) { + /* ODR not allowed (was not found in tids_odr_list) */ + LOG_ERR("Bad sampling frequency %d.%d", odr->val1, abs(odr->val2)); + return -EINVAL; + } + + if (TIDS_setOutputDataRate(&data->sensor_interface, (TIDS_outputDataRate_t)odr_index) != + WE_SUCCESS) { + LOG_ERR("Failed to set output data rate"); + return -EIO; + } + + return 0; +} + +static int tids_attr_set(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, const struct sensor_value *val) +{ + if (chan != SENSOR_CHAN_ALL) { + LOG_WRN("attr_set() is not supported on channel %d.", chan); + return -ENOTSUP; + } + + switch (attr) { + case SENSOR_ATTR_SAMPLING_FREQUENCY: + return tids_odr_set(dev, val); + +#ifdef CONFIG_WSEN_TIDS_TRIGGER + case SENSOR_ATTR_LOWER_THRESH: + return tids_threshold_set(dev, val, false); + + case SENSOR_ATTR_UPPER_THRESH: + return tids_threshold_set(dev, val, true); +#endif /* CONFIG_WSEN_TIDS_TRIGGER */ + + default: + LOG_ERR("Operation not supported."); + return -ENOTSUP; + } +} + +static const struct sensor_driver_api tids_driver_api = { + .attr_set = tids_attr_set, +#if CONFIG_WSEN_TIDS_TRIGGER + .trigger_set = tids_trigger_set, +#endif + .sample_fetch = tids_sample_fetch, + .channel_get = tids_channel_get, +}; + +static int tids_init(const struct device *dev) +{ + const struct tids_config *const config = dev->config; + struct tids_data *data = dev->data; + int status; + uint8_t device_id; + struct sensor_value odr; + + /* Initialize WE sensor interface */ + TIDS_getDefaultInterface(&data->sensor_interface); + data->sensor_interface.interfaceType = WE_i2c; + data->sensor_interface.handle = (void *)&config->bus_cfg.i2c; + + /* First communication test - check device ID */ + if (TIDS_getDeviceID(&data->sensor_interface, &device_id) != WE_SUCCESS) { + LOG_ERR("Failed to read device ID."); + return -EIO; + } + + if (device_id != TIDS_DEVICE_ID_VALUE) { + LOG_ERR("Invalid device ID 0x%x.", device_id); + return -EIO; + } + + /* Reset the sensor with an arbitrary off time of 5 us */ + TIDS_softReset(&data->sensor_interface, TIDS_enable); + k_sleep(K_USEC(5)); + TIDS_softReset(&data->sensor_interface, TIDS_disable); + + odr.val1 = tids_odr_list[config->odr]; + odr.val2 = 0; + status = tids_odr_set(dev, &odr); + if (status < 0) { + LOG_ERR("Failed to set output data rate."); + return status; + } + + if (TIDS_enableBlockDataUpdate(&data->sensor_interface, TIDS_enable) != WE_SUCCESS) { + LOG_ERR("Failed to enable block data update."); + return -EIO; + } + + if (TIDS_enableContinuousMode(&data->sensor_interface, TIDS_enable) != WE_SUCCESS) { + LOG_ERR("Failed to enable continuous mode."); + return -EIO; + } + +#ifdef CONFIG_WSEN_TIDS_TRIGGER + status = tids_init_interrupt(dev); + if (status < 0) { + LOG_ERR("Failed to initialize threshold interrupt."); + return status; + } +#endif + + return 0; +} + +#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 0 +#warning "TIDS driver enabled without any devices" +#endif + +/* + * Device creation macros + */ + +#define TIDS_DEVICE_INIT(inst) \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, \ + tids_init, \ + NULL, \ + &tids_data_##inst, \ + &tids_config_##inst, \ + POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, \ + &tids_driver_api); + +#ifdef CONFIG_WSEN_TIDS_TRIGGER +#define TIDS_CFG_IRQ(inst) \ + .gpio_threshold = GPIO_DT_SPEC_INST_GET(inst, int_gpios), \ + .high_threshold = DT_INST_PROP(inst, temp_high_threshold), \ + .low_threshold = DT_INST_PROP(inst, temp_low_threshold) +#else +#define TIDS_CFG_IRQ(inst) +#endif /* CONFIG_WSEN_TIDS_TRIGGER */ + +/* + * Main instantiation macro. + */ +#define TIDS_DEFINE(inst) \ + static struct tids_data tids_data_##inst; \ + static const struct tids_config tids_config_##inst = \ + { \ + .bus_cfg = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + }, \ + .odr = (TIDS_outputDataRate_t)(DT_INST_ENUM_IDX(inst, odr)), \ + COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, int_gpios), \ + (TIDS_CFG_IRQ(inst)), ()) \ + }; \ + TIDS_DEVICE_INIT(inst) + +DT_INST_FOREACH_STATUS_OKAY(TIDS_DEFINE) diff --git a/drivers/sensor/wsen_tids/wsen_tids.h b/drivers/sensor/wsen_tids/wsen_tids.h new file mode 100644 index 00000000000..3ce898893f0 --- /dev/null +++ b/drivers/sensor/wsen_tids/wsen_tids.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 Würth Elektronik eiSos GmbH & Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_SENSOR_WSEN_TIDS_WSEN_TIDS_H_ +#define ZEPHYR_DRIVERS_SENSOR_WSEN_TIDS_WSEN_TIDS_H_ + +#include +#include + +#include + +#include "WSEN_TIDS_2521020222501.h" + +#include + +struct tids_data { + /* WE sensor interface configuration */ + WE_sensorInterface_t sensor_interface; + + /* Last temperature sample */ + int16_t temperature; + +#ifdef CONFIG_WSEN_TIDS_TRIGGER + const struct device *dev; + + /* Callback for high/low limit interrupts */ + struct gpio_callback threshold_cb; + + struct sensor_trigger threshold_trigger; + sensor_trigger_handler_t threshold_handler; + +#if defined(CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD) + K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_WSEN_TIDS_THREAD_STACK_SIZE); + struct k_thread thread; + struct k_sem threshold_sem; +#elif defined(CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD) + struct k_work work; +#endif +#endif /* CONFIG_WSEN_TIDS_TRIGGER */ +}; + +struct tids_config { + union { + const struct i2c_dt_spec i2c; + } bus_cfg; + + /* Output data rate */ + const TIDS_outputDataRate_t odr; + +#ifdef CONFIG_WSEN_TIDS_TRIGGER + /* Interrupt pin used for high and low limit interrupt events */ + const struct gpio_dt_spec gpio_threshold; + + /* High temperature interrupt threshold */ + const int high_threshold; + + /* Low temperature interrupt threshold */ + const int low_threshold; +#endif +}; + +#ifdef CONFIG_WSEN_TIDS_TRIGGER +int tids_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler); + +int tids_threshold_set(const struct device *dev, const struct sensor_value *thresh_value, + bool upper); + +int tids_init_interrupt(const struct device *dev); +#endif + +int tids_i2c_init(const struct device *dev); + +#endif /* ZEPHYR_DRIVERS_SENSOR_WSEN_TIDS_WSEN_TIDS_H_ */ diff --git a/drivers/sensor/wsen_tids/wsen_tids_trigger.c b/drivers/sensor/wsen_tids/wsen_tids_trigger.c new file mode 100644 index 00000000000..c940b614611 --- /dev/null +++ b/drivers/sensor/wsen_tids/wsen_tids_trigger.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2022 Würth Elektronik eiSos GmbH & Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT we_wsen_tids + +#include + +#include + +#include "wsen_tids.h" + +LOG_MODULE_DECLARE(WSEN_TIDS, CONFIG_SENSOR_LOG_LEVEL); + +#define THRESHOLD_TEMPERATURE2REGISTER_OFFSET (double)63. +#define THRESHOLD_TEMPERATURE2REGISTER_STEP (double)0.64 + +/* Enable/disable threshold interrupt handling */ +static inline void tids_setup_threshold_interrupt(const struct device *dev, bool enable) +{ + const struct tids_config *cfg = dev->config; + unsigned int flags = enable ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE; + + gpio_pin_interrupt_configure_dt(&cfg->gpio_threshold, flags); +} + +/* + * Is called when a "threshold exceeded" interrupt occurred. Triggers + * asynchronous processing of the interrupt in tids_process_threshold_interrupt(). + */ +static void tids_handle_threshold_interrupt(const struct device *dev) +{ + struct tids_data *data = dev->data; + + /* Disable interrupt handling until the interrupt has been processed */ + tids_setup_threshold_interrupt(dev, false); + +#if defined(CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD) + k_sem_give(&data->threshold_sem); +#elif defined(CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD) + k_work_submit(&data->work); +#endif +} + +/* + * Is called after a "threshold exceeded" interrupt occurred. + * Checks the sensor's status register for the limit exceeded flags and + * calls the trigger handler if one of the flags is set. + */ +static void tids_process_threshold_interrupt(const struct device *dev) +{ + struct tids_data *data = dev->data; + TIDS_status_t status; + + /* + * Read the sensor's status register - this also causes the interrupt pin + * to be de-asserted + */ + if (TIDS_getStatusRegister(&data->sensor_interface, &status) != WE_SUCCESS) { + LOG_ERR("Failed to read status register"); + return; + } + + if (data->threshold_handler != NULL && + (status.upperLimitExceeded != 0 || status.lowerLimitExceeded != 0)) { + data->threshold_handler(dev, &data->threshold_trigger); + } + + if (data->threshold_handler != NULL) { + tids_setup_threshold_interrupt(dev, true); + } +} + +/* Enables/disables processing of the "threshold exceeded" interrupt. */ +int tids_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) +{ + struct tids_data *data = dev->data; + const struct tids_config *cfg = dev->config; + + if (trig->type != SENSOR_TRIG_THRESHOLD) { + LOG_ERR("Unsupported sensor trigger"); + return -ENOTSUP; + } + + tids_setup_threshold_interrupt(dev, false); + + data->threshold_handler = handler; + if (handler == NULL) { + return 0; + } + + data->threshold_trigger = *trig; + + tids_setup_threshold_interrupt(dev, true); + + /* + * If threshold interrupt is active we probably won't get the rising edge, so + * invoke the callback manually. + */ + if (gpio_pin_get_dt(&cfg->gpio_threshold) > 0) { + tids_handle_threshold_interrupt(dev); + } + + return 0; +} + +static void tids_threshold_callback(const struct device *dev, struct gpio_callback *cb, + uint32_t pins) +{ + struct tids_data *data = CONTAINER_OF(cb, struct tids_data, threshold_cb); + + ARG_UNUSED(pins); + + tids_handle_threshold_interrupt(data->dev); +} + +#ifdef CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD +static void tids_thread(struct tids_data *tids) +{ + while (true) { + k_sem_take(&tids->threshold_sem, K_FOREVER); + tids_process_threshold_interrupt(tids->dev); + } +} +#endif /* CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD */ + +#ifdef CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD +static void tids_work_cb(struct k_work *work) +{ + struct tids_data *tids = CONTAINER_OF(work, struct tids_data, work); + + tids_process_threshold_interrupt(tids->dev); +} +#endif /* CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD */ + +int tids_threshold_set(const struct device *dev, const struct sensor_value *thresh_value, + bool upper) +{ + struct tids_data *data = dev->data; + double thresh = (sensor_value_to_double(thresh_value) / THRESHOLD_TEMPERATURE2REGISTER_STEP) + + THRESHOLD_TEMPERATURE2REGISTER_OFFSET; + + if (thresh < 0) { + thresh = 0; + } else if (thresh > 255) { + thresh = 255; + } + + if (upper) { + if (TIDS_setTempHighLimit(&data->sensor_interface, (uint8_t)thresh) != WE_SUCCESS) { + LOG_ERR("Failed to set high temperature threshold to %d.%d (%d).", + thresh_value->val1, abs(thresh_value->val2), (uint8_t)thresh); + return -EIO; + } + } else { + if (TIDS_setTempLowLimit(&data->sensor_interface, (uint8_t)thresh) != WE_SUCCESS) { + LOG_ERR("Failed to set low temperature threshold to %d.%d (%d).", + thresh_value->val1, abs(thresh_value->val2), (uint8_t)thresh); + return -EIO; + } + } + + return 0; +} + +int tids_init_interrupt(const struct device *dev) +{ + struct tids_data *data = dev->data; + const struct tids_config *cfg = dev->config; + int status; + struct sensor_value upper_limit; + struct sensor_value lower_limit; + + if (cfg->gpio_threshold.port == NULL) { + LOG_ERR("int-gpios is not defined in the device tree."); + return -EINVAL; + } + + if (!device_is_ready(cfg->gpio_threshold.port)) { + LOG_ERR("Device %s is not ready", cfg->gpio_threshold.port->name); + return -ENODEV; + } + + data->dev = dev; + + /* Setup threshold gpio interrupt */ + status = gpio_pin_configure_dt(&cfg->gpio_threshold, GPIO_INPUT); + if (status < 0) { + LOG_ERR("Failed to configure %s.%02u", cfg->gpio_threshold.port->name, + cfg->gpio_threshold.pin); + return status; + } + + gpio_init_callback(&data->threshold_cb, tids_threshold_callback, + BIT(cfg->gpio_threshold.pin)); + + status = gpio_add_callback(cfg->gpio_threshold.port, &data->threshold_cb); + if (status < 0) { + LOG_ERR("Failed to set gpio callback."); + return status; + } + + /* + * Enable interrupt on high/low temperature (interrupt generation is enabled if at + * least one threshold is non-zero) + */ + upper_limit.val1 = cfg->high_threshold; + upper_limit.val2 = 0; + lower_limit.val1 = cfg->low_threshold; + lower_limit.val2 = 0; + + status = tids_threshold_set(dev, &upper_limit, true); + if (status < 0) { + return status; + } + + status = tids_threshold_set(dev, &lower_limit, false); + if (status < 0) { + return status; + } + +#if defined(CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD) + k_sem_init(&data->threshold_sem, 0, K_SEM_MAX_LIMIT); + + k_thread_create(&data->thread, data->thread_stack, CONFIG_WSEN_TIDS_THREAD_STACK_SIZE, + (k_thread_entry_t)tids_thread, data, NULL, NULL, + K_PRIO_COOP(CONFIG_WSEN_TIDS_THREAD_PRIORITY), 0, K_NO_WAIT); +#elif defined(CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD) + data->work.handler = tids_work_cb; +#endif + + tids_setup_threshold_interrupt(dev, true); + + return 0; +} diff --git a/dts/bindings/sensor/we,wsen-tids.yaml b/dts/bindings/sensor/we,wsen-tids.yaml new file mode 100644 index 00000000000..202ff994597 --- /dev/null +++ b/dts/bindings/sensor/we,wsen-tids.yaml @@ -0,0 +1,37 @@ +# Copyright (c) 2022 Würth Elektronik eiSos GmbH & Co. KG +# SPDX-License-Identifier: Apache-2.0 + +description: | + Würth Elektronik WSEN-TIDS temperature sensor + +compatible: "we,wsen-tids" + +include: [sensor-device.yaml, i2c-device.yaml] + +properties: + int-gpios: + type: phandle-array + description: Threshold interrupt pin. + Interrupt is active low by default. + + odr: + type: int + required: true + enum: + - 25 + - 50 + - 100 + - 200 + description: | + Sensor output data rate expressed in samples per second. + Data rates supported by the chip are 25, 50, 100 and 200. + + temp-high-threshold: + type: int + description: | + Threshold for temperature high limit interrupt event. + + temp-low-threshold: + type: int + description: | + Threshold for temperature low limit interrupt event. diff --git a/tests/drivers/build_all/sensor/i2c.dtsi b/tests/drivers/build_all/sensor/i2c.dtsi index b75e7cbcd22..0bddfb4938f 100644 --- a/tests/drivers/build_all/sensor/i2c.dtsi +++ b/tests/drivers/build_all/sensor/i2c.dtsi @@ -657,3 +657,10 @@ test_i2c_akm09918c: akm09918c@66 { compatible = "asahi-kasei,akm09918c"; reg = <0x66>; }; + +test_i2c_wsen_tids: wsen_tids@67 { + compatible = "we,wsen-tids"; + reg = <0x67>; + int-gpios = <&test_gpio 0 0>; + odr = <25>; +};