[RK3399][Android7.1] TSADC驱动流程小结(With thermal core)

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

参考:
Linux Thermal 框架解析


未研究问题:
1. trips为什么要分三级?
2. GPU不需要trips?
3. cooling map如何处理?
4. 降温策略研究

猜你喜欢

转载自blog.csdn.net/kris_fei/article/details/79713193