Linux驱动开发(十七):LCD驱动(Framebuffer子系统)

Framebuffer设备

在裸机开发LCD的时候要初始化eLCDIF控制器,重点是LCD屏幕width、height、hspw、hbp、hfp、vspw、vbp、vfp
Linux中的应用程序最终通过操作LCD的显存来实现在LCD上显示字符、图片等信息,因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存
Framebuffer子系统,帧缓冲,简称fb,fb是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一个 fb设备,当我们编写好 LCD驱动以后会生成一个名为 /dev/fbX(X=0~n)的设备,应用程序通过访问 /dev/fbX这个设备就可以访问 LCD
fb的file_operations操作集为fb_fop

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,
};

LCD 驱动

不同分辨率的LCD屏幕区别在于设置屏幕参数,这部分要放到设备树中
打开 imx6ull.dtsi 中的lcdif节点

lcdif: lcdif@021c8000 {
				compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
				reg = <0x021c8000 0x4000>;
				interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
					 <&clks IMX6UL_CLK_LCDIF_APB>,
					 <&clks IMX6UL_CLK_DUMMY>;
				clock-names = "pix", "axi", "disp_axi";
				status = "disabled";
			};

以上信息是所有使用imx6ull芯片的板子共有的,并不完整,在板级dtsi文件中会对屏幕参数等信息进行添加
\drivers\video\fbdev\mxsfb.c 是 imx6ull的LCD驱动文件

static struct platform_driver mxsfb_driver = {
	.probe = mxsfb_probe,
	.remove = mxsfb_remove,
	.shutdown = mxsfb_shutdown,
	.id_table = mxsfb_devtype,
	.driver = {
		   .name = DRIVER_NAME,
		   .of_match_table = mxsfb_dt_ids,
		   .pm = &mxsfb_pm_ops,
	},
};

可以看出这是标准的platform驱动

Framebuffer驱动编写流程

Linux内核将所有的Framebuffer抽象为一个fb_info结构体,包含了Framebuffer设备的完整属性和操作集合,每一个Framebuffer设备都必须有一个fb_info
LCD的驱动就是构建fb_info并向系统注册fb_info的过程
定义在fb.h,如下

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 用于fb_mmap和smem_*域的互斥锁*/
	struct fb_var_screeninfo var;	/* Current var 当前可变参数*/
	struct fb_fix_screeninfo fix;	/* Current fix 当前固定参数*/
	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 /*如果LCD支持背光*/
	/* 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; /*帧缓冲操作函数集*/
	struct device *device;		/* This is the parent父设备 */
	struct device *dev;		/* This is this fb device 当前fb设备*/
	int class_flag;                    /* private sysfs flags 私有sysfs标志*/
#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 伪16位调色板*/ 
#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;

	bool skip_vt_switch; /* no VT switch on suspend/resume required */
};

fb_info结构体的成员变量很多,我们重点关注 var、 fix、 fbops、 screen_base、 screen_size和 pseudo_palette
mxsfb_probe函数的主要工作内容为:
1、申请 fb_info。
2、初始化 fb_info结构体中的各个成员变量。
3、初始化 eLCDIF控制器。
4、使用 register_framebuffer函数向 Linux内核注册初始化好的 fb_info
register_framebuffer函数原型如下

int register_framebuffer(struct fb_info *fb_info);

驱动程序的编写

6ULL的 eLCDIF接口驱动程序 NXP已经编写好了,因此 LCD驱动部分我们不需要去修改。我们需要做的就是按照所使用的 LCD来修改设备树。重点要注意三个地方
1、LCD所使用的IO配置
这个部分NXP已经写好了

