• 6410 spi 设备的驱动


     
     
    主题 : linux 2.6.36+s3c6410 SPI子系统接口讨论(刚刚创建设备成功,与大家分享经验,欢迎讨论) 复制链接 | 浏览器收藏 | 打印
     
    级别: 新手上路
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
     
    UID: 36931
    精华: 1
    发帖: 22
    金钱: 160 两
    威望: 32 点
    贡献值: 1 点
    综合积分: 64 分
    注册时间: 2011-01-24
    最后登录: 2012-08-02
    楼主  发表于: 2011-03-19 21:31
     

     linux 2.6.36+s3c6410 SPI子系统接口讨论(刚刚创建设备成功,与大家分享经验,欢迎讨论)

     
     
    管理提醒: 本帖被 xoom 执行加亮操作(2011-06-19)
    下图是安装成功后,sys目录下的主要结构,由于目录非常复杂仅仅列出了主要的结构
    sys目录下spi子系统结构


    接下来将从各struct开始进行分析,struct是构成内核对象的基础,函数是动态的构建和执行的工具。所以梳理脉络就从结构开始。

    linux下的设备模型包括几个主要的概念
    sysfs (dev是用户空间接口,根据sysfs下的class目录由mdev负责建立)
    bus总线,linux下的设备都是建立在总线上的,platform总线是一个虚拟的总线,所有的的片上设备基本上都接在这个虚拟总线上
    device是设备
    device_driver是设备驱动
    class是类别,从功能角度对设备进行分类

    注意,在sys/bus目录下有platform目录和spi目录
    这两个目录下面的设备分别代表什么呢?
    platform下的设备有s3c64xx-spi0和s3c64xx-spi1分别对应了s3c6410上的spi0和spi1接口





    一、先说说platform
    platform.txt是需要阅读的参考文档
    platform子系统是linux对不同的架构下设备的抽象归纳,即所有片上的设备都会放在这个子目录下
    我们先看一下platform相关的struct

    struct platform_device {
        const char    * name;
        int        id;
        struct device    dev;
        u32        num_resources;
        struct resource    * resource;

        const struct platform_device_id    *id_entry;

        /* arch specific additions */
        struct pdev_archdata    archdata;
    };

    platform_device从字面理解就是平台设备,注意它内含了一个名为dev的device结构,这有点像C++的类继承的关系,linux内核大量利用了这种类似继承的结构实现了C语言下的面向对象编程。
    后面谈到的SPI的设备继承了platform_device的结构。    另外,还包含了一个resource    结构的指针,注意和dev的区别。也就是说资源是需要另外定义然后,将对象指针赋予这里的结构指针。资源包含IOMEM和IRQ。


    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 (*resume)(struct platform_device *);
        struct device_driver driver;
        const struct platform_device_id *id_table;
    };

    platform_driver结构就不多说了,主要是用来说明platform驱动的结构。

    下面看看对象实例s3c64xx-spi.0的定义
    static struct resource s3c64xx_spi0_resource[] = {
        [0] = {
            .start = S3C64XX_PA_SPI0,
            .end   = S3C64XX_PA_SPI0 + 0x100 - 1,
            .flags = IORESOURCE_MEM,
        },
        [1] = {
            .start = DMACH_SPI0_TX,
            .end   = DMACH_SPI0_TX,
            .flags = IORESOURCE_DMA,
        },
        [2] = {
            .start = DMACH_SPI0_RX,
            .end   = DMACH_SPI0_RX,
            .flags = IORESOURCE_DMA,
        },
        [3] = {
            .start = IRQ_SPI0,
            .end   = IRQ_SPI0,
            .flags = IORESOURCE_IRQ,
        },
    };

    static struct s3c64xx_spi_info s3c64xx_spi0_pdata = {
        .cfg_gpio = s3c64xx_spi_cfg_gpio,
        .fifo_lvl_mask = 0x7f,
        .rx_lvl_offset = 13,
    };

    static u64 spi_dmamask = DMA_BIT_MASK(32);

    struct platform_device s3c64xx_device_spi0 = {
        .name          = "s3c64xx-spi",
        .id          = 0,
        .num_resources      = ARRAY_SIZE(s3c64xx_spi0_resource),
        .resource      = s3c64xx_spi0_resource,
        .dev = {
            .dma_mask        = &spi_dmamask,
            .coherent_dma_mask    = DMA_BIT_MASK(32),
            .platform_data = &s3c64xx_spi0_pdata,
        },
    };
    从上面的定义可以看出,s3c64xx_device_spi0是s3c64xx-spi.0的定义,系统根据这个变量实现了s3c64xx-spi.0,而它的类型是platform_device,也就是说这是一个平台设备。s3c64xx-spi.1就不说了同理。

    下面是驱动s3c64xx_spi_driver 的定义,系统根据这个定义实现了平台驱动s3c64xx-spi
    static struct platform_driver s3c64xx_spi_driver = {
        .driver = {
            .name    = "s3c64xx-spi",
            .owner = THIS_MODULE,
        },
        .remove = s3c64xx_spi_remove,
        .suspend = s3c64xx_spi_suspend,
        .resume = s3c64xx_spi_resume,
    };
    注意驱动的定义中含有很多的函数指针,这些函数是用来控制设备工作的标准动作。具体功能这里就不解释了,各位看源码。

    各位看到这里可能要问了那s3c64xx-spi.0下的spi0.0是在哪里实现的?它的类结构是什么?
    首先,我们得明白s3c64xx-spi.0代表了s3c6410下的spi0接口,那么spi0.0代表什么呢?注意spi0.0有两个数字,第一个0代表spi0接口,第2个0呢?它代表这spi0总线上的片外设备,这意味着一个spi0总线可以接多个片外设备,通过cs进行片选。属于分时复用,那么spi0.0的定义在什么地方呢?首先,它和开发板的定义高度相关,也就是说它不会存在于内核代码,也不会存在于芯片厂商三星公司提供的s3c64xx系列代码中,那么它应该在什么地方呢?答案是它应该在开发板的初始化代码中,即在友善之臂的mach_mini6410.c中,很遗憾友善之臂暂时没有支持spi,spi的相关初始化代码需要我们自行定义,如何定义这里暂且不说,先说说这个设备的类型定义在哪里?

    下面就是片外设备的定义,s3c64xx_spi_csinfo是片外设备需要的片选信号的定义,这个片选信号的定义是必须的,很多网上的帖子并没有介绍到,这也是我之前很郁闷的地方一直无法成功的加载设备。
    /**
    * struct s3c64xx_spi_csinfo - ChipSelect description
    * @fb_delay: Slave specific feedback delay.
    *            Refer to FB_CLK_SEL register definition in SPI chapter.
    * @line: Custom 'identity' of the CS line.
    * @set_level: CS line control.
    *
    * This is per SPI-Slave Chipselect information.
    * Allocate and initialize one in machine init code and make the
    * spi_board_info.controller_data point to it.
    */
    struct s3c64xx_spi_csinfo {
        u8 fb_delay;
        unsigned line;
        void (*set_level)(unsigned line_id, int lvl);
    };

    /*
    * INTERFACE between board init code and SPI infrastructure.
    *
    * No SPI driver ever sees these SPI device table segments, but
    * it's how the SPI core (or adapters that get hotplugged) grows
    * the driver model tree.
    *
    * As a rule, SPI devices can't be probed.  Instead, board init code
    * provides a table listing the devices which are present, with enough
    * information to bind and set up the device's driver.  There's basic
    * support for nonstatic configurations too; enough to handle adding
    * parport adapters, or microcontrollers acting as USB-to-SPI bridges.
    */

    /**
    * struct spi_board_info - board-specific template for a SPI device
    * @modalias: Initializes spi_device.modalias; identifies the driver.
    * @platform_data: Initializes spi_device.platform_data; the particular
    *    data stored there is driver-specific.
    * @controller_data: Initializes spi_device.controller_data; some
    *    controllers need hints about hardware setup, e.g. for DMA.
    * @irq: Initializes spi_device.irq; depends on how the board is wired.
    * @max_speed_hz: Initializes spi_device.max_speed_hz; based on limits
    *    from the chip datasheet and board-specific signal quality issues.
    * @bus_num: Identifies which spi_master parents the spi_device; unused
    *    by spi_new_device(), and otherwise depends on board wiring.
    * @chip_select: Initializes spi_device.chip_select; depends on how
    *    the board is wired.
    * @mode: Initializes spi_device.mode; based on the chip datasheet, board
    *    wiring (some devices support both 3WIRE and standard modes), and
    *    possibly presence of an inverter in the chipselect path.
    *
    * When adding new SPI devices to the device tree, these structures serve
    * as a partial device template.  They hold information which can't always
    * be determined by drivers.  Information that probe() can establish (such
    * as the default transfer wordsize) is not included here.
    *
    * These structures are used in two places.  Their primary role is to
    * be stored in tables of board-specific device descriptors, which are
    * declared early in board initialization and then used (much later) to
    * populate a controller's device tree after the that controller's driver
    * initializes.  A secondary (and atypical) role is as a parameter to
    * spi_new_device() call, which happens after those controller drivers
    * are active in some dynamic board configuration models.
    */
    struct spi_board_info {
        /* the device name and module name are coupled, like platform_bus;
         * "modalias" is normally the driver name.
         *
         * platform_data goes to spi_device.dev.platform_data,
         * controller_data goes to spi_device.controller_data,
         * irq is copied too
         */
        char        modalias[SPI_NAME_SIZE];
        const void    *platform_data;
        void        *controller_data;
        int        irq;

        /* slower signaling on noisy or low voltage boards */
        u32        max_speed_hz;


        /* bus_num is board specific and matches the bus_num of some
         * spi_master that will probably be registered later.
         *
         * chip_select reflects how this chip is wired to that master;
         * it's less than num_chipselect.
         */
        u16        bus_num;
        u16        chip_select;

        /* mode becomes spi_device.mode, and is essential for chips
         * where the default of SPI_CS_HIGH = 0 is wrong.
         */
        u8        mode;

        /* ... may need additional spi_device chip config data here.
         * avoid stuff protocol drivers can set; but include stuff
         * needed to behave without being bound to a driver:
         *  - quirks like clock rate mattering when not selected
         */
    };

    上面的介绍已经将平台下的各设备及驱动的定义讲清楚了

    二、接下来介绍SPI目录下的各对象的定义。
    spi-summary.txt是必须阅读的参考文档
    从spi目录可以看出这是与平台无关的,也就是linux对spi接口进行抽象,形成的用户层相关代码,而platform下面的代码是具体进行工作的代码,它们与平台相关,而spi代码是与用户空间接口相关的代码它必须与平台无关,即无论平台如何更换,对用户空间的接口都是一致的,这就是linux设备驱动架构的精髓。
    我们注意到spi目录下只有一个spidev的驱动,其他都是指向platform下的设备链接,这里先不说整个spi子系统的运作机制,先搞清楚spi子系统的静态结构
    spidev是由spidev_spi_driver 定义的,它的结构类型是spi_driver
    static struct spi_driver spidev_spi_driver = {
        .driver = {
            .name =        "spidev",
            .owner =    THIS_MODULE,
        },
        .probe =    spidev_probe,
        .remove =    __devexit_p(spidev_remove),

        /* NOTE:  suspend/resume methods are not necessary here.
         * We don't do anything except pass the requests to/from
         * the underlying controller.  The refrigerator handles
         * most issues; the controller driver handles the rest.
         */
    };

    spi_driver 的定义如下:

    /**
    * struct spi_driver - Host side "protocol" driver
    * @id_table: List of SPI devices supported by this driver
    * @probe: Binds this driver to the spi device.  Drivers can verify
    *    that the device is actually present, and may need to configure
    *    characteristics (such as bits_per_word) which weren't needed for
    *    the initial configuration done during system setup.
    * @remove: Unbinds this driver from the spi device
    * @shutdown: Standard shutdown callback used during system state
    *    transitions such as powerdown/halt and kexec
    * @suspend: Standard suspend callback used during system state transitions
    * @resume: Standard resume callback used during system state transitions
    * @driver: SPI device drivers should initialize the name and owner
    *    field of this structure.
    *
    * This represents the kind of device driver that uses SPI messages to
    * interact with the hardware at the other end of a SPI link.  It's called
    * a "protocol" driver because it works through messages rather than talking
    * directly to SPI hardware (which is what the underlying SPI controller
    * driver does to pass those messages).  These protocols are defined in the
    * specification for the device(s) supported by the driver.
    *
    * As a rule, those device protocols represent the lowest level interface
    * supported by a driver, and it will support upper level interfaces too.
    * Examples of such upper levels include frameworks like MTD, networking,
    * MMC, RTC, filesystem character device nodes, and hardware monitoring.
    */
    struct spi_driver {
        const struct spi_device_id *id_table;
        int            (*probe)(struct spi_device *spi);
        int            (*remove)(struct spi_device *spi);
        void            (*shutdown)(struct spi_device *spi);
        int            (*suspend)(struct spi_device *spi, pm_message_t mesg);
        int            (*resume)(struct spi_device *spi);
        struct device_driver    driver;
    };

    static inline struct spi_driver *to_spi_driver(struct device_driver *drv)
    {
        return drv ? container_of(drv, struct spi_driver, driver) : NULL;
    }

    extern int spi_register_driver(struct spi_driver *sdrv);

    /**
    * spi_unregister_driver - reverse effect of spi_register_driver
    * @sdrv: the driver to unregister
    * Context: can sleep
    */
    static inline void spi_unregister_driver(struct spi_driver *sdrv)
    {
        if (sdrv)
            driver_unregister(&sdrv->driver);
    }


    三、下面介绍class目录下的对象定义结构
    spi_master的定义如下

    static struct class spi_master_class = {
        .name        = "spi_master",
        .owner        = THIS_MODULE,
        .dev_release    = spi_master_release,
    };

    spidev的类定义如下:
    /*-------------------------------------------------------------------------*/

    /* The main reason to have this class is to make mdev/udev create the
    * /dev/spidevB.C character device nodes exposing our userspace API.
    * It also simplifies memory management.
    */

    static struct class *spidev_class;

    文章写到这里,spi子系统主要的设备、驱动、类定义的结构已经介绍清楚了,即静态的结构大家已经明白了。
    下面我们开始介绍SPI子系统的初始化过程,这也是我和大伙都很头痛的地方,SPI子系统是如何初始化的?

    四、SPI子系统的初始化过程

    先说一下和SPI子系统初始化相关的主要代码
    spi.c是spi子系统初始化的核心代码,由内核负责初始化
    spidev.c是spi用户接口初始化的代码,编译的时候需要选择该模块
    spi_s3c64xx.c是平台驱动的初始化代码,编译时需要选择spi s3c64xx模块
    mach-mini6410.c是开发板初始化的代码

    上述核心设备、驱动、类的初始化过程就是在上述代码中实现的,它们分别负责那些对象的初始化过程,次序是什么,需要各位思考一下,我也没有完全搞清楚。

    先说说spi.c中的初始化代码

    static int __init spi_init(void)
    {
        int    status;

        buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
        if (!buf) {
            status = -ENOMEM;
            goto err0;
        }

        status = bus_register(&spi_bus_type);
        if (status < 0)
            goto err1;

        status = class_register(&spi_master_class);
        if (status < 0)
            goto err2;
        return 0;

    err2:
        bus_unregister(&spi_bus_type);
    err1:
        kfree(buf);
        buf = NULL;
    err0:
        return status;
    }

    从这段代码,我们可以看出,系统注册及初始化了总线spi和类spi_master

    总线spi的定义代码前面没有列出,这里补充如下:
    struct bus_type spi_bus_type = {
        .name        = "spi",
        .dev_attrs    = spi_dev_attrs,
        .match        = spi_match_device,
        .uevent        = spi_uevent,
        .suspend    = spi_suspend,
        .resume        = spi_resume,
    };
    这个初始化过程是内核在初始化过程中,调用spi_init(void)函数执行的,由宏postcore_initcall(spi_init);加入到启动代码。

    platform总线的初始化代码由platform.c中的函数platform_bus_init执行
    int __init platform_bus_init(void)
    {
        int error;

        early_platform_cleanup();

        error = device_register(&platform_bus);
        if (error)
            return error;
        error =  bus_register(&platform_bus_type);
        if (error)
            device_unregister(&platform_bus);
        return error;
    }

    下面是用户空间接口的初始化代码,从代码可以看出,初始化了字符设备spi
    注册&初始化类spidev
    注册&初始化驱动spidev

    static int __init spidev_init(void)
    {
        int status;

        /* Claim our 256 reserved device numbers.  Then register a class
         * that will key udev/mdev to add/remove /dev nodes.  Last, register
         * the driver which manages those device numbers.
         */
        BUILD_BUG_ON(N_SPI_MINORS > 256);
        status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
        if (status < 0)
            return status;

        spidev_class = class_create(THIS_MODULE, "spidev");
        if (IS_ERR(spidev_class)) {
            unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
            return PTR_ERR(spidev_class);
        }

        status = spi_register_driver(&spidev_spi_driver);
        if (status < 0) {
            class_destroy(spidev_class);
            unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
        }
        return status;
    }
    module_init(spidev_init);

    下面平台驱动的初始化过程
    static int __init s3c64xx_spi_init(void)
    {
        return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
    }

    int __init_or_module platform_driver_probe(struct platform_driver *drv,
            int (*probe)(struct platform_device *))
    {
        int retval, code;

        /* make sure driver won't have bind/unbind attributes */
        drv->driver.suppress_bind_attrs = true;

        /* temporary section violation during probe() */
        drv->probe = probe;
        retval = code = platform_driver_register(drv);

        /*
         * Fixup that section violation, being paranoid about code scanning
         * the list of drivers in order to probe new devices.  Check to see
         * if the probe was successful, and make sure any forced probes of
         * new devices fail.
         */
        spin_lock(&platform_bus_type.p->klist_drivers.k_lock);
        drv->probe = NULL;
        if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list))
            retval = -ENODEV;
        drv->driver.probe = platform_drv_probe_fail;
        spin_unlock(&platform_bus_type.p->klist_drivers.k_lock);

        if (code != retval)
            platform_driver_unregister(drv);
        return retval;
    }

    上面这段代码注册了平台SPI驱动s3c64xx-spi

    看到这里,我们可以发现还有什么活没有干?
    s3c64xx-spi.0和s3c64xx-spi.1设备没有建立,它们下面的spi0.0和spi1.0设备也没有建立。
    spidev类下面的spidev0.0和spidev1.0也没有建立
    spi_master类下面的spi0和spi1也没有建立
    这些都是在什么地方建立的?分析一下这些都与具体的开发板有关系,也就是说这部分代码必须由开发板的初始化代码来实现,这里必须由mach-mini6410.c负责实现。

    mach-mini6410.c的初始化函数是
    static void __init mini6410_machine_init(void)
    也就是说上述工作必须在这个函数中实现。

    这里到了本文的高潮部分了,也是很多兄弟姐妹们迫切希望直接拷贝然后加入到自己的代码中去的地方。但是请注意,我也会犯错,这里的代码我也许说的并不对,各位看官如果发现了错误请指出及批判。
    我们先分析s3c64xx-spi.0和s3c64xx-spi.1是在什么地方定义的,因为它与平台架构相关,所以我们到arch/arm/mach-s3c64xx目录中找到了dev-spi.c,这个代码中定义了s3c64xx_device_spi0和s3c64xx_device_spi1也就是设备s3c64xx-spi.0和s3c64xx-spi.1的实现代码,所以dev-spi.c必须加入到编译中,需要修改相关的编译配置文件。
    然后,我们在分析mini6410的初始化代码,注意代码中的这个函数
        platform_add_devices(mini6410_devices, ARRAY_SIZE(mini6410_devices));
    可以看出该函数是批量初始化了一批设备,所以将
        &s3c64xx_device_spi0,
        &s3c64xx_device_spi1,
    加入到mini6410_devices结构中,就可以将设备s3c64xx-spi.0和s3c64xx-spi.1进行初始化,
    但是事情没有做完,s3c64xx_device_spi0和s3c64xx_device_spi1结构中的片选数量和时钟并未定义,需要根据开发板的具体情况进行定义,
    函数原型  void __init s3c64xx_spi_set_info(int cntrlr, int src_clk_nr, int num_cs)

    这里具体的执行代码如下:
        s3c64xx_spi_set_info(0,0,1);
        s3c64xx_spi_set_info(1,0,1);
    到这里各位已经可以在平台目录中看见s3c64xx_device_spi0和s3c64xx_device_spi1,在类spi_master目录下看见spi0和spi1了

    下面介绍如何增加spi0.0和spi1.0设备?

    各位请仔细分析在spi.h中的这段代码
    /*---------------------------------------------------------------------------*/

    /*
    * INTERFACE between board init code and SPI infrastructure.
    *
    * No SPI driver ever sees these SPI device table segments, but
    * it's how the SPI core (or adapters that get hotplugged) grows
    * the driver model tree.
    *
    * As a rule, SPI devices can't be probed.  Instead, board init code
    * provides a table listing the devices which are present, with enough
    * information to bind and set up the device's driver.  There's basic
    * support for nonstatic configurations too; enough to handle adding
    * parport adapters, or microcontrollers acting as USB-to-SPI bridges.
    */

    /**
    * struct spi_board_info - board-specific template for a SPI device
    * @modalias: Initializes spi_device.modalias; identifies the driver.
    * @platform_data: Initializes spi_device.platform_data; the particular
    *    data stored there is driver-specific.
    * @controller_data: Initializes spi_device.controller_data; some
    *    controllers need hints about hardware setup, e.g. for DMA.
    * @irq: Initializes spi_device.irq; depends on how the board is wired.
    * @max_speed_hz: Initializes spi_device.max_speed_hz; based on limits
    *    from the chip datasheet and board-specific signal quality issues.
    * @bus_num: Identifies which spi_master parents the spi_device; unused
    *    by spi_new_device(), and otherwise depends on board wiring.
    * @chip_select: Initializes spi_device.chip_select; depends on how
    *    the board is wired.
    * @mode: Initializes spi_device.mode; based on the chip datasheet, board
    *    wiring (some devices support both 3WIRE and standard modes), and
    *    possibly presence of an inverter in the chipselect path.
    *
    * When adding new SPI devices to the device tree, these structures serve
    * as a partial device template.  They hold information which can't always
    * be determined by drivers.  Information that probe() can establish (such
    * as the default transfer wordsize) is not included here.
    *
    * These structures are used in two places.  Their primary role is to
    * be stored in tables of board-specific device descriptors, which are
    * declared early in board initialization and then used (much later) to
    * populate a controller's device tree after the that controller's driver
    * initializes.  A secondary (and atypical) role is as a parameter to
    * spi_new_device() call, which happens after those controller drivers
    * are active in some dynamic board configuration models.
    */
    struct spi_board_info {
        /* the device name and module name are coupled, like platform_bus;
         * "modalias" is normally the driver name.
         *
         * platform_data goes to spi_device.dev.platform_data,
         * controller_data goes to spi_device.controller_data,
         * irq is copied too
         */
        char        modalias[SPI_NAME_SIZE];
        const void    *platform_data;
        void        *controller_data;
        int        irq;

        /* slower signaling on noisy or low voltage boards */
        u32        max_speed_hz;


        /* bus_num is board specific and matches the bus_num of some
         * spi_master that will probably be registered later.
         *
         * chip_select reflects how this chip is wired to that master;
         * it's less than num_chipselect.
         */
        u16        bus_num;
        u16        chip_select;

        /* mode becomes spi_device.mode, and is essential for chips
         * where the default of SPI_CS_HIGH = 0 is wrong.
         */
        u8        mode;

        /* ... may need additional spi_device chip config data here.
         * avoid stuff protocol drivers can set; but include stuff
         * needed to behave without being bound to a driver:
         *  - quirks like clock rate mattering when not selected
         */
    };

    这段代码中,spi_board_info 是spi总线上从设备的定义由于和硬件的具体种类有非常大的关系,所以需要针对具体的设备类型进行定义,这里先给一个例子

    先在mach-mini6410.c中增加如下代码

    //zhuyong add start

    static void  cs_set_level(unsigned line_id, int lvl) {
        gpio_direction_output(line_id, lvl);
    };

    static struct s3c64xx_spi_csinfo s3c64xx_spi0_csinfo = {
      .fb_delay=100,
      .line=S3C64XX_GPC(3),
      .set_level=cs_set_level,
    };

    static struct spi_board_info s3c6410_spi0_board[] = {
    [0] = {
    .modalias = "spidev",
    .bus_num= 0,
    .chip_select= 0, //必须小于s3c6410_spi0_platdata.num_cs
    .irq =IRQ_SPI0,
    .max_speed_hz= 500*1000,
    .mode=SPI_MODE_0,
    .controller_data=&s3c64xx_spi0_csinfo,
    },
    };

    static struct s3c64xx_spi_csinfo s3c64xx_spi1_csinfo = {
      .fb_delay=100,
      .line=S3C64XX_GPC(7),
      .set_level=cs_set_level,
    };

    static struct spi_board_info s3c6410_spi1_board[] = {
    [0] = {
    .modalias = "spidev",
    .bus_num= 1,//代表使用芯片的第二个spi模块
    .chip_select= 0, //必须小于s3c6410_spi1_platdata.num_cs
    .irq = IRQ_SPI1,
    .max_speed_hz = 500*1000,
    .mode=SPI_MODE_0,
    .controller_data=&s3c64xx_spi1_csinfo,
    },
    };

    //zhuyong add end

    为什么要定义s3c64xx_spi0_csinfo?这是spi从设备必须的片选信号的定义,片选信号中有一个关键的函数set_level需要各位自己进行定义,这个函数由系统在打开设备时调用,即设置该SPI从设备的片选信号,一开始由于没有设置该设备的片选信号,导致spi0.0和spi1.0一直加载不成功。

    需要注意是,这里仅仅只是给出了一个例子,s3c64xx_spi0_info中还有很多具体的设置需要在该结构中进行定义。

    最后在函数 mini6410_machine_init中,增加如下代码
        spi_register_board_info(s3c6410_spi0_board, ARRAY_SIZE(s3c6410_spi0_board));
        spi_register_board_info(s3c6410_spi1_board, ARRAY_SIZE(s3c6410_spi1_board));
    这段代码必须加在
       s3c64xx_spi_set_info(0,0,1);
        s3c64xx_spi_set_info(1,0,1);
    之后,至于为什么请各位自己思考,到这里所有的spi设备都已经初始化结束,在目录中已经可以看见本文开头的文件结构了。

    下面就有两个议题了,
    spi用户空间接口如何使用?
    spi子系统是如何运行的?




    在黄埔江畔的星巴克写完这篇文章,已经是华灯初上了,对岸的外滩建筑美丽而宁静,这篇文章也用来纪念前段时间死去的成千上万的脑细胞。希望还能挽救各位兄弟姐妹的脑细胞。

    email:alexis93@163.com
    如果本文中有错误,请各位及时指出。

    待续
    [ 此帖被zhuyong74在2011-03-20 20:11重新编辑 ]
     
     
    顶端
    更多
     
    级别: 新手上路
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
     
    UID: 38056
    精华: 0
    发帖: 16
    金钱: 80 两
    威望: 16 点
    贡献值: 0 点
    综合积分: 32 分
    注册时间: 2011-02-21
    最后登录: 2012-02-13
    1楼  发表于: 2011-03-20 09:56
     
     
     
    高手。一出手就知道是高手。
    期待下文。
     
     
     
    级别: 新手上路
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
     
    UID: 19802
    精华: 0
    发帖: 41
    金钱: 210 两
    威望: 42 点
    贡献值: 0 点
    综合积分: 82 分
    注册时间: 2010-04-23
    最后登录: 2011-11-08
    2楼  发表于: 2011-03-21 12:18
     
     
     
    期待下文
     
     
     
    级别: 新手上路
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
     
    UID: 38056
    精华: 0
    发帖: 16
    金钱: 80 两
    威望: 16 点
    贡献值: 0 点
    综合积分: 32 分
    注册时间: 2011-02-21
    最后登录: 2012-02-13
    3楼  发表于: 2011-03-21 12:39
     
     
     
    下午要出去。浏览了下总体文章。
    发现可以细写的地方很多。

    我最近在研究linux的sdio接口,和你的spi接口差不多。比如在arch\arm\plat-samsung目录下,会有几个文件dev-hsmmc.c,dev-hsmmc1.c,dev-hsmmc2.c,dev-hsmmc3.c,就是分别定义mmc接口实体的文件。
    有时候觉得这帮写代码的家伙也懒惰。这几个文件大同小异,干嘛不整合到一个文件中去。

    对mach-mini6410.c的分析,也比较彻底。友善之臂已经定义了mini6410_devices数组,你的spi设备可以直接加在这个数组的尾部。保持统一。
    *************

    回来再细读。有很多地方,我要学习。
     
     
     
    级别: 新手上路
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
     
    UID: 36931
    精华: 1
    发帖: 22
    金钱: 160 两
    威望: 32 点
    贡献值: 1 点
    综合积分: 64 分
    注册时间: 2011-01-24
    最后登录: 2012-08-02
    4楼  发表于: 2011-03-21 14:26
     

     回 3楼(newer2k) 的帖子

     
     
    注意到platform_add_devices这个函数是用来批量增加平台设备的,而s3c6410_spi1_board数组定义的是平台以外的板上设备,这个和平台设备的注册函数所提供的功能,所以在未研究清楚platform_add_devices之前不敢使用该机制,如果你很清楚该机制,请回帖说明。
     
     
     
    级别: 新手上路
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
     
    UID: 36931
    精华: 1
    发帖: 22
    金钱: 160 两
    威望: 32 点
    贡献值: 1 点
    综合积分: 64 分
    注册时间: 2011-01-24
    最后登录: 2012-08-02
    5楼  发表于: 2011-03-21 15:08
     

     Re:linux 2.6.36+s3c6410 SPI子系统接口讨论---用户空间接口

     
     
    说到用户空间接口就不得不提到cdev结构,这个是字符设备的结构
    struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;
        struct list_head list;
        dev_t dev;
        unsigned int count;
    };
    我们分析一下这个结构,包含了kobject 对象,file_operations 函数结构,kobject 我们都知道这是linux文件系统结构中最重要的结构,用来表示文件对象,这里就不细说了,而file_operations 都包含了什么呢?
    /*
    * NOTE:
    * all file operations except setlease can be called without
    * the big kernel lock held in all filesystems.
    */
    struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
    };
    上面是file_operations 的结构,也就是文件可以操作的API

    再回过来看看spidev中的初始化函数中的cdev的注册函数
    status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
    这里注册了spi设备,spidev_fops的定义如下:
    static const struct file_operations spidev_fops = {
        .owner =    THIS_MODULE,
        /* REVISIT switch to aio primitives, so that userspace
         * gets more complete API coverage.  It'll simplify things
         * too, except for the locking.
         */
        .write =    spidev_write,
        .read =        spidev_read,
        .unlocked_ioctl = spidev_ioctl,
        .open =        spidev_open,
        .release =    spidev_release,
    };
    也就是说spi用户空间接口提供了,write 、read、unlocked_ioctl 、open 、release 等五个cdev设备的标准操作。
    看到这里大家应该知道该如何操作spi设备了吧。
    spidev0.0和spidev1.0都是挂接到spidev字符设备下子设备,它们共用一套设备操作函数。
    这里要重点分析的是spidev_ioctl函数,它用来设置spi设备的状态等等操作。
    这个函数中以下这一段代码
    switch (cmd) {
        /* read requests */
        case SPI_IOC_RD_MODE:
            retval = __put_user(spi->mode & SPI_MODE_MASK,
                        (__u8 __user *)arg);
            break;
        case SPI_IOC_RD_LSB_FIRST:
            retval = __put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,
                        (__u8 __user *)arg);
            break;
        case SPI_IOC_RD_BITS_PER_WORD:
            retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);
            break;
        case SPI_IOC_RD_MAX_SPEED_HZ:
            retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg);
            break;

        /* write requests */
        case SPI_IOC_WR_MODE:
            retval = __get_user(tmp, (u8 __user *)arg);
            if (retval == 0) {
                u8    save = spi->mode;

                if (tmp & ~SPI_MODE_MASK) {
                    retval = -EINVAL;
                    break;
                }

                tmp |= spi->mode & ~SPI_MODE_MASK;
                spi->mode = (u8)tmp;
                retval = spi_setup(spi);
                if (retval < 0)
                    spi->mode = save;
                else
                    dev_dbg(&spi->dev, "spi mode %02x\n", tmp);
            }
            break;
        case SPI_IOC_WR_LSB_FIRST:
            retval = __get_user(tmp, (__u8 __user *)arg);
            if (retval == 0) {
                u8    save = spi->mode;

                if (tmp)
                    spi->mode |= SPI_LSB_FIRST;
                else
                    spi->mode &= ~SPI_LSB_FIRST;
                retval = spi_setup(spi);
                if (retval < 0)
                    spi->mode = save;
                else
                    dev_dbg(&spi->dev, "%csb first\n",
                            tmp ? 'l' : 'm');
            }
            break;
        case SPI_IOC_WR_BITS_PER_WORD:
            retval = __get_user(tmp, (__u8 __user *)arg);
            if (retval == 0) {
                u8    save = spi->bits_per_word;

                spi->bits_per_word = tmp;
                retval = spi_setup(spi);
                if (retval < 0)
                    spi->bits_per_word = save;
                else
                    dev_dbg(&spi->dev, "%d bits per word\n", tmp);
            }
            break;
        case SPI_IOC_WR_MAX_SPEED_HZ:
            retval = __get_user(tmp, (__u32 __user *)arg);
            if (retval == 0) {
                u32    save = spi->max_speed_hz;

                spi->max_speed_hz = tmp;
                retval = spi_setup(spi);
                if (retval < 0)
                    spi->max_speed_hz = save;
                else
                    dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
            }
            break;
    可以看出它支持如下命令
    SPI_IOC_RD_MODE    设置读的模式
    SPI_IOC_RD_LSB_FIRST   设置读字的格式
    SPI_IOC_RD_BITS_PER_WORD 设置读字长
    SPI_IOC_RD_MAX_SPEED_HZ   设置读的速度

    SPI_IOC_WR_MODE    设置写的模式
    SPI_IOC_WR_LSB_FIRST   设置写的字格式
    SPI_IOC_WR_BITS_PER_WORD   设置写的字长
    SPI_IOC_WR_MAX_SPEED_HZ   设置写的速度

    mode的取值可以是如下常量的组合
    #define    SPI_CPHA    0x01            /* clock phase */
    #define    SPI_CPOL    0x02            /* clock polarity */
    #define    SPI_MODE_0    (0|0)            /* (original MicroWire) */
    #define    SPI_MODE_1    (0|SPI_CPHA)
    #define    SPI_MODE_2    (SPI_CPOL|0)
    #define    SPI_MODE_3    (SPI_CPOL|SPI_CPHA)
    #define    SPI_CS_HIGH    0x04            /* chipselect active high? */
    #define    SPI_LSB_FIRST    0x08            /* per-word bits-on-wire */
    #define    SPI_3WIRE    0x10            /* SI/SO signals shared */
    #define    SPI_LOOP    0x20            /* loopback mode */
    #define    SPI_NO_CS    0x40            /* 1 dev/bus, no chipselect */
    #define    SPI_READY    0x80            /* slave pulls low to pause */
    [ 此帖被zhuyong74在2011-03-21 16:17重新编辑 ]
     
     
     
    级别: 新手上路
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
     
    UID: 36931
    精华: 1
    发帖: 22
    金钱: 160 两
    威望: 32 点
    贡献值: 1 点
    综合积分: 64 分
    注册时间: 2011-01-24
    最后登录: 2012-08-02
    6楼  发表于: 2011-03-21 16:38
     

     Re:linux 2.6.36+s3c6410 SPI子系统接口讨论--深入SPI运行机制

     
     
    前面简要讨论了如何构建和运行SPI,使得大家对如何用SPI有了一个基本的了解,下面我们将深入SPI内部的运行机制的讨论。
    同样,我们将从系统运行时的对象开始讨论,而这又离不开对struct的分析,以及系统运行时的内核SPI对象的分析。
    我们首先从spi.h文件中定义的各个struct对象开始分析
    在这个文件中定义了如下结构
    spi_device、spi_driver、spi_master、spi_transfer、spi_message、spi_board_info
    spi_device 是SPI从设备,即连接在SPI总线上的外围设备
    spi_driver是SPI主机边的协议驱动
    spi_master是spi控制器,即s3c6410中的spi端口的软件控制器
    spi_transfer :a read/write buffer pair
    struct spi_message : one multi-segment SPI transaction

    在spidev.h中定义了如下结构
    struct spi_ioc_transfer - describes a single SPI transfer
    This structure is mapped directly to the kernel spi_transfer structure;
    从注释中可以知道这个结构是对内核中的spi_transfer 的映射

    我们再看看s3c64xx-spi.h中的定义,这里定义了
    struct s3c64xx_spi_csinfo - ChipSelect description
    struct s3c64xx_spi_info - SPI Controller defining structure

    在spidev.c中定义了
    spidev_data结构

    在spi.c中定义了
    boardinfo结构

    在spi-s3c64xx.c中定义了
    struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver.
    s3c64xx_spi_driver_data 是SPI驱动的运行时信息

    上述这些结构构成了SPI运行的基础架构,也就是spi的静态结构,但是要搞清楚spi的运行机制从用户接口的read和write开始顺藤摸瓜会比较简单
    让我们看看函数
    static ssize_t
    spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
    这个函数是spi用户接口的函数
    这个函数中status = spidev_sync_read(spidev, count);负责从spi总线上读出数据
    missing = copy_to_user(buf, spidev->buffer, status);负责将数据从内核空间拷贝到用户空间

    我们再分析spidev_sync_read函数
    static inline ssize_t
    spidev_sync_read(struct spidev_data *spidev, size_t len)
    {
        struct spi_transfer    t = {
                .rx_buf        = spidev->buffer,
                .len        = len,
            };
        struct spi_message    m;

        spi_message_init(&m);
        spi_message_add_tail(&t, &m);
        return spidev_sync(spidev, &m);
    }
    这个函数生成了一个spi_message    和一个spi_transfer    
    并把他们加入到了一个传输队列中。spi_message_init函数就不细讲了,就是初始化spi_message    的结构
    而spi_message_add_tail函数做了什么工作呢?是将spi_transfer加入到spi_message的传输任务列表
    即spi_transfer其实是一个原子的传输任务单元

    最终由spidev_sync负责执行,这个函数是同步执行的所以函数内部由异步执行的
    status = spi_async(spidev->spi, message);
    和wait_for_completion(&done);转换为同步执行
    我们再看spi_async函数都做了什么?
    它执行了一个内部函数ret = __spi_async(spi, message);
    这个内部函数真正的传输动作是master->transfer(spi, message);
    而我们追踪到这里发现spi_master结构中transfer仅仅是一个预定义的函数指针
    我们回过头来从spi_master的初始化过程开始追踪,发现在spi_s3c64xx.c  中s3c64xx_spi_probe这个函数初始化spi_master时将函数s3c64xx_spi_transfer安装到了spi_master的函数指针transfer上,这种方法被大量用在linux内核中,这里暂时偏题一下,这种方法有什么好处呢?就是可以预先定义行为,但是却不用给出行为的实现,而行为的实现由于和具体的芯片架构有关系,所以由架构类的代码在初始化进行组装,最终形成完整的对象。就好比linux内核负责提供了汽车底盘,而大量的芯片厂商负责了给底盘组装轮子等外围设备,所以底盘厂商只要定义好接口就可以了。
    让我们看看这个函数都干了什么?
    static int s3c64xx_spi_transfer(struct spi_device *spi,
                            struct spi_message *msg)
    {
        struct s3c64xx_spi_driver_data *sdd;
        unsigned long flags;

        sdd = spi_master_get_devdata(spi->master);

        spin_lock_irqsave(&sdd->lock, flags);

        if (sdd->state & SUSPND) {
            spin_unlock_irqrestore(&sdd->lock, flags);
            return -ESHUTDOWN;
        }

        msg->status = -EINPROGRESS;
        msg->actual_length = 0;

        list_add_tail(&msg->queue, &sdd->queue);

        queue_work(sdd->workqueue, &sdd->work);


        spin_unlock_irqrestore(&sdd->lock, flags);

        return 0;
    }
    注意加粗的函数,原来它仅仅是将传输任务加到了spi_master的运行时数据s3c64xx_spi_driver_data的任务队列中
    而queue_work是干什么的?
    分析s3c64xx_spi_driver_data这个结构对work和workqueue的作用不是很了解,而work和workqueue似乎又和执行transfer有非常大的关系。

    分析spi_master的初始化过程发现work是由这个宏命令初始化的
        INIT_WORK(&sdd->work, s3c64xx_spi_work);
    分析s3c64xx_spi_work函数,发现传输任务最终由handle_msg函数负责执行
    也就是说handle_msg函数最终操纵芯片执行了传输任务

    我们先搁下handle_msg不表,先来回忆一下这个过程,可以发现整个过程是这样的,具体的传输任务其实最后都是由芯片厂商提供的代码完成,而linux内核团队负责制定了spi的用户标准,并为芯片厂商制定了向下的接口标准,这就是linux内核负责的核心负责的工作,它是与架构无关的,它负责制定标准。芯片厂商根据linux内核的标准,又提供了一些芯片相关的基础设施,而由主板厂商负责进行设备的组装。最终完成了spi控制器和spi从设备在内核中的安装工作,包括静态的数据结构和动态的函数功能的组装。具体的函数功能主要由芯片厂商的代码负责完成。

    整个linux体系的生态链完整的呈现在我们的面前,linux内核研发团队、芯片厂商、主板厂商、应用厂商的分工明确而清晰。
    到这里整个SPI的read过程的运行机制已经十分的清晰,而write过程与此类似就不剖析了。

    下一帖,我们再来详细剖析handle_msg的工作,由于它其实是s3c64xx的代码,与架构相关,并且利用了片上的DMA控制器。
    [ 此帖被zhuyong74在2011-03-22 18:02重新编辑 ]
     
     
     
    级别: 侠客
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
     
    UID: 21733
    精华: 0
    发帖: 61
    金钱: 320 两
    威望: 64 点
    贡献值: 0 点
    综合积分: 122 分
    注册时间: 2010-05-20
    最后登录: 2013-01-19
    7楼  发表于: 2011-03-26 15:22
     
     
     
    支持 学习了 谢谢~
     
     
    http://item.taobao.com/item.htm?id=8568899543
     
    级别: 新手上路
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
    • QQ联系
    • 阿里旺旺
     
    UID: 12458
    精华: 0
    发帖: 42
    金钱: 210 两
    威望: 42 点
    贡献值: 0 点
    综合积分: 84 分
    注册时间: 2010-01-07
    最后登录: 2011-09-14
    8楼  发表于: 2011-04-01 15:54
     
     
     
    好好的帖子!!顶起来
     
     
     
    http://item.taobao.com/item.htm?id=8568899543
    新手上路
     
    级别: 新手上路
    • 作者资料
    • 发送短消息
    • 加为好友
    • 使用道具
    • QQ联系
     
    UID: 34574
    精华: 0
    发帖: 49
    金钱: 245 两
    威望: 49 点
    贡献值: 0 点
    综合积分: 98 分
    注册时间: 2010-12-17
    最后登录: 2013-01-31
    9楼  发表于: 2011-04-02 22:50
     
     
     
    非常谢谢楼主的详细记载,我也正在调试SPI,还望赐教 QQ43121140
     
     
     
     
     
    快速发帖
     
    内容
    HTML 代码不可用
    使用签名
    Wind Code自动转换
    匿名帖
    隐藏此帖
    隐藏附件
    出售
    加密
    描述 附件 售/密 积分 价格  
     
    按 Ctrl+Enter 直接提交
    表情 [更多][个性表情]
     
     
     

  • 相关阅读:
    linux系统中输入输出重定向 0<、<、1>、>、2>、1>>、>>、2>>、&>、>&、&>>、2>&1、<<
    linux系统统计某一行出现特定字符的次数
    linux系统中常用的通配符*、?、[ ]、[^xxx]、{}
    R语言strsplit函数用法
    linux系统统计某一字符出现的次数
    什么时候你需要一个虚构函数是虚的
    strcpy的返回值有什么用?
    boost.array 使用实例
    《DB 查询分析器》使用技巧之(七)
    《微型电脑应用》2011年第11期刊登出《万能数据库查询分析器中的事务管理在Oracle中的应用》
  • 原文地址:https://www.cnblogs.com/luxiaolai/p/2891126.html
Copyright © 2020-2023  润新知