• 《Linux驱动:s3c2440 lcd 驱动分析--终结篇》


    一,前言

    s3c2440 lcd 驱动分析,涉及到的内容有,LCD图像显示原理、s3c2440的LCD控制器的操作、LCD驱动使用平台总线-设备-驱动模型的实例、LCD相关参数的设置、fb字符设备驱动实例、framebuffer的注册和管理、以及一次LCD显示的完整过程分析。

    二,LCD原理和硬件分析

    2.1 LCD原理解析

    SDRAM:在SDRAM中申请了一块连续的内存作为LCD显示数据的存储,叫做显存(framebuffer)。
    LCD控制器:LCD控制器通过硬件电路和LCD屏连接。
    LCD屏:作为一个外设通过硬件电路和MCU(引脚配置为LCD引脚)连接。
    在这里插入图片描述

    图像在LCD屏上显示,可以看成是LCD控制器先从显存中取出一帧图像数据,然后输入到LCD屏上。480*272的屏,所显示的一帧有480*272个像素点、272行、480列。对于每一行的像素点,LCD控制器有一个VCLK信号控制,每来一个VCLK,显示的像素点就向右移动一个,当移动到这一行中的最后一个像素点时,LCD控制器有一个HSYNC信号,控制像素点跳到下一行的第一个像素显示。对于一帧图像(也叫一场),即当像素点移动到最后一行的最后一个位置显示完后,LCD控制器有一个VSYNC信号,控制像素点重新移动到第一行的第一个像素显示下一帧图像。

    2.2 硬件电路

    2.2.1 LCD背光电路

    开启LCD显示,需要使能KEYBOARD(一般EN表示高电平有效,EN上面画一横表示低电平有效)开启背光。
    在这里插入图片描述
    背光开关通过主控,GPB0引脚设置
    在这里插入图片描述

    2.2.2 LCD屏

    VLINE:HSYNC信号输出引脚(由LCD控制器操作)
    VFRAME:VSYNC信号输出引脚(由LCD控制器操作)
    VCLK:VCLK信号输出引脚(由LCD控制器操作)
    VD3~VD7:RGB(565)中B数据输出引脚
    VD10~VD15:RGB(565)中G数据输出引脚
    VD19~VD23:RGB(565)中R数据输出引脚
    TS*:供ts触摸屏连接
    在这里插入图片描述

    2.2.3 S3c2440主控

    涉及 GPG、GPD、GPC引脚。
    VM:LCD控制器使能引脚(由LCD控制器的寄存器配置),开启LCD显示需要配置相关寄存器的相应位使能。
    LCD_PWREN:LCD电源使能引脚(由LCD控制器的寄存器配置),开启LCD显示需要配置相关寄存器的相应位使能。
    在这里插入图片描述

    三,LCD应用平台总线-设备-驱动模型

    3.1 lcd 设备的加载和注册

    MACHINE_START(S3C2440, "SMDK2440")
    /* Maintainer: Ben Dooks  */
    .phys_io	= S3C2410_PA_UART,
    .io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params	= S3C2410_SDRAM_PA + 0x100,
    
    .init_irq	= s3c24xx_init_irq,
    .map_io		= smdk2440_map_io,
    .init_machine	= smdk2440_machine_init,
    .timer		= &s3c24xx_timer,
    MACHINE_END
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    将上面的宏展开

    
    static const struct machine_desc __mach_desc_SMDK2440
     __attribute_used__
     __attribute__((__section__(".arch.info.init"))) = {
     .nr = MACH_TYPE_SMDK2410, /* architecture number */
     .name = "SMDK2440", /* architecture name */
     /* Maintainer: Jonas Dietsche */
     .phys_io = S3C2410_PA_UART, /* start of physical io */
     .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
     .boot_params = S3C2410_SDRAM_PA + 0x100, /* tagged list */
     .map_io = smdk2440_map_io, /* IO mapping function */
     .init_irq = s3c24xx_init_irq,
     .init_machine = smdk2440_machine_init,
     .timer = &s3c24xx_timer,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    MACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(“.arch.info.init”),是初始化数据,Kernel 起来之后将被丢弃。
    各个成员函数在不同时期被调用:

    1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用。
    2. init_irq在start_kernel() -> init_IRQ() -> init_arch_irq() 被调用
    3. map_io 在 setup_arch() -> paging_init() -> devicemaps_init()被调用
      其他主要都在 setup_arch() 中用到。

    系统初始化时,会调用smdk2440_machine_init

    static void __init smdk2440_machine_init(void)
    {
        // 这里设置LCD的参数,和驱动分离。这样要修改LCD时,驱动层程序可以不需要改动,只需修改设备层参数就行了,方便移植。
    	s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
        
        // 将smdk2440_devices数组中的设备注册到平台总线
    	platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
    	smdk_machine_init();
    }
    
    // smdk2440_devices数组
    static struct platform_device *smdk2440_devices[] __initdata = {
    	&s3c_device_usb,
    	&s3c_device_lcd,
    	&s3c_device_wdt,
    	&s3c_device_i2c,
    	&s3c_device_iis,
        &s3c2440_device_sdi,
    };
    
    // lcd设备
    struct platform_device s3c_device_lcd = {
    	.name		  = "s3c2410-lcd",
    	.id		  = -1,
    	.num_resources	  = ARRAY_SIZE(s3c_lcd_resource),
    	.resource	  = s3c_lcd_resource,
    	.dev              = {
    		.dma_mask		= &s3c_device_lcd_dmamask,
    		.coherent_dma_mask	= 0xffffffffUL
    	}
    };
    
    // 设置lcd设备参数 smdk2440_lcd_cfg。各参数含义后面在probe分析时解析
    /* 480x272 */ 
    static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = {
        .regs   = {
            .lcdcon1 =  S3C2410_LCDCON1_TFT16BPP | \
                    S3C2410_LCDCON1_TFT | \
    				  S3C2410_LCDCON1_CLKVAL(0x04),
    
            .lcdcon2 =  S3C2410_LCDCON2_VBPD(1) | \
                    S3C2410_LCDCON2_LINEVAL(271) | \
                    S3C2410_LCDCON2_VFPD(1) | \
                    S3C2410_LCDCON2_VSPW(9),
    
            .lcdcon3 =  S3C2410_LCDCON3_HBPD(1) | \
                    S3C2410_LCDCON3_HOZVAL(479) | \
                    S3C2410_LCDCON3_HFPD(1),
    
            .lcdcon4 =  S3C2410_LCDCON4_HSPW(40),
    
    		.lcdcon5	= S3C2410_LCDCON5_FRM565 |
    				  S3C2410_LCDCON5_INVVLINE |
    				  S3C2410_LCDCON5_INVVFRAME |
    				  S3C2410_LCDCON5_PWREN |
    				  S3C2410_LCDCON5_HWSWP,
    	},
    
        .gpccon      =  0xaaaaaaaa,
    	.gpccon_mask	= 0xffffffff,
        .gpcup       =  0xffffffff,
    	.gpcup_mask	= 0xffffffff,
    
        .gpdcon      =  0xaaaaaaaa,
    	.gpdcon_mask	= 0xffffffff,
        .gpdup       =  0xffffffff,
    	.gpdup_mask	= 0xffffffff,
    
        .fixed_syncs =  1,
        .type        =  S3C2410_LCDCON1_TFT, 
    	.width		= 480,
    	.height		= 272,
    
    	.xres		= {
    		.min	= 480,
    		.max	= 480,
    		.defval	= 480,
    	},
    
    	.yres		= {
            .max    =   272,
            .min    =   272,
            .defval =   272,
        },
    
        .bpp    = {
            .min    =   16,
            .max    =   16,
            .defval =   16,
        },
    };
    
    • 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

    调用platform_device_register注册平台设备

    int platform_add_devices(struct platform_device **devs, int num)
    {
    	int i, ret = 0;
    
    	for (i = 0; i < num; i++) {
    		ret = platform_device_register(devs[i]);
    		if (ret) {
    			while (--i >= 0)
    				platform_device_unregister(devs[i]);
    			break;
    		}
    	}
    
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.2 lcd 驱动的加载和注册

    3.2.1 编译进内核,加载驱动

    编译内核设置,make menuconfig

    -> Device Drivers
      -> Graphics support
        <*> Support for frame buffer devices
    
    • 1
    • 2
    • 3

    linux-2.6.22.6/.config
    CONFIG_FB_S3C2410=y
    linux-2.6.22.6/drivers/video/Makefile
    obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o

    然后make uImage,将s3c2410fb驱动编译进内核,系统启动便会加载,即调用驱动的s3c2410fb_init函数。

    int __devinit s3c2410fb_init(void)
    {
        // 注册到平台总线驱动
    	return platform_driver_register(&s3c2410fb_driver);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.3 lcd 设备和驱动的匹配

    3.3.1 lcd设备注册时的匹配

    platform_device_register ->
    	platform_device_add ->
    		device_add ->
    			bus_attach_device ->
    				device_attach -> 
    					bus_for_each_drv -> // 从平台总线的的驱动链表中,取出每一项驱动进行匹配
    						__device_attach ->
    							driver_probe_device ->
    								if (drv->bus->match && !drv->bus->match(dev, drv)) 此总线类型为平台总线,其存在match函数,即调用platform_match进行匹配
    
    // 平台总线                            
    struct bus_type platform_bus_type = {
    	.name		= "platform",
    	.dev_attrs	= platform_dev_attrs,
    	.match		= platform_match,
    	.uevent		= platform_uevent,
    	.suspend	= platform_suspend,
    	.suspend_late	= platform_suspend_late,
    	.resume_early	= platform_resume_early,
    	.resume		= platform_resume,
    };              
    
    static int platform_match(struct device * dev, struct device_driver * drv)
    {
    	struct platform_device *pdev = container_of(dev, struct platform_device, dev);
        
        // 平台总线匹配设备和驱动的名称
    	return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
    }
    
    // lcd 设备   name = "s3c2410-lcd"
    struct platform_device s3c_device_lcd = {
    	.name		  = "s3c2410-lcd",
    	.id		  = -1,
    	.num_resources	  = ARRAY_SIZE(s3c_lcd_resource),
    	.resource	  = s3c_lcd_resource,
    	.dev              = {
    		.dma_mask		= &s3c_device_lcd_dmamask,
    		.coherent_dma_mask	= 0xffffffffUL
    	}
    };
    
    // lcd 驱动 name	= "s3c2410-lcd"
    static struct platform_driver s3c2410fb_driver = {
    	.probe		= s3c2410fb_probe,
    	.remove		= s3c2410fb_remove,
    	.suspend	= s3c2410fb_suspend,
    	.resume		= s3c2410fb_resume,
    	.driver		= {
    		.name	= "s3c2410-lcd",
    		.owner	= THIS_MODULE,
    	},
    };
    
    • 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

    3.3.2 lcd驱动注册时的匹配

    platform_driver_register->
    	driver_register->
    		bus_add_driver->
    			driver_attach->
    				bus_for_each_dev-> // 从平台总线的的设备链表中,取出每一项设备进行匹配
    					__driver_attach->
                            driver_probe_device->
                                if (drv->bus->match && !drv->bus->match(dev, drv)) // 此总线类型为平台总线,其存在match函数,即调用platform_match进行匹配
                                // 之后的执行和上一小节分析的一样
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.3.3 匹配成功后 driver_probe_device 调用驱动层的probe

    driver_probe_device-> // 在此函数中匹配成功的话,就会去调用驱动的probe函数
        really_probe->
            drv->probe(dev)
    
    • 1
    • 2
    • 3

    四,probe函数分析(s3c2410fb_probe)

    4.1 注册framebuffer

    4.1.1 申请struct fb_info

    struct fb_info	   *fbinfo;
    ...
    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
    if (!fbinfo) {
        return -ENOMEM;
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.1.2 参数设置

    见4.2 LCD参数设置

    4.1.3 注册

    ...
    ret = register_framebuffer(fbinfo);
    if (ret < 0) {
        printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
        goto free_video_memory;
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.2 LCD 参数设置

    获取平台设备数据,即smdk2440_lcd_cfg结构体内数据

    // 获取平台数据即 linux-2.6.22.6/arch/arm/mach-s3c2440/mach-smdk2440.c中配置的smdk2440_lcd_cfg 
    	mach_info = pdev->dev.platform_data;
    	if (mach_info == NULL) {
    		dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n");
    		return -EINVAL;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.2.1 固定参数设置

    struct fb_fix_screeninfo {
    	char id[16];			/* identification string eg "TT Builtin" */
    	unsigned long smem_start;	/* Start of frame buffer mem */
    					/* (physical address) */
    	__u32 smem_len;			/* Length of frame buffer mem */
    	__u32 type;			/* see FB_TYPE_*		*/
    	__u32 type_aux;			/* Interleave for interleaved Planes */
    	__u32 visual;			/* see FB_VISUAL_*		*/ 
    	__u16 xpanstep;			/* zero if no hardware panning  */
    	__u16 ypanstep;			/* zero if no hardware panning  */
    	__u16 ywrapstep;		/* zero if no hardware ywrap    */
    	__u32 line_length;		/* length of a line in bytes    */
    	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
    					/* (physical address) */
    	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
    	__u32 accel;			/* Indicate to driver which	*/
    					/*  specific chip/card we have	*/
    	__u16 reserved[3];		/* Reserved for future compatibility */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    ...
    id:driver 标识
        strcpy(fbinfo->fix.id, driver_name);
    
    smem_start:frame buffer 的起始地址
            fbi->map_cpu  = dma_alloc_writecombine(fbi->dev, fbi->map_size,
                                   &fbi->map_dma, GFP_KERNEL);
            ...
                fbi->screen_dma		= fbi->map_dma;
                fbi->fb->fix.smem_start  = fbi->screen_dma;
            ...
    
    smem_len:frame buffer 的长度字节为单位 = 480*272*16/8   屏宽*屏高*一个像素的数据位数/一个字节的位数
        fbinfo->fix.smem_len        =	mach_info->xres.max *
                            mach_info->yres.max *
                            mach_info->bpp.max / 8;
    
    type、type_aux:fb数据类型为像素类型,还有平面模式等
    fbinfo->fix.type	    = FB_TYPE_PACKED_PIXELS;
    fbinfo->fix.type_aux	    = 0;
    
    visual:设置为真彩色,还有单色模式,黑/白
    fbi->fb->fix.visual = FB_VISUAL_TRUECOLOR;
    
    默认设置为0,简单理解为边框宽度
    fbinfo->fix.xpanstep	    = 0;
    fbinfo->fix.ypanstep	    = 0;
    fbinfo->fix.ywrapstep	    = 0;
    
    line_length:一行fb数据的字节数 480*16/8
    fbi->fb->fix.line_length     = (var->width*var->bits_per_pixel)/8;
    
    • 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

    4.2.2 可变参数设置

    struct fb_var_screeninfo {
    	__u32 xres;			/* visible resolution		*/
    	__u32 yres;
    	__u32 xres_virtual;		/* virtual resolution		*/
    	__u32 yres_virtual;
    	__u32 xoffset;			/* offset from virtual to visible */
    	__u32 yoffset;			/* resolution			*/
    
    	__u32 bits_per_pixel;		/* guess what			*/
    	__u32 grayscale;		/* != 0 Graylevels instead of colors */
    
    	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
    	struct fb_bitfield green;	/* else only length is significant */
    	struct fb_bitfield blue;
    	struct fb_bitfield transp;	/* transparency			*/	
    
    	__u32 nonstd;			/* != 0 Non standard pixel format */
    
    	__u32 activate;			/* see FB_ACTIVATE_*		*/
    
    	__u32 height;			/* height of picture in mm    */
    	__u32 width;			/* width of picture in mm     */
    
    	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */
    
    	/* Timing: All values in pixclocks, except pixclock (of course) */
    	__u32 pixclock;			/* pixel clock in ps (pico seconds) */
    	__u32 left_margin;		/* time from sync to picture	*/
    	__u32 right_margin;		/* time from picture to sync	*/
    	__u32 upper_margin;		/* time from sync to picture	*/
    	__u32 lower_margin;
    	__u32 hsync_len;		/* length of horizontal sync	*/
    	__u32 vsync_len;		/* length of vertical sync	*/
    	__u32 sync;			/* see FB_SYNC_*		*/
    	__u32 vmode;			/* see FB_VMODE_*		*/
    	__u32 rotate;			/* angle we rotate counter clockwise */
    	__u32 reserved[5];		/* Reserved for future compatibility */
    };
    
    • 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
    屏幕分辨率和单个像素点的数据位数
    fbinfo->var.xres	    = mach_info->xres.defval; 		480
    fbinfo->var.xres_virtual    = mach_info->xres.defval; 	480
    fbinfo->var.yres	    = mach_info->yres.defval;   	272
    fbinfo->var.yres_virtual    = mach_info->yres.defval;	272
    fbinfo->var.bits_per_pixel  = mach_info->bpp.defval;	16
    
    设置一个像素点的位数分配,rgb+透明度 rrrrrggggggbbbbb 位数从左边为第0位算
    fbinfo->var.red.offset      = 11;
    fbinfo->var.green.offset    = 5;
    fbinfo->var.blue.offset     = 0;
    fbinfo->var.transp.offset   = 0;
    fbinfo->var.red.length      = 5;
    fbinfo->var.green.length    = 6;
    fbinfo->var.blue.length     = 5;
    fbinfo->var.transp.length   = 0;
    
    fbinfo->var.nonstd	    = 0;   // 标准像素格式
    fbinfo->var.activate	    = FB_ACTIVATE_NOW;
    // 真实分辨率,设置480*272就行了,影响不大
    fbinfo->var.height	    = mach_info->height;
    fbinfo->var.width	    = mach_info->width;
    fbinfo->var.accel_flags     = 0;
    fbinfo->var.vmode	    = FB_VMODE_NONINTERLACED;
    
    // VBPD:一个VSYNC信号到来之后,多才时间才开始输出数据(显示图像)。会造成上边黑框
    // VFPD:一帧(场)数据结束后多长时间,才来一个VSYNC信号。会造成下边黑框
    // VSPW: 一个VSYNC信号的时间宽度
    fbinfo->var.upper_margin    = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
    fbinfo->var.lower_margin    = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
    fbinfo->var.vsync_len	    = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;
    
    // HFPD:一行数据结束多长时间才来一个HSYNC信号。会造成左边黑框
    // HBPD:一个HSYNC信号到来之后,多长时间才开始输出数据(显示图像)。会造成右边黑框
    // HSPW: 一个HSYNC信号的时间宽度
    fbinfo->var.left_margin	    = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
    fbinfo->var.right_margin    = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
    fbinfo->var.hsync_len	    = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;
    
    
    • 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

    4.2.3 其他参数设置

    fbinfo->fbops		    = &s3c2410fb_ops;
    fbinfo->flags		    = FBINFO_FLAG_DEFAULT;
    
    // 设置调色板
    fbinfo->pseudo_palette      = &info->pseudo_pal;
    
    s3c2410fb_ops:
    static struct fb_ops s3c2410fb_ops = {
    	.owner		= THIS_MODULE,
    	.fb_check_var	= s3c2410fb_check_var,
    	.fb_set_par	= s3c2410fb_set_par,
    	.fb_blank	= s3c2410fb_blank,
    	.fb_setcolreg	= s3c2410fb_setcolreg,
    	.fb_fillrect	= cfb_fillrect,
    	.fb_copyarea	= cfb_copyarea,
    	.fb_imageblit	= cfb_imageblit,
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.2.4 硬件相关设置(相关寄存器和引脚)

    /* Stop the video and unset ENVID if set */
    // 设置LCD控制器的lcdcon1寄存器,第0位为0,先失能lcd等设置完其他参数后,需要开启LCD,再使能该位
    info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
    lcdcon1 = readl(S3C2410_LCDCON1);
    writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
    
    // 设置GPB0 低电平,即关闭LCD背光,后面需要开启背光
    // add by thisway.diy@163.com, for eBlocks
    s3c2410_gpio_setpin(S3C2410_GPB0, 0);	// back light control
    
    // 设置调色板数据
    for (i = 0; i < 256; i++)
        info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
    
    if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {
        ret = -EBUSY;
        goto dealloc_fb;
    }
    
    
    dprintk("got LCD region\n");
    
    // 设置LCD中断
    ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
    if (ret) {
        dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
        ret = -EBUSY;
        goto release_mem;
    }
    
    // 使能LCD clk 
    info->clk = clk_get(NULL, "lcd");
    if (!info->clk || IS_ERR(info->clk)) {
        printk(KERN_ERR "failed to get lcd clock source\n");
        ret = -ENOENT;
        goto release_irq;
    }
    clk_enable(info->clk);
    dprintk("got and enabled clock\n");
    
    msleep(1);
    
    
    /* Initialize video memory */
    // 申请frame buff内存并初始化
    ret = s3c2410fb_map_video_memory(info);
    if (ret) {
        printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
        ret = -ENOMEM;
        goto release_clock;
    }
    dprintk("got video memory\n");
    
    // 根据s3c2440芯片手册和使用的屏的规格书设置相关寄存器和引脚
    ret = s3c2410fb_init_registers(info);
    
    ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);
    
    • 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
    static int s3c2410fb_init_registers(struct s3c2410fb_info *fbi)
    {
    	unsigned long flags;
    
    	/* Initialise LCD with values from haret */
    
    	local_irq_save(flags);
    
    	/* modify the gpio(s) with interrupts set (bjd) */
    	// 根据s3c2440芯片手册设置
    	// 设置GPC引脚上拉使能 mach_info->gpcup 0xffffffff
    	modify_gpio(S3C2410_GPCUP,  mach_info->gpcup,  mach_info->gpcup_mask); 
    	// 设置GPC引脚为LCD功能 mach_info->gpccon 0xaaaaaaaa -> 1010 1010 1010 1010 1010 1010 1010 1010
    	modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
    	// 设置GPD引脚上拉使能  mach_info->gpdup  0xffffffff
    	modify_gpio(S3C2410_GPDUP,  mach_info->gpdup,  mach_info->gpdup_mask); 
    	// 设置GPD引脚为LCD功能 mach_info->gpdcon 0xaaaaaaaa
    	modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);
    
    	local_irq_restore(flags);
    
    	// 根据s3c2440芯片手册设置
    	// lcdcon1: S3C2410_LCDCON1_TFT16BPP | S3C2410_LCDCON1_TFT | S3C2410_LCDCON1_CLKVAL(0x04),
    	writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
    	// lcdcon2: S3C2410_LCDCON2_VBPD(1) | S3C2410_LCDCON2_LINEVAL(271) | S3C2410_LCDCON2_VFPD(1) | S3C2410_LCDCON2_VSPW(9) 
    	writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);
    	// lcdcon3: S3C2410_LCDCON3_HBPD(1) |  S3C2410_LCDCON3_HOZVAL(479) |  S3C2410_LCDCON3_HFPD(1)
    	writel(fbi->regs.lcdcon3, S3C2410_LCDCON3);
    	// lcdcon4: S3C2410_LCDCON4_HSPW(40)
    	writel(fbi->regs.lcdcon4, S3C2410_LCDCON4);
    	// lcdcon5: S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_PWREN | S3C2410_LCDCON5_HWSWP,
    	writel(fbi->regs.lcdcon5, S3C2410_LCDCON5);
    
    	// 根据s3c2440芯片手册设置
    	// 设置地址相关寄存器 LCDSADDR1、LCDSADDR2、LCDSADDR3
     	s3c2410fb_set_lcdaddr(fbi);
    
    	dprintk("LPCSEL    = 0x%08lx\n", mach_info->lpcsel);
    	writel(mach_info->lpcsel, S3C2410_LPCSEL);
    
    	dprintk("replacing TPAL %08x\n", readl(S3C2410_TPAL));
    	// 不使用调色板
    	/* ensure temporary palette disabled */
    	writel(0x00, S3C2410_TPAL);
    
    
    #if 0	
    	/* ghcstop modified */
    	s3c2410_gpio_cfgpin(S3C2410_GPC5, S3C2410_GPC5_OUTP); // lcd display enable/disable
    	s3c2410_gpio_cfgpin(S3C2410_GPB1, S3C2410_GPB1_OUTP); // back light control
    	s3c2410_gpio_cfgpin(S3C2410_GPH6, S3C2410_GPH6_OUTP); 
    	
    	s3c2410_gpio_pullup(S3C2410_GPC5, 0); 
    	s3c2410_gpio_pullup(S3C2410_GPB1, 0);
    	s3c2410_gpio_pullup(S3C2410_GPH6, 0);
    
    	s3c2410_gpio_setpin(S3C2410_GPC5, 1);
    	s3c2410_gpio_setpin(S3C2410_GPH6, 1); 
    	s3c2410_gpio_setpin(S3C2410_GPB1, 1);
    #else
    	/* thisway.diy@163.com modify again, for eBlocks */
    	s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP); // back light control
    
    	s3c2410_gpio_pullup(S3C2410_GPB0, 0); 
    
    	s3c2410_gpio_setpin(S3C2410_GPB0, 1);	// back light control, enable
    #endif
    	
    	/* probably not required */
    	msleep(10);		
    
        // 使能lcd
    	/* Enable video by setting the ENVID bit to 1 */
    	fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID;
    	writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
    
    	// add by thisway.diy@163.com, for eBlocks
        // 开启背光
    	s3c2410_gpio_setpin(S3C2410_GPB0, 1);	// back light control
    
    	return 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
    • 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

    五,fbmem字符设备驱动

    5.1 驱动加载并初始化

    编译进内核,加载

    linux-2.6.22.6/drivers/video/Makefile

    fb-y                              := fbmem.o fbmon.o fbcmap.o fbsysfs.o \
                                         modedb.o fbcvt.o
    fb-objs                           := $(fb-y)
    
    • 1
    • 2
    • 3

    linux-2.6.22.6/drivers/video/fbmem.c

    subsys_initcall(fbmem_init);
    
    • 1
    static int __init
    fbmem_init(void)
    {
    	create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
        
        // 注册字符设备
    	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
    		printk("unable to get major %d for fb devs\n", FB_MAJOR);
        
        // 创建设备类 /sys/class/graphics
    	fb_class = class_create(THIS_MODULE, "graphics");
    	if (IS_ERR(fb_class)) {
    		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
    		fb_class = NULL;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5.2 register_framebuffer

    int
    register_framebuffer(struct fb_info *fb_info)
    {
    	int i;
    	struct fb_event event;
    	struct fb_videomode mode;
    
    	// 最多支持32个fb设备
    	if (num_registered_fb == FB_MAX)
    		return -ENXIO;
    	num_registered_fb++;
    	// 从registered_fb中找到一项空的位置,存放本次fb。下标作为次设备号,创建设备节点,以供应用程序使用
    	for (i = 0 ; i < FB_MAX; i++)
    		if (!registered_fb[i])
    			break;
    	fb_info->node = i;
    
    	// 创建设备 节点。/dev/fb%d
    	fb_info->dev = device_create(fb_class, fb_info->device,
    				     MKDEV(FB_MAJOR, i), "fb%d", i);
    	if (IS_ERR(fb_info->dev)) {
    		/* Not fatal */
    		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
    		fb_info->dev = NULL;
    	} else
    		fb_init_device(fb_info);
    
    	if (fb_info->pixmap.addr == NULL) {
    		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
    		if (fb_info->pixmap.addr) {
    			fb_info->pixmap.size = FBPIXMAPSIZE;
    			fb_info->pixmap.buf_align = 1;
    			fb_info->pixmap.scan_align = 1;
    			fb_info->pixmap.access_align = 32;
    			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
    		}
    	}	
    	fb_info->pixmap.offset = 0;
    
    	if (!fb_info->pixmap.blit_x)
    		fb_info->pixmap.blit_x = ~(u32)0;
    
    	if (!fb_info->pixmap.blit_y)
    		fb_info->pixmap.blit_y = ~(u32)0;
    
    	if (!fb_info->modelist.prev || !fb_info->modelist.next)
    		INIT_LIST_HEAD(&fb_info->modelist);
    
    	fb_var_to_videomode(&mode, &fb_info->var);
    	fb_add_videomode(&mode, &fb_info->modelist);
    	registered_fb[i] = fb_info;
    
    	event.info = fb_info;
    	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
    	return 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    六,一次LCD显示的过程

    应用程序app: file = open("/dev/fb0",0)
    将要显示的图像数据,使用write接口写入,
    write->
        fb_write->
            // 如果lcd驱动的fbops中存在write函数,则使用lcd驱动中的数据
            if (info->fbops->fb_write)
                return info->fbops->fb_write(info, buf, count, ppos);
        否则使用fb_write写到显存中
        
        产生一个LCD中断,进入中断处理函数
        static irqreturn_t s3c2410fb_irq(int irq, void *dev_id)
        {
            struct s3c2410fb_info *fbi = dev_id;
            unsigned long lcdirq = readl(S3C2410_LCDINTPND);
        
            if (lcdirq & S3C2410_LCDINT_FRSYNC) {
                if (fbi->palette_ready)
                    s3c2410fb_write_palette(fbi);
                // 控制LCD控制器开始从显存中取数据传输到LCD屏,显示图像
                writel(S3C2410_LCDINT_FRSYNC, S3C2410_LCDINTPND);
                writel(S3C2410_LCDINT_FRSYNC, S3C2410_LCDSRCPND);
            }
        
            return IRQ_HANDLED;
        }
    
    static struct fb_ops s3c2410fb_ops = {
    	.owner		= THIS_MODULE,
    	.fb_check_var	= s3c2410fb_check_var,
    	.fb_set_par	= s3c2410fb_set_par,
    	.fb_blank	= s3c2410fb_blank,
    	.fb_setcolreg	= s3c2410fb_setcolreg,
    	.fb_fillrect	= cfb_fillrect,
    	.fb_copyarea	= cfb_copyarea,
    	.fb_imageblit	= cfb_imageblit,
    };
        
    
    • 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

    七,其他处理函数和逻辑

    待定

  • 相关阅读:
    SpringBoot项目--电脑商城【显示勾选的购物车数据】
    PWmat案例赏析:计算精度高、速度快的第一性原理计算,研究表面终端结构对NV色心影响
    Qt使用QAudioInput、QAudioOutput实现局域网的音频通话
    [ vulhub漏洞复现篇 ] Grafana任意文件读取漏洞CVE-2021-43798
    前端基础HTML-基础标签
    Java 编码那些事(二)
    纳米TiO-聚苯乙烯微球复合物/聚苯乙烯中空微球表面包覆Fe3O4/二氧化钛核壳粒子的制备方式
    代码随想录 -- day55 --392.判断子序列 、115.不同的子序列
    Java项目:SSM游戏点评网站
    todolist案例——vue脚手架(2)
  • 原文地址:https://blog.csdn.net/qq_40709487/article/details/126436481