Tiny210 LCD驱动分析

 

     lcd整个架构是已platform_form总线的形式注册在系统中的,platform_form总线有device和driver,下面分别从lcd的device和driver两部分来分析tiny210 lcd架构。

  • device部分

      linux很多总线的device一般在开机过程中由系统去注册好,driver部分的注册则在驱动中进行注册,像spi、i2c等等都是这样。Lcd是通过虚拟总线进行注册的,所以也是在系统中去注册device,具体流程图如下所示:

                                   Tiny210 LCD驱动分析_第1张图片

        在linux-3.0.8/arch/arm/mach-s5pv210/mach-mini210.c中的mini210_machine_init函数定义在MACHINE_START中,在系统启动后,会直接运行改函数,mini210_machine_init函数中有注册device设备:

                                       Tiny210 LCD驱动分析_第2张图片

            注册完成后,会去设置获取一些lcd参数、地址资源:

                                      Tiny210 LCD驱动分析_第3张图片

         mini210_get_lcd定义在linux-3.0.8/arch/arm/mach-s5pv210/mini210-lcds.c中:

struct s3cfb_lcd *mini210_get_lcd(void)
{
	return panel_lcd_list[lcd_idx].lcd;
}

        会去获取一些定义好的lcd参数,如分辨率、频率、lcd name等等。

        调用s3c_fb_set_platdata函数时会用mini210_fb_data作为参数,mini210_fb_data定义如下所示:

static struct s3c_platform_fb mini210_fb_data __initdata = {
	.hw_ver			= 0x62,
	.clk_name       = "sclk_fimd", //clk 名字
	.nr_wins		= 5,  //默认定义窗口
	.default_win	= CONFIG_FB_S3C_DEFAULT_WINDOW, 
	.swap			= FB_SWAP_WORD | FB_SWAP_HWORD,

	.cfg_gpio		= lcd_cfg_gpio, // 初始化配置GPIO
	.backlight_on	= lcd_backlight_on, //打开背光
	.backlight_onoff= lcd_backlight_off, //关闭背光
	.reset_lcd		= lcd_reset_lcd, // reset资源
};

      s3c_fb_set_platdata函数在linux-3.0.8/arch/arm/plat-samsung/ dev-fb.c中,如下所示:

static struct resource s3cfb_resource[] = {
	[0] = {
		.start		= S5P_PA_LCD, //资源地址
		.end		= S5P_PA_LCD + S5P_SZ_LCD - 1,
		.flags		= IORESOURCE_MEM, //driver会获取这个资源
	},
	[1] = {
		.start		= IRQ_LCD1, //lcd中断1
		.end		= IRQ_LCD1,
		.flags		= IORESOURCE_IRQ,
	},
	[2] = {
		.start		= IRQ_LCD0, //lcd中断0
		.end		= IRQ_LCD0,
		.flags		= IORESOURCE_IRQ,
	},
};

static u64 fb_dma_mask = 0xffffffffUL;  //dma 掩码

struct platform_device s3c_device_fb = { //fb设备定义和描述
	.name			= "s3cfb",  //设备名字
	.id				= -1,
	.num_resources	= ARRAY_SIZE(s3cfb_resource),
	.resource		= s3cfb_resource,
	.dev			= {
		.dma_mask			= &fb_dma_mask,
		.coherent_dma_mask	= 0xffffffffUL
	}
};

static struct s3c_platform_fb default_fb_data __initdata = {
#if defined(CONFIG_CPU_S5PV210_EVT0)
	.hw_ver			= 0x60,
#else
	.hw_ver			= 0x62,
#endif
	.nr_wins		= 5, //支持的窗口数

	.default_win	= CONFIG_FB_S3C_DEFAULT_WINDOW, //指定默认显示窗口
	.swap			= FB_SWAP_WORD | FB_SWAP_HWORD, //数据字交换
};

void __init s3c_fb_set_platdata(struct s3c_platform_fb *pd)
{
	struct s3c_platform_fb *npd;
	struct s3cfb_lcd *lcd;
	phys_addr_t pmem_start;
	int i, default_win, num_overlay_win;
	int frame_size;

	if (!pd)  //判断传入的s3c_platform_fb是否有定义,没有定义使用默认定义
		pd = &default_fb_data;

	npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL);
	if (!npd) {
		printk(KERN_ERR "%s: no memory for platform data\n", __func__);

	} else {
		for (i = 0; i < npd->nr_wins && i < NR_BUFFERS; i++) {
			npd->nr_buffers[i] = 1; //初始化每个窗口
		}

		default_win = npd->default_win;
		num_overlay_win = CONFIG_FB_S3C_NUM_OVLY_WIN;
		if (num_overlay_win >= default_win) {
			printk(KERN_WARNING "%s: NUM_OVLY_WIN should be less than default \
					window number. set to 0.\n", __func__);
			num_overlay_win = 0;
		}

		lcd = (struct s3cfb_lcd *)npd->lcd;
		frame_size = ALIGN(lcd->width * lcd->height * 4, PAGE_SIZE);

		s3cfb_get_clk_name(npd->clk_name); //获取时钟

		npd->backlight_onoff = NULL;
		npd->clk_on = s3cfb_clk_on; //使能lcd设备
		npd->clk_off = s3cfb_clk_off; //关闭lcd 设备

		/* set starting physical address & size of memory region for
		 * overlay window and default window */
		pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, 1);
		printk("fimd at 0x%08x\n", pmem_start);

		for (i = 0; i < num_overlay_win; i++) {
			npd->nr_buffers[i] = CONFIG_FB_S3C_NUM_BUF_OVLY_WIN;
			npd->pmem_start[i] = pmem_start;
			npd->pmem_size[i] = frame_size * npd->nr_buffers[i];
			pmem_start += npd->pmem_size[i];
		}

		npd->nr_buffers[default_win] = CONFIG_FB_S3C_NR_BUFFERS;
		npd->pmem_start[default_win] = pmem_start;
		npd->pmem_size[default_win] = frame_size * npd->nr_buffers[default_win];

#if defined(CONFIG_MACH_MINI210)
		npd->pmem_size[default_win] += ALIGN(1280*720, PAGE_SIZE) * 3;
		npd->pmem_size[default_win] += ALIGN(1280*360, PAGE_SIZE) * 3 + PAGE_SIZE;

		if (frame_size > 0x200000) {
			pmem_start += npd->pmem_size[default_win];
			for (; i < npd->nr_wins; i++) {
				if (i != default_win) {
					npd->nr_buffers[i] = 2;
					npd->pmem_start[i] = pmem_start;
					npd->pmem_size[i] = frame_size * npd->nr_buffers[i];
					break;
				}
			}
		}
