imx6 通过移植XRM117x(SPI转串口)对Linux中的SPI驱动框架进行分析

 

最近分析了一下Linux 中的SPI驱动框架,将自己的理解总结一下,不足之处还请斧正!

1、SPI通信基础知识

    SPI(Serial Peripheral Interface)是一种串行(一次发送1bit数据),同步(使用同一个时钟,时钟信号有SPI的Master发出),全双工(收发能够同时进行)通信接口,速度可达到十几Mbps,SPI通信分为主从设备,在主设备(Master)上可挂接多个从设备(Slave),Master通过CS片选信号决定与哪个具体的Slave进行通信
    SPI使用4线CS(片选线)、CLK(时钟线)、MISO(Master in and Slave out)与MOSI(Master out and Slave in)
    SPI的4种工作模式:工作模式描述是SPI数据传输的时序问题,由时钟极性(CPOL---Clock Polarity)和时钟相位(CPHA---Clock Phase)控制,CPOL表示CLK空闲时电平的高低,CPHA表示数据传输发生在CLK的上升沿还是下降沿,通过CPOL和CPHA组合成4种工作状态,例如模式0:CPOL : CPHA(0:0)表示CLK空闲时为低电平,并在时钟上升沿进行数据传输(具体时序图可自行百度)


2、SPI驱动分析(Linux Kernel Version 3.14.52,SoC为freescale imx6) 

    在Linux中将其分为3层:
    第一层:SPI Core层,由Linux Kernel维护者进行编写与维护,对SPI通信的抽象,为Master和Slave提供接口,将Master和Slave分离开,Master不需要操心将来会挂载哪个Slave,Slave也不需要知道自己会被挂载到谁上
    第二层:SPI Master层,由SoC厂商根据SoC中的SPI Master特性实例化SPI Core层提供的接口,实现对Master进行的硬件操作,并注册到SPI总线上
    第三层:SPI Device层(或者SPI Slave层),具体SPI Slave设备的初始化等工作,例如我使用的XRM117X,通过调用SPI Core提供的接口实现Slave 和Master的对接工作.

来一张SPI驱动框架思维导图(嗯!图片有点大,实际调用过程其实并没有那么复杂,不想看图的可以看后面的代码分析)
这里有个pdf的文档  https://download.csdn.net/download/qq_37809296/10914161

注:本思维导图仅分析SPI相关的部分,和Linux驱动框架相关的未进行分析

注:结合上面的思维导图和接下的代码分析可能更快理解整个系统过程


分析主要根据前面所说的三层进行:分析过程请注意参数的初始化的传递过程

第一层SPI Core
1、SPI Core的注册

// 文件位置 kernel/driver/spi/spi.c
// 去掉了一些安全检查等代码
static int __init spi_init(void)
{
	int	status;
	buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); // buf为全局变量在传输过程(spi_write_then_read)中会用到如

	status = bus_register(&spi_bus_type);// 总线的注册,关系到Linux中的驱动框架,没有进行分析

	status = class_register(&spi_master_class); //类的注册,所有注册到SPI总线上的Master属于这个class,在/sys/class/spi_master可以看到所有的SPI master

	return 0;
}
postcore_initcall(spi_init); // 决定了调用顺序,kernel调用init是分等级调用的,没有进行分析

struct bus_type spi_bus_type = {
	.name		= "spi",
	.dev_groups	= spi_dev_groups,
	.match		= spi_match_device,
	.uevent		= spi_uevent,
	.pm		= &spi_pm,
};

static struct class spi_master_class = {
	.name		= "spi_master",
	.owner		= THIS_MODULE,
	.dev_release	= spi_master_release,
};

    SPI核心层的注册就这么多,主要是注册了一个SPI 总线,和注册一个SPI_master的类,之后的匹配过程就交到了总线上

 



第二层 SPI Master相关的注册过程

注:我使用的是imx6的板子

// 代码位置 Spi-imx.c (drivers\spi)

// 首先将master注册为platform设备,主要是用来匹配设备树中的device,然后执行porbe函数
// 和platform总线相关暂时不分析,只需要知道他会执行 .probe = spi_imx_probe, 就可以了
static struct platform_driver spi_imx_driver = {
	.driver = {
		   .name = DRIVER_NAME,
		   .owner = THIS_MODULE,
		   .of_match_table = spi_imx_dt_ids,
		   .pm = IMX_SPI_PM,
	},
	.id_table = spi_imx_devtype,
	.probe = spi_imx_probe,
	.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);// init时调用

经过platform 总线的match之后进入 probe函数

// 代码位置 Spi-imx.c (drivers\spi)
// 去除一些安全检查和无关信息
static int spi_imx_probe(struct platform_device *pdev)
{
// 从设备树中查找fsl,spi-num-chipselects ,表示在该Master中挂载了多少个Slave设备
	ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);
// 调用spi core提供的接口 分配了两个结构体空间(struct spi_master + struct spi_imx_data)
// spi_master 是spi core提供的表示一个master ,spi_imx_data对master的补充作用
// spi_master 放在前面, spi_imx_data紧随其后,所以我们可以从spi_master得到spi_imx_data
	master = spi_alloc_master(&pdev->dev,sizeof(struct spi_imx_data) + sizeof(int) * num_cs);

	platform_set_drvdata(pdev, master);

	master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
	master->bus_num = pdev->id;
	master->num_chipselect = num_cs;

// 前面说的 从spi_master得到spi_imx_data
	spi_imx = spi_master_get_devdata(master);
// 结构体的相互绑定
	spi_imx->bitbang.master = master;

// 设置片选信号引脚,并向内核申请
	for (i = 0; i < master->num_chipselect; i++) {
		int cs_gpio = of_get_named_gpio(np, "cs-gpios", i);
		if (!gpio_is_valid(cs_gpio) && mxc_platform_info)
			cs_gpio = mxc_platform_info->chipselect[i];

		spi_imx->chipselect[i] = cs_gpio;
		if (!gpio_is_valid(cs_gpio))
			continue;

		ret = devm_gpio_request(&pdev->dev, spi_imx->chipselect[i],DRIVER_NAME);

	}
// 方法的绑定,将来调用过程都是通过这些函数指针进行的
	spi_imx->bitbang.chipselect = spi_imx_chipselect;
	spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
// 从device传输时会调用的函数
	spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
	spi_imx->bitbang.master->setup = spi_imx_setup;
	spi_imx->bitbang.master->cleanup = spi_imx_cleanup;
	spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message;
	spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message;
// 设置SPI的传输模式,前面说的4中传输模式
	spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;

	init_completion(&spi_imx->xfer_done);
// 对具体硬件操作的函数
	spi_imx->devtype_data = of_id ? of_id->data : (struct spi_imx_devtype_data *) pdev->id_entry->driver_data;
// 获取SPI的地址
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	spi_imx->base = devm_ioremap_resource(&pdev->dev, res);

// 获取中断
	spi_imx->irq = platform_get_irq(pdev, 0);
	ret = devm_request_irq(&pdev->dev, spi_imx->irq, spi_imx_isr, 0, dev_name(&pdev->dev), spi_imx);
// 时钟相关
	spi_imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
	spi_imx->clk_per = devm_clk_get(&pdev->dev, "per");
	ret = clk_prepare_enable(spi_imx->clk_per);
	ret = clk_prepare_enable(spi_imx->clk_ipg);
	spi_imx->spi_clk = clk_get_rate(spi_imx->clk_per);

// && 表示前面为真才执行后面的函数 对DMA进行初始化
	if (spi_imx->devtype_data == &imx51_ecspi_devtype_data&& spi_imx_sdma_init(&pdev->dev, spi_imx, master, res))
// 对spi master进行初始化
	spi_imx->devtype_data->reset(spi_imx);
	spi_imx->devtype_data->intctrl(spi_imx, 0);
	master->dev.of_node = pdev->dev.of_node;

// 重点函数 //SPI Master的注册到SPI 总线上的过程 是SPI Core提供的接口
	ret = spi_bitbang_start(&spi_imx->bitbang);


	clk_disable_unprepare(spi_imx->clk_ipg);
	clk_disable_unprepare(spi_imx->clk_per);
	return ret;

}

由probe引入的重点函数进行spi Master向spi 总线注册的过程 spi_bitbang_start

// 代码位置Spi-bitbang.c (drivers\spi)
// 去除一些非重点信息
int spi_bitbang_start(struct spi_bitbang *bitbang)
{
	struct spi_master *master = bitbang->master;
// 从这里可以看出一定要有chipselect函数
	if (!master || !bitbang->chipselect)
		return -EINVAL;

// 未设置spi传输模式就使用默认的传输模式
	if (!master->mode_bits)
		master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;

// 不能使用master-》transfer 和 master->transfer_one_message函数指针
	if (master->transfer || master->transfer_one_message)
		return -EINVAL;

	master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;
	master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;

// 上层传输时将会调用 // 到时候调用到了这个函数指针我希望你还有点印象
	master->transfer_one_message = spi_bitbang_transfer_one; 

// spi-imx中定义了这个 spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
	if (!bitbang->txrx_bufs) {
        ......
	}

// 重点函数对spi进行注册 spi core提供的接口
	ret = spi_register_master(spi_master_get(master));

	return 0;
}

