linux驅動移植-LCD驅動基礎


一、LCD基礎知識

1.1 LCD硬件原理

Mini2440裸機開發之LCD基礎我們介紹了LCD的硬件原理,有興趣的可以去看看,這里我們僅僅簡述一下LCD的原理。

下圖是LCD示意圖,里面的每個點就是一個像素點。它里面有一個電子槍,一邊移動,一邊發出各種顏色的光。用動態圖表示如下:

電子槍是如何移動的?

  • 有一條CLK時鍾線與LCD相連,每發出一次CLK(高低電平),電子槍就移動一個像素。

顏色如何確定?

  • 由連接LCD的三組線RGB三原色混合而成:R(Red)、G(Green)、B(Blue)確定。

電子槍如何得知應跳到下一行?

  • 有一條HSYNC信號線與LCD相連,每發出一次脈沖(高低電平),電子槍就跳到下一行,該信號叫做行同步信號。

電子槍如何得知應跳到原點?

  • 有一條VSYNC信號線與LCD相連,每發出一次脈沖(高低電平),電子槍就跳到原點,該信號叫做幀同步信號。

RGB線上的數據從何而來?

  • 內存里面划分一塊顯存(framebuffer),里面存放了要顯示的數據,LCD控制器從里面將數據讀出來,通過RGB三組線傳給電子槍,電子槍再依次打到顯示屏上。

前面的信號由誰發給LCD?

  • 由S3C2440里面的LCD控制器來控制發出信號。

工作原理:

  • LCD屏可以看作是由許多像素構成的,比如320*240就是由320*240個像素構成的,每個像素由RGB三色調和,每種顏色又由多個位組成。比如我們的開發板上的LCD,有320*240個像素,每個像素由RGB三色調和,RGB三色位數分別為:565。
  • S3C2440內集成了LCD控制器,LCD控制器外接LCD,每來一個VLCK,就會從左到右在LCD屏幕上顯示一個像素的顏色,而這一個個像素的顏色就存放在顯存里,在嵌入式領域,一般不會佩戴專門的顯存,而是從內存SDRAM中划分出一部分充當顯存;
  • HSYNC引腳每發出一個脈沖,表示一行的數據開始發送;
  • VSYNC引腳每發出一個脈沖,表示一幀的數據開始發送。

1.2  frambuffer設備

我們在Mini2440裸機開發之LCD編程(GB2312、ASCII字庫制作) 中介紹了如何在LCD顯示屏中顯示一張圖片,其核心步驟就是向framebuffer中寫入圖片數據。

在linux中,如果我們的系統想使用GUI(圖形界面接口),這時LCD設備驅動程序就應該編寫成frambuffer接口,而不是像裸機中那樣只編寫操作底層的LCD控制器接口。

framebuffer是linux系統為顯示設備提供的一個用戶接口,它將顯示緩沖區抽象,屏蔽圖像硬件的底層差異,允許上層應用程序在圖形模式下直接對顯示緩沖區進行操作,用戶應用程序可以通過framebuffer透明地訪問不同類型的顯示設備。

linux抽象出framebuffer這個幀緩沖區可以供用戶應用程序直接讀寫,通過更改framebuffer中的內容,就可以立刻顯示在LCD顯示屏上。

framebuffer是一個標准的字符設備,主設備號是29,次設備號根據緩沖區的數目而定。framebuffer對應/dev/fb%d設備文件。

對用戶程序而言,framebuffer設備它和/dev下面的其它設備沒有什么區別,用戶可以把frameBuffer看成一塊內存,既可以寫,又可以讀。顯示器將根據內存數據顯示對應的圖像界面,這一切都由framebuffer設備驅動來完成。

注意:如果“framebuffer設備”你理解起來比較變扭,你就把它當做LCD設備即可,實際上我認為他們就是一個意思,只是叫法不一樣。

1.3 實現思路

我們在介紹linux framebuffer驅動實現框架之前,我們試想一下,如果讓我們按照之前所學的知識來實現framebuffer設備驅動我們會怎么做呢?

  • 首先我們會動態注冊設備編號;
  • 初始化一個字符設備,並注冊設備的file_operations;
  • 創建字符設備節點;
  • 編寫file_operations成員函數open、write等函數;

這么做當然也是可以的,不過這么存在一個問題,如果我們需要同時注冊多個framebuffer設備,並且每個framebuffer設備具有很多共性的東西,他們的設備操作函數基本相同,只是LCD的屏幕尺寸、LCD控制器時序參數等信息存在差異,那我們應該如何進行抽象,屏蔽不同framebuffer之間的差異呢。

實際上如果采用面向對象編程的話,我們可以這么干,我們抽象出frambebufer設備接口,然后實現一個抽象類繼承該接口,在抽象類中實現一個些共性屬性和方法,大致如下:

實際上linux內核也是這么干的,只不過由於C不支持面向對象,在linux系統中,當系統接入若干個不同型號的LCD設備時,linux內核做了一層抽象,使用fb_info結構體來表示每一個LCD設備的信息以及設備操作方法。

二、framebuffer設備驅動框架圖

framebuffer設備驅動在linux系統框架如下圖:

從上圖可以看出frambebuffer設備驅動主要由兩部分組成:

  • fbmem.c:為應用程序提供了對frambebuffer設備操作的系統調用,同時為硬件層提供了注冊framebuffer設備的接口,比如register_frambebuffer;
  • xxxfb.c:主要實現了framebuffer設備的注冊,實際上就是填充fb_info結構體,關於該結構的含義我們后面介紹。

通過引入fb_info的形式,將硬件相關的部分與文件設備操作分離開,增加了內核代碼的穩定性。我們只需調用register_framebuffer函數注冊一個新的fb_info結構體,即可向內核新增一個framebuffer設備。

三、基礎數據結構

3.1 fb_info結構體

struct fb_info定義在include/linux/fb.h文件中,用於保存我們framebuffer設備信息,其內部提供了對framebuffer設備操作的函數指針:

struct fb_info {
        atomic_t count;
        int node;
        int flags;
        /*
         * -1 by default, set to a FB_ROTATE_* value by the driver, if it knows
         * a lcd is not mounted upright and fbcon should rotate to compensate.
         */
        int fbcon_rotate_hint;
        struct mutex lock;              /* Lock for open/release/ioctl funcs */
        struct mutex mm_lock;           /* Lock for fb_mmap and smem_* fields */
        struct fb_var_screeninfo var;   /* Current var */
        struct fb_fix_screeninfo fix;   /* Current fix */
        struct fb_monspecs monspecs;    /* Current Monitor specs */
        struct work_struct queue;       /* Framebuffer event queue */
        struct fb_pixmap pixmap;        /* Image hardware mapper */
        struct fb_pixmap sprite;        /* Cursor hardware mapper */
        struct fb_cmap cmap;            /* Current cmap */
        struct list_head modelist;      /* mode list */
        struct fb_videomode *mode;      /* current mode */

#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
        /* assigned backlight device */
        /* set before framebuffer registration,
           remove after unregister */
        struct backlight_device *bl_dev;

        /* Backlight level curve */
        struct mutex bl_curve_mutex;
        u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
        struct delayed_work deferred_work;
        struct fb_deferred_io *fbdefio;
#endif

        struct fb_ops *fbops;
        struct device *device;          /* This is the parent */
        struct device *dev;             /* This is this fb device */
        int class_flag;                    /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
        struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
        union {
                char __iomem *screen_base;      /* Virtual address */
                char *screen_buffer;
        };
        unsigned long screen_size;      /* Amount of ioremapped VRAM or 0 */
        void *pseudo_palette;           /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING    0
#define FBINFO_STATE_SUSPENDED  1
        u32 state;                      /* Hardware state i.e suspend */
        void *fbcon_par;                /* fbcon use-only private area */
        /* From here on everything is device dependent */
        void *par;
        /* we need the PCI or similar aperture base/size not
           smem_start/size as smem_start may just be an object
           allocated inside the aperture so may not actually overlap */
        struct apertures_struct {
                unsigned int count;
                struct aperture {
                        resource_size_t base;
                        resource_size_t size;
                } ranges[0];
        } *apertures;

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

部分參數含義如下:

  • count:fb_info的引用計數,fb_open時使其+1,release時使其-1,為0時銷毀;
  • node:全局變量registered_fb中的索引值,注冊的時候分配,通過node可以索引fb_info;
  • flags:一些標志位,有關於硬件加速的,大小端,fb的內存位置(設備或者內存),具體硬件加速的方法,表明哪個使用了硬件加速;
  • var:描述的是LCD屏幕的可變參數,包括可見的分辨率,Bpp(bits_per_pixel),還有具體的時鍾信號,包括bp,fp,vsync,hsync等,可以通過應用層設置也可以驅動層配置,相關設置時序的工具有fbset,還有相關的一些調色板配置;
  • fix:描述的是LCD屏幕的不可參數,不能在用戶層更改。包括framebuffer緩沖區的區里起始位置(一般是顯示控制器DMA起始地址,smem_start),framebuffer的長度(單位為字節,smem_len);
  • monspecs:描述的是顯示器的一些參數,時序,生產日期等,一般這種信息描述在顯示器中的EDID中,通過解析EDID來填充此參數;
  • queue:事件隊列;
  • pixmap,sprite(光標)都是像素圖,注冊framebuffer的時候會默認申請;
  • cmap:設備獨立的 colormap 信息,可以通過 ioctl 的 FBIOGETCMAP 和 FBIOPUTCMAP 命令設置 colormap;
  • modelist:將var參數轉化成video mode,然后存入這個鏈表;
  • mode:一些時序,刷新率掃描方式(vmode)(隔行,逐行),極性(sync);
  • CONFIG_FB_BACKLIGHT:有關於背光曲線以及背光設備注冊,需要注意的是需要在注冊framebuffer之前就對其初始化;
  • CONFIG_FB_DEFERRED_IO:延遲IO,使用缺頁中斷的原理操作,減少FBIOPAN_DISPLAY帶來的系統調用開支;
  • screen_base/screen_buffer:framebuffer緩沖區虛擬地址,u8類型,對應的物理地址保存在fix.smem_start中;
  • screen_size:framebuffer緩沖區大小;
  • fbops:提供具體的fb操作函數,主要是通過fbmem.c中提供的文件操作函數,間接調用fb_ops,主要的操作有fb_check_var,fb_pan_display,fb_mmap,等,以下三個函數提供了繪圖的操作,可以使用系統中的繪圖函數,也可以重寫硬件加速的繪圖函數;
  • device:fb_info的設備父節點,對應即/sys/device/xxx/fb_info;
  • dev:設備指針,注冊framebuffer時創建;
  • pseudo_palette:偽調色板;
  • state:硬件狀態,在fbmem中會設置成suspend以及resume;
  • skip_vt_switch:關於VT switch,是與console切換以及PM相關的;

3.2 fb_info標志位

fb_info標志位定義如下:

/* FBINFO_* = fb_info.flags bit flags */
#define FBINFO_DEFAULT          0
#define FBINFO_HWACCEL_DISABLED 0x0002
        /* When FBINFO_HWACCEL_DISABLED is set:
         *  Hardware acceleration is turned off.  Software implementations
         *  of required functions (copyarea(), fillrect(), and imageblit())
         *  takes over; acceleration engine should be in a quiescent state */

/* hints */
#define FBINFO_VIRTFB           0x0004 /* FB is System RAM, not device. */
#define FBINFO_PARTIAL_PAN_OK   0x0040 /* otw use pan only for double-buffering */
#define FBINFO_READS_FAST       0x0080 /* soft-copy faster than rendering */

/* hardware supported ops */
/*  semantics: when a bit is set, it indicates that the operation is
 *   accelerated by hardware.
 *  required functions will still work even if the bit is not set.
 *  optional functions may not even exist if the flag bit is not set.
 */
#define FBINFO_HWACCEL_NONE             0x0000
#define FBINFO_HWACCEL_COPYAREA         0x0100 /* required */
#define FBINFO_HWACCEL_FILLRECT         0x0200 /* required */
#define FBINFO_HWACCEL_IMAGEBLIT        0x0400 /* required */
#define FBINFO_HWACCEL_ROTATE           0x0800 /* optional */
#define FBINFO_HWACCEL_XPAN             0x1000 /* optional */
#define FBINFO_HWACCEL_YPAN             0x2000 /* optional */
#define FBINFO_HWACCEL_YWRAP            0x4000 /* optional */

#define FBINFO_MISC_USEREVENT          0x10000 /* event request
                                                  from userspace */
#define FBINFO_MISC_TILEBLITTING       0x20000 /* use tile blitting */

/* A driver may set this flag to indicate that it does want a set_par to be
 * called every time when fbcon_switch is executed. The advantage is that with
 * this flag set you can really be sure that set_par is always called before
 * any of the functions dependent on the correct hardware state or altering
 * that state, even if you are using some broken X releases. The disadvantage
 * is that it introduces unwanted delays to every console switch if set_par
 * is slow. It is a good idea to try this flag in the drivers initialization
 * code whenever there is a bug report related to switching between X and the
 * framebuffer console.
 */
#define FBINFO_MISC_ALWAYS_SETPAR   0x40000

/* where the fb is a firmware driver, and can be replaced with a proper one */
#define FBINFO_MISC_FIRMWARE        0x80000
/*
 * Host and GPU endianness differ.
 */
#define FBINFO_FOREIGN_ENDIAN   0x100000
/*
 * Big endian math. This is the same flags as above, but with different
 * meaning, it is set by the fb subsystem depending FOREIGN_ENDIAN flag
 * and host endianness. Drivers should not use this flag.
 */
#define FBINFO_BE_MATH  0x100000
/*
 * Hide smem_start in the FBIOGET_FSCREENINFO IOCTL. This is used by modern DRM
 * drivers to stop userspace from trying to share buffers behind the kernel's
 * back. Instead dma-buf based buffer sharing should be used.
 */
#define FBINFO_HIDE_SMEM_START  0x200000

3.3 fb_ops 

fb_ops里存放時的framebuffer設備操作函數:

/*
 * Frame buffer operations
 *
 * LOCKING NOTE: those functions must _ALL_ be called with the console
 * semaphore held, this is the only suitable locking mechanism we have
 * in 2.6. Some may be called at interrupt time at this point though.
 *
 * The exception to this is the debug related hooks.  Putting the fb
 * into a debug state (e.g. flipping to the kernel console) and restoring
 * it must be done in a lock-free manner, so low level drivers should
 * keep track of the initial console (if applicable) and may need to
 * perform direct, unlocked hardware writes in these hooks.
 */

struct fb_ops {
        /* open/release and usage marking */
        struct module *owner;
        int (*fb_open)(struct fb_info *info, int user);
        int (*fb_release)(struct fb_info *info, int user);

        /* For framebuffers with strange non linear layouts or that do not
         * work with normal memory mapped access
         */
        ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
                           size_t count, loff_t *ppos);
        ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
                            size_t count, loff_t *ppos);

        /* checks var and eventually tweaks it to something supported,
         * DO NOT MODIFY PAR */
        int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

        /* set the video mode according to info->var */
        int (*fb_set_par)(struct fb_info *info);

        /* set color register */
        int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
                            unsigned blue, unsigned transp, struct fb_info *info);

        /* set color registers in batch */
        int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

        /* blank display */
        int (*fb_blank)(int blank, struct fb_info *info);

        /* pan display */
        int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

        /* Draws a rectangle */
        void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
        /* Copy data from area to another */
        void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
        /* Draws a image to the display */
        void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

        /* Draws cursor */
        int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);

        /* wait for blit idle, optional */
        int (*fb_sync)(struct fb_info *info);

        /* perform fb specific ioctl (optional) */
        int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
                        unsigned long arg);

        /* Handle 32bit compat ioctl (optional) */
        int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
                        unsigned long arg);

        /* perform fb specific mmap */
        int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

        /* get capability given var */
        void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
                            struct fb_var_screeninfo *var);

        /* teardown any resources to do with this framebuffer */
        void (*fb_destroy)(struct fb_info *info);

        /* called at KDB enter and leave time to prepare the console */
        int (*fb_debug_enter)(struct fb_info *info);
        int (*fb_debug_leave)(struct fb_info *info);
};

