EDA365电子论坛网

标题: 基于S3C2440的嵌入式Linux驱动——Framebuffer子系统解读 [打印本页]

作者: baqiao    时间: 2020-6-24 15:55
标题: 基于S3C2440的嵌入式Linux驱动——Framebuffer子系统解读
本文将介绍Framebuffer子系统& M1 F! y- k' L$ Q. L9 ^/ K

7 [- M9 K4 V7 L, U9 ~目标平台:TQ2440 CPU:s3c2440
/ s0 s  J7 B, s2 \, x0 S( L7 a6 v" r, T
LCD设备:3.5英寸,分辨率320X240
6 k0 p3 u- d6 q) y4 `' [. ~( n  R. C; Z) x3 e' E

% I' Q  z' i% `. n( F) d" q. U& _" ^3 q  Z: j& l
1. 概述
- d$ \( s# Z) c  q; E; B9 \$ j7 x  \5 N. p. B
Framebuffer,中文名字是帧缓冲,这个帧也就是一副图像所需要的数据。因此,帧缓冲其实就是LCD设备的驱动程序。Linux中,framebuffer子系统框架如下:
3 @, S9 p' _% ?/ _" Z$ a% w  @6 s# Q$ c

2 B- J4 Y% t9 h) L, O+ C
* f' \: a0 w9 W# _4 x# d5 j) ?1 u核心层的代码以fbmem.c为主,核心层包括许多与具体硬件无关的代码,并且提供了API给用户空间。用户空间使用系统调用,系统调用会使用相应的API函数,最后会调用驱动层实现功能。对于不同的设备,驱动层的代码将有所不同。1 E/ F8 m9 q! q. V6 i- n

6 P& e. z; \: Z: n' _接下来的内容中,首先给出framerbuffer使用的数据结构;随后简单描述framerbuffer核心层;最后,针对S3C2440,对驱动代码进行分析。7 L$ J. g2 C8 y) r9 Y

3 l! Q+ F& b9 B% H, r  d# E3 S2. 数据结构3 S2 J) |/ A$ k) s2 @
2.1 fb_info 结构' l  W; U7 e- C9 A+ a( d- Q
  该结构是内核用于描述一个特定framebuffer设备。其中包含了几个重要的结构,将在下面介绍。
$ y" T% ^; O. F* w5 V$ I6 u& K+ V
( G& o6 T" u$ n- c4 a/ p- a. p% G  下列代码位于include/linux/fb.h8 J  W- r" P1 J; I

2 c0 d6 p1 n0 r. d" I$ @struct fb_info {4 g' d" N+ B( u! \5 Q6 a% W; E5 @
        int node;2 _& }' v& H# p$ A3 ^% ^7 S
        int flags;% H7 Z: D$ U, l: _$ D, ]
        struct mutex lock;                /* Lock for open/release/ioctl funcs */
; l  H6 c, [# W2 V        struct fb_var_screeninfo var;        /* Current var */
6 ^' g0 @, ^! ]        struct fb_fix_screeninfo fix;        /* Current fix */! \) v2 Q* F5 G  H; i5 Z
        struct fb_monspecs monspecs;        /* Current Monitor specs */
7 P& z2 y. G4 `: @0 M8 }' s        struct work_struct queue;        /* Framebuffer event queue */! A7 s0 o  G$ u" y& M- W" U
        struct fb_pixmap pixmap;        /* Image hardware mapper */
- F0 U; E) }" m* t: G        struct fb_pixmap sprite;        /* Cursor hardware mapper */
' [5 D! Q7 @& v- j. H" ?        struct fb_cmap cmap;                /* Current cmap */
- A# C6 O$ c, Z% }* c7 x* n# h4 O        struct list_head modelist;      /* mode list */
$ W/ W% D! A# t" h' @' p+ t1 w, |        struct fb_videomode *mode;        /* current mode */) Q- Q( T' p6 w
4 y3 b' y. t% M0 x1 x1 O2 d
#ifdef CONFIG_FB_BACKLIGHT
$ A# S# L' _$ j4 w. r. @& {6 o        /* assigned backlight device */0 ^! I4 W+ E9 n) T9 P/ m+ G
        /* set before framebuffer registration,
: ^8 B; r* H0 J& X. I9 `! T/ {           remove after unregister */
+ c" `' ]" ^  C3 z0 _/ @        struct backlight_device *bl_dev;6 X; W# _# T; y6 X1 `

! N$ ~6 {+ l6 Y% t: g. I" q) o        /* Backlight level curve */8 o) N4 v* z5 |9 G$ ~4 S( q
        struct mutex bl_curve_mutex;       
) N' l! }' ]6 A5 L. X" Y        u8 bl_curve[FB_BACKLIGHT_LEVELS];
; E$ D+ y* p/ a#endif
; m; N3 A2 B6 x#ifdef CONFIG_FB_DEFERRED_IO
3 F% `2 g7 U5 o        struct delayed_work deferred_work;+ i; B- d" y' `4 x; m6 _) i
        struct fb_deferred_io *fbdefio;: ^4 k0 c, ^# Z5 c
#endif
7 J% t  z0 J  n- g
$ i. w7 a" f& T7 `        struct fb_ops *fbops;
/ Q1 g$ z- P8 j1 v        struct device *device;                /* This is the parent */
0 Q* l5 t; b! |1 T0 t, F- K        struct device *dev;                /* This is this fb device */$ k+ W0 m8 {1 ^0 \
        int class_flag;                    /* private sysfs flags */& b" \% U0 y) ^; k9 l" `
#ifdef CONFIG_FB_TILEBLITTING) ~4 L' u& P7 c2 d# _; b) o: m
        struct fb_tile_ops *tileops;    /* Tile Blitting */
5 Q) a! n; {+ m2 ^% f#endif
. r1 |2 F/ h6 a0 b        char __iomem *screen_base;        /* Virtual address */5 G# n6 r, \. P/ F" @
        unsigned long screen_size;        /* Amount of ioremapped VRAM or 0 */
- j# x: L3 g. |: a# p# @; ?, N        void *pseudo_palette;                /* Fake palette of 16 colors */ 2 J9 W; f# s3 c+ V+ ]( v
#define FBINFO_STATE_RUNNING        00 z+ }6 ^5 `5 z8 d6 G2 V
#define FBINFO_STATE_SUSPENDED        13 L% B$ O3 z8 V+ _0 B
        u32 state;                        /* Hardware state i.e suspend */
* ~! Z5 H/ J" r) R5 Y  j        void *fbcon_par;                /* fbcon use-only private area */
- S# w; L0 A3 b9 D/ R: y        /* From here on everything is device dependent */# N# ^/ P7 I9 [8 x5 W5 z" T
        void *par;       
2 [" r+ H7 \5 d; l* Y4 ~% L6 S};- W" Q- t3 r* y  F0 q1 E! p  v$ W
2.2 fb_fix_screeninfo结构
* W; h8 _% N0 f# q* `  该数据结构是不可改变的,也就是说用户空间不能改变该结构中的任何成员,在核心层我们将会看到这点。, C4 ^+ H5 o  G9 V+ O+ r! _
7 ~; W5 m, n6 c; O
  下列代码位于include/linux/fb.h
: B8 \. k! W) y( o: J# I- O, m/ O5 p1 K( U3 }: c
struct fb_fix_screeninfo {/ a; k& g/ j$ `, q/ ]' r" U
        char id[16];                        /* identification string eg "TT Builtin" */( g0 U$ e0 U9 h7 e* `/ \. J
        unsigned long smem_start;        /* Start of frame buffer mem */8 D! ^0 x; k- J# s6 ?$ i8 f2 w4 |
                                        /* (physical address) */
; A+ T/ c: L* ?3 D! N7 v3 L& h5 C. h        __u32 smem_len;                        /* Length of frame buffer mem */
# _) t! C5 x, _6 g, d        __u32 type;                        /* see FB_TYPE_*                */: g+ Y0 {% d8 r8 `; f
        __u32 type_aux;                        /* Interleave for interleaved Planes */
8 T, Q* p" f1 w  p2 ~" t        __u32 visual;                        /* see FB_VISUAL_*                */ ' g. }! j* g- l2 T9 {, h* U9 b7 T
        __u16 xpanstep;                        /* zero if no hardware panning  */  d# A6 @+ s! G, P' V# O, l3 ]
        __u16 ypanstep;                        /* zero if no hardware panning  */
+ `5 e( m( z" f( z* C% o7 z4 E7 r        __u16 ywrapstep;                /* zero if no hardware ywrap    */1 m! @& n0 v$ \3 s9 J! J6 {
        __u32 line_length;                /* length of a line in bytes    */9 p6 Y0 U5 d4 M$ h. m9 R. `' v
        unsigned long mmio_start;        /* Start of Memory Mapped I/O   */4 `9 M) s: r3 g! B
                                        /* (physical address) */
  f7 j8 \! z* R: V' a3 {        __u32 mmio_len;                        /* Length of Memory Mapped I/O  */
( J$ S" D+ l, o  g5 p* ]        __u32 accel;                        /* Indicate to driver which        */
6 @9 o% }% E3 A! K- U, {                                        /*  specific chip/card we have        */. @9 ~  O+ d6 P: O7 w6 G6 L+ w+ c
        __u16 reserved[3];                /* Reserved for future compatibility */- u$ \8 D/ A, F1 t
};
% w9 m2 z9 B2 q; N* ]( Z- A* F' w! i: W7 @
2.3 fb_var_screeninfo结构
  Z9 o# m; y- [: K; g3 b  该数据结构是可以改变的,也就是说用户空间可以改变该结构中的成员。该数据结构中的很多成员就由板级信息复制而来,在驱动代码中我们将会看到这点。
$ N6 ~# Q$ D9 b8 B
1 {% G" {7 u4 C( y) b$ n4 {/ o  下列代码位于include/linux/fb.h1 x% c* V1 F% w# F! L. P
; Y$ Z) b6 `$ |7 q1 [/ x
struct fb_var_screeninfo {: Z5 l1 n% p/ w$ e: Q/ W6 E" `/ L
        __u32 xres;                        /* visible resolution                */
  i. k: n/ [4 U' Y1 W6 K        __u32 yres;
9 _, _+ q5 E2 H! _7 D7 g        __u32 xres_virtual;                /* virtual resolution                */
& _- U$ X3 q# ^1 g; o        __u32 yres_virtual;
7 m/ G1 V( f8 P, Z        __u32 xoffset;                        /* offset from virtual to visible */
/ C6 d7 H. _9 {- v  R- ]        __u32 yoffset;                        /* resolution                        */
5 s) ?% T2 a3 p$ G2 D# o, d- @( y5 D" ]$ z0 z, |
        __u32 bits_per_pixel;                /* guess what                        */
: J( l4 G- v# T* L8 O" x        __u32 grayscale;                /* != 0 Graylevels instead of colors */% Q) ?4 X9 `! ~4 L, y" l
  D" a  g2 ^+ b) E! a% v
        struct fb_bitfield red;                /* bitfield in fb mem if true color, */
, h- Z4 @9 l: v+ o3 ?        struct fb_bitfield green;        /* else only length is significant */, S" \5 z! i% J. i, N0 ~
        struct fb_bitfield blue;
( B3 \9 M9 k) ]2 Q* h- N        struct fb_bitfield transp;        /* transparency                        */        $ Q" Y1 n. V, e; X. N1 o# N$ o

& u2 }" n2 [* ?! {3 M8 m        __u32 nonstd;                        /* != 0 Non standard pixel format */
' o& J& {* i7 I7 b) z& H3 F1 n! I2 F# i2 l
        __u32 activate;                        /* see FB_ACTIVATE_*                */
+ {. a  |' Z! Y3 D8 j& y% I& @
( J( g! N# O% R% `        __u32 height;                        /* height of picture in mm    */
* x" Y# w& P  ^8 a8 ^6 H        __u32 width;                        /* width of picture in mm     */
3 `, {3 A- m% `! [2 p& G7 s- }7 @2 Z' w% S. I) K: j3 g
        __u32 accel_flags;                /* (OBSOLETE) see fb_info.flags */
9 K' Z) z% a4 k9 l
6 x9 `- c. t& e        /* Timing: All values in pixclocks, except pixclock (of course) */& b0 P8 M! R5 Q) e7 ^6 L' ^1 p0 I
        __u32 pixclock;                        /* pixel clock in ps (pico seconds) */
( @5 I) p! }5 h0 ^        __u32 left_margin;                /* time from sync to picture        */
( Y6 h" w2 j% D0 Q9 B9 [        __u32 right_margin;                /* time from picture to sync        */
7 u0 [5 n. Q) v! x        __u32 upper_margin;                /* time from sync to picture        */
* K+ R3 [/ O. Q3 v$ i$ v2 ~0 W/ u  \        __u32 lower_margin;
. q  ^; n, C/ Y* H% A1 G, P0 e        __u32 hsync_len;                /* length of horizontal sync        */
1 w5 A/ K7 U3 f5 _* e; t        __u32 vsync_len;                /* length of vertical sync        */) w9 y( g) w; M/ |6 R; U
        __u32 sync;                        /* see FB_SYNC_*                */) w1 ~2 B; `6 E8 R4 w
        __u32 vmode;                        /* see FB_VMODE_*                */; C9 q' V' X5 U7 ~8 d
        __u32 rotate;                        /* angle we rotate counter clockwise */
, Y7 u+ K: i: e# m" ^' j2 W8 Q% a/ v        __u32 reserved[5];                /* Reserved for future compatibility */) T' h" Q9 v" A8 u$ x
};9 W. H5 R) p: a1 h! n& y4 m

# H. w0 j1 }8 e2.4 fb_ops结构: @- R0 N: ~! c
  该结构描述了用于fb_info的方法,这些方法中有些是要驱动程序提供的,而有些可以使用内核提供的方法。1 U& S, ^/ W8 l# ?

  k( n, D+ D3 R/ p: B+ i 下列代码位于include/linux/fb.h. Z' M2 L4 d1 J( r

- U  M6 m5 i4 l/*$ O* I. D! S5 i
* Frame buffer operations" ]+ b9 Z' A+ _" ?4 \
*, j/ R2 A8 a$ q( l$ _  ^. @; Q) `
* LOCKING NOTE: those functions must _ALL_ be called with the console0 m- f& h" L% P/ G5 F: b4 K
* semaphore held, this is the only suitable locking mechanism we have
7 d+ ^  A0 N3 p0 M; c6 V' @+ ^ * in 2.6. Some may be called at interrupt time at this point though.
0 o1 V. g( f9 d) S */
2 Q' A! q, P! ]0 r1 R
' l! x# H/ i: B: K; x; ^struct fb_ops {  m0 R% t6 S2 d9 h$ K9 w2 S
        /* open/release and usage marking */
! Z) v! ]0 C* P, s$ k' S  F        struct module *owner;# W# {. h: m6 X" t2 L6 _9 s! X
        int (*fb_open)(struct fb_info *info, int user);4 {' T1 Z& P& E! u; q$ f: ~/ Q
        int (*fb_release)(struct fb_info *info, int user);9 H: q7 Z) W1 T& _6 S
; Q! f' I; K1 O' }% P
        /* For framebuffers with strange non linear layouts or that do not
. D4 O1 p3 e7 R         * work with normal memory mapped access
: v- h7 \" i0 m" E: T0 A+ p         */: R7 Z  u: a! O  G# Z
        ssize_t (*fb_read)(struct fb_info *info, char __user *buf,' {4 N5 N& M  i% c1 y
                           size_t count, loff_t *ppos);
2 \, C9 S! `+ B1 n6 l9 e0 I" Z: q        ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
& ]* X6 `/ e) r& p5 ~                            size_t count, loff_t *ppos);2 K! W& o; F) U+ w" c0 @! d

8 h2 w2 u2 S- h        /* checks var and eventually tweaks it to something supported,
% v9 W6 F( w6 _" a         * DO NOT MODIFY PAR */$ U, f) N2 Y( V' N
        int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
% a6 y! x/ ~: w" s/ K
8 e& r) I6 i; D0 Y        /* set the video mode according to info->var */
* O8 D8 c8 y  p2 i3 Z9 A+ }7 q        int (*fb_set_par)(struct fb_info *info);: n5 W( Y0 V7 x& N) v8 c

6 |- o: v9 G# }5 L' l' C        /* set color register */
+ @7 t  e9 @/ I; l  R* K( @/ q        int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
' |! A7 O' w. Y& p                            unsigned blue, unsigned transp, struct fb_info *info);, I1 ~- `6 c% [* Q+ Z* a% M6 Y
/ K4 Q% m" [9 O5 S
        /* set color registers in batch */
8 c! {: n9 j3 [1 F# a* @, L+ }        int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);- V- M8 i& W" w/ y+ M  `5 n, L
( n7 [3 Z2 i$ ], M
        /* blank display */
! H9 l( Q  E1 q, t, K5 j" q! X9 Y- a        int (*fb_blank)(int blank, struct fb_info *info);
  E4 d2 l: t  Z, x/ ?3 y3 H) `4 B5 h7 }: W9 v
        /* pan display */' o4 P, N1 y  ]' O9 w$ ~
        int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
4 O: V9 Q8 g% n& m; b
0 S1 q* \- p+ z        /* Draws a rectangle */
, y0 a* I% }$ w* G6 `4 B        void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);  e; y7 v) L/ B6 M
        /* Copy data from area to another */' ?6 r- O  ~/ N  t6 y
        void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
2 M. `. E+ p# ^        /* Draws a image to the display */) V" l" Z. o; Y; v4 b
        void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
. P5 y! T1 x9 Z5 a
) I3 T7 u# H5 A& @        /* Draws cursor */
( ~4 Q9 B8 R/ z1 z) E  @# c2 z: ]        int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
; ^/ N+ [" N/ `
! c/ f: ?% ]/ o# `/ l        /* Rotates the display */# v% i3 @/ O+ h- T3 E+ f4 `5 I& Y
        void (*fb_rotate)(struct fb_info *info, int angle);
