linux驅動(第二十六課 基於DTS的系統移植)

對於系統移植者而言,主要是兩種驅動方面的工作
1)SOC已經提供的驅動,在DTS中進行對應修改,提供給內核板子相關的資源。
2)自己實現的驅動,在DTS中進行添加,提供相關的資源。並在drivers下添加對應的目錄或者文件。

本文來看看,如何讀懂SOC已經提供的驅動,DTS又是如何對應的。
內核提供了zedboard的DTS。我們來看看內核是如何構建device_node,又是如何創建device,並和對應的driver關聯起來的。
本文不關注於driver的具體語句的含義,而是將重心放在權項的使用上。

來看看根節點。
在arch/arm/mach-zynq/common.c中,如果匹配,則使用該文件中的
DT_MACHINE_START
MACHINE_END

來看看cpus.
主要是爲CPUFREQ驅動提供所需的資源。
參考documentation/devicetree/bindings/cpufreq/cpufreq-dt.txt。

來看看fpga-region。
驅動文件在/drivers/fpga/fpga-region.c中
參考Documentation\devicetree\bindings\fpga\fpga-region.txt。
主要是定義了關聯的FPGAMGR是哪一個設備節點。

來看看PMU。
驅動文件在perf_event_v7.c (kernel-201704\arch\arm\kernel)。
參考文件在pmu.txt (Documentation\devicetree\bindings\arm)。

來看看regulator
驅動文件在fixed.c (kernel-201704\drivers\regulator)
參考文件在fixed-regulator.txt (Documentation\devicetree\bindings\regulator)

來看看simplebus。
驅動文件在platform.c (drivers\of)
參考文件在resource-names.txt (Documentation\devicetree\bindings)

來看看memory.
參考文件在usage-model.txt (Documentation\devicetree)。
定義了可用的物理地址範圍。

memory@0 {
		device_type = "memory";
		reg = <0x0 0x20000000>;
};

定義了物理地址範圍是從0到512MByte。

來看ULPI_PHY。
驅動文件在phy-ulpi.c (drivers\usb\phy)

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ret = of_property_read_u32(np, "view-port", &uphy->vp_offset);
flag = of_property_read_bool(np, "drv-vbus");

這三條語句用於從DTB中獲取資源。

來看看adc。
驅動文件在xilinx-xadc-core.c (drivers\iio\adc)
參考文件在xilinx-xadc.txt (Documentation\devicetree\bindings\iio\adc)

來看看can。
驅動文件在xilinx_can.c (drivers\net\can)
參考文件在xilinx_can.txt (Documentation\devicetree\bindings\net\can)

來看看GPIO。
驅動文件在gpio-zynq.c (kernel-201704\drivers\gpio)
參考文件在gpio-zynq.txt (Documentation\devicetree\bindings\gpio)

來看看I2C。
驅動文件在i2c-cadence.c (drivers\i2c\busses)
參考文件在i2c-cadence.txt (Documentation\devicetree\bindings\i2c)

來看看INTC。
驅動文件在irq-gic.c (drivers\irqchip)
參考文件在arm,gic.txt (Documentation\devicetree\bindings\interrupt-controller)

來看看cache.
驅動文件在cache-l2x0.c (arch\arm\mm)
參考文件在l2c2x0.txt (Documentation\devicetree\bindings\arm)

來看看MEMCTRL。
驅動文件在pm.c (arch\arm\mach-zynq)
參考文件在synopsys.txt (Documentation\devicetree\bindings\memory-controllers):

來看看OCMCTRL。
驅動文件在zynq_ocm.c (arch\arm\mach-zynq)
參考文件在zynq-ocmc.txt (Documentation\devicetree\bindings\arm\zynq)

來看看UARTPS
驅動文件在xilinx_uartps.c (drivers\tty\serial)
參考文件在cdns,uart.txt (Documentation\devicetree\bindings\serial)

來看看SPIMASTER
驅動文件在spi-cadence.c (drivers\spi)
參考文件在spi-cadence.txt (Documentation\devicetree\bindings\spi)

來看看QSPI
驅動文件在spi-zynq-qspi.c (drivers\spi)
參考文件在spi-zynq-qspi.txt (Documentation\devicetree\bindings\spi)

來看看SMCCTRL
驅動文件在pl35x-smc.c (drivers\memory)
參考文件在pl353-smc.txt (Documentation\devicetree\bindings\memory-controllers)

來看看NANDFLASH
驅動文件在pl35x_nand.c (drivers\mtd\nand):
參考文件在pl353-smc.txt (Documentation\devicetree\bindings\memory-controllers)

來看看NORFLASH
驅動文件在pl35x-smc.c (drivers\memory)
參考文件在pl353-smc.txt (Documentation\devicetree\bindings\memory-controllers)

