加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

Linux-SPI设备驱动架构和用户空间程序设计

(2020-05-13 09:00:08)
标签:

linux

driver

spi

bus

杂谈

分类: programming
SPI设备和驱动可以分为两类,控制器驱动抽象了控制器硬件,这可能包括2个FIFO,连接到双DMA引擎,而在另一侧为移位寄存器,这样的驱动器在内部总线和SPI设备间桥接,结构struct spi_master描述了SPI控制器驱动,SPI设备是控制器的子节点,描述为struct spi_device,其中的一些信息可以定义在struct spi_board_info,

SPI通讯可以划分为transfers和messages,SPI的I/O模式是一个消息队列集合,协议驱动提交struct spi_message消息对象,被异步的处理,消息建立在一个或多个 struct spi_transfer对象基础上,每个struct spi_transfer对象包裹了一个全双工的SPI transfer.

transfer定义了一个在master和slave之间的单次数据传递,spi transfer总是读写相同的字节数,因为使用同一个采样时钟,协议驱动同时提供读写缓冲区rx_buf/tx_buf,transfer可以设置DMA以减轻开销,如果底层驱动支持DMA方式。
=========================
struct spi_transfer {
  const void * tx_buf;
  void * rx_buf;
  unsigned len;
  dma_addr_t tx_dma;
  dma_addr_t rx_dma;
  struct sg_table tx_sg;
  struct sg_table rx_sg;
  unsigned cs_change:1;
  unsigned tx_nbits:3;
  unsigned rx_nbits:3;
#define SPI_NBITS_SINGLE      0x01
#define SPI_NBITS_DUAL                0x02
#define SPI_NBITS_QUAD                0x04
  u8 bits_per_word;
  u16 delay_usecs;
  u32 speed_hz;
  struct list_head transfer_list;
};
==========================
一个spi_message用来执行一个原子数据传送序列,每个数据传送由struct spi_transfer表示,原子操作意思是在一个spi_message完成之前不能执行另一个spi_message。spi_message是SPI各子系统读写API的基本参数。
======================
struct spi_message {
  struct list_head transfers;
  struct spi_device * spi;
  unsigned is_dma_mapped:1;
  void (* complete) (void *context);
  void * context;
  unsigned frame_length;
  unsigned actual_length;
  int status;
  struct list_head queue;
  void * state;
  struct list_head resources;
};
======================

struct spi_board_info表示spi设备的特定spi板级相关信息,当添加一个新的spi设备到设备树时,这些结构作为部分设备模板,容纳了一些驱动不会由驱动确定的信息,那些可以在probe()中获取的信息不会包括在这。
这些结构用于2个地方,基本角色存储于板级特定设备描述符,这在硬件初始化时申明,然后在控制器驱动初始化后用来填写控制器设备树;另一个用法是作为函数spi_new_device的一个参数,这发生在某些动态单板配置模型中,这些控制器驱动激活后。

struct spi_device * spi_new_device(struct spi_controller * ctlr, struct spi_board_info * chip)


SPI系统初始化

SPI子系统一开始调用bus_register(struct bus_type *bus)注册一个spi总线类型到系统总线子系统,并调用
sysfs_create_file创建sysfs的spi子系统属性文件,以及相应的属性子节点。这时dmesg可以看到bus spi被注册的信息。

spi设备通常是不可以向PCI或USB设备那样自动发现的,所以要调用platform_driver_register注册到平台伪总线上(platform bus),参数如:
int platform_driver_register(struct platform_driver *driver);

