2014-05-22 14:34:02 xzongyuan 阅读数 1090
  • framebuffer驱动详解-linux驱动开发第7部分

    本课程是linux驱动开发的第7个课程,主要内容是linux的framebuffer驱动详解,本课程带大家分析fb驱动的框架、构成以及一些代码细节,目标是让大家彻底掌握fb驱动在内核中的配置和移植方法、掌握显示设备驱动的关键点。

    6120 人正在学习 去看看 朱有鹏

今天看了下LCD驱动开发的代码,发现思路是这样的。

如以下任务,需要用主控芯片Cortext M3架构的MCU,驱动字符液晶模组tc1602。另外,配以串并转换芯片74HC595。给你以上这样3个器件,你要怎么让LCD动态显示一串字符和时间?

1.首先,为了节省IO口资源,MCU一般会使用SSI(同步串行传输接口)传指令和数据。而SSI的协议有多种,其中最著名的就是SPI接口。所以,你必须得懂得SSI的驱动怎么写。

2.因为字符液晶模组一般是并口的,可以是4位或者8位或者更多,所以MCU和LCD之间的数据传输,要有串并转换芯片来实现,如74HC595。

3.串口芯片有什么特征?首先,串口输入部分是用移位寄存器,而输出部分是并口存储器,很明显,输出比较慢,要等待串行输入结束才可以输出。所以输出和输入是两个时钟,在SSI编程时,会用两个不同的延时MCU端口来发时钟给串并转换芯片,以同步输入和输出端的数据(如下图的SCK和RCK)。

4.那么MCU发送数据给LCD的流程是怎样的呢?如下:

(1)首先,MCU把数据串行地发给串并转换芯片。这时,要根据串并转换芯片的要求,允许一些数据操作端口。

(2)其次,根据串并转换芯片的输出端的时钟,允许串并转换芯片的数据从并口输出。但在这个操作之前,MCU要在相应的端口提醒LCD接收数据

5.这有点需要注意的地方,那就是MCU既有控制LCD读取的端口,也有控制串并转换芯片读写的端口。如下图的R/W   RS  OE

 

总结:可见,给LCD发数据,主要是给串并转换芯片发数据,在写函数的时候,其实是环环嵌套的,打个比方:

函数嵌套如下:

LCD字符串send->调用LCD字节SEND->调用SSI_SEND->调用纯字节send

SSI_SEND和纯字节SEND函数有什么区别呢?区别就是纯send,只是把数据写入寄存器,而协议SSI_SEND需要把相应的位OE拉高或者拉低,还要判断芯片是否busy等。LCD_SEND也是这样,需要在SSI_SEND的基础上给控制端口拉高或拉低电平。

可见,驱动开发=写命令和数据寄存器+写控制端口+准确设计延时。而上面提到的嵌套,就是因为普通的写数据寄存器都依赖写控制端口+写命令寄存器+时延函数,所以会出现很多调用。但如上所述,原理其实很简单。


以上说的参考代码如下(《CortexM3内核微控制器 快速入门与应用》):

//*********************************************************************************
 //博圆周立功单片机&嵌入式系统培训[博圆单片机初学之路]
 //Email: bymcupx@126.com 
 //文件名: TC1602_lcd.h
 //功能: TC1602液晶驱动程序
 //开发者:刘同法
 //日期: 2008年9月15日
//---------------------------------------------------------------------------------
#include "hw_memmap.h"
#include "hw_types.h"
#include "ssi.h"
#include "gpio.h"
#include "sysctl.h"
#include "systick.h"


#define uchar unsigned char  //映射uchar 为无符号字符
#define uint  unsigned int   //映射uint  为无符号整数 

#define RS GPIO_PIN_0  //PA0 用于命令与数据选择RS=0选择发送命令,RS=1选择发送数据
#define RW GPIO_PIN_1  //PA1 读/写数据选择脚 RW=0选择命令或数据写,Rw=1选择命令或数据读
#define E  GPIO_PIN_6  //PB6 使能线

#define GCS    GPIO_PIN_3  //SPI    片选              PA3
#define GDIO   GPIO_PIN_5  //SPIDAT[MOSI] 数据         PA5
#define GSCLK  GPIO_PIN_2  //SPICLK 时钟                PA2
uchar uchBuff;

