【IMX6ULLドライバ開発学習】11.Linux SPIドライバ

参考: ドライバー開発: SPI デバイス driver_spi driver_Deng Jiawen007 のブログ - CSDN ブログ

目次

1. SPIドライバーの概要

1.1 SPI アーキテクチャの概要

1.2 SPIアダプタ(コントローラ)のデータ構造

1.2 SPI デバイスのデータ構造

1.3 SIPデバイスドライバー

1.4 インターフェース機能

 2. SPIドライバーテンプレート


1. SPIドライバーの概要

SPI ドライバー フレームワークは I2C ドライバー フレームワークと非常によく似ていますが、違いは、SPI がチップ セレクト ピンを通じてスレーブ デバイスを選択するため、SPI は I2C のようなアドレス指定操作 (スレーブ アドレスのクエリ) を実行する必要がなくなったことです。 SPI は全二重通信であり、通信速度は I2C よりもはるかに高速です。

ただし、SPI は明らかに I2C より多くのハードウェア リソースを消費し、SPI には I2C のような特定のフロー制御 (開始信号や停止信号など) や I2C のような応答機構がありません (データが受信されたかどうかを確認できません) )。

1.1 SPI アーキテクチャの概要

Linux の SPI アーキテクチャは 3 つのコンポーネントに分割できます。

  • spi コア (SPI コア): SPI コアは、spi の保守と管理に使用される Linux カーネルのコア部分です。SPI コアは、初期化中に spi マスター、spi ドライバー、spi デバイスを SPI コアに登録できるようにする操作インターフェイス機能を提供し、終了します。ログアウトします。
  • spi コントローラー ドライバーまたはアダプター ドライバー (SPI マスター ドライバー): SPI マスターは、さまざまな種類の spi コントローラー ハードウェアに対する spi バスのハードウェア アクセス操作を実装します。SPI マスターは、インターフェイス関数を通じてコン​​トローラーを SPI コアに登録します。
  • spi デバイス ドライバー: SPI ドライバーは、spi デバイスに対応するドライバーです。インターフェイス関数を通じて SPI コアに登録されます。SPI ドライバーの機能は、spi デバイスを spi バスに接続することです。

Linux のソフトウェア アーキテクチャ図を以下に示します。 

1.2 SPIアダプタ(コントローラ)のデータ構造

参照カーネル ファイル: include/linux/spi/spi.h

spi_master 構造体は、Linux で SPI コントローラーを記述するために使用されます。最も重要なメンバーはtransfer関数ポインターです。

転送関数は、i2c_algorithm の master_xfer 関数、コントローラー データ転送関数と同じです。
transfer_one_message 関数は、SPI データの送信にも使用されます。spi_message の送信に使用されます。SPI データは spi_message にパッケージ化され、キューに送信されます。

1.2 SPI デバイスのデータ構造

参照カーネル ファイル: include/linux/spi/spi.h

Linux は、spi_device 構造体を使用して SPI デバイスを記述し、デバイスのチップ選択ピン、周波数、および接続されている SPI コントローラーを記録します。

1.3 SIPデバイスドライバー

参照カーネル ファイル: include/linux/spi/spi.h

spi_driver 構造体は、Linux で SPI デバイス ドライバーを記述するために使用されます。

spi_driver は基本的に i2c_driver および platform_driver と同じであることがわかり、SPI デバイスとドライバーが正常に一致すると、プローブ関数が実行されます。 

例: spi1 に 2 つのデバイスが接続されている (2 つのチップ選択信号を使用)、そのデバイスを子ノードに配置できます。子ノードはカーネルによって解析され、spi_device に変換されます。特定の spi_driver と一致した後、子ノードはプローブ関数が呼び出され、プローブ関数にキャラクターデバイスドライバーを登録できます。

1.4 インターフェース機能

関数プロトタイプ:

  • シンプルな機能
