/* * TI DaVinci GPIO Support * * Copyright (c) 2006 David Brownell * Copyright (c) 2007, MontaVista Software, Inc. <source@mvista.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */
#include <linux/errno.h> #include <linux/kernel.h> #include <linux/list.h> #include <linux/module.h> #include <linux/err.h> #include <linux/bitops.h>
#include <asm/irq.h> #include <asm/io.h> #include <asm/hardware/clock.h>
#include <asm/arch/irqs.h> #include <asm/arch/hardware.h> #include <asm/arch/gpio.h> #include <asm/arch/cpu.h>
#include <asm/mach/irq.h>
/* 該文件實現了gpio的各種應用功能和向內核註冊gpio的中斷例程等功能。 用戶的驅動程序可呼叫gpio_request和gpio_free使用或釋放該gpio, 可以呼叫gpio_direction_input和gpio_direction_output函數設置gpio輸入輸出方向, 呼叫gpio_get_value和gpio_set_value獲取設置值。 */
static DEFINE_SPINLOCK(gpio_lock);
/* 總共有DAVINCI_N_GPIO(71)個gpio引腳,故使用相應多的bit來記錄這些引腳的使用狀態 */ static DECLARE_BITMAP(gpio_in_use, DAVINCI_N_GPIO);
/* 申請一個gpio,其實就是檢查該gpio是否空閒,如果空閒就可以使用並將該gpio相應的bit置位 (在gpio_in_use中)。 */ int gpio_request(unsigned gpio, const char *tag) { if (gpio >= DAVINCI_N_GPIO) return -EINVAL;
if (test_and_set_bit(gpio, gpio_in_use)) return -EBUSY;
return 0; } EXPORT_SYMBOL(gpio_request);
/* 釋放一個gpio,其實就是清除gpio相應的控制bit位(在gpio_in_use中)。 */ void gpio_free(unsigned gpio) { if (gpio >= DAVINCI_N_GPIO) return;
clear_bit(gpio, gpio_in_use); } EXPORT_SYMBOL(gpio_free);
/* 獲得gpio_controller結構體指針,gpio_controller結構體是gpio的核心控制單元,裡面包含 gpio的設置和數據暫存器。該結構體和__gpio_to_controller函數在/include/asm-arm/ arch-davinci/gpio.h中定義,具體如下: struct gpio_controller { u32 dir; u32 out_data; u32 set_data; u32 clr_data; u32 in_data; u32 set_rising; u32 clr_rising; u32 set_falling; u32 clr_falling; u32 intstat; };
static inline struct gpio_controller *__iomem __gpio_to_controller(unsigned gpio) { void *__iomem ptr;
if (gpio >= DAVINCI_N_GPIO) return NULL;
if (gpio < 32) ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x10); else if (gpio < 64) ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x38); else if (gpio < 96) ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x60); else ptr = (void *__iomem)IO_ADDRESS(DAVINCI_GPIO_BASE + 0x88);
return ptr; } 由上面的定義和ti的SPRUE25.pdf手冊可以看出,__gpio_to_controller函數返回的是 gpio_controller結構體到第一個成員dir的虛擬地址。獲取了這個結構體指針後, 便可以控制相應的gpio了。dm644x共有71個gpio, 所以使用三個gpio_controller結構體控制,關於這個後面會由更詳細的分析, */ /* create a non-inlined version */ static struct gpio_controller *__iomem gpio2controller(unsigned gpio) { return __gpio_to_controller(gpio); }
/* 向某個gpio設置值,0或1。如果向gpio寫1,則向set_data暫存器相應的位置1,如果寫0, 則向clr_data暫存器相應的位置1.__gpio_mask函數在gpio.h中定義,定義如下, static inline u32 __gpio_mask(unsigned gpio) { return 1 << (gpio % 32); } 因為71個引腳由3個結構體控制,第一個控制前32個gpio,第二個控制次32個gpio, 最後一個控制剩餘的7個gpio,故__gpio_mask函數的作用是找到在其相應控制結構體裡的偏移數, 比如gpio34,那麼其由第二個結構體控制,在這個機構體裡的偏移是3(從0開始算,就是第二位)。 使用這個函數之前,必須確認該gpio設置成輸出模式。 */ /* * Assuming the pin is muxed as a gpio output, set its output value. */ void __gpio_set(unsigned gpio, int value) { struct gpio_controller *__iomem g = gpio2controller(gpio);
// 設置gpio的值
__raw_writel(__gpio_mask(gpio), value ? &g->set_data : &g->clr_data); } EXPORT_SYMBOL(__gpio_set);
/* 通過讀取in_data暫存器相應該gpio的位來讀取gpio的值。 使用這個函數之前,必須確認該gpio設置成輸入模式,否則獲得到值不可預料。 */ /* * Read the pin's value (works even if it's set up as output); * returns zero/nonzero. * * Note that changes are synched to the GPIO clock, so reading values back * right after you've set them may give old values. */ int __gpio_get(unsigned gpio) { struct gpio_controller *__iomem g = gpio2controller(gpio); /* 讀取gpio的值,!!的目的是使得返回的值為0或1.*/ return !!(__gpio_mask(gpio) & __raw_readl(&g->in_data)); } } EXPORT_SYMBOL(__gpio_get);
/* 通過dir暫存器相應該gpio的位來設置gpio輸入輸出方向,為0,則設置成輸出,為1,則設置出輸入。 該函數是設置成輸入,故設置dir暫存器為1. 正如應為所說的,必須確認該引腳是作為gpio功能,而不是某個模塊到功能,比如spi。通過PINMUX0 和PINMUX1兩個暫存器來設置。 */ /*--------------------------------------------------------------------------*/
/* * board setup code *MUST* set PINMUX0 and PINMUX1 as * needed, and enable the GPIO clock. */ int gpio_direction_input(unsigned gpio) { struct gpio_controller *__iomem g = gpio2controller(gpio); u32 temp; u32 mask;
if (!g) return -EINVAL;
spin_lock(&gpio_lock); mask = __gpio_mask(gpio); temp = __raw_readl(&g->dir); temp |= mask; // 設置成1
__raw_writel(temp, &g->dir); // 設置該gpio為輸入
spin_unlock(&gpio_lock); return 0; } EXPORT_SYMBOL(gpio_direction_input);
/* 通過dir暫存器相應該gpio的位來設置gpio輸入輸出方向,為0,則設置成輸出,為1,則設置出輸入。 該函數是設置成輸出,故設置dir暫存器為0. value參數用於選擇gpio設置成輸出後該gpio輸出的值。 */ int gpio_direction_output(unsigned gpio, int value) { struct gpio_controller *__iomem g = gpio2controller(gpio); u32 temp; u32 mask;
if (!g) return -EINVAL;
spin_lock(&gpio_lock); mask = __gpio_mask(gpio); temp = __raw_readl(&g->dir); temp &= ~mask; // 設置成0
//設置該gpio輸出值
__raw_writel(mask, value ? &g->set_data : &g->clr_data); __raw_writel(temp, &g->dir); // 設置gpio為輸出
spin_unlock(&gpio_lock); return 0; } EXPORT_SYMBOL(gpio_direction_output);
/* 向gpio設置值,0或1。 */ void gpio_set_value(unsigned gpio, int value) { if (__builtin_constant_p(value)) { struct gpio_controller *__iomem g; u32 mask;
if (gpio >= DAVINCI_N_GPIO) __error_inval_gpio();
g = __gpio_to_controller(gpio); mask = __gpio_mask(gpio); if (value) __raw_writel(mask, &g->set_data); // 該gpio輸出高
else __raw_writel(mask, &g->clr_data); // 該gpio輸出低
return; }
__gpio_set(gpio, value); } EXPORT_SYMBOL(gpio_set_value);
/* 讀取gpio的值,0或1. */ int gpio_get_value(unsigned gpio) { struct gpio_controller *__iomem g;
if (!__builtin_constant_p(gpio))/* 判斷該gpio值是否為編譯時常數,如果是常數, 函數返回 1,否則返回 0 */ return __gpio_get(gpio);
if (gpio >= DAVINCI_N_GPIO) return __error_inval_gpio();
g = __gpio_to_controller(gpio); // 讀取該gpio的值
return !!(__gpio_mask(gpio) & __raw_readl(&g->in_data)); } EXPORT_SYMBOL(gpio_get_value);
/* * We expect irqs will normally be set up as input pins, but they can also be * used as output pins ... which is convenient for testing. * * NOTE: GPIO0..GPIO7 also have direct INTC hookups, which work in addition * to their GPIOBNK0 irq (but with a bit less overhead). But we don't have * a good way to hook those up ... * * All those INTC hookups (GPIO0..GPIO7 plus five IRQ banks) can also * serve as EDMA event triggers. */
/* 禁止相應該irq的gpio的中斷。每個gpio都可以作為中斷的來源,其中gpio0-gpio7是獨立的中斷來源, 也就是分配獨立的中斷號,其他gpio則共用5個GPIOBNK中斷線。其優先級可以在board-evm.c 中設置(已經介紹過)。在dm644x平台上,中斷是電平邊緣觸發的,禁止中斷其實就是既不設置 上升沿觸發,也不設置下降沿觸發。 */ static void gpio_irq_disable(unsigned irq) { struct gpio_controller *__iomem g = get_irq_chipdata(irq); u32 mask = __gpio_mask(irq_to_gpio(irq));
__raw_writel(mask, &g->clr_falling); // 清除下降沿觸發
__raw_writel(mask, &g->clr_rising); // 清除上升沿觸發
}
/* 中斷使能。 在dm644x平台上,中斷是電平邊緣觸發的,其實就是設置為上升沿或下降沿中斷。 */ static void gpio_irq_enable(unsigned irq) { struct gpio_controller *__iomem g = get_irq_chipdata(irq); u32 mask = __gpio_mask(irq_to_gpio(irq));
// 如果先前為下降沿中斷,則使能為下降沿中斷
if (irq_desc[irq].status & IRQT_FALLING) __raw_writel(mask, &g->set_falling); // 如果先前為上升沿中斷,則使能為上升沿中斷
if (irq_desc[irq].status & IRQT_RISING) __raw_writel(mask, &g->set_rising); }
/* 設置中斷類型。 在dm644x平台上,中斷有上升沿和下降沿兩種觸發方式。 */ static int gpio_irq_type(unsigned irq, unsigned trigger) { struct gpio_controller *__iomem g = get_irq_chipdata(irq); u32 mask = __gpio_mask(irq_to_gpio(irq));
if (trigger & ~(IRQT_FALLING | IRQT_RISING)) return -EINVAL;
irq_desc[irq].status &= ~IRQT_BOTHEDGE; irq_desc[irq].status |= trigger;
__raw_writel(mask, (trigger & IRQT_FALLING) ? &g->set_falling : &g->clr_falling); // 設置為下降沿觸發
__raw_writel(mask, (trigger & IRQT_RISING) ? &g->set_rising : &g->clr_rising); // 設置為上升沿觸發
return 0; }
/* 該結構體用於註冊到所有irq的中斷描述結構體中(struct irqdesc), 而所有中斷描述結構體定義成一個全局數組irq_desc 。 */ static struct irqchip gpio_irqchip = { .unmask = gpio_irq_enable, /* 用於使能中斷, 在enable_irq()等內核函數中會用到。*/ .mask = gpio_irq_disable,/* 用於禁止中斷, 在disable_irq()等內核函數中會用到。*/ .type = gpio_irq_type, /* 用於設置中斷類型, 在set_irq_type()內核函數中會用到。*/ };
/* 該函數將在下面的davinci_gpio_irq_setup中使用,將被註冊到五個gpio bank中斷的 irq_desc結構中,目的是處理所有級聯的gpio中斷。所謂級聯的中斷, 就是指有n個中斷 共用同一個中斷線。 在dm644x平台中,除了gpio0-gpio7外,其他63個gpio都共用五個gpiobank中斷線,在這裡, gpio0-gpio7也被註冊到gpiobank中斷線,但實際上並不會使用,因為它們擁有自己的 中斷線。其中,gpio0-gpio15共用IRQ_GPIOBNK0(56)中斷線,gpio16-gpio31共用 IRQ_GPIOBNK1(57)中斷線,gpio32-gpio47共用IRQ_GPIOBNK2(58)中斷線, gpio48-gpio63共用IRQ_GPIOBNK4(59)中斷線,gpio64-gpio70共用 IRQ_GPIOBNK5(60)中斷線, 因為暫存器是32位的,所以實際上只有三組暫存器,第一組包含bank0和bank1, 也就是gpio0-gpio31,第二組包含bank2和bank3,也就是gpio32-gpio63, 第三組包含bank4和bank5,也就是gpio64-gpio70,剩餘了25個位沒有使用。 */ static void gpio_irq_handler(unsigned irq, struct irqdesc *desc, struct pt_regs *regs) { struct gpio_controller *__iomem g = get_irq_chipdata(irq); u32 mask = 0xffff;
/* we only care about one bank */ // 如果bank中斷線是寄數,則說明該中斷的中斷狀態位在INTSTATn暫存器的高16位
if (irq & 1) mask <<= 16;
/* temporarily mask (level sensitive) parent IRQ */ desc->chip->ack(irq);// 該ack函數會在arch/arm/mach-davinci/irq.c中註冊。
while (1) { u32 status; struct irqdesc *gpio; int n; int res;
/* ack any irqs */ /*gpio中斷髮生後,硬體會在INTSTATn暫存器中置位相應位, 以備程序查詢,確定是哪個gpio*/ status = __raw_readl(&g->intstat) & mask; if (!status) break; __raw_writel(status, &g->intstat); // 向該位寫1清除
if (irq & 1) status >>= 16;
/* now demux them to the right lowlevel handler */ // 從下面的davinci_gpio_irq_setup函數可以看出來以下程序的運作。
n = (int)get_irq_data(irq); // 獲取該bank對應的第一個gpio號
gpio = &irq_desc[n]; // 獲取該bank第一個gpio號對應的中斷描述符
while (status) { // 該bank可能有多個gpio發生了中斷
res = ffs(status); // 獲取第一個發生了中斷的位(1-32)
n += res; /* 獲得該gpio的中斷線(系統實際上只有64(0-63)個中斷線, 但那些共用的gpio的中斷也有自己的斷描述符和中斷線(從64開始), 僅僅是為了管理,不能通過request_irq()函數來申請。*/ gpio += res; // 獲得該gpio的中斷描述符
/* 呼叫下面註冊的do_simple_IRQ例程 其又會呼叫用戶通過request_irq() 註冊的中斷例程 */ desc_handle_irq(n - 1, gpio - 1, regs); status >>= res; } } desc->chip->unmask(irq); // 打開該irq中斷線
/* now it may re-trigger */ }
/* * NOTE: for suspend/resume, probably best to make a sysdev (and class) * with its suspend/resume calls hooking into the results of the set_wake() * calls ... so if no gpios are wakeup events the clock can be disabled, * with outputs left at previously set levels, and so that VDD3P3V.IOPWDN0 * can be set appropriately for GPIOV33 pins. */ /* 註冊gpio中斷例程到內核中,並初始化了一些暫存器。 該函數將會被board_evm.c(其淺析已經發表)中的evm_init()函數呼叫。具體呼叫過程如下: start_kernel()-->setup_arch()-->init_machine = mdesc->init_machine (init_machine是個全局函數指針變量,其指向的就是已經註冊到機器描述符裡evm_init()); 呼叫函數指針init_machine()的例程是customize_machine(),其定義為 arch_initcall(customize_machine),所以,接下來的呼叫過程是: start_kernel()-->do_basic_setup()-->do_initcalls()-->customize_machine()--> init_machine()(也就是evm_init())-->davinci_gpio_irq_setup。 從上可以看出經歷了兩個過程,才呼叫davinci_gpio_irq_setup例程來初始化gpio中斷。 */ int __init davinci_gpio_irq_setup(void) { unsigned gpio, irq, bank, banks; struct clk *clk;
clk = clk_get(NULL, "gpio"); // 獲取時鐘
if (IS_ERR(clk)) { printk(KERN_ERR "Error %ld getting gpio clock?\n", PTR_ERR(clk)); return 0; }
clk_enable(clk); // 使能gpio時鐘並打開該模塊電源
for (gpio = 0, irq = gpio_to_irq(0), bank = (cpu_is_davinci_dm355() ? IRQ_DM355_GPIOBNK0 : (cpu_is_davinci_dm6467() ? IRQ_DM646X_GPIOBNK0 : IRQ_GPIOBNK0)); // dm644x的IRQ_GPIOBNK0(56)
gpio < DAVINCI_N_GPIO; bank++) { // dm644x的DAVINCI_N_GPIO(71)
struct gpio_controller *__iomem g = gpio2controller(gpio); unsigned i;
// 關該bank所有gpio的中斷
__raw_writel(~0, &g->clr_falling); __raw_writel(~0, &g->clr_rising);
/* set up all irqs in this bank */ // 同一個bank的所有gpio共用一個中斷例程gpio_irq_handler
set_irq_chained_handler(bank, gpio_irq_handler); set_irq_chipdata(bank, g); set_irq_data(bank, (void *)irq);
for (i = 0; i < 16 && gpio < DAVINCI_N_GPIO; i++, irq++, gpio++) { set_irq_chip(irq, &gpio_irqchip); /* 註冊用於gpio中斷禁止、設能 和類型選擇的回調例程 */ set_irq_chipdata(irq, g); // 保存控制結構體(暫存器)的地址
set_irq_handler(irq, do_simple_IRQ);/* 為每個gpio中斷設置同一個中 斷例程do_simple_IRQ*/ set_irq_flags(irq, IRQF_VALID); // fiq中斷有效
} } /* 一個共用bank中斷線的gpio中斷髮生後的大致的流程是: --> gpio_irq_handler --> do_simple_IRQ --> __do_irq --> action->handler(用戶使用request_irq()註冊的中斷例程) */ /* BINTEN -- per-bank interrupt enable. genirq would also let these * bits be set/cleared dynamically. */ if (cpu_is_davinci_dm355()) banks = 0x3f; else banks = 0x1f; // 向BINTEN暫存器寫入0x1f(共5個位,每個位控制1個bank),打開所有的bank中斷
__raw_writel(banks, (void *__iomem) IO_ADDRESS(DAVINCI_GPIO_BASE + 0x08));
printk(KERN_INFO "DaVinci: %d gpio irqs\n", irq - gpio_to_irq(0));
return 0; }
|