第六课. 设备树的实践操作

it2024-10-08  37

设备树的实践操作

第01节_使用设备树给DM9000网卡_触摸屏指定中断第02节_在设备树中时钟的简单使用第03节_在设备树中pinctrl的简单使用第04节_使用设备树给LCD指定各种参数后记:vmlinux虚拟地址和物理地址的确定

第01节_使用设备树给DM9000网卡_触摸屏指定中断

修改方法:

根据设备节点的compatible属性,在驱动程序中构造/注册 platform_driver,在 platform_driver 的 probe 函数中获得中断资源

实验方法:

以下是修改好的代码: 第6课第1节_网卡_触摸屏驱动\001th_dm9000\dm9dev9000c.c 第6课第1节_网卡_触摸屏驱动\002th_touchscreen\s3c_ts.c 分别上传到内核如下目录: drivers/net/ethernet/davicom drivers/input/touchscreen a. 编译内核 b. 使用新的uImage启动 c. 测试网卡:     ifconfig eth0 192.168.1.101     ping 192.168.1.1 d. 测试触摸屏:     hexdump /dev/evetn0 // 然后点击触摸屏


第02节_在设备树中时钟的简单使用

文档:   内核 Documentation/devicetree/bindings/clock/clock-bindings.txt   内核 Documentation/devicetree/bindings/clock/samsung,s3c2410-clock.txt

a. 设备树中定义了各种时钟, 在文档中称之为"Clock providers", 比如: clocks: clock-controller@4c000000 { compatible = "samsung,s3c2440-clock"; reg = <0x4c000000 0x20>; #clock-cells = <1>; // 想使用这个clocks时要提供1个u32来指定它, 比如选择这个clocks中发出的LCD时钟、PWM时钟 }; b. 设备需要时钟时, 它是"Clock consumers", 它描述了使用哪一个"Clock providers"中的哪一个时钟(id), 比如: fb0: fb@4d000000{ compatible = "jz2440,lcd"; reg = <0x4D000000 0x60>; interrupts = <0 0 16 3>; clocks = <&clocks HCLK_LCD>; // 使用clocks即clock-controller@4c000000中的HCLK_LCD }; c. 驱动中获得/使能时钟: // 确定时钟个数 int nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks", "#clock-cells"); // 获得时钟 for (i = 0; i < nr_pclks; i++) { struct clk *clk = of_clk_get(dev->of_node, i); } // 使能时钟 clk_prepare_enable(clk); // 禁止时钟 clk_disable_unprepare(clk);

第03节_在设备树中pinctrl的简单使用

文档:   内核 Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt

几个概念: Bank: 以引脚名为依据, 这些引脚分为若干组, 每组称为一个Bank 比如s3c2440里有GPA、GPB、GPC等Bank, 每个Bank中有若干个引脚, 比如GPA0,GPA1, ..., GPC0, GPC1,...等引脚 Group: 以功能为依据, 具有相同功能的引脚称为一个Group 比如s3c2440中串口0的TxD、RxD引脚使用 GPH2,GPH3, 那这2个引脚可以列为一组 比如s3c2440中串口0的流量控制引脚使用 GPH0,GPH1, 那这2个引脚也可以列为一组 State: 设备的某种状态, 比如内核自己定义的"default","init","idel","sleep"状态; 也可以是其他自己定义的状态, 比如串口的"flow_ctrl"状态(使用流量控制) 设备处于某种状态时, 它可以使用若干个Group引脚 a. 设备树中pinctrl节点: a.1 它定义了各种 pin bank, 比如s3c2440有GPA,GPB,GPC,...,GPB各种BANK, 每个BANK中有若干引脚: pinctrl_0: pinctrl@56000000 { reg = <0x56000000 0x1000>; gpa: gpa { gpio-controller; #gpio-cells = <2>; /* 以后想使用gpa bank中的引脚时, 需要2个u32来指定引脚 */ }; gpb: gpb { gpio-controller; #gpio-cells = <2>; }; gpc: gpc { gpio-controller; #gpio-cells = <2>; }; gpd: gpd { gpio-controller; #gpio-cells = <2>; }; }; a.2 它还定义了各种group(组合), 某种功能所涉及的引脚称为group, 比如串口0要用到2个引脚: gph0, gph1: uart0_data: uart0-data { samsung,pins = "gph-0", "gph-0"; samsung,pin-function = <2>; /* 在GPHCON寄存器中gph0,gph1可以设置以下值: 0 --- 输入功能 1 --- 输出功能 2 --- 串口功能 我们要使用串口功能, samsung,pin-function 设置为2 */ }; uart0_sleep: uart0_sleep { samsung,pins = "gph-0", "gph-1"; samsung,pin-function = <0>; /* 在GPHCON寄存器中gph0,gph1可以设置以下值: 0 --- 输入功能 1 --- 输出功能 2 --- 串口功能 我们要使用输入功能, samsung,pin-function 设置为0 */ }; b. 设备节点中要使用某一个 pin group: serial@50000000 { ...... pinctrl-names = "default", "sleep"; /* 既是名字, 也称为state(状态) */ pinctrl-0 = <&uart0_data>; pinctrl-1 = <&uart0_sleep>; }; // pinctrl-names中定义了2种state: default 和 sleep, // default 对应的引脚是: pinctrl-0, 它指定了使用哪些pin group: uart0_data // sleep 对应的引脚是: pinctrl-1, 它指定了使用哪些pin group: uart0_sleep c. platform_device, platform_driver匹配时: //"第3课第06节_platform_device跟platform_driver的匹配" 中讲解了platform_device和platform_driver的匹配过程, //最终都会调用到 really_probe (drivers/base/dd.c) really_probe: /* If using pinctrl, bind pins now before probing */ ret = pinctrl_bind_pins(dev); dev->pins->default_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_DEFAULT); /* 获得"default"状态的pinctrl */ dev->pins->init_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_INIT); /* 获得"init"状态的pinctrl */ ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state); /* 优先设置"init"状态的引脚 */ ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state); /* 如果没有init状态, 则设置"default"状态的引脚 */ ...... ret = drv->probe(dev); //所以: 如果设备节点中指定了pinctrl, 在对应的probe函数被调用之前, 先"bind pins", 即先绑定、设置引脚 d. 驱动中想选择、设置某个状态的引脚: devm_pinctrl_get_select_default(struct device *dev); // 使用"default"状态的引脚 pinctrl_get_select(struct device *dev, const char *name); // 根据name选择某种状态的引脚 pinctrl_put(struct pinctrl *p); // 不再使用, 退出时调用

第04节_使用设备树给LCD指定各种参数

参考文章: 让TQ2440也用上設设备树(1)

参考代码: https://github.com/pengdonglin137/linux-4.9/blob/tq2440_dt/drivers/video/fbdev/s3c2410fb.c

实验方法: 所用文件在: doc_and_sources_for_device_tree\source_and_images\第5,6课的源码及映像文件(使用了完全版的设备树)\第6课第4节_LCD驱动\02th_我修改的

a. 替换dts文件: 把"jz2440_irq.dts" 放入内核 arch/arm/boot/dts目录, b. 替换驱动文件: 把"s3c2410fb.c" 放入内核 drivers/video/fbdev/ 目录, 修改 内核 drivers/video/fbdev/Makefile : obj-$(CONFIG_FB_S3C2410) += lcd_4.3.o 改为: obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o c. 编译驱动、编译dtbs: export PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin cp config_ok .config make uImage // 生成 arch/arm/boot/uImage make dtbs // 生成 arch/arm/boot/dts/jz2440_irq.dtb d. 使用上述uImage, dtb启动内核即可看到LCD有企鹅出现 (1). 设备树中的描述: fb0: fb@4d000000{ compatible = "jz2440,lcd"; reg = <0x4D000000 0x60>; interrupts = <0 0 16 3>; clocks = <&clocks HCLK_LCD>; /* a. 时钟 */ clock-names = "lcd"; pinctrl-names = "default"; /* b. pinctrl */ pinctrl-0 = <&lcd_pinctrl &lcd_backlight &gpb0_backlight>; status = "okay"; /* c. 根据LCD引脚特性设置lcdcon5, 指定lcd时序参数 */ lcdcon5 = <0xb09>; type = <0x60>; width = /bits/ 16 <480>; height = /bits/ 16 <272>; pixclock = <100000>; /* 单位: ps, 10^-12 S, */ xres = /bits/ 16 <480>; yres = /bits/ 16 <272>; bpp = /bits/ 16 <16>; left_margin = /bits/ 16 <2>; right_margin =/bits/ 16 <2>; hsync_len = /bits/ 16 <41>; upper_margin = /bits/ 16 <2>; lower_margin = /bits/ 16 <2>; vsync_len = /bits/ 16 <10>; }; &pinctrl_0 { gpb0_backlight: gpb0_backlight { samsung,pins = "gpb-0"; samsung,pin-function = <1>; samsung,pin-val = <1>; }; }; (2) 代码中的处理: a. 时钟: info->clk = of_clk_get(dev->of_node, 0); clk_prepare_enable(info->clk); b. pinctrl: 代码中无需处理, 在 platform_device/platform_driver匹配之后就会设置"default"状态对应的pinctrl c. 根据LCD引脚特性设置lcdcon5, 指定lcd时序参数: 直接读设备树节点中的各种属性值, 用来设置驱动参数 of_property_read_u32(np, "lcdcon5", (u32 *)(&display->lcdcon5)); of_property_read_u32(np, "type", &display->type); of_property_read_u16(np, "width", &display->width); of_property_read_u16(np, "height", &display->height); of_property_read_u32(np, "pixclock", &display->pixclock); of_property_read_u16(np, "xres", &display->xres); of_property_read_u16(np, "yres", &display->yres); of_property_read_u16(np, "bpp", &display->bpp); of_property_read_u16(np, "left_margin", &display->left_margin); of_property_read_u16(np, "right_margin", &display->right_margin); of_property_read_u16(np, "hsync_len", &display->hsync_len); of_property_read_u16(np, "upper_margin", &display->upper_margin); of_property_read_u16(np, "lower_margin", &display->lower_margin); of_property_read_u16(np, "vsync_len", &display->vsync_len);

后记:vmlinux虚拟地址和物理地址的确定

vmlinux虚拟地址的确定:

内核源码: .config : CONFIG_PAGE_OFFSET=0xC0000000 arch/arm/include/asm/memory.h #define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET) arch/arm/Makefile textofs-y := 0x00008000 TEXT_OFFSET := $(textofs-y) arch/arm/kernel/vmlinux.lds.S: . = PAGE_OFFSET + TEXT_OFFSET; // // 即0xC0000000+0x00008000 = 0xC0008000, vmlinux的虚拟地址为0xC0008000 arch/arm/kernel/head.S #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET) // 即0xC0000000+0x00008000 = 0xC0008000

vmlinux物理地址的确定:

内核源码: arch/arm/mach-s3c24xx/Makefile.boot : zreladdr-y += 0x30008000 // zImage自解压后得到vmlinux, vmlinux的存放位置 params_phys-y := 0x30000100 // tag参数的存放位置, 使用dtb时不再需要tag arch/arm/boot/Makefile: ZRELADDR := $(zreladdr-y) arch/arm/boot/Makefile: UIMAGE_LOADADDR=$(ZRELADDR) scripts/Makefile.lib: UIMAGE_ENTRYADDR ?= $(UIMAGE_LOADADDR) // 制作uImage的命令, uImage = 64字节的头部 + zImage, 头部信息中含有内核的入口地址(就是vmlinux的物理地址) cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \ -C $(UIMAGE_COMPRESSION) $(UIMAGE_OPTS-y) \ -T $(UIMAGE_TYPE) \ -a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \ -n $(UIMAGE_NAME) -d $(UIMAGE_IN) $(UIMAGE_OUT)
以上内容参考自韦东山老师设备树的教学资料
最新回复(0)