#endif

		s3c_device_fb.dev.platform_data = npd;
	}
}
  • driver部分

       lcd从应用层到硬件驱动流程如下所示:

                                  Tiny210 LCD驱动分析_第4张图片

       对于lcd框架,我们主要关注的是后面三部分,Framebuffer子系统内部框架、LCD控制器编程、LCD硬件驱动,前两部分已经被抽象并实现在Linux driver发布源码中了,我们只需要理解framebuffer内部框架和接口即可,第三部分,主要是对具体的硬件平台SOC和具体的LCD(焊接连接到该SOC的引脚上)来进行的寄存器设置。

      Lcd整天框架如下所示: 

                                   Tiny210 LCD驱动分析_第5张图片

        由上图可以看出 lcd的应用层 通过内核的fbmem接口再调用驱动xxxfb.c的内容,而fbmem接口是内核提供的,所有驱动设计人员主要的任务就是定义一个fb_info 结构体(该结构由内核提供),然后填充结构体中的内容做好相应的初始化后,提交给内核就可以了。

    Tiny210开发板驱动在linux-3.0.8/drivers/video/samsung目录下面:

  •         fbmem.c ---framebuffer框架
  •         s3cfb.c ---驱动框架
  •         s3cfb_fimd6x.c  ---硬件相关驱动
  1. framebuffer注册

      Framebuffer类似于input一样的子系统,在fbmem_init中通过register_chrdev接口向系统注册一个主设备号位29的字符设备驱动。通过class_create创建graphics设备类,配合mdev机制生成供用户访问的设备文件(位于/dev目录)。

static int __init
fbmem_init(void)
{
    //向proc文件系统报告驱动状态和参数
	proc_create("fb", 0, NULL, &fb_proc_fops);

    //注册字符设备驱动,主设备号29
	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

    //创建/sys/class/graphics设备类,配合mdev生成设备问题
	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;
}

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write, //二次拷贝
	.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif
	.mmap =		fb_mmap, // 映射 一次拷贝
	.open =		fb_open,
	.release =	fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
	.llseek =	default_llseek,
};

      Framebuffer注册就是一个字符设备注册,应用层通过open(“/dev/fb0”,…)访问framebuffer设备,然后通过read、ioctl、mmap等方式获取lcd信息。在linux设备驱动中,所有的显示缓存设备均由framebuffer子系统内部管理,即linux设备驱动框架只认识一个主设备号为29的framebuffer设备。应用层所有针对显示缓存(最多32个)的访问均会推送给fb_fops进行进一步分发操作。

 fbmem.c除了注册设备节点外,还提供了注册framebuff接口,驱动加载时候,可以通过register_framebuffer接口从framebuffer子系统中注册自己的接口,我们先看几个比较重要的结构体:

fb_info结构体代表单个显示缓存从设备,在调用register_framebuffer接口之前,必须要初始化其中的重要数据成员。其定义如下:

struct fb_info {
	atomic_t count;
	int node; //次设备号
	int flags;
	struct mutex lock;		/* Lock for open/release/ioctl funcs */
	struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */
	struct fb_var_screeninfo var;	/* Current var */ // LCD可变参数结构体
	struct fb_fix_screeninfo fix;	/* Current fix */ // LCD固定参数结构体
	struct fb_monspecs monspecs;	/* Current Monitor specs */
	struct work_struct queue;	/* Framebuffer event queue */ //帧缓存事件队列
	struct fb_pixmap pixmap;	/* Image hardware mapper */
	struct fb_pixmap sprite;	/* Cursor hardware mapper */
	struct fb_cmap cmap;		/* Current cmap */
	struct list_head modelist;      /* mode list */
	struct fb_videomode *mode;	/* current mode */ //显示模式

#ifdef CONFIG_FB_BACKLIGHT
	/* assigned backlight device */
	/* set before framebuffer registration, 
	   remove after unregister */
	struct backlight_device *bl_dev;

	/* Backlight level curve */
	struct mutex bl_curve_mutex;	
	u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	struct delayed_work deferred_work;
	struct fb_deferred_io *fbdefio;
#endif

	struct fb_ops *fbops;  //LCD底层硬件操作接口
	struct device *device;		/* This is the parent */ //设备驱动模型
	struct device *dev;		/* This is this fb device */
	int class_flag;                    /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
	struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
	char __iomem *screen_base;	/* Virtual address */ //显示内存虚拟地址
	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ //显示内存大小
	void *pseudo_palette;		/* Fake palette of 16 colors */ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* Hardware state i.e suspend */
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;
	/* we need the PCI or similar aperture base/size not
	   smem_start/size as smem_start may just be an object
	   allocated inside the aperture so may not actually overlap */
	struct apertures_struct {
		unsigned int count;
		struct aperture {
			resource_size_t base;
			resource_size_t size;
		} ranges[0];
	} *apertures;
}

      其中,fb_var_screeninfo和fb_fix_screeninfo两个结构体跟LCD硬件属性相关,fb_var_screeninfo代表可修改的LCD显示参数,如分辨率和像素比特数;fb_fix_screeninfo代表不可修改的LCD属性参数,如显示内存的物理地址和长度等。另外一个非常重要的成员是fb_ops,其是LCD底层硬件操作接口集。

      fb_ops硬件操作接口集包含很多接口,如设置可变参数fb_set_par、设置颜色寄存器fb_setcolreg、清屏接口fb_blank、画位图接口fb_imageblit、内存映射fb_mmap等等。

struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution		*/ //LCD分辨率
	__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 */
};
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 */
};

      然后,在看看framebuffer注册过程:

