Qualcomm Note

Android MTK GPIO 控制驱动 Android MTK GPIO 控制驱动 ——— MTK GPIO 驱动控制 手机Android系统驱动控制GPIO的操作是最常规的操作,此篇将简述 在MTK6765平台Android 9.0 (kernel-4.9) 为例的添加的GPIO控制驱动 目录Android MTK GPIO 控制驱动一、添加驱动控制文件1、驱动文件(1)添加驱动文件夹和文件路径(2)make文件内容(3)驱动文件描述2、添加DTS设备树 一、添加驱动控制文件 1、驱动文件 (1)添加驱动文件夹和文件路径 \kernel- 阅读详情
1. repo init -u git://review.sonyericsson.net/platform/manifest -b volatile-jb-mr1-yangtze
2. https://wiki.sonyericsson.net/androiki/CN3-II/Bringup_Trail_FC34  如何编译
3. https://wiki.sonyericsson.net/androiki/PLD_CM/Yangtze  如何编译和flash
4. https://wiki.sonyericsson.net/androiki/MIB_Tokyo/Rhine/Hardware_Watchdog_debugging

小技巧:
MISCTA 2473控制kernel log输出: 01 输出,00 无输出
repo sync [目录路径]  比如: kenel,system/vold
repo sync kernel 则只是同步kernel目录下的代码
grep  "ifelse" * -r  查找当前目录下(递归)所有文件中包含ifelse的内容
owner:xiaoguang2.lu@sonymobile.com status:merged  //查找谁提交并且已经merged的gerrit
printk("func = %pf",func)可打印函数名称出来


充电缩写语:(acronym)
FSM: Finite State Machine
CC: Coulcomb Counter
FCC: Full Charge Capacity
OCV: Open Circuit Voltage
PMIC: Power Management IC
PC: Percentage Charge
RC: Remaining Charge
SOC: State of Charge
RUC: Remaining Usable Charge
UUC: Unusable Charge

一、DTS 学习
1. kernel/arch/arm/boot/dts 目录下包含所有的dts.
    a. board , msm8226.dtsi
    b. pmic, msm-pm8226.dtsi
2. kernel/Androidkernel.mk;  Android makefile to build kernel as a part of Android Build
3. kernel/arch/arm/configs 目录下保存对应的config定义, CONFIG_XXXXXX
4. 可参考 jb-mr1-rhine/kernel/arch/arm/boot/dts/msm8974-rhine_togari_row.dtsi

二、LCD Porting
1. http://review.sonyericsson.net/#/c/508903

三、Charger bringup
1. 和battery and hw guys 确定相关 硬件参数
2. http://review.sonyericsson.net/#/c/516297/
3. kernel 发送给battery_logging的uevent格式?
    power_supply_uevent@kernel/drivers/power/power_supply_sysfs.c 该函数添加发送的信息到uevent
    ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->name); //psy->name="battery"
    for (j = 0; j < psy->num_properties; j++) {
        attr = &power_supply_attrs[psy->properties[j]]; //得到对应的power supply attr,
        attrname = kstruprdup(attr->attr.name, GFP_KERNEL);//会把属性名字转成大写,比如“status”->"STATUS"
        ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf); //增加该属性的信息到uevent buffer中
    }
    
    qpnp_charger_probe@kernel/drivers/power/qpnp-charger.c中会定义"battery"的properties = msm_batt_power_props
    static enum power_supply_property msm_batt_power_props[] = { //对应power_supply_attrs[]@kernel/drivers/power/power_supply_sysfs.c
        POWER_SUPPLY_PROP_CHARGING_ENABLED,             //表示这些属性是被battery psy所需要的属性,具体如何得到可查看    
        POWER_SUPPLY_PROP_STATUS,               //qpnp_batt_power_get_property 函数
        POWER_SUPPLY_PROP_CHARGE_TYPE,                    //这些属性会添加到/sys/class/power_supply/battery    
        POWER_SUPPLY_PROP_HEALTH,        
        POWER_SUPPLY_PROP_PRESENT,        
        POWER_SUPPLY_PROP_TECHNOLOGY,        
        POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
        POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
        POWER_SUPPLY_PROP_VOLTAGE_NOW,        
        POWER_SUPPLY_PROP_CAPACITY,        
        POWER_SUPPLY_PROP_CURRENT_NOW,        
        POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
        POWER_SUPPLY_PROP_TEMP,            
        POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
    };
    
    static struct device_attribute power_supply_attrs[] = {
        /* Properties of type `int' */
        POWER_SUPPLY_ATTR(status),  //表示attr.name = "status"
        POWER_SUPPLY_ATTR(charge_type),
        ...
    }
4. parse_uevent@vendor/semc/hardware/power/charge-log/battery_logging/battery_logging.c会分析从kernel传上来的power uevent格式
    发送该uevent的path  '\0'
    POWER_SUPPLY_NAME=battery '\0'
    POWER_SUPPLY_CHARGING_ENABLE=%d '\0'
    POWER_SUPPLY_CHARGING_STATUS=%d '\0'    
    ...

5. 顺便分析下UEvent如何传递给user space?
    a. @kernel/drivers/power/qpnp-charger.c中只要有任何关于charger的变化,比如charger的各种irq handler
    b. @kernel/drivers/power/qpnp-bms.c中calculate_soc_from_voltage和calculate_state_of_charge
    c. @kernel/arch/arm/boot/dts/msm-pm8226.dtsi中有pm8226_bms的device定义,msm8226-cn3ii.dtsi中有pm8226_bms的定义补充
        其中没有 qcom,use-voltage-soc 的定义所以chip->use_voltage_soc=false, 也就是只用calculate_state_of_charge计算soc
        顺便chip->use_external_rsense=true,表示用外部的rsense.
    d. @kernel/driver/power/power_supply_core.c中的power_supply_changed会被以上内容调用
        schedule_work(&psy->changed_work);
    e. power_supply_changed_work@kernel/driver/power/power_supply_core.c
        kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); //action=KOBJ_CHANGE
    f. kobject_uevent_env@kernel/lib/kobject_uevent.c
        static const char *kobject_actions[] = {
            [KOBJ_ADD] =        "add",
            [KOBJ_REMOVE] =        "remove",
            [KOBJ_CHANGE] =        "change",
            [KOBJ_MOVE] =        "move",
            [KOBJ_ONLINE] =        "online",
            [KOBJ_OFFLINE] =    "offline",
        };
        以下为添加到uevent buffer中发送的内容
        /* default keys */
        retval = add_uevent_var(env, "ACTION=%s", action_string);//action_string="change"        
        retval = add_uevent_var(env, "DEVPATH=%s", devpath);//devpath = kobject_get_path(kobj, GFP_KERNEL);
        retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);//也就是uevent 的name
        retval = uevent_ops->uevent(kset, kobj, env); //会调用到power_supply_uevent@kernel/drivers/power/power_supply_sysfs.c
                                //也就是会添加具体的power battery信息到uevent buffer中了
        retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum); //增加uevent发送的计数,
                    //KERNEL_ATTR_RO(uevent_seqnum); 用cat sys/kernel/uevent_seqnum 可以查看
        CONFIG_NET=y被定义,以下代码是发送uevent到userspace 的关键代码
        #if defined(CONFIG_NET)
            /* send netlink message */
            list_for_each_entry(ue_sk, &uevent_sock_list, list) { //uevent_net_init会初始化一个uevent_sock,也只有一个
                if (!netlink_has_listeners(uevent_sock, 1))
                    continue;

                /* allocate message with the maximum possible size */
                len = strlen(action_string) + strlen(devpath) + 2;
                skb = alloc_skb(len + env->buflen, GFP_KERNEL);
                if (skb) {
                    retval = netlink_broadcast_filtered(uevent_sock, skb,
                                        0, 1, GFP_KERNEL,
                                        kobj_bcast_filter,
                                        kobj);
                } else
                    retval = -ENOMEM;
            }
        #endif
6. Battery充满的识别  
    1). @kernel/arch/arm/boot/dts/msm-pm8226.dtsi和msm8226-cn3ii.dtsi
    qcom,chg-chgr@1000 {
    status = "disabled";
    reg = <0x1000 0x100>;
    interrupts =    <0x0 0x10 0x0>,
            <0x0 0x10 0x1>,
            <0x0 0x10 0x2>,
            <0x0 0x10 0x3>,
            <0x0 0x10 0x4>,
            <0x0 0x10 0x5>,
            <0x0 0x10 0x6>,
            <0x0 0x10 0x7>;  //具体的含义?

    interrupt-names =    "vbat-det-lo",
                "vbat-det-hi",
                "chgwdog",
                "state-change",
                "trkl-chg-on",
                "fast-chg-on",
                "chg-failed",
                "chg-done";
    };
    2). qpnp_chg_hwinit@kernel/drivers/power
    chip->chg_done_irq = spmi_get_irq_byname(chip->spmi,spmi_resource, "chg-done");
       如何触发"chg-done"这个interrupt?     
    3).获取当前充电状态
        rc = qpnp_chg_read(chip, &chgr_sts,INT_RT_STS(chip->chgr_base), 1);    
        如果chgr_sts&CHG_DONE_IRQ == 1则表示充电完成
    4).http://review.sonyericsson.net/#/c/480849, workaround for fake charge done    
