• 嵌入式Linux驱动开发(LCD屏幕专题)(三)


    1. 硬件相关的操作

    LCD驱动程序的核心就是:

    • 分配fb_info
    • 设置fb_info
    • 注册fb_info
    • 硬件相关的设置

    硬件相关的设置又可以分为3部分:

    • 引脚设置
    • 时钟设置
    • LCD控制器设置

    2. 在设备树里指定LCD参数

    	framebuffer-mylcd {
    			compatible = "100ask,lcd_drv";
    	        pinctrl-names = "default";
    			pinctrl-0 = <&mylcd_pinctrl>;
    			backlight-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
    
                clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
                         <&clks IMX6UL_CLK_LCDIF_APB>;
                clock-names = "pix", "axi";
                
                display = <&display0>;
    
    			display0: display {
    				bits-per-pixel = <24>;
    				bus-width = <24>;
    
    				display-timings {
    					native-mode = <&timing0>;
    
    					 timing0: timing0_1024x768 {
    					 clock-frequency = <50000000>;
    					 hactive = <1024>;
    					 vactive = <600>;
    					 hfront-porch = <160>;
    					 hback-porch = <140>;
    					 hsync-len = <20>;
    					 vback-porch = <20>;
    					 vfront-porch = <12>;
    					 vsync-len = <3>;
    
    					 hsync-active = <0>;
    					 vsync-active = <0>;
    					 de-active = <1>;
    					 pixelclk-active = <0>;
    					 };
    
    				};
    			};            
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    3. 编程

    3.1 从设备树获得参数

    时序参数、引脚极性等信息,都被保存在一个display_timing结构体里:
    在这里插入图片描述

    参考内核文件:

    • drivers\video\of_display_timing.c
    • drivers\video\fbdev\mxsfb.c
    3.2 使用参数配置LCD控制器

    根据芯片手册,一个一个设置寄存器

    • Framebuffer地址设置
    • Framebuffer中数据格式设置
    • LCD时序参数设置
    • LCD引脚极性设置

    4.上机实验

    1. 要做的事情

    • 去除内核自带的驱动程序

    • 加入我们编写的驱动程序、设备树文件

    • 重新编译内核、设备树

    • 上机测试:使用编译出来的内核、设备树启动板子

    2. 去除内核自带的驱动程序

    修改内核文件:drivers/video/fbdev/Makefile,把内核自带驱动程序mxsfb.c对应的那行注释掉,如下:

    #obj-$(CONFIG_FB_MXS)             += mxsfb.o
    
    • 1

    3. 加入新驱动程序、设备树

    • 复制驱动程序:

      • 11_lcd_drv_imx6ull_ok\lcd_drv.c放到内核源码目录drivers/video/fbdev
      • 备份内核自带设备树文件:arch/arm/boot/dts/100ask_imx6ull-14x14.dts
      • 11_lcd_drv_imx6ull_ok\100ask_imx6ull-14x14.dts放到内核源码目录arch/arm/boot/dts/
    • 修改内核文件:

      • 修改:drivers/video/fbdev/Makefile,使用我们提供的lcd_drv.c,如下:
    #obj-$(CONFIG_FB_MXS)             += mxsfb.o
    obj-$(CONFIG_FB_MXS)             += lcd_drv.o
    
    • 1
    • 2

    4. 重新编译内核、设备树

    以下命令在Ubuntu中执行。

    • 设置工具链
      export ARCH=arm
      export CROSS_COMPILE=arm-linux-gnueabihf-
      export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
    
    • 1
    • 2
    • 3
    • 配置、编译
      book@100ask:~/100ask_imx6ull-sdk$ cd Linux-4.9.88
      book@100ask:~/100ask_imx6ull-sdk/Linux-4.9.88$ make 100ask_imx6ull_defconfig   
      book@100ask:~/100ask_imx6ull-sdk/Linux-4.9.88$ make zImage 
      book@100ask:~/100ask_imx6ull-sdk/Linux-4.9.88$ make dtbs
    
    • 1
    • 2
    • 3
    • 4
    • 得到

      • 内核:arch/arm/boot/zImage
      • 设备树文件:arch/arm/boot/dts/100ask_imx6ull-14x14.dtb
    • 复制到NFS目录:

      $ cp arch/arm/boot/zImage ~/nfs_rootfs/
      $ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
    
    • 1
    • 2

    5. 上机测试

    以下命令在开发板中执行。

    • 挂载NFS

      • vmware使用NAT(假设windowsIP为192.168.1.100)

        [root@100ask:~]# mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 
        192.168.1.100:/home/book/nfs_rootfs /mnt
        
        • 1
        • 2
      • vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为192.168.1.137

        [root@100ask:~]#  mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
        
        • 1
    • 更新单板文件

      [root@100ask:~]# cp /mnt/zImage /boot
      [root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot
      [root@100ask:~]# sync
    
    • 1
    • 2
    • 3
    • 重启开发板观察现象

      • 如果可以看到企鹅LOGO,就表示正常
      • 如果在终端中可以查看到存在/dev/fb0节点,也表示正常
    • 解决BUG

      • 现象:LCD上没有企鹅LOGO,在终端中执行ls -l /dev/fb0发现没有设备节点

      • 观察内核启动信息,看到:

        [    0.619880] imx6ul-pinctrl 20e0000.iomuxc: pin MX6UL_PAD_GPIO1_IO08 already requested by 2080000.pwm; cannot claim for 21c8000.framebuffer-mylcd
        [    0.619920] imx6ul-pinctrl 20e0000.iomuxc: pin-31 (21c8000.framebuffer-mylcd) status -22
        [    0.619954] imx6ul-pinctrl 20e0000.iomuxc: could not request pin 31 (MX6UL_PAD_GPIO1_IO08) from group mylcd_pingrp  on device 20e0000.iomuxc
        [    0.619985] mylcd 21c8000.framebuffer-mylcd: Error applying setting, reverse things back
        [    0.620070] mylcd: probe of 21c8000.framebuffer-mylcd failed with error -22
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • 原因:引脚冲突

        • 设备树中pwm节点、framebuffer-mylcd节点,都使用到的同一个引脚:PAD_GPIO1_IO08
      • 解决方法:修改arch/arm/boot/dts/100ask_imx6ull-14x14.dts,禁止pwm节点,如下:
        在这里插入图片描述

    6、代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    struct imx6ull_lcdif {
      volatile unsigned int CTRL;                              
      volatile unsigned int CTRL_SET;                        
      volatile unsigned int CTRL_CLR;                         
      volatile unsigned int CTRL_TOG;                         
      volatile unsigned int CTRL1;                             
      volatile unsigned int CTRL1_SET;                         
      volatile unsigned int CTRL1_CLR;                       
      volatile unsigned int CTRL1_TOG;                       
      volatile unsigned int CTRL2;                            
      volatile unsigned int CTRL2_SET;                       
      volatile unsigned int CTRL2_CLR;                        
      volatile unsigned int CTRL2_TOG;                        
      volatile unsigned int TRANSFER_COUNT;   
           unsigned char RESERVED_0[12];
      volatile unsigned int CUR_BUF;                          
           unsigned char RESERVED_1[12];
      volatile unsigned int NEXT_BUF;                        
           unsigned char RESERVED_2[12];
      volatile unsigned int TIMING;                          
           unsigned char RESERVED_3[12];
      volatile unsigned int VDCTRL0;                         
      volatile unsigned int VDCTRL0_SET;                      
      volatile unsigned int VDCTRL0_CLR;                     
      volatile unsigned int VDCTRL0_TOG;                     
      volatile unsigned int VDCTRL1;                          
           unsigned char RESERVED_4[12];
      volatile unsigned int VDCTRL2;                          
           unsigned char RESERVED_5[12];
      volatile unsigned int VDCTRL3;                          
           unsigned char RESERVED_6[12];
      volatile unsigned int VDCTRL4;                           
           unsigned char RESERVED_7[12];
      volatile unsigned int DVICTRL0;    
      	   unsigned char RESERVED_8[12];
      volatile unsigned int DVICTRL1;                         
           unsigned char RESERVED_9[12];
      volatile unsigned int DVICTRL2;                        
           unsigned char RESERVED_10[12];
      volatile unsigned int DVICTRL3;                        
           unsigned char RESERVED_11[12];
      volatile unsigned int DVICTRL4;                          
           unsigned char RESERVED_12[12];
      volatile unsigned int CSC_COEFF0;  
      	   unsigned char RESERVED_13[12];
      volatile unsigned int CSC_COEFF1;                        
           unsigned char RESERVED_14[12];
      volatile unsigned int CSC_COEFF2;                        
           unsigned char RESERVED_15[12];
      volatile unsigned int CSC_COEFF3;                        
           unsigned char RESERVED_16[12];
      volatile unsigned int CSC_COEFF4;   
      	   unsigned char RESERVED_17[12];
      volatile unsigned int CSC_OFFSET;  
           unsigned char RESERVED_18[12];
      volatile unsigned int CSC_LIMIT;  
           unsigned char RESERVED_19[12];
      volatile unsigned int DATA;                              
           unsigned char RESERVED_20[12];
      volatile unsigned int BM_ERROR_STAT;                     
           unsigned char RESERVED_21[12];
      volatile unsigned int CRC_STAT;                        
           unsigned char RESERVED_22[12];
      volatile  unsigned int STAT;                             
           unsigned char RESERVED_23[76];
      volatile unsigned int THRES;                             
           unsigned char RESERVED_24[12];
      volatile unsigned int AS_CTRL;                           
           unsigned char RESERVED_25[12];
      volatile unsigned int AS_BUF;                            
           unsigned char RESERVED_26[12];
      volatile unsigned int AS_NEXT_BUF;                     
           unsigned char RESERVED_27[12];
      volatile unsigned int AS_CLRKEYLOW;                    
           unsigned char RESERVED_28[12];
      volatile unsigned int AS_CLRKEYHIGH;                   
           unsigned char RESERVED_29[12];
      volatile unsigned int SYNC_DELAY;                      
    } ;
    
    struct lcd_regs {
    	volatile unsigned int fb_base_phys;
    	volatile unsigned int fb_xres;
    	volatile unsigned int fb_yres;
    	volatile unsigned int fb_bpp;	
    };
    
    static struct lcd_regs *mylcd_regs;
    static struct fb_info *myfb_info;
    static unsigned int pseudo_palette[16];
    
    static struct gpio_desc *bl_gpio;
    static struct clk* clk_pix;
    static struct clk* clk_axi;
    
    static void lcd_controller_enable(struct imx6ull_lcdif *lcdif)
    {
    	lcdif->CTRL |= (1<<0);
    }
    
    static int lcd_controller_init(struct imx6ull_lcdif *lcdif, struct display_timing *dt, int lcd_bpp, int fb_bpp, unsigned int fb_phy)
    {
    	int lcd_data_bus_width;
    	int fb_width;
    	int vsync_pol = 0;
    	int hsync_pol = 0;
    	int de_pol = 0;
    	int clk_pol = 0;
    
    	if (dt->flags & DISPLAY_FLAGS_HSYNC_HIGH)
    		hsync_pol = 1;
    	if (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH)
    		vsync_pol = 1;
    	if (dt->flags & DISPLAY_FLAGS_DE_HIGH)
    		de_pol = 1;
    	if (dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
    		clk_pol = 1;
    	
    	if (lcd_bpp == 24)
    		lcd_data_bus_width = 0x3;
    	else if (lcd_bpp == 18)
    		lcd_data_bus_width = 0x2;
    	else if (lcd_bpp == 8)
    		lcd_data_bus_width = 0x1;
    	else if (lcd_bpp == 16)
    		lcd_data_bus_width = 0x0;
    	else
    		return -1;
    
    	if (fb_bpp == 24 || fb_bpp == 32)
    		fb_width = 0x3;
    	else if (fb_bpp == 18)
    		fb_width = 0x2;
    	else if (fb_bpp == 8)
    		fb_width = 0x1;
    	else if (fb_bpp == 16)
    		fb_width = 0x0;
    	else
    		return -1;
    
    	/* 
         * 初始化LCD控制器的CTRL寄存器
         * [19]       :  1      : DOTCLK和DVI modes需要设置为1 
         * [17]       :  1      : 设置为1工作在DOTCLK模式
         * [15:14]    : 00      : 输入数据不交换(小端模式)默认就为0,不需设置
         * [13:12]    : 00      : CSC数据不交换(小端模式)默认就为0,不需设置
         * [11:10]    : 11		: 数据总线为24bit
         * [9:8]    根据显示屏资源文件bpp来设置:8位0x1 , 16位0x0 ,24位0x3
         * [5]        :  1      : 设置elcdif工作在主机模式
         * [1]        :  0      : 24位数据均是有效数据,默认就为0,不需设置
    	 */	
    	lcdif->CTRL = (0<<30) | (0<<29) | (0<<28) | (1<<19) | (1<<17) | (lcd_data_bus_width << 10) |\
    	              (fb_width << 8) | (1<<5);
    
    	/*
    	* 设置ELCDIF的寄存器CTRL1
    	* 根据bpp设置,bpp为24或32才设置
    	* [19:16]  : 111  :表示ARGB传输格式模式下,传输24位无压缩数据,A通道不用传输)
    	*/	  
    	if(fb_bpp == 24 || fb_bpp == 32)
    	{	  
    		  lcdif->CTRL1 &= ~(0xf << 16); 
    		  lcdif->CTRL1 |=  (0x7 << 16); 
    	}
    	else
    		lcdif->CTRL1 |= (0xf << 16); 
    	  
    	/*
    	* 设置ELCDIF的寄存器TRANSFER_COUNT寄存器
    	* [31:16]  : 垂直方向上的像素个数  
    	* [15:0]   : 水平方向上的像素个数
    	*/
    	lcdif->TRANSFER_COUNT  = (dt->vactive.typ << 16) | (dt->hactive.typ << 0);
    
    	/*
    	* 设置ELCDIF的VDCTRL0寄存器
    	* [29] 0 : VSYNC输出  ,默认为0,无需设置
    	* [28] 1 : 在DOTCLK模式下,设置1硬件会产生使能ENABLE输出
    	* [27] 0 : VSYNC低电平有效	,根据屏幕配置文件将其设置为0
    	* [26] 0 : HSYNC低电平有效 , 根据屏幕配置文件将其设置为0
    	* [25] 1 : DOTCLK下降沿有效 ,根据屏幕配置文件将其设置为1
    	* [24] 1 : ENABLE信号高电平有效,根据屏幕配置文件将其设置为1
    	* [21] 1 : 帧同步周期单位,DOTCLK mode设置为1
    	* [20] 1 : 帧同步脉冲宽度单位,DOTCLK mode设置为1
    	* [17:0] :  vysnc脉冲宽度 
    	*/
    	  lcdif->VDCTRL0 = (1 << 28)|( vsync_pol << 27)\
    					  |( hsync_pol << 26)\
    					  |( clk_pol << 25)\
    					  |( de_pol << 24)\
    					  |(1 << 21)|(1 << 20)|( dt->vsync_len.typ << 0);
    	/*
    	* 设置ELCDIF的VDCTRL1寄存器
    	* 设置垂直方向的总周期:上黑框tvb+垂直同步脉冲tvp+垂直有效高度yres+下黑框tvf
    	*/	  
    	lcdif->VDCTRL1 = dt->vback_porch.typ + dt->vsync_len.typ + dt->vactive.typ + dt->vfront_porch.typ;  
    
    	/*
    	* 设置ELCDIF的VDCTRL2寄存器
    	* [18:31]  : 水平同步信号脉冲宽度
    	* [17: 0]   : 水平方向总周期
    	* 设置水平方向的总周期:左黑框thb+水平同步脉冲thp+水平有效高度xres+右黑框thf
    	*/ 
    	lcdif->VDCTRL2 = (dt->hsync_len.typ << 18) | (dt->hback_porch.typ + dt->hsync_len.typ + dt->hactive.typ + dt->hfront_porch.typ);
    	/*
    	* 设置ELCDIF的VDCTRL3寄存器
    	* [27:16] :水平方向上的等待时钟数 =thb + thp
    	* [15:0]  : 垂直方向上的等待时钟数 = tvb + tvp
    	*/ 
    	lcdif->VDCTRL3 = ((dt->hback_porch.typ + dt->hsync_len.typ) << 16) | (dt->vback_porch.typ + dt->vsync_len.typ);
    	/*
    	* 设置ELCDIF的VDCTRL4寄存器
    	* [18]	   使用VSHYNC、HSYNC、DOTCLK模式此为置1
    	* [17:0]  : 水平方向的宽度
    	*/ 
    	lcdif->VDCTRL4 = (1<<18) | (dt->hactive.typ);
    	/*
    	* 设置ELCDIF的CUR_BUF和NEXT_BUF寄存器
    	* CUR_BUF	 :	当前显存地址
    	* NEXT_BUF :	下一帧显存地址
    	* 方便运算,都设置为同一个显存地址
    	*/ 
    	lcdif->CUR_BUF  =  fb_phy;
    	lcdif->NEXT_BUF =  fb_phy;
    	return 0;
    }
    
    
    /* from pxafb.c */
    static inline unsigned int chan_to_field(unsigned int chan,
    					 struct fb_bitfield *bf)
    {
    	chan &= 0xffff;
    	chan >>= 16 - bf->length;
    	return chan << bf->offset;
    }
    
    static int mylcd_setcolreg(unsigned regno,
    			       unsigned red, unsigned green, unsigned blue,
    			       unsigned transp, struct fb_info *info)
    {
    	unsigned int val;
    
    	/* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n",
    		   regno, red, green, blue); */
    
    	switch (info->fix.visual) {
    	case FB_VISUAL_TRUECOLOR:
    		/* true-colour, use pseudo-palette */
    
    		if (regno < 16) {
    			u32 *pal = info->pseudo_palette;
    			val  = chan_to_field(red,   &info->var.red);
    			val |= chan_to_field(green, &info->var.green);
    			val |= chan_to_field(blue,  &info->var.blue);
    			pal[regno] = val;
    		}
    		break;
    
    	default:
    		return 1;	/* unknown type */
    	}
    
    	return 0;
    }
    
    static struct fb_ops myfb_ops = {
    	.owner		= THIS_MODULE,
    	.fb_setcolreg	= mylcd_setcolreg,
    	.fb_fillrect	= cfb_fillrect,
    	.fb_copyarea	= cfb_copyarea,
    	.fb_imageblit	= cfb_imageblit,
    };
    
    static int mylcd_probe(struct platform_device *pdev)
    {
    	struct device_node *display_np;
    	dma_addr_t phy_addr;
    	int ret;
    	int width;
    	int bits_per_pixel;
    	struct display_timings *timings = NULL;
    	struct display_timing *dt = NULL;
    	struct imx6ull_lcdif *lcdif;
    	struct resource *res;
    
    	display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);
    
    	/* get common info */
    	ret = of_property_read_u32(display_np, "bus-width", &width);
    	ret = of_property_read_u32(display_np, "bits-per-pixel",
    				   &bits_per_pixel);
    
        /* get timming */
    	timings = of_get_display_timings(display_np);
    	dt = timings->timings[timings->native_mode];
    
    	/* get gpio from device tree */
    	bl_gpio = gpiod_get(&pdev->dev, "backlight", 0);
    
    	/* config bl_gpio as output */
    	gpiod_direction_output(bl_gpio, 1);
    
    	/* set val: gpiod_set_value(bl_gpio, status); */
    
    	/* get clk from device tree */
    	clk_pix = devm_clk_get(&pdev->dev, "pix");
    	clk_axi = devm_clk_get(&pdev->dev, "axi");
    
    	/* set clk rate */
    	clk_set_rate(clk_pix, dt->pixelclock.typ);
    
    	/* enable clk */
    	clk_prepare_enable(clk_pix);
    	clk_prepare_enable(clk_axi);
    	
    	/* 1.1 分配fb_info */
    	myfb_info = framebuffer_alloc(0, NULL);
    
    	/* 1.2 设置fb_info */
    	/* a. var : LCD分辨率、颜色格式 */
    	myfb_info->var.xres_virtual = myfb_info->var.xres = dt->hactive.typ;
    	myfb_info->var.yres_virtual = myfb_info->var.yres = dt->vactive.typ;
    	
    	myfb_info->var.bits_per_pixel = 16;  /* rgb565 */
    	myfb_info->var.red.offset = 11;
    	myfb_info->var.red.length = 5;
    
    	myfb_info->var.green.offset = 5;
    	myfb_info->var.green.length = 6;
    
    	myfb_info->var.blue.offset = 0;
    	myfb_info->var.blue.length = 5;
    	
    
    	/* b. fix */
    	strcpy(myfb_info->fix.id, "100ask_lcd");
    	myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * myfb_info->var.bits_per_pixel / 8;
    	if (myfb_info->var.bits_per_pixel == 24)
    		myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * 4;
    
    	/* fb的虚拟地址 */
    	myfb_info->screen_base = dma_alloc_wc(NULL, myfb_info->fix.smem_len, &phy_addr,
    					 GFP_KERNEL);
    	myfb_info->fix.smem_start = phy_addr;  /* fb的物理地址 */
    	
    	myfb_info->fix.type = FB_TYPE_PACKED_PIXELS;
    	myfb_info->fix.visual = FB_VISUAL_TRUECOLOR;
    
    	myfb_info->fix.line_length = myfb_info->var.xres * myfb_info->var.bits_per_pixel / 8;
    	if (myfb_info->var.bits_per_pixel == 24)
    		myfb_info->fix.line_length = myfb_info->var.xres * 4;
    	
    
    	/* c. fbops */
    	myfb_info->fbops = &myfb_ops;
    	myfb_info->pseudo_palette = pseudo_palette;
    
    	/* 1.3 注册fb_info */
    	register_framebuffer(myfb_info);
    
    	/* 1.4 硬件操作 */
    	//lcdif = ioremap(0x021C8000, sizeof(*lcdif));
    	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    	lcdif = devm_ioremap_resource(&pdev->dev, res);
    	lcd_controller_init(lcdif, dt, bits_per_pixel, 16, phy_addr);
    	lcd_controller_enable(lcdif);
    	gpiod_set_value(bl_gpio, 1); 
    
    	return 0;
    }
    
    static int mylcd_remove(struct platform_device *pdev)
    {
    	/* 反过来操作 */
    	/* 2.1 反注册fb_info */
    	unregister_framebuffer(myfb_info);
    
    	/* 2.2 释放fb_info */
    	framebuffer_release(myfb_info);
    	
    	iounmap(mylcd_regs);
    
    	return 0;
    }
    
    static const struct of_device_id mylcd_of_match[] = {
    	{ .compatible = "100ask,lcd_drv", },
    	{ },
    };
    MODULE_DEVICE_TABLE(of, simplefb_of_match);
    
    static struct platform_driver mylcd_driver = {
    	.driver = {
    		.name = "mylcd",
    		.of_match_table = mylcd_of_match,
    	},
    	.probe = mylcd_probe,
    	.remove = mylcd_remove,
    };
    
    static int __init lcd_drv_init(void)
    {
    	int ret;
    
    	ret = platform_driver_register(&mylcd_driver);
    	if (ret)
    		return ret;
    
    	return 0;
    }
    
    /* 2. 出口 */
    static void __exit lcd_drv_exit(void)
    {
    	platform_driver_unregister(&mylcd_driver);
    }
    
    module_init(lcd_drv_init);
    module_exit(lcd_drv_exit);
    MODULE_LICENSE("GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
  • 相关阅读:
    信息熵原理与Python实现
    SpringMvc(一)-初识
    15【存储过程和存储函数】
    3、组件和容器
    微软新型云计算——利用xarray-spatialDEM进行分类(重分类)
    编译器-条件/循环代码生成
    【重新定义matlab强大系列十七】Matlab深入浅出长短期记忆神经网络LSTM
    【Midjourney入门教程2】Midjourney的基础操作和设置
    docker容器健康状态健康脚本
    金融计量学实验报告一
  • 原文地址:https://blog.csdn.net/afddasfa/article/details/132737012