四、fbmem.c源碼分析

fbmem.c是framebuffer設備驅動的核心,它向上給應用程序提供了系統調用的接口,向下對特定的硬件提供了設備注冊接口。

fbmem.c位於drivers/video/fbdev/core路徑下。我們可以在該文件定位到驅動模塊的入口和出口:

module_init(fbmem_init);
module_exit(fbmem_exit);

4.1 入口函數

我們定位到fbmem.c的入口函數,也就是fbmem_init:

/**
 *      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)
{
        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;

err_class:
        unregister_chrdev(FB_MAJOR, "fb");
err_chrdev:
        remove_proc_entry("fb", NULL);
        return ret;
}

簡要分析一下該函數執行流程:

  • 創建/proc/fb文件;

  • 創建字符設備fb,主設備編號為FB_MAJOR(29),注冊file_operations結構體fb_fops;

  • 調用class_create在/sys/class目錄下創建graphics這個類,但是此時並沒有調用device_create在/dev下創建設備節點 ;

可以通過如下命令查看字符設備:

root@zhengyang:/work/sambashare/linux-5.2.8# cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound/midi
 14 sound/dmmidi
 21 sg
 29 fb  // 這個名字來自register_chrdev函數第二個參數
 89 i2c

可以看到,確實是創建了主設備號為29的"fb"字符設備,而這里還沒有創建設備節點,后面會提到,內核將該工作放到register_framebuffer函數里了。

4.2 fb_fops

我們再來看看file_operations結構體fb_fops:

static const struct file_operations fb_fops = {
        .owner =        THIS_MODULE,
        .read =         fb_read,
        .write =        fb_write,
        .unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
        .compat_ioctl = fb_compat_ioctl,
#endif
        .mmap =         fb_mmap,
        .open =         fb_open,
        .release =      fb_release,
#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \
        (defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \
         !defined(CONFIG_MMU))
        .get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
        .fsync =        fb_deferred_io_fsync,
#endif
        .llseek =       default_llseek,
};

下面我們來一一分析這些成員函數。

4.3 fb_open

static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
        int fbidx = iminor(inode);       // 獲取設備節點的次設備號
        struct fb_info *info;            // 定義fb_info指針
        int res = 0;

        info = get_fb_info(fbidx);       // 根據次設備編號獲取fb_info
        if (!info) {
                request_module("fb%d", fbidx);
                info = get_fb_info(fbidx);
                if (!info)
                        return -ENODEV;
        }
        if (IS_ERR(info))
                return PTR_ERR(info);

        mutex_lock(&info->lock);               // 獲取互斥鎖
        if (!try_module_get(info->fbops->owner)) {
                res = -ENODEV;
                goto out;
        }
        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_IO
        if (info->fbdefio)
                fb_deferred_io_open(info, inode, file);
#endif
out:
        mutex_unlock(&info->lock);             // 釋放互斥鎖  
        if (res)
                put_fb_info(info);
        return res;
}

這里我們來看一下get_fb_info函數的實現:

static struct fb_info *get_fb_info(unsigned int idx)
{
        struct fb_info *fb_info;

        if (idx >= FB_MAX)
                return ERR_PTR(-ENODEV);

        mutex_lock(&registration_lock);
        fb_info = registered_fb[idx];
        if (fb_info)
                atomic_inc(&fb_info->count);
        mutex_unlock(&registration_lock);

        return fb_info;
}

可以看到get_fb_info函數將registered_fb數組的第idx個元素賦值給了fb_info,registered_fb是一個struct fb_info結構類型的全局數組:

struct fb_info *registered_fb[FB_MAX] __read_mostly;

這和數組會在register_framebuffer函數中賦值。

經過分析,我們最終會發現fb_open執行的是fbops的操作函數中的fbopen函數。

4.4 fb_read

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
        unsigned long p = *ppos;                    // 讀取起始位置
        struct fb_info *info = file_fb_info(file);  // 獲取fb_info
        u8 *buffer, *dst;
        u8 __iomem *src;
        int c, cnt = 0, err = 0;
        unsigned long total_size;

        if (!info || ! info->screen_base)
                return -ENODEV;

        if (info->state != FBINFO_STATE_RUNNING)
                return -EPERM;

        if (info->fbops->fb_read)
                return info->fbops->fb_read(info, buf, count, ppos); //執行fbops操作函數里的fb_read函數

        total_size = info->screen_size;       // 屏幕尺寸   假設屏幕大小240*320,每個像素占n字節數 則屏幕尺寸為240*320*n

        if (total_size == 0)
                total_size = info->fix.smem_len;    //frmebuffer緩沖區大小

        if (p >= total_size)
                return 0;

        if (count >= total_size)          // 最多把整個屏幕數據全部讀取了
                count = total_size;

        if (count + p > total_size)
                count = total_size - p;

        buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,  // 分配緩沖器 大於一頁的話,按頁大小讀取
                         GFP_KERNEL);
        if (!buffer)
                return -ENOMEM;

        src = (u8 __iomem *) (info->screen_base + p);  // 獲取讀取起始地址(虛擬地址)

        if (info->fbops->fb_sync)
                info->fbops->fb_sync(info);

        while (count) {
                c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;  
                dst = buffer;
                fb_memcpy_fromfb(dst, src, c);    // 一次從src讀取c個字節到dst
                dst += c;
                src += c;

                if (copy_to_user(buf, buffer, c)) {   // 寫回用戶空間buf中,長度為c
                        err = -EFAULT;
                        break;
                }
                *ppos += c;
                buf += c;
                cnt += c;
                count -= c;
        }

        kfree(buffer); // 釋放內存

        return (err) ? err : cnt;
}

可以看到如果提供了fbops操作函數里的fb_read函數,則直接調用info->fbops->fb_read(info, buf, count, ppos)從framebuff緩沖區讀取數據。

否則直接從info->screen_base + ppos地址讀取count字節數據到buf緩沖區。

4.5 fb_write

static ssize_t
fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
        unsigned long p = *ppos;   // 寫入起始位置
        struct fb_info *info = file_fb_info(file); // 獲取fb_info
        u8 *buffer, *src;
        u8 __iomem *dst;
        int c, cnt = 0, err = 0;
        unsigned long total_size;

        if (!info || !info->screen_base)
                return -ENODEV;

        if (info->state != FBINFO_STATE_RUNNING)
                return -EPERM;

        if (info->fbops->fb_write)
                return info->fbops->fb_write(info, buf, count, ppos); // 執行fbops操作函數里的fb_write函數

        total_size = info->screen_size;  // 屏幕尺寸   假設屏幕大小240*320,每個像素占n字節數 則屏幕尺寸為240*320*n

        if (total_size == 0)
                total_size = info->fix.smem_len; //frmebuffer緩沖區大小

        if (p > total_size)
                return -EFBIG;

        if (count > total_size) {
                err = -EFBIG;
                count = total_size;
        }

        if (count + p > total_size) {
                if (!err)
                        err = -ENOSPC;

                count = total_size - p;
        }

        buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
                         GFP_KERNEL); // 分配緩沖器 大於一頁的話,按頁大小寫入
        if (!buffer)
                return -ENOMEM;

        dst = (u8 __iomem *) (info->screen_base + p); // 獲取寫入起始地址(虛擬地址)

        if (info->fbops->fb_sync)
                info->fbops->fb_sync(info);

        while (count) {
                c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
                src = buffer;

                if (copy_from_user(src, buf, c)) {  // 從用戶空間獲取數據到內容
                        err = -EFAULT;
                        break;
                }

                fb_memcpy_tofb(dst, src, c);  // 一次將src中c個字節寫到dst
                dst += c;
                src += c;
                *ppos += c;
                buf += c;
                cnt += c;
                count -= c;
        }

        kfree(buffer); // 釋放內存

        return (cnt) ? cnt : err;
}

可以看到如果提供了fbops操作函數里的fb_write函數,則直接調用iinfo->fbops->fb_write(info, buf, count, ppos)向framebuffer緩沖區寫入數據。

否則直接向從buf中讀取count個字節寫入nfo->screen_base + ppos地址處。

4.6 fb_mmap

static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
        struct fb_info *info = file_fb_info(file);  // 獲取fb_info
        struct fb_ops *fb;
        unsigned long mmio_pgoff;
        unsigned long start;
        u32 len;

        if (!info)
                return -ENODEV;
        fb = info->fbops;
        if (!fb)
                return -ENODEV;
        mutex_lock(&info->mm_lock);
        if (fb->fb_mmap) {
                int res;

                /*
                 * The framebuffer needs to be accessed decrypted, be sure
                 * SME protection is removed ahead of the call
                 */
                vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
                res = fb->fb_mmap(info, vma);
                mutex_unlock(&info->mm_lock);
                return res;
        }

        /*
         * Ugh. This can be either the frame buffer mapping, or
         * if pgoff points past it, the mmio mapping.
         */
        start = info->fix.smem_start;  // framebuffer緩沖區起始地址(物理地址)
        len = info->fix.smem_len;      //  framebuffer緩沖區大小
        mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
        if (vma->vm_pgoff >= mmio_pgoff) {
                if (info->var.accel_flags) {
                        mutex_unlock(&info->mm_lock);
                        return -EINVAL;
                }

                vma->vm_pgoff -= mmio_pgoff;
                start = info->fix.mmio_start;
                len = info->fix.mmio_len;
        }
        mutex_unlock(&info->mm_lock);

        vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
        /*
         * The framebuffer needs to be accessed decrypted, be sure
         * SME protection is removed
         */
        vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
        fb_pgprotect(file, vma, start);

        return vm_iomap_memory(vma, start, len);
}

