SPI驅動之子系統架構及重要數據結構

據個人所知,Linux下SPI一直是處於被“忽略”的角色,市場上大部分板子在板級文件裏面都沒有關於SPI的相關代碼(例如,mini2440),而大部分講解驅動的的書籍也沒有專門的一章來講述關於Linux下SPI方面的內容(例如,宋寶華的Linux設備驅動開發詳解)。與I2C相比,SPI就是一個不被重視的“傢伙”,爲什麼?我也不甚瞭解。由於項目需要在UT4412BV01上移植SPI,查閱網絡上幾乎所有的SPI相關資料,都是對S3C2440和S3C6410的SPI驅動分析,而EXYNOS4412卻隻字不提,但仔細一想它們彼此之間是相通的,遂研究一番記於此,以便日後查閱之便。

1. SPI子系統架構詳解

SPI總線上有兩類設備:一類是主控端,通常作爲SOC系統的一個子模塊出現,比如很多嵌入式MPU中都常常包含SPI模塊;一類是受控端,例如一些SPI接口的Flash、傳感器等等。主控端是SPI總線的控制者,通過使用SPI協議主動發起SPI總線上的會話,而受控端則被動接受SPI主控端的指令,並作出相應的響應。

圖還未畫好,之後補上,補上後在一些寫分析,哈哈。。。

2. 重要數據結構

(1)spi_master

struct spi_master用來描述一個SPI主控制器,我們一般不需要自己編寫spi控制器驅動。

/** 
 * 結構體master代表一個SPI接口,或者叫一個SPI主機控制器,
 * 一個接口對應一條SPI總線,master->bus_num則記錄了這個總線號。 
 */
struct spi_master {
	struct device	dev;

	struct list_head list;

	/** 
	 * 總線編號,從零開始。
	 * 系統會用這個值去和系統中board_list鏈表中加入的每一個boardinfo結構中的每一個spi_board_info中的bus_num進行匹配,
	 * (每個boardinfo結構都是一個spi_board_info的集合,每一個spi_board_info都是對應一個SPI(從)設備的描述)
	 * 如果匹配上就說明這個spi_board_info描述的SPI(從)設備是鏈接在此總線上的,因此就會調用spi_new_device去創建一個spi_device。
	 */
	s16			bus_num;

	/* 支持的片選的數量。從設備的片選號不能大於這個數.該值當然不能爲0,否則會註冊失敗 */
	u16			num_chipselect;

	/* some SPI controllers pose alignment requirements on DMAable
	 * buffers; let protocol drivers know about these requirements.
	 */
	u16			dma_alignment;

	/* spi_device.mode flags understood by this controller driver */
	u16			mode_bits;

	/* other constraints relevant to this driver */
	u16			flags;
#define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* can't do full duplex */
#define SPI_MASTER_NO_RX	BIT(1)		/* can't do buffer read */
#define SPI_MASTER_NO_TX	BIT(2)		/* can't do buffer write */

	/* lock and mutex for SPI bus locking */
	spinlock_t		bus_lock_spinlock;
	struct mutex		bus_lock_mutex;

	/* flag indicating that the SPI bus is locked for exclusive use */
	bool			bus_lock_flag;

	int			(*setup)(struct spi_device *spi); //根據spi設備更新硬件配置

	/**
	 * 添加消息到隊列的方法。此函數不可睡眠,其作用只是安排需要的傳送,
	 * 並且在適當的時候(傳送完成或者失敗)調用spi_message中的complete方法,來將結果報告給用戶。
	 */
	int			(*transfer)(struct spi_device *spi, struct spi_message *mesg);

	/*cleanup函數會在spidev_release函數中被調用,spidev_release被登記爲spidev的release函數*/
	void			(*cleanup)(struct spi_device *spi);
};

spi控制器的驅動在kernel3.0.15/arch/arm/mach-exynos/mach-smdk4x12.c中聲明和註冊一個平臺設備,然後在kernel3.0.15/driver/spi下面建立一個平臺驅動。spi_master註冊過程中會掃描kernel3.0.15/arch/arm/mach-exynos/mach-smdk4x12.c中調用spi_register_board_info註冊的信息,爲每一個與本總線編號相同的信息建立一個spi_device。根據Linux內核的驅動模型,註冊在同一總線下的驅動和設備會進行匹配。spi_bus_type總線匹配的依據是名字,這樣當自己編寫的spi_driver和spi_device同名的時候,spi_driver的probe方法就會被用,spi_driver就能看到與自己匹配的spi_device了。

(2)spi_device

struct spi_device用來描述一個SPI從設備。

/**
 * 該結構體用於描述SPI設備,也就是從設備的相關信息。
 * 注意:SPI子系統只支持主模式,也就是說SOC上的SPI只能工作在master模式,
 * 外圍設備只能爲slave模式。
 */