來看看GEM
驅動文件在macb.c (drivers\net\ethernet\cadence)
參考文件在macb.txt (Documentation\devicetree\bindings\net)

來看看SDHCI
驅動文件在sdhci-of-arasan.c (drivers\mmc\host)
參考文件在arasan,sdhci.txt (Documentation\devicetree\bindings\mmc)

來看看SLCR
驅動文件在slcr.c (arch\arm\mach-zynq)

來看看CLKCTRL
驅動文件在clkc.c (drivers\clk\zynq)
參考文件在zynq-7000.txt (Documentation\devicetree\bindings\clock)

來看看RSTCTRL
驅動文件在reset-zynq.c (drivers\reset)
參考文件在zynq-reset.txt (Documentation\devicetree\bindings\reset)

來看看PINCTRL
驅動文件在pinctrl-zynq.c (drivers\pinctrl)
參考文件在xlnx,zynq-pinctrl.txt (Documentation\devicetree\bindings\pinctrl)

來看看DMACTRL
參考文件在arm-pl330.txt (Documentation\devicetree\bindings\dma)

來看看DEVCFG
驅動文件在xilinx_devcfg.c (drivers\char)
參考文件在xilinx-zynq-fpga-mgr.txt (Documentation\devicetree\bindings\fpga)

來看看EFUSE
驅動文件在efuse.c (arch\arm\mach-zynq)
參考文件在zynq-efuse.txt (Documentation\devicetree\bindings\arm\zynq)

來看看GLOBAL TIMER
驅動文件在arm_global_timer.c (drivers\clocksource)
參考文件在global_timer.txt (Documentation\devicetree\bindings\arm)

來看看TTC
驅動文件在cadence_ttc_timer.c (drivers\clocksource)
參考文件在cadence,ttc-timer.txt (Documentation\devicetree\bindings\timer)

來看看SCUTIMER
驅動文件在smp_twd.c (arch\arm\kernel)
參考文件在twd.txt (Documentation\devicetree\bindings\arm)

來看看USB
驅動文件在ci_hdrc_usb2.c (drivers\usb\chipidea)
參考文件在ci-hdrc-usb2.txt (Documentation\devicetree\bindings\usb)

來看看WATCHDOG
驅動文件在cadence_wdt.c (drivers\watchdog)
參考文件在cadence-wdt.txt (Documentation\devicetree\bindings\watchdog

從這些設備節點中,我們可以總結出一些通用的權項。
0)status,用來指定葉子設備節點的enable狀態。有"okay""disabled"兩種,如果是disabled,那麼解析時,不會生成設備節點。
1)reg,用來指定寄存器所在的地址區間。這個地址,通過數據手冊可以查找,ug585。
例如:

platform_get_resource(pdev, IORESOURCE_MEM, 0);
of_iomap(np, 0);

2)interrupt-parent,用來指定中斷控制器。通常是GIC。被引用爲&intc。
3)interrupts,以條目列表的形式,按表項順序指定設備所使用的irq。這個權項,通過數據手冊ug585也是可以查找的。
例如:

platform_get_irq(pdev, 0);
irq_of_parse_and_map(np, 1);

4)interrupt-names,以字符串列表的形式,按表項順序指定設備使用的irq的name。

5)clocks,以條目列表的形式,按表項順序指定設備所使用的clock。
例如:

of_clk_get(np, 0);

6)clock-names,以字符串列表的形式,按表項順序指定設備使用的clock的name。
例如:

devm_clk_get(&pdev->dev, "pclk");
devm_clk_get(&pdev->dev, NULL);

7)gpios,以條目列表的形式,按表項順序指定設備所使用的gpios。

8)gpio-names,以字符串列表的形式,按表項順序指定設備使用的gpio的name。
例如:

of_get_named_gpio(phy_node, "reset-gpios", 0);

9)named property,屬於設備特定的權項。
例如:

of_property_read_u32(pdev->dev.of_node, "tx-fifo-depth", &tx_max);

下面,我們來仔細分析下面幾個重要的設備節點。
1)intc

intc: interrupt-controller@f8f01000 {
			compatible = "arm,cortex-a9-gic";
			#interrupt-cells = <3>;
			interrupt-controller;
			reg = <0xF8F01000 0x1000>,
			      <0xF8F00100 0x100>;
};

從中看出,聲明瞭兩個地址區間。對應於驅動函數中的

gic->raw_dist_base = of_iomap(node, 0);
gic->raw_cpu_base = of_iomap(node, 1);

