| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019, Linaro Limited. |
| * Copyright (c) 2015, 2017, 2021 The Linux Foundation. All rights reserved. |
| * |
| */ |
| |
| #include <linux/bitops.h> |
| #include <linux/regmap.h> |
| #include <linux/delay.h> |
| #include <linux/slab.h> |
| #include <linux/thermal.h> |
| #include <linux/io.h> |
| #include <linux/interrupt.h> |
| #include "tsens.h" |
| |
| /* TSENS register data */ |
| #define TSENS_CNTL_ADDR 0x4 |
| #define TSENS_CONFIG_ADDR 0x50 |
| #define TSENS_TRDY_TIMEOUT_US 20000 |
| #define TSENS_THRESHOLD_MAX_CODE 0x3ff |
| #define TSENS_THRESHOLD_MIN_CODE 0x0 |
| #define TSENS_SN_CTRL_EN BIT(0) |
| |
| #define TSENS_TM_INT_EN_ADDR 0x0 |
| #define TSENS_TM_INT_EN BIT(0) |
| |
| #define TSENS_TM_SN_STATUS 0x0044 |
| #define TSENS_TM_SN_STATUS_VALID_BIT BIT(14) |
| #define TSENS_TM_SN_STATUS_CRITICAL_STATUS BIT(19) |
| #define TSENS_TM_SN_STATUS_UPPER_STATUS BIT(12) |
| #define TSENS_TM_SN_STATUS_LOWER_STATUS BIT(11) |
| #define TSENS_TM_SN_LAST_TEMP_MASK 0x3ff |
| |
| #define TSENS_TM_UPPER_LOWER_STATUS_CTRL(n) ((0x4 * n) + 0x0004) |
| #define TSENS_TM_UPPER_STATUS_CLEAR BIT(21) |
| #define TSENS_TM_LOWER_STATUS_CLEAR BIT(20) |
| #define TSENS_TM_UPPER_THRESHOLD_SET(n) ((n) << 10) |
| #define TSENS_TM_UPPER_THRESHOLD_MASK 0xffc00 |
| #define TSENS_TM_LOWER_THRESHOLD_MASK 0x3ff |
| #define TSENS_TM_UPPER_THRESHOLD_VALUE(n) (((n) & TSENS_TM_UPPER_THRESHOLD_MASK) >> 10) |
| #define TSENS_TM_LOWER_THRESHOLD_VALUE(n) ((n) & TSENS_TM_LOWER_THRESHOLD_MASK) |
| |
| #define MAX_SENSOR 5 |
| |
| /* |
| * ADC is 10 bits long so it can't go beyond 1023 |
| */ |
| #define MAX_ADC 1023 |
| #define MIN_ADC 0 |
| |
| #define MAX_TEMP 243 /* Temperature for adc code 1023 */ |
| #define MIN_TEMP -77 /* Temperature for adc code 0 */ |
| |
| /* eeprom layout data for ipq5018 */ |
| #define S0_P1_MASK_4_0 0xf8000000 |
| #define BASE0_MASK 0x0007f800 |
| #define BASE1_MASK 0x07f80000 |
| #define CAL_SEL_MASK 0x00000700 |
| |
| #define S3_P1_MASK_0 0x80000000 |
| #define S2_P2_MASK 0x7e000000 |
| #define S2_P1_MASK 0x01f80000 |
| #define S1_P2_MASK 0x0007e000 |
| #define S1_P1_MASK 0x00001f80 |
| #define S0_P2_MASK 0x0000007e |
| #define S0_P1_MASK_5 0x00000001 |
| |
| #define S4_P1_MASK 0x0001f800 |
| #define S3_P2_MASK 0x000007e0 |
| #define S3_P1_MASK_5_1 0x0000001f |
| |
| #define S4_P2_MASK 0x0000003f |
| |
| #define S0_P1_SHIFT_4_0 27 |
| #define BASE0_SHIFT 11 |
| #define BASE1_SHIFT 19 |
| #define CAL_SEL_SHIFT 8 |
| |
| #define S3_P1_SHIFT_0 31 |
| #define S2_P2_SHIFT 25 |
| #define S2_P1_SHIFT 19 |
| #define S1_P2_SHIFT 13 |
| #define S1_P1_SHIFT 7 |
| #define S0_P2_SHIFT 1 |
| #define S0_P1_SHIFT_5 0 |
| |
| #define S4_P1_SHIFT 11 |
| #define S3_P2_SHIFT 5 |
| #define S3_P1_SHIFT_5_1 0 |
| |
| #define S4_P2_SHIFT 0 |
| |
| #define CONFIG 0x9c |
| #define CONFIG_MASK 0xf |
| #define CONFIG_SHIFT 0 |
| |
| #define EN BIT(0) |
| #define SW_RST BIT(1) |
| #define SENSOR0_EN BIT(3) |
| #define SLP_CLK_ENA BIT(26) |
| #define MEASURE_PERIOD 1 |
| #define SENSOR0_SHIFT 3 |
| |
| static bool int_clr_deassert_quirk; |
| |
| static int get_temp_ipq5018(struct tsens_priv *tmdev, int id, int *temp); |
| |
| int code2degc_lut_degc[MAX_SENSOR][1024]; |
| |
| /* Trips: from very hot to very cold */ |
| /* Maintain same enum values as in tsens-ipq807x.c:enum tsens_trip_type */ |
| enum tsens_trip_type { |
| TSENS_TRIP_STAGE3 = 0, /* Critical high, not available in IPQ5018 */ |
| TSENS_TRIP_STAGE2 = 1, /* Configurable high */ |
| TSENS_TRIP_STAGE1, /* Configurable low */ |
| TSENS_TRIP_STAGE0, /* Critical low, not available in IPQ5018 */ |
| TSENS_TRIP_NUM, |
| }; |
| |
| int degc_to_code(int temp, int sensor) |
| { |
| int code; |
| |
| for (code = 0; code < 1024; code++) { |
| if (temp == code2degc_lut_degc[sensor][code]) |
| return code; |
| } |
| |
| return MAX_TEMP; |
| } |
| |
| /* |
| * Set Trip temp in degree Celcius |
| */ |
| static int set_trip_temp(struct tsens_priv *tmdev, int sensor, |
| enum tsens_trip_type trip, int temp) |
| { |
| u32 reg_th, th_hi, th_lo, reg_th_offset; |
| |
| if (!tmdev) |
| return -EINVAL; |
| |
| if ((sensor < 0) || (sensor > (MAX_SENSOR - 1))) |
| return -EINVAL; |
| |
| if ((temp < MIN_TEMP) || (temp > MAX_TEMP)) |
| return -EINVAL; |
| |
| temp = degc_to_code(temp, sensor); |
| |
| reg_th_offset = TSENS_TM_UPPER_LOWER_STATUS_CTRL(sensor); |
| regmap_read(tmdev->tm_map, |
| TSENS_TM_UPPER_LOWER_STATUS_CTRL(sensor), ®_th); |
| |
| th_hi = TSENS_TM_UPPER_THRESHOLD_VALUE(reg_th); |
| th_lo = TSENS_TM_LOWER_THRESHOLD_VALUE(reg_th); |
| |
| switch (trip) { |
| case TSENS_TRIP_STAGE2: |
| if (temp <= th_lo) |
| return -EINVAL; |
| |
| temp = TSENS_TM_UPPER_THRESHOLD_SET(temp); |
| /* Don't change lower threshold */ |
| reg_th &= TSENS_TM_LOWER_THRESHOLD_MASK; |
| reg_th |= temp; |
| break; |
| case TSENS_TRIP_STAGE1: |
| if (temp >= th_hi) |
| return -EINVAL; |
| |
| /* Don't change upper threshold */ |
| reg_th &= TSENS_TM_UPPER_THRESHOLD_MASK; |
| reg_th |= temp; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| regmap_write(tmdev->tm_map, TSENS_TM_INT_EN_ADDR, 0); |
| regmap_write(tmdev->tm_map, reg_th_offset, reg_th); |
| regmap_write(tmdev->tm_map, TSENS_TM_INT_EN_ADDR, TSENS_TM_INT_EN); |
| |
| /* Sync registers */ |
| mb(); |
| |
| return 0; |
| } |
| |
| static void notify_uspace_tsens_fn(struct work_struct *work) |
| { |
| struct tsens_sensor *s = container_of(work, struct tsens_sensor, |
| notify_work); |
| |
| if (!s || !s->tzd) |
| /* Do nothing. TSENS driver has not been registered yet */ |
| return; |
| |
| sysfs_notify(&s->tzd->device.kobj, NULL, "type"); |
| } |
| |
| static void tsens_scheduler_fn(struct work_struct *work) |
| { |
| struct tsens_priv *tmdev = container_of(work, struct tsens_priv, |
| tsens_work); |
| int i, reg_thr, temp, th_upper = 0, th_lower = 0; |
| u32 reg_val, reg_addr; |
| |
| /*Check whether TSENS is enabled */ |
| regmap_read(tmdev->srot_map, TSENS_CNTL_ADDR, ®_val); |
| if (!(reg_val & TSENS_SN_CTRL_EN)) |
| return; |
| |
| /* Iterate through all sensors */ |
| for (i = 0; i < tmdev->num_sensors; i++) { |
| /* Reset for each iteration */ |
| reg_thr = th_upper = th_lower = 0; |
| |
| regmap_read(tmdev->tm_map, tmdev->sensor[i].status, ®_val); |
| |
| /* Check whether the temp is valid */ |
| if (!(reg_val & TSENS_TM_SN_STATUS_VALID_BIT)) |
| continue; |
| |
| /* Check whether upper threshold is hit */ |
| if (reg_val & TSENS_TM_SN_STATUS_UPPER_STATUS) { |
| reg_thr |= TSENS_TM_UPPER_STATUS_CLEAR; |
| reg_addr = TSENS_TM_UPPER_LOWER_STATUS_CTRL(i); |
| th_upper = 1; |
| } |
| /* Check whether lower threshold is hit */ |
| if (reg_val & TSENS_TM_SN_STATUS_LOWER_STATUS) { |
| reg_thr |= TSENS_TM_LOWER_STATUS_CLEAR; |
| reg_addr = TSENS_TM_UPPER_LOWER_STATUS_CTRL(i); |
| th_lower = 1; |
| } |
| |
| if (th_upper || th_lower) { |
| regmap_write(tmdev->tm_map, reg_addr, reg_thr); |
| /* Notify user space */ |
| schedule_work(&tmdev->sensor[i].notify_work); |
| |
| if (int_clr_deassert_quirk) |
| regmap_write(tmdev->tm_map, reg_addr, 0); |
| |
| if (!get_temp_ipq5018(tmdev, i, &temp)) |
| pr_debug("Trigger (%d degrees) for sensor %d\n", |
| temp, i); |
| } |
| } |
| |
| /* Sync registers */ |
| mb(); |
| } |
| |
| static irqreturn_t tsens_isr(int irq, void *data) |
| { |
| struct tsens_priv *tmdev = data; |
| |
| schedule_work(&tmdev->tsens_work); |
| return IRQ_HANDLED; |
| } |
| |
| static int init_ipq5018(struct tsens_priv *tmdev) |
| { |
| int ret; |
| int i; |
| u32 reg_cntl; |
| |
| init_common(tmdev); |
| if (!tmdev->tm_map) |
| return -ENODEV; |
| |
| /* Store all sensor address for future use */ |
| for (i = 0; i < tmdev->num_sensors; i++) { |
| tmdev->sensor[i].status = TSENS_TM_SN_STATUS + (i * 4); |
| INIT_WORK(&tmdev->sensor[i].notify_work, |
| notify_uspace_tsens_fn); |
| } |
| |
| reg_cntl = SW_RST; |
| ret = regmap_update_bits(tmdev->srot_map, TSENS_CNTL_ADDR, SW_RST, reg_cntl); |
| if (ret) |
| return ret; |
| |
| reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18); |
| reg_cntl &= ~SW_RST; |
| ret = regmap_update_bits(tmdev->srot_map, TSENS_CONFIG_ADDR, |
| CONFIG_MASK, CONFIG); |
| |
| reg_cntl |= GENMASK(MAX_SENSOR - 1, 0) << SENSOR0_SHIFT; |
| ret = regmap_write(tmdev->srot_map, TSENS_CNTL_ADDR, reg_cntl); |
| if (ret) |
| return ret; |
| |
| reg_cntl |= EN; |
| ret = regmap_write(tmdev->srot_map, TSENS_CNTL_ADDR, reg_cntl); |
| if (ret) |
| return ret; |
| |
| /* Init tsens worker thread */ |
| INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn); |
| |
| /* Register and enable the ISR */ |
| ret = devm_request_irq(tmdev->dev, tmdev->tsens_irq, tsens_isr, |
| IRQF_TRIGGER_RISING, "tsens_interrupt", tmdev); |
| if (ret < 0) { |
| pr_err("%s: request_irq FAIL: %d\n", __func__, ret); |
| return ret; |
| } |
| enable_irq_wake(tmdev->tsens_irq); |
| |
| regmap_write(tmdev->tm_map, TSENS_TM_INT_EN_ADDR, TSENS_TM_INT_EN); |
| |
| int_clr_deassert_quirk = device_property_read_bool(tmdev->dev, |
| "tsens-up-low-int-clr-deassert-quirk"); |
| /* Sync registers */ |
| mb(); |
| |
| return 0; |
| } |
| |
| static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s) |
| { |
| int degc, num, den; |
| |
| num = (adc_code * SLOPE_FACTOR) - s->offset; |
| den = s->slope; |
| |
| if (num > 0) |
| degc = num + (den / 2); |
| else if (num < 0) |
| degc = num - (den / 2); |
| else |
| degc = num; |
| |
| degc /= den; |
| |
| return degc; |
| } |
| |
| static int calibrate_ipq5018(struct tsens_priv *tmdev) |
| { |
| u32 base0 = 0, base1 = 0; |
| u32 p1[MAX_SENSOR], p2[MAX_SENSOR]; |
| u32 mode = 0, lsb = 0, msb = 0; |
| u32 *qfprom_cdata; |
| int i, sensor; |
| |
| qfprom_cdata = (u32 *)qfprom_read(tmdev->dev, "calib"); |
| if (IS_ERR(qfprom_cdata)) |
| return PTR_ERR(qfprom_cdata); |
| |
| mode = (qfprom_cdata[0] & CAL_SEL_MASK) >> CAL_SEL_SHIFT; |
| |
| dev_dbg(tmdev->dev, "calibration mode is %d\n", mode); |
| |
| switch (mode) { |
| case TWO_PT_CALIB: |
| base1 = (qfprom_cdata[0] & BASE1_MASK) >> BASE1_SHIFT; |
| p2[0] = (qfprom_cdata[1] & S0_P2_MASK) >> S0_P2_SHIFT; |
| p2[1] = (qfprom_cdata[1] & S1_P2_MASK) >> S1_P2_SHIFT; |
| p2[2] = (qfprom_cdata[1] & S2_P2_MASK) >> S2_P2_SHIFT; |
| p2[3] = (qfprom_cdata[2] & S3_P2_MASK) >> S3_P2_SHIFT; |
| p2[4] = (qfprom_cdata[3] & S4_P2_MASK) >> S4_P2_SHIFT; |
| for (i = 0; i < MAX_SENSOR; i++) |
| p2[i] = ((base1 + p2[i]) << 2); |
| /* Fall through */ |
| case ONE_PT_CALIB2: |
| base0 = (qfprom_cdata[0] & BASE0_MASK) >> BASE0_SHIFT; |
| /* This value is split over two registers, 5 bits and 1 bit */ |
| lsb = (qfprom_cdata[0] & S0_P1_MASK_4_0) >> S0_P1_SHIFT_4_0; |
| msb = (qfprom_cdata[1] & S0_P1_MASK_5) >> S0_P1_SHIFT_5; |
| p1[0] = msb << 5 | lsb; |
| p1[1] = (qfprom_cdata[1] & S1_P1_MASK) >> S1_P1_SHIFT; |
| p1[2] = (qfprom_cdata[1] & S2_P1_MASK) >> S2_P1_SHIFT; |
| /* This value is split over two registers, 1 bit and 5 bits */ |
| lsb = (qfprom_cdata[1] & S3_P1_MASK_0) >> S3_P1_SHIFT_0; |
| msb = (qfprom_cdata[2] & S3_P1_MASK_5_1) >> S3_P1_SHIFT_5_1; |
| p1[3] = msb << 1 | lsb; |
| p1[4] = (qfprom_cdata[2] & S4_P1_MASK) >> S4_P1_SHIFT; |
| for (i = 0; i < MAX_SENSOR; i++) |
| p1[i] = (((base0) + p1[i]) << 2); |
| break; |
| default: |
| pr_info("Using default calibration\n"); |
| p1[0] = 403; |
| p2[0] = 688; |
| p1[1] = 390; |
| p2[1] = 674; |
| p1[2] = 341; |
| p2[2] = 635; |
| p1[3] = 387; |
| p2[3] = 673; |
| p1[4] = 347; |
| p2[4] = 639; |
| break; |
| } |
| |
| compute_intercept_slope(tmdev, p1, p2, mode); |
| kfree(qfprom_cdata); |
| |
| for (sensor = 0; sensor < tmdev->num_sensors; sensor++) { |
| for (i = MIN_ADC; i <= MAX_ADC; i++) { |
| code2degc_lut_degc[sensor][i] = code_to_degc(i, |
| &tmdev->sensor[sensor]); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int set_trip_temp_ipq5018(void *data, int trip, int temp) |
| { |
| const struct tsens_sensor *s = data; |
| |
| if (!s) |
| return -EINVAL; |
| |
| return set_trip_temp(s->priv, s->id, trip, temp); |
| } |
| |
| static int get_temp_ipq5018(struct tsens_priv *tmdev, int id, int *temp) |
| { |
| const struct tsens_sensor *s = &tmdev->sensor[id]; |
| u32 code; |
| unsigned int sensor_addr; |
| int ret, last_temp = 0, last_temp2 = 0, last_temp3 = 0; |
| |
| sensor_addr = TSENS_TM_SN_STATUS + s->hw_id * 4; |
| ret = regmap_read(tmdev->tm_map, sensor_addr, &code); |
| if (ret) |
| return ret; |
| last_temp = code & TSENS_TM_SN_LAST_TEMP_MASK; |
| if (code & TSENS_TM_SN_STATUS_VALID_BIT) |
| goto done; |
| |
| /* Try a second time */ |
| ret = regmap_read(tmdev->tm_map, sensor_addr, &code); |
| if (ret) |
| return ret; |
| if (code & TSENS_TM_SN_STATUS_VALID_BIT) { |
| last_temp = code & TSENS_TM_SN_LAST_TEMP_MASK; |
| goto done; |
| } else { |
| last_temp2 = code & TSENS_TM_SN_LAST_TEMP_MASK; |
| } |
| |
| /* Try a third/last time */ |
| ret = regmap_read(tmdev->tm_map, sensor_addr, &code); |
| if (ret) |
| return ret; |
| if (code & TSENS_TM_SN_STATUS_VALID_BIT) { |
| last_temp = code & TSENS_TM_SN_LAST_TEMP_MASK; |
| goto done; |
| } else { |
| last_temp3 = code & TSENS_TM_SN_LAST_TEMP_MASK; |
| } |
| |
| if (last_temp == last_temp2) |
| last_temp = last_temp2; |
| else if (last_temp2 == last_temp3) |
| last_temp = last_temp3; |
| done: |
| /* Convert temperature from ADC code to Celsius */ |
| *temp = code_to_degc(last_temp, s); |
| |
| return 0; |
| } |
| |
| const struct tsens_ops ops_ipq5018 = { |
| .init = init_ipq5018, |
| .calibrate = calibrate_ipq5018, |
| .get_temp = get_temp_ipq5018, |
| .set_trip_temp = set_trip_temp_ipq5018, |
| }; |