mipi接口 1280(RGB)*720 LCD屏开发驱动笔记帖
ps:创业开发产品,自学笔记,不一定适合教材性的阅读,零碎整理,自我总结用
材料:4.1寸lcd屏两块,屏自带触控,屏幕资料具备,rk3399和MK8788开发版上分别开发。
开发环境:
1】rk3399
-
- 开发板:友善的rk3399v2开发版,资料链接:
- 处理器:RK3399
- 内核:
- 编译工具:
- LCD:4.1‘’ LCD屏幕
- 参考文献
LCD驱动程序详细讲解(一)_weixin_33935505的博客-CSDN博客
LCD驱动详解 - Lilto - 博客园 (cnblogs.com)
主题:s3c2440移植linux-3.4.2中的LCD驱动_大白菜的博客-CSDN博客
【第2期】韦东山嵌入式Linux之第2期_驱动大全 (100ask.net)
2】Mk8788
- 开发板:自购mk8788 ,具备开发板接口讲解资料、源码
产品规格书:仿照此页面采用联发科I500P(MT8788)芯片方案的4G全网通安卓10平板电脑 | ScenSmart一站式智能制造平台|OEM|ODM|行业方案
一、搭建ubuntu
ps:人傻毛病多,自己给自己制造问题,数之不尽问题,自己慢慢解决吧,主要是真的很琐碎
硬件:
多台电脑、不同系统电脑
下载好ubuntu的桌面版和服务器版(相信未来可以驾驭它),
1、windows环境搭建虚拟机
windows中使用微软的那个啥啥来着,创建实例不会,还没搜索找资料
windows中使用ubuntu官网中的虚拟机感觉很难用
windows中不想使用那个啥啥流行的虚拟机
2、mac m2和m1 mimi搭建
1】下载了paralleres虚拟机,只有试用14天,对此就直接很不想用了,先学linux命令,学会买
2】mac的性能感觉真的很强,编译在虚拟机中可能会很强,然后装个ubuntu的主机去移植吧,主要是写帖子时候目前还到不了移植测试步骤
3、单独设备安装ubuntu系统
二、配置ubuntu
1】开启ssh
2】开启wiff、固定ip
3】更换国内源
4】安装输入法
5】linux 更新命令、安装工具来一遍
三、了解rk3399和mk8788的开发、编译等教程
四、解读lcd屏幕资料
ps:英文很差,基本一字一句的翻译理解,电气术语翻译还会有不通顺处。
五、下载安卓源码、rk3399v2源码和mk8788源码
复制
解压
移动
创建文件
六、.mk文件理解
七、源码文件目录用途
八、移植工具和移植教程
九、刷机相关学习
十、mipi接口的学习
用途:相机和屏幕,接口定义一致时,不知硬件上是否可以调换,然后代码控制?
mipi data是成对的差分信号,屏幕一般为2mipi和4mipi,区别在哪?
博主要开发的屏幕资料中有说明可以选择几路mipi,区别在哪?
对于相机传感器:同一颗sensor【传感器】由于register setting【寄存器/注册设置】不同,输出的信号有可能是2 lane或者4lane等
对于屏幕:lcd drive ic的寄存器?或者ic支持最大4lu,但具体要看Lcd屏的排线实现原理?待定未知
知识点1
MIPI_RDN和MIPI_RDP,有几对这样的pin脚就代表有*路mipi通道
第一路 | MIPI_RDN0 | |
MIPI_RDP0 | ||
第二路 | MIPI_RDN1 | |
MIPI_RDP1 | ||
第三路 | MIPI_RDN2 | |
MIPI_RDP2 | ||
第四路 | MIPI_RDN3 | |
MIPI_RDP3 |
知识点二 时钟
MIPI_RCN和MIPI_RCP:时钟信号
RCN | |
RCP |
CMMCLK | 时钟引脚 |
用法:
知识点三 I2C接口
SCL、SDA十I2C接口
SCL | |
DA |
用法:
mipi接口 参考文:
1⃣️此文作者笔风可爱,所以排第一
【Camera专题】你应该了解的Camera HW-硬件知识_c枫_撸码的日子的博客-CSDN博客_mipi2lane和4laneppi
2⃣️专业度高,直接飙代码
Android Camera驱动分析_Mr.How的博客-CSDN博客_查看当前安卓相机驱动
屏幕mipi接口
mipi接口原理
rgb lcd屏幕和MCU-LCD区别
(转)RGB接口和i80接口的区别
此链接中很多专业解释的片段,建议大量阅读!
重点1:MCU-LCD屏它与RGB-LCD屏主要区别在于显存的位置.RGB-LCD的显存是由系统内存充当的,因此其大小只受限于系统内存的大小,这样 RGB-LCD可以做出较大尺寸,像4.3"只能算入门级,而MID中7",10"的屏都开始大量使用.
而MCU-LCD的设计之初只要考虑单片机的 内存较小,因此都是把显存内置在LCD模块内部.然后软件通过专门显示命令来更新显存,因此MCU屏往往不能做得很大.同时显示更新速度也比RGB- LCD慢
重点2:RGB显示速度明显比MCU快是因为。。。
mipi和lvds区别
液晶屏MIPI接口与LVDS接口区别(总结)
十一、mk8788开发文章
1、MTK8788[android 9.0]GT9XX TP触摸屏驱动流程分析
MTK8788[android 9.0]GT9XX TP触摸屏驱动流程分析_Jimmy8618的博客-CSDN博客_vtouch-supply
摘录:
/* TOUCH start */
&touch {tpd-resolution = <800 1280>;//分辨率use-tpd-button = <0>;//如果TP有待按键。则定义值为1tpd-key-num = <3>;//按键的数量tpd-key-local= <139 172 158 0>;//按键的编码,一般为KEY_MENU,KEY_HOMEPAGE,KEY_BACK的键值tpd-key-dim-local = <90 883 100 40 230 883 100 40 370 883 100 40 0 0 0 0>;//按键的布局信息,包含按键的宽度,高度,中心点的坐标tpd-max-touch-num = <5>;//支持的最大触摸点数
goodix_touch@5e {compatible = "mediatek,goodix_touch"; //用于匹配GT9xx这个TP驱动reg = <0x5e>;//GT9xx I2c的地址interrupt-parent = <&pio>; //中断脚interrupts = <1 IRQ_TYPE_EDGE_RISING 1 0>;//中断模式是上升沿触发//vtouch-supply = <&mt_pmic_vldo28_ldo_reg>; //原生MTK节点TP 2.8V PMIC供电reg-tp-supply = <&mt_pmic_vldo28_ldo_reg>; //由于我们代码上修改了TP 2.8V PMIC供电节点获取是以reg-tp-supply这个字符获取的原生的是由vtouch-supply这个获取的rst-gpio = <&pio 158 0>;//TP的复位引脚int-gpio = <&pio 1 0>;//TP的中断引脚
2、
MTK8788[android 9.0]调试笔记 MIPI屏驱动移植_sdkdlwk的博客-CSDN博客
原生MTK LCM屏添加的路径在\vendor\mediatek\proprietary\bootable\bootloader\lk\dev\lcm*
多个屏驱动兼容。。。。
3、显示驱动,源码文件位置
mt8788_android9.0_lcd(lt8912b_mipi2lvds)_1024_768-Android文档类资源-CSDN下载
4、背光调节
mtk8788 pwm频率及背光亮度调节_阿闷的博客-CSDN博客_pwm背光调节
5、小平米调整系统字体
mtk8788 android 9.0 加大系统字体和图标_cerzong的博客-CSDN博客
摘要:由于屏幕(320*320)太小了,客户提个需求希望将APP图标跟系统字体加大,APP图标加大实际就是增大屏幕密度
6、LCD-TFT
LCD 液晶屏驱动详解_屏幕驱动_LTracer的博客-CSDN博客
LCD种类、电路连接及显示原理
LCD,即液晶显示器,是一种采用了液晶控制透光技术来实现色彩的显示器。LCD有很多种类型,比如STN、TFT、LTPS、OLED等。各有优缺点。TFT类型液晶显示器是目前最为主流的液晶显示器。
TFT-LCD的数据传输方式有2种:
单扫:对于一整屏的数据,从上到下,从左到右,一个一个地发送出来。
双扫:将一整屏的数据分为上下两部分,同时的从上到下,从左到右,一个一个的发送出来。
区别在哪里?对硬件要求区别?
LCD的信号种类:
信号名称 | 描述 |
---|---|
VSYNC | 垂直同步信号 |
HSYNC | 水平同步信号 |
VD[23:0] | 数据信号 |
HCLK | 时钟信号 |
LEND | 行结束信号 |
PWREN | 电源开关信号 |
- 借鉴其他电路模块图
- LCD控制器原理图
- 显示原理
除了配置一些寄存器告诉LCD控制器图像中像素的格式(RGB565),frameBuffer的首地址之类外,对于TFT LCD的访问还需要用到一些信号,所以需要通过配置寄存器来告诉LCD控制器这些信号的信息(比如何时发出控制信号,发出信号的持续时间等),
举个例子:
向LCD驱动器发送图片数据时需要时钟控制(VCLK),一个时钟发送一个像素点,那么控制器就需要主动发出时钟信号,这个时钟是由哪个引脚发出的,发出的频率是多少,这个都是要配置寄存器的,
通过时序图来分析需要用到的一些信号以及如何去配置它们,如果是第一次了解LCD控制,直接看时序还是比较困难的,所以先给出一个形象的比喻 :
frame buffer: 显存,用于存放LCD显示数据;frame buffer通过LCD控制器和LCD Panel建立一一映射关系;
LCD控制器: 参考LCD用户手册,配置LCD控制器,用于发出LCD控制信号,驱动LCD显示;
扫描方式: 如图所示,由start到end的扫描方向是:从左到右,从上到下(扫描方向的一种);
HSYNC: 行同步信号,用于行切换,一行扫描结束,需要扫描新行时,需要先发送行同步信号;
VSYNC: 列同步信号,用于列切换,一帧扫描结束,需要扫描新的一帧时,需要先发送列同步信号;
时钟信号: 每来一个时钟,扫描的点移位一;
上图中LD驱动器可以比喻成电子枪,LCD控制器就是控制这个电子枪的,它从显示缓存中拿像素数据传给电子枪并发送命令让电子枪发射像素颜色, 上图中,成像过程
- LCD控制器发出VSYNC信号,告诉电子枪,要发出一张新帧了,然后电子枪把枪头调转到LCD屏幕的左上角准备开始发射像素
- 发出VSYNC信号的同时,发出HSYNC信号(告诉电子枪新行开始, 从左向右动发射子弹吧)但是电子枪毕竟反应比较慢,过了少许开始发射子弹
对于上面两个过程,由于电子枪接受了VSYNC信号,调转枪头后,需要反应一段时间才能正常开始工作, 所以就白白扫射了几行的无效数据,相当于经过了几个HSYNC信号周期的时间, 一个HSYNC周期就是电子枪扫射一行的时间(从HSYNC信号开始扫射第一行直到到一行结束扫射结束所用时间),就出现了上方无效区 - 当第一行结束时,LCD控制器又发出HSYNC信号, 电子枪枪头扭转到下一行新行开始发射数据, 但是枪头扭转的比较慢, 所以出现了左右的无效区(即第一行结束后,电子枪由于硬件原因要反应一段时间, 所以在右边出现了无效数据区, 调转枪头后, 也得反应一段时间开始发射子弹,所以出现了左边的无效区),有人会问电子枪如何知道第一行何时结束(其实是我们通过寄存器告诉LCD控制器第一行有多少个数据的,我们的屏幕分辨率是480*272, 即这个信息会设置到寄存器里), 当一行结束时,LCD控制器就不会再发有效像素数据,并且等待电子枪游离一段时间,之后再发下一行的HSYNC信号.
- loop第三个过程
- 当扫描到最后一行结束时(一帧即将结束),LCD控制器就不会再发有效像素数据,并且等待电子枪游离一段时间,所以会继续往下扫描,出现了下方的无效区, 之后再发下一行的VSYNC信号, 之后回到过程1开始重复。
在工作中的显示器上,可以在四周看见黑色的边框。上方的黑框是因为当发出VSYNC信号时,需要经过若干行之后第一行数据才有效;下方的黑框是因为显示完所有行的数据时,显示器还没有扫描到最下边(VSYNC信号还没有发出),这时数据是无效的;左边的黑框是因为当发出HSYNC信号时,需要经过若干像素之后第一列数据才有效;右边的黑框是因为显示完一行数据时,显示器还没扫描到最右边(HSYNC信号还没有发出),这时数据已经无效。显示器只会依据VSYNC、HSYNC信号来取得、显示数据,并不理会该数据是否有效,何时发出有效的数据由显卡或LCD控制器决定。
VSYNC信号出现的频率表示一秒钟内能显示多少帧图像,称为垂直频率或场频率,这就是我们常说的“显示器频率”;
HSYNC信号出现的频率称为水平频率,表示一秒钟能显示多少个像素的数据。
显示器上,一帧数据的存放位置与VSYNC、HSYNC信号的关系如下图所示:
image-20210718155614982
有效数据的行数、列数,即分辨率,它与VSYNC、HSYNC信号之间的距离等,都是可以设置的,这由LCD控制器来完成。
- 数据组织方式
一幅图像被称为一帧(frame),每帧由多行组成,每行由多个像素组成,每个像素的颜色使用若干位的数据来表示。对于单色显示器,每个像素使用1位来表示,称为1BPP;对于256色显示器,每个像素使用8位来表示,被称为8BPP。
显示器上每个像素的颜色由3部分组成:红(Red)、绿(Green)、蓝(Blue)。它们被称为三基色,这三者的混合几乎可以表示人眼所能识别的所有颜色。比如可以根据颜色的浓烈程度将三基色都分为256个级别,则可以使用255级的红色、255级的绿色、255级的蓝色组合成白色,可以使用0级红色、0级的绿色、0级的蓝色组合成黑色。
LCD控制器可以支持单色(1BPP)、4级灰度(2BPP)、16级灰度(4BPP)、256色(8BPP)的调色板显示模式,支持64K(16BPP)和16M(24BPP)非调色板显示模式。下面介绍64K(16BPP)色显示模式下,图像数据的存储格式。
64K(16BPP)色的显示模式就是使用16位的数据来表示一个像素的颜色。这16位数据的格式又分为两种:5:6:5、5:5:5:1,前者使用高5位来表示红色,中间的6位来表示绿色,低5位来表示蓝色;后者的高15从高到低分成3个5位来表示红、绿、蓝色,最低位来表示透明度。5:5:5:1的格式也被称为RGBA格式(A:Alpha,表示透明度)。
一个4字节可以表示两个16BPP的像素,使用高2字节还是低2字节来表示第一个像素,这也是可以选择的。显示模式为16BPP时,内存数据与像素位置的关系如下:
- 当BSWP=0、HWSWP=0时,内存中像素的排列格式:
地址 | D[31:16] | D[15:0] |
---|---|---|
00H | P1 | P2 |
04H | P3 | P4 |
08H | P5 | P6 |
- 当BSWP=0、HWSWP=1时,内存中像素的排列格式:
地址 | D[31:16] | D[15:0] |
---|---|---|
00H | P2 | P1 |
04H | P4 | P3 |
08H | P6 | P5 |
- 像素在LCD屏上的排列
- 像素色值与VD[23:0]引脚的对应关系
VD | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
RED | 4 | 3 | 2 | 1 | 0 | NC | NC | NC | NC | NC | NC | NC | NC | |||||||||||
GREEN | 5 | 4 | 3 | 2 | 1 | 0 | ||||||||||||||||||
BLUE | 4 | 3 | 2 | 1 | 0 |
输出方式
- 1、通过frame buffer显示(最典型的方式)
上图中可以看到,我们需要在内存里面申请一块内存(此内存被称为frame buffer),之后各种配置LCD控制器,配置显示模式为16PP, 显示模式为5:6:5, 把frame buffer的首地址告诉控制器, 那么控制器就会从frame buffer获取像素值,根据像素的不同值将不同颜色打向LCD屏幕(LCD控制器类似于电子枪, 向玻璃板发不同的光,LCD控制器内部有个DMA通道)对于frameBuffer来讲,每个值对应LCD屏幕的一个像素, 如上图,LCD屏分辨率为(480*272),我们可以定义一个数组a[272][480]大小的数组,并把数组首地址告诉LCD控制器, 那么数组每一项对应LCD屏的一个像素, 比如a[0][0]赋值为0xFFE0,对应LCD屏幕的第一个像素显示为黄色。
- 2、通过临时调色板显示
image-20210719221941279
这里要解释几点:
- 什么叫临时调色板
我们根据2440 用户手册的一句话:this register value will be video data at next frame.(在下一帧显示寄存器的值, tips:一帧就一个图像)
可知:临时调色板是一个寄存器,我们向此寄存器写入颜色,那么LCD屏幕下一次显示图像就会是此寄存器中记录的颜色, 即起到了刷屏的作用(整个屏幕都是一个颜色) - 什么时候起作用以及用途
当使能此寄存器时, 临时调色板起作用, 这时之前配置的功能(如:通过frame buffer显示)就会无效, 因为使能,LCD屏幕会被迅速刷屏,达到了快速刷屏的目的(不需要通过SDRAM中向frameBuffer中所有元素赋值同一个值来实现刷屏 tips:使用SDRAM,本来就慢),如果要恢复之前的配置功能, 即disable临时调色板功能即可
image-20210719222211405
- 3、通过调色板显示
image-20210719222237700
上图中, 调色板在控制器内部(注意区别临时调色板)是一块儿内存,首地址为0x4D00400, 一共有256个2字节大小(每两个字节表示一个颜色), 上图中,通过配置寄存器告诉LCD控制器调色板的显示格式为RGB565,之后需要手动将此调色板赋值,比如图00H的位置赋值为”黄色”,之后对于framebuffer来讲,其中的每一项代表一个调色板中的索引, 比如frameBuffer的第一项的值为0,则硬件就会自动找调色板中的第一项值, 即将0xFFE0输出, LCD第一个像素点显示黄色
还有一个问题,如何使能调色板功能呢? 我们上面介绍”通过frame buffer显示”中提到,配置寄存器显示模式为16BPP,显示方式是5:6:5,那么控制器就会认为frame buffer中的每一个元素代表的就是颜色的值,并且显示方式是5:6:5, 但是如果我们配置显示模式为8BPP,显示方式是5:6:5, LCD控制器就自动认为用的调色板模式,且调色板中颜色的显示方式为(5:6:5)(这里的8Bpp,代表frame buffer中的每个元素都是8位2进制表示,每个元素的值是调色板中的索引值),那么调色板的应用场合是什么样呢?我们引用网上的一个说明(稍微修改下):
在笔者开发LCD,其显示分辨率设置为640×480。如果色深设置为16 bpp,在系统使用时,画面将会出现明显的抖动、不连贯,这是由于芯片的运算负荷过重造成的。如果按本文中提到的调色板方法,即采用8位色深显示,颜色的选取可以满足需要,画面的显示将明显稳定。这说明,在显示分辨率较高,色彩种类要求比较简单的嵌入式应用中,调色板技术是一个非常值得重视的选择
- TFT-LCD的时序
每个VSYNC信号表示一帧数据的开始;每个HSYNC信号表示一行数据的开始,无论这些数据是否有效;每个VCLK信号表示正在传输一个像素的数据,无论它是否有效。数据是否有效只是对CPU的LCD控制器来说的,LCD根据VSYNC、HSYNC、VCLK不停的读取总线数据并显示。
image-20210719223036418
上图中的时序图,分为两个部分,上面部分是一帧的时序图,下面部分是一行的时序图我们分析下时序图中每个参数的意义(上图中的①->⑩) :
对应于上述的过程1,2, VSYNC信号(代表一帧的开始)需要持续一段时间②(VSPW+1), 电子枪认为收到了VSYNC信号(即白扫射了VSPW+1行,也可以说白扫射了(VSPW+1)个HSYNC周期时间),收到信号后,还要继续持续时间③(VBPD+1), LCD控制器才开始发送有效数据, 从而电子枪发射有效像素, 即(② + ③)为LCD屏幕上边的无效区, 对于①参数, 这是手册上的数据, 即告我们默认LCD控制器发送HSYNC信号为高电平,但实际LCD接受HSYNC硬件上有可能设计成低电平有效, 所以可以对寄存器进行修改, 让LCD控制器发出HSYNC控制信号为低电平
tips: VSPW VBPD参数会根据datasheet来具体设置(下文会提到), 设置这些参数的目的是告诉LCD控制器电子枪的反应时间以便发送帧数据(比如电子枪, 发送HSYNC后, 得知道电子枪的反应时间后才开始传送有效数据)
④为, 即有效数据为(LINEVAL+1)行,我们分辨率为480*272,所以LINEVAL为271 *
⑤VFPD+1参数对应于过程5, 当扫描到最后一行结束时(即一帧结束了),LCD控制器不会再发送有效像素数据, 此时电子枪会收游离一段时间(会继续往下白扫好几行(VFPD+1行)无效数据), 这个时间需要告诉LCD控制器,以便控制器知道等待多长时间在发送VSYNC信号,从而进行下一帧的开始
对于⑥、⑦、⑧、⑩三个参数,对应于上述过程3, 接受到HSYNC信号(表示一行的开始)后,此信号必须持续一段时间⑦(HSPW+1个VCLK周期)后, 电子枪才认为信号有效,接受到HSYNC信号后,电子枪还要反应一段时间⑧(白白扫射HBPD+1个VCLK周期后,也可以说发射HBPD+1个无效像素点)后, LCD控制器才开始传送有效数据给电子枪, 当一行扫描结束后,即LCD控制器不发射有效数据了,此时电子枪要游离一段时间⑩(HFPB+1), 这段时间需要告诉LCD控制器,以便让LCD控制器等待此段时间后在发送HSYNC信号从而进行下一行的扫描, 对于⑨参数来说, 分辨率为480*272,所以HOZVAL = 479, 即一行有480个有效数据, 注意有效数据的开始时机, 即需要经历(⑦、⑧)时间后,LCD控制器才开始发送有效数据 。
参数计算
根据LCDdatasheet确认上述参数的值, 下图为AT043TN24数据手册的时序图, 我们很容易对应上面2440手册中LCD的时序图中的参数
image-20210719223335769
上图中已经标注对应关系,就不细说了,强调一点, VSYNC与HSYNC信号都是低电平有效,但是2440手册中LCD时序是高电平有效,所以在配置寄存器时需要注意,要将这两个VSYNC,HSYNC信号设置成低电平有效(极性反转: 默认为高电平,反转后为低电平)
image-20210719223531086
我们可以看到,上图中左边是具体的参数值,Min(最小值), Typ.(典型值), Max(最大值),我们举个例子,在右图中,我们知道关系 VSPW+1 = tvp, 我们在左图中发现tvp的典型值为10, 单位是H(Hsync), 所以VSPW+1 = 10==> VSPW=9, 其余参数的取值都能通过上述方法确定, 还有个问题,VSPW, VSPD,VFBD的时间都依赖于HSYNC周期时间,那么HSYNC周期时间如何确认呢? 查看了下寄存器的设置中好像也没找到相关设置,最后在2440手册中找到这句话
image-20210719223757113
其实意思就是说 LCD控制器会根据电子枪发射像素点的个数来确认HSYNC时间的,比如我们LCD屏幕分辨率是480*272, 当发出VSYNC信号后,要经过VSPW+1反应时间,即VSPW+1个HSYNC周期,我们假设VSPW+1的值为10,那么就是10个HSYNC周期,也就是电子枪扫描了10 x 480个像素点后,LCD控制器就认为经历了10个HSYNC周期时间 。
1、VCLK(Hz) = HCLK/[(CLKVAL+1)*2]
2、VSYNC =1/[ {(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)} x {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)} x {2x (CLKVAL+1) / (HCLK )} ]
3、HSYNC = 1/[{(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)} x {2x (CLKVAL+1) / (HCLK )}]
image-20210718175250797
将VSYNC、HSYNC、VCLK等信号的时间参数设置好之后,并将帧内存的地址告诉LCD控制器,它即可自动地发出DMA传输从帧内存中得到图像数据,最终在上述信号的控制下出现在数据总线VD[23:0]上。用户只需要把要显示的图像数据写入帧内存中。
2、LCD控制器REGBANK寄存器组介绍
LCD控制器中REGBANK的17个寄存器可以分为6种,如下表所示:
对于TFT-LCD,一般情况下只需要设置前两种寄存器,即LCDCON和LCDSADDR。
名称 | 说明 |
---|---|
LCDCON1~LCDCON5 | 用于选择LCD类型,设置各类控制信号的时间特性等 |
LCDSADDR1~LCDSADDR5 | 用于设置帧内存的地址 |
TPAL | 临时调色板寄存器,可以快速的输出一帧单色的图像 |
LCDINTPND | 用于LCD的中断,在一般应用中无需中断 |
LCDSRCPND | 用于LCD的中断,在一般应用中无需中断 |
LCDINTMSK | 用于LCD的中断,在一般应用中无需中断 |
REDLUT | 专用于STN-LCD |
GREENLUT | 专用于STN-LCD |
BLUELUT | 专用于STN-LCD |
DITHMODE | 专用于STN-LCD |
TCONSEL | 专用于SEC TFT-LCD |
2.1、LCD控制寄存器LCDCON1
主要用于选择LCD类型、设置像素时钟、使能LCD信号的输出等,格式如下表所示:
功能 | 位 | 说明 |
---|---|---|
LINECNT | [27:18] | 只读,每输出一个有效行其值减一,从LINEVAL减到0; |
CLKVAL | [17:8] | 用于设置VCLK(像素时钟); |
MMODE | [7] | 设置VM信号的反转效率,专用于STN-LCD; |
PNRMODE | [6:5] | 设置LCD类型,对于TFT-LCD设置0b11; |
BPPMODE | [4:1] | 设置BPP,对于TFT-LCD:0b1100 = 16BPP; |
ENVID | [0] | LCD信号输出使能位,0:禁止,1:使能; |
2.2、LCD控制寄存器LCDCON2
功能 | 位 | 说明 |
---|---|---|
VBPD | [31:24] | VSYNC信号脉冲之后,还要经过(VBPD+1)个HSYNC信号周期,有效的行数据才出现; |
LINEVAL | [23:14] | LCD的垂直宽度,(LINEVAL+1)行; |
VFPD | [13:6] | 一帧中的有效数据完结后,到下一个VSYNC信号有效前的无效行数目:VFPD+1行; |
VSPW | [5:0] | 表示VSYNC信号的脉冲宽度位(VSPW+1)个HSYNC信号周期,即(VSPW+1)行,这个(VSPW+1)行的数据是无效的; |
2.3、LCD控制寄存器LCDCON3
功能 | 位 | 说明 |
---|---|---|
HBPD | [25:19] | HSYNC信号脉冲之后,还要经过(HBPD+1)个VCLK信号周期,有效的像素数据才出现; |
HOZVAL | [18:8] | LCD的水平宽度,(HOZVAL+1)类(像素); |
HFPD | [7:0] | 一行中的有效数据完结后,到下一个HSYNC信号有效前的无效像素个数,HFPD+1个像素; |
2.4、LCD控制寄存器LCDCON4
对于TFT-LCD,这个寄存器只用来设置HSYNC信号的脉冲宽度,位[7:0]的数值称为HSPW,表示脉冲宽度位(HSPW+1)个VCLK周期。
2.5、LCD控制寄存器LCDCON5
用于设置各个控制信号的极性,并可从中读到一些状态信息,格式如下表所示:
功能 | 位 | 说明 |
---|---|---|
VSTATUS | [16:15] | 只读,垂直状态;00:正处于VSYNC信号脉冲期间;01:正处于VSYNC信号结束到行有效之间;10:正处于有效行期间;11:正处于行有效结束到下一个VSYNC信号之间; |
HSTATUS | [14:13] | 只读,水平状态;00:正处于HSYNC信号脉冲期间;01:正处于HSYNC信号结束到像素有效之间;01:正处于像素有效期间;11:正处于像素有效结束到下一个HSYNC信号之间; |
BPP24BL | [12] | 设置TFT-LCD的显示模式为24BPP时,一个4字节中的哪3个字节有效,0:LSB有效,1:MSB有效(高地址的3个字节); |
FRM565 | [11] | 设置TFT-LCD的显示模式为16BPP时,使用的数据格式,0表示5:5:5:1格式,1表示5:6:5格式; |
INVVCLK | [10] | 设置VCLK信号有效沿极性:0表示在VCLK的下降沿读取数据;1表示在VCLK的上升沿读取数据; |
INVVLINE | [9] | 设置VINE/HSYNC脉冲的极性;0表示正常极性,1表示反转的极性; |
INVVFRAME | [8] | 设置VFRAME/VSYNC脉冲的极性;0表示正常极性,1表示反转的极性; |
INVVD | [7] | 设置VD数据线表示数据的极性;0表示正常极性,1表示反转的极性; |
INVVDEN | [6] | 设置VDEN信号的极性;0表示正常进行,1表示反转的极性; |
INVPWREN | [5] | 设置PWREN信号的极性;0表示正常进行,1表示反转的极性; |
INVLEND | [4] | 设置LEND信号的极性;0表示正常进行,1表示反转的极性; |
PWREN | [3] | LCD_PWREN信号输出使能;0表示禁止,1表示使能; |
ENLEND | [2] | LEND信号输出使能;0表示禁止,1表示使能; |
BSWP | [1] | 字节交换使能;0表示禁止,1表示使能; |
HWSWP | [0] | 半字(2字节)交换使能,0表示禁止,1表示使能; |
2.6、帧内存地址寄存器LCDSDRR1~LCDSDRR3
帧内存可以很大,而真正要显示的区域被称为视口(view point),它处于帧内存之内,这个3个寄存器用于确定帧内存的起始地址,定位视口在帧内存中的位置。
功能 | 位 | 说明 |
---|---|---|
LCDBANK | [29:21] | 用于保存帧内存起始地址A[30:22],帧内存起始地址必须为4MB对齐; |
LCDBASEU | [20:0] | 对于TFT-LCD,用于保存视口所对应的内存起始地址A[21:1],这块内存也被称为LCD的帧缓冲区(frame buffer); |
功能 | 位 | 说明 |
---|---|---|
LCDBASEL | [20:0] | 对于TFT-LCD,用来保存LCD的帧缓冲区结束地址A[21:1],其值可如下计算:LCDBASEL=LCDBASEU+(PAGEWIDTH+OFFSIZE)*(LINEVAL+1) |
注意:可以修改LCDBASEU、LCDBASEL的值来实现图像的移动,不过不能在一帧图像的结束阶段进行修改;
功能 | 位 | 说明 |
---|---|---|
OFFSIZE | [21:11] | 表示上一行最后一个数据与下一行第一个数据之间地址差值的半字节,即以半字位单位的地址差;0表示两行数据是紧接着的,1表示它们之间相差2个字节,以此类推; |
PAGEWIDTH | [10:0] | 视口的宽度,以半字位为单位; |
3、调色板
如果要输出一帧单色的图像,可以在TPAL寄存器中设定这个颜色值,然后使能TPAL寄存器,这种方法可以避免修改整个调色板或帧缓冲区。
功能 | 位 | 说明 |
---|---|---|
TPALEN | [24] | 调色板寄存器使能位,0禁止,1使能; |
TPALVAL | [23:0] | 颜色值;TPALVAL[23:16]:红色TPALVAL[15:8]:绿色TPALVAL[7:0]:蓝色 |
注意:临时调色板寄存器TPAL可以用在任何显示模式下,并非只能用在8BPP模式下。
4、编写驱动
4.1 lcd.c
可参考内核自带的相关lcd驱动(drivers/video/),添加头文件:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>#include <asm/mach/map.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>static struct fb_info *s3c_lcd;
static int lcd_init(void)
{/* 1. 分配一个fb_info */s3c_lcd = framebuffer_alloc(0, NULL);/* 2. 设置 *//* 2.1 设置固定参数 *//* 2.2 设置可变参数 *//* 2.3 设置操作函数 *//* 2.4 设置其它内容 *//* 3. 硬件相关的操作 *//* 3.1 配置GPIO用于LCD *//* 3.2 根据LCD手册设置LCD控制器,例如VCLK频率等 *//* 3.3 分配显存(frambuffer),并将地址告诉LCD控制器 *//* 4. 注册 */register_framebuffer(s3c_lcd);return 0;
}static void lcd_exit(void)
{}module_init(lcd_init);
module_exit(lcd_exit);MODULE_LICENSE("GPL");
入口函数lcd_init()
- 1、分配一个fb_info
s3c_lcd = framebuffer_alloc(0, NULL);
- 1
- 2、 设置
- 设置固定参数——fb_fix_screeninfo结构体
image-20210718213023752
/* 2.1 设置固定的参数 */ strcpy(s3c_lcd->fix.id, "mylcd"); s3c_lcd->fix.smem_len = 480*272*16/8; //屏幕分辨率480X272,16bpp/pix s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS; //屏幕类型 s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* 真彩TFT */ s3c_lcd->fix.line_length = 480*2; //一行需要的存储长度=480像素X2字节
- 1
- 2
- 3
- 4
- 5
- 6
- 设置可变参数
image-20210718222829528
/* 2.2 设置可变参数 */
s3c_lcd->var.xres = 480; //X方向的分辨率
s3c_lcd->var.yres = 272; //y方向的分辨率
s3c_lcd->var.xres_virtual = 480; //X方向虚拟的分辨率
s3c_lcd->var.yres_virtual = 272; //y方向的虚拟分辨率
s3c_lcd->var.bits_per_pixel = 16; //每个像素用多少位表示/* RGB:565 */
s3c_lcd->var.red.offset = 11; //从第11位开始
s3c_lcd->var.red.length = 5; //占5个位
s3c_lcd->var.red.msb_right = 0; //数据在offset的右边吗?默认为0,表示在左边(高位方向)。可以不需设置s3c_lcd->var.green.offset = 5; //从第5位开始
s3c_lcd->var.green.length = 6;s3c_lcd->var.blue.offset = 0; //从第0位开始
s3c_lcd->var.blue.length = 5;s3c_lcd->var.activate = FB_ACTIVATE_NOW; //不明白,暂用默认值
- 19
- 设置操作函数——fbops
s3c_lcd->fbops = &s3c_lcdfb_ops;
- 1
- 在函数外定义fb_ops结构体:
static struct fb_ops s3c_lcdfb_ops = {.owner = THIS_MODULE,.fb_setcolreg = s3c_lcdfb_setcolreg, //调色板设置函数.fb_fillrect = cfb_fillrect,.fb_copyarea = cfb_copyarea,.fb_imageblit = cfb_imageblit,
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 其它的设置
s3c_lcd->pseudo_palette = pseudo_palette; //调色板数组地址
s3c_lcd->screen_base = ; /* 显存的虚拟地址 */
s3c_lcd->screen_size = 480*272*16/8;
- 1
- 2
- 3
- 3、硬件相关的操作
- 配置GPIO用于LCD
image-20210718230408435
image-20210718231335391
通过原理图可知,所有使用到的引脚均要配置。然后查看原理图,找到各引脚对应的IO端口:
image-20210718230823866
image-20210718231849600
首先在函数外定义用到的IO口的寄存器指针变量:
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
- 1
- 2
- 3
- 4
- 5
然后在函数体内映射地址:
/*配置GPIO用于LCD*/
//即使你写了仅映射4个字节,系统也还是会映射至少1页(4KB)
gpbcon = ioremap(0x56000010, 8);
gpbdat = gpbcon+1;
gpccon = ioremap(0x56000020, 4);
gpdcon = ioremap(0x56000030, 4);
gpgcon = ioremap(0x56000060, 4);*gpccon = 0xaaaaaaaa; /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
*gpdcon = 0xaaaaaaaa; /* GPIO管脚用于VD[23:8] *//* GPB0设置为输出引脚 */
*gpbcon &= ~(3);
*gpbcon |= 1;
*gpbdat &= ~1; /* 先输出低电平,使背光电源关闭 */*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN(LCD本身电源) */
根据LCD手册设置LCD控制器,例如VCLK频率等
首先,查看S3C2440芯片手册,并设置LCD controller章节中的控制寄存器。为了方便引用,先定义一个全局结构体(lcd_regs),内容就是各寄存器的地址。即:
- 1
struct lcd_regs {unsigned long lcdcon1;
unsigned long lcdcon2;unsigned long lcdcon3;
unsigned long lcdcon4;unsigned long lcdcon5;unsigned long lcdsaddr1;unsigned long lcdsaddr2;unsigned long lcdsaddr3;unsigned long redlut;unsigned long greenlut;unsigned long bluelut;unsigned long reserved[9];unsigned long dithmode;unsigned long tpal;unsigned long lcdintpnd;unsigned long lcdsrcpnd;unsigned long lcdintmsk;unsigned long lpcsel;
};
然后,再定义一个指向该类型结构体的指针`lcd_regs`:
static volatile struct lcd_regs* lcd_regs; //所有指向寄存器的地址必须是volatile修饰的
最后,到`lcd.c`函数中进行地址映射,而后根据LCD数据手册设置LCD控制寄存器:
为了便于大家查看,将这3幅图重新放在这边:
LCD时间参数
LCD时间序列
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));//1.设置LCDCON1寄存器/* CLKVAL => bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2],* VCLK取值查看LCD手册3.5.1节的Clock cycle* 9MHz(Typ) = 100MHz / [(CLKVAL+1) x 2]* CLKVAL = 4(在此取整数4)* MMODE => bit[7]:取默认值* PRNMODE => bit[6:5]: 0b11 (TFT LCD panel)* BPPmode => bit[4:1]: 0b1100(16 bpp for TFT)* ENVID => bit[0] : 0b0 (先暂时禁止,需要时打开.)*/lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);//2.设置LCDCON2-4寄存器
#if 1/* 垂直方向的时间参数* VBPD => bit[31:24]: 1, VSYNC之后再过多长时间才能发出第1行数据* LINEVAL => bit[23:14]: 271, 所以LINEVAL=272-1=271* VFPD => bit[13:6] : 1, 发出最后一行数据之后,再过多长时间才发出VSYNC,所以VFPD=2-1=1* VSPW => bit[5:0] : 9, VSYNC信号的脉冲宽度,VSPW=10-1=9*/lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6) | (9);/* 水平方向的时间参数* HBPD => bit[25:19]: 1, VSYNC之后再过多长时间才能发出第1行数,HBPD=2-1* HOZVAL => bit[18:8]: 479, HOZVAL=480-1=479* HFPD => bit[7:0] : 1 , 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC,HFPD=2-1=1*/lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);/* 水平方向的同步信号* HSPW => bit[7:0]: 40, HSYNC信号的脉冲宽度, HSPW=41-1=40*/ lcd_regs->lcdcon4 = 40;#elselcd_regs->lcdcon2 = S3C2410_LCDCON2_VBPD(5) | \S3C2410_LCDCON2_LINEVAL(319) | \S3C2410_LCDCON2_VFPD(3) | \S3C2410_LCDCON2_VSPW(1);lcd_regs->lcdcon3 = S3C2410_LCDCON3_HBPD(10) | \S3C2410_LCDCON3_HOZVAL(239) | \S3C2410_LCDCON3_HFPD(1);lcd_regs->lcdcon4 = S3C2410_LCDCON4_MVAL(13) | \S3C2410_LCDCON4_HSPW(0);
#endif/* 信号的极性 * bit[11]: 1=565 format* bit[10]: 0 = 根据LCD手册,其在下降沿取数据* bit[9] : 1 = HSYNC信号要反转(对比S3C2440手册与LCD手册的时序图)* bit[8] : 1 = VSYNC信号要反转,* bit[7] : 0 = INVVD不用反转(数据引脚低电平表示数据1)* bit[6] : 0 = VDEN不用反转(对比S3C2440手册与LCD手册的时序图)* bit[5] : 0 = INVPWREN不用反转(电源使能开关高电平有效)* bit[3] : 0 = PWREN信号输出使能(暂时先不使能它,到后面设置完后再打开)* bit[1:0] : 01,内存数据和像素点对应关系,00表示D[31:0]的高8位对应Pix1,* 低8位对应Pix2,01表示D[31:0]的高8位对应Pix2,低8位对应Pix1* S3C2440手册第413页*/lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
- 62
- 63
- 分配显存(frambuffer),并将地址告诉LCD控制器 ,最后启动LCD
/* screen_base:显存的虚拟地址;smem_start:显存的物理地址。*/
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);/* Frame Buffer 的起始地址* LCDBANK => bit[29:21]: ,对应视频缓存区开始地址的A[30:22]位,(4MB地址对齐)* LCDBASEU => bit[20:0]: ,对应视频缓存区开始地址的A[21:1]位,*/lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);/* Frame Buffer 的结束地址* LCDBASEL => bit[20:0]: ,对应视频缓存区结束地址的A[21:1]位,*/lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;/* Frame Buffer 的有效显示区的宽度(半字,即2字节为单位)* OFFSIZE => bit[21:11]: ,不懂,取默认值* PAGEWIDTH => bit[10:0]: ,一行的长度(单位: 2字节) */lcd_regs->lcdsaddr3 = (480*16/16); /* 启动LCD */lcd_regs->lcdcon1 |= (1<<0); /* 使能ENVID信号,表示传输数据 */lcd_regs->lcdcon5 |= (1<<3); /* 使能PWREN信号 */*gpbdat |= 1; /* 输出高电平, 使能背光 */
- 4、注册
/* 4. 注册 */
register_framebuffer(s3c_lcd);
- 1
- 2
- 出口函数lcd_exit()
static void lcd_exit(void)
{unregister_framebuffer(s3c_lcd); //注销fblcd_regs->lcdcon1 &= ~(1<<0); /* 停止向LCD发送数据 */lcd_regs->lcdcon5 &= ~(1<<3); /* 关闭PWREN信号 */*gpbdat &= ~1; /* 关闭背光 *///dma_free_writecombine(设备,内存长度,虚拟起始地址,起始物理地址)dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);iounmap(lcd_regs);iounmap(gpbcon);iounmap(gpccon);iounmap(gpdcon);iounmap(gpgcon);framebuffer_release(s3c_lcd); //释放fb
}
- 调色板相关函数设置
- 为了兼容性,我们要先定义一个伪调色板数组,
static u32 pseudo_palette[16];
- 1
- 设置颜色填充函数
/* 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 s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info)
{unsigned int val;if (regno > 16)return 1;/* 用red,green,blue三原色构造出val */val = chan_to_field(red, &info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue, &info->var.blue);//((u32 *)(info->pseudo_palette))[regno] = val;pseudo_palette[regno] = val;return 0;
}
- lcd.c文件的整体代码结构
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>#include <asm/mach/map.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info);struct lcd_regs {unsigned long lcdcon1;unsigned long lcdcon2;unsigned long lcdcon3;unsigned long lcdcon4;unsigned long lcdcon5;unsigned long lcdsaddr1;unsigned long lcdsaddr2;unsigned long lcdsaddr3;unsigned long redlut;unsigned long greenlut;unsigned long bluelut;unsigned long reserved[9];unsigned long dithmode;unsigned long tpal;unsigned long lcdintpnd;unsigned long lcdsrcpnd;unsigned long lcdintmsk;unsigned long lpcsel;
};static struct fb_ops s3c_lcdfb_ops = {.owner = THIS_MODULE,.fb_setcolreg = s3c_lcdfb_setcolreg,.fb_fillrect = cfb_fillrect,.fb_copyarea = cfb_copyarea,.fb_imageblit = cfb_imageblit,
};static struct fb_info *s3c_lcd;
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile struct lcd_regs* lcd_regs;
static u32 pseudo_palette[16];/* 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 s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info)
{unsigned int val;if (regno > 16)return 1;/* 用red,green,blue三原色构造出val */val = chan_to_field(red, &info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue, &info->var.blue);//((u32 *)(info->pseudo_palette))[regno] = val;pseudo_palette[regno] = val;return 0;
}static int lcd_init(void)
{/* 1. 分配一个fb_info */s3c_lcd = framebuffer_alloc(0, NULL);/* 2. 设置 *//* 2.1 设置固定的参数 */strcpy(s3c_lcd->fix.id, "mylcd");s3c_lcd->fix.smem_len = 480*272*16/8;s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* TFT */s3c_lcd->fix.line_length = 480*2;/* 2.2 设置可变的参数 */s3c_lcd->var.xres = 480;s3c_lcd->var.yres = 272;s3c_lcd->var.xres_virtual = 480;s3c_lcd->var.yres_virtual = 272;s3c_lcd->var.bits_per_pixel = 16;/* RGB:565 */s3c_lcd->var.red.offset = 11;s3c_lcd->var.red.length = 5;s3c_lcd->var.green.offset = 5;s3c_lcd->var.green.length = 6;s3c_lcd->var.blue.offset = 0;s3c_lcd->var.blue.length = 5;s3c_lcd->var.activate = FB_ACTIVATE_NOW;/* 2.3 设置操作函数 */s3c_lcd->fbops = &s3c_lcdfb_ops;/* 2.4 其他的设置 */s3c_lcd->pseudo_palette = pseudo_palette;//s3c_lcd->screen_base = ; /* 显存的虚拟地址 */ s3c_lcd->screen_size = 480*272*16/8;/* 3. 硬件相关的操作 *//* 3.1 配置GPIO用于LCD */gpbcon = ioremap(0x56000010, 8);gpbdat = gpbcon+1;gpccon = ioremap(0x56000020, 4);gpdcon = ioremap(0x56000030, 4);gpgcon = ioremap(0x56000060, 4);*gpccon = 0xaaaaaaaa; /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */*gpdcon = 0xaaaaaaaa; /* GPIO管脚用于VD[23:8] */*gpbcon &= ~(3); /* GPB0设置为输出引脚 */*gpbcon |= 1;*gpbdat &= ~1; /* 输出低电平 */*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN *//* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14* 10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]* CLKVAL = 4* bit[6:5]: 0b11, TFT LCD* bit[4:1]: 0b1100, 16 bpp for TFT* bit[0] : 0 = Disable the video output and the LCD control signal.*/lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);#if 1/* 垂直方向的时间参数* bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据* LCD手册 T0-T2-T1=4* VBPD=3* bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319* bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC* LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1* bit[5:0] : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0*/lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6) | (9);/* 水平方向的时间参数* bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据* LCD手册 T6-T7-T8=17* HBPD=16* bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239* bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC* LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10*/lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);/* 水平方向的同步信号* bit[7:0] : HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4*/ lcd_regs->lcdcon4 = 40;#else
lcd_regs->lcdcon2 = S3C2410_LCDCON2_VBPD(5) | \S3C2410_LCDCON2_LINEVAL(319) | \S3C2410_LCDCON2_VFPD(3) | \S3C2410_LCDCON2_VSPW(1);lcd_regs->lcdcon3 = S3C2410_LCDCON3_HBPD(10) | \S3C2410_LCDCON3_HOZVAL(239) | \S3C2410_LCDCON3_HFPD(1);lcd_regs->lcdcon4 = S3C2410_LCDCON4_MVAL(13) | \S3C2410_LCDCON4_HSPW(0);#endif/* 信号的极性 * bit[11]: 1=565 format* bit[10]: 0 = The video data is fetched at VCLK falling edge* bit[9] : 1 = HSYNC信号要反转,即低电平有效 * bit[8] : 1 = VSYNC信号要反转,即低电平有效 * bit[6] : 0 = VDEN不用反转* bit[3] : 0 = PWREN输出0* bit[1] : 0 = BSWP* bit[0] : 1 = HWSWP 2440手册P413*/lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;lcd_regs->lcdsaddr3 = (480*16/16); /* 一行的长度(单位: 2字节) */ //s3c_lcd->fix.smem_start = xxx; /* 显存的物理地址 *//* 启动LCD */lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */*gpbdat |= 1; /* 输出高电平, 使能背光 */ /* 4. 注册 */register_framebuffer(s3c_lcd);return 0;
}static void lcd_exit(void)
{unregister_framebuffer(s3c_lcd);lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */lcd_regs->lcdcon5 &= ~(1<<3); /* 关闭PWREN信号 */*gpbdat &= ~1; /* 关闭背光 */dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);iounmap(lcd_regs);iounmap(gpbcon);iounmap(gpccon);iounmap(gpdcon);iounmap(gpgcon);framebuffer_release(s3c_lcd);
}module_init(lcd_init);
module_exit(lcd_exit);MODULE_LICENSE("GPL");
5、编译和测试
cd ~/linux3.4.2/
make menuconfig
cd ~/linux3.4.2
make uImage
make modules
cd arch/arm/boot
mv uImage uImage_nolcd
cd ~/linux3.4.2
//复制新的内核到tftp共享文件夹
cp arch/arm/boot/uImage_nolcd /mnt/hghf/virtual_shared/tftp//复制包含cfb_fillrect、cfb_copyarea、cfb_imageblit这3个函数的驱动模块cfb*.ko到网络文件系统
cp drivers/video/cfb*.ko ~/nfs_root/first_fs
//启动开发板时,按任意键进入uboot菜单
# q //退出菜单,进入命令行//将新编译的内核复制到tftp共享文件夹中马克后执行下面语句
# tftp 30000000 uImage_nolcd ec//将内核镜像下载到内存30000000处//或者使用NFS服务下载到内存的30000000处
# nfs 30000000 192.168.1.101:/home/leon/nfs_root/first_fs/uImage# bootm 30000000 //启动//系统成功启动后,挂接网络文件系统
# mount -nfs -o nolock 192.168.1.101:/home/leon/nfs_root/first_fs /mnt
//安装复制过来的三个模块
# insmod cfbcopyarea.ko
# insmod cfbfillrect.ko
# insmod cfbimgblt.ko//安装LCD驱动
# insmod lcd.ko//检查是否安装成功
# ls /dev/fb*
/dev/fb0 //发现设备文件里出现了新安装的fb0,开发板屏幕也亮了起来
测试lcd驱动(3种方法)
- 随便向驱动文件写入数据,观察LCD屏幕是否有花屏
# cat anyfile > /dev/fb0
- 1
- 或者向屏幕输出字符串,观察LCD屏幕是否有字符串输出
# echo "hello darkbird!" > /dev/tty1
- 1
- 修改/etc/inittab文件,添加tty1后,重启新内核,重新安装各模块后再测试
tty1:输入时对应开发板键盘,输出时对应我们的LCD,其对应的驱动文件是之前写过的buttons.ko
tty1::askfirst:-/bin/sh
- 1
# mount -nfs -o nolock 192.168.1.101:/home/leon/nfs_root/first_fs /mnt # cd /mnt # insmod cfbcopyarea.ko # insmod cfbfillrect.ko # insmod cfbimgblt.ko # insmod lcd.ko # insmod buttons.ko而后按下开发板上的按键,LCD频幕上会显示进入命令行, 之后,按下ls+回车对应的3个按键,屏幕上显示`ls`命令执行结果。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
6、知识拓展——修改内核自带lcd驱动,使之支持4.3寸(480X272)屏
6.1 linux内核驱动框架分析及修改
fbmem.c
:实现帧缓冲的具体细节,只是一个抽象层,对上提供操作函数接口,对下提供硬件操作函数。
s3c2410fb.c
:用于初始化一个lcd硬件设备的,内含硬件操作的具体细节。
mach-smdk2440.c
:提供了LCD显示屏的配置信息,例如长,宽,像素位数,像素时钟频率,以及LCD显示屏的时序等。
6.2 修改mach-smdk2440.c代码(LCD驱动部分)
/* LCD driver info */
/************************************************************************/
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {.lcdcon5 = S3C2410_LCDCON5_FRM565 |S3C2410_LCDCON5_INVVLINE |S3C2410_LCDCON5_INVVFRAME |S3C2410_LCDCON5_PWREN |S3C2410_LCDCON5_HWSWP,.type = S3C2410_LCDCON1_TFT,.width = 480,.height = 272,.pixclock = 111000,.xres = 480,.yres = 272,.bpp = 16,.left_margin = 2,/*HBP=VBPD+1,行切换,从同步到绘图之间的延迟*/ .right_margin = 2,/*HFP=HFPD+1,行切换,从绘图到同步之间的延迟*/ .hsync_len = 41,/*HSPW+1,水平同步的长度*/ .upper_margin = 2, /*VBP=VBPD+1,帧切换,从同步到绘图之间的延迟*/ .lower_margin = 2, /*VFB=VFPD+1,帧切换,从绘图到同步之间的延迟*/ .vsync_len = 10,/*VSPW+1,垂直同步的长度*/
};static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {.displays = &smdk2440_lcd_cfg,.num_displays = 1,.default_display = 0,#if 1/* currently setup by downloader */.gpccon = 0xaaaaaaaa, /* 将GPC端口配置为LCD控制管脚 */.gpccon_mask = 0xffffffff,.gpcup = 0xffff, /* 禁止GPC端口内部上拉 */.gpcup_mask = 0x0000,.gpdcon = 0xaaaaaaaa, /* 将GPD端口配置为LCD控制管脚 */.gpdcon_mask = 0xffffffff,.gpdup = 0xffff, /* 禁止GPD端口内部上拉 */.gpdup_mask = 0x0000,
#endif//.lpcsel = ((0xCE6) & ~7) | 1<<4,//非LPC3600三星自家的显示屏
}====================================================================
static void __init smdk2440_machine_init(void)
{ s3c24xx_fb_set_platdata(&smdk2440_fb_info);s3c_i2c0_set_platdata(NULL);platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));smdk_machine_init();/* 添加以下两句代码 */writel((readl(S3C2410_GPBCON) & ~(3)) | 1, S3C2410_GPBCON); // 初始化背光控制引脚为输出writel((readl(S3C2410_GPBDAT) | 1), S3C2410_GPBDAT); // 打开背光
}
像素时钟pixclock的概念:
pixclock=1/dotclock 其中dotclock是视频硬件在显示器上绘制像素的速率 dotclock=(x向分辨率+左空边+右空边+HSYNC长度)* (y向分辨率+上空边+下空边+YSYNC长度)整屏的刷新率 其中x向分辨率、左空边、右空边、HSYNC长度、y向分辨率、上空边、下空边和YSYNC长度可以在X35LCD说明文档中查到。 整屏的刷新率计算方法如下: 假如我们通过查X35LCD说明文档,知道fclk=6.34MHZ,那么画一个像素需要的时间就是1/6.34us,如果屏的大小是240320,那么现实一行需要的时间就是240/6.34us,每条扫描线是240,但是水平回扫和水平同步也需要时间,如果水平回扫和水平同步需要29个像素时钟,因此,画一条扫描线完整的时间就是(240+29) /6.34us。完整的屏有320根线,但是垂直回扫和垂直同步也需要时间,如果垂直回扫和垂直同步需要13个像素时钟,那么画一个完整的屏需要(240+29)(320+13)/6.34us,所以整屏的刷新率就是6.34/((240+29)(320+13))MHZ
pixclock计算方法:
DOTCLK = fframe × (X + HBP + HFP+HSPW) × (Y + VBP + VFP+VSPW) (单位:MHz)
pixclock = 10^12/ DOTCLK=10^12/ (fframe × (X + HBP + HFP+hsynclen) × (Y + VBP + VFP+vsynclen)) (单位:皮秒)
例如:
假设有fframe=60,X=480,Y=272,VBP=2, VFP=2,HBP=2, HFP=2,HSPW=40,VSPW=9。pixclock = 10^12/(fframe × (X + HBP + HFP+hsync_len) × (Y + VBP + VFP+vsync_len))= 10^12/(60*(480+2+41+2)*(272+2+10+2))= 10^12/8960400= 111000皮秒
- 1
- 2
- 3
- 4
- 5
- 6
- s3c24xx_fb_set_platdata作用就是把传入的结构体smdk2440_fb_info存入s3c_device_lcd.dev中的platform_data,即:s3c_device_lcd.dev.platform_data = smdk2440_fb_info.目的为了在使用platform虚拟总线设备是可以通过platform_device(也就是s3c_device_lcd的结构类型)可以找到smdk2440_fb_info(该结构体记录了lcd的配置信息,像素,长,宽等)
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)//smdk2440_fb_info
{struct s3c2410fb_mach_info *npd;npd = s3c_set_platdata(pd, sizeof(*npd), &s3c_device_lcd);if (npd) {npd->displays = kmemdup(pd->displays,sizeof(struct s3c2410fb_display) * npd-> num_displays,GFP_KERNEL);if (!npd->displays)printk(KERN_ERR "no memory for LCD display data\n");} else {printk(KERN_ERR "no memory for LCD platform data\n");}
}
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- LCD的配对使用的是platform框架,这是设备信息 s3c_device_lcd设备在mach-smdk2440.c中被使用,当uboot传入的machid等于smdk2440的机器id就会通过platform一次性注册多个设备。
static struct resource s3c_lcd_resource[] = {[0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),[1] = DEFINE_RES_IRQ(IRQ_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 = &samsung_device_dma_mask,.coherent_dma_mask = DMA_BIT_MASK(32),}
};/* 需要注册的多个设备 */
static struct platform_device *smdk2440_devices[] __initdata = {&s3c_device_ohci,&s3c_device_lcd,&s3c_device_wdt,&s3c_device_i2c0,&s3c_device_iis,&wr2440_device_eth,
}
//通过platform_add_devices函数实现platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
//具体实现如下
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;
}
- 38
- 39
- 40
- 41
- 当在内核中开启配置s3c2410 LCD framebuffer时就会把s3c2410fb.c编译进内核
->Device Drivers->Graphics support->Support for fame buffer devices->S3C2440 LCD framebuffer support(需要开启)
- 1
- 2
- 3
- 4
6.3 分析s3c2410fb.c中代码(不用修改)
//驱动的入口函数,使用platform框架进行匹配
int __init s3c2410fb_init(void)
{int ret = platform_driver_register(&s3c2410fb_driver);//在此处进行匹配,s3c2410fb_driver在下一步if (ret == 0)ret = platform_driver_register(&s3c2412fb_driver);return ret;
}//s3c2410fb_driver是设备驱动信息根据driver.name进行匹配的
static struct platform_driver s3c2410fb_driver = {.probe = s3c2410fb_probe,.remove = __devexit_p(s3c2410fb_remove),.suspend = s3c2410fb_suspend,.resume = s3c2410fb_resume,.driver = {.name = "s3c2410-lcd",.owner = THIS_MODULE,},
};
=======================================================================
//当匹配成功时,调用s3c2410fb_probe函数
static int __devinit s3c2410fb_probe(struct platform_device *pdev)
{return s3c24xxfb_probe(pdev, DRV_S3C2410);//DRV_S3C2410是type类型,为了区分DRV_S3C2412
}
//看一下具体的s3c24xxfb_probe
static int __devinit s3c24xxfb_probe(struct platform_device *pdev, enum s3c_drv_type drv_type)
{ //传入的参数pdev = s3c_device_lcd,
/* static struct resource s3c_lcd_resource[] = {[0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),[1] = DEFINE_RES_IRQ(IRQ_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 = &samsung_device_dma_mask,.coherent_dma_mask = DMA_BIT_MASK(32),}};
*/struct s3c2410fb_info *info;struct s3c2410fb_display *display;struct fb_info *fbinfo;struct s3c2410fb_mach_info *mach_info;struct resource *res;int ret;int irq;int i;int size;u32 lcdcon1;mach_info = pdev->dev.platform_data;//这里取出来前面存入的东西,就是smdk2440_fb_info,此变量内含各种LCD的参数信息//找到是哪个display,default_display = 0,num_displays = 1display = mach_info->displays + mach_info->default_display;irq = platform_get_irq(pdev, 0);//注册LCD中断//申请一个帧缓冲区结构体,内含有帧缓冲区设备的属性和操作函数的集合fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);platform_set_drvdata(pdev, fbinfo);//把fbinfo存入pdev.dev.driver_data,pdev也就是s3c_device_lcdinfo = fbinfo->par;//fbinfo->par是在framebuffer_alloc()申请时开辟了一块空间info->dev = &pdev->dev;info->drv_type = drv_type;//drv_type = DRV_S3C2410res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获得平台资源size = resource_size(res);info->mem = request_mem_region(res->start, size, pdev->name);//申请内存info->io = ioremap(res->start, size);//内存映射if (drv_type == DRV_S3C2412)info->irq_base = info->io + S3C2412_LCDINTBASE;elseinfo->irq_base = info->io + S3C2410_LCDINTBASE;//0x4D000000 + 0x54 = 0x4D000054strcpy(fbinfo->fix.id, driver_name);//driver_name = s3c2410fb/* Stop the video */lcdcon1 = readl(info->io + S3C2410_LCDCON1);//读lcdcon1寄存器的内容writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);//S3C2410_LCDCON1_ENVID = 1,把lcdcon1中第0位清零。意思是关闭图像输出和lcd 控制器信号输出fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;fbinfo->fix.type_aux = 0;fbinfo->fix.xpanstep = 0;fbinfo->fix.ypanstep = 0;fbinfo->fix.ywrapstep = 0;fbinfo->fix.accel = FB_ACCEL_NONE;fbinfo->var.nonstd = 0;fbinfo->var.activate = FB_ACTIVATE_NOW;fbinfo->var.accel_flags = 0;fbinfo->var.vmode = FB_VMODE_NONINTERLACED;fbinfo->fbops = &s3c2410fb_ops;//应用层使用open,read,write等操作函数集合fbinfo->flags = FBINFO_FLAG_DEFAULT;fbinfo->pseudo_palette = &info->pseudo_pal;for (i = 0; i < 256; i++)info->palette_buffer[i] = PALETTE_BUFF_CLEAR;//清除缓冲区ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info);//申请lcd中断//获取lcd的时钟,并使能时钟info->clk = clk_get(NULL, "lcd");clk_enable(info->clk);info->clk_rate = clk_get_rate(info->clk);/* 计算缓存,一帧图像的大小 */for (i = 0; i < mach_info->num_displays; i++) {unsigned long smem_len = mach_info->displays[i].xres;smem_len *= mach_info->displays[i].yres;smem_len *= mach_info->displays[i].bpp;smem_len >>= 3;//长*宽*bpp(16位) / 8if (fbinfo->fix.smem_len < smem_len)fbinfo->fix.smem_len = smem_len;}/* Initialize video memory */ret = s3c2410fb_map_video_memory(fbinfo);fbinfo->var.xres = display->xres;fbinfo->var.yres = display->yres;fbinfo->var.bits_per_pixel = display->bpp;//初始化lcd内部寄存器,主要是gpio,用于数据传输的VD[0:23]s3c2410fb_init_registers(fbinfo);//注册一个帧缓冲实体,也就是fb_info结构体ret = register_framebuffer(fbinfo);/* create device files */ret = device_create_file(&pdev->dev, &dev_attr_debug);if (ret)printk(KERN_ERR "failed to add debug attribute\n");printk(KERN_INFO "fb%d: %s frame buffer device\n",fbinfo->node, fbinfo->fix.id);return 0;
}
fbinfo->fbops = &s3c2410fb_ops; //操作函数集合
int register_framebuffer(struct fb_info *fb_info)
{//省略了不必要代码ret = do_register_framebuffer(fb_info);return ret;
}
===================================================================
static int do_register_framebuffer(struct fb_info *fb_info)
{int i;struct fb_event event;struct fb_videomode mode;//遍历register_fb数组中找到一个未用的空项for (i = 0 ; i < FB_MAX; i++)if (!registered_fb[i])break;//在此处注册了设备节点,FB_MAJOR = 29,i根据register_fb中第几个空项来确定,如果是第一个就是i = 0,名字fb0/1/2fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), NULL, "fb%d", i);//从此处开始,没看懂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);//以上的不知道要干嘛,但是下面的看懂了,就是把一个帧缓冲实体fb_info填入registered_fb中的一个空项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;
}
- 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
/*** fbmem_init - init frame buffer subsystem** Initialize the frame buffer subsystem.** NOTE: This function is _only_ to be called by drivers/char/mem.c.**/static int __init fbmem_init(void)
{proc_create("fb", 0, NULL, &fb_proc_fops);//注册字符设备,fb_fops是操作函数集合,主设备号:FB_MAJOR = 29,和上面s3c2410fb.c中注册设备节点使用的主设备号一样。//发现在fbmem_init只是注册设备并没有生成设备节点,只有在一个实际的帧缓冲区也就是lcd设备注册 时候才生成设备节点if (register_chrdev(FB_MAJOR,"fb",&fb_fops))printk("unable to get major %d for fb devs\n", FB_MAJOR);fb_class = class_create(THIS_MODULE, "graphics");return 0;
}//fb_fops操作函数集合如下
static const struct file_operations fb_fops = {.owner = THIS_MODULE,.read = fb_read,.write = fb_write,.unlocked_ioctl = fb_ioctl,.mmap = fb_mmap,.open = fb_open,.release = fb_release,.llseek = default_llseek,
};
这些操作函数是给应用调用的
应用层:open("/dev/fb0",O_RDWR);||
==================================================
驱动层: || || \/fb_fops->open(),||||\/info->fbops->fb_open根据s3c240fb.c中fbinfo->fbops = &s3c2410fb_ops;故此也就是s3c2410fb_ops函数中的open
虽然s3c2410fb_ops中没有open函数,那就应用层使用open也就是不调用实际的函数。fbmem.c中 fb_fops->open函数实现如下:
static int fb_open(struct inode *inode, struct file *file){int fbidx = iminor(inode);struct fb_info *info;int res = 0;info = get_fb_info(fbidx);if (!info) {request_module("fb%d", fbidx);info = get_fb_info(fbidx);if (!info)return -ENODEV;}file->private_data = info;if (info->fbops->fb_open) {//这里没运行res = info->fbops->fb_open(info,1);if (res)module_put(info->fbops->owner);}#ifdef CONFIG_FB_DEFERRED_IOif (info->fbdefio)fb_deferred_io_open(info, inode, file);
#endifreturn res;
}
6.4内核配置
在linux-3.4.2/目录下输入“make menuconfig”,配置如下:
# cd linux-3.4.2/
# make menuconfigDevice Drivers --->Graphics support ---><*> Support for frame buffer devices --->--- Support for frame buffer devices[*] Enable firmware EDID[ ] Framebuffer foreign endianness support ----[*] Enable Video Mode Handling Helpers[ ] Enable Tile Blitting Support*** Frame buffer hardware drivers **< > Epson S1D13XXX framebuffer support<*> S3C2410 LCD framebuffer support[ ] S3C2410 lcd debug messages< > SMSC UFX6000/7000 USB Framebuffer suppor< > Displaylink USB Framebuffer support< > Virtual Frame Buffer support (ONLY FOR TESTING!)< > E-Ink Metronome/8track controller support< > E-Ink Broadsheet/Epson S1D13521 controller suppor[*] Bootup logo --->--- Bootup logo[ ] Standard black and white Linux logo[ ] Standard 16-color Linux logo[*] Standard 224-color Linux logo
6.5 编译测试
修改好./arch/arm/mach-s3c24xx/mach-smdk2440.c文件后,重新编译生成uImage映像文件。
将生成的uImage文件复制到tftp共享文件夹
重启开发板,进入菜单,退出菜单。
将uImage文件通过tftp方式下载到开发板内存30000000处:
tftp 30000000 uImage
从30000000处重启开发板:
bootm 30000000
此时你会在开发板屏幕上看到可爱的小企鹅图片
向屏幕输出字符串,可执行
echo “hello DarkBirds!” > /dev/tty1
mipi接口 1280(RGB)*720 LCD屏开发驱动笔记帖相关推荐
- 驱动程序开发:LCD屏显示驱动
1.简介 在 Linux 中应用程序最终也是通过操作 RGB LCD 的显存来实现在 LCD 上显示字符.图片等信息.在裸机中我们可以随意的分配显存,但是在 Linux 系统中内存的管理很严格,显 ...
- 小明分享|sigmstar SSD201/SSD202 针对RGB的LCD屏配置操作说明分享
SSD20X 平 台 LCD 屏 的 配 置 主 要 涉 及 两 个 结 构 体 :MI_PANEL_ParamConfig_t . MI_PANEL_MipiDsiConfig_t 和他们定义在:p ...
- MT7688下的RA8871 LCD屏开发测试
最近在调LCD屏时,走了很多弯路,浪费了很多时间,写文档做个记录. RA8871没有写寄存器/写数据切换脚,它使用的是命令类型:每次会传送双倍的数据,如写数据0x22,则需要发送0x80 0x22: ...
- 关于TFT LCD屏ST7735S驱动移植STM32HAL库的部分问题总结
项目场景: STM32L431RCT驱动1.44寸TFT屏(ST7735S)移植HAL库 1.问题描述: 移植成功后,有复位不显示的情况,意思是同一段代码,有时候能显示,有时候下载到板子上死活不显示, ...
- 屏幕篇—如何最快速驱动LCD屏
屏幕篇-如何最快速驱动LCD屏 前言 LCD屏的驱动对于新手来说,这是一个很大的拦路虎.学完基础外设后,在这里就不知道怎么下手了.一个完整的LCD驱动包含的内容是非常多的,最基础的描点,画线,显示字符 ...
- 段码LCD屏幕的驱动方法
大家平常在生活之中见到最多的可能就是段码液晶屏了,它有着普通数码管的特征,又有着点阵液晶屏的特征,已经固定的图形,既省成本而且又好看,那么,我们今天来一起试一试! 首先,大家都不要以为使用单片机来驱动 ...
- 常用LVDS接口LCD屏
1.常用LVDS接口LCD屏 具体请参考网站:LCD显示--配套软硬件模块--英创.各种屏实物介绍和手册 英创公司ESMARC系列中的ESM6802.ESM7000等主板型号可以直接引出LVDS信号和 ...
- STM32 FSMC接口驱动4.3寸TFT LCD屏
STM32 FSMC接口驱动4.3寸TFT LCD屏 STM32的FSMC接口是并行总线接口,可以用于驱动存储芯片如FLASH/SRAM等,也可以用于驱动并口LCD屏. 这里以STM32F103VET ...
- RGB_TTL、LVDS、MIPI接口液晶屏
液晶屏有RGB TTL.LVDS.MIPI DSI接口,这些接口区别于信号的类型(种类),也区别于信号内容. RGB TTL接口信号类型是TTL电平,信号的内容是RGB666或者RGB888还有行场同 ...
最新文章
- 企业大数据运用实战案例分享
- 大数据时代 我们还有隐私吗?
- CSS3基础03(3D②) 求粉丝
- shell脚本练习之——形状练习(菱形、直角三角形、等腰三角形、五角星)
- leetcode 263. 丑数
- 【TensorFlow系列一】TensorFlow工作原理
- Maintenance Plans(维护计划)详解【转】
- 远程命令行添加(删除)注册表键值(远程打开)
- log4j 日志输出级别
- 红警代码开源了 来瞅瞅源码 文内送Win10可联机的红警2标准版游戏
- excel单元格内容拆分_Excel批量合并相同内容的单元格
- python免费自学资源(视频+图文)
- VisionPro控件的使用 C# 开发篇
- 【2389. 和有限的最长子序列】
- 微信小程序实例:实现tabs选项卡效果
- 2020人口普查全国人口学历分布情况
- 《张维迎:反思经济学》读后感作文4300字
- dhcp显示否服务器怎么设置,怎么开启 dhcp服务器配置
- 面渣逆袭:线程池夺命连环十八问
- 简单的CMD命令-进入到某个文件夹
热门文章
- Python量化交易平台开发教程系列7-顶层GUI界面开发(1)
- js+json实现哔哩哔哩番剧时间表
- Linux下安装lsof
- org.apache.flink.util.FlinkRuntimeException: Exceeded checkpoint tolerable failure threshold
- IOS 逆向开发(一)密码学 非对称加密RSA
- 化模糊为清晰的图片清晰术:动手尝试修复模糊老照片
- 视频号还原老照片高光时刻,他在视频号变现60万+!
- Pygame游戏编程
- 使用Windows自带工具WINSat给硬盘测速
- 直接耦合的互补输出级