第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)
http://www.cnblogs.com/pengdonglin137/p/6241895.html
参考代码: 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);
	
	
临时笔记:
(1) 下面是确定内核的虚拟地址、物理地址的关键信息, 感兴趣的同学可以自己看:
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)
00-Linux设备树系列-简介 - 飞翔de刺猬 - CSDN博客.html
https://blog.csdn.net/lhl_blog/article/details/82387486
Linux kernel的中断子系统之(二):IRQ Domain介绍_搜狐科技_搜狐网.html
http://www.sohu.com/a/201793206_467784
基于设备树的TQ2440的中断(1)
https://www.cnblogs.com/pengdonglin137/p/6847685.html
基于设备树的TQ2440的中断(2)
https://www.cnblogs.com/pengdonglin137/p/6848851.html
基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)
http://www.cnblogs.com/pengdonglin137/p/6349209.html
Linux kernel的中断子系统之(一):综述
http://www.wowotech.net/irq_subsystem/interrupt_subsystem_architecture.html
Linux kernel的中断子系统之(二):IRQ Domain介绍
linux kernel的中断子系统之(三):IRQ number和中断描述符
linux kernel的中断子系统之(四):High level irq event handler
Linux kernel中断子系统之(五):驱动申请中断API
Linux kernel的中断子系统之(六):ARM中断处理过程
linux kernel的中断子系统之(七):GIC代码分析
原文:https://www.cnblogs.com/liusiluandzhangkun/p/11925064.html