DM365的BSP主要包含mach-davinci和plat-davinci兩個目錄(及相關頭文件),BSP復雜龐大又極其重要,它主要完成了板級的初始化,比如內存映射,時鐘和電源初始化,中斷和IO初始化,CPU及各模塊的初始化,相關平臺設備,總線設備的注冊等等。下面就分板一下DM365的BSP部分,以程序流程為縱線,以各個文件為橫線,進行分析。
?
????? 內核在經過一系列的初始化后會進行板級的初始化,主要依靠一個MACHINE_START宏,DM365的板定義宏在board-dm365-evm.c文件中,這個宏有以下幾個成員phys_io,io_pg_offst,boot_params,map_io,init_irq,timer,init_machine。這些成員變量或函數將會由內核在初始化時在適當的時機調用。DM365開發板對這幾個成員都有針對其開發板的內容,phy_io設置為0x01c00000,這是DM365寄存器的起始地址。boot_params為0x80000100,它在DDR中,是用來存放UBOOT的傳遞參數的。map_io會調用davinci_map_io函數,完成開發板的靜態地址映射,同時開發板中此函數還完成了MUX的功能初始化及各模塊時鐘的初始化。init_irq會調用davinci_dm365_evm_irq_init函數,對開發板的中斷系統進行初始化。timer會調用davinci_timer相關的成員,它完成開發板的時間管理初始化。init_machine調用dm365_evm_init函數,完成板級設備(部分)的初始化。下面我們從davinci_map_io函數開始。
?
????? davinci_map_io函數主要由三個部分組成,第一是中斷優先級的初始化,中斷優先級的初始化就是將全局中斷優先級結構指針davinci_irq_priorities指向一個默認的中斷優先級數組dm365_default_priorities,這個第二是調用davinci_map_common_io函數完成靜態地址映射,第三是調用davinci_init_common_hw函數完成MUX的功能初始化及各模塊時鐘的初始化。
?
????? davinci_map_common_io函數最主要就是完成了一段物理地址的靜態虛擬地址映射,此后程序中操作這段物理地址的內容時不必再用ioremap動態得到一個虛擬地址了。至于如何做到映射的,不用去深究,只需要在map_desc結構定義的本平臺使用的davinci_io_desc[]數組中中添加我們需要映射的地址段就可以了。開發板原程序中只映射了平臺寄存器部分的地址空間,其物理起始地址,地址段大小,欲映射成的虛擬起始地址等值填入這個結構中就可以了。這些值定義中io.h文件中,其中物理起始地址IO_PHYS為0x01c00000,這正是寄存器物理地址段的起始地址,地址段大小IO_SIZE為0x00400000,這兩個值由手冊都可以知道。至于欲映射成的虛擬起始地址則由我們人為決定,可以選用IO_VIRT為0xfbc00000,這樣物理地址和虛擬地址就有一個簡單的偏移關系IO_OFFSET為0xfa000000。有了這個偏移關系,物理地址便和虛擬地址隨時進行變換了,我們可以封裝一個宏IO_ADDRESS用來做為物理地址到虛擬地址的轉換。這里需要特別注意的,物理地址和虛擬地址的這層映射關系是內核來確定的,而兩個地址的簡單偏移關系是我們人為決定的,為了處理簡單我們選了一個與物理地址有簡單偏移關系的地址作為虛擬地址,并讓內核去認同。靜態映射一定要搞清楚這里面的東西,一開始很容易被迷惑的。davinci_map_common_io函數完成以后,接著是davinci_init_common_hw函數。
?
????? davinci_init_common_hw函數極其重要,它里面只有兩個函數,davinci_mux_init函數完成模塊引腳復用功能的初始化,davinci_clk_init函數完成模塊時鐘的初始化。
?
????? davinci_mux_init函數將各模塊的復用進行初始化。它首先將幾個重要的結構賦值,davinci_pins是二維指針,它被賦給一個和平臺有關的默認的全局結構,比如dm365_pins,它是一個二維數組,分別定義了若干模塊及每個模塊需要用到的引腳,注意這個維數的索引值是對應模塊在PSC管理中的索引值的。davinci_num_pins就是dm365_pins數組的維數。定義一個pin_config結構的指針table,并指向一個默認的全局結構數組davinci_dm365_pinmux,并得到這個數組的維數size。davinci_dm365_pinmux數組極其重要,它的每一維元素都是一個pin_config結構,它是用一個MUX_CFG宏來構造的,用于模塊引腳復用功能的選擇,實質是構造了欲對PINMUXx寄存器操作的位和欲寫入的值。這個要好好理解,配合手冊中PINMUXx寄存器理解。幾個結構賦值以后就調用davinci_mux_register函數,這個函數將table賦給了全局結構指針pin_table,將size賦給全局變量pin_table_sz,將davinci_get_pins函數地址賦給函數指針get_pin_list,將一個標識用到哪一個pinmux寄存器的數組首址賦給全局指針pinmux_in_use。davinci_get_pins這個函數的作用是通過參數ctr和id來返回davinci_pins數組的某一維,它表示某一個模塊用到的引腳。
?
????? davinci_clk_init函數完成模塊時鐘的初始化。它首先將PSC寄存器的地址賦給全局指針psc_bases,然后通過讀取PLL相關的寄存器得到預分頻,倍頻,后分頻,分頻等值(注意這些設置都在UBL進行設置好了,這里僅僅是讀值),這樣就得到了各個模塊的時鐘值,把它們賦給若干全局變量,比如armrate,voicerate,commonrate,vpssrate,ddrrate等。然后定義一個clk結構的指針clk_list,并指向一個默認的全局結構數組davinci_dm365_clks,并得到這個數組的維數num_clks。davinci_dm365_clks數組的每一維元素都是一個clk結構,定義了平臺各個模塊的時鐘管理參數,包括模塊名,時鐘值,本模塊在電源域的索引序號等。以后對某一模塊進行時鐘電源的開關就是操縱這個結構。
????? 然后調用davinci_enable_clks函數,這個函數實質是循環調用了clk_register函數和clk_enable函數,從而使能davinci_dm365_clks數組中定義的每一個模塊的電源時鐘。clk_register函數將當前模塊放入全局的clocks鏈表中,而后根據usecount的判斷值調用clk_enable函數,再調用__clk_enable函數,而其最終調用davinci_psc_config函數完成某一模塊的時鐘電源使能。需要特別注意的是此時幾乎全部模塊的usecount都不滿足,不會調用clk_enable函數,而即使調用了clk_enable函數(AEMIF),也不會真正調用__clk_enable函數。也就是說這初始化部分并沒有真正使能很多模塊的時鐘電源,只是按初始化的流程走了一個過程而已。在后面要提到的板級設備初始化部分會調用davinci_psc_config函數真正使能一些模塊的時鐘電源。另外其它模塊的時鐘電源則分布在各個模塊的驅動程序中,當驅動加載的時候才會調用clk_enable函數打開這個模塊的時鐘電源。這部分挺迷惑人的,好好理解。
????? davinci_psc_config函數的前一半工作是操作PSC相關的寄存器來使能模塊的時鐘電源,這部分的操作詳見數據手冊,是按手冊的步驟來實現的,另一半工作是調用davinci_pinmux_setup函數來將此模塊相關的復用引腳功能確定,它首先調用get_pin_list函數,它是個函數指針,在先前已經被指向了davinci_get_pins函數,它通過ctlr和id兩個參數從先前的dm365_pins數組中得到相應的某一維,這正是某一模塊需要的那些引腳。然后再通過一個循環將這幾個引腳調用davinci_cfg_reg函數實現其復用的本模塊的功能。
????? davinci_cfg_reg函數,很重要也很復雜。它定義一個pin_config類型的指針cfg指向當前引腳在pin_table的位置,然后根據其所在的PINMUXx寄存器的位為操作。這里的實現過程操作比較復雜,涉及到屏蔽位,模式等,但總的來說就是模塊需要這個引腳做此模塊功能而不是其它功能,暫不深究。
?
????? 到這里,davinci_cfg_reg函數結束了,davinci_pinmux_setup函數也就結束了,davinci_psc_config函數也就結束了,__clk_enable,clk_enable,davinci_enable_clks函數也就結束了,davinci_clk_init函數也就結束了,davinci_init_common_hw函數也就結束了,davinci_map_io函數也就結束了。DM365的BSP板定義宏中的map_io也就結束了。下面分析板定義宏中的init_irq,它指向davinci_dm365_evm_irq_init函數,下面開始分析davinci_dm365_evm_irq_init函數。
?
????? davinci_dm365_evm_irq_init函數,只調用一個davinci_irq_init函數。它首先獲得全部64個中斷的優先級數組,這個前面已經得到并放在全局數組davinci_irq_priorities中。而后操作INTC的寄存器,比如清除中斷請求,禁止中斷等,而后將設置好的優先級值寫入優先級寄存器中。然后就將全部64個中斷注冊到內核中,主要是利用內核中斷管理中的set_irq_chip函數,set_irq_flags函數,set_irq_handler函數,特別是set_irq_chip函數,它將每一個中斷綁定到一個irq_chip結構類型的davinci_irq_chip_0,而它包括了三個對中斷的處理的函數指針,比如使能unmask,禁止mask等,其實質還是操作了INTC的相關寄存器。這些函數就是在程序中,對某一中斷進行使能,禁止的處理,比如調用enable_irq使能某一個中斷時,其實最終就是調用unmask所指的函數。
?
????? 至此,davinci_irq_init函數結束了,davinci_dm365_evm_irq_init函數也就結束了。DM365的BSP板定義宏中的init_irq也就結束了。板定義宏中的timer感覺很復雜,現在不是很理解,以后有機會再看。下面分析板定義宏中的init_machine,它指向dm365_evm_init函數,下面開始分析dm365_evm_init函數。
?
????? dm365_evm_init函數是BSP中最重要的了,它完成了部分設備的初始化及注冊。
?
????? 內核在經過一系列的初始化后會進行板級的初始化,主要依靠一個MACHINE_START宏,DM365的板定義宏在board-dm365-evm.c文件中,這個宏有以下幾個成員phys_io,io_pg_offst,boot_params,map_io,init_irq,timer,init_machine。這些成員變量或函數將會由內核在初始化時在適當的時機調用。DM365開發板對這幾個成員都有針對其開發板的內容,phy_io設置為0x01c00000,這是DM365寄存器的起始地址。boot_params為0x80000100,它在DDR中,是用來存放UBOOT的傳遞參數的。map_io會調用davinci_map_io函數,完成開發板的靜態地址映射,同時開發板中此函數還完成了MUX的功能初始化及各模塊時鐘的初始化。init_irq會調用davinci_dm365_evm_irq_init函數,對開發板的中斷系統進行初始化。timer會調用davinci_timer相關的成員,它完成開發板的時間管理初始化。init_machine調用dm365_evm_init函數,完成板級設備(部分)的初始化。下面我們從davinci_map_io函數開始。
?
????? davinci_map_io函數主要由三個部分組成,第一是中斷優先級的初始化,中斷優先級的初始化就是將全局中斷優先級結構指針davinci_irq_priorities指向一個默認的中斷優先級數組dm365_default_priorities,這個第二是調用davinci_map_common_io函數完成靜態地址映射,第三是調用davinci_init_common_hw函數完成MUX的功能初始化及各模塊時鐘的初始化。
?
????? davinci_map_common_io函數最主要就是完成了一段物理地址的靜態虛擬地址映射,此后程序中操作這段物理地址的內容時不必再用ioremap動態得到一個虛擬地址了。至于如何做到映射的,不用去深究,只需要在map_desc結構定義的本平臺使用的davinci_io_desc[]數組中中添加我們需要映射的地址段就可以了。開發板原程序中只映射了平臺寄存器部分的地址空間,其物理起始地址,地址段大小,欲映射成的虛擬起始地址等值填入這個結構中就可以了。這些值定義中io.h文件中,其中物理起始地址IO_PHYS為0x01c00000,這正是寄存器物理地址段的起始地址,地址段大小IO_SIZE為0x00400000,這兩個值由手冊都可以知道。至于欲映射成的虛擬起始地址則由我們人為決定,可以選用IO_VIRT為0xfbc00000,這樣物理地址和虛擬地址就有一個簡單的偏移關系IO_OFFSET為0xfa000000。有了這個偏移關系,物理地址便和虛擬地址隨時進行變換了,我們可以封裝一個宏IO_ADDRESS用來做為物理地址到虛擬地址的轉換。這里需要特別注意的,物理地址和虛擬地址的這層映射關系是內核來確定的,而兩個地址的簡單偏移關系是我們人為決定的,為了處理簡單我們選了一個與物理地址有簡單偏移關系的地址作為虛擬地址,并讓內核去認同。靜態映射一定要搞清楚這里面的東西,一開始很容易被迷惑的。davinci_map_common_io函數完成以后,接著是davinci_init_common_hw函數。
?
????? davinci_init_common_hw函數極其重要,它里面只有兩個函數,davinci_mux_init函數完成模塊引腳復用功能的初始化,davinci_clk_init函數完成模塊時鐘的初始化。
?
????? davinci_mux_init函數將各模塊的復用進行初始化。它首先將幾個重要的結構賦值,davinci_pins是二維指針,它被賦給一個和平臺有關的默認的全局結構,比如dm365_pins,它是一個二維數組,分別定義了若干模塊及每個模塊需要用到的引腳,注意這個維數的索引值是對應模塊在PSC管理中的索引值的。davinci_num_pins就是dm365_pins數組的維數。定義一個pin_config結構的指針table,并指向一個默認的全局結構數組davinci_dm365_pinmux,并得到這個數組的維數size。davinci_dm365_pinmux數組極其重要,它的每一維元素都是一個pin_config結構,它是用一個MUX_CFG宏來構造的,用于模塊引腳復用功能的選擇,實質是構造了欲對PINMUXx寄存器操作的位和欲寫入的值。這個要好好理解,配合手冊中PINMUXx寄存器理解。幾個結構賦值以后就調用davinci_mux_register函數,這個函數將table賦給了全局結構指針pin_table,將size賦給全局變量pin_table_sz,將davinci_get_pins函數地址賦給函數指針get_pin_list,將一個標識用到哪一個pinmux寄存器的數組首址賦給全局指針pinmux_in_use。davinci_get_pins這個函數的作用是通過參數ctr和id來返回davinci_pins數組的某一維,它表示某一個模塊用到的引腳。
?
????? davinci_clk_init函數完成模塊時鐘的初始化。它首先將PSC寄存器的地址賦給全局指針psc_bases,然后通過讀取PLL相關的寄存器得到預分頻,倍頻,后分頻,分頻等值(注意這些設置都在UBL進行設置好了,這里僅僅是讀值),這樣就得到了各個模塊的時鐘值,把它們賦給若干全局變量,比如armrate,voicerate,commonrate,vpssrate,ddrrate等。然后定義一個clk結構的指針clk_list,并指向一個默認的全局結構數組davinci_dm365_clks,并得到這個數組的維數num_clks。davinci_dm365_clks數組的每一維元素都是一個clk結構,定義了平臺各個模塊的時鐘管理參數,包括模塊名,時鐘值,本模塊在電源域的索引序號等。以后對某一模塊進行時鐘電源的開關就是操縱這個結構。
????? 然后調用davinci_enable_clks函數,這個函數實質是循環調用了clk_register函數和clk_enable函數,從而使能davinci_dm365_clks數組中定義的每一個模塊的電源時鐘。clk_register函數將當前模塊放入全局的clocks鏈表中,而后根據usecount的判斷值調用clk_enable函數,再調用__clk_enable函數,而其最終調用davinci_psc_config函數完成某一模塊的時鐘電源使能。需要特別注意的是此時幾乎全部模塊的usecount都不滿足,不會調用clk_enable函數,而即使調用了clk_enable函數(AEMIF),也不會真正調用__clk_enable函數。也就是說這初始化部分并沒有真正使能很多模塊的時鐘電源,只是按初始化的流程走了一個過程而已。在后面要提到的板級設備初始化部分會調用davinci_psc_config函數真正使能一些模塊的時鐘電源。另外其它模塊的時鐘電源則分布在各個模塊的驅動程序中,當驅動加載的時候才會調用clk_enable函數打開這個模塊的時鐘電源。這部分挺迷惑人的,好好理解。
????? davinci_psc_config函數的前一半工作是操作PSC相關的寄存器來使能模塊的時鐘電源,這部分的操作詳見數據手冊,是按手冊的步驟來實現的,另一半工作是調用davinci_pinmux_setup函數來將此模塊相關的復用引腳功能確定,它首先調用get_pin_list函數,它是個函數指針,在先前已經被指向了davinci_get_pins函數,它通過ctlr和id兩個參數從先前的dm365_pins數組中得到相應的某一維,這正是某一模塊需要的那些引腳。然后再通過一個循環將這幾個引腳調用davinci_cfg_reg函數實現其復用的本模塊的功能。
????? davinci_cfg_reg函數,很重要也很復雜。它定義一個pin_config類型的指針cfg指向當前引腳在pin_table的位置,然后根據其所在的PINMUXx寄存器的位為操作。這里的實現過程操作比較復雜,涉及到屏蔽位,模式等,但總的來說就是模塊需要這個引腳做此模塊功能而不是其它功能,暫不深究。
?
????? 到這里,davinci_cfg_reg函數結束了,davinci_pinmux_setup函數也就結束了,davinci_psc_config函數也就結束了,__clk_enable,clk_enable,davinci_enable_clks函數也就結束了,davinci_clk_init函數也就結束了,davinci_init_common_hw函數也就結束了,davinci_map_io函數也就結束了。DM365的BSP板定義宏中的map_io也就結束了。下面分析板定義宏中的init_irq,它指向davinci_dm365_evm_irq_init函數,下面開始分析davinci_dm365_evm_irq_init函數。
?
????? davinci_dm365_evm_irq_init函數,只調用一個davinci_irq_init函數。它首先獲得全部64個中斷的優先級數組,這個前面已經得到并放在全局數組davinci_irq_priorities中。而后操作INTC的寄存器,比如清除中斷請求,禁止中斷等,而后將設置好的優先級值寫入優先級寄存器中。然后就將全部64個中斷注冊到內核中,主要是利用內核中斷管理中的set_irq_chip函數,set_irq_flags函數,set_irq_handler函數,特別是set_irq_chip函數,它將每一個中斷綁定到一個irq_chip結構類型的davinci_irq_chip_0,而它包括了三個對中斷的處理的函數指針,比如使能unmask,禁止mask等,其實質還是操作了INTC的相關寄存器。這些函數就是在程序中,對某一中斷進行使能,禁止的處理,比如調用enable_irq使能某一個中斷時,其實最終就是調用unmask所指的函數。
?
????? 至此,davinci_irq_init函數結束了,davinci_dm365_evm_irq_init函數也就結束了。DM365的BSP板定義宏中的init_irq也就結束了。板定義宏中的timer感覺很復雜,現在不是很理解,以后有機會再看。下面分析板定義宏中的init_machine,它指向dm365_evm_init函數,下面開始分析dm365_evm_init函數。
?
????? dm365_evm_init函數是BSP中最重要的了,它完成了部分設備的初始化及注冊。