int
register_framebuffer(struct fb_info *fb_info)
{
	int ret;

	mutex_lock(®istration_lock);
	ret = do_register_framebuffer(fb_info);
	mutex_unlock(®istration_lock);

	return ret;
}
static int do_register_framebuffer(struct fb_info *fb_info)
{
	int i;
	struct fb_event event;
	struct fb_videomode mode;

	if (fb_check_foreignness(fb_info))
		return -ENOSYS;

	do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
					 fb_is_primary_device(fb_info));

    //如果注册的帧缓冲设备超过了FB_MAX(目前定义为32),
    //则返回为-ENXIO,注册成功则返回为0。
    if (num_registered_fb == FB_MAX)
		return -ENXIO;

	num_registered_fb++; //已经注册了的帧缓冲区硬件设备个数
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i]) //registered_fb[i]保存所有已经注册了的帧缓冲区硬件设备
			break;
	fb_info->node = i;
	atomic_set(&fb_info->count, 1);
	mutex_init(&fb_info->lock);
	mutex_init(&fb_info->mm_lock);

    //在/sys/class/graphics下创建fbx设备,用于设备文件的创建
	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), NULL, "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;
	if (!lock_fb_info(fb_info))
		return -ENODEV;

    //通知帧缓冲区控制台,有一个新的帧缓冲区设备被注册到内核中来了
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	return 0;
}

       每个调用register_framebuffer注册的从设备都要传递一个fb_info结构体指针,其即代表单个显示缓存设备。从中,可以看到fb_info最终会存储到全局数组struct fb_info*registered_fb[FB_MAX]中,FB_MAX是32,从这里我们也可以看出,framebuffer最多支持32个从设备。另外,每个从设备注册还会在/sys/class/graphics/设备类中创建一个设备,最终由mdev在/dev/目录中生成对应的设备文件。假设M个从设备调用register_framebuffer接口,即会在/dev中生成M个设备文件,如/dev/fb0、/dev/fb1、/dev/fb2等等。这M个设备的主设备号都是29,从设备则是0、1、2等等。

2.框架驱动注册

      看完了framebuffer的注册,在来看看s3cfb.c中驱动框架的注册,驱动框架的注册也是设备驱动模型,注册的是platform总线:

static struct platform_driver s3cfb_driver = {
	.probe = s3cfb_probe,
	.remove = __devexit_p(s3cfb_remove),
	.driver = {
		   .name = S3CFB_NAME,
		   .owner = THIS_MODULE,
	},
};

static int __init s3cfb_register(void)
{
	platform_driver_register(&s3cfb_driver);

	return 0;
}

static void __exit s3cfb_unregister(void)
{
	platform_driver_unregister(&s3cfb_driver);
}

module_init(s3cfb_register);
module_exit(s3cfb_unregister);

      s3cfb_probe探测函数是重点,我们重点来看这个函数,s3cfb_probe主要完成以下工作:

  • 申请、使能pm供电
  • 获取平台设备device资源,包括填充结构体资源、io资源等等
  • 初始化硬件设备
  • 申请fb_info 结构体,并进行填充
  • 用fb_info 结构体,注册framebuffer
  •  创建属性文件
static int __devinit s3cfb_probe(struct platform_device *pdev)
{
	struct s3c_platform_fb *pdata; //s3c平台资源结构体
	//lcd driver全局指针结构体指针,是分析驱动的核心指针
	struct s3cfb_global *fbdev;
	struct resource *res; //资源指针
	int i, j, ret = 0;

	fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL);
	if (!fbdev) {
		dev_err(fbdev->dev, "failed to allocate for "
				"global fb structure\n");
		ret = -ENOMEM;
		goto err_global;
	}
	fbdev->dev = &pdev->dev;

	fbdev->regulator = regulator_get(&pdev->dev, "pd"); //获取pm供电资源
	if (IS_ERR(fbdev->regulator)) {
		dev_err(fbdev->dev, "failed to get regulator\n");
		ret = -EINVAL;
		goto err_regulator;
	}

	ret = regulator_enable(fbdev->regulator); //使能供电
	if (ret < 0) {
		dev_err(fbdev->dev, "failed to enable regulator\n");
		ret = -EINVAL;
		goto err_regulator;
	}

#ifdef RITESH
	fbdev->vcc_lcd = regulator_get(&pdev->dev, "vcc_lcd");
	if (IS_ERR(fbdev->vcc_lcd)) {
		dev_err(fbdev->dev, "failed to get vcc_lcd\n");
		ret = -EINVAL;
		goto err_vcc_lcd;
	}
	ret = regulator_enable(fbdev->vcc_lcd);
	if (ret < 0) {
		dev_err(fbdev->dev, "failed to enable vcc_lcd\n");
		ret = -EINVAL;
		goto err_vcc_lcd;
	}

	fbdev->vlcd = regulator_get(&pdev->dev, "vlcd");
	if (IS_ERR(fbdev->vlcd)) {
		dev_err(fbdev->dev, "failed to get vlcd\n");
		ret = -EINVAL;
		goto err_vlcd;
	}
	ret = regulator_enable(fbdev->vlcd);
	if (ret < 0) {
		dev_err(fbdev->dev, "failed to enable vlcd\n");
		ret = -EINVAL;
		goto err_vlcd;
	}
#endif

    /* 获取devcie注册资源,这里得到s3c_fb_set_platdata函数填充的资源*/
	pdata = to_fb_plat(&pdev->dev);
	if (!pdata) {
		dev_err(fbdev->dev, "failed to get platform data\n");
		ret = -EINVAL;
		goto err_pdata;
	}

	fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd;

	if (pdata->cfg_gpio)
		pdata->cfg_gpio(pdev); //初始化io

	if (pdata->clk_on)
		pdata->clk_on(pdev, &fbdev->clock);

    //从平台设备中获取io资源,申请io资源,映射io资源
    //即可得到一个IO内存资源节点指针, 包括了地址的开始,结束地址等
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(fbdev->dev, "failed to get io memory region\n");
		ret = -EINVAL;
		goto err_io;
	}

	res = request_mem_region(res->start,
			res->end - res->start + 1, pdev->name);
	if (!res) {
		dev_err(fbdev->dev, "failed to request io memory region\n");
		ret = -EINVAL;
		goto err_io;
	}

    //映射出寄存器操作基地址
	fbdev->regs = ioremap(res->start, res->end - res->start + 1);
	if (!fbdev->regs) {
		dev_err(fbdev->dev, "failed to remap io region\n");
		ret = -EINVAL;
		goto err_mem;
	}

    //初始化屏操作 硬件的初始化
	s3cfb_set_vsync_interrupt(fbdev, 1);
	s3cfb_set_global_interrupt(fbdev, 1);
	s3cfb_init_global(fbdev);

    //申请fb_info资源
    /*这个函数的作用是申请struct fb_info 结构体,初始化fb_info 结构体的信息*/
    /*fb_info 结构体 上与内核接口耦合的关系,
          我们写lcd驱动就是除了初始化相关硬件以外*/
    /*就是把fb_info 结构体初始化后 注册到内核,
            供 fb_mem 调用,上层才能跟底层结合起来*/
	if (s3cfb_alloc_framebuffer(fbdev)) { 
		ret = -ENOMEM;
		goto err_alloc;
	}

    //注册framebuff
	if (s3cfb_register_framebuffer(fbdev)) {
		ret = -EINVAL;
		goto err_register;
	}

     /* 设置时钟、选择windows通道,使能wins窗口*/
	s3cfb_set_clock(fbdev);
	s3cfb_set_window(fbdev, pdata->default_win, 1);

	s3cfb_display_on(fbdev);

	fbdev->irq = platform_get_irq(pdev, 0);//从平台驱动获取frame中断, 申请注册中断
	if (request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED,
			pdev->name, fbdev)) {
		dev_err(fbdev->dev, "request_irq failed\n");
		ret = -EINVAL;
		goto err_irq;
	}