framebuffer的顯示緩沖區位於linux的內核態地址空間。而在linux中,每個應用程序都有自己的虛擬地址空間,在應用程序中是不能直接訪問物理緩沖區的。

為此,linux在文件操作file_operations結構中提供了mmap函數,可將文件的內容映射到用戶空間。

對應幀緩沖設備,則可以通過映射操作,將屏幕緩沖區的物理地址映射到用戶空間的一段虛擬地址中,之后用戶就可以通過讀寫這段虛擬地址訪問屏幕緩沖區,在屏幕上繪圖。

4.7 register_framebuffer

register_framebuffer函數用於向內核注冊framebuffer設備:

/**
 *      register_framebuffer - registers a frame buffer device
 *      @fb_info: frame buffer info structure
 *
 *      Registers a frame buffer device @fb_info.
 *
 *      Returns negative errno on error, or zero for success.
 *
 */
int
register_framebuffer(struct fb_info *fb_info)
{
        int ret;

        mutex_lock(&registration_lock);
        ret = do_register_framebuffer(fb_info);
        mutex_unlock(&registration_lock);

        return ret;
}

在do_register_farmebuffer之前前后加入了互斥鎖,可以判斷出該操作是線程安全的,我們定位到 do_register_framebuffer函數:

static int do_register_framebuffer(struct fb_info *fb_info)
{
        int i, ret;
        struct fb_event event;
        struct fb_videomode mode;

        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;

        if (num_registered_fb == FB_MAX)   // 已達到最大注冊設備數
                return -ENXIO;

        num_registered_fb++;               // 已注冊設備計數+1
        for (i = 0 ; i < FB_MAX; i++)      // 查找空的數組項 
                if (!registered_fb[i])
                        break;
        fb_info->node = i;               // 設置fb_info在registered_fb數組中的索引號 
        atomic_set(&fb_info->count, 1);  // 引用計數設置為1 
        mutex_init(&fb_info->lock);      // 初始化互斥鎖
        mutex_init(&fb_info->mm_lock);   // 初始化互斥鎖  

        fb_info->dev = device_create(fb_class, fb_info->device,  // 設備創建是在這里完成的,設備名稱為fb%d 可以在/dev下看到fb%d設備 次設備號為i
                                     MKDEV(FB_MAJOR, i), NULL, "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部分參數

        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);              // 初始化雙向鏈表

        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_to_videomode(&mode, &fb_info->var);
        fb_add_videomode(&mode, &fb_info->modelist);
        registered_fb[i] = fb_info;                            // 設置數組第i個元素

        event.info = fb_info;
        if (!lockless_register_fb)
                console_lock();
        else
                atomic_inc(&ignore_console_lock_warning);
        if (!lock_fb_info(fb_info)) {
                ret = -ENODEV;
                goto unlock_console;
        }
        ret = 0;

        fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
        unlock_fb_info(fb_info);
unlock_console:
        if (!lockless_register_fb)
                console_unlock();
        else
                atomic_dec(&ignore_console_lock_warning);
        return ret;
}

do_register_farmebuffer函數首先從registered_fb數組中查找空的數組項,然后填充fb_info結構體,賦給這個空的數組項中。在這里還創建了設備節點(前面創建字符設備未完成的工作)。

從這里我們可以看出,register_framebuffer()函數通過注冊各種各樣的fb_info,來讓內核支持多種framebuffer設備,並且以/dev/fb%d的形式命名。

五、platform設備注冊(s3c2410-lcd)

5.1 LCD相關結構體

我們定位到arch/arm/plat-samsung/include/plat/fb-s3c2410.h頭文件:

struct s3c2410fb_hw {
        unsigned long   lcdcon1;
        unsigned long   lcdcon2;
        unsigned long   lcdcon3;
        unsigned long   lcdcon4;
        unsigned long   lcdcon5;
};

/* LCD description */
struct s3c2410fb_display {
        /* LCD type */
        unsigned type;

        /* Screen size */
        unsigned short width;
        unsigned short height;

        /* Screen info */
        unsigned short xres;
        unsigned short yres;
        unsigned short bpp;

        unsigned pixclock;              /* pixclock in picoseconds */
        unsigned short left_margin;  /* value in pixels (TFT) or HCLKs (STN) */
        unsigned short right_margin; /* value in pixels (TFT) or HCLKs (STN) */
        unsigned short hsync_len;    /* value in pixels (TFT) or HCLKs (STN) */
        unsigned short upper_margin;    /* value in lines (TFT) or 0 (STN) */
        unsigned short lower_margin;    /* value in lines (TFT) or 0 (STN) */
        unsigned short vsync_len;       /* value in lines (TFT) or 0 (STN) */

        /* lcd configuration registers */
        unsigned long   lcdcon5;
};

struct s3c2410fb_mach_info {

        struct s3c2410fb_display *displays;     /* attached displays info */
        unsigned num_displays;                  /* number of defined displays */
        unsigned default_display;

        /* GPIOs */

        unsigned long   gpcup;
        unsigned long   gpcup_mask;
        unsigned long   gpccon;
        unsigned long   gpccon_mask;
        unsigned long   gpdup;
        unsigned long   gpdup_mask;
        unsigned long   gpdcon;
        unsigned long   gpdcon_mask;

        /* lpc3600 control register */
        unsigned long   lpcsel;
};

這里定義了s3c24xx系列SOC關於LCD相關配置的結構體:

  • s3c2410fb_hw:定義了s3c2440 LCD控制寄存器需要配置的值;
  • s3c2410fb_display:定義了開發板所使用的的LCD描述信息,比如LCD的時序參數、LCD屏的寬、高等;
  • s3c2410fb_mach_info :包含了s3c2410fb_display以及s3c2440 GPIO相關信息;

5.2  結構體全局變量

我們定位到 arch/arm/mach-s3c24xx/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          = 240,
        .height         = 320,

        .pixclock       = 166667, /* 每個像素時長,10^12/VCLK */
        .xres           = 240,
        .yres           = 320,
        .bpp            = 16,
        .left_margin    = 20,
        .right_margin   = 8,
        .hsync_len      = 4,
        .upper_margin   = 8,
        .lower_margin   = 7,
        .vsync_len      = 4,
};

