[置顶] Linux spi字符收发细节

2015/12/03 17:37
本文以飞思卡尔P1020 SOC(powerpc架构),Linux2.6.35内核为依据
这篇文章是关于spi字符收发细节的描述


spi整体框架如下:


其中我主要讲到的是如下红框的内容



1.先说一下这个bitbang_work的由来

在spi设备驱动(主要是设备)的初始化中/drivers/spi/fsl_espi.c 

fsl_espi_probe中调用了spi_bitbang_start,其中注册了队列处理bitbang_work

INIT_WORK(&bitbang->work, bitbang_work);

这就是它的由来了。

2.bitbang_work就实现了上图右侧部分的取message,这个message就是包含该主从设备配置和真实要发送的信息

简单看一下其中的几段代码

1)从message中获取配置数据然后配置到master

跟进去后会发现是设置master的位宽、模式、时钟频率、片选等

			/* init (-1) or override (1) transfer params */
			if (do_setup != 0) {
				if (!setup_transfer) {
					status = -ENOPROTOOPT;
					break;
				}
				status = setup_transfer(spi, t);
				if (status < 0)
					break;
			}

2)设置片选
选择从设备,其实这个地方不是必须的,P1020上这个地方chipselect函数是空,片选是通过配置master的某个寄存器进行选择
具体后续分析
			/* set up default clock polarity, and activate chip;
			 * this implicitly updates clock and spi modes as
			 * previously recorded for this device via setup().
			 * (and also deselects any other chip that might be
			 * selected ...)
			 */
			if (cs_change) {
				bitbang->chipselect(spi, BITBANG_CS_ACTIVE);
				ndelay(nsecs);
			}

3)数据发送

这个就是数据收发的关键

			/* transfer data.  the lower level code handles any
			 * new dma mappings it needs. our caller always gave
			 * us dma-safe buffers.
			 */
			if (t->len) {
				/* REVISIT dma API still needs a designated
				 * DMA_ADDR_INVALID; ~0 might be better.
				 */
				if (!m->is_dma_mapped)
					t->rx_dma = t->tx_dma = 0;
				status = bitbang->txrx_bufs(spi, t);
			}

3.数据收发具体实现

static int fsl_espi_bufs(struct spi_device *spi, struct spi_transfer *t)
{
	struct fsl_espi *fsl_espi;
	u32 word, len, bits_per_word;

	fsl_espi = spi_master_get_devdata(spi->master);

	fsl_espi->tx = t->tx_buf;
	fsl_espi->rx = t->rx_buf;
	bits_per_word = spi->bits_per_word;
	if (t->bits_per_word)
		bits_per_word = t->bits_per_word;
	len = t->len;
	fsl_espi->count = len;

	/* every frame owns one byte */
	out_be32(&fsl_espi->regs->command,
			(spi->chip_select << 30) | (len - 1));
	INIT_COMPLETION(fsl_espi->done);

	/* enable rx ints */
	out_be32(&fsl_espi->regs->mask, SPIM_NE);

	/* transmit word */
	word = fsl_espi->get_tx(fsl_espi);
	out_be32(&fsl_espi->regs->transmit, word);

	wait_for_completion(&fsl_espi->done);

	return t->len;
}
①设置master的command寄存器,要发送的字符长度和从设备的片选

②初始化完成量

③使能收中断

④发送第一个字

⑤完成量等待

这个部分不难理解,重点就是在之后数据的收发
来看一下中断处理函数

irqreturn_t fsl_espi_irq(s32 irq, void *context_data)
{
	struct fsl_espi *fsl_espi = context_data;
	u32 event, rx_data, word;
	int ret;

	/* Get interrupt events(tx/rx) */
	event = in_be32(&fsl_espi->regs->event);

	/* We need handle RX first */
	if (event & SPIE_NE) {
		/* spin until RX is done */
		void *event_ptr = &fsl_espi->regs->event;
		int limit = min(4, fsl_espi->count);

		ret = spin_event_timeout(
			SPIE_RXCNT(event = in_be32(event_ptr)) >=  limit,
			500, 0);
		if (!ret)
			return IRQ_NONE;
		rx_data = in_be32(&fsl_espi->regs->receive);

		if (fsl_espi->rx)
			fsl_espi->get_rx(rx_data, fsl_espi);
	} else {
		/* Clear the events */
		out_be32(&fsl_espi->regs->event, event);
		return IRQ_HANDLED;
	}

	fsl_espi->count -= 4;
	if (fsl_espi->count > 0) {
		if ((event & SPIE_NF) == 0) {
			/* spin until TX is done */
			ret = spin_event_timeout((event =
				in_be32(&fsl_espi->regs->event)) & SPIE_NF,
				500, 0);
			if (!ret)
				return IRQ_NONE;
		}
		word = fsl_espi->get_tx(fsl_espi);
		out_be32(&fsl_espi->regs->transmit, word);
	} else {
		fsl_espi->count = 0;
		/* disable rx ints */
		out_be32(&fsl_espi->regs->mask, 0);
		complete(&fsl_espi->done);
	}

	/* Clear the events */
	out_be32(&fsl_espi->regs->event, event);

	return IRQ_HANDLED;
}

⑥读取中断,判断中断类型

⑦读取数据,保存

⑧判断剩余长度,若没发送完继续发送一个字

⑨已发送完,关中断,返回完成量

⑩清中断,返回


关于这个中断处理容易进入一个误区,就是:

先发送一个字后,从设备回应给主设备时产生的中断

那么按照这个思路分析,如果从设备收到数据后故意不给回应(fpga可以模拟spi时序),那程序会一直等在⑤的地方

导致spi异常死锁...

这种情况是一定不能出现的

正确的机制是:当spi片选中某一从设备,发送第一个字时,片选、时钟、MOSI、MISO会同时有效

通俗一点就是,当我们在发数据的时候spi同时在接收数据(全双工)

[置顶] Linux spi字符收发细节_第1张图片

即使从设备没有发数据,spi的外围电路会将MISO拉高或拉低,同样相当于数据进来了。

那么就很明了了,过程如下:

Ⅰ、使能收中断,发送第一个字,同时收到来的数据

Ⅱ、收中断到来,收取数据

Ⅲ、继续发送剩余数据

Ⅱ、Ⅲ循环

Ⅳ、数据发送完成,关中断,完成量返回





你可能感兴趣的:([置顶] Linux spi字符收发细节)