#include <linux/err.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
+#include <linux/iopoll.h>
 #include <linux/kernel.h>
+#include <linux/limits.h>
 #include <linux/module.h>
 #include <linux/math64.h>
 #include <linux/of.h>
 /* Control register address - volatile */
 #define MLX90632_REG_CONTROL   0x3001 /* Control Register address */
 #define   MLX90632_CFG_PWR_MASK                GENMASK(2, 1) /* PowerMode Mask */
+#define   MLX90632_CFG_MTYP_MASK               GENMASK(8, 4) /* Meas select Mask */
+
 /* PowerModes statuses */
 #define MLX90632_PWR_STATUS(ctrl_val) (ctrl_val << 1)
 #define MLX90632_PWR_STATUS_HALT MLX90632_PWR_STATUS(0) /* hold */
 #define MLX90632_PWR_STATUS_STEP MLX90632_PWR_STATUS(2) /* step */
 #define MLX90632_PWR_STATUS_CONTINUOUS MLX90632_PWR_STATUS(3) /* continuous*/
 
+/* Measurement types */
+#define MLX90632_MTYP_MEDICAL 0
+#define MLX90632_MTYP_EXTENDED 17
+
+/* Measurement type select*/
+#define MLX90632_MTYP_STATUS(ctrl_val) (ctrl_val << 4)
+#define MLX90632_MTYP_STATUS_MEDICAL MLX90632_MTYP_STATUS(MLX90632_MTYP_MEDICAL)
+#define MLX90632_MTYP_STATUS_EXTENDED MLX90632_MTYP_STATUS(MLX90632_MTYP_EXTENDED)
+
+/* I2C command register - volatile */
+#define MLX90632_REG_I2C_CMD    0x3005 /* I2C command Register address */
+
 /* Device status register - volatile */
 #define MLX90632_REG_STATUS    0x3fff /* Device status register */
 #define   MLX90632_STAT_BUSY           BIT(10) /* Device busy indicator */
 #define MLX90632_RAM_2(meas_num)       (MLX90632_ADDR_RAM + 3 * meas_num + 1)
 #define MLX90632_RAM_3(meas_num)       (MLX90632_ADDR_RAM + 3 * meas_num + 2)
 
+/* Name important RAM_MEAS channels */
+#define MLX90632_RAM_DSP5_EXTENDED_AMBIENT_1 MLX90632_RAM_3(17)
+#define MLX90632_RAM_DSP5_EXTENDED_AMBIENT_2 MLX90632_RAM_3(18)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_1 MLX90632_RAM_1(17)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_2 MLX90632_RAM_2(17)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_3 MLX90632_RAM_1(18)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_4 MLX90632_RAM_2(18)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_5 MLX90632_RAM_1(19)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_6 MLX90632_RAM_2(19)
+
 /* Magic constants */
 #define MLX90632_ID_MEDICAL    0x0105 /* EEPROM DSPv5 Medical device id */
 #define MLX90632_ID_CONSUMER   0x0205 /* EEPROM DSPv5 Consumer device id */
+#define MLX90632_ID_EXTENDED   0x0505 /* EEPROM DSPv5 Extended range device id */
+#define MLX90632_ID_MASK       GENMASK(14, 0) /* DSP version and device ID in EE_VERSION */
 #define MLX90632_DSP_VERSION   5 /* DSP version */
 #define MLX90632_DSP_MASK      GENMASK(7, 0) /* DSP version in EE_VERSION */
 #define MLX90632_RESET_CMD     0x0006 /* Reset sensor (address or global) */
 #define MLX90632_REF_3         12LL /**< ResCtrlRef value of Channel 3 */
 #define MLX90632_MAX_MEAS_NUM  31 /**< Maximum measurements in list */
 #define MLX90632_SLEEP_DELAY_MS 3000 /**< Autosleep delay */
+#define MLX90632_EXTENDED_LIMIT 27000 /* Extended mode raw value limit */
 
 /**
  * struct mlx90632_data - private data for the MLX90632 device
  * @lock: Internal mutex for multiple reads for single measurement
  * @regmap: Regmap of the device
  * @emissivity: Object emissivity from 0 to 1000 where 1000 = 1.
+ * @mtyp: Measurement type physical sensor configuration for extended range
+ *        calculations
+ * @object_ambient_temperature: Ambient temperature at object (might differ of
+ *                              the ambient temperature of sensor.
  */
 struct mlx90632_data {
        struct i2c_client *client;
        struct mutex lock;
        struct regmap *regmap;
        u16 emissivity;
+       u8 mtyp;
+       u32 object_ambient_temperature;
 };
 
 static const struct regmap_range mlx90632_volatile_reg_range[] = {
        regmap_reg_range(MLX90632_REG_I2C_ADDR, MLX90632_REG_CONTROL),
+       regmap_reg_range(MLX90632_REG_I2C_CMD, MLX90632_REG_I2C_CMD),
        regmap_reg_range(MLX90632_REG_STATUS, MLX90632_REG_STATUS),
        regmap_reg_range(MLX90632_RAM_1(0),
                         MLX90632_RAM_3(MLX90632_MAX_MEAS_NUM)),
        regmap_reg_range(MLX90632_EE_CTRL, MLX90632_EE_I2C_ADDR),
        regmap_reg_range(MLX90632_EE_Ha, MLX90632_EE_Hb),
        regmap_reg_range(MLX90632_REG_I2C_ADDR, MLX90632_REG_CONTROL),
+       regmap_reg_range(MLX90632_REG_I2C_CMD, MLX90632_REG_I2C_CMD),
        regmap_reg_range(MLX90632_REG_STATUS, MLX90632_REG_STATUS),
        regmap_reg_range(MLX90632_RAM_1(0),
                         MLX90632_RAM_3(MLX90632_MAX_MEAS_NUM)),
        return (reg_status & MLX90632_STAT_CYCLE_POS) >> 2;
 }
 