static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
        .displays       = &smdk2440_lcd_cfg,
        .num_displays   = 1,
        .default_display = 0,

#if 0
        /* currently setup by downloader */
        .gpccon         = 0xaa940659,
        .gpccon_mask    = 0xffffffff,
        .gpcup          = 0x0000ffff,
        .gpcup_mask     = 0xffffffff,
        .gpdcon         = 0xaa84aaa0,
        .gpdcon_mask    = 0xffffffff,
        .gpdup          = 0x0000faff,
        .gpdup_mask     = 0xffffffff,
#endif

        .lpcsel         = ((0xCE6) & ~7) | 1<<4,
};

可以看到這里聲明了全局變量smdk2440_lcd_cfg、smdk2440_fb_info並進行了初始化,如果我們想支持我們LCD的話,實際上只要修改這些配置信息即可。

5.3  smdk2440_machine_init

linux內核啟動的時候會根據uboot中設置的機器id執行相應的初始化工作,比如.init_machine、.init_irq:

static void __init smdk2440_machine_init(void)
{
        s3c24xx_fb_set_platdata(&smdk2440_fb_info);  // 設置s3c_device_lcd->dev.platform_data=&smdk2440_fb_info
        s3c_i2c0_set_platdata(NULL);

        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));  // s3c2440若干個platform設備注冊 usb host controller、lcd、wdt等
        smdk_machine_init();   // s3c24x0系列若干個platform設備注冊(通用)
}

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END
5.3.1 s3c24xx_fb_set_platdata

這里我們只關注smdk2440_fb_info相關的代碼,我們定位到s3c24xx_fb_set_platdata函數,位於 arch/arm/plat-samsung/devs.c文件中,實際上在這個文件里根據我們內核編譯配置的宏,注冊不同的platform設備,比如這里我們定義了名字為"s3c2410-lcd"的platform設備:

/* LCD Controller */

#ifdef CONFIG_PLAT_S3C24XX
static struct resource s3c_lcd_resource[] = {
        [0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),  // 定義內存資源 起始地址0x4D000000(LCD相關寄存器基地址)、大小為1M
        [1] = DEFINE_RES_IRQ(IRQ_LCD),
};

struct platform_device s3c_device_lcd = {   // 定義platform設備
        .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),
        }
};

void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd) // pd = &smdk2440_fb_info
{
        struct s3c2410fb_mach_info *npd;

        npd = s3c_set_platdata(pd, sizeof(*npd), &s3c_device_lcd);  // 設置s3c_device_lcd->dev.platform_data=&smdk2440_fb_info 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");
        }
}
#endif /* CONFIG_PLAT_S3C24XX */

在s3c24xx_fb_set_platdata這里我們調用了s3c_set_platdata函數,該函數設置s3c_device_lcd->dev.platform_data=&smdk2440_fb_info。

5.3.2 s3c_set_platdata

s3c_set_platdata定義在arch/arm/plat-samsung/platformdata.c文件中:

void __init *s3c_set_platdata(void *pd, size_t pdsize,   // pd = &smdk2440_fb_info , pdev = &s3c_device_lcd
                              struct platform_device *pdev)
{
        void *npd;

        if (!pd) {
                /* too early to use dev_name(), may not be registered */
                printk(KERN_ERR "%s: no platform data supplied\n", pdev->name);
                return NULL;
        }

        npd = kmemdup(pd, pdsize, GFP_KERNEL);
        if (!npd)
                return NULL;

        pdev->dev.platform_data = npd;
        return npd;
}

這個函數主要是用來設置pdev->dev的platform_data成員,是個void *類型,可以給平台driver提供各種數據(比如:GPIO引腳等等)。

5.4 platform設備注冊

我們已經定義了LCD相關的platform_device設備s3c_device_lcd,並進行了初始化,那platform設備啥時候注冊的呢?

我們定位到smdk2440_machine_init中的如下函數:

 platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));

這里利用platform_add_devices進行若干個platform設備的注冊,該函數還是通過調用platform_device_register實現platform設備注冊:

/**
 * platform_add_devices - add a numbers of platform devices
 * @devs: array of platform devices to add
 * @num: number of platform devices in array
 */
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;
}

smdk2440_devices中就包含了s3c_device_lcd:

static struct platform_device *smdk2440_devices[] __initdata = {
        &s3c_device_ohci,
        &s3c_device_lcd,
        &s3c_device_wdt,
        &s3c_device_i2c0,
        &s3c_device_iis,
        &smdk2440_device_eth,
};

六、platform驅動注冊(s3c2410-lcd)

既然注冊了名字為"s3c2410-lcd"的platform設備,那么名字為"s3c2410-lcd"的platform驅動在哪里注冊的呢?

其相關代碼為drivers/video/fbdev/s3c2410fb.c,在該文件里構建了fb_info結構體。我們可以在該文件定位到驅動模塊的入口和出口:

module_init(s3c2410fb_init);
module_exit(s3c2410fb_cleanup);

6.1 入口函數

我們定位到fbmem.c的入口函數,也就是s3c2410fb_init:

int __init s3c2410fb_init(void)
{
        int ret = platform_driver_register(&s3c2410fb_driver);

        if (ret == 0)
                ret = platform_driver_register(&s3c2412fb_driver);

        return ret;
}

看到這里是不是有點意外,這里是通過platform_driver_register函數注冊了一個platform驅動。

在plaftrom總線設備驅動模型中,我們知道當內核中有platform設備的.name名稱和platform驅動s3c2410fb_driver里driver的name相同,會調用到platform_driver里的成員.probe,在這里就是s3c2410fb_probe函數。

static struct platform_driver s3c2410fb_driver = {
        .probe          = s3c2410fb_probe,
        .remove         = s3c2410fb_remove,
        .suspend        = s3c2410fb_suspend,
        .resume         = s3c2410fb_resume,
        .driver         = {
                .name   = "s3c2410-lcd",
        },
};

6.2 s3c2410fb_probe

static int s3c2410fb_probe(struct platform_device *pdev)
{
        return s3c24xxfb_probe(pdev, DRV_S3C2410);
}

定位到s3c24xxfb_probe:smdk2440_fb_info

static int s3c24xxfb_probe(struct platform_device *pdev,
                           enum s3c_drv_type drv_type)
{
        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 = dev_get_platdata(&pdev->dev);      // 這里實際獲取到的就是smdk2440_fb_info,類型為s3c2440fb_mach_info
        if (mach_info == NULL) {
                dev_err(&pdev->dev,
                        "no platform data for lcd, cannot attach\n");
                return -EINVAL;
        }

        if (mach_info->default_display >= mach_info->num_displays) { // 默認使用的LCD索引號 >= 支持的LCD總數
                dev_err(&pdev->dev, "default is %d but only %d displays\n",
                        mach_info->default_display, mach_info->num_displays);
                return -EINVAL;
        }

        display = mach_info->displays + mach_info->default_display;   // 獲取使用的LCD描述信息

        irq = platform_get_irq(pdev, 0);
        if (irq < 0) {
                dev_err(&pdev->dev, "no irq for device\n");
                return -ENOENT;
        }

        fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);  // 分配一個fb_info結構體,額外分配s3c2410fb_info大小的內存,初始化fbinfo->device = &pdev->dev
        if (!fbinfo)
                return -ENOMEM;

        platform_set_drvdata(pdev, fbinfo);             // 設置pdev->dev.driver_data = fbinfo

        info = fbinfo->par;                         // 獲取成員par
        info->dev = &pdev->dev;                     // 初始化par成員
        info->drv_type = drv_type;

        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  // 獲取第一個內存資源,地址范圍0x4D000000~(0x4D000000+1MB)
        if (res == NULL) {
                dev_err(&pdev->dev, "failed to get memory registers\n");
                ret = -ENXIO;
                goto dealloc_fb;
        }
        size = resource_size(res);           // 1MB
        info->mem = request_mem_region(res->start, size, pdev->name); // 動態申請內存
        if (info->mem == NULL) {       // 內存已經被使用
                dev_err(&pdev->dev, "failed to get memory region\n");
                ret = -ENOENT;
                goto dealloc_fb;
        }

        info->io = ioremap(res->start, size);   // 將LCD相關寄存器起始物理地址映射到虛擬地址,並返回虛擬地址
        if (info->io == NULL) {
                dev_err(&pdev->dev, "ioremap() of registers failed\n");
                ret = -ENXIO;
                goto release_mem;
        }

        if (drv_type == DRV_S3C2412)
                info->irq_base = info->io + S3C2412_LCDINTBASE;
        else
                info->irq_base = info->io + S3C2410_LCDINTBASE;

        dprintk("devinit\n");

        strcpy(fbinfo->fix.id, driver_name);  // 設置fb_info成員id為s3c2410fb

        /* Stop the video */
        lcdcon1 = readl(info->io + S3C2410_LCDCON1);   // S3C2410_LCDCON1=0,從而得到LCDCON1寄存器地址 讀取寄存器值
        writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1); // 輸出使能位設置為禁止

        // 設置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;

       // 設置LCD可變參數
        fbinfo->var.nonstd          = 0;
        fbinfo->var.activate        = FB_ACTIVATE_NOW;
        fbinfo->var.accel_flags     = 0;
        fbinfo->var.vmode           = FB_VMODE_NONINTERLACED;

     // 設置LCD操作函數
        fbinfo->fbops               = &s3c2410fb_ops;
        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);    // 申請中斷
        if (ret) {
                dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
                ret = -EBUSY;
                goto release_regs;
        }

        info->clk = clk_get(NULL, "lcd");         // 獲取lcd時鍾 if (IS_ERR(info->clk)) {
                dev_err(&pdev->dev, "failed to get lcd clock source\n");
                ret = PTR_ERR(info->clk);
                goto release_irq;
        }
clk_prepare_enable(info->clk); // 使能時鍾 dprintk("got and enabled clock\n"); usleep_range(1000, 1100); info->clk_rate = clk_get_rate(info->clk); // 獲取時鍾頻率 /* find maximum required memory size for display */ 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; if (fbinfo->fix.smem_len < smem_len) fbinfo->fix.smem_len = smem_len; } /* Initialize video memory */ ret = s3c2410fb_map_video_memory(fbinfo); //為framgebuffer緩沖區動態申請內存空間,物理地址為fbinfo->fix.smem_start,虛擬地址為fbinfo->screen_base,大小為頁對齊(fbinbfo->fix.smem_len) if (ret) { dev_err(&pdev->dev, "Failed to allocate video RAM: %d\n", ret); ret = -ENOMEM; goto release_clock; } dprintk("got video memory\n"); fbinfo->var.xres = display->xres; fbinfo->var.yres = display->yres; fbinfo->var.bits_per_pixel = display->bpp; s3c2410fb_init_registers(fbinfo); // 設置GPIO,配置GPCUP、GPCCON、GPDUP、GPDCON為LCD功能 實際上就是給寄存器賦值,值來自smdk2440_fb_info中設置的值 s3c2410fb_check_var(&fbinfo->var, fbinfo); ret = s3c2410fb_cpufreq_register(info); // 根據LCD可變參數、lefrt_margin、right_margin、以及LCD控制器時鍾頻率(HCLK),計算LCD控制器時序參數,並設置相應控制寄存器值 s3c2410fb_calculate_tft_lcd_regs if (ret < 0) { dev_err(&pdev->dev, "Failed to register cpufreq\n"); goto free_video_memory; } ret = register_framebuffer(fbinfo); // 注冊設備 if (ret < 0) { dev_err(&pdev->dev, "Failed to register framebuffer device: %d\n", ret); goto free_cpufreq; } /* create device files */ ret = device_create_file(&pdev->dev, &dev_attr_debug); if (ret) dev_err(&pdev->dev, "failed to add debug attribute\n"); dev_info(&pdev->dev, "fb%d: %s frame buffer device\n", fbinfo->node, fbinfo->fix.id); return 0; free_cpufreq: s3c2410fb_cpufreq_deregister(info); free_video_memory: s3c2410fb_unmap_video_memory(fbinfo); release_clock: clk_disable_unprepare(info->clk); clk_put(info->clk); release_irq: free_irq(irq, info); release_regs: iounmap(info->io); release_mem: release_mem_region(res->start, size); dealloc_fb: framebuffer_release(fbinfo); return ret; }

這段代碼是在太長了,我直接挑重點說:

  • 分配一個struct fb_info結構體變量fbinfo;
  • 設置fbinfo:
    • 設置LCD不可變參數;fbinfo->fix;
    • 設置LCD可變參數;fbinfo->var;
    • 設置LCD操作函數;fbinfo->fbops;
    • 設置其他成員,fbinfo->flags、fbinfo->pseudo_palette、fbinfo->clk_rate;
  • 硬件相關的操作:
    • 將LCD相關寄存器起始物理地址映射到虛擬地址,並返回虛擬地址;
    • 設置中斷:通過request_irq注冊中斷,中斷類型為IRQ_LCD,中斷處理函數為s3c2410fb_irq
    • 設置framebuffer緩沖器地址:通過s3c2410fb_map_video_memory申請framebuffer顯存,然后設置LCDSADDR1、LCDSADDR2、LCDSADDR3寄存器;
    • 配置引腳:通過s3c2410fb_init_registers設置GPIO端口C和GPIO端口D用於LCD;
    • 設置LCD控制器時序參數:通過s3c2410fb_cpufreq_register設置LCDCON1、LCDCON2、LCDCON3、LCDCON4、LCDCON5寄存器;
  • 注冊fb_info結構體;

需要注意的是這里並沒有打開背光燈,開不開背光燈影響不大。

6.2.1 struct s3c2410fb_info

struct s3c2410fb_info定義在drivers/video/fbdev/s3c2410fb.h:

struct s3c2410fb_info {
        struct device           *dev;  // 設備基類 struct clk              *clk;  // lcd時鍾 struct resource         *mem;  // i/o 內存地址(虛擬地址) void __iomem            *io;   // lcd控制器寄存器基地址(虛擬地址) void __iomem            *irq_base;

        enum s3c_drv_type       drv_type;
        struct s3c2410fb_hw     regs;   // lcd控制寄存器

        unsigned long           clk_rate;   // 時鍾頻率
        unsigned int            palette_ready;

#ifdef CONFIG_ARM_S3C24XX_CPUFREQ
        struct notifier_block   freq_transition;
#endif

        /* keep these registers in case we need to re-write palette */
        u32                     palette_buffer[256];
        u32                     pseudo_pal[16];
};

6.3 s3c2410fb_cpufreq_register

該函數主要用來初始化LCD控制器時序參數:

static inline int s3c2410fb_cpufreq_register(struct s3c2410fb_info *info)
{
        info->freq_transition.notifier_call = s3c2410fb_cpufreq_transition;

        return cpufreq_register_notifier(&info->freq_transition,
                                         CPUFREQ_TRANSITION_NOTIFIER);
}

定位到函數s3c2410fb_cpufreq_transition:

static int s3c2410fb_cpufreq_transition(struct notifier_block *nb,
                                        unsigned long val, void *data)
{
        struct s3c2410fb_info *info;
        struct fb_info *fbinfo;
        long delta_f;

        info = container_of(nb, struct s3c2410fb_info, freq_transition);
        fbinfo = dev_get_drvdata(info->dev);

        /* work out change, <0 for speed-up */
        delta_f = info->clk_rate - clk_get_rate(info->clk);

        if ((val == CPUFREQ_POSTCHANGE && delta_f > 0) ||
            (val == CPUFREQ_PRECHANGE && delta_f < 0)) {
                info->clk_rate = clk_get_rate(info->clk);
                s3c2410fb_activate_var(fbinfo);
        }

        return 0;
}

最終定位到s3c2410fb_activate_var:

/* s3c2410fb_activate_var
 *
 * activate (set) the controller from the given framebuffer
 * information
 */