struct spi_device {
	struct device		dev;
	struct spi_master	*master; //對應的控制器指針
	u32			max_speed_hz; //spi傳輸時鐘
	u8			chip_select; //片選號,用來區分同一主控制器上的設備
	u8			mode; //各bit的定義如下,主要是時鐘相位/時鐘極性
#define	SPI_CPHA	0x01			/* clock phase */
#define	SPI_CPOL	0x02			/* clock polarity */
#define	SPI_MODE_0	(0|0)			/* (original MicroWire) */
#define	SPI_MODE_1	(0|SPI_CPHA)
#define	SPI_MODE_2	(SPI_CPOL|0)
#define	SPI_MODE_3	(SPI_CPOL|SPI_CPHA)
#define	SPI_CS_HIGH	0x04			/* chipselect active high? */ //片選電位爲高
#define	SPI_LSB_FIRST	0x08			/* per-word bits-on-wire */ //先輸出低比特位
#define	SPI_3WIRE	0x10			/* SI/SO signals shared */ //輸入輸出共享接口,此時只能做半雙工
#define	SPI_LOOP	0x20			/* loopback mode */
#define	SPI_NO_CS	0x40			/* 1 dev/bus, no chipselect */
#define	SPI_READY	0x80			/* slave pulls low to pause */
	u8			bits_per_word; //每個字長的比特數
	int			irq; //使用到的比特數
	void			*controller_state;
	void			*controller_data;
	char			modalias[SPI_NAME_SIZE]; //spi是設備的名字

	/*
	 * likely need more hooks for more protocol options affecting how
	 * the controller talks to each chip, like:
	 *  - memory packing (12 bit samples into low bits, others zeroed)
	 *  - priority
	 *  - drop chipselect after each word
	 *  - chipselect delays
	 *  - ...
	 */
};

(3)spi_driver

struct spi_driver用於描述SPI從設備驅動。驅動核心將根據driver.name和spi_board_info的modalias進行匹配,如果modalias和name相等,則綁定驅動程序和kernel3.0.5/arch/arm/mach-exynos/mach-smdk4x12.c中調用spi_register_board_info註冊的信息對應的spi_device設備。它們的形式和struct platform_driver是一致的。

struct spi_driver {
	const struct spi_device_id *id_table; //可以驅動的設備表,也就是說該驅動可以驅動一類設備
	int			(*probe)(struct spi_device *spi); //和spi_device匹配成功之後會調用這個方法.因此這個方法需要對設備和私有數據進行初始化
	int			(*remove)(struct spi_device *spi); //解除spi_device和spi_driver的綁定,釋放probe申請的資源
	void			(*shutdown)(struct spi_device *spi); //一般牽扯到電源管理會用到,關閉
	int			(*suspend)(struct spi_device *spi, pm_message_t mesg); //一般牽扯到電源管理會用到,掛起
	int			(*resume)(struct spi_device *spi); //一般牽扯到電源管理會用到,恢復
	struct device_driver	 driver;
};
(4)spi_transfer

struct spi_transfer是對一次完整的數據傳輸的描述。每個spi_transfer總是讀取和寫入同樣的長度的比特數,但是可以很容易的使用空指針捨棄讀或寫,爲spi_transfer和spi_message分配的內存應該在消息處理期間保證是完整的。

struct spi_transfer {
	/* it's ok if tx_buf == rx_buf (right?)
	 * for MicroWire, one buffer must be null
	 * buffers must work with dma_*map_single() calls, unless
	 *   spi_message.is_dma_mapped reports a pre-existing mapping
	 */
	const void	*tx_buf; //要寫入設備的數據(必須是dma_safe),或者爲NULL
	void		*rx_buf; //要讀取的數據緩衝(必須是dma_safe),或者爲NULL
	unsigned	len; //tx和tr的大小(字節數),這裏不是指它的和,而是各自的長度,它們總是相等的

	dma_addr_t	tx_dma; //如果spi_message.is_dma_mapped是真,這個是tx的dma地址
	dma_addr_t	rx_dma; //如果spi_message.is_dma_mapped是真,這個是rx的dma地址

	unsigned	cs_change:1; //影響此次傳輸之後的片選,指示本次transfer結束之後是否要重新片選並調用setup改變設置
	u8		bits_per_word; //每個字長的比特數,如果是0,使用默認值
	u16		delay_usecs; //此次傳輸結束和片選改變之間的延時,之後就會啓動另一個傳輸或者結束整個消息
	u32		speed_hz; //通信時鐘,如果是0,使用默認值

	struct list_head transfer_list; //用來連接的雙向鏈表節點
};

(5)spi_message

struct spi_message就是對多個spi_transfer的封裝。在消息需要傳遞的時候,會將spi_transfer通過自己的transfer_list字段掛到spi_message的transfers鏈表頭上。spi_message用來原子的執行spi_transfer表示的一串數組傳輸請求。這個傳輸隊列是原子的,這意味着在這個消息完成之前不會有其它消息佔用總線。消息的執行總是按照FIFO的順序,向底層提交spi_message的代碼要負責管理它的內存空間。未顯示初始化的內存需要使用0來初始化。爲spi_transfer和spi_message分配的內存應該在消息處理期間保證是完整的。

struct spi_message {
	struct list_head	transfers; //此次消息的傳輸隊列,一個消息可以包含多個傳輸段

	struct spi_device	*spi; //傳輸的目的設備

	unsigned		is_dma_mapped:1; //如果爲真,此次調用提供dma和cpu虛擬地址

	/* REVISIT:  we might want a flag affecting the behavior of the
	 * last transfer ... allowing things like "read 16 bit length L"
	 * immediately followed by "read L bytes".  Basically imposing
	 * a specific message scheduling algorithm.
	 *
	 * Some controller drivers (message-at-a-time queue processing)
	 * could provide that as their default scheduling algorithm.  But
	 * others (with multi-message pipelines) could need a flag to
	 * tell them about such special cases.
	 */

	/* completion is reported through a callback */
	void			(*complete)(void *context); //異步調用完成後的回調函數
	void			*context; //回調函數的參數
	unsigned		actual_length; //此次傳輸的實際長度
	int			status; //執行的結果,成功被置0,否則是一個負的錯誤碼

	/* for optional use by whatever driver currently owns the
	 * spi_message ...  between calls to spi_async and then later
	 * complete(), that's the spi_master controller driver.
	 */
	struct list_head	queue;
	void			*state;
};

(6)兩個重要的板級結構

兩個板級結構,其中spi_board_info用來初始化spi_device,s3c64xx_spi_info用來初始化spi_master。這兩個板級的結構需要在移植的時候在kernel3.0.15/arch/arm/mach-exynos/mach-smdk4x12.c中初始化。

spi_board_info(kernel3.0.15/linux/include/spi/spi.h)

/* 該結構也是對SPI從設備(spi_device)的描述,只不過它是板級信息,最終該結構的所有字段都將用於初始化SPI設備結構體spi_device */
struct spi_board_info {
	/* the device name and module name are coupled, like platform_bus;
	 * "modalias" is normally the driver name.
	 *
	 * platform_data goes to spi_device.dev.platform_data,
	 * controller_data goes to spi_device.controller_data,
	 * irq is copied too
	 */
	char		modalias[SPI_NAME_SIZE]; //spi設備名,會拷貝到spi_device的相應字段中.這是設備spi_device在SPI總線spi_bus_type上匹配驅動的唯一標識
	const void	*platform_data; //平臺數據
	void		*controller_data;
	int		irq; //中斷號

	/* slower signaling on noisy or low voltage boards */
	u32		max_speed_hz; //SPI設備工作時的波特率


	/* bus_num is board specific and matches the bus_num of some
	 * spi_master that will probably be registered later.
	 *
	 * chip_select reflects how this chip is wired to that master;
	 * it's less than num_chipselect.
	 */
	u16		bus_num; //該SPI(從)設備所在總線的總線號,就記錄了所屬的spi_master之中的bus_num編號.一個spi_master就對應一條總線
	u16		chip_select; //片選號.該SPI(從)設備在該條SPI總線上的設備號的唯一標識

	/* mode becomes spi_device.mode, and is essential for chips
	 * where the default of SPI_CS_HIGH = 0 is wrong.
	 */
	u8		mode; //參考spi_device中的成員

	/* ... may need additional spi_device chip config data here.
	 * avoid stuff protocol drivers can set; but include stuff
	 * needed to behave without being bound to a driver:
	 *  - quirks like clock rate mattering when not selected
	 */
};
s3c64xx_spi_info(kernel3.0.15/arch/arm/plat-samsung/include/plat/s3c64xx-spi.h)
struct s3c64xx_spi_info {
	int src_clk_nr;
	char *src_clk_name;
	bool clk_from_cmu;

	int num_cs; //總線上的設備數

	int (*cfg_gpio)(struct platform_device *pdev);

	/* Following two fields are for future compatibility */
	int fifo_lvl_mask;
	int rx_lvl_offset;
	int high_speed;
	int tx_st_done;
};

boardinfo是用來管理spi_board_info的結構,spi_board_info在板級文件中通過spi_register_board_info(struct spi_board_info const *info, unsigned n)交由boardinfo來管理,並掛到board_list鏈表上。

boardinfo(kernel3.0.15/drivers/spi/spi.c)

struct boardinfo {
	struct list_head	list; //用於掛到鏈表board_list上
	struct spi_board_info	board_info; //存放結構體spi_board_info
};
spi_register_board_info(kernel3.0.15/drivers/spi/spi.c)

int __init
spi_register_board_info(struct spi_board_info const *info, unsigned n)
{
	struct boardinfo *bi;
	int i;

	bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
	if (!bi)
		return -ENOMEM;

	for (i = 0; i < n; i++, bi++, info++) {
		struct spi_master *master;

		memcpy(&bi->board_info, info, sizeof(*info));
		mutex_lock(&board_lock);
		list_add_tail(&bi->list, &board_list);
		list_for_each_entry(master, &spi_master_list, list)
			spi_match_master_to_boardinfo(master, &bi->board_info);
		mutex_unlock(&board_lock);
	}

	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章