Platform: RK3399
OS: Android 7.1
Kernel: v4.4.83
Board: Firefly-RK3399
背景:
和RK3288一样,RK3399上有两路TSADC通道分别用于监测CPU和GPU的温度。
不同的是RK3399结合kernel thermal core框架来管控,而RK3288直接是写了一个独立驱动控制硬件。
RK3288平台可参考: [RK3288][Android6.0] TS-ADC驱动流程小结
TSADC两种模式:
1.用户自定义模式。 所以信号都通过是user写到寄存器中控制。
2.自动模式。 控制器自动查询TSADC输出,如果温度过高就会产生中断,如果再高就会发信号给CRU模块复位整个芯片或者通过gpio通知PMU做处理。
TSADC控制器特性:
1. 支持用户自定义和自动模式
2. 支持两路通道
3. 系统复位的温度点可被配置
4. 可设置范围:~40-125°, 精度是5°
5. ADC精度10bit,采样率50kb/s
6. 温度探测和周期值可配置
控制器配置:
rk3399.dtsi:
tsadc: tsadc@ff260000 {
compatible = "rockchip,rk3399-tsadc"; //设备名,和驱动匹配
reg = <0x0 0xff260000 0x0 0x100>;
interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH 0>;
rockchip,grf = <&grf>;
clocks = <&cru SCLK_TSADC>, <&cru PCLK_TSADC>;
clock-names = "tsadc", "apb_pclk";
assigned-clocks = <&cru SCLK_TSADC>;
assigned-clock-rates = <750000>;
resets = <&cru SRST_TSADC>;//reset对应的cru
reset-names = "tsadc-apb";
pinctrl-names = "init", "default", "sleep";
pinctrl-0 = <&otp_gpio>;
pinctrl-1 = <&otp_out>;
pinctrl-2 = <&otp_gpio>;
#thermal-sensor-cells = <1>;
rockchip,hw-tshut-temp = <95000>; //设置的关机温度
status = "disabled";
};
另一部分配置是在rk3399-firefly-core.c中:
&tsadc {
/* tshut mode 0:CRU 1:GPIO */
rockchip,hw-tshut-mode = <1>; //关机模式,设置为通过gpio方式
/* tshut polarity 0:LOW 1:HIGH */
rockchip,hw-tshut-polarity = <1>; //对应关机极性
status = "okay";
};
除了tsadc之外,我们也需要了解thermal zone的配置:
thermal_zones: thermal-zones {
soc_thermal: soc-thermal { //对应cpu thermal
polling-delay-passive = <20>; /* milliseconds */ //当超过阀值时,每隔20ms查询一次。
polling-delay = <1000>; /* milliseconds */ //未超过阀值时,每隔1000ms查询一次。
sustainable-power = <1000>; /* milliwatts */ //当前温度到达预设的最高值时,系统能分配给 cooling 设备的能量。
//用于power allocator策略。
thermal-sensors = <&tsadc 0>; //使用tsadc的channel 0.
trips { //控制温度触发范围值配置
threshold: trip-point@0 {
temperature = <70000>; /* millicelsius */ //温度超过70°温控策略开始工作,但不一定马上限制频率。
hysteresis = <2000>; /* millicelsius */ //迟滞值,允许一定时间缓冲后才开始执行控制策略。
type = "passive"; //要设置成"passive"
};
target: trip-point@1 { //
temperature = <85000>; /* millicelsius */
hysteresis = <2000>; /* millicelsius */
type = "passive";
};
soc_crit: soc-crit { //系统温度的临界值,超过就要关机了。
temperature = <95000>; /* millicelsius */
hysteresis = <2000>; /* millicelsius */
type = "critical"; //要设置成critical才会起作用。
};
};
cooling-maps { //降温策略配置,有三个要被降温的设备,分别是小核A53(cpu_l0), 大核A72(cpu_b0)以及GPU
map0 {
trip = <&target>; //power_allocater策略子节点的trip属性都设置target。
cooling-device =
<&cpu_l0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; //允许被限制最低和最高频率。
contribution = <4096>; //注释是 the percentage (from 0 to 100) of cooling contribution,还未研究其意义。
};
map1 {
trip = <&target>;
cooling-device =
<&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
contribution = <1024>;
};
map2 {
trip = <&target>;
cooling-device =
<&gpu THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
contribution = <4096>;
};
};
};
gpu_thermal: gpu-thermal {
polling-delay-passive = <100>; /* milliseconds */
polling-delay = <1000>; /* milliseconds */
thermal-sensors = <&tsadc 1>;
};
};
驱动文件:
rockchip_thermal.c:
rk3399 thermal controller驱动。
of-thermal.c:
thermal core device tree接口。
thermal_core.c:
linux thermal core核心框架。
重要数据结构:
如下这个数据结构,里面基本上包含了驱动所有信息和功能,其中有些对应的值比如关机模式,关机温度等可以在dts中修改(前面dts已经提及过)
rockchip_thermal.c:
static const struct rockchip_tsadc_chip rk3399_tsadc_data = {
.chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */
.chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */
.chn_num = 2, /* two channels for tsadc */
.tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
.tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
.tshut_temp = 95000,
//操作函数集
.initialize = rk_tsadcv3_initialize,
.irq_ack = rk_tsadcv3_irq_ack,
.control = rk_tsadcv3_control,
.get_temp = rk_tsadcv2_get_temp,
.set_alarm_temp = rk_tsadcv2_alarm_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
//表格就是读到的ADC和温度的关系。
.table = {
.id = rk3399_code_table,
.length = ARRAY_SIZE(rk3399_code_table),
.data_mask = TSADCV3_DATA_MASK,
.mode = ADC_INCREMENT,
},
};
thermal core框架分三个模块:获取温度、降温以及控制温度策略模块。
对应的是struct thermal_zone_device,struct thermal_cooling_device 以及struct thermal_governor结构。
图片来源于网络:
代码调用流程:
TSADC驱动初始化:
rockchip_thermal_probe -> rockchip-thermal.c 用struct rockchip_thermal_data结构体表示thermal信息。
thermal->chip = (const struct rockchip_tsadc_chip *)match->data; //data就是rk3399_tsadc_data变量
rockchip_thermal_reset_controller //先复位控制器
rockchip_configure_from_dt //解析dts中的"rockchip,hw-tshut-temp","rockchip,hw-tshut-mode"以及"rockchip,hw-tshut-polarity"
thermal->chip->initialize ->
rk_tsadcv3_initialize //初始化TSADC控制器
rockchip_thermal_register_sensor -> //注册控制器
tsadc->set_tshut_mode -> //设置关机模式
rk_tsadcv2_tshut_mode
tsadc->set_tshut_temp -> //设置关机温度
devm_thermal_zone_of_sensor_register -> of-thermal.c //注册thermal zone到系统中,留意其中的ops: rockchip_of_thermal_ops
thermal_zone_of_sensor_register -> //返回的struct thermal_zone_device是thermal框架中标准结构
of_find_node_by_name(NULL, "thermal-zones"); //rk3399.dtsi中有此节点
thermal_zone_of_add_sensor -> //有soc_thermal和gpu_thermal两个zone,rockchip定义的ops会和
//struct rockchip_thermal_sensor被放到struct __thermal_zone中
//可以看到,thermal_zone_device包含__thermal_zone和ops, __thermal_zone包含
//rk自己定义的rockchip_thermal_sensor和ops,注意两者ops的区别,不要混淆了,下面的
//set_mode调用过程可以看到ops的一级级调用,最终控制到rk的tsadc控制器。
thermal_zone_get_zone_by_name //thermal zone在thermal_core.c中注册过了。
tzd->ops->set_mode ->
of_thermal_set_mode -> of-thermal.c
thermal_zone_device_update ->
update_temperature -> //更新温度
thermal_zone_get_temp ->
tz->ops->get_temp ->
of_thermal_get_temp ->
data->ops->get_temp -> //对应ops是rockchip_of_thermal_ops
rockchip_thermal_get_temp ->
tsadc->get_temp ->
rk_tsadcv2_get_temp -> //调用控制器的get temp接口
rk_tsadcv2_code_to_temp //code转换成temperature
thermal_zone_set_trips ->
tz->ops->get_trip_temp -> //获取各个trip对应的温度,trip有三个,配置在thermal_zones节点中定义。
of_thermal_get_trip_temp ->
*temp = data->trips[trip].temperature //根据trip number获取对应的温度
tz->ops->get_trip_hyst -> //获取迟滞值,此值会和上面的temperature结合,比如原本是75°触发,迟滞值是5°,那么80°才会真的触发thermal core做策略调整
of_thermal_get_trip_hyst
tz->ops->set_trips ->
of_thermal_set_trips ->
data->ops->set_trips ->
rockchip_thermal_set_trips ->
tsadc->set_alarm_temp ->
rk_tsadcv2_alarm_temp -> //设置触发alarm的温度,对应中断处理在下面注册
handle_thermal_trip ->
tz->ops->get_trip_type -> //获取trip type
of_thermal_get_trip_type
handle_critical_trips -> //如果type是critical就走此流程
tz->ops->get_trip_temp //获取此trip对应设置的温度(dts中配置),如果没达到此温度就不做处理
orderly_poweroff //执行关机动作
handle_non_critical_trips -> //非critical的情况
tz->governor->throttle //执行thermal core cooling策略
monitor_thermal_zone //继续定时监测温度
devm_request_threaded_irq //创建一个thermal thread,处理函数是rockchip_thermal_alarm_irq_thread()
thermal->chip->control -> //使能tsadc控制器
rk_tsadcv3_control
rockchip_thermal_toggle_sensor ->
tzd->ops->set_mode //又重新调用了一次
atomic_notifier_chain_register //系统panic的时候会发送回调,对应的处理函数是rockchip_thermal_panic(),打印当前cpu和gpu温度和寄存器信息。
alarm中断触发:
中断函数是根据设置的温度来触发的,首次的温度触发soc_thermal子节点trips中最小的temperature值,即threshold节点的temperature值。
GPU通道并没有设置,因此不会触发中断(后面会提到thermal core框架自身就带有周期查询温度功能)。
当中断触发时也是调用的thermal_zone_device_update()来更新温度和trips等参数
static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev)
{
struct rockchip_thermal_data *thermal = dev;
int i;
dev_dbg(&thermal->pdev->dev, "thermal alarm\n");
thermal->chip->irq_ack(thermal->regs);
for (i = 0; i < thermal->chip->chn_num; i++)
thermal_zone_device_update(thermal->sensors[i].tzd);
return IRQ_HANDLED;
}
当cpu温度超过第一级阀值即threshold节点的temperature值时,alarm会被设置成第二级阀值即target节点中的temperature值,也就是85°才会触发,如果再高,就会设置成第三级阀值即soc_crit节点中设置的95°。如果CPU温度降到第一级阀值,那么alarm又重新被设置到threshold节点的temperature值,此功能实现是在thermal_zone_set_trips()中。
static void thermal_zone_set_trips(struct thermal_zone_device *tz)
{
int low = -INT_MAX;
int high = INT_MAX;
int trip_temp, hysteresis;
int temp = tz->temperature;
int i, ret;
if (!tz->ops->set_trips)
return;
for (i = 0; i < tz->trips; i++) {
int trip_low;
tz->ops->get_trip_temp(tz, i, &trip_temp);
tz->ops->get_trip_hyst(tz, i, &hysteresis);
trip_low = trip_temp - hysteresis;
if (trip_low < temp && trip_low > low)
low = trip_low;
if (trip_temp > temp && trip_temp < high)
high = trip_temp;
}
/* No need to change trip points */
if (tz->prev_low_trip == low && tz->prev_high_trip == high)
return;
tz->prev_low_trip = low;
tz->prev_high_trip = high;
dev_info(&tz->device, "new temperature boundaries: %d < x < %d\n",
low, high);
ret = tz->ops->set_trips(tz, low, high);
if (ret)
dev_err(&tz->device, "Failed to set trips: %d\n", ret);
}
thermal温度定时查询:
thermal core框架中有个轮询队列,会根据当前状态来决定多少时间监测更新温度。
struct thermal_zone_device *thermal_zone_device_register(.....)
{
......
INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check);
......
}
查询时间周期根据当前状态来设置:
static void monitor_thermal_zone(struct thermal_zone_device *tz)
{
mutex_lock(&tz->lock);
if (tz->passive)
thermal_zone_device_set_polling(tz, tz->passive_delay);
else if (tz->polling_delay)
thermal_zone_device_set_polling(tz, tz->polling_delay);
else
thermal_zone_device_set_polling(tz, 0);
mutex_unlock(&tz->lock);
}
获取当前温度:
CPU:
rk3399_firefly_edp_box:/ # cat /sys/class/thermal/thermal_zone0/temp
46250
GPU:
rk3399_firefly_edp_box:/ # cat /sys/class/thermal/thermal_zone1/temp
46875
thermal_zone2是”test_battery”注册的thermal zone驱动:
rk3399_firefly_edp_box:/sys/class/thermal/thermal_zone2 # cat type
test_battery
thermal zone的注册通过thermal_zone_device_register()完成,相应地也会在
/sys/class/thermal/下创建thermal_zoneX目录,包含当前thermal所有信息,如当前策略,温度等。
CPU和GPU注册过程:
thermal_init ->
of_parse_thermal_zones ->
thermal_zone_device_register
test_battery设备注册流程:
test_power_init -> test_power.c
power_supply_register ->
__power_supply_register ->
psy_register_thermal ->
thermal_zone_device_register
未研究问题:
1. trips为什么要分三级?
2. GPU不需要trips?
3. cooling map如何处理?
4. 降温策略研究