spi控制器的一个例子,如龙芯ls2k芯片的spi控制器驱动。
==================================ls-spi controller==========================
static int __init ls_spi_init(void)
{
    int ret;

    ret =  platform_driver_register(&ls_spi_driver);
    if(!ret)
        ret = pci_register_driver(&ls_spi_pci_driver);
    return ret;
}
========================================================================
平台设备通过设备名绑定到设备驱动,平台设备由结构struct platform_device定义,这些设备被认为连接到虚拟"platform bus";平台设备的驱动注册它们自己到平台总线,平台驱动定义如下
==============================================================
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;
};
=============================================================
龙芯spi平台驱动定义可见:
============================
static struct platform_driver ls_spi_driver = {
    .probe = ls_spi_probe,
    .driver    = {
        .name    = "ls-spi",
        .owner    = THIS_MODULE,
        .bus = &platform_bus_type,
#ifdef CONFIG_OF
        .of_match_table = of_match_ptr(ls_spi_id_table),
#endif
    },
};
==============================
驱动还需要提供一个方法以便总线代码绑定真实的设备到驱动,有2个方法来完成这个功能,其一是id_table参数,类型为struct platform_device_id
========================
 struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];
    kernel_ulong_t driver_data;
    };
========================
如果有ID表,平台代码将扫描此表以发现和驱动匹配的设备,如果设备名和ID表中某个匹配,该设备将被此驱动管理,然而大多数驱动不会提供一个ID表,只有一个驱动名信息,这样和这个驱动名相同的设备将被绑定到此驱动,如上面的”ls-spi“。

在平台驱动注册完成后,probe()函数会带着新设备platform_device 参数被调用,
===============================
    struct platform_device {
    const char    *name;
    int        id;
    struct device   dev;
    u32        num_resources;
    struct resource    *resource;
    const struct platform_device_id    *id_entry;
   
 };
===============================

Platform devices

平台设备是不可以自动发现的,所以内核需要被通知设备的存在,通常是静态定义一个platform_device结构,例如。
===================================
static struct resource ls_spi_resources[] = {
    [0] = {
        .flags  = IORESOURCE_MEM,
    },
    [1] = {
        .flags  = IORESOURCE_IRQ,
    },
};

static struct platform_device ls_spi_device = {
    .name           = "ls-spi",
    .id             = 0,
    .num_resources  = ARRAY_SIZE(ls_spi_resources),
    .resource   = ls_spi_resources,
};
==================================
平台设备也要注册到系统,调用函数原型如下
int platform_device_register(struct platform_device *pdev);

SPI控制器设备驱动

spi控制器作为spi主设备,Linux定义了spi_master结构来描述spi控制器,在probe阶段系统分配spi_master并根据设备参数初始化控制器结构,最后调用spi_register_master()注册到系统。
======================================
static int ls_spi_probe(struct platform_device *pdev)
{
    struct spi_master    *master;
    struct ls_spi        *spi;
    struct resource        *res;
    int ret;
    master = spi_alloc_master(&pdev->dev, sizeof(struct ls_spi));
    .....
    if (pdev->id != -1)
        master->bus_num    = pdev->id;

    master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH ;
    master->setup = ls_spi_setup;
    master->transfer = ls_spi_transfer;
    master->num_chipselect = 4;
#ifdef CONFIG_OF
    master->dev.of_node = of_node_get(pdev->dev.of_node);
#endif
    dev_set_drvdata(&pdev->dev, master);

    spi = spi_master_get_devdata(master);

    spi->wq    = create_singlethread_workqueue(pdev->name);

    spi->master = master;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (res == NULL) {
        dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
        ret = -ENOENT;
        goto free_master;
    }

    spi->base = ioremap(res->start, (res->end - res->start)+1);
    if (spi->base == NULL) {
        dev_err(&pdev->dev, "Cannot map IO\n");
        ret = -ENXIO;
        goto unmap_io;
    }

    ls_spi_write_reg(spi, SPCR, 0x51);
    ls_spi_write_reg(spi, SPER, 0x00);
    ls_spi_write_reg(spi, TIMI, 0x01);
    ls_spi_write_reg(spi, PARA, 0x40);
    spi->mode = 0;
    INIT_WORK(&spi->work, ls_spi_work);

    spin_lock_init(&spi->lock);
    INIT_LIST_HEAD(&spi->msg_queue);

    ret = spi_register_master(master);
    if (ret < 0)
        goto unmap_io;

    return ret;

unmap_io:
    iounmap(spi->base);
free_master:
    kfree(master);
    spi_master_put(master);
    return ret;

}
=========================================

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有