Linux2.6.35下s3c64xx/spi子系统回顾一

1000阅读 0评论2013-11-21 zhendongzd
分类:嵌入式

上次在s5pc110板子上用spidev.c 读写spi设备失败,错误在wait_for_xfer函数中,提示spi控制器busy,换了一个spi控制器驱动,顺利通过,说明spi-s3c64xx.c控制器驱动不能直接用在s5pc110板子上。
于是回顾头来看下spi控制器驱动,想从中得到一些信息,下面算是学习笔记,与大家分享下,希望对初学者有帮助。另外,由于个人也是一个初学者,下面的内容只做参考,有误的地方望指教。
先整理下思路,避免看代码的时候晕菜。
主要涉及到的文件如下:
arch/arm/mach-s5pv210/dev-spi.c:平台设备文件
arch/arm/plat-samsung/include/plat/s3c64xx-spi.h:平台头文件,这个文件只是对s3c64xx_spi_info和s3c64xx_spi_csinfo的定义,还有s3c64xx_spi_set_info函数的声明
drivers/spi/spi_s3c64xx.c:控制器驱动文件
drivers/spi/spi.c:spi子系统核心层文件,连接控制器驱动和设备驱动,也是spi总线驱动的实现文件
include/linux/spi/spi.h spi子系统头文件
arch/arm/mach-s5pv210/mach-smdkc110.c:平台文件
这么多文件我们怎么看,我觉得从代码执行流程来看比较好,虽然spi子系统的关联是通过数据结构关联起来的,但是对于初学者,一开始就顺着数据结构的关系来看代码有点吃力。
代码的执行流程,大部分可以通过调试信息来确定。
初看在mach-smdkc110.c文件中有一个machine_init函数,我们肯能觉得一切从这里开始,初始化machine.
但spi.c里的spi_init执行在这之前,然后才是machine_init()在machine_init中回去注册spi平台设备。spi_init为什在在前执行
我们可以看到在spi_init函数实现下面有一条这样的语句postcore_initcall(spi_init);
在init.h#define postcore_initcall(fn) __define_initcall("2",fn,2)

machine_init是mdesc结构体的一个成员
init_machine = mdesc->init_machine;//setup.c
customize_machine()函数中被调用//setup.c
arch_initcall(customize_machine);arch_initcall被修饰
arch_initcall的实现
#define arch_initcall(fn) __define_initcall("3",fn,3)
__define_initcall的数字参数代表执行顺序,在do_initcalls中被按照顺序调用,由此可以看出是spi_init在machine_init 之前执行

那我们就看spi_init里面做了什么

点击(此处)折叠或打开

  1. static int __init spi_init(void)
  2. {
  3.     int    status;

  4.     buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);//buf for what
  5.     if (!buf) {
  6.         status = -ENOMEM;
  7.         goto err0;
  8.     }

  9.     status = bus_register(&spi_bus_type);//sys/bus/spi目录和spi目录下的devices和drivers目录。初始化设备链表和驱动链表
  10.     if (status < 0)
  11.         goto err1;

  12.     status = class_register(&spi_master_class);//sys/class/spi_master目录
  13.     if (status < 0)
  14.         goto err2;
  15.     return 0;

  16. err2:
  17.     bus_unregister(&spi_bus_type);
  18. err1:
  19.     kfree(buf);
  20.     buf = NULL;
  21. err0:
  22.     return status;
  23. }
调用bus_register和class_register分别注册了一条总线和一个类

点击(此处)折叠或打开

  1. struct bus_type spi_bus_type = {drivers/spi/spi.c
  2.     .name        = "spi",
  3.     .dev_attrs    = spi_dev_attrs,
  4.     .match        = spi_match_device,
  5.     .uevent        = spi_uevent,
  6.     .suspend    = spi_suspend,
  7.     .resume        = spi_resume,
  8. };

  9. static struct class spi_master_class = {drivers/spi/spi.c
  10.     .name        = "spi_master",
  11.     .owner        = THIS_MODULE,
  12.     .dev_release    = spi_master_release,
  13. };
bus_register(&spi_bus_type);究竟干了什么?
回答这个问题之前先看下bus_type这个结构体
struct bus_type {  <-----------------------------------------------------------------------------
    struct bus_type_private *p;--->struct bus_type_private {                      |
                                                                struct kset subsys; ---------------|------------------------------->struct kset {
                                                                struct kset *drivers_kset;      |                                          struct kobject kobj;----------->struct kobject {
                                                                struct kset *devices_kset;     |                                                                                       const char *name;//设置name
                                                                struct klist klist_devices;        |//传说中的设备链表                                                                struct list_head entry;
                                                                struct klist klist_drivers;         |//传说中的驱动链表                                                                struct kobject *parent;
                                                                struct bus_type *bus;-----------                                                                                           struct kset *kset;
                                                };                                                                                                                                                  struct kobj_type *ktype;
                                                                                                                                                                                                     struct sysfs_dirent *sd;
                                                                                                                                                                                                     struct kref kref;
                                                                                                                                                                                            }                                           
};
一个目录或文件对应一个kobject
spi_init完后
1在/sys/bus/下创建了一个spi目录spi目录下创建了devices和drivers两个目录和一些设备文件
2初始化设备链表和驱动链表
3 在sys/class/下创建了spi_master目录
----------------------
接着执行的是machine_init()函数
在machine_init 函数中会去注册spi平台设备,其实就是注册两个结构体spi0 和spi1 这里要看你的平台有几个控制器,有几个就注册几个
platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));

点击(此处)折叠或打开

  1. int platform_add_devices(struct platform_device **devs, int num)
  2. {
  3.     int i, ret = 0;

  4.     for (i = 0; i < num; i++) {
  5.         ret = platform_device_register(devs[i]);
  6.         if (ret) {
  7.             while (--i >= 0)
  8.                 platform_device_unregister(devs[i]);
  9.             break;
  10.         }
  11.     }

  12.     return ret;
  13. }
smdkc110_devices数组
static struct platform_device *smdkc110_devices[] __initdata = {
        #ifdef CONFIG_S3C64XX_DEV_SPI
            &s5pv210_device_spi0,
            &s5pv210_device_spi1,
        #endif

&s5pv210_device_spi0和&s5pv210_device_spi1,这连个结构体就在arch/arm/mach-s5pv210/dev-spi.c
dev-spi.c

点击(此处)折叠或打开

  1. static struct resource s5pv210_spi0_resource[] = {
  2.     [0] = {
  3.         .start = S5PV210_PA_SPI0,
  4.         .end = S5PV210_PA_SPI0 + 0x100 - 1,
  5.         .flags = IORESOURCE_MEM,
  6.     },
  7.     [1] = {
  8.         .start = DMACH_SPI0_TX,
  9.         .end = DMACH_SPI0_TX,
  10.         .flags = IORESOURCE_DMA,
  11.     },
  12.     [2] = {
  13.         .start = DMACH_SPI0_RX,
  14.         .end = DMACH_SPI0_RX,
  15.         .flags = IORESOURCE_DMA,
  16.     },
  17.     [3] = {
  18.         .start = IRQ_SPI0,
  19.         .end = IRQ_SPI0,
  20.         .flags = IORESOURCE_IRQ,
  21.     },
  22. };
  23. //在s5pv210_spi_set_info中会对s3c64xx_spi_set_info做进一步的设置
  24. static struct s3c64xx_spi_info s5pv210_spi0_pdata = { //平台数据
  25.     .cfg_gpio = s5pv210_spi_cfg_gpio,
  26.     .fifo_lvl_mask = 0x1ff,//写缓冲寄存器最大字节数,用来判断是否用DMA
  27.     .rx_lvl_offset = 15,//读缓冲寄存器最大字节数
  28.     .high_speed = 1,//是否使用高速模式
  29. };

  30. static u64 spi_dmamask = DMA_BIT_MASK(32);

  31. struct platform_device s5pv210_device_spi0 = {
  32.     .name         = "s3c64xx-spi",
  33.     .id         = 0,
  34.     .num_resources     = ARRAY_SIZE(s5pv210_spi0_resource),
  35.     .resource     = s5pv210_spi0_resource,
  36.     .dev = {
  37.         .dma_mask        = &spi_dmamask,//DMA寻址范围
  38.         .coherent_dma_mask    = DMA_BIT_MASK(32),//cache一致性相关
  39.         .platform_data = &s5pv210_spi0_pdata,//在probe函数中会取出使用
  40.     },
  41. };
这里以spi控制器0为例。
spi平台设备注册进内核,挂入到平台设备总线的设备总线上。平台总线跟spi总线一样在调用machine_init前便挂入内核了
其实就是创建了一个全局变量,里面有两个内核链表成员,以后注册一个设备就将其插入到这个全局变量的成员链表里
------------------------
之后spi控制器驱动便会去注册,在注册的时候回去到平台设备总线的设备总线上去找刚注册的spi设备。
根据名字匹配到了便会调用probe函数。
刚做的一切都是为了能调用到probe函数服务的,probe函数才是关键,下面来看下spi控制器驱动的probe函数中都做了那些工作
下面是probe的代码,一句句的啃

点击(此处)折叠或打开

  1. static int __init s3c64xx_spi_probe(struct platform_device *pdev)
  2. {
  3.     struct resource    *mem_res, *dmatx_res, *dmarx_res;
  4.     struct s3c64xx_spi_driver_data *sdd;
  5.     struct s3c64xx_spi_info *sci;
  6.     struct spi_master *master;
  7.     int ret;

  8.     if (pdev->id < 0) {//判断平台设备的id是否合法,在平台设备结构体定义的时候已经初始化了
  9.         dev_err(&pdev->dev,
  10.                 "Invalid platform device id-%d\n", pdev->id);
  11.         return -ENODEV;
  12.     }

  13.     if (pdev->dev.platform_data == NULL) {//平台设备私有数据是否为空,在平台设备结构体定义的时候初始化,在板级文件里调用s5pv210_spi_set_info函数进一步填充
  14.         dev_err(&pdev->dev, "platform_data missing!\n");
  15.         return -ENODEV;
  16.     }

  17.     /* Check for availability of necessary resource */

  18.     dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);//获取dma写资源
  19.     if (dmatx_res == NULL) {
  20.         dev_err(&pdev->dev, "Unable to get SPI-Tx dma resource\n");
  21.         return -ENXIO;
  22.     }

