Huashan worknote

一,有用的连接
1. repo init -u git://review.sonyericsson.net/platform/manifest -b feature-ics-viskan-es2-bringup
2. repo init -u git://review.sonyericsson.net/platform/manifest -b jb-viskan-int
3. https://wiki.sonyericsson.net/androiki/Huashan_SW_Program
4. https://wiki.sonyericsson.net/androiki/PLD/SW/QC/PLD_SW_Test_Plan_FP8640_Thermal_shutdown
5. https://wiki.sonyericsson.net/androiki/FP6777_Thermal_Shutdown
6. https://wiki.sonyericsson.net/androiki/8640_Thermal_Function
7. https://wiki.sonyericsson.net/androiki/MIB_Tokyo/Blue/Thermal_Management
8. https://wiki.sonyericsson.net/androiki/Odin/Thermal
9. http://android-ci-platform.cnbj.sonyericsson.net/job/offbuild_jb-viskan/ holdson 自动编译界面http://android-ci-platform.cnbj.sonyericsson.net/job/offbuild_jb-viskan/
10. http://relassist.sonyericsson.net/relassist/ 查看各版本之间提交的gerrityhttp://relassist.sonyericsson.net/relassist/
11. 在manifest中添加<project name="platform/vendor/semc/debugmenu" path="vendor/semc/packages/apps/debugmenu" revision="jb-common"/> 可以下载debugmenu的源代码


二、Debugfs如何使用
1. 主要支持文件@kernel/fs/debugfs/inode.c
    a. struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)@kernel/fs/debugfs/inode.c
       e.g:    struct dentry  *parentdent,*childdent;
         parentdent = debugfs_create_dir("pm8xxx-pwm-dbg", NULL);
        childdent = debugfs_create_dir("0", parentdent);
    b. struct dentry *debugfs_create_file(const char *name, umode_t mode,struct dentry *parent, void *data,const struct file_operations *fops)
       e.g: temp = debugfs_create_file("period", S_IRUGO | S_IWUSR, childdent, puser, &dbg_pwm_period_fops);
        static int dbg_pwm_period_set(void *data, u64 val)
        {
            struct pm8xxx_pwm_user      *puser = data;
            struct pm8xxx_pwm_dbg_device    *dbgdev = puser->dbgdev;
            int     rc;

            mutex_lock(&dbgdev->dbg_mutex);
            rc = dbg_pwm_check_period(val);
            if (!rc)
                puser->period = val;
            mutex_unlock(&dbgdev->dbg_mutex);
            return 0;
        }

        static int dbg_pwm_period_get(void *data, u64 *val)
        {
            struct pm8xxx_pwm_user      *puser = data;
            struct pm8xxx_pwm_dbg_device    *dbgdev = puser->dbgdev;

            mutex_lock(&dbgdev->dbg_mutex);
            *val = puser->period;
            mutex_unlock(&dbgdev->dbg_mutex);
            return 0;
        }

        DEFINE_SIMPLE_ATTRIBUTE(dbg_pwm_period_fops,dbg_pwm_period_get, dbg_pwm_period_set, "%lld\n");
    c. void debugfs_remove_recursive(struct dentry *dentry) or void debugfs_remove(struct dentry *dentry)
       e.g: debugfs_remove_recursive(parentdent);

三、Thermal kernel hwmon分析
1. @kernel/arch/arm/configs/viskan_huashan_defconfig 有定义
    CONFIG_SENSORS_PM8XXX_ADC=y
    CONFIG_SENSORS_EPM_ADC=y
    CONFIG_THERMAL=y
    CONFIG_THERMAL_TSENS8960=y
    CONFIG_THERMAL_PM8XXX=y
    CONFIG_THERMAL_MONITOR=y

2. @kernel/drivers/hwmon/Makefile
    obj-$(CONFIG_SENSORS_PM8XXX_ADC)        += pm8xxx-adc.o pm8xxx-adc-scale.o
    obj-$(CONFIG_SENSORS_EPM_ADC)   += epm_adc.o
3.1 @kernel/drivers/hwmon/pm8xxx-adc.c
    static struct platform_driver pm8xxx_adc_driver = {
        .probe    = pm8xxx_adc_probe,
        .remove    = __devexit_p(pm8xxx_adc_teardown),
        .driver    = {
            .name    = PM8XXX_ADC_DEV_NAME, //"pm8xxx-adc"
            .owner    = THIS_MODULE,
            .pm    = PM8XXX_ADC_DEV_PM_OPS,
        },
    };
    platform_driver_register(&pm8xxx_adc_driver);    //增加"pm8xxx-adc"驱动
3.2 @kernel/drivers/mfd/pm8921-core.c
    static const struct resource adc_cell_resources[] __devinitconst = {
        SINGLE_IRQ_RESOURCE(NULL, PM8921_ADC_EOC_USR_IRQ),
        SINGLE_IRQ_RESOURCE(NULL, PM8921_ADC_BATT_TEMP_WARM_IRQ),
        SINGLE_IRQ_RESOURCE(NULL, PM8921_ADC_BATT_TEMP_COLD_IRQ),
    };
    static struct mfd_cell adc_cell __devinitdata = {
        .name        = PM8XXX_ADC_DEV_NAME, //"pm8xxx-adc"
        .id        = -1,
        .resources    = adc_cell_resources,
        .num_resources    = ARRAY_SIZE(adc_cell_resources),
    };
    if (pdata->adc_pdata) {
        adc_cell.platform_data = pdata->adc_pdata;
        adc_cell.pdata_size = sizeof(struct pm8xxx_adc_platform_data);
        ret = mfd_add_devices(pmic->dev, 0, &adc_cell, 1, NULL,    irq_base); //增加"pm8xxx-adc"设备
    }
4.1 @kernel/arch/arm/mach-msm/board-viskan_huashan-pmic.c
    static struct pm8xxx_adc_amux pm8xxx_adc_channels_data[] = {...};
    static struct pm8xxx_adc_platform_data pm8xxx_adc_pdata = {
        .adc_channel            = pm8xxx_adc_channels_data,
        .adc_num_board_channel  = ARRAY_SIZE(pm8xxx_adc_channels_data),
        .adc_prop               = &pm8xxx_adc_data,
        .adc_mpp_base        = PM8921_MPP_PM_TO_SYS(1),
    };
    static struct pm8921_platform_data pm8921_platform_data __devinitdata = {...
        .adc_pdata        = &pm8xxx_adc_pdata,        
    ...};
    static struct msm_ssbi_platform_data msm8960_ssbi_pm8921_pdata __devinitdata = {
        .controller_type = MSM_SBI_CTRL_PMIC_ARBITER,
        .slave    = {
            .name            = "pm8921-core",
            .platform_data        = &pm8921_platform_data,
        },
    };
    void __init msm8960_init_pmic(void){...
        msm8960_device_ssbi_pmic.dev.platform_data = &msm8960_ssbi_pm8921_pdata;
    }
    @kernel/drivers/platfrom/msm/ssbi.c
    static int __devinit msm_ssbi_probe(struct platform_device *pdev){ //pdev = msm8960_device_ssbi_pmic
        const struct msm_ssbi_platform_data *pdata = pdev->dev.platform_data;    //pdata=&msm8960_ssbi_pm8921_pdata
        ...
        ret = msm_ssbi_add_slave(ssbi, &pdata->slave);//增加“pm8921-core”设备
    }
    @kernel/arch/arm/mach-msm/board-viskan_huashan-pmic.c
    static struct pm8xxx_adc_amux pm8xxx_adc_channels_data[] = { //注册的监控的sensor
        {"vcoin", CHANNEL_VCOIN, CHAN_PATH_SCALING2, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"vbat", CHANNEL_VBAT, CHAN_PATH_SCALING2, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"dcin", CHANNEL_DCIN, CHAN_PATH_SCALING4, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"ichg", CHANNEL_ICHG, CHAN_PATH_SCALING1, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"vph_pwr", CHANNEL_VPH_PWR, CHAN_PATH_SCALING2, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"ibat", CHANNEL_IBAT, CHAN_PATH_SCALING1, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"batt_therm", CHANNEL_BATT_THERM, CHAN_PATH_SCALING1, AMUX_RSV2,
            ADC_DECIMATION_TYPE2, ADC_SCALE_BATT_THERM},
        {"batt_id", CHANNEL_BATT_ID, CHAN_PATH_SCALING1, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"usbin", CHANNEL_USBIN, CHAN_PATH_SCALING3, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"pmic_therm", CHANNEL_DIE_TEMP, CHAN_PATH_SCALING1, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_PMIC_THERM},
        {"625mv", CHANNEL_625MV, CHAN_PATH_SCALING1, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"125v", CHANNEL_125V, CHAN_PATH_SCALING1, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"chg_temp", CHANNEL_CHG_TEMP, CHAN_PATH_SCALING1, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT},
        {"pa_therm1", ADC_MPP_1_AMUX8, CHAN_PATH_SCALING1, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_PA_THERM},
        {"xo_therm", CHANNEL_MUXOFF, CHAN_PATH_SCALING1, AMUX_RSV0,
            ADC_DECIMATION_TYPE2, ADC_SCALE_XOTHERM},
        {"pa_therm0", ADC_MPP_1_AMUX3, CHAN_PATH_SCALING1, AMUX_RSV1,
            ADC_DECIMATION_TYPE2, ADC_SCALE_PA_THERM},
    };
4.2 @kernel/drivers/mfd/pm8921-core.c
    static int __devinit pm8921_probe(struct platform_device *pdev)
    {...
        rc = pm8921_add_subdevices(pdata, pmic);
    }
    static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data *pdata, struct pm8921 *pmic)
    {...
        if (pdata->adc_pdata) {
            adc_cell.platform_data = pdata->adc_pdata;
            adc_cell.pdata_size =
                sizeof(struct pm8xxx_adc_platform_data);
            ret = mfd_add_devices(pmic->dev, 0, &adc_cell, 1, NULL,
                        irq_base);
            if (ret) {
                pr_err("Failed to add regulator subdevices ret=%d\n",
                        ret);
            }
        }....        
    }
    static struct platform_driver pm8921_driver = {
        .probe        = pm8921_probe,
        .remove        = __devexit_p(pm8921_remove),
        .driver        = {
            .name    = "pm8921-core",
            .owner    = THIS_MODULE,
        },
    };
    platform_driver_register(&pm8921_driver); //增加"pm8921-core"驱动
6.1 @kernel/arch/arm/mach-msm/devices-8960.c
    struct platform_device msm8960_device_ssbi_pmic = {
        .name           = "msm_ssbi",<project name="platform/vendor/semc/products" path="device/semc"/>

        .id             = 0,
        .resource       = resources_ssbi_pmic,
        .num_resources  = ARRAY_SIZE(resources_ssbi_pmic),
    };
   @kernel/arch/arm/mach-msm/board-viskan.c
    static struct platform_device *common_devices[] __initdata = {...
        &msm8960_device_ssbi_pmic,    
    }
    static void __init msm8960_cdp_init(void){ ..
        platform_add_devices(common_devices, ARRAY_SIZE(common_devices));//增加"msm_ssbi"设备
    }
6.2 @kernel/drivers/platform/msm/ssbi.c
    static struct platform_driver msm_ssbi_driver = {
        .probe        = msm_ssbi_probe,
        .remove        = __exit_p(msm_ssbi_remove),
        .driver        = {
            .name    = "msm_ssbi",
            .owner    = THIS_MODULE,
        },
    };
    platform_driver_register(&msm_ssbi_driver);//增加"msm_ssbi"驱动
7. 手机中驱动注册路径/sys/devices/platform/msm_ssbi.0/pm8921-core/pm8xxx-adc
   另外只要注册了platform设比,在/sys/bus/platform/devices/pm8xxx-adc 下都有记录
8. vendor/semc/hardware/sysmon/sysmon.h
    #define DEFAULT_PLUGIN_PATH "/system/lib/sysmon/"
    #define DEFAULT_CONFIG_PATH "/system/etc/sysmon.cfg"
    #define DEFAULT_SOCKET_PATH "/tmp/sysmon.skt"
9. device/semc/viskan/sysmon/pm8921_sensor.c

