diff options
Diffstat (limited to 'drivers/thermal/qcom/tsens.c')
| -rw-r--r-- | drivers/thermal/qcom/tsens.c | 422 |
1 files changed, 313 insertions, 109 deletions
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 99a8d9f3e03c..a2422ebee816 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -17,7 +17,9 @@ #include <linux/pm.h> #include <linux/regmap.h> #include <linux/slab.h> +#include <linux/suspend.h> #include <linux/thermal.h> +#include "../thermal_hwmon.h" #include "tsens.h" /** @@ -69,6 +71,185 @@ char *qfprom_read(struct device *dev, const char *cname) return ret; } +int tsens_read_calibration(struct tsens_priv *priv, int shift, u32 *p1, u32 *p2, bool backup) +{ + u32 mode; + u32 base1, base2; + char name[] = "sXX_pY_backup"; /* s10_p1_backup */ + int i, ret; + + if (priv->num_sensors > MAX_SENSORS) + return -EINVAL; + + ret = snprintf(name, sizeof(name), "mode%s", backup ? "_backup" : ""); + if (ret < 0) + return ret; + + ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &mode); + if (ret == -ENOENT) + dev_warn(priv->dev, "Please migrate to separate nvmem cells for calibration data\n"); + if (ret < 0) + return ret; + + dev_dbg(priv->dev, "calibration mode is %d\n", mode); + + ret = snprintf(name, sizeof(name), "base1%s", backup ? "_backup" : ""); + if (ret < 0) + return ret; + + ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &base1); + if (ret < 0) + return ret; + + ret = snprintf(name, sizeof(name), "base2%s", backup ? "_backup" : ""); + if (ret < 0) + return ret; + + ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &base2); + if (ret < 0) + return ret; + + for (i = 0; i < priv->num_sensors; i++) { + ret = snprintf(name, sizeof(name), "s%d_p1%s", priv->sensor[i].hw_id, + backup ? "_backup" : ""); + if (ret < 0) + return ret; + + ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &p1[i]); + if (ret) + return ret; + + ret = snprintf(name, sizeof(name), "s%d_p2%s", priv->sensor[i].hw_id, + backup ? "_backup" : ""); + if (ret < 0) + return ret; + + ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &p2[i]); + if (ret) + return ret; + } + + switch (mode) { + case ONE_PT_CALIB: + for (i = 0; i < priv->num_sensors; i++) + p1[i] = p1[i] + (base1 << shift); + break; + case TWO_PT_CALIB: + case TWO_PT_CALIB_NO_OFFSET: + for (i = 0; i < priv->num_sensors; i++) + p2[i] = (p2[i] + base2) << shift; + fallthrough; + case ONE_PT_CALIB2: + case ONE_PT_CALIB2_NO_OFFSET: + for (i = 0; i < priv->num_sensors; i++) + p1[i] = (p1[i] + base1) << shift; + break; + default: + dev_dbg(priv->dev, "calibrationless mode\n"); + for (i = 0; i < priv->num_sensors; i++) { + p1[i] = 500; + p2[i] = 780; + } + } + + /* Apply calibration offset workaround except for _NO_OFFSET modes */ + switch (mode) { + case TWO_PT_CALIB: + for (i = 0; i < priv->num_sensors; i++) + p2[i] += priv->sensor[i].p2_calib_offset; + fallthrough; + case ONE_PT_CALIB2: + for (i = 0; i < priv->num_sensors; i++) + p1[i] += priv->sensor[i].p1_calib_offset; + break; + } + + return mode; +} + +int tsens_calibrate_nvmem(struct tsens_priv *priv, int shift) +{ + u32 p1[MAX_SENSORS], p2[MAX_SENSORS]; + int mode; + + mode = tsens_read_calibration(priv, shift, p1, p2, false); + if (mode < 0) + return mode; + + compute_intercept_slope(priv, p1, p2, mode); + + return 0; +} + +int tsens_calibrate_common(struct tsens_priv *priv) +{ + return tsens_calibrate_nvmem(priv, 2); +} + +static u32 tsens_read_cell(const struct tsens_single_value *cell, u8 len, u32 *data0, u32 *data1) +{ + u32 val; + u32 *data = cell->blob ? data1 : data0; + + if (cell->shift + len <= 32) { + val = data[cell->idx] >> cell->shift; + } else { + u8 part = 32 - cell->shift; + + val = data[cell->idx] >> cell->shift; + val |= data[cell->idx + 1] << part; + } + + return val & ((1 << len) - 1); +} + +int tsens_read_calibration_legacy(struct tsens_priv *priv, + const struct tsens_legacy_calibration_format *format, + u32 *p1, u32 *p2, + u32 *cdata0, u32 *cdata1) +{ + u32 mode, invalid; + u32 base1, base2; + int i; + + mode = tsens_read_cell(&format->mode, 2, cdata0, cdata1); + invalid = tsens_read_cell(&format->invalid, 1, cdata0, cdata1); + if (invalid) + mode = NO_PT_CALIB; + dev_dbg(priv->dev, "calibration mode is %d\n", mode); + + base1 = tsens_read_cell(&format->base[0], format->base_len, cdata0, cdata1); + base2 = tsens_read_cell(&format->base[1], format->base_len, cdata0, cdata1); + + for (i = 0; i < priv->num_sensors; i++) { + p1[i] = tsens_read_cell(&format->sp[i][0], format->sp_len, cdata0, cdata1); + p2[i] = tsens_read_cell(&format->sp[i][1], format->sp_len, cdata0, cdata1); + } + + switch (mode) { + case ONE_PT_CALIB: + for (i = 0; i < priv->num_sensors; i++) + p1[i] = p1[i] + (base1 << format->base_shift); + break; + case TWO_PT_CALIB: + for (i = 0; i < priv->num_sensors; i++) + p2[i] = (p2[i] + base2) << format->base_shift; + fallthrough; + case ONE_PT_CALIB2: + for (i = 0; i < priv->num_sensors; i++) + p1[i] = (p1[i] + base1) << format->base_shift; + break; + default: + dev_dbg(priv->dev, "calibrationless mode\n"); + for (i = 0; i < priv->num_sensors; i++) { + p1[i] = 500; + p2[i] = 780; + } + } + + return mode; +} + /* * Use this function on devices where slope and offset calculations * depend on calibration data read from qfprom. On others the slope @@ -84,11 +265,11 @@ void compute_intercept_slope(struct tsens_priv *priv, u32 *p1, for (i = 0; i < priv->num_sensors; i++) { dev_dbg(priv->dev, "%s: sensor%d - data_point1:%#x data_point2:%#x\n", - __func__, i, p1[i], p2[i]); + __func__, i, p1[i], p2 ? p2[i] : 0); if (!priv->sensor[i].slope) priv->sensor[i].slope = SLOPE_DEFAULT; - if (mode == TWO_PT_CALIB) { + if (mode == TWO_PT_CALIB || mode == TWO_PT_CALIB_NO_OFFSET) { /* * slope (m) = adc_code2 - adc_code1 (y2 - y1)/ * temp_120_degc - temp_30_degc (x2 - x1) @@ -266,7 +447,7 @@ static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id, dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__, irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW", enable ? "en" : "dis"); - if (tsens_version(priv) > VER_1_X) + if (tsens_version(priv) >= VER_2_X) tsens_set_interrupt_v2(priv, hw_id, irq_type, enable); else tsens_set_interrupt_v1(priv, hw_id, irq_type, enable); @@ -318,7 +499,7 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id, ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear); if (ret) return ret; - if (tsens_version(priv) > VER_1_X) { + if (tsens_version(priv) >= VER_2_X) { ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask); if (ret) return ret; @@ -362,7 +543,7 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id, static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver) { - if (ver > VER_1_X) + if (ver >= VER_2_X) return mask & (1 << hw_id); /* v1, v0.1 don't have a irq mask register */ @@ -458,12 +639,9 @@ static irqreturn_t tsens_irq_thread(int irq, void *data) { struct tsens_priv *priv = data; struct tsens_irq_data d; - bool enable = true, disable = false; - unsigned long flags; - int temp, ret, i; + int i; for (i = 0; i < priv->num_sensors; i++) { - bool trigger = false; const struct tsens_sensor *s = &priv->sensor[i]; u32 hw_id = s->hw_id; @@ -471,52 +649,8 @@ static irqreturn_t tsens_irq_thread(int irq, void *data) continue; if (!tsens_threshold_violated(priv, hw_id, &d)) continue; - ret = get_temp_tsens_valid(s, &temp); - if (ret) { - dev_err(priv->dev, "[%u] %s: error reading sensor\n", - hw_id, __func__); - continue; - } - spin_lock_irqsave(&priv->ul_lock, flags); - - tsens_read_irq_state(priv, hw_id, s, &d); - - if (d.up_viol && - !masked_irq(hw_id, d.up_irq_mask, tsens_version(priv))) { - tsens_set_interrupt(priv, hw_id, UPPER, disable); - if (d.up_thresh > temp) { - dev_dbg(priv->dev, "[%u] %s: re-arm upper\n", - hw_id, __func__); - tsens_set_interrupt(priv, hw_id, UPPER, enable); - } else { - trigger = true; - /* Keep irq masked */ - } - } else if (d.low_viol && - !masked_irq(hw_id, d.low_irq_mask, tsens_version(priv))) { - tsens_set_interrupt(priv, hw_id, LOWER, disable); - if (d.low_thresh < temp) { - dev_dbg(priv->dev, "[%u] %s: re-arm low\n", - hw_id, __func__); - tsens_set_interrupt(priv, hw_id, LOWER, enable); - } else { - trigger = true; - /* Keep irq masked */ - } - } - - spin_unlock_irqrestore(&priv->ul_lock, flags); - - if (trigger) { - dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n", - hw_id, __func__, temp); - thermal_zone_device_update(s->tzd, - THERMAL_EVENT_UNSPECIFIED); - } else { - dev_dbg(priv->dev, "[%u] %s: no violation: %d\n", - hw_id, __func__, temp); - } + thermal_zone_device_update(s->tzd, THERMAL_EVENT_UNSPECIFIED); if (tsens_version(priv) < VER_0_1) { /* Constraint: There is only 1 interrupt control register for all @@ -531,9 +665,30 @@ static irqreturn_t tsens_irq_thread(int irq, void *data) return IRQ_HANDLED; } -static int tsens_set_trips(void *_sensor, int low, int high) +/** + * tsens_combined_irq_thread() - Threaded interrupt handler for combined interrupts + * @irq: irq number + * @data: tsens controller private data + * + * Handle the combined interrupt as if it were 2 separate interrupts, so call the + * critical handler first and then the up/low one. + * + * Return: IRQ_HANDLED + */ +static irqreturn_t tsens_combined_irq_thread(int irq, void *data) +{ + irqreturn_t ret; + + ret = tsens_critical_irq_thread(irq, data); + if (ret != IRQ_HANDLED) + return ret; + + return tsens_irq_thread(irq, data); +} + +static int tsens_set_trips(struct thermal_zone_device *tz, int low, int high) { - struct tsens_sensor *s = _sensor; + struct tsens_sensor *s = thermal_zone_device_priv(tz); struct tsens_priv *priv = s->priv; struct device *dev = priv->dev; struct tsens_irq_data d; @@ -551,8 +706,8 @@ static int tsens_set_trips(void *_sensor, int low, int high) dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n", hw_id, __func__, low, high); - cl_high = clamp_val(high, -40000, 120000); - cl_low = clamp_val(low, -40000, 120000); + cl_high = clamp_val(high, priv->feat->trip_min_temp, priv->feat->trip_max_temp); + cl_low = clamp_val(low, priv->feat->trip_min_temp, priv->feat->trip_max_temp); high_val = tsens_mC_to_hw(s, cl_high); low_val = tsens_mC_to_hw(s, cl_low); @@ -578,7 +733,7 @@ static int tsens_set_trips(void *_sensor, int low, int high) static int tsens_enable_irq(struct tsens_priv *priv) { int ret; - int val = tsens_version(priv) > VER_1_X ? 7 : 1; + int val = tsens_version(priv) >= VER_2_X ? 7 : 1; ret = regmap_field_write(priv->rf[INT_EN], val); if (ret < 0) @@ -691,7 +846,7 @@ static int dbg_version_show(struct seq_file *s, void *data) return ret; seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver); } else { - seq_puts(s, "0.1.0\n"); + seq_printf(s, "0.%d.0\n", priv->feat->ver_major); } return 0; @@ -703,21 +858,14 @@ DEFINE_SHOW_ATTRIBUTE(dbg_sensors); static void tsens_debug_init(struct platform_device *pdev) { struct tsens_priv *priv = platform_get_drvdata(pdev); - struct dentry *root, *file; - root = debugfs_lookup("tsens", NULL); - if (!root) + priv->debug_root = debugfs_lookup("tsens", NULL); + if (!priv->debug_root) priv->debug_root = debugfs_create_dir("tsens", NULL); - else - priv->debug_root = root; - - file = debugfs_lookup("version", priv->debug_root); - if (!file) - debugfs_create_file("version", 0444, priv->debug_root, - pdev, &dbg_version_fops); /* A directory for each instance of the TSENS IP */ priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root); + debugfs_create_file("version", 0444, priv->debug, pdev, &dbg_version_fops); debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops); } #else @@ -828,9 +976,15 @@ int __init init_common(struct tsens_priv *priv) if (ret) goto err_put_device; if (!enabled) { - dev_err(dev, "%s: device not enabled\n", __func__); - ret = -ENODEV; - goto err_put_device; + switch (tsens_version(priv)) { + case VER_1_X_NO_RPM: + case VER_2_X_NO_RPM: + break; + default: + dev_err(dev, "%s: device not enabled\n", __func__); + ret = -ENODEV; + goto err_put_device; + } } priv->rf[SENSOR_EN] = devm_regmap_field_alloc(dev, priv->srot_map, @@ -892,7 +1046,7 @@ int __init init_common(struct tsens_priv *priv) } } - if (tsens_version(priv) > VER_1_X && ver_minor > 2) { + if (tsens_version(priv) >= VER_2_X && ver_minor > 2) { /* Watchdog is present only on v2.3+ */ priv->feat->has_watchdog = 1; for (i = WDOG_BARK_STATUS; i <= CC_MON_MASK; i++) { @@ -917,32 +1071,19 @@ int __init init_common(struct tsens_priv *priv) if (tsens_version(priv) >= VER_0_1) tsens_enable_irq(priv); - tsens_debug_init(op); - err_put_device: put_device(&op->dev); return ret; } -static int tsens_get_temp(void *data, int *temp) +static int tsens_get_temp(struct thermal_zone_device *tz, int *temp) { - struct tsens_sensor *s = data; + struct tsens_sensor *s = thermal_zone_device_priv(tz); struct tsens_priv *priv = s->priv; return priv->ops->get_temp(s, temp); } -static int tsens_get_trend(void *data, int trip, enum thermal_trend *trend) -{ - struct tsens_sensor *s = data; - struct tsens_priv *priv = s->priv; - - if (priv->ops->get_trend) - return priv->ops->get_trend(s, trend); - - return -ENOTSUPP; -} - static int __maybe_unused tsens_suspend(struct device *dev) { struct tsens_priv *priv = dev_get_drvdata(dev); @@ -967,18 +1108,45 @@ static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume); static const struct of_device_id tsens_table[] = { { + .compatible = "qcom,ipq5018-tsens", + .data = &data_ipq5018, + }, { + .compatible = "qcom,ipq5332-tsens", + .data = &data_ipq5332, + }, { + .compatible = "qcom,ipq5424-tsens", + .data = &data_ipq5424, + }, { .compatible = "qcom,ipq8064-tsens", .data = &data_8960, }, { + .compatible = "qcom,ipq8074-tsens", + .data = &data_ipq8074, + }, { .compatible = "qcom,mdm9607-tsens", .data = &data_9607, }, { + .compatible = "qcom,msm8226-tsens", + .data = &data_8226, + }, { + .compatible = "qcom,msm8909-tsens", + .data = &data_8909, + }, { .compatible = "qcom,msm8916-tsens", .data = &data_8916, }, { + .compatible = "qcom,msm8937-tsens", + .data = &data_8937, + }, { .compatible = "qcom,msm8939-tsens", .data = &data_8939, }, { + .compatible = "qcom,msm8956-tsens", + .data = &data_8956, + }, { + .compatible = "qcom,msm8960-tsens", + .data = &data_8960, + }, { .compatible = "qcom,msm8974-tsens", .data = &data_8974, }, { @@ -998,9 +1166,8 @@ static const struct of_device_id tsens_table[] = { }; MODULE_DEVICE_TABLE(of, tsens_table); -static const struct thermal_zone_of_device_ops tsens_of_ops = { +static const struct thermal_zone_device_ops tsens_of_ops = { .get_temp = tsens_get_temp, - .get_trend = tsens_get_trend, .set_trips = tsens_set_trips, }; @@ -1045,6 +1212,36 @@ static int tsens_register_irq(struct tsens_priv *priv, char *irqname, return ret; } +#ifdef CONFIG_SUSPEND +static int tsens_reinit(struct tsens_priv *priv) +{ + if (tsens_version(priv) >= VER_2_X) { + /* + * Re-enable the watchdog, unmask the bark. + * Disable cycle completion monitoring + */ + if (priv->feat->has_watchdog) { + regmap_field_write(priv->rf[WDOG_BARK_MASK], 0); + regmap_field_write(priv->rf[CC_MON_MASK], 1); + } + + /* Re-enable interrupts */ + tsens_enable_irq(priv); + } + + return 0; +} + +int tsens_resume_common(struct tsens_priv *priv) +{ + if (pm_suspend_target_state == PM_SUSPEND_MEM) + tsens_reinit(priv); + + return 0; +} + +#endif /* !CONFIG_SUSPEND */ + static int tsens_register(struct tsens_priv *priv) { int i, ret; @@ -1052,14 +1249,16 @@ static int tsens_register(struct tsens_priv *priv) for (i = 0; i < priv->num_sensors; i++) { priv->sensor[i].priv = priv; - tzd = devm_thermal_zone_of_sensor_register(priv->dev, priv->sensor[i].hw_id, - &priv->sensor[i], - &tsens_of_ops); + tzd = devm_thermal_of_zone_register(priv->dev, priv->sensor[i].hw_id, + &priv->sensor[i], + &tsens_of_ops); if (IS_ERR(tzd)) continue; priv->sensor[i].tzd = tzd; if (priv->ops->enable) priv->ops->enable(priv, i); + + devm_thermal_add_hwmon_sysfs(priv->dev, tzd); } /* VER_0 require to set MIN and MAX THRESH @@ -1075,13 +1274,18 @@ static int tsens_register(struct tsens_priv *priv) tsens_mC_to_hw(priv->sensor, 0)); } - ret = tsens_register_irq(priv, "uplow", tsens_irq_thread); - if (ret < 0) - return ret; + if (priv->feat->combo_int) { + ret = tsens_register_irq(priv, "combined", + tsens_combined_irq_thread); + } else { + ret = tsens_register_irq(priv, "uplow", tsens_irq_thread); + if (ret < 0) + return ret; - if (priv->feat->crit_int) - ret = tsens_register_irq(priv, "critical", - tsens_critical_irq_thread); + if (priv->feat->crit_int) + ret = tsens_register_irq(priv, "critical", + tsens_critical_irq_thread); + } return ret; } @@ -1150,17 +1354,19 @@ static int tsens_probe(struct platform_device *pdev) if (priv->ops->calibrate) { ret = priv->ops->calibrate(priv); - if (ret < 0) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "%s: calibration failed\n", __func__); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "%s: calibration failed\n", + __func__); } - return tsens_register(priv); + ret = tsens_register(priv); + if (!ret) + tsens_debug_init(pdev); + + return ret; } -static int tsens_remove(struct platform_device *pdev) +static void tsens_remove(struct platform_device *pdev) { struct tsens_priv *priv = platform_get_drvdata(pdev); @@ -1168,8 +1374,6 @@ static int tsens_remove(struct platform_device *pdev) tsens_disable_irq(priv); if (priv->ops->disable) priv->ops->disable(priv); - - return 0; } static struct platform_driver tsens_driver = { |