+static int mlx90632_set_meas_type(struct regmap *regmap, u8 type)
+{
+       int ret;
+
+       if ((type != MLX90632_MTYP_MEDICAL) && (type != MLX90632_MTYP_EXTENDED))
+               return -EINVAL;
+
+       ret = regmap_write(regmap, MLX90632_REG_I2C_CMD, MLX90632_RESET_CMD);
+       if (ret < 0)
+               return ret;
+
+       ret = regmap_write_bits(regmap, MLX90632_REG_CONTROL,
+                                (MLX90632_CFG_MTYP_MASK | MLX90632_CFG_PWR_MASK),
+                                (MLX90632_MTYP_STATUS(type) | MLX90632_PWR_STATUS_HALT));
+       if (ret < 0)
+               return ret;
+
+       return mlx90632_pwr_continuous(regmap);
+}
+
 static int mlx90632_channel_new_select(int perform_ret, uint8_t *channel_new,
                                       uint8_t *channel_old)
 {
        return ret;
 }
 
+static int mlx90632_read_ambient_raw_extended(struct regmap *regmap,
+                                             s16 *ambient_new_raw, s16 *ambient_old_raw)
+{
+       unsigned int read_tmp;
+       int ret;
+
+       ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_AMBIENT_1, &read_tmp);
+       if (ret < 0)
+               return ret;
+       *ambient_new_raw = (s16)read_tmp;
+
+       ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_AMBIENT_2, &read_tmp);
+       if (ret < 0)
+               return ret;
+       *ambient_old_raw = (s16)read_tmp;
+
+       return 0;
+}
+
+static int mlx90632_read_object_raw_extended(struct regmap *regmap, s16 *object_new_raw)
+{
+       unsigned int read_tmp;
+       s32 read;
+       int ret;
+
+       ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_1, &read_tmp);
+       if (ret < 0)
+               return ret;
+       read = (s16)read_tmp;
+
+       ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_2, &read_tmp);
+       if (ret < 0)
+               return ret;
+       read = read - (s16)read_tmp;
+
+       ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_3, &read_tmp);
+       if (ret < 0)
+               return ret;
+       read = read - (s16)read_tmp;
+
+       ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_4, &read_tmp);
+       if (ret < 0)
+               return ret;
+       read = (read + (s16)read_tmp) / 2;
+
+       ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_5, &read_tmp);
+       if (ret < 0)
+               return ret;
+       read = read + (s16)read_tmp;
+
+       ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_6, &read_tmp);
+       if (ret < 0)
+               return ret;
+       read = read + (s16)read_tmp;
+
+       if (read > S16_MAX || read < S16_MIN)
+               return -ERANGE;
+
+       *object_new_raw = read;
+
+       return 0;
+}
+
+static int mlx90632_read_all_channel_extended(struct mlx90632_data *data, s16 *object_new_raw,
+                                             s16 *ambient_new_raw, s16 *ambient_old_raw)
+{
+       s32 ret, meas;
+
+       mutex_lock(&data->lock);
+       ret = mlx90632_set_meas_type(data->regmap, MLX90632_MTYP_EXTENDED);
+       if (ret < 0)
+               goto read_unlock;
+
+       ret = read_poll_timeout(mlx90632_perform_measurement, meas, meas == 19,
+                               50000, 800000, false, data);
+       if (ret != 0)
+               goto read_unlock;
+
+       ret = mlx90632_read_object_raw_extended(data->regmap, object_new_raw);
+       if (ret < 0)
+               goto read_unlock;
+
+       ret = mlx90632_read_ambient_raw_extended(data->regmap, ambient_new_raw, ambient_old_raw);
+
+read_unlock:
+       (void) mlx90632_set_meas_type(data->regmap, MLX90632_MTYP_MEDICAL);
+
+       mutex_unlock(&data->lock);
+       return ret;
+}
+
 static int mlx90632_read_ee_register(struct regmap *regmap, u16 reg_lsb,
                                     s32 *reg_value)
 {
        return div64_s64((tmp << 19ULL), 1000LL);
 }
 