static void s3c2410fb_activate_var(struct fb_info *info)
{
        struct s3c2410fb_info *fbi = info->par;
        void __iomem *regs = fbi->io;
        int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT;
        struct fb_var_screeninfo *var = &info->var;
        int clkdiv;

        clkdiv = DIV_ROUND_UP(s3c2410fb_calc_pixclk(fbi, var->pixclock), 2);  

        dprintk("%s: var->xres  = %d\n", __func__, var->xres);
        dprintk("%s: var->yres  = %d\n", __func__, var->yres);
        dprintk("%s: var->bpp   = %d\n", __func__, var->bits_per_pixel);

        if (type == S3C2410_LCDCON1_TFT) {   // 走這里 TFT真彩
                s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);  // 計算LCD控制器控制寄存器的值 --clkdiv;
                if (clkdiv < 0)
                        clkdiv = 0;
        } else {
                s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);
                if (clkdiv < 2)
                        clkdiv = 2;
        }

        fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);

        /* write new registers */

        dprintk("new register set:\n");
        dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);
        dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);
        dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);
        dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);
        dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);

        writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,   // 向LCD控制寄存器寫入對應值
                regs + S3C2410_LCDCON1);           // LCD控制器寄存器基地址 + 控制寄存器偏移 (虛擬地址)
        writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);
        writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);
        writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);
        writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);

        /* set lcd address pointers */
        s3c2410fb_set_lcdaddr(info);

        fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,    // 使能LCD
        writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
}

這里我們簡單介紹一下CLKVAL的計算方法,這里計算方式如下:

$$clkdiv =\frac{clk\_rate * pixclock} {2^{12}*2}-1 $$

clk_rate為HCLK頻率,也就是100HMz,而 pixclock為每個像素時長,由於我們采用T35屏幕像素時鍾信號$VCLK=6.4MHz$,對應每像素時長1/(6.4*10^6)*10^12=156250皮秒。

所以:

$$VCLK=\frac{10^{12}}{pixclock}$$

因此可以計算出:

$$CLKVAL=HCLK/VCLK/2−1=\frac{HCLK*pixclock}{2*10^{12}}-1$$

6.4 s3c2410fb_calculate_tft_lcd_regs

s3c2410fb_calculate_tft_lcd_regs函數用於計算LCD控制寄存器的值,並保存到regs對應成員變量中。

/* s3c2410fb_calculate_tft_lcd_regs
 *
 * calculate register values from var settings
 */
static void s3c2410fb_calculate_tft_lcd_regs(const struct fb_info *info,
                                             struct s3c2410fb_hw *regs)
{
        const struct s3c2410fb_info *fbi = info->par;
        const struct fb_var_screeninfo *var = &info->var;

        switch (var->bits_per_pixel) {
        case 1:
                regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
                break;
        case 2:
                regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
                break;
        case 4:
                regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
                break;
        case 8:
                regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
                regs->lcdcon5 |= S3C2410_LCDCON5_BSWP |
                                 S3C2410_LCDCON5_FRM565;
                regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP;
                break;
        case 16:
                regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
                regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;
                regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;
                break;
        case 32:
                regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;
                regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP |
                                   S3C2410_LCDCON5_HWSWP |
                                   S3C2410_LCDCON5_BPP24BL);
                break;
        default:
                /* invalid pixel depth */
                dev_err(fbi->dev, "invalid bpp %d\n",
                        var->bits_per_pixel);
        }
        /* update X/Y info */
        dprintk("setting vert: up=%d, low=%d, sync=%d\n",
                var->upper_margin, var->lower_margin, var->vsync_len);

        dprintk("setting horz: lft=%d, rt=%d, sync=%d\n",
                var->left_margin, var->right_margin, var->hsync_len);

        regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1) |
                        S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |
                        S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |
                        S3C2410_LCDCON2_VSPW(var->vsync_len - 1);

        regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin - 1) |
                        S3C2410_LCDCON3_HFPD(var->left_margin - 1) |
                        S3C2410_LCDCON3_HOZVAL(var->xres - 1);

        regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len - 1);
}

七、platform總線設備驅動(s3c2410-lcd)

我們已經介紹了:

  • name為s3c2410-lcd的platform設備的注冊;
  • name為s3c2410-lcd的platform驅動的注冊;

我們把這兩部分代碼整理成如下框框,方便查看:

從這張圖我們可以學習linux內核platform總線設備驅動的編寫,以及如何將各部分代碼進行拆分放到linux各個目錄結構下。

八、修改內核自帶的LCD驅動

8.1 修改smdk2440_lcd_cfg

這里主要就是設置LCD時序相關參數,我們在Mini2440裸機開發之LCD基礎中介紹過型號為LCD-P35(LQ035Q1DG04和ZQ3506_V0手冊通用)的時序參數設置。這里我們以另外一款LCD為例,型號為LCD-T35(TD035STEB4),參考TD035STEB4 LCD數據手冊上的參數性能,見下表:

LCD 大小為240×320,16BPP數據格式,則:$$HOZVAL=240-1,LINEVAL=240-1$$
水平同步信號的脈寬、前肩和后肩分別取10、10和20,則:$$HSPW=10-1=9,HFPD=10−1=9,HBPD=20−1=19$$
垂直同步信號的脈寬、前肩和后肩分別取2、2和2,則:$$VSPW=2-1=1,VFPD=2−1=1,VBPD=2−1=1$$
HCLK的頻率為100MHz,要想驅動像素時鍾信號為6.4MHz的LCD屏,則通過上式計算CLKVAL值:
$$CLKVAL=HCLK/VCLK/2−1=100/6.4/2−1=6.8$$

結果CLKVAL為6.8MHZ,取整后(值為7)放入寄存器LCDCON1中相應的位置即可。由於CLKVAL進行了取整,因此我們把取整后的值代入上式,重新計算VCLK,得到$VCLK=6.25MHz$。
修改arch/arm/mach-s3c24xx/mach-smdk2440.c文件,定義宏:

/*  LCD T35參數設定 */
#define     LCD_WIDTH   240             /* LCD面板的行寬 */
#define     LCD_HEIGHT  320             /* LCD面板的列寬 */

#define     VSPW        1                /* 通過計算無效行數垂直同步脈沖寬度決定VSYNC脈沖的高電平寬度 */
#define     VBPD        1                /* 垂直同步周期后的無效行數 */
#define     LINEVAL     (LCD_HEIGHT-1)  /* LCD的垂直寬度-1 */
#define     VFPD        1                /* 垂直同步周期前的的無效行數 */

#define     CLKVAL      7              /* VCLK = HCLK / [(CLKVAL  + 1)  × 2] */

#define     HSPW        9                /* 通過計算VCLK的數水平同步脈沖寬度決定HSYNC脈沖的高電平寬度 */
#define     HBPD        19               /* 描述水平后沿為HSYNC的下降沿與有效數據的開始之間的VCLK周期數 */
#define     HOZVAL      (LCD_WIDTH-1)    /* LCD的水平寬度-1 */
#define     HFPD        9                /* 水平后沿為有效數據的結束與HSYNC的上升沿之間的VCLK周期數 */

修改中smdk2440_lcd_cfg:

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          = LCD_WIDTH,
        .height         = LCD_HEIGHT,

        .pixclock       = 156250, /* 每個像素時長,10^12/VCLK */
        .xres           = LCD_WIDTH,
        .yres           = LCD_HEIGHT,
        .bpp            = 16,
        .left_margin    = HFPD,      // HFPD
        .right_margin   = HBPD,      // HBPD   
        .hsync_len      = HSPW,      // HSPW 
        .upper_margin   = VBPD,      // VBPD
        .lower_margin   = VFPD,      // VFPD
        .vsync_len      = VSPW,      // VSPW
};

8.2 修改smdk2440_fb_info 