/**
 * SPI同步写
 * @spi: 写哪个设备
 * @buf: 数据buffer
 * @len: 长度
 * 这个函数可以休眠
 *
 * 返回值: 0-成功, 负数-失败码
 */
static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len);

/**
 * SPI同步读
 * @spi: 读哪个设备
 * @buf: 数据buffer
 * @len: 长度
 * 这个函数可以休眠
 *
 * 返回值: 0-成功, 负数-失败码
 */
static inline int
spi_read(struct spi_device *spi, void *buf, size_t len);


/**
 * spi_write_then_read : 先写再读, 这是一个同步函数
 * @spi: 读写哪个设备
 * @txbuf: 发送buffer
 * @n_tx: 发送多少字节
 * @rxbuf: 接收buffer
 * @n_rx: 接收多少字节
 * 这个函数可以休眠
 * 
 * 这个函数执行的是半双工的操作: 先发送txbuf中的数据,在读数据,读到的数据存入rxbuf
 *
 * 这个函数用来传输少量数据(建议不要操作32字节), 它的效率不高
 * 如果想进行高效的SPI传输,请使用spi_{async,sync}(这些函数使用DMA buffer)
 *
 * 返回值: 0-成功, 负数-失败码
 */
extern int spi_write_then_read(struct spi_device *spi,
		const void *txbuf, unsigned n_tx,
		void *rxbuf, unsigned n_rx);

/**
 * spi_w8r8 - 同步函数,先写8位数据,再读8位数据
 * @spi: 读写哪个设备
 * @cmd: 要写的数据
 * 这个函数可以休眠
 *
 *
 * 返回值: 成功的话返回一个8位数据(unsigned), 负数表示失败码
 */
static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd);

/**
 * spi_w8r16 - 同步函数,先写8位数据,再读16位数据
 * @spi: 读写哪个设备
 * @cmd: 要写的数据
 * 这个函数可以休眠
 *
 * 读到的16位数据: 
 *     低地址对应读到的第1个字节(MSB),高地址对应读到的第2个字节(LSB)
 *     这是一个big-endian的数据
 *
 * 返回值: 成功的话返回一个16位数据(unsigned), 负数表示失败码
 */
static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);

/**
 * spi_w8r16be - 同步函数,先写8位数据,再读16位数据,
 *               读到的16位数据被当做big-endian,然后转换为CPU使用的字节序
 * @spi: 读写哪个设备
 * @cmd: 要写的数据
 * 这个函数可以休眠
 *
 * 这个函数跟spi_w8r16类似,差别在于它读到16位数据后,会把它转换为"native endianness"
 *
 * 返回值: 成功的话返回一个16位数据(unsigned, 被转换为本地字节序), 负数表示失败码
 */
static inline ssize_t spi_w8r16be(struct spi_device *spi, u8 cmd);
  •  複素関数
/**
 * spi_async - 异步SPI传输函数,简单地说就是这个函数即刻返回,它返回后SPI传输不一定已经完成
 * @spi: 读写哪个设备
 * @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback)
 * 上下文: 任意上下文都可以使用,中断中也可以使用
 *
 * 这个函数不会休眠,它可以在中断上下文使用(无法休眠的上下文),也可以在任务上下文使用(可以休眠的上下文) 
 *
 * 完成SPI传输后,回调函数被调用,它是在"无法休眠的上下文"中被调用的,所以回调函数里不能有休眠操作。
 * 在回调函数被调用前message->statuss是未定义的值,没有意义。
 * 当回调函数被调用时,就可以根据message->status判断结果: 0-成功,负数表示失败码
 * 当回调函数执行完后,驱动程序要认为message等结构体已经被释放,不能再使用它们。
 *
 * 在传输过程中一旦发生错误,整个message传输都会中止,对spi设备的片选被取消。
 *
 * 返回值: 0-成功(只是表示启动的异步传输,并不表示已经传输成功), 负数-失败码
 */
extern int spi_async(struct spi_device *spi, struct spi_message *message);