void GSEND_DATA8(uchar chSDAT);
void GSEND_COM8(uchar chSDAT);
/****************************************************************************
* 名称:Delay50uS
* 功能:50uS软件延时
* 说明:用户根据自已的系统相应更改
****************************************************************************/
void Delay_uS(uint nNum2)
{ 
  unsigned long i;
  i=SysCtlClockGet()/1000000;  //获取系统1us的时间*1得出1个us
   i=i*nNum2;
   while(i--); 
}
//-------------------------------------------------------------------------------
//延时函数
//说明:每执行一次大约100us
//------------------------------------------------------------------------------
void DelayDS(uint nNum)
{ 
  unsigned long i;
  i=SysCtlClockGet()/1000000*100;  //获取系统1us的时间*100得出100个us
   i=i*nNum;
   while(i--);   
}
//------------------------------------------------------------------------------
// 函数名称  GPio_Initi_PA
// 函数功能  启动外设GPIO输入输出
// 输入参数  无。
// 输出参数  无。
//-----------------------------------------------------------------------------
void GPio_Initi_PA(void)
{
    // 使能GPIO B口外设。用于指示灯
   SysCtlPeripheralEnable( SYSCTL_PERIPH_GPIOA | SYSCTL_PERIPH_GPIOB );   
   
   GPIODirModeSet(GPIO_PORTA_BASE, RS | RW | E,GPIO_DIR_MODE_OUT);
    
   GPIODirModeSet(GPIO_PORTB_BASE, E,GPIO_DIR_MODE_OUT);
   
    // 设定 GPIO A 2~5 引脚为使用外设功能 GPIO_DIR_MODE_HW由硬件进行控制
   GPIODirModeSet(GPIO_PORTA_BASE, (GPIO_PIN_2 | GPIO_PIN_3 | 
                              GPIO_PIN_5), GPIO_DIR_MODE_OUT); //HW[]  
   
}
//-----------------------------------------------------------------------------
//函数名称: Write_Command()
//函数功能: 向TC1602写入命令[将ACC寄存器命令内容发送到P1口]
//入口参数:chComm[送递要发送的命令]
//出口参数:无
//编程序:  刘同法
//编程序日期:2008年9月15日
//------------------------------------------------------------------------------
void Write_Command(uchar chComm)
{
      //RS=0;    //CLR RS 命令与数据选择脚 RS=0选择发送命令,RS=1选择发送数据
     GPIOPinWrite( GPIO_PORTA_BASE, RS,~RS );
      //RW=0;  //CLR RW 读/写数据选择脚 RW=0选择命令或数据写,Rw=1选择命令或数据读
     GPIOPinWrite( GPIO_PORTA_BASE, RW,~RW );
    // E=1;           //SETB E 开启数据锁存脚[数据锁存允许]
     GPIOPinWrite( GPIO_PORTB_BASE, E,E );
 
     GSEND_COM8(chComm);
     
     //E=0;    //CLR E  关闭数据锁存脚并向对方锁存数据[告诉对方数据已发送请接收]
     GPIOPinWrite( GPIO_PORTB_BASE, E,~E );
     DelayDS(10);  //延时1ms 100*10=1000us=1ms
 
     
}
//--------------------------------------------------------------
//函数名称: Write_Data()
//函数功能: 向TC1602写入数据[将ACC寄存器数据内容发送到P1口]
//入口参数:chData[送递要发送的数据]
//出口参数:无
//编程序:  刘同法
//编程序日期:2008年9月15日
//----------------------------------------------------------------
void Write_Data(uchar chData)
{
      //RS=1;    //SETB RS 命令与数据选择脚 RS=0选择发送命令,RS=1选择发送数据
     GPIOPinWrite( GPIO_PORTA_BASE, RS,RS );
      //RW=0;    //CLR RW  读/写数据选择脚 RW=0选择命令或数据写,Rw=1选择命令或数据读
     GPIOPinWrite( GPIO_PORTA_BASE, RW,~RW );
      //E=1;     //SETB E 开启数据锁存脚[数据锁存允许]
     GPIOPinWrite( GPIO_PORTB_BASE, E,E );
 
     GSEND_COM8(chData);
     //E=0;    //CLR E 关闭数据锁存脚并向对方锁存数据[告诉对方数据已发送请接收]
     GPIOPinWrite( GPIO_PORTB_BASE, E,~E );
     
     DelayDS(5);  //延时0.5ms 5*100=500us=0.5ms
      
}     
//--------------------------------------
//函数名称:Busy_tc1602()
//函数功能:判忙子程序[用于判断LCD是否在忙于写入,如LCD在忙于别的事情,那就等LCD忙完后才操作]
//入出参数:无
//编程序:  刘同法
//编程序日期:2008年9月15日
//-----------------------------------------
void Busy_tc1602()
{
     uchar ACC; 
    // RS=0;  //CLR RS
     GPIOPinWrite( GPIO_PORTA_BASE, RS,~RS );
     //RW=1;  //SETB RW  设为从TC1602中读取数据
     GPIOPinWrite( GPIO_PORTA_BASE, RW,RW );
     do
     { 
       //E=1;   //SETB E
       GPIOPinWrite( GPIO_PORTB_BASE, E,E );
       //ACC=P1; //MOV A,P1 读取P1口数据
       //ACC=Lm101_Ssi_Rcv();
       //E=0;   //CLR E   锁存数据
       GPIOPinWrite( GPIO_PORTB_BASE, E,~E );
       ACC=ACC&0x80; //ANL A,#80H
       
      }while(ACC);    //JNZ TT0
 
     //POP   ACC
 
}  
//-------------------------------------------------------------
//函数名称: Delay_1602()
//函数功能: 用于毫秒级延时
//入口参数:nDTime[用于传递延时间单位为ms,如果nDTime=1即为1ms]
//出口参数: 无
//编程序:  刘同法
//编程序日期:2008年9月15日
//---------------------------------------------------------------
void Delay_1602(uint nDTime)
{
   uint a;
   for(a=0;a<nDTime;a++)
      DelayDS(10);  //取每循环一次为1ms
   
}   
//------------------------------------------------------------
//下面是TC1602外用程序
//-------------------------------------------------------------
//函数名称: Init_TC1602()(外部调用)
//函数功能: 初始化TC1602液晶显示屏[TC1602必须要初使化才能使用]
//入出参数:无
//编程序:  刘同法
//编程序日期:2008年9月15日
//-----------------------------------------------------------
void Init_TC1602()
{
      //初始化SPI 
      GPio_Initi_PA();  
      //共延时15ms
      Delay_1602(15);

      //发送命令
      Write_Command(0x38);
      Delay_1602(5);  //LCALL TME0    ;延时5ms
      
      //重发一次
      Write_Command(0x38);
      Delay_1602(5);  //LCALL TME0    ;延时5ms
      
      Write_Command(0x38);  //设置为8总线16*2 5*7点阵
      Write_Command(0x01);  //发送清屏命令
      Write_Command(0x06);  //设读写字符时地址加1,且整屏显示不移动
      Write_Command(0x0F);  //开显示,开光标显示,光标和光标所在的字符闪烁
      Delay_1602(5);  //LCALL TME0    ;延时5ms
      
}      
//------------------------------------------------------
//函数名称: Cls()
//函数功能: 用于清屏
//入出参数:元
//编程序:  刘同法
//编程序日期:2008年9月15日
//--------------------------------------------------------
void Cls()
{
  Write_Command(0x01);  //发送清屏命令  
}
//--------------------------------------------------------
//下面是应用部分
//--------------------------------------------------------
//函数名称: Send_String_1602()
//函数功能: 用于向TC1602发送字符串
//入口参数:chCom[传送命令行列] lpDat[传送数据串不要超个16个字符] nCount[传送发送数据的个数]
//出口参数: 无
//编程序:  刘同法
//编程序日期:2008年9月15日
//---------------------------------------------------------
void Send_String_1602(uchar chCom,uchar *lpDat,uint nCount)
{
   uint i=0;
   
   Write_Command(chCom);  //发送起始行列号
   Delay_1602(10);
   for(i=0;i<nCount;i++)
   {
     Write_Data(*lpDat);  //发送数据
     lpDat++;             //让指针向前进1[加1]读取下一个字符
   }
  Delay_1602(20);  
}
//--------------------------------------------------------
//函数名称: Send_Data_1602()
//函数功能: 用于向TC1602发送整型数
//入口参数:chCom[传送命令行列] nDat[传送整型数据] nCount[传送发送数据的个数]
//出口参数: 无
//编程序:  刘同法
//编程序日期:2008年9月15日
//---------------------------------------------------------
void Send_Data_1602(uchar chCom,uint nData,uint nCount)
{
   uint nInt,nInt1,nInt2;  //用来存放数据
   uchar chC[5];
   if(nCount>4)return ;   //判断是否大于4个,如果大于4个就反回
                          //控制5个不准显示
   if(nCount==1)
   { chC[0]=nData%10;
     chC[0]|=0x30;      //使用逻辑或加入显示字符因为tc1602使用的是ASCII码作显示
     Write_Command(chCom);  //发送起始行列号
     Write_Data(chC[0]);  //发送数据
    }
    else if(nCount==2)
    {
        nInt=nData%100;
        chC[0]=nInt/10;
        chC[0]|=0x30;      //使用逻辑或加入显示字符因为tc1602使用的是ASCII码作显示
        chC[1]=nInt%10;
        chC[1]|=0x30;      //同chC[1]=chC[1]|0x30;  逻辑或运算,但是千万不能用加法运算,否则得出来的数是乱码
        Write_Command(chCom);  //发送起始行列号
        Write_Data(chC[0]);  //发送数据
        Write_Data(chC[1]);  //发送数据
      }
      else if(nCount==3)
      {
        nInt=nData%1000;
        chC[0]=nInt/100;
        chC[0]|=0x30;          //逻辑或运算变为ASCII美国国家标准信息码用于显示
        nInt=nInt%100;
        chC[1]=nInt/10;
        chC[1]|=0x30;
        chC[2]=nInt%10;
        chC[2]|=0x30;
        Write_Command(chCom);  //发送起始行列号
        Write_Data(chC[0]);  //发送数据
        Write_Data(chC[1]);  //发送数据
        Write_Data(chC[2]);  //发送数据
       }
       else if(nCount==4)      
       {
        nInt=nData%10000;
        nInt1=nInt/100;   //取商
        nInt2=nInt%100;   //取余
        chC[0]=nInt1/10;
        chC[0]|=0x30;
        chC[1]=nInt1%10;
        chC[1]|=0x30;
        chC[2]=nInt2/10;
        chC[2]|=0x30;
        chC[3]=nInt2%10;
        chC[3]|=0x30; 
        Write_Command(chCom);  //发送起始行列号
        Write_Data(chC[0]);  //发送数据
        Write_Data(chC[1]);  //发送数据
        Write_Data(chC[2]);  //发送数据
        Write_Data(chC[3]);  //发送数据
        }else;           
      
   return ;   
}
//------------------------------------------------------------------------------------
//为自编写字模用WRCGRAM子程序写入1602LCD夜晶显示器CGRAM存储器
uchar ZhiMou[]={0x08,0x0F,0x12,0x0F,0x0A,0x1F,0x02,0x02, //年
	                 0x0F,0x09,0x0F,0x09,0x0F,0x09,0x11,0x00, // ;月
	                 0x0F,0x09,0x09,0x0F,0x09,0x09,0x0F,0x00}; //;日
	                 
