/* * Copyright (c) 2023 PHYTEC Messtechnik GmbH * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ams_tmd2620 #include #include #include #include #include #include #include #include #include #include #include #include #include "tmd2620.h" LOG_MODULE_REGISTER(TMD2620, CONFIG_SENSOR_LOG_LEVEL); static void tmd2620_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { LOG_DBG("Interrupt Callback was called"); struct tmd2620_data *data = CONTAINER_OF(cb, struct tmd2620_data, gpio_cb); tmd2620_setup_int(data->dev->config, false); #ifdef CONFIG_TMD2620_TRIGGER k_work_submit(&data->work); #else k_sem_give(&data->data_sem); #endif } static int tmd2620_configure_interrupt(const struct device *dev) { struct tmd2620_data *data = dev->data; const struct tmd2620_config *config = dev->config; int ret; LOG_DBG("Configuring Interrupt."); if (!gpio_is_ready_dt(&config->int_gpio)) { LOG_ERR("Interrupt GPIO device not ready"); return -ENODEV; } ret = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT); if (ret < 0) { LOG_ERR("Failed to configure interrupt pin"); return ret; } gpio_init_callback(&data->gpio_cb, tmd2620_gpio_callback, BIT(config->int_gpio.pin)); ret = gpio_add_callback(config->int_gpio.port, &data->gpio_cb); if (ret < 0) { LOG_ERR("Failed to set GPIO callback"); return ret; } data->dev = dev; #ifdef CONFIG_TMD2620_TRIGGER data->work.handler = tmd2620_work_cb; #else k_sem_init(&data->data_sem, 0, K_SEM_MAX_LIMIT); #endif return 0; } static int tmd2620_sample_fetch(const struct device *dev, enum sensor_channel chan) { LOG_DBG("Fetching Sample..."); struct tmd2620_data *data = dev->data; const struct tmd2620_config *config = dev->config; uint8_t tmp; int ret; if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_PROX) { LOG_ERR("Unsupported sensor channel"); return -ENOTSUP; } #ifndef CONFIG_TMD2620_TRIGGER /* enabling interrupt */ ret = i2c_reg_update_byte_dt(&config->i2c, TMD2620_INTENAB_REG, TMD2620_INTENAB_PIEN, TMD2620_INTENAB_PIEN); if (ret < 0) { LOG_ERR("Failed enabling interrupt."); return ret; } tmd2620_setup_int(config, true); /* Enabling proximity and powering up device */ tmp = TMD2620_ENABLE_PEN | TMD2620_ENABLE_PON; ret = i2c_reg_update_byte_dt(&config->i2c, TMD2620_ENABLE_REG, tmp, tmp); if (ret < 0) { LOG_ERR("Failed enabling device."); return ret; } LOG_DBG("waiting for semaphore.."); k_sem_take(&data->data_sem, K_FOREVER); #endif ret = i2c_reg_read_byte_dt(&config->i2c, TMD2620_STATUS_REG, &tmp); if (ret < 0) { LOG_ERR("Failed reading status register."); return ret; } LOG_DBG("Status register: 0x%x", tmp); if (tmp & TMD2620_STATUS_PINT) { LOG_DBG("Proximity interrupt detected."); ret = i2c_reg_read_byte_dt(&config->i2c, TMD2620_PDATA_REG, &data->pdata); if (ret < 0) { LOG_ERR("Failed reading proximity data."); return ret; } } #ifndef CONFIG_TMD2620_TRIGGER tmp = TMD2620_ENABLE_PEN | TMD2620_ENABLE_PON; /* Disabling proximity and powering down device */ ret = i2c_reg_update_byte_dt(&config->i2c, TMD2620_ENABLE_REG, tmp, 0); if (ret < 0) { LOG_ERR("Failed powering down device."); return ret; } #endif /* clearing interrupt flag */ ret = i2c_reg_update_byte_dt(&config->i2c, TMD2620_STATUS_REG, TMD2620_STATUS_PINT, TMD2620_STATUS_PINT); if (ret < 0) { LOG_ERR("Failed clearing interrupt flag."); return ret; } return 0; } static int tmd2620_channel_get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val) { struct tmd2620_data *data = dev->data; if (chan == SENSOR_CHAN_PROX) { /* inverting sensor data to fit Zephyr */ val->val1 = (256 - data->pdata); val->val2 = 0; return 0; } return -ENOTSUP; } static int tmd2620_sensor_setup(const struct device *dev) { const struct tmd2620_config *config = dev->config; uint8_t chip_id; uint8_t tmp; int ret; /* trying to read the id twice, as the sensor does not answer the first request */ /* because of this no return code is checked in this line */ (void)i2c_reg_read_byte_dt(&config->i2c, TMD2620_ID_REG, &chip_id); ret = i2c_reg_read_byte_dt(&config->i2c, TMD2620_ID_REG, &chip_id); if (ret < 0) { LOG_ERR("Failed reading chip id"); return ret; } if (chip_id != TMD2620_CHIP_ID) { LOG_ERR("Chip id is invalid! Device @%02x is no TMD2620!", config->i2c.addr); return -EIO; } ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_ENABLE_REG, 0); if (ret < 0) { LOG_ERR("ENABLE Register was not cleared"); return ret; } tmp = config->wait_time_factor; ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_WTIME_REG, tmp); if (ret < 0) { LOG_ERR("Failed setting wait time"); return ret; } tmp = config->proximity_low_threshold; ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_PILT_REG, tmp); if (ret < 0) { LOG_ERR("Failed setting PILT"); return ret; } tmp = config->proximity_high_threshold; ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_PIHT_REG, (255 - tmp)); if (ret < 0) { LOG_ERR("Failed setting PIHT"); return ret; } #ifdef CONFIG_TMD2620_TRIGGER tmp = (config->proximity_interrupt_filter << 3); ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_PERS_REG, tmp); if (ret < 0) { LOG_ERR("Failed setting PERS"); return ret; } #endif if (config->wait_long) { tmp = TMD2620_CFG0_WLONG; } else { tmp = 0; } ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_CFG0_REG, tmp); if (ret < 0) { LOG_ERR("Failed setting CFG0"); return ret; } switch (config->proximity_pulse_length) { case 4: tmp = TMD2620_PCFG0_PPULSE_LEN_4US; break; case 8: tmp = TMD2620_PCFG0_PPULSE_LEN_8US; break; case 16: tmp = TMD2620_PCFG0_PPULSE_LEN_16US; break; case 32: tmp = TMD2620_PCFG0_PPULSE_LEN_32US; break; default: LOG_ERR("Invalid proximity pulse length"); return -EINVAL; } tmp |= config->proximity_pulse_count; ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_PCFG0_REG, tmp); if (ret < 0) { LOG_ERR("Failed setting PPULSE"); return ret; } switch (config->proximity_gain) { case 1: tmp = TMD2620_PCFG1_PGAIN_X1; break; case 2: tmp = TMD2620_PCFG1_PGAIN_X2; break; case 4: tmp = TMD2620_PCFG1_PGAIN_X4; break; case 8: tmp = TMD2620_PCFG1_PGAIN_X8; break; default: LOG_ERR("Invalid proximity gain"); return -EINVAL; } tmp |= config->proximity_led_drive_strength; ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_PCFG1_REG, tmp); if (ret < 0) { LOG_ERR("Failed setting PCGF1"); return ret; } tmp = TMD2620_CFG3_INT_READ_CLEAR; ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_CFG3_REG, tmp); if (ret < 0) { LOG_ERR("Failed setting CFG3"); return ret; } tmp = 1; /* enable interrupt */ ret = i2c_reg_write_byte_dt(&config->i2c, TMD2620_INTENAB_REG, tmp); if (ret < 0) { LOG_ERR("Failed setting INTENAB"); return ret; } if (config->enable_wait_mode) { ret = i2c_reg_update_byte_dt(&config->i2c, TMD2620_ENABLE_REG, TMD2620_ENABLE_WEN, TMD2620_ENABLE_WEN); if (ret < 0) { LOG_ERR("Failed enabling wait mode"); return ret; } } return 0; } static int tmd2620_init(const struct device *dev) { const struct tmd2620_config *config = dev->config; struct tmd2620_data *data = dev->data; int ret; #ifdef CONFIG_TMD2620_TRIGGER uint8_t tmp; #endif if (!i2c_is_ready_dt(&config->i2c)) { LOG_ERR("I2C bus not ready!"); return -ENODEV; } data->pdata = 0U; ret = tmd2620_sensor_setup(dev); if (ret < 0) { LOG_ERR("Failed to configure device"); return ret; } LOG_DBG("Device setup complete"); ret = tmd2620_configure_interrupt(dev); if (ret < 0) { LOG_ERR("Failed configuring interrupt!"); return ret; } #ifdef CONFIG_TMD2620_TRIGGER tmp = TMD2620_ENABLE_PEN | TMD2620_ENABLE_PON; ret = i2c_reg_update_byte_dt(&config->i2c, TMD2620_ENABLE_REG, tmp, tmp); if (ret < 0) { LOG_ERR("Failed enabling device."); return ret; } #endif LOG_DBG("Driver init complete."); return 0; } #ifdef CONFIG_PM_DEVICE static int tmd2620_pm_action(const struct device *dev, enum pm_device_action action) { const struct tmd2620_config *config = dev->config; int ret; switch (action) { case PM_DEVICE_ACTION_RESUME: ret = i2c_reg_update_byte_dt(&config->i2c, TMD2620_ENABLE_REG, TMD2620_ENABLE_PON, TMD2620_ENABLE_PON); if (ret < 0) { LOG_ERR("Failed enabling sensor."); return ret; } break; case PM_DEVICE_ACTION_SUSPEND: ret = i2c_reg_update_byte_dt(&config->i2c, TMD2620_ENABLE_REG, TMD2620_ENABLE_PON, 0); if (ret < 0) { LOG_ERR("Failed suspending sensor."); return ret; } break; default: return -ENOTSUP; } return 0; } #endif static DEVICE_API(sensor, tmd2620_driver_api) = { .sample_fetch = tmd2620_sample_fetch, .channel_get = tmd2620_channel_get, #ifdef CONFIG_TMD2620_TRIGGER .attr_set = tmd2620_attr_set, .trigger_set = tmd2620_trigger_set, #endif }; #define TMD2620_INIT_INST(n) \ struct tmd2620_data tmd2620_data_##n; \ static const struct tmd2620_config tmd2620_config_##n = { \ .i2c = I2C_DT_SPEC_INST_GET(n), \ .int_gpio = GPIO_DT_SPEC_INST_GET(n, int_gpios), \ .proximity_gain = DT_INST_PROP(n, proximity_gain), \ .proximity_pulse_length = DT_INST_PROP(n, proximity_pulse_length), \ .proximity_pulse_count = DT_INST_PROP(n, proximity_pulse_count), \ .proximity_high_threshold = DT_INST_PROP(n, proximity_high_threshold), \ .proximity_low_threshold = DT_INST_PROP(n, proximity_low_threshold), \ .proximity_led_drive_strength = DT_INST_PROP(n, proximity_led_drive_strength), \ .proximity_interrupt_filter = DT_INST_PROP(n, proximity_interrupt_filter), \ .enable_wait_mode = DT_INST_PROP(n, enable_wait_mode), \ .wait_time_factor = DT_INST_PROP(n, wait_time_factor), \ .wait_long = DT_INST_PROP(n, wait_long), \ }; \ \ PM_DEVICE_DT_INST_DEFINE(n, tmd2620_pm_action); \ SENSOR_DEVICE_DT_INST_DEFINE(n, tmd2620_init, PM_DEVICE_DT_INST_GET(n), &tmd2620_data_##n, \ &tmd2620_config_##n, POST_KERNEL, \ CONFIG_SENSOR_INIT_PRIORITY, &tmd2620_driver_api); DT_INST_FOREACH_STATUS_OKAY(TMD2620_INIT_INST);