驱动是基于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_PL022tristate "ARM AMBA PL022 SSP controller"depends on ARM_AMBAdefault y if MACH_U300default y if ARCH_REALVIEWdefault y if INTEGRATOR_IMPD1default y if ARCH_VERSATILEhelpThis selects the ARM(R) AMBA(R) PrimeCell PL022 SSPcontroller. 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>; //periphidnum-cs = <1>; //chipselect numstatus = "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); //绑定deviceif (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);elseret = really_probe(dev, drv); //正真的probepm_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的probeif (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; //总线idu8 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; //realtimeint *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;
#endifint cur_cs;int *chipselects;
};
vendor_data结构体为
struct vendor_data {int fifodepth; //fifo深度int max_bpw; //最大bits per wordbool 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)) { //切换到下一个transfermessage->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, structssp_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);
elsedo_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