uchar  chTB1[6]={0x42,0x59,0x50,0x58,0x42};  //   ;BYPXB
uchar  chTB2[14]={0x6C,0x74,0x66,0x32,0x30,0x30,0x35,0x00,0x31,0x30,0x01,0x31,0x02};
                    //  l   t     f   2    0    0     5   年   1    0    月   1    日	
uchar  chTB4[8]={0x62,0x79,0x6D,0x63,0x75,0x70,0x78}; //bymcupx
uchar  chTB5[8]={0x32,0x30,0x30,0x35,0x31,0x30,0x39}; //2005109
uchar  chTB6[10]={'L','i','u','T','o','n','g','F','a'}; 
              	                 
//-------------------------------------------------------------------------------
//写入用户汉字字模数据子程序	
//------------------------------------------------------------------------------
//函数名称:Write_WRCGRAM()
//功能:[创建用户字模地址从00~07共8个,且只能创建8个]把要建立的汉字字模数据写入用户字模存储器[CGRAM]
//入出参数:无
//编程序:  刘同法
//编程序日期:2008年9月15日
//------------------------------------------------------------------------------
void Write_WRCGRAM()
{
     uint i=0;
     
     Write_Command(0x40);  //发送命令从00H地址开始存放字模
     
     for(i=0;i<24;i++)    //8*8*8=24
       Write_Data(ZhiMou[i]);  //发送字模数据

}
//----------------------------------------------------------------------------
//下面是74HC595驱动程序
//用于向74HC595发送数据
//-----------------------------------------------------------------------------
//程序名称:GSEND_DATA8()
//程序功能:用于发送8位数据[单字节发送子程序]
//入口参数: chSDAT[传送要发送的数据]
//出口参数:无
//编程:刘同法
//时间:2008年6月10日
//说明:ZLG7289使用的是SPI同步串行通信,本子程序已是串行通信程序,
//     再加上CS片选动作即可实施SPI同步串行通信
//     数据发送高位在前
//---------------------------------------------------------------------------------
void GSEND_DATA8(uchar chSDAT)
{
        
        GPIODirModeSet(GPIO_PORTA_BASE, GPIO_PIN_5 , GPIO_DIR_MODE_OUT);

        uint  JSQ1=8;   //准备发送8次
        Delay_uS(50);    //调用50us延时子程序
        uchBuff=chSDAT;
        
     SD:
        //先将高7位发送出去
        if(uchBuff&0x80)
          GPIOPinWrite( GPIO_PORTA_BASE, GDIO, GDIO );  //如果是1就发1
         else 
           GPIOPinWrite( GPIO_PORTA_BASE, GDIO, 0x00 ); // 如果是0就发0
       //将高6位移到高7位准备下一次发送
        uchBuff=uchBuff<<1; //然后向左移一位准备下一次的发送
       
        Delay_uS(2);
        
        GPIOPinWrite( GPIO_PORTA_BASE, GSCLK, GSCLK ); //准备数据锁存 =1
        Delay_uS(8);    //12微秒延时
        GPIOPinWrite( GPIO_PORTA_BASE, GSCLK, 0x00 );  //在脉冲的下沿锁存数据 
        Delay_uS(8);    //12微秒延时锁存数据
         
        if(--JSQ1)goto SD;

        GPIOPinWrite( GPIO_PORTA_BASE, GDIO, 0x00 );//清零数据线 
              
       return;
}
//----------------------------------------------------  
//程序名称:GSEND_COM8()
//程序功能:单字节命令发送子程序[发送纯指令的子程序]
//入口参数: chSDAT[传送要发送的数据]
//出口参数:无
//编程:刘同法
//时间:2008年6月10日
//----------------------------------------------------        
void GSEND_COM8(uchar chSDAT)
{
      
      GPIOPinWrite( GPIO_PORTA_BASE, GCS, 0x00 );//GCS=0;
    
      GPIOPinWrite( GPIO_PORTA_BASE, GSCLK, 0x00 ); // GSCLK=0;
      
      GSEND_DATA8(chSDAT);
      
      GPIOPinWrite( GPIO_PORTA_BASE, GCS, GCS ); // GCS=1;
    
}             
//-----------------------------------------------------------------------------
//******************************************************************************



2019-04-27 14:45:23 qq_40008325 阅读数 167
  • framebuffer驱动详解-linux驱动开发第7部分

    本课程是linux驱动开发的第7个课程,主要内容是linux的framebuffer驱动详解,本课程带大家分析fb驱动的框架、构成以及一些代码细节,目标是让大家彻底掌握fb驱动在内核中的配置和移植方法、掌握显示设备驱动的关键点。

    6120 人正在学习 去看看 朱有鹏

做Linux lcd驱动移植时,先找到lcd驱动的位置:grep "s702" -nR
例如:我的nanopc t3 plus可以看到:
 

可以看到,上面有相应的lcds.c,因为我们的开发板是nanopi t3 plus,即进入到nanopi3,看看里面的lcds.c的类型的s702的结构体即可,既是如下

static struct nxp_lcd wvga_s702 = {
        .width = 800,
        .height = 480,
        .p_width = 155,
        .p_height = 93,
        .bpp = 24,
        .freq = 61,

        .timing = {
                .h_fp = 44,
                .h_bp = 26,
                .h_sw = 20,
                .v_fp = 22,
                .v_fpe = 1,
                .v_bp = 15,
                .v_bpe = 1,
                .v_sw = 8,
        },
        .polarity = {
                .rise_vclk = 1,
                .inv_hsync = 1,
                .inv_vsync = 1,
                .inv_vden = 0,
        },
        .gpio_init = s702_gpio_init,
};

