1. 程式人生 > >MT7688 坑爹的 SPI Master 半雙工全雙工問題

MT7688 坑爹的 SPI Master 半雙工全雙工問題

MTK的東西便宜是真的便宜,好用也相對比較好用,但是總有那麼幾個地方,讓人用著心裡就窩火,就MT76x8來說,第一個窩火的地方就是啟動跳線選擇,非得把串列埠用作啟動跳線,導致除錯起來非常麻煩,第二個就是本文要說的SPI問題,此問題分析和測試了很久,主要是關於spi 半雙工和全雙工的問題。

首先,來看下datesheet關於SPI Master的描述:

一共就這麼幾個暫存器:

關於半雙工和全雙工的描述在SPI_MASTER的BIT10

僅僅只有more_buf_mode=1時,全雙工才有效;

關於more_buf_mode的描述如下:

即當more_buf_mode = 0時,spi控制器的傳輸資料域最多有2個位元組,等於1時,最多有8個位元組;此處為什麼寫傳輸資料域,不是最多傳送位元組數,繼續看關於more_buf_mode的描述

等於0,即只能工作於半雙工模式;等於1,可以選擇半雙工,也可以全雙工;此處我們只分析1的情況,因為0傳輸的資料是在太少了;

等於1 及 半雙工時:SPI_OP_ADDR 暫存器也用作了資料暫存器,加上D0-D8幾個,一共9個暫存器,因此最多一次傳送36個位元組,接收時將迴圈覆蓋D0-D7;

等於1 及 全雙工時,D0-D3用於傳送,D4-D7用於接收;

以上文件關於暫存器及功能的描述,非常正常,看不出什麼問題所在。

下面,將結合程式碼以及邏輯分析儀來繼續深入查探虛實:

SPI Master驅動程式碼位於drivers/spi/mt-7621.c

static int mt7621_spi_transfer_one_message(struct spi_master *master,
					   struct spi_message *m)
{
	struct spi_device *spi = m->spi;
	int cs = spi->chip_select;

	return mt7621_spi_transfer_half_duplex(master, m);
}

程式碼中只實現了半雙工模式,而我們現在是要分析全雙工,因此需要修改驅動,配置為全雙工,MT7688的SPI控制器只有一個,但是有2個片選,SPI_CS0用於系統SPI flash,這個我們不能動;另一個SPI_CS1 我們可以用來測試全雙工;

兩個片選,複用一個控制器,複用一個驅動,因此我們可以根據片選的情況來確定傳輸方式,給CS1配置為全雙工傳輸;

static int mt7621_spi_transfer_one_message(struct spi_master *master,
					   struct spi_message *m)
{
	struct spi_device *spi = m->spi;
	int cs = spi->chip_select;
	if(cs == 0)
		return mt7621_spi_transfer_half_duplex(master, m);
	if(cs == 1)
		return mt7621_spi_transfer_full_duplex(master, m);
}

具體的full_deplex函式就不貼了,其實主要就是修改SPI_MASTER的BIT10,重寫一個reset函式用於full_deplex就行了,還有就是注意full_deplex 的if (WARN_ON(rx_len > 16)) ,一次傳輸長度的判斷,全雙工應該是最多16個位元組

static void mt7621_spi_reset_full(struct mt7621_spi *rs)
{
	u32 master = mt7621_spi_read(rs, MT7621_SPI_MASTER);

	master |= 7 << 29;
	master |= 1 << 2;
	//master &= ~(1 << 10);
        master |= (1 << 10);

	mt7621_spi_write(rs, MT7621_SPI_MASTER, master);
}

至此,驅動已沒什麼好分析的了。

最後就剩下SPI應用層使用了,有兩種方式:

第一,直接write;第二,ioctl方式

我想既然是全雙工,肯定是想要得到回覆的資料了,因此選用ioctl方式,其主要就是這麼一個結構體

struct spi_ioc_transfer tr ={
        .tx_buf = (unsigned long) TxBuf,
	.rx_buf = (unsigned long) RxBuf,
	.len =len,
	.delay_usecs = delay,
};

txbuf和rxbuf的長度要相等,len為單個tx或者rx的長度,delay為兩次msg傳輸的間隔

可是執行後,通過邏輯分析儀抓到的波形,跟傳送的資料卻對不上,此處不便貼邏輯分析儀的圖,以文字方式描述一下,

其區別在於,在每傳送的位元組後面都多發了個0x00,

這個0x00明顯不是應用的資料,看驅動,也沒有發現主動會多發資料的地方,那麼這應該就是spi master自己發出去的;

換種方式再次驗證一下猜想,將rx_buf = NULL,即只發不讀,相當於於直接write,此後再抓波形,發現0x00沒有了;

應用換成直接write,抓到的波形跟rx_buf=NULL的結果一樣;

到此我們可以肯定多發的0x00是控制器自己發出的;

再來一次確定性的驗證:應用直接read,只讀資料我們看看有沒有波形,結果抓到的資料傳送全部是0x00,這個00肯定就是控制器為了讀取spi的資料,自動傳送的,這是由spi的傳輸機制來決定的,在SCK的跳變處傳送或者讀取資料;

到此,我們可以看出MT7688的SPI控制器的“特殊”之處。

當然這個特點,對於使用LCD等只需要接受資料的slave來說,沒有影響;但是對於 其他有雙向資料互動的裝置,將導致資料錯亂;剛好我也是因為在使用spi加密晶片時,發現此問題的;

對於雙向互動的裝置來說,MT7688只能使用spi-gpio來模擬了,其限制在於時鐘速度沒有硬體SPI快,只適合低速裝置。

總結,此問題搞了幾天,也算是一個教訓,光看文件和程式碼是看不出問題的,通過邏輯分析儀直接就可以看出,再結合程式碼測試,定位問題所在。因此結合各種除錯工具,可以加快分析問題的速度。