- f( Q1 l4 o8 K9 ], ~, M+ {% D1 v
        /* wait for blit idle, optional */
! s$ P5 C" @3 t* o# n* m        int (*fb_sync)(struct fb_info *info);
& s& Q# R" y. Y
% I; K6 f0 j- _8 k2 M        /* perform fb specific ioctl (optional) */8 A) r* [+ I& u; X
        int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,% x$ u& O1 h: t0 R; N6 E7 `
                        unsigned long arg);) Z% j7 n( x  v& V' L0 Y

5 ^3 U. C1 I+ V# {" \, @        /* Handle 32bit compat ioctl (optional) */+ x+ t. U% Z( S2 R0 R0 u
        int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,4 ^; i0 x% d+ U& [) {
                        unsigned long arg);
  F* i* |3 n; \7 I  c# C0 T, j1 X& i9 K; t
        /* perform fb specific mmap */
$ h( E, s0 n! S$ y% G7 ]        int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);; X, f0 t0 g+ z- \

9 W* @: m9 X+ z; B2 E        /* save current hardware state */; Z4 r1 p, m. z( o+ z
        void (*fb_save_state)(struct fb_info *info);
+ d3 W9 r7 L! F! {3 T6 p, O; o# P4 i% e5 e: ?. l" ]
        /* restore saved state */
# g& ?( k4 d! C6 _" W% p' t        void (*fb_restore_state)(struct fb_info *info);
9 i# V; A+ g) H* r  W- l/ M: M6 u' y
9 a3 x3 v7 R% ^' O. D6 |        /* get capability given var */2 c. r) p! b' y- a- z+ q
        void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
9 [& W0 Y/ n9 _. P, C6 t                            struct fb_var_screeninfo *var);/ D! V8 C% e) ]
};
! j, q0 p8 ^+ R. Z: Q( O; x9 n% r/ B5 b4 y; e) _; @3 J. E
5 P1 _/ M7 X3 K7 y+ s7 x
3. frambuffer核心层; ?* Q2 h/ @% o4 f
首先来看下frmaebuffer子系统的初始化函数。
' p3 X9 ?& ~5 a+ R6 P, G4 e$ F. ~
3.1 fbmem_init和fbmem_exit
' M2 q! \0 \# ?- S3 z下列代码位于drivers/video/fbmem.c
" v4 ?6 {% J( w8 @0 P
- V  S0 }* G# ?" `* V1 {5 P7 q- \0 U/**
3 v6 q% G0 h2 r) `3 T, f *        fbmem_init - init frame buffer subsystem
! e8 W/ u8 }1 l  R *
- j( C& d7 _* C" o* n, } *        Initialize the frame buffer subsystem.+ T3 L+ f$ g# [0 H7 u" ^  z
*7 [4 d" Z/ X* v3 S; B
*        NOTE: This function is _only_ to be called by drivers/char/mem.c.
' L3 b! `  t( f$ Q9 e *
8 h  @, f; H( B6 |  ?- v. i$ y */
' B, y; I3 `8 Z/ q8 W2 Z# u0 q9 V( T& h* I# [* ]3 }
static int __init
3 i' P, r% O. sfbmem_init(void)
! O" s) }! E, @4 l6 ?* p# [{
% I: V$ Y* ?2 h; C9 h( ]2 m# D        proc_create("fb", 0, NULL, &fb_proc_fops);! L% X0 T6 j' |7 e

! r+ V' l1 t5 U        if (register_chrdev(FB_MAJOR,"fb",&fb_fops))                /*注册字符设备,major=29*/
, F' s+ @+ Y" s2 y( r6 B$ @                printk("unable to get major %d for fb devs\n", FB_MAJOR);: G: c; n: w6 o8 F* U

2 e  Q* M/ m$ Q" ]        fb_class = class_create(THIS_MODULE, "graphics");        /*创建类*/4 u# ~, m4 I. _0 p  I; [
        if (IS_ERR(fb_class)) {/ |* ?7 F+ p% E6 T+ w6 v
                printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
' O/ |9 C% _3 ]/ s                fb_class = NULL;( ?3 L0 b# s5 R+ U/ _) \/ E2 ?
        }+ C) U+ W' g/ _, X% W% i
        return 0;0 g- @: }% v) d9 r. N9 N1 U7 P2 M
}
& Q- N  F! z/ G8 k( g
5 S7 q! w, N9 ~#ifdef MODULE
5 l0 Q: d  X& v( Tmodule_init(fbmem_init);
1 r% g6 r5 `5 ~+ Q* |2 sstatic void __exit
( x# |3 U) d/ h- Hfbmem_exit(void)
  B2 [- [2 N( H7 I{
2 j9 C* ~7 ^+ T; k, n% G        remove_proc_entry("fb", NULL);
7 k. e: ~( M% o+ g+ B4 d! {        class_destroy(fb_class);
0 n( J& f1 h- b' \        unregister_chrdev(FB_MAJOR, "fb");( H1 e  \* @& f* n
}
' z: B$ Y0 e, M! s& s) @6 `& W* y8 o9 s: i2 m+ z
module_exit(fbmem_exit);/ W. L) ?+ V" @1 ^% R% [9 N
MODULE_LICENSE("GPL");& ?, \$ Z3 v; K+ @- ^* \
MODULE_DESCRIPTION("Framebuffer base");% t( w. c& S4 w* V1 j( T
#else$ z8 i! @5 q$ l4 @% x8 t
subsys_initcall(fbmem_init);. W. N* ~/ Y& e3 P6 B, `
#endif
4 b: T. `! T9 K2 y  _% n
/ P+ h$ G: v" U# P/ j# H  l; B* Ustatic const struct file_operations fb_fops = {8 l3 g( J  ~) X" q! d; H& B, M$ o
    .owner =    THIS_MODULE,8 ~, f+ }) q, J; H
    .read =        fb_read,
1 E: D( o3 [& r. }5 |# ]0 k% P    .write =    fb_write,4 A6 m  H& m; ]( \3 T' J
    .unlocked_ioctl = fb_ioctl,5 W8 c( T2 e; ]$ K+ Y/ M. O, d
#ifdef CONFIG_COMPAT% L6 `' k0 t9 N
    .compat_ioctl = fb_compat_ioctl,- y+ _$ d9 D/ {& G$ j; F
#endif
; s+ J9 R* p2 ^6 w9 U    .mmap =        fb_mmap,9 h$ w! a1 {7 d
    .open =        fb_open,9 X( z' i! n/ n. h9 s$ ]( d$ q$ y6 B; f
    .release =    fb_release,
3 d2 _+ u+ n$ U3 n. K#ifdef HAVE_ARCH_FB_UNMAPPED_AREA6 Y" f2 \" L9 Y
    .get_unmapped_area = get_fb_unmapped_area,  H" Z% l( c8 ]! H( }
#endif
( b2 A, e, J0 z2 ?+ }& _- [#ifdef CONFIG_FB_DEFERRED_IO
* F2 ?5 M$ `+ {  H1 C5 W: j; g3 s" G    .fsync =    fb_deferred_io_fsync,* X* v7 T, Y, g  i3 a
#endif
3 E9 I  M, i$ g" w6 N6 a# g; H$ ~; _};
/ F! b- a5 G! ^
* }# v+ A9 ]* m' n+ G2 f我们看到,如果不是作为模块,那么该初始化程序将在subsys_initcall阶段被调用。初始化时,仅仅注册了一个字符设备,并创建了一个类。通过字符设备,提供了API给用户空间,包open,release,write,read等。
0 c9 }  n7 p7 X; n' o& W' K7 a随后我们看看如何分配一个fb_info结构。
1 N2 X% t; @9 D
+ K: s' s8 [( t7 _' i# G3.2 framebuffer_alloc
5 H! U" y# p" b9 j1 B下列代码位于drivers/video/fbmem.c2 k# n7 P: ~  S
4 O. e# f  v* [4 w; E
/**
/ \5 ~0 Y- }5 Q" [ * framebuffer_alloc - creates a new frame buffer info structure9 N$ [) v8 g) Y5 T+ b
*: v4 v9 x2 X4 Y5 [- S
* @size: size of driver private data, can be zero
1 h; y% I$ o8 D7 w# X0 n; O * @dev: pointer to the device for this fb, this can be NULL. f5 Y: g* ~. L
*6 }: y' ?7 _4 L0 R* @
* Creates a new frame buffer info structure. Also reserves @size bytes
% X1 v9 ^# d% Y) e  w; | * for driver private data (info->par). info->par (if any) will be
; r% Q5 h5 k; L9 g- H8 l& s * aligned to sizeof(long).
3 g) O. p1 v2 {( ~& p *1 @5 f' \3 M, k$ }9 X. J
* Returns the new structure, or NULL if an error occured.
9 p5 ~$ |6 e- c5 v *
- e; D9 X' ]6 A) I2 L *// U+ C! L# e; N) y7 S3 _
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)0 l4 o  j5 c2 q
{
( m+ G" u, `. N# z- u- U/ \# [4 F. I# r#define BYTES_PER_LONG (BITS_PER_LONG/8): }; o- d0 h- c1 C1 q  b
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
; t# G8 @4 X1 C2 }3 a. r        int fb_info_size = sizeof(struct fb_info);0 _& M0 G1 Z' J3 b3 `8 u) @7 j) B
        struct fb_info *info;
% Y2 n7 G% b9 Q2 Z4 [+ V! D        char *p;
+ v. T1 I/ F5 n) d5 I
$ a! ]3 w$ k; Z  ~1 e        if (size)5 x# D& K; n! U1 h6 c0 n
                fb_info_size += PADDING;
* Q$ k. `2 T+ k- K) `2 J2 S
/ j  Z% F3 i  B        p = kzalloc(fb_info_size + size, GFP_KERNEL);
, f2 ~3 ~* P3 M2 l/ Y5 t- `
8 o- I. S4 n- I2 \! T: U        if (!p)
; X5 B) V+ P1 a0 H                return NULL;5 {& T2 B3 e' M
* p  l+ ^+ z1 R5 K# X+ `4 x# u( a
        info = (struct fb_info *) p;
2 [6 K1 g1 w, j) Q" Y
+ i! q+ N3 a+ W1 x: R        if (size)" }" K0 X3 `5 `/ n) q7 }6 W: V1 r
                info->par = p + fb_info_size;% q! U+ ~1 ~6 h3 c0 Z) H
0 d- k& j9 c* ?& E  P5 O5 g
        info->device = dev;! |/ Y/ G9 _/ _. ^! u# |
# M. s) B) r' m6 B
#ifdef CONFIG_FB_BACKLIGHT
/ M. W/ `4 d" F        mutex_init(&info->bl_curve_mutex);( |2 y2 M' F4 {4 m% F
#endif+ r. j6 [* Z8 _  c2 D7 t2 r

* O' C. T0 {- J. S. c        return info;1 b" j6 o' A# X: V. @
#undef PADDING( n- u7 {( Z6 ]; g: X; a' S
#undef BYTES_PER_LONG
* x4 S& \" n& c5 S7 F6 Q% _* C}$ J8 V. z& e1 p- G
EXPORT_SYMBOL(framebuffer_alloc);
& }3 O- w* \# \" R! f! h& V0 S在进行分配时,根据参数size的大小,分配了b_info_size + size的空间,然后让fb_info->par指向size的空间。因此par所指向的空间可视为设备特有的数据。
  ~: e7 L) x1 X在分配了fb_info结构之后,需要将它注册到内核中。注册由register_framebuffer完成。我们来看下。2 `! P8 B1 V+ J. s

3 C* ~9 k: e' r( N3.3 register_framebuffer
2 s* N/ v" E  ^: S0 p/ s% b下列代码位于drivers/video/fbmem.c
: |/ L; o3 j' E5 g' L. X3 X
+ |" J+ w5 q: \- J. p( f3 [; n/**4 g/ A" W* u& a% }( C) j) b* x
*        register_framebuffer - registers a frame buffer device: ?. i" f2 P9 b% y( v
*        @fb_info: frame buffer info structure: g% U; x7 F1 v! q4 m9 `
*3 P, M# m6 i7 M7 ~# L
*        Registers a frame buffer device @fb_info.
; _7 H/ l/ }6 Y' w5 Z0 P" l: P *
) m6 u! @3 o6 w3 J *        Returns negative errno on error, or zero for success.
& P6 k( B& D+ H: b8 h8 Y *
/ E* \8 D, U  r* `- z' N */' H3 r0 Z2 B: T8 i% f9 Z) V: ]0 V

% l' p; `5 P+ u7 W3 R) X( ~int
5 o/ g9 v6 L& k2 `# wregister_framebuffer(struct fb_info *fb_info)* E) N) W( L  q. \$ n
{
- E8 P' m8 `$ b, O4 C        int i;8 k& N$ C3 R7 c9 Q+ L6 A
        struct fb_event event;. v4 l. V# T9 J9 b4 p4 I3 t& C
        struct fb_videomode mode;
3 Q2 y1 A5 w8 G1 ]6 C* `2 q, f4 U, s, I. k( S
        if (num_registered_fb == FB_MAX) /*最多32个FB*/- Z( d; h. \5 X+ P
                return -ENXIO;
7 d6 N/ A- b  L+ u" f+ W$ N! B( O9 F) w! K  X
        if (fb_check_foreignness(fb_info))8 L4 ~) o( B% u( e. r: c* u
                return -ENOSYS;- R8 u' z" D$ U
+ ]% X, P) ?, u$ R* [# L9 w$ L
        num_registered_fb++;                /*对注册的FB计数*/  N2 I, }: W' e) Q
        /*寻找第一个空位*/1 |: Z9 H9 e+ Q- n
        for (i = 0 ; i < FB_MAX; i++)/*FB_MAX=32,也就是最多32个framebuffer*/
4 A2 U  K! O; T& @                if (!registered_fb)        /*struct fb_info *registered_fb[FB_MAX]*/1 A! o+ ]6 N% B2 S7 I7 O$ s5 `
                        break;0 H% O3 N8 U1 t, T) S0 V1 R
        fb_info->node = i;9 q1 A  Z1 l3 L7 C( X3 f
        mutex_init(&fb_info->lock);        /*初始化互斥体*/
; s  u& }% x- y- p  x: a6 b6 i4 K
1 v+ [2 r/ T% e9 P0 p        fb_info->dev = device_create(fb_class, fb_info->device,/*创建设备节点,节点名为fbx*/
- g/ W) p3 _( ?6 T+ p% n/ E, K- `                                     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
. N; z! D  F, Y0 Z* B        if (IS_ERR(fb_info->dev)) {1 f5 B7 p) K/ n1 v6 g
                /* Not fatal */7 ~- u+ P1 E5 p( a4 Q
                printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
( u. `1 X( J: i) n4 W0 R3 E                fb_info->dev = NULL;
+ M( A; ]( l% O* m" Q" f        } else6 @) x8 H4 V  L+ Y! J
                fb_init_device(fb_info);        /*初始化,在class/graphics/fbx/下创建设备属性*/
+ Y# K9 A9 z6 M% r5 p7 m' O  N, s: }( Q- w5 ~. T0 \+ ]0 ^
        if (fb_info->pixmap.addr == NULL) {
! z5 ~( ^  e# x+ f                fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);/*分配内存,1024 * 8字节*// ?7 X8 t% c: `5 v
                if (fb_info->pixmap.addr) {
2 U5 J. p2 v0 I0 d0 F3 y* L" m/ C& C                        fb_info->pixmap.size = FBPIXMAPSIZE;
9 W& Y' f5 d4 z7 ]% L1 R                        fb_info->pixmap.buf_align = 1;
7 n6 o. s. ?# {, Y                        fb_info->pixmap.scan_align = 1;
& A: J9 v6 |+ `7 `2 X6 G                        fb_info->pixmap.access_align = 32;
# v; g! g- ^7 z2 ~8 S                        fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
; U2 R+ J% `' B3 [                }
) G  L9 i4 o: j+ y        }       
. U, T% `* n3 V        fb_info->pixmap.offset = 0;
- K1 \2 E3 K5 {' {2 J! @. p- A& i9 R  m9 r9 U, U+ c
        if (!fb_info->pixmap.blit_x)8 k! a) h6 X* V$ i1 N" O6 S
                fb_info->pixmap.blit_x = ~(u32)0;* q. ~2 Y( l+ o  b: x0 M' c% f

: C9 j8 R$ ?0 h5 e6 r& B3 n        if (!fb_info->pixmap.blit_y)5 _& e( S$ J3 B3 D3 s) L0 O
                fb_info->pixmap.blit_y = ~(u32)0;
+ |  ^- c# n, G
2 c0 R8 F) R2 b9 N4 ]- R. |0 Z1 U        if (!fb_info->modelist.prev || !fb_info->modelist.next)        /*该链表没有指向其他节点*/4 o: r: J5 n+ }9 j2 y3 [
                INIT_LIST_HEAD(&fb_info->modelist);        /*初始化链表头*/
$ ]# j  [4 g- \# l
( i4 V: W; W4 y" ^        fb_var_to_videomode(&mode, &fb_info->var);/*转换fb_var_screeninfo成fb_videomode*/( }8 w/ w" `- k3 K
        fb_add_videomode(&mode, &fb_info->modelist);/*添加mode至链表中*/
6 d: n7 Q; w6 l9 c        registered_fb = fb_info;
( Z+ ^& U/ O6 [& b/ j( w0 D) o8 `7 c1 |' m- u
        event.info = fb_info;% T5 |* P7 ~) ^4 T: h, `1 b
        if (!lock_fb_info(fb_info))
& d" {% v# e( k9 I2 V                return -ENODEV;
0 {3 z: o* L6 m) T- W        fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);/*???*/8 I/ l$ l+ ?, V' ~5 M% _
        unlock_fb_info(fb_info);- _+ B' [8 h4 o8 y
        return 0;1 }+ d& y- [) |4 J% K
}1 |7 c) C0 r2 o
! I, m" i, i. h. c
从这个函数我们可以看出,framebuffer子系统只支持32个设备。在创建了设备节点以后,建立设备属性节点,随后将fb_var_screeninfo转换成fb_videomode,最后添加fb_videomode至链表中。
* P: \8 Z: X" U我们看下其中调用的函数,首先是fb_init_device。! D+ n3 L' h; L8 w, d

' K4 D- Q" P, k) @下列代码位于drivers/video/fbsysfs.c
+ t4 F( I% P' G6 f: H2 n; S& z
- R6 J0 |0 I: s0 X. T$ D: A" }+ K0 ?int fb_init_device(struct fb_info *fb_info)9 Y' B2 a5 L7 x6 T( x2 W! ~  S
{
- ^4 l  q" s8 [( c. r        int i, error = 0;" B  p- x% ^+ G
+ |2 n7 T( u9 e) }6 a' O8 F
        dev_set_drvdata(fb_info->dev, fb_info);' ]2 l+ G" k, f" X$ |
# u2 w- i/ X. J2 T
        fb_info->class_flag |= FB_SYSFS_FLAG_ATTR;! G! _4 G+ r9 _8 p  D5 P  Q1 a9 O

3 P+ B9 M/ W& K* \% a        /*建立设备属性*/4 H, r% K7 o' @/ p) f8 |. i
        for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {( ~4 E5 g* L2 V1 U7 e
                error = device_create_file(fb_info->dev, &device_attrs);
! p; d" e! u7 U  h( q& T* U9 e' p& f: J% H* t
                if (error)% S; M0 x/ t% I
                        break;  G  r) J6 w) ]) l# n
        }0 E( r3 \& l+ K

- u) u$ F$ p1 I9 V0 _  q: Q        if (error) {
$ @- z1 w) D$ [* U                while (--i >= 0)& G1 S) `; B; a
                        device_remove_file(fb_info->dev, &device_attrs);
( x8 T: u4 ~4 ~7 s. b( ~                fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;5 h5 N/ W8 f; X  i2 }) h
        }% o* W4 E, {) |% z7 j/ `; Q

# Q- Y0 {- q% n% A; W        return 0;
  v; d4 v1 ?1 u* q- N& [! w}6 M. n9 A6 N5 V$ l% g' {; h

* I$ s, t4 r, [: e/* When cmap is added back in it should be a binary attribute
7 L9 k3 E* U  v& H( _ * not a text one. Consideration should also be given to converting
1 M  S0 |4 l" J  p& w# e * fbdev to use configfs instead of sysfs */: o$ @/ G0 F1 ^
static struct device_attribute device_attrs[] = {
2 J+ v8 a* a4 x    __ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),
( f; s1 S3 F- ~8 D# K  s    __ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),
$ W/ a) V. [: u2 _* X9 h    __ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console),
5 w- ~# d1 b1 O    __ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor),
. m4 _  M- |/ [4 T- d    __ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode),
' e7 f( s) i- P- {1 n! g    __ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes),
2 k9 E- _! s( O$ ?! O    __ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan),3 Z) @2 T0 V1 p3 P! c0 \8 y# V
    __ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual),& x( ^; k. H2 k+ w* p
    __ATTR(name, S_IRUGO, show_name, NULL),
% U# \' I0 f  A+ a    __ATTR(stride, S_IRUGO, show_stride, NULL),: H8 X8 _$ f7 ?# \! X
    __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate),
+ R8 J8 [- i  v# H$ T. L- Q% I! x    __ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate),
7 u1 e- }9 n) u2 _" T#ifdef CONFIG_FB_BACKLIGHT
; _2 [3 q' R9 n& H! \( N! T( @    __ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve),
7 h7 W$ A! |; a0 W8 T9 U#endif0 g' I* n( e" p; t& p  e. N
};; B$ S3 [% h. g; W8 K+ O8 u& p( V
# c: {6 s; E# r9 p1 A5 a
我们可以在/sys/class/graphics/fb0下发现这些属性文件。
( Z5 G  ]; O7 ~& Q[root@yj423 fb0]#pwd" K8 Y# C$ J3 @. L* Y7 `
/sys/class/graphics/fb0' u$ I- m$ V2 k! H1 l
[root@yj423 fb0]#ls
% O1 R, D1 Y1 ]0 pbits_per_pixel  cursor          mode            pan             state           uevent
6 s9 a8 K0 K7 O( fblank           dev             modes           power           stride          virtual_size
  g( F- {/ ]) {3 o9 Qconsole         device          name            rotate          subsystem* W% m* f- e2 y, K  h# I' _
( W, z9 p: Q8 W& D
; F' G8 {' C- r" v$ [
接着看下fb_var_to_videomode和fb_add_videomode函数。! z; Q  s8 m6 p" ~

$ g9 T) \, Z" Q' p9 `7 X* y下列代码位于drivers/video/modedb.c和drivers/video/fb.h
+ Y5 F1 Q, W5 U3 B; V4 R
1 O* k) }' J6 r5 wstruct fb_videomode {
9 j9 B3 ^* G& ?1 R1 C1 L. R        const char *name;        /* optional */
8 |: e6 d2 B, e+ ^3 C+ Q0 ]        u32 refresh;                /* optional *// k' [6 S: [0 B
        u32 xres;