正式开始向spi core注册spi master调用的是 spi_register_master

// 代码位置  Spi.c (drivers\spi)
int spi_register_master(struct spi_master *master)
{

	struct device		*dev = master->dev.parent;


	status = of_spi_register_master(master);

	if (master->num_chipselect == 0)
		return -EINVAL;
// 设备驱动模型内容
	dev_set_name(&master->dev, "spi%u", master->bus_num);
	status = device_add(&master->dev);

// 调用队列的初始化
	if (master->transfer)
		dev_info(dev, "master is unqueued, this is deprecated\n");
	else {
// 重点函数 ,初始化了传输函数 做个标记 在后面分析
		status = spi_master_initialize_queue(master);
	}
 
// 将master加到spi core维护的链表 spi_master_list //所有的spi master都在这个链表中
// spi core还维护了一个 device链表
	list_add_tail(&master->list, &spi_master_list);

// 使用了设备树方式获取设备信息将不会掉用这里的函数进行匹配
	list_for_each_entry(bi, &board_list, list)
		spi_match_master_to_boardinfo(master, &bi->board_info);

// 重点函数 
    of_register_spi_devices(master);


	/* Register devices from the device tree and ACPI */
	acpi_register_spi_devices(master);

done:
	return status;
}

 对 of_register_spi_devices 的分析

// 代码位置  Spi.c (drivers\spi)
static void of_register_spi_devices(struct spi_master *master)
{
	struct spi_device *spi;
    struct device_node *nc;

// 遍历设备树中spi master 节点下的所有 spi device 
	for_each_available_child_of_node(master->dev.of_node, nc) {
// 重点函数
// SPI从设备的主机端代理之后这个结构体指针将会通过spi总线传递到spi_device驱动中去
		spi = spi_alloc_device(master);

		if (of_modalias_node(nc, spi->modalias,sizeof(spi->modalias)) < 0) 
		rc = of_property_read_u32(nc, "reg", &value);
		spi->chip_select = value;

// 对模式的设置 //所以可以在设备树中直接指定是spi工作模式
		if (of_find_property(nc, "spi-cpha", NULL))
			spi->mode |= SPI_CPHA;
		if (of_find_property(nc, "spi-cpol", NULL))
			spi->mode |= SPI_CPOL;
		if (of_find_property(nc, "spi-cs-high", NULL))
			spi->mode |= SPI_CS_HIGH;
		if (of_find_property(nc, "spi-3wire", NULL))
			spi->mode |= SPI_3WIRE;
		if (of_find_property(nc, "spi-lsb-first", NULL))
			spi->mode |= SPI_LSB_FIRST;

		if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) 
            ......
		if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) 
            ......
		/* Device speed */
		rc = of_property_read_u32(nc, "spi-max-frequency", &value);
            ......
		spi->max_speed_hz = value;

		/* IRQ */
		spi->irq = irq_of_parse_and_map(nc, 0);

		/* Store a pointer to the node in the device structure */
		of_node_get(nc);
		spi->dev.of_node = nc;

		/* Register the new device */
		request_module("%s%s", SPI_MODULE_PREFIX, spi->modalias);

// 重点函数
		rc = spi_add_device(spi);


	}
}

// 代码位置  Spi.c (drivers\spi)
spi_add_device的分析

// 代码位置  Spi.c (drivers\spi)
int spi_add_device(struct spi_device *spi)
{
	static DEFINE_MUTEX(spi_add_lock);
	struct spi_master *master = spi->master;
	struct device *dev = master->dev.parent;
	int status;

	spi_dev_set_name(spi);

	status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);

	if (master->cs_gpios)
		spi->cs_gpio = master->cs_gpios[spi->chip_select];

// 重点函数 对spi master的设置
	status = spi_setup(spi);

	status = device_add(&spi->dev);

}
// 代码位置  Spi.c (drivers\spi)
int spi_setup(struct spi_device *spi)
{

    ......
	if (spi->master->setup)
		status = spi->master->setup(spi); //这个函数指针的初始化在spi master的probe函数中
    ...... 
	return status;
}

到此SPI Master端的初始化就构建成功了,主要注意一下Master中有一个 device的代理,通过master和device的桥梁就是通过这个 struct spi_device 这个结构体的挂接实现的
 


第三层 : spi device层或spi slave层

这里我将不进行XRM117X怎么实现SPI转串口的功能,主要分析XRM117X与imx6通过SPI进行通信的过程

1、注册过程

// 代码位置 Xrm117x.c (drivers\tty\serial)
static int __init xrm117x_init(void)
{
	return spi_register_driver(&xrm117x_spi_uart_driver);
}
module_init(xrm117x_init);
// 代码位置  Spi.c (drivers\spi)
int spi_register_driver(struct spi_driver *sdrv)
{
	sdrv->driver.bus = &spi_bus_type;
	if (sdrv->probe)
		sdrv->driver.probe = spi_drv_probe;
	if (sdrv->remove)
		sdrv->driver.remove = spi_drv_remove;
	if (sdrv->shutdown)
		sdrv->driver.shutdown = spi_drv_shutdown;
	return driver_register(&sdrv->driver); // 驱动模型相关内容,暂时不分析
}

2、对SPI进行读写的过程:主要分析怎么从device中调用到master中的对SPI 控制器具体硬件操作

// 代码位置 Xrm117x.c (drivers\tty\serial)
static u8 xrm117x_port_read(struct uart_port *port, u8 reg)
{
	struct xrm117x_port *s = dev_get_drvdata(port->dev);

	mutex_lock(&s->mutex_bus_access);

// 重点函数,将进行SPI传输
	status = spi_write_then_read(spi_dev, &cmd, 1, &result, 1);

	mutex_unlock(&s->mutex_bus_access);

	return result;
		
}

SPI传输过程 

// 代码位置  Spi.c (drivers\spi)
int spi_write_then_read(struct spi_device *spi,const void *txbuf, unsigned n_tx,void *rxbuf, unsigned n_rx)
{


	struct spi_message	message;
	struct spi_transfer	x[2];


	if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(&lock)) {
		local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx), GFP_KERNEL | GFP_DMA);
	} else {
		local_buf = buf; // 还记的 spi_init中为全局变量buf分配了内存吗?
	}

// 初始化 message //传输都是以message方式传输的,每个message下面都有transfer结构体
	spi_message_init(&message);

	if (n_tx) {
		x[0].len = n_tx;
		spi_message_add_tail(&x[0], &message); //将transfer挂载到message中
	}
	if (n_rx) {
		x[1].len = n_rx;
		spi_message_add_tail(&x[1], &message);
	}

	memcpy(local_buf, txbuf, n_tx);
	x[0].tx_buf = local_buf;
	x[1].rx_buf = local_buf + n_tx;

// 重点函数 进行spi传输
	status = spi_sync(spi, &message);
	if (status == 0)
		memcpy(rxbuf, x[1].rx_buf, n_rx);

	return status;
}

int spi_sync(struct spi_device *spi, struct spi_message *message)
{
// 重点函数 进行spi传输
	return __spi_sync(spi, message, 0);
}


static int __spi_sync(struct spi_device *spi, struct spi_message *message,
		      int bus_locked)
{
// 还记得 master中对spi device的代理吗,在那时已经将spi_device结构体和spi_master绑定在一起了
	struct spi_master *master = spi->master;

// 重点函数
	status = spi_async_locked(spi, message);

	if (status == 0) {
		wait_for_completion(&done);
		status = message->status;
	}

	return status;
}

// 去掉了很多无用信息
int spi_async_locked(struct spi_device *spi, struct spi_message *message)
{
	struct spi_master *master = spi->master;

// 重点函数
	ret = __spi_async(spi, message);

	return ret;

}


static int __spi_async(struct spi_device *spi, struct spi_message *message)
{
	struct spi_master *master = spi->master;

	message->spi = spi;

	trace_spi_message_submit(message);
// 重点函数 
// 你可能很想知道这个函数指针是在哪里初始化的,
// spi.c中搜这个函数试试 spi_master_initialize_queue
// 看下面的分析
	return master->transfer(spi, message);
}


补充:spi_master_initialize_queue中初始化 master->transfer函数指针

// 代码位置  Spi.c (drivers\spi)
// 去除了很多无用的东西
static int spi_master_initialize_queue(struct spi_master *master)
{
	int ret;

	master->transfer = spi_queued_transfer; // 在这里初始化了transfer函数指针//
	if (!master->transfer_one_message)
		master->transfer_one_message = spi_transfer_one_message;

// 主要工作是建立一个工作线程//绑定线程的工作函数
// 请记住这里,后面一段代码中的疑问的答案在这里
	ret = spi_init_queue(master);

	ret = spi_start_queue(master);// 启动工作线程

}

继续具体的传输过程;:

// 代码位置  Spi.c (drivers\spi)
// 去除了很多非主线信息
static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
{
	struct spi_master *master = spi->master;

	list_add_tail(&msg->queue, &master->queue);
// 重点函数 进入工作队列,进行内核线程时的调度
// 执行master->pump_messages这个函数指针
// 那么问题来了,这个函数指针是在哪里初始化的?
	if (!master->busy)
		queue_kthread_work(&master->kworker, &master->pump_messages);

}

解决上面代码中的问题: 线程初始化时线程工作函数的绑定

// 代码位置  Spi.c (drivers\spi)
// 去除了很多信息
static int spi_init_queue(struct spi_master *master)
{
// 没错这就是线程的工作函数,前面一路的调用就到了这个函数中 spi_pump_messages
	init_kthread_work(&master->pump_messages, spi_pump_messages);
}

static void spi_pump_messages(struct kthread_work *work)
{
	struct spi_master *master = container_of(work, struct spi_master, pump_messages);

    ...........
// 去除的代码有点多,主要是线程的一些机制,who care ?
    ...........
	if (!was_busy)
		trace_spi_master_busy(master);

	if (!was_busy && master->prepare_transfer_hardware) {
		ret = master->prepare_transfer_hardware(master);
	}

	trace_spi_message_start(master->cur_msg);

	if (master->prepare_message) {
		ret = master->prepare_message(master, master->cur_msg);
	}

	ret = spi_map_msg(master, master->cur_msg);

// 重点函数 别在问我这个函数指针是在哪里初始化的了
// 还记得 spi_bitbang_start 这个函数吗?
// 不记得就ctl+f搜一下,之前的分析
// 你都能看到这里说明很有耐心呀,马上就到硬件的操作了
	ret = master->transfer_one_message(master, master->cur_msg);

}

spi_bitbang_transfer_one 是怎么调用硬件控制器进行传输的呢

// 代码位置Spi-bitbang.c (drivers\spi)
static int spi_bitbang_transfer_one(struct spi_master *master, struct spi_message *m)
{
	struct spi_bitbang	*bitbang;

	bitbang = spi_master_get_devdata(master);

	cs_change = 1;
	status = 0;
// 对于一个message中有多个transfer的情况,遍历所有transfer
	list_for_each_entry(t, &m->transfers, transfer_list) {
		/* override speed or wordsize? */
		if (t->speed_hz || t->bits_per_word)
			do_setup = 1;

// 是否需要调用控制器的setup函数
		if (do_setup != 0) {
			status = bitbang->setup_transfer(spi, t);
		}
// 对片选信号的操作
		if (cs_change) {
			bitbang->chipselect(spi, BITBANG_CS_ACTIVE);
			ndelay(nsecs);
		}
		cs_change = t->cs_change;

		if (t->len) {
// 重点函数 直接进行了硬件信息传输
// 是不是又忘记在哪里初始化了的
// 所以我还是建议你去下载一下那个思维导图
// 直接告诉你在 spi_imx_probe 这个函数中 // spi-imx.c中
// spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
			status = bitbang->txrx_bufs(spi, t);
		}

		if (cs_change &&!list_is_last(&t->transfer_list, &m->transfers)) {
			ndelay(nsecs);
			bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
			ndelay(nsecs);
		}
	}

	if (!(status == 0 && cs_change)) {
		ndelay(nsecs);
		bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
		ndelay(nsecs);
	}

	spi_finalize_current_message(master);

	return status;
}

到这里了也就是具体硬件的操作了

// 代码位置 Spi-imx.c (drivers\spi)
static int spi_imx_transfer(struct spi_device *spi, struct spi_transfer *transfer)
{

	struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
// 是否通过DMA进行传输?
// imx6在这里的使用SPI的DMA传输是有条件的,需要每次传输大于64字节才会启动DMA进行传输
	if (spi_imx->bitbang.master->can_dma && spi_imx_can_dma(spi_imx->bitbang.master, spi, transfer)) {
		ret = spi_imx_dma_transfer(spi_imx, transfer);
	}
// 一般情况是通过PIO的模式进行传输的
	return spi_imx_pio_transfer(spi, transfer);
}

问题:上面说到imx6要使用SPI的DMA功能必须要求一次transfer大于64字节,但现在的情况是我用的XRM117X转串口,其FIFO最大只有64字节,所以我在这个时候是用不上SPI的DMA功能的,并且发现,每次只传输几个字节,不停的发,占用CPU达50%,因此效率无从谈起,then 有没有人遇到与我相同的困惑,是否能指点一下!!不胜感激!!!!

best regards 
Alee

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值