/**
 * spi_sync - 同步的、阻塞的SPI传输函数,简单地说就是这个函数返回时,SPI传输要么成功要么失败
 * @spi: 读写哪个设备
 * @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback)
 * 上下文: 能休眠的上下文才可以使用这个函数
 *
 * 这个函数的message参数中,使用的buffer是DMA buffer
 *
 * 返回值: 0-成功, 负数-失败码
 */
extern int spi_sync(struct spi_device *spi, struct spi_message *message);


/**
 * spi_sync_transfer - 同步的SPI传输函数
 * @spi: 读写哪个设备
 * @xfers: spi_transfers数组,用来描述传输
 * @num_xfers: 数组项个数
 * 上下文: 能休眠的上下文才可以使用这个函数
 *
 * 返回值: 0-成功, 负数-失败码
 */
static inline int
spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers,
	unsigned int num_xfers);

 2. SPIドライバーテンプレート

spi_drv.c

#include <linux/spi/spi.h>
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

/* 主设备号                                                                 */
static int major = 0;
static struct class *my_spi_class;

static struct spi_device *g_spi;

static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
struct fasync_struct *spi_fasync;


/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t spi_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	// int err;

	// struct spi_transfer msgs[2];

	/* 初始化 spi_transfer */

	// static inline int
    //   spi_sync_transfer(struct   spi_device *spi, struct spi_transfer *xfers,
	//   unsigned int num_xfers);

	/* copy_to_user  */
	
	return 0;
}

static ssize_t spi_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	//int err;

	/* copy_from_user  */


	// struct spi_transfer msgs[2];

	/* 初始化 spi_transfer */

	// static inline int
    //   spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers,
	//   unsigned int num_xfers);

	
	return 0;    
}


static unsigned int spi_drv_poll(struct file *fp, poll_table * wait)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_wait, wait);
	//return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
	return 0;
}

static int spi_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &spi_fasync) >= 0)
		return 0;
	else
		return -EIO;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations spi_drv_fops = {
	.owner	 = THIS_MODULE,
	.read    = spi_drv_read,
	.write   = spi_drv_write,
	.poll    = spi_drv_poll,
	.fasync  = spi_drv_fasync,
};


static int spi_drv_probe(struct spi_device *spi)
{
	// struct device_node *np = client->dev.of_node;

	/* 记录spi_device */
	g_spi = spi;

	/* 注册字符设备 */
	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_spi", &spi_drv_fops);  /* /dev/gpio_desc */

	my_spi_class = class_create(THIS_MODULE, "100ask_spi_class");
	if (IS_ERR(my_spi_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_spi");
		return PTR_ERR(my_spi_class);
	}

	device_create(my_spi_class, NULL, MKDEV(major, 0), NULL, "myspi"); /* /dev/myspi */
	
	return 0;
}

static int spi_drv_remove(struct spi_device *spi)
{
	/* 反注册字符设备 */
	device_destroy(my_spi_class, MKDEV(major, 0));
	class_destroy(my_spi_class);
	unregister_chrdev(major, "100ask_spi");

	return 0;
}

static const struct of_device_id myspi_dt_match[] = {
	{ .compatible = "100ask,spidev" },
	{},
};
static struct spi_driver my_spi_driver = {
	.driver = {
		   .name = "100ask_spi_drv",
		   .owner = THIS_MODULE,
		   .of_match_table = myspi_dt_match,
	},
	.probe = spi_drv_probe,
	.remove = spi_drv_remove,
};


static int __init spi_drv_init(void)
{
	/* 注册spi_driver */
	return spi_register_driver(&my_spi_driver);
}

static void __exit spi_drv_exit(void)
{
	/* 反注册spi_driver */
	spi_unregister_driver(&my_spi_driver);
}

/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(spi_drv_init);
module_exit(spi_drv_exit);

MODULE_LICENSE("GPL");


おすすめ

転載: blog.csdn.net/qq_43460230/article/details/132502516