  23.     dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);//获取dma读资源
  24.     if (dmarx_res == NULL) {
  25.         dev_err(&pdev->dev, "Unable to get SPI-Rx dma resource\n");
  26.         return -ENXIO;
  27.     }

  28.     mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取内存资源
  29.     if (mem_res == NULL) {
  30.         dev_err(&pdev->dev, "Unable to get SPI MEM resource\n");
  31.         return -ENXIO;
  32.     }

  33.     master = spi_alloc_master(&pdev->dev,
  34.                 sizeof(struct s3c64xx_spi_driver_data));//构建spi_master结构体,代表一个控制器

  35. 点击(此处)折叠或打开

    1. struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
    2. {
    3.     struct spi_master    *master;

    4.     if (!dev)
    5.         return NULL;

    6.     master = kzalloc(size + sizeof *master, GFP_KERNEL);
    7.     if (!master)
    8.         return NULL;

    9.     device_initialize(&master->dev);
    10.     master->dev.class = &spi_master_class;
    11.     master->dev.parent = get_device(dev);
    12.     spi_master_set_devdata(master, &master[1]);//设置devdata,待会儿取出

    13.     return master;
    14. }


  36.     if (master == NULL) {
  37.         dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
  38.         return -ENOMEM;
  39.     }

  40.     sci = pdev->dev.platform_data;//取出平台数据

  41.     platform_set_drvdata(pdev, master);//master作为平台设备的私有数据,dev->p->driver_data = master;

  42.     sdd = spi_master_get_devdata(master);//将上一步设置的数据拿出,下面是对这个结构体的填充(上一步是对指针赋值,这一步是为这个指针娶个临时的名字,让后通过这个名字去访问它)
  43.     sdd->master = master;
  44.     sdd->cntrlr_info = sci;//平台数据
  45.     sdd->pdev = pdev;//属于那个平台设备
  46.     sdd->sfr_start = mem_res->start;//内存起始地址
  47.     sdd->tx_dmach = dmatx_res->start;//dma写起始地址
  48.     sdd->rx_dmach = dmarx_res->start;dma读起始地址

  49.     sdd->cur_bpw = 8;//传输时每个word的位数

  50.     master->bus_num = pdev->id;//控制器编号
  51.     master->setup = s3c64xx_spi_setup;//spi控制器设置函数指针
  52.     master->transfer = s3c64xx_spi_transfer;//控制器发送数据函数指针
  53.     master->num_chipselect = sci->num_cs;//num_ns在函数s5pv210_spi_set_info(dev-spi.c)中被赋值,在machine_init函数中调用(mach-smdkc110.c),意思为控制器上挂载从设备的个数
  54.     master->dma_alignment = 8;//DMA相关
  55.     /* the spi->mode bits understood by this driver: */
  56.     master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;//传输方式设置
  57. --------------------------------------
  58.     if (request_mem_region(mem_res->start,
  59.             resource_size(mem_res), pdev->name) == NULL) {
  60.         dev_err(&pdev->dev, "Req mem region failed\n");
  61.         ret = -ENXIO;
  62.         goto err0;
  63.     }

  64.     sdd->regs = ioremap(mem_res->start, resource_size(mem_res));
  65.     if (sdd->regs == NULL) {
  66.         dev_err(&pdev->dev, "Unable to remap IO\n");
  67.         ret = -ENXIO;
  68.         goto err1;
  69.     }

  70.     if (sci->cfg_gpio == NULL || sci->cfg_gpio(pdev)) {
  71.         dev_err(&pdev->dev, "Unable to config gpio\n");
  72.         ret = -EBUSY;
  73.         goto err2;
  74.     }
  75.    -------------------------------------
  76. 以上都是申请资源
  77.     /* Setup clocks */
  78.     sdd->clk = clk_get(&pdev->dev, "spi");//从pdev->dev中获取clk,clk在哪里设置?
  79.     if (IS_ERR(sdd->clk)) {
  80.         dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n");
  81.         ret = PTR_ERR(sdd->clk);
  82.         goto err3;
  83.     }

  84.     if (clk_enable(sdd->clk)) {
  85.         dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n");
  86.         ret = -EBUSY;
  87.         goto err4;
  88.     }

  89.     sdd->src_clk = clk_get(&pdev->dev, sci->src_clk_name);//pdev->dev->platformdata有一个成员src_clk_name,在s5pv210_spi_set_info里设置,在machine_init里调用
  90.     if (IS_ERR(sdd->src_clk)) {
  91.         dev_err(&pdev->dev,
  92.             "Unable to acquire clock '%s'\n", sci->src_clk_name);
  93.         ret = PTR_ERR(sdd->src_clk);
  94.         goto err5;
  95.     }

  96.     if (clk_enable(sdd->src_clk)) {
  97.         dev_err(&pdev->dev, "Couldn't enable clock '%s'\n",
  98.                             sci->src_clk_name);
  99.         ret = -EBUSY;
  100.         goto err6;
  101.     }

  102.     sdd->workqueue = create_singlethread_workqueue(
  103.                         dev_name(master->dev.parent));//创建工作队列线程,master->dev.parent就是pdev->dev(平台设备的dev)
  104.     if (sdd->workqueue == NULL) {
  105.         dev_err(&pdev->dev, "Unable to create workqueue\n");
  106.         ret = -ENOMEM;
  107.         goto err7;
  108.     }

  109.     /* Setup Deufult Mode */
  110.     s3c64xx_spi_hwinit(sdd, pdev->id);//spi硬件初始化

  111. 点击(此处)折叠或打开

    1. static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd, int channel)
    2. {
    3.     struct s3c64xx_spi_info *sci = sdd->cntrlr_info;
    4.     void __iomem *regs = sdd->regs;
    5.     unsigned int val;

    6.     sdd->cur_speed = 0;

    7.     S3C64XX_SPI_DEACT(sdd);//#define S3C64XX_SPI_DEACT(c) writel(S3C64XX_SPI_SLAVE_SIG_INACT, (c)->regs + S3C64XX_SPI_SLAVE_SEL)//不片选


    8.     /* Disable Interrupts - we use Polling if not DMA mode */
    9.     writel(0, regs + S3C64XX_SPI_INT_EN);//all interrupt disable

    10.     writel(sci->src_clk_nr << S3C64XX_SPI_CLKSEL_SRCSHFT,
    11.                 regs + S3C64XX_SPI_CLK_CFG);//时钟源头的选择,CLK_CFG0的第10位,0表示PCLK,1表示EXT_CLK为时钟源
    12.     writel(0, regs + S3C64XX_SPI_MODE_CFG);//传输模式配置寄存器设置
    13.     writel(0, regs + S3C64XX_SPI_PACKET_CNT);

    14.     /* Clear any irq pending bits */
    15.     writel(readl(regs + S3C64XX_SPI_PENDING_CLR),
    16.                 regs + S3C64XX_SPI_PENDING_CLR);

    17.     writel(0, regs + S3C64XX_SPI_SWAP_CFG);

    18.     val = readl(regs + S3C64XX_SPI_MODE_CFG);
    19.     val &= ~S3C64XX_SPI_MODE_4BURST;
    20.     val &= ~(S3C64XX_SPI_MAX_TRAILCNT << S3C64XX_SPI_TRAILCNT_OFF);
    21.     val |= (S3C64XX_SPI_TRAILCNT << S3C64XX_SPI_TRAILCNT_OFF);
    22.     writel(val, regs + S3C64XX_SPI_MODE_CFG);//以上都是控制器的配置,内核一般都会自带,不用我们写,若要移植到不同的平台,才需要相应的修改

    23.     flush_fifo(sdd);//清空rx tx  buffer 
    24. }
  112.     spin_lock_init(&sdd->lock);
  113.     init_completion(&sdd->xfer_completion);
  114.     INIT_WORK(&sdd->work, s3c64xx_spi_work);
  115.     INIT_LIST_HEAD(&sdd->queue);//以上是初始化工作队列

  116.     if (spi_register_master(master)) {//注册master
  117.         dev_err(&pdev->dev, "cannot register SPI master\n");
  118.         ret = -EBUSY;
  119.         goto err8;
  120.     }

  121. 点击(此处)折叠或打开

    1. int spi_register_master(struct spi_master *master)
    2. {
    3.     static atomic_t        dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
    4.     struct device        *dev = master->dev.parent;
    5.     int            status = -ENODEV;
    6.     int            dynamic = 0;

    7.     if (!dev)
    8.         return -ENODEV;

    9.     /* even if it's just one always-selected device, there must
    10.      * be at least one chipselect
    11.      */
    12.     if (master->num_chipselect == 0)
    13.         return -EINVAL;

    14.     /* convention: dynamically assigned bus IDs count down from the max */
    15.     if (master->bus_num < 0) {
    16.         /* FIXME switch to an IDR based scheme, something like
    17.          * I2C now uses, so we can't run out of "dynamic" IDs
    18.          */
    19.         master->bus_num = atomic_dec_return(&dyn_bus_id);
    20.         dynamic = 1;
    21.     }

    22.     /* register the device, then userspace will see it.
    23.      * registration fails if the bus ID is in use.
    24.      */
    25.     dev_set_name(&master->dev, "spi%u", master->bus_num);//spi0
    26.     status = device_add(&master->dev);//注册master
    27.     if (status < 0)
    28.         goto done;
    29.     dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
    30.             dynamic ? " (dynamic)" : "");

    31.     /* populate children from any spi device tables */
    32.     scan_boardinfo(master);//扫描device链表,在registered_spi_board_info创建的(machien_init函数中调用)

    33. 点击(此处)折叠或打开

      1. static void scan_boardinfo(struct spi_master *master)
      2. {
      3.     struct boardinfo    *bi;

      4.     mutex_lock(&board_lock);
      5.     list_for_each_entry(bi, &board_list, list) {//扫描board_list链表,取出在register_spi_board_info放入的s3c64xx_board_info,来构建master_device
      6.         struct spi_board_info    *chip = bi->board_info;
      7.         unsigned        n;

      8.         for (n = bi->n_board_info; n > 0; n--, chip++) {
      9.             if (chip->bus_num != master->bus_num)//根据总线号找
      10.                 continue;
      11.             /* NOTE: this relies on spi_new_device to
      12.              * issue diagnostics when given bogus inputs
      13.              */
      14.             (void) spi_new_device(master, chip);

      15. 点击(此处)折叠或打开

        1. struct spi_device *spi_new_device(struct spi_master *master,
        2.                  struct spi_board_info *chip)
        3. {
        4.     struct spi_device    *proxy;
        5.     int            status;

        6.     /* NOTE: caller did any chip->bus_num checks necessary.
        7.      *
        8.      * Also, unless we change the return value convention to use
        9.      * error-or-pointer (not NULL-or-pointer), troubleshootability
        10.      * suggests syslogged diagnostics are best here (ugh).
        11.      */

        12.     proxy = spi_alloc_device(master);//分配一个spi_device(master是它的一个成员)
        13.     if (!proxy)
        14.         return NULL;

        15.     WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));

        16.     proxy->chip_select = chip->chip_select;
        17.     proxy->max_speed_hz = chip->max_speed_hz;
        18.     proxy->mode = chip->mode;
        19.     proxy->irq = chip->irq;
        20.     strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
        21.     proxy->dev.platform_data = (void *) chip->platform_data;
        22.     proxy->controller_data = chip->controller_data;
        23.     proxy->controller_state = NULL;

        24.     status = spi_add_device(proxy);//注册spi_device 
        25.     if (status < 0) {
        26.         spi_dev_put(proxy);
        27.         return NULL;
        28.     }

        29.     return proxy;
        30. }


      16.         }
      17.     }
      18.     mutex_unlock(&board_lock);
      19. }


    34.     status = 0;
    35. done:
    36.     return status;
    37. }


  122.     dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d "
  123.                     "with %d Slaves attached\n",
  124.                     pdev->id, master->num_chipselect);
  125.     dev_dbg(&pdev->dev, "\tIOmem=[0x%x-0x%x]\tDMA=[Rx-%d, Tx-%d]\n",
  126.                     mem_res->end, mem_res->start,
  127.                     sdd->rx_dmach, sdd->tx_dmach);
  128.     return ret;
  129. }
到此整个spi控制器驱动的加载过程就分析完了,控制器驱动的probe函数会去扫描spi_register_board_info添加到某条链表的spi_board_info信息,然后根据这个信息创建spi_device设备并注册
当有设备驱动加载时就可以到spi总线上去匹配spi设备
剩下的就是终极目标数据是怎样传输的,因为上面的总总一切只为了传输数据

上一篇:s5pc110/smdkc110下的按键驱动分析
下一篇:static inline内联函数