| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * mlx90635.c - Melexis MLX90635 contactless IR temperature sensor |
| * |
| * Copyright (c) 2023 Melexis <cmo@melexis.com> |
| * |
| * Driver for the Melexis MLX90635 I2C 16-bit IR thermopile sensor |
| */ |
| #include <linux/bitfield.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/i2c.h> |
| #include <linux/iopoll.h> |
| #include <linux/jiffies.h> |
| #include <linux/kernel.h> |
| #include <linux/limits.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/module.h> |
| #include <linux/math64.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.h> |
| |
| #include <linux/iio/iio.h> |
| |
| /* Memory sections addresses */ |
| #define MLX90635_ADDR_RAM 0x0000 /* Start address of ram */ |
| #define MLX90635_ADDR_EEPROM 0x0018 /* Start address of user eeprom */ |
| |
| /* EEPROM addresses - used at startup */ |
| #define MLX90635_EE_I2C_CFG 0x0018 /* I2C address register initial value */ |
| #define MLX90635_EE_CTRL1 0x001A /* Control register1 initial value */ |
| #define MLX90635_EE_CTRL2 0x001C /* Control register2 initial value */ |
| |
| #define MLX90635_EE_Ha 0x001E /* Ha customer calib value reg 16bit */ |
| #define MLX90635_EE_Hb 0x0020 /* Hb customer calib value reg 16bit */ |
| #define MLX90635_EE_Fa 0x0026 /* Fa calibration register 32bit */ |
| #define MLX90635_EE_FASCALE 0x002A /* Scaling coefficient for Fa register 16bit */ |
| #define MLX90635_EE_Ga 0x002C /* Ga calibration register 16bit */ |
| #define MLX90635_EE_Fb 0x002E /* Fb calibration register 16bit */ |
| #define MLX90635_EE_Ea 0x0030 /* Ea calibration register 32bit */ |
| #define MLX90635_EE_Eb 0x0034 /* Eb calibration register 32bit */ |
| #define MLX90635_EE_P_G 0x0038 /* P_G calibration register 16bit */ |
| #define MLX90635_EE_P_O 0x003A /* P_O calibration register 16bit */ |
| #define MLX90635_EE_Aa 0x003C /* Aa calibration register 16bit */ |
| #define MLX90635_EE_VERSION 0x003E /* Version bits 4:7 and 12:15 */ |
| #define MLX90635_EE_Gb 0x0040 /* Gb calibration register 16bit */ |
| |
| /* Device status register - volatile */ |
| #define MLX90635_REG_STATUS 0x0000 |
| #define MLX90635_STAT_BUSY BIT(6) /* Device busy indicator */ |
| #define MLX90635_STAT_BRST BIT(5) /* Brown out reset indicator */ |
| #define MLX90635_STAT_CYCLE_POS GENMASK(4, 2) /* Data position */ |
| #define MLX90635_STAT_END_CONV BIT(1) /* End of conversion indicator */ |
| #define MLX90635_STAT_DATA_RDY BIT(0) /* Data ready indicator */ |
| |
| /* EEPROM control register address - volatile */ |
| #define MLX90635_REG_EE 0x000C |
| #define MLX90635_EE_ACTIVE BIT(4) /* Power-on EEPROM */ |
| #define MLX90635_EE_BUSY_MASK BIT(15) |
| |
| #define MLX90635_REG_CMD 0x0010 /* Command register address */ |
| |
| /* Control register1 address - volatile */ |
| #define MLX90635_REG_CTRL1 0x0014 |
| #define MLX90635_CTRL1_REFRESH_RATE_MASK GENMASK(2, 0) |
| #define MLX90635_CTRL1_RES_CTRL_MASK GENMASK(4, 3) |
| #define MLX90635_CTRL1_TABLE_MASK BIT(15) /* Table select */ |
| |
| /* Control register2 address - volatile */ |
| #define MLX90635_REG_CTRL2 0x0016 |
| #define MLX90635_CTRL2_BURST_CNT_MASK GENMASK(10, 6) /* Burst count */ |
| #define MLX90635_CTRL2_MODE_MASK GENMASK(12, 11) /* Power mode */ |
| #define MLX90635_CTRL2_SOB_MASK BIT(15) |
| |
| /* PowerModes statuses */ |
| #define MLX90635_PWR_STATUS_HALT 0 |
| #define MLX90635_PWR_STATUS_SLEEP_STEP 1 |
| #define MLX90635_PWR_STATUS_STEP 2 |
| #define MLX90635_PWR_STATUS_CONTINUOUS 3 |
| |
| /* Measurement data addresses */ |
| #define MLX90635_RESULT_1 0x0002 |
| #define MLX90635_RESULT_2 0x0004 |
| #define MLX90635_RESULT_3 0x0006 |
| #define MLX90635_RESULT_4 0x0008 |
| #define MLX90635_RESULT_5 0x000A |
| |
| /* Timings (ms) */ |
| #define MLX90635_TIMING_RST_MIN 200 /* Minimum time after addressed reset command */ |
| #define MLX90635_TIMING_RST_MAX 250 /* Maximum time after addressed reset command */ |
| #define MLX90635_TIMING_POLLING 10000 /* Time between bit polling*/ |
| #define MLX90635_TIMING_EE_ACTIVE_MIN 100 /* Minimum time after activating the EEPROM for read */ |
| #define MLX90635_TIMING_EE_ACTIVE_MAX 150 /* Maximum time after activating the EEPROM for read */ |
| |
| /* Magic constants */ |
| #define MLX90635_ID_DSPv1 0x01 /* EEPROM DSP version */ |
| #define MLX90635_RESET_CMD 0x0006 /* Reset sensor (address or global) */ |
| #define MLX90635_MAX_MEAS_NUM 31 /* Maximum number of measurements in list */ |
| #define MLX90635_PTAT_DIV 12 /* Used to divide the PTAT value in pre-processing */ |
| #define MLX90635_IR_DIV 24 /* Used to divide the IR value in pre-processing */ |
| #define MLX90635_SLEEP_DELAY_MS 6000 /* Autosleep delay */ |
| #define MLX90635_MEAS_MAX_TIME 2000 /* Max measurement time in ms for the lowest refresh rate */ |
| #define MLX90635_READ_RETRIES 100 /* Number of read retries before quitting with timeout error */ |
| #define MLX90635_VERSION_MASK (GENMASK(15, 12) | GENMASK(7, 4)) |
| #define MLX90635_DSP_VERSION(reg) (((reg & GENMASK(14, 12)) >> 9) | ((reg & GENMASK(6, 4)) >> 4)) |
| #define MLX90635_DSP_FIXED BIT(15) |
| |
| |
| /** |
| * struct mlx90635_data - private data for the MLX90635 device |
| * @client: I2C client of the device |
| * @lock: Internal mutex because multiple reads are needed for single triggered |
| * measurement to ensure data consistency |
| * @regmap: Regmap of the device registers |
| * @regmap_ee: Regmap of the device EEPROM which can be cached |
| * @emissivity: Object emissivity from 0 to 1000 where 1000 = 1 |
| * @regulator: Regulator of the device |
| * @powerstatus: Current POWER status of the device |
| * @interaction_ts: Timestamp of the last temperature read that is used |
| * for power management in jiffies |
| */ |
| struct mlx90635_data { |
| struct i2c_client *client; |
| struct mutex lock; |
| struct regmap *regmap; |
| struct regmap *regmap_ee; |
| u16 emissivity; |
| struct regulator *regulator; |
| int powerstatus; |
| unsigned long interaction_ts; |
| }; |
| |
| static const struct regmap_range mlx90635_volatile_reg_range[] = { |
| regmap_reg_range(MLX90635_REG_STATUS, MLX90635_REG_STATUS), |
| regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5), |
| regmap_reg_range(MLX90635_REG_EE, MLX90635_REG_EE), |
| regmap_reg_range(MLX90635_REG_CMD, MLX90635_REG_CMD), |
| regmap_reg_range(MLX90635_REG_CTRL1, MLX90635_REG_CTRL2), |
| }; |
| |
| static const struct regmap_access_table mlx90635_volatile_regs_tbl = { |
| .yes_ranges = mlx90635_volatile_reg_range, |
| .n_yes_ranges = ARRAY_SIZE(mlx90635_volatile_reg_range), |
| }; |
| |
| static const struct regmap_range mlx90635_read_reg_range[] = { |
| regmap_reg_range(MLX90635_REG_STATUS, MLX90635_REG_STATUS), |
| regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5), |
| regmap_reg_range(MLX90635_REG_EE, MLX90635_REG_EE), |
| regmap_reg_range(MLX90635_REG_CMD, MLX90635_REG_CMD), |
| regmap_reg_range(MLX90635_REG_CTRL1, MLX90635_REG_CTRL2), |
| }; |
| |
| static const struct regmap_access_table mlx90635_readable_regs_tbl = { |
| .yes_ranges = mlx90635_read_reg_range, |
| .n_yes_ranges = ARRAY_SIZE(mlx90635_read_reg_range), |
| }; |
| |
| static const struct regmap_range mlx90635_no_write_reg_range[] = { |
| regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5), |
| }; |
| |
| static const struct regmap_access_table mlx90635_writeable_regs_tbl = { |
| .no_ranges = mlx90635_no_write_reg_range, |
| .n_no_ranges = ARRAY_SIZE(mlx90635_no_write_reg_range), |
| }; |
| |
| static const struct regmap_config mlx90635_regmap = { |
| .name = "mlx90635-registers", |
| .reg_stride = 1, |
| .reg_bits = 16, |
| .val_bits = 16, |
| |
| .volatile_table = &mlx90635_volatile_regs_tbl, |
| .rd_table = &mlx90635_readable_regs_tbl, |
| .wr_table = &mlx90635_writeable_regs_tbl, |
| |
| .use_single_read = true, |
| .use_single_write = true, |
| .can_multi_write = false, |
| .reg_format_endian = REGMAP_ENDIAN_BIG, |
| .val_format_endian = REGMAP_ENDIAN_BIG, |
| .cache_type = REGCACHE_RBTREE, |
| }; |
| |
| static const struct regmap_range mlx90635_read_ee_range[] = { |
| regmap_reg_range(MLX90635_EE_I2C_CFG, MLX90635_EE_CTRL2), |
| regmap_reg_range(MLX90635_EE_Ha, MLX90635_EE_Gb), |
| }; |
| |
| static const struct regmap_access_table mlx90635_readable_ees_tbl = { |
| .yes_ranges = mlx90635_read_ee_range, |
| .n_yes_ranges = ARRAY_SIZE(mlx90635_read_ee_range), |
| }; |
| |
| static const struct regmap_range mlx90635_no_write_ee_range[] = { |
| regmap_reg_range(MLX90635_ADDR_EEPROM, MLX90635_EE_Gb), |
| }; |
| |
| static const struct regmap_access_table mlx90635_writeable_ees_tbl = { |
| .no_ranges = mlx90635_no_write_ee_range, |
| .n_no_ranges = ARRAY_SIZE(mlx90635_no_write_ee_range), |
| }; |
| |
| static const struct regmap_config mlx90635_regmap_ee = { |
| .name = "mlx90635-eeprom", |
| .reg_stride = 1, |
| .reg_bits = 16, |
| .val_bits = 16, |
| |
| .volatile_table = NULL, |
| .rd_table = &mlx90635_readable_ees_tbl, |
| .wr_table = &mlx90635_writeable_ees_tbl, |
| |
| .use_single_read = true, |
| .use_single_write = true, |
| .can_multi_write = false, |
| .reg_format_endian = REGMAP_ENDIAN_BIG, |
| .val_format_endian = REGMAP_ENDIAN_BIG, |
| .cache_type = REGCACHE_RBTREE, |
| }; |
| |
| /** |
| * mlx90635_reset_delay() - Give the mlx90635 some time to reset properly |
| * If this is not done, the following I2C command(s) will not be accepted. |
| */ |
| static void mlx90635_reset_delay(void) |
| { |
| usleep_range(MLX90635_TIMING_RST_MIN, MLX90635_TIMING_RST_MAX); |
| } |
| |
| static int mlx90635_pwr_sleep_step(struct mlx90635_data *data) |
| { |
| int ret; |
| |
| if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) |
| return 0; |
| |
| ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, MLX90635_CTRL2_MODE_MASK, |
| FIELD_PREP(MLX90635_CTRL2_MODE_MASK, MLX90635_PWR_STATUS_SLEEP_STEP)); |
| if (ret < 0) |
| return ret; |
| |
| data->powerstatus = MLX90635_PWR_STATUS_SLEEP_STEP; |
| return 0; |
| } |
| |
| static int mlx90635_pwr_continuous(struct mlx90635_data *data) |
| { |
| int ret; |
| |
| if (data->powerstatus == MLX90635_PWR_STATUS_CONTINUOUS) |
| return 0; |
| |
| ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, MLX90635_CTRL2_MODE_MASK, |
| FIELD_PREP(MLX90635_CTRL2_MODE_MASK, MLX90635_PWR_STATUS_CONTINUOUS)); |
| if (ret < 0) |
| return ret; |
| |
| data->powerstatus = MLX90635_PWR_STATUS_CONTINUOUS; |
| return 0; |
| } |
| |
| static int mlx90635_read_ee_register(struct regmap *regmap, u16 reg_lsb, |
| s32 *reg_value) |
| { |
| unsigned int read; |
| u32 value; |
| int ret; |
| |
| ret = regmap_read(regmap, reg_lsb + 2, &read); |
| if (ret < 0) |
| return ret; |
| |
| value = read; |
| |
| ret = regmap_read(regmap, reg_lsb, &read); |
| if (ret < 0) |
| return ret; |
| |
| *reg_value = (read << 16) | (value & 0xffff); |
| |
| return 0; |
| } |
| |
| static int mlx90635_read_ee_ambient(struct regmap *regmap, s16 *PG, s16 *PO, s16 *Gb) |
| { |
| unsigned int read_tmp; |
| int ret; |
| |
| ret = regmap_read(regmap, MLX90635_EE_P_O, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *PO = (s16)read_tmp; |
| |
| ret = regmap_read(regmap, MLX90635_EE_P_G, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *PG = (s16)read_tmp; |
| |
| ret = regmap_read(regmap, MLX90635_EE_Gb, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *Gb = (u16)read_tmp; |
| |
| return 0; |
| } |
| |
| static int mlx90635_read_ee_object(struct regmap *regmap, u32 *Ea, u32 *Eb, u32 *Fa, s16 *Fb, |
| s16 *Ga, s16 *Gb, s16 *Ha, s16 *Hb, u16 *Fa_scale) |
| { |
| unsigned int read_tmp; |
| int ret; |
| |
| ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Ea, Ea); |
| if (ret < 0) |
| return ret; |
| |
| ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Eb, Eb); |
| if (ret < 0) |
| return ret; |
| |
| ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Fa, Fa); |
| if (ret < 0) |
| return ret; |
| |
| ret = regmap_read(regmap, MLX90635_EE_Ha, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *Ha = (s16)read_tmp; |
| |
| ret = regmap_read(regmap, MLX90635_EE_Hb, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *Hb = (s16)read_tmp; |
| |
| ret = regmap_read(regmap, MLX90635_EE_Ga, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *Ga = (s16)read_tmp; |
| |
| ret = regmap_read(regmap, MLX90635_EE_Gb, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *Gb = (s16)read_tmp; |
| |
| ret = regmap_read(regmap, MLX90635_EE_Fb, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *Fb = (s16)read_tmp; |
| |
| ret = regmap_read(regmap, MLX90635_EE_FASCALE, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *Fa_scale = (u16)read_tmp; |
| |
| return 0; |
| } |
| |
| static int mlx90635_calculate_dataset_ready_time(struct mlx90635_data *data, int *refresh_time) |
| { |
| unsigned int reg; |
| int ret; |
| |
| ret = regmap_read(data->regmap, MLX90635_REG_CTRL1, ®); |
| if (ret < 0) |
| return ret; |
| |
| *refresh_time = 2 * (MLX90635_MEAS_MAX_TIME >> FIELD_GET(MLX90635_CTRL1_REFRESH_RATE_MASK, reg)) + 80; |
| |
| return 0; |
| } |
| |
| static int mlx90635_perform_measurement_burst(struct mlx90635_data *data) |
| { |
| unsigned int reg_status; |
| int refresh_time; |
| int ret; |
| |
| ret = regmap_write_bits(data->regmap, MLX90635_REG_STATUS, |
| MLX90635_STAT_END_CONV, MLX90635_STAT_END_CONV); |
| if (ret < 0) |
| return ret; |
| |
| ret = mlx90635_calculate_dataset_ready_time(data, &refresh_time); |
| if (ret < 0) |
| return ret; |
| |
| ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, |
| FIELD_PREP(MLX90635_CTRL2_SOB_MASK, 1), |
| FIELD_PREP(MLX90635_CTRL2_SOB_MASK, 1)); |
| if (ret < 0) |
| return ret; |
| |
| msleep(refresh_time); /* Wait minimum time for dataset to be ready */ |
| |
| ret = regmap_read_poll_timeout(data->regmap, MLX90635_REG_STATUS, reg_status, |
| (!(reg_status & MLX90635_STAT_END_CONV)) == 0, |
| MLX90635_TIMING_POLLING, MLX90635_READ_RETRIES * 10000); |
| if (ret < 0) { |
| dev_err(&data->client->dev, "data not ready"); |
| return -ETIMEDOUT; |
| } |
| |
| return 0; |
| } |
| |
| static int mlx90635_read_ambient_raw(struct regmap *regmap, |
| s16 *ambient_new_raw, s16 *ambient_old_raw) |
| { |
| unsigned int read_tmp; |
| int ret; |
| |
| ret = regmap_read(regmap, MLX90635_RESULT_2, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *ambient_new_raw = (s16)read_tmp; |
| |
| ret = regmap_read(regmap, MLX90635_RESULT_3, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *ambient_old_raw = (s16)read_tmp; |
| |
| return 0; |
| } |
| |
| static int mlx90635_read_object_raw(struct regmap *regmap, s16 *object_raw) |
| { |
| unsigned int read_tmp; |
| s16 read; |
| int ret; |
| |
| ret = regmap_read(regmap, MLX90635_RESULT_1, &read_tmp); |
| if (ret < 0) |
| return ret; |
| |
| read = (s16)read_tmp; |
| |
| ret = regmap_read(regmap, MLX90635_RESULT_4, &read_tmp); |
| if (ret < 0) |
| return ret; |
| *object_raw = (read - (s16)read_tmp) / 2; |
| |
| return 0; |
| } |
| |
| static int mlx90635_read_all_channel(struct mlx90635_data *data, |
| s16 *ambient_new_raw, s16 *ambient_old_raw, |
| s16 *object_raw) |
| { |
| int ret; |
| |
| mutex_lock(&data->lock); |
| if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) { |
| /* Trigger measurement in Sleep Step mode */ |
| ret = mlx90635_perform_measurement_burst(data); |
| if (ret < 0) |
| goto read_unlock; |
| } |
| |
| ret = mlx90635_read_ambient_raw(data->regmap, ambient_new_raw, |
| ambient_old_raw); |
| if (ret < 0) |
| goto read_unlock; |
| |
| ret = mlx90635_read_object_raw(data->regmap, object_raw); |
| read_unlock: |
| mutex_unlock(&data->lock); |
| return ret; |
| } |
| |
| static s64 mlx90635_preprocess_temp_amb(s16 ambient_new_raw, |
| s16 ambient_old_raw, s16 Gb) |
| { |
| s64 VR_Ta, kGb, tmp; |
| |
| kGb = ((s64)Gb * 1000LL) >> 10ULL; |
| VR_Ta = (s64)ambient_old_raw * 1000000LL + |
| kGb * div64_s64(((s64)ambient_new_raw * 1000LL), |
| (MLX90635_PTAT_DIV)); |
| tmp = div64_s64( |
| div64_s64(((s64)ambient_new_raw * 1000000000000LL), |
| (MLX90635_PTAT_DIV)), VR_Ta); |
| return div64_s64(tmp << 19ULL, 1000LL); |
| } |
| |
| static s64 mlx90635_preprocess_temp_obj(s16 object_raw, |
| s16 ambient_new_raw, |
| s16 ambient_old_raw, s16 Gb) |
| { |
| s64 VR_IR, kGb, tmp; |
| |
| kGb = ((s64)Gb * 1000LL) >> 10ULL; |
| VR_IR = (s64)ambient_old_raw * 1000000LL + |
| kGb * (div64_s64((s64)ambient_new_raw * 1000LL, |
| MLX90635_PTAT_DIV)); |
| tmp = div64_s64( |
| div64_s64((s64)(object_raw * 1000000LL), |
| MLX90635_IR_DIV) * 1000000LL, |
| VR_IR); |
| return div64_s64((tmp << 19ULL), 1000LL); |
| } |
| |
| static s32 mlx90635_calc_temp_ambient(s16 ambient_new_raw, s16 ambient_old_raw, |
| u16 P_G, u16 P_O, s16 Gb) |
| { |
| s64 kPG, kPO, AMB; |
| |
| AMB = mlx90635_preprocess_temp_amb(ambient_new_raw, ambient_old_raw, |
| Gb); |
| kPG = ((s64)P_G * 1000000LL) >> 9ULL; |
| kPO = AMB - (((s64)P_O * 1000LL) >> 1ULL); |
| |
| return 30 * 1000LL + div64_s64(kPO * 1000000LL, kPG); |
| } |
| |
| static s32 mlx90635_calc_temp_object_iteration(s32 prev_object_temp, s64 object, |
| s64 TAdut, s64 TAdut4, s16 Ga, |
| u32 Fa, u16 Fa_scale, s16 Fb, |
| s16 Ha, s16 Hb, u16 emissivity) |
| { |
| s64 calcedGa, calcedGb, calcedFa, Alpha_corr; |
| s64 Ha_customer, Hb_customer; |
| |
| Ha_customer = ((s64)Ha * 1000000LL) >> 14ULL; |
| Hb_customer = ((s64)Hb * 100) >> 10ULL; |
| |
| calcedGa = ((s64)((s64)Ga * (prev_object_temp - 35 * 1000LL) |
| * 1000LL)) >> 24LL; |
| calcedGb = ((s64)(Fb * (TAdut - 30 * 1000000LL))) >> 24LL; |
| |
| Alpha_corr = ((s64)((s64)Fa * Ha_customer * 10000LL) >> Fa_scale); |
| Alpha_corr *= ((s64)(1 * 1000000LL + calcedGa + calcedGb)); |
| |
| Alpha_corr = div64_s64(Alpha_corr, 1000LL); |
| Alpha_corr *= emissivity; |
| Alpha_corr = div64_s64(Alpha_corr, 100LL); |
| calcedFa = div64_s64((s64)object * 100000000000LL, Alpha_corr); |
| |
| return (int_sqrt64(int_sqrt64(calcedFa * 100000000LL + TAdut4)) |
| - 27315 - Hb_customer) * 10; |
| } |
| |
| static s64 mlx90635_calc_ta4(s64 TAdut, s64 scale) |
| { |
| return (div64_s64(TAdut, scale) + 27315) * |
| (div64_s64(TAdut, scale) + 27315) * |
| (div64_s64(TAdut, scale) + 27315) * |
| (div64_s64(TAdut, scale) + 27315); |
| } |
| |
| static s32 mlx90635_calc_temp_object(s64 object, s64 ambient, u32 Ea, u32 Eb, |
| s16 Ga, u32 Fa, u16 Fa_scale, s16 Fb, s16 Ha, s16 Hb, |
| u16 tmp_emi) |
| { |
| s64 kTA, kTA0, TAdut, TAdut4; |
| s64 temp = 35000; |
| s8 i; |
| |
| kTA = (Ea * 1000LL) >> 16LL; |
| kTA0 = (Eb * 1000LL) >> 8LL; |
| TAdut = div64_s64(((ambient - kTA0) * 1000000LL), kTA) + 30 * 1000000LL; |
| TAdut4 = mlx90635_calc_ta4(TAdut, 10000LL); |
| |
| /* Iterations of calculation as described in datasheet */ |
| for (i = 0; i < 5; ++i) { |
| temp = mlx90635_calc_temp_object_iteration(temp, object, TAdut, TAdut4, |
| Ga, Fa, Fa_scale, Fb, Ha, Hb, |
| tmp_emi); |
| } |
| return temp; |
| } |
| |
| static int mlx90635_calc_object(struct mlx90635_data *data, int *val) |
| { |
| s16 ambient_new_raw, ambient_old_raw, object_raw; |
| s16 Fb, Ga, Gb, Ha, Hb; |
| s64 object, ambient; |
| u32 Ea, Eb, Fa; |
| u16 Fa_scale; |
| int ret; |
| |
| ret = mlx90635_read_ee_object(data->regmap_ee, &Ea, &Eb, &Fa, &Fb, &Ga, &Gb, &Ha, &Hb, &Fa_scale); |
| if (ret < 0) |
| return ret; |
| |
| ret = mlx90635_read_all_channel(data, |
| &ambient_new_raw, &ambient_old_raw, |
| &object_raw); |
| if (ret < 0) |
| return ret; |
| |
| ambient = mlx90635_preprocess_temp_amb(ambient_new_raw, |
| ambient_old_raw, Gb); |
| object = mlx90635_preprocess_temp_obj(object_raw, |
| ambient_new_raw, |
| ambient_old_raw, Gb); |
| |
| *val = mlx90635_calc_temp_object(object, ambient, Ea, Eb, Ga, Fa, Fa_scale, Fb, |
| Ha, Hb, data->emissivity); |
| return 0; |
| } |
| |
| static int mlx90635_calc_ambient(struct mlx90635_data *data, int *val) |
| { |
| s16 ambient_new_raw, ambient_old_raw; |
| s16 PG, PO, Gb; |
| int ret; |
| |
| ret = mlx90635_read_ee_ambient(data->regmap_ee, &PG, &PO, &Gb); |
| if (ret < 0) |
| return ret; |
| |
| mutex_lock(&data->lock); |
| if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) { |
| ret = mlx90635_perform_measurement_burst(data); |
| if (ret < 0) |
| goto read_ambient_unlock; |
| } |
| |
| ret = mlx90635_read_ambient_raw(data->regmap, &ambient_new_raw, |
| &ambient_old_raw); |
| read_ambient_unlock: |
| mutex_unlock(&data->lock); |
| if (ret < 0) |
| return ret; |
| |
| *val = mlx90635_calc_temp_ambient(ambient_new_raw, ambient_old_raw, |
| PG, PO, Gb); |
| return ret; |
| } |
| |
| static int mlx90635_get_refresh_rate(struct mlx90635_data *data, |
| unsigned int *refresh_rate) |
| { |
| unsigned int reg; |
| int ret; |
| |
| ret = regmap_read(data->regmap, MLX90635_REG_CTRL1, ®); |
| if (ret < 0) |
| return ret; |
| |
| *refresh_rate = FIELD_GET(MLX90635_CTRL1_REFRESH_RATE_MASK, reg); |
| |
| return 0; |
| } |
| |
| static const struct { |
| int val; |
| int val2; |
| } mlx90635_freqs[] = { |
| { 0, 200000 }, |
| { 0, 500000 }, |
| { 0, 900000 }, |
| { 1, 700000 }, |
| { 3, 0 }, |
| { 4, 800000 }, |
| { 6, 900000 }, |
| { 8, 900000 } |
| }; |
| |
| /** |
| * mlx90635_pm_interaction_wakeup() - Measure time between user interactions to change powermode |
| * @data: pointer to mlx90635_data object containing interaction_ts information |
| * |
| * Switch to continuous mode when interaction is faster than MLX90635_MEAS_MAX_TIME. Update the |
| * interaction_ts for each function call with the jiffies to enable measurement between function |
| * calls. Initial value of the interaction_ts needs to be set before this function call. |
| */ |
| static int mlx90635_pm_interaction_wakeup(struct mlx90635_data *data) |
| { |
| unsigned long now; |
| int ret; |
| |
| now = jiffies; |
| if (time_in_range(now, data->interaction_ts, |
| data->interaction_ts + |
| msecs_to_jiffies(MLX90635_MEAS_MAX_TIME + 100))) { |
| ret = mlx90635_pwr_continuous(data); |
| if (ret < 0) |
| return ret; |
| } |
| |
| data->interaction_ts = now; |
| |
| return 0; |
| } |
| |
| static int mlx90635_read_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *channel, int *val, |
| int *val2, long mask) |
| { |
| struct mlx90635_data *data = iio_priv(indio_dev); |
| int ret; |
| int cr; |
| |
| pm_runtime_get_sync(&data->client->dev); |
| ret = mlx90635_pm_interaction_wakeup(data); |
| if (ret < 0) |
| goto mlx90635_read_raw_pm; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_PROCESSED: |
| switch (channel->channel2) { |
| case IIO_MOD_TEMP_AMBIENT: |
| ret = mlx90635_calc_ambient(data, val); |
| if (ret < 0) |
| goto mlx90635_read_raw_pm; |
| |
| ret = IIO_VAL_INT; |
| break; |
| case IIO_MOD_TEMP_OBJECT: |
| ret = mlx90635_calc_object(data, val); |
| if (ret < 0) |
| goto mlx90635_read_raw_pm; |
| |
| ret = IIO_VAL_INT; |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| break; |
| case IIO_CHAN_INFO_CALIBEMISSIVITY: |
| if (data->emissivity == 1000) { |
| *val = 1; |
| *val2 = 0; |
| } else { |
| *val = 0; |
| *val2 = data->emissivity * 1000; |
| } |
| ret = IIO_VAL_INT_PLUS_MICRO; |
| break; |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| ret = mlx90635_get_refresh_rate(data, &cr); |
| if (ret < 0) |
| goto mlx90635_read_raw_pm; |
| |
| *val = mlx90635_freqs[cr].val; |
| *val2 = mlx90635_freqs[cr].val2; |
| ret = IIO_VAL_INT_PLUS_MICRO; |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| mlx90635_read_raw_pm: |
| pm_runtime_mark_last_busy(&data->client->dev); |
| pm_runtime_put_autosuspend(&data->client->dev); |
| return ret; |
| } |
| |
| static int mlx90635_write_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *channel, int val, |
| int val2, long mask) |
| { |
| struct mlx90635_data *data = iio_priv(indio_dev); |
| int ret; |
| int i; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_CALIBEMISSIVITY: |
| /* Confirm we are within 0 and 1.0 */ |
| if (val < 0 || val2 < 0 || val > 1 || |
| (val == 1 && val2 != 0)) |
| return -EINVAL; |
| data->emissivity = val * 1000 + val2 / 1000; |
| return 0; |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| for (i = 0; i < ARRAY_SIZE(mlx90635_freqs); i++) { |
| if (val == mlx90635_freqs[i].val && |
| val2 == mlx90635_freqs[i].val2) |
| break; |
| } |
| if (i == ARRAY_SIZE(mlx90635_freqs)) |
| return -EINVAL; |
| |
| ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL1, |
| MLX90635_CTRL1_REFRESH_RATE_MASK, i); |
| |
| return ret; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int mlx90635_read_avail(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| const int **vals, int *type, int *length, |
| long mask) |
| { |
| switch (mask) { |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| *vals = (int *)mlx90635_freqs; |
| *type = IIO_VAL_INT_PLUS_MICRO; |
| *length = 2 * ARRAY_SIZE(mlx90635_freqs); |
| return IIO_AVAIL_LIST; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static const struct iio_chan_spec mlx90635_channels[] = { |
| { |
| .type = IIO_TEMP, |
| .modified = 1, |
| .channel2 = IIO_MOD_TEMP_AMBIENT, |
| .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), |
| .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), |
| .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), |
| }, |
| { |
| .type = IIO_TEMP, |
| .modified = 1, |
| .channel2 = IIO_MOD_TEMP_OBJECT, |
| .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | |
| BIT(IIO_CHAN_INFO_CALIBEMISSIVITY), |
| .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), |
| .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), |
| }, |
| }; |
| |
| static const struct iio_info mlx90635_info = { |
| .read_raw = mlx90635_read_raw, |
| .write_raw = mlx90635_write_raw, |
| .read_avail = mlx90635_read_avail, |
| }; |
| |
| static void mlx90635_sleep(void *_data) |
| { |
| struct mlx90635_data *data = _data; |
| |
| mlx90635_pwr_sleep_step(data); |
| } |
| |
| static int mlx90635_suspend(struct mlx90635_data *data) |
| { |
| return mlx90635_pwr_sleep_step(data); |
| } |
| |
| static int mlx90635_wakeup(struct mlx90635_data *data) |
| { |
| s16 Fb, Ga, Gb, Ha, Hb, PG, PO; |
| unsigned int dsp_version; |
| u32 Ea, Eb, Fa; |
| u16 Fa_scale; |
| int ret; |
| |
| regcache_cache_bypass(data->regmap_ee, false); |
| regcache_cache_only(data->regmap_ee, false); |
| regcache_cache_only(data->regmap, false); |
| |
| ret = mlx90635_pwr_continuous(data); |
| if (ret < 0) { |
| dev_err(&data->client->dev, "Switch to continuous mode failed\n"); |
| return ret; |
| } |
| ret = regmap_write_bits(data->regmap, MLX90635_REG_EE, |
| MLX90635_EE_ACTIVE, MLX90635_EE_ACTIVE); |
| if (ret < 0) { |
| dev_err(&data->client->dev, "Powering EEPROM failed\n"); |
| return ret; |
| } |
| usleep_range(MLX90635_TIMING_EE_ACTIVE_MIN, MLX90635_TIMING_EE_ACTIVE_MAX); |
| |
| regcache_mark_dirty(data->regmap_ee); |
| |
| ret = regcache_sync(data->regmap_ee); |
| if (ret < 0) { |
| dev_err(&data->client->dev, |
| "Failed to sync cache: %d\n", ret); |
| return ret; |
| } |
| |
| ret = mlx90635_read_ee_ambient(data->regmap_ee, &PG, &PO, &Gb); |
| if (ret < 0) { |
| dev_err(&data->client->dev, |
| "Failed to read to cache Ambient coefficients EEPROM region: %d\n", ret); |
| return ret; |
| } |
| |
| ret = mlx90635_read_ee_object(data->regmap_ee, &Ea, &Eb, &Fa, &Fb, &Ga, &Gb, &Ha, &Hb, &Fa_scale); |
| if (ret < 0) { |
| dev_err(&data->client->dev, |
| "Failed to read to cache Object coefficients EEPROM region: %d\n", ret); |
| return ret; |
| } |
| |
| ret = regmap_read(data->regmap_ee, MLX90635_EE_VERSION, &dsp_version); |
| if (ret < 0) { |
| dev_err(&data->client->dev, |
| "Failed to read to cache of EEPROM version: %d\n", ret); |
| return ret; |
| } |
| |
| regcache_cache_only(data->regmap_ee, true); |
| |
| return ret; |
| } |
| |
| static void mlx90635_disable_regulator(void *_data) |
| { |
| struct mlx90635_data *data = _data; |
| int ret; |
| |
| ret = regulator_disable(data->regulator); |
| if (ret < 0) |
| dev_err(regmap_get_device(data->regmap), |
| "Failed to disable power regulator: %d\n", ret); |
| } |
| |
| static int mlx90635_enable_regulator(struct mlx90635_data *data) |
| { |
| int ret; |
| |
| ret = regulator_enable(data->regulator); |
| if (ret < 0) { |
| dev_err(regmap_get_device(data->regmap), "Failed to enable power regulator!\n"); |
| return ret; |
| } |
| |
| mlx90635_reset_delay(); |
| |
| return ret; |
| } |
| |
| static int mlx90635_probe(struct i2c_client *client) |
| { |
| struct mlx90635_data *mlx90635; |
| struct iio_dev *indio_dev; |
| unsigned int dsp_version; |
| struct regmap *regmap; |
| struct regmap *regmap_ee; |
| int ret; |
| |
| indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90635)); |
| if (!indio_dev) |
| return dev_err_probe(&client->dev, -ENOMEM, "failed to allocate device\n"); |
| |
| regmap = devm_regmap_init_i2c(client, &mlx90635_regmap); |
| if (IS_ERR(regmap)) |
| return dev_err_probe(&client->dev, PTR_ERR(regmap), |
| "failed to allocate regmap\n"); |
| |
| regmap_ee = devm_regmap_init_i2c(client, &mlx90635_regmap_ee); |
| if (IS_ERR(regmap)) |
| return dev_err_probe(&client->dev, PTR_ERR(regmap), |
| "failed to allocate regmap\n"); |
| |
| mlx90635 = iio_priv(indio_dev); |
| i2c_set_clientdata(client, indio_dev); |
| mlx90635->client = client; |
| mlx90635->regmap = regmap; |
| mlx90635->regmap_ee = regmap_ee; |
| mlx90635->powerstatus = MLX90635_PWR_STATUS_SLEEP_STEP; |
| |
| mutex_init(&mlx90635->lock); |
| indio_dev->name = "mlx90635"; |
| indio_dev->modes = INDIO_DIRECT_MODE; |
| indio_dev->info = &mlx90635_info; |
| indio_dev->channels = mlx90635_channels; |
| indio_dev->num_channels = ARRAY_SIZE(mlx90635_channels); |
| |
| mlx90635->regulator = devm_regulator_get(&client->dev, "vdd"); |
| if (IS_ERR(mlx90635->regulator)) |
| return dev_err_probe(&client->dev, PTR_ERR(mlx90635->regulator), |
| "failed to get vdd regulator"); |
| |
| ret = mlx90635_enable_regulator(mlx90635); |
| if (ret < 0) |
| return ret; |
| |
| ret = devm_add_action_or_reset(&client->dev, mlx90635_disable_regulator, |
| mlx90635); |
| if (ret < 0) |
| return dev_err_probe(&client->dev, ret, |
| "failed to setup regulator cleanup action\n"); |
| |
| ret = mlx90635_wakeup(mlx90635); |
| if (ret < 0) |
| return dev_err_probe(&client->dev, ret, "wakeup failed\n"); |
| |
| ret = devm_add_action_or_reset(&client->dev, mlx90635_sleep, mlx90635); |
| if (ret < 0) |
| return dev_err_probe(&client->dev, ret, |
| "failed to setup low power cleanup\n"); |
| |
| ret = regmap_read(mlx90635->regmap_ee, MLX90635_EE_VERSION, &dsp_version); |
| if (ret < 0) |
| return dev_err_probe(&client->dev, ret, "read of version failed\n"); |
| |
| dsp_version = dsp_version & MLX90635_VERSION_MASK; |
| |
| if (FIELD_GET(MLX90635_DSP_FIXED, dsp_version)) { |
| if (MLX90635_DSP_VERSION(dsp_version) == MLX90635_ID_DSPv1) { |
| dev_dbg(&client->dev, |
| "Detected DSP v1 calibration %x\n", dsp_version); |
| } else { |
| dev_dbg(&client->dev, |
| "Detected Unknown EEPROM calibration %lx\n", |
| MLX90635_DSP_VERSION(dsp_version)); |
| } |
| } else { |
| return dev_err_probe(&client->dev, -EPROTONOSUPPORT, |
| "Wrong fixed top bit %x (expected 0x8X0X)\n", |
| dsp_version); |
| } |
| |
| mlx90635->emissivity = 1000; |
| mlx90635->interaction_ts = jiffies; /* Set initial value */ |
| |
| pm_runtime_get_noresume(&client->dev); |
| pm_runtime_set_active(&client->dev); |
| |
| ret = devm_pm_runtime_enable(&client->dev); |
| if (ret) |
| return dev_err_probe(&client->dev, ret, |
| "failed to enable powermanagement\n"); |
| |
| pm_runtime_set_autosuspend_delay(&client->dev, MLX90635_SLEEP_DELAY_MS); |
| pm_runtime_use_autosuspend(&client->dev); |
| pm_runtime_put_autosuspend(&client->dev); |
| |
| return devm_iio_device_register(&client->dev, indio_dev); |
| } |
| |
| static const struct i2c_device_id mlx90635_id[] = { |
| { "mlx90635" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, mlx90635_id); |
| |
| static const struct of_device_id mlx90635_of_match[] = { |
| { .compatible = "melexis,mlx90635" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, mlx90635_of_match); |
| |
| static int mlx90635_pm_suspend(struct device *dev) |
| { |
| struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev)); |
| int ret; |
| |
| ret = mlx90635_suspend(data); |
| if (ret < 0) |
| return ret; |
| |
| ret = regulator_disable(data->regulator); |
| if (ret < 0) |
| dev_err(regmap_get_device(data->regmap), |
| "Failed to disable power regulator: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static int mlx90635_pm_resume(struct device *dev) |
| { |
| struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev)); |
| int ret; |
| |
| ret = mlx90635_enable_regulator(data); |
| if (ret < 0) |
| return ret; |
| |
| return mlx90635_wakeup(data); |
| } |
| |
| static int mlx90635_pm_runtime_suspend(struct device *dev) |
| { |
| struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev)); |
| |
| return mlx90635_pwr_sleep_step(data); |
| } |
| |
| static const struct dev_pm_ops mlx90635_pm_ops = { |
| SYSTEM_SLEEP_PM_OPS(mlx90635_pm_suspend, mlx90635_pm_resume) |
| RUNTIME_PM_OPS(mlx90635_pm_runtime_suspend, NULL, NULL) |
| }; |
| |
| static struct i2c_driver mlx90635_driver = { |
| .driver = { |
| .name = "mlx90635", |
| .of_match_table = mlx90635_of_match, |
| .pm = pm_ptr(&mlx90635_pm_ops), |
| }, |
| .probe = mlx90635_probe, |
| .id_table = mlx90635_id, |
| }; |
| module_i2c_driver(mlx90635_driver); |
| |
| MODULE_AUTHOR("Crt Mori <cmo@melexis.com>"); |
| MODULE_DESCRIPTION("Melexis MLX90635 contactless Infra Red temperature sensor driver"); |
| MODULE_LICENSE("GPL"); |