+static s64 mlx90632_preprocess_temp_obj_extended(s16 object_new_raw, s16 ambient_new_raw,
+                                                s16 ambient_old_raw, s16 Ka)
+{
+       s64 VR_IR, kKa, tmp;
+
+       kKa = ((s64)Ka * 1000LL) >> 10ULL;
+       VR_IR = (s64)ambient_old_raw * 1000000LL +
+               kKa * div64_s64((s64)ambient_new_raw * 1000LL,
+                               MLX90632_REF_3);
+       tmp = div64_s64(
+                       div64_s64((s64) object_new_raw * 1000000000000LL, MLX90632_REF_12),
+                       VR_IR);
+       return div64_s64(tmp << 19ULL, 1000LL);
+}
+
 static s32 mlx90632_calc_temp_ambient(s16 ambient_new_raw, s16 ambient_old_raw,
-                                     s32 P_T, s32 P_R, s32 P_G, s32 P_O,
-                                     s16 Gb)
+                                     s32 P_T, s32 P_R, s32 P_G, s32 P_O, s16 Gb)
 {
        s64 Asub, Bsub, Ablock, Bblock, Cblock, AMB, sum;
 
        return temp;
 }
 
+static s32 mlx90632_calc_temp_object_extended(s64 object, s64 ambient, s64 reflected,
+                                             s32 Ea, s32 Eb, s32 Fa, s32 Fb, s32 Ga,
+                                             s16 Ha, s16 Hb, u16 tmp_emi)
+{
+       s64 kTA, kTA0, TAdut, TAdut4, Tr4, TaTr4;
+       s64 temp = 25000;
+       s8 i;
+
+       kTA = (Ea * 1000LL) >> 16LL;
+       kTA0 = (Eb * 1000LL) >> 8LL;
+       TAdut = div64_s64((ambient - kTA0) * 1000000LL, kTA) + 25 * 1000000LL;
+       Tr4 = mlx90632_calc_ta4(reflected, 10);
+       TAdut4 = mlx90632_calc_ta4(TAdut, 10000LL);
+       TaTr4 = Tr4 - div64_s64(Tr4 - TAdut4, tmp_emi) * 1000;
+
+       /* Iterations of calculation as described in datasheet */
+       for (i = 0; i < 5; ++i) {
+               temp = mlx90632_calc_temp_object_iteration(temp, object, TAdut, TaTr4,
+                                                          Fa / 2, Fb, Ga, Ha, Hb,
+                                                          tmp_emi);
+       }
+
+       return temp;
+}
+
 static int mlx90632_calc_object_dsp105(struct mlx90632_data *data, int *val)
 {
        s32 ret;
        if (ret < 0)
                return ret;
 
+       if (object_new_raw > MLX90632_EXTENDED_LIMIT &&
+           data->mtyp == MLX90632_MTYP_EXTENDED) {
+               ret = mlx90632_read_all_channel_extended(data, &object_new_raw,
+                                                        &ambient_new_raw, &ambient_old_raw);
+               if (ret < 0)
+                       return ret;
+
+               /* Use extended mode calculations */
+               ambient = mlx90632_preprocess_temp_amb(ambient_new_raw,
+                                                      ambient_old_raw, Gb);
+               object = mlx90632_preprocess_temp_obj_extended(object_new_raw,
+                                                              ambient_new_raw,
+                                                              ambient_old_raw, Ka);
+               *val = mlx90632_calc_temp_object_extended(object, ambient,
+                                                         data->object_ambient_temperature,
+                                                         Ea, Eb, Fa, Fb, Ga,
+                                                         Ha, Hb, data->emissivity);
+               return 0;
+       }
+
        ambient = mlx90632_preprocess_temp_amb(ambient_new_raw,
                                               ambient_old_raw, Gb);
        object = mlx90632_preprocess_temp_obj(object_new_raw,
        i2c_set_clientdata(client, indio_dev);
        mlx90632->client = client;
        mlx90632->regmap = regmap;
+       mlx90632->mtyp = MLX90632_MTYP_MEDICAL;
 
        mutex_init(&mlx90632->lock);
        indio_dev->name = id->name;
                dev_err(&client->dev, "read of version failed: %d\n", ret);
                return ret;
        }
+       read = read & MLX90632_ID_MASK;
        if (read == MLX90632_ID_MEDICAL) {
                dev_dbg(&client->dev,
                        "Detected Medical EEPROM calibration %x\n", read);
        } else if (read == MLX90632_ID_CONSUMER) {
                dev_dbg(&client->dev,
                        "Detected Consumer EEPROM calibration %x\n", read);
+       } else if (read == MLX90632_ID_EXTENDED) {
+               dev_dbg(&client->dev,
+                       "Detected Extended range EEPROM calibration %x\n", read);
+               mlx90632->mtyp = MLX90632_MTYP_EXTENDED;
        } else if ((read & MLX90632_DSP_MASK) == MLX90632_DSP_VERSION) {
                dev_dbg(&client->dev,
                        "Detected Unknown EEPROM calibration %x\n", read);      
        }
 
        mlx90632->emissivity = 1000;
+       mlx90632->object_ambient_temperature = 25000; /* 25 degrees milliCelsius */
 
        pm_runtime_disable(&client->dev);
        ret = pm_runtime_set_active(&client->dev);