#ifdef CONFIG_FB_S3C_LCD_INIT
	if (pdata->backlight_on)
		pdata->backlight_on(pdev);  //使能打开背光
	if (!bootloaderfb && pdata->reset_lcd)
		pdata->reset_lcd(pdev);
#endif

#ifdef CONFIG_HAS_EARLYSUSPEND
	fbdev->early_suspend.suspend = s3cfb_early_suspend;
	fbdev->early_suspend.resume = s3cfb_late_resume;
	fbdev->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB;
	register_early_suspend(&fbdev->early_suspend);
#endif

    /*在/sys/class/下创建一个属性文件*/   
	ret = device_create_file(&(pdev->dev), &dev_attr_win_power);
	if (ret < 0)
		dev_err(fbdev->dev, "failed to add sysfs entries\n");

	dev_info(fbdev->dev, "registered successfully\n");

#if defined(CONFIG_LOGO)
	if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {
		printk("Start display and show logo\n");
		/* Start display and show logo on boot */
		fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);
		fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);
	}
#endif

	return 0;

err_irq:
	s3cfb_display_off(fbdev);
	s3cfb_set_window(fbdev, pdata->default_win, 0);
	for (i = pdata->default_win;
			i < pdata->nr_wins + pdata->default_win; i++) {
		j = i % pdata->nr_wins;
		unregister_framebuffer(fbdev->fb[j]);
	}

err_register:
	for (i = 0; i < pdata->nr_wins; i++) {
		if (i == pdata->default_win)
			s3cfb_unmap_default_video_memory(fbdev->fb[i]);
		framebuffer_release(fbdev->fb[i]);
	}
	kfree(fbdev->fb);

err_alloc:
	iounmap(fbdev->regs);

err_mem:
	release_mem_region(res->start, res->end - res->start + 1);

err_io:
	pdata->clk_off(pdev, &fbdev->clock);

err_pdata:
#ifdef RITESH
	if (!IS_ERR(fbdev->vlcd))
		regulator_disable(fbdev->vlcd);

err_vlcd:
	if (!IS_ERR(fbdev->vcc_lcd))
		regulator_disable(fbdev->vcc_lcd);

err_vcc_lcd:
#endif
	regulator_disable(fbdev->regulator);

err_regulator:
	kfree(fbdev);

err_global:
	return ret;
}
  •       pdata->cfg_gpio(pdev)配置gpio

        cfg_gpio是调用到linux-3.0.8\arch\arm\mach-s5pv210 \ mach-mini210.c 中的lcd_cfg_gpio函数:

static void lcd_cfg_gpio(struct platform_device *pdev)
{
	int i;

	for (i = 0; i < 8; i++) {
		s3c_gpio_cfgpin(S5PV210_GPF0(i), S3C_GPIO_SFN(2));
		s3c_gpio_setpull(S5PV210_GPF0(i), S3C_GPIO_PULL_NONE);
	}

	for (i = 0; i < 8; i++) {
		s3c_gpio_cfgpin(S5PV210_GPF1(i), S3C_GPIO_SFN(2));
		s3c_gpio_setpull(S5PV210_GPF1(i), S3C_GPIO_PULL_NONE);
	}

	for (i = 0; i < 8; i++) {
		s3c_gpio_cfgpin(S5PV210_GPF2(i), S3C_GPIO_SFN(2));
		s3c_gpio_setpull(S5PV210_GPF2(i), S3C_GPIO_PULL_NONE);
	}

	for (i = 0; i < 4; i++) {
		s3c_gpio_cfgpin(S5PV210_GPF3(i), S3C_GPIO_SFN(2));
		s3c_gpio_setpull(S5PV210_GPF3(i), S3C_GPIO_PULL_NONE);
	}

	/* mDNIe SEL: why we shall write 0x2 ? */
	writel(0x2, S5P_MDNIE_SEL);

	/* drive strength to 1x ....(max for smdkv210) */
	writel(0x00000080, S5PV210_GPF0_BASE + 0xc);
	writel(0x00000000, S5PV210_GPF1_BASE + 0xc);
	writel(0x00000000, S5PV210_GPF2_BASE + 0xc);
	writel(0x00000000, S5PV210_GPF3_BASE + 0xc);
}

      该函数是将GFP0(0-7)、GPF1(0-7)、GPF2(0-7)、GPF3(0-3)等引脚设置成lcd模式(参考数据手册P151):

                                       Tiny210 LCD驱动分析_第6张图片

  • s3cfb_set_vsync_interrupt(fbdev, 1)、s3cfb_set_global_interrupt(fbdev, 1);
int s3cfb_set_vsync_interrupt(struct s3cfb_global *ctrl, int enable)
{
	u32 cfg = 0;

	cfg = readl(ctrl->regs + S3C_VIDINTCON0); //Specifies video interrupt control register
	cfg &= ~S3C_VIDINTCON0_FRAMESEL0_MASK;

	if (enable) {
		dev_dbg(ctrl->dev, "vsync interrupt is on\n");
		cfg |= S3C_VIDINTCON0_FRAMESEL0_VSYNC;
	} else {
		dev_dbg(ctrl->dev, "vsync interrupt is off\n");
		cfg &= ~S3C_VIDINTCON0_FRAMESEL0_VSYNC;
	}

	writel(cfg, ctrl->regs + S3C_VIDINTCON0);

	return 0;
}

