Chinaunix首页 | 论坛 | 博客
  • 博客访问: 805520
  • 博文数量: 281
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2770
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-02 19:45
个人简介

邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛

文章分类
文章存档

2020年(1)

2018年(1)

2017年(56)

2016年(72)

2015年(151)

分类: LINUX

2016-12-26 14:14:32

platform: JZ2440
Kernel:   linux-2.6.22.6
==========================================================

1st_drv:

        编写第一个驱动:点亮LED灯。
        注册字符设备,应用程序一直在查询读取到的命令。
    总结知识点:
        如何注册字符设备:
        1. 主设备号
        2. file_operations结构体分配
        3. 注册字符设备:register_chrdev(major, "name", &file_operations) // 建立主设备号和file_operation结构体之间的关系
        4. 修饰符: module_init, module_exit
        5. MODULE_LICENSE("GPL");
        
        应用层写操作函数,如何进入到内核驱动层去写呢?
        驱动操作函数: copy_from_user(dest, src, size);
        
    相关操作命令:
        insmod
        rmmod
        lsmod
        
        cat /proc/devices
        
        可以利用man +命令的方式,查看命令所需要的头文件,方法以及返回值。
        比如:$man open
        查看manual。
            
    
2nd_drv:
        读取按键的状态值。
        应用程序一直在查询按键管脚状态值,CPU一直被占用。
    总结知识点:
        如何注册字符设备:
        1. 主设备号
        2. file_operations结构体分配
        3. 注册字符设备:register_chrdev(major, "name", &file_operations) // 建立主设备号和file_operation结构体之间的关系
        4. 修饰符: module_init, module_exit
        5. MODULE_LICENSE("GPL");
        
        应用层读操作函数,如何进入到内核驱动层去读呢?
        驱动操作函数: copy_to_user(dest, src, size);
    
    相关操作命令:
        ./seconddrvtest &    // “&”可以让应用程序在后台运行
        top                  // 命令可以查看进程使用CPU的情况
        kill -9 PID          // 可以杀死进程号为PID的进程
        
        
3rd_drv:
        利用中断方式,注册按键中断到内核。
        这样解放了CPU一直查询按键的任务。
        但没有按键按下的时候,读取按键的任务进入休眠态;只有当按键按下,触发中断时候,则进程成休眠态返回,并读取相应的按键值。
        注意:没有按键按下时,进程一直休眠不返回。
        
    知识点总结:
        1. 中断注册:
            int request_irq(unsigned int irq, irq_handler_t handler,
                unsigned long irqflags, const char *devname, void *dev_id)
            中断释放:
            void free_irq(unsigned int irq, void *dev_id)
            
        2. 休眠队列:
        2.1 声明定义休眠队列:
            static DECLARE_WAIT_QUEUE_HEAD(buttonQueue);
            
        2.2 唤醒队列,在中断里面调用
            wake_up_interruptible(&buttonQueue);
            
        2.3 等待队列唤醒,读取按键,ev_press为状态值,当ev_press = 1,唤醒队列:buttonQueue
            在读取函数中调用,如果没有按键按下,则进入休眠;如果按下则唤醒进程,读取按键值
            wait_event_interruptible(buttonQueue, ev_press);    

        3. file_operations函数:
            static const struct file_operations third_fops = {

                .owner   = THIS_MODULE,
                .open    = third_drv_open,     /* 应用程序打开时调用 */
                .read    = third_drv_read,     // 在这个函数中,读取按键值
                .release = third_drv_close,    /* 应用程序退出时调用 */
            };

    相关操作命令:
        cat /proc/interrupt      //用来查看在内核中注册的中断情况
        
        
4th_drv:
        POLL机制,可以设置休眠的超时时间。当到达超时时间并且仍然没有按键数据可以读取,还在休眠的应用程序会超时返回。
        防止该进程永不返回。
        
    知识点总结:
    
        1. 驱动POLL,在file_operations结构体中添加poll函数:
            static const struct file_operations forth_fops = {

                ....
                .poll    = forth_drv_poll,
            };    
            
            unsigned int forth_drv_poll(struct file *file, struct poll_table_struct *wait)
            {
                unsigned int mask = 0;
                poll_wait(file, &buttonQueue, wait); // 不会立即休眠

                if (ev_press) // 如果有按键按下,则触发事件POLLIN
                    mask |= POLLIN | POLLRDNORM;

                return mask;
            }    
        
        2. 应用程序POLL:
            struct pollfd fds[1];
            fds[0].fd     = fd;       /* 对应的文件句柄 */
            fds[0].events = POLLIN;   /* 触发的事件     */
            
            // 设置超时时间:5000ms
            ret = poll(fds, 1, 5000);
            if (ret == 0)
            {
                /* 超时打印 */
                printf("time out\n");
            }
            else
            {
                /* 在未超时时间内读取键值,如果有按键按下,则返回;如果没有按键按下,则进入休眠态 */
                read(fd, &keyVal, 1);
                printf("cnt: %d, keyVal: 0x%x\n", cnt++, keyVal);
            }
        
5th_drv:
        驱动:异步通知机制
        如果有按键按下,则驱动会发出一个按键按下的信号给应用程序,通知它来读取。
        相当于:驱动实现"软件"中断的方式,当有按键按下时,打印按键信息。
        
    知识点总结:
        1. 驱动aysnc,在file_operations结构体中添加fasync函数
        
        // 定义声明
        static struct fasync_struct *button_async;
        
        // 在按键中断处理函数中
        static irqreturn_t key_isr(int irq, void *dev_id)
        {
            ...
            
            kill_fasync (&button_async, SIGIO, POLL_IN);    //如果进入按键中断,则发送信号 SIGIO 给应用层程序
        }
        
        static const struct file_operations fifth_fops = {
            ...
            .fasync  = fifth_drv_fasync,
        };
        
        /* 这个函数什么时候被调用呢?下面有解释 */
        static int fifth_drv_fasync (int fd, struct file *filp, int on)
        {
            printk("driver: fifth_drv_fasync\n");
            return fasync_helper (fd, filp, on, &button_async);
        }
        
        2. 应用程序设置启动async功能:
        void my_signal_fun(int signum)
        {
            unsigned char key_val;
            read(fd, &key_val, 1);
            printf("key_val: 0x%x\n", key_val);
        }    

        main{        
            ...
            
            signal(SIGIO, my_signal_fun);        // 设置当驱动层有信号传来(表示有按键按下),调用哪个函数来处理
            
            fcntl(fd, F_SETOWN, getpid());       // 设置fd属于哪个进程pid:绑定文件句柄fd和进程PID
            Oflags = fcntl(fd, F_GETFL);         // 获取该fd的标志参数
            fcntl(fd, F_SETFL, Oflags | FASYNC); // 设置并启动fd的FASYNC功能: 这句话设置完成将调用驱动程序的函数:fifth_drv_fasync        
        }
    
        
6th_drv:
        定义互斥锁机制,保证应用程序不会同时被调用。
        知识点:共享资源的使用。比如对同一个缓冲区进行读写操作,这会导致数据混乱。
        例子:互斥锁机制,阻塞/非阻塞模式
        
    知识点总结:
    
        1. 驱动程序:互斥锁机制
        // 定义互斥锁
        static DECLARE_MUTEX(button_lock);     
        
        // 获取信号量
        // 打开文件:阻塞和非阻塞方式访问
        int sixth_drv_open(struct inode *inode, struct file *file)
        {
            if (file->f_flags & O_NONBLOCK)
            {
                if (down_trylock(&button_lock))
                {
                    //如果为非阻塞方式访问,则获取不到资源立刻返回
                    return -EBUSY;            
                }
            }
            else
            {
                /* 阻塞方式 获取信号量:
                 * 如果该信号量已经被占用, 则该进程一直在这里等待不返回,直到该信号量被释放
                 */
                down(&button_lock);
            }
            
            ...
            // 初始化操作....
        }
        
        // 读函数
        ssize_t sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
            // 非阻塞方式读取,则如果获取不到资源,立刻返回
            if (file->f_flags & O_NONBLOCK)
            {
                if (!ev_press)
                {   
                    printk("sixth_drv_read: O_NONBLOCK return\n");
                    return -EAGAIN;
                }
            }
            else  // 阻塞方式访问,如果获取不到资源,进入休眠态等待
            {
                /* 等待按键按下事件发生 */
                wait_event_interruptible(buttonQueue, ev_press);
            }
        
        // 释放信号量
        int sixth_drv_close(struct inode *inode, struct file *file)
            up(&button_lock);  /* 释放信号量*/
            
        2. 应用程序
        fd = open("/dev/sixthDrv", O_RDWR | O_NONBLOCK);  // 以非阻塞的方式访问文件
        fd = open("/dev/sixthDrv", O_RDWR );              // 以阻塞的方式访问文件

        
7th_drv:
        定义一个定时器模块,用来消除按键抖动。10ms的按键采样频率.
        
    知识点总结:
    
        1. 驱动模块
        
            // 定义timer链表
            static struct timer_list buttons_timer;
        
            //定时器的初始化
            int seventh_drv_init(void)
            {
                ...
                init_timer(&buttons_timer);                        // 初始化定时器
                buttons_timer.function = buttons_timer_function;   // 设置定时器超时处理函数
                buttons_timer.expires  = 0;                        // 设置定时器超时时间
                add_timer(&buttons_timer);                         // 注册定时器模块
            }
            
            // 在按键中断里,用10ms的频率处理按键抖动情况:
            // 一次按键中断发生,就延长10ms后处理,直到在10ms里检测不到中断,表示一次有效按键产生
            static irqreturn_t key_isr(int irq, void *dev_id)
            {
                /* 10ms后启动定时器 */
                irq_pd = (struct pin_desc *)dev_id;
                mod_timer(&buttons_timer, jiffies + (HZ / 100));  /* 10ms按键去抖动*/
                
                return IRQ_RETVAL(IRQ_HANDLED);
            }
            
            // 在定时器超时中断函数中,读取有效的按键值
            static void buttons_timer_function(unsigned long data)
            {
                ...
                ev_press = 1;                          /* 表示中断发生了 */
                wake_up_interruptible(&buttonQueue);   /* 唤醒休眠的进程 */        
            }
        
        2. 应用程序:
        
        
        3. 测试
        先输入两次:
            ./seventhdrvtest &
            ./seventhdrvtest &
        再输入
            top
        
        看到打印信息:
            Mem: 6652K used, 54532K free, 0K shrd, 0K buff, 1996K cached
            CPU:   0% usr   0% sys   0% nice  99% idle   0% io   0% irq   0% softirq
            Load average: 0.56 0.15 0.05
              PID  PPID USER     STAT   VSZ %MEM %CPU COMMAND
              779   771 0        R     3096   5%   0% top
              771     1 0        S     3096   5%   0% -sh
                1     0 0        S     3092   5%   0% init       
              777   771 0        S     1312   2%   0% ./seventhdrvtest   (sleep模式,当没有按键按下的时候)
              778   771 0        D     1308   2%   0% ./seventhdrvtest   (获取不到资源,进入僵死状态,除非上一个应用程序退出)
        
        
        
8th_drv:
        学习输入子系统,重点啊。linux驱动大多数都是用输入子系统。
    知识点总结:
        驱动分层结构 - 两部分:input_handler, input_device
        0. 核心层:\drivers\input\input.c
            在该文件中定义了:驱动和设备向核心层注册/卸载等函数。
        
        1. 软件部分:驱动部分,linux内核已经做好了,这部分不需要修改,只需要知道怎么工作。
            在内核启动时,就会注册输入子系统的驱动部分,调用核心层(input.c)的: input_register_handler
            注册input_handler结构体,具体有:
            a. 文件: \drivers\char\keyboard.c
                input_register_handler(&kbd_handler);      // kbd, 没有生成对应的设备文件
            
            b. 文件: \drivers\input\evdev.c
                input_register_handler(&evdev_handler);    // 注册设备:/dev/event0...
            
            c. 文件: \drivers\input\mousedev.c
                input_register_handler(&mousedev_handler); // 注册设备:/dev/mouse0...
            
            d. 文件:\drivers\input\tsdev.c
                input_register_handler(&tsdev_handler);    // 注册设备: /dev/ts0
            
            注意:设备文件只有当input_handler和input_device匹配的时候,才创建。
            
        2. 设备部分,这部分是需要修改的,根据自己的硬件来进行修改。
            驱动编写:
            2.1 分配一个input_device结构体
            2.2 设置该结构体
            2.3 注册:input_register_device(input_device)
            
        3. 注册input_device程序流程分析:
        input_register_device(input_device)
            // 将该input_device放入链表input_dev_list
            list_add_tail(&dev->node, &input_dev_list);
            
            // 遍历input_handler_list链表,寻找与之匹配的input_handler
            list_for_each_entry(handler, &input_handler_list, node)
                input_attach_handler(dev, handler);    
                
                    // 找到匹配的input_hanlder,返回id
                    id = input_match_device(handler->id_table, dev);
                    
                        // input_handler里面有个id_table,用来和input_device设置的标志位进行比较,
                        // 看看是否匹配;如果匹配则返回id
                        for (; id->flags || id->driver_info; id++) // 比较id->flags
                            MATCH_BIT(evbit,  EV_MAX);             // 比较相关的标志位
                    
                    // 如果找到了匹配的input_handler,则调用input_handler里面的connect函数,来进行input_device和input_handler的绑定
                    handler->connect(handler, dev, id);
                            举个例子, 对于evdev_handler来说,就是: evdev_connect
                            evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
                                
                                // 分配结构体:struct evdev,里面有个handle结构体
                                struct evdev *evdev;
                                evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
                                
                                // 设置,注意handle结构体,一边连着input_handler,一边连着input_device
                                evdev->handle.dev     = dev;
                                evdev->handle.handler = handler;
                                sprintf(evdev->name, "event%d", minor);
                                
                                // 设置主次设备号
                                devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor)
                                
                                // 创建设备文件/dev/event0...,主设备号为:INPUT_MAJOR = 23,次设备号为:EVDEV_MINOR_BASE + 0 = 64
                                cdev = class_device_create(&input_class, &dev->cdev, devt, dev->cdev.dev, evdev->name);
        
                                // 注册handle结构体
                                input_register_handle(&evdev->handle);
                                    // 把handle同时挂在input_handler和input_device上面
                                    list_add_tail(&handle->d_node, &handle->dev->h_list);
                                    list_add_tail(&handle->h_node, &handler->h_list);
                                    
        总结一下:
        a. 内核启动后,就会自动注册各种input_handler,并且将他们挂接到同一个链表input_handler_list上面
        b. 程序员需要编写与硬件相关的驱动程序,调用register_input_device向内核注册一个input_device结构体
        c. 一旦向上注册,则会遍历input_handler链表,找到与input_device相匹配的一个或者多个input_handler结构体
        d. 当找到匹配的input_handler后,就会调用input_hanlder的connect函数
        e. 在input_handler->connect函数中,分配并设置input_handler里的成员,然后创建与input_device对应的设备驱动文件
        f. 最后注册input_handle结构体,这个结构体将input_handler和input_device绑定在一起
        
        4. 输入子系统设备驱动文件的open, read, write函数
        见文档:[1st_input子系统.TXT]
    

    驱动编写:
        1. 分配input_device结构体
            static struct input_dev *button_dev;
            button_dev = input_allocate_device();
        2. 设置
            button_dev->name = "button_input";

            /* 设置产生的事件 */
            set_bit(EV_KEY, button_dev->evbit);  // 按键事件
            set_bit(EV_REP, button_dev->evbit);  // 重复按键事件

            /* 设置产生哪些按键事件*/
            set_bit(KEY_L,         button_dev->keybit);
            set_bit(KEY_S,         button_dev->keybit);
            set_bit(KEY_ENTER,     button_dev->keybit);
            set_bit(KEY_LEFTSHIFT, button_dev->keybit);
        
        3. 注册input_device
            input_register_device(button_dev);
            注册之后,如果找到匹配的input_handler,则会在/dev目录下创建相应的设备文件
            
        4. 上报事件,同步事件
            当检测到有效的按键中断后,则读取按键值,并且上报事件给内核:
                input_event(button_dev, EV_KEY, ptPinDesc->keyCode, 0);
                input_sync(button_dev);
            
    测试:
        
    
9th_drv:
        分离结构: bus-driver-device驱动模型
        linux内核驱动有一些是依赖于真正的总线,比如SPI, IIC, USB等。
        但是有一些设备是没有真正总线的,linux内核将这些设备抽象出来,定义了一个虚拟的总线,
        叫做:platform bus - 平台总线
        
        分离:
            platform_bus连接platform_device和platform_driver
            核心层:\drivers\base\platform.c,里面定义了注册/卸载设备和驱动的程序等。
            
    知识点总结:
        1. 几个重要的数据结构:
        1.1 struct platform_driver:
            可以看出:platform_driver是struct device_driver的继承,核心的是struct device_driver;
            只是将一些方法
                比如:probe, remove, shutdown, ...等等
            进行了重载。
            struct platform_driver {
                int (*probe)(struct platform_device *);
                int (*remove)(struct platform_device *);
                void (*shutdown)(struct platform_device *);
                int (*suspend)(struct platform_device *, pm_message_t state);
                int (*suspend_late)(struct platform_device *, pm_message_t state);
                int (*resume_early)(struct platform_device *);
                int (*resume)(struct platform_device *);
                struct device_driver driver;
            };
            
        1.2 struct device_driver
            可以看到在struct device_driver的结构体中也定义了方法:probe, remove, shutdown... 等,
            而在struct platform_driver也定义了这些方法,这就可以方便地进行方法的重载。
            
            在该结构体中,还包含一个重要的结构体:struct bus_type
            struct device_driver {
                const char        * name;
                struct bus_type        * bus;

                struct kobject        kobj;
                struct klist        klist_devices;
                struct klist_node    knode_bus;

                struct module        * owner;
                const char         * mod_name;    /* used for built-in modules */
                struct module_kobject    * mkobj;

                int    (*probe)    (struct device * dev);
                int    (*remove)    (struct device * dev);
                void    (*shutdown)    (struct device * dev);
                int    (*suspend)    (struct device * dev, pm_message_t state);
                int    (*resume)    (struct device * dev);
            };
            
        1.3 struct bus_type
            看看在bus_type结构体中也定义probe, remove, shutdown, ...等等。
            这说明bus_type才是父结构体,其他struct device_driver和struct platform_driver是在这个结构体上的继承。
            在bus_type结构体中,注意有个match函数,用来匹配dev和drv。
            struct bus_type {
                const char        * name;
                struct module        * owner;

                struct kset        subsys;
                struct kset        drivers;
                struct kset        devices;
                struct klist        klist_devices;
                struct klist        klist_drivers;

                struct blocking_notifier_head bus_notifier;

                struct bus_attribute    * bus_attrs;
                struct device_attribute    * dev_attrs;
                struct driver_attribute    * drv_attrs;
                struct bus_attribute drivers_autoprobe_attr;
                struct bus_attribute drivers_probe_attr;

                int        (*match)(struct device * dev, struct device_driver * drv);
                int        (*uevent)(struct device *dev, char **envp,
                              int num_envp, char *buffer, int buffer_size);
                int        (*probe)(struct device * dev);
                int        (*remove)(struct device * dev);
                void    (*shutdown)(struct device * dev);

                int (*suspend)(struct device * dev, pm_message_t state);
                int (*suspend_late)(struct device * dev, pm_message_t state);
                int (*resume_early)(struct device * dev);
                int (*resume)(struct device * dev);

                unsigned int drivers_autoprobe:1;
            };
        
        1.4 struct platform_device
            这个结构体包含struct device    dev,以及资源定义:struct resource。
            这个结构体继承自:struct device    dev
            struct platform_device {
                const char    * name;
                u32        id;
                struct device    dev;
                u32        num_resources;
                struct resource    * resource;
            };
            
        1.5 struct device
            里面含有对应的driver结构体:struct device_driver
            struct device {

                ......
                char    bus_id[BUS_ID_SIZE];             // bus总线名称
                struct bus_type    * bus;                     // bus_type结构体, 里面有个match函数, 用来drv和dev匹配
                struct device_driver *driver;             // 匹配的struct device_driver
                void        *platform_data;                 // 私有数据
                void    (*release)(struct device * dev);
            };

        1.6 platform_bus_type
        struct bus_type platform_bus_type = {
            .name        = "platform",
            .dev_attrs    = platform_dev_attrs,
            .match        = platform_match,
            .uevent        = platform_uevent,
            .suspend    = platform_suspend,
            .suspend_late    = platform_suspend_late,
            .resume_early    = platform_resume_early,
            .resume        = platform_resume,
        };
        
        2. 程序流程分析
        
        2.1 具体流程分析
        从注册platform_driver开始分析:
        int platform_driver_register(struct platform_driver *drv)
        
            // 设置struct bus_type类型为platform_bus_type
            drv->driver.bus = &platform_bus_type;
            
            // 方法的重载,比如 drv->driver.probe = drv->probe = platform_drv->probe.
            // 如果子模块定义了probe函数,则父模块重载为子模块定义的probe函数
            if (drv->probe)
                drv->driver.probe = platform_drv_probe;
            if (drv->remove)
                drv->driver.remove = platform_drv_remove;
            if (drv->shutdown)
                drv->driver.shutdown = platform_drv_shutdown;
            if (drv->suspend)
                drv->driver.suspend = platform_drv_suspend;
            if (drv->resume)
                drv->driver.resume = platform_drv_resume;
                
            // 注册父模块:struct device_driver = drv->driver
            总结:
            设置bus type: drv->driver.bus       = &platform_bus_type;
            重载方法为:   drv->driver->probe    = platform_drv->probe;
                          drv->driver->remove   = platform_drv->remove;
                          drv->driver->shutdown = platform_drv->shutdown;
                          ....
            driver_register(&drv->driver);    
                //这里drv->bus设置为: platform_bus_type;
                //下面这个if不成立
                if ((drv->bus->probe && drv->probe) ||
                    (drv->bus->remove && drv->remove) ||
                    (drv->bus->shutdown && drv->shutdown)) {
                    printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods\n", drv->name);
                }
                bus_add_driver(drv);                             // struct device_driver drv;
                    struct bus_type * bus = get_bus(drv->bus);   // bus = &platform_bus_type;
                    if (drv->bus->drivers_autoprobe)             // 内核启动的时候,注册过总线: bus_register(&platform_bus_type);
                                                                 // 在该函数中,设置:bus->drivers_autoprobe = 1;
                        driver_attach(drv);
                            // 遍历struct device链表上的每个device, 调用__driver_attach进行匹配
                            bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
                                __driver_attach
                                    if (!dev->driver)  // 如果该device没有匹配驱动,则看看是否与现在在注册的驱动能否匹配(很合理吧!!!)
                                        driver_probe_device(drv, dev);
                                            // dev->is_registered = 1表示该设备device挂接到BUS总线上
                                            if (! device_is_registered(dev))
                                                return -ENODEV;
                                                
                                            // 前面分析知道: drv->bus = &platform_bus_type
                                            // 所以:  drv->bus->match = platform_bus_type.match = platform_match
                                            // 如果匹配不成功,则返回,这里仍然设置: dev->is_registered = 1, 并将该driver挂接到BUS的driver链表中
                                            // 这样的话,当有新的设备device注册进内核时,仍然会调用该驱动看是否匹配新加进来的设备(这样做很合理)
                                            if (drv->bus->match && !drv->bus->match(dev, drv))
                                                // 替换可知,最后执行函数: platform_match
                                                platform_match(struct device * dev, struct device_driver * drv)
                                                    // 找到包含struct device的struct platform_device结构体
                                                    struct platform_device *pdev = container_of(dev, struct platform_device, dev);
                                                    // 比较 platform_device 和 platform_driver 的name, 如果相等,则表示匹配上了
                                                    return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);    
                                            
                                            // 如果匹配上了,则调用下面的函数,进行device的probe
                                            really_probe(dev, drv)
                                                
                                                // 将drv挂接到dev上,表示该设备的驱动找到了
                                                // 以后注册驱动跟它无关,前面有提到只有没有匹配上驱动的设备才能遍历
                                                dev->driver = drv;   
                                                
                                                // 调用真正的probe函数
                                                if (dev->bus->probe) {   
                                                    // 设备dev层应该另外分析: dev->bus = &platform_bus_type,没有定义probe函数
                                                    ret = dev->bus->probe(dev);
                                                } else if (drv->probe) {
                                                    // 这里drv->probe = platform_drv->probe,用前面在struct platform结构体中重载过的方法: platform_drv->probe
                                                    ret = drv->probe(dev);
                                                }
                                                
                                                driver_bound(struct device *dev)
                                                    // 猜测是: 将device绑定到该驱动下,一个驱动下面可以绑定很多设备(很合理吧)
                                                    // 猜想:   设备端注册会这样做吗?一个设备下面绑定很多驱动(貌似不可以吧)
                                                    klist_add_tail(&dev->knode_driver, &dev->driver->klist_devices);
                
                if (ret >= 0)
                    // 将drv挂接到bus下的: bus->klist_drivers
                    klist_add_tail(&dev->knode_bus, &bus->klist_devices);
                else
                    // 只有当出现错误的时候,ret < 0,
                    dev->is_registered = 0;
        ---------------------------------------------------------------
        从注册 platform_device 开始分析:
        platform_device_register(struct platform_device * pdev)
            platform_device_add(pdev);
                pdev->dev.bus = &platform_bus_type; // 设置 platform_device -> struct device -> bus_type = platform_bus_type;
                device_add(&pdev->dev);           // pdev->dev 为 struct device 结构体;
                    bus_add_device(struct device * dev)
                    bus_attach_device(struct device * dev)
                        struct bus_type *bus = dev->bus;  // bus = &platform_bus_type;
                        if (bus) {
                            dev->is_registered = 1;      // 为什么设置为1,看下面就知道了
                            if (bus->drivers_autoprobe)  // bus->drivers_autoprobe 在哪里设置的呢?上面的分析同样有这一句话
                                ret = device_attach(dev);
                                    if (dev->driver)
                                        // 如果该dev已经指定了driver,则直接绑定就好
                                        ret = device_bind_driver(dev);    
                                    else
                                        // 如果没有指定驱动,则遍历bus下面的driver链表: bus->klist_drivers
                                        // 并且调用函数: __device_attach(struct device_driver * drv, void * data)
                                        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
                                            __device_attach(struct device_driver * drv, void * data)
                                                struct device * dev = data;
                                                return driver_probe_device(drv, dev);  // 这里开始与上面的分析一样
                                                    if (!device_is_registered(dev))    // 这里才看明白,为什么上面设置 dev->is_registered = 1
                                                        return -ENODEV;                // dev->is_registered = 1 表示BUS总线上注册了一个有效的设备
                                                    // 同样比较drv和dev的名字name是否一致,如果一致,则匹配,调用真正的probe函数
                                                    if (drv->bus->match && !drv->bus->match(dev, drv))  // platform_match
                                                        goto done;
                                                        
                                                    .... 与前面一样分析
                            if (ret >= 0)
                                // 当device_attach(dev)绑定成功,则将dev挂接到bus的dev链表中
                                klist_add_tail(&dev->knode_bus, &bus->klist_devices);
                            else
                                dev->is_registered = 0; // 看这里,才知道只有当device_attach(dev)成功,则置位,表示成功注册一个有效设备
                                                        // 如果没有找到相应的驱动程序,则设置该设备没有注册成功
                                                        // 为什么要这么做: 猜想: 这样的话,如果有新的驱动加载进来,这个设备还是有权利可以匹配驱动啊
                        }    
                    
        2.2 问题:            
        问题1:bus->drivers_autoprobe 在哪里设置的呢?并且设置为真?
        回答1:
            内核启动,会运行函数:\drivers\base\platform.c
                int __init platform_bus_init(void)
                    device_register(&platform_bus);
                        device_add(dev);
                    bus_register(&platform_bus_type);
                        bus->drivers_autoprobe = 1;    // 在这里设置为1, 可以测试一下

        问题2: 总线是什么时候注册的呢?有什么总线呢?
        回答2:linux启动的时候,就注册了很多总线,比如SPI,IIC,Platform等总线


        2.3 platform总线驱动设备流程总结:
        
            注意:下面讨论的是,当字符设备利用platform总线的方式进行注册驱动;当然块设备也有可能利用platform总线的方式。        
            在字符设备驱动中,需要操作函数来open, read, write设备文件,那么问题是:
            a. 设备文件什么时候创建的?
            b. file_operations在哪里定义的?
            
            在内核中,找个例子,就可以看到流程:
            drivers\char\watchdog\advantechwdt.c
                static int __init advwdt_init(void)            // 内核启动会初始化的函数
                    platform_driver_register(&advwdt_driver);  // 注册平台驱动
                        // 如果有匹配的平台设备注册进内核,就会调用platform_driver下定义的probe函数,也就是:advwdt_driver.probe
                        advwdt_probe(struct platform_device *dev)
                            misc_register(&advwdt_miscdev);    // 混杂设备注册,在advwdt_miscdev定义了操作函数
                                dev = MKDEV(MISC_MAJOR, misc->minor);
                                // 创建设备节点/dev/misc->name
                                misc->this_device = device_create(misc_class, misc->parent, dev, "%s", misc->name);
            所以:当设备和驱动匹配,在调用platform_driver的probe函数期间,创建设备节点,并且绑定操作函数,类似地提供file_operations结构体:
            里面提供对设备文件的操作函数,比如:open, read, write等。

        3. 驱动的编写
            一个led的点灯驱动程序,利用平台总线的方式编写驱动:
            分为两个部分:
                led_drv: 这是内核做好的,不需要改变,主要是提供probe函数,创建设备节点以及提供设备文件的操作函数;
                led_dev: 这是需要程序员根据自己的硬件资源进行修改的部分。
            
            在 led_dev 中定义资源:
            static struct resource led_resource[] = {
                [0] = {
                    .start = 0x56000050,          // 寄存器的起始地址
                    .end   = 0x56000050 + 8 - 1,  // 寄存器的结束地址
                    .flags = IORESOURCE_MEM,      // 内存资源
                },
                [1] = {
                    .start = 5,                  // 定义中断的管脚
                    .end   = 5,
                    .flags = IORESOURCE_IRQ,     // 中断资源
                }
            };
        
            在 led_drv 中定义probe函数,进行设备资源的获取,并创建设备节点:
            static int led_probe(struct platform_device *pdev)
            {
                struct resource        *res;
                /* 根据platform_device的资源进行ioremap */
                res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
                gpio_con = ioremap(res->start, res->end - res->start + 1);
                gpio_dat = gpio_con + 1;
                res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
                pin = res->start;

                /* 注册字符设备驱动程序 */
                printk("led_probe, found led\n");
                major = register_chrdev(0, "myled", &led_fops);
                cls = class_create(THIS_MODULE, "myled");
                class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
                
                return 0;
            }
        
            总结:
            驱动部分不需要修改,仅仅只是需要修改device部分,device不一样,你所定义的资源就不一样。
            但是驱动可以保持一致,这就是内核分离结构的魅力。
            
10th_drv:
        lcd驱动程序,这个在linux内核有个专业的名字:framebuffer驱动程序
        注意:又是分层结构,核心层:\drivers\video\fbmem.c,这里面定义了注册和卸载framebuffer的函数,
        驱动层可以调用核心层 fbmem.c 中的相关函数,进行framebuffer的注册,设置,卸载,或者获取硬件(LCD显示屏)的参数(比如分辨率,坐标值)等操作。
        
        既然有核心层,那肯定分为两部分,
            一部分为软件层,内核定义好的,不需要程序员去修改,固定不变的;
            一部分为硬件相关层,程序员需要根据自己的硬件进行修改,自己构造framebuffer中需要的结构体,设置,然后向内核注册LCD设备。
            
    知识点总结:
        1. 核心层fbmem.c分析:
        1.1 fb设备的入口与出口函数
        
            fb设备: 也是字符设备,
            初始化入口函数:
            static int __init fbmem_init(void)
                // 注册字符设备,主设备号固定为: FB_MAJOR = 29;
                register_chrdev(FB_MAJOR,"fb",&fb_fops)
                // 创建fb的一个类,注意这里还没有创建设备文件,
                // 猜想:应该是等到向内核注册一个framebuffer设备并成功以后,才会创建与该设备对应的设备文件,例如/dev/fbxx
                fb_class = class_create(THIS_MODULE, "graphics");
        
            卸载出口函数:
            static void __exit fbmem_exit(void)
                // 卸载类设备
                class_destroy(fb_class);
                // 注销fb字符设备
                unregister_chrdev(FB_MAJOR, "fb");
        
        
        1.2 framebuffer的注册
        int register_framebuffer(struct fb_info *fb_info)        
            // 记录fb注册的个数
            num_registered_fb++
            
            // 找到空闲的registered_fb,后面用来存储新注册的framebuffer结构体
            for (i = 0 ; i < FB_MAX; i++)
                if (!registered_fb[i])
                    break;
            // 记录fb_info处于的位置
            fb_info->node = i;   
            
            // 这里注册class下面的device,创建设备文件/dev/fbxxx,class前面内核启动的时候已经创建好了
            fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), "fb%d", i);
        
            // ....设置函数
            
            // 把fb_info注册进内核,放入到registered_fb[]数组中;
            registered_fb[i] = fb_info;
        
        1.3 framebuffer的卸载
        int unregister_framebuffer(struct fb_info *fb_info)
            i = fb_info->node;                             // 找到fb_info节点的位置
            registered_fb[i]=NULL;                         // 注销数组
            num_registered_fb--;                           // fb_info计数减一
            device_destroy(fb_class, MKDEV(FB_MAJOR, i));  // 注销设备节点
        
        
        1.4 fb操作函数分析
            问题:framebuffer如何打开,读写设备文件呢?
            回答:在入口函数中,注册字符设备: register_chrdev(FB_MAJOR,"fb",&fb_fops) 的时候,对应了一个file_operations结构体fb_fops
            由前面的学习知道,在这个fb_fops中定义了该设备文件的各种操作函数,比如open, read, write, poll等。
            当然,也可以由用户自己定义设备的操作函数,然后注册进内核。这相当于方法的重载。见下面更详细的说明。
            
            ----------------------------------------|
            app:      open,      read,       write  | 应用层打开或者读写framebuffer文件:open("/dev/fb0...", O_RDWR)
            ----------------------------------------|
                                                    |
            kernel: fb_open,     fb_read,  fb_write | fbmem.c    
                                                    |
            ----------------------------------------|
            driver:         LCD硬件                 |
            ----------------------------------------|
        
            1.4.1 fb_open:
                static int fb_open(struct inode *inode, struct file *file)
                
                    // 获取次设备号
                    int fbidx = iminor(inode);
                    struct fb_info *info;
                    
                    // 获得在数组中保存的fb_info结构体
                    info = registered_fb[fbidx]
                    
                    // 如果新注册的fb_info中的fbops定义了fb_open函数,则调用该open函数。
                    // 看看,这里又是如果重载了该open的方法,则调用重载的方法进行操作 [在内核里,面向对象的思想编程很多啊]
                    // 也就是说: 在framebuffer核心层fbmem.c文件中定义了一套fb的操作方法,比如open, read, write;
                    // 但是用户也可以在自己定义的fb_info结构体中构造自己的另一套fb的操作方法(也就是方法重载),然后注册进内核,以后的操作就会用用户定义的重载方法
                    if (info->fbops->fb_open)
                        res = info->fbops->fb_open(info,1);
            
            1.4.2 fb_read
                static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
                    
                    // 获得fb_info结构体
                    int fbidx = iminor(inode);
                    struct fb_info *info = registered_fb[fbidx];    

                    // 又是该死的方法重载:如果用户自己定义了fb_read方法,则用用户定义的
                    if (info->fbops->fb_read)
                        return info->fbops->fb_read(info, buf, count, ppos);
                    
                    // 下面的是不是很熟悉啊.....
                    copy_to_user(buf, buffer, c)
                    
                    
            1.4.3 fb_write
             static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
                int fbidx = iminor(inode);
                struct fb_info *info = registered_fb[fbidx];
                if (info->fbops->fb_write)
                    return info->fbops->fb_write(info, buf, count, ppos);
                    
                copy_from_user(src, buf, c)
                    
                和fb_read一样,没有好分析的....
                
        
        1.5 重要的数据结构体
            
            struct fb_info {
                int node;                       // 节点的位置
                int flags;
                struct fb_var_screeninfo var;    /* Current var 可变参数*/
                struct fb_fix_screeninfo fix;    /* Current fix 固定参数*/
                struct fb_monspecs monspecs;    /* Current Monitor specs */
                struct work_struct queue;        /* Framebuffer event queue */
                struct fb_pixmap pixmap;        /* Image hardware mapper */
                struct fb_pixmap sprite;        /* Cursor hardware mapper */
                struct fb_cmap cmap;            /* Current cmap */
                struct list_head modelist;      /* mode list */
                struct fb_videomode *mode;        /* current mode */

                struct fb_ops *fbops;       // 类似于 file_operations结构体,定义了fb的操作函数
                struct device *device;        /* This is the parent */
                struct device *dev;            /* This is this fb device */
                int class_flag;             /* private sysfs flags */

                char __iomem *screen_base;    /* Virtual address                   虚拟地址*/
                unsigned long screen_size;    /* Amount of ioremapped VRAM or 0    虚拟地址的大小*/
                void *pseudo_palette;        /* Fake palette of 16 colors         调色板 */

                u32 state;                        /* Hardware state i.e suspend */
                void *fbcon_par;                /* fbcon use-only private area */
                /* From here on everything is device dependent */
                void *par;    
            };
        
            // 可变参数
            struct fb_var_screeninfo {
                __u32 xres;            /* visible resolution        屏幕x轴的分辨率*/
                __u32 yres;         /*                          屏幕y轴的分辨率*/
                __u32 xres_virtual;        /* virtual resolution        */
                __u32 yres_virtual;
                __u32 xoffset;            /* offset from virtual to visible */
                __u32 yoffset;            /* resolution            */

                __u32 bits_per_pixel;        /* guess what            每一个像素点的位数: 如果是565,则每一个pixel是16bits */
                __u32 grayscale;        /* != 0 Graylevels instead of colors */

                struct fb_bitfield red;        /* bitfield in fb mem if true color, */
                struct fb_bitfield green;    /* else only length is significant */
                struct fb_bitfield blue;
                struct fb_bitfield transp;    /* transparency            */    

                __u32 nonstd;            /* != 0 Non standard pixel format */

                __u32 activate;            /* see FB_ACTIVATE_*        */

                __u32 height;            /* height of picture in mm    */
                __u32 width;            /* width of picture in mm     */

                __u32 accel_flags;        /* (OBSOLETE) see fb_info.flags */

                /* Timing: All values in pixclocks, except pixclock (of course) */
                __u32 pixclock;            /* pixel clock in ps (pico seconds) */
                __u32 left_margin;        /* time from sync to picture    */
                __u32 right_margin;        /* time from picture to sync    */
                __u32 upper_margin;        /* time from sync to picture    */
                __u32 lower_margin;
                __u32 hsync_len;        /* length of horizontal sync    */
                __u32 vsync_len;        /* length of vertical sync    */
                __u32 sync;            /* see FB_SYNC_*        */
                __u32 vmode;            /* see FB_VMODE_*        */
                __u32 rotate;            /* angle we rotate counter clockwise */
                __u32 reserved[5];        /* Reserved for future compatibility */
            };
        
            // 固定参数
            struct fb_fix_screeninfo {
                char id[16];            /* identification string eg "TT Builtin" */
                unsigned long smem_start;    /* Start of frame buffer mem 显存的起始地址,这是物理地址*/
                                /* (physical address) */
                __u32 smem_len;            /* Length of frame buffer mem */
                __u32 type;            /* see FB_TYPE_*        */
                __u32 type_aux;            /* Interleave for interleaved Planes */
                __u32 visual;            /* see FB_VISUAL_*        */
                __u16 xpanstep;            /* zero if no hardware panning  */
                __u16 ypanstep;            /* zero if no hardware panning  */
                __u16 ywrapstep;        /* zero if no hardware ywrap    */
                __u32 line_length;        /* length of a line in bytes    */
                unsigned long mmio_start;    /* Start of Memory Mapped I/O   */
                                /* (physical address) */
                __u32 mmio_len;            /* Length of Memory Mapped I/O  */
                __u32 accel;            /* Indicate to driver which    */
                                /*  specific chip/card we have    */
                __u16 reserved[3];        /* Reserved for future compatibility */
            };

        2. 流程总结:
            2.1 内核启动后,会初始化注册一个字符设备:主设备号为:FB_MAJOR,对应的fops为fb_fops:register_chrdev(FB_MAJOR,"fb",&fb_fops)
                在fb_fops中,定义了操作函数,比如open, read, write等函数;
                这是内核定义好的软件部分,不需要程序员去修改的。
                
            2.2 当程序员自己注册framebuffer设备到内核中,首先的定义一个fb_info结构体,并设置它包含的参数:
                - 可变参数
                - 固定参数
                - 该设备文件的操作函数的结构体:fb_fops
                - ....
                然后,调用核心层的register_framebuffer(struct fb_info *fb_info)向内核注册;
                在该函数中,创建设备对应的设备文件,比如/dev/fb0.
                
            2.3 对于这个设备文件/dev/fb0,主设备号为: FB_MAJOR, 次设备号为分配的: minor = 0
                其中主设备号对应的fops为内核自己注册定义好的:fb_fops;
                而用户注册的fops可以通过次设备作为下标,找到对应的数组元素:fb_info[minor], 该数组元素中就定义了该设备的fops。
                如果它重载了内核自己定义的fb_fops中的操作方法,则调用重载后的方法。
        
    驱动编写:
        1. 分配一个fb_info结构体: framebuffer_alloc
        2. 设置:就是对fb_info结构体的填充
        3. 注册: register_framebuffer
        4. 硬件相关的操作
        
    具体实现:

        // LCD初始化函数:
        static int lcd_init(void)
        {
            /* 1. 分配一个fb_info */
            s3c_lcd = framebuffer_alloc(0, NULL);

            /* 2. 设置 */
            /* 2.1 设置固定的参数 */
            strcpy(s3c_lcd->fix.id, "mylcd");
            s3c_lcd->fix.smem_len = 240*320*16/8;
            s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
            s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* TFT */
            s3c_lcd->fix.line_length = 240*2;
            
            /* 2.2 设置可变的参数 */
            s3c_lcd->var.xres           = 240;
            s3c_lcd->var.yres           = 320;
            s3c_lcd->var.xres_virtual   = 240;
            s3c_lcd->var.yres_virtual   = 320;
            s3c_lcd->var.bits_per_pixel = 16;

            /* RGB:565 */
            s3c_lcd->var.red.offset     = 11;
            s3c_lcd->var.red.length     = 5;
            
            s3c_lcd->var.green.offset   = 5;
            s3c_lcd->var.green.length   = 6;

            s3c_lcd->var.blue.offset    = 0;
            s3c_lcd->var.blue.length    = 5;

            s3c_lcd->var.activate       = FB_ACTIVATE_NOW;
            
            
            /* 2.3 设置操作函数 */
            s3c_lcd->fbops              = &s3c_lcdfb_ops;
            
            /* 2.4 其他的设置 */
            s3c_lcd->pseudo_palette = pseudo_palette;
            //s3c_lcd->screen_base  = ;  /* 显存的虚拟地址 */
            s3c_lcd->screen_size   = 240*324*16/8;

            /* 3. 硬件相关的操作 */
            /* 3.1 配置GPIO用于LCD */
            gpbcon = ioremap(0x56000010, 8);
            gpbdat = gpbcon+1;
            gpccon = ioremap(0x56000020, 4);
            gpdcon = ioremap(0x56000030, 4);
            gpgcon = ioremap(0x56000060, 4);

            *gpccon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
            *gpdcon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[23:8] */
            
            *gpbcon &= ~(3);  /* GPB0设置为输出引脚 */
            *gpbcon |= 1;
            *gpbdat &= ~1;     /* 输出低电平 */

            *gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
            
            /* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
            lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));

            /* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
             *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
             *            CLKVAL = 4
             * bit[6:5]: 0b11, TFT LCD
             * bit[4:1]: 0b1100, 16 bpp for TFT
             * bit[0]  : 0 = Disable the video output and the LCD control signal.
             */
            lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

            /* 垂直方向的时间参数
             * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
             *             LCD手册 T0-T2-T1=4
             *             VBPD=3
             * bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319
             * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
             *             LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1
             * bit[5:0]  : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0
             */
            lcd_regs->lcdcon2  = (3<<24) | (319<<14) | (1<<6) | (0<<0);


            /* 水平方向的时间参数
             * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
             *             LCD手册 T6-T7-T8=17
             *             HBPD=16
             * bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239
             * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
             *             LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10
             */
            lcd_regs->lcdcon3 = (16<<19) | (239<<8) | (10<<0);

            /* 水平方向的同步信号
             * bit[7:0]    : HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4
             */    
            lcd_regs->lcdcon4 = 4;


            /* 信号的极性
             * bit[11]: 1=565 format
             * bit[10]: 0 = The video data is fetched at VCLK falling edge
             * bit[9] : 1 = HSYNC信号要反转,即低电平有效
             * bit[8] : 1 = VSYNC信号要反转,即低电平有效
             * bit[6] : 0 = VDEN不用反转
             * bit[3] : 0 = PWREN输出0
             * bit[1] : 0 = BSWP
             * bit[0] : 1 = HWSWP 2440手册P413
             */
            lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
            
            /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
            s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, (dma_addr_t *)&s3c_lcd->fix.smem_start, GFP_KERNEL);
            
            lcd_regs->lcdsaddr1  = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
            lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
            lcd_regs->lcdsaddr3  = (240*16/16);  /* 一行的长度(单位: 2字节) */    
            
            //s3c_lcd->fix.smem_start = xxx;  /* 显存的物理地址 */
            /* 启动LCD */
            lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
            lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
            *gpbdat |= 1;     /* 输出高电平, 使能背光 */        

            /* 4. 注册 */
            register_framebuffer(s3c_lcd);
            
            return 0;
        }
    
        // LCD卸载函数:
        static void lcd_exit(void)
        {
            unregister_framebuffer(s3c_lcd);
            lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
            *gpbdat &= ~1;     /* 关闭背光 */
            dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
            iounmap(lcd_regs);
            iounmap(gpbcon);
            iounmap(gpccon);
            iounmap(gpdcon);
            iounmap(gpgcon);
            framebuffer_release(s3c_lcd);
        }
        
        
11th_ts
        该篇主要是移植触摸屏的驱动程序:触摸屏也是一个输入子系统结构体,当注册进内核,生成的设备文件就是/dev/event...
        可以参考前面的输入子系统讲的知识点。
        
        对于触摸屏,无非就是触摸操作,类似于按键按下,抬起等等。
        对于硬件驱动来讲,差别在于它的实现方式与按键不一样而已,对内核来说都一样,都属于输入子系统那一套东西。内核归纳的好啊!!!
        
        移植ts_lib库,就是可以执行应用程序,屏蔽硬件层,直接看到触摸到哪个位置,方便测试。
        
        ----
        驱动程序自行分析
        

12th_iic
        iic驱动程序的移植. 重点啊.
        
    知识点总结:
        1. IIC驱动流程分析:
            核心层: \drivers\i2c\i2c-core.c
            内核又是分离结构,bus_type为: i2c_bus_type,内核启动后,首先自己会初始化该总线,注册到内核中。
            类似于:platform_bus,但是又不同。
            
            1.1 初始化入口函数:
            static int __init i2c_init(void)
                // 注册总线: i2c_bus_type
                bus_register(&i2c_bus_type);
                // 注册一个类
                class_register(&i2c_adapter_class);
                
            1.2 卸载出口函数
            static void __exit i2c_exit(void)
                class_unregister(&i2c_adapter_class);
                bus_unregister(&i2c_bus_type);
            
            猜想:IIC设备驱动,也是分离为为drv和dev两部分,分别挂接到IIC_bus上的drv和dev链表上。一边是内核定义的不变部分,一边是与硬件相关的,需要
            程序员修改的部分。
            
            
            1.3 流程分析:
            
            1.3.1 重要数据结构

            /*
             * i2c_adapter is the structure used to identify a physical i2c bus along
             * with the access algorithms necessary to access it.
             */
            struct i2c_adapter {
                struct module *owner;
                unsigned int id;
                unsigned int class;
                const struct i2c_algorithm *algo; /* the algorithm to access the bus */
                void *algo_data;

                /* --- administration stuff. */
                int (*client_register)(struct i2c_client *);
                int (*client_unregister)(struct i2c_client *);

                /* data fields that are valid for all devices    */
                u8 level;             /* nesting level for lockdep */
                struct mutex bus_lock;
                struct mutex clist_lock;

                int timeout;
                int retries;
                struct device dev;        /* the adapter device */

                int nr;
                struct list_head clients;
                struct list_head list;
                char name[48];
                struct completion dev_released;
            };

            struct i2c_driver {
                int id;
                unsigned int class;

                /* Notifies the driver that a new bus has appeared. This routine
                 * can be used by the driver to test if the bus meets its conditions
                 * & seek for the presence of the chip(s) it supports. If found, it
                 * registers the client(s) that are on the bus to the i2c admin. via
                 * i2c_attach_client.  (LEGACY I2C DRIVERS ONLY)
                 */
                int (*attach_adapter)(struct i2c_adapter *);
                int (*detach_adapter)(struct i2c_adapter *);

                /* tells the driver that a client is about to be deleted & gives it
                 * the chance to remove its private data. Also, if the client struct
                 * has been dynamically allocated by the driver in the function above,
                 * it must be freed here.  (LEGACY I2C DRIVERS ONLY)
                 */
                int (*detach_client)(struct i2c_client *);

                /* Standard driver model interfaces, for "new style" i2c drivers.
                 * With the driver model, device enumeration is NEVER done by drivers;
                 * it's done by infrastructure.  (NEW STYLE DRIVERS ONLY)
                 */
                int (*probe)(struct i2c_client *);
                int (*remove)(struct i2c_client *);

                /* driver model interfaces that don't relate to enumeration  */
                void (*shutdown)(struct i2c_client *);
                int (*suspend)(struct i2c_client *, pm_message_t mesg);
                int (*resume)(struct i2c_client *);

                /* a ioctl like command that can be used to perform specific functions
                 * with the device.
                 */
                int (*command)(struct i2c_client *client,unsigned int cmd, void *arg);

                struct device_driver driver;
                struct list_head list;
            };
        
            struct i2c_driver里面包含了一个struct device_driver driver结构体,所以说struct i2c_driver是继承struct device_driver driver的结构体。
            前面讲到过: platform_driver结构体,同样也是继承自struct device_driver driver。
            注意:在struct device_driver driver包含 device 和 driver 挂接的总线类型 bus_type。
            
            
            
            1.3.2 IIC设备驱动流程分析

            在内核代码中,定义了很多struct i2c_driver 结构体变量,举例说明:\drivers\rtc\rtc-ds1307.c
            static struct i2c_driver ds1307_driver = {
                .driver = {
                    .name    = "ds1307",
                    .owner    = THIS_MODULE,
                },
                .attach_adapter    = ds1307_attach_adapter,
                .detach_client    = __devexit_p(ds1307_detach_client),
            };
            
            IIC总线类型:
            struct bus_type i2c_bus_type = {
                .name        = "i2c",
                .dev_attrs    = i2c_dev_attrs,
                .match        = i2c_device_match,
                .uevent        = i2c_device_uevent,
                .probe        = i2c_device_probe,
                .remove        = i2c_device_remove,
                .shutdown    = i2c_device_shutdown,
                .suspend    = i2c_device_suspend,
                .resume        = i2c_device_resume,
            };
            
            i2c驱动注册流程:\drivers\rtc\rtc-ds1307.c为例子
            static int __init ds1307_init(void)
                i2c_add_driver(&ds1307_driver);
                    i2c_register_driver(THIS_MODULE, ds1307_driver);
                        
                        driver->driver.owner = THIS_MODULE;
                        driver->driver.bus = &i2c_bus_type;       // 设置bus_type = i2c_bus_type;
                        driver_register(&driver->driver);      // 注册struct device_driver,前面platfrom_driver讲到过
问题:i2c_bus_type总线上的device是在哪里注册的?
                        
                            // 添加i2c_driver.driver到总线 i2c_bus_type 上, 并遍历iic总线上device链表,找到匹配的device进行绑定,但是并不probe设备
                            bus_add_driver(struct device_driver driver);
                                struct bus_type * bus = get_bus(drv->bus);   // bus = i2c_bus_type
                                if (drv->bus->drivers_autoprobe)             // 这个应该是在IIC BUS注册的时候设置过了,参考platform_bus注册
                                    error = driver_attach(drv);              // 进行遍历dev,并且匹配绑定过程
                                        
                                        // 从drv的BUS,也即i2c_bus_type的device链表上取出device,一一和driver进行比较
                                        bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
                                            __driver_attach(struct device * dev, void * data)
                                                driver_probe_device(drv, dev);
                                                    drv->bus->match(dev, drv)  // 这里: drv->bus->match = i2c_bus_type.match = i2c_device_match
                                                    i2c_device_match(struct device *dev, struct device_driver *drv)
                                                        struct i2c_client    *client = to_i2c_client(dev);
                                                        struct i2c_driver    *driver = to_i2c_driver(drv)
                                                        
                                                        // 比较client->driver_name和drv->name是否一致,相同则找到对应的设备
                                                        // 注意:这里drv->name在全局变量ds1307_driver中已定义 = "ds1307"
                                                        strcmp(client->driver_name, drv->name) == 0;
                                                    
                                                    really_probe(struct device *dev, struct device_driver *drv)
                                                        dev->driver = drv;               // dev->driver = ds1307_driver.driver
                                                        if (dev->bus->probe) {           // 注意:这里dev->bus = i2c_bus_type
                                                            ret = dev->bus->probe(dev);  // = i2c_device_probe    
                                                                i2c_device_probe(struct device *dev)
                                                                    struct i2c_client    *client = to_i2c_client(dev);
                                                                    struct i2c_driver    *driver = to_i2c_driver(dev->driver); //dev->driver = i2c_driver.driver
问题:driver->probe(client)这个函数不执行,那么问:IIC驱动在哪里probe设备呢?
                                                                    driver->probe(client);  // 这里: driver->probe = ds1307_driver.probe, 并没有定义,为空
                                                                
                                                        }
                                                        
                            //如果上面的操作成功, 则将该i2c_driver.driver放入i2c_bus_type的drivers链表中
                            klist_add_tail(&drv->knode_bus, &bus->klist_drivers);                                
                        // 将这个i2c_driver挂接到drivers链表上             
                        list_add_tail(&driver->list,&drivers);
                        
                        /* legacy drivers scan i2c busses directly */
                        if (driver->attach_adapter) {   // 前面定义了这个函数:ds1307_attach_adapter
                            struct i2c_adapter *adapter;

                            // 遍历adapters链表,一个一个拿出来,调用i2c_driver的attach_adapter函数
问题:adapters链表上注册了什么呢?有多少adapter挂接到了adapters链表上了?
                            list_for_each_entry(adapter, &adapters, list) {
                                driver->attach_adapter(adapter);
                                
                                    // 举例子: ds1307_driver.attach_adapter    = ds1307_attach_adapter,
                                    ds1307_attach_adapter(struct i2c_adapter *adapter)
                                    
                                        // 终于看到probe函数了,在基类的 dev->bus->probe = i2c_bus_type.probe 和drv->probe = i2c_driver.probe 中,并没有真正的probe设备;
                                        // 在这里才真正probe,继续看.......
                                        i2c_probe(adapter, &addr_data, ds1307_detect);
                                            i2c_probe_address(adapter, address_data->normal_i2c[i], -1, found_proc);
                                              i2c_probe_address(adapter, 0x50, -1, ds1307_detect);
                                                    i2c_smbus_xfer(adapter, addr, 0, 0, 0, I2C_SMBUS_QUICK, NULL)
                                                    
                                                        if (adapter->algo->smbus_xfer) // 没有定义,不执行下面的语句
                                                            res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write, command,size,data);
                                                        else
                                                            i2c_smbus_xfer_emulated(adapter,addr,flags,read_write, command,size,data);
                                                                i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
                                                                    ret = adap->algo->master_xfer(adap,msgs,num);  // 调用adapter->algo中定义的主机传输函数: s3c24xx_i2c_xfer
                                                    
                                                    // 调用回调函数
                                                    found_proc(adapter, addr, kind);        
                                                        ds1307_detect(struct i2c_adapter *adapter, int address, -1)
                                                            struct ds1307        *ds1307;
                                                            struct i2c_client    *client;
                                                            ds1307 = kzalloc(sizeof(struct ds1307), GFP_KERNEL)
                                                            
                                                            // 设置client
                                                            client = &ds1307->client;
                                                            client->addr = address;
                                                            client->adapter = adapter;
                                                            client->driver = &ds1307_driver;
                                                            client->flags = 0;
                                                            
                                                            // 设置消息
                                                            ds1307->msg[0].addr = client->addr;
                                                            ds1307->msg[0].flags = 0;
                                                            ds1307->msg[0].len = 1;
                                                            ds1307->msg[0].buf = &ds1307->reg_addr;

                                                            ds1307->msg[1].addr = client->addr;
                                                            ds1307->msg[1].flags = I2C_M_RD;
                                                            ds1307->msg[1].len = sizeof(ds1307->regs);
                                                            ds1307->msg[1].buf = ds1307->regs;                                                            
                                                            
                                                            // 设置类型
                                                            ds1307->type = ds_1307;
                                                            
                                                            // 读check
                                                            i2c_transfer(client->adapter, ds1307->msg, 2)
                                                            
                                                            strlcpy(client->name, "ds1307", I2C_NAME_SIZE);
                                                            i2c_attach_client(struct i2c_client *client)
                                                                struct i2c_adapter *adapter = client->adapter;
                                                                // 把新注册的client挂接到该adpter中的clinets链表中去
                                                                list_add_tail(&client->list,&adapter->clients);
                                                                client->dev.parent = &client->adapter->dev;
                                                                client->dev.bus = &i2c_bus_type;  // client挂接在i2c_bus_type上面
                                                                if (client->driver) // client->driver = &ds1307_driver
                                                                    // client->dev.driver = &client->driver->driver = &ds1307_driver.driver
                                                                    client->dev.driver = &client->driver->driver;
                                                                    client->dev.release = i2c_client_dev_release
                                                                    
                                                                    device_register(&client->dev);
                                                                        bus_attach_device(struct device * dev)
                                                                            device_attach(struct device * dev)
                                                                            if (dev->driver)  // 这里dev->driver =client->dev.driver = &client->driver->driver = &ds1307_driver.driver
                                                                                    ret = device_bind_driver(dev);  // 由于driver已知,所以直接绑定,注意和platform的区别
                                                                            
                                                                            adapter->client_register(client)
                            }
                        }
                    
        问题1:adapters链表上挂接了什么?由谁挂载的?
        回答1: 当向内核注册adapter时,就会将该adapter挂接到adapters链表上
                
            i2c_add_adapter(struct i2c_adapter *adapter)
                adapter->nr = id;               // 获取adapter的id号
                i2c_register_adapter(adapter);  // 注册adapter
                    struct i2c_driver  *driver;
                    list_add_tail(&adap->list, &adapters);    // 将注册的adapter挂接到adapters链表
                    adap->dev.parent = &platform_bus;         
                    
                    // 设置adapter->device.
                    sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);   // 设置名字为: i2c-0, i2c-1......等等
                    adap->dev.release = &i2c_adapter_dev_release;    // 设置release函数
                    adap->dev.class = &i2c_adapter_class;            // 设置类
                                    ---------------------------------// 设备如何挂接到IIC bus上去呢?
                    res = device_register(&adap->dev);               // 注册adapter下的device
                        device_add(struct device *dev)
                            bus_attach_device(struct device * dev)    
问题:                            
                                struct bus_type *bus = dev->bus;     // 这个dev->bus在哪里设置的呢?分析不下去了。
-------------------------------------
                    /* let legacy drivers scan this bus for matching devices */
                    list_for_each(item,&drivers) {
                        // 遍历i2c_driver链表中的每一个driver,调用driver->attach_adapter(adap);
                        driver = list_entry(item, struct i2c_driver, list);
                        if (driver->attach_adapter)
                            /* We ignore the return code; if it fails, too bad */
                            driver->attach_adapter(adap);
                                //同样:在这里才真正的probe IIC设备
                    }    
                    
        问题2: 注册的adapter中的struct device实体的BUS_TYPE挂接在什么上了?
        回答2: 当注册 i2c_adapter 的时候, 先probe是否有IIC设备,如果有,则继续执行: 构造i2c_client结构体,
               设置它,然后将 i2c_client.device 注册到总线 i2c_bus_type 上。
                                
        问题3:IIC总线 i2c_bus_type 上的probe函数:i2c_device_probe,在struct i2c_driver中的diver->probe函数为空,
               那怎么probe i2c设备呢?       
        回答3: 在driver->attach_adapter(adapter)中才真正的probe i2c设备
        
        问题4:struct i2c_driver xxx_driver结构体中的xxx_driver.driver挂接到 i2c_bus_type 的driver链表上了,
               那么 i2c_bus_type 上的设备链表是什么时候挂接的?
        回答4:在内核中搜索:i2c_bus_type,肯定能找到设备挂接的代码。
                
        问题5: 对于不同的开发板,会有不同的IIC控制器,如果开发板设置为IIC的主设备,那么主设备如何来发现IIC从设备呢?
        回答5: 注册的adapter为: s3c24xx_i2c.adapter
        \drivers\i2c\busses\i2c-s3c2410.c
            初始化注册的adapter为:
            static struct s3c24xx_i2c s3c24xx_i2c = {
                .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
                .wait        = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
                .tx_setup    = 50,
                .adap        = {
                    .name            = "s3c2410-i2c",
                    .owner            = THIS_MODULE,
                    .algo            = &s3c24xx_i2c_algorithm,
                    .retries        = 2,
                    .class            = I2C_CLASS_HWMON,
                },
            };
            
            static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
                .master_xfer        = s3c24xx_i2c_xfer,
                .functionality        = s3c24xx_i2c_func,
            };
            
        当注册i2c_driver到内核中,都会遍历adapters链表上的adapter,然后调用 i2c_driver->attach_adapter(adapter) 函数;
        而所有的i2c_driver.attach_adapter函数都会调用同一个函数接口:i2c_probe
        int i2c_probe(struct i2c_adapter *adapter, struct i2c_client_address_data *address_data, int (*found_proc) (struct i2c_adapter *, int, int))
        举例:\drivers\rtc\rtc-ds1307.c
        ds1307_attach_adapter(struct i2c_adapter *adapter)
            i2c_probe(adapter, &addr_data, ds1307_detect);
                i2c_probe_address(adapter, 设备地址, -1, ds1307_detect);
                    // 分为两步走:
                    // 1. 先发一个写命令到IIC从设备,查询是否收到ACK信号,一旦ack信号收到,表示该从设备存在
                    i2c_smbus_xfer(adapter, 设备地址, 0, 0, 0, I2C_SMBUS_QUICK, NULL)
                        // adapter->algo->smbus_xfer = s3c24xx_i2c.adap->algo->smbus_xfer = NULL
                        // 所以if里面的语句不执行
                        if (adapter->algo->smbus_xfer) {
                            mutex_lock(&adapter->bus_lock);
                            res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,
                                                            command,size,data);
                            mutex_unlock(&adapter->bus_lock);
                        } else
                            // 执行这句代码
                            res = i2c_smbus_xfer_emulated(adapter, addr,    flags,read_write,command, size,           data);
                                    // 这里 read_write = 0, 表示为"写操作"   参考头文件定义:#define I2C_SMBUS_WRITE    0
                                    i2c_smbus_xfer_emulated(adapter, 设备地址, 0,    0,          0,    I2C_SMBUS_QUICK, NULL)
                                        // 构造消息
                                        struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
                                                                { addr, flags | I2C_M_RD, 0, msgbuf1 };
                                                                
                                        case I2C_SMBUS_QUICK:
                                            msg[0].len = 0;
                                            // 这里为写操作
                                            msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;
                                            num = 1;
                                        break;
                                            
                                        i2c_transfer(adapter, msg, num);
                                            adap->algo->master_xfer(adap,msgs,num);
                                            s3c24xx_i2c_xfer(adap,msgs,num);  // 在msgs里面保存有probe从设备的地址
                                                // 从adapter获得i2c控制器设备,就是从这个设备发出IIC信号的;
                                                // 前面注册adapter的时候,就是将struct s3c24xx_i2c保存在adapter的变量里的
                                                struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
                                                for (retry = 0; retry < adap->retries; retry++) // adap->retries = 2, 显示设置好了
                                                    s3c24xx_i2c_doxfer(i2c, msgs, num);   // 真正地到底层,发起IIC信号了
                                                        s3c24xx_i2c_set_master(i2c);      // 设置为主机设备模式
                                                        // 设置
                                                        i2c->msg     = msgs;
                                                        i2c->msg_num = num;
                                                        i2c->msg_ptr = 0;
                                                        i2c->msg_idx = 0;
                                                        i2c->state   = STATE_START;

                                                        s3c24xx_i2c_enable_irq(i2c);             // 开启iic中断
                                                        s3c24xx_i2c_message_start(i2c, msgs);    // 启动传输信息
                                                        
                                                        // 进入休眠态,等待传输信息完成
                                                        timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
                                                        // 问题:谁来唤醒i2c->wait呢?
                                                           回答:猜想,应该是当IIC bus上的msg传输完成,就会来唤醒该进程
                                                                s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret)
                                                                    wake_up(&i2c->wait);  // 唤醒等待队列
                    // 2. 如果第1步成功,表示IIC从设备存在
                    // 调用用户定义的detect函数:构造一个client结构体,将client绑定到i2c_bus_type,
                    err = found_proc(adapter, addr, kind);
                        ds1307_detect(struct i2c_adapter *adapter, int address, int kind)
                        ds1307_detect(adapter, 设备地址, -1)
                            struct ds1307        *ds1307;   // 定义一个 ds1307
                            struct i2c_client    *client;   // 定义一个 i2c_client
                            
                            // 分配并设置ds1307, client
                            ds1307 = kzalloc(sizeof(struct ds1307), GFP_KERNEL)
                            client = &ds1307->client;
                            client->addr = 设备地址;           // 设备地址
                            client->adapter = adapter;         // 一边是 i2c_adapter
                            client->driver = &ds1307_driver;   // 一边是 i2c_driver
                            client->flags = 0;
                            client->dev   = (struct dev *)ds1307;
                            
                            // 设置消息
                            ds1307->msg[0].addr = client->addr;      // client->addr = 设备地址
                            ds1307->msg[0].flags = 0;                // 表示"写IIC从设备操作"
                            ds1307->msg[0].len = 1;                  // 表示消息长度为 1
                            ds1307->msg[0].buf = &ds1307->reg_addr;  // 表示写操作的buffer为:ds1307->reg_addr

                            ds1307->msg[1].addr = client->addr;        // client->addr = 设备地址
                            ds1307->msg[1].flags = I2C_M_RD;           // 表示"读IIC从设备操作"
                            ds1307->msg[1].len = sizeof(ds1307->regs); // 表示消息长度为 sizeof(ds1307->regs)
                            ds1307->msg[1].buf = ds1307->regs;         // 表示写操作的buffer为:ds1307->regs
                            
                            i2c_transfer(client->adapter, ds1307->msg, 2);
                            
                            // 设置client名字
                            strlcpy(client->name, "ds1307", I2C_NAME_SIZE);
                            
                            // 将client绑定attach到 i2c_bus_type
                            i2c_attach_client(client)
                                struct i2c_adapter *adapter = client->adapter;
                                list_add_tail(&client->list,&adapter->clients);
                                // client进一步设置
                                client->usage_count = 0;
                                
                                // 开始设置 client->dev
                                client->dev.parent = &client->adapter->dev;
                                
                                // 注意:看到这里是不是明白了,client下面的device挂接到 i2c_bus_type 上
                                // 前面一直在找 i2c_bus_type 的device是什么时候挂接的?
                                client->dev.bus = &i2c_bus_type;
                                
                                // 进一步设置,很明显:设置client下面的device下的driver = client->dev->driver = ds1307_driver.driver
                                // 前面讲到:在注册ds1307_driver的时候,是将ds1307_driver.driver挂接到IIC总线 i2c_bus_type
                                client->dev.driver = &client->driver->driver;
                                client->dev.release = i2c_client_release;
                                client->dev.uevent_suppress = 1;
                                snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id), "%d-%04x", i2c_adapter_id(adapter), client->addr);
                                
                                // 注册 client->dev
                                device_register(&client->dev)  // 匹配之前注册i2c_driver时构造的 i2c_driver.driver,挂接在 i2c_bus_type 下

                            // 注册client到内核中,作为字符设备
                            ds1307->rtc = rtc_device_register(client->name, &client->dev, &ds13xx_rtc_ops, THIS_MODULE);
                                struct rtc_device *rtc;
                                rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
                                rtc->id = id;
                                rtc->ops = ops;             // ops = ds13xx_rtc_ops
                                rtc->owner = owner;
                                rtc->max_user_freq = 64;
                                rtc->dev.parent = dev;        // rtc->dev.parent = dev = client->dev
                                rtc->dev.class = rtc_class;   // 注册到rtc_class
                            strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);  // rtc->name = “ds1307”
                            snprintf(rtc->dev.bus_id, BUS_ID_SIZE, "rtc%d", id);
                            rtc_dev_prepare(rtc);
                                rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);  // 构造devt(major, minor)
                                cdev_init(&rtc->char_dev, &rtc_dev_fops);         // 初始化字符设备,绑定主设备号和rtc_dev的fops
                            // 注册rtc下的struct device结构体到内核中
                            device_register(&rtc->dev);
                            // 注册rtc下面的字符设备到内核中
                            cdev_add(&rtc->char_dev, rtc->dev.devt, 1)

                            
        // rtc类的操作函数                    
        static const struct rtc_class_ops ds13xx_rtc_ops = {
            .read_time    = ds1307_get_time,
            .set_time    = ds1307_set_time,
        };                        
        // rtc设备操作函数(向内核注册了字符设备)
        static const struct file_operations rtc_dev_fops = {
            .owner        = THIS_MODULE,
            .llseek        = no_llseek,
            .read        = rtc_dev_read,
            .poll        = rtc_dev_poll,
            .ioctl        = rtc_dev_ioctl,
            .open        = rtc_dev_open,
            .release    = rtc_dev_release,
            .fasync        = rtc_dev_fasync,
        };
                            
    
    
    1.3.3 IIC设备驱动框架
    
    app:       open,    read,    write  
    ------------------------------------------------
    驱动:
            drv_open, drv_read, drv_write          drivers/i2c/chip, iic设备驱动:知道数据的含义(针对具体的芯片)
            ----------------------------------------
               i2c总线驱动程序                     drivers/i2c/buses, iic总线驱动程序:1. 识别IIC从设备; 2. 提供读写函数,知道如何收发数据
    ------------------------------------------------
    硬件:     AT24C02, AT24C08
    
    
IIC驱动程序理解:
硬件部分:
    i2c驱动里面,构造了 i2c_adapter 这个结构体,看源代码知道,adapter就对应特定芯片的一个IIC控制器,对应的是硬件部分。
    所以,adapter结构体里面定义了与IIC传输协议相关的函数,比如IIC信号如何传输。
    而client则相当于该"adapter(插槽)"下挂接的"IIC从设备",所以client保存有变量"adapter"和i2c_driver,而且内核最终将利用它
    来对IIC设备进行数据的读写等操作。
    
    对于S3C2440开发板,内核启动时,会首先注册一个adapter到内核,就是说先注册一个"adapter(插槽)",并将adapter挂接到adapters(IIC驱动相关)链表上
    不过内核将它封装成一个平台设备结构体,进行注册,见文件:drivers\i2c\busses\i2c-s3c2410.c
    static struct s3c24xx_i2c s3c24xx_i2c = {
        .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
        .wait        = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
        .tx_setup    = 50,
        .adap        = {
            .name            = "s3c2410-i2c",
            .owner            = THIS_MODULE,
            .algo            = &s3c24xx_i2c_algorithm,
            .retries        = 2,
            .class            = I2C_CLASS_HWMON,
        },
    };
    在 s3c24xx_i2c.adap.algo = s3c24xx_i2c_algorithm 中就定义了IIC传输函数:s3c24xx_i2c_xfer
    static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
        .master_xfer        = s3c24xx_i2c_xfer,
        .functionality        = s3c24xx_i2c_func,
    };
    函数 s3c24xx_i2c_xfer 就是最底层的IIC传输函数了。
    
    
软件部分:
    软件部分构造了 i2c_driver 结构体,里面定义了 attach_adapter, detach_adapter, detach_client,当注册i2c_driver结构体时,会做如下工作:
    1. 把 i2c_driver.driver 挂接到 i2c_bus_type 上,也就相当于把i2c_driver挂接到 i2c_bus_type 上
    2. 取出adapters链表中的每个adapter结构体,调用 i2c_driver.attach_adapter 函数,来探测IIC设备是否真的存在。
    3. 在i2c_driver.attach_adapter("adapter")函数中,会做如下工作:
        3.0 首先会调用统一的函数接口:i2c_probe(adapter, 设备地址, IIC设备真正的probe函数)来探测IIC设备是否真的存在
            如果存在,则会往下继续做:
        3.1 构造一个 i2c_client 结构体,设置它:
            a. 保存IIC设备地址;
            b. 一边挂接"刚注册的i2c_driver",一边挂接"探测到iic设备的adapter";
        3.2 注册 i2c_client.dev 到总线 i2c_bus_type 上,和前面注册的 i2c_driver.driver 绑定到一起,都挂接在 i2c_bus_type 总线上。
            这样的话,i2c_client 和 i2c_driver 都挂接在 i2c_bus_type 总线上。
        
IIC驱动中相关结构体关系连接图:
-----------------------------------------------------------------------------------------------------------------------------------------
                                          client
                                             . addr      = IIC设备地址
                                             . adapter   =                    i2c_adapter      -->  adapters链表
    drivers链表     <---------i2c_driver    =    . driver                             . algo
                                .driver      . dev ------------------>|               .master_xfer (定义硬件底层IIC传输信号的函数)
                                   |----------->. driver              |
                                   |                                  |
                                   |----------> i2c_bus_type <--------|

----------------------------------------------------------------------------------------------------------------------------------------
                                   
                                   
S3C2440内核IIC设备驱动建立流程:
------------------------------------------------
分析:\drivers\i2c\i2c-dev.c
流程分析:
static int __init i2c_dev_init(void)
    // 注册IIC字符设备:主设备号为I2C_MAJOR = 89; 对应的 file_operations = i2cdev_fops;
    register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
    // 创建一个IIC类:i2c_dev_class
    i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
                                                         -----------------------------------------------------问题:哪里创建设备呢?
    // 注册iic driver
    i2c_add_driver(&i2cdev_driver);
        i2cdev_driver->driver.owner = THIS_MODULE;
        i2cdev_driver->driver.bus   = &i2c_bus_type;
        // 从总线 i2c_bus_type 的device一端取出各个device,和 i2cdev_driver->driver 进行比较是否匹配
        driver_register(&i2cdev_driver->driver);
            // 调用总线的match函数: i2c_bus_type.match = i2c_device_match
            i2c_device_match(struct device *dev, struct device_driver *drv)
                struct i2c_client    *client = to_i2c_client(dev);
                struct i2c_driver    *driver = to_i2c_driver(drv);
                // 由总线:i2c_bus_type 下的挂接的device找到 i2c_client;
                // 比较i2c_client和i2c_driver的名字是否相等
                strcmp(client->driver_name, drv->name)
                
            // 如果找到匹配的device,则调用probe函数
            really_probe(struct device *dev, struct device_driver *drv)
                dev->bus->probe(dev)
                i2c_device_probe(struct device *dev)
                    driver->probe(client)  
                    
        // 将i2cdev_driver放入drivers链表中    
        list_add_tail(&i2cdev_driver->list,&drivers);
        
        // 如果定义了i2cdev_driver->attach_adapter函数,则遍历链表adapters下的adapter,
        // 取出每个adapter,调用函数:i2cdev_driver->attach_adapter(adapter)
        if (i2cdev_driver->attach_adapter) {
            struct i2c_adapter *adapter;
            list_for_each_entry(adapter, &adapters, list) {
                i2cdev_driver->attach_adapter(adapter);
            }
        }
        
由前面的分析知道,当注册 i2c_driver 到内核的时候,将遍历adapters链表下的"adapter",然后调用该注册的i2c_driver下的 .attach_adapter 函数,
进行绑定。所以一旦内核有"adapter"存在,将调用函数: i2cdev_driver.attach_adapter = i2cdev_attach_adapter
static int i2cdev_attach_adapter(struct i2c_adapter *adap)
    struct i2c_dev *i2c_dev;
    i2c_dev = get_free_i2c_dev(adap);
        struct i2c_dev *i2c_dev;
        i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL);
        i2c_dev->adap = adap;                           // 指定adapter
        list_add_tail(&i2c_dev->list, &i2c_dev_list);   // 将该 i2c_dev 挂接到链表 i2c_dev_list 上面
                                                          ----------------------------------------------------回答:在这里创建设备
    // 在类 i2c_dev_class 下面创建设备,比如“/dev/i2c-0”, “/dev/i2c-1”......
    // 主设备号为:I2C_MAJOR = 89,次设备号为:IIC设备的编号,操作函数 file_operations 结构体为:i2cdev_fops;
    i2c_dev->dev = device_create(i2c_dev_class, &adap->dev, MKDEV(I2C_MAJOR, adap->nr), "i2c-%d", adap->nr);
    
    
    
结构体定义:\drivers\i2c\i2c-dev.c
static const struct file_operations i2cdev_fops = {
    .owner        = THIS_MODULE,
    .llseek        = no_llseek,
    .read        = i2cdev_read,
    .write        = i2cdev_write,
    .ioctl        = i2cdev_ioctl,
    .open        = i2cdev_open,
    .release    = i2cdev_release,
};
    
static struct i2c_driver i2cdev_driver = {
    .driver = {
        .name    = "dev_driver",
    },
    .id        = I2C_DRIVERID_I2CDEV,
    .attach_adapter    = i2cdev_attach_adapter,
    .detach_adapter    = i2cdev_detach_adapter,
    .detach_client    = i2cdev_detach_client,
};


由内核打印信息,追索驱动流程:
----------------------------------------
内核注册流程:
由打印信息:
1. 先注册 i2c_driver: i2cdev_driver                --- \drivers\i2c\i2c-dev.c
前面分析过,内核启动初始化会执行函数:i2c_dev_init
i2c_dev_init(void)
    i2c_add_driver(&i2cdev_driver);
        i2c_register_driver(THIS_MODULE, driver);
            driver_register(&driver->driver);
            list_add_tail(&driver->list,&drivers);   // 这里将 i2cdev_driver 挂接到 drivers 链表上
            driver->attach_adapter(adapter);
            
static struct i2c_driver i2cdev_driver = {
    .driver = {
        .name    = "dev_driver",
    },
    .id        = I2C_DRIVERID_I2CDEV,
    .attach_adapter    = i2cdev_attach_adapter,
    .detach_adapter    = i2cdev_detach_adapter,
    .detach_client    = i2cdev_detach_client,
};

打印信息:
    i2c /dev entries driver
    i2c_dev_init, i2c_add_driver: i2cdev_driver
-------------------------------------------------
    
2. 然后注册i2c_adapter:  s3c24xx_i2c.adap       --- \drivers\i2c\busses\i2c-s3c2410.c
s3c24xx_i2c_probe(struct platform_device *pdev)
    struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
    i2c->adap.algo_data = i2c;
    i2c->adap.dev.parent = &pdev->dev;
    s3c24xx_i2c_init(i2c);                            // 初始化IIC控制器
    i2c_add_adapter(&i2c->adap);
        i2c_register_adapter(adapter);
            list_add_tail(&adap->list, &adapters);    // 这里将 s3c24xx_i2c.adap 挂接到 adapters 链表上
            driver->attach_adapter(adap);

static struct s3c24xx_i2c s3c24xx_i2c = {
    .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
    .wait        = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
    .tx_setup    = 50,
    .adap        = {
        .name            = "s3c2410-i2c",
        .owner            = THIS_MODULE,
        .algo            = &s3c24xx_i2c_algorithm,
        .retries        = 2,
        .class            = I2C_CLASS_HWMON,
    },
};
    
打印信息:              
    cnt: 1, i2c_adapter name: s3c2410-i2c
-------------------------------------------
    
3. 当注册 i2c_adapter:  s3c24xx_i2c.adap 的时候,就会遍历drivers链表,把drivers链表中的 i2c_driver 一个一个取出来,然后调用 i2c_driver.attach_adapter(adapt)函数。
因为前面只注册了一个 i2c_driver结构体: i2cdev_driver,所以就将调用函数:i2cdev_driver.attach_adapter(adapter) = i2cdev_attach_adapter(s3c24xx_i2c.adap)
i2cdev_attach_adapter(s3c24xx_i2c.adap)
    device_create(i2c_dev_class, &adap->dev, MKDEV(I2C_MAJOR, adap->nr), "i2c-%d", adap->nr);
    这个函数前面已经分析了,将会创建设备文件:"/dev/i2c-0"
所以当内核启动后,可以查看是否建立的设备文件:
# ls /dev/i2c* -l
crw-rw----    1 0        0         89,   0 Jan  1 00:00 /dev/i2c-0
可以看到,主设备号为89,次设备号为0,建立了设备文件: "/dev/i2c-0"

内核打印信息:
    cnt: 1, i2cdev_attach_adapter,adap.name:s3c2410-i2c
    cnt: 1, i2c_register_driver, i2c_driver->name: dev_driver
    

--------------------------------------------------------------
如何编写i2c驱动呢?
1. 构造一个i2c_driver结构体
    该结构体包括函数:
        attach_adapter
        detach_client
2. 注册i2c_driver结构体
    当向内核注册i2c_driver,将会调用该结构体下面的attach_adapter函数
3. 所以当内核注册i2c_driver,会先探测IIC设备时候存在,如果存在,则调用attach_adapter函数来probe设备,来建立某种联系:
3.1 在attach_adapter函数中,调用统一的函数接口:i2c_probe(adapter, 设备地址, "设备真正的probe函数");
3.2 在"设备真正的probe函数"中,构造一个:struct i2c_client结构体:
    该结构体一端连着"adapter",一端连着"i2c_driver",并保存IIC设备地址;最后调用函数 i2c_attach_client 将该client注册到内核的 i2c_bus_type 上。
3.3 运行到这里,表示已经找到IIC设备,并且已经利用client建立了某种联系,就可以创建与IIC设备对应的字符设备文件,这个创建过程和前面讲的建立字符设备驱动一样。
    以后IIC设备文件的读写等操作,都是借助于client这个桥梁。

    
    
13th_chrdev_another:
    讲的是:字符设备注册的另外一种方法。
    前面讲的字符设备注册:register_chrdev(0, "hello", &hello_fops);
    这将会导致:(major,  0), (major, 1), ..., (major, 255)都对应hello_fops
    这一注册,将会导致 minor = 0 - 255 的次设备号都被霸占。
    
    新的方法, 两步走:
        1. 找到设备号
        主设备号major已知情况:
            devid = MKDEV(major, 0);
            register_chrdev_region(devid, 2, "hello");  /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
        主设备号major由内核自动分配情况:
            alloc_chrdev_region(&devid, 0, 2, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
            major = MAJOR(devid);
        2. 注册字符设备到内核
            cdev_init(&hello_cdev, &hello_fops);
                memset(cdev, 0, sizeof *cdev); // 初始化
                cdev->ops = fops;              // 绑定fops到cdev
            cdev_add(&hello_cdev, devid, 2);   // 真正注册字符设备到内核
        

14th_sound:
    音频设备驱动:重点了.....
        
    知识点总结:
    1. 音频驱动流程分析:
        举例子:\sound\soc\s3c24xx\s3c2410-uda1341.c
        
    1.1 驱动端
        s3c2410_uda1341_init(void)
            driver_register(&s3c2410iis_driver);
            
        static struct device_driver s3c2410iis_driver = {
            .name = "s3c2410-iis",
            .bus = &platform_bus_type,
            .probe = s3c2410iis_probe,
            .remove = s3c2410iis_remove,
        };
        
        struct bus_type platform_bus_type = {
            .name        = "platform",
            .dev_attrs    = platform_dev_attrs,
            .match        = platform_match,
            .uevent        = platform_uevent,
            .suspend    = platform_suspend,
            .suspend_late    = platform_suspend_late,
            .resume_early    = platform_resume_early,
            .resume        = platform_resume,
        };
        
        前面分析很多了,找名字相同的device,调用:drv->bus->match(dev, drv) = s3c2410iis_driver.bus.match = platform_match
        platform_match(struct device * dev, struct device_driver * drv)
            struct platform_device *pdev = container_of(dev, struct platform_device, dev);
            // 比较platform_device的name和driver的name是否一致,
            // 如果一致,则调用函数: dev->bus->probe(dev) = 没有定义; 或者:drv->probe(dev) = s3c2410iis_probe(struct device *dev)
            strncmp(pdev->name, drv->name, BUS_ID_SIZE)
        
        问题:设备端是如何注册的呢?
        回答:继续往下看......
        
    1.2 设备端
    
        定义设备结构体:\arch\arm\plat-s3c24xx\devs.c
        struct platform_device s3c_device_iis = {
            .name          = "s3c2410-iis",
            .id          = -1,
            .num_resources      = ARRAY_SIZE(s3c_iis_resource),
            .resource      = s3c_iis_resource,
            .dev              = {
                .dma_mask = &s3c_device_iis_dmamask,
                .coherent_dma_mask = 0xffffffffUL
            }
        };
    
        设置并注册设备: \arch\arm\mach-s3c2440\Mach-smdk2440.c
        static struct platform_device *smdk2440_devices[] __initdata = {
            &s3c_device_usb,
            &s3c_device_lcd,
            &s3c_device_wdt,
            &s3c_device_i2c,
            &s3c_device_iis,     // 音频设备
            &s3c2440_device_sdi,
        };
        
        // 内核启动初始化设备数组
        static void __init smdk2440_machine_init(void)
            platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));  // 向内核注册设备
                platform_device_register(devs[i]);
                    platform_device_add(pdev);
                        // 设置platform_device.dev挂接的总线为:platform_bus_type,和驱动挂接的总线一致
                        // 为什么要一致?因为待会儿会从挂接的"总线的driver链表"上一个一个取出driver,来和pdev->dev进行匹配
                        // 只有相同的总线,才有绑定的必要(这是很合理的)
                        pdev->dev.bus = &platform_bus_type;  
                        device_add(&pdev->dev);
        问题:如何匹配绑定呢?
        回答:前面讲过了,比较platform_device的name和driver的name是否一致:
            因为:s3c_device_iis.name  = "s3c2410-iis" = s3c2410iis_driver
            所以:两者匹配,调用driver重载的probe函数:s3c2410iis_probe
            
            s3c2410iis_probe(struct device *dev)
                // 获取平台设备: pdev = s3c_device_iis
                struct platform_device *pdev = to_platform_device(dev);
                res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
                
                // 使能IIS模块时钟
                iis_clock = clk_get(dev, "iis");
                clk_enable(iis_clock);
                
                // 配置GPIO管脚为IIS模块
                s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);
                s3c2410_gpio_pullup(S3C2410_GPB4,1);
                ......
                
                // 初始化IIS bus
                init_s3c2410_iis_bus
                // 初始化uda1341模块的控制器寄存器
                init_uda1341(void)
                
                /* 设置两个DMA通道:一个用于播放,另一个用于录音 */
                output_stream.dma_ch = DMACH_I2S_OUT;
                audio_init_dma(&output_stream, "UDA1341 out")
                input_stream.dma_ch = DMACH_I2S_IN;
                audio_init_dma(&input_stream, "UDA1341 in")
                
                
                audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);
                    sound_insert_unit(&chains[3], fops, dev, 3, 131, "dsp", S_IWUSR | S_IRUSR, NULL);
                        struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);
                        sprintf(s->name, "sound/%s", name);
                        sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);
                        // 注册类sound_class下的设备: "/dev/dsp"
                        device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), s->name+6);
                        
                audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);
                    sound_insert_unit(&chains[0], fops, dev, 0, 128, "mixer", S_IRUSR | S_IWUSR, NULL);
                        struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);
                        sprintf(s->name, "sound/%s", name);
                        sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);
                        // 注册类sound_class下的设备: "/dev/mixer"
                        device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), s->name+6);    
                说明:
                    /dev/dsp:   用于播放/录音
                    /dev/mixer: 调整音量
                        
        static struct file_operations smdk2410_audio_fops = {
            llseek: smdk2410_audio_llseek,
            write:  smdk2410_audio_write,
            read:   smdk2410_audio_read,
            poll:   smdk2410_audio_poll,
            ioctl:  smdk2410_audio_ioctl,
            open:   smdk2410_audio_open,
            release: smdk2410_audio_release
        };

        static struct file_operations smdk2410_mixer_fops = {
            ioctl: smdk2410_mixer_ioctl,
            open: smdk2410_mixer_open,
            release: smdk2410_mixer_release
        };
        
        
        问题: 类sound_class是在什么时候注册的?
        回答: 参考声卡的核心程序:\sound\sound_core.c
        static int __init init_soundcore(void)
            // 内核启动,初始化注册声卡字符设备,主设备号为:SOUND_MAJOR = 14; 对应的file_operations = soundcore_fops
            register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops)
            // 创建一个类: sound_class
            sound_class = class_create(THIS_MODULE, "sound");
        
        static const struct file_operations soundcore_fops=
        {
            /* We must have an owner or the module locking fails */
            .owner    = THIS_MODULE,
            .open    = soundcore_open,
        };
        看到soundcore_fops结构体,只定义了:open函数,就知道肯定在open函数中,"移花接木"了。
        soundcore_open(struct inode *inode, struct file *file)
            int unit = iminor(inode);
            const struct file_operations *new_fops = NULL;
            s = __look_for_unit(chain, unit);
            new_fops = fops_get(s->unit_fops);                     // 找到真正的设备的open函数
            const struct file_operations *old_fops = file->f_op;
            file->f_op = new_fops;                                 // 获取真正的open函数
            file->f_op->open(inode,file);  // 真正的open函数,"移花接木"

            
    2. 应用程序相关操作:
    
        app: open () // 假设主设备号为14
        -------------------------------------------
                  soundcore_open
                        int unit = iminor(inode);
                        s = __look_for_unit(chain, unit);
                                // 从chains数组里得到, 谁来设置这个数组? 肯定是注册相关的字符设备的时候,添加进数组的。
                                
                        new_fops = fops_get(s->unit_fops);
                        file->f_op = new_fops;
                        err = file->f_op->open(inode,file);
                        

        录音:
        app:   read
        ----------------------
                    file->f_op->read

        播放:
        app:  write
        -------------------------
                file->f_op->write


    3. 驱动程序的编写:        
    
    对于声卡为:uda1341的开发板:
    ---------------------------
        测试:
        1. 确定内核里已经配置了sound\soc\s3c24xx\s3c2410-uda1341.c
        -> Device Drivers
          -> Sound
            -> Advanced Linux Sound Architecture
              -> Advanced Linux Sound Architecture
                -> System on Chip audio support
                <*> I2S of the Samsung S3C24XX chips

        2. make uImage
           使用新内核启动

        3. ls -l /dev/dsp /dev/mixer
        4. 播放:
           在WINDOWS PC里找一个wav文件,放到开发板根文件系统里
           cat Windows.wav > /dev/dsp
        5. 录音:
           cat /dev/dsp > sound.bin  
           然后对着麦克风说话
           ctrl+c退出
           cat sound.bin > /dev/dsp  // 就可以听到录下的声音


                
    对于声卡为:WM8976的开发板:
    ---------------------------
        怎么写WM8976驱动程序?
        1. IIS部分一样,保持不变
        2. 控制部分不同,重写

        测试WM8976:
        1. 确定内核里已经配置了sound\soc\s3c24xx\s3c2410-uda1341.c
        -> Device Drivers
          -> Sound
            -> Advanced Linux Sound Architecture  // 兼容OSS
              -> Advanced Linux Sound Architecture
                -> System on Chip audio support
                <*> I2S of the Samsung S3C24XX chips
        2. 修改sound/soc/s3c24xx/Makefile
        obj-y += s3c2410-uda1341.o
        改为:
        obj-y += s3c-wm8976.o   

        3. make uImage
           使用新内核启动

        4. ls -l /dev/dsp /dev/mixer
        5. 播放:
           在WINDOWS PC里找一个wav文件,放到开发板根文件系统里
           cat Windows.wav > /dev/dsp
        6. 录音:
           cat /dev/dsp > sound.bin  
           然后对着麦克风说话
           ctrl+c退出
           cat sound.bin > /dev/dsp  // 就可以听到录下的声音   
   
   

        
    4. 使用madplay测试声卡:
        1. 解压:
        tar xzf libid3tag-0.15.1b.tar.gz  // 库
        tar xzf libmad-0.15.1b.tar.gz     // 库
        tar xzf madplay-0.15.2b.tar.gz    // APP

        2. 编译 libid3tag-0.15.1b
        mkdir tmp
        cd libid3tag-0.15.1b
        ./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp
        make
        make install

        3. 编译 libmad-0.15.1b
        cd libmad-0.15.1b
        ./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp
        make
        make install

        4. 编译madplay
        cd madplay-0.15.2b/
        ./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp LDFLAGS="-L/work/drivers_and_test/21th_sound/app/tmp/lib" CFLAGS="-I /work/drivers_and_test/21th_sound/app/tmp/include"
        make
        make install

        5. 把tmp/bin/*  tmp/lib/*so* 复制到根文件系统:

        6. 把一个mp3文件复制到根文件系统

        7. madplay --tty-control /1.mp3
           播放过程中不断按小键盘的减号("-")会降低音量
                     不断按小键盘的加号("+")会降低音量
阅读(1303) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~