四、Thermal 测试
1. #adb logcat -v time -s system_monitor xo_therm | tee thermal_log.txt

五、vendor sysmon 代码编译分析
1. vendor/semc/hardware/sysmon/Android.mk 有
     ifeq ($(SEMC_CFG_SYSMON),yes)
   所以device/semc/viskan/BoardConfig.mk 增加,现改为 vendor/semc/hardware/device/sysmon_cfg.mk
    SEMC_CFG_SYSMON = yes
    SEMC_CFG_SYSMON_TESTSENSOR = yes
    SEMC_CFG_SYSMON_SENSOR_TSENS_TZ_SENSOR0 = yes
    SEMC_CFG_SYSMON_SENSOR_TSENS_TZ_SENSOR1 = yes
    SEMC_CFG_SYSMON_SENSOR_TSENS_TZ_SENSOR2 = yes
    SEMC_CFG_SYSMON_SENSOR_TSENS_TZ_SENSOR3 = yes
    SEMC_CFG_SYSMON_SENSOR_TSENS_TZ_SENSOR4 = yes
    SEMC_CFG_SYSMON_SENSOR_PM8921_TZ = yes
    SEMC_CFG_SYSMON_SENSOR_PM8921_XO_THERM = yes
    SEMC_CFG_SYSMON_SENSOR_PM8921_BATT_THERM = yes
    SEMC_CFG_SYSMON_SENSOR_PM8921_PA_THERM0 = yes
    SEMC_CFG_SYSMON_SENSOR_PM8921_PA_THERM1 = yes
    SEMC_CFG_SYSMON_MITIGATION_LCD_LEVEL = yes
    SEMC_CFG_SYSMON_MITIGATION_DISABLE_CHARGING1 = yes
    SEMC_CFG_SYSMON_MITIGATION_DISABLE_CHARGING2 = yes
    SEMC_CFG_SYSMON_MITIGATION_ENABLE_CHARGING = yes
    SEMC_CFG_SYSMON_MITIGATION_USB_CURRENT_LEVEL0 = yes
    SEMC_CFG_SYSMON_MITIGATION_USB_CURRENT_LEVEL1 = yes
    SEMC_CFG_SYSMON_MITIGATION_USB_CURRENT_LEVEL2 = yes
    SEMC_CFG_SYSMON_MITIGATION_USB_CURRENT_LEVEL3 = yes
    SEMC_CFG_SYSMON_MITIGATION_USB_CURRENT_LEVEL4 = yes
    SEMC_CFG_SYSMON_MITIGATION_CPU_PERFLEVEL = yes
    SEMC_CFG_SYSMON_MITIGATION_CHARGE_CURRENT_LEVEL0 = yes
    SEMC_CFG_SYSMON_MITIGATION_CHARGE_CURRENT_LEVEL1 = yes
    SEMC_CFG_SYSMON_MITIGATION_CHARGE_CURRENT_LEVEL2 = yes
    SEMC_CFG_SYSMON_MITIGATION_CHARGE_CURRENT_LEVEL3 = yes
    SEMC_CFG_SYSMON_MITIGATION_MSM_THERMAL_DISABLE = yes
    SEMC_CFG_SYSMON_MITIGATION_MODEM = yes
    SEMC_CFG_SYSMON_MITIGATION_CPU_CORELIMIT = yes

2. vendor/semc/hardware/sysmon/Android.mk 有
    LOCAL_MODULE:= system_monitor
    LOCAL_PRELINK_MODULE := false
    include $(BUILD_EXECUTABLE)
   生成system_monitor的执行文件
3. device/semc/viskan/viskan.mk 添加
    # System_monitor
    PRODUCT_PACKAGES += \
        system_monitor \
        sysmon_test_sensor \
        sysmon_tsens_tz_sensor0 \
        sysmon_tsens_tz_sensor1 \
        sysmon_tsens_tz_sensor2 \
        sysmon_tsens_tz_sensor3 \
        sysmon_tsens_tz_sensor4 \
        sysmon_pm8921_tz \
        sysmon_xo_therm \
        sysmon_batt_therm \
        sysmon_pa_therm0 \
        sysmon_pa_therm1 \
        sysmon_lcd_brightness_level \
        sysmon_charge_current_limit_level0 \
        sysmon_charge_current_limit_level1 \
        sysmon_charge_current_limit_level2 \
        sysmon_charge_current_limit_level3 \
        sysmon_enable_charging \
        sysmon_disable_charging1 \
        sysmon_disable_charging2 \
        sysmon_usb_current_limit_level0 \
        sysmon_usb_current_limit_level1 \
        sysmon_usb_current_limit_level2 \
        sysmon_usb_current_limit_level3 \
        sysmon_usb_current_limit_level4 \
        sysmon_perflevel \
        sysmon_corelimit \
        sysmon_msm_thermal_disable \
        sysmon_modem_level0 \
        sysmon_modem_level1 \
        sysmon_modem_level2 \
        sysmon_modem_level3

    # System monitor config file
    PRODUCT_COPY_FILES += device/semc/$(TARGET_PRODUCT)/files/sysmon.cfg:system/etc/sysmon.cfg

4. device/semc/viskan/files/init.viskan.rc中添加
    # use system_monitor
    on init
        symlink /dev/socket /tmp

    service system_monitor /system/bin/system_monitor
        class core
        user root

    ONLY_IN_DEBUG(
        service battery_logging /system/bin/battery_logging
        disabled
        on property:dev.bootcomplete=1
        start battery_logging)
5. sysmon.cfg@/device/semc/huashan/files, 目前与Fussion_Platform/ics-odin/device/semc/odin/files/sysmon.cfg 一样
6. ThermalDaemon @vendor/qcom/proprietary/thermal/thermal.c 不需要用到qcom的thermal机制
   去掉@device/semc/viskan/init.target.rc中的
     #service thermald /system/bin/thermald
     #   class main
     #   user root
     #   group root
7. 增加device/semc/viskan/sysmon,和Fussion_Platform/ics-odin/device/semc/fusion3/sysmon一样
8. 增加Blue平台的  vendor/semc/system/idd 到huashan 的 vendor/semc/system/ ,所有的plugin 都需要idd,这样 sysmon 添加到device/semc/viskan/下也能编译出来了,但编译出来的*.so 没有把sysmon放到device/semc/huashan 下的多(out/target/huashan/system/lib/sysmon)
9. 在device/semc/common/semc-generic.mk
     ## Sysmon  ##
    PRODUCT_PACKAGES += \
            libsysmon \
            SysmonService \
            com.sonyericsson.sysmon \
            com.sonyericsson.sysmon.xml \
            libsysmon_jni
10. 删除 device/semc/viskan/Android.mk ,因为该文件是空的,会阻止其目录下的 子目录编译。
11. vendor/semc/hardware/sysmon/libsysmon和libsysmon 没有编译? 用mm 编译可以
12. vendor/semc/hardware/sysmon/frameworks/test中SysmonTests 没有编译成功
13. 把blue platform 的vendor/semc/system/idd 拷贝到对应目录

六、分析android.mk技巧
1. 输出信息
    a. $(info "abc")   , $(error "error")
    b. $(info $(SEMC_CFG_SYSMON) )

七、提交代码
1. 因为 .repo/manfest.xml 中有 <project name="platform/vendor/semc/products" path="device/semc"/> ,所以直接在device/semc目录下提交代码即可,不需要修改manifest.xml
    a. device/semc$ git add viskan/sysmon/
    b. device/semc$ git commit
    c. 添加      Thermal: add device/semc/viskan/sysmon
            Integrate the thermal function
            Category: development
    d. repo upload . //失败
    e. 直接用git push ssh://gang.xu@review.sonyericsson.net:29418/platform/vendor/semc/products HEAD:refs/for/feature-ics-viskan-es2-bringup
    f. 在http://review.sonyericsson.net/#/c/306828/中确认代码提交
2. 提交完后,新建一个branch验证下
3. 如果有冲突
    a. 执行git rebase origin/feature-ics-viskan-es2-bringup
     有提示CONFLICT (content): Merge conflict in viskan/files/init.viskan.rc ,表示该文件冲突,需要修改, 这时候branch会切换到 *(no branch)
    b. 修改完冲突
    c. git add viskan/files/init.viskan.rc
    d. git rebase --continue
    e. 再提交代码 git push ssh://gang.xu@review.sonyericsson.net:29418/platform/vendor/semc/products HEAD:refs/for/feature-ics-viskan-es2-bringup
    f. 再回到http://review.sonyericsson.net/#/c/306828/中 comments 中有新的patch提交提示, 再次确认代码提交, 其中 Status 为Mergerd 才表示成功


2. git rebase origin/feature-ics-viskan-es2-bringup

八. 分析sysmon_monitor excutable service
1. main@vendor/semc/hardware/sysmon/sysmon.c
1.1 @device/semc/viskan/files/init.viskan.rc, 会启动 system_monitor
                # use system_monitor
                service system_monitor /system/bin/system_monitor
                    class core
                    user root
1.2 分析parse_options
    a. #system_monitor -h //列出帮助信息
    b. #system_monitor -l List all available sensors,也就是类型为 SENSOR 的plugin
    c. #system_monitor -a List all avalable actions,也就是类型为 MITIGATION 的plugin

1.3. 分析load_plugins@vendor/semc/hardware/sysmon/plugins.c
    a. 获取手机中system/lib/sysmon 下的所有*.so动态库的路径,具体可查看@@bionic/linker/dlfcn.c
    b. handle = dlopen(buf, RTLD_LAZY); //该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的
    c. dlerror() //通过调用dlerror()函数,我们可以获得最后一次调用dlopen(),dlsym(),或者dlclose()的错误信息。
    d. func = (struct sysmon_plugin *(*)(void))dlsym(handle,ENTRY_FUNC_NAME);//ENTRY_FUNC_NAME="tp_init",也就是每个装载的so中必须包含该函数,并且该函数的返回值为结构struct sysmon_plugin指针,该struct包含了很多信息@vendor/semc/hardware/sysmon/include/sysmon_plugin.h
    e. p = func();//运行tp_init函数,返回struct sysmon_plugin 指针
    f. 分析add_plugin(struct sysmon_plugin *plugin, void *handle)@vendor/semc/hardware/sysmon/plugins.c
       static struct list plugin_list[NUM_PLUGIN_TYPES];定义了两种plug_list sensor和mitigation, 不同的plugin分别插入list
1.5 分析parse_config_file@vendor/semc/hardware/sysmon/config.c, 该函数会分析手机中 /system/etc/sysmon.cfg, 比如有如下内容
        pm8921_tz 0 -1000 1000 60 NONE
        pm8921_tz CRITICAL 1 NONE
        bat_ctrl 2 18 43 5 NOTIFY lcd_brightness_reset
        bat_ctrl 1 40 53 5 NOTIFY lcd_brightnesslevel3 WIFIOFF_CLR
    a. sensor_register_sensor(word)@vendor/semc/hardware/sysmon/sensor.c //只有plugin已经装入的sensor/mitigation才能register,保存到static struct sensor *configured_sensors;
    b. sensor_register_level(s, word); //会填充struct sensor 中的struct alarm_level *levels[NUM_LEVELS];NUM_LEVELS表示最多可有33个level可以设置给一个sensor,比如上面的sysmon.cfg例子,表示需要为pm8921_tz设置两个alarm_level
        pm8921_tz 0 -1000 1000 60 NONE 依次表示sensor名字,alarm lever级别[level=1],最小警报值[min_alarm],最大警报值[max_alarm],循环次数[poll_time],不做任何action[action_list]
        pm8921_tz CRITICAL 1 NONE 依次表示sensor名字,alarm lever级别[level=0]表明是critical level without min/max threshold, 不做任何action[action_list]
    c. sensor_add_action_entry(l, word); //注册一旦超过警报值,需要采取的动作, 生成一个struct action_entry 添加到struct alarm_level中的struct action_entry *action_list;
        标准格式为<action_name>:<variable value> //但目前没有这么用
        特殊格式有 NONE/NOTIFY/SHUTDOWN --> no action/notify=1/shutdown=1 在struct alarm_level中
1.6 分析socket_open(td)@vendor/semc/hardware/sysmon/socket.c
    a. 初始化data参数,设置socket file path[/tmp/sysmon.skt], 和最大socket 连接数[5]
    b. 创建socket_server线程 pthread_create(&data->socket_server_pid, NULL,socket_server_thread, NULL)
    c. 启动socket_server_thread线程
        -->data->socket_server_fd = socket(AF_UNIX, SOCK_STREAM, 0); //SOCK_STREAM有数据传输保障的socket
        -->bind(data->socket_server_fd, (struct sockaddr *) &srv, len) //设置socket file path
        -->listen(data->socket_server_fd, data->max_clients) //设置client 同时最大连接数
        -->开始处理accept client的连接请求,如果有client连接则添加到socket_data->socket_clients链表中,并且创建一个client rev theread, 用pthread_create(&sclient->pid, NULL, socket_client_thread,(void *) sclient)
    d. 启动socket_client_thread线程
        -->首先获取thermal_socket_msg_t msgheader;消息头
        -->再接收socket 数据,获取完整消息
        -->最后用socket_handle_message 处理从client获取的消息
    e. 启动socket_handle_message开始处理client端发过来的请求,以下消息ID定义@vendor/semc/hardware/sysmon/include/socketmsgs.h
        GET_ACTIONS_REQ, /*< Requests all actions in platform */
        -->调用handle_get_actions_req ,得到plugins中类型为MITIGATION的所有plugin的名称 字符串
        GET_SENSORS_REQ, /*< Requests all sensors in platform */
        -->调用handle_get_sensors_req,
        -->sensor_get_all_configured_sensors@vendor/semc/hardware/sysmon/sensors.c 得到所有注册的sensor的名称字符串和数量,也就是configured_sensors的数据
        GET_SENSOR_CONFIG_REQ, /*< Request a specific sensor configuration */
        -->调用handle_get_sensor_config_req, 根据client传给server的sensor name,在configured_sensors找到对应的struct sensor,然后把该sensor的configs及所有的alarm_level一块打包
       问题: thermal_socket_msg_t,thermal_get_actions_resp_msg_t,thermal_get_sensors_resp_msg_t在那定义的呢??????
    f. socket_send_msg(fd, (thermal_socket_msg_t *) msg); //把得到的数据通过socket 发送给client

1.7 分析sensor_enable_all_sensors(alarm_callback, td);
    a. 仅仅开始监听那些注册到configured_sensors的sensor
    b. p = find_plugin_by_name(s->sensor_name);//通过注册的sensor名字找到对应的plugin;
       if (p->interface.sensor.listen_for_event)//如果找到plugin,并且需要listen_for_event 则为该plugin注册上alarm_callback回掉函数
        p->interface.sensor.listen_for_event(cb, data);
    c. 比如@device/semc/viskan/sysmon/pm8921_sensor.c
        static struct sysmon_plugin plg = {
            .name = SENSOR_NAME,
            .type = SENSOR,
            ....
            .interface.sensor = {...
                .listen_for_event = listen_for_event,
            },
        };
       该plugin有listen_for_event函数,所以会注册alarm_callback回掉函数
       listen_for_event@device/semc/viskan/sysmon/pm8921_sensor.c,实际上创建一个thread,运行sensor_work函数
       static void *sensor_work(void *data)    @device/semc/viskan/sysmon/pm8921_sensor.c
       {...
        fd = open(d->input_path, O_RDONLY);//打开"/sys/bus/platform/devices/pm8xxx-adc"文件
        while (1) {
            if (read(fd, read_buf, sizeof(read_buf)) != -1) { //读取对应kernel 文件接口中的值
            ...
            if (val > max_val) { //如果值大于监控的最大值,则调用回调函数plg_data->cb,最后会调用到alarm_callback@vendor/semc/hardware/sysmon
                d->cb(MAX_ALARM, d->sensor_name, d->call_data);
            } else if (val < min_val) {
                d->cb(MIN_ALARM, d->sensor_name, d->call_data);
            }
        }    
       }
    d. cb(STRT_ALARM, s->sensor_name, data); //cb=alarm_callback, STRT_ALARM/* Sent when a sensor has been started */
1.8 分析socket_wait()
    a. Blocking call that waits for the server thread to shutdown
    b. system_monitor 将hold在这儿

2. @vendor/semc/hardware/sysmon/builtin.c
    a. add_plugin(&notify, NULL); 增加一个plugin,名称为NOTIFY_STRING
        static struct sysmon_plugin notify = {
            .name = NOTIFY_STRING,
            .type = MITIGATION,

            .interface.mitigation.execute_action = execute_notify}
        };
    b. static void built_in_init(void) __attribute__ ((constructor));立即执行
            add_plugin(&notify, NULL); //增加该 notify plugin到类型为mitigation的plugin list中


3. 如何把所有的sensors 得到的数据及时传给对应的上层应用
    a. 前面已经说明 application 通过socket与 system_monitor service 建立通讯联系
    b. alarm_callback@sysmon.c,中有
        if (l->notify) { //如果该alarm_level需要notify给上层,则选择执行如下步骤
            pi = find_plugin_by_name(NOTIFY_STRING); //找到注册的 notify plugin ,实际上就是struct sysmon_plugin notify@builtin.c
            if (pi && pi->interface.mitigation.execute_action)
                pi->interface.mitigation.execute_action(s);
                //-->execute_notify@builtin.c-->socket_notify@socket.c-->send_event@socket.c-->socket_send_msg@socket.c

        }
        /* Perform all supported actions registered on this level */
        a = l->action_list;

        while (a) {
            LOGV("%s - looking for action %s", __func__, a->action);
            pi = find_plugin_by_name(a->action);
            if (pi)
                LOGV("%s - found plugin name: %s", __func__, pi->name);
            if (pi && strcmp(pi->name, NOTIFY_STRING))
                if (pi->interface.mitigation.execute_action)
                    pi->interface.mitigation.execute_action(s);

            a = a->next;
        }
    c. 到此为止,如果注册的struct sensor中添加的struct alarm_level包含notify=1,则需要通知给所有连接的client socket;另外其struct alarm_level中的所有actionlist找到的plugin名字也是NOTIFY_STRING "NOTIFY",也需要执行对应的execute_action

4. sysmon_init@vendor/semc/hardware/sysmon/sysmon.c
    a. static void sysmon_init(void) __attribute__ ((constructor)); //表示system_init为构造函数(C++中常用到),需要在main函数之前运行
       static void sysmon_cleanup(void) __attribute__ ((destructor));//表示system_clearup为析造函数(C++中常用到),需要在main函数退出时运行
    b. sysmon_init该函数主要初始化了static struct sysmon *td; 结构
        td->plugin_path = strdup(DEFAULT_PLUGIN_PATH); //plugin插件路径"/system/lib/sysmon/"
        td->config_file = strdup(DEFAULT_CONFIG_PATH); //plugin插件参数配置文件路径"/system/etc/sysmon.cfg"
        td->socket_path = strdup(DEFAULT_SOCKET_PATH); //plugin插件server socket的建立路径"/tmp/sysmon.skt"
        td->num_socket_clients = DEFAULT_SOCKET_MAX_CLNT; //plugin插件server socket的最大连接数 = 5    
    
5. 如何生成plugin, 即位于/system/lib/sysmon/*.so 的文件
5.1. 主要文件位于device/semc/viskan/sysmon.
    a. pm8921_sensor.c, tz_sensor.c 注册为type=SENSOR的plugin
    b. modem.c,msm_corelimit.c,msm_thermal_disable.c,pm8921_battery_charging.c,pm8921_charge_current_limit.c,pm8921_usb_current_limit.c 注册为type=MITIGATION
5.2. 分析pm8921_sensor.c中如何注册各种sensor
    a. 实际上是多个senor共用一个文件pm8921_sensor.c完成多个sensor plugin的编译
    b. 查看viskan/sysmon/Android.mk可以看到如下内容
        # xo_therm
        ifeq ($(SEMC_CFG_SYSMON_SENSOR_PM8921_XO_THERM),yes)
        include $(CLEAR_VARS)
        LOCAL_CFLAGS := -DTZ_USE_HW_UNIT
        LOCAL_CFLAGS += -DPM8921_SENSOR_NAME=\"xo_therm\"
        LOCAL_SRC_FILES := pm8921_sensor.c
        LOCAL_MODULE := sysmon_xo_therm
        include $(LOCAL_PATH)/main.mk
        endif
        
        # batt_therm
        ifeq ($(SEMC_CFG_SYSMON_SENSOR_PM8921_BATT_THERM),yes)
        include $(CLEAR_VARS)
        LOCAL_CFLAGS := -DPM8921_SENSOR_NAME=\"batt_therm\"
        LOCAL_SRC_FILES := pm8921_sensor.c
        LOCAL_MODULE := sysmon_batt_therm
        include $(LOCAL_PATH)/main.mk
        endif
        
        ....
       最终通过设置PM8921_SENSOR_NAME不同,LOCAL_SRC_FILES一样,LOCAL_MODULE不同,生成不同的*.so
    c. pm8921_sensor.c中有如下定义
        #define SENSOR_NAME PM8921_SENSOR_NAME //不同的therm 其PM8921_SENSOR_NAME在viskan/sysmon/Android.mk定义不一样,如上
        #define PM8921_SENSOR_PATH "/sys/bus/platform/devices/pm8xxx-adc"
        static struct sysmon_plugin plg = {
            .name = SENSOR_NAME,
            .type = SENSOR,
            ....
        }
        static int start_plugin(void)
        {...
            rc = find_pm8921_sensor_path(plg.name); //寻找目录"/sys/bus/platform/devices/pm8xxx-adc"下是否存在对应的plugin 名字的文件节点
            ...            
            snprintf(buf, sizeof(buf) - 1, PM8921_SENSOR_PATH SENSOR_NAME_PARAM, plg.name);    
        }
6. CPU支持的频率设置
    @/jb-viskan/kernel/arch/arm/mach-msm/acpuclock-8960ab.c
    
九、huashan LCD 分析
1. 在arch/arm/configs/viskan_huashan_defconfig中有
    CONFIG_FB_MSM_MIPI_R63306_PANEL_SHARP_LS043K3SX04=y
    CONFIG_FB_MSM_MIPI_R63306_PANEL_SHARP_LS043K3SX01=y
    CONFIG_FB_MSM_MIPI_R63306_PANEL_SHARP_LS046K3SY01=y
   在drivers/video/msm/Kconfig中有
    config FB_MSM_MIPI_R63306_PANEL_SHARP_LS046K3SY01
        bool "SHARP LS046K3SY01 Panel"
        select FB_MSM_MIPI_DSI_RENESAS_R63306
    config FB_MSM_MIPI_R63306_PANEL_SHARP_LS043K3SX04
        bool "SHARP LS043K3SX04 Panel"
        select FB_MSM_MIPI_DSI_RENESAS_R63306
    config FB_MSM_MIPI_R63306_PANEL_SHARP_LS043K3SX01
        bool "SHARP LS043K3SX01 Panel"
        select FB_MSM_MIPI_DSI_RENESAS_R63306
   在drivers/video/msm/Makefile中有
    obj-$(CONFIG_FB_MSM_MIPI_DSI_RENESAS_R63306) += mipi_r63306_panels/
   所以LCD的驱动文件在mipi_r63306_panels/目录下
   在drivers/video/msm/mipi_r63306_panels/Makefile中有
      obj-$(CONFIG_FB_MSM_MIPI_R63306_PANEL_SHARP_LS043K3SX04) += mipi_sharp_ls043k3sx04.o
      obj-$(CONFIG_FB_MSM_MIPI_R63306_PANEL_SHARP_LS043K3SX01) += mipi_sharp_ls043k3sx01.o
      obj-$(CONFIG_FB_MSM_MIPI_R63306_PANEL_SHARP_LS046K3SY01) += mipi_sharp_ls046k3sy01.o
    
2. 在kernel/arch/arm/configs/viskan_huashan_defconfig中有
    CONFIG_ARCH_MSM=y
    CONFIG_MACH_VISKAN_HUASHAN=y
   在kernel/arch/arm/mach-msm/Makefile中有
    board-sony_viskan-all-objs += board-sony_viskan.o board-sony_viskan-display.o board-sony_viskan-storage.o
    obj-$(CONFIG_MACH_VISKAN_HUASHAN) += board-sony_viskan-all.o board-sony_viskan_huashan-gpiomux.o board-sony_viskan_huashan-pmi    c.o board-sony_viskan_huashan-regulator.o
    obj-$(CONFIG_MACH_VISKAN_HUASHAN) += board-sony_viskan_huashan-camera.o bms-batterydata-sleipner.o
    2.1 LCD Power on and Reset
    lcd_power@kernel/arch/arm/mach-msm/board-sony_viskan-display.c
    lcd_reset@kernel/arch/arm/mach-msm/board-sony_viskan-display.c
    2.2 How to initialze FB
    msm8960_cdp_init@kernel/arch/arm/mach-msm/board-sony_viskan.c
    -->msm8960_init_fb@kernel/arch/arm/mach-msm/board-sony_viskan-display.c
    --->mipi_dsi_panel_add_device@kernel/arch/arm/mach-msm/board-sony_viskan-display.c  //增加名字为#define MIPI_DSI_PANEL_NAME "mipi_dsi_panel"的platform device
    mipi_dsi_panel_init@kernel/drivers/video/msm/mipi_dsi_panel_driver.c //增加名字为MIPI_DSI_PANEL_NAME的platform driver
    注册MIPI_DSI_PANEL_NAME的driver或device后会运行 mipi_dsi_panel_probe->msm_fb_add_device
    -->platform_device_register(&msm_fb_device);//注册名字为"msm_fb"的设备
    -->msm_fb_register_device("mdp", &mdp_pdata);
        --->msm_register_device(&msm_mdp_device, data);//注册名字为 "mdp" device
        @kernel/drivers/video/msm/mdp.c
        mdp_register_driver(void)->platform_driver_register(&mdp_driver);//注册名字为"mdp"的driver
    -->msm_fb_register_device("mipi_dsi", &mipi_dsi_pdata); //其中mipi_dsi_pdata.dsi_power_save = r63306_lcd_power ,这儿reset和poweron LCD
        --->msm_register_device(&msm_mipi_dsi1_device, data); //注册名字为 "mipi_dsi" 的device
        @kernel/drivers/video/msm/mipi_dsi.c中 platform_driver_register(&mipi_dsi_driver); //注册名字为“mipi_dsi”的driver
        -->mipi_dsi_probe中会设置msm_fb_panel_data->on=mipi_dsi_on, 而mipi_dsi_on会调用mipi_dsi_pdata->dsi_power_save(1);
    -->msm_fb_register_device("dtv", &dtv_pdata);
        --->msm_register_device(&msm_dtv_device, data); //注册名字为"dtv"的设备
    2.3 Panels 的定义static const struct panel *panel_ids_r63306和default_panel_ids_r63306 @kernel/arch/arm/mach-msm/board-sony_viskan-display.c
    比如单个的panel定义,const struct panel sharp_ls043k3sx01_panel_id@kernel/drivers/video/msm/mipi_r63306_panels/mipi_sharp_ls043k3sx01.c
    在struct panel中包含struct dsi_controller,比如
    static struct dsi_controller dsi_video_controller_panel = {
        .get_panel_info = get_panel_info,
        .display_init = display_init_cmds,
        .display_on = display_on_cmds,
        .display_off = display_off_cmds,
        .read_id = read_ddb_cmds,
    };
     2.4 继续分析msm8960_init_fb@kernel/arch/arm/mach-msm/board-sony_viskan-display.c
    

十、sysmon测试支持
1. 如果需要LOGV_IF(sysmon_debug,"%s - looking for action %s", __func__,a->action); 输出log,通过分析parse_config_file@vendor/semc/hardware/sysmon/config.c, 发现需要在sysmon.cfg配置文件中第一行添加 DEBUG,然后才是其他的配置
2. 通过 adb shell "cat /sys/bus/platform/devices/pm8xxx-adc/batt_therm" 或者
    #adb shell "while true; do cat /sys/devices/virtual/thermal/thermal_zone5/temp; sleep 1; done;"
   读取的温度,输出的log格式为Result:%lld Raw:%d, 其中result才是表示的实际温度,针对不同的thermal,具体如下
     * -Battery temperature units are represented as 0.1 DegC
     * -PA Therm temperature units are represented as DegC
     * -PMIC Die temperature units are represented as 0.001 DegC
   raw表示的是The pre-calibrated digital output of a given ADC relative to the ADC reference

十一、如何申请branch out

十二、如何输出LOGV 和pr_debug
  1. 在Android.mk文件中修改如下,即可
  # Comment to enable debug
  LOCAL_CFLAGS += -DNDEBUG -DLOG_NDEBUG -DLOG_NDDEBUG -DLOG_NIDEBUG=0
  # Uncomment to enable debug
  #LOCAL_CFLAGS += -DDEBUG -UNDEBUG -ULOG_NDEBUG

  2.如何输出pr_debug, 只需要在文件中定义#define DEBUG 即可

十三、Huashan tsens 的kernel 驱动路径在SP上建立不成功
1. SP 的硬件类型是 8960pro, DP的硬件类型是8960
2. @kernel/arch/mach-msm/board-sony_viskan.c有定义static struct tsens_platform_data msm_tsens_pdata static struct tsens_platform_data msm_tsens_pdata
3. 修改 kernel/drivers/thermal/msm8960_tsens.c中的函数tsens_check_version_support, 不做相关的check即可

十四、如何编译vendor/semc/tools/sensorlogger
1. $semc-build
2. <project name="platform/vendor/semc/tools/sensorlogger" path="vendor/semc/tools/sensorlogger" revision="master"/>


十五、如何branch out
1. http://android-cm-web.sonyericsson.net/branch/index.php 选择 Request a Branch 参照其他填写相关信息

*****************************************************************************************************************
十六、PMIC Core 分析
1. 设备注册
   @kernel/arch/arm/mach-msm/board-sony_viskan_huashan-pmic.c 中关键数据
    static struct msm_ssbi_platform_data msm8960_ssbi_pm8921_pdata __devinitdata
2. 驱动注册
 

十七、PMIC ADC 分析
1. @kernel/include/linux/mfd/pm8xxx/pm8xxx-adc.h 关键定义
    enum pm8xxx_adc_channels
    struct pm8xxx_adc_chan_result
2. adc 设备注册
   @kernel/arch/arm/mach-msm/board-sony_viskan_huashan-pmic.c 关键数据
    static struct pm8xxx_adc_amux pm8xxx_adc_channels_data
   adc device: static struct pm8xxx_adc_platform_data pm8xxx_adc_pdata
   @kernel/drivers/mfd/pm8921-core.c中有pm8921_add_subdevices函数会添加adc设备mfd_add_devices(pmic->dev, 0, &adc_cell, 1, NULL,irq_base);    
3. adc 驱动注册    
   @kernel/drivers/hwmon/pm8xxx-adc.c中函数pm8xxx_adc_init_hwmon会初始化驱动文件注册路径:
   pm8xxx_adc_read 主要函数,读取各个adc的转化完后保存在寄存器的值
4. Debugfs for pm8921 adc
   mount -t debugfs none /sys/kernel/debug
   cd sys/kernel/debug/pm8xxx_adc

十八、PMIC charger 分析
1. charger 设备注册
   @kernel/arch/arm/mach-msm/board-sony_viskan_huashan-pmic.c 关键数据
    static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata
   @kernel/drivers/mfd/pm8921-core.c中有pm8921_add_subdevices函数会添加charger设备mfd_add_devices(pmic->dev, 0, &charger_cell, 1, NULL,    irq_base);
2. charger 驱动注册
   @kernel/drivers/power/pm8921-charger.c中函数pm8921_charger_init 会初始化驱动文件注册路径:    
3. ADB 调试路径
   a. /sys/module/pm8921_charger/parameters
   b. /sys/class/power_supply/battery
   c. /sys/bus/platform/devices/pm8921-charger
   d. Disable charging: echo 2 > /sys/module/pm8921_charger/parameters/disabled
      Enable charging: echo 0 > /sys/module/pm8921_charger/parameters/disabled
4. 动态输出log
   a. /sys/kernel/debug/dynamic_debug/control
   b. echo "file pm8921-charger.c +p" > /sys/kernel/debug/dynamic_debug/control

十九、Battery voltage alarm
1. 最小门限电压如何定义和设置
   @kernel/drivers/power/pm8921-charger.c
    struct pm8921_chg_chip.alarm_low_mv //the battery alarm voltage low
   @kernel/arch/arm/mach-msm/board-sony_viskan_huashan-pmic.c
    static struct pm8921_charger_platform_data pm8921_chg_pdata.alarm_low_mv = 3400,  //低电触发
                                .alarm_high_mv = 4000,    //高电触发
2. 如何设置trigger the alarm of battery low voltage
   @kernel/drivers/power/pm8921-charger.c
    pm8921_charger_probe-->pm8921_charger_configure_batt_alarm
    -->pm8xxx_batt_alarm_threshold_set(PM8XXX_BATT_ALARM_LOWER_COMPARATOR,chip->alarm_low_mv); //往寄存器里面写入最低电压门限
       pm8xxx_batt_alarm_register_notifier(&alarm_notifier);@kernel/drivers/mfd/pm8xxx-batt-alarm.c  //设置触发回调函数
           需要说明的是该函数只是往 battery alarm 中断触发函数链表中添加一个响应函数
3. Battery alarm中断如何设置和触发  
   a. 设备注册 @kernel/drivers/mfd/pm8921-core.c
    static struct mfd_cell batt_alarm_cell = {
        .resources    = batt_alarm_cell_resources, //PM8921_BATT_ALARM_IRQ  是其中断资源号PM8921_IRQ_BLOCK_BIT(5, 6)
    }
   b. 驱动注册 @kernel/drivers/mfd/pm8xxx-batt-alarm.c
    pm8xxx_batt_alarm_probe-->request_irq(chip->irq, pm8xxx_batt_alarm_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, cdata->irq_name,chip); //中断注册函数为 pm8xxx_batt_alarm_isr
   c. 如何触发 低电处理函数
    当电池电压低于alarm_low_mv 或 高于alarm_high_mv 时硬件中断产生 --> pm8xxx_batt_alarm_isr@pm8xxx-batt-alarm.c --> pm8xxx_batt_alarm_isr_work@pm8xxx-batt-alarm.c
    -->pm8921_battery_gauge_alarm_notify@pm8921-charger.c
4. 如何把低电信息传递给user space
   set_heartbeat_update_interval@kernel/drivers/mfd/pm8921-core.c //设置多少时间间隔触发update_heartbeat    
   实际上电池的状态没间隔心跳时间获取一次,然后传递给userspace
   update_heartbeat 将得到电池容量百分比get_prop_batt_capacity,
   然后power_supply_changed-->power_supply_changed_work-->kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);发送给user space.    

二十、电池容量的计算
1. @kernel/arch/arm/mach-msm/board-sony_viskan_huashan-pmic.c
    static struct pm8921_charger_platform_data pm8921_chg_pdata.max_voltage    = MAX_VOLTAGE_MV,(4200) //the max voltage (mV) the battery should be charged up to
                                    .min_voltage    = 3200, //the voltage (mV) where charging method switches from trickle to fast. This is also the minimum voltage    
2. 调试路径 可以设置最小电池电压值
    /sys/devices/platform/msm_ssbi.0/pm8921-core/pm8921-charger/min_voltage_mv
     或者/sys/bus/platform/drivers/pm8921-charger/pm8921-charger/min_voltage_mv    
3. update_heartbeat函数会定时调用
    a. update_temp_state --> get_prop_batt_temp
    b .get_prop_batt_capacity --> voltage_based_capacity -->get_prop_battery_uvolts
    c. check_soc_and_resume --> update_charge_state
    d. power_supply_changed(&chip->batt_psy);-->power_supply_changed_work-->kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);//发送uevent到用户空间

二十一、如何触发低电关机
1. get_prop_batt_capacity@kernel/drivers/power/pm8921-charger.c
   如果get_prop_battery_uvolts得到的电压值连续小于min_voltage_mv 次RETRY_NUM_FOR_FORCE_SHUTDOWN=5,就会触发
      update_charge_state(BIT(DIS_BIT_CHG_SHUTDOWN),DIS_BIT_CHG_SHUT_MASK);该函数会disable 充电
2. 然后把电池容量为0的信息传递给userspace
    update_heartbeat@pm8921-charger.c --> power_supply_changed@power_supply_core.c
    -->power_supply_changed_work 另外说明下power_supply_update_leds 会根据电量改变led灯的显示颜色
    -->psy->changed //如果property有任何改变则向user space 发送uevent.
    -->kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); //发送电池电量已经改变的信息给userspace
3. /sys/class/power_supply/battery如何创建的
    a. pm8921_charger_probe@pm8921-charger.c
        chip->batt_psy.name = "battery",
        chip->batt_psy.get_property = pm_batt_power_get_property,
        rc = power_supply_register(chip->dev, &chip->usb_psy);
    b. power_supply_register@power_supply_core.c
        dev->class = power_supply_class; //class.name="power_suppl"
        dev->type = &power_supply_dev_type; //最终会关联到static struct device_attribute power_supply_attrs@power_supply_sysfs.c
            //并且static struct device_attribute power_supply_attrs必须和enum power_supply_property 里面的变量定义一致
        rc = kobject_set_name(&dev->kobj, "%s", psy->name); //psy->name="battery"
        rc = device_add(dev); //生成/sys/class/power_supply/battery/*.*
    c. power_supply_class_init@power_supply_core.c
        power_supply_class = class_create(THIS_MODULE, "power_supply");
        power_supply_class->dev_uevent = power_supply_uevent;
        power_supply_init_attrs(&power_supply_dev_type);
        {        
            dev_type->groups = power_supply_attr_groups;//会关联到__power_supply_attrs和power_supply_attr_is_visible
            //通过power_supply_attr_is_visible函数过滤只有msm_batt_power_props@pm8921-charger.c中的prop才会生成文件节点, 也就是/sys/class/power_supply/battery/下的文件
        }
    d. static enum power_supply_property msm_batt_power_props@pm8921-charger.c
4. User space 收到charging 的uevent事件后,会主动读取/sys/class/power_supply/battery/*.* 某些文件以获取充电相关的信息        
    -->power_supply_uevent@kernel/drivers/power/power_supply_sysfs.c
 
二十二、充电各种中断处理
1. struct pm_chg_irq_init_data chg_irq_data @kernel/drivers/power/pm8921-charger.c

二十三、充电器类型如何识别(AC charger, USB charger)
###用户空间的分析如下:
1. 检查 /sys/class/power_supply/pm8921-dc/online 和 /sys/class/power_supply/usb/online  的值,可以知道是何充电器类型
    @frameworks/base/services/jni/com_android_server_batteryservice.cpp中有
    int register_android_server_BatteryService(JNIEnv* env)    
    {
                if (strcmp(buf, "Mains") == 0) { ///sys/class/power_supply/pm8921-dc/type == Mains
                    snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
                    if (access(path, R_OK) == 0)
                        gPaths.acOnlinePath = strdup(path);
                }
     }
   如果/sys/class/power_supply/pm8921-dc/online==1, 则插入的充电器是wall charger
      /sys/class/power_supply/usb/online==1, 则插入的充电器是usb charger
   调试路径:/sys/kernel/debug/msm_otg
2. 分析frameworks/base/services/jni/com_android_server_batteryservice.cpp
    a.函数register_android_server_BatteryService 将为被frameworks/base/services/jni/onload.cpp中的JNI_OnLoad调用,用于关联java和C++之间的变量
    b.jclass clazz = env->FindClass("com/android/server/BatteryService"); //根据java的路径找到batteryservice.java对应的jclass    
      gFieldIds.mAcOnline = env->GetFieldID(clazz, "mAcOnline", "Z");
      gFieldIds.mUsbOnline = env->GetFieldID(clazz, "mUsbOnline", "Z"); //用于关联batteryservice.java中变量private boolean mAcOnline和gFieldIds.mAcOnline
    c. android_server_BatteryService_update
        static JNINativeMethod sMethods[] = {
             /* name, signature, funcPtr */
            {"native_update", "()V", (void*)android_server_BatteryService_update},
        };
       关联到native_update@frameworks/base/services/java/com/android/server/batteryservice.java
3. 分析frameworks/base/services/java/com/android/server/batteryservice.java
    a. mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply"); //注册监听该uevent
    b.     private UEventObserver mPowerSupplyObserver = new UEventObserver() {
        public void onUEvent(UEventObserver.UEvent event) { //如果就收到来自kernel的uevent,触发该函数
            update(); //-->native_update(); 和processValues();
        }
           };
    c. native_update函数会通过JNI调用得到更新的mAcOnline和mUsbOnline值
    d. processValues会根据mAcOnline和mUsbOnline值 对mPlugType赋值
        if (mAcOnline) {
            mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
        } else if (mUsbOnline) {
            mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
        } else {
            mPlugType = BATTERY_PLUGGED_NONE;
        }
    e. sendIntent函数会准备Intent并且broadcast这个Intent
        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); //public static final String ACTION_BATTERY_CHANGED = "android.intent.action.BATTERY_CHANGED";@batteryservice.java
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                | Intent.FLAG_RECEIVER_REPLACE_PENDING);

            intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
        ActivityManagerNative.broadcastStickyIntent(intent, null);
4. 分析vendor/semc/packages/apps/debugmenu/src/com/sonyericsson/debugmenu/battery.java
    a. 接收intent
        private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context arg0, Intent intent) {
            if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
                mPowerInformation.parseIntent(intent);
                updateViews();
            }
        }
        };
        public void registerBroadcastReceivers() {
        IntentFilter mIntentFilter;
        mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); //只接收Intent.ACTION_BATTERY_CHANGED的intent
        // Register battery receiver.
        registerReceiver(mBatteryInfoReceiver, mIntentFilter);
        }    
    b. parseIntent 得到最新的"plugged"值赋给mPlugged
    c. updateViews 更新charger类型显示
###内核空间
1. 插入充电器触发的过程@kernel/drivers/usb/otg/msm_otg.c
    a. CONFIG_PM_RUNTIME 定义 @kernel/arch/arm/configs/viskan_huashan_defconfig
    b.msm_otg_runtime_resume-->msm_otg_resume-->??-->msm_otg_sm_work
    c.msm_otg_sm_work,先进入OTG_STATE_UNDEFINED case,然后进入OTG_STATE_B_IDLE case-->motg->chg_state=USB_CHG_STATE_UNDEFINED-->启动msm_chg_detect_work
    d.msm_chg_detect_work 该函数主要完成USB charger类型的识别
        USB_CHG_STATE_UNDEFINED //其中psy = power_supply_get_by_name("usb");很关键,表明只是获取usb的power_supply,实际上就是chip->usb_psy@pm8921-charger.c
        USB_CHG_STATE_WAIT_FOR_DCD    //Waiting for Data pins contact.
        然后多次(MSM_CHG_DCD_MAX_RETRIES=6)检测USB状态,启动queue_delayed_work(motg->wq, &motg->chg_work, delay); //motg->chg_work=msm_chg_detect_work
        USB_CHG_STATE_DETECTED //最后USB charge类型检测完毕
    e. 启动queue_work(motg->wq, &motg->sm_work); //motg->sm_work=msm_otg_sm_work
    f. 由于motg->chg_state==USB_CHG_STATE_DETECTED,
        motg->chg_type==USB_DCP_CHARGER -->msm_otg_notify_charger(motg,IDEV_CHG_MAX);//IDEV_CHG_MAX=1500
2. 发送uevent到用户层
    a. msm_otg_notify_charger@kernel/drivers/usb/otg/msm_otg.c
    b. -->msm_otg_notify_chg_type@msm_otg.c, 该函数中充电类型只归纳成4种:POWER_SUPPLY_TYPE_USB,POWER_SUPPLY_TYPE_USB_CDP,POWER_SUPPLY_TYPE_USB_DCP,POWER_SUPPLY_TYPE_USB_ACA,POWER_SUPPLY_TYPE_BATTERY
    c. -->power_supply_set_supply_type@power_supply_core.c // set type of the power supply
    d. -->psy->set_property(psy, POWER_SUPPLY_PROP_TYPE,&ret);
        那么在用usb_psy.get_property和dc_psy.get_property (在函数中pm8921_charger_probe)读取property时
        如果是USB type,则在pm_power_get_property_usb 读取POWER_SUPPLY_PROP_ONLINE的值,其中POWER_SUPPLY_TYPE_USB,POWER_SUPPLY_TYPE_USB_CDP和POWER_SUPPLY_TYPE_USB_ACA 都被归纳成USB charger
        如果是AC type,则在pm_power_get_property_mains 读取POWER_SUPPLY_PROP_ONLINE的值,只有POWER_SUPPLY_TYPE_USB_DCP为AC charger.
    e. psy=chip->usb_psy@pm8921-charger.c,所以继续调用的是pm_power_set_property_usb@pm8921-charger.c
    f. -->pm8921_set_usb_power_supply_type(val->intval)@pm8921-charger.c
        -->power_supply_changed(&the_chip->usb_psy);
        -->power_supply_changed(&the_chip->dc_psy);
    g. power_supply_changed@pm8921-charger.c , 就会马上发送uevent给user space,然后user space通过sysfs得到USB type变化后的信息,参考用户空间的分析

二十四、充电最长时间设置
1. adb path
    /sys/module/pm8921_charger/parameters/disable_safety_timer , 如果为0则表示用的是系统设置的最大时间,如果为1则表示没有最大充电时间限制
2. 具体代码分析
    a. @kernel/drivers/power/pm8921-charger.c
        module_param(disable_safety_timer, int, 0644);
    b. @kernel/arch/arm/mach-msm/board-sony_viskan_huashan-pmic.c
        static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata = {
            .safety_time        = 512, //设置最大充电时间512分钟, 设置区间为[4,512]读写CHG_TCHG_MAX寄存器
            .ttrkl_time        = 64, //设置最大的trickle 充电时间, 设置区间为[1,64]读写CHG_ITTRKL_MAX寄存器
    c. pm8921_chg_hw_init@kernel/drivers/power/pm8921-charger.c 函数中会设置以上时间
    d. @kernel/drivers/power/pm8921-charger.c
        static int pm_chg_failed_clear(struct pm8921_chg_chip *chip, int clear)
        {...
            rc = pm_chg_masked_write(chip, CHG_CNTRL_3, ATC_FAILED_CLEAR,clear ? ATC_FAILED_CLEAR : 0);//操作CHG_CNTRL_3寄存器
            if (disable_safety_timer)
                clear_long_time_chg_failed(chip);//清除充电失败的状态,比如超时充电,如此可以继续充电
                -->rc = pm_chg_masked_write(chip, CHG_CNTRL_3,CHG_FAILED_CLEAR, CHG_FAILED_CLEAR);//操作CHG_CNTRL_3寄存器
        }
    e. CHG_IRQ(CHGFAIL_IRQ, IRQF_TRIGGER_RISING, chgfail_irq_handler) //设置充电失败后的中断触发函数
        中断触发条件CHGFAIL: auto charging has failed Maximum fast charging time exceeded without ever reaching termination current
    f. 一旦超时充电则触发chgfail_irq_handler-->pm_chg_failed_clear

二十五、充电的几个过程如何控制的
1. pm8921_chg_hw_init@kernel/drivers/power/pm8921-charger.c
    pm_chg_vbatdet_set(chip,chip->max_voltage_mv- chip->resume_voltage_delta); //comparator triggering voltage after top-off,现在设置的大小是4200-60=4140mv
    pm_chg_vddsafe_set(chip, vdd_safe); // Charger regulator output voltage. It is either the voltage the battery is charged up to or the VPH_PWR voltage when the battery is disconnected; The actual usable (i. e. spec compliant) range for CHG_VDD_MAX is 3.4 V to 4.5 V,现在设置的是4200+400=4600mv,
    pm_chg_vddmax_set(chip, chip->max_voltage_mv);//,也就是在4200mv时充电从CC->CV,Charger regulator output voltage. It is either the voltage the battery is charged up to or the VPH_PWR voltage when the battery is disconnected.
    pm_chg_vddsafe_set和pm_chg_vddmax_set的作用似乎一样
2. struct pm8921_charger_platform_data@kernel/include/linux/mfd/pm8xxx/pm8921-charger.h
    max_voltage=4200mv:    the max voltage (mV) the battery should be charged up to
    resume_voltage_delta=60mv: the (mV) drop to wait for before resume charging
    trkl_voltage=0 (default 2.8V) : the trkl voltage in (mV) below which hw controlled trkl charging happens with linear charger
    weak_voltage=0 (default 3.2V) : .... with switching mode charger
    trkl_current=0 (default 50mA) : the trkl current in (mA) to use for trkl charging phase
    weak_current=0 (default 50mA)

二十六、充电的温度保护
1. @kernel/arch/arm/mach-msm/board-sony_huashan-pmic.c
    static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata = {
        .cold_temp        = 5,
        .cool_temp        = 10,
        .warm_temp        = 45,
        .hot_temp        = 55,
        .hysterisis_temp    = 3,
    }
2. pm8921_charger_probe@kernel/drivers/power/pm8921-charger.c
        chip->cool_temp_dc = pdata->cool_temp * 10;
        chip->warm_temp_dc = pdata->warm_temp * 10;
        chip->cold_temp_dc = pdata->cold_temp * 10;
        chip->hot_temp_dc = pdata->hot_temp * 10;
3. configure_temp_control@kernel/drivers/power/pm8921-charger.c, 设置温度控制的区间
    cold: (INT_MIN, 8]     -->temp_cold_func    -->update_charge_state(BIT(DIS_BIT_CHG_TEMP), DIS_BIT_CHG_TEMP_MASK);//disable charging
    cool: [5,13]        -->temp_cool_func    -->set_appropriate_battery_current(the_chip); //设置最大充电电流为cool_bat_chg_current,如果有mitigation策略,则mitigation优先
                               pm_chg_vddmax_set(the_chip, the_chip->cool_bat_voltage);//设置最大充电电压cool_bat_voltage
                               update_charge_state(false, DIS_BIT_CHG_TEMP_MASK);//设置为充电状态
    nomral: [10,45]        -->temp_normal_func    -->set_appropriate_battery_current(the_chip); //设置最大充电电流为max_bat_chg_current=1525mA,如果有mitigation策略,则mitigation优先
                               pm_chg_vddmax_set(the_chip, the_chip->max_voltage_mv);//设置最大充电电压max_voltage_mv=4200mV
                               update_charge_state(false, DIS_BIT_CHG_TEMP_MASK);//设置为充电状态
    warm: [42,55]        -->temp_warm_func    -->set_appropriate_battery_current(the_chip);//设置最大充电电流为warm_bat_chg_current=325mA
                               pm_chg_vddmax_set(the_chip, the_chip->warm_bat_voltage);//设置最大充电电压warm_bat_voltage=4000mV
                               update_charge_state(false, DIS_BIT_CHG_TEMP_MASK);//设置为充电状态
    hot: [52,INT_MAX]    -->temp_hot_func    -->update_charge_state(BIT(DIS_BIT_CHG_TEMP), DIS_BIT_CHG_TEMP_MASK);//disable charging
    @kernel/arch/arm/mach-msm/board-sony_viskan_huashan-pmic.c
    static int pm8921_therm_mitigation[] = {
        1525,    //charge_current_limit0
        825,    //charge_current_limit1
        475,    //charge_current_limit2
        325,    //charge_current_limit3
    };
    shutdownIfOverTemp@frameworks/base/services/java/com/android/server/batteryservice.java
        private final void shutdownIfOverTemp() {
        // shut down gracefully if temperature is too high (> 68.0C)
        // wait until the system has booted before attempting to display the shutdown dialog.
        if (mBatteryTemperature > 680 && ActivityManagerNative.isSystemReady()) {
            Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
            intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mContext.startActivity(intent);
        }
        }
4. update_temp_state@kernel/drivers/power/pm8921-charger.c
    -->get_prop_batt_temp //得到电池的温度
       is_temp_condition //得到电池温度的状态
       chip->temp_state.data[].temp_func() //运行对应的电池温度状态处理函数
5. update_heartbeat@kernel/drivers/power/pm8921-charger.c 心跳运行函数,每隔pm8921_chg_pdata.update_time = 30000 ,30秒运行一次
    if (chip->is_temp_control)
        update_temp_state(chip);
    
6. update_charge_state@kernel/drivers/power/pm8921-charger.c
    pm_chg_auto_enable //操作CHG_CNTRL_3寄存器的第7 bit: Enables FSM controlled charging(1)
    pm_chg_charge_dis  //操作CHG_CNTRL寄存器第1 bit: Inhibit(1)/Allow(0) all battery charging functions including trickle charger,buck reg, and batfet control
7. 验证charge_current_limitx mitigation是否起作用
    a. #echo 3 > /sys/module/pm8921_charger/parameters/thermal_mitigation //设置charge_current_limit3,此时电流是325
    b. #cat /sys/bus/platform/devices/pm8921-charger/read_ibat_max //325
****修改后的控制逻辑
1. cold,cool,normal,warm,hot的温度设置没有改变
    @kernel/arch/arm/mach-msm/board-sony_huashan-pmic.c
        static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata = {
            .cool_temp        = 10,
            .warm_temp        = 45,
            .hysterisis_temp    = 3,
            
            .btc_override        = 1,    //是否使用高低温的控制逻辑
            .btc_override_cold_degc    = 5,    //cold temperature
            .btc_override_hot_degc    = 55,  //hot temperature
            .btc_delay_ms        = 10000, //每间隔10ms,启动btc_override_work,监控电池温度(hot,cold)
            .btc_panic_if_cant_stop_chg    = 1,
        }
2. 对应温度区间的控制
    cold: (INT_MIN, 8]    -->pm_chg_override_cold
    cool: [5,13]        -->battery_cool
    nomral: [10,45]
    warm: [42,55]        -->battery_warm
    hot: [52,INT_MAX]    -->pm_chg_override_hot

    /* Need to monitor battery health for COLD and HOT */
    if (chip->btc_override && !chg_present) //监控 cold和hot
        btc_override_worker_helper(chip);
    /* At fully charged condition temperature can change that should give a change on the recharge condition. */
    if (chg_present && !wake_lock_active(&chip->eoc_wake_lock))//监控warm和cool
        check_temp_thresholds(chip);
2.1 btc_override_worker_helper处理cold和hot
    get_batttemp_irq(..)//得到寄存器中断,电池温度是否hot和cold
    pm_chg_override_hot(...)和pm_chg_override_cold(...) //写入寄存器COMPARATOR_OVERRIDE,设置当前的电池充电状态,这个由QM自己的硬件状态机控制了
2.2 check_temp_thresholds 处理warm和cool
    battery_warm和battery_cool
    ->pm_chg_vddmax_set //设置最大充电电压, 也就是从CC到CV的转换电压
    set_appropriate_battery_current //设置最大充电电流为
    set_appropriate_vbatdet //设置重新开始充电触发的电压

3. update_heartbeat@kernel/drivers/power/pm8921-charger.c 心跳运行函数,每隔pm8921_chg_pdata.update_time = 30000 ,30秒运行一次
    /* Need to monitor battery health for COLD and HOT */
    if (chip->btc_override && !chg_present)
        btc_override_worker_helper(chip);
    /* At fully charged condition temperature can change that should give a change on the recharge condition.*/
    if (chg_present && !wake_lock_active(&chip->eoc_wake_lock))
        check_temp_thresholds(chip);

二十七、Battery_logging分析
1. 所有代码位于 @vendor/qcom/opensource/battery_monitor/,可以用$mm 编译
2. @battery_logging.c

二十八、充电recharge
1. @kernel/arch/arm/mach-msm/Board-sony_viskan_huashan-pmic.c
    static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata = {
        .resume_voltage_delta    = 60, //the (mV) drop to wait for before resume charging after the battery has been fully charged
        .resume_soc        = 99, //the state of charge (%) to wait for resume charging after the battery has been fully charged
        .term_current         = CHG_TERM_MA, //CHG_TERM_MA=115, the charger current (mA) at which EOC happens
    }
2. update_heartbeat-->check_soc_and_resume, 通过定时检测电池容量来 判断是否重新开始充电
    如果 chip->resume_soc >= chip->soc 成立,
    -->update_charge_state(false, DIS_BIT_EOC_MASK);//表示需要enable charge
    -->pm_chg_auto_enable(the_chip, en_bit_val);//en_bit_val=true
       pm_chg_charge_dis(the_chip, dis_bit_val); //dis_bit_val=false
3. 具体分析如下含义????
    enum charging_dis_bit {
        /* lowest prio */
        DIS_BIT_EOC,             //充电完成,charger还给系统供电
        DIS_BIT_THERM_FET_OPEN,        //因为温度升高,充电被迫完成,但charger还给系统供电
        DIS_BIT_CHG_BATT_INVALID,    //
        DIS_BIT_THERM_FET_CLOSE,    //因为温度升高,充电被迫中止,此时只有battery给系统供电
        DIS_BIT_USB,
        DIS_BIT_CHG_SHUTDOWN,
        DIS_BIT_MAX_NUM,
        /* highest prio */
    };    
    enum chg_fsm_state {
        FSM_STATE_OFF_0 = 0,
        FSM_STATE_BATFETDET_START_12 = 12,
        FSM_STATE_BATFETDET_END_16 = 16,
        FSM_STATE_ON_CHG_HIGHI_1 = 1,     //not charging or full
        FSM_STATE_ATC_2A = 2,
        FSM_STATE_ATC_2B = 18,
        FSM_STATE_ON_BAT_3 = 3,
        FSM_STATE_ATC_FAIL_4 = 4 ,
        FSM_STATE_DELAY_5 = 5,
        FSM_STATE_ON_CHG_AND_BAT_6 = 6,
        FSM_STATE_FAST_CHG_7 = 7,    //charging
        FSM_STATE_TRKL_CHG_8 = 8,
        FSM_STATE_CHG_FAIL_9 = 9,
        FSM_STATE_EOC_10 = 10,
        FSM_STATE_ON_CHG_VREGOK_11 = 11,
        FSM_STATE_ATC_PAUSE_13 = 13,
        FSM_STATE_FAST_CHG_PAUSE_14 = 14,
        FSM_STATE_TRKL_CHG_PAUSE_15 = 15,
        FSM_STATE_START_BOOT = 20,
        FSM_STATE_FLCB_VREGOK = 21,
        FSM_STATE_FLCB = 22,
    };
4. fastchg_irq_handler-->eoc_worker //internal function to check if battery EOC has happened
    -->is_charging_finished 检测是否charging完成
    -->is_ext_charging //如果POWER_SUPPLY_CHARGE_TYPE_NONE/TRICKLE/FAST
        -->chip->ext_psy->get_property(chip->ext_psy,POWER_SUPPLY_PROP_CHARGE_TYPE, &ret)
        -->pm_batt_power_get_property
        -->val->intval = get_prop_charge_type(chip); //得到当前的充电状态
        -->get_prop_batt_present(chip)
    -->pm_chg_iterm_get //得到触发结束充电的最小电流,实际读写的CHG_ITERM寄存器(会触发ICHG_END的中断)但没找到相关处理的函数?????是否就是CHGDONE
    -->get_prop_batt_current //得到电池的输出(>0 provide)或输入电流(<0 draw),由其正负值大小可得到是在充电否
   eoc_worker函数会每隔EOC_CHECK_PERIOD_MS=10秒再检测一次是否充电完成,共检测CONSECUTIVE_COUNT=3次
   如果充电完成则会调用
    -->update_charge_state(BIT(DIS_BIT_EOC), DIS_BIT_EOC_MASK); //设置寄存器为不充电的模式
        -->pm_chg_auto_enable(the_chip, en_bit_val);//en_bit_val=false
           pm_chg_charge_dis(the_chip, dis_bit_val); //dis_bit_val=false
    -->chgdone_irq_handler(chip->pmic_chg_irq[CHGDONE_IRQ], chip);//触发充电完成回调函数,通知
        -->handle_stop_ext_chg(chip);//设置此时充电状态为POWER_SUPPLY_CHARGE_TYPE_NONE
        -->power_supply_changed //通知上层
5. chgdone_irq_handler 会在充电完成的时候触发?而充电完成的条件是 充电电流足够小CHG_TERM_MA=115mA?????目前没有用到
6. 目前只有fastchg_irq_handler 会-->eoc_worker-->is_charging_finished这个是关键函数用于判断充电是否完成,
    a.主要还是通过流经电池的电流是否小于pm8921_chg_pdata.term_current=CHG_TERM_MA=115mA;
    b.如果流经电池的电流小于0则表示charger在给电池充电,并且此时充电的电流<115mA则认为充电结束;如果流经电池的电流大于0则表示电池在对外供电,一般情况下表示还在充电,warm和cool下则充满了,hot和cold则是直接通过充电状态机控制

    实际上不需要中断触发,以下内容没有用到
    BATT_INSERTED_IRQ,(BATT_REPLACE: battery replaced: battery thermistor monitor disabled or BAT_THERM voltage below open circuit threshold(95%)),batt_inserted_irq_handler,
    BAT_TEMP_OK_IRQ, (Battery temperature is in the normal range), bat_temp_ok_irq_handler,
    DCIN_OV_IRQ,(DC_IN > Vmax), dcin_valid_irq_handler
7. vbatdet_low_irq_handler //电压低于vbat_det触发,this interrupt used to restart charging a battery. 用于触发recharge
8. 触发recharge还有一条路:
    update_heartbeat->update_soc->get_prop_batt_capacity中会调用 get_prop_batt_status(..),其中有
    int fsm_state = pm_chg_get_fsm_state(chip);//得到当前的电池状态
    if (fsm_state == FSM_STATE_ON_CHG_HIGHI_1) // FSM_STATE_ON_CHG_HIGHI_1 表示充满电结束或不充电
        if( ... || chip->soc[SOC_TRUE] < chip->resume_charge_percent ) //如果此时电池容量百分比又小于chip->resume_charge_percent=95,则说明当前的状态是电池没有充满的,只是因为其他原因导致不充电了
            batt_state = POWER_SUPPLY_STATUS_NOT_CHARGING;
    回到get_prop_batt_capacity函数,有
    if (percent_soc <= chip->resume_charge_percent && (status == POWER_SUPPLY_STATUS_FULL || status == POWER_SUPPLY_STATUS_NOT_CHARGING))
    如果不是因为电池温度的原因,导致不充电,那有可能是硬件其他原因,比如vbatdet没有触发recharge,那么强行设置
        pm_chg_vbatdet_set(chip, PM8921_CHG_VBATDET_MAX); //PM8921_CHG_VBATDET_MAX=5780,这一定会通过vbatdet触发recharge;需要说明的是vbatdet还是要设置回正常值的,在soc_worker或者warm/cool中会做这事

二十九、如何得到电池百分比
1. pm8921_bms_get_percent_charge@kernel/drivers/power/pm8921-bms.c
    a. pm8921_bms_get_battery_temp(..) //获取电池温度
    b. read_soc_params_raw(...) //具体作用????
        ->calculate_soc_params ???
    c. calculate_state_of_charge
2. 计算电池百分比的 算法很复杂,至少与下列因素有关
    a. 电池的温度
    b. 电池的使用情况,当前的供电能力(这是会随着电池的老化而逐步改变,会不断的修正)   
    
三十、USB 插入和拔出的处理
1. kernel/drivers/power/pm8921-charger.c
    a. CHG_IRQ(USBIN_VALID_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,    usbin_valid_irq_handler),//注册USBIN中断响应函数
    b. usbin_valid_irq_handler //USB 插入和拔出都会触发该函数
        -->handle_usb_insertion_removal
        -->马上开始btc_override_work 间隔10ms
2. 另外USB的driver 还会处理@kernel/drivers/usb/otg/msm_otg.c

*****************************************************************************************************************
Charging Questions
1. is_usb_chg_plugged_in 函数检测是否连着USB Port 充电?  true: charger connect; false: charger remove
2. is_dc_chg_plugged_in 函数检测是否连着DC charger充电?
3. BATFET 中断? 1: close, 给电池充电,还有电流流向电池; 0: Open,充电结束或停止充电,没有电流流向电池了,但是外部充电器会继续给手机系统供电
4. BATT_INSERTED中断?BATT_REPLACE: battery replaced: battery thermistor monitor disabled or BAT_THERM voltage below open circuit threshold(95%)
5. BAT_TEMP_OK中断? 1:温度正常;0:温度不正常(hot or cold)


*****************************************************************************************************************
十八、LOGI没有输出
1. 查看vendor/semc/hardware/device/viskan/sysmon/main.mk
    # Comment to enable debug
    # LOCAL_CFLAGS += -DNDEBUG -DLOG_NDEBUG //修改如下
    LOCAL_CFLAGS += -DNDEBUG -DLOG_NDEBUG -DLOG_NDDEBUG -DLOG_NIDEBUG=0
    # Uncomment to enable debug
    #LOCAL_CFLAGS += -DDEBUG -UNDEBUG -ULOG_NDEBUG
2. 只修改*.mk文件,如果不修改*.c文件,以前编译成功后,如果再修改*.mk其变化不能反映到新的结果上。
    需要touch 一下你需要编译的文件

十九、power on 出问题,当休眠后
rm system/bin/mm-pp-daemon

二十、Semc apk build
1. vendor/semc/build/composition-config/variant_specs/huashan-base.xml, 在该文件中会设置那些apk需要打包进入system/app/
2. 编译semc app, $semc-build

二十一、Thermal Frame&APP 层分析
1. @vendor/semc/hardware/sysmon/libsysmon/libsysmon.c
    a.


二十二、多点触摸如何区分 有几点
1. adb shell getevent -p 可以查看所有的input event类型,然后找出touch event, 还可以cat proc/bus/input/devices 列出所有的input devices
2. adb shell getevent /dev/input/event7 直接获取touch event上报的内容
    Event_type    Event_code    Event_value
    0003         0035         00000137
3. @kernel/include/linux/input.h,
    a. Event Type
        #define EV_SYN            0x00
        #define EV_KEY            0x01
        #define EV_REL            0x02
        #define EV_ABS            0x03
    b. Evnet Code
        #define ABS_MT_SLOT        0x2f    /* MT slot being modified */
        #define ABS_MT_TOUCH_MAJOR    0x30    /* Major axis of touching ellipse */
        #define ABS_MT_TOUCH_MINOR    0x31    /* Minor axis (omit if circular) */
        #define ABS_MT_WIDTH_MAJOR    0x32    /* Major axis of approaching ellipse */
        #define ABS_MT_WIDTH_MINOR    0x33    /* Minor axis (omit if circular) */
        #define ABS_MT_ORIENTATION    0x34    /* Ellipse orientation */
        #define ABS_MT_POSITION_X    0x35    /* Center X ellipse position */
        #define ABS_MT_POSITION_Y    0x36    /* Center Y ellipse position */
        #define ABS_MT_TOOL_TYPE    0x37    /* Type of touching device */
        #define ABS_MT_BLOB_ID        0x38    /* Group a set of packets as a blob */
        #define ABS_MT_TRACKING_ID    0x39    /* Unique ID of initiated contact */
        #define ABS_MT_PRESSURE        0x3a    /* Pressure on contact area */
        #define ABS_MT_DISTANCE        0x3b    /* Contact hover distance */
4. Touch Case
    a. one touch
        0003 0039 00000003
        0003 0035 00000216
        0003 0036 00000368
        0003 003a 00000048
        0000 0000 00000000
        0003 0036 00000367
        0003 003a 0000004b
        0000 0000 00000000
        0003 0039 ffffffff
        0000 0000 00000000
    b. Two touchs
        0003 002f 00000000    //第一个touch
        0003 0039 00000008
        0003 0035 00000245
        0003 0036 0000039b
        0003 003a 00000073
        0000 0000 00000000
        0003 003a 0000007c
        0003 002f 00000001    //第二个touch
        0003 0039 00000009
        0003 0035 000000a8
        0003 0036 00000345
        0003 003a 00000082
        0000 0000 00000000
4. #getevent -l /dev/input/event8 获取touch的所有event
5. 重新校准touch parameters:
    echo 1 > /sys/devices/i2c-3/3-0024/main_ttsp_core.cyttsp4_i2c_adapter/calibration
   得到校准参数
    cat /sys/devices/i2c-3/3-0024/main_ttsp_core.cyttsp4_i2c_adapter/calibration

二十三、开机画面
static void __init draw_logo(void)
{
    struct fb_info *fb_info;

    fb_info = registered_fb[0];
    if (fb_info && fb_info->fbops->fb_open) {
        printk(KERN_INFO "Drawing logo.\n");
        fb_info->fbops->fb_open(fb_info, 0);
        fb_info->fbops->fb_pan_display(&fb_info->var, fb_info);
    }
}
@/kernel/drivers/video/msm/logo.c            

二十四、设置8960 GPIO
1. @kernel/arch/arm/mach-msm/board-sony_viskan_huashan-gpiomux.c
    static struct msm_gpiomux_config semc_viskan_all_cfgs[]
2.具体操作GPIO的函数 @kernel/drivers/gpio/gpiolib.c

二十五、充电log
1. charge only mode (chargemon)的log输出
    a.
    b. /data/cm.log
2. charge battery
    a. 设置etc/battery_logging.conf, 如果没有则需要adb push;默认已经有输出;其中格式为 <name>,<path>,[format]
        e.g: read_soc_original,/sys/bus/platform/devices/pm8921-bms/read_soc_original,
    b. 输出的log路径:data/battery_logging_a.log等

二十六、disable led in charge-only mode
1. TA 的参考 https://wiki.sonyericsson.net/androiki/PLD_System_Core/Parameter_Handling/MiscTA_Parameter_List#Parameter_List
   目前使用的是2313https://wiki.sonyericsson.net/androiki/PLD_System_Core/Parameter_Handling/MiscTA_Parameter_List#Parameter_List
2. 具体的gerrit
    http://review.sonyericsson.net/#/c/464976/
    http://review.sonyericsson.net/#/c/463992/

二十七、Audio jack
1. https://wiki.sonyericsson.net/androiki/Huashan_legacy_feature:_Vibrator_%26_Audio_Jack
2. How to get debug info
    1 dump register before and after headset plug, use following command to dump register
    1.1 $ adb shell 'mount -t debugfs debugfs /sys/kernel/debug/'
    1.2 $ adb shell 'cat /sys/kernel/debug/asoc/msm8960-snd-card/tabla_codec/codec_reg'
    2 Log message during insertion please enable wcd9xxx-core.c +p, wcd9310.c +p, wcd9xxx-irq.c like following
    2.1 $ adb shell 'mount -t debugfs debugfs /sys/kernel/debug/'
    2.2 $ adb shell 'echo -n "file wcd9310.c +p" > /sys/kernel/debug/dynamic_debug/control'
    2.3 $ adb shell 'echo -n "file wcd9xxx-core.c +p" > /sys/kernel/debug/dynamic_debug/control'
    2.4 $ adb shell 'echo -n "file wcd9xxx-irq.c +p" > /sys/kernel/debug/dynamic_debug/control'
3. Lund DD ,audio jack: Karlsson, Stefan 3;
4. $ adb shell 'cat /sys/kernel/debug/gpio' 得到GPIO状态  gpio-189 = 151 + PMIC8921的38 Pin
    gpio-189 (--          ) in         lo 0x05 0x10 0x22 0x30 0x40 0x58
5. cat sys/module/snd_soc_msm8960/parameters/hs_detect_use_gpio; 可查看是否使用gpio中断检测headset insert/remove


二十八、如何检测 headset/headphone 插入或拔出
0. 针对wcd9310不需要读取firmware.
1. 主要分析 /kernel/sound/soc/codecs/wcd9310.c, kernel/sound/soc/msm/msm8960.c
2. msm8960_audrx_init@msm8960.c中 全局变量hs_detect_use_gpio 设置是否用PMIC的GPIO检测耳机插入
    err = tabla_hs_detect(codec, &mbhc_cfg); //设置GPIO38的中断处理函数
       tabla_hs_detect->tabla_mbhc_init_and_calibrate 设置中断处理函数    tabla_mechanical_plug_detect_irq,
    中断pin是tabla->mbhc_cfg.gpio_irq;
    @kernel/sound/soc/msm/msm8960.c中找到中断Pin的定义msm8960_audrx_init函数中 mbhc_cfg.gpio_irq = JACK_DETECT_INT; 也就是PM8921的第38Pin
    mbhc_cfg.gpio_level_insert=1;表示headset插入时,中断JACK_DETECT_INT的值为高
   如果不用GPIO中断检测耳机插入,则需要用到 TABLA_IRQ_MBHC_INSERTION 的内部中断
    wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION);     
3. tabla_mbhc_init_and_calibrate@wcd9310.c中设置中断回调函数tabla_mechanical_plug_detect_irq --> tabla_hs_gpio_handler        
    insert = (gpio_get_value_cansleep(tabla->mbhc_cfg.gpio) == tabla->mbhc_cfg.gpio_level_insert);
4. tabla_codec_detect_plug_type-->tabla_codec_get_plug_type@wcd9310.c 检测耳机类型
   其中plug_type_ptr->v_no_mic =73,
      plug_type_ptr->v_hs_max = 2850    
   重要结构static struct tabla_mbhc_config mbhc_cfg @kernel/sound/soc/msm/msm8960.c
    mbhc_cfg.gpio_level_insert = 1 //表示GPIO38为高时为耳机插入
   struct viskan_mbhc_data viskan_mbhc_data@kernel/arch/arm/mach-msm
    .v_hs_max = 2850,  //
   S(v_no_mic, 73);    
5. 支持的耳机jack顺序, MIC|GND|HPHR|HPHL, 在tabla_codec_get_plug_type@wcd9310.c中一共要读取4次电压
    1和2时,分别读取两次MIC和GND之间的电压
    3时,HPHR和GND都切换,读取两次MIC和GND之间的电压
    4时,只有MIC切换,读取两次MIC和GND之间的电压,同时会判断是否为PLUG_TYPE_GND_MIC_SWAP或PLUG_TYPE_INVALID,没搞清楚具体硬件原理
   如果 所有4次得到的MIC和GND之间的电压,均
    < v_no_mic 则是headphone (PLUG_TYPE_HEADPHONE)
    > v_hs_max 则是High impedance plug type,高阻态的耳机类型,线性输出(PLUG_TYPE_HIGH_HPH)
       > v_no_mic & < v_hs_max,则是headset (PLUG_TYPE_HEADSET)
   如果前后两次得到的耳机类型不一样,则说明该耳机不支持(PLUG_TYPE_INVALID)    
   支持的headset, 主要比较的是第一个电压mb_v[i]
    DCE #1, 003e, V 1638, scaled V 1638, GND 0, VDDIO 0, inval 0        
    DCE #2, 0071, V 1733, scaled V 1733, GND 0, VDDIO 0, inval 0
    DCE #3, ffa8, V 1360, scaled V 2092, GND 1, VDDIO 1, inval 1
    DCE #4, ffa8, V 1360, scaled V 2092, GND 0, VDDIO 1, inval 0
   不支持的headset
    DCE #1, fe30, V 661, scaled V 661, GND 0, VDDIO 0, inval 0     
    DCE #2, fe30, V 661, scaled V 661, GND 0, VDDIO 0, inval 0
    DCE #3, fd2f, V 183, scaled V 281, GND 1, VDDIO 1, inval 1
    DCE #4, fe17, V 615, scaled V 946, GND 0, VDDIO 1, inval 1
   支持的headphone
    DCE #1, fcd2, V 11, scaled V 11, GND 0, VDDIO 0, inval 0
    DCE #2, fcd2, V 11, scaled V 11, GND 0, VDDIO 0, inval 0
    DCE #3, fcd0, V 7, scaled V 10, GND 1, VDDIO 1, inval 0
    DCE #4, fcd0, V 7, scaled V 10, GND 0, VDDIO 1, inval 0
