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

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

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

  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);
	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;

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

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

  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, struct
    				    ssp_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);
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
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于 Exynos4412 上的 MCP2515 SPI 硬件回环的驱动程序的示例: ```c #include <linux/init.h> #include <linux/module.h> #include <linux/spi/spi.h> static struct spi_device *mcp2515_spi_device; static int mcp2515_loopback_probe(struct spi_device *spi) { // 设置 SPI 设备相关参数 spi->bits_per_word = 8; spi->mode = SPI_MODE_0; spi->max_speed_hz = 10000000; // 初始化 SPI 设备 int ret = spi_setup(spi); if (ret < 0) { dev_err(&spi->dev, "Failed to setup SPI\n"); return ret; } // 注册 SPI 设备 mcp2515_spi_device = spi; ret = spi_register_device(spi); if (ret < 0) { dev_err(&spi->dev, "Failed to register SPI device\n"); return ret; } // 启用硬件回环模式 u8 ctrl_reg; ret = spi_read(spi, &ctrl_reg, sizeof(ctrl_reg)); if (ret < 0) { dev_err(&spi->dev, "Failed to read control register\n"); return ret; } ctrl_reg |= 0x02; // 设置回环模式位 ret = spi_write(spi, &ctrl_reg, sizeof(ctrl_reg)); if (ret < 0) { dev_err(&spi->dev, "Failed to write control register\n"); return ret; } return 0; } static int mcp2515_loopback_remove(struct spi_device *spi) { // 停用硬件回环模式 u8 ctrl_reg; int ret = spi_read(spi, &ctrl_reg, sizeof(ctrl_reg)); if (ret < 0) { dev_err(&spi->dev, "Failed to read control register\n"); return ret; } ctrl_reg &= ~0x02; // 清除回环模式位 ret = spi_write(spi, &ctrl_reg, sizeof(ctrl_reg)); if (ret < 0) { dev_err(&spi->dev, "Failed to write control register\n"); return ret; } // 注销 SPI 设备 spi_unregister_device(spi); return 0; } static struct spi_device_id mcp2515_loopback_id[] = { { "mcp2515", 0 }, { }, }; MODULE_DEVICE_TABLE(spi, mcp2515_loopback_id); static struct spi_driver mcp2515_loopback_driver = { .driver = { .name = "mcp2515-loopback", .owner = THIS_MODULE, }, .probe = mcp2515_loopback_probe, .remove = mcp2515_loopback_remove, .id_table = mcp2515_loopback_id, }; static int __init mcp2515_loopback_init(void) { return spi_register_driver(&mcp2515_loopback_driver); } static void __exit mcp2515_loopback_exit(void) { spi_unregister_driver(&mcp2515_loopback_driver); } module_init(mcp2515_loopback_init); module_exit(mcp2515_loopback_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("MCP2515 SPI Loopback Driver"); ``` 这个驱动程序使用了 Linux 内核提供的 SPI 框架来初始化和控制 SPI 设备。在 `mcp2515_loopback_probe` 函数中,我们设置了 SPI 设备的相关参数,并通过调用 `spi_setup` 来初始化 SPI 设备。然后,我们注册了 SPI 设备,并启用了硬件回环模式。在 `mcp2515_loopback_remove` 函数中,我们停用了硬件回环模式并注销了 SPI 设备。 请注意,这只是一个简单的示例,实际的驱动程序可能需要根据 MCP2515 的规格和功能进行适当的修改和拓展。此外,还需要确保 Linux 内核中已经加载了 SPI 驱动和相关的依赖项。 编译驱动程序并将生成的模块文件加载到系统中。如果一切顺利,你应该能够看到 MCP2515 在硬件回环模式下工作。 请确保在使用该驱动程序之前仔细阅读 MCP2515 的数据手册并了解其寄存器和通信协议的细节。还建议参考 Linux 内核文档和相关的驱动开发文档,以获取更详细的指导和参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值