Commit 37713a1e authored by Philipp Zabel's avatar Philipp Zabel Committed by Zhang Rui

thermal: imx: implement thermal alarm interrupt handling

Enable automatic measurements at 10 Hz and use the alarm interrupt to react
more quickly to sudden temperature changes above the passive or critical
temperature trip points.
Signed-off-by: default avatarPhilipp Zabel <p.zabel@pengutronix.de>
Acked-by: default avatarShawn Guo <shawn.guo@linaro.org>
Signed-off-by: default avatarZhang Rui <rui.zhang@intel.com>
parent 017e5142
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/mfd/syscon.h> #include <linux/mfd/syscon.h>
...@@ -31,6 +32,8 @@ ...@@ -31,6 +32,8 @@
#define MISC0_REFTOP_SELBIASOFF (1 << 3) #define MISC0_REFTOP_SELBIASOFF (1 << 3)
#define TEMPSENSE0 0x0180 #define TEMPSENSE0 0x0180
#define TEMPSENSE0_ALARM_VALUE_SHIFT 20
#define TEMPSENSE0_ALARM_VALUE_MASK (0xfff << TEMPSENSE0_ALARM_VALUE_SHIFT)
#define TEMPSENSE0_TEMP_CNT_SHIFT 8 #define TEMPSENSE0_TEMP_CNT_SHIFT 8
#define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT) #define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT)
#define TEMPSENSE0_FINISHED (1 << 2) #define TEMPSENSE0_FINISHED (1 << 2)
...@@ -66,33 +69,62 @@ struct imx_thermal_data { ...@@ -66,33 +69,62 @@ struct imx_thermal_data {
int c1, c2; /* See formula in imx_get_sensor_data() */ int c1, c2; /* See formula in imx_get_sensor_data() */
unsigned long temp_passive; unsigned long temp_passive;
unsigned long temp_critical; unsigned long temp_critical;
unsigned long alarm_temp;
unsigned long last_temp;
bool irq_enabled;
int irq;
}; };
static void imx_set_alarm_temp(struct imx_thermal_data *data,
signed long alarm_temp)
{
struct regmap *map = data->tempmon;
int alarm_value;
data->alarm_temp = alarm_temp;
alarm_value = (alarm_temp - data->c2) / data->c1;
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_ALARM_VALUE_MASK);
regmap_write(map, TEMPSENSE0 + REG_SET, alarm_value <<
TEMPSENSE0_ALARM_VALUE_SHIFT);
}
static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp) static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
{ {
struct imx_thermal_data *data = tz->devdata; struct imx_thermal_data *data = tz->devdata;
struct regmap *map = data->tempmon; struct regmap *map = data->tempmon;
static unsigned long last_temp;
unsigned int n_meas; unsigned int n_meas;
bool wait;
u32 val; u32 val;
/* if (data->mode == THERMAL_DEVICE_ENABLED) {
* Every time we measure the temperature, we will power on the /* Check if a measurement is currently in progress */
* temperature sensor, enable measurements, take a reading, regmap_read(map, TEMPSENSE0, &val);
* disable measurements, power off the temperature sensor. wait = !(val & TEMPSENSE0_FINISHED);
*/ } else {
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); /*
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); * Every time we measure the temperature, we will power on the
* temperature sensor, enable measurements, take a reading,
* disable measurements, power off the temperature sensor.
*/
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
wait = true;
}
/* /*
* According to the temp sensor designers, it may require up to ~17us * According to the temp sensor designers, it may require up to ~17us
* to complete a measurement. * to complete a measurement.
*/ */
usleep_range(20, 50); if (wait)
usleep_range(20, 50);
regmap_read(map, TEMPSENSE0, &val); regmap_read(map, TEMPSENSE0, &val);
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); if (data->mode != THERMAL_DEVICE_ENABLED) {
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
}
if ((val & TEMPSENSE0_FINISHED) == 0) { if ((val & TEMPSENSE0_FINISHED) == 0) {
dev_dbg(&tz->device, "temp measurement never finished\n"); dev_dbg(&tz->device, "temp measurement never finished\n");
...@@ -104,9 +136,24 @@ static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp) ...@@ -104,9 +136,24 @@ static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
/* See imx_get_sensor_data() for formula derivation */ /* See imx_get_sensor_data() for formula derivation */
*temp = data->c2 + data->c1 * n_meas; *temp = data->c2 + data->c1 * n_meas;
if (*temp != last_temp) { /* Update alarm value to next higher trip point */
if (data->alarm_temp == data->temp_passive && *temp >= data->temp_passive)
imx_set_alarm_temp(data, data->temp_critical);
if (data->alarm_temp == data->temp_critical && *temp < data->temp_passive) {
imx_set_alarm_temp(data, data->temp_passive);
dev_dbg(&tz->device, "thermal alarm off: T < %lu\n",
data->alarm_temp / 1000);
}
if (*temp != data->last_temp) {
dev_dbg(&tz->device, "millicelsius: %ld\n", *temp); dev_dbg(&tz->device, "millicelsius: %ld\n", *temp);
last_temp = *temp; data->last_temp = *temp;
}
/* Reenable alarm IRQ if temperature below alarm temperature */
if (!data->irq_enabled && *temp < data->alarm_temp) {
data->irq_enabled = true;
enable_irq(data->irq);
} }
return 0; return 0;
...@@ -126,13 +173,30 @@ static int imx_set_mode(struct thermal_zone_device *tz, ...@@ -126,13 +173,30 @@ static int imx_set_mode(struct thermal_zone_device *tz,
enum thermal_device_mode mode) enum thermal_device_mode mode)
{ {
struct imx_thermal_data *data = tz->devdata; struct imx_thermal_data *data = tz->devdata;
struct regmap *map = data->tempmon;
if (mode == THERMAL_DEVICE_ENABLED) { if (mode == THERMAL_DEVICE_ENABLED) {
tz->polling_delay = IMX_POLLING_DELAY; tz->polling_delay = IMX_POLLING_DELAY;
tz->passive_delay = IMX_PASSIVE_DELAY; tz->passive_delay = IMX_PASSIVE_DELAY;
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
if (!data->irq_enabled) {
data->irq_enabled = true;
enable_irq(data->irq);
}
} else { } else {
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
tz->polling_delay = 0; tz->polling_delay = 0;
tz->passive_delay = 0; tz->passive_delay = 0;
if (data->irq_enabled) {
disable_irq(data->irq);
data->irq_enabled = false;
}
} }
data->mode = mode; data->mode = mode;
...@@ -181,6 +245,8 @@ static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip, ...@@ -181,6 +245,8 @@ static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
data->temp_passive = temp; data->temp_passive = temp;
imx_set_alarm_temp(data, temp);
return 0; return 0;
} }
...@@ -299,11 +365,34 @@ static int imx_get_sensor_data(struct platform_device *pdev) ...@@ -299,11 +365,34 @@ static int imx_get_sensor_data(struct platform_device *pdev)
return 0; return 0;
} }
static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
{
struct imx_thermal_data *data = dev;
disable_irq_nosync(irq);
data->irq_enabled = false;
return IRQ_WAKE_THREAD;
}
static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
{
struct imx_thermal_data *data = dev;
dev_dbg(&data->tz->device, "THERMAL ALARM: T > %lu\n",
data->alarm_temp / 1000);
thermal_zone_device_update(data->tz);
return IRQ_HANDLED;
}
static int imx_thermal_probe(struct platform_device *pdev) static int imx_thermal_probe(struct platform_device *pdev)
{ {
struct imx_thermal_data *data; struct imx_thermal_data *data;
struct cpumask clip_cpus; struct cpumask clip_cpus;
struct regmap *map; struct regmap *map;
int measure_freq;
int ret; int ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
...@@ -318,6 +407,18 @@ static int imx_thermal_probe(struct platform_device *pdev) ...@@ -318,6 +407,18 @@ static int imx_thermal_probe(struct platform_device *pdev)
} }
data->tempmon = map; data->tempmon = map;
data->irq = platform_get_irq(pdev, 0);
if (data->irq < 0)
return data->irq;
ret = devm_request_threaded_irq(&pdev->dev, data->irq,
imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
0, "imx_thermal", data);
if (ret < 0) {
dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, data); platform_set_drvdata(pdev, data);
ret = imx_get_sensor_data(pdev); ret = imx_get_sensor_data(pdev);
...@@ -356,6 +457,15 @@ static int imx_thermal_probe(struct platform_device *pdev) ...@@ -356,6 +457,15 @@ static int imx_thermal_probe(struct platform_device *pdev)
return ret; return ret;
} }
/* Enable measurements at ~ 10 Hz */
regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ);
measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
regmap_write(map, TEMPSENSE1 + REG_SET, measure_freq);
imx_set_alarm_temp(data, data->temp_passive);
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
data->irq_enabled = true;
data->mode = THERMAL_DEVICE_ENABLED; data->mode = THERMAL_DEVICE_ENABLED;
return 0; return 0;
...@@ -364,6 +474,10 @@ static int imx_thermal_probe(struct platform_device *pdev) ...@@ -364,6 +474,10 @@ static int imx_thermal_probe(struct platform_device *pdev)
static int imx_thermal_remove(struct platform_device *pdev) static int imx_thermal_remove(struct platform_device *pdev)
{ {
struct imx_thermal_data *data = platform_get_drvdata(pdev); struct imx_thermal_data *data = platform_get_drvdata(pdev);
struct regmap *map = data->tempmon;
/* Disable measurements */
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
thermal_zone_device_unregister(data->tz); thermal_zone_device_unregister(data->tz);
cpufreq_cooling_unregister(data->cdev); cpufreq_cooling_unregister(data->cdev);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment