Linux下spi驱动分析与测试【详细流程】

news/2024/11/25 23:42:27/

驱动是基于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;
}

在上面的内容中,主要进行了两个工作:

  1. driver与device匹配
  2. 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;

设置片选资源,本驱动中通过三种方法来设置片选:

  1. 通过设备树获取platform_info信息,将结果赋值给pl022->chipselects[i]

    pl022->chipselects[i] = platform_info->chipselects[i];
    
  2. 通过internal_cs_ctrl标志位,来判断是否支持内部片选控制,赋值给pl022->chipselects[i]

    pl022->chipselects[i] = i;
    
  3. 通过设备树节点cs-gpios属性来赋值给pl022->chipselects[i]

    cs_gpio = of_get_named_gpio(np, "cs-gpios", i);
    pl022->chipselects[i] = cs_gpio;
    

内存地址设置:

  1. 为amba设备申请内存区域

    status = amba_request_regions(adev, NULL);
    
  2. 物理地址赋值

    pl022->phybase = adev->res.start;
    
  3. 获取虚拟地址

    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函数(函数太长不附程序),主要工作为以下几点:

  1. 通过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,
    };
    
  2. 计算传入频率是否有效;

    本传入频率指的是SPI主设备传输的时钟频率,比如在使用测试工具spidev_test时,-v指定的频率。

    由于-v指定的频率时,需要计算分频参数以及判断传入的频率是否有效,因此通过函数以下函数实现

    static int calculate_effective_freq(struct pl022 *pl022, int freq, structssp_clock_params * clk_freq)
    

    参数@freq:传入频率

    参数@clk_freq:输出参数包括分频参数

  3. 将传入的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);
    
  4. 设置主从模式,并disable SPI

    默认情况下设置为chip_info_dt.hierarchy = SSP_MASTER主机模式;

    设置完参数后,disable模块。当有消息进行传输时,则重新使能模块;消息传输完,disable模块。


在前面内容中,为开辟的spi_master结构体成员例化,其中重要的一个函数为pl022_transfer_one_message,此函数用于传输数据。

在本驱动中,有三种传输方式:

  1. polling(轮询)
  2. DMA传输
  3. 中断传输

其中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

http://www.ppmy.cn/news/292707.html

相关文章

YAMAHA 钢琴型号与年代对应表

YAMAHA不同时代对应的编号和主要型号 发行年 制造编号 主要系列型号 1946~ 40000 100 200 300 1954~ 50000 U1a U1b U1c U3a 1959~ 90000 100D u2a ucb u3b u3c u3D 1965~ 300000 U1E U2C U3E 1970~ 820000 U1F U2F U3F U5C U7B 1971~ 980000 U1G U2G U3G 1972~ 115…

Linux内核移植入门

文章目录 基本概念内核源码目录结构内核配置主目录Makefile各子目录Makefile如何配置内核?1. 配置仓库选取2.交叉编译器的修改3.体系结构体的选择4.修改配置文件 内核编译编译结果:几种linux内核文件的区别开发板上U-Boot启动linux内核内核Kconfig语法使用1.make menuconfig 是…

Linux内核开发人员考虑剔除对更多老旧平台的处理器支持

随着 Linux 5.10 走入长期支持&#xff08;LTS&#xff09;&#xff0c;内核开发人员也对未来五年的平台支持展开了探讨&#xff0c;比如剔除干线内核项目中的大量旧款 CPU 的支持。 Phoronix 援引 Arnd Bergmann 的话称&#xff0c;包括 ARM 在内的缺乏生命迹象的老旧 CPU 架构…

联想U300 做的最漂亮的笔记本比苹果的Mac Air还漂亮的本

【曝光多图】联想U300 做的最漂亮的笔记本比苹果的Mac Air还漂亮的本 联想今年真的很给力啊~发布了彪悍的小Y的新一代&#xff0c;现在又推出UltraBook U300 U300S U400等等&#xff0c;网上已经是消息满天飞了&#xff0c;所以呢&#xff0c;我就来汇总一下&#xff0c;顺便抖…

U300 超高频读写器 - 固定式rfid读写器

超高频rfid读写器厂家专久智能提供的基于Impinj E710芯片制成的U300固定式rfid读写器&#xff0c;采用 Android 11 操作系统、四核 2.0GHz CPU&#xff0c;数据处理能力强大。 此款U300 超高频固定式读写器支持6dBic&#xff0c;9dBic 等多种天线&#xff0c;最大群读速率可达 …

四种经典的知识变现盈利模式,看你适合哪一种

哈喽&#xff0c;大家好&#xff0c;我是海哥&#xff0c;知识付费变现创业教练&#xff0c;教育公司培训总监&#xff0c;从事知识付费变现咨询10年&#xff0c;已助力3000人实现知识付费变现。 知识变现有四种典型的盈利模式&#xff0c;看看你合适哪一种&#xff1f; 模式1&…

Unity如何设计一个战斗系统

战斗系统的基本原理 在游戏中&#xff0c;战斗系统的基本原理是通过计算双方的属性和技能等信息&#xff0c;来模拟双方的战斗过程。在战斗过程中&#xff0c;玩家需要根据自己的策略和技能来进行操作&#xff0c;以便战胜对手。在战斗过程中&#xff0c;需要考虑到双方的攻击…

Vue 中的数据请求如何进行拦截与错误处理

Vue 中的数据请求拦截与错误处理 在 Vue.js 中&#xff0c;我们经常需要向后端服务器发送数据请求&#xff0c;以获取或提交数据。在这个过程中&#xff0c;我们可能会遇到一些问题&#xff0c;例如无效的请求参数、网络连接错误、服务器错误等。为了更好地处理这些问题&#…