Pull thermal managament updates from Zhang Rui: - Enhance thermal "userspace" governor to export the reason when a thermal event is triggered and delivered to user space. From Srinivas Pandruvada - Introduce a single TSENS thermal driver for the different versions of the TSENS IP that exist, on different qcom msm/apq SoCs'. Support for msm8916, msm8960, msm8974 and msm8996 families is also added. From Rajendra Nayak - Introduce hardware-tracked trip points support to the device tree thermal sensor framework. The framework supports an arbitrary number of trip points. Whenever the current temperature is changed, the trip points immediately below and above the current temperature are found, driver callback is invoked to program the hardware to get notified when either of the two trip points are triggered. Hardware-tracked trip points support for rockchip thermal driver is also added at the same time. From Sascha Hauer, Caesar Wang - Introduce a new thermal driver, which enables TMU (Thermal Monitor Unit) on QorIQ platform. From Jia Hongtao - Introduce a new thermal driver for Maxim MAX77620. From Laxman Dewangan - Introduce a new thermal driver for Intel platforms using WhiskeyCove PMIC. From Bin Gao - Add mt2701 chip support to MTK thermal driver. From Dawei Chien - Enhance Tegra thermal driver to enable soctherm node and set "critical", "hot" trips, for Tegra124, Tegra132, Tegra210. From Wei Ni - Add resume support for tango thermal driver. From Marc Gonzalez - several small fixes and improvements for rockchip, qcom, imx, rcar, mtk thermal drivers and thermal core code. From Caesar Wang, Keerthy, Rocky Hao, Wei Yongjun, Peter Robinson, Bui Duc Phuc, Axel Lin, Hugh Kang * 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: (48 commits) thermal: int3403: Process trip change notification thermal: int340x: New Interface to read trip and notify thermal: user_space gov: Add additional information in uevent thermal: Enhance thermal_zone_device_update for events arm64: tegra: set hot trips for Tegra210 arm64: tegra: set critical trips for Tegra210 arm64: tegra: add soctherm node for Tegra210 arm64: tegra: set hot trips for Tegra132 arm64: tegra: set critical trips for Tegra132 arm64: tegra: use tegra132-soctherm for Tegra132 arm: tegra: set hot trips for Tegra124 arm: tegra: set critical trips for Tegra124 thermal: tegra: add hw-throttle for Tegra132 thermal: tegra: add hw-throttle function of: Add bindings of hw throttle for Tegra soctherm thermal: mtk_thermal: Check return value of devm_thermal_zone_of_sensor_register thermal: Add Mediatek thermal driver for mt2701. dt-bindings: thermal: Add binding document for Mediatek thermal controller thermal: max77620: Add thermal driver for reporting junction temp thermal: max77620: Add DT binding doc for thermal driver ...tirimbino
commit
2d2474a194
@ -0,0 +1,70 @@ |
||||
Thermal driver for MAX77620 Power management IC from Maxim Semiconductor. |
||||
|
||||
Maxim Semiconductor MAX77620 supports alarm interrupts when its |
||||
die temperature crosses 120C and 140C. These threshold temperatures |
||||
are not configurable. Device does not provide the real temperature |
||||
of die other than just indicating whether temperature is above or |
||||
below threshold level. |
||||
|
||||
Required properties: |
||||
------------------- |
||||
#thermal-sensor-cells: Please refer <devicetree/bindings/thermal/thermal.txt> |
||||
for more details. |
||||
The value must be 0. |
||||
|
||||
For more details, please refer generic thermal DT binding document |
||||
<devicetree/bindings/thermal/thermal.txt>. |
||||
|
||||
Please refer <devicetree/bindings/mfd/max77620.txt> for mfd DT binding |
||||
document for the MAX77620. |
||||
|
||||
Example: |
||||
-------- |
||||
#include <dt-bindings/mfd/max77620.h> |
||||
#include <dt-bindings/thermal/thermal.h> |
||||
... |
||||
|
||||
i2c@7000d000 { |
||||
spmic: max77620@3c { |
||||
compatible = "maxim,max77620"; |
||||
::::: |
||||
#thermal-sensor-cells = <0>; |
||||
::: |
||||
}; |
||||
}; |
||||
|
||||
cool_dev: cool-dev { |
||||
compatible = "cooling-dev"; |
||||
#cooling-cells = <2>; |
||||
}; |
||||
|
||||
thermal-zones { |
||||
PMIC-Die { |
||||
polling-delay = <0>; |
||||
polling-delay-passive = <0>; |
||||
thermal-sensors = <&spmic>; |
||||
|
||||
trips { |
||||
pmic_die_warn_temp_thresh: hot-die { |
||||
temperature = <120000>; |
||||
type = "hot"; |
||||
hysteresis = <0>; |
||||
}; |
||||
|
||||
pmic_die_cirt_temp_thresh: cirtical-die { |
||||
temperature = <140000>; |
||||
type = "critical"; |
||||
hysteresis = <0>; |
||||
}; |
||||
}; |
||||
|
||||
cooling-maps { |
||||
map0 { |
||||
trip = <&pmic_die_warn_temp_thresh>; |
||||
cooling-device = <&cool_dev THERMAL_NO_LIMIT |
||||
THERMAL_NO_LIMIT>; |
||||
contribution = <100>; |
||||
}; |
||||
}; |
||||
}; |
||||
}; |
@ -0,0 +1,21 @@ |
||||
* QCOM SoC Temperature Sensor (TSENS) |
||||
|
||||
Required properties: |
||||
- compatible : |
||||
- "qcom,msm8916-tsens" : For 8916 Family of SoCs |
||||
- "qcom,msm8974-tsens" : For 8974 Family of SoCs |
||||
- "qcom,msm8996-tsens" : For 8996 Family of SoCs |
||||
|
||||
- reg: Address range of the thermal registers |
||||
- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description. |
||||
- Refer to Documentation/devicetree/bindings/nvmem/nvmem.txt to know how to specify |
||||
nvmem cells |
||||
|
||||
Example: |
||||
tsens: thermal-sensor@900000 { |
||||
compatible = "qcom,msm8916-tsens"; |
||||
reg = <0x4a8000 0x2000>; |
||||
nvmem-cells = <&tsens_caldata>, <&tsens_calsel>; |
||||
nvmem-cell-names = "caldata", "calsel"; |
||||
#thermal-sensor-cells = <1>; |
||||
}; |
@ -0,0 +1,300 @@ |
||||
/*
|
||||
* Intel Broxton PMIC thermal driver |
||||
* |
||||
* Copyright (C) 2016 Intel Corporation. All rights reserved. |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License version |
||||
* 2 as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/module.h> |
||||
#include <linux/kernel.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/delay.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/device.h> |
||||
#include <linux/thermal.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/sched.h> |
||||
#include <linux/mfd/intel_soc_pmic.h> |
||||
|
||||
#define BXTWC_THRM0IRQ 0x4E04 |
||||
#define BXTWC_THRM1IRQ 0x4E05 |
||||
#define BXTWC_THRM2IRQ 0x4E06 |
||||
#define BXTWC_MTHRM0IRQ 0x4E12 |
||||
#define BXTWC_MTHRM1IRQ 0x4E13 |
||||
#define BXTWC_MTHRM2IRQ 0x4E14 |
||||
#define BXTWC_STHRM0IRQ 0x4F19 |
||||
#define BXTWC_STHRM1IRQ 0x4F1A |
||||
#define BXTWC_STHRM2IRQ 0x4F1B |
||||
|
||||
struct trip_config_map { |
||||
u16 irq_reg; |
||||
u16 irq_en; |
||||
u16 evt_stat; |
||||
u8 irq_mask; |
||||
u8 irq_en_mask; |
||||
u8 evt_mask; |
||||
u8 trip_num; |
||||
}; |
||||
|
||||
struct thermal_irq_map { |
||||
char handle[20]; |
||||
int num_trips; |
||||
const struct trip_config_map *trip_config; |
||||
}; |
||||
|
||||
struct pmic_thermal_data { |
||||
const struct thermal_irq_map *maps; |
||||
int num_maps; |
||||
}; |
||||
|
||||
static const struct trip_config_map bxtwc_str0_trip_config[] = { |
||||
{ |
||||
.irq_reg = BXTWC_THRM0IRQ, |
||||
.irq_mask = 0x01, |
||||
.irq_en = BXTWC_MTHRM0IRQ, |
||||
.irq_en_mask = 0x01, |
||||
.evt_stat = BXTWC_STHRM0IRQ, |
||||
.evt_mask = 0x01, |
||||
.trip_num = 0 |
||||
}, |
||||
{ |
||||
.irq_reg = BXTWC_THRM0IRQ, |
||||
.irq_mask = 0x10, |
||||
.irq_en = BXTWC_MTHRM0IRQ, |
||||
.irq_en_mask = 0x10, |
||||
.evt_stat = BXTWC_STHRM0IRQ, |
||||
.evt_mask = 0x10, |
||||
.trip_num = 1 |
||||
} |
||||
}; |
||||
|
||||
static const struct trip_config_map bxtwc_str1_trip_config[] = { |
||||
{ |
||||
.irq_reg = BXTWC_THRM0IRQ, |
||||
.irq_mask = 0x02, |
||||
.irq_en = BXTWC_MTHRM0IRQ, |
||||
.irq_en_mask = 0x02, |
||||
.evt_stat = BXTWC_STHRM0IRQ, |
||||
.evt_mask = 0x02, |
||||
.trip_num = 0 |
||||
}, |
||||
{ |
||||
.irq_reg = BXTWC_THRM0IRQ, |
||||
.irq_mask = 0x20, |
||||
.irq_en = BXTWC_MTHRM0IRQ, |
||||
.irq_en_mask = 0x20, |
||||
.evt_stat = BXTWC_STHRM0IRQ, |
||||
.evt_mask = 0x20, |
||||
.trip_num = 1 |
||||
}, |
||||
}; |
||||
|
||||
static const struct trip_config_map bxtwc_str2_trip_config[] = { |
||||
{ |
||||
.irq_reg = BXTWC_THRM0IRQ, |
||||
.irq_mask = 0x04, |
||||
.irq_en = BXTWC_MTHRM0IRQ, |
||||
.irq_en_mask = 0x04, |
||||
.evt_stat = BXTWC_STHRM0IRQ, |
||||
.evt_mask = 0x04, |
||||
.trip_num = 0 |
||||
}, |
||||
{ |
||||
.irq_reg = BXTWC_THRM0IRQ, |
||||
.irq_mask = 0x40, |
||||
.irq_en = BXTWC_MTHRM0IRQ, |
||||
.irq_en_mask = 0x40, |
||||
.evt_stat = BXTWC_STHRM0IRQ, |
||||
.evt_mask = 0x40, |
||||
.trip_num = 1 |
||||
}, |
||||
}; |
||||
|
||||
static const struct trip_config_map bxtwc_str3_trip_config[] = { |
||||
{ |
||||
.irq_reg = BXTWC_THRM2IRQ, |
||||
.irq_mask = 0x10, |
||||
.irq_en = BXTWC_MTHRM2IRQ, |
||||
.irq_en_mask = 0x10, |
||||
.evt_stat = BXTWC_STHRM2IRQ, |
||||
.evt_mask = 0x10, |
||||
.trip_num = 0 |
||||
}, |
||||
}; |
||||
|
||||
static const struct thermal_irq_map bxtwc_thermal_irq_map[] = { |
||||
{ |
||||
.handle = "STR0", |
||||
.trip_config = bxtwc_str0_trip_config, |
||||
.num_trips = ARRAY_SIZE(bxtwc_str0_trip_config), |
||||
}, |
||||
{ |
||||
.handle = "STR1", |
||||
.trip_config = bxtwc_str1_trip_config, |
||||
.num_trips = ARRAY_SIZE(bxtwc_str1_trip_config), |
||||
}, |
||||
{ |
||||
.handle = "STR2", |
||||
.trip_config = bxtwc_str2_trip_config, |
||||
.num_trips = ARRAY_SIZE(bxtwc_str2_trip_config), |
||||
}, |
||||
{ |
||||
.handle = "STR3", |
||||
.trip_config = bxtwc_str3_trip_config, |
||||
.num_trips = ARRAY_SIZE(bxtwc_str3_trip_config), |
||||
}, |
||||
}; |
||||
|
||||
static const struct pmic_thermal_data bxtwc_thermal_data = { |
||||
.maps = bxtwc_thermal_irq_map, |
||||
.num_maps = ARRAY_SIZE(bxtwc_thermal_irq_map), |
||||
}; |
||||
|
||||
static irqreturn_t pmic_thermal_irq_handler(int irq, void *data) |
||||
{ |
||||
struct platform_device *pdev = data; |
||||
struct thermal_zone_device *tzd; |
||||
struct pmic_thermal_data *td; |
||||
struct intel_soc_pmic *pmic; |
||||
struct regmap *regmap; |
||||
u8 reg_val, mask, irq_stat, trip; |
||||
u16 reg, evt_stat_reg; |
||||
int i, j, ret; |
||||
|
||||
pmic = dev_get_drvdata(pdev->dev.parent); |
||||
regmap = pmic->regmap; |
||||
td = (struct pmic_thermal_data *) |
||||
platform_get_device_id(pdev)->driver_data; |
||||
|
||||
/* Resolve thermal irqs */ |
||||
for (i = 0; i < td->num_maps; i++) { |
||||
for (j = 0; j < td->maps[i].num_trips; j++) { |
||||
reg = td->maps[i].trip_config[j].irq_reg; |
||||
mask = td->maps[i].trip_config[j].irq_mask; |
||||
/*
|
||||
* Read the irq register to resolve whether the |
||||
* interrupt was triggered for this sensor |
||||
*/ |
||||
if (regmap_read(regmap, reg, &ret)) |
||||
return IRQ_HANDLED; |
||||
|
||||
reg_val = (u8)ret; |
||||
irq_stat = ((u8)ret & mask); |
||||
|
||||
if (!irq_stat) |
||||
continue; |
||||
|
||||
/*
|
||||
* Read the status register to find out what |
||||
* event occurred i.e a high or a low |
||||
*/ |
||||
evt_stat_reg = td->maps[i].trip_config[j].evt_stat; |
||||
if (regmap_read(regmap, evt_stat_reg, &ret)) |
||||
return IRQ_HANDLED; |
||||
|
||||
trip = td->maps[i].trip_config[j].trip_num; |
||||
tzd = thermal_zone_get_zone_by_name(td->maps[i].handle); |
||||
if (!IS_ERR(tzd)) |
||||
thermal_zone_device_update(tzd, |
||||
THERMAL_EVENT_UNSPECIFIED); |
||||
|
||||
/* Clear the appropriate irq */ |
||||
regmap_write(regmap, reg, reg_val & mask); |
||||
} |
||||
} |
||||
|
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
static int pmic_thermal_probe(struct platform_device *pdev) |
||||
{ |
||||
struct regmap_irq_chip_data *regmap_irq_chip; |
||||
struct pmic_thermal_data *thermal_data; |
||||
int ret, irq, virq, i, j, pmic_irq_count; |
||||
struct intel_soc_pmic *pmic; |
||||
struct regmap *regmap; |
||||
struct device *dev; |
||||
u16 reg; |
||||
u8 mask; |
||||
|
||||
dev = &pdev->dev; |
||||
pmic = dev_get_drvdata(pdev->dev.parent); |
||||
if (!pmic) { |
||||
dev_err(dev, "Failed to get struct intel_soc_pmic pointer\n"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
thermal_data = (struct pmic_thermal_data *) |
||||
platform_get_device_id(pdev)->driver_data; |
||||
if (!thermal_data) { |
||||
dev_err(dev, "No thermal data initialized!!\n"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
regmap = pmic->regmap; |
||||
regmap_irq_chip = pmic->irq_chip_data_level2; |
||||
|
||||
pmic_irq_count = 0; |
||||
while ((irq = platform_get_irq(pdev, pmic_irq_count)) != -ENXIO) { |
||||
virq = regmap_irq_get_virq(regmap_irq_chip, irq); |
||||
if (virq < 0) { |
||||
dev_err(dev, "failed to get virq by irq %d\n", irq); |
||||
return virq; |
||||
} |
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, virq, |
||||
NULL, pmic_thermal_irq_handler, |
||||
IRQF_ONESHOT, "pmic_thermal", pdev); |
||||
|
||||
if (ret) { |
||||
dev_err(dev, "request irq(%d) failed: %d\n", virq, ret); |
||||
return ret; |
||||
} |
||||
pmic_irq_count++; |
||||
} |
||||
|
||||
/* Enable thermal interrupts */ |
||||
for (i = 0; i < thermal_data->num_maps; i++) { |
||||
for (j = 0; j < thermal_data->maps[i].num_trips; j++) { |
||||
reg = thermal_data->maps[i].trip_config[j].irq_en; |
||||
mask = thermal_data->maps[i].trip_config[j].irq_en_mask; |
||||
ret = regmap_update_bits(regmap, reg, mask, 0x00); |
||||
if (ret) |
||||
return ret; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct platform_device_id pmic_thermal_id_table[] = { |
||||
{ |
||||
.name = "bxt_wcove_thermal", |
||||
.driver_data = (kernel_ulong_t)&bxtwc_thermal_data, |
||||
}, |
||||
{}, |
||||
}; |
||||
|
||||
static struct platform_driver pmic_thermal_driver = { |
||||
.probe = pmic_thermal_probe, |
||||
.driver = { |
||||
.name = "pmic_thermal", |
||||
}, |
||||
.id_table = pmic_thermal_id_table, |
||||
}; |
||||
|
||||
MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table); |
||||
module_platform_driver(pmic_thermal_driver); |
||||
|
||||
MODULE_AUTHOR("Yegnesh S Iyer <yegnesh.s.iyer@intel.com>"); |
||||
MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver"); |
||||
MODULE_LICENSE("GPL v2"); |
@ -0,0 +1,166 @@ |
||||
/*
|
||||
* Junction temperature thermal driver for Maxim Max77620. |
||||
* |
||||
* Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. |
||||
* |
||||
* Author: Laxman Dewangan <ldewangan@nvidia.com> |
||||
* Mallikarjun Kasoju <mkasoju@nvidia.com> |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it |
||||
* under the terms and conditions of the GNU General Public License, |
||||
* version 2, as published by the Free Software Foundation. |
||||
*/ |
||||
|
||||
#include <linux/irq.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/mfd/max77620.h> |
||||
#include <linux/module.h> |
||||
#include <linux/of.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/regmap.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/thermal.h> |
||||
|
||||
#define MAX77620_NORMAL_OPERATING_TEMP 100000 |
||||
#define MAX77620_TJALARM1_TEMP 120000 |
||||
#define MAX77620_TJALARM2_TEMP 140000 |
||||
|
||||
struct max77620_therm_info { |
||||
struct device *dev; |
||||
struct regmap *rmap; |
||||
struct thermal_zone_device *tz_device; |
||||
int irq_tjalarm1; |
||||
int irq_tjalarm2; |
||||
}; |
||||
|
||||
/**
|
||||
* max77620_thermal_read_temp: Read PMIC die temperatue. |
||||
* @data: Device specific data. |
||||
* temp: Temperature in millidegrees Celsius |
||||
* |
||||
* The actual temperature of PMIC die is not available from PMIC. |
||||
* PMIC only tells the status if it has crossed or not the threshold level |
||||
* of 120degC or 140degC. |
||||
* If threshold has not been crossed then assume die temperature as 100degC |
||||
* else 120degC or 140deG based on the PMIC die temp threshold status. |
||||
* |
||||
* Return 0 on success otherwise error number to show reason of failure. |
||||
*/ |
||||
|
||||
static int max77620_thermal_read_temp(void *data, int *temp) |
||||
{ |
||||
struct max77620_therm_info *mtherm = data; |
||||
unsigned int val; |
||||
int ret; |
||||
|
||||
ret = regmap_read(mtherm->rmap, MAX77620_REG_STATLBT, &val); |
||||
if (ret < 0) { |
||||
dev_err(mtherm->dev, "Failed to read STATLBT: %d\n", ret); |
||||
return ret; |
||||
} |
||||
|
||||
if (val & MAX77620_IRQ_TJALRM2_MASK) |
||||
*temp = MAX77620_TJALARM2_TEMP; |
||||
else if (val & MAX77620_IRQ_TJALRM1_MASK) |
||||
*temp = MAX77620_TJALARM1_TEMP; |
||||
else |
||||
*temp = MAX77620_NORMAL_OPERATING_TEMP; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct thermal_zone_of_device_ops max77620_thermal_ops = { |
||||
.get_temp = max77620_thermal_read_temp, |
||||
}; |
||||
|
||||
static irqreturn_t max77620_thermal_irq(int irq, void *data) |
||||
{ |
||||
struct max77620_therm_info *mtherm = data; |
||||
|
||||
if (irq == mtherm->irq_tjalarm1) |
||||
dev_warn(mtherm->dev, "Junction Temp Alarm1(120C) occurred\n"); |
||||
else if (irq == mtherm->irq_tjalarm2) |
||||
dev_crit(mtherm->dev, "Junction Temp Alarm2(140C) occurred\n"); |
||||
|
||||
thermal_zone_device_update(mtherm->tz_device, |
||||
THERMAL_EVENT_UNSPECIFIED); |
||||
|
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
static int max77620_thermal_probe(struct platform_device *pdev) |
||||
{ |
||||
struct max77620_therm_info *mtherm; |
||||
int ret; |
||||
|
||||
mtherm = devm_kzalloc(&pdev->dev, sizeof(*mtherm), GFP_KERNEL); |
||||
if (!mtherm) |
||||
return -ENOMEM; |
||||
|
||||
mtherm->irq_tjalarm1 = platform_get_irq(pdev, 0); |
||||
mtherm->irq_tjalarm2 = platform_get_irq(pdev, 1); |
||||
if ((mtherm->irq_tjalarm1 < 0) || (mtherm->irq_tjalarm2 < 0)) { |
||||
dev_err(&pdev->dev, "Alarm irq number not available\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
pdev->dev.of_node = pdev->dev.parent->of_node; |
||||
|
||||
mtherm->dev = &pdev->dev; |
||||
mtherm->rmap = dev_get_regmap(pdev->dev.parent, NULL); |
||||
if (!mtherm->rmap) { |
||||
dev_err(&pdev->dev, "Failed to get parent regmap\n"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
mtherm->tz_device = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, |
||||
mtherm, &max77620_thermal_ops); |
||||
if (IS_ERR(mtherm->tz_device)) { |
||||
ret = PTR_ERR(mtherm->tz_device); |
||||
dev_err(&pdev->dev, "Failed to register thermal zone: %d\n", |
||||
ret); |
||||
return ret; |
||||
} |
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, mtherm->irq_tjalarm1, NULL, |
||||
max77620_thermal_irq, |
||||
IRQF_ONESHOT | IRQF_SHARED, |
||||
dev_name(&pdev->dev), mtherm); |
||||
if (ret < 0) { |
||||
dev_err(&pdev->dev, "Failed to request irq1: %d\n", ret); |
||||
return ret; |
||||
} |
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, mtherm->irq_tjalarm2, NULL, |
||||
max77620_thermal_irq, |
||||
IRQF_ONESHOT | IRQF_SHARED, |
||||
dev_name(&pdev->dev), mtherm); |
||||
if (ret < 0) { |
||||
dev_err(&pdev->dev, "Failed to request irq2: %d\n", ret); |
||||
return ret; |
||||
} |
||||
|
||||
platform_set_drvdata(pdev, mtherm); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct platform_device_id max77620_thermal_devtype[] = { |
||||
{ .name = "max77620-thermal", }, |
||||
{}, |
||||
}; |
||||
|
||||
static struct platform_driver max77620_thermal_driver = { |
||||
.driver = { |
||||
.name = "max77620-thermal", |
||||
}, |
||||
.probe = max77620_thermal_probe, |
||||
.id_table = max77620_thermal_devtype, |
||||
}; |
||||
|
||||
module_platform_driver(max77620_thermal_driver); |
||||
|
||||
MODULE_DESCRIPTION("Max77620 Junction temperature Thermal driver"); |
||||
MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); |
||||
MODULE_AUTHOR("Mallikarjun Kasoju <mkasoju@nvidia.com>"); |
||||
MODULE_LICENSE("GPL v2"); |
@ -0,0 +1,11 @@ |
||||
config QCOM_TSENS |
||||
tristate "Qualcomm TSENS Temperature Alarm" |
||||
depends on THERMAL |
||||
depends on QCOM_QFPROM |
||||
depends on ARCH_QCOM || COMPILE_TEST |
||||
help |
||||
This enables the thermal sysfs driver for the TSENS device. It shows |
||||
up in Sysfs as a thermal zone with multiple trip points. Disabling the |
||||
thermal zone device via the mode file results in disabling the sensor. |
||||
Also able to set threshold temperature for both hot and cold and update |
||||
when a threshold is reached. |
@ -0,0 +1,2 @@ |
||||
obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
|
||||
qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-8996.o
|
@ -0,0 +1,113 @@ |
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License version 2 and |
||||
* only version 2 as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/platform_device.h> |
||||
#include "tsens.h" |
||||
|
||||
/* eeprom layout data for 8916 */ |
||||
#define BASE0_MASK 0x0000007f |
||||
#define BASE1_MASK 0xfe000000 |
||||
#define BASE0_SHIFT 0 |
||||
#define BASE1_SHIFT 25 |
||||
|
||||
#define S0_P1_MASK 0x00000f80 |
||||
#define S1_P1_MASK 0x003e0000 |
||||
#define S2_P1_MASK 0xf8000000 |
||||
#define S3_P1_MASK 0x000003e0 |
||||
#define S4_P1_MASK 0x000f8000 |
||||
|
||||
#define S0_P2_MASK 0x0001f000 |
||||
#define S1_P2_MASK 0x07c00000 |
||||
#define S2_P2_MASK 0x0000001f |
||||
#define S3_P2_MASK 0x00007c00 |
||||
#define S4_P2_MASK 0x01f00000 |
||||
|
||||
#define S0_P1_SHIFT 7 |
||||
#define S1_P1_SHIFT 17 |
||||
#define S2_P1_SHIFT 27 |
||||
#define S3_P1_SHIFT 5 |
||||
#define S4_P1_SHIFT 15 |
||||
|
||||
#define S0_P2_SHIFT 12 |
||||
#define S1_P2_SHIFT 22 |
||||
#define S2_P2_SHIFT 0 |
||||
#define S3_P2_SHIFT 10 |
||||
#define S4_P2_SHIFT 20 |
||||
|
||||
#define CAL_SEL_MASK 0xe0000000 |
||||
#define CAL_SEL_SHIFT 29 |
||||
|
||||
static int calibrate_8916(struct tsens_device *tmdev) |
||||
{ |
||||
int base0 = 0, base1 = 0, i; |
||||
u32 p1[5], p2[5]; |
||||
int mode = 0; |
||||
u32 *qfprom_cdata, *qfprom_csel; |
||||
|
||||
qfprom_cdata = (u32 *)qfprom_read(tmdev->dev, "calib"); |
||||
if (IS_ERR(qfprom_cdata)) |
||||
return PTR_ERR(qfprom_cdata); |
||||
|
||||
qfprom_csel = (u32 *)qfprom_read(tmdev->dev, "calib_sel"); |
||||
if (IS_ERR(qfprom_csel)) |
||||
return PTR_ERR(qfprom_csel); |
||||
|
||||
mode = (qfprom_csel[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[1] & BASE1_MASK) >> BASE1_SHIFT; |
||||
p2[0] = (qfprom_cdata[0] & S0_P2_MASK) >> S0_P2_SHIFT; |
||||
p2[1] = (qfprom_cdata[0] & S1_P2_MASK) >> S1_P2_SHIFT; |
||||
p2[2] = (qfprom_cdata[1] & S2_P2_MASK) >> S2_P2_SHIFT; |
||||
p2[3] = (qfprom_cdata[1] & S3_P2_MASK) >> S3_P2_SHIFT; |
||||
p2[4] = (qfprom_cdata[1] & S4_P2_MASK) >> S4_P2_SHIFT; |
||||
for (i = 0; i < tmdev->num_sensors; i++) |
||||
p2[i] = ((base1 + p2[i]) << 3); |
||||
/* Fall through */ |
||||
case ONE_PT_CALIB2: |
||||
base0 = (qfprom_cdata[0] & BASE0_MASK); |
||||
p1[0] = (qfprom_cdata[0] & S0_P1_MASK) >> S0_P1_SHIFT; |
||||
p1[1] = (qfprom_cdata[0] & S1_P1_MASK) >> S1_P1_SHIFT; |
||||
p1[2] = (qfprom_cdata[0] & S2_P1_MASK) >> S2_P1_SHIFT; |
||||
p1[3] = (qfprom_cdata[1] & S3_P1_MASK) >> S3_P1_SHIFT; |
||||
p1[4] = (qfprom_cdata[1] & S4_P1_MASK) >> S4_P1_SHIFT; |
||||
for (i = 0; i < tmdev->num_sensors; i++) |
||||
p1[i] = (((base0) + p1[i]) << 3); |
||||
break; |
||||
default: |
||||
for (i = 0; i < tmdev->num_sensors; i++) { |
||||
p1[i] = 500; |
||||
p2[i] = 780; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
compute_intercept_slope(tmdev, p1, p2, mode); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct tsens_ops ops_8916 = { |
||||
.init = init_common, |
||||
.calibrate = calibrate_8916, |
||||
.get_temp = get_temp_common, |
||||
}; |
||||
|
||||
const struct tsens_data data_8916 = { |
||||
.num_sensors = 5, |
||||
.ops = &ops_8916, |
||||
.hw_ids = (unsigned int []){0, 1, 2, 4, 5 }, |
||||
}; |
@ -0,0 +1,292 @@ |
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License version 2 and |
||||
* only version 2 as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/platform_device.h> |
||||
#include <linux/delay.h> |
||||
#include <linux/bitops.h> |
||||
#include <linux/regmap.h> |
||||
#include <linux/thermal.h> |
||||
#include "tsens.h" |
||||
|
||||
#define CAL_MDEGC 30000 |
||||
|
||||
#define CONFIG_ADDR 0x3640 |
||||
#define CONFIG_ADDR_8660 0x3620 |
||||
/* CONFIG_ADDR bitmasks */ |
||||
#define CONFIG 0x9b |
||||
#define CONFIG_MASK 0xf |
||||
#define CONFIG_8660 1 |
||||
#define CONFIG_SHIFT_8660 28 |
||||
#define CONFIG_MASK_8660 (3 << CONFIG_SHIFT_8660) |
||||
|
||||
#define STATUS_CNTL_ADDR_8064 0x3660 |
||||
#define CNTL_ADDR 0x3620 |
||||
/* CNTL_ADDR bitmasks */ |
||||
#define EN BIT(0) |
||||
#define SW_RST BIT(1) |
||||
#define SENSOR0_EN BIT(3) |
||||
#define SLP_CLK_ENA BIT(26) |
||||
#define SLP_CLK_ENA_8660 BIT(24) |
||||
#define MEASURE_PERIOD 1 |
||||
#define SENSOR0_SHIFT 3 |
||||
|
||||
/* INT_STATUS_ADDR bitmasks */ |
||||
#define MIN_STATUS_MASK BIT(0) |
||||
#define LOWER_STATUS_CLR BIT(1) |
||||
#define UPPER_STATUS_CLR BIT(2) |
||||
#define MAX_STATUS_MASK BIT(3) |
||||
|
||||
#define THRESHOLD_ADDR 0x3624 |
||||
/* THRESHOLD_ADDR bitmasks */ |
||||
#define THRESHOLD_MAX_LIMIT_SHIFT 24 |
||||
#define THRESHOLD_MIN_LIMIT_SHIFT 16 |
||||
#define THRESHOLD_UPPER_LIMIT_SHIFT 8 |
||||
#define THRESHOLD_LOWER_LIMIT_SHIFT 0 |
||||
|
||||
/* Initial temperature threshold values */ |
||||
#define LOWER_LIMIT_TH 0x50 |
||||
#define UPPER_LIMIT_TH 0xdf |
||||
#define MIN_LIMIT_TH 0x0 |
||||
#define MAX_LIMIT_TH 0xff |
||||
|
||||
#define S0_STATUS_ADDR 0x3628 |
||||
#define INT_STATUS_ADDR 0x363c |
||||
#define TRDY_MASK BIT(7) |
||||
#define TIMEOUT_US 100 |
||||
|
||||
static int suspend_8960(struct tsens_device *tmdev) |
||||
{ |
||||
int ret; |
||||
unsigned int mask; |
||||
struct regmap *map = tmdev->map; |
||||
|
||||
ret = regmap_read(map, THRESHOLD_ADDR, &tmdev->ctx.threshold); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
ret = regmap_read(map, CNTL_ADDR, &tmdev->ctx.control); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
if (tmdev->num_sensors > 1) |
||||
mask = SLP_CLK_ENA | EN; |
||||
else |
||||
mask = SLP_CLK_ENA_8660 | EN; |
||||
|
||||
ret = regmap_update_bits(map, CNTL_ADDR, mask, 0); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int resume_8960(struct tsens_device *tmdev) |
||||
{ |
||||
int ret; |
||||
struct regmap *map = tmdev->map; |
||||
|
||||
ret = regmap_update_bits(map, CNTL_ADDR, SW_RST, SW_RST); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
/*
|
||||
* Separate CONFIG restore is not needed only for 8660 as |
||||
* config is part of CTRL Addr and its restored as such |
||||
*/ |
||||
if (tmdev->num_sensors > 1) { |
||||
ret = regmap_update_bits(map, CONFIG_ADDR, CONFIG_MASK, CONFIG); |
||||
if (ret) |
||||
return ret; |
||||
} |
||||
|
||||
ret = regmap_write(map, THRESHOLD_ADDR, tmdev->ctx.threshold); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
ret = regmap_write(map, CNTL_ADDR, tmdev->ctx.control); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int enable_8960(struct tsens_device *tmdev, int id) |
||||
{ |
||||
int ret; |
||||
u32 reg, mask; |
||||
|
||||
ret = regmap_read(tmdev->map, CNTL_ADDR, ®); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
mask = BIT(id + SENSOR0_SHIFT); |
||||
ret = regmap_write(tmdev->map, CNTL_ADDR, reg | SW_RST); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
if (tmdev->num_sensors > 1) |
||||
reg |= mask | SLP_CLK_ENA | EN; |
||||
else |
||||
reg |= mask | SLP_CLK_ENA_8660 | EN; |
||||
|
||||
ret = regmap_write(tmdev->map, CNTL_ADDR, reg); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void disable_8960(struct tsens_device *tmdev) |
||||
{ |
||||
int ret; |
||||
u32 reg_cntl; |
||||
u32 mask; |
||||
|
||||
mask = GENMASK(tmdev->num_sensors - 1, 0); |
||||
mask <<= SENSOR0_SHIFT; |
||||
mask |= EN; |
||||
|
||||
ret = regmap_read(tmdev->map, CNTL_ADDR, ®_cntl); |
||||
if (ret) |
||||
return; |
||||
|
||||
reg_cntl &= ~mask; |
||||
|
||||
if (tmdev->num_sensors > 1) |
||||
reg_cntl &= ~SLP_CLK_ENA; |
||||
else |
||||
reg_cntl &= ~SLP_CLK_ENA_8660; |
||||
|
||||
regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); |
||||
} |
||||
|
||||
static int init_8960(struct tsens_device *tmdev) |
||||
{ |
||||
int ret, i; |
||||
u32 reg_cntl; |
||||
|
||||
tmdev->map = dev_get_regmap(tmdev->dev, NULL); |
||||
if (!tmdev->map) |
||||
return -ENODEV; |
||||
|
||||
/*
|
||||
* The status registers for each sensor are discontiguous |
||||
* because some SoCs have 5 sensors while others have more |
||||
* but the control registers stay in the same place, i.e |
||||
* directly after the first 5 status registers. |
||||
*/ |
||||
for (i = 0; i < tmdev->num_sensors; i++) { |
||||
if (i >= 5) |
||||
tmdev->sensor[i].status = S0_STATUS_ADDR + 40; |
||||
tmdev->sensor[i].status += i * 4; |
||||
} |
||||
|
||||
reg_cntl = SW_RST; |
||||
ret = regmap_update_bits(tmdev->map, CNTL_ADDR, SW_RST, reg_cntl); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
if (tmdev->num_sensors > 1) { |
||||
reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18); |
||||
reg_cntl &= ~SW_RST; |
||||
ret = regmap_update_bits(tmdev->map, CONFIG_ADDR, |
||||
CONFIG_MASK, CONFIG); |
||||
} else { |
||||
reg_cntl |= SLP_CLK_ENA_8660 | (MEASURE_PERIOD << 16); |
||||
reg_cntl &= ~CONFIG_MASK_8660; |
||||
reg_cntl |= CONFIG_8660 << CONFIG_SHIFT_8660; |
||||
} |
||||
|
||||
reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << SENSOR0_SHIFT; |
||||
ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
reg_cntl |= EN; |
||||
ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int calibrate_8960(struct tsens_device *tmdev) |
||||
{ |
||||
int i; |
||||
char *data; |
||||
|
||||
ssize_t num_read = tmdev->num_sensors; |
||||
struct tsens_sensor *s = tmdev->sensor; |
||||
|
||||
data = qfprom_read(tmdev->dev, "calib"); |
||||
if (IS_ERR(data)) |
||||
data = qfprom_read(tmdev->dev, "calib_backup"); |
||||
if (IS_ERR(data)) |
||||
return PTR_ERR(data); |
||||
|
||||
for (i = 0; i < num_read; i++, s++) |
||||
s->offset = data[i]; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/* Temperature on y axis and ADC-code on x-axis */ |
||||
static inline int code_to_mdegC(u32 adc_code, const struct tsens_sensor *s) |
||||
{ |
||||
int slope, offset; |
||||
|
||||
slope = thermal_zone_get_slope(s->tzd); |
||||
offset = CAL_MDEGC - slope * s->offset; |
||||
|
||||
return adc_code * slope + offset; |
||||
} |
||||
|
||||
static int get_temp_8960(struct tsens_device *tmdev, int id, int *temp) |
||||
{ |
||||
int ret; |
||||
u32 code, trdy; |
||||
const struct tsens_sensor *s = &tmdev->sensor[id]; |
||||
unsigned long timeout; |
||||
|
||||
timeout = jiffies + usecs_to_jiffies(TIMEOUT_US); |
||||
do { |
||||
ret = regmap_read(tmdev->map, INT_STATUS_ADDR, &trdy); |
||||
if (ret) |
||||
return ret; |
||||
if (!(trdy & TRDY_MASK)) |
||||
continue; |
||||
ret = regmap_read(tmdev->map, s->status, &code); |
||||
if (ret) |
||||
return ret; |
||||
*temp = code_to_mdegC(code, s); |
||||
return 0; |
||||
} while (time_before(jiffies, timeout)); |
||||
|
||||
return -ETIMEDOUT; |
||||
} |
||||
|
||||
static const struct tsens_ops ops_8960 = { |
||||
.init = init_8960, |
||||
.calibrate = calibrate_8960, |
||||
.get_temp = get_temp_8960, |
||||
.enable = enable_8960, |
||||
.disable = disable_8960, |
||||
.suspend = suspend_8960, |
||||
.resume = resume_8960, |
||||
}; |
||||
|
||||
const struct tsens_data data_8960 = { |
||||
.num_sensors = 11, |
||||
.ops = &ops_8960, |
||||
}; |
@ -0,0 +1,244 @@ |
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License version 2 and |
||||
* only version 2 as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/platform_device.h> |
||||
#include "tsens.h" |
||||
|
||||
/* eeprom layout data for 8974 */ |
||||
#define BASE1_MASK 0xff |
||||
#define S0_P1_MASK 0x3f00 |
||||
#define S1_P1_MASK 0xfc000 |
||||
#define S2_P1_MASK 0x3f00000 |
||||
#define S3_P1_MASK 0xfc000000 |
||||
#define S4_P1_MASK 0x3f |
||||
#define S5_P1_MASK 0xfc0 |
||||
#define S6_P1_MASK 0x3f000 |
||||
#define S7_P1_MASK 0xfc0000 |
||||
#define S8_P1_MASK 0x3f000000 |
||||
#define S8_P1_MASK_BKP 0x3f |
||||
#define S9_P1_MASK 0x3f |
||||
#define S9_P1_MASK_BKP 0xfc0 |
||||
#define S10_P1_MASK 0xfc0 |
||||
#define S10_P1_MASK_BKP 0x3f000 |
||||
#define CAL_SEL_0_1 0xc0000000 |
||||
#define CAL_SEL_2 0x40000000 |
||||
#define CAL_SEL_SHIFT 30 |
||||
#define CAL_SEL_SHIFT_2 28 |
||||
|
||||
#define S0_P1_SHIFT 8 |
||||
#define S1_P1_SHIFT 14 |
||||
#define S2_P1_SHIFT 20 |
||||
#define S3_P1_SHIFT 26 |
||||
#define S5_P1_SHIFT 6 |
||||
#define S6_P1_SHIFT 12 |
||||
#define S7_P1_SHIFT 18 |
||||
#define S8_P1_SHIFT 24 |
||||
#define S9_P1_BKP_SHIFT 6 |
||||
#define S10_P1_SHIFT 6 |
||||
#define S10_P1_BKP_SHIFT 12 |
||||
|
||||
#define BASE2_SHIFT 12 |
||||
#define BASE2_BKP_SHIFT 18 |
||||
#define S0_P2_SHIFT 20 |
||||
#define S0_P2_BKP_SHIFT 26 |
||||
#define S1_P2_SHIFT 26 |
||||
#define S2_P2_BKP_SHIFT 6 |
||||
#define S3_P2_SHIFT 6 |
||||
#define S3_P2_BKP_SHIFT 12 |
||||
#define S4_P2_SHIFT 12 |
||||
#define S4_P2_BKP_SHIFT 18 |
||||
#define S5_P2_SHIFT 18 |
||||
#define S5_P2_BKP_SHIFT 24 |
||||
#define S6_P2_SHIFT 24 |
||||
#define S7_P2_BKP_SHIFT 6 |
||||
#define S8_P2_SHIFT 6 |
||||
#define S8_P2_BKP_SHIFT 12 |
||||
#define S9_P2_SHIFT 12 |
||||
#define S9_P2_BKP_SHIFT 18 |
||||
#define S10_P2_SHIFT 18 |
||||
#define S10_P2_BKP_SHIFT 24 |
||||
|
||||
#define BASE2_MASK 0xff000 |
||||
#define BASE2_BKP_MASK 0xfc0000 |
||||
#define S0_P2_MASK 0x3f00000 |
||||
#define S0_P2_BKP_MASK 0xfc000000 |
||||
#define S1_P2_MASK 0xfc000000 |
||||
#define S1_P2_BKP_MASK 0x3f |
||||
#define S2_P2_MASK 0x3f |
||||
#define S2_P2_BKP_MASK 0xfc0 |
||||
#define S3_P2_MASK 0xfc0 |
||||
#define S3_P2_BKP_MASK 0x3f000 |
||||
#define S4_P2_MASK 0x3f000 |
||||
#define S4_P2_BKP_MASK 0xfc0000 |
||||
#define S5_P2_MASK 0xfc0000 |
||||
#define S5_P2_BKP_MASK 0x3f000000 |
||||
#define S6_P2_MASK 0x3f000000 |
||||
#define S6_P2_BKP_MASK 0x3f |
||||
#define S7_P2_MASK 0x3f |
||||
#define S7_P2_BKP_MASK 0xfc0 |
||||
#define S8_P2_MASK 0xfc0 |
||||
#define S8_P2_BKP_MASK 0x3f000 |
||||
#define S9_P2_MASK 0x3f000 |
||||
#define S9_P2_BKP_MASK 0xfc0000 |
||||
#define S10_P2_MASK 0xfc0000 |
||||
#define S10_P2_BKP_MASK 0x3f000000 |
||||
|
||||
#define BKP_SEL 0x3 |
||||
#define BKP_REDUN_SEL 0xe0000000 |
||||
#define BKP_REDUN_SHIFT 29 |
||||
|
||||
#define BIT_APPEND 0x3 |
||||
|
||||
static int calibrate_8974(struct tsens_device *tmdev) |
||||
{ |
||||
int base1 = 0, base2 = 0, i; |
||||
u32 p1[11], p2[11]; |
||||
int mode = 0; |
||||
u32 *calib, *bkp; |
||||
u32 calib_redun_sel; |
||||
|
||||
calib = (u32 *)qfprom_read(tmdev->dev, "calib"); |
||||
if (IS_ERR(calib)) |
||||
return PTR_ERR(calib); |
||||
|
||||
bkp = (u32 *)qfprom_read(tmdev->dev, "calib_backup"); |
||||
if (IS_ERR(bkp)) |
||||
return PTR_ERR(bkp); |
||||
|
||||
calib_redun_sel = bkp[1] & BKP_REDUN_SEL; |
||||
calib_redun_sel >>= BKP_REDUN_SHIFT; |
||||
|
||||
if (calib_redun_sel == BKP_SEL) { |
||||
mode = (calib[4] & CAL_SEL_0_1) >> CAL_SEL_SHIFT; |
||||
mode |= (calib[5] & CAL_SEL_2) >> CAL_SEL_SHIFT_2; |
||||
|
||||
switch (mode) { |
||||
case TWO_PT_CALIB: |
||||
base2 = (bkp[2] & BASE2_BKP_MASK) >> BASE2_BKP_SHIFT; |
||||
p2[0] = (bkp[2] & S0_P2_BKP_MASK) >> S0_P2_BKP_SHIFT; |
||||
p2[1] = (bkp[3] & S1_P2_BKP_MASK); |
||||
p2[2] = (bkp[3] & S2_P2_BKP_MASK) >> S2_P2_BKP_SHIFT; |
||||
p2[3] = (bkp[3] & S3_P2_BKP_MASK) >> S3_P2_BKP_SHIFT; |
||||
p2[4] = (bkp[3] & S4_P2_BKP_MASK) >> S4_P2_BKP_SHIFT; |
||||
p2[5] = (calib[4] & S5_P2_BKP_MASK) >> S5_P2_BKP_SHIFT; |
||||
p2[6] = (calib[5] & S6_P2_BKP_MASK); |
||||
p2[7] = (calib[5] & S7_P2_BKP_MASK) >> S7_P2_BKP_SHIFT; |
||||
p2[8] = (calib[5] & S8_P2_BKP_MASK) >> S8_P2_BKP_SHIFT; |
||||
p2[9] = (calib[5] & S9_P2_BKP_MASK) >> S9_P2_BKP_SHIFT; |
||||
p2[10] = (calib[5] & S10_P2_BKP_MASK) >> S10_P2_BKP_SHIFT; |
||||
/* Fall through */ |
||||
case ONE_PT_CALIB: |
||||
case ONE_PT_CALIB2: |
||||
base1 = bkp[0] & BASE1_MASK; |
||||
p1[0] = (bkp[0] & S0_P1_MASK) >> S0_P1_SHIFT; |
||||
p1[1] = (bkp[0] & S1_P1_MASK) >> S1_P1_SHIFT; |
||||
p1[2] = (bkp[0] & S2_P1_MASK) >> S2_P1_SHIFT; |
||||
p1[3] = (bkp[0] & S3_P1_MASK) >> S3_P1_SHIFT; |
||||
p1[4] = (bkp[1] & S4_P1_MASK); |
||||
p1[5] = (bkp[1] & S5_P1_MASK) >> S5_P1_SHIFT; |
||||
p1[6] = (bkp[1] & S6_P1_MASK) >> S6_P1_SHIFT; |
||||
p1[7] = (bkp[1] & S7_P1_MASK) >> S7_P1_SHIFT; |
||||
p1[8] = (bkp[2] & S8_P1_MASK_BKP) >> S8_P1_SHIFT; |
||||
p1[9] = (bkp[2] & S9_P1_MASK_BKP) >> S9_P1_BKP_SHIFT; |
||||
p1[10] = (bkp[2] & S10_P1_MASK_BKP) >> S10_P1_BKP_SHIFT; |
||||
break; |
||||
} |
||||
} else { |
||||
mode = (calib[1] & CAL_SEL_0_1) >> CAL_SEL_SHIFT; |
||||
mode |= (calib[3] & CAL_SEL_2) >> CAL_SEL_SHIFT_2; |
||||
|
||||
switch (mode) { |
||||
case TWO_PT_CALIB: |
||||
base2 = (calib[2] & BASE2_MASK) >> BASE2_SHIFT; |
||||
p2[0] = (calib[2] & S0_P2_MASK) >> S0_P2_SHIFT; |
||||
p2[1] = (calib[2] & S1_P2_MASK) >> S1_P2_SHIFT; |
||||
p2[2] = (calib[3] & S2_P2_MASK); |
||||
p2[3] = (calib[3] & S3_P2_MASK) >> S3_P2_SHIFT; |
||||
p2[4] = (calib[3] & S4_P2_MASK) >> S4_P2_SHIFT; |
||||
p2[5] = (calib[3] & S5_P2_MASK) >> S5_P2_SHIFT; |
||||
p2[6] = (calib[3] & S6_P2_MASK) >> S6_P2_SHIFT; |
||||
p2[7] = (calib[4] & S7_P2_MASK); |
||||
p2[8] = (calib[4] & S8_P2_MASK) >> S8_P2_SHIFT; |
||||
p2[9] = (calib[4] & S9_P2_MASK) >> S9_P2_SHIFT; |
||||
p2[10] = (calib[4] & S10_P2_MASK) >> S10_P2_SHIFT; |
||||
/* Fall through */ |
||||
case ONE_PT_CALIB: |
||||
case ONE_PT_CALIB2: |
||||
base1 = calib[0] & BASE1_MASK; |
||||
p1[0] = (calib[0] & S0_P1_MASK) >> S0_P1_SHIFT; |
||||
p1[1] = (calib[0] & S1_P1_MASK) >> S1_P1_SHIFT; |
||||
p1[2] = (calib[0] & S2_P1_MASK) >> S2_P1_SHIFT; |
||||
p1[3] = (calib[0] & S3_P1_MASK) >> S3_P1_SHIFT; |
||||
p1[4] = (calib[1] & S4_P1_MASK); |
||||
p1[5] = (calib[1] & S5_P1_MASK) >> S5_P1_SHIFT; |
||||
p1[6] = (calib[1] & S6_P1_MASK) >> S6_P1_SHIFT; |
||||
p1[7] = (calib[1] & S7_P1_MASK) >> S7_P1_SHIFT; |
||||
p1[8] = (calib[1] & S8_P1_MASK) >> S8_P1_SHIFT; |
||||
p1[9] = (calib[2] & S9_P1_MASK); |
||||
p1[10] = (calib[2] & S10_P1_MASK) >> S10_P1_SHIFT; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
switch (mode) { |
||||
case ONE_PT_CALIB: |
||||
for (i = 0; i < tmdev->num_sensors; i++) |
||||
p1[i] += (base1 << 2) | BIT_APPEND; |
||||
break; |
||||
case TWO_PT_CALIB: |
||||
for (i = 0; i < tmdev->num_sensors; i++) { |
||||
p2[i] += base2; |
||||
p2[i] <<= 2; |
||||
p2[i] |= BIT_APPEND; |
||||
} |
||||
/* Fall through */ |
||||
case ONE_PT_CALIB2: |
||||
for (i = 0; i < tmdev->num_sensors; i++) { |
||||
p1[i] += base1; |
||||
p1[i] <<= 2; |
||||
p1[i] |= BIT_APPEND; |
||||
} |
||||
break; |
||||
default: |
||||
for (i = 0; i < tmdev->num_sensors; i++) |
||||
p2[i] = 780; |
||||
p1[0] = 502; |
||||
p1[1] = 509; |
||||
p1[2] = 503; |
||||
p1[3] = 509; |
||||
p1[4] = 505; |
||||
p1[5] = 509; |
||||
p1[6] = 507; |
||||
p1[7] = 510; |
||||
p1[8] = 508; |
||||
p1[9] = 509; |
||||
p1[10] = 508; |
||||
break; |
||||
} |
||||
|
||||
compute_intercept_slope(tmdev, p1, p2, mode); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct tsens_ops ops_8974 = { |
||||
.init = init_common, |
||||
.calibrate = calibrate_8974, |
||||
.get_temp = get_temp_common, |
||||
}; |
||||
|
||||
const struct tsens_data data_8974 = { |
||||
.num_sensors = 11, |
||||
.ops = &ops_8974, |
||||
}; |
@ -0,0 +1,84 @@ |
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License version 2 and |
||||
* only version 2 as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/platform_device.h> |
||||
#include <linux/regmap.h> |
||||
#include "tsens.h" |
||||
|
||||
#define STATUS_OFFSET 0x10a0 |
||||
#define LAST_TEMP_MASK 0xfff |
||||
#define STATUS_VALID_BIT BIT(21) |
||||
#define CODE_SIGN_BIT BIT(11) |
||||
|
||||
static int get_temp_8996(struct tsens_device *tmdev, int id, int *temp) |
||||
{ |
||||
struct tsens_sensor *s = &tmdev->sensor[id]; |
||||
u32 code; |
||||
unsigned int sensor_addr; |
||||
int last_temp = 0, last_temp2 = 0, last_temp3 = 0, ret; |
||||
|
||||
sensor_addr = STATUS_OFFSET + s->hw_id * 4; |
||||
ret = regmap_read(tmdev->map, sensor_addr, &code); |
||||
if (ret) |
||||
return ret; |
||||
last_temp = code & LAST_TEMP_MASK; |
||||
if (code & STATUS_VALID_BIT) |
||||
goto done; |
||||
|
||||
/* Try a second time */ |
||||
ret = regmap_read(tmdev->map, sensor_addr, &code); |
||||
if (ret) |
||||
return ret; |
||||
if (code & STATUS_VALID_BIT) { |
||||
last_temp = code & LAST_TEMP_MASK; |
||||
goto done; |
||||
} else { |
||||
last_temp2 = code & LAST_TEMP_MASK; |
||||
} |
||||
|
||||
/* Try a third/last time */ |
||||
ret = regmap_read(tmdev->map, sensor_addr, &code); |
||||
if (ret) |
||||
return ret; |
||||
if (code & STATUS_VALID_BIT) { |
||||
last_temp = code & LAST_TEMP_MASK; |
||||
goto done; |
||||
} else { |
||||
last_temp3 = code & LAST_TEMP_MASK; |
||||
} |
||||
|
||||
if (last_temp == last_temp2) |
||||
last_temp = last_temp2; |
||||
else if (last_temp2 == last_temp3) |
||||
last_temp = last_temp3; |
||||
done: |
||||
/* Code sign bit is the sign extension for a negative value */ |
||||
if (last_temp & CODE_SIGN_BIT) |
||||
last_temp |= ~CODE_SIGN_BIT; |
||||
|
||||
/* Temperatures are in deciCelicius */ |
||||
*temp = last_temp * 100; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct tsens_ops ops_8996 = { |
||||
.init = init_common, |
||||
.get_temp = get_temp_8996, |
||||
}; |
||||
|
||||
const struct tsens_data data_8996 = { |
||||
.num_sensors = 13, |
||||
.ops = &ops_8996, |
||||
}; |
@ -0,0 +1,141 @@ |
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License version 2 and |
||||
* only version 2 as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/err.h> |
||||
#include <linux/io.h> |
||||
#include <linux/nvmem-consumer.h> |
||||
#include <linux/of_address.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/regmap.h> |
||||
#include "tsens.h" |
||||
|
||||
#define S0_ST_ADDR 0x1030 |
||||
#define SN_ADDR_OFFSET 0x4 |
||||
#define SN_ST_TEMP_MASK 0x3ff |
||||
#define CAL_DEGC_PT1 30 |
||||
#define CAL_DEGC_PT2 120 |
||||
#define SLOPE_FACTOR 1000 |
||||
#define SLOPE_DEFAULT 3200 |
||||
|
||||
char *qfprom_read(struct device *dev, const char *cname) |
||||
{ |
||||
struct nvmem_cell *cell; |
||||
ssize_t data; |
||||
char *ret; |
||||
|
||||
cell = nvmem_cell_get(dev, cname); |
||||
if (IS_ERR(cell)) |
||||
return ERR_CAST(cell); |
||||
|
||||
ret = nvmem_cell_read(cell, &data); |
||||
nvmem_cell_put(cell); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
/*
|
||||
* Use this function on devices where slope and offset calculations |
||||
* depend on calibration data read from qfprom. On others the slope |
||||
* and offset values are derived from tz->tzp->slope and tz->tzp->offset |
||||
* resp. |
||||
*/ |
||||
void compute_intercept_slope(struct tsens_device *tmdev, u32 *p1, |
||||
u32 *p2, u32 mode) |
||||
{ |
||||
int i; |
||||
int num, den; |
||||
|
||||
for (i = 0; i < tmdev->num_sensors; i++) { |
||||
dev_dbg(tmdev->dev, |
||||
"sensor%d - data_point1:%#x data_point2:%#x\n", |
||||
i, p1[i], p2[i]); |
||||
|
||||
tmdev->sensor[i].slope = SLOPE_DEFAULT; |
||||
if (mode == TWO_PT_CALIB) { |
||||
/*
|
||||
* slope (m) = adc_code2 - adc_code1 (y2 - y1)/ |
||||
* temp_120_degc - temp_30_degc (x2 - x1) |
||||
*/ |
||||
num = p2[i] - p1[i]; |
||||
num *= SLOPE_FACTOR; |
||||
den = CAL_DEGC_PT2 - CAL_DEGC_PT1; |
||||
tmdev->sensor[i].slope = num / den; |
||||
} |
||||
|
||||
tmdev->sensor[i].offset = (p1[i] * SLOPE_FACTOR) - |
||||
(CAL_DEGC_PT1 * |
||||
tmdev->sensor[i].slope); |
||||
dev_dbg(tmdev->dev, "offset:%d\n", tmdev->sensor[i].offset); |
||||
} |
||||
} |
||||
|
||||
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; |
||||
} |
||||
|
||||
int get_temp_common(struct tsens_device *tmdev, int id, int *temp) |
||||
{ |
||||
struct tsens_sensor *s = &tmdev->sensor[id]; |
||||
u32 code; |
||||
unsigned int sensor_addr; |
||||
int last_temp = 0, ret; |
||||
|
||||
sensor_addr = S0_ST_ADDR + s->hw_id * SN_ADDR_OFFSET; |
||||
ret = regmap_read(tmdev->map, sensor_addr, &code); |
||||
if (ret) |
||||
return ret; |
||||
last_temp = code & SN_ST_TEMP_MASK; |
||||
|
||||
*temp = code_to_degc(last_temp, s) * 1000; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct regmap_config tsens_config = { |
||||
.reg_bits = 32, |
||||
.val_bits = 32, |
||||
.reg_stride = 4, |
||||
}; |
||||
|
||||
int __init init_common(struct tsens_device *tmdev) |
||||
{ |
||||
void __iomem *base; |
||||
|
||||
base = of_iomap(tmdev->dev->of_node, 0); |
||||
if (!base) |
||||
return -EINVAL; |
||||
|
||||
tmdev->map = devm_regmap_init_mmio(tmdev->dev, base, &tsens_config); |
||||
if (IS_ERR(tmdev->map)) { |
||||
iounmap(base); |
||||
return PTR_ERR(tmdev->map); |
||||
} |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,200 @@ |
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License version 2 and |
||||
* only version 2 as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/err.h> |
||||
#include <linux/module.h> |
||||
#include <linux/of.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/pm.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/thermal.h> |
||||
#include "tsens.h" |
||||
|
||||
static int tsens_get_temp(void *data, int *temp) |
||||
{ |
||||
const struct tsens_sensor *s = data; |
||||
struct tsens_device *tmdev = s->tmdev; |
||||
|
||||
return tmdev->ops->get_temp(tmdev, s->id, temp); |
||||
} |
||||
|
||||
static int tsens_get_trend(void *p, int trip, enum thermal_trend *trend) |
||||
{ |
||||
const struct tsens_sensor *s = p; |
||||
struct tsens_device *tmdev = s->tmdev; |
||||
|
||||
if (tmdev->ops->get_trend) |
||||
return tmdev->ops->get_trend(tmdev, s->id, trend); |
||||
|
||||
return -ENOTSUPP; |
||||
} |
||||
|
||||
static int __maybe_unused tsens_suspend(struct device *dev) |
||||
{ |
||||
struct tsens_device *tmdev = dev_get_drvdata(dev); |
||||
|
||||
if (tmdev->ops && tmdev->ops->suspend) |
||||
return tmdev->ops->suspend(tmdev); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int __maybe_unused tsens_resume(struct device *dev) |
||||
{ |
||||
struct tsens_device *tmdev = dev_get_drvdata(dev); |
||||
|
||||
if (tmdev->ops && tmdev->ops->resume) |
||||
return tmdev->ops->resume(tmdev); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume); |
||||
|
||||
static const struct of_device_id tsens_table[] = { |
||||
{ |
||||
.compatible = "qcom,msm8916-tsens", |
||||
.data = &data_8916, |
||||
}, { |
||||
.compatible = "qcom,msm8974-tsens", |
||||
.data = &data_8974, |
||||
}, { |
||||
.compatible = "qcom,msm8996-tsens", |
||||
.data = &data_8996, |
||||
}, |
||||
{} |
||||
}; |
||||
MODULE_DEVICE_TABLE(of, tsens_table); |
||||
|
||||
static const struct thermal_zone_of_device_ops tsens_of_ops = { |
||||
.get_temp = tsens_get_temp, |
||||
.get_trend = tsens_get_trend, |
||||
}; |
||||
|
||||
static int tsens_register(struct tsens_device *tmdev) |
||||
{ |
||||
int i; |
||||
struct thermal_zone_device *tzd; |
||||
u32 *hw_id, n = tmdev->num_sensors; |
||||
|
||||
hw_id = devm_kcalloc(tmdev->dev, n, sizeof(u32), GFP_KERNEL); |
||||
if (!hw_id) |
||||
return -ENOMEM; |
||||
|
||||
for (i = 0; i < tmdev->num_sensors; i++) { |
||||
tmdev->sensor[i].tmdev = tmdev; |
||||
tmdev->sensor[i].id = i; |
||||
tzd = devm_thermal_zone_of_sensor_register(tmdev->dev, i, |
||||
&tmdev->sensor[i], |
||||
&tsens_of_ops); |
||||
if (IS_ERR(tzd)) |
||||
continue; |
||||
tmdev->sensor[i].tzd = tzd; |
||||
if (tmdev->ops->enable) |
||||
tmdev->ops->enable(tmdev, i); |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
static int tsens_probe(struct platform_device *pdev) |
||||
{ |
||||
int ret, i; |
||||
struct device *dev; |
||||
struct device_node *np; |
||||
struct tsens_sensor *s; |
||||
struct tsens_device *tmdev; |
||||
const struct tsens_data *data; |
||||
const struct of_device_id *id; |
||||
|
||||
if (pdev->dev.of_node) |
||||
dev = &pdev->dev; |
||||
else |
||||
dev = pdev->dev.parent; |
||||
|
||||
np = dev->of_node; |
||||
|
||||
id = of_match_node(tsens_table, np); |
||||
if (id) |
||||
data = id->data; |
||||
else |
||||
data = &data_8960; |
||||
|
||||
if (data->num_sensors <= 0) { |
||||
dev_err(dev, "invalid number of sensors\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
tmdev = devm_kzalloc(dev, sizeof(*tmdev) + |
||||
data->num_sensors * sizeof(*s), GFP_KERNEL); |
||||
if (!tmdev) |
||||
return -ENOMEM; |
||||
|
||||
tmdev->dev = dev; |
||||
tmdev->num_sensors = data->num_sensors; |
||||
tmdev->ops = data->ops; |
||||
for (i = 0; i < tmdev->num_sensors; i++) { |
||||
if (data->hw_ids) |
||||
tmdev->sensor[i].hw_id = data->hw_ids[i]; |
||||
else |
||||
tmdev->sensor[i].hw_id = i; |
||||
} |
||||
|
||||
if (!tmdev->ops || !tmdev->ops->init || !tmdev->ops->get_temp) |
||||
return -EINVAL; |
||||
|
||||
ret = tmdev->ops->init(tmdev); |
||||
if (ret < 0) { |
||||
dev_err(dev, "tsens init failed\n"); |
||||
return ret; |
||||
} |
||||
|
||||
if (tmdev->ops->calibrate) { |
||||
ret = tmdev->ops->calibrate(tmdev); |
||||
if (ret < 0) { |
||||
dev_err(dev, "tsens calibration failed\n"); |
||||
return ret; |
||||
} |
||||
} |
||||
|
||||
ret = tsens_register(tmdev); |
||||
|
||||
platform_set_drvdata(pdev, tmdev); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static int tsens_remove(struct platform_device *pdev) |
||||
{ |
||||
struct tsens_device *tmdev = platform_get_drvdata(pdev); |
||||
|
||||
if (tmdev->ops->disable) |
||||
tmdev->ops->disable(tmdev); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct platform_driver tsens_driver = { |
||||
.probe = tsens_probe, |
||||
.remove = tsens_remove, |
||||
.driver = { |
||||
.name = "qcom-tsens", |
||||
.pm = &tsens_pm_ops, |
||||
.of_match_table = tsens_table, |
||||
}, |
||||
}; |
||||
module_platform_driver(tsens_driver); |
||||
|
||||
MODULE_LICENSE("GPL v2"); |
||||
MODULE_DESCRIPTION("QCOM Temperature Sensor driver"); |
||||
MODULE_ALIAS("platform:qcom-tsens"); |
@ -0,0 +1,94 @@ |
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved. |
||||
* |
||||
* This software is licensed under the terms of the GNU General Public |
||||
* License version 2, as published by the Free Software Foundation, and |
||||
* may be copied, distributed, and modified under those terms. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
*/ |
||||
#ifndef __QCOM_TSENS_H__ |
||||
#define __QCOM_TSENS_H__ |
||||
|
||||
#define ONE_PT_CALIB 0x1 |
||||
#define ONE_PT_CALIB2 0x2 |
||||
#define TWO_PT_CALIB 0x3 |
||||
|
||||
#include <linux/thermal.h> |
||||
|
||||
struct tsens_device; |
||||
|
||||
struct tsens_sensor { |
||||
struct tsens_device *tmdev; |
||||
struct thermal_zone_device *tzd; |
||||
int offset; |
||||
int id; |
||||
int hw_id; |
||||
int slope; |
||||
u32 status; |
||||
}; |
||||
|
||||
/**
|
||||
* struct tsens_ops - operations as supported by the tsens device |
||||
* @init: Function to initialize the tsens device |
||||
* @calibrate: Function to calibrate the tsens device |
||||
* @get_temp: Function which returns the temp in millidegC |
||||
* @enable: Function to enable (clocks/power) tsens device |
||||
* @disable: Function to disable the tsens device |
||||
* @suspend: Function to suspend the tsens device |
||||
* @resume: Function to resume the tsens device |
||||
* @get_trend: Function to get the thermal/temp trend |
||||
*/ |
||||
struct tsens_ops { |
||||
/* mandatory callbacks */ |
||||
int (*init)(struct tsens_device *); |
||||
int (*calibrate)(struct tsens_device *); |
||||
int (*get_temp)(struct tsens_device *, int, int *); |
||||
/* optional callbacks */ |
||||
int (*enable)(struct tsens_device *, int); |
||||
void (*disable)(struct tsens_device *); |
||||
int (*suspend)(struct tsens_device *); |
||||
int (*resume)(struct tsens_device *); |
||||
int (*get_trend)(struct tsens_device *, int, enum thermal_trend *); |
||||
}; |
||||
|
||||
/**
|
||||
* struct tsens_data - tsens instance specific data |
||||
* @num_sensors: Max number of sensors supported by platform |
||||
* @ops: operations the tsens instance supports |
||||
* @hw_ids: Subset of sensors ids supported by platform, if not the first n |
||||
*/ |
||||
struct tsens_data { |
||||
const u32 num_sensors; |
||||
const struct tsens_ops *ops; |
||||
unsigned int *hw_ids; |
||||
}; |
||||
|
||||
/* Registers to be saved/restored across a context loss */ |
||||
struct tsens_context { |
||||
int threshold; |
||||
int control; |
||||
}; |
||||
|
||||
struct tsens_device { |
||||
struct device *dev; |
||||
u32 num_sensors; |
||||
struct regmap *map; |
||||
struct regmap_field *status_field; |
||||
struct tsens_context ctx; |
||||
bool trdy; |
||||
const struct tsens_ops *ops; |
||||
struct tsens_sensor sensor[0]; |
||||
}; |
||||
|
||||
char *qfprom_read(struct device *, const char *); |
||||
void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32); |
||||
int init_common(struct tsens_device *); |
||||
int get_temp_common(struct tsens_device *, int, int *); |
||||
|
||||
extern const struct tsens_data data_8916, data_8974, data_8960, data_8996; |
||||
|
||||
#endif /* __QCOM_TSENS_H__ */ |
@ -0,0 +1,328 @@ |
||||
/*
|
||||
* Copyright 2016 Freescale Semiconductor, Inc. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it |
||||
* under the terms and conditions of the GNU General Public License, |
||||
* version 2, as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope it will be useful, but WITHOUT |
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
||||
* more details. |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/module.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/err.h> |
||||
#include <linux/io.h> |
||||
#include <linux/of.h> |
||||
#include <linux/of_address.h> |
||||
#include <linux/thermal.h> |
||||
|
||||
#include "thermal_core.h" |
||||
|
||||
#define SITES_MAX 16 |
||||
|
||||
/*
|
||||
* QorIQ TMU Registers |
||||
*/ |
||||
struct qoriq_tmu_site_regs { |
||||
u32 tritsr; /* Immediate Temperature Site Register */ |
||||
u32 tratsr; /* Average Temperature Site Register */ |
||||
u8 res0[0x8]; |
||||
}; |
||||
|
||||
struct qoriq_tmu_regs { |
||||
u32 tmr; /* Mode Register */ |
||||
#define TMR_DISABLE 0x0 |
||||
#define TMR_ME 0x80000000 |
||||
#define TMR_ALPF 0x0c000000 |
||||
u32 tsr; /* Status Register */ |
||||
u32 tmtmir; /* Temperature measurement interval Register */ |
||||
#define TMTMIR_DEFAULT 0x0000000f |
||||
u8 res0[0x14]; |
||||
u32 tier; /* Interrupt Enable Register */ |
||||
#define TIER_DISABLE 0x0 |
||||
u32 tidr; /* Interrupt Detect Register */ |
||||
u32 tiscr; /* Interrupt Site Capture Register */ |
||||
u32 ticscr; /* Interrupt Critical Site Capture Register */ |
||||
u8 res1[0x10]; |
||||
u32 tmhtcrh; /* High Temperature Capture Register */ |
||||
u32 tmhtcrl; /* Low Temperature Capture Register */ |
||||
u8 res2[0x8]; |
||||
u32 tmhtitr; /* High Temperature Immediate Threshold */ |
||||
u32 tmhtatr; /* High Temperature Average Threshold */ |
||||
u32 tmhtactr; /* High Temperature Average Crit Threshold */ |
||||
u8 res3[0x24]; |
||||
u32 ttcfgr; /* Temperature Configuration Register */ |
||||
u32 tscfgr; /* Sensor Configuration Register */ |
||||
u8 res4[0x78]; |
||||
struct qoriq_tmu_site_regs site[SITES_MAX]; |
||||
u8 res5[0x9f8]; |
||||
u32 ipbrr0; /* IP Block Revision Register 0 */ |
||||
u32 ipbrr1; /* IP Block Revision Register 1 */ |
||||
u8 res6[0x310]; |
||||
u32 ttr0cr; /* Temperature Range 0 Control Register */ |
||||
u32 ttr1cr; /* Temperature Range 1 Control Register */ |
||||
u32 ttr2cr; /* Temperature Range 2 Control Register */ |
||||
u32 ttr3cr; /* Temperature Range 3 Control Register */ |
||||
}; |
||||
|
||||
/*
|
||||
* Thermal zone data |
||||
*/ |
||||
struct qoriq_tmu_data { |
||||
struct thermal_zone_device *tz; |
||||
struct qoriq_tmu_regs __iomem *regs; |
||||
int sensor_id; |
||||
bool little_endian; |
||||
}; |
||||
|
||||
static void tmu_write(struct qoriq_tmu_data *p, u32 val, void __iomem *addr) |
||||
{ |
||||
if (p->little_endian) |
||||
iowrite32(val, addr); |
||||
else |
||||
iowrite32be(val, addr); |
||||
} |
||||
|
||||
static u32 tmu_read(struct qoriq_tmu_data *p, void __iomem *addr) |
||||
{ |
||||
if (p->little_endian) |
||||
return ioread32(addr); |
||||
else |
||||
return ioread32be(addr); |
||||
} |
||||
|
||||
static int tmu_get_temp(void *p, int *temp) |
||||
{ |
||||
u32 val; |
||||
struct qoriq_tmu_data *data = p; |
||||
|
||||
val = tmu_read(data, &data->regs->site[data->sensor_id].tritsr); |
||||
*temp = (val & 0xff) * 1000; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int qoriq_tmu_get_sensor_id(void) |
||||
{ |
||||
int ret, id; |
||||
struct of_phandle_args sensor_specs; |
||||
struct device_node *np, *sensor_np; |
||||
|
||||
np = of_find_node_by_name(NULL, "thermal-zones"); |
||||
if (!np) |
||||
return -ENODEV; |
||||
|
||||
sensor_np = of_get_next_child(np, NULL); |
||||
ret = of_parse_phandle_with_args(sensor_np, "thermal-sensors", |
||||
"#thermal-sensor-cells", |
||||
0, &sensor_specs); |
||||
if (ret) { |
||||
of_node_put(np); |
||||
of_node_put(sensor_np); |
||||
return ret; |
||||
} |
||||
|
||||
if (sensor_specs.args_count >= 1) { |
||||
id = sensor_specs.args[0]; |
||||
WARN(sensor_specs.args_count > 1, |
||||
"%s: too many cells in sensor specifier %d\n", |
||||
sensor_specs.np->name, sensor_specs.args_count); |
||||
} else { |
||||
id = 0; |
||||
} |
||||
|
||||
of_node_put(np); |
||||
of_node_put(sensor_np); |
||||
|
||||
return id; |
||||
} |
||||
|
||||
static int qoriq_tmu_calibration(struct platform_device *pdev) |
||||
{ |
||||
int i, val, len; |
||||
u32 range[4]; |
||||
const u32 *calibration; |
||||
struct device_node *np = pdev->dev.of_node; |
||||
struct qoriq_tmu_data *data = platform_get_drvdata(pdev); |
||||
|
||||
if (of_property_read_u32_array(np, "fsl,tmu-range", range, 4)) { |
||||
dev_err(&pdev->dev, "missing calibration range.\n"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
/* Init temperature range registers */ |
||||
tmu_write(data, range[0], &data->regs->ttr0cr); |
||||
tmu_write(data, range[1], &data->regs->ttr1cr); |
||||
tmu_write(data, range[2], &data->regs->ttr2cr); |
||||
tmu_write(data, range[3], &data->regs->ttr3cr); |
||||
|
||||
calibration = of_get_property(np, "fsl,tmu-calibration", &len); |
||||
if (calibration == NULL || len % 8) { |
||||
dev_err(&pdev->dev, "invalid calibration data.\n"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
for (i = 0; i < len; i += 8, calibration += 2) { |
||||
val = of_read_number(calibration, 1); |
||||
tmu_write(data, val, &data->regs->ttcfgr); |
||||
val = of_read_number(calibration + 1, 1); |
||||
tmu_write(data, val, &data->regs->tscfgr); |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void qoriq_tmu_init_device(struct qoriq_tmu_data *data) |
||||
{ |
||||
/* Disable interrupt, using polling instead */ |
||||
tmu_write(data, TIER_DISABLE, &data->regs->tier); |
||||
|
||||
/* Set update_interval */ |
||||
tmu_write(data, TMTMIR_DEFAULT, &data->regs->tmtmir); |
||||
|
||||
/* Disable monitoring */ |
||||
tmu_write(data, TMR_DISABLE, &data->regs->tmr); |
||||
} |
||||
|
||||
static struct thermal_zone_of_device_ops tmu_tz_ops = { |
||||
.get_temp = tmu_get_temp, |
||||
}; |
||||
|
||||
static int qoriq_tmu_probe(struct platform_device *pdev) |
||||
{ |
||||
int ret; |
||||
const struct thermal_trip *trip; |
||||
struct qoriq_tmu_data *data; |
||||
struct device_node *np = pdev->dev.of_node; |
||||
u32 site = 0; |
||||
|
||||
if (!np) { |
||||
dev_err(&pdev->dev, "Device OF-Node is NULL"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(struct qoriq_tmu_data), |
||||
GFP_KERNEL); |
||||
if (!data) |
||||
return -ENOMEM; |
||||
|
||||
platform_set_drvdata(pdev, data); |
||||
|
||||
data->little_endian = of_property_read_bool(np, "little-endian"); |
||||
|
||||
data->sensor_id = qoriq_tmu_get_sensor_id(); |
||||
if (data->sensor_id < 0) { |
||||
dev_err(&pdev->dev, "Failed to get sensor id\n"); |
||||
ret = -ENODEV; |
||||
goto err_iomap; |
||||
} |
||||
|
||||
data->regs = of_iomap(np, 0); |
||||
if (!data->regs) { |
||||
dev_err(&pdev->dev, "Failed to get memory region\n"); |
||||
ret = -ENODEV; |
||||
goto err_iomap; |
||||
} |
||||
|
||||
qoriq_tmu_init_device(data); /* TMU initialization */ |
||||
|
||||
ret = qoriq_tmu_calibration(pdev); /* TMU calibration */ |
||||
if (ret < 0) |
||||
goto err_tmu; |
||||
|
||||
data->tz = thermal_zone_of_sensor_register(&pdev->dev, data->sensor_id, |
||||
data, &tmu_tz_ops); |
||||
if (IS_ERR(data->tz)) { |
||||
ret = PTR_ERR(data->tz); |
||||
dev_err(&pdev->dev, |
||||
"Failed to register thermal zone device %d\n", ret); |
||||
goto err_tmu; |
||||
} |
||||
|
||||
trip = of_thermal_get_trip_points(data->tz); |
||||
|
||||
/* Enable monitoring */ |
||||
site |= 0x1 << (15 - data->sensor_id); |
||||
tmu_write(data, site | TMR_ME | TMR_ALPF, &data->regs->tmr); |
||||
|
||||
return 0; |
||||
|
||||
err_tmu: |
||||
iounmap(data->regs); |
||||
|
||||
err_iomap: |
||||
platform_set_drvdata(pdev, NULL); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static int qoriq_tmu_remove(struct platform_device *pdev) |
||||
{ |
||||
struct qoriq_tmu_data *data = platform_get_drvdata(pdev); |
||||
|
||||
thermal_zone_of_sensor_unregister(&pdev->dev, data->tz); |
||||
|
||||
/* Disable monitoring */ |
||||
tmu_write(data, TMR_DISABLE, &data->regs->tmr); |
||||
|
||||
iounmap(data->regs); |
||||
platform_set_drvdata(pdev, NULL); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
#ifdef CONFIG_PM_SLEEP |
||||
static int qoriq_tmu_suspend(struct device *dev) |
||||
{ |
||||
u32 tmr; |
||||
struct qoriq_tmu_data *data = dev_get_drvdata(dev); |
||||
|
||||
/* Disable monitoring */ |
||||
tmr = tmu_read(data, &data->regs->tmr); |
||||
tmr &= ~TMR_ME; |
||||
tmu_write(data, tmr, &data->regs->tmr); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int qoriq_tmu_resume(struct device *dev) |
||||
{ |
||||
u32 tmr; |
||||
struct qoriq_tmu_data *data = dev_get_drvdata(dev); |
||||
|
||||
/* Enable monitoring */ |
||||
tmr = tmu_read(data, &data->regs->tmr); |
||||
tmr |= TMR_ME; |
||||
tmu_write(data, tmr, &data->regs->tmr); |
||||
|
||||
return 0; |
||||
} |
||||
#endif |
||||
|
||||
static SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops, |
||||
qoriq_tmu_suspend, qoriq_tmu_resume); |
||||
|
||||
static const struct of_device_id qoriq_tmu_match[] = { |
||||
{ .compatible = "fsl,qoriq-tmu", }, |
||||
{}, |
||||
}; |
||||
MODULE_DEVICE_TABLE(of, qoriq_tmu_match); |
||||
|
||||
static struct platform_driver qoriq_tmu = { |
||||
.driver = { |
||||
.name = "qoriq_thermal", |
||||
.pm = &qoriq_tmu_pm_ops, |
||||
.of_match_table = qoriq_tmu_match, |
||||
}, |
||||
.probe = qoriq_tmu_probe, |
||||
.remove = qoriq_tmu_remove, |
||||
}; |
||||
module_platform_driver(qoriq_tmu); |
||||
|
||||
MODULE_AUTHOR("Jia Hongtao <hongtao.jia@nxp.com>"); |
||||
MODULE_DESCRIPTION("QorIQ Thermal Monitoring Unit driver"); |
||||
MODULE_LICENSE("GPL v2"); |
Loading…
Reference in new issue