Linux-SPI设备驱动架构和用户空间程序设计
(2020-05-13 09:00:08)
标签:
linuxdriverspibus杂谈 |
分类: 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;
};
#define SPI_NBITS_SINGLE
#define SPI_NBITS_DUAL
#define SPI_NBITS_QUAD
};
==========================
一个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的一个参数,这发生在某些动态单板配置模型中,这些控制器驱动激活后。
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
},
};
#ifdef CONFIG_OF
#endif
};
==============================
驱动还需要提供一个方法以便总线代码绑定真实的设备到驱动,有2个方法来完成这个功能,其一是id_table参数,类型为struct
platform_device_id
========================
========================
如果有ID表,平台代码将扫描此表以发现和驱动匹配的设备,如果设备名和ID表中某个匹配,该设备将被此驱动管理,然而大多数驱动不会提供一个ID表,只有一个驱动名信息,这样和这个驱动名相同的设备将被绑定到此驱动,如上面的”ls-spi“。
在平台驱动注册完成后,probe()函数会带着新设备platform_device
参数被调用,
===============================
===============================
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,
};
};
static struct platform_device ls_spi_device = {
};
==================================
平台设备也要注册到系统,调用函数原型如下
SPI控制器设备驱动
int platform_device_register(struct platform_device *pdev);
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;
}
{
#ifdef CONFIG_OF
#endif
unmap_io:
free_master:
}
=========================================