6. 如何查看当前的charger信息?
    /sys/devices/qpnp-charger-eab16c00
7. 手机/sys/class/power_supply/battery和bms和usb的来源
    1)qpnp_charger_proble@kernel/drivers/power/qpnp-charger.c
        if (chip->bat_if_base){
            chip->batt_psy.name = "battery";
            chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
            chip->batt_psy.properties = msm_batt_power_props;//所选择的属性
            
            rc = power_supply_register(chip->dev, &chip->batt_psy);//注册到/sys/class/power_supply/battery
        }
    2)qpnp_bms_probe@kernel/drivers/power/qpnp-bms.c
        chip->bms_psy.name = "bms";
        chip->bms_psy.type = POWER_SUPPLY_TYPE_BMS;
        chip->bms_psy.properties = msm_bms_power_props;
    
        rc = power_supply_register(chip->dev, &chip->bms_psy);//注册到/sys/class/power_supply/bms
    3)msm_otg_proble@kernel/drivers/usb/otg, 注意CONFIG_USB_MSM_OTG的定义在kernel/drivers/usb/gadget/Kconfig: config USB_CI13XXX_MSM-->select USB_MSM_OTG
        motg->usb_psy.name = "usb";
        motg->usb_psy.type = POWER_SUPPLY_TYPE_USB;
        motg->usb_psy.supplied_to = otg_pm_power_supplied_to;
        
        msm_otg_register_power_supply(pdev, motg))->power_supply_register(&pdev->dev, &motg->usb_psy);
    4)充电器的识别过程
        msm_chg_detect_work@kernel/drivers/usb/otg/msm_otg.c 主要完成充电器类别的识别过程
        ->msm_otg_notify_chg_type@msm_otg.c
            ->power_supply_set_supply_type(psy, charger_type)@msm_otg.c;//psy=motg->usb_psy
            ->psy->set_property(psy, POWER_SUPPLY_PROP_TYPE,&ret); //@kernel/drivers/power/power_supply_core.c, 最后会call
            ->otg_power_set_property_usb@msm_otg.c 但是该函数中没有POWER_SUPPLY_PROP_TYPE属性的设置??
        ->queue_work(system_nrt_wq, &motg->sm_work);->msm_otg_sm_work[otg->phy->state=OTG_STATE_B_IDLE]
            -->msm_otg_notify_charger->msm_otg_notify_power_supply->power_supply_set_online//设置是否online

四、Audio Jack Porting
0. HW Guys: Wang, Junchao(9601), Liu,Xun
1. kernel/sound/soc/msm/msm8226.c;
   kernel/sound/soc/codecs/wcd9306.c,Wcd9xxx-mbhc.c
    tapan_slimbus_irq
2. adb shell 'echo -n "file wcd9xxx-mbhc.c +p" > /sys/kernel/debug/dynamic_debug/control'

3. wcd9xxx_insert_detect_setup@Wcd9xxx-mbhc.c
    0x6c-->0x68  //参考80-NC836-2 WCD9306 AUDIO CODEC SOFTWARE INTERFACE.pdf 中0x14B MBHC_INSERT_DET_STATUS寄存器的说明,PLUG_TYPE 0:NC 1:NO (注意原来的文档有错)
    Audio Jack不插入耳机时MBHC_HSDET是低,插入耳机时MBHC_HSDET是高,所以是Normal Close(NC)类型的Audio Jack
4. @kernel/sound/soc/msm/msm8226.c;
    S(v_no_mic, 30);
    S(v_hs_max, 1650);
5. @kernel/sound/soc/codecs/Wcd9xxx-mbhc.c
    wcd9xxx_codec_get_plug_type->
    wcd9xxx_find_plug_type
6. Audio jack检测耳机的流程
在函数wcd9xxx_mbhc_decide_swch_plug中如果检测到是headset则不重复多次检测,如果是headphone则需要
wcd9xxx_schedule_hs_detect_plug(mbhc,&mbhc->correct_plug_swch);启动重复检测以确认正确
插入headphone耳机
<7>[  105.233608] wcd9xxx_mech_plug_detect_irq: enter
<7>[  105.233625] wcd9xxx_swch_irq_handler: enter
<7>[  105.238692] wcd9xxx_swch_irq_handler: Acquiring BCL
<7>[  105.238706] wcd9xxx_swch_irq_handler: Acquiring BCL done
<7>[  105.238864] wcd9xxx_swch_irq_handler: Current plug type 0, insert 1
<7>[  105.238876] wcd9xxx_cancel_hs_detect_plug: Canceling correct_plug_swch
<7>[  105.238885] wcd9xxx_cancel_hs_detect_plug: Release BCL
<7>[  105.238897] wcd9xxx_cancel_hs_detect_plug: Acquiring BCL
<7>[  105.238907] wcd9xxx_cancel_hs_detect_plug: Acquiring BCL done
<7>[  105.239014] wcd9xxx_mbhc_detect_plug_type: enter
<7>[  105.497933] wcd9xxx_mbhc_decide_swch_plug: enter
<7>[  105.498378] wcd9xxx_codec_get_plug_type: enter
<7>[  105.501350] wcd9xxx_mbhc_setup_hs_polling: enter
...
<7>[  105.590299] wcd9xxx_find_plug_type: DCE #0, fc06, V 0008(0008), GND 0, VDDIO 0, HPHL 1 TYPE 2
<7>[  105.590317] wcd9xxx_find_plug_type: DCE #1, fc06, V 0008(0008), GND 0, VDDIO 0, HPHL 1 TYPE 2
<7>[  105.590332] wcd9xxx_find_plug_type: DCE #2, fc06, V 0008(0008), GND 1, VDDIO 0, HPHL 1 TYPE 2
<7>[  105.590346] wcd9xxx_find_plug_type: DCE #3, fc06, V 0008(0008), GND 0, VDDIO 0, HPHL 1 TYPE 2
<7>[  105.590359] wcd9xxx_find_plug_type: Plug type 2 detected
<7>[  105.590368] wcd9xxx_codec_get_plug_type: leave
<7>[  105.590655] wcd9xxx_report_plug: enter insertion 1 hph_status 0
<7>[  105.590666] wcd9xxx_report_plug: Reporting insertion 1(1)
继续进行检测wcd9xxx_correct_swch_plug,用以确认检测正确,如果检测到是PLUG_TYPE_HEADPHONE则需要在 HS_DETECT_PLUG_TIME_MS=5000毫秒内多次检测
<7>[  105.717321] wcd9xxx_correct_swch_plug: Acquiring BCL
<7>[  105.717335] wcd9xxx_correct_swch_plug: Acquiring BCL done
<7>[  105.717344] wcd9xxx_codec_get_plug_type: enter
<7>[  105.720967] wcd9xxx_mbhc_setup_hs_polling: enter
<7>[  105.804823] wcd9xxx_find_plug_type: DCE #0, fc08, V 0010(0010), GND 0, VDDIO 0, HPHL 1 TYPE 2
<7>[  105.804841] wcd9xxx_find_plug_type: DCE #1, fc08, V 0010(0010), GND 0, VDDIO 0, HPHL 1 TYPE 2
<7>[  105.804856] wcd9xxx_find_plug_type: DCE #2, fc08, V 0010(0010), GND 1, VDDIO 0, HPHL 1 TYPE 2
<7>[  105.804871] wcd9xxx_find_plug_type: DCE #3, fc08, V 0010(0010), GND 0, VDDIO 0, HPHL 1 TYPE 2
<7>[  105.804884] wcd9xxx_find_plug_type: Plug type 2 detected
<7>[  105.804892] wcd9xxx_codec_get_plug_type: leave
<7>[  105.804901] wcd9xxx_correct_swch_plug: Release BCL
<7>[  105.804912] wcd9xxx_correct_swch_plug: attempt(1) current_plug(2) new_plug(2)
<7>[  105.804922] Good headphone detected, continue polling
。。。

拔出耳机
<7>[ 1010.417287] wcd9xxx_mech_plug_detect_irq: enter
<7>[ 1010.417304] wcd9xxx_swch_irq_handler: enter
<7>[ 1010.422617] wcd9xxx_swch_irq_handler: Acquiring BCL
<7>[ 1010.422635] wcd9xxx_swch_irq_handler: Acquiring BCL done
<7>[ 1010.422809] wcd9xxx_swch_irq_handler: Current plug type 2, insert 0
<7>[ 1010.422820] wcd9xxx_cancel_hs_detect_plug: Canceling correct_plug_swch
<7>[ 1010.422830] wcd9xxx_cancel_hs_detect_plug: Release BCL
<7>[ 1010.422842] wcd9xxx_cancel_hs_detect_plug: Acquiring BCL
<7>[ 1010.422851] wcd9xxx_cancel_hs_detect_plug: Acquiring BCL done
<7>[ 1010.422863] wcd9xxx_report_plug: enter insertion 0 hph_status 1
<7>[ 1010.422876] wcd9xxx_report_plug: Reporting removal 1(0)
<7>[ 1010.422911] wcd9xxx_set_and_turnoff_hph_padac PA is off
<7>[ 1010.445247] __hphocp_off_report: clear ocp status 80
<7>[ 1010.445261] __hphocp_off_report: clear ocp status 40
<7>[ 1010.445272] wcd9xxx_insert_detect_setup: Setting up insert detection
<7>[ 1010.445468] wcd9xxx_report_plug: leave hph_status 0
<7>[ 1010.446056] wcd9xxx_swch_irq_handler: Release BCL
<7>[ 1010.446067] wcd9xxx_swch_irq_handler: leave
<7>[ 1010.446079] wcd9xxx_mech_plug_detect_irq: leave 1
<7>[ 1010.446408] wcd9xxx_hs_insert_irq: enter
<7>[ 1010.446418] wcd9xxx_hs_insert_irq: Acquiring BCL
<7>[ 1010.446427] wcd9xxx_hs_insert_irq: Acquiring BCL done
<7>[ 1010.446880] wcd9xxx_hs_insert_irq_swch: Removal
<7>[ 1010.447010] wcd9xxx_hs_insert_irq: Release BCL