. u) ~; X$ J( c' L. f. N4 ?        u32 yres;
1 M6 @8 ~" \# H, m; _" [, I0 p        u32 pixclock;. g9 D) m1 f4 g1 W& O  I
        u32 left_margin;
) W( ~/ Y$ c) V        u32 right_margin;
) X3 R% G" z" r: F. c1 [        u32 upper_margin;3 l& C9 s' b" c* H/ {& ?9 [; W
        u32 lower_margin;& T6 o9 k, q" p7 f0 `" b
        u32 hsync_len;
5 j9 x! g2 j/ ^6 M        u32 vsync_len;
- p" L9 o5 s3 D2 D# M% \        u32 sync;. r4 q2 w& n) n& d9 J6 u- N
        u32 vmode;' G9 C# k4 V3 q9 }/ {
        u32 flag;+ B* H( U7 e$ W0 V8 z  m: B9 s6 J
};: ]! n, r" F/ ^2 L
- W: x( R* D" z' Q: k8 ?/ M4 T
/**
/ H. g& M$ e; A, Z. ~2 x: f  ^ * fb_var_to_videomode - convert fb_var_screeninfo to fb_videomode
1 c' Z" N5 b  S# T4 W% g6 |6 o+ V * @mode: pointer to struct fb_videomode% g/ E) v) i) X- U& _2 I
* @var: pointer to struct fb_var_screeninfo
) c+ X' U$ s& i5 P2 i */8 ^" y; ^/ T3 s) z3 \2 V
void fb_var_to_videomode(struct fb_videomode *mode,
/ h6 D$ K) f6 l* l2 T. y# P- P             const struct fb_var_screeninfo *var)( z# v$ I* D: b, N1 u' C; [
{& `. i( _+ [4 b$ h9 f
    u32 pixclock, hfreq, htotal, vtotal;
7 q' L+ Y" p& p" x& F4 f8 ]
, T/ v( ?, {7 `: P. m    mode->name = NULL;
6 @  I% a1 Y/ N7 y    mode->xres = var->xres;' p6 ]& {# Q# T& j, a
    mode->yres = var->yres;
, k; `, q' q, S; ]  |6 T( N    mode->pixclock = var->pixclock;4 L& _2 Y; m9 U. Z3 Q
    mode->hsync_len = var->hsync_len;* F- O' z& Z- w  C- R) K
    mode->vsync_len = var->vsync_len;
8 Z- a5 n8 o! A4 U5 K    mode->left_margin = var->left_margin;2 }3 ]* b% A  X" s. c" ^1 K
    mode->right_margin = var->right_margin;
; D0 D. O0 i6 Q+ w6 U% ]    mode->upper_margin = var->upper_margin;- M# _9 \) z4 o/ H% w
    mode->lower_margin = var->lower_margin;
1 i1 X, Z5 L9 \  c: p) O/ y    mode->sync = var->sync;6 s, D6 B9 ^1 I% W
    mode->vmode = var->vmode & FB_VMODE_MASK;2 A8 m: n* Y) Y- v( a6 ^* u
    mode->flag = FB_MODE_IS_FROM_VAR;0 A: \# e; h% @( M! ?4 \1 {* v
    mode->refresh = 0;
+ I8 A1 b1 E# Y9 ~! f" I5 o( y8 C
5 a% ^3 d# r. H# |    if (!var->pixclock)4 M/ G* e9 e3 ]7 I  b+ l
        return;0 U& s* t! D% e& S# K

1 m. d2 G+ D6 X6 P: q. t$ h    pixclock = PICOS2KHZ(var->pixclock) * 1000;$ n$ u; r* ?, Q* |* N' _  N

& _( i6 }+ s  v7 p* L    htotal = var->xres + var->right_margin + var->hsync_len +
( D( o0 k9 b8 `3 v! V% z        var->left_margin;& p: \; b% E* q3 h# T% v% y. X
    vtotal = var->yres + var->lower_margin + var->vsync_len +
$ m, x3 O- v* i( b# @4 V- e        var->upper_margin;+ x, z* }/ u* h2 q4 v5 P
4 H: |( M% L. V8 e# q
    if (var->vmode & FB_VMODE_INTERLACED)
: o7 J& P/ @7 N) m6 W" M        vtotal /= 2;
3 V: b. g8 ^$ H: C, N    if (var->vmode & FB_VMODE_DOUBLE)* D) N* s+ y+ `8 n/ l: B
        vtotal *= 2;
9 w8 j) h  M# J! K6 `6 G0 {, @6 x, e2 @
    hfreq = pixclock/htotal;
+ I) Q: @. ?7 r  C. s* ?% v    mode->refresh = hfreq/vtotal;
/ n' `) r8 U2 i( B, P$ u; w+ W7 K}
2 |* [5 S2 y2 \# S1 n# T/ f5 r) j$ z& z- F! B3 M- H  W/ q( G
/**. k' b2 S1 \9 {( H
* fb_add_videomode: adds videomode entry to modelist  e4 d5 \# P# N/ r& L
* @mode: videomode to add
1 ~0 _9 _' h3 c% }& ^$ U- v * @head: struct list_head of modelist
$ N* u8 m  y8 i5 Z */ t, X# x9 F$ d+ h/ @' o8 N
* NOTES:) F  [" _1 k" D: L2 B
* Will only add unmatched mode entries
  V+ K, T! @7 Q */
9 e/ Y2 [( D! q+ `2 ^4 Kint fb_add_videomode(const struct fb_videomode *mode, struct list_head *head)/ |" i7 X0 e7 M! a
{
- ?0 _9 C& L! F    struct list_head *pos;0 z0 f, r% c0 R, a* _( e
    struct fb_modelist *modelist;d
6 Z* k5 c5 s* c3 O# y% L' r    struct fb_videomode *m;- W+ ]( o: v: G0 ]
    int found = 0;
$ I0 z2 J5 S$ K; T& l: l. Z3 e    /*遍历所有的fb_modelist,查找mode是否存在*/, g) Y% V; W& l* h3 ?4 P* W$ _6 a
    list_for_each(pos, head) {
/ g! I; H6 {8 W        modelist = list_entry(pos, struct fb_modelist, list);5 ]0 i' I) z# m9 `. t
        m = &modelist->mode;
8 E! |! L2 p7 r& _        if (fb_mode_is_equal(m, mode)) {    /*比较两个fb_videomode*/
4 `" s5 Y, c- n* Y            found = 1;                        /*该fb_videomode已存在*/' G7 \( C3 I: P
            break;
% E+ F+ ]  ?) ]& s; ~* D        }
4 ~$ R7 }, D$ R9 Z6 r    }
# V4 Q0 D! \5 w/ ?+ `/ H+ g    if (!found) {    /*不存在*/
) v/ V7 @3 z! g8 J        modelist = kmalloc(sizeof(struct fb_modelist),    /*分配fb_modelist*/
8 a: r3 s7 A6 P0 z, w: v# G                          GFP_KERNEL);
, i; o; {. q+ l; f6 b/ E/ ^; o/ Y, W5 Y. j' e1 q: s
        if (!modelist)
5 M  j6 y9 i2 S* _            return -ENOMEM;
) j4 U0 N8 J4 \+ k" [0 n        modelist->mode = *mode;            /*保存mode*/
5 k9 x; K+ r0 I4 e* `! W$ m( w' p        list_add(&modelist->list, head);/*添加mode至链表中*/
* Z. B- U3 _5 i# i; H$ T* M    }) O3 B( W9 @0 a8 Y9 K7 w. l
    return 0;: M6 J1 o6 P- n; R9 X& W% G( w- X
}
( D: f9 L' ^  `$ I9 d+ E: F3 t- v
/**
! N. U7 S8 s) F6 q4 r4 v- S# a * fb_mode_is_equal - compare 2 videomodes2 i# j; i; B- a' ~
* @mode1: first videomode
' f; ]! Q9 V& p * @mode2: second videomode
- `* R4 A( e6 j: _) ?3 X *
% Y  s3 S  d; b4 g1 t3 T * RETURNS:
+ R2 [- g! K9 E * 1 if equal, 0 if not1 u# v6 ?: {; N9 X2 _
*/5 F% c) N6 e; J8 K' v% {- a1 y
int fb_mode_is_equal(const struct fb_videomode *mode1,- i" b0 V0 ^- ]: h% J+ m( T: |
             const struct fb_videomode *mode2)
2 D% e/ N6 E9 T& z% {{9 v$ {  \5 J2 }1 s8 p  ~
    return (mode1->xres         == mode2->xres &&
* r$ |! s* X$ ^. W; A; J0 O6 S        mode1->yres         == mode2->yres &&% n% U5 m0 `  {2 N4 o
        mode1->pixclock     == mode2->pixclock &&2 V% _: F6 w( ^1 F* A( N
        mode1->hsync_len    == mode2->hsync_len &&
! P; _* S# s* b' ]2 d6 U' B! M        mode1->vsync_len    == mode2->vsync_len &&. M7 H# h$ E2 q- C  p: U$ X' s
        mode1->left_margin  == mode2->left_margin &&
& q4 s$ H: H; p, H6 x( T: R0 @7 N        mode1->right_margin == mode2->right_margin &&' n" x. G. b0 I% b
        mode1->upper_margin == mode2->upper_margin &&& U( ^1 M* ^6 v# O. R
        mode1->lower_margin == mode2->lower_margin &&
: J( a* Q2 T) g# G$ g. {/ K7 N        mode1->sync         == mode2->sync &&
% n3 E( w) o1 _        mode1->vmode        == mode2->vmode);! a; u7 Q0 `! C$ I4 H
}
' ]; V4 U/ I. _7 [( n" n* \, d2 ~, O* d* g2 i* f" o

! |. E5 m6 e( ]7 l8 Dfb_var_to_videomode函数只是将fb_var_screeninfo结构转换成fb_videomode结构。而fb_add_videomode函数查找是否该fb_videomode已经存在,如果不存在则添加到列表中。
" k: R. f' e- t0 t1 ?- e4 H3.4 字符设备方法
3 V: U  J7 b/ {. p  J9 d6 g  在看过framebuffer子系统建立和注册过程后,我们看下framebuffer留给用户空间的API是怎样实现的。" d6 v' H( f4 F: m4 I: P

/ R2 t2 M: D" D本小结只分析5个常用的方法,即open,release,read,write和ioctl。9 T+ @9 `! Q) _- |+ \6 h
$ X; e* |6 O1 U) c
  因为所有的方法和struct fb_ops定义的方法有紧密的联系,而该结构的定义由驱动程序给出,在这里我们提前看下在驱动中是如何定义的。0 H6 |# {. B4 E

" |8 {. ?* I4 s" P- } 下列代码位于drivers/video/s3c2410fb..c
! S% K$ U* B( e. _0 J4 f8 X/ c. j) {# r$ Z
static struct fb_ops s3c2410fb_ops = {
. ?+ }2 \9 }! }6 M) K2 `$ ~        .owner                = THIS_MODULE,
3 S2 ]+ u' j3 l3 w9 O" h  f        .fb_check_var        = s3c2410fb_check_var,                        /*检查变量的合法性*/
- J5 e* c  n; Q/ w" Z# r( D        .fb_set_par        = s3c2410fb_set_par,                        /*将参数写入LCD控制器,该函数由帧缓冲核心调用*/% J$ R, O+ Z' F! {
        .fb_blank        = s3c2410fb_blank,                        /*该方法支持显示消隐和去消隐*/0 E0 o7 Y2 {, {2 S: B9 M
        .fb_setcolreg        = s3c2410fb_setcolreg,                  /*设置颜色寄存器*/' z! Z) u% M  L6 f- Q1 g9 V
        .fb_fillrect        = cfb_fillrect,                                /*用像素行填充矩形框,通用库函数*/- f; e2 w& n3 m$ @
        .fb_copyarea        = cfb_copyarea,                                /*将屏幕的一个矩形区域复制到另一个区域,通用库函数*/! U/ X7 a/ ]# N
        .fb_imageblit        = cfb_imageblit,                        /*显示一副图像,通用库函数*/
: y( E9 u! Z0 y2 U4 @- g};6 O0 B. V+ T3 C+ a* z, N5 H
最下面的三个方法使用的是内核提供的库函数,而上面4个则是由驱动提供。
% b, h6 a2 s( ?, a" N" N: S3.4.1 open方法
2 A0 ?4 q' d# B* w4 Q3 K2 q下列代码位于drivers/video/fbmem.c# U7 P( s! V8 y. Z' P# h! ~& L

) I/ t8 |5 x, j% estatic int5 S9 W1 Y# |3 h/ D
fb_open(struct inode *inode, struct file *file)2 {2 [  e# R' I( ^
__acquires(&info->lock)# M3 S4 B+ H8 c. x+ X
__releases(&info->lock)3 Z/ a  Q7 {+ v; z: w/ i3 u
{$ F, }, D; h1 @1 D2 P3 }' c
        int fbidx = iminor(inode);
0 P# j3 Y  c/ W1 S1 S        struct fb_info *info;6 b: X% ?' v. z# A& J% R; k5 f
        int res = 0;
) L1 y, m: i  x, a7 c+ O3 t; f2 o0 P) t/ |% a7 K
        if (fbidx >= FB_MAX)
3 N+ i& p( L# F                return -ENODEV;# \# W! v/ f0 u2 r" I0 ^
        info = registered_fb[fbidx];                /*在register_framebuffer函数中已经设置了元素*// O" q+ g. q# p0 z
        if (!info)
& r' w5 y  t( r                request_module("fb%d", fbidx);        /*加载模块,这里不加载*/
# l4 ?' T/ s1 l+ h        info = registered_fb[fbidx];
0 y8 ~6 E! j( M) `; M& W        if (!info)
! R- \- v! c; {2 _3 T& G                return -ENODEV;
% [& ~6 Z( x( a# F( q8 ?( X2 g/ g        mutex_lock(&info->lock);                        /*加锁互斥体*/% K, k8 _; w0 X4 ]
        if (!try_module_get(info->fbops->owner)) {        /*增加模块引用计数*/# i; l& P3 Q& h, Y
                res = -ENODEV;* X2 `3 q& z& X* i* H+ K% M' f
                goto out;
) V# J4 \+ S5 E5 o        }
; _0 s0 ^. e, F9 L        file->private_data = info;                /*保存info*/
/ e( r5 ~' h" ]4 |) Q% N" A  m        if (info->fbops->fb_open) {                /*这里fb_open方法为空*/
8 G4 ]1 U: w. x                res = info->fbops->fb_open(info,1);
8 q8 I9 g; v3 i& m- c# j                if (res)
& z; j! B" j. H* m! ~                        module_put(info->fbops->owner);1 q1 V7 \2 ?& g) K- }9 |) e9 X
        }
3 ~. L! g6 Q: ?8 I6 ~#ifdef CONFIG_FB_DEFERRED_IO
9 J2 y5 J2 Z& M% B. d5 U" @# H        if (info->fbdefio)
+ t  T8 B# G# ^6 y1 ~3 B                fb_deferred_io_open(info, inode, file);
( J3 K( Z0 V) m/ X% e) p#endif
! X  J: V1 g+ n5 {# M3 S/ E7 lout:
* P3 |; |8 }" t0 K3 W) K5 C        mutex_unlock(&info->lock);                /*解锁互斥体*/
1 {, M: }7 J" i        return res;7 w+ |8 u! T  A4 v
}
2 A8 \+ N* c% Q3 N8 y, R& P主要的一个工作就是增加模块引用计数。还有,程序会判断是否fb_open在驱动中给出,如果有则调用该方法。我们已经知道fb_open没有给出。+ s# b2 m2 ?% g, U/ A5 g# d7 F
3.4.2 release方法" \& p, n' f# m
下列代码位于drivers/video/fbmem.c8 u+ f0 l7 A9 E6 m# u

2 B1 ^9 }. w' k' p6 Rstatic int
8 z4 R0 o! V8 X: ?3 {fb_release(struct inode *inode, struct file *file)
  K( O: ?: K' F) b__acquires(&info->lock)
. q! _7 u% ^1 `7 [) K& W! \. s__releases(&info->lock)' Y/ G9 M) ?% q, v
{
0 t' _; x: u7 }0 B: z        struct fb_info * const info = file->private_data;) W1 q  r9 w, b3 D" }
5 h$ \' ~, r$ C; F4 l+ F3 m; G5 g
        mutex_lock(&info->lock);
4 ?8 e/ x/ V$ b0 b) x        if (info->fbops->fb_release)        /*这里fb_release为空*/
+ g" d. _9 Q$ D9 u                info->fbops->fb_release(info,1);
* i' q( m2 a% d7 f        module_put(info->fbops->owner);        /*减少模块引用计数*/
* t! V2 L4 J0 Q5 h+ |' m, X        mutex_unlock(&info->lock);
7 e5 A" `$ J3 B! Q: ?5 |        return 0;& m7 \: [1 G2 F% k; q
}
9 I6 N0 |+ A4 e, ^和open相反,减少模块引用计数。
; z3 u, m9 m1 n0 o
6 a; n& @" N9 R* V" o+ F2 j3.4.3 write方法
' B- \( |6 T2 M  通过调用该方法,LCD将显示画面。- U  C1 [  n) d/ @1 y3 @

! W3 B% Y. n) {  下列代码位于drivers/video/fbmem.c
8 b! a0 o& X5 b8 C% i$ w; w
" e+ A" o' W7 J$ i) y: ]7 wstatic ssize_t
3 f4 b% J, _( ^0 c# B5 @fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos): A" u& [( L0 K' B
{% A" E/ Q# W! C# ]1 w0 D
    unsigned long p = *ppos;7 e4 R0 R! D% y0 o* E5 U
    struct inode *inode = file->f_path.dentry->d_inode;: K  Z3 q, Y* q9 @9 o
    int fbidx = iminor(inode);$ \% M! w1 L* q  I* W8 T$ B' S4 C
    struct fb_info *info = registered_fb[fbidx];# U+ y1 S% D6 Q
    u32 *buffer, *src;; v# r9 S' Z8 S7 ~8 g
    u32 __iomem *dst;6 s- ]. e& h; @: e& c; |3 q) N
    int c, i, cnt = 0, err = 0;* K/ z4 L) ]. l
    unsigned long total_size;
+ `& f  S9 w( K: O1 R4 ]5 G( Y# ~! y
    if (!info || !info->screen_base)    /*screen_base在驱动中给出*/( S) D9 z1 g' w* x  x
        return -ENODEV;: C% y7 h) L( K5 l; M7 Q

7 M& v" q  o; R& \$ J; g: f% l    if (info->state != FBINFO_STATE_RUNNING)4 h( C0 N+ ^% O1 b8 j; ]
        return -EPERM;
! L( N, h2 [/ _1 h# s9 y
. ]; l- y4 t6 q* _: {2 a( R    if (info->fbops->fb_write)    /*没有fb_write方法*/
+ ]% d: j( ~2 B4 P        return info->fbops->fb_write(info, buf, count, ppos);/ s4 j9 j" {  S- n

8 @( L6 q7 U2 U$ A$ S! K/ F( C    total_size = info->screen_size;    /*screen_size没有给出*/
3 J4 ^/ }2 C/ W; q! J/ i) r
; {' ^. A# z6 m1 L' B    if (total_size == 0): b; R) Y( B# j" @
        total_size = info->fix.smem_len;/*153600字节,驱动probe方法中计算*/, v9 y5 b+ [* e
, D( C9 W4 y: o4 H6 r
    if (p > total_size)
+ ]9 u8 i" l4 E! R1 ?. G% u$ ~        return -EFBIG;; {& B. @9 Y) D5 n- U

0 h  j& C& H& B* T+ q' }* b. A6 @    if (count > total_size) {    /*要写入的字节数大于153600*/' v1 f! h; c# c8 v2 U& A
        err = -EFBIG;        /*file too big*/
+ W* g. q- [8 \8 K' j: `        count = total_size;
) p6 D0 g4 V; {- A! W4 ^0 e: c    }; W4 f  K( H0 Q+ _# u" g

/ k' N4 _0 v. u( Q% N+ K3 M$ @; z% S9 Y    if (count + p > total_size) {/*偏移量加上字节数超出了缓冲区*/8 W3 O! H+ E- s5 P. o
        if (!err)* @- [% T9 {) ^/ U) f
            err = -ENOSPC;* b: H! A6 J8 {

' u: b, p! E9 _        count = total_size - p;
. \+ k5 G. y6 |& W  `' M    }
" r, D# t! h! N: N" ?2 y. f5 t- h- K5 u6 F" O
    /*分配buffer,GFP_KERNEL*/
2 O4 }4 o* S" q0 [4 R& U    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,1 ?2 D% Z- @  o: b" x
             GFP_KERNEL);                /
9 ]* ?3 Q. P! a/ M( G0 P- j" j& t    if (!buffer)
5 e9 L+ k) @* `" c  n        return -ENOMEM;  K+ y$ a0 O* A) N8 J: z2 A* u! \- H

! B. h2 {& g9 F) P    dst = (u32 __iomem *) (info->screen_base + p);/*修改目的指针*/* M* |) ~1 R- _8 K

, `7 u% m2 Y- q% a    if (info->fbops->fb_sync)    /*没有定义fb_sync*/, c* S1 m9 R2 ~- H
        info->fbops->fb_sync(info);
& M% G( l; N! {0 x* [1 v
) f/ [5 C5 d) u+ O* M3 i9 `    while (count) {
( i5 e! E2 F9 k$ M8 W        c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
, H' w: i; p3 T7 w* C        src = buffer;8 S4 D3 d, z6 d5 Z: a

+ L$ F" Q9 Y3 T+ I7 ]( }        /*从buf(用户空间)拷贝数据到src中,一开始为1页,最后为count字节*/- e" \0 j- s: ]0 `6 b) y
        if (copy_from_user(src, buf, c)) {    - b6 ~2 }$ T! E* d
            err = -EFAULT;
4 I- ~' z' d; O* R6 Q* A0 i6 F5 v' t            break;
$ {, F& N2 i. _9 C        }
( K- J# j. X) g! H  u/ `" |        /*一次for循环,写入4个字节数据到dst处*/& }2 L! N! L* R
        for (i = c >> 2; i--; )
  G6 b' j2 f6 E& g' V$ D            fb_writel(*src++, dst++);
2 j9 S0 F7 G$ B5 ^; w0 P" a; g" f        /*最后还有3个,2个或者1个字节*/, k5 x9 {  F1 C1 K* O; b
        if (c & 3) {. F, b* n. z: ^3 J1 X
            u8 *src8 = (u8 *) src;
# M1 o* D) c! @% g0 v            u8 __iomem *dst8 = (u8 __iomem *) dst;
$ a( n! q$ q$ ^            /*一次写入一个字节*/5 T4 H( @+ r7 W# r8 z4 _
            for (i = c & 3; i--; )  E) `5 D  ]+ B2 m. U: h/ X% C
                fb_writeb(*src8++, dst8++);
7 V! q' M" |* `( i# `8 X. `( g/ k/ g
            dst = (u32 __iomem *) dst8;
7 W" g! U8 }- F) [1 s- K        }
( V% Z8 k  R" Y# x* b) @9 G- a4 ]& K' v. M, n- B( B, Z; @
        *ppos += c;    /*用户空间偏移量增加*/& I( n4 ^2 M* W
        buf += c;    /*用户空间指针增加*/
4 d0 ]" w5 S  O9 }        cnt += c;    /*修改已发送字节数*/
: {. K, t4 l8 h) P: q% w        count -= c;    /*减去1页*/
2 _) z9 q4 n! [2 {    }
. ^3 M  ?* O7 @+ r, U6 Z5 B$ U- C8 |3 d9 b* g' Z4 {6 ]
    kfree(buffer);    /*释放buffer*/
0 O) U2 q/ V) a4 K! o1 S4 z5 Z) Z* [& a8 N) O
    return (cnt) ? cnt : err;
2 c+ E/ M4 c! P& o}
# m  H7 P. Q0 [
5 }: [  d- g* W0 e! P这里,做了一系列的检查之后,开始拷贝数据。这里一个有三个buffer,一个是用户空间提供的buf,一个是在这里新开辟的buffer,还有就是驱动层提供的screen_base。
4 c: R8 W( Y; Z0 P  G# I$ C数据流如下:4 m+ c, }- |9 t- f/ O

4 b% |( _" Y8 ?! K6 Z
% [: [5 C4 k- d! S: _
. E5 n1 [5 U3 C$ u: M用户空间的数据首先被复制到buffer中,然后从buffer中复制到screen_base中,最后被映射到LCD上,LCD就显示响应的画面了。
% u( ]7 h" a8 f; Z8 r6 _6 [  b  W8 |3 _  R/ s
3.4.4 read方法& F& P8 \8 k; _6 p+ O3 p- @
该方法用于读取屏幕画面的数据。
" M8 z0 y; _7 a1 t0 ~5 C
& ~2 x, a% @7 oread和write类似,只是数据流是反响的,就不多做介绍了。
) z# q9 O  e, N8 T$ U  F: `5 E
& Z% p- L. \, n3 ?" [下列代码位于drivers/video/fbmem.c
2 [0 P+ l, v5 \* i3 V
; h' g/ x, j. N: Rstatic ssize_t
2 a# E3 K: \/ b  X+ O& pfb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
& O2 P' j1 i! s+ @{, T" Y, S8 }: r4 M0 G
        unsigned long p = *ppos;' B! C  [+ t9 [. T5 w* R+ C$ k
        struct inode *inode = file->f_path.dentry->d_inode;
* h7 d# p* k! m. ?3 X        int fbidx = iminor(inode);# G. `; u! Z: ^. B" E, K
        struct fb_info *info = registered_fb[fbidx];
+ I+ D) c* O  w- `! ]        u32 *buffer, *dst;
& e* \) @% ]: k7 T  Q        u32 __iomem *src;
8 U0 t' M$ I% k$ u& `* u& ]  A        int c, i, cnt = 0, err = 0;
' T  S" d7 s4 n' L" |        unsigned long total_size;* H' b; r1 d$ {% n6 L3 K& w

# s6 U  u9 L: J% y9 D        if (!info || ! info->screen_base)! m0 G1 O/ r8 L  u
                return -ENODEV;& f( S4 z, }  F% ~4 e8 L
- b3 B) k1 }8 l+ L5 f; u# L
        if (info->state != FBINFO_STATE_RUNNING)# }: l6 M9 ~4 |8 W3 R6 h; c7 }
                return -EPERM;/ G1 X$ n# ^' [, V, z: N1 m

4 i: F5 d% F3 F) ^! ?/ }        if (info->fbops->fb_read)        /*没有定义fb_read*/! J% v! w& A( l+ N+ |- _$ ^
                return info->fbops->fb_read(info, buf, count, ppos);( H2 L0 a) [! a- i
        5 K. C' n) H# E  D4 m! R
        total_size = info->screen_size;
$ w$ x. R: q; O& N; x+ x( A) K5 Z0 g) z( i4 a: Y
        if (total_size == 0)
+ N2 b9 _+ F/ Q9 C# q& _                total_size = info->fix.smem_len;
' p- l4 a5 F( T. o" n" Q, c! L3 I
        if (p >= total_size)
5 @* t' `/ K/ G/ ~/ g8 h                return 0;
7 _+ \6 ]7 o# b' _, j7 j% L/ S- X# A6 l' n6 f
        if (count >= total_size)
( t  L3 _& W9 `: v                count = total_size;
% f, n5 t" W/ S, S" @- d7 V' C; m0 h- X) K$ U& |
        if (count + p > total_size)
* E( D5 b( g9 j1 A1 \                count = total_size - p;3 Q! D! s9 O- W  a- ^
' Y/ r, o; m/ g; }/ C, i) c
        buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,; i; a5 c; G% o  o, f8 F9 ^
                         GFP_KERNEL);8 A4 `% {7 _# _% O. E* i- F; ]: f
        if (!buffer)% m: C9 }# k9 D( \  H; ?
                return -ENOMEM;
6 f. J% X) b5 K, U/ F9 q( X: _( J! L
7 p) K+ `+ ~% G/ h3 ]' M( r; N        src = (u32 __iomem *) (info->screen_base + p);  w, g0 x' x: j0 `* r7 p* v

4 d1 V) D- ?, [, M! Y0 {. r1 c' W        if (info->fbops->fb_sync)
" i* h& i1 a& Q( z                info->fbops->fb_sync(info);/*没有定义fb_sync*/, W0 l% P" D' v% x1 K4 n
7 g9 Y& _+ ]$ ?. z  w
        while (count) {
* x, G& h6 z% r6 n; ?6 F                c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
5 M" k0 u1 j0 q3 @' i                dst = buffer;2 S0 l, w* B% O* d; n
                for (i = c >> 2; i--; )
# {9 l$ B$ i$ d; [9 H                        *dst++ = fb_readl(src++);
9 ~; m8 H3 z$ e9 z                if (c & 3) {4 X8 F6 F4 C' J
                        u8 *dst8 = (u8 *) dst;0 T/ ^, Q, j; c- y- _9 P) L
                        u8 __iomem *src8 = (u8 __iomem *) src;
7 i+ O5 U* d$ L* A: g) Y- R+ [  H8 C
                        for (i = c & 3; i--;)# n! w7 e3 G' @- H6 b
                                *dst8++ = fb_readb(src8++);
. M  R3 E8 m) L4 {: F7 `/ v  O: m& @* o, M
                        src = (u32 __iomem *) src8;
: M1 [9 O4 H- h. t& u( I( U7 `                }$ U& k+ D' z! f# o- [( H6 N
0 N4 [7 l, ]7 B3 C" t
                if (copy_to_user(buf, buffer, c)) {3 W2 g5 M; w4 P# j6 r
                        err = -EFAULT;0 N# J6 J1 |% F- H2 `* ?
                        break;
3 Z$ Y1 G& o$ B' G                }
$ P0 k7 Y: l& _3 x& S$ N5 ?, w                *ppos += c;+ x. e6 i1 p- u! f8 {
                buf += c;
8 |) v+ }: j$ G                cnt += c;
0 B2 N, |: L, {% V/ V  w                count -= c;- k" Z9 L! U# S! V
        }! s2 K: z. f* Z  z: {& t& U; }
5 J3 T  V# P# [. W
        kfree(buffer);* z/ [7 q5 k; @7 ^0 j$ @
, L, {, j+ W& a3 J
        return (err) ? err : cnt;
3 j7 ?4 f' w% V' k3 X}9 q  P' y# g& n3 |8 m# C6 Q
3.4.5 ioctl方法$ ?2 k, Z  j3 W
这里只是简单的看下ioctl方法,这个函数调用很多其他的函数,详细的请自己看吧。
3 ~! _1 |& }6 r- m% Z
, f' r5 }1 |7 R7 V# Y7 o% I下列代码位于drivers/video/fbmem.c! P" G0 T, G* O

" k3 U+ Y: I+ b+ s( P: astatic long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)- B! a9 O3 H0 ~) j5 X
{
( X, T9 G. I2 K8 @3 L# f4 ^        /*获取inode,再获取对应的fb_info*/& ^2 v8 F7 ~3 W
        struct inode *inode = file->f_path.dentry->d_inode;1 @4 v  }+ u* F( k" _$ N1 n8 p
        int fbidx = iminor(inode);       
+ o0 ]' N5 N. O7 _% E        struct fb_info *info = registered_fb[fbidx];
& H$ s' z; i+ [: p5 Y  C, c/ u
$ P5 |4 A: Y- M7 Q, h) m        return do_fb_ioctl(info, cmd, arg);
) c# j* |6 x" _+ ~}$ R/ u* g. n5 A7 e- T' Q

) n1 ^0 n) ~! ostatic long do_fb_ioctl(struct fb_info *info, unsigned int cmd,2 w- n+ n& A0 D6 n' b% S8 Y
            unsigned long arg)
& L3 l% F2 ^$ A+ {# \{# S1 H8 g1 W; r* Y+ Z
    struct fb_ops *fb;2 N6 u8 c+ n! O9 L7 v3 i* E+ E  U
    struct fb_var_screeninfo var;
4 E4 ?0 J$ S  `- C+ k    struct fb_fix_screeninfo fix;
6 h) u! g0 D: v5 b# C: ]6 m& _    struct fb_con2fbmap con2fb;
! ^# g; c' k  ]' B- G* M( V    struct fb_cmap cmap_from;
/ o8 k& U* @7 k* ^0 k    struct fb_cmap_user cmap;
& s; G; a% T; d# M2 Y1 R$ j( m/ w' d    struct fb_event event;
/ {5 ^$ j3 {% w8 X6 P' V    void __user *argp = (void __user *)arg;. v+ O4 {* A# \9 }. ?2 u
    long ret = 0;
8 T. N+ M! @# B, ?; ~/ Q& X% |8 |3 n" _8 L7 f
    switch (cmd) {: t) }8 P6 ^/ c) y5 P) z
    /*获取fb_var_screeninfo*/
3 H  C$ e: V) I0 L  X4 r! A4 Q    case FBIOGET_VSCREENINFO:    & K8 F# I# _8 Y9 E
        if (!lock_fb_info(info))    /*加锁互斥体info->lock*/+ q: y/ _# ^+ r1 N7 f
            return -ENODEV;: W0 Y9 C% y3 u0 r
        var = info->var;            /*复制var*/
+ |+ ^  d, L1 }* q7 r1 G8 l        unlock_fb_info(info);
: U; t; i8 R! b
# _: b- E# _1 m% r# e        ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;    /*复制var到用户空间*/8 B4 V* Q, n% R# d8 c! f' E
        break;
+ h; _% [% F5 B* ?8 D( u' Q/ e2 B    /*设置fb_var_screeninfo*/3 C2 v) g2 e; J/ B2 I, h
    case FBIOPUT_VSCREENINFO:& r0 s* L( C6 u7 H% E
        if (copy_from_user(&var, argp, sizeof(var)))    /*从用户空间获取var*/
- T+ Q/ u& L7 O            return -EFAULT;- z5 \$ |  ~0 F) o
        if (!lock_fb_info(info)), V) S6 Q! ?/ C& \' g
            return -ENODEV;! u3 _" X$ ?, q! g: V
        acquire_console_sem();
1 `$ ^" }' Z- i# O* |: w        info->flags |= FBINFO_MISC_USEREVENT;, {$ ^1 ~6 O6 E4 W0 X
        ret = fb_set_var(info, &var);            /*设置var*/
/ ?! }8 i" n- H7 n% ?8 B$ o  [# O        info->flags &= ~FBINFO_MISC_USEREVENT;
- D  j. {& A1 _, B/ T/ y2 X        release_console_sem();+ |% @$ _- o5 h3 ~& U9 t
        unlock_fb_info(info);) M0 F: M" c  [7 o- {
        if (!ret && copy_to_user(argp, &var, sizeof(var)))
: U% [7 h$ ^8 Z$ Z3 T& U$ x- f) K            ret = -EFAULT;  b( K2 G; b- f7 m$ e. k! l1 ]
        break;
  J+ W2 L# z( _    /*获取fb_fix_screeninfo*/    /*fix为不可改变信息,只能获取,不能设置*/" O, m7 z+ t- I- P$ v
    case FBIOGET_FSCREENINFO:
* j8 y  G0 O) U: B        if (!lock_fb_info(info))  |: S; }- b2 G$ e% m
            return -ENODEV;% F% x2 p  I+ i' a$ {
        fix = info->fix;
! }- S2 N! q  C/ ^        unlock_fb_info(info);1 j" }" ^/ W8 j1 X! `
( a: o2 q2 M- P1 \1 O6 o" |( x" T
        ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;" ]4 O" |2 y  U
        break;5 N. _6 P8 f7 T' @# {: [% @
    /*设置fb_cmap*/   
0 ~. d8 E9 n7 K4 T( N  I. N    case FBIOPUTCMAP:
. ?- y3 K5 e" C+ q6 k4 E        if (copy_from_user(&cmap, argp, sizeof(cmap)))
4 M( k  w# x' E8 y& E            return -EFAULT;
5 k" o4 V) U! ?, e/ z5 O        ret = fb_set_user_cmap(&cmap, info);    /*设置fb_cmap*/3 d2 m; d% P$ }- T% t, `* v& V
        break;- s( t2 \( I5 i3 k* E/ j
    /*获取fb_cmap*/    7 [# x) v' p. o, f
    case FBIOGETCMAP:& J9 N: I# V( h
        if (copy_from_user(&cmap, argp, sizeof(cmap)))- z! _% j5 Z7 ]' f% s: L8 e7 C
            return -EFAULT;; B/ c  z0 G0 o, m- H
        if (!lock_fb_info(info))
( p/ N4 T* p6 \6 [  w8 z& P( T1 @4 [            return -ENODEV;
1 x' D; K5 U* T- L+ @! b        cmap_from = info->cmap;
6 Q! A% y' ]  P  ?. B8 v; H        unlock_fb_info(info);
7 c! M+ i9 M, S% q        ret = fb_cmap_to_user(&cmap_from, &cmap);/*获取fb_cmp*/$ T' `" S' w; s  ], N- z7 K; c  H
        break;5 D5 `. J, q+ I
    case FBIOPAN_DISPLAY:
: e. i# [5 e1 i, u3 N0 G) F        if (copy_from_user(&var, argp, sizeof(var)))
& h( ?- Z" k5 e% A: H& Q            return -EFAULT;
  e3 z, D9 [7 f( v8 `        if (!lock_fb_info(info))
  p: u- K% r. x' |! j( z4 h% r            return -ENODEV;
+ n) o/ v" M7 G1 Z0 c# G" R        acquire_console_sem();0 x+ c5 |6 s* \, e! w
        ret = fb_pan_display(info, &var);+ x& b% B4 O6 j
        release_console_sem();  ?7 b* G- n6 N
        unlock_fb_info(info);6 `+ y# e* m4 f& e7 Y$ R, M
        if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))  y8 @# Z4 J5 G* H% |- @1 p6 M( }
            return -EFAULT;
% E. `3 Y9 G& B5 o# a; E        break;/ g, j$ x7 w& ]: J3 I1 l5 \" I. o/ z
    case FBIO_CURSOR:' R7 G; z) i: n9 V+ J+ ]" b
        ret = -EINVAL;! M5 R, |4 s( p# q' O4 E/ @
        break;; @* B; w" k- M# ~  b( f
    case FBIOGET_CON2FBMAP:( o% a- J7 p1 ?: D- F5 \% D: c
        if (copy_from_user(&con2fb, argp, sizeof(con2fb)))% F. Z: I4 x$ Q0 o6 y
            return -EFAULT;: _  {7 \# G  H# c# `5 f2 {- d) Q/ j
        if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)+ f/ w1 j) O2 }% N6 y
            return -EINVAL;
1 n5 F7 s; X8 I+ f  f        con2fb.framebuffer = -1;
8 M0 t; V" t8 Q, g  |        event.data = &con2fb;
. z- @% ?1 w8 D8 r3 n' O        if (!lock_fb_info(info))' ?  N9 ~, r9 K( b' Q
            return -ENODEV;
" m- I1 j3 F* U. b8 s; H2 n        event.info = info;7 W! }/ p  u4 e5 S$ }) c
        fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);* l$ q0 U6 `8 Q+ l# I* R( m) f6 @
        unlock_fb_info(info);& L& v5 y' Q  L4 O0 k" i! i. [! u
        ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
" _4 c8 }% W9 r) ~6 d4 I        break;
; f" w7 Q* }6 |& _" u) [    case FBIOPUT_CON2FBMAP:; F/ F. Z/ H4 j/ u" S
        if (copy_from_user(&con2fb, argp, sizeof(con2fb)))% t5 u" ]  z% |, Q5 R3 i# I
            return -EFAULT;" B1 e3 o( A: V" Z0 q7 K: ~
        if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
7 f; {/ I$ }' ]' A: {! i            return -EINVAL;5 B  C, H2 m9 T% K, z, W+ G# D
        if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
1 M) o6 N4 k& L0 v9 f7 O% R, K            return -EINVAL;$ k8 ~, R& k3 m4 ~
        if (!registered_fb[con2fb.framebuffer])1 }; [0 s5 p8 x+ U& W
            request_module("fb%d", con2fb.framebuffer);
. q& p/ h- ]7 U# U. c; k        if (!registered_fb[con2fb.framebuffer]) {
0 c0 n- q2 m/ l+ m, }+ w3 a  Y            ret = -EINVAL;+ i& o% p6 b- L5 v
            break;
, b0 Y* a9 r. m: G% L- f        }5 G& P% J1 R3 G  h4 f6 f4 x
        event.data = &con2fb;0 M! f/ b1 F' M  q7 t/ T: Z) J
        if (!lock_fb_info(info))4 c# K- ^0 O( J$ ]! s
            return -ENODEV;
9 ]: _+ E, s. a7 q5 [' ?        event.info = info;+ B8 @6 y, V8 [+ l4 C! k' V: t
        ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);, }! a9 a; k4 ~# }: X
        unlock_fb_info(info);! R5 T) n) T1 @0 J
        break;
& z% X+ F; |. [; r; u/ M    case FBIOBLANK:
& y8 l: m* G0 T. B        if (!lock_fb_info(info))
8 y# y  ]+ x! F6 ^            return -ENODEV;
! e' P* J' t0 i2 x# M; t        acquire_console_sem();! F$ P  }. K8 v1 \0 V
        info->flags |= FBINFO_MISC_USEREVENT;
7 S- J5 t9 _: \        ret = fb_blank(info, arg);        ?*最后调用驱动提供的s3c2410fb_blank*/1 B) @5 v. `  y* c7 I' U+ A2 {& s
        info->flags &= ~FBINFO_MISC_USEREVENT;
9 Z, g8 F% O6 s- X- |( I: t% C        release_console_sem();
. t, ]% h( h7 R) v0 D        unlock_fb_info(info);
0 X' e; L4 m5 ?1 U) `        break;
- t' _0 _9 I1 Y5 h    default:( r( e% i8 |. ]3 R% M/ Q% I% M- ?
        if (!lock_fb_info(info))
* b/ ]9 L1 {( ?4 _; p( r# I            return -ENODEV;
! \) E2 a' F3 `% q        fb = info->fbops;/ A& _0 H: }# k4 M- p; J- F
        if (fb->fb_ioctl)        /*fb_ioctl为空*/
( ^/ _, W% p8 B$ b. @            ret = fb->fb_ioctl(info, cmd, arg);! h1 U" y% Q; L$ ^' p3 K- q+ ~
        else4 u; o8 s% c: l! R
            ret = -ENOTTY;/ N" c3 y: ]8 L+ @7 W, U
        unlock_fb_info(info);9 x, S# N! e; a+ s* [
    }8 G# ~. i& N* ]4 m3 H: z- ]
    return ret;5 w9 {; G" f, s( E/ H9 R
}
0 A# p2 T( y: w( k! z4 @- j正如2.2小结所说的,fb_fix_screeninfo只能获取不能设置,因此,ioctl只提供了获取fb_fix_screeninfo的方法,而没有提供设置fb_fix_screeninfo的方法。7 a, G7 P, |) j0 ~
6 g: a; E' h4 I4 _# ], S+ u
3.5 小结
& N5 Z( ?* F; o2 S. b6 a9 {  本节对frambuffer的核心层进行了介绍。包括frambuffer子系统的创建,frambuffer的注册和提供给用户空间的5个API函数。下面开始介绍驱动层。: t* V: b5 W6 P4 ?
8 X$ @! L9 m0 X7 S4 q3 [! k
4. 驱动层
4 y# F) C/ y4 W$ f% \/ J本节将开始介绍S3C2440的frambuffer驱动,该驱动源码位于drivers/video/s3c2410fb.c9 Z9 J- e8 i: H  I, Z, Z
! |! n# f# Q( n7 u- K, U+ X% r
首先来看下驱动模块的初始化和清除函数。
6 `, B0 c1 g' I1 ^7 o6 y) ?% F9 g) i3 Z( l/ [% @1 P* k
4.1 s3c2410fb_init和s3c2410fb_cleanup
+ }& E9 |# O: N9 kstatic struct platform_driver s3c2410fb_driver = {
! E$ z% }) P3 F$ a% D& [    .probe        = s3c2410fb_probe,
( R( S9 a3 @4 x1 F1 u    .remove        = s3c2410fb_remove,
& H4 j% Z% E" _7 G: G    .suspend    = s3c2410fb_suspend,9 q6 i5 K6 |4 X) P$ W) v) i
    .resume        = s3c2410fb_resume,( o! F/ p4 N* `: H
    .driver        = {! d) e3 B: ?# R3 N9 Y/ y
        .name    = "s3c2410-lcd",) P0 z. I; ^. v2 V1 F
        .owner    = THIS_MODULE,
3 ~6 G6 W1 x2 L7 q    },  G, E: ~8 q: P# h4 H* c
};4 z( P. L& j5 x5 D2 I( H
$ {  m- g* j0 ]0 g
int __init s3c2410fb_init(void)
. t6 ^9 y4 U8 ]! P- R4 B{3 N( S9 d1 {& ]7 \% n
        int ret = platform_driver_register(&s3c2410fb_driver);: }; d; ~1 u6 E4 x  x
9 C) V$ y+ s; j* V3 P
        if (ret == 0)8 V  @: S5 @, t; G: _1 `6 h7 i
                ret = platform_driver_register(&s3c2412fb_driver);;
; L: W/ s/ E& {  a8 Z. `7 |2 e2 T( q# E* w1 T( N4 q# o9 a
        return ret;0 ?& }  p  Z* P! U* A
}
0 u+ i3 _. U* W1 N- ?. k, H! F
! O3 N' z3 G. Z1 X+ _9 Estatic void __exit s3c2410fb_cleanup(void)
$ U3 a; N# Y$ w( a# S{8 B$ P0 b2 a+ D$ i! |- X6 F
        platform_driver_unregister(&s3c2410fb_driver);
0 u- `! {, @" G; L( f' }  R# `# w4 L        platform_driver_unregister(&s3c2412fb_driver);
5 p, x9 b! _7 o$ S. q) k, h! E}! T& p5 @, e7 M

+ j; B7 d8 [! |" Q" Rmodule_init(s3c2410fb_init);
# S# l: c/ A! C$ ^0 n2 ]module_exit(s3c2410fb_cleanup);2 m# H3 K: E4 D7 ?, K
3 T# k5 {, {- c- M/ X. x" G
当platform_driver_register调用的最后会调用probe方法。接下来就来看看probe方法。  F5 x' z0 r& V5 L4 U' Y: H2 v+ G
4.2 probe方法
6 o8 j; c1 v  w1 R4 ^+ X6 @struct s3c2410fb_info {9 i! _/ e4 |# Z3 {5 F8 j
    struct device        *dev;
6 p# [# [: E/ Z  L4 I    struct clk        *clk;
. @+ ^9 ^& h. i* T* b4 C1 z6 x( T8 p2 t  E$ i% d
    struct resource        *mem;
5 I7 K; Y9 ?' U/ s) U    void __iomem        *io;        /*虚拟地址*/
0 V2 Y6 ~2 V& ~7 ~+ A8 b    void __iomem        *irq_base;
; H: D7 L) z+ d) M  Z" d- g0 ]) z' o
    enum s3c_drv_type    drv_type;
" J, E# x0 Z7 y- `! r    struct s3c2410fb_hw    regs;
/ ^% l7 o- R+ K5 S8 ^, U4 l/ B  F! c, j
    unsigned int        palette_ready;3 v2 }# O/ W, o5 o2 l1 T/ e5 Y& e
# l) w/ E2 h% n
    /* keep these registers in case we need to re-write palette */
" x) C" C; t* o/ l3 e+ A" I4 Y    u32            palette_buffer[256];
/ ~) \& b1 L1 d( ?    u32            pseudo_pal[16];0 ]1 k- v1 ?1 Z' P) v; g
};1 B( U9 p. t7 n7 k3 D

# `9 Y8 u6 {! S0 ^struct s3c2410fb_mach_info {6 B- v! h* T' r% R& N  K$ q: h
2 p( |5 w5 D3 }, p" R
        struct s3c2410fb_display *displays;        /* attached diplays info */$ h) I  l9 [: W& R# g1 n, r
        unsigned num_displays;                        /* number of defined displays */
3 e5 K1 U6 Q# s" D4 B  N        unsigned default_display;9 O" Y, h% x6 U* e) U' z
& c" t+ Y! Q8 e  q* ~& ~  U
        /* GPIOs */; C/ M$ f/ O. P0 F- G, Q

" O6 o) O; g- b/ R4 E        unsigned long        gpcup;# w/ ]6 P- A$ Y, g
        unsigned long        gpcup_mask;  p; `  H2 v" F$ p! Y
        unsigned long        gpccon;# U$ X  H# Q% h, J
        unsigned long        gpccon_mask;
1 }4 Z! C* l8 k: S" v5 i% [        unsigned long        gpdup;
5 l1 d4 u/ o/ i& O% e        unsigned long        gpdup_mask;2 }# J$ ^! U- D0 X
        unsigned long        gpdcon;
8 N7 m2 V* P3 `        unsigned long        gpdcon_mask;- M, T8 b* \" z; ?7 R
  f3 e: C5 ~+ k
        /* lpc3600 control register */6 c( S1 Y6 A7 y6 `
        unsigned long        lpcsel;/ a/ x( y2 g6 C. h  M: O1 N# D
};$ V/ u/ g9 F+ B: n$ G- |
% C7 |( j  v& h, [
/* LCD description */: z# c8 w& t5 {0 J6 f( j, {; T
struct s3c2410fb_display {, `  V! g9 X7 Y
    /* LCD type */
) Z5 y5 M7 ]4 O# @2 C. e6 B    unsigned type;
& y9 @( ^4 R' K  D3 G0 C$ k8 ^+ u' t% l( R
    /* Screen size */6 {, f$ @/ O: K
    unsigned short width;
* M# [/ u! Q( I9 S7 g    unsigned short height;' X& D: g  }6 F% z* X

. x3 l( \9 M- `$ h$ s" j& }$ H: ?    /* Screen info */
8 u2 |2 O+ e) ]. @0 I2 q3 [    unsigned short xres;
. g6 Y/ [# b- g) m3 R, N0 }    unsigned short yres;
' s: e) W( D+ B6 k# e    unsigned short bpp;$ j8 J' b+ K. o0 A( z

& a* M+ f5 s3 H. S" i! A! \+ Q    unsigned pixclock;        /* pixclock in picoseconds */- B" ]$ ^7 @0 Z  c+ i
    unsigned short left_margin;  /* value in pixels (TFT) or HCLKs (STN) */2 T5 j. e; S+ m. e) ?  }
    unsigned short right_margin; /* value in pixels (TFT) or HCLKs (STN) */) B7 @" `! {" G$ q: c" g
    unsigned short hsync_len;    /* value in pixels (TFT) or HCLKs (STN) */
+ k' i* ?  G! k7 B" \, M# o, t    unsigned short upper_margin;    /* value in lines (TFT) or 0 (STN) */3 f+ t$ [, M3 d* \1 G5 L
    unsigned short lower_margin;    /* value in lines (TFT) or 0 (STN) */
6 H6 U6 o" Q; ^0 i. {; Q* M1 {    unsigned short vsync_len;    /* value in lines (TFT) or 0 (STN) */
  O9 N( A! b  W
! E0 b" L. _( Q    /* lcd configuration registers */7 Y2 c% f+ m# ~: E4 U  U
    unsigned long    lcdcon5;
' U& `! B6 J- q};7 T- X: N% G; L7 ^

# ?& Q. u2 o. p4 I/ B0 v% T2 ?) Vstatic int __init s3c24xxfb_probe(struct platform_device *pdev,8 K0 E) }; i  g* H
                  enum s3c_drv_type drv_type)
/ g  S) b4 X+ K6 Z1 `$ g, E7 j# i+ R$ o{2 d7 ]3 U$ O* z4 E
    struct s3c2410fb_info *info;
. J/ M& w7 M5 w0 ~. b& u- o4 O( _  x    struct s3c2410fb_display *display;2 u: O1 t0 p  L& j0 \
    struct fb_info *fbinfo;
4 ], V9 k" a* y; |7 _' a    struct s3c2410fb_mach_info *mach_info;
- S0 o( [. Y$ `1 r2 k7 l    struct resource *res;" ~* T9 b4 r9 T; B- j7 }5 F
    int ret;- \# d. r8 r6 W% [
    int irq;
( \1 x2 A, m8 v3 K& O    int i;
2 ~, M. n% d  t. q( p! F" H    int size;
" C0 S: U; {$ Y( Q+ h    u32 lcdcon1;) D  ?9 }4 n7 y+ w) X
    /*dev.platform_data由函数s3c24xx_fb_set_platdata(mach-smdk2410.c)设置,指向s3c2410fb_mach_info*/! K; z( b9 a# X% K2 T) x
    mach_info = pdev->dev.platform_data;    ! `" H, N# Y2 l% b) G
    if (mach_info == NULL) {
  Z) Y0 b5 Y3 }5 }: H        dev_err(&pdev->dev,
5 z7 E* b0 \5 [            "no platform data for lcd, cannot attach\n");5 M+ X' {% U- K- _
        return -EINVAL;
0 l0 G( u) h/ l* `$ m2 |' D7 m/ W    }
, O! i( w- V2 u; p( U  F                                    /*在mach-smdk2440.c中,default_display=0, num_displays=1*/+ i7 q7 I/ C2 K$ y# |
    if (mach_info->default_display >= mach_info->num_displays) {     1 M. c2 I5 l! ?$ p) r
        dev_err(&pdev->dev, "default is %d but only %d displays\n",
! T. L+ [5 f& F' V  y3 [* V7 |            mach_info->default_display, mach_info->num_displays);
# H* r+ f% l9 A1 J. O        return -EINVAL;5 d5 P* a4 S  q) s& l8 d/ H* b' c
    }
! j! `1 a) `% F$ h
7 b4 m' t8 ^/ k: L. U* }4 h    display = mach_info->displays + mach_info->default_display;2 n- j1 m# I2 g/ _

0 Z! W9 }8 X- ^* K8 q0 f7 D    irq = platform_get_irq(pdev, 0);    /*获取IRQ号,16号中断*/
" l; _+ E; P5 j3 L" B    if (irq < 0) {3 e9 M5 q' a, \* B8 R
        dev_err(&pdev->dev, "no irq for device\n");# J8 V% ^9 ]$ g% m
        return -ENOENT;# p6 D4 I6 {" n' E2 H! z+ C7 _
    }! j" P+ u1 P! s; }9 E
                                        /*分配struct fb_info 其中包括sizeof字节的私有数据区*/
0 q( C7 q" B+ R! j# h3 B% `    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);* K3 s0 Z5 V# l; L, D* e( _
    if (!fbinfo)( x6 E" F% c" G& ~) l2 m
        return -ENOMEM;
$ c) {: @2 @- T0 L' B' q$ J) f
% v$ k; B6 q4 R1 w6 D    platform_set_drvdata(pdev, fbinfo);    /*让platform_device->dev.driver_data指向struct fb_info*/
/ B: c) t, T8 x1 x
, g; p6 f, z7 y8 G2 Z9 g" v* f    info = fbinfo->par;                    /*par指向s3c2410fb_info*/
9 O* P- a9 j* I. r' c9 C' E    info->dev = &pdev->dev;/ y# O  P3 ]! N- k
    info->drv_type = drv_type;* J: c9 m) @% _! L( }/ n7 N

9 @4 f1 \. I- n) v2 H: k7 H    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);/*获取平台资源*/. B" ~% ]( B3 l5 c. q; q
    if (res == NULL) {
& s+ w1 W* y1 D( F        dev_err(&pdev->dev, "failed to get memory registers\n");
9 M& [% D& A# Y" ^        ret = -ENXIO;7 A0 F8 m. R/ l
        goto dealloc_fb;9 \2 v" p5 E  w' \2 I6 q1 n1 p- ?! @
    }8 C( R8 r4 W( z" E  b2 [4 x% A

* H/ ?0 k0 u/ y$ _/ ^$ h; o    size = (res->end - res->start) + 1;                /*IO内存申请*/
0 z% h* V: L" _" g2 o7 ~" o    info->mem = request_mem_region(res->start, size, pdev->name);    # i6 ?6 T; G9 m6 G) F
    if (info->mem == NULL) {
5 y; o4 g* F  c# f% X9 L        dev_err(&pdev->dev, "failed to get memory region\n");
( k" L4 T2 M' b4 t3 a8 j" b9 f        ret = -ENOENT;& h  B7 X; C6 m! `( d0 S8 B
        goto dealloc_fb;8 H( a4 \1 w4 W* j. J6 ?2 ~
    }
7 N+ C5 J2 |+ r1 E: l
+ x, @" T; d. J7 E. G" q    info->io = ioremap(res->start, size);            /*IO内存映射,获取lcd第一个寄存器的映射地址*/                3 E! Y4 d; ?) ~
    if (info->io == NULL) {5 t" A  z. _" T0 U7 X1 ]5 c
        dev_err(&pdev->dev, "ioremap() of registers failed\n");        
& w7 i6 G- Z8 B2 q/ I        ret = -ENXIO;    1 ^: z% p+ H  V3 E# X# ]/ V: q
        goto release_mem;7 i0 a& x; k. R& r$ q% o
    }! H5 `0 D7 ^% b& c' j% M8 f$ s
                                            /*irq_base对应的物理地址是0X4D00 0054(寄存器LCDINTPND)*/; W* w8 @  z+ e+ G
    info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);; V. j' A6 J8 n7 F0 @- S
$ W6 @* D! Y9 f) Y+ o2 [
    dprintk("devinit\n");9 l4 `8 b, D, V- y4 g  m

  {- e6 Q: w. e4 H% G" T    strcpy(fbinfo->fix.id, driver_name);    /*复制名字*/
, s4 J& Z6 k& N, m% k* Z% A& V/ h1 S4 H3 v# `; x& Q
    /* Stop the video */3 w. B/ _' k6 M- s. V2 d* V8 a
    lcdcon1 = readl(info->io + S3C2410_LCDCON1);& x/ ~6 @& ?# I
    writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1); /*禁止LCD*/
! X7 A5 j5 G  `% s2 I; z, c- M, `1 Q2 r( F! k
    fbinfo->fix.type        = FB_TYPE_PACKED_PIXELS;
6 ?' A4 l1 T$ ?5 Y) L! V& a3 i# i    fbinfo->fix.type_aux        = 0;
5 M* e; I& d+ X! N9 O! u& Z    fbinfo->fix.xpanstep        = 0;. A/ F* ?1 n1 i  W& p- R$ y
    fbinfo->fix.ypanstep        = 0;+ P" |( Y8 h* J/ h" ~: R8 f
    fbinfo->fix.ywrapstep        = 0;
2 P& Q) @% O5 v" z    fbinfo->fix.accel        = FB_ACCEL_NONE;    /* no hardware accelerator    */
& n% ~& O: I0 R" f# D3 c* P0 X. |
% V$ k# Q# w5 _    fbinfo->var.nonstd        = 0;7 }; ]7 J* }* `3 f- T8 h! u
    fbinfo->var.activate        = FB_ACTIVATE_NOW;
1 ^$ g5 i! V: D* @7 w  p    fbinfo->var.accel_flags     = 0;  `" E. Z3 O1 F4 w
    fbinfo->var.vmode        = FB_VMODE_NONINTERLACED;# ^& T5 L2 L0 s0 M) f/ S

' u, @0 a! F1 S* |    fbinfo->fbops            = &s3c2410fb_ops;0 W; L1 Z2 L7 ?  o* o$ y' w
    fbinfo->flags            = FBINFO_FLAG_DEFAULT;
( }* ?' l6 T# R    fbinfo->pseudo_palette      = &info->pseudo_pal;- ~" a8 {: k5 A& c4 H

7 T' X- I7 |( b/ w( M, h. ?    for (i = 0; i < 256; i++)$ C! ?, n' \3 K8 ]
        info->palette_buffer = PALETTE_BUFF_CLEAR;* L. P8 W! \& A

9 u! V& Y+ c( d' C    ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);    /*申请IRQ,快速中断*/' W. b% q; C# J0 c% u
    if (ret) {/ q$ ^$ @; t1 w  y, N' U  ]
        dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
3 j0 [. i' `5 v8 Z' i5 G; o        ret = -EBUSY;. a* j6 @0 O) E- Q
        goto release_regs;1 f9 ~1 h( T5 G2 e0 |/ g4 \: l
    }+ s. A0 Q2 |3 g1 i/ n0 H2 M7 f- n' R
+ s" n) v; }8 o2 q7 a
    info->clk = clk_get(NULL, "lcd");        /*获取时钟信息*/
# B' S4 g1 L+ Y0 {& q/ C6 A8 ~    if (!info->clk || IS_ERR(info->clk)) {7 O9 I/ ]! e9 w! h- n* Q: v' R
        printk(KERN_ERR "failed to get lcd clock source\n");4 {5 B  {/ ^7 y/ M1 R, m) C  `
        ret = -ENOENT;6 a& x& K- Y. @/ o- c, o
        goto release_irq;
2 e* B% D- n" _9 S    }
  H# g. J; ^  r" {4 P+ T+ k
6 w- x  Q! H4 @. L' ^. {4 L' W    clk_enable(info->clk);                    /*使能时钟*/7 J8 k8 P+ h6 D
    dprintk("got and enabled clock\n");5 ]; X8 t( b* k

) C: U+ w* M0 e, N5 A* f    msleep(1);7 _7 O7 J8 w3 _& N

; z2 r: O6 x9 ?& j. y) b1 @    /* find maximum required memory size for display */
5 f* c0 l7 g) b7 _    /*在多个屏幕中,找出需要的最大memory*/0 p' a/ E7 H* j2 n# \( G
    for (i = 0; i < mach_info->num_displays; i++) {
7 i, z  t9 L% L9 D        unsigned long smem_len = mach_info->displays.xres;- Y0 t3 N+ u7 `6 J' Y: ]+ D
        /*所需的memory空间 = xres * yres * bpp / 8*/
7 F3 h. R  r7 l5 r$ U+ Q        smem_len *= mach_info->displays.yres;7 v3 W! q5 T6 x. R! e7 @7 D8 v
        smem_len *= mach_info->displays.bpp;. q- \: n3 [" H
        smem_len >>= 3;
$ C* A4 H0 N1 N( W        if (fbinfo->fix.smem_len < smem_len)2 {! Q% M5 v( G+ a# J
            fbinfo->fix.smem_len = smem_len;6 B) z. Y& N" w& \' |  T. `4 e
    }
6 d" t$ R& W$ ]+ Y7 ?
4 l  F& p7 h  ]0 @    /* Initialize video memory */    /*根据上面fix.smem_len的大小,获取DMA映射内存,一致性映射方式*/% f: ^3 u" T1 {( N% P, u
    ret = s3c2410fb_map_video_memory(fbinfo);9 y+ |! K& z4 C
    if (ret) {4 B, q* s- q  q: u6 F
        printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);- B' C6 C$ A2 Y# o
        ret = -ENOMEM;
! h) i1 U5 W/ F; ^8 t        goto release_clock;4 t! j) g; \% W& M6 i1 h/ R  k
    }
4 M: W% E* a9 \2 e$ x/ Y# G: S/ e; }6 \& o5 R. _8 Z; M7 ]; Y
    dprintk("got video memory\n");
1 t. z3 y+ s3 y- H/ L' c# P; F  \9 z/ i- K4 a8 N# u
    fbinfo->var.xres = display->xres;            /*320*/
6 w% r+ k* q  w8 z7 m    fbinfo->var.yres = display->yres;            /*240*/   
$ b8 }5 A( b4 R/ \! Q    fbinfo->var.bits_per_pixel = display->bpp;    /*16*/
2 @" O. b( ~" I) c' r3 c) k, Y+ Y: ~( Y) W8 x% V$ B; z. ]
    s3c2410fb_init_registers(fbinfo);            /*LCD寄存器初始化*/        
5 I' A  U& Z, b# i0 T. z! Q4 E0 z) t
3 e/ c0 `. ^) n; H: |4 S3 m    s3c2410fb_check_var(&fbinfo->var, fbinfo);" k; D3 b: {) |( b1 p8 w

3 r7 v# l9 _5 d$ n' E8 X/ P4 A3 F    ret = register_framebuffer(fbinfo);            /*注册framebuffer*/
2 z* t6 l2 Y( P% E9 O0 g1 b    if (ret < 0) {% }  Z2 y5 G0 L( ?; K' V& A
        printk(KERN_ERR "Failed to register framebuffer device: %d\n",ret);
- u% d7 `: x8 o# z        goto free_video_memory;! n# B( J7 d4 P: E& y/ D
    }
/ i* c- }0 w# j+ |! n6 M
4 O2 X7 O8 y' l! B, A    /* create device files */2 d/ j' S# W6 k& A4 O, p9 H
    ret = device_create_file(&pdev->dev, &dev_attr_debug); /*添加设备属性*/0 w; y; E7 K% u" I( @. d
    if (ret) {) V$ u& p9 B* g8 x2 u
        printk(KERN_ERR "failed to add debug attribute\n");' X, I! Q- P1 }2 E; }
    }% M% H: J2 y. \0 Z# y# p& y
" o4 G4 K( v, ]0 y( L. d! M, S
    printk(KERN_INFO "fb%d: %s frame buffer device\n",
* u- [% U3 e# x4 W6 P$ L4 I        fbinfo->node, fbinfo->fix.id);
! b0 o# d4 E! A. \* t7 g7 ^9 {
) Y2 _0 P% O9 {. \9 l    return 0;
' @8 H  C( ~( F/*一旦某个步骤发生错误,以注册的相反顺序开始注销*/  x" O7 o, \; W0 P% q# b9 T
free_video_memory:  N: P  C$ T6 p& I* g
    s3c2410fb_unmap_video_memory(fbinfo);( g; p7 O& p" `5 M  r. C
release_clock:) S; M, N& n! l, Q. l; X7 m
    clk_disable(info->clk);
2 e& U- t* O7 u6 d    clk_put(info->clk);
; D6 {$ e2 t! P, O, n$ ~$ Trelease_irq:
8 p0 @1 a& m4 x2 Z/ J- Y9 A! v4 N    free_irq(irq, info);3 w4 I. W4 U' u1 _: {
release_regs:
( x$ G( U; a/ o  J; {% U9 W    iounmap(info->io);/ S* v3 v5 N4 q+ P( X
release_mem:
4 G8 H8 V9 s/ R5 b' ^1 \% A    release_resource(info->mem);
1 z' I) J$ i' Q+ ^    kfree(info->mem);1 ]+ V+ v$ g9 l2 N" e4 A2 D$ |
dealloc_fb:+ e3 Q' O, {, N) ?; D9 U' ]+ z
    platform_set_drvdata(pdev, NULL);/ F/ K0 M/ a( ~
    framebuffer_release(fbinfo);   
( P+ H: q0 B9 R/ {    return ret;) l6 V* P4 P$ y  Z
}2 x0 B" D4 t4 s7 `+ l' V

3 k0 {: X) `& a这里使用了三个新的数据结构。s3c2410fb_info是驱动程序使用的,里面将保存所有驱动程序所要使用的资源等。而s3c2410fb_display和s3c2410fb_mach_info,是由板级信息,通过platform总线添加到内核中。8 m3 N, j& F* ~% s6 V3 n  O

' ~; S& }. w+ W) ~, w- R6 \" {s3c2410fb_display中的成员将被复制到fb_var_screeninfo结构中。
  F* j- f  w$ m( C% ]& F/ p' W6 S  D7 j' C# h
该板级信息的定义在arch/arm/mach-s3c2440/mach-smdk2440.c中,来看下
* y5 e0 ^) d2 k" E. D* n
+ g7 s& _5 v5 t$ I0 |8 \! u7 v# bstatic struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
3 w% w) ?9 u3 t$ _: y! P% `; {
; K# ]3 b# I% }, x: L        .lcdcon5        = S3C2410_LCDCON5_FRM565 |
: M7 y8 c2 F2 D7 b5 q& r                          S3C2410_LCDCON5_INVVLINE |( o& x3 X/ G0 S# L8 t8 E
                          S3C2410_LCDCON5_INVVFRAME |
- `4 l/ j% W% l6 L# ?* v0 ]9 }" K                          S3C2410_LCDCON5_PWREN |
5 v, A2 r( w, J  m! A. Q+ l                          S3C2410_LCDCON5_HWSWP,
- ~0 ~9 a" |, @7 E- Y9 p/ m
6 e+ `; r8 O& a9 }& u+ y4 ^9 o        .type                = S3C2410_LCDCON1_TFT,
* B8 i  L" B, s% o2 l. c9 _! x* G& @
        .width                = 320,//240,
  ?4 K+ ~" T3 B) A- ?! L0 ^        .height                = 240,//320,+ d  S6 p* f/ g. a; @3 D4 s
0 E" q# |% s) U7 K9 z8 o
        .pixclock        = 149000,//166667, /* HCLK 60 MHz, divisor 10 */
2 Z0 j9 u& ~" a1 `$ R3 [/ q        .xres                = 320,//240,
9 [: A. A7 T/ z, I3 s/ Q        .yres                = 240,//320,
2 g7 M$ z$ z8 F" y! G; p        .bpp                = 16,
7 x* a7 {& \# c4 h! Q. G        .left_margin        = 20,
' X) h$ X" [4 K$ t/ ^' s% D        .right_margin        = 38,//8,
3 ]! ?: G# ?5 L8 u/ t        .hsync_len        = 30,//4,
" p9 I# A2 R! Z+ m% ]4 _+ K        .upper_margin        = 15,//8,4 X8 t8 @) e' t# R7 `% v
        .lower_margin        = 12,//7,
( i+ @: R) z( z5 Z% @, \& F        .vsync_len        = 3,//4,& h, Q; t0 t1 j$ p0 A7 _
};" I* V4 ]' N+ y! i1 Z3 a

. ?! r% T: B( [: Xstatic struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {% [' `* x7 k0 q. s2 q: A8 R. y
        .displays        = &smdk2440_lcd_cfg,
  g4 |6 u1 n" y1 Y        .num_displays        = 1,: L- e- f# m% w: C
        .default_display = 0,5 E( \1 m: s3 f& m" o. ], U

/ _7 O: x" M0 B! N$ H" c#if 0& {) i+ O- g0 h8 D% a5 T" G
        /* currently setup by downloader */
& J( E4 d- S, p4 s: D        .gpccon                = 0xaa940659,9 ]$ A$ j4 M) Q5 Z! G; e1 N
        .gpccon_mask        = 0xffffffff,5 B' q# J0 E2 w, y0 B1 X8 g
        .gpcup                = 0x0000ffff,
9 K% V5 z3 p+ D6 C( {3 o" B        .gpcup_mask        = 0xffffffff,
0 T' U. \" b# V& t+ z        .gpdcon                = 0xaa84aaa0,  B. ~& R5 A$ b4 X  u
        .gpdcon_mask        = 0xffffffff,4 n# |# W$ _7 k7 l
        .gpdup                = 0x0000faff,
# w$ f6 _3 P% N* _. b$ p        .gpdup_mask        = 0xffffffff,
5 i" g# k9 G# w9 O# N$ t" I#endif& h% s- A1 I# L4 `. J
//no
; J0 m  n) b' O8 F& a//        .lpcsel                = ((0xCE6) & ~7) | 1<<4,5 U9 f7 m3 n9 @# p/ x" S/ q( J
};4 ?/ b7 f7 }# Z: y( A0 s
! i* M1 _! E$ C2 k+ ?, G# y
这里NOTE:每个LCD屏幕的参数不一样,因此上面的参数会有所不同,这是需要移植的地方。
! E8 x9 p5 B( Q! T" [# u0 b$ H2 M7 l# I
随后,我们看下在probe方法中调用的几个函数。首先是s3c2410fb_map_video_memory。
3 b7 t6 L7 {1 ]# h- R' i( {8 ?: H) c5 i6 Z# ?
/*
2 d' K, C+ B5 |$ z4 C * s3c2410fb_map_video_memory():
4 Q  o0 `/ h! c+ h6 y) o1 V *        Allocates the DRAM memory for the frame buffer.  This buffer is4 `) C0 t/ X& h/ K4 z. o
*        remapped into a non-cached, non-buffered, memory region to# y3 r* D/ I3 o$ o, x( K3 e
*        allow palette and pixel writes to occur without flushing the: o7 X: ]% g( A, e
*        cache.  Once this area is remapped, all virtual memory
% H: f. N* s; h) Y  @- ?, d *        access to the video memory should occur at the new region.
5 D, T; K4 ^8 Y% Z/ @ */
, ^& N9 c/ s( Lstatic int __init s3c2410fb_map_video_memory(struct fb_info *info)) K7 N& m& X5 h8 _
{! G6 F# t- x: N0 a6 l6 D, c" ?6 P
        struct s3c2410fb_info *fbi = info->par;
& i; S3 c3 |3 n# U5 y& S: S! a8 W        dma_addr_t map_dma;* ^2 R) w: ]' T3 y# K1 L
        unsigned map_size = PAGE_ALIGN(info->fix.smem_len);
8 G6 b5 _+ z' P% v1 B' Q3 K
9 ?- f! u# x: u# X% d  A        dprintk("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size);3 H# W5 u2 M4 q& b
                /*分配DMA缓冲区,并保存DMA缓冲区虚拟地址*/. W) y3 J7 @6 h5 ^$ U
        info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,: U' e# i5 P" Q5 G, s/ J' w
                                                   &map_dma, GFP_KERNEL);
, F% o$ \$ o/ A3 J* s: j4 q0 F. ]7 ^$ j( Y8 a
        if (info->screen_base) {9 z# m, M2 K( t: W
                /* prevent initial garbage on screen */
: r! i0 i! o; ]" }+ I9 u                dprintk("map_video_memory: clear %p:%08x\n",
. l3 W% s- r% h6 i# R                        info->screen_base, map_size);& u! L( S) N+ B* Q
                memset(info->screen_base, 0x00, map_size);        /*DMA缓冲区清0*/) |6 {2 x- p  |7 |2 `& r

( @% U. c; Y4 D7 v                info->fix.smem_start = map_dma;        /*保存DMA缓冲区物理地址*/5 U. \7 u( v; n) R: A4 K, u

2 L* O& [/ J- g4 I                dprintk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",$ R: E% D3 f9 P' M( i
                        info->fix.smem_start, info->screen_base, map_size);: ?6 r+ G  b, m7 b$ S
        }
/ i$ |; b* e/ A4 ^6 t$ H
5 a3 `/ [& E6 |  ?/ ^2 b/ c5 P        return info->screen_base ? 0 : -ENOMEM;3 \0 ]6 l5 A- Z7 \" f6 r
}4 g& ?2 K: n* {0 d% _
2 X8 L5 @3 @. F* Z3 D, ~+ c
2 `$ P. `) J5 {+ i# o6 V8 G0 _
该函数根据fix.smem_len的大小,分配了一个DMA缓冲区,保存了该缓冲区的物理地址和虚拟地址。
- q1 p0 P' i' Y$ s7 g  k( C. k8 o" z  a8 E) e
接着是s3c2410fb_init_registers:
$ c  J7 ^" E) M1 P" V$ _1 m! L- X" t: R# }! m3 Q9 j4 V& M; L* x
& e1 v! b$ X, k
/*
+ P' e5 q- T; K% a* V( @/ c * s3c2410fb_init_registers - Initialise all LCD-related registers6 F, g  w' H; `9 w4 Z" q; x
*/
) j0 v! s' o. ?! {% ^3 J2 rstatic int s3c2410fb_init_registers(struct fb_info *info)$ A0 s" z' g2 ?2 T
{
" e: e8 O' I" x& B( z; ?+ W    struct s3c2410fb_info *fbi = info->par;        /*par指向s3c2410fb_info*/% n. g% l$ z2 O7 D- u3 f+ Z( _
    struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;3 [, O& S( e, D# l6 S) u
    unsigned long flags;) G9 l) \2 G8 w
    void __iomem *regs = fbi->io;
' k- b) f, @) f2 W    void __iomem *tpal;9 d: A2 ^9 F$ @7 ^$ j. w! I
    void __iomem *lpcsel;" k% L" i6 b% G3 a6 _
1 C  l* {# r$ p+ c, G7 X
    if (is_s3c2412(fbi)) {/ w; S1 X: O% ]
        tpal = regs + S3C2412_TPAL;. l3 M, s# i6 M) [4 V( O
        lpcsel = regs + S3C2412_TCONSEL;
) P- D0 a8 O. ~3 C* F0 i    } else {
/ T- B1 W$ [; ?        tpal = regs + S3C2410_TPAL;% f& q+ y3 c  s7 H2 m& p1 P2 L
        lpcsel = regs + S3C2410_LPCSEL;
. t2 L7 D  F& S6 T7 J" A    }+ S8 k2 a5 E+ F
* \. A) ~, k/ L) ^. z0 d
    /* Initialise LCD with values from haret *// D! d- \1 f2 s6 H, T5 t0 t) [
7 p! A! c$ {2 V6 p
    local_irq_save(flags);                        /*禁止所有中断*/. p  W. E' \' f* R
0 k- p2 x) Q, x) A1 j% Z
    /* modify the gpio(s) with interrupts set (bjd) */    /*初始化io管脚*/- l! F. \0 G" a$ Q6 ^) X9 j

6 C* `* [& \& ?) F8 \' A  K    modify_gpio(S3C2410_GPCUP,  mach_info->gpcup,  mach_info->gpcup_mask);
7 o2 z' j0 G( g0 t1 Q6 }2 D    modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
0 P: T0 n" h0 B1 u( g    modify_gpio(S3C2410_GPDUP,  mach_info->gpdup,  mach_info->gpdup_mask);
( t& j& Q' t& @2 e    modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);5 D& m; X6 r& a( H6 N* U" _8 H7 g' b
( ?8 R5 Z! S  A# X
    local_irq_restore(flags);                    /*恢复中断*/( e( Z# a3 f6 ~  p4 _3 z1 D: Y* F

: p* [6 g# x7 |, z+ w' b1 v    dprintk("LPCSEL    = 0x%08lx\n", mach_info->lpcsel);    /*设置TCONSEL,禁止LPC3600*/
4 q* `7 d) I& U- _( {; L6 j4 S* C    writel(mach_info->lpcsel, lpcsel);
/ g9 A$ ~0 S" |# \2 ?
* {& m. C4 _7 O    dprintk("replacing TPAL %08x\n", readl(tpal));5 J; P' e1 S4 s7 Z+ @  ^
, ~( `/ f  G; Y+ v
    /* ensure temporary palette disabled */: a/ l5 @% ]6 R) R% v
    writel(0x00, tpal);    /*禁止调色板*/
( ^+ A5 t& Q- R1 i5 F$ ]$ ^. L- |0 m7 C/ I6 l% {; u
    return 0;
! r1 }# D# O- p}
( y1 C$ `7 ^2 O6 u- J# ^! a, ?) Y: O
static inline void modify_gpio(void __iomem *reg,2 M8 N. M& j3 z9 |
                   unsigned long set, unsigned long mask)  k2 e: m8 \( \+ ^" M
{; d) Z$ j" q2 a  j9 q; P
    unsigned long tmp;
& b, O# F& r8 n& ~  {/ w+ |) h1 H
  D6 g$ Y, e# M3 w. o* G" y2 N    tmp = readl(reg) & ~mask;. y. U8 T( m* J; R" A( c$ L7 u
    writel(tmp | set, reg);
) Z% H6 H7 K8 d' n8 Q: X+ b6 I}
' `1 T$ }% I- n* b最后是s3c2410fb_check_var:
5 `8 q; i" S" ?3 a( \3 s% E+ H( D; g0 [8 A/ A
/*5 k2 W; `  w9 ?( Z4 c
*        s3c2410fb_check_var():
6 L- n7 b3 [! k) o& h7 W, @ *        Get the video params out of 'var'. If a value doesn't fit, round it up,
$ B5 B/ h+ f& O0 v& V2 l& A *        if it's too big, return -EINVAL.
$ l1 F4 m. S+ }6 a1 e: g0 X *        检查变量的合法性0 X5 h: n! o: [; X
*/
. t" s3 B4 {5 ?" R, O" c/ _+ i9 Ustatic int s3c2410fb_check_var(struct fb_var_screeninfo *var,! U5 |0 D6 T6 m# ]7 H3 C9 _6 `
                               struct fb_info *info)& {' P& q7 o+ F/ c0 r
{7 ~  N$ J9 A( W
        struct s3c2410fb_info *fbi = info->par;                        /*par指向s3c2410fb_info*/
; n1 i! E2 Y2 J2 v9 v        struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;/*指向s3c2410fb_mach_info*/  ]6 B7 U5 C% m
        struct s3c2410fb_display *display = NULL;4 J! n% m! S* Q5 y  N
        struct s3c2410fb_display *default_display = mach_info->displays +
) w/ p: i* D! w  B                                                    mach_info->default_display;
) X9 I/ \9 n2 p  G- b2 W$ S6 @        int type = default_display->type;         /*S3C2410_LCDCON1_TFT*/% v9 `5 T* a6 @9 T
        unsigned i;, o9 g% i1 X' E* O
6 \8 N2 @( ?/ k& v; u# e; B) `9 ]
        dprintk("check_var(var=%p, info=%p)\n", var, info);
2 z2 q1 r) m, @: D& a0 i) q' C6 s7 o4 e/ z( M( i
        /* validate x/y resolution */
% W" N) D% v3 R& L" e        /* choose default mode if possible */                        /*var中的成员在probe中设置*/
' p  g0 `5 V/ m( W; w3 J6 l        if (var->yres == default_display->yres &&; K% _0 f' C. U( _5 c
            var->xres == default_display->xres &&
$ Q  m! k% E0 A6 d* y4 R+ P            var->bits_per_pixel == default_display->bpp)
, ]  O5 h0 M% Z! w. X3 s                display = default_display;
0 P% I& o; T' ^        else0 X: D3 K, o0 W! j( i
                for (i = 0; i < mach_info->num_displays; i++)( ~% R7 G+ t+ b* B9 c+ B
                        if (type == mach_info->displays.type &&
6 }! ?6 G" H" u- F/ P& C                            var->yres == mach_info->displays.yres &&
; H  E& O8 _! |6 b9 G                            var->xres == mach_info->displays.xres &&
3 E) \7 X( d7 `5 a- i                            var->bits_per_pixel == mach_info->displays.bpp) {
# ^5 _0 W, u$ p9 b4 l                                display = mach_info->displays + i;# B5 u; m# r: c
                                break;
, ^; J& q' U, B, p                        }" O0 \- E. ^9 Q+ N* O7 l5 h- o/ @

& a# H1 z/ o8 y        if (!display) {
- ^2 j2 S' k; N& S) _# ^                dprintk("wrong resolution or depth %dx%d at %d bpp\n",+ F; k; T* U- q- {
                        var->xres, var->yres, var->bits_per_pixel);/ T  P& v; S. F6 F+ ^; k
                return -EINVAL;8 S* q& A- T9 t$ S) \9 [( ]
        }
' E/ e  y$ a# Y2 T
0 x; k+ L% F+ z0 `; ?        /* it is always the size as the display */" e7 W: v- }# R( G; }5 \
        var->xres_virtual = display->xres;7 T8 b) ?0 E$ ^2 s8 y+ D
        var->yres_virtual = display->yres;! N' p5 f$ \7 F0 [. `4 O
        var->height = display->height;
% i' T' [; X6 s5 `9 T        var->width = display->width;5 \6 H! |; H) F' M4 F6 M

( j* |, O/ A* w. ^% I        /* copy lcd settings */% R! P% b' D& y  t
        var->pixclock = display->pixclock;9 V( ]" p7 w! ]! b2 D, K. g0 a( }( _
        var->left_margin = display->left_margin;1 e* Z4 p, N5 N3 S
        var->right_margin = display->right_margin;
& l8 T; ^) ~' }) e: G; w$ R: [' y( b        var->upper_margin = display->upper_margin;
, Y# N- C/ i) ?8 z* t& e8 c        var->lower_margin = display->lower_margin;2 A, `8 A0 |# }7 A8 Y
        var->vsync_len = display->vsync_len;. m' b+ p% e' ~8 S
        var->hsync_len = display->hsync_len;6 \$ o/ f6 ]- Y6 G

5 x3 `; t$ L3 M" ?        fbi->regs.lcdcon5 = display->lcdcon5;
" M% T0 K+ s3 Y; I; |2 C) ]        /* set display type */
$ V0 P9 s0 ]" {$ h- F( t% }9 T        fbi->regs.lcdcon1 = display->type;
4 S; j" c" h0 H2 ]: {
0 g: k$ x9 A, I* w$ X, C        var->transp.offset = 0;- \' i0 M& E: T* \3 z/ T" z6 F* O
        var->transp.length = 0;
; K3 j5 v1 Q" o/ n        /* set r/g/b positions */5 \6 f: J& g; i3 Y3 ^# F, g
        switch (var->bits_per_pixel) {
5 S; Y- a2 S! S5 z        case 1:% i5 q0 a- w. |9 X1 @8 E
        case 2:
1 X+ j: c. l0 Y5 \3 l: r        case 4:
; Z' R$ S  z9 Z; q5 ]* Q                var->red.offset        = 0;; g7 w" o8 F" b" }0 A
                var->red.length        = var->bits_per_pixel;
1 F# J' L# I2 R2 c                var->green        = var->red;9 L+ \# L/ J0 `- k, I
                var->blue        = var->red;
: P) N# }; U, D8 k; p' _& T                break;$ v4 x# E+ Z# U6 {/ m# N
        case 8:
; F7 `6 @' ~* l! Y2 W) N: \% A% i                if (display->type != S3C2410_LCDCON1_TFT) {7 t. u0 `6 ]8 q3 x' V
                        /* 8 bpp 332 */
! d0 q9 c  g" G& E' y                        var->red.length                = 3;
7 B1 ?1 {$ W2 f3 s/ F0 E% t; F/ ?. c                        var->red.offset                = 5;. l' \8 d* ~6 `: ?
                        var->green.length        = 3;
' W! ^! n+ K1 n9 i3 j8 v5 A% M                        var->green.offset        = 2;
7 u2 g" `- r7 ]4 k4 j                        var->blue.length        = 2;' i% i5 C% S; j$ g1 ~
                        var->blue.offset        = 0;1 Q4 a/ Q/ H! W# ^1 z: @4 g! i
                } else {
: \! Q( ?& t! u# n# |                        var->red.offset                = 0;
8 q+ o, y, V- f  E( H& l                        var->red.length                = 8;
! j% K- c* h6 H0 t                        var->green                = var->red;
0 t0 ~+ N2 H. K3 p4 y                        var->blue                = var->red;4 S; [$ Z$ X6 L" v0 v
                }
) n4 ?+ d8 c+ K) h+ c9 s4 Q; f7 W: L                break;
6 O% H7 _. S& ]0 ^9 c! K! }        case 12:4 E" }& D& Q  Q2 L: h( G
                /* 12 bpp 444 */( ]0 p4 V" M( }9 q; m7 y* U" B
                var->red.length                = 4;5 U/ u$ ~( Y5 G; [
                var->red.offset                = 8;
5 l# c5 D* F& V                var->green.length        = 4;
! @6 A& M2 m4 O: e" p/ s0 N                var->green.offset        = 4;
+ g1 I7 e& N& u/ i# J  ?                var->blue.length        = 4;1 Z! C/ T( C0 _' l) a) T, F" u
                var->blue.offset        = 0;: Z! s- d' h2 d& j) x4 s# v4 m1 h
                break;( o1 I" b! E% L  T1 G% z: V5 x, i
& A0 R0 V; V* a, [1 c+ x
        default:5 s% f' q6 e# b) ~
        case 16:
- x# a2 \% x# J7 q6 ?8 {- k                if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) { /*使用565格式*/
4 G: U1 U( P3 @  {0 G. y) p                        /* 16 bpp, 565 format */2 y) g6 \1 a, v. N" ?5 y1 T+ r( B
                        var->red.offset                = 11;
- R6 m! w( e7 O                        var->green.offset        = 5;
/ u( k. Q' `5 O                        var->blue.offset        = 0;
0 J! t$ `" M7 P  M) x* w8 L                        var->red.length                = 5;
5 W( D8 L* L% k                        var->green.length        = 6;6 a! d1 e6 L: H+ w* I
                        var->blue.length        = 5;+ p+ \+ A( [9 K/ h+ [; h
                } else {% S( {6 V8 z0 P6 E* ?7 i1 M
                        /* 16 bpp, 5551 format */& Z0 ~5 Z9 z% R# e3 {2 k
                        var->red.offset                = 11;5 d. A( T( ]! m  K& j' ?) ^
                        var->green.offset        = 6;
. p5 f; {- A3 }+ }                        var->blue.offset        = 1;) [5 n: s+ D* z; }+ m9 I- ]
                        var->red.length                = 5;% X, Z5 H  s, {# n" O! I
                        var->green.length        = 5;
  c% ^" k: f( {$ M$ J9 c" Q1 l# Y% M                        var->blue.length        = 5;* X% P; P1 f5 w6 E6 m( l  X
                }
- d- p4 [6 @, q& N$ S# T) x                break;3 e  {5 r: ], ^  ^
        case 32:5 H5 c& a' k6 F7 G
                /* 24 bpp 888 and 8 dummy */
! ?% |5 D7 b6 I9 G+ I                var->red.length                = 8;
' y% |1 p( ^/ h- N                var->red.offset                = 16;
5 m& G1 M- Y# e- I+ x" o                var->green.length        = 8;1 n! Z1 @# F( l- ]2 G8 O- T% \
                var->green.offset        = 8;
+ t2 {8 W+ l& D# n; X, d/ j/ |                var->blue.length        = 8;
  a7 S# |) }% J: K  Y4 r2 \  t                var->blue.offset        = 0;; H/ T5 {0 H9 h) p" x- p8 a
                break;
. M" ^) W8 I" y$ }! s        }
" W. u/ z' v- w* }, A4 y        return 0;" I) W2 Q9 s8 x8 F/ g' ]7 y( d
}
1 U$ H  j, }: J+ n. ]. H0 e7 {* y4 F) Q1 }
/* Interpretation of offset for color fields: All offsets are from the right,8 J5 s- t! V! j5 b9 H4 N2 l  Y3 S
* inside a "pixel" value, which is exactly 'bits_per_pixel' wide (means: you
% |2 X' `- |" r. ]8 T6 [6 d; l * can use the offset as right argument to <<). A pixel afterwards is a bit/ ]0 n" P- {3 T' N5 F+ W
* stream and is written to video memory as that unmodified.9 K( h# V8 N! w- X& j, n
*
" r5 p% G9 g- F1 O  Q  N. h2 [ * For pseudocolor: offset and length should be the same for all color1 K% g* A2 z- W- T& k
* components. Offset specifies the position of the least significant bit) Z& W) }) p% t; k5 J" D% H% M- y
* of the pallette index in a pixel value. Length indicates the number
9 N/ Z' l& g% d; s9 e- c * of available palette entries (i.e. # of entries = 1 << length).' z5 B% S" `" B3 q. Q8 v7 ?$ q
*/
8 j! c3 g( `" f; i/ \$ Bstruct fb_bitfield {; I8 ]8 K# l3 X' [0 g* T
    __u32 offset;            /* beginning of bitfield    */! j6 x- F1 D2 u9 x% r
    __u32 length;            /* length of bitfield        */
3 \( E# M$ T$ j  K* H    __u32 msb_right;        /* != 0 : Most significant bit is */
! q% V7 D9 l4 x) B2 x* {                    /* right */ # R/ X5 b0 b9 F5 W: g
};& \5 p( h4 |' k$ e( J7 g6 i% h4 l% L
该函数主要将板级信息s3c2410fb_display复制到对应的地方,然后根据RGB的模式设置位域。- h3 [4 c! ^2 ?. w( {2 u% _: x
4.3 fb_ops方法$ `) x" T6 L7 z- ?; z
在驱动程序中,定义了fb_ops,如下:: w5 W+ L3 z  j4 V0 A7 y
8 z9 N  _6 ^7 d
static struct fb_ops s3c2410fb_ops = {
( K* f# ^5 k! @; V6 d$ D        .owner                = THIS_MODULE,1 N7 w4 K2 N3 s/ F; u8 G
        .fb_check_var        = s3c2410fb_check_var,                /*检查变量的合法性*// Q+ Q& n% O4 p6 T0 E7 ^; ~
        .fb_set_par        = s3c2410fb_set_par,                        /*将参数写入LCD控制器,该函数由帧缓冲核心调用*/
  c- W. T; m( n3 O        .fb_blank        = s3c2410fb_blank,                                /*该方法支持显示消隐和去消隐*/
9 G0 r' ^+ |8 M        .fb_setcolreg        = s3c2410fb_setcolreg,                /*设置颜色寄存器*/
; |4 M  c4 P' X5 G4 s        .fb_fillrect        = cfb_fillrect,                                /*用像素行填充矩形框,通用库函数*/7 O. |$ W, o/ ^  N& t+ Y% f
        .fb_copyarea        = cfb_copyarea,                                /*将屏幕的一个矩形区域复制到另一个区域,通用库函数*/
0 S$ }" G) C( ^        .fb_imageblit        = cfb_imageblit,                        /*显示一副图像,通用库函数*// v' d7 w8 C* E* v) o7 p
};6 x1 a# I% Y$ z! j
: m* \3 D+ k* L/ [3 V  z2 z
其中s3c2410fb_check_var在4.2节中已经分析过了,最后三个方法是通用库函数,在这里不作分析。; O1 }- ?0 _3 b! Q, I! ^# R
剩余三个也是驱动程序提供的,现在对这三个程序进行分析。; e. u- c' @" p4 n2 s: Y3 r' l
4.3. 1 s3c2410fb_set_par: W: R4 H) t- Q/ J$ m
/*
, U( @3 U  G( J *      s3c2410fb_set_par - Alters the hardware state.
" L2 I$ }+ x  E; `* F *      @info: frame buffer structure that represents a single frame buffer
+ J/ }" q' M- M; P7 f8 I% W& H/ x *                根据var中的值设置LCD控制器的寄存器
; x; u' o3 O3 m3 K */
/ D% ]! p( Q- j1 L; z1 _static int s3c2410fb_set_par(struct fb_info *info)! q- s9 g5 @5 Q
{
! F/ _" d6 ?; c( \: V* ]) z' b6 q/ r        struct fb_var_screeninfo *var = &info->var;
8 n; P7 a1 K( q4 {
1 r  \( ^+ \6 X: {6 y( U1 n: r        switch (var->bits_per_pixel) {
7 t) z9 y8 B9 Z) |% N& l8 Z( s+ e        case 32:& t; e1 b7 P1 U3 Y8 I
        case 16:
  V. S" r9 m. Z4 B4 ~8 h        case 12:
( |9 c! r* E$ W. D" D                info->fix.visual = FB_VISUAL_TRUECOLOR;7 h+ ~+ Q) {/ r+ w# h9 p
                break;2 x8 G9 P7 w4 z( m( q( i) O" P
        case 1:4 k, ^0 N  B3 \% D- G4 S
                info->fix.visual = FB_VISUAL_MONO01;' }; q  x! G# _
                break;7 m) I1 [, b" Z8 u' L- C
        default:; P7 y6 I1 n- [- W. [9 T( _
                info->fix.visual = FB_VISUAL_PSEUDOCOLOR;2 a' v$ H# d" K- X
                break;
5 i( j, [& S3 }4 a& e& T& A        }
7 n* _, j/ z# Y/ b# c* P3 O% H: ~. b. ?4 l3 j* `# n; Y
        info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; /* 320*16/8 = 640Bytes */# {8 a6 u9 r0 J# S4 |1 X
+ U/ k$ n( P/ o( a
        /* activate this new configuration */
$ X+ K; _% m8 h6 ^8 B
9 v' K0 \* L$ [/ q$ I  Z3 K        s3c2410fb_activate_var(info);
3 H( Z) v& p: a4 x8 h        return 0;& k* u1 ^6 P: |+ h: m
}
3 i9 X& G8 g& I  @# G! S该函数根据像素的位数设置了视觉模式,本例为16为,使用真彩色。然后计算了每行的数据元素大小。共240行。& Q9 ?7 H( B& o2 b" @6 k& C% l

4 s: q& U5 s9 b4 y0 E( O# ~4 L然后调用了s3c2410fb_activate_var来设置控制器并激活LCD。% X. l* J% T- V+ @8 }7 I/ h, N9 v
# v  e/ w1 C' O4 N  N" C# c! ]
s3c2410fb_activate_var函数如下:
. l  i! @8 @1 A" r
6 f* i) n2 F1 ^( N% L% }: l/* s3c2410fb_activate_var
0 X/ W) y3 M* h  |' Y2 [ *
# w" u2 v7 U/ S! p+ Z; y/ @ * activate (set) the controller from the given framebuffer
7 S8 }, K  P5 q3 N$ W * information- K7 [4 z# B5 S- \5 M
*/& t* y( O( G5 z. j+ }% H! U) u0 `
static void s3c2410fb_activate_var(struct fb_info *info)
) P9 _4 I3 b5 D- \- _{
, M* g' Q& V: q5 [, M        struct s3c2410fb_info *fbi = info->par;
4 l, ^: l- {9 F" J& a        void __iomem *regs = fbi->io;8 V. g" w5 X$ U' }& h
        int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT; /*regs.lcdcon1在s3c2410fb_check_var设置*/
; v# b0 l# ]/ p4 y3 J        struct fb_var_screeninfo *var = &info->var;
) S+ m( C  \9 }+ P6 V        int clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock) / 2;& f9 a) ~7 U5 h) m7 F# d+ Q* B
8 H' @" K, k9 I3 ?3 s3 m5 W% q
        dprintk("%s: var->xres  = %d\n", __func__, var->xres);4 r! l- I( V5 \* p! J, `* D
        dprintk("%s: var->yres  = %d\n", __func__, var->yres);
- N6 O8 n. s  S        dprintk("%s: var->bpp   = %d\n", __func__, var->bits_per_pixel);* j* v4 j0 C9 A# Z
. N1 _% R  ]* F) p
        if (type == S3C2410_LCDCON1_TFT) {' ~0 @5 H  x' U+ \
                s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);/*根据var,计算出控制寄存器需要设置的值*/
) d% S( ~) K0 m+ v) R                --clkdiv;7 f* [  K& _6 }) z
                if (clkdiv < 0)
/ g, h+ z" H! d( b- y/ ~                        clkdiv = 0;2 V( d4 r* Z  I
        } else {# |6 J2 E) ?  U" h) J
                s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);0 P" r) u. q& y
                if (clkdiv < 2)
, ~7 M; d2 @% e4 @                        clkdiv = 2;# C! J' v/ y  j% p( c" r
        }
5 E9 m/ M" A( }* R4 k4 ~8 C. {, {' L3 a+ B
        fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);/*设置CLKVAL*/
+ m: _8 R3 U: y) j- H9 m
$ c5 ]/ I7 o+ d0 B        /* write new registers */
" M( e& d2 k# S; Y2 a3 [' u$ |) i9 C7 S; A" e( W( x4 w
        dprintk("new register set:\n");
) f3 l8 w4 L, c2 Q. A! L        dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);* `5 m2 p& E* a6 q; U7 M/ `2 l
        dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);2 B( z" V% O* `/ l& A- J4 Q8 F
        dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);
4 ?$ P( D+ K7 d, \% W: O        dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);
& y; D% `4 |) C: Q        dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);
+ d8 q' o1 }) C6 D9 s: x        /*把计算好的值填入LCD控制器中*/" O1 H2 i( a6 U% F
        writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,       
. S3 }( J) F6 l7 j1 ]                regs + S3C2410_LCDCON1);                                                /*仍然禁止LCD*/
/ T8 \: Z; k8 k8 V1 C$ U        writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);
$ d& \7 ?! o7 i: U% w( _' Z  {        writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);
; g7 g) G2 h; i8 @: V* x& p- h        writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);
: F% v! C& G& u7 {        writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);
; X9 I) J) R2 N& |# e4 h# L( v! Z" T/ O1 V9 O. J
        /* set lcd address pointers */
( p# t7 x1 r$ M! F' w" ~( g0 i        s3c2410fb_set_lcdaddr(info);                                                /*设置LCD帧缓冲起始地址*/
! M% W* _* ]( w7 y0 V, B+ m. R, a1 s) o: o& B
        fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
7 L+ ~" M5 F2 j5 R" s. n        writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);        /*使能LCD*/- H& Q9 t+ X% ~" |4 t7 ]; t
}
' x& y+ A6 N+ c. b其中调用的三个函数如下:5 R0 L) S' P" O9 G. a9 J* U
static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi,5 y3 ?2 k! I& e2 B1 ?" A/ @
                      unsigned long pixclk)
* V8 E# V7 s1 U) h; Q{
0 `& c. z4 s* q    unsigned long clk = clk_get_rate(fbi->clk);         /*获取当前时钟频率(Hz)*/
% s7 `& n2 Z0 |% w/ E% _, _% e1 x    unsigned long long div;
& |7 Y* v9 Z# X+ ^
5 n" ~0 V. W* ]& s    /* pixclk is in picoseconds, our clock is in Hz$ F. v2 G% C/ n* [( U4 k
     *
; z$ S4 v# C6 g5 W5 m* z9 ?0 N     * Hz -> picoseconds is / 10^-12
  l7 U; q( ]- E( W1 R     */& F! o3 x( U5 I

6 `! i" Q( G1 x# p2 |    div = (unsigned long long)clk * pixclk;
6 i: U  h0 ?4 w* ~6 L- v    div >>= 12;            /* div / 2^12 */
. r+ h0 X5 U* r$ F7 {$ O  w5 t    do_div(div, 625 * 625UL * 625); /* div / 5^12 */
( M0 u5 R. @5 B9 e+ Z+ ~
7 [: p# }6 D) [. Z& k* p2 D    dprintk("pixclk %ld, divisor is %ld\n", pixclk, (long)div);# }" t$ @3 ^4 T
    return div;% P) L3 m& d" R3 p- |% x
}
' _/ h( o. t; ]
# I7 z) t, Z+ {# j* q& [8 T/* s3c2410fb_calculate_tft_lcd_regs* V$ j" g* C' r& a
*
# l" y- k8 Q5 Z3 L+ b3 A * calculate register values from var settings
- L8 {! A: w' z& z */% j. r+ c/ r, m8 i+ L! P8 M
static void s3c2410fb_calculate_tft_lcd_regs(const struct fb_info *info,/ T& ~1 g8 \! }& m& X
                         struct s3c2410fb_hw *regs)
' X) _$ Z" {5 q5 ^# H; D- x7 b) Z{6 U* i1 q: c$ {# r' b3 K; T
    const struct s3c2410fb_info *fbi = info->par;
' d) p% K* X2 A# Z. a    const struct fb_var_screeninfo *var = &info->var;
8 Y# }( B  W: V6 `% ^
' p0 t2 q9 ?  ~- X1 N) k    switch (var->bits_per_pixel) {  s8 I: G2 e( F4 l$ h* l9 S
    case 1:9 q* o3 {1 g# K5 V& z+ }
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;: o# X: ~) c: [& s: b1 p1 [/ k+ C
        break;# p; ^7 M  Y. V1 X
    case 2:
* H: y# n7 ?, r( r3 H# g: L        regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
" Z2 X) T5 Y2 n* a  X        break;4 \" y: I, t# {/ W* n
    case 4:2 \# D) N0 L9 R- d/ x. t$ Z/ _& ~2 N
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;' V. @0 S. d8 M9 M
        break;
7 x/ Z" ?+ s$ H' W; A* t    case 8:) I+ @4 I: I1 l
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;% V! \9 B$ `% }$ ?+ Q/ ?; f
        regs->lcdcon5 |= S3C2410_LCDCON5_BSWP |" g- V+ M( \1 @8 l% y
                 S3C2410_LCDCON5_FRM565;
; C1 N" V% \7 ^, o- K        regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP;- s$ N& x" |7 }  {2 v5 U
        break;
4 f# X) R5 N9 J1 k3 \$ ~; V& M  b    case 16:: M2 L) Z, ?: ?# C
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
# t+ i8 U* N7 ?! }7 M        regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;
2 f* v1 G: s# A) C9 N        regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;
8 {' r2 ?: F2 F/ }& Y4 }        break;
3 }) m% c6 f" t0 |$ U    case 32:
7 ]  ]3 F: g$ Z( ~        regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;
$ v  @& O! p- f' A5 {2 X1 d        regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP |! A3 D5 K3 G( H
                   S3C2410_LCDCON5_HWSWP |
+ {, f* {( A9 f' c( W4 c                   S3C2410_LCDCON5_BPP24BL);4 l) h; q) `" P3 R# E
        break;$ K1 w. y% A5 {. {& \" c
    default:
+ _" X$ B' h- l. z2 Z        /* invalid pixel depth */, X( z$ s# u  N% J& F+ ^
        dev_err(fbi->dev, "invalid bpp %d\n",! Z& D1 q* e5 w- B# [- j
            var->bits_per_pixel);
; q2 L# R) a* W7 ]$ c; W4 o    }
" Z- u* H3 j% g+ M    /* update X/Y info */
; [$ i" Z* S0 L1 A% P    dprintk("setting vert: up=%d, low=%d, sync=%d\n",
1 R$ \' S9 B4 x$ l7 e' b        var->upper_margin, var->lower_margin, var->vsync_len);
" |5 a5 V& l$ l' _$ K! l
5 r4 M1 g  ?2 G- ~- h    dprintk("setting horz: lft=%d, rt=%d, sync=%d\n",
9 L5 h, D/ g+ [6 a- G* D        var->left_margin, var->right_margin, var->hsync_len);
" |& ^% F2 \9 Z( A, a1 d2 s5 \6 n    /*
9 {) l- j# p4 [  V        所有时序参数必须减1,因为在公式中:
! R) u2 Z* S  _* ]* q1 W5 x        Frame Rate = 1/ [ { (VSPW+1) + (VBPD+1) + (LIINEVAL + 1) + (VFPD+1) } x {(HSPW+1) + (HBPD +1)) \" N1 v3 g6 T
        + (HFPD+1) + (HOZVAL + 1) } x { 2 x ( CLKVAL+1 ) / ( HCLK ) } ]) Y: f5 Z# d/ r* i+ p: C9 A$ o  [
    */2 e2 {$ O3 d# w7 z8 B
    regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1) |
! F6 o7 ]( O3 s  v! X1 n: m; K            S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |  a  M& h, ~$ W, e: A8 {
            S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |
2 j% W+ D  s) t5 Y" ]# ~% a9 N" l( L4 H% C            S3C2410_LCDCON2_VSPW(var->vsync_len - 1);1 ~" d1 g7 F$ i0 s3 j; i( E

4 I; ~9 g& c1 [0 U. |: V    regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin - 1) |! Y2 d: p* y; e7 k$ ]
            S3C2410_LCDCON3_HFPD(var->left_margin - 1) |
% C8 S" b+ [4 X$ \            S3C2410_LCDCON3_HOZVAL(var->xres - 1);
1 _1 ^- [- o: G" d5 ?. E3 s6 y" d5 Z' B2 w  b7 z0 A
    regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len - 1);
8 q, J! I/ S" F) P) e2 q8 _}* T3 f7 j* m/ }. L
, s; P) }$ a/ X  G
/* s3c2410fb_set_lcdaddr
2 e% ~0 b( n4 J/ R* j *
) T4 E5 `2 F( j; p9 N * initialise lcd controller address pointers
2 v5 Q& J% B3 a) v+ S; T */1 w+ m" w  x2 Z2 c
static void s3c2410fb_set_lcdaddr(struct fb_info *info)2 i# I7 D! s, Q: Y+ B
{* T+ q2 _* L3 Z) d9 e
        unsigned long saddr1, saddr2, saddr3;0 D- o' T/ v, f& x) |$ |
        struct s3c2410fb_info *fbi = info->par;
' g. n# `8 v; ]# @6 O2 a9 Q6 b! d        void __iomem *regs = fbi->io;+ V5 J' E; a% _" I5 W8 C5 D
" O4 V/ ~  _2 S) k
        saddr1  = info->fix.smem_start >> 1;          /*帧缓冲区起始地址*/
3 h$ Y) z8 Z9 M" ^7 N' o3 q& _        saddr2  = info->fix.smem_start;                       
3 M1 @. s; \1 F8 N        saddr2 += info->fix.line_length * info->var.yres;
2 N  j8 O3 o9 x4 [        saddr2 >>= 1;                                                        /*帧缓冲区结束地址*/. R" ]9 x3 H1 C2 C, `! X
* }% c% |4 |: d3 X* b
        saddr3 = S3C2410_OFFSIZE(0) |
4 a* |, I* q/ X7 {                 S3C2410_PAGEWIDTH((info->fix.line_length / 2) & 0x3ff); /*offset = 0, pagewidth = 320*/
  J' ]" N+ o6 x1 X) }* y- }& o4 \# U" N! A: b9 S- ~6 {
        dprintk("LCDSADDR1 = 0x%08lx\n", saddr1);( I4 s9 }8 ]5 Z, h- X
        dprintk("LCDSADDR2 = 0x%08lx\n", saddr2);
, S; O' o- `: p        dprintk("LCDSADDR3 = 0x%08lx\n", saddr3);* X) y  F7 M2 }8 n4 r
& x7 ~' W. A- n7 Q4 X4 V2 w
        writel(saddr1, regs + S3C2410_LCDSADDR1);: R, c3 L0 {: ^* d5 U/ Y: Z2 {" U6 d
        writel(saddr2, regs + S3C2410_LCDSADDR2);6 q* B8 L6 T8 h$ y" k# i
        writel(saddr3, regs + S3C2410_LCDSADDR3);. u3 n! n7 z" V4 R. `7 c% F
}+ j  J$ b( N6 T
s3c2410fb_calc_pixclk用于计算时钟频率。
4 W1 d  A* t1 L- W8 X
1 v. Y# g/ c' A  ?2 ss3c2410fb_calculate_tft_lcd_regs设置了LCD的控制寄存器。
$ c! n2 l% B- k1 E6 |" s+ g! z$ q& M2 ~% \  N
s3c2410fb_set_lcdaddr设置了LCD的地址寄存器,该寄存器的设置请参考datasheet。
0 U3 h6 v9 f) k) Z% C
. H4 P/ F1 @4 O) G4.3. 2 s3c2410fb_blank- H+ v( X7 k; L, ]& O  e/ h  Z+ y
该方法完成消隐功能。4 Z3 K' y+ z$ V4 `
, v" F, [2 G" P1 c
/*
6 V0 q! u* j$ v1 R9 M: | *      s3c2410fb_blank; H4 C$ L' X; G. S2 p
*        @blank_mode: the blank mode we want.
1 ?9 F4 j8 X. [6 P8 ~* a& M *        @info: frame buffer structure that represents a single frame buffer+ y' g- w9 H  ^& t+ c
*
; V; {( p/ o( ]" l* N: `( i1 Y5 A *        Blank the screen if blank_mode != 0, else unblank. Return 0 if7 a6 T- f( i# B2 d# U
*        blanking succeeded, != 0 if un-/blanking failed due to e.g. a5 N  Q! S# _0 _: C
*        video mode which doesn't support it. Implements VESA suspend5 u- f; y+ o$ r' u' i/ h
*        and powerdown modes on hardware that supports disabling hsync/vsync:9 d1 g, l+ t: G1 M' m5 R
*
% q# k  C& c0 V- O  A *        Returns negative errno on error, or zero on success.
9 s9 M5 ^4 V" ? */ y- `$ b6 a# g5 h$ \. z9 a3 U- Y$ i
*/
  z. o' Y0 u0 }4 Astatic int s3c2410fb_blank(int blank_mode, struct fb_info *info)
  W1 m3 x' r: b* @# M$ G{
9 v5 ^3 r# O3 y9 S: D5 F% A- y    struct s3c2410fb_info *fbi = info->par;  i4 `/ K9 D0 D+ V6 @" H- m: p0 F
    void __iomem *tpal_reg = fbi->io;# q0 |+ \1 e/ f9 K5 V2 o& X" Q2 q6 h

  Y! s2 t* u8 k    dprintk("blank(mode=%d, info=%p)\n", blank_mode, info);
; i8 b7 B; P. W. x+ X0 v" d: r( }* f2 G
    tpal_reg += is_s3c2412(fbi) ? S3C2412_TPAL : S3C2410_TPAL;; \  b9 N+ `* A
! |) k& I9 Q4 n6 @4 r
    if (blank_mode == FB_BLANK_POWERDOWN) {    /*消隐*/& q  D$ S$ {) I* h$ s
        s3c2410fb_lcd_enable(fbi, 0);        /*禁止LCD*/5 g- l4 s9 S$ l9 Q! \
    } else {
9 i& t1 V, T" i! W        s3c2410fb_lcd_enable(fbi, 1);        /*启动LCD*/4 c. o% h: N, c
    }# i6 r! u7 C. s/ @5 Z; B9 m# p
7 x/ h* j6 z) }, N; V1 q
    if (blank_mode == FB_BLANK_UNBLANK)        /*去消隐*/
2 t; o9 C  d1 n/ S) _. P        writel(0x0, tpal_reg);                /*禁止temporary palette*/7 [4 y0 Q9 V9 }; V5 H3 m& g
    else {                                    /*消隐*/
. u' R9 Q+ {  P  n0 Y        dprintk("setting TPAL to output 0x000000\n");
6 `2 z( q+ P7 B- s% a6 s        writel(S3C2410_TPAL_EN, tpal_reg);    /*使能temporary palette,颜色为黑色*/
- p1 d" K0 `  T2 q) f/ l5 `    }
( a+ P  H2 Q: Z0 Y) f" G) M9 [; k' G& `6 b
    return 0;2 Q) y0 B4 ~( S# O
}
! O7 j# n3 x- G' i% F; e, F" a在消隐时,屏幕将全黑。
: T1 i  T# f& [8 c9 d" a
/ k9 {* d) U3 T2 W  {, C4.3.3 s3c2410fb_setcolreg" R% ^$ v$ L' {
该函数的功能用于设置LCD的调色板。调色板的概念请看我的转帖:LCD调色板。
4 V& V# {, g  p5 @% ~7 \4 ?. W3 z' C# f. Z- Q& D
static int s3c2410fb_setcolreg(unsigned regno,
# k- u- Q& Y5 R1 X                   unsigned red, unsigned green, unsigned blue,/ y& R) P3 k1 H: i1 P  [
                   unsigned transp, struct fb_info *info)
6 N# T$ \# X7 B* i7 z( O' b0 E, ?{
4 K: i! m+ ~. Q# E    struct s3c2410fb_info *fbi = info->par;    /*par指向s3c2410fb_info*/
& x) J1 X& z* l    void __iomem *regs = fbi->io;
0 p8 r/ M6 C; `& x0 L8 _& }& a0 H    unsigned int val;. q) b" V; w5 [
" i. t1 z6 _' ^, k' l
    /* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n",
9 p: T3 @6 E( ?1 R- s" n" z+ _* v           regno, red, green, blue); */
9 Z( T9 |8 y. P$ q) O3 I, r* a$ b- s7 o$ C. y# _- c! w
    switch (info->fix.visual) {    2 E+ x9 F: }% I/ K! W
    case FB_VISUAL_TRUECOLOR:    /*使用真彩色*/
/ A  d1 ?) L1 M7 ^5 C4 I, l        /* true-colour, use pseudo-palette */
$ h" C# n$ A7 s& M  U) H
) j( Z0 f* K' _8 T/ ~' b        if (regno < 16) {    /*16种颜色,为什么只用16种颜色???????????*/
, J3 I. N( s4 y$ ]1 v! o1 {: o            u32 *pal = info->pseudo_palette;
+ n" Z( m* [  e8 H2 K! R0 }: m8 N5 H% _0 ~8 D
            val  = chan_to_field(red,   &info->var.red);
8 O) N2 \" d, A& v, `5 _! A  Z  _. n            val |= chan_to_field(green, &info->var.green);
$ E# J6 d! T8 i' Z2 s            val |= chan_to_field(blue,  &info->var.blue);5 X  _$ a7 q  G5 s+ l

; y: u& S6 B2 c            pal[regno] = val;    /*保存颜色值*/
* E) z9 B% Z4 F& C; N# O% K        }8 s( p1 g. F, i
        break;
9 [: M$ T; Y  C
0 y: h- C& r/ m+ C5 A3 n# V    case FB_VISUAL_PSEUDOCOLOR:- Z5 f1 k3 a& ^# I# Y& A5 [
        if (regno < 256) {
+ @& Y. `7 p! R9 Q! a            /* currently assume RGB 5-6-5 mode */
6 {) e3 i# a+ L4 e1 ~7 C4 \
: H( O$ T0 a/ I( b+ R) ?: s            val  = (red   >>  0) & 0xf800;
: c  Q& u* M; @- D" m, h- b            val |= (green >>  5) & 0x07e0;
' ?: c. ^' `. k# \' H7 l& v            val |= (blue  >> 11) & 0x001f;
1 H( o$ w4 |9 h( e
5 r  D! N+ O9 w% N+ \            writel(val, regs + S3C2410_TFTPAL(regno));
% v3 U/ X" a$ e1 }3 C  C/ c            schedule_palette_update(fbi, regno, val);
2 F) \* U! H, n; p        }, {+ [6 D, `) |8 `2 |' ?2 P
! M& w! E/ W; m) X- W" s5 H# p' K
        break;
* d) D) U# B* d) R4 }4 \. p# y7 L6 F
    default:* q7 X( b9 B0 G; Q4 T  q" p5 D* @
        return 1;    /* unknown type */0 h/ j/ L4 t  [* T- M3 g  z
    }( c5 S2 |9 t, m& D

9 P- G2 b7 W+ w- }1 Q1 b; N    return 0;
/ d4 h! R. a1 Q( K2 y}
2 V* V2 f* w- c+ m4 s2 d: w/* from pxafb.c */' K' ]0 s# f: h  h. x: O
static inline unsigned int chan_to_field(unsigned int chan,
# e. z# M" a. N/ ^6 K7 T                     struct fb_bitfield *bf)
/ W  G* \( i. m7 J) B- t, q{   
7 I. A, O8 B  U" g( b/ `    /*下面用到的length和offset在s3c2410fb_check_var函数中设置*/& U& G% \" I" f2 t; D3 t* L
    chan &= 0xffff;                /*取低16位*/- W. n8 \4 G0 u" z& P: M" W
    chan >>= 16 - bf->length;   /*取第length为到16位为有效位*/
: _6 M$ ~  n( j& d9 t    return chan << bf->offset;    /*移动到相应的位置。*/4 b; O% U* C2 {( B
}7 D9 J) W% }! }5 D3 M: |
我们使用的是真彩色,可以设置16种颜色,颜色的位域值通过调用chan_to_field获得,然后保存了颜色的值。但是比较奇怪的是,这里并没有将值保存到0x4d000400为起始的内存中,不知为何。 反而倒是在伪彩色模式下, 使用了writel(val, regs + S3C2410_TFTPAL(regno))写到内存中,然后调用schedule_palette_update函数来更新palette表。我们来看下。' T6 K3 M' C4 [! y) a
  w! L/ C: f: J( T  n4 C
static void schedule_palette_update(struct s3c2410fb_info *fbi,- O- \& z2 z- D# g& x% U
                                    unsigned int regno, unsigned int val)/ X1 H# g& }( U8 x) T: x% e' K
{' ~4 A/ B# d" g% [- C
        unsigned long flags;
% M6 d3 |+ x. }        unsigned long irqen;
! s+ \* |* c- I" _8 i! A4 O        void __iomem *irq_base = fbi->irq_base;0 Y: o1 {" c8 R) t, H) y
, x  C9 }" p7 b* @' K
        local_irq_save(flags);
8 E' Y- N: R" G
2 J1 ~. V0 Y8 y        fbi->palette_buffer[regno] = val;
9 L( X6 `) W: S" w1 ?* {5 E, \, I* l' c2 q2 k
        if (!fbi->palette_ready) {3 g3 z, Y' X: m" J5 p& J5 T1 P$ @  L
                fbi->palette_ready = 1;
! N( T4 m3 |$ |+ x* n1 l3 f6 F- s: K- f6 G1 z7 H( j
                /* enable IRQ */9 w# i3 B( t7 u
                irqen = readl(irq_base + S3C24XX_LCDINTMSK);3 D; V9 a2 f- s' x# v1 F
                irqen &= ~S3C2410_LCDINT_FRSYNC;
! {/ V5 \9 @$ n0 j/ B                writel(irqen, irq_base + S3C24XX_LCDINTMSK);' e) I, i$ }! D- G& b- q. O" [
        }
0 x9 ]7 U& t0 W. N( Q
4 D3 t( j2 j' p+ T        local_irq_restore(flags);
3 p* v$ E) Q( E, S}
- s, h! ~% e* w' u. U- {这个函数的作用就是开启了LCD的帧同步中断,这个中断在VSYNC信号从无效变成有效时产生。当中断产生时,会调用在probe方法中注册的ISR。ISR如下:/ x( `2 z; O' r
static irqreturn_t s3c2410fb_irq(int irq, void *dev_id)1 L  |& C: b- V+ @0 g& p# Y
{
; n; d+ y9 j9 h        struct s3c2410fb_info *fbi = dev_id;3 G/ u( v) P: z  I7 a
        void __iomem *irq_base = fbi->irq_base;8 m$ B3 C3 y$ V
        unsigned long lcdirq = readl(irq_base + S3C24XX_LCDINTPND);
) \. D; O$ r1 J/ J1 {# k6 Z0 [b" ^9 G3 |/ R# N- l
        if (lcdirq & S3C2410_LCDINT_FRSYNC) {2 z0 u  U1 V8 F
                if (fbi->palette_ready)
8 x% X  L( K7 R+ q$ D; K- r                        s3c2410fb_write_palette(fbi);7 D- b$ ^' E% Y- P' }+ R
1 r1 ^/ p& Y( x7 j* |
                writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDINTPND);
( `: L1 U6 t! ]. v8 V' H- q0 }                writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDSRCPND);" h3 i: J4 V0 f0 J! v
        }
/ ?# E# o6 S( H0 ~6 I1 o+ {
- `$ a8 x. m" Q! s        return IRQ_HANDLED;; d, G, D+ x4 `+ F
}/ |6 O, `: C2 Y) c. g! I
! v# h9 Z4 Q7 {4 Y5 i/ G
static void s3c2410fb_write_palette(struct s3c2410fb_info *fbi)
: H3 o! T8 [3 N# L' n7 U2 K3 b{
2 B9 W" ^0 F, j1 C) K+ R    unsigned int i;3 Q- l+ }/ o; z- \$ o. E
    void __iomem *regs = fbi->io;6 g" o* i* l" D  u1 O" T

9 K! m0 c6 y- j$ F0 S& k+ y  C' X- u$ p    fbi->palette_ready = 0;  /*清除ready标志*/, ^% }" M' t& I

* _2 w8 ]5 C" m! g9 n$ \3 y5 {$ k6 l    for (i = 0; i < 256; i++) {  ^" a# e3 H& q# _% r/ K
        unsigned long ent = fbi->palette_buffer;
/ Z* c' e' g0 `        if (ent == PALETTE_BUFF_CLEAR)
! @: K* N) o/ n/ f$ W            continue;/ H/ L5 w" O  \6 V, H, m4 ~& G" s3 H
2 Z+ E& e0 G' L; i" c; A5 y
        writel(ent, regs + S3C2410_TFTPAL(i));
0 c3 O# q+ E; ]
' {2 v: s% a/ k, h2 Q4 W        /* it seems the only way to know exactly
- `, i" k% [' I0 z         * if the palette wrote ok, is to check
9 M' C- J$ }3 k3 x- P         * to see if the value verifies ok
" s0 m  n, a6 s) B; |. ]         */
- _5 f' c/ R* w/ [% x3 ^; J5 j2 y& W1 \, `1 y
        if (readw(regs + S3C2410_TFTPAL(i)) == ent)
: }8 F2 N; }! {! @! N            fbi->palette_buffer = PALETTE_BUFF_CLEAR;
7 d* f3 S3 p# }; w        else% V8 Q5 t% E! z0 c2 a! q
            fbi->palette_ready = 1;   /* retry */
- O3 }3 a0 x5 k    }/ }# ^$ T4 n$ O+ P- S' |
}; T% |2 \; [# [6 K8 n3 v3 M
在中断函数中调用了s3c2410fb_write_palette,该函数对写入内存的颜色值进行检查。如果确认其已经写入,则将palette_buffer中的清除,否则retry。! B- M3 t* _7 b. B) [! q/ f

' h. G. w$ q7 s8 ]6 b5 m  }5. 总结/ W7 i5 y* Y+ C/ C) s- o: C, P7 H
本文对frambuffer子系统做了简单的介绍。frambuffer子系统的函数相当之多,在这里是不可能一一介绍的。本文首先介绍了主要的数据结构,6 t* r+ \% N& b. V3 ]' Z) h: }
" C4 A9 ^0 F/ |) ~) |: D* m% [& x! y
随后分析了frambuffer核心层,在核心层简单的分析了5个API函数,接着,对驱动层做了介绍,驱动层的大多数函数都给出了分析。% G% K: N5 _# r! }/ }/ \4 H
% Q# b" u# B; U9 w! {
1 G9 N& v* \8 V( i0 K

( g& p( Q: k/ T5 Z. z' a
. O& ^+ f% T* L) o5 ?" V8 O
6 B4 G: {* j' _- z+ X
) w$ a6 v' x- j- @( q# R; F+ n
. r% u  s/ ]" d
  s& i1 L8 T" x5 u
作者: SsaaM7    时间: 2020-6-24 17:11
基于S3C2440的嵌入式Linux驱动——Framebuffer子系统解读




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2