修改arch/arm/mach-s3c24xx/mach-smdk2440.c中smdk2440_lcd_cfg:

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,
        .gpccon_mask    = 0xffffffff,
        .gpcup          = 0xffffffff,
        .gpcup_mask     = 0xffffffff,
        .gpdcon         = 0xaaaaaaaa,
        .gpdcon_mask    = 0xffffffff,
        .gpdup          = 0xffffffff,
        .gpdup_mask     = 0xffffffff,
#endif

        .lpcsel         = ((0xCE6) & ~7) | 1<<1,   // 第一位設置為1 選擇輸出分片率類型0:320 * 240  1:240*320
};

8.3 配置啟動logo

執行如下命令:

root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig

配置內核,顯示啟動logo:

Device Drivers  --->
    Graphics support  --->
        [*] Bootup logo --->
        Frame buffer Devices  --->
               <*> Support for frame buffer devices --->
                     <*> S3C2410 LCD framebuffer support      // 支持S3C2410、S3C2440

保存文件,輸入文件名s3c2440_defconfig,在當前路徑下生成s3c2440_defconfig:存檔:

mv s3c2440_defconfig ./arch/arm/configs/

8.4 編譯內核

此時重新執行:

make distclean
make s3c2440_defconfig    
make uImage V=1

將uImage復制到tftp服務器路徑下:

 cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

8.5 燒錄內核

開發板uboot啟動完成后,內核啟動前,按下任意鍵,進入uboot,可以通過print查看uboot中已經設置的環境變量。

設置開發板ip地址,從而可以使用網絡服務:

SMDK2440 # set ipaddr 192.168.0.105
SMDK2440 # save
Saving Environment to NAND...
Erasing NAND...

Erasing at 0x40000 -- 100% complete.
Writing to NAND... OK
SMDK2440 # ping 192.168.0.200
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 08:00:3e:26:0a:5b
operating at unknown: 0 mode
Using dm9000 device
host 192.168.0.200 is alive

設置tftp服務器地址,也就是我們ubuntu服務器地址:

set serverip 192.168.0.200
save

下載內核到內存,並寫NAND FLASH:

tftp 30000000 uImage
nand erase.part kernel
nand write 30000000 kernel

運行結果如下:

SMDK2440 # tftp 30000000 uImage
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 08:00:3e:26:0a:5b
operating at unknown: 0 mode
Using dm9000 device
TFTP from server 192.168.0.200; our IP address is 192.168.0.188
Filename 'uImage'.
Load address: 0x30000000
Loading: *#################################################################
     #################################################################
     #################################################################
     ##############################################################
     429.7 KiB/s
done
Bytes transferred = 3766128 (397770 hex)
SMDK2440 # nand erase.part kernel

NAND erase.part: device 0 offset 0x60000, size 0x400000

Erasing at 0x60000 --   3% complete.
Erasing at 0x80000 --   6% complete.
Erasing at 0xa0000 --   9% complete.
Erasing at 0xc0000 --  12% complete.
Erasing at 0xe0000 --  15% complete.
Erasing at 0x100000 --  18% complete.
Erasing at 0x120000 --  21% complete.
Erasing at 0x140000 --  25% complete.
Erasing at 0x160000 --  28% complete.
Erasing at 0x180000 --  31% complete.
Erasing at 0x1a0000 --  34% complete.
Erasing at 0x1c0000 --  37% complete.
Erasing at 0x1e0000 --  40% complete.
Erasing at 0x200000 --  43% complete.
Erasing at 0x220000 --  46% complete.
Erasing at 0x240000 --  50% complete.
Erasing at 0x260000 --  53% complete.
Erasing at 0x280000 --  56% complete.
Erasing at 0x2a0000 --  59% complete.
Erasing at 0x2c0000 --  62% complete.
Erasing at 0x2e0000 --  65% complete.
Erasing at 0x300000 --  68% complete.
Erasing at 0x320000 --  71% complete.
Erasing at 0x340000 --  75% complete.
Erasing at 0x360000 --  78% complete.
Erasing at 0x380000 --  81% complete.
Erasing at 0x3a0000 --  84% complete.
Erasing at 0x3c0000 --  87% complete.
Erasing at 0x3e0000 --  90% complete.
Erasing at 0x400000 --  93% complete.
Erasing at 0x420000 --  96% complete.
Erasing at 0x440000 -- 100% complete.
OK
SMDK2440 # nand write 30000000 kernel

NAND write: device 0 offset 0x60000, size 0x400000
 4194304 bytes written: OK
下載完成后,重啟開發板,內核啟動完成后會在顯示屏上看到啟動logo:

在LCD參數設置過程中,為了查看寄存器設置的參數,我在drivers/video/fbdev/s3c2410fb.c中多處地方輸出了寄存器的值信息,這樣就可以在linux啟動過程中看到寄存器的值,從而知道寄存器值有沒有設置成功.

比如在s3c24xxfb_probe函數最后加入:

  printk("lcdcon[1] = 0x%08lx\n", info->regs.lcdcon1);
  printk("lcdcon[2] = 0x%08lx\n", info->regs.lcdcon2);
  printk("lcdcon[3] = 0x%08lx\n", info->regs.lcdcon3);
  printk("lcdcon[4] = 0x%08lx\n", info->regs.lcdcon4);
  printk("lcdcon[5] = 0x%08lx\n", info->regs.lcdcon5);

截取部分內核啟動輸出信息如下:

map_video_memory: clear (ptrval):00026000
map_video_memory: dma=339c0000 cpu=(ptrval) size=00026000
got video memory
gpcup     = 0xffffffff
gpcup     = 0xaaaaaaaa
gpcup     = 0xffffffff
gpcup     = 0xaaaaaaaa
gpcup     = 0xffffffff
gpccon    = 0xaaaaaaaa
cpdup     = 0xffffffff
gpdcon    = 0xaaaaaaaa
s3c2410fb_activate_var: var->xres  = 240
s3c2410fb_activate_var: var->yres  = 320
s3c2410fb_activate_var: var->bpp   = 16
LCDSADDR1 = 0x19ce0000
LCDSADDR2 = 0x19cf2c00
LCDSADDR3 = 0x000000f0
Console: switching to colour frame buffer device 30x40
s3c2410-lcd s3c2410-lcd: fb0: s3c2410fb frame buffer device
lcdcon[1] = 0x00000779
lcdcon[2] = 0x014fc041
lcdcon[3] = 0x0098ef09
lcdcon[4] = 0x00000009
lcdcon[5] = 0x00000f09 

8.6 演示

修改根文件系統inittab文件:

root@zhengyang:/work/nfs_root/rootfs# cd /work/nfs_root/rootfs
root@zhengyang:/work/nfs_root/rootfs# vim etc/inittab

添加如下代碼:

tty1::askfirst:-/bin/sh  #重啟一個sh終端,並將信息輸出到tty1設備

重新啟動開發板,LCD被點亮,並有“Please press Enter to activate this console.”提示字樣。

運行如下命令:

 echo hello > /dev/tty1 

此時在LCD可以看到由hello顯示出來。

安裝linux驅動移植-輸入子系統示例中介紹的案例驅動:

[root@zy:/]# insmod button_dev.ko
button_dev: loading out-of-tree module taints kernel.
button driver init
input: Unspecified device as /devices/virtual/input/input0
register irq

隨便按下K1、K2、...,可以在LCD看到輸出信息:

此外我們可以重定位控制台到LCD設備,重新啟動開發板,在uboot運行過程中按下任意鍵,然后設定啟動參數:

set bootargs "noinitrd console=tty1 root=/dev/nfs rw nfsroot=192.168.0.200:/work/nfs_root/rootfs ip=192.168.0.105:192.168.0.200:192.168.0.1:255.255.255.0::eth0:off"
save

重啟開發板,此時啟動輸出信息就會輸出到LCD顯示屏上:

參考文章

[1]十二、Linux驅動之LCD驅動

[2]15.linux-LCD層次分析(詳解)

[3]Linux LCD Frambuffer 基礎介紹和使用(1)

[4]Linux驅動開發 (framebuffer驅動)

[5]Linux-FrameBuffer fb_info結構體解析申請以及注冊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM