| /* |
| * Copyright (c) 2022 Mizuki Agawa <agawa.mizuki@fujitsu.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT invensense_icp10125 |
| |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/byteorder.h> |
| |
| #ifdef CONFIG_ICP10125_CHECK_CRC |
| #include <zephyr/sys/crc.h> |
| #endif /* CONFIG_ICP10125_CHECK_CRC */ |
| |
| LOG_MODULE_REGISTER(ICP10125, CONFIG_SENSOR_LOG_LEVEL); |
| |
| #define CRC_POLY 0x31 |
| #define SENSOR_DATA_SIZE 2 |
| |
| #define AMBIENT_TEMP_DATA_NUM 1 |
| #define PRESS_DATA_NUM 2 |
| #define PRESS_AND_AMBIENT_TEMP_DATA_NUM (AMBIENT_TEMP_DATA_NUM + PRESS_DATA_NUM) |
| |
| enum { |
| LOW_POWER, |
| NORMAL, |
| LOW_NOISE, |
| ULTRA_LOW_NOISE, |
| NUM_MEASURE_MODE |
| }; |
| |
| struct icp10125_data { |
| uint16_t raw_ambient_temp; |
| uint32_t raw_press; |
| float sensor_constants[4]; |
| }; |
| |
| struct icp10125_dev_config { |
| struct i2c_dt_spec i2c; |
| uint8_t ambient_temp_mode; |
| uint8_t press_mode; |
| }; |
| |
| struct icp10125_cmd { |
| uint8_t data[2]; |
| }; |
| |
| struct icp10125_sensor_data { |
| uint8_t data[2]; |
| uint8_t crc; |
| }; |
| |
| struct icp10125_otp_read_setup { |
| struct icp10125_cmd cmd; |
| uint8_t data[3]; |
| } __packed __aligned(1); |
| |
| /* ambient temperature measurement command for each mode. |
| * (Section 5.2 MEASUREMENT COMMANDS in the Datasheet) |
| */ |
| static const struct icp10125_cmd ambient_temp_measurement_cmds[] = { |
| {{0x60, 0x9C}}, {{0x68, 0x25}}, {{0x70, 0xDF}}, {{0x78, 0x66}} |
| }; |
| |
| /* pressure measurement command for each mode. |
| * (Section 5.2 MEASUREMENT COMMANDS in the Datasheet) |
| */ |
| static const struct icp10125_cmd press_measurement_cmds[] = { |
| {{0x40, 0x1A}}, {{0x48, 0xA3}}, {{0x50, 0x59}}, {{0x59, 0xE0}} |
| }; |
| |
| /* Request preparation for OTP data read. It should issue before data read request. |
| * (Section 5.2 MEASUREMENT COMMANDS in the Datasheet) |
| */ |
| static const struct icp10125_otp_read_setup otp_read_setup = { |
| .cmd = {{0xC5, 0x95}}, |
| .data = {0x00, 0x66, 0x9C} |
| }; |
| |
| /* OTP data read request. |
| * After issue this command 2byte x 4 sensor constant value can readable. |
| */ |
| static const struct icp10125_cmd otp_read_request_cmd = {{0xC7, 0xF7}}; |
| |
| /* The max conversion time for each modes. |
| * (Section 2.2 OPERATION MODES in the Datasheet) |
| */ |
| static const uint32_t conv_time_max[] = {1800, 6300, 23800, 94500}; |
| |
| /* The typical conversion time for each modes. |
| * (Section 2.2 OPERATION MODES in the Datasheet) |
| */ |
| static const uint32_t conv_time_typ[] = {1600, 5600, 20800, 83200}; |
| |
| /* The Datasheet has no mention of the constants and formulas. |
| * Instead, it shows only how to use it in the sample code. |
| * Since there is no detailed description in the ICP10125 product manual, |
| * the calculation of the pressure implements is the same as shown in |
| * the 5.11 SAMPLE CODE: EXAMPLE C SYNTAX |
| */ |
| |
| static void icp10125_calculate_conversion_constants(const float *p_LUT, float *A, float *B, |
| float *C) |
| { |
| const float p_Pa[] = {45000.0, 80000.0, 105000.0}; |
| |
| *C = (p_LUT[0] * p_LUT[1] * (p_Pa[0] - p_Pa[1]) + |
| p_LUT[1] * p_LUT[2] * (p_Pa[1] - p_Pa[2]) + |
| p_LUT[2] * p_LUT[0] * (p_Pa[2] - p_Pa[0])) / |
| (p_LUT[2] * (p_Pa[0] - p_Pa[1]) + p_LUT[0] * (p_Pa[1] - p_Pa[2]) + |
| p_LUT[1] * (p_Pa[2] - p_Pa[0])); |
| *A = (p_Pa[0] * p_LUT[0] - p_Pa[1] * p_LUT[1] - (p_Pa[1] - p_Pa[0]) * (*C)) / |
| (p_LUT[0] - p_LUT[1]); |
| *B = (p_Pa[0] - (*A)) * (p_LUT[0] + (*C)); |
| } |
| |
| static float icp10125_calc_calibrated_ambient_temp(const struct icp10125_data *data) |
| { |
| return -45.f + 175.f / 65536.f * data->raw_ambient_temp; |
| } |
| |
| static float icp10125_calc_calibrated_press(const struct icp10125_data *data) |
| { |
| const float quadr_factor = 1 / 16777216.0; |
| const float offst_factor = 2048.0; |
| const float LUT_lower = 3.5 * (1 << 20); |
| const float LUT_upper = 11.5 * (1 << 20); |
| float t; |
| float in[3]; |
| float A, B, C; |
| |
| t = data->raw_ambient_temp - 32768.f; |
| in[0] = LUT_lower + (data->sensor_constants[0] * t * t) * quadr_factor; |
| in[1] = offst_factor * data->sensor_constants[3] + |
| (data->sensor_constants[1] * t * t) * quadr_factor; |
| in[2] = LUT_upper + (data->sensor_constants[2] * t * t) * quadr_factor; |
| icp10125_calculate_conversion_constants(in, &A, &B, &C); |
| |
| return A + B / (C + data->raw_press); |
| } |
| |
| /* End of porting the 5.11 SAMPLE CODE: EXAMPLE C SYNTAX */ |
| |
| static int icp10125_read_otp(const struct device *dev) |
| { |
| struct icp10125_data *data = dev->data; |
| struct icp10125_sensor_data sensor_data; |
| |
| const struct icp10125_dev_config *cfg = dev->config; |
| int rc = 0; |
| |
| rc = i2c_write_dt(&cfg->i2c, (uint8_t *)&otp_read_setup, sizeof(otp_read_setup)); |
| if (rc < 0) { |
| LOG_ERR("Failed to write otp_read_setup.\n"); |
| return rc; |
| } |
| |
| for (size_t i = 0; i < ARRAY_SIZE(data->sensor_constants); i++) { |
| rc = i2c_write_dt(&cfg->i2c, (uint8_t *)&otp_read_request_cmd, |
| sizeof(otp_read_request_cmd)); |
| if (rc < 0) { |
| LOG_ERR("Failed to write otp_read_request.\n"); |
| return rc; |
| } |
| |
| rc = i2c_read_dt(&cfg->i2c, (uint8_t *)&sensor_data, sizeof(sensor_data)); |
| if (rc < 0) { |
| LOG_ERR("Failed to read otp_read_request.\n"); |
| return rc; |
| } |
| |
| data->sensor_constants[i] = sys_get_be16(sensor_data.data); |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_ICP10125_CHECK_CRC |
| static int icp10125_check_crc(const uint8_t *data, const size_t len) |
| { |
| /* Details of CRC are described in Chapter 5 Section 8 of the product |
| * specifications. |
| */ |
| return crc8(data, len, CRC_POLY, 0xFF, false); |
| } |
| #endif |
| |
| static int icp10125_measure(const struct i2c_dt_spec *i2c, const struct icp10125_cmd *cmds, |
| const uint8_t mode, struct icp10125_sensor_data *sensor_data, |
| const size_t data_num) |
| { |
| int rc = 0; |
| |
| rc = i2c_write_dt(i2c, (uint8_t *)&cmds[mode], sizeof(cmds[mode])); |
| if (rc < 0) { |
| LOG_ERR("Failed to start measurement.\n"); |
| return rc; |
| } |
| |
| /* Wait for the sensor to become readable. |
| * First wait for the typical time and then read. |
| * If that fails, wait until the time to surely became readable. |
| */ |
| k_sleep(K_USEC(conv_time_typ[mode])); |
| if (i2c_read_dt(i2c, (uint8_t *)sensor_data, sizeof(sensor_data[0]) * data_num) < 0) { |
| k_sleep(K_USEC(conv_time_max[mode] - conv_time_typ[mode])); |
| rc = i2c_read_dt(i2c, (uint8_t *)sensor_data, sizeof(sensor_data[0]) * data_num); |
| if (rc < 0) { |
| LOG_ERR("Failed to read measurement.\n"); |
| return rc; |
| } |
| } |
| |
| #ifdef CONFIG_ICP10125_CHECK_CRC |
| /* Calculate CRC from Chapter 5 Section 8 of ICP10125 Product manuals. */ |
| for (size_t i = 0; i < data_num; i++) { |
| if (!icp10125_check_crc(sensor_data[i].data, SENSOR_DATA_SIZE)) { |
| LOG_ERR("Sensor data has invalid CRC.\n"); |
| return -EIO; |
| } |
| } |
| #endif /* CONFIG_ICP10125_CHECK_CRC */ |
| |
| return 0; |
| } |
| |
| static int icp10125_sample_fetch(const struct device *dev, const enum sensor_channel chan) |
| { |
| struct icp10125_data *data = dev->data; |
| const struct icp10125_dev_config *cfg = dev->config; |
| uint8_t endian_conversion[3]; |
| struct icp10125_sensor_data sensor_data[PRESS_AND_AMBIENT_TEMP_DATA_NUM] = {0}; |
| int rc = 0; |
| |
| if (!(chan == SENSOR_CHAN_AMBIENT_TEMP || chan == SENSOR_CHAN_PRESS || |
| chan == SENSOR_CHAN_ALL)) { |
| return -ENOTSUP; |
| } |
| |
| if (chan == SENSOR_CHAN_AMBIENT_TEMP) { |
| rc = icp10125_measure(&cfg->i2c, ambient_temp_measurement_cmds, |
| cfg->ambient_temp_mode, sensor_data, AMBIENT_TEMP_DATA_NUM); |
| if (rc < 0) { |
| return rc; |
| } |
| |
| data->raw_ambient_temp = sys_get_be16(sensor_data[0].data); |
| } else { |
| rc = icp10125_measure(&cfg->i2c, press_measurement_cmds, cfg->press_mode, |
| sensor_data, PRESS_AND_AMBIENT_TEMP_DATA_NUM); |
| if (rc < 0) { |
| return rc; |
| } |
| |
| endian_conversion[0] = sensor_data[0].data[0]; |
| endian_conversion[1] = sensor_data[0].data[1]; |
| endian_conversion[2] = sensor_data[1].data[0]; |
| data->raw_press = sys_get_be24(endian_conversion); |
| data->raw_ambient_temp = sys_get_be16(sensor_data[2].data); |
| } |
| |
| return 0; |
| } |
| |
| static void icp10125_convert_press_value(struct icp10125_data *data, struct sensor_value *val) |
| { |
| sensor_value_from_float(val, icp10125_calc_calibrated_press(data) / 1000.f); |
| } |
| |
| static void icp10125_convert_ambient_temp_value(struct icp10125_data *data, |
| struct sensor_value *val) |
| { |
| sensor_value_from_float(val, icp10125_calc_calibrated_ambient_temp(data)); |
| } |
| |
| static int icp10125_channel_get(const struct device *dev, enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| struct icp10125_data *data = dev->data; |
| |
| if (chan == SENSOR_CHAN_AMBIENT_TEMP) { |
| icp10125_convert_ambient_temp_value(data, val); |
| } else if (chan == SENSOR_CHAN_PRESS) { |
| icp10125_convert_press_value(data, val); |
| } else { |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static int icp10125_init(const struct device *dev) |
| { |
| int rc = icp10125_read_otp(dev); |
| |
| if (rc < 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static const struct sensor_driver_api icp10125_api_funcs = { |
| .sample_fetch = icp10125_sample_fetch, |
| .channel_get = icp10125_channel_get, |
| }; |
| |
| #define ICP10125_DEFINE(inst) \ |
| static struct icp10125_data icp10125_drv_##inst; \ |
| static const struct icp10125_dev_config icp10125_config_##inst = { \ |
| .i2c = I2C_DT_SPEC_INST_GET(inst), \ |
| .ambient_temp_mode = DT_INST_ENUM_IDX(inst, temperature_measurement_mode), \ |
| .press_mode = DT_INST_ENUM_IDX(inst, pressure_measurement_mode)}; \ |
| DEVICE_DT_INST_DEFINE(inst, icp10125_init, NULL, &icp10125_drv_##inst, \ |
| &icp10125_config_##inst, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \ |
| &icp10125_api_funcs); |
| |
| DT_INST_FOREACH_STATUS_OKAY(ICP10125_DEFINE) |