1. 程式人生 > >Linux 4.x 之Gpio分析(一)Gpiolib庫1

Linux 4.x 之Gpio分析(一)Gpiolib庫1

下面的內容均在imx6平臺上舉例,這一次分析希望將整個GPIO子系統的所有細節整理清晰。

第一篇從gpiolib入手,後面的邊分析邊寫.

開始之前給自己提幾個問題

  1. 驅動開發中的GPIO API 究竟是怎麼實現的?
  2. GPIO的中斷又是怎麼實現的?
  3. GPIO號和IRQ的號碼怎麼對映的?

1.晶片定義

我們在驅動程式中會用到gpio_request(x),這裡的x便是gpio的編號,而GPIO通常會分組,在原理圖經常會看見GPIO2_5類似的標識,通常我們先會翻閱一下datesheet來了解一下這塊晶片的定義是如何。

在imx6晶片中的將GPIO分成了若干組,每組為32個管腳。

我們簡單的閱讀一下晶片手冊:

IMX6的gpio控制結構:

imx6 gpio block結構

我們的晶片會引出一堆引腳,而在晶片的內部會整合很多control,(我們稱這些control為block)其中的引腳可以功能複用,比如說A1 A2腳支援I2C功能也支援GPIO功能,我可以將I2C1_control的管腳連線過來,或者將GPIO1_1的管腳連線上來,這個就是IOMUX的作用:提供PIN不同的功能切換。

所以我們在使用晶片之前會先根據主機板的配置先將各個管腳配置為正確的功能的Pin.

暫存器:
- Data register (GPIO_DR)
- GPIO direction register (GPIO_GDIR)
- Pad sample register (GPIO_PSR)
- Interrupt control registers (GPIO_ICR1, GPIO_ICR2)
- Interrupt mask register (GPIO_IMR)
- Interrupt status register (GPIO_ISR)
- GPIO edge select register (GPIO_EDEG_SEL )

簡單介紹一下
- DR 當作為輸出時控制管腳高低電平
- GDIR 控制管腳作為輸入還是輸出
- PSR 作為輸出時獲取管腳的高低電平
- ICR1 ICR2 是配置中斷的觸發方式 高/低電平觸發 上升/下降沿 觸發
- IMR 中斷遮蔽暫存器
- ISR 中斷狀態暫存器 哪個管腳觸發了中斷
- EDEG_SEL 邊緣觸發模式(上/下沿都觸發)

GPIO有7組,每組32個,最後一組14個

2.裝置樹

我們瞭解了一下內部的結構,接著就要開始看程式碼了,在看程式碼之前看看裝置樹,才能定位到程式碼在哪裡。

這裡只列出一個gpio-controller

 aliases {
                ethernet0 =
&fec; can0 = &can1; can1 = &can2; gpio0 = &gpio1; gpio1 = &gpio2; ..... } gpio1: [email protected]0209c000 { compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio"; reg = <0x0209c000 0x4000>; interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>, <0 67 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; };

我們通過compatible的值可以找到對應的驅動程式碼路徑 driver/gpio/gpio-mxc.c

GPIO1控制暫存器的地址是0x0209c000,長度0x4000,使用66和67號中斷。

這裡疑惑的地方是為什只用了兩個中斷號,而不是32個?

我們知道一個GPIO控制是通過匯流排連線到CPU,中斷連線到中斷控制器(GIC),那麼中斷線太多就以為著GIC的數量也需要增加,如果GPIO控制器內部進行判斷是哪個管腳觸發的那麼就可以避免GIC的數量。
而我們的晶片內部只有一個GIC.

IRQ Source Interrupt Description
32 IOMUXC General Purpose Register 1 from IOMUXC. Used to notify cores on exception condition whileboot.
33 DAP Debug Access Port interrupt request
98 GPIO1 Combined interrupt indication for GPIO1 signals 0 - 15.
99 GPIO1 Combined interrupt indication for GPIO1 signals 16 - 31.

這裡中斷號的來源是晶片手冊,因為GIC的連線是這樣的,這裡0-31是內部使用,32開始用作外設。
驅動中用編號0直接開始編號。

地址參考暫存器MAP

Absolute address Register name Widt (bits) Access Reset value
209_C000 GPIO data register (GPIO1_DR) 32 R/W 0000_0000h
209_C004 GPIO direction register (GPIO1_GDIR) 32 R/W 0000_0000h
209_C008 GPIO pad status register (GPIO1_PSR) 32 R 0000_0000h
209_C00C GPIO interrupt configuration register1 (GPIO1_ICR1) 32 R/W 0000_0000h
209_C010 GPIO interrupt configuration register2 (GPIO1_ICR2) 32 R/W 0000_0000h
209_C014 GPIO interrupt mask register (GPIO1_IMR) 32 R/W 0000_0000h
209_C018 GPIO interrupt status register (GPIO1_ISR) 32 w1c 0000_0000h
209_C018 GPIO interrupt status register (GPIO1_ISR) 32 w1c 0000_0000h
209_C01C GPIO edge select register (GPIO1_EDGE_SEL) 32 R/W 0000_0000h
20A_0000 GPIO data register (GPIO2_DR) 32 R/W 0000_0000h

3.程式碼

從裝置樹中我們知道了程式碼的路徑位置,我們先看看gpio這個目錄的Makefile:

obj-$(CONFIG_GPIO_DEVRES)       += devres.o
obj-$(CONFIG_GPIOLIB)           += gpiolib.o
obj-$(CONFIG_GPIOLIB)           += gpiolib-legacy.o
obj-$(CONFIG_OF_GPIO)           += gpiolib-of.o
obj-$(CONFIG_GPIO_SYSFS)        += gpiolib-sysfs.o
obj-$(CONFIG_GPIO_ACPI)         += gpiolib-acpi.o

# Device drivers. Generally keep list sorted alphabetically
obj-$(CONFIG_GPIO_GENERIC)      += gpio-generic.o

obj-$(CONFIG_GPIO_74X164)       += gpio-74x164.o
.....(省略一些無關緊要的內容)
obj-$(CONFIG_GPIO_MXC)          += gpio-mxc.o

這裡分三類:
1. gpiolib的程式碼
2. gpio-generic程式碼
3. gpio特定平臺的程式碼

程式碼的結構:

GPIOLIB 呼叫 GPIO_GENRIC 呼叫 GPIO_MXC

接著我們開始分析程式碼:

程式碼主要講了初始化GPIO控制器和GPIO的中斷控制器,這裡先分析gpio控制器和如何與gpiolib聯絡

static const struct of_device_id mxc_gpio_dt_ids[] = {
        { .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
        { .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
        { .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
        { .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
        { /* sentinel */ }
};
enum mxc_gpio_hwtype {
        IMX1_GPIO,      /* runs on i.mx1 */
        IMX21_GPIO,     /* runs on i.mx21 and i.mx27 */
        IMX31_GPIO,     /* runs on i.mx31 */
        IMX35_GPIO,     /* runs on all other i.mx */
};


static struct platform_device_id mxc_gpio_devtype[] = {
        {
                .name = "imx1-gpio",
                .driver_data = IMX1_GPIO,
        }, {
                .name = "imx21-gpio",
                .driver_data = IMX21_GPIO,
        }, {
                .name = "imx31-gpio",
                .driver_data = IMX31_GPIO,
        }, {
                .name = "imx35-gpio",
                .driver_data = IMX35_GPIO,
        }, {
                /* sentinel */
        }
};

這裡是為了相容性考慮,這個分支感覺寫的很有水準以後可以借鑑一下。

接著來看看porbe 的流程

static int mxc_gpio_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct mxc_gpio_port *port;
    struct resource *iores;
    int irq_base;
    int err;

    mxc_gpio_get_hw(pdev); //1

    port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
    if (!port)
        return -ENOMEM;

    //從裝置樹中獲取暫存器地址,IOREMAP就是將實體地址轉化稱虛擬地址
    iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    port->base = devm_ioremap_resource(&pdev->dev, iores);
    if (IS_ERR(port->base))
        return PTR_ERR(port->base);
    //從裝置樹中獲取中斷號,上面也解釋了為啥是2個。
    port->irq_high = platform_get_irq(pdev, 1);
    port->irq = platform_get_irq(pdev, 0);
    if (port->irq < 0)
        return port->irq;

    //關中斷,清理中斷觸發狀態。
    //中斷遮蔽位的作用就是是否要關心這個中斷
    //中斷狀態位的作用就是,當中斷髮生了,我們需要查一下是誰發生了中斷。
    /* disable the interrupt and clear the status */
    writel(0, port->base + GPIO_IMR);
    writel(~0, port->base + GPIO_ISR);

    if (mxc_gpio_hwtype == IMX21_GPIO) {
        /*
         * Setup one handler for all GPIO interrupts. Actually setting
         * the handler is needed only once, but doing it for every port
         * is more robust and easier.
         */
        irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
    } else {
        /* setup one handler for each entry */
        //2
        //函式修改父中斷的流控函式,當發生中斷,中斷控制器會去呼叫這個函式
        irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
        irq_set_handler_data(port->irq, port);
        if (port->irq_high > 0) {
            /* setup handler for GPIO 16 to 31 */
            irq_set_chained_handler(port->irq_high,
                        mx3_gpio_irq_handler);
            irq_set_handler_data(port->irq_high, port);
        }
    }

    //3
    err = bgpio_init(&port->bgc, &pdev->dev, 4,
             port->base + GPIO_PSR,
             port->base + GPIO_DR, NULL,
             port->base + GPIO_GDIR, NULL, 0);
    if (err)
        goto out_bgio;
//設定gpio和軟體中斷號對映的關係
    port->bgc.gc.to_irq = mxc_gpio_to_irq;
//設定gpio的編號基數
    port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
                         pdev->id * 32;
//將GPIO控制器計入到gpiolib驅動的連結串列中,也就是之後我們可以gpio_direction去操作io了
    err = gpiochip_add(&port->bgc.gc);
    if (err)
        goto out_bgpio_remove;

    irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
    if (irq_base < 0) {
        err = irq_base;
        goto out_gpiochip_remove;
    }
//向中斷控制器註冊並建立對映關係
    port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
                         &irq_domain_simple_ops, NULL);
    if (!port->domain) {
        err = -ENODEV;
        goto out_irqdesc_free;
    }
//初始化gpio中斷控制器
    /* gpio-mxc can be a generic irq chip */
    mxc_gpio_init_gc(port, irq_base);

    list_add_tail(&port->node, &mxc_gpio_ports);

    return 0;

out_irqdesc_free:
    irq_free_descs(irq_base, 32);
out_gpiochip_remove:
    gpiochip_remove(&port->bgc.gc);
out_bgpio_remove:
    bgpio_remove(&port->bgc);
out_bgio:
    dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err);
    return err;
}

先看 mxc_gpio_get_hw吧,其實也就是初始化了兩個全域性變數:

static enum mxc_gpio_hwtype mxc_gpio_hwtype;
static struct mxc_gpio_hwdata *mxc_gpio_hwdata;

static void mxc_gpio_get_hw(struct platform_device *pdev)
{
    const struct of_device_id *of_id =
            of_match_device(mxc_gpio_dt_ids, &pdev->dev);
    enum mxc_gpio_hwtype hwtype;

    if (of_id)
        pdev->id_entry = of_id->data;
    hwtype = pdev->id_entry->driver_data;

    if (mxc_gpio_hwtype) {
        /*
         * The driver works with a reasonable presupposition,
         * that is all gpio ports must be the same type when
         * running on one soc.
         */
        BUG_ON(mxc_gpio_hwtype != hwtype);
        return;
    }

    if (hwtype == IMX35_GPIO)
        mxc_gpio_hwdata = &imx35_gpio_hwdata;
    else if (hwtype == IMX31_GPIO)
        mxc_gpio_hwdata = &imx31_gpio_hwdata;
    else
        mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata;

    mxc_gpio_hwtype = hwtype;
}

看一眼這兩個變數是何方神聖:


enum mxc_gpio_hwtype {
        IMX1_GPIO,      /* runs on i.mx1 */
        IMX21_GPIO,     /* runs on i.mx21 and i.mx27 */
        IMX31_GPIO,     /* runs on i.mx31 */
        IMX35_GPIO,     /* runs on all other i.mx */
};

struct mxc_gpio_hwdata {
        unsigned dr_reg;
        unsigned gdir_reg;
        unsigned psr_reg;
        unsigned icr1_reg;
        unsigned icr2_reg;
        unsigned imr_reg;
        unsigned isr_reg;
        int edge_sel_reg;
        unsigned low_level;
        unsigned high_level;
        unsigned rise_edge;
        unsigned fall_edge;
};

static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
        .dr_reg         = 0x00,//寫如內容驅動IO
        .gdir_reg       = 0x04,//控制IO的方向 input output
        .psr_reg        = 0x08,//讀IO口電平
        .icr1_reg       = 0x0c,//中斷1 0-15
        .icr2_reg       = 0x10,//中斷2 16-31 控制電平觸發方式 00 low 01 hig 10 rise 11 fall
        .imr_reg        = 0x14,//中斷遮蔽位
        .isr_reg        = 0x18,//中斷狀態暫存器,中斷是否發生
        .edge_sel_reg   = 0x1c,//覆蓋ICR暫存器,選擇EDGE方式(上升沿下降沿均觸發這個意思把?)
        .low_level      = 0x00,
        .high_level     = 0x01,
        .rise_edge      = 0x02,
        .fall_edge      = 0x03,
};

原來不就是記錄一下控制暫存器是 imx35,和對應的暫存器的偏移位置而已。
我們先看port這個變數的結構,這裡就是為了初始化這個結構體。

struct mxc_gpio_port {
        struct list_head node;
        void __iomem *base; //gpio暫存器基地址
        int irq;            //gpio中斷號0-15用這個
        int irq_high;       //gpio中斷號16-31用這個
        struct irq_domain *domain; //irq_domain放到中斷裡面再介紹吧
        struct bgpio_chip bgc;//這個結構存放了暫存器的資訊主要在gpio-genirc中用
        u32 both_edges;//雙邊沿觸發的標識位,主要是為了相容不帶邊雙沿觸發暫存器的晶片
};

struct bgpio_chip {
        struct gpio_chip gc;

        unsigned long (*read_reg)(void __iomem *reg);
        void (*write_reg)(void __iomem *reg, unsigned long data);

        void __iomem *reg_dat;
        void __iomem *reg_set;
        void __iomem *reg_clr;
        void __iomem *reg_dir;

        /* Number of bits (GPIOs): <register width> * 8. */
        int bits;

        /*
         * Some GPIO controllers work with the big-endian bits notation,
         * e.g. in a 8-bits register, GPIO7 is the least significant bit.
         */
        unsigned long (*pin2mask)(struct bgpio_chip *bgc, unsigned int pin);


        /*
         * Used to lock bgpio_chip->data. Also, this is needed to keep
         * shadowed and real data registers writes together.
         */
        spinlock_t lock;

        /* Shadowed data register to clear/set bits safely. */
        unsigned long data;

        /* Shadowed direction registers to clear/set direction safely. */
        unsigned long dir;
};

bgpio_chip 我們可以在程式碼bgpio_init那裡讀程式碼分析它,暫時先不看了。

好!我們接著看probe中註釋2程式碼:

static void mx3_gpio_irq_handler(u32 irq, struct irq_desc *desc)
{
    u32 irq_stat;
    struct mxc_gpio_port *port = irq_get_handler_data(irq);
    struct irq_chip *chip = irq_get_chip(irq);

    chained_irq_enter(chip, desc);

   //讀中斷狀態暫存器確定哪個管腳發生中斷,並且如何中斷遮蔽位為0,忽略這個中斷
    irq_stat = readl(port->base + GPIO_ISR) & readl(port->base + GPIO_IMR);
    //呼叫中斷處理程式
    mxc_gpio_irq_handler(port, irq_stat);

    chained_irq_exit(chip, desc);
}

/* handle 32 interrupts in one status register */
static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat)
{
    while (irq_stat != 0) {
        //fls或去最高有效位(從低位往左數最後的有效bit位)
        //這裡就是得到哪個管腳觸發的中斷,一一處理,而且優先順序就是按照這個規則來了。
        int irqoffset = fls(irq_stat) - 1;

        //這裡的做法是為了讓沒有雙邊沿觸發的晶片,用輪流高低電平觸發的方式解決。
        //imx6晶片忽略就好,也看了半天才看清楚原來是這樣。
        if (port->both_edges & (1 << irqoffset))
            mxc_flip_edge(port, irqoffset);
        //irq_find_mapping 這個再說吧,先記一下
        generic_handle_irq(irq_find_mapping(port->domain, irqoffset));

        irq_stat &= ~(1 << irqoffset);
    }
}

static void mxc_flip_edge(struct mxc_gpio_port *port, u32 gpio)
{
    void __iomem *reg = port->base;
    u32 bit, val;
    int edge;

    /*0-15 ICR1 16-31 IRC2*/
    reg += GPIO_ICR1 + ((gpio & 0x10) >> 2); /* lower or upper register */
    bit = gpio & 0xf;
    val = readl(reg);
    edge = (val >> (bit << 1)) & 3;
    val &= ~(0x3 << (bit << 1));
    if (edge == GPIO_INT_HIGH_LEV) {
        edge = GPIO_INT_LOW_LEV;
        pr_debug("mxc: switch GPIO %d to low trigger\n", gpio);
    } else if (edge == GPIO_INT_LOW_LEV) {
        edge = GPIO_INT_HIGH_LEV;
        pr_debug("mxc: switch GPIO %d to high trigger\n", gpio);
    } else {
        pr_err("mxc: invalid configuration for GPIO %d: %x\n",
               gpio, edge);
        return;
    }
    writel(val | (edge << (bit << 1)), reg);
}

//中斷處理程式看完了,我們接著看註釋3

    err = bgpio_init(&port->bgc, &pdev->dev, 4,
             port->base + GPIO_PSR,
             port->base + GPIO_DR, NULL,
             port->base + GPIO_GDIR, NULL, 0);

    int bgpio_init(struct bgpio_chip *bgc, struct device *dev,
               unsigned long sz, void __iomem *dat, void __iomem *set,
               void __iomem *clr, void __iomem *dirout, void __iomem *dirin,
               unsigned long flags)
{
        int ret;

        if (!is_power_of_2(sz))
                return -EINVAL;

        bgc->bits = sz * 8; //暫存器位數 4*8=32

        spin_lock_init(&bgc->lock);
        bgc->gc.dev = dev;
        bgc->gc.label = dev_name(dev);
        bgc->gc.base = -1;
        bgc->gc.ngpio = bgc->bits; //gpio數量
        bgc->gc.request = bgpio_request;

        //1
        ret = bgpio_setup_io(bgc, dat, set, clr);

        //2
        ret = bgpio_setup_accessors(dev, bgc, flags & BGPIOF_BIG_ENDIAN,
                                    flags & BGPIOF_BIG_ENDIAN_BYTE_ORDER);

        //3
        ret = bgpio_setup_direction(bgc, dirout, dirin);

        bgc->data = bgc->read_reg(bgc->reg_dat);
        if (bgc->gc.set == bgpio_set_set &&
                        !(flags & BGPIOF_UNREADABLE_REG_SET))
                bgc->data = bgc->read_reg(bgc->reg_set);
        if (bgc->reg_dir && !(flags & BGPIOF_UNREADABLE_REG_DIR))
                bgc->dir = bgc->read_reg(bgc->reg_dir);

        return ret;

我們這裡主要對gc這個引數進行了初始化,這個結構gpio控制器,最重要的參io的數量,對應的操作函式。

struct gpio_chip {
        const char              *label;
        struct device           *dev;
        struct module           *owner;
        struct list_head        list;

        int                     (*request)(struct gpio_chip *chip, unsigned offset);
        void                    (*free)(struct gpio_chip *chip, unsigned offset);
        int                     (*get_direction)(struct gpio_chip *chip, unsigned offset);
        int                     (*direction_input)(struct gpio_chip *chip, unsigned offset);
        int                     (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
        int                     (*get)(struct gpio_chip *chip,  unsigned offset);
        void                    (*set)(struct gpio_chip *chip,  unsigned offset, int value);
        void                    (*set_multiple)(struct gpio_chip *chip,
                                                unsigned long *mask,
                                                unsigned long *bits);
        int                     (*set_debounce)(struct gpio_chip *chip,
                                                unsigned offset,
                                                unsigned debounce);

                int                     (*get_direction)(struct gpio_chip *chip,
                                                unsigned offset);
        int                     (*direction_input)(struct gpio_chip *chip,
                                                unsigned offset);
        int                     (*direction_output)(struct gpio_chip *chip,
                                                unsigned offset, int value);
        int                     (*get)(struct gpio_chip *chip,
                                                unsigned offset);
        void                    (*set)(struct gpio_chip *chip,
                                                unsigned offset, int value);
        void                    (*set_multiple)(struct gpio_chip *chip,
                                                unsigned long *mask,
                                                unsigned long *bits);
        int                     (*set_debounce)(struct gpio_chip *chip,
                                                unsigned offset,
                                                unsigned debounce);

        int                     (*to_irq)(struct gpio_chip *chip,
                                                unsigned offset);

        void                    (*dbg_show)(struct seq_file *s,
                                                struct gpio_chip *chip);
        int                     base;   
        u16                     ngpio;
        struct gpio_desc        *desc;
        const char              *const *names;
        bool                    can_sleep;
        bool                    irq_not_threaded;
        bool                    exported;

}

接著我們就看看三個setup函式如何去初始化這些介面.

static int bgpio_setup_io(struct bgpio_chip *bgc,
                          void __iomem *dat,
                          void __iomem *set,
                          void __iomem *clr)
{

        bgc->reg_dat = dat;
        if (!bgc->reg_dat)
                return -EINVAL;
        if (set && clr) {
                bgc->reg_set = set;
                bgc->reg_clr = clr;
                bgc->gc.set = bgpio_set_with_clear;
                bgc->gc.set_multiple = bgpio_set_multiple_with_clear;
        } else if (set && !clr) {
                bgc->reg_set = set;
                bgc->gc.set = bgpio_set_set;
                bgc->gc.set_multiple = bgpio_set_multiple_set;
        } else {
                bgc->gc.set = bgpio_set;
                bgc->gc.set_multiple = bgpio_set_multiple;
        }

        bgc->gc.get = bgpio_get;
        return 0;
}

配置了get和set函式,這裡我們沒有clr暫存器

static int bgpio_setup_accessors(struct device *dev,
                 struct bgpio_chip *bgc,
                 bool bit_be,
                 bool byte_be)
{

    switch (bgc->bits) {
    case 8:
        bgc->read_reg   = bgpio_read8;
        bgc->write_reg  = bgpio_write8;
        break;
    case 16:
        if (byte_be) {
            bgc->read_reg   = bgpio_read16be;
            bgc->write_reg  = bgpio_write16be;
        } else {
            bgc->read_reg   = bgpio_read16;
            bgc->write_reg  = bgpio_write16;
        }
        break;
    case 32:
        if (byte_be) {
            bgc->read_reg   = bgpio_read32be;
            bgc->write_reg  = bgpio_write32be;
        } else {
            bgc->read_reg   = bgpio_read32;
            bgc->write_reg  = bgpio_write32;
        }
        break;
#if BITS_PER_LONG >= 64
    case 64:
        if (byte_be) {
            dev_err(dev,
                "64 bit big endian byte order unsupported\n");
            return -EINVAL;
        } else {
            bgc->read_reg   = bgpio_read64;
            bgc->write_reg  = bgpio_write64;
        }
        break;
#endif /* BITS_PER_LONG >= 64 */
    default:
        dev_err(dev, "unsupported data width %u bits\n", bgc->bits);
        return -EINVAL;
    }

    bgc->pin2mask = bit_be ? bgpio_pin2mask_be : bgpio_pin2mask;

    return 0;
}

這裡配置暫存器讀寫大小端和位數的問題,像bgpio_get等函式都是會呼叫對應暫存器讀,這裡很簡單的流程就不在追下去了,接著是最後一個setup

static int bgpio_setup_direction(struct bgpio_chip *bgc,
                 void __iomem *dirout,
                 void __iomem *dirin)
{
    if (dirout && dirin) {
        return -EINVAL;
    } else if (dirout) {
        bgc->reg_dir = dirout;
        bgc->gc.direction_output = bgpio_dir_out;
        bgc->gc.direction_input = bgpio_dir_in;
    } else if (dirin) {
        bgc->reg_dir = dirin;
        bgc->gc.direction_output = bgpio_dir_out_inv;
        bgc->gc.direction_input = bgpio_dir_in_inv;
    } else {
        bgc->gc.direction_output = bgpio_simple_dir_out;
        bgc->gc.direction_input = bgpio_simple_dir_in;
    }

    return 0;
}

這裡我們只有一個dirout的暫存器,作為輸出只用將哪一個暫存器是能即可。
這裡init就分析完了,如何或去電平高低,設定io口方向,和輸出電平高低的功能都OK了。

最後probe 中一共最關鍵的函式gpiochip_add 我們再看看如何將GPIO控制器和gpiolib關聯。

關於中斷我們放到下一張講解。

接著我們看一下gpiochip_add

int gpiochip_add(struct gpio_chip *chip)
{
        unsigned long   flags;
        int             status = 0;
        unsigned        id;
        int             base = chip->base;
        struct gpio_desc *descs;

        descs = kcalloc(chip->ngpio, sizeof(descs[0]), GFP_KERNEL);
        if (!descs)
                return -ENOMEM;

        spin_lock_irqsave(&gpio_lock, flags);

        if (base < 0) {
                base = gpiochip_find_base(chip->ngpio);
                if (base < 0) {
                        status = base;
                        spin_unlock_irqrestore(&gpio_lock, flags);
                        goto err_free_descs;
                }
                chip->base = base;
 }

        status = gpiochip_add_to_list(chip);
        if (status) {
                spin_unlock_irqrestore(&gpio_lock, flags);
                goto err_free_descs;
        }

        for (id = 0; id < chip->ngpio; id++) {
                struct gpio_desc *desc = &descs[id];

                desc->chip = chip;

                /* REVISIT: most hardware initializes GPIOs as inputs (often
                 * with pullups enabled) so power usage is minimized. Linux
                 * code should set the gpio direction first thing; but until
                 * it does, and in case chip->get_direction is not set, we may
                 * expose the wrong direction in sysfs.
                 */
                desc->flags = !chip->direction_input ? (1 << FLAG_IS_OUT) : 0;
        }

        chip->desc = descs;
         spin_unlock_irqrestore(&gpio_lock, flags);

#ifdef CONFIG_PINCTRL
        INIT_LIST_HEAD(&chip->pin_ranges);
#endif

        of_gpiochip_add(chip);
        acpi_gpiochip_add(chip);

        status = gpiochip_export(chip);
        if (status)
                goto err_remove_chip;

        pr_debug("%s: registered GPIOs %d to %d on device: %s\n", __func__,
                chip->base, chip->base + chip->ngpio - 1,
                chip->label ? : "generic");

        return 0;

這裡最關鍵的就是將控制器加入到連結串列中,然後初始化了各個desc也就是管腳的資訊
gpiochip_add_to_list(chip);

struct gpio_desc *gpio_to_desc(unsigned gpio)
{
        struct gpio_chip *chip;
        unsigned long flags;

        spin_lock_irqsave(&gpio_lock, flags);

        list_for_each_entry(chip, &gpio_chips, list) {
                if (chip->base <= gpio && chip->base + chip->ngpio > gpio) {
                        spin_unlock_irqrestore(&gpio_lock, flags);
                        return &chip->desc[gpio - chip->base];
                }
        }

        spin_unlock_irqrestore(&gpio_lock, flags);

        if (!gpio_is_valid(gpio))
                WARN(1, "invalid GPIO %d\n", gpio);

        return NULL;
}

static bool _gpiod_get_raw_value(const struct gpio_desc *desc)
{
        struct gpio_chip        *chip;
        bool value;
        int offset;

        chip = desc->chip;
        offset = gpio_chip_hwgpio(desc);
        value = chip->get ? chip->get(chip, offset) : false;
        trace_gpio_value(desc_to_gpio(desc), 1, value);
        return value;
}



int gpiod_get_value_cansleep(const struct gpio_desc *desc)
{
        int value;

        might_sleep_if(extra_checks);
        if (!desc)
                return 0;

        value = _gpiod_get_raw_value(desc);
        if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
                value = !value;

        return value;
}

這裡發現gpio庫的變化非常大,類似gpio_direction_input的實現都消失了,並不瞭解為什麼?

如果我們在驅動中需要獲得某個關鍵的電平:
int val =gpiod_get_value_cansleep(gpio_to_desc(37));

首先會通過連結串列查詢到控制器,然後呼叫控制器的get函式或去暫存器的值。

後來發現編譯時會自動生成gpiolib庫的標頭檔案:

include/asm-generic/gpio.h
static inline int gpio_direction_input(unsigned gpio)
{
        return gpiod_direction_input(gpio_to_desc(gpio));
}
static inline int gpio_direction_output(unsigned gpio, int value)
{
        return gpiod_direction_output_raw(gpio_to_desc(gpio), value);
}

4.小結

開始提了3個問題

1. 驅動開發中的GPIO API 究竟是怎麼實現的?

這裡我們花了大力氣閱讀了晶片手冊和原始碼.重點關注了三個問題 gpio-mxc.c gpio-genric.c gpiolib.c
gpio-mxc 重點在維護的結構體mxc_gpio_port。

最重要的兩個結構:

  • struct bgpio_chip
  • struct gpio_chip gc;
struct mxc_gpio_port {
    struct list_head node;
    void __iomem *base;
    int irq;
    int irq_high;
    struct irq_domain *domain;
    struct bgpio_chip bgc;
    u32 both_edges;
};

struct bgpio_chip {
        struct gpio_chip gc;

        unsigned long (*read_reg)(void __iomem *reg);
        void (*write_reg)(void __iomem *reg, unsigned long data);
        void __iomem *reg_dat;
        void __iomem *reg_set;
        void __iomem *reg_clr;
        void __iomem *reg_dir;

        int bits;
        unsigned long (*pin2mask)(struct bgpio_chip *bgc, unsigned int pin);
        spinlock_t lock;
        unsigned long data;
        unsigned long dir;
};

通過bgpio_init() 完成了對struct bgpio_chip的初始化,尤其是和gpiolib互動的重要資料結構struct gpio_chip,至此完成對暫存器操作的抽象.

最後使用gpiolib_add()將我們的GPIO控制器加入到了gpiolib.c的控制器連結串列中,完成了gpiolib庫的封裝

2. GPIO的中斷又是怎麼實現的?
我們分析一部分程式碼,中斷函式的註冊和當中斷髮生後如何區分是哪個IO口發生的中斷,中斷控制器的實現我們還沒分析到,相信後面分析會將這個流程連線起來。

3. GPIO號和IRQ的號碼怎麼對映的?
中斷號和IO後對映問題,關鍵在函式irq_find_mapping中,我們暫時還沒有跳轉過程式碼去研究這一部分。

相關推薦

Linux 4.x Gpio分析Gpiolib1

晶片定義 裝置樹 程式碼 小結 下面的內容均在imx6平臺上舉例,這一次分析希望將整個GPIO子系統的所有細節整理清晰。 第一篇從gpiolib入手,後面的邊分析邊寫. 開始之前給自己提幾個問題 驅動開發中的GPIO API 究竟是怎麼實現的

linux學習筆記shell程式設計

shell程式設計 基礎正則表示式 正則和萬用字元的區別:正則是包含匹配,匹配檔案內容,grep,awk等支援正則表示式。萬用字元是完全匹配,匹配檔名,例如find,ls不認識正則表示式 ####正則

linux學習筆記流程控制if分支語句

流程控制 if語句 單分支if語句 程式 fi``` ```if [ 判斷 ] then 程式 fi``` 例項程式碼指令碼: #!/bin/bash

Linux常用命令新手上路

個人說明:對於習慣了Windows平臺的Linux初學者來說,剛接觸Linux時是比較懵逼的,為了幫助和我一樣的小白更加快速的入門,我打算養成學習筆記的習慣,將自己在學習Linux和Java的所學所得都記錄在部落格上。 新手入門8個常見的任務和常用的命令     1、檢視當

linux學習筆記shell程式設計正則表示式與字元處理

shell程式設計 基礎正則表示式 正則和萬用字元的區別:正則是包含匹配,匹配檔案內容,grep,awk等支援正則表示式。萬用字元是完全匹配,匹配檔名,例如find,ls不認識正則表示式 ####正則表示式常用的字元(注意區別於萬用字元裡面的符號)#### -*

Linux裝置驅動字元裝置

Linux中裝置驅動的分類 從上圖可以看到Linux系統將各異的裝置分為三大類:字元裝置,塊裝置和網路裝置。核心針對每一類裝置都提供了對應驅動模型架構,包括基本的核心設施和檔案系統介面。 字元裝置:在傳送過程中以字元為單位,一個位元組一個位元組的讀寫,不

LINUX日誌系統WEB日誌

linux日誌系統的重要性想必大家都非常清楚了,我們平時的登入資訊,各種操作資訊,軟體錯誤資訊等等,想要系統一直健康穩定的運轉,學會檢視和管理日誌系統是必不可少的技能。下面就來認識下web系統中的各種常用的日誌檔案:一、linux系統日誌:centos6上的日誌系統是rsyslog 啟動程序是rsyslogd

linux裝置驅動模型架構分析——概述

概述 LDD3中說:“Linux核心需要一個對系統結構的一般性描述。”這個描述就是linux裝置驅動模型(下面簡稱為LDDM)。LDDM不是獨立存在,其體系如下圖所示: LDDM體系結構 對架構的每一部分本文都會開闢獨立的章節進行描述。暫且拋開這個架構,首先從總體上了

Spring Framework 4.x 參考文件 Spring 入門

第一部分 Spring 框架概覽 Spring框架是您建立企業級應用的輕量級解決方案與具有潛力的一站式商店。不過,Spring是模組化的,允許你只使用需要的部分,而不必帶上其他部分。你可以使用IoC容器,頂層用任意web框架,你也可以只使用Hibernate整

Linux系統spi驅動程式分析---

說明:本文將分析Linux-2.6.17原始碼中的spi驅動程式,其內容為本人閱讀原始碼後的一些理解。由於本人水平有限,所以內容可能比較雜亂零散,權當個人筆記記錄之用。而以下內容均以powerpc架構為例說明。 在Linux系統中,spi驅動的設計採用了分層設計模式的思想

關於Linux系統性能瓶頸定位分析,Nginx狀態頁測試

關於系統性能瓶頸的定位,今天搬來一例項。希望和廣大網友溝通分享 使用場景:         Nginx對外提供介面服務,本文以Nginx的狀態頁(stub_status)為例。 需要解決的問題:         定位效能瓶頸,並調優 測試方法         使

linux核心SPI匯流排驅動分析

下面有兩個大的模組: 一個是SPI匯流排驅動的分析            (研究了具體實現的過程) 另一個是SPI匯流排驅動的編寫(不用研究具體的實現過程) SPI匯流排驅動分析 1 SPI概述      SPI是英語Serial Peripheral inte

MyBatis啟動分析

前言 MyBatis 作為目前最常用的持久層框架之一,分析其原始碼,對我們的使用過程中可更好的運用它。本系列基於mybatis-

文字挖掘情感分析

一、文字挖掘        文字挖掘則是對文字進行處理,從中挖掘出來文字中有用的資訊和關鍵的規則,在文字挖掘領域應用最往廣泛的是對文字進行分類和聚類,其挖掘的方法分為無監督學習和監督學習。文字挖掘還可以劃分為7大類:關鍵詞提取、文字摘要、文字主題模型、文字聚類

linux設備驅動misc驅動框架源碼分析

linux驅動開發misc設備驅動 1、misc設備驅動框架源碼部分是由內核開發者實現提供的,主要是創建misc類和為驅動開發者提供misc_register函數,來進行創建misc設備。 這部分的源碼在/drvier/char/misc.c裏,代碼如下:/* * linux/drivers/c

linux驅動開發蜂鳴器驅動源碼分析

linux 蜂鳴器 驅動 蜂鳴器的驅動源碼在/driver/char/buzzer/x210-buzzer.c文件中,源碼如下#include <linux/module.h> #include <linux/kernel.h> #include <linux

linux驅動篇 driver_register 過程分析

linux驅動註冊過程分析--driver_register(一) 個人筆記,歡迎轉載,請註明出處,共同分享 共同進步  http://blog.csdn.net/richard_liujh/article/details/45825333 kernel版本3.10.1

Android4.4.2原始碼分析WiFi模組

已經寫了幾篇關於Android原始碼的,原始碼程式碼量太大,所以如果想分析某個模組可能不知如何下手,說一下思路 1,分析原始碼英文閱讀能力要夠,想要分析某個模組一般找模組對應的英文,就是模組 2,找到之後首先檢視清單配置檔案Androidmani.fest,找到程式主介面activity 3,通過檢視配置檔

linux裝置驅動USB主機控制器驅動分析

一:前言 Usb是一個很複雜的系統.在usb2.0規範中,將其定義成了一個分層模型.linux中的程式碼也是按照這個分層模型來設計的.具體的分為 usb裝置,hub和主機控制器三部份.在閱讀程式碼的時候,必須要參考相應的規範.最基本的就是USB2.0的spec.

linux核心分析排程演算法

linux排程演算法在2.6.32中採用排程類實現模組式的排程方式。這樣,能夠很好的加入新的排程演算法。 linux排程器是以模組方式提供的,這樣做的目的是允許不同型別的程序可以有針對性地選擇排程演算法。這種模組化結構被稱為排程器類,他允許多種不同哦可動態新增的排程演算法並