int s3cfb_set_global_interrupt(struct s3cfb_global *ctrl, int enable)
{
	u32 cfg = 0;

	cfg = readl(ctrl->regs + S3C_VIDINTCON0);
	cfg &= ~(S3C_VIDINTCON0_INTFRMEN_ENABLE | S3C_VIDINTCON0_INT_ENABLE);

	if (enable) {
		dev_dbg(ctrl->dev, "video interrupt is on\n");
		cfg |= (S3C_VIDINTCON0_INTFRMEN_ENABLE |
			S3C_VIDINTCON0_INT_ENABLE);
	} else {
		dev_dbg(ctrl->dev, "video interrupt is off\n");
		cfg |= (S3C_VIDINTCON0_INTFRMEN_DISABLE |
			S3C_VIDINTCON0_INT_DISABLE);
	}

	writel(cfg, ctrl->regs + S3C_VIDINTCON0);

	return 0;
}

         s3cfb_set_vsync_interrupt用来设置VSYNC中断,s3cfb_set_global_interrupt用来设置全局中断(P1260)。

                                     Tiny210 LCD驱动分析_第7张图片

  • s3cfb_init_global
static int s3cfb_init_global(struct s3cfb_global *ctrl)
{
	ctrl->output = OUTPUT_RGB; //指定了输出格式
	ctrl->rgb_mode = MODE_RGB_P; //指定了rgb模式

	init_completion(&ctrl->fb_complete); //初始化信号量
	mutex_init(&ctrl->lock);

	s3cfb_set_output(ctrl);//设置输出格式
	s3cfb_set_display_mode(ctrl);//设置模式
	s3cfb_set_polarity(ctrl);//设置引脚极性
	s3cfb_set_timing(ctrl);//设置时序
	s3cfb_set_lcd_size(ctrl);//设置lcd大小

	return 0;
}

      s3cfb_init_global是对硬件的初始化,主要是进行一些寄存设置;

a、s3cfb_set_output(ctrl)设置输出格式

      指定输出RGB数据类型,支持格式如下:

enum s3cfb_output_t {
	OUTPUT_RGB,
	OUTPUT_ITU,
	OUTPUT_I80LDI0,
	OUTPUT_I80LDI1,
	OUTPUT_WB_RGB,
	OUTPUT_WB_I80LDI0,
	OUTPUT_WB_I80LDI1,
};

                                    Tiny210 LCD驱动分析_第8张图片

int s3cfb_set_output(struct s3cfb_global *ctrl)
{
	u32 cfg;

	cfg = readl(ctrl->regs + S3C_VIDCON0); 
	cfg &= ~S3C_VIDCON0_VIDOUT_MASK; //清空VIDCON0寄存器28-26位

	if (ctrl->output == OUTPUT_RGB)
		cfg |= S3C_VIDCON0_VIDOUT_RGB;
	else if (ctrl->output == OUTPUT_ITU)
		cfg |= S3C_VIDCON0_VIDOUT_ITU;
	else if (ctrl->output == OUTPUT_I80LDI0)
		cfg |= S3C_VIDCON0_VIDOUT_I80LDI0;
	else if (ctrl->output == OUTPUT_I80LDI1)
		cfg |= S3C_VIDCON0_VIDOUT_I80LDI1;
	else if (ctrl->output == OUTPUT_WB_RGB)
		cfg |= S3C_VIDCON0_VIDOUT_WB_RGB;
	else if (ctrl->output == OUTPUT_WB_I80LDI0)
		cfg |= S3C_VIDCON0_VIDOUT_WB_I80LDI0;
	else if (ctrl->output == OUTPUT_WB_I80LDI1)
		cfg |= S3C_VIDCON0_VIDOUT_WB_I80LDI1;
	else {
		dev_err(ctrl->dev, "invalid output type: %d\n", ctrl->output);
		return -EINVAL;
	}

	writel(cfg, ctrl->regs + S3C_VIDCON0); //写入VIDCON0寄存器

	cfg = readl(ctrl->regs + S3C_VIDCON2);
	cfg &= ~(S3C_VIDCON2_WB_MASK | S3C_VIDCON2_TVFORMATSEL_MASK | \
					S3C_VIDCON2_TVFORMATSEL_YUV_MASK); //清楚VIDCON2寄存器

	if (ctrl->output == OUTPUT_RGB)
		cfg |= S3C_VIDCON2_WB_DISABLE;
	else if (ctrl->output == OUTPUT_ITU)
		cfg |= S3C_VIDCON2_WB_DISABLE;
	else if (ctrl->output == OUTPUT_I80LDI0)
		cfg |= S3C_VIDCON2_WB_DISABLE;
	else if (ctrl->output == OUTPUT_I80LDI1)
		cfg |= S3C_VIDCON2_WB_DISABLE;
	else if (ctrl->output == OUTPUT_WB_RGB)
		cfg |= (S3C_VIDCON2_WB_ENABLE | S3C_VIDCON2_TVFORMATSEL_SW | \
					S3C_VIDCON2_TVFORMATSEL_YUV444);
	else if (ctrl->output == OUTPUT_WB_I80LDI0)
		cfg |= (S3C_VIDCON2_WB_ENABLE | S3C_VIDCON2_TVFORMATSEL_SW | \
					S3C_VIDCON2_TVFORMATSEL_YUV444);
	else if (ctrl->output == OUTPUT_WB_I80LDI1)
		cfg |= (S3C_VIDCON2_WB_ENABLE | S3C_VIDCON2_TVFORMATSEL_SW | \
					S3C_VIDCON2_TVFORMATSEL_YUV444);
	else {
		dev_err(ctrl->dev, "invalid output type: %d\n", ctrl->output);
		return -EINVAL;
	}

	writel(cfg, ctrl->regs + S3C_VIDCON2); //写入VIDCON2寄存器

	return 0;
}	

     b、s3cfb_set_display_mode(ctrl)设置输出数据模式  

      RGB模式:

enum s3cfb_rgb_mode_t {
	MODE_RGB_P = 0,
	MODE_BGR_P = 1,
	MODE_RGB_S = 2,
	MODE_BGR_S = 3,
};

                                    Tiny210 LCD驱动分析_第9张图片

int s3cfb_set_display_mode(struct s3cfb_global *ctrl)
{
	u32 cfg;

	cfg = readl(ctrl->regs + S3C_VIDCON0);
	cfg &= ~S3C_VIDCON0_PNRMODE_MASK; //清空18 、17为
	cfg |= (ctrl->rgb_mode << S3C_VIDCON0_PNRMODE_SHIFT);
	writel(cfg, ctrl->regs + S3C_VIDCON0); //写入数据

	return 0;
}

c、s3cfb_set_polarity(fbdev)设置引脚极性 

        s3cfb_set_polarity函数主要是对vden(使能信号)、vsynsc(垂直同步信号)、hsync(行同步信号)、vclk (时钟clk)进行使能设置。

static struct s3cfb_lcd wvga_s70 = {
	………
	.polarity = {
		.rise_vclk = 0,
		.inv_hsync = 1,
		.inv_vsync = 1,
		.inv_vden = 0,
	},
};
int s3cfb_set_polarity(struct s3cfb_global *ctrl)
{
	struct s3cfb_lcd_polarity *pol;
	u32 cfg;

	pol = &ctrl->lcd->polarity;
	cfg = (ctrl->lcd->args & 0xf0);

	if (pol->rise_vclk)
		cfg |= S3C_VIDCON1_IVCLK_RISING_EDGE;

	if (pol->inv_hsync)
		cfg |= S3C_VIDCON1_IHSYNC_INVERT;

	if (pol->inv_vsync)
		cfg |= S3C_VIDCON1_IVSYNC_INVERT;

	if (pol->inv_vden)
		cfg |= S3C_VIDCON1_IVDEN_INVERT;

	writel(cfg, ctrl->regs + S3C_VIDCON1);

	return 0;
}

                                        Tiny210 LCD驱动分析_第10张图片

  vden、vsynsc、hsync、vclk参数的设置是通过主控datashee和LCD datasheet来确定的。下图为Tiny210主控时序(P1207):

                                     Tiny210 LCD驱动分析_第11张图片

      从上图可以看出,Tiny210 vden高电平,vsynsc、hsync都是高脉冲触发,vclk触发方式待指定。

      LCD时序图如下所示:  

                                        Tiny210 LCD驱动分析_第12张图片

        而LCD时序可以看到,vden高电平,vsynsc、hsync都是低脉冲触发,vclk触发方式待指定。

       所以,比较两边时序,vden、vclk可以不需要调整,vsynsc、hsync需要反转,所以参数如下:

	.polarity = {
		.rise_vclk = 0,
		.inv_hsync = 1,
		.inv_vsync = 1,
		.inv_vden = 0,
	},

d、s3cfb_set_timing(ctrl)设置时序

int s3cfb_set_lcd_size(struct s3cfb_global *ctrl)
{
	u32 cfg = 0;

#if defined(CONFIG_FB_S3C_101WA01S)
	cfg |= S3C_VIDTCON2_HOZVAL(1366 - 1);
	cfg |= S3C_VIDTCON2_LINEVAL(ctrl->lcd->height - 1);

#elif defined(CONFIG_FB_S3C_TL2796)
	cfg |= S3C_VIDTCON2_HOZVAL(1280 - 1);
	cfg |= S3C_VIDTCON2_LINEVAL(800 - 1);

#else
	cfg |= S3C_VIDTCON2_HOZVAL(ctrl->lcd->width - 1);
	cfg |= S3C_VIDTCON2_LINEVAL(ctrl->lcd->height - 1);
#endif

	writel(cfg, ctrl->regs + S3C_VIDTCON2);

	return 0;
}

       这个函数主要供能是设置各种时序和脉冲宽度,我们先看寄存器:

                                            Tiny210 LCD驱动分析_第13张图片

      而对应LCD的时序如下所示:

                                              Tiny210 LCD驱动分析_第14张图片

           HSPW :HS pulse width 水平同步信号脉宽

           HBPD :HS Blanking水平同步信号前肩

           HFPD :HS Front Porch水平同步信号后肩

           VSPW :VS pulse width垂直同步信号脉宽

           VBPD :VS Blanking垂直同步信号前肩

           VFPD :VS Front Potch垂直同步信号后肩

e、s3cfb_set_lcd_size设置lcd大小

 int s3cfb_set_lcd_size(struct s3cfb_global *ctrl)
{
	u32 cfg = 0;

#if defined(CONFIG_FB_S3C_101WA01S)
	cfg |= S3C_VIDTCON2_HOZVAL(1366 - 1);
	cfg |= S3C_VIDTCON2_LINEVAL(ctrl->lcd->height - 1);

#elif defined(CONFIG_FB_S3C_TL2796)
	cfg |= S3C_VIDTCON2_HOZVAL(1280 - 1);
	cfg |= S3C_VIDTCON2_LINEVAL(800 - 1);

#else
	cfg |= S3C_VIDTCON2_HOZVAL(ctrl->lcd->width - 1);
	cfg |= S3C_VIDTCON2_LINEVAL(ctrl->lcd->height - 1);
#endif

	writel(cfg, ctrl->regs + S3C_VIDTCON2);

	return 0;
}

          这里是设置分辨率大小,将LCD分辨率写入相对应的寄存器。

                                  Tiny210 LCD驱动分析_第15张图片

      所以,我们看到,s3cfb_init_global函数主要完成的设置为:

     output—–指定输出格式为RGB 
     display —-指定输出的rgb格式为normal ,且是并行输出 
     polartiy —-设置触发极性 
     timing —– 设置时间参数 
     lcd_size —–设置大小参数

f、s3cfb_alloc_framebuffer和s3cfb_register_framebuffer

      上面我们已经介绍了fb_info、 fb_fix_screeninfo、fb_var_screeninfo结构体,s3cfb_alloc_framebuffer函数的作用是申请fb_info 结构体,初始化fb_info 结构体的信息。

static int s3cfb_alloc_framebuffer(struct s3cfb_global *ctrl)
{
	struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
	int ret, i;

    // 根据wins 个数开辟fb_info 空间的指针,这个在platform device中指定了
	ctrl->fb = kmalloc(pdata->nr_wins *
			sizeof(*(ctrl->fb)), GFP_KERNEL);
	if (!ctrl->fb) {
		dev_err(ctrl->dev, "not enough memory\n");
		ret = -ENOMEM;
		goto err_alloc;
	}

    //接下来就是为每个fb_info 申请空间,进行初始化了
	for (i = 0; i < pdata->nr_wins; i++) {
        /*这个函数的功能是:开辟 fb_info s3cfb_windows 空间
                并且把 fb_info 中的dev 指向 fbdev->dev 
                把 fb_info->par 指向 s3cfb_windows */
		ctrl->fb[i] = framebuffer_alloc(sizeof(*ctrl->fb), ctrl->dev);
		if (!ctrl->fb[i]) {
			dev_err(ctrl->dev, "not enough memory\n");
			ret = -ENOMEM;
			goto err_alloc_fb;
		}

        /*主要是初始化fb_info 结构体,对var ,fix进行填充等....*/
		s3cfb_init_fbinfo(ctrl, i);

		if (i == pdata->default_win) {
            /*主要是为窗体分配存放RGB数据的空间。(该分配一般用DMA)*/
			if (s3cfb_map_video_memory(ctrl->fb[i])) {
				dev_err(ctrl->dev,
					"failed to map video memory "
					"for default window (%d)\n", i);
				ret = -ENOMEM;
				goto err_map_video_mem;
			}
		}
	}

	return 0;

err_alloc_fb:
	while (--i >= 0) {
		if (i == pdata->default_win) 
			s3cfb_unmap_default_video_memory(ctrl->fb[i]);

err_map_video_mem:
		framebuffer_release(ctrl->fb[i]);
	}
	kfree(ctrl->fb);

err_alloc:
	return ret;
}

      s3cfb_init_fbinfo函数主要初始化fbinfo结构体,并填充相关内容,参数如下:

static void s3cfb_init_fbinfo(struct s3cfb_global *ctrl, int id)
{
	struct fb_info *fb = ctrl->fb[id];
	struct fb_fix_screeninfo *fix = &fb->fix;
	struct fb_var_screeninfo *var = &fb->var;
	struct s3cfb_window *win = fb->par;
	struct s3cfb_alpha *alpha = &win->alpha;
	struct s3cfb_lcd *lcd = ctrl->lcd;
	struct s3cfb_lcd_timing *timing = &lcd->timing;

	memset(win, 0, sizeof(*win));
	platform_set_drvdata(to_platform_device(ctrl->dev), ctrl);
	strcpy(fix->id, S3CFB_NAME);

    /*    指定窗体id,窗体数据路径,dma burst ,win的win->power_state 状态,设置透明度方式*/
	win->id = id;
    /*选择数据来源DMA*/
	win->path = DATA_PATH_DMA;
     /*设置dma_burst ,大小范围可以根据数据数据手册决定*/
	win->dma_burst = 16;
	alpha->mode = PLANE_BLENDING;

    //设置fb_open、fb_read等接口,s3cfb_ops是个全局接口
	fb->fbops = &s3cfb_ops;
	fb->flags = FBINFO_FLAG_DEFAULT; //  然后是设置FBINFO
	fb->pseudo_palette = &win->pseudo_pal; //设置虚拟的调色板地址
#if (CONFIG_FB_S3C_NR_BUFFERS != 1)
	fix->xpanstep = 2;
	fix->ypanstep = 1;
#else
	fix->xpanstep = 0;
	fix->ypanstep = 0;
#endif
    /*  设置type --- FB_TYPE_PACKED_PIXELS   -----像素与内存对应,
        TFT就是基于这个管理内存。根据设备需求不同选择*/
	fix->type = FB_TYPE_PACKED_PIXELS;  
	fix->accel = FB_ACCEL_NONE;
    /*-----设置显示格式真彩,当然还有黑白*/
	fix->visual = FB_VISUAL_TRUECOLOR;
    //设置lcd分辨率
	var->xres = lcd->width;
	var->yres = lcd->height;
    /*设置虚拟分辨率,嵌入式设备一般不该分辨率*/
#if defined(CONFIG_FB_S3C_VIRTUAL)
	var->xres_virtual = CONFIG_FB_S3C_X_VRES;
	var->yres_virtual = CONFIG_FB_S3C_Y_VRES * CONFIG_FB_S3C_NR_BUFFERS;
#else
	var->xres_virtual = var->xres;
	var->yres_virtual = var->yres * CONFIG_FB_S3C_NR_BUFFERS;
#endif
     /* 设置成 32 bpp 的分辨率*/
	var->bits_per_pixel = 32;
    /*设置xoffset ,yoffset 偏移 都为0*/
    /*不为0 的话:var->xoffset = var->xres_virtual - var->xres - 1*/
    /*不为0的话:var->yoffset = var->yres_virtual - var->yres - 1;*/
	var->xoffset = 0;
	var->yoffset = 0;
	var->width = lcd->p_width;
	var->height = lcd->p_height;
	var->transp.length = 0;

	fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
	fix->smem_len = fix->line_length * var->yres_virtual;

	var->nonstd = 0; //----标准格式 ,!=0则为非标准格式
	var->activate = FB_ACTIVATE_NOW;  //----完全应用
	var->vmode = FB_VMODE_NONINTERLACED;  //-----正常扫描
	//设置timing参数
	var->hsync_len = timing->h_sw;
	var->vsync_len = timing->v_sw;
	var->left_margin = timing->h_fp;
	var->right_margin = timing->h_bp;
	var->upper_margin = timing->v_fp;
	var->lower_margin = timing->v_bp;

    //根据相应参数计算pixclock的值,
	ctrl->pixclock_hz = lcd->freq * (var->left_margin + var->right_margin +
#if defined(CONFIG_FB_S3C_101WA01S)
			var->hsync_len + 1366) *
			(var->upper_margin + var->lower_margin +
			 var->vsync_len + var->yres);
#elif defined(CONFIG_FB_S3C_TL2796)
			var->hsync_len + 1280) *
			(var->upper_margin + var->lower_margin +
			 var->vsync_len + 800);
#else
			var->hsync_len + var->xres) *
			(var->upper_margin + var->lower_margin +
			 var->vsync_len + var->yres);
#endif

#ifdef CONFIG_FB_S3C_LTE480WV
	ctrl->pixclock_hz = 2 * ctrl->pixclock_hz;
#endif

	var->pixclock = KHZ2PICOS(ctrl->pixclock_hz / 1000);

	dev_dbg(ctrl->dev, "pixclock: %d\n", var->pixclock);

    //设置fb的R/G/B位域
	s3cfb_set_bitfield(var);
   /*
        设置透明度 模式
        设置channel---0 通道
        设置value -------最大值不透明
    */
	s3cfb_set_alpha_info(var, win);
}

      调用s3cfb_alloc_framebuffer申请fb_info 结构体后,然后就是用s3cfb_register_framebuffer向内核注册lcd设备。

static int s3cfb_register_framebuffer(struct s3cfb_global *ctrl)
{
	struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
	int ret, i, j;

	for (i = pdata->default_win;
		i < pdata->nr_wins + pdata->default_win; i++) {
			j = i % pdata->nr_wins;
			ret = register_framebuffer(ctrl->fb[j]); //像内核注册lcd设置,注册成功会有/dev/fbx节点
			if (ret) {
				dev_err(ctrl->dev, "failed to register "
						"framebuffer device\n");
				ret = -EINVAL;
				goto err_register_fb;
			}
#ifndef CONFIG_FRAMEBUFFER_CONSOLE
			if (j == pdata->default_win) {
                // 检查参数
				s3cfb_check_var(&ctrl->fb[j]->var, ctrl->fb[j]);
                //设置参数
				s3cfb_set_par(ctrl->fb[j]);
                //显示log
				s3cfb_draw_logo(ctrl->fb[j]);
			}
#endif
	}

	return 0;

err_register_fb:
	while (--i >= pdata->default_win) {
		j = i % pdata->nr_wins;
		unregister_framebuffer(ctrl->fb[j]);
	}
	return ret;
}

g、s3cfb_set_clock、s3cfb_set_window、s3cfb_display_on设置时钟、选择windows通道,使能wins窗口

 /*
     该函数的主要作用是获得时钟,计算clk,
     向VIDCON0 配置相应的时钟参数
*/
int s3cfb_set_clock(struct s3cfb_global *ctrl)
{
	struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
	u32 cfg, maxclk, src_clk, vclk, div;

	maxclk = 86 * 1000000;

	/* fixed clock source: hclk */
	cfg = readl(ctrl->regs + S3C_VIDCON0);
	cfg &= ~(S3C_VIDCON0_CLKSEL_MASK | S3C_VIDCON0_CLKVALUP_MASK |
		S3C_VIDCON0_VCLKEN_MASK | S3C_VIDCON0_CLKDIR_MASK);
	cfg |= (S3C_VIDCON0_CLKVALUP_ALWAYS | S3C_VIDCON0_VCLKEN_NORMAL |
		S3C_VIDCON0_CLKDIR_DIVIDED);


	if (strcmp(pdata->clk_name, "sclk_fimd") == 0) {
		cfg |= S3C_VIDCON0_CLKSEL_SCLK;
		src_clk = clk_get_rate(ctrl->clock);
		printk(KERN_INFO "FIMD src sclk = %d\n", src_clk);
	} else {
		cfg |= S3C_VIDCON0_CLKSEL_HCLK;
		src_clk = ctrl->clock->parent->rate;
		printk(KERN_INFO "FIMD src hclk = %d\n", src_clk);
	}

	vclk = ctrl->pixclock_hz;

	if (vclk > maxclk) {
		dev_info(ctrl->dev, "vclk(%d) should be smaller than %d\n",
			vclk, maxclk);
		/* vclk = maxclk; */
	}

	div = src_clk / vclk;
	if (src_clk % vclk)
		div++;

	if ((src_clk/div) > maxclk)
		dev_info(ctrl->dev, "vclk(%d) should be smaller than %d Hz\n",
			src_clk/div, maxclk);

	cfg |= S3C_VIDCON0_CLKVAL_F(div - 1);
	writel(cfg, ctrl->regs + S3C_VIDCON0);

	dev_dbg(ctrl->dev, "parent clock: %d, vclk: %d, vclk div: %d\n",
			src_clk, vclk, div);

	return 0;
}

        s3cfb_set_window选择windows通道:

static void s3cfb_set_window(struct s3cfb_global *ctrl, int id, int enable)
{
	struct s3cfb_window *win = ctrl->fb[id]->par;

	if (enable) {
		s3cfb_window_on(ctrl, id); //打开窗口
		win->enabled = 1;
	} else {
		s3cfb_window_off(ctrl, id); // 关闭窗口
		win->enabled = 0;
	}
}
int s3cfb_window_on(struct s3cfb_global *ctrl, int id)
{
	struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
	u32 cfg;

	cfg = readl(ctrl->regs + S3C_WINCON(id));
	cfg |= S3C_WINCON_ENWIN_ENABLE;
	writel(cfg, ctrl->regs + S3C_WINCON(id));

	if (pdata->hw_ver == 0x62) {
		cfg = readl(ctrl->regs + S3C_WINSHMAP);
		cfg |= S3C_WINSHMAP_CH_ENABLE(id);
		writel(cfg, ctrl->regs + S3C_WINSHMAP);
	}

	dev_dbg(ctrl->dev, "[fb%d] turn on\n", id);

	return 0;
}
int s3cfb_window_off(struct s3cfb_global *ctrl, int id)
{
	struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
	u32 cfg;

	cfg = readl(ctrl->regs + S3C_WINCON(id));
	cfg &= ~(S3C_WINCON_ENWIN_ENABLE | S3C_WINCON_DATAPATH_MASK);
	cfg |= S3C_WINCON_DATAPATH_DMA;
	writel(cfg, ctrl->regs + S3C_WINCON(id));

	if (pdata->hw_ver == 0x62) {
		cfg = readl(ctrl->regs + S3C_WINSHMAP);
		cfg &= ~S3C_WINSHMAP_CH_DISABLE(id);
		writel(cfg, ctrl->regs + S3C_WINSHMAP);
	}

	dev_dbg(ctrl->dev, "[fb%d] turn off\n", id);

	return 0;
}

      而s3cfb_display_on真正使能lcd:

int s3cfb_display_on(struct s3cfb_global *ctrl)
{
	u32 cfg;

	cfg = readl(ctrl->regs + S3C_VIDCON0);
	cfg |= (S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE);
	writel(cfg, ctrl->regs + S3C_VIDCON0);

	dev_dbg(ctrl->dev, "global display is on\n");

	return 0;
}

                                Tiny210 LCD驱动分析_第16张图片

      至此,LCD框架的probe函数基本结束,还有像注册irq中断,在/sys/class/下创建一个属性文件之类的跟一般驱动中的类似,这里不着重介绍了。

你可能感兴趣的:(Tiny210 LCD驱动分析)