[Linuxドライバー]----LCDディスプレイドライバー

目次

1. LCDドライバーフレームワーク

1.1LCDドライバーの概要

1.2Fbmem.c 分析

1.3 基礎となるハードウェアドライバーを分析する

3. ハードウェア関連の操作

3.1 ハードウェアピン

3.2 ハードウェアピンの構成

2. 物理アドレスのマッピング: ioremap()

3.設定ピン

3.3 パレット

5. 参考リンク


1. LCDドライバーフレームワーク

1.1LCDドライバーの概要

仮説

app: open("/dev/fb0", ...) メジャーデバイス番号: 29、マイナーデバイス番号: 0

-------------------------------------------------- ------------

カーネル:

fb_open

int fbidx = iminor(inode);

struct fb_info *info = = registered_fb[0];

アプリ: read()

-------------------------------------------------- -------------

カーネル:

fb_read

int fbidx = iminor(inode);

struct fb_info *info = registered_fb[fbidx];

if (情報 -> fbops -> fb_read)

return info->fbops->fb_read(info, buf, count, ppos);

src = (u32 __iomem *) (info->screen_base + p);

dst = バッファ;

*dst++ = fb_readl(src++);

copy_to_user(buf, バッファ, c)

 

1.2Fbmem.c 分析

「エントリ関数」—— fbmem_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;
}

「fbmem.c」は汎用ファイルであるため、file_operations 構造体の .read などの関数を直接使用することはできません。作成クラスはデバイス クラスの下にデバイスを作成しません。デバイスは実際のハードウェアがある場合にのみこのクラスで作成されます。 . . 作成されたデバイスは「register_framebuffer()」で確認できます。

fb_info->dev = device_create(fb_class, fb_info->device,

        MKDEV(FB_MAJOR, i), "fb%d", i);

fb_open:

APP:open(): メモリ内に何があるか知りたい場合は、file_operations fb_fops 構造体を確認してください。

「.read = fb_open」

static int
fb_open(struct inode *inode, struct file *file)
{
    int fbidx = iminor(inode); //次设备号
    struct fb_info *info;
    int res = 0;
    .........
    if (!(info = registered_fb[fbidx]))  //从registered_fb数组中赋值给info
        return -ENODEV;
    ......
    if (info->fbops->fb_open) {
        res = info->fbops->fb_open(info,1);
        if (res)
            module_put(info->fbops->owner);
    }
    return res;
}

fb_read:

APP:read(): メモリ内に何があるか知りたい場合は、file_operations fb_fops 構造体を見てください。

「.read = fb_read」

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    int fbidx = iminor(inode);
    struct fb_info *info = registered_fb[fbidx];
    ......
    if (info->fbops->fb_read)
        return info->fbops->fb_read(info, buf, count, ppos);
    /*没有read函数*/
    total_size = info->screen_size; //屏幕大小
    .....
    
    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
             GFP_KERNEL); //分配缓冲区
    if (!buffer)
        return -ENOMEM;

    src = (u32 __iomem *) (info->screen_base + p);  /*screen_base 是指
    显存的基地址。这里是读源 src 等于显存的*/

    while (count) {
        .....
        dst = buffer;
        for (i = c >> 2; i--; )
            *dst++ = fb_readl(src++);/*读源(从显存基地址+P 偏移)那里读到一 
个数据放到目标“*dst++”里。dst 是 buffer,buffer 是 kmalloc()上面分 
配的空间。*/
        ..........
        if (copy_to_user(buf, buffer, c)) { //把数据拷贝到用户空间
            err = -EFAULT;
            break;
        }
        ......
    }

    kfree(buffer);

    return (err) ? err : cnt;
}

上記のことから、「.open」と「.read」は両方とも構造体、fb_info 構造体に依存していることがわかります。この構造体は、配列「registered_fd[]」から「マイナーデバイス番号」を添え字として取得したfd_info構造体です。

struct fb_info *info = registered_fb[fbidx];

Q: registered_fb はどこに設定されますか?

register_framebuffer

register_framebufferを分析します

int
register_framebuffer(struct fb_info *fb_info)
{
    int i;
    struct fb_event event;
    struct fb_videomode mode;
    ............
    for (i = 0 ; i < FB_MAX; i++) //FB_MAX:32
        if (!registered_fb[i]) //找出一个空项
            break;
    fb_info->node = i;

    fb_info->dev = device_create(fb_class, fb_info->device,
                     MKDEV(FB_MAJOR, i), "fb%d", i);/*在fb_class类下
   创建设备*/
      ..........
}

1.3 基礎となるハードウェアドライバーを分析する

例として path:/drivers/video/s3c2440.c (エントリ関数から)

int __devinit s3c2410fb_init(void)
{
    //注册一个平台驱动(总线设备驱动模型)
    return platform_driver_register(&s3c2410fb_driver);
}

s3c2410fb_プローブ:

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;//根据pdev获得"mach_info"信息

    mregs = &mach_info->regs;

    irq = platform_get_irq(pdev, 0);//根据pdev获得"irq "中断信息

    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), 
                                &pdev->dev);
    if (!fbinfo) {
        return -ENOMEM;
    }
     /*硬件相关设置*/
    info = fbinfo->par;
    info->fb = fbinfo;
    info->dev = &pdev->dev;

    ......

    /* 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);
    /*硬件相关的操作:*/
    s3c2410_gpio_setpin(S3C2410_GPB0, 0); // back light control 

    info->mach_info         = pdev->dev.platform_data;

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

    /* Initialize video memory */
    ret = s3c2410fb_map_video_memory(info);
    if (ret) {
        printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
        ret = -ENOMEM;
        goto release_clock;
    }
    .........
    return 0;
    .........
release_clock:
    clk_disable(info->clk);
    clk_put(info->clk);
}

LCD の解像度情報を取得するには: 上から下に見てください (fbmem.c)

fbmem.c は「.ioctl」を参照します。

fbmem_init->fb_ops->ioctl(fb_ioctl)

static int 
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
     unsigned long arg)
{
        int fbidx = iminor(inode);
        struct fb_info *info = registered_fb[fbidx];
        ......
        case FBIOGET_VSCREENINFO:/*GET 获得.V(var)可变的.SCREEN 屏幕.INFO 信 
息*/
        return copy_to_user(argp, &info->var,
                    sizeof(var)) ? -EFAULT : 0;
        ......
}

info->var に画面情報があります。

 

 2. LCDハードウェアパラメータ設定

ドライバーの作成を正式に開始する前に、まず次のようにハードウェアの具体的な動作を理解してください。

 

LCD コントローラを設定する場合、LCD の「VCLK」は異なります。周波数を速くまたは遅く設定するには、LCD コントローラを調整する必要があります。ハードウェア動作プロセスは次のとおりです。

1. LCD のマニュアルに従って LCD コントローラを設定します。(クロックパラメータなど)

2. ビデオ メモリを割り当て、LCD コントローラにビデオ メモリのアドレスを伝えます。カラーフォーマットについても説明します。ピクセルを表すために何バイトが使用されるかなど。

3. 2440 関連操作: LCD 用のピンの構成 チップのピンは入力および出力として構成でき、また LCD ピンとして構成することもできます。

static struct fb_info *s3c_led;

static void lcd_setting(void)
{
    /* 2.1 设置固定的参数 */
    strcpy(s3c_led->fix.id,"mylcd");       //名字
    s3c_led->fix.smem_len = 240*320*16/8;  //显存的长度
    s3c_led->fix.typ      = FB_TYPE_PACKED_PIXELS;
    s3c_led->fix.visual   = FB_VISUAL_TRUECOLOR;  /*TFT*/
    s3c_led->fix.line_length = 240*16/8; //固定信息里面,一行的长度

    /* 2.2 设置可变的参数 */
    s3c_led->var.xres           = 240; //x方向的分辨率240
    s3c_led->var.yres           = 320; //y方向的分辨率240
    s3c_led->var.xres_virtual   = 240; //x方向的虚拟分辨率240
    s3c_led->var.yres_virtual   = 320; //y方向的虚拟分辨率240
    s3c_led->bits_per_pixel     = 16;  //BPP

    /* RGB:565 */
    s3c_lcd->var.red.offset     = 11; //RGB---565:红色的偏移值从bit11开始
    s3c_lcd->var.red.length     = 5;  //RGB---565,红色占5位
    
    s3c_lcd->var.green.offset   = 5; //RGB---565:红色的偏移值从bit5开始
    s3c_lcd->var.green.length   = 6; //RGB---565,红色占6位

    s3c_lcd->var.blue.offset    = 0; //RGB---565:红色的偏移值从bit0开始
    s3c_lcd->var.blue.length    = 5; //RGB---565,红色占5位
    
    s3c_lcd->var.activate = FB_ACTIVATE_NOW;  //默认值
    /* 2.3 设置操作函数 */
    s3c_lcd->fbops        = &s3c_lcdfb_ops; 
    /* 2.4 其他的设置 */
    //s3c_lcd->pseudo_palette =; //
    //s3c_lcd->screen_base  = ;  /* 显存的虚拟地址 */ 
    s3c_lcd->screen_size   = 240*324*16/8;  //显存的大小
}

3. ハードウェア関連の操作

3.1 ハードウェアピン

画面で使用されるインターフェイスは次のとおりです。

 

 上の回路図は、LCD によって使用されるレジスタが GPCx GPGx GPDx であり、コンフィギュレーション ピンが使用されることを示しています。

バックライト回路:

「KEYBOARD」ピン: KEYBOARD --- J6 TOUT0/GPB0 (GPB0 は出力ピンとして構成する必要があります: GPBCON |=

GPB0_out;)

3.2 ハードウェアピンの構成

1. マッピングピンを定義する

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;

2. 物理アドレスのマッピング: ioremap()

void * __ioremap(unsigned long phys_addr, unsigned long size)

ioremap() は、簡単にアクセスできるように IO アドレス空間をカーネルの仮想アドレス空間にマップします。

    gpbcon = ioremap(0x56000010, 8);
    gpbdat = gpbcon+1;
    gpccon = ioremap(0x56000020, 4);
    gpdcon = ioremap(0x56000030, 4);
    gpgcon = ioremap(0x56000060, 4);

3.設定ピン

    *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 */

a. LCD コントローラーの構成

パッケージ化されたLCDコントローラー:

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;     // 0X4D000028 STN:蓝色查找表
    unsigned long   reserved[9]; //保留字节
    unsigned long   dithmode;    // 0X4D00004C STN:抖动模式
    unsigned long   tpal;
    unsigned long   lcdintpnd;
    unsigned long   lcdsrcpnd;
    unsigned long   lcdintmsk;
    unsigned long   lpcsel;
};
static volatile struct lcd_regs *lcd_regs; //结构体指针
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));

b. ビデオメモリを割り当てる

メモリからビデオ メモリ (フレーム バッファ) を割り当て、そのアドレスを LCD コントローラに伝えると、LCD コントローラはセットアップ後、自動的にビデオ メモリからピクセル値を取得し、VD0 ~ VD23 を介して LCD 画面にピクセル データを送信します。次に、次のビデオを取り出して最初からやり直し、ビデオ メモリの最後のビデオを取得すると (ビデオ メモリはそれほど大きくありません)、ビデオ メモリの先頭に戻ります。ビデオ メモリのアドレスは連続している必要があります。kmolloc() を使用してビデオ メモリを割り当てることはできません ->dma_alloc_writecombine()

static inline 
void dma_alloc_writecombine(struct device *dev, size_t size,dma_addr_t *dma_addr, gfp_t gfp)
{
return dma_alloc_attrs(dev, size, dma_addr, gfp,
                        DMA_ATTR_WRITE_COMBINE);
}
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);//lcdsaddr1显存地址
lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + 
                        s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;lcdsaddr2帧缓冲器地址
lcd_regs->lcdsaddr3 = (240*16/16); 

/* 启动LCD */
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器  */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1;     /* 输出高电平, 使能背光 */

3.3 パレット

パレットはメモリの一部であり、LCD コントローラがビデオ メモリから取得した 8 ビット データは、現時点では LCD 画面に直接送信されません。代わりに、実際の 16 ビット データを格納するメモリ ブロックであるパレットに送信されます。

LCD コントローラは、ディスプレイ メモリから 8 ビット データを取得した後、それをインデックスとして使用します。8 ビット値が 100 の場合、「パレット」(配列など) から 100 番目の項目を見つけて、 100番目の項目で16bitが出ます。液晶画面に送信されます。

パレットは色が入った絵の具箱のようなもので、どの色を入手したいのかをインデックスから検索してパレットのカラーボックスにどの色が入っているかを調べ、見つけたら取り出します。ここでのパレットはメモリのブロックであり、いくつかの色が事前に設定されています。LCD コントローラがそれを使用する必要がある場合、ディスプレイ メモリから 8 ビット データを読み取り、この 8 ビット データを「インデックス」値として使用します。 、「パレット」配列から実際の色を取り出し、この実際の色を LCD ハードウェアに送信します。

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 inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}

5. 参考リンク

https://gitee.com/change-ly/linux_project

おすすめ

転載: blog.csdn.net/weixin_45281868/article/details/126736261