上面的结构体是uboot的结构体,Linux内核中也有一样的lcd结构体,同样参考数据手册改成一样的参数即可。

s702是开发板的lcd屏幕的规格类型,找到之后找lcd屏幕厂家找到相应的datasheet,既是对应相应的规格书,对照着规格书改写里面的参数即可,记一下我在网上找到的规格书参数:

             参照上面表中的参数修改lcd结构体里面的相应的参数即可。

2019-11-12 15:43:11 weixin_39234635 阅读数 21
  • framebuffer驱动详解-linux驱动开发第7部分

    本课程是linux驱动开发的第7个课程,主要内容是linux的framebuffer驱动详解,本课程带大家分析fb驱动的框架、构成以及一些代码细节,目标是让大家彻底掌握fb驱动在内核中的配置和移植方法、掌握显示设备驱动的关键点。

    6120 人正在学习 去看看 朱有鹏

本篇博客LCD驱动开发环境,内核基于Linux2.6.22,soc基于s3c2440
我们先来看下内核中相关代码,其实我们会发现LCD驱动和前面输入子系统在理念上非常相似(有趣的是,LCD驱动可以基于输入子系统来开发),我个人认为,内核在驱动中最重要的一个思想就是机制和策略分离,将代表着硬件设备(机制)的代码和代表着驱动(策略)的代码尽量剥离开来,然后从大体相同而在细节上不同的驱动之间找出它们的相同代码,将相同的代码构建成一个类似于平台一样的东西,这个平台尽量具有包容性,这样就可以让两端的驱动和设备的代码量减少,有效了减少了内核驱动的冗余代码,也给我们这样的普通开发者一个更友好的开发环境。
类型与输入子系统,LCD驱动也有一个相当于核心层
/driver/video/fbmem.c

