framebuff 大小是 申请地址的 乘以长度
遇到的问题,各种报错出现引脚抢占 pwm1 与lcd引脚重突,禁止pwm
屏幕出现了 花屏 驱动不兼容
芯片io地址是为了开发板上找到io,我们换一个屏幕地址是不变的
时钟设备树使用了时钟子系统,只需要在设备树中填入值就可以
framebuffer 大小在驱动中初始化的使用一次性申请
LCD操作原理
RGB 接口的 TFT-LCD 驱动
芯片集成了显存(ddr、SDRM)和控制器
1、看lcd手册,修改对应的lcd控制器
2、看lcd控制器手册,
一个像素点的颜色使用 24位表示,这样的话硬件对应数据引脚有24个
LCD硬件模型
RGB 传输数据
HSYNC 行同步
VSYNC 帧同步
DE 使能有效
declock 移动一个像素(频率)
HSYNC 信号发送过来电子枪就会换行,VSYNC信号发送过来就会电子枪换下一帧,DE处于高电平,电子枪才能有效
LCD驱动框架
很多人都会说操纵lcd显示就是操纵framebuffer,表面上来看是这样的。实际上是frambuffer就是linux内核驱动申请的一片内存空间,然后lcd内有一片sram,cpu内部有个lcd控制器,它有个单独的dma用来将frambuffer中的数据拷贝到lcd的sram中去 拷贝到lcd的sram中的数据就会显示在lcd上,LCD驱动和framebuffer驱动没有必然的联系,它只是驱动LCD正常工作的,比如有信号传过来,那么LCD驱动负责把信号转成显示屏上的内容,至于什么内容这就是应用层要处理的
整体驱动框架流程
应用程序通过open 打开主设备为29的设备节点 ,然后通过文件描述符使用 ioctl获取到var fix等硬件参数 然后使用mmap映射显存 最后通过映射出来的地址读写Framebuffer
|
|
|
V
应用程序的read、write调用到 内核中的fbmem.c 中的file_operations结构体fb_ops 中的fb_read、fb_write
|
|
|
V
fb_read、fb_write 中通过 该设备节点对应的次设备号从registered_fb【】这个数组中 得到描述具体设备的fb_info结构体 info ,通过info->fbops 就可以得到具体设备对应的具体操作函数了!
fbmen 从传入的innode节点中获取egistered_fb[],最后获得匹配的info机构体
1、fbmen 驱动框架
通用框架
operation 操作函数*(都是空定义,具体操作在别的文件,通过fb_info结构体获得具体的硬件之类的信息)
register 注册函数
·创建 class
注销函数
在fbmen.c中写出字符设备所需的一切,当应用程序调用fbmen。然后通过fb_info跳转到具体硬件操作函数中去(具体的硬件操写在fb_fo中实现,fb_info 实现在具体硬件程序中)
核心层(抽象层):只有驱动架构没有具体的硬件操作
fbmen.c中存在
fb_fops 操作定义函数,设备号创建函数、设备注册函数、设备文件创造函数
1、fbmen_init (1848)
fbmem_init(void)
{
int ret;
if (!proc_create_seq("fb", 0, NULL, &proc_fb_seq_ops))
return -ENOMEM;
ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
if (ret) {
printk("unable to get major %d for fb devs\n", FB_MAJOR);
goto err_chrdev;
}
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
ret = PTR_ERR(fb_class);
pr_warn("Unable to create fb class; errno = %d\n", ret);
fb_class = NULL;
goto err_class;
}
fb_console_init();
return 0;
register_chrdev
将fb注册进内核,
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))//fb设备注册进内核
->static inline int register_chrdev(unsigned int major, const char *name,//只有一个定义
const struct file_operations *fops)
创建graphices类
创建一个新的 "graphics" 类别在 sysfs 中用于管理帧缓冲设备,直接对文件读写修改一个配置
2、register_framebuffer(1774)
注册一个帧缓冲设备,函数执行了帧缓冲设备的实际注册过程,包括设备节点的创建、内存分配和初始化等操作,并调用了已注册的fb_notifier链表的相关处理函数
static int do_register_framebuffer(struct fb_info *fb_info)
{
int i, ret;
struct fb_event event;
struct fb_videomode mode;
// 检查 fb_info 是否是外部设备(foreignness)。如果是外部设备,则返回错误码 -ENOSYS。
if (fb_check_foreignness(fb_info))
return -ENOSYS;
// 检查是否存在冲突的帧缓冲设备,并在有冲突的情况下尝试移除冲突设备。
ret = do_remove_conflicting_framebuffers(fb_info->apertures,
fb_info->fix.id,
fb_is_primary_device(fb_info));
if (ret)
return ret;
// 检查已注册的帧缓冲设备数量是否已达到 FB_MAX,如果是,则返回错误码 -ENXIO。
if (num_registered_fb == FB_MAX)
return -ENXIO;
// 增加已注册帧缓冲设备数量并寻找一个未被使用的索引 i。
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
// 初始化帧缓冲设备的计数器 count 和互斥锁 lock、mm_lock。
atomic_set(&fb_info->count, 1);
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
// 在 sysfs 中创建设备节点,fb_info->dev 用于表示创建的设备。
// 如果创建失败,打印警告消息,并将 fb_info->dev 设置为 NULL。
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
if (IS_ERR(fb_info->dev)) {
printk(KERN_WARNING "无法为帧缓冲设备 %d 创建设备节点; 错误号 = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info); // 初始化设备
// 在 fb_info 中分配并设置帧缓冲设备的 pixmap 参数。
// pixmap 用于维护图形操作时的缓冲区信息,例如 X server 的像素深度等。
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;
// 设置帧缓冲设备的 pixmap.blit_x 和 pixmap.blit_y 的默认值。
// 这些值用于定义帧缓冲设备支持的 blit 操作的边界。
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;
// 如果帧缓冲设备的 modelist.prev 或 modelist.next 为空,则将其初始化为双向链表头。
if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist);
// 根据设备配置决定是否需要交由 pm_vt_switch_required 来控制终端的切换。
if (fb_info->skip_vt_switch)
pm_vt_switch_required(fb_info->dev, false);
else
pm_vt_switch_required(fb_info->dev, true);
// 将 fb_var(可变信息)转换为 fb_videomode,并将其添加到 modelist 链表中。
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
// 将 fb_info 添加到已注册的帧缓冲设备数组中。
registered_fb[i] = fb_info;
// 触发 FB_EVENT_FB_REGISTERED 事件,并传递 fb_info 作为事件信息。
event.info = fb_info;
console_lock();
if (!lock_fb_info(fb_info)) {
console_unlock();
return -ENODEV;
}
// 调用已注册的 fb_notifier,并传递 FB_EVENT_FB_REGISTERED 事件。
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
unlock_fb_info(fb_info);
console_unlock();
return 0;
}
3、unresign_framebuffer
注销一个帧缓冲设备
2、驱动部分*(硬件操作)
总线平台架构 ,下面的都在probe函数中执行
1. 分配一个fb_info结构体
fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
2. 设置fb_info结构体参数
1、填充固定参数结构体
fb_fix_screeninfo
//名字
strcpy(s3c_lcd->fix.id, "mylcd");
// MINI2440的LCD位宽是24,但是2440里会分配4字节即32位(浪费1字节)
s3c_lcd->fix.smem_len = 320*240*32/8;
//类型,这里选择默认值
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
//我们是TFT屏,所以选择真彩色
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR;
//一行的长度大小,320位*4字节
s3c_lcd->fix.line_length = 320*4;
2、填充不固定参数结构体
fb_var_screeninfo
s3c_lcd->var.xres = 320;//x方向的分辨率
s3c_lcd->var.yres = 240;//y方向的分辨率
s3c_lcd->var.xres_virtual = 320;//x方向的虚拟分辨率
s3c_lcd->var.yres_virtual = 240;//y方向的虚拟分辨率
s3c_lcd->var.bits_per_pixel = 32;//每个像素用32位/* RGB:888 */
s3c_lcd->var.red.offset = 16;//偏移
s3c_lcd->var.red.length = 8;//长度s3c_lcd->var.green.offset = 8;
s3c_lcd->var.green.length = 8;s3c_lcd->var.blue.offset = 0;
s3c_lcd->var.blue.length = 8;
//立刻生效
s3c_lcd->var.activate = FB_ACTIVATE_NOW;
3、设置操作函数
static struct fb_ops mxsfb_ops = {
.owner = THIS_MODULE,
.fb_check_var = mxsfb_check_var,
.fb_set_par = mxsfb_set_par,
.fb_setcolreg = mxsfb_setcolreg, //设置调色板
.fb_ioctl = mxsfb_ioctl,
.fb_blank = mxsfb_blank,
.fb_pan_display = mxsfb_pan_display,
.fb_mmap = mxsfb_mmap,
.fb_fillrect = cfb_fillrect, //画矩形
.fb_copyarea = cfb_copyarea, //拷贝区域
.fb_imageblit = cfb_imageblit,//画图
};
3. 硬件相关的操作
3.1 引脚设置
根据开发板lcd原理图,和外界屏幕的lcd原理图
这个是芯片的lcd原理图,我们根据外接屏幕原理图判断出芯片lcd只有上面24+1+4个引脚起作用。
背光引脚复用为·gpio负责输出高低电平,其他配置为lcd功能,
这个不用该
GPIO设置
LCD引脚
背光引脚
使用pinctrl配置LCD引脚,然后使用GPIO控制背光
看原理图,要点亮lcd需要配置什么gpio,还有背光引脚
3.2 时钟设置
一般都是51.2kMZ
/* 时钟计算公式如下
* Video PLL output frequency(PLL5)= Fref * (DIV_SELECT + NUM/DENOM)
* PLL5_MAIN_CLK = PLL5 / POST_DIV_SELECT / VIDEO_DIV
* LCDIF1_CLK_ROOT = PLL5_MAIN_CLK /LCDIF1_PRED / LCDIF1_PODF
*
* 输出LCDIF1_CLK_ROOT七寸屏:51.2Mhz
* DIV_SELECT = 32 (寄存器相应位设置为32)
* NUM = 0 (寄存器相应位设置为0)
* DENOM = 0 (寄存器相应位设置为0)
* POST_DIV_SELECT = 1 (寄存器相应位设置为2)
* VIDEO_DIV = 1 (寄存器相应位设置为0)
* LCDIF1_PRED = 3 (寄存器相应位设置为2)
* LCDIF1_PODF = 5 (寄存器相应位设置为4)
*
*/
/*
*为方便使用 ,不使用小数分频器
*NUM = 0
*DENOM = 0
*公式简化为:
* Video PLL output frequency(PLL5)= Fref * DIV_SELECT
* PLL5_MAIN_CLK = PLL5 / POST_DIV_SELECT / VIDEO_DIV
* LCDIF1_CLK_ROOT = PLL5_MAIN_CLK /LCDIF1_PRED / LCDIF1_PODF
根据LCD手册设置LCD控制器, 比如VCLK的频率等
确定LCD 控制器的时钟(驱动代码中修改)
pix:Pixel clock,用于LCD接口,设置为LCD手册上的参数
axi:AXI clock,用于传输数据、读写寄存器,使能即可
根据LCD的DCLK计算相关时钟
3.3 配置LCD控制器
0、在设备树中指定参数(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>; timing 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>; DE高电平有效 pixelclk-active = <0>; 像素时钟 下降沿有效 }; }; }; };
1、从设备树中获得参数
时序参数、引脚极性等信息,都被保存在一个display_timing结构体里:
获得节点共有属性
获得子节点display-timing私有属性
使用read函数,以设备树节点为基点读取属性参数
时序参数、引脚极性等信息,都被保存在一个display_timing结构体里:
flag 引脚极性
2、使用参数配置lcd控制器(核心)
Framebuffer地址设置
Framebuffer中数据格式设置
LCD时序参数设置
LCD引脚极性设置
4. 注册
ret = register_framebuffer(fb_info);//注册设备结构体,info里面包含了opeatioion操作函数
直接调用框架中的函数就可以
5、卸载unregister_framebuffer
调用传入fbmen 框架中的unregister_framebuffer,然后传入fb_info,
驱动代码
1、平台总线框架
2、probe函数