6. 根据测量audio jack中GND,MIC 脚与地之间的电压,来比较得出不同的耳机类型。目前只支持MIC|GND|HPHR|HPHL(CTIA)不支持(OMTP)
7. 耳机插入如何上报event
    tabla_codec_detect_plug_type
    -->tabla_codec_report_plug    (只有PLUG_TYPE_GND_MIC_SWAP,PLUG_TYPE_HEADPHONE,PLUG_TYPE_HEADSET,PLUG_TYPE_HIGH_HPH才上报)
    -->tabla_snd_soc_jack_report
    -->snd_soc_jack_report_no_dapm
    -->snd_jack_report@kernel/sound/core/jack.c
    -->input_report_switch@kernel/include/linux/input.h
    -->input_event(dev, EV_SW, code, !!value);
二十九,如何检测耳机的按键事件
1. tabla_hs_gpio_handler-->tabla_codec_detect_plug_type
    只有耳机类型为headset 才能触发tabla_codec_start_hs_polling, 设置各按键的 电压门限值;(mbhc_state == MBHC_STATE_POTENTIAL)
2. 设置中断处理函数 @tabla_codec_probe
    wcd9xxx_request_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL,tabla_dce_handler, "DC Estimation detect", tabla);
   其中 tabla_hs_detect->tabla_mbhc_init_and_calibrate-->tabla_mbhc_cal会enable 中断TABLA_IRQ_MBHC_POTENTIAL
    wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL);
3. 当headset插入后,如果有按键则会触发 TABLA_IRQ_MBHC_POTENTIAL 中断,从而call tabla_dce_handler-->tabla_determine_button
    tabla_determine_button函数会通过读取的此时 micphone pin上的电压来决定是那个button key.
4. 而具体的按键是通过
    @kernel/sound/soc/msm/msm8960.c中的函数snd_soc_jack_new(codec, "Button Jack",TABLA_JACK_BUTTON_MASK, &button_jack);注册一个jack
    -->snd_jack_new
    最终button_jack会接收按键信息
5. 根据测量micphone pin上的电压决定是那个按键,
    而各按键的电压定义在 def_tabla_mbhc_cal@kernel/sound/soc/msm/msm8960.c
    各个按键的结构定义为 struct tabla_mbhc_btn_detect_cfg@kernel/sound/soc/codecs/wcd9310.c
    目前定义了4个按键 TABLA_MBHC_DEF_BUTTONS = 4,电压区间如下
        btn_low[0] = -30;
        btn_high[0] = 73;
        btn_low[1] = 74;
        btn_high[1] = 336;
        btn_low[2] = 337;
        btn_high[2] = 680;
        btn_low[3] = 681;
        btn_high[3] = 1257;
6. 在tabla_dce_handler函数中得到 具体是那个button按键被按下,然后检测中断TABLA_IRQ_MBHC_RELEASE (该中断一直是enable),当按键释放后触发tabla_release_handler-->tabla_snd_soc_jack_report 然后上报该event
    -->snd_jack_report-->input_report_key@kernel/sound/core/jack.c
    -->input_event(dev, EV_KEY, code, !!value);


三十. 如何在eclipse 中生成签名
0. http://blog.csdn.net/pugongying1988/article/details/6973981
1. 得到签名文件http://opengrok.sonyericsson.net/jellybean/xref/jb-viskan/vendor/semc/build/core/semcsdk/sdk_files/platform/
2. 在eclipse window->preferences->android->build的 custome debug keystore下 输入上面的签名文件
3. 重新生成apk
4. 编译生成的sensorlogger.apk 中如果包含了com.sonyericsson.sysmon 包,则需要删除手机中 system/framework中的 com.sonyericsson.sysmon.jar, 不然会出现引用冲突


三十一、长按PowerKey会触发dump
1. http://review.sonyericsson.net/#/c/302246
2. drivers/input/misc/pmic8xxx-forcecrash.c
   drivers/input/misc/pmic8xxx-pwrkey.c
3. /sys/devices/platform/msm_ssbi.0/pm8921-core/pm8xxx-pwrkey/forcecrash_on 可以查看是否设置了长按powerkey (FORCE_CRASH_TIMEOUT 10秒)触发panic
4. @device/semc/viskan/files/init.viskan.rc中会设置只有debug和eng版本才需要该功能
    ONLY_IN_VARIANT(userdebug, write /sys/devices/platform/msm_ssbi.0/pm8921-core/pm8xxx-pwrkey/forcecrash_on 1)
    ONLY_IN_VARIANT(eng, write /sys/devices/platform/msm_ssbi.0/pm8921-core/pm8xxx-pwrkey/forcecrash_on 1)

三十二、early suspend/late resume, suspend/resume相关代码
[Kernel]
1. @kernel/drivers/base/power/main.c
2. @kernel/kernel/power/suspend.c
3. @kernel/kernel/power/main.c
4. @kernel/kernel/power/earlysuspend.c
5. @kernel/kernel/power/wakelock.c
6. @kernel/drivers/base/syscore.c
7. @kernel/kernel/power/userwakelock.c
[User space]
1. @frameworks/base/services/java/com/android/server/powermanagerservice.java
2. @frameworks/base/services/jni/com_android_server_powermanagerservice.cpp
3. @system/core/libsuspend/autosuspend.c
4. @system/core/libsuspend/autosuspend_earlysuspend.c
5. @frameworks/base/services/java/com/android/server/systemserver.java
6. @hardware/libhardware_legacy/power/power.c
7. @frameworks/base/core/java/android/os/powermanager.java
总结说明:
1. Early suspend/ late resume 是android 添加的机制,用户关闭LCD, TS, Sensors等为了省电。可称之为浅度休眠
2. Linux的Suspend/resume 仍然有效,可称之为深度休眠
3. echo mem > sys/power/state 会触发early suspend,
    echo on > sys/power/state 会触发late resume,
    cat sys/power/state 会显示当前支持的休眠状态,有mem, on, standby选项,但针对android 则只有mem, 同时无条件支持on
4. LCD, TS, Sensors等设备会注册early suspend 和late resume 回调函数,用于early suspend/late resume时逐个执行。
5. User space 通过操作 sys/power/wake_lock, sys/power/wake_unlock 可以向kernel申请一个wake lock;
    cat sys/power/wake_lock 或wake_unlock 只是显示User Space 申请的wake lock, kernel 申请的wake lock显示不出来.
6. cat proc/wakelocks 可以显示所有的wake lock的信息
7. Early suspend/Late resume中有一个关键wake lock是main_wake_lock, 执行early suspend后会unlock main_wake_lock;执行late resume之前会lock main_wake_lock.
8. 当任何一个wake lock被unlock时,会检测是否有其他wake lock处于locked状态?如果没有则系统进入 linux 常规suspend
9. 一般而言android系统是否休眠,是由user space发起的,而kernel space 是不能主动进入休眠的;当android系统需要进入休眠时,user space 执行echo mem > sys/power/state;强迫kernel 进入early suspend状态,如果还没有任何wake lock被locked,则进入linux 常规suspend.
10.  如果android系统没有进入深度休眠,则user space发起echo on> sys/power/state, 则系统马上执行late resume, 唤醒LCD,TS,sensor等;如果已经进入深度休眠,则另外处理
11. kernel会依次同步文件系统,执行各设备注册的suspend, 执行system core 的suspend disble bus和irq等,然后进入深度休眠后会在suspend_enter@kernel/kernel/power/suspend.c中的  suspend_ops->enter(state) 系统挂起,直到有硬件的操作唤醒;一旦被唤醒则依次执行system core resume, device resume等,最后还会执行late resume.
12. User space 向kernel申请wake lock时, 写入sys/power/wake_lock 的buf格式为<wakelock名称> [延时的纳秒数];但是User space 没有这么使用,android在java层自己实现了 计数wake lock和记次wake lock.


blacklcd bf6002f2c0bf963d60199a39b81224828713fdd7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值