static int __init
fbmem_init(void)
{
	create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);

	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

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

这个函数向内核申请了主设备号,注册了一个fb_fops,同时还向创建了一个class,但是没有在这个class基础上创建一个设备节点,这是由原因的,稍后我们就会知道
看下这个fb_fops,当我们调用注册在这个平台下的fb(frambuffer)read、write等函数时,对应的系统调用就是fb_fops中的函数

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.ioctl =	fb_ioctl,
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
};

我们首先看下open函数

static int
fb_open(struct inode *inode, struct file *file)
{
	int fbidx = iminor(inode);
	struct fb_info *info;
	int res = 0;
	//判断次设备号是否大于FB_MAX,这个是由限制的
	if (fbidx >= FB_MAX)
		return -ENODEV;
	//这个是最为核心的部分,从registered_fb这个全局数组中根据次设备号来获取一个info
	if (!(info = registered_fb[fbidx]))
		return -ENODEV;
	if (!try_module_get(info->fbops->owner))
		return -ENODEV;
	//把info存放在file->private_data中
	file->private_data = info;
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}
	return res;
}

这个open函数和输入子系统中的open函数其实没有太大区别,都是根据次设备号从一个全局数组中获取一个成员(info)。然后把info保存在file->private_data中。所以我们可能想知道到底是哪个函数对这个registered_fb全局数组进行初始化,这个稍后分析,继续看fb_fops

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	unsigned long p = *ppos;
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];
	u32 *buffer, *dst;
	u32 __iomem *src;
	int c, i, cnt = 0, err = 0;
	unsigned long total_size;
	...
	//判断info->fbops里有没有fb_read函数,如果有,就直接执行info->fbops函数
	if (info->fbops->fb_read)
		return info->fbops->fb_read(info, buf, count, ppos);
	
	total_size = info->screen_size;
	
	...
	//申请一个buffer,这个buffer等下是作为向用户空间传递信息的载体
	//有趣的是这里对buffer的空间大小有要求,所以可以猜测一下,等下信息传递肯定会有一个循序
	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;
	//读操作的src地址是显存的基地址加上传进来的文件指针偏移
	src = (u32 __iomem *) (info->screen_base + p);
	//如果fbops中有定义fb_sync就执行
	if (info->fbops->fb_sync)
		info->fbops->fb_sync(info);
	
	while (count) {
		c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		dst = buffer;
		//因为src和dst都是32位数,所以fb_readl每次读的是四个字节,所以需要对c除以4,
		for (i = c >> 2; i--; )
			*dst++ = fb_readl(src++);
			//那如果除以4有余数呢,显然就不可以直接32位一起读了,所以只能以一个字节为单位来读了。
		if (c & 3) {
			u8 *dst8 = (u8 *) dst;
			u8 __iomem *src8 = (u8 __iomem *) src;

			for (i = c & 3; i--;)
				*dst8++ = fb_readb(src8++);

			src = (u32 __iomem *) src8;
		}

		if (copy_to_user(buf, buffer, c)) {
			err = -EFAULT;
			break;
		}
		*ppos += c;
		buf += c;
		cnt += c;
		count -= c;
	}

	kfree(buffer);

	return (err) ? err : cnt;
}

这里对缓冲区的读操作写得非常地巧妙,值得我们好好学习一下。读懂了read函数,write函数就很好懂了,我们来看下之前提到的那个问题,到底是哪个函数初始化了registered_fb这个全局数组