插入Headset
<7>[ 1084.338102] wcd9xxx_mech_plug_detect_irq: enter
<7>[ 1084.338119] wcd9xxx_swch_irq_handler: enter
<7>[ 1084.343428] wcd9xxx_swch_irq_handler: Acquiring BCL
<7>[ 1084.343447] wcd9xxx_swch_irq_handler: Acquiring BCL done
<7>[ 1084.343619] wcd9xxx_swch_irq_handler: Current plug type 0, insert 1
<7>[ 1084.343631] wcd9xxx_cancel_hs_detect_plug: Canceling correct_plug_swch
<7>[ 1084.343641] wcd9xxx_cancel_hs_detect_plug: Release BCL
<7>[ 1084.343653] wcd9xxx_cancel_hs_detect_plug: Acquiring BCL
<7>[ 1084.343662] wcd9xxx_cancel_hs_detect_plug: Acquiring BCL done
<7>[ 1084.343676] wcd9xxx_mbhc_detect_plug_type: enter
<7>[ 1084.598139] wcd9xxx_mbhc_decide_swch_plug: enter
<7>[ 1084.598340] wcd9xxx_codec_get_plug_type: enter
<7>[ 1084.601291] wcd9xxx_mbhc_setup_hs_polling: enter
<7>[ 1084.676366] wcd9xxx_find_plug_type: DCE #0, 026d, V 1306(1306), GND 0, VDDIO 0, HPHL 1 TYPE 1
<7>[ 1084.676384] wcd9xxx_find_plug_type: DCE #1, 02b7, V 1365(1365), GND 0, VDDIO 0, HPHL 1 TYPE 1
<7>[ 1084.676399] wcd9xxx_find_plug_type: DCE #2, 02bb, V 1368(1368), GND 1, VDDIO 0, HPHL 1 TYPE 1
<7>[ 1084.676414] wcd9xxx_find_plug_type: DCE #3, 02bb, V 1368(1368), GND 0, VDDIO 0, HPHL 1 TYPE 1
<7>[ 1084.676427] wcd9xxx_find_plug_type: Plug type 1 detected
<7>[ 1084.676436] wcd9xxx_codec_get_plug_type: leave
<7>[ 1084.676720] wcd9xxx_mbhc_decide_swch_plug: Valid plug found, determine plug type 1
<7>[ 1084.676733] wcd9xxx_find_plug_and_report: enter current_plug(0) new_plug(1)
<7>[ 1084.676745] wcd9xxx_report_plug: enter insertion 1 hph_status 0
<7>[ 1084.676755] wcd9xxx_report_plug: Reporting insertion 3(3)
<7>[ 1084.676802] wcd9xxx_insert_detect_setup: Setting up removal detection
<7>[ 1084.676955] wcd9xxx_report_plug: leave hph_status 3
<7>[ 1084.777467] wcd9xxx_start_hs_polling: enter
<7>[ 1084.779191] wcd9xxx_start_hs_polling: leave
<7>[ 1084.779202] wcd9xxx_find_plug_and_report: leave
<7>[ 1084.779212] wcd9xxx_mbhc_decide_swch_plug: leave
<7>[ 1084.779221] wcd9xxx_mbhc_detect_plug_type: leave
<7>[ 1084.779229] wcd9xxx_swch_irq_handler: Release BCL
<7>[ 1084.779238] wcd9xxx_swch_irq_handler: leave
<7>[ 1084.779250] wcd9xxx_mech_plug_detect_irq: leave 1




五、如何flash开发板
1. lunch
2. fastboot flash boot boot.img, flash kernel的image
   fastboot flash system system.img, flash system的image

六、SIM card detection
1. kernel/arch/arm/boot/dts/msm8226-cn3ii.dts 中有gpio_keys节点
    gpio_keys {
        compatible = "gpio-keys"
        input-name = "gpio-keys"  //表示输出event的名字
        ...
    }
   kernel/drivers/input/keyboard/gpio_keys.c会解析 gpio-keys 的dts
   增加sim card detection 的gpio key
   sim1 {
    ompatible = "gpio-keys";    
    input-name = "sim1-detection";    
    
    sim1_det {    
        label = "sim1-detection";    
        gpios = <&msmgpio 60 0x0>;    //msmgpio表示使用的是8226的gpio, 60表示GPIO60
        linux,input-type = <5>;        //define EV_SW 0x05
        linux,code = <7>;        //#define SW_JACK_PHYSICAL_INSERT 0x07  /* set = mechanical switch set */
        gpio-key,wakeup;    
        debounce-interval = <10>;    
    };    
   };
2. http://review.sonyericsson.net/#/c/518583
3. https://wiki.sonyericsson.net/androiki/FP8615_Support_for_SIM_detection_for_Zeus_Docomo_Definition_Report

七、Vibrator
1. 检查是否工作 #echo 1000 > /sys/class/timed_output/vibrator/enable

八、GPIO设置的确认
1. msmgpio@kernel/arch/arm/boot/dts/msm8226-cn3ii.dtsi
    msmgpio: gpio@fd510000 {
        compatible = "qcom,msm-gpio";
        interrupt-controller;
        #interrupt-cells = <2>;
        reg = <0xfd510000 0x4000>;
        gpio-controller;
        #gpio-cells = <2>;
        ngpio = <117>;
        interrupts = <0 208 0>;
        qcom,direct-connect-irqs = <8>;
    };  
2. pm8226_gpios@kernel/arch/arm/boot/dts/msm8226-cn3ii.dts
    &pm8226_gpios {
        gpio@c000 { /* GPIO 1 */
        /* XO_PMIC_CDC_MCLK enable for tapan codec */
        qcom,mode = <1>;        /* Digital output */
        qcom,output-type = <0>; /* CMOS logic */
        qcom,pull = <5>;        /* QPNP_PIN_PULL_NO*/
        qcom,vin-sel = <2>;     /* QPNP_PIN_VIN2 */
        qcom,out-strength = <3>;/* QPNP_PIN_OUT_STRENGTH_HIGH */
        qcom,src-sel = <2>;     /* QPNP_PIN_SEL_FUNC_1 */
        qcom,master-en = <1>;   /* Enable GPIO */
        };  

        gpio@c100 { /* GPIO 2 */
        qcom,mode = <1>;
        qcom,output-type = <0>;
        qcom,pull = <5>;
        qcom,vin-sel = <2>;
        qcom,out-strength = <3>;
        qcom,src-sel = <2>;
        qcom,master-en = <1>;
        };  
    ...    
    }
3. http://review.sonyericsson.net/#/c/518583/
4. GPIO 如何配置成I2C
    1)@kernel/arch/arm/boot/dts/msm8226-cn3ii.dtsi, 以下设置i2c的参数
        i2c@f9926000 { /* BLSP-1 QUP-4 */
            cell-index = <0>;
            compatible = "qcom,i2c-qup";
            reg = <0xf9926000 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;
            reg-names = "qup_phys_addr";
            interrupts = <0 98 0>;
            interrupt-names = "qup_err_intr";
            qcom,i2c-bus-freq = <400000>;
            qcom,i2c-src-freq = <400000>;
            ti_lm3630_bl@36{
                compatible = "ti,lm3630_bl";
                reg = <0x36>;
                gpio_hw_en = <&msmgpio 27 0>;
                pwm_feedback = <0x08 0x10>;
                ctl = <0x00 0x00>;
                fsc = <20200 20200>;
                iname = "lm3630-lcd-bl-1","lm3630-lcd-bl-2";
                connected = <0x01 0x01>;
                bank = <0x00 0x01>;
                filter_str = <0x03>;
                ovp_boost = <0x24>;
                led_fault = <0x00>;
                intf_name = "lm3630-lcd-bl-1","lm3630-lcd-bl-2";
                brightness = <255 255>;
            };
        };
    2)kernel/arch/arm/mach-msm/board-sony_cn3ii-gpiomux.c,以下配置GPIO14和15为I2C的功能,即GPIOMUX_FUNC_3
    static struct msm_gpiomux_config msm_blsp_configs[] __initdata = {
        {
            .gpio      = 14,    /* BLSP1 QUP4 I2C_SDA */
            .settings = {
                [GPIOMUX_SUSPENDED] = &gpio_i2c_config,
            },
        },
        {
            .gpio      = 15,    /* BLSP1 QUP4 I2C_SCL */
            .settings = {
                [GPIOMUX_SUSPENDED] = &gpio_i2c_config,
            },
        },
    }
    static struct gpiomux_setting gpio_i2c_config = {
        .func = GPIOMUX_FUNC_3,
        .drv = GPIOMUX_DRV_2MA,
        .pull = GPIOMUX_PULL_NONE,
    };
八、GPIO Check
1. AP GPIO:
    kernel/arch/arm/mach-msm/board-sony_cn3ii-gpiomux.c //这其中只是定义了某个GPIO的配置,如果程序需要使用,还要gpio_request
        /
   PMIC GPIO:
    kernel/arch/arm/boot/dts/msm-pm8226.dtsi中有pm8226_gpios的定义    
    kernel/arch/arm/boot/dts/msm8226-cn3ii.dts中的&pm8226_gpios,
        kernel/drivers/gpio/gpnp-pin.c 中会解析pm8226_gpios dts.
2. HW Guy: Liu, Helson for GPIO setting.
3. GPIO设置文档http://metadoc.sonyericsson.net/login_link.asp?doc=1/10262-17/FCP1191405/6&rev=latest
4. 还可以在 cat sys/kernel/debug/gpio 确认当前GPIO的状态


九、battery logging port
1. device/somc/yangtze/BoardConfig.mk
    # battery logging   //打开编译libbatterylogging的开关
    ifneq ($(TARGET_BUILD_VARIANT), user)
    SONY_CFG_LIBBATTERYLOGGING := true
    endif
   device/somc/yangtze/platform.mk
    ->PRODUCT_PACKAGES += battery_logging  //添加battery_logging编译目录

   device/somc/yangtze/files/init.sony-platform.rc
    ONLY_IN_DEBUG(  //设置是否 实时开始和停止运行battery_logging, 其定义在/build/core/definitions.mk (-DONLY_IN_DEBUG)
            //实际上表示只要编译的版本是eng和userdebug版本都需要执行
    on property:battery_log=1    //init.c分析时会把property:battery_log 当作一个action 的条件加入trigger链表,当通过setprop改变
                    //battery_log的属性时会触发该action: start/stop battery_logging
                    //具体的代码分析:property_set@system/core/init/property_service.c
        start battery_logging
    on property:battery_log=0    //setprop battery_log 0 可以停止battery_logging service,
        stop battery_logging)

    ONLY_IN_VARIANT(eng,        //其定义在/build/core/definitions.mk (-DONLY_IN_VARIANT)
    on property:dev.bootcomplete=1
        start battery_logging)

    ONLY_IN_VARIANT(userdebug,
    on property:dev.bootcomplete=1
        start battery_logging)

    ONLY_IN_DEBUG(
    service battery_logging /system/bin/battery_logging
        disabled) //disable表示需要手动启动
2. vendor/semc/hardware/device/yangtze/libbatterylogging
    ->out/target/product/cn3ii/obj/STATIC_LIBRARIES/libbatterylogging_intermediates/libbatterylogging.a
3. vendor/semc/hardware/power/charge-log/battery_logging
    ->out/target/product/cn3ii/system/bin/battery_logging
4. #battery_logging -p //输出所有的需要输出信息的名称和路径
  #battery_logging -d //输出运行时的log
十、device tree source(DTS)
0. http://devicetree.org/Main_Page
1. *.dts/dtsi 文件一般位于 kernel/arch/arm/boot/dts,比如msm8226-cn3ii.dts  msm8226-cn3ii.dtsi
2. 如何决定用那个*.dts文件?
   @kernel/arch/arm/mach-msm/Makefile.boot
    # MSM8226
       zreladdr-$(CONFIG_ARCH_MSM8226)  := 0x00008000 //是啥地址?
    dtb-$(CONFIG_MACH_SONY_CN3II) += msm8226-cn3ii.dtb
    @kernel/arch/arm/configs/cn3ii_defconfig    
    CONFIG_ARCH_MSM8226=y
    CONFIG_MACH_SONY_CN3II=y

3. 编译dts为dtb的工具为DTC,位于kernel/scripts/dtc
    hostprogs-y := dtc //@Makefile中,该行表示用hostprogs编译生成dtc
    最后生成的dtc可执行文件位于out/target/product/cn3ii/obj/KERNEL_OBJ/scripts/dtc/dtc
4.用dtc编译*.dts生成的*.dtb文件位于
    out/target/product/cn3ii/obj/KERNEL_OBJ/arch/arm/boot/msm8226-cn3ii.dtb
    存放在手机什么地方?
    bootloader如何读取?如何传递给kernel?
    head-common.S@kernel\arch\arm\kernel中有    
    .long    __atags_pointer @ r6 //表示保存dtb的地址通过r6传递给kernel

5. kernel中解析DTS
    setup_machine_fdt@kernel/arch/arm/kernel/devtree.c
    struct boot_param_header *devtree = phys_to_virt(dt_phys); //注意boot_param_header结构
    setup_machine_fdt将分析dtb最后所有devices生成树,如何和以前的版本结果就一样了
6. kernel中各个device和driver谁先加载?

十一、charge only porting
1. device/somc/common/sony-generic.mk 添加
    -include vendor/semc/hardware/device/device.mk
2. device/somc/yangtze/files/init.sony-platform.rc 中添加
    on early-boot
        exec /system/bin/chargemon
        //write /sys/class/power_supply/battery/enable_stop_charging_at_low_battery 1
   device/somc/yangtze/BoardConfig.mk 增加
    # Device sysfs path definitions
    TARGET_BACKLIGHT_DEVICE_PATH := /sys/class/leds/wled:backlight/brightness
    TARGET_LED_R_DEVICE_PATH := /sys/class/leds/led:rgb_red/brightness
    TARGET_LED_G_DEVICE_PATH := /sys/class/leds/led:rgb_green/brightness
    TARGET_LED_B_DEVICE_PATH := /sys/class/leds/led:rgb_blue/brightness
    TARGET_DISPLAY_DEVICE_PATH := /sys/devices/mdss_dsi_panel
3. vendor/semc/hardware/device/目录下添加device.mk 和 cn3ii/cn3ii.mk, 添加以下关键内容
    @device.mk
     DEVICE_CONFIG=vendor/semc/hardware/device
     # Call product specific makefiles.
     -include $(DEVICE_CONFIG)/$(TARGET_PRODUCT)/$(TARGET_PRODUCT).mk
     # Call platform specific makefiles.
     -include $(DEVICE_CONFIG)/yangtze/platform.mk
    @cn3ii.mk
    SONY_CFG_CHARGEMON := true
    PRODUCT_PACKAGES += chargemon
  vendor/semc/hardware/device/yangtze$ 目录下添加chargemon_common.mk和platform.mk
     @platform.mk    
    PLATFORM_DEVICE_CONFIG=vendor/semc/hardware/device/yangtze
    # chargemon
    include $(PLATFORM_DEVICE_CONFIG)/chargemon_common.mk
6. 注意:
    *.rc 文件放在ramdisk中,而一般打包在boot.img或kernel.sin中
    修改完*.mk文件后,需要重新lunch才会生效
7. 如何debug
    #define DEBUG_LOG //在log.h中定义,打开log输出到手机 data/cm.log
    LOGM(2); //在main代码中打开log输出mode,

十二、电池充电的几个关键过程
Trigle charge:
0. 当电池电压很低时,如何charge?
1. Vbat>3.42V,直接进入sys boot; Vbat<3.42时进入SBL的充电逻辑,Vbat<2.9v进入SBL的trigle charge(200mA充电),当超过2.9v时,进入SBL的fast charge模式(500mA充电),一直充直到Vbat>3.7V后,boot sys.
进入trigle charge mode,然后当电池电压充到3.7V(system weak threshod, Vweak)则退出trigle charge mode,启动kernel
2. Auto trigle charge(ATC)的触发条件: 电池电压< Vweak系统需要启动的最低电压,然后 充电电流被限制在100mA;手机不能用100mA的电流启动。
3. Two-step ATC 目前没有用,当电池电压特别低时分两步充电
    a. Vbat < Vtrkl, Buck off, Bat FET closed, 充电电流50mA-200mA,小电流充电, Vtrkl一般设置区间为2.05V-2.8V
    b. Vweak > Vbat > Vtrkl, Buck on, Bat FET closed,充电电流300mA-500mA, Vweak一般设置区间为2.1V-3.6V
4. 用FLCB 充电,目前CN3-ii用该方案
    Buck on, Bat FET open, 用VCHG直接给电池充电,不经过Bat FET.一直用ITRKL电流给电池充电,该Itrkl电流一般多大?
    当Vbat > Vweak后,系统就启动了

Fast charging
1. CC , Vweak < Vbat < Vbat_max, 用最大电流IBAT_MAX 给电池充电,IBAT_MAX的一般取值区间300mA-3000mA
2. CV, Vbat > Vbate_max, 逐渐降低充电电流Ibat,直到给电池的充电电压VPH_PWR==VDD_MAX,VDD_MAX一般设置区间3.4V-4.5V

Charging termination[依据的是充电电流大小]
1. 结束条件:Ibat(充电电流) < Iterm (with deglitching),Iterm一般设置区间35mA-276mA
2. 结果:Bat FET Open, Buck on 这样仍然可以给系统供电。如果系统需要的电流>Buck能提供的,那么Battery如何供电给系统?Bat FET Open也能反向把电流从电池输送给系统吗?

Automatic Recharge[依据的是电池电压大小]
1. 开始条件:Vbat < Vbat_det, 一般Vbat_det设置区间是3.3V-4.7V
2. 结果,又进入Fast charging阶段

OVP(过压保护)
1. 从外部USB 进入的电压保护范围[5v,28v],可以设置该指?如果大于这最大进入电压,则第一级的保护电路生效。
2. 进入第二级保护电路,如果通过1进入的电压,大于预先SSBI-programmable设定的门限(该门限设置区间[5.5v,7v]),如果电压大于该门限,则OVP FET会OPEN,反之。



十三、如何读写充电寄存器
1. 读寄存器
   echo 0xccc > /sys/kernel/debug/spmi/spmi-0/address
   echo 0xccc > /sys/kernel/debug/spmi/spmi-0/count
   cat /sys/kernel/debug/spmi/spmi-0/data
   写寄存器
   echo 0xccc > /sys/kernel/debug/spmi/spmi-0/address
   echo 0xccc > /sys/kernel/debug/spmi/spmi-0/data    
2. 如何控制使用内部或外部电阻rsense.
   0x3848 IADC2_BMS_ADC_CH_SEL_CTL
   0x0: INTRSNS
   0x1: EXTRSNS
   @kernel/arch/arm/boot/dts/msm8226-cn3ii.dtsi中
   &pm8226_bms {
       qcom,use-external-rsense = <1>;
   }

   qcom,pm8226@0 {
    iadc@3600 {
            qcom,rsense = <10000000>;
    };   
   };

3. 另外Huashan PM8921的路径为: sys/kernel/debug/pm8921-dbg

十四、增加识别wall charger
http://review.sonyericsson.net/#/c/534625/

十五、如何disable/enable充电
1. [User]SysFS: /sys/class/power_supply/battery/charging_enabled;
   在charge only程序中,会通过miscTA 2312的值,来判断是否diable/enable充电(1:disable;非1:enable)
   disableCharging@vendor/semc/hardware/power/chargemon/src/main.c
   hw_disableCharging@vendor/semc/hardware/power/chargemon/src/main.c
    ->hw_writeSysfs(DISABLE_CHARGING_PATH, DISABLE_CHARGING); //DISABLE_CHARGING_PATH=/sys/class/power_supply/battery/charging_enabled
  [Kernel]的对应处理 qpnp_batt_power_set_property@kernel/drivers/power/qpnp-charger.c
    chip->charging_disabled = !(val->intval);
    qpnp_chg_charge_en(chip, !chip->charging_disabled); //操作0x1049的bit7,主要控制FSM是否工作
    qpnp_chg_force_run_on_batt(chip, chip->charging_disabled);//0x1049的bit7,主要控制所有与电池相关的功能(trikle charger, buck reg, batfet)是否工作。
2. Debugmenu中如何控制充电
   @vendor/semc/packages/apps/debugmenu/src/com/sonyericsson/debugmenu/charging.java中会操作MiscTA 2312, 写入1 disable或0 enable;
   然后下次启动运行charge only的时候会根据该Misc TA的值,enable/disbale充电。


十六、Charge only中的重要控制
1. charge only 是否进入的控制。
   miscTA 2311, 0: 可以进入charge only; 2: 这次不让进入,但下次让进入; 其他(1):不让进入charge only.
2. 在charge only程序中是否充电
   miscTA 2312的值,来判断是否diable/enable充电(1:disable;非1:enable)
   /sys/class/power_supply/battery/charging_enabled;     
3. Android USB device的控制
   sys/class/android_usb/android0/enable 控制是否enable ADB功能
   sys/class/android_usb/android0/functions, 设置USB支持的功能,在属性sys.usb.config,两者一致
   sys/class/android_usb/android0/idProduct or idVendor, 重置USB的ID,但必须先disable USB
   @vendor/semc/hardware/device/yangtze/chargemon_common.mk中有如下定义
    USB_VENDOR_ID := 0fce
    USB_PRODUCT_ID := e000
    USB_ACCESSORY_VID := 18d1
    USB_ACCESSORY_PID := 2d00
    MASS_STORAGE := mass_storage
    ACCESSORY := accessory
    USB_ENABLE_PATH := /sys/class/android_usb/android0/enable
    USB_SETFUNC_PATH := /sys/class/android_usb/android0/functions
    USB_PRODUCT_ID_PATH := /sys/class/android_usb/android0/idProduct
    USB_VENDOR_ID_PATH := /sys/class/android_usb/android0/idVendor
   @device/somc/cn3ii/System.prop,中有 ro.usb.pid_suffix=1A9 重新设置USB PID
   $在Ubantu上lsusb可以所有的usb device,其中就包括
   Bus 001 Device 111: ID 0fce:51a9 Sony Ericsson Mobile Communications AB ,
   
   idProduct,idVendor,function是在那里设定的?
   @device/somc/common/files/init.sony.usb.rc
    on property:sys.usb.config=mtp,adb //任何sys.usb.config的改变,就会触发后面的动作
    exec /init.usbmode.sh
    start adbd
    setprop sys.usb.state ${sys.usb.config}
   @device/somc/common/files/init.usbmode.sh
          VENDOR_ID=0FCE
    PID_SUFFIX_PROP=$(/system/bin/getprop ro.usb.pid_suffix)
    USB_FUNCTION=$(/system/bin/getprop sys.usb.config)
    get_pid_prefix ${USB_FUNCTION}中确定PID_PREFI的值,实际上是根据sys.usb.config
    PID=${PID_PREFIX}${PID_SUFFIX_PROP},其中PID_PREFIX表示sony自己定义的功能 5:mtp,adb;0:mtp
    echo ${VENDOR_ID} > /sys/class/android_usb/android0/idVendor
    echo ${PID} > /sys/class/android_usb/android0/idProduct
   @device/somc/common/sony-generic.mk
    ADDITIONAL_DEFAULT_PROPERTIES += persist.sys.usb.config=mtp 但是只设置了mtp功能,没有adb功能?
   @frameworks/base/services/java/com/android/server/usb/usbdevicemanager.java
    函数setEnabledFunctions(..) 会根据setting UI的设置改变persist.sys.usb.config的值
                if (mAdbEnabled) {
                    functions = addFunction(functions, UsbManager.USB_FUNCTION_ADB);
                } else {
                    functions = removeFunction(functions, UsbManager.USB_FUNCTION_ADB);
                }
        SystemProperties.set("persist.sys.usb.config", functions);
   有个问题,设置persist.sys.usb.config是如何传到sys.usb.config的???
   @system/core/rootdir/init.usb.rc
    on property:persist.sys.usb.config=*
        setprop sys.usb.config none
        setprop sys.usb.config ${persist.sys.usb.config} //两者会同步
4. 可以优化的地方
    a. 现在charge eoc后,如果拔掉充电器,则会上报report key KEY_F24, charge only process会在
        hw_waitForUserAction@vendor/semc/hardware/power/chargemon/src/hw.c中
        hw_wakeUnlock();
        i = select(max_fd + 1, &listen, NULL, NULL, &delay);//等待5秒,也只有在这5秒中,系统才有机会suspend
        hw_wakeLock();
           而上报KEY_F24事件,主要是解决在charge only模式下,系统深度睡眠,拔掉充电器时,charge only不会退出关机
    b. 而在 check_plugin_wakelock@kernel/drivers/power/qpnp-charger.c中,当拔掉充电器时会有wakelock,超时2秒
        wake_lock_timeout(&chip->somc_params.plugin_wake_lock, PLUGIN_WAKELOCK_TIME_SEC);
    c. 由此,是否可以不需要上报KEY_F24给charge only呢?直需要缩短 select 的延时<2秒,就可以避免charge only不会退出关机的问题
    

十七、电池参数的设置(百分比,温度,电压 之间的关系)
1. @kernel/arch/arm/boot/dts/msm8226-cn3ii.dtsi
    &pm8226_bms {
        qcom,batt-type = <3>;  //电池类型为 BATT_OEM
    }
    @kernel/arch/arm/mach-msm/Makefile, 当前用的是pc_temp_ocv@bms-batterydata-sleipner.c
    ifdef CONFIG_MACH_SONY_CN3II
    obj-$(CONFIG_MACH_SONY_CN3II) += bms-batterydata-sleipner.o
    设置电池的相关参数        
    set_battery_data@kernel/drivers/power/qpnp-bms.c
    插值计算@kernel/arch/arm/mach-msm/baterydata-lib.c

十八、电池低电关机过程
1. 涉及关机的几个参数配置
    pm8226_bms: qcom,bms {
        qcom,v-cutoff-uv = <3400000>;  //关机电压,但只用与计算SOC
        qcom,low-soc-calculate-soc-threshold = <15>; //认为是低电的电池百分比
            qcom,low-soc-calculate-soc-ms = <5000>;  //低电情况下的心跳
            qcom,calculate-soc-ms = <20000>;
        qcom,low-voltage-threshold = <3420000>;
    }
2. 系统休眠情况下的低电关机触发
   setup_vbat_monitoring@kernel/drivers/power/qpnp-bms.c 中会设置低电(高电,高温,低温)的threshold,
   还有call back函数btm_notify_vbat, 如果电压低于low-voltage-threshold-VBATT_ERROR_MARGIN也就是3.4V
    ->configure_vbat_monitor_low
        唤醒系统 wake_lock(&chip->low_voltage_wake_lock);
        开始计算soc, schedule_delayed_work(&chip->calculate_soc_delayed_work, 0);
        针对电池低压的情况下,重新配置参数
           如果此时soc=0,则上报android 关机
3. 系统正常情况下的低电关机触发
    开机后马上运行 calculate_soc_work,以后只要系统不休眠则 心跳间隔20s(soc > 15%),5s(soc < 15% & wake_lock_active(&chip->low_voltage_wake_lock))
    ->recalculate_soc
    ->calculate_state_of_charge    
    ->adjust_soc
        ->very_low_voltage_check 如果电池电压<3.42v,则wake_lock(&chip->low_voltage_wake_lock);不让系统休眠,    随后心跳间隔一直继续,上报soc,如果soc=0则系统会关机。
 
十九、PMIC中充电部分中断的申请和处理
1. @kernel/arch/arm/boot/dts/msm-pm8226.dtsi
            qcom,chgr@1000 {
                status = "disabled";
                reg = <0x1000 0x100>;
                interrupts =    <0x0 0x10 0x0>,
                        <0x0 0x10 0x1>,
                        <0x0 0x10 0x2>,
                        <0x0 0x10 0x3>,
                        <0x0 0x10 0x4>,
                        <0x0 0x10 0x5>, //"fast-chg-on"对应的寄存器0x1000 + 0x10 = 0x1010, 0x05表示的是第5个bit
                        <0x0 0x10 0x6>,
                        <0x0 0x10 0x7>;

                interrupt-names =   "vbat-det-lo",
                            "vbat-det-hi",
                            "chgwdog",
                            "state-change",
                            "trkl-chg-on",
                            "fast-chg-on",
                            "chg-failed",
                            "chg-done";
            };  
    对应的是SMBBP_CHAR_SMNNP_CHAR中 0x1010 SMBBP_CHGR_INT_RT_STS 中断寄存器的bits
2. 如何引用,例子如下:
   qpnp_chg_request_irqs@kernel/drivers/power/qpnp-charger.c
    chip->chg_fastchg.irq = spmi_get_irq_byname(spmi,spmi_resource, "fast-chg-on");

二十、充电最大时间设置(trickle + fast charging)
1. @kernel/arch/arm/boot/dts/msm-pm8226.dtsi
    pm8226_chg: qcom,charger {
        qcom,tchg-mins = <150>; //表示150分钟, 取值区间[4, 512]分钟
    }
2. qpnp_charger_read_dt_props@kernel/drivers/power/qpnp-charger.c
    OF_PROP_READ(chip, tchg_mins, "tchg-mins", rc, 1);
   qpnp_chg_hwinit
    rc = qpnp_chg_tchg_max_set(chip, chip->tchg_mins); //此处往寄存器0x1061 SMBBP_CHGR_TCHG_MAX 写入
3. 如果需要disable 充电最大时间的限制,则需要操作 寄存器0x1060 SMBBP_CHGR_TCHG_MAX_EN
4. 如果需要设置trickle charge的最大时间限制,则需要操作 寄存器0x105F SMBBP_CHGR_TTRKL_MAX,
   enable/disable 则需要操作 寄存器0x105E SMBBP_CHGR_TTRKL_MAX_EN,

二十一、当kernel用power_supply_changed 发送uevent 到 userspace的后续处理
1. register_android_server_BatteryService@frameworks/base/services/jni/com_android_server_BatteryService.cpp 注册的sys path如下:
    /sys/class/power_supply/???/online  其中???/type=Mains //注意大小写是区分的
    /sys/class/power_supply/usb/online  其中usb/type=USB
    /sys/class/power_supply/battery/online   其中usb/type=Battery
    /sys/class/power_supply/battery/status
    /sys/class/power_supply/battery/health
    /sys/class/power_supply/battery/present
    /sys/class/power_supply/battery/capacity
    /sys/class/power_supply/battery/voltage_now[batt_vol]
    /sys/class/power_supply/battery/temp[batt_temp]
    /sys/class/power_supply/battery/technology
2. 如果是power_supply_changed(&chip->batt_psy);则user space 会读取所有注册的关于battery 的sys path.

二十二、如何在kernel添加input key event给user space用
   @kernel/drivers/power/qpnp-charger.c
   注册/* register input device */
    chip->somc_params.chg_unplug_key = input_allocate_device();
    input_set_capability(chip->somc_params.chg_unplug_key,    EV_KEY, KEY_F24);
    chip->somc_params.chg_unplug_key->name = "qpnp_chg_unplug_key";
    chip->somc_params.chg_unplug_key->dev.parent = &(spmi->dev);
    rc = input_register_device(chip->somc_params.chg_unplug_key);    
        //input_free_device(chip->somc_params.chg_unplug_key);
   使用:
    input_report_key(chip->somc_params.chg_unplug_key, KEY_F24, value ? 1 : 0);
    input_sync(chip->somc_params.chg_unplug_key);

二十三、chager debug
1. echo -n "file qpnp-charger.c +p" > /sys/kernel/debug/dynamic_debug/control
2. disable charging:
    echo 0 > /sys/class/power_supply/battery/charging_enabled
3. WARN_ON(1) & BUG_ON

二十四、高通Case和公司DMS的关系
1. 如果遇到问题是高通的bug,提交一个case给高通
2. 在公司的DMS中创建一个主DMS, 并且assign给 coordinator, 并且在Attachments/Reference页签中的External References上
    External Reference 选择Qcom
    External Reference ID 输入 Qcom的case ID;
3. 如果Qcom的solution没有及时deliver, 我们可以创建一个child dms, 在Linked Records页签中单击 Create Child,在该child DMS中diliver我们自己的solution;一旦Qcom diliver了Solution,则我们自己的solution需要revert.

二十五、 Touch screen, Display新的early suspend机制
1. User space 如果需要early suspend,则需要操作 "/dev/graphics/fb0"
    ioctl(fb.fd, FBIOBLANK, FB_BLANK_UNBLANK);  //resume
    ioctl(fb.fd, FBIOBLANK, FB_BLANK_POWERDOWN);  //suspend
2. 通过操作display的ioctl, 进而控制其他需要early supend的设备,主要机制是notify的回调
    do_fb_ioctl@kernel/drivers/video/fbmem.c
    fb_blank(info, arg)@kernel/drivers/video/fbmem.c
    fb_notifier_call_chain(FB_EVENT_BLANK, &event)@kernel/drivers/video/fbmem.c
    blocking_notifier_call_chain@kernel/kernel/notifier.c
    notifier_call_chain(&nh->head, val, v, nr_to_call,nr_calls)@kernel/kernel/notifier.c
    nb->notifier_call(nb, val, v)@kernel/kernel/notifier.c
3. 需要和display一块suspend的设备如何注册notify, 比如touch screen
     clearpad_probe@kernel/drivers/input/touchscreen/clearpad_core.c
    this->fb_notif.notifier_call = synaptics_fb_notifier_callback;
    rc = fb_register_client(&this->fb_notif); //注册display notify的关键函数    
  另外还注册正常的linux power管理的suspend/resume
    static const struct dev_pm_ops synaptics_clearpad_pm = {
        .suspend = synaptics_clearpad_pm_suspend,
        .resume = synaptics_clearpad_pm_resume,
        .suspend_noirq = synaptics_clearpad_pm_suspend_noirq,
    };
   以上两条suspend的路径最后都调用 synaptics_clearpad_suspend和synaptics_clearpad_resume

二十六、低电关机(soc=0%和电压低于设置的低电压 都会触发低电关机)
1. 电压低于设置电压的处理
    @msm-pm8226.dtsi中 
    pm8226_bms: qcom,bms{
        qcom,low-soc-calculate-soc-threshold = <15>; //认为是低电压的soc(15%)
        qcom,low-soc-calculate-soc-ms = <5000>; //低电压情况下,计算soc 的时间间隔5s
        qcom,calculate-soc-ms = <20000>; //正常情况下,计算soc 的时间间隔20s
        qcom,v-cutoff-uv = <3400000>;//用于计算soc
        qcom,low-voltage-threshold = <3420000>;//用于设置关机低电压
    }
2. 系统一开机会马上运行calculate_soc_work,然后间隔20s或5s 循环执行,只要系统不suspend,该schedule work会一直执行,如果s计算的soc有变化还会power_supply_changed上报给用户空间,如果此时soc=0%则用户空间会强制关机shut down.
   
3. 另外还有一种情况,就是系统已经suspend,但电池电压小于预先设置的门限电压,会触发notify也就是btm_notify_vbat@qpnp-bms.c,如果是低电状态则继续调用configure_vbat_monitor_low,其中会wake_lock(&chip->low_voltage_wake_lock);让系统不再suspend,马上开始运行calculate_soc_work,也就是把soc=0%的信息赶快通知给user space,好执行低电关机。具体分析如下:
3.1 设置低电notify的相关参数,setup_vbat_monitoring@qpnp-bms.c
   ->qpnp_adc_tm_channel_measure@qpnp-adc-tm.c; 设置adc channel的监控设置
    chip->vbat_monitor_params.low_thr = chip->low_voltage_threshold; //低电压门限设置
    chip->vbat_monitor_params.high_thr = chip->max_voltage_uv - VBATT_ERROR_MARGIN; //高电压门限设置
    chip->vbat_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; //触发的类型 满足高低电压门限时触发
    chip->vbat_monitor_params.channel = VBAT_SNS;
    chip->vbat_monitor_params.btm_ctx = (void *)chip;
    chip->vbat_monitor_params.timer_interval = ADC_MEAS1_INTERVAL_1S; //Polling 时间
    chip->vbat_monitor_params.threshold_notification = &btm_notify_vbat; //回调函数
    rc = qpnp_adc_tm_channel_measure(&chip->vbat_monitor_params);
3.2 @qpnp-adc-tm.c
    notify_adc_tm_fn是其中关键的处理函数
    ->notify_clients(adc_tm)-->adc_tm->btm_param->threshold_notification(ADC_TM_LOW_STATE, adc_tm->btm_param->btm_ctx);
    qpnp_adc_tm_probe中初始化INIT_WORK(&adc_tm->sensor[sen_idx].work, notify_adc_tm_fn);
      初始化qpnp_adc_tm_low_thr_isr中断处理函数
        rc = devm_request_irq(&spmi->dev, adc_tm->adc->adc_low_thr_irq,
                qpnp_adc_tm_low_thr_isr,IRQF_TRIGGER_RISING, "qpnp_adc_tm_low_interrupt", adc_tm);
    一旦电压低于chip->vbat_monitor_params.low_thr则触发qpnp_adc_tm_low_thr_isr,然后schedule_work(qpnp_adc_tm_low_thr_work);最后会调用qpnp_adc_tm_read_status,又会触发schedule_work(&adc_tm->sensor[sensor_num].work);即notify_adc_tm_fn函数,从而会发notify消息给BMS的btm_notify_vbat->configure_vbat_monitor_low和power_supply_changed

二十七、读取的adc电压和电池温度的关系
1. @kernel/drivers/hwnon/qpnp-adc-common.c
    计算电池温度和电压关系的函数qpnp_adc_scale_batt_therm
    电池温度和电压关系的对应表adcmap_btm_threshold[], 该表需要电池team提供
        static const struct qpnp_vadc_map_pt adcmap_btm_threshold[] = {
            {-300,    1686},    ...  
            {0,    1342}, ...
            {450,    619}, ... {温度45度,电压619mv}
        }
    通过线性逼近插值计算精度可以到达0.1Degc
2. 读取电池温度的流程
    get_prop_batt_temp@kernel/drivers/power/qpnp-charger.c
    qpnp_vadc_read(LR_MUX1_BATT_THERM, &results)@kernel/drivers/hwmon/qpnp-adc-voltage.c
    qpnp_vadc_conv_seq_request(ADC_SEQ_NONE, channel, result)@kernel/drivers/hwmon/qpnp-adc-voltage.c
    vadc_scale_fn[scale_type].chan(result->adc_code,vadc->adc->adc_prop, vadc->adc->amux_prop->chan_prop, result)@qpnp-adc-voltage.c
    qpnp_adc_scale_batt_therm@kernel/drviers/hwmon/qpnp-adc-common.c
   重点分析qpnp_vadc_conv_seq_request@qpnp-adc-voltage.c
    a. vadc->adc->adc_channels 的赋值
           qpnp_adc_get_devicetree_data@qpnp-adc-common.c
       ->adc_qpnp->adc_channels = adc_channel_list;其中adc_channel_list是分析读取msm8226-cn3ii.dtsi中&pm8226_vadc的节点内容,其中有
                chan@30 {
                label = "batt_therm";
                reg = <0x30>; //channel_num 通道号,也就是需要读取的channel,针对battery thermal是LR_MUX1_BATT_THERM=48,正好匹配
                qcom,decimation = <0>;
                qcom,pre-div-channel-scaling = <0>;
                qcom,calibration-type = "ratiometric";
                qcom,scale-function = <1>; //adc_scale_fn的ID,也就是scale_type
                qcom,hw-settle-time = <2>;
                qcom,fast-avg-setup = <0>;
            };
       ->vadc_scale_fn的定义
        static struct qpnp_vadc_scale_fn vadc_scale_fn[] = {
            [SCALE_DEFAULT] = {qpnp_adc_scale_default},
            [SCALE_BATT_THERM] = {qpnp_adc_scale_batt_therm}, //battery thermal的scale函数qpnp_adc_scale_batt_therm@qpnp-adc-common.c
            [SCALE_PMIC_THERM] = {qpnp_adc_scale_pmic_therm},
            [SCALE_XOTHERM] = {qpnp_adc_tdkntcg_therm},
            [SCALE_THERM_100K_PULLUP] = {qpnp_adc_scale_therm_pu2},
            [SCALE_THERM_150K_PULLUP] = {qpnp_adc_scale_therm_pu1},
            [SCALE_QRD_BATT_THERM] = {qpnp_adc_scale_qrd_batt_therm},
        };          



estimate_ocv
clamp_soc_based_on_voltage_with_max_count
问题:
DMS01852397
qpnp_chg_idcmax_set 中算法和datasheet不一致?
Ambari2.7+HDP3.0安装(基于Centos7) 1.Ambari简介 Apache Ambari项目旨在通过开发用于配置,管理和监视Apache Hadoop集群的软件来简化Hadoop管理。Ambari通过其RESTful API提供了直观,易于使用的Hadoop管理Web UI。 Ambari使系统管理员可以: 设置Hadoop集群 Ambari提供了用于在任意数量的主机上安装Hadoop服务的分步向导。 Ambari处理群集的Hadoop服务的配置。 管理Hadoop集群 Ambari提供了用于在整个集群中启动,停止和重新配置Hado 阅读详情

相关推荐

Qualcomm Wi-Fi配置文件调优:连接管理、功耗控制与漫游参数详解

Wi-Fi连接稳定性与功耗优化是无线网络开发中的核心挑战。通过调整底层配置文件中的关键参数,可以显著改善断线重连速度、漫游切换效率及设备续航。例如,合理设置重连超时与漫游触发阈值,能平衡连接可靠性与功耗;而启用802.11k/v/r协议族,则能实现快速漫游,降低延迟。这些技术广泛应用于移动设备、物联网网关及企业级AP部署中。本文深入解析Qualcomm平台WCNSS_qcom_cfg.ini中的连接管理、功耗控制及漫游行为参数,帮助开发者解决实际工程问题。

weixin_34187862的博客 426

Yangtze worknote

1. repo init -u git://review.sonyericsson.net/platform/manifest -b volatile-jb-mr1-yangtze 2. https://wiki.sonyericsson.net/androiki/CN3-II/Bringup_Trail_FC34  如何编译 3. https://wiki.sonyericsson.net/

pillarbuaa的专栏 1万+

DH_IPC-HX4XXX-Eos4_Chn_PN_Stream3_V2.680.0000000.2.R.190410.bin

运动高手秀的摄像头,默认是不支持乐橙云的,升级这个固件可以支持乐橙云,在自己的手机上面观看,实现随时随地的查看自己的摄像头,之所以提供给大家是因为我自己使用后感觉非常方便。这是冒着摄像头被杀死的风险测试出来的一个固件

iphone4 xxx@2.png图片作用和如何使用

The retinal display on the new iPhone 4 doubles the pixel resolution of the display. However when it comes to laying out the UI elements you can still think about a full screen display with dimensio...

zani的博客 1107

全网首发:香橙派5MAX安装ubuntu20.04(换了官方桌面)及ros,运行LIVOX-mid360混合固态激光雷达,实际测试fastlio(只能说可以跑,还是有点吃力)

独家发布!!!香橙派5MAX安装ubuntu20.04(换了官方桌面)及ros,运行mid360混合固态激光雷达,测试fastlio(只能说可以跑,还是有点吃力),国货也算崛起了。

2402_82745259的博客 5995

Ubuntu中交叉编译armadillo库

网上关于交叉编译armdillo库比较少,借鉴了一些但是在前几天编译时总是磕磕绊绊,于是写一个详细的编译过程。交叉编译armdillo库包含两个步骤:交叉编译依赖库和交叉编译armdillo库。

weixin_39813867的博客 1879

【笔记】拯救红米Note黑砖

【笔记】拯救红米Note黑砖 这几天在玩以前买的手机,从粑粑给买的HTC G7到后来工作自己买的红米Note,华为Y511,华为荣耀5A…除了G7,其他都是便宜机。红米Note刚买就刷了开发版得了root,后来系统推送了升级点了下,然后又有推送,没注意看就继续点,结果从MIUI9的开发版变成了稳定版,没有了root,广告又铺天盖地,不能忍,刷机! 因为时间长了,记不得是红米Note 1的4G增强版...

匆匆往站 8092

(三)从零开始学习模糊控制——模糊决策

模糊控制之基础——模糊数学 (一)模糊数学——基本概念 (二)模糊数学——模糊关系 (三)模糊数学——模糊决策 文章目录模糊控制之基础——模糊数学前言一、模糊决策法1.最大值原理2.模糊排序2.模糊寻优总结 前言   模糊决策是指在模糊环境下进行决策的数学理论和方法。这类问题─般具有大系统特征,系统之间的关系十分复杂,存在不能准确赋值的变量,这些变量属于模糊因素,涉及到一定的主观因素,使得子系统之间、变量之间的关系不清晰,从而必须借助排序、模糊评判等方法来进行处理。 一、模糊决策

qq_43667552的博客 4750

9106w android7,三星Galaxy Note 4(N9106W)强势搭载高通骁龙805处理器 | Qualcomm

References to "Qualcomm" may mean Qualcomm Incorporated, or subsidiaries or business units within the Qualcomm corporate structure, as applicable.Qualcomm Incorporated includes Qualcomm's licensing bu...

weixin_31300697的博客 182

教程 | 基于遥感生态指数(RSEI)模型的生态环境质量评价

使用数据:Landsat8 OLI数据,行列号:122036,时间:20210527。 使用软件:ENVI5.3、ArcGIS10.2 二、4个指数计算 下述的四个指数的计算均是通过ENVI软件中的Band math工具进行计算。 (1)绿度指数 在常用的植被指数当中,归一化植被指数 ( NDVI) 能够有效地反映植被的生长情况与植被 覆盖度等重要植被的物理性质,检测灵敏度高,能 够较为真实地展现区域的地表空间变化规律,已经得到广泛利用,本文采用 NDVI 指数来代表绿度指数。 在ENV

YW_remote的博客 1万+

【事件相机实战】噪声识别与智能滤波技术解析

本文深入解析了事件相机应用中关键的噪声识别与智能滤波技术。针对背景活动噪声、阈值波动噪声、热点噪声及运动边缘尾迹等核心噪声类型,文章剖析了其成因与特征,并详细介绍了从经典的时空相关性滤波、热点像素滤除到前沿的深度学习(如EDnCNN)等实战滤波方法,旨在帮助开发者在保证低延迟的前提下,有效提升事件流数据质量。

weixin_29169899的博客 439

n9100 分区 Linux,三星Galaxy Note 4(N9100)强势搭载高通骁龙805处理器 | Qualcomm

References to "Qualcomm" may mean Qualcomm Incorporated, or subsidiaries or business units within the Qualcomm corporate structure, as applicable.Qualcomm Incorporated includes Qualcomm's licensing bu...

weixin_39666782的博客 99

LangFlow开源神器:拖拽构建AI Agent与MCP协议集成实战

LangFlow 15万星开源神器:拖拽搭建 AI Agent,一键部署 API / MCP / JSON 在AI应用开发领域,传统编码方式往往需要处理复杂的API调用、数据处理和流程编排,这让很多开发者望而却步。LangFlow作为GitHub上获得15万星的开源项目,通过可视化拖拽的方式彻底改变了AI Agent的开发体验。本文将完整介绍如何使用LangFlow快速构建功能强大的AI Agent,并实现API、MCP协议和JSON配置的一键部署。 1. LangFlow与AI Agent核心概念解析 1

weixin_33717117的博客 931

骁龙660_高通骁龙660可以带动6g运行内存吗?

CPU 主频可达2.2 GHzCPU 内核八核 Kryo 260CPU 架构ARM 64位GPUGPU 型号Qualcomm? Adreno? 512 GPUAPI 支持Modem 调制解调器调试解调器型号Qualcomm? 骁龙? X12 LTE内存双通道LPDDR4x《最大可支持8GB》近场通讯支持充电支持 Qualcomm? Quick Charge?Qualcomm? Quick Char...

weixin_36252577的博客 556

【规则引擎连载3】 WebSphere ILOG JRules 开发——新建一个规则项目

规则项目依赖于bom,bom来自于一个Java项目,所以在建规则项目之前,需要先建立一个Java项目,也就是建模。 说明:原则上是一个规则项目,一个bom。如果规则项目很多,并且每个规则项目的bom都是一样的,那么可以将bom提取出来,单独作为一个项目,这样减少不必要的重复操作,减少维护成本,降低出错几率。这里逐步讲解两种方法来创建规则项目,推荐第二种。 本文主要建立两种规则项目与bom之间的关系,编写相关的规则内容,如操作规则、规则流、决策表等,在后续文章中详细讲解 。

manman的分享 2327

【个人成长笔记】在Ubuntu中安装RTL8188GU USB无线网卡驱动(Driver for Linux RTL8188GU)

这篇笔记提供了在Ubuntu系统中安装RTL8188GU无线网卡驱动的完整指南。主要内容包括:驱动程序的官方来源(Github链接)、详细安装步骤(通过git下载、编译安装等命令),以及将设备转换为USB网卡模式的关键指令。特别提醒每次开机都需要执行模式转换命令,因为设备默认会以U盘模式启动。文章以简洁的技术文档形式呈现,适合Linux用户快速查阅和操作。

yuhuasheng1997的博客 1822

三星通过Galaxy Note20,Note20 Ultra在功率,尺寸和5G方面大放异彩

Samsung is making up for its mistakes in the Galaxy S20 series with the Galaxy Note20 and Note20 Ultra. The phones have improved cameras, work well with Windows, and, of course, have S Pens. 三星正在通过Gal...

weixin_26744853的博客 2073

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

美国高通公司完成首个智能手机上的LTE Advanced Category 6演示

在采用Qualcomm Gobi 9x35调制解调器的三星Galaxy Note 3上 演示最高达300Mbps的下载速度 2014年2月25日,巴塞罗那——美国高通公司宣布,其全资子公司美国高通技术公司与三星、SK电信及KT合作,在2014年移动通信世界大会(MWC)上通过三星Galaxy Note 3智能手机完成世界上首次LTE Advanced Category 6连接(下载速度最高达30...

weixin_43735236的博客

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Qualcomm平台编译环境

Qualcomm平台编译环境 1、             需要的编译环境这里假设PC OS为Windows(windows98、2000、NT、XP等)。 Qualcomm的平台编译环境主要需要Cygwin(包含make和gcc)、ADS 1.2(编译器)、Perl这三个软件;不同的平台和软件版本对编译环境软件的版本的要求也有所差异,具体的请参照高通的文档为准,一般情

Tom Song的专栏 5356

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Cryptography Note

Symmetric Cryptography (fast, one key)             - DES (dataencryption standard)                         -56-bitkey             - 3DES                         -3different keys, each key is 56

riverlau的专栏 479

三星android pro,够大才能爽 三星Galaxy Note Pro评测

在不久前结束的CES2014上,三星众望所归的发布了旗下最新的GalaxyNotePro与GalaxyTabPro的系列产品,包含12.2英寸、10.1英寸以及8.4英寸三个屏幕尺寸类型。而在2014年2月11日,三星官方发布了国行版本Pro系列平板的邀请函。在CES现场的三星展台,笔者作为前方记者也体验了Pro系列新品,本篇文章就请大家先跟随笔者的脚步,从多个方面了解三星Galaxy...

weixin_42304604的博客 624

3步快速上手:qualcomm_gbl_exploit_poc 解锁高通 Bootloader 完整教程(含 efisp 分区刷写实操)

qualcomm_gbl_exploit_poc 是一个基于 GBL 漏洞、通过向 efisp 分区刷写 UEFI 应用来解锁高通 Bootloader(引导加载器)的 PoC 工具。如果你的设备使用高通平台且 ABL 中集成了未签名的 GBL 启动阶段,就可以用它改写 RPMB 中保存的锁定状态,完成 Bootloader 解锁。本文带你用 3 步完成 efisp 分区刷写与解锁实操。 ##

gitblog_00686的博客 428

【UEFI探索】高可信 ARM 平台(Qualcomm SoC)UEFI 下电池与热管理实现:ACPI SSDT、HII 配置与 Standalone MM 通信完整示例

本文展示了针对高通SoC和Windows 11 ARM平台的系统实现方案,主要包含三个核心技术组件: ACPI SSDT示例:提供了电池(BAT0)和热区(TZ00)的ACPI实现,包括_HID、_BIF、_BST、_BIX等方法,通过SystemMemory共享区域与固件交换数据。 HII Form实现:采用EDK II框架构建用户配置界面。 Standalone MM架构:为ARM平台设计的SMM替代方案,包含完整的通信协议实现(DXE与MM侧驱动)。 关键适配要点: 使用共享内存区域传递电池和温度数据

就聊这个了,window/linux 嵌入式驱动 558

note

CPU,此处CPU与BL芯片断开,没有连背光芯片。屏,此处屏与BL芯片断开,没有连背光芯片。虽然signal函数更简单些。行 3744: 07-22 23:56:01.139 <6>[ 5.577037][ T919] [2024:07:22 15:55:51](7)[919:modprobe][drm] [lcdkit /I]: type:66, name:[qcom,mdss-dsi-apl-bl-mode-off-command], length:16, pkt-count=1。

m0_73462935的博客 10万+

BTLinkKeys工具使用---QUALCOMM方案

BTLinkKeys工具使用

Andy的博客 928
上一篇: C语言 指针数组 多维数组
下一篇: Ext4文件系统架构分析(一)
TopicDev
博客等级 码龄14年 128粉丝 111原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值