從GIC的參考文件中,我們可以得到如下信息:
The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI interrupts.
The 2nd cell contains the interrupt number for the interrupt type.
SPI interrupts are in the range [0-987]. PPI interrupts are in the range [0-15].
The 3rd cell is the flags, encoded as follows:
bits[3:0] trigger type and level flags.
1 = low-to-high edge triggered
2 = high-to-low edge triggered (invalid for SPIs)
4 = active high level-sensitive
8 = active low level-sensitive (invalid for SPIs).

2)clkc

slcr: slcr@f8000000 {
			#address-cells = <1>;
			#size-cells = <1>;
			compatible = "xlnx,zynq-slcr", "syscon", "simple-mfd";
			reg = <0xF8000000 0x1000>;
			ranges;
			clkc: clkc@100 {
				#clock-cells = <1>;
				compatible = "xlnx,ps7-clkc";
				fclk-enable = <0xf>;
				clock-output-names = 
						"armpll", "ddrpll", "iopll", "cpu_6or4x",
						"cpu_3or2x", "cpu_2x", "cpu_1x", "ddr2x", 
						"ddr3x","dci", "lqspi", "smc", 
						"pcap", "gem0", "gem1","fclk0", 
						"fclk1", "fclk2", "fclk3", "can0", 
						"can1","sdio0", "sdio1", "uart0", 
						"uart1", "spi0", "spi1","dma", 
						"usb0_aper", "usb1_aper", "gem0_aper","gem1_aper", 
						"sdio0_aper", "sdio1_aper","spi0_aper", "spi1_aper", 
						"can0_aper", "can1_aper","i2c0_aper", "i2c1_aper", 
						"uart0_aper", "uart1_aper","gpio_aper", "lqspi_aper", 
						"smc_aper", "swdt","dbg_trc", "dbg_apb"
						;
				reg = <0x100 0x100>;
			};		
};

&clkc {
	ps-clk-frequency = <33333333>;
};

從中可以看出,CLKC位於SLCR的地址區間,所以用偏移的方式,給出了CLKC的reg。
DTS中按順序指定了各個CLK的名字。
來看看驅動函數中如何處理。

enum zynq_clk {
	armpll, ddrpll, iopll,
	cpu_6or4x, cpu_3or2x, cpu_2x, cpu_1x,
	ddr2x, ddr3x, dci,
	lqspi, smc, pcap, gem0, gem1, fclk0, fclk1, fclk2, fclk3, can0, can1,
	sdio0, sdio1, uart0, uart1, spi0, spi1, dma,
	usb0_aper, usb1_aper, gem0_aper, gem1_aper,
	sdio0_aper, sdio1_aper, spi0_aper, spi1_aper, can0_aper, can1_aper,
	i2c0_aper, i2c1_aper, uart0_aper, uart1_aper, gpio_aper, lqspi_aper,
	smc_aper, swdt, dbg_trc, dbg_apb, clk_max
};


for (i = 0; i < clk_max; i++) {
		if (of_property_read_string_index(np, "clock-output-names",
				  i, &clk_output_name[i])) {
			pr_err("%s: clock output name not in DT\n", __func__);
		}
}

of_property_read_u32(np, "fclk-enable", &fclk_enable);

ret = of_property_read_u32(np, "ps-clk-frequency", &tmp);
if (ret) {
	pr_warn("ps_clk frequency not specified, using 33 MHz.\n");
	tmp = 33333333;
}

驅動模塊中,定義了enum,對應於各個時鐘的名字。編號從0開始。

驅動函數中,讀取了各個時鐘的名字。
然後讀取了FCLK的使能情況。
然後讀取了PSCLK的頻率。

3)gpioc

gpio0: gpio@e000a000 {
			compatible = "xlnx,zynq-gpio-1.0";
			#gpio-cells = <2>;
			clocks = <&clkc 42>;
			gpio-controller;
			interrupt-controller;
			#interrupt-cells = <2>;
			interrupt-parent = <&intc>;
			interrupts = <0 20 4>;
			reg = <0xe000a000 0x1000>;
};

從中可以看出,
DTS中,指定了REG,
指定了interrupt-parent以及所需要的IRQ,
指定了CLK,
指定了GPIOC同時是一個INTC。

來看看驅動函數

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

gpio->irq = platform_get_irq(pdev, 0);

gpio->clk = devm_clk_get(&pdev->dev, NULL);

ret = gpiochip_irqchip_add(chip, &zynq_gpio_edge_irqchip, 0,
				   handle_level_irq, IRQ_TYPE_NONE);
gpiochip_set_chained_irqchip(chip, &zynq_gpio_edge_irqchip, gpio->irq,
				     zynq_gpio_irqhandler);

這些代碼,需要使用到DTS中提供的信息。

作爲系統移植工程師,並不需要寫驅動模塊,但是需要十分清楚DTS中的信息,是如何被驅動函數使用的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章