Linux下spi驱动分析与测试【详细流程】
驱动是基于ARM的pl022的SSP控制器,内容包括驱动流程调用分析以及模块测试。
驱动是基于ARM的pl022的SSP控制器,其支持三种通信格式:SPI、SSI以及Microwrite,llinux5.4内核下,SSP控制器驱动的路径为/drivers/spi/spi-pl022.c
。下面将从spi-pl022.c
文件开始分析SPI驱动。
1 AMBA总线
与platform总线相同,pl022的SSP控制器的驱动是维护在amba总线上。
我们需要首先了解amba总线,内核中专门定义了一类amba_bustype、amba_device、amba_driver。具体定义在内核的 drivers/amba/bus.c
中。按理说AMBA是一种片内总线,对驱动程序员来说是透明的,为什么还要定义一类amba_device和amba_driver呢?看看内核源码中linux/include/amba/bus.h
的解释:
This device type deals with ARM PrimeCells and anything else that presents a proper CID ( 0xB105F00D ) at the end of the I/O register region or that is derived from a PrimeCell.
也就是说amba_device定义的是ARM的PrimeCells提供的片内外设,当然这些外设都使用AMBA总线。这些外设有一个特征,那就是在自己的IO地址空间的尾部存放了一个固定的CID( 0xB105F00D),表明这是一个amba_device。
由于ARM众多的合作伙伴都会或多或少的使用ARM提供的片内外设,所以众多厂商的ARM处理器的一些外设可以使用相同的驱动,只有IO地址空间和中断号的区别,寄存器和操作方式都是类似的。为了管理这类驱动,内核中专门建立了amba子系统。
CID正是为了向驱动表明这是一个amba_device。但是仅仅表明这是一个amba_device还是不够的,因为amba_device包括了lcd控制器、ssp、中断控制器等多种设备。为了让ambe驱动识别设备的类型,amba_device在自己IO地址空间的尾部还存放了一个四字节的 periphid,内核使用它来确认是否可以使用标准的amba驱动。在SSP的数据手册中,有寄存器用于保存CID以及periphid的信息。
注:
以上内容为网络资源
1.1 amba总线结构体
amba总线相关结构体(drivers/amba/bus.c)
如下:
struct bus_type amba_bustype = {
.name = "amba",
.dev_groups = amba_dev_groups,
.match = amba_match, //匹配函数,重要
.uevent = amba_uevent,
.dma_configure = platform_dma_configure,
.pm = &amba_pm,
};
当系统启动时会执行下面代码,向系统注册amba总线。
static int __init amba_init(void)
{
return bus_register(&amba_bustype);
}
postcore_initcall(amba_init);
1.2 amba_driver结构体
amba_driver相关结构体(drivers/amba/bus.c)
如下:
struct amba_driver {
struct device_driver drv;
int (*probe)(struct amba_device *, const struct amba_id *);
int (*remove)(struct amba_device *);
void (*shutdown)(struct amba_device *);
const struct amba_id *id_table;
};
使用下面代码,来注册amba_driver
,后续将详细介绍此函数。
int amba_driver_register(struct amba_driver *drv)
{
if (!drv->probe)
return -EINVAL;
drv->drv.bus = &amba_bustype;
drv->drv.probe = amba_probe;
drv->drv.remove = amba_remove;
drv->drv.shutdown = amba_shutdown;
return driver_register(&drv->drv); //调用此函数注册驱动
}
1.3 amba_device结构体
amba_device相关结构体(drivers/amba/bus.c)
如下:
struct amba_device {
struct device dev;
struct resource res;
struct clk *pclk;
unsigned int periphid;
unsigned int cid;
struct amba_cs_uci_id uci;
unsigned int irq[AMBA_NR_IRQS];
char *driver_override;
};
使用下面代码,来注册amba_device
。
int amba_device_register(struct amba_device *dev, struct resource *parent)
{
amba_device_initialize(dev, dev->dev.init_name);
dev->dev.init_name = NULL;
return amba_device_add(dev, parent); //调用此函数注册设备
}
2 SPI驱动源码
2.1 驱动配置
2.1.1 驱动基础配置信息
spi驱动源码路径路径为/drivers/spi/spi-pl022.c
,其Makefile信息为:
obj-$(CONFIG_SPI_PL022) += spi-pl022.o
Kconfig信息为:
config SPI_PL022
tristate "ARM AMBA PL022 SSP controller"
depends on ARM_AMBA
default y if MACH_U300
default y if ARCH_REALVIEW
default y if INTEGRATOR_IMPD1
default y if ARCH_VERSATILE
help
This selects the ARM(R) AMBA(R) PrimeCell PL022 SSP
controller. If you have an embedded system with an AMBA(R)
bus and a PL022 controller, say Y or M here.
menuconfig配置信息如下,红色区域为必选。
2.1.2 设备树信息
设备树的内容如下:
spi0: spi@55558888 {
compatible = "arm,pl022", "arm,primecell";
reg = <0x55558888 0x1000>;
interrupt-parent = <&gic>;
interrupts = <0 120 IRQ_TYPE_EDGE_RISING>;
clock-names = "spiclk", "apb_pclk";
clocks = <&sysclk1>, <&sysclk1>;
arm,primecell-periphid = <0x00041022>; //periphid
num-cs = <1>; //chipselect num
status = "okay";
};
2.2 驱动注册
在驱动注册过程中的工作,不仅将驱动注册到相应的bus上,而且在bus上寻找到驱动对应的设备。
2.2.1 驱动信息
pl022 SPI驱动为amba_driver类型,其驱动信息为
static struct amba_driver pl022_driver = {
.drv = {
.name = "ssp-pl022",
.pm = &pl022_dev_pm_ops,
},
.id_table = pl022_ids,
.probe = pl022_probe, //amba_driver的probe函数
.remove = pl022_remove,
};
其中id_table
内容为
static const struct amba_id pl022_ids[] = {
{
.id = 0x00041022, //SSPPeriphid0-3寄存器保存
.mask = 0x000fffff,
.data = &vendor_arm,
},
...
{ 0, 0 },
};
2.2.2 驱动注册过程
在加载pl022 SPI驱动时,首先需要注册驱动,执行下段代码实现
static int __init pl022_init(void)
{
return amba_driver_register(&pl022_driver);
}
subsys_initcall(pl022_init);
其中函数amba_driver_register()
,在1.2小节中说明过,其具体内容为
int amba_driver_register(struct amba_driver *drv)
{
if (!drv->probe)
return -EINVAL;
drv->drv.bus = &amba_bustype; //在1.1小节给出内容
drv->drv.probe = amba_probe; //decive_driver的probe函数,要区分amba_dirver的probe函数
drv->drv.remove = amba_remove;
drv->drv.shutdown = amba_shutdown;
return driver_register(&drv->drv); //调用此函数注册驱动
}
与platformat总线相似,amba总线在注册驱动时,例化其device_driver的成员变量,包括:
- 总线类型
- probe函数 (重要,driver与device匹配后调用)
- remove函数
- shutdown函数
最后调用到driver_register()
函数,将spi驱动注册到系统中。driver_register()
函数在/drivers/base/driver.c
文件中,其主要内容为
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
...
ret = bus_add_driver(drv); //添加驱动
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv);
return ret;
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
return ret;
}
其中会调用bus_add_driver(drv)
函数将驱动添加到相应的总线上,函数主要内容为
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
...
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
//添加到driver链表
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
if (error)
goto out_unregister;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv); //绑定device
if (error)
goto out_unregister;
}
module_add_driver(drv->owner, drv);
...
return 0;
}
其中会执行到driver_attach(drv)
函数来绑定device,在/drivers/base/dd.c
路径下,此函数的主要内容为
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
函数会在相应bus上(amba)遍历device链表,调用__driver_attach()
来绑定device,函数的主要内容为
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
int ret;
...
//匹配
ret = driver_match_device(drv, dev);
if (ret == 0) {
/* no match */
return 0;
} else if (ret == -EPROBE_DEFER) {
dev_dbg(dev, "Device match requests probe deferral\n");
driver_deferred_probe_add(dev);
} else if (ret < 0) {
dev_dbg(dev, "Bus failed to match device: %d", ret);
return ret;
} /* ret > 0 means positive match */
...
//绑定
device_driver_attach(drv, dev);
return 0;
}
在上面的内容中,主要进行了两个工作:
- driver与device匹配
- driver与device绑定
driver与device匹配
driver与device匹配由函数driver_match_device(drv, dev)
实现,其内容为
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
其中会调用总线提供的匹配函数drv->bus->match
,在1.1小节中曾说明匹配函数,内容为
struct bus_type amba_bustype = {
.name = "amba",
.dev_groups = amba_dev_groups,
.match = amba_match, //匹配函数,重要
.uevent = amba_uevent,
.dma_configure = platform_dma_configure,
.pm = &amba_pm,
};
amba_match
函数的内容为
static int amba_match(struct device *dev, struct device_driver *drv)
{
struct amba_device *pcdev = to_amba_device(dev);
struct amba_driver *pcdrv = to_amba_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pcdev->driver_override)
return !strcmp(pcdev->driver_override, drv->name);
return amba_lookup(pcdrv->id_table, pcdev) != NULL;
}
优先使用driver_override来进行匹配(一般没使用),若不适用driver_override,则调用amba_lookup()
函数使用id_table
进行匹配
static const struct amba_id *
amba_lookup(const struct amba_id *table, struct amba_device *dev)
{
while (table->mask) {
if (((dev->periphid & table->mask) == table->id) &&
((dev->cid != CORESIGHT_CID) ||
(amba_cs_uci_id_match(table, dev))))
return table;
table++;
}
return NULL;
}
其中(dev->periphid & table->mask) == table->id
是重要的匹配过程,传入参数amba_id
由amba_driver提供(2.2.1小节),dev->periphid
由设备树提供。
至此device与driver匹配成功,然后进行绑定工作。
driver与device绑定
driver与device绑定主要由函数device_driver_attach(drv, dev)
实现,其内容为
int device_driver_attach(struct device_driver *drv, struct device *dev)
{
int ret = 0;
__device_driver_lock(dev, dev->parent);
if (!dev->p->dead && !dev->driver)
ret = driver_probe_device(drv, dev);
__device_driver_unlock(dev, dev->parent);
return ret;
}
在绑定函数中,主要的目的是为了执行到probe函数。使用driver_probe_device()
函数实现
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
if (!device_is_registered(dev))
return -ENODEV;
...
pm_runtime_barrier(dev);
if (initcall_debug)
ret = really_probe_debug(dev, drv);
else
ret = really_probe(dev, drv); //正真的probe
pm_request_idle(dev);
if (dev->parent)
pm_runtime_put(dev->parent);
pm_runtime_put_suppliers(dev);
return ret;
}really_probe(dev, drv)
实际调用really_probe(dev, drv)
实现,其函数主要内容如下(只保留probe部分)
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = -EPROBE_DEFER;
int local_trigger_count = atomic_read(&deferred_trigger_count);
bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
!drv->suppress_bind_attrs;
...
re_probe:
dev->driver = drv;
...
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev); //调用device_driver的probe
if (ret)
goto probe_failed;
}
if (device_add_groups(dev, drv->dev_groups)) {
dev_err(dev, "device_add_groups() failed\n");
goto dev_groups_failed;
}
...
}
在上面内容中,最终调用的是 drv->probe(dev)
函数,即device_driver的probe。对于本SPI驱动,调用的是amba_probe()
,在2.2.2小节开始曾说明,如下
int amba_driver_register(struct amba_driver *drv)
{
if (!drv->probe)
return -EINVAL;
drv->drv.bus = &amba_bustype; //在1.1小节给出内容
drv->drv.probe = amba_probe; //decive_driver的probe函数,要区分amba_dirver的probe函数
drv->drv.remove = amba_remove;
drv->drv.shutdown = amba_shutdown;
return driver_register(&drv->drv); //调用此函数注册驱动
}
amba_probe()
函数主要内容如下所示
static int amba_probe(struct device *dev)
{
struct amba_device *pcdev = to_amba_device(dev);
struct amba_driver *pcdrv = to_amba_driver(dev->driver);
const struct amba_id *id = amba_lookup(pcdrv->id_table, pcdev);
int ret;
do {
...
ret = pcdrv->probe(pcdev, id); //调用amba_dirver的probe函数
if (ret == 0)
break;
...
} while (0);
return ret;
}
上述内容中最终调用的是amba_dirver的probe函数,即pl022_probe()
函数,在2.2.1小节曾说明,如下
static struct amba_driver pl022_driver = {
.drv = {
.name = "ssp-pl022",
.pm = &pl022_dev_pm_ops,
},
.id_table = pl022_ids,
.probe = pl022_probe, //amba_driver的probe函数
.remove = pl022_remove,
};
到此,整个驱动完成注册操作,程序就跟踪到到spi-pl022.c
文件中,pl022_probe()
函数就进行spi框架调用、硬件的操作等将在后续介绍。
2.2.3 注册总结
将上2.2.2小节中源码调用的主要流程总结如下
2.3 probe函数分析
当驱动与设备树上的设备匹配成功后,将执行pl022_probe
函数。匹配规则的是compatible
以及arm,primecell-periphid
属性。
2.3.1 相关数据结构
在阐述函数前先说明几个总要的数据结构(只针对pl022控制器)。
struct pl022_ssp_controller {
u16 bus_id; //总线id
u8 num_chipselect; //片选数量 决定该控制器下面挂接多少个SPI设备
//DMA相关
u8 enable_dma:1;
bool (*dma_filter)(struct dma_chan *chan, void *filter_param);
void *dma_rx_param;
void *dma_tx_param;
int autosuspend_delay;
bool rt; //realtime
int *chipselects; //片选数组
};
pl022私有数据的结构体为
struct pl022 {
struct amba_device *adev;
struct vendor_data *vendor; //厂商各自的数据
resource_size_t phybase; //真实物理地址
void __iomem *virtbase; //映射后的虚拟地址
struct clk *clk;
struct spi_master *master; //spi_master(重要) spi_master结构体为spi_controller结构体
struct pl022_ssp_controller *master_info;
/* Message per-transfer pump */
struct tasklet_struct pump_transfers;
struct spi_message *cur_msg; //多个spi_transfer的封装,执行由spi_transfer表示的一串数组传输请求
struct spi_transfer *cur_transfer; //struct spi_transfer是对一次完整的数据传输的描述。
struct chip_data *cur_chip;
bool next_msg_cs_active;
void *tx;
void *tx_end;
void *rx;
void *rx_end;
enum ssp_reading read;
enum ssp_writing write;
u32 exp_fifo_level;
enum ssp_rx_level_trig rx_lev_trig;
enum ssp_tx_level_trig tx_lev_trig;
/* DMA settings */
#ifdef CONFIG_DMA_ENGINE //dma相关
struct dma_chan *dma_rx_channel;
struct dma_chan *dma_tx_channel;
struct sg_table sgt_rx;
struct sg_table sgt_tx;
char *dummypage;
bool dma_running;
#endif
int cur_cs;
int *chipselects;
};
vendor_data结构体为
struct vendor_data {
int fifodepth; //fifo深度
int max_bpw; //最大bits per word
bool unidir;
bool extended_cr;
bool pl023;
bool loopback;
bool internal_cs_ctrl; //是否支持内部片选寄存器
};
2.3.2 函数分析
从设备数获取struct pl022_ssp_controller *platform_info
信息,调用pl022_platform_data_dt_get
函数实现
static struct pl022_ssp_controller *
pl022_platform_data_dt_get(struct device *dev)
{
struct device_node *np = dev->of_node;
struct pl022_ssp_controller *pd;
u32 tmp = 0;
//...
pd = devm_kzalloc(dev, sizeof(struct pl022_ssp_controller), GFP_KERNEL); //开辟内存
if (!pd)
return NULL;
pd->bus_id = -1;
pd->enable_dma = 1;
of_property_read_u32(np, "num-cs", &tmp);
pd->num_chipselect = tmp;
of_property_read_u32(np, "pl022,autosuspend-delay",
&pd->autosuspend_delay);
pd->rt = of_property_read_bool(np, "pl022,rt");
return pd;
}
从上述函数内容可以看出,根据设备树的节点信息给pl022_ssp_controller
赋值。需要注意的是,在节点上"num-cs"
属性必须赋值,也就是至少有一个片选。
调用spi_alloc_master
函数来开辟一个spi_master
结构体,实际调用函数__spi_alloc_controller
来实现,在drivers/spi/spi.c
位置,其主要内容为
struct spi_controller *__spi_alloc_controller(struct device *dev,
unsigned int size, bool slave)
{
struct spi_controller *ctlr;
//...
ctlr = kzalloc(size + ctlr_size, GFP_KERNEL); //申请空间
if (!ctlr)
return NULL;
device_initialize(&ctlr->dev); //初始化新创建的逻辑设备
ctlr->bus_num = -1;
ctlr->num_chipselect = 1;
ctlr->slave = slave;
//...
return ctlr;
}
开辟完结构体后,对结构的成员进行例化
master->bus_num = platform_info->bus_id;
master->num_chipselect = num_cs;
master->cleanup = pl022_cleanup;
master->setup = pl022_setup; //注册spi_controller是会被调用
master->auto_runtime_pm = true;
master->transfer_one_message = pl022_transfer_one_message; //spi发送数据,后面会详解
master->unprepare_transfer_hardware = pl022_unprepare_transfer_hardware;
master->rt = platform_info->rt;
master->dev.of_node = dev->of_node;
//设置支持的模式
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP;
设置片选资源,本驱动中通过三种方法来设置片选:
-
通过设备树获取platform_info信息,将结果赋值给pl022->chipselects[i]
pl022->chipselects[i] = platform_info->chipselects[i];
-
通过internal_cs_ctrl标志位,来判断是否支持内部片选控制,赋值给pl022->chipselects[i]
pl022->chipselects[i] = i;
-
通过设备树节点
cs-gpios
属性来赋值给pl022->chipselects[i]cs_gpio = of_get_named_gpio(np, "cs-gpios", i); pl022->chipselects[i] = cs_gpio;
内存地址设置:
-
为amba设备申请内存区域
status = amba_request_regions(adev, NULL);
-
物理地址赋值
pl022->phybase = adev->res.start;
-
获取虚拟地址
pl022->virtbase = devm_ioremap(dev, adev->res.start,resource_size(&adev->res));
初始化pump_transfers的tasklet,并提供tasklet函数
tasklet_init(&pl022->pump_transfers, pump_transfers,(unsigned long)pl022);
pump_transfers函数会在中断或者DMA传输过程中使用,调用tasklet_schedule(&pl022->pump_transfers)
使用,其函数的主要内容为
static void pump_transfers(unsigned long data)
{
struct pl022 *pl022 = (struct pl022 *) data;
struct spi_message *message = NULL;
struct spi_transfer *transfer = NULL;
struct spi_transfer *previous = NULL;
//...
/* Delay if requested at end of transfer before CS change */
if (message->state == STATE_RUNNING) {
previous = list_entry(transfer->transfer_list.prev,
struct spi_transfer,
transfer_list);
if (previous->delay_usecs)
/*
* FIXME: This runs in interrupt context.
* Is this really smart?
*/
udelay(previous->delay_usecs);
/* Reselect chip select only if cs_change was requested */
if (previous->cs_change)
pl022_cs_control(pl022, SSP_CHIP_SELECT);
} else {
/* STATE_START */
message->state = STATE_RUNNING;
}
if (set_up_next_transfer(pl022, transfer)) { //切换到下一个transfer
message->state = STATE_ERROR;
message->status = -EIO;
giveback(pl022);
return;
}
//...
}
申请中断,并提供中断函数
status = devm_request_irq(dev, adev->irq[0], pl022_interrupt_handler,0, "pl022", pl022);
if (status < 0) {
dev_err(&adev->dev, "probe - cannot get IRQ (%d)\n", status);
goto err_no_irq;
}
注册spi_master,devm_spi_register_master(&adev->dev, master)
,实际调用的是devm_spi_register_controller
函数
int devm_spi_register_controller(struct device *dev,
struct spi_controller *ctlr)
{
struct spi_controller **ptr;
int ret;
//...
ret = spi_register_controller(ctlr);
//...
return ret;
}
其注册流程总结为
注册流程的最后会调用到setup函数,本驱动中为pl022_setup
函数(函数太长不附程序),主要工作为以下几点:
-
通过pl022_config_chip结构体来初始化硬件参数,比如接口协议类型(SPI/SSI)、rx/tx的FIFO参数等;
static const struct pl022_config_chip pl022_default_chip_info = { .com_mode = POLLING_TRANSFER, //传输方式 轮询 .iface = SSP_INTERFACE_MOTOROLA_SPI, //SPI协议 .hierarchy = SSP_SLAVE, .slave_tx_disable = DO_NOT_DRIVE_TX, .rx_lev_trig = SSP_RX_1_OR_MORE_ELEM, .tx_lev_trig = SSP_TX_1_OR_MORE_EMPTY_LOC, .ctrl_len = SSP_BITS_8, .wait_state = SSP_MWIRE_WAIT_ZERO, .duplex = SSP_MICROWIRE_CHANNEL_FULL_DUPLEX, .cs_control = null_cs_control, };
-
计算传入频率是否有效;
本传入频率指的是SPI主设备传输的时钟频率,比如在使用测试工具spidev_test时,
-v
指定的频率。由于
-v
指定的频率时,需要计算分频参数以及判断传入的频率是否有效,因此通过函数以下函数实现static int calculate_effective_freq(struct pl022 *pl022, int freq, struct ssp_clock_params * clk_freq)
参数@freq:传入频率
参数@clk_freq:输出参数包括分频参数
-
将传入的mode参数写进寄存器
传入的mode参数指定是传输mode0-3、回环模式
SSP_WRITE_BITS(chip->cr0, tmp, SSP_CR0_MASK_SPO, 6); SSP_WRITE_BITS(chip->cr0, tmp, SSP_CR0_MASK_SPH, 7); SSP_WRITE_BITS(chip->cr1, tmp, SSP_CR1_MASK_LBM, 0);
-
设置主从模式,并disable SPI
默认情况下设置为
chip_info_dt.hierarchy = SSP_MASTER
主机模式;设置完参数后,disable模块。当有消息进行传输时,则重新使能模块;消息传输完,disable模块。
在前面内容中,为开辟的spi_master
结构体成员例化,其中重要的一个函数为pl022_transfer_one_message
,此函数用于传输数据。
在本驱动中,有三种传输方式:
- polling(轮询)
- DMA传输
- 中断传输
其中polling以及DMA传输过程在pl022_transfer_one_message
函数中调用,通过xfer_type
标志位来区分传输方式,代码为
if (pl022->cur_chip->xfer_type == POLLING_TRANSFER)
do_polling_transfer(pl022);
else
do_interrupt_dma_transfer(pl022);
pl022_transfer_one_message
函数调用使用了内核的kthread_work机制来实现管理。
kthread_work机制其说明参考https://blog.csdn.net/zhoutaopower/article/details/100032048,主要使用步骤
1.初始化worker kthread_init_worker(&ctlr->kworker); 2.创建一个内核线程来处理 work ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker, "%s", dev_name(&ctlr->dev)) 3.初始化kthread_work,并提供工作函数 kthread_init_work(&ctlr->pump_messages, spi_pump_messages) 4.启动work kthread_queue_work(&ctlr->kworker, &ctlr->pum_messages)
pl022_transfer_one_message
函数被调用的过程总结为
pl022_transfer_one_message
函数实际调用到do_polling_transfer
或者do_interrupt_dma_transfer
,本节分析do_polling_transfer
函数。
根据pl022_transfer_one_message
函数内容,其实际与硬件相关的数据传输调用的时static void readwriter(struct pl022 *pl022)
。由于此时的SPI模块的FIFO状态不清楚且,模块是disable状态,在调用readwriter
函数传输前,使用函数flush(pl022)
清空FIFO,然后使能SPI模块。
在readwriter
函数中,向数据寄存器进行读写操作
//读数据
*(u8 *) (pl022->rx) = readw(SSP_DR(pl022->virtbase)) & 0xFFU
//写数据
writew(*(u8 *) (pl022->tx), SSP_DR(pl022->virtbase))
pl022_transfer_one_message
函数内容总结为
2.3.3 probe函数总结
上一小节详细说明了probe函数的内容,主要步骤总结为
2.3.4 spi传输速率设置
在说明pl022的SSP控制器的传输速率前,先补充两个概念:SPI模块时钟以及SPI传输速率。
-
**SPI模块时钟:**是通过芯片的PLL模块得到,在控制器的设备树上设置,如pl022的SSP控制器:
clock-names = "spiclk", "apb_pclk"; clocks = <&sysclk1>, <&sysclk1>;
在控制器的驱动中,通过linux时钟子系统的相关API获取时钟资源。
-
**SPI传输速率:**是指SPI控制器与SPI设备之间通信的传输速率,在SPI模块时钟上通过设置时钟分频系数得到。
在一般的应用场景中,首先设置SPI传输速率,然后通过函数选择合适的分频系数。
SPI传输速率设置有两种方法:第一种在SPI设备的设备树上设置,第二种在测试应用程序中设置。
在 pl022的SSP控制器驱动中,通过calculate_effective_freq
函数,计算合适的分频系数,max_speed_hz
代表SPI设备传输速率。
3 SPI测试
在linux系统上测试SPI驱动时,需要的工作为两个部分:第一是在文件系统的/dev目录创建一个设备;第二是准备能够调用spi驱动的应用程序(或者说是工具)。幸运的是,在linux kernel下都有这些资源供测试人员使用。
3.1 创建SPI设备
3.1.1 SPI设备信息
在linux的derivers/spi/spidev.c
,提供了一个spidev设备,当向文件系统添加设备,并操作此设备就能完成对spi驱动调用。此设备匹配属性为
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "rohm,dh2228fv" },
{ .compatible = "lineartechnology,ltc2488" },
{ .compatible = "ge,achc" },
{ .compatible = "semtech,sx1301" },
{ .compatible = "lwn,bk4" },
{ .compatible = "dh,dhcom-board" },
{ .compatible = "menlo,m53cpld" },
{},
};
在spi驱动的设备树节点下添加spidev节点,完整设备树信息为
spi0: spi@55558888 {
compatible = "arm,pl022", "arm,primecell";
reg = <0x55558888 0x1000>;
interrupt-parent = <&gic>;
interrupts = <0 120 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "spiclk", "apb_pclk";
clocks = <&sysclk1>, <&sysclk1>;
arm,primecell-periphid = <0x00041022>;
num-cs = <1>;
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
spidev@0 {
compatible = "rohm,dh2228fv"; // "spidev";
spi-max-frequency = <25000000>; // <20000000>;
reg = <0>;
};
};
在编译spidev时需要在menuconfig中添加如下信息
3.1.2 SPI设备程序简析
spidev.c
的主要流程为
在第二步中注册字符设备时,提供的ops的内容如下
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
.write = spidev_write,
.read = spidev_read,
.unlocked_ioctl = spidev_ioctl, //用于传输数据
.compat_ioctl = spidev_compat_ioctl,
.open = spidev_open,
.release = spidev_release,
.llseek = no_llseek,
};
3.2 SPI测试工具
在linux的tools/spi/spidev_test.c
文件中,提供了spi的应用层测试工具。此工具需要编译,在文件目录下输入编译命令
make CC=arm-linux-gnueabi-gcc LD=arm-linux-gnueabi-ld
或
arm-linux-gnueabi-gcc -o spidev_test spidev_test.c -lpthread -static
编译后,会在目录下生成spidev_test
可执行文件,将其拷贝到文件系统中,即可使用。
输入./spidev_test -h
可查看工具使用说明
注释:简要说明参数信息
-D:指定spi设备
-s:指定传输速率
-H -O:设置spi传输模式
-v:显示传输数据
-p:发送数据(字符串形式)
-l: 回环模式
测试命令
./spidev_test -D /dev/spidev0.0 -v -s 5000000 -p 1
更多推荐
所有评论(0)