pinctrl_lcdif_dat: lcdifdatgrp {
			fsl,pins = <
				MX6UL_PAD_LCD_DATA00__LCDIF_DATA00  0x79
				MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x79
				MX6UL_PAD_LCD_DATA02__LCDIF_DATA02  0x79
				MX6UL_PAD_LCD_DATA03__LCDIF_DATA03  0x79
				MX6UL_PAD_LCD_DATA04__LCDIF_DATA04  0x79
				MX6UL_PAD_LCD_DATA05__LCDIF_DATA05  0x79
				MX6UL_PAD_LCD_DATA06__LCDIF_DATA06  0x79
				MX6UL_PAD_LCD_DATA07__LCDIF_DATA07  0x79
				MX6UL_PAD_LCD_DATA08__LCDIF_DATA08  0x79
				MX6UL_PAD_LCD_DATA09__LCDIF_DATA09  0x79
				MX6UL_PAD_LCD_DATA10__LCDIF_DATA10  0x79
				MX6UL_PAD_LCD_DATA11__LCDIF_DATA11  0x79
				MX6UL_PAD_LCD_DATA12__LCDIF_DATA12  0x79
				MX6UL_PAD_LCD_DATA13__LCDIF_DATA13  0x79
				MX6UL_PAD_LCD_DATA14__LCDIF_DATA14  0x79
				MX6UL_PAD_LCD_DATA15__LCDIF_DATA15  0x79
				MX6UL_PAD_LCD_DATA16__LCDIF_DATA16  0x79
				MX6UL_PAD_LCD_DATA17__LCDIF_DATA17  0x79
				MX6UL_PAD_LCD_DATA18__LCDIF_DATA18  0x79
				MX6UL_PAD_LCD_DATA19__LCDIF_DATA19  0x79
				MX6UL_PAD_LCD_DATA20__LCDIF_DATA20  0x79
				MX6UL_PAD_LCD_DATA21__LCDIF_DATA21  0x79
				MX6UL_PAD_LCD_DATA22__LCDIF_DATA22  0x79
				MX6UL_PAD_LCD_DATA23__LCDIF_DATA23  0x79
			>;
		};
pinctrl_lcdif_ctrl: lcdifctrlgrp {
			fsl,pins = <
				MX6UL_PAD_LCD_CLK__LCDIF_CLK	    0x79
				MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE  0x79
				MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC    0x79
				MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC    0x79
			>;
		};

2、LCD屏幕节点修改,修改相应的属性值,换成我们所使用的LCD屏幕参数

&lcdif {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_lcdif_dat
		     &pinctrl_lcdif_ctrl>;

	display = <&display0>;
	status = "okay";

	display0: display {
		bits-per-pixel = <24>;
		bus-width = <24>;

		display-timings {
			native-mode = <&timing0>;
			timing0: timing0 {
			clock-frequency = <9000000>;
			hactive = <480>;
			vactive = <272>;
			hfront-porch = <5>;
			hback-porch = <40>;
			hsync-len = <20>;
			vback-porch = <8>;
			vfront-porch = <8>;
			vsync-len = <3>;

			hsync-active = <0>;
			vsync-active = <0>;
			de-active = <1>;
			pixelclk-active = <0>;
			};
		};
	};
};

3、LCD背光节点信息修改,要根据实际所使用的背光IO来修改相应的设备节点信息

pinctrl_pwm1: pwm1grp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
			>;
		};
backlight {
		compatible = "pwm-backlight";
		pwms = <&pwm1 0 5000000>;
		brightness-levels = <0 4 8 16 32 64 128 255>;
		default-brightness-level = <6>;
		status = "okay";
	};

设置LCD作为终端

1、在Uboot中修改bootargs
之前的bootargs为

bootargs=console=ttymxc0,115200 root=/dev/nfs rw \
nfsroot=192.168.1.111:/home/gyy/linux/nfs/buildrootfs \
ip=192.168.1.144:192.168.1.111:192.168.1.1:255.255.255.0::eth0:off

我们修改为

bootargs=console=tty1 console=ttymxc0,115200 root=/dev/nfs rw \
nfsroot=192.168.1.111:/home/gyy/linux/nfs/buildrootfs \
ip=192.168.1.144:192.168.1.111:192.168.1.1:255.255.255.0::eth0:off

重点就是加了一句console=tty1
2、修改/etc/inittab文件
在里面加一句

tty1::askfirst:-/bin/sh

打开tty1,将LCD设置为终端

这时我们连接上LCD屏幕启动板子后就可以在LCD上看到Linux的启动画面了

LCD背光调节

前面在设备树的backlight节点设置了七个亮度等级(其实就是七个不同占空比)
在命令行界面使用命令

cd /sys/devices/platform/backlight/backlight/backlight
echo 7 > brightness

这样就设置了最大亮度

你可能感兴趣的:(arm+linux开发)