int
register_framebuffer(struct fb_info *fb_info)
{
	int i;
	struct fb_event event;
	struct fb_videomode mode;
	
	if (num_registered_fb == FB_MAX)
		return -ENXIO;
		//其实我有疑问,在一开始就对num_registered_fb++全局变量加一计数,
		//如果最终没有申请成功,也没有看到对这个全局变量进行恢复,所以是不是有问题?
	num_registered_fb++;
	//找出第一个没有被使用的次设备号
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
	fb_info->node = i;
	//在fb_class基础上创建一个设备节点(device),在入口函数中我们发现只有register_chrdev和class_create,但是没有device_create。
	//原来是把device_create放在了register_framebuffer中。首先这可以说明,一个主设备号可以对应多个次设备号(废话),一个fb_class仅仅只是对应
	//着一个主设备号,可以依据不同的次设备号在同一个class基础上创建不同的device
	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), "fb%d", i);
	if (IS_ERR(fb_info->dev)) {
		/* Not fatal */
		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
		fb_info->dev = NULL;
	} else
		fb_init_device(fb_info);
	//对fb_info进行基本的初始化,代码比较多我给删掉了
	...
	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注册到全局数组register_fb中,用次设备号来管理
	registered_fb[i] = fb_info;

	event.info = fb_info;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	return 0;
}

那是谁调用了register_framebuffer?这里我们以s3c2410fb.c为例
可以看到是s3c2410fb_probe调用了register_framebuffer,这个文件是建立在平台总线之上

static int __init s3c2410fb_probe(struct platform_device *pdev)
{
	struct s3c2410fb_info *info;
	struct fb_info	   *fbinfo;
	struct s3c2410fb_hw *mregs;
	int ret;
	int irq;
	int i;
	u32 lcdcon1;

	mach_info = pdev->dev.platform_data;
	if (mach_info == NULL) {
		dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n");
		return -EINVAL;
	}

	mregs = &mach_info->regs;
	//从pdev中获取irq
	irq = platform_get_irq(pdev, 0);
	//申请一个fb_info,为什么要在这里用sizeof(struct s3c2410fb_info)?
	//可以进入framebuffer_alloc看一下,size_t size参数表示的是额外申请空间,就是说如果我们把这个参数设置为0
	//那么将会申请到sizeof(struct fb_info)大小的空间。那为什么要申请额外空间呢?这个额外空间是给private_data使用的
	//在核心层fbmem.c的fb_open函数中,将会把fb_info保存在其中	
	fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
	if (!fbinfo) {
		return -ENOMEM;
	}

	info = fbinfo->par;
	info->fb = fbinfo;
	info->dev = &pdev->dev;

	platform_set_drvdata(pdev, fbinfo);

	dprintk("devinit\n");

	strcpy(fbinfo->fix.id, driver_name);

	memcpy(&info->regs, &mach_info->regs, sizeof(info->regs));

	/* Stop the video and unset ENVID if set */
	info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
	lcdcon1 = readl(S3C2410_LCDCON1);
	writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);

	info->mach_info		    = pdev->dev.platform_data;
	/*fix对应的是fb_fix_screeninfo,代表着固定参数*/
	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;
	/*var 代表着可变参数的设置*/
	fbinfo->var.nonstd	    = 0;
	fbinfo->var.activate	    = FB_ACTIVATE_NOW;
	fbinfo->var.height	    = mach_info->height;
	fbinfo->var.width	    = mach_info->width;
	fbinfo->var.accel_flags     = 0;
	fbinfo->var.vmode	    = FB_VMODE_NONINTERLACED;

	fbinfo->fbops		    = &s3c2410fb_ops;
	fbinfo->flags		    = FBINFO_FLAG_DEFAULT;
	fbinfo->pseudo_palette      = &info->pseudo_pal;

	fbinfo->var.xres	    = mach_info->xres.defval;
	fbinfo->var.xres_virtual    = mach_info->xres.defval;
	fbinfo->var.yres	    = mach_info->yres.defval;
	fbinfo->var.yres_virtual    = mach_info->yres.defval;
	fbinfo->var.bits_per_pixel  = mach_info->bpp.defval;

	fbinfo->var.upper_margin    = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
	fbinfo->var.lower_margin    = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
	fbinfo->var.vsync_len	    = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;

	fbinfo->var.left_margin	    = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
	fbinfo->var.right_margin    = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
	fbinfo->var.hsync_len	    = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;

	fbinfo->var.red.offset      = 11;   //三种颜色的offset和length是和他的bpp mode有关,我们这里选择的是565
	fbinfo->var.green.offset    = 5;	
	fbinfo->var.blue.offset     = 0;
	fbinfo->var.transp.offset   = 0;
	fbinfo->var.red.length      = 5;
	fbinfo->var.green.length    = 6;
	fbinfo->var.blue.length     = 5;
	fbinfo->var.transp.length   = 0;
	fbinfo->fix.smem_len        =	mach_info->xres.max *
					mach_info->yres.max *
					mach_info->bpp.max / 8;


	for (i = 0; i < 256; i++)
		info->palette_buffer[i] = PALETTE_BUFF_CLEAR;

	if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {
		ret = -EBUSY;
		goto dealloc_fb;

	dprintk("got LCD region\n");

	ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);

	info->clk = clk_get(NULL, "lcd");

	clk_enable(info->clk);
	dprintk("got and enabled clock\n");

	msleep(1);

	/* Initialize video memory */
	ret = s3c2410fb_map_video_memory(info);

	dprintk("got video memory\n");

	ret = s3c2410fb_init_registers(info);

	ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);
	//初始化fbinfo完毕,将其注册到全局数组中
	ret = register_framebuffer(fbinfo);


	/* create device files */
	device_create_file(&pdev->dev, &dev_attr_debug);

	printk(KERN_INFO "fb%d: %s frame buffer device\n",
		fbinfo->node, fbinfo->fix.id);

	return 0;

}

如下是s3c2410-lcd对应的resource

/* LCD Controller */

static struct resource s3c_lcd_resource[] = {
	[0] = {
		.start = S3C24XX_PA_LCD,
		.end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_LCD,
		.end   = IRQ_LCD,
		.flags = IORESOURCE_IRQ,
	}

};

static u64 s3c_device_lcd_dmamask = 0xffffffffUL;

struct platform_device s3c_device_lcd = {
	.name		  = "s3c2410-lcd",
	.id		  = -1,
	.num_resources	  = ARRAY_SIZE(s3c_lcd_resource),
	.resource	  = s3c_lcd_resource,
	.dev              = {
		.dma_mask		= &s3c_device_lcd_dmamask,
		.coherent_dma_mask	= 0xffffffffUL
	}
};

2011-10-23 10:00:01 eqwewr 阅读数 1094
  • framebuffer驱动详解-linux驱动开发第7部分

    本课程是linux驱动开发的第7个课程,主要内容是linux的framebuffer驱动详解,本课程带大家分析fb驱动的框架、构成以及一些代码细节,目标是让大家彻底掌握fb驱动在内核中的配置和移植方法、掌握显示设备驱动的关键点。

    6120 人正在学习 去看看 朱有鹏
 

                 LCM(LCD Module)即LCD显示模组、液晶模块,是指将液晶显示器件,连接件,控制与驱动等外围电路,PCB电路板,背光源,结构件等装配在一起的组件。

 

LCM的驱动接口

  LCM提供用户一个标准的LCD显示驱动接口(有4位、8位、VGA等不同类型),用户按照接口要求进行操作来控制LCD正确显示。LCM相比较玻璃是一种更高集成度的LCD产品,对小尺寸LCD显示,LCM可以比较方便地与各种微控制器(比如单片机)连接;但是,对于大尺寸或彩色的LCD显示,一般会占用控制系统相当大部分的资源或根本无法实现控制,比如320×240 256色的彩色LCM,以20场/秒(即1秒钟全屏刷新显示20次)显示,一秒钟仅传输的数据量就高达: 320×240×8×20=11.71875Mb或1.465MB,如果让标准MCS-51系列单片机处理,假设重复使用MOVX指令连续传输这些数据,考虑地址计算时间,至少需要接421.875MHz的时钟才能完成数据的传输,可见处理数据量的巨大。 

  传真的分辨率也就是扫描密度,分辨率越高代表扫描的精度就越高,它可分为垂直分辨率和水平分辨率。垂直分辨率是指垂直水平线上每毫米显示的像素点数,水平分辨率是指平行水平线上每毫米显示的像素点数。按照三类传真机的国际标准规定,水平分辨率为8像素/mm,因此传真机的分辨率一般表示为8像素/mm×垂直像素/mm,一般我们就将水平分辨率省却,只以垂直分辨率来表示分辨率。垂直分辨率主要有标准3.85像素/mm,精细7.7像素/mm、超精细15.4像素/mm三种。 

没有更多推荐了,返回首页