1. 程式人生 > 實用技巧 >高通驅動樹中的GPIO詳解

高通驅動樹中的GPIO詳解

高通驅動樹中的GPIO詳解

Drive Strength && tri-state相關概念

Drive Strength(也被稱為:driving strength):表示“驅動強度”。這個引數用來控制訊號強度,數值越大代表訊號強度越高。

tri-state:三態,高電平、低電平、高阻。

實質

電路分析時高阻態可做開路理解。你可以把它看作輸出(輸入)電阻非常大。它的極限狀態可以認為懸空(開路)。也就是說理論上高阻態不是懸空,它是對地或對電源電阻極大的狀態。而實際應用上與引腳的懸空幾乎是一樣的。

意義

當閘電路的輸出上拉管導通而下拉管截止時,輸出為高電平;反之就是低電平;如上拉管和下拉管都截止時,輸出端就相當於浮空(沒有電流流動),其電平隨外部電平高低而定,即該閘電路放棄對輸出端電路的控制 。

表示方法

高阻態常用字母 Z 表示。

原有GPIO配置框架

之前所有的gpio操作都是通過gpiolib來實現,常用的api包括:

static inline int gpio_request(unsigned gpio, const char *label);
static inline int gpio_direction_input(unsigned gpio);
static inline int gpio_direction_output(unsigned gpio, int value);
static inline void gpio_set_value(unsigned gpio, int value);
static inline void gpio_free(unsigned gpio);

在硬體設計確定了某個裝置需要使用哪些gpio之後,軟體需要做的是:

以msm8916平臺tp的中斷為例

1)在msm8916-cdp.dsi中定義使用哪個gpio

i2c@f9924000{
    goodix@5d{
        compatible= "goodix,gt9xx";
        reg= <0x5d>;
        interrupt-parent= <&msmgpio>;
        interrupts= <13 0x2>;
        ▲interrupt-gpios= <&msm_gpio 13 0x00>;
    };
}

2)在board-8916-gpiomux.c中定義gpio的suspend和active狀態

static struct gpiomux_setting ▲atmel_int_act_cfg = {
    .func =GPIOMUX_FUNC_GPIO,
    .drv = GPIOMUX_DRV_2MA,
    .pull = GPIOMUX_PULL_UP,
};

static struct gpiomux_setting ▲atmel_int_sus_cfg = {
    .func =GPIOMUX_FUNC_GPIO,
    .drv = GPIOMUX_DRV_2MA,
    .pull =GPIOMUX_PULL_NONE,
};

static struct msm_gpiomux_config ▲msm_touch_configs[] __initdata = {
    ▲ .gpio = 13,
    .settings = {
        [GPIOMUX_ACTIVE]   = ▲ &atmel_int_act_cfg,
        [GPIOMUX_SUSPENDED]= ▲ &atmel_int_sus_cfg,
    },
},

PinControl 框架

Gpiolib方式的缺點在於:當同一套程式碼對應多個board設計時,需要在board--gpiomux.c檔案中加巨集進行區分。如在同一個分支上支援3個專案,在board-msm8974-gpiomux.c檔案中添加了很多巨集控。

pinctrl方式可以避免程式碼中的這種冗餘程式碼:它將board--gpiomux.c檔案中的配置資訊移到xx-pinctrl.dtsi;這樣,針對不同project的board設計,分別在各自project的xx-pinctrl.dtsi中定義各自的gpio配置資訊。

Pinctrlsubsystem 分為3部分:Pinctrl core、Pinmux和Pinconf。

  • pinctrlcore是pincontrol子系統的核心,提供了和devicedriver互動的API;
  • pinmux用於實現pin的複用;
  • pinconf用於實現pin的配置,如輸入/輸出、pulldown/pull up、driverstrength等;另外還提供了用於debug的介面。

與gpio子系統的互動

雖然pinctrl提供了pinctrl_request_gpio()這樣的API,但在程式碼中不可以直接呼叫pinctrl_request_gpio(),在該函式的定義處也有說明,如下:

/* This function should *ONLY* be used from gpiolib-based GPIO drivers,
* as part of their gpio_request() semantics, platforms and individualdrivers
* shall *NOT* request GPIO pins to be muxed in.”
*/

當裝置驅動申請一個gpio時,仍然需要呼叫gpio_request(),這裡會呼叫pinctrl_request_gpio()。呼叫過程如下:

gpio_request()
    gpiod_request()
    	chip->request(chip,gpio_chip_hwgpio(desc));

在pinctrl_msm.c中,重新定義了chip->request()。

msm_pinctrl_probe()
    msm_register_gpiochip()
    	gc->request= msm_pinctrl_request_gpio;

這裡msm_pinctrl_request_gpio()會調pinctrl_request_gpio();

同樣地,對於pinctrl_free_gpio()pinctrl_gpio_direction_input()pinctrl_gpio_direction_output()也有類似說明。

因此在clientdevice驅動中,申請和釋放gpio仍然要調gpio_request()gpio_free();設定gpio為input/output仍然要調gpio_direction_input()gpio_direction_output()

Pinctrl註冊

全文以msm8916平臺為例進行分析。

當tlmm載入時,msm_tlmm_v4_probe()最後會調msm_pinctrl_probe(),其中會將pinctrl.dtsi中定義的pinctrlinfo解析出來,並且重新定義chip->request()chip->free()等函式。
具體的呼叫關係如下圖所示:

postcore_initcall(msm_tlmm_v4_drv_register); //Pinctrl-msm-tlmm-v4.c
msm_tlmm_v4_probe //匹配pinctrl.dtsi定義的compatible
    msm_pinctrl_probe //pinctrl_msm.c
    msm_pinctrl_get_drvdata(dd,pdev); //解析pinctrl.dtsi,儲存到dd
	msm_pinctrl_dt_parse_pintype(node,dd);
		msm_pinctrl_dt_parse_pins(node,dd);
	msm_register_gpiochip(dd); //定義gpio_request()、gpio_free()
gc->request= msm_pinctrl_request_gpio;
gc->free= msm_pinctrl_free_gpio;
msm_register_pinctrl(dd);
dd->pctl_dev= pinctrl_register(ctrl_desc, dd->dev, dd);

Pinstates

一個pinstate對應對pin腳的一種配置,一個pin腳可以配置多個狀態,對狀態的個數也沒有限制。
state的定義和電源管理關係比較緊密,例如當裝置active的時候,我們需要pincontroller將相關的一組pin設定為具體的裝置功能,而當裝置進入sleep狀態的時候,需要pincontroller將相關的一組pin設定為普通GPIO,並精確的控制GPIO狀態以便節省系統的功耗。
Pinctrl-state.h中給出了常用的3種狀態:

  • default:default狀態表示裝置處於active時的狀態,一般在裝置驅動的.resume中配置,另外在啟動時也會配置pin腳為default狀態。
  • idle:idle狀態表示系統處於idle時需要配置的pin腳狀態,此時系統並沒有進入深度休眠。
  • sleep:sleep狀態表示系統處於深度休眠時的pin腳狀態,一般在裝置驅動的.suspend中配置。

當然我們也可以定義任意形式的state,如“on”、“off”等。

goodix@5d{
    compatible= "goodix,gt9xx";
    reg= <0x5d>;
    pinctrl-names= "gt9xx_int_active", "gt9xx_int_suspend";
    pinctrl-0= <&gt9xx_int_active>;
    pinctrl-1= <&gt9xx_int_sleep>;
    interrupt-parent= <&msm_gpio>;
    interrupts= <13 0x2>;
    ……
}

pinctrl-names定義了clientdevice用到的state列表。

  • state有兩種標識,一種就是pinctrl-names定義的字串列表,另外一種就是ID。ID從0開始,依次加一。根據例子中的定義,stateID等於0(名字是"gt9xx_int_active")的state對應pinctrl-0屬性,stateID等於1(名字是"gt9xx_int_suspend")的state對應pinctrl-1屬性。

pinctrl-x是一個控制代碼(phandle)列表,每個控制代碼指向一個pinconfiguration。

Boot時配置default狀態

如果pin只定義了default狀態,那麼在裝置驅動中不需要再對該pin作處理,因為在啟動時會自動設為default狀態。

在載入驅動模組時,如果驅動和裝置匹配,最終就會調到driver定義的probe函式。在這個過程中,如果使能了pinctrl,而且定義了pin的default狀態,就會配置pin腳為該狀態。

具體程式碼流程如下:

driver_probe_device(structdevice_driver *drv, struct device *dev)
{
    really_probe(dev,drv);
    pinctrl_bind_pins(dev); // 函式呼叫過程如下:
    	dev->pins->p= devm_pinctrl_get(dev);
		dev->pins->default_state= pinctrl_lookup_state(dev->pins->p,
                                               PINCTRL_STATE_DEFAULT);
		pinctrl_select_state(dev->pins->p,dev->pins->default_state);
	    對於不使用pinctrl的平臺,pinctrl_bind_pins(dev)直接返回0;
    if(dev->bus->probe) {
        ret= dev->bus->probe(dev);
    }else if (drv->probe) {
        ret= drv->probe(dev);
    }
}

Pingroups

SOC上需要同時配置一組gpio來支援某些功能,如I2C、SPI、UART、SDC等,在-pinctrl.dtsi中將這些pin定義為一個group。

配置統一的情況

一組gpio在各個狀態下的配置都相同,這種配置比較常見也比較簡單。如i2c,在active和suspend狀態下,SDA和SCL的配置都相同:active狀態下都是配置為8mA上拉,suspend狀態下都配置為2mA和nopull。

首先在msm8916_pinctrl.c中定義suspend和active狀態下的pin腳配置資訊,如下:

pmx_i2c_0{
    qcom,pins= <&gp 7>, <&gp 6>; //使用gpio_6和gpio_7
    qcom,num-grp-pins= <2>; //共兩個gpio
    qcom,pin-func= <3>; //複用功能為i2c
    label = "pmx_i2c_0"; //表示同一組
    i2c_0_active:i2c_0_active {
        drive-strength= <8>;
        bias-pull-up;
    };
    
    i2c_0_sleep:i2c_0_sleep {
        drive-strength= <2>;
        bias-disable;
    };
};

然後在msm8916.dtsi中增加pinctrlinfo的引用:

i2c_0:i2c@78b6000 {
    compatible= "qcom,i2c-msm-v2";
    reg-names= "qup_phys_addr", "bam_phys_addr";
    reg= <0x78b6000 0x600>,
    <0x7884000 0x23000>;
    interrupt-names= "qup_irq", "bam_irq";
    interrupts= <0 96 0>, <0 238 0>;
    clocks= <&clock_gcc clk_gcc_blsp1_ahb_clk>,
    <&clock_gcc clk_gcc_blsp1_qup2_i2c_apps_clk>;
    clock-names= "iface_clk", "core_clk";
    qcom,clk-freq-out= <100000>;
    qcom,clk-freq-in = <19200000>;
    pinctrl-names= "i2c_active", "i2c_sleep";
    pinctrl-0= <&i2c_0_active>;
    pinctrl-1= <&i2c_0_sleep>;
    qcom,master-id= <86>;
};

配置不統一的情況

如SDC,每個pin腳的active和sleep狀態配置各不相同,需要分開設定。

1)在msm8916_pinctrl.c中分別定義各個pin的suspend和active狀態下的配置資訊,如下:

sdc:sdc {
    qcom,pin-type-sdc;
    qcom,num-pins= <6>;
    #qcom,pin-cells= <1>;
};

pmx_sdc1_clk{
    qcom,pins= <&sdc 0>;
    qcom,num-grp-pins= <1>;
    label= "sdc1-clk";
    sdc1_clk_on:clk_on {
        bias-disable;
        drive-strength= <16>;
    };
    sdc1_clk_off:clk_off {
        bias-disable;
        drive-strength= <2>;
    };
};

pmx_sdc1_cmd{
    qcom,pins= <&sdc 1>;
    qcom,num-grp-pins= <1>;
    label= "sdc1-cmd";
    sdc1_cmd_on:cmd_on {
        bias-pull-up;
        drive-strength= <10>;
    };
    sdc1_cmd_off:cmd_off {
        bias-pull-up;
        drive-strength= <2>;
    };
};

pmx_sdc1_data{
    qcom,pins= <&sdc 2>;
    qcom,num-grp-pins= <1>;
    label= "sdc1-data";
    sdc1_data_on:data_on {
        bias-pull-up;
        drive-strength= <10>;
    };
    sdc1_data_off:data_off {
        bias-pull-up;
        drive-strength= <2>;
    };
};

2)msm8916-cdp.dtsi中作出相應修改:

&sdhc_1{
    vdd-supply= <&pm8916_l8>;
    qcom,vdd-voltage-level= <2900000 2900000>;
    qcom,vdd-current-level= <200 400000>;
    vdd-io-supply= <&pm8916_l5>;
    qcom,vdd-io-always-on;
    qcom,vdd-io-lpm-sup;
    qcom,vdd-io-voltage-level= <1800000 1800000>;
    qcom,vdd-io-current-level= <200 60000>;
    //qcom,pad-pull-on= <0x0 0x3 0x3>;
    //qcom,pad-pull-off= <0x0 0x3 0x3>;
    //qcom,pad-drv-on= <0x4 0x4 0x4>;
    //qcom,pad-drv-off= <0x0 0x0 0x0>;
    pinctrl-names= "active", "sleep";
    pinctrl-0= <&sdc1_clk_on &sdc1_cmd_on &sdc1_data_on>;
    pinctrl-1= <&sdc1_clk_off &sdc1_cmd_off &sdc1_data_off>;
    qcom,nonremovable;
    status= "ok";
};

對sdhc_2也是同樣的配置。
另外還有一種情況,對於一組gpio,在不同state下pin_func定義不同的情況,如wifi,在active狀態設定為wifi功能,在suspend狀態下設定為普通gpio。

pinctrl@fd511000{
    //...
    pmx-wcnss-5wire-active{
        qcom,pins= <&gp 40>, <&gp 41>, <&gp 42>, <&gp43>.
            <&gp44>;
        qcom,pin-func= <1>;
        qcom,num-grp-pins= <5>;
        label= "wcnss-5wire-active";
        wcnss-5wire-active:wcnss-active {
            drive-strength= <6>; / * 6MA */
                bias-pull-up;
        };
    };
    pmx-wcnss-5wire-suspend{
        qcom,pins= <&gp 40>, <&gp 41>, <&gp 42>, <&gp43>.
            <&gp44>;
        qcom,pin-func= <0>;
        qcom,num-grp-pins= <5>;
        label= "wcnss-5wire-suspend";
        wcnss-5wire-sleep:wcnss-sleep {
            drive-strength= <6>; / * 6MA */
                bias-pull-down;
        };
    };
};

PinctrlAPI

//獲取該device對應的pinctrlhandler。
struct pinctrl *devm_pinctrl_get(struct device *dev); 

// 查詢name指定的pinctrlstate。
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char*name);

// 配置pin腳為指定的state。    
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state);

Usecase

下面舉例配置tp的中斷腳。

1)在msm8916-pinctrl.dtsi中定義pinctrlinfo:

&soc{
    tlmm_pinmux:pinctrl@1000000
        gt9xx_int_pin{
     ▲  qcom,pins= <&gp 13>;
        qcom,num-grp-pins= <1>;
        qcom,pin-func= <0>;
        label= "gt9xx_int_pin";

      ▲ gt9xx_int_active:active {
            drive-strength= <2>;  
            bias-pull-up;
        };

        ▲gt9xx_int_sleep:sleep {
            drive-strength= <2>;
            bias-disable;
        };
    };
}

2)在msm8916-cdp.dtsi中tp的節點中新增引用:

goodix@5d{
    compatible= "goodix,gt9xx";
    reg= <0x5d>;
▲   pinctrl-names= "gt9xx_int_active", "gt9xx_int_suspend";
▲   pinctrl-0= <>9xx_int_active>;
▲   pinctrl-1= <>9xx_int_sleep>;
▲   interrupt-parent= <&msm_gpio>;
    interrupts= <13 0x2>;
    //……
}

3)在tp驅動中新增配置。

a.定義pinctrl_info:

#define GOODIX_PINCTRL_STATE_SLEEP "gt9xx_int_suspend"
#define GOODIX_PINCTRL_STATE_DEFAULT "gt9xx_int_active"
struct gtp_pinctrl_info{
    structpinctrl *pinctrl;
    structpinctrl_state *gpio_state_active;
    structpinctrl_state *gpio_state_suspend;
};
static struct gtp_pinctrl_info gt9xx_pctrl;
static int **gtp_pinctrl_init**(struct device *dev)
{
    gt9xx_pctrl.pinctrl= ▲ devm_pinctrl_get(dev);
    if(IS_ERR_OR_NULL(gt9xx_pctrl.pinctrl)) {
        pr_err("%s:%dGetting pinctrl handle failed\n",
               __func__,__LINE__);
        return-EINVAL;
    }
▲   gt9xx_pctrl.gpio_state_active= pinctrl_lookup_state(gt9xx_pctrl.pinctrl,
                                                        GOODIX_PINCTRL_STATE_DEFAULT);
    if(IS_ERR_OR_NULL(gt9xx_pctrl.gpio_state_active)) {
        pr_err("%s:Failed to get the active state pinctrl handle\n",
               __func__,__LINE__);
        return-EINVAL;
    }
    gt9xx_pctrl.gpio_state_suspend= pinctrl_lookup_state(
        gt9xx_pctrl.pinctrl,
        GOODIX_PINCTRL_STATE_SLEEP);
    if(IS_ERR_OR_NULL(gt9xx_pctrl.gpio_state_suspend)) {
        pr_err("%s:Failed to get the suspend state pinctrl handle\n",
               __func__,__LINE__);
        return-EINVAL;
    }
    return0;
}

b.在probe函式中初始化pinctrl_info,並設定state:

static int goodix_ts_probe(struct i2c_client *client, const structi2c_device_id *id)
{
    goodix_parse_dt(&client->dev,pdata);
    gtp_request_io_port(ts);
    gtp_pinctrl_init(&ts->client->dev);
    pinctrl_select_state(gt9xx_pctrl.pinctrl,gt9xx_pctrl.gpio_state_active);
    // ……
}

c.在suspend()和resume()中分別設定為activestate和suspendstate:

static int goodix_ts_suspend(struct device *dev)
{
    struct goodix_ts_data *ts = dev_get_drvdata(dev);
    int ret = 0, i;
    ret= pinctrl_select_state(gt9xx_pctrl.pinctrl,
                              gt9xx_pctrl.gpio_state_suspend);
    if(ret)
        pr_err("%s:Cannot set pin to suspend state",
               __func__,__LINE__);
    // ……
    if(ts->use_irq)
        gtp_irq_disable(ts);
    // ……
    return ret;
}
static int goodix_ts_resume(struct device *dev)
{
    struct goodix_ts_data *ts = dev_get_drvdata(dev);
    int ret = 0;
    ret= pinctrl_select_state(gt9xx_pctrl.pinctrl,
                              gt9xx_pctrl.gpio_state_active);
    if(ret)
        pr_err("%s:Cannot set pin to suspend state",
               __func__,__LINE__);
    // ……
    if(ts->use_irq)
        gtp_irq_enable(ts);
    // ……
    returnret;
}