組み込み Linux システムにおける SPI サブシステムの基本的な実装

1. SPIドライバーのソースファイルディレクトリ

Linux共通spiドライバー

kernel-4.14/drivers/spi/spi.c  Linux 提供的通用接口封装层驱动
kernel-4.14/drivers/spi/spidev.c  linux 提供的 SPI 通用设备驱动程序
kernel-4.14/include/linux/spi/spi.h  linux 提供的包含 SPI 的主要数据结构和函数

spi コントローラー ドライバー、IC メーカーによって提供され、メーカーによって名前が異なります。

kernel-4.14/drivers/spi/spi-mt65xx.c  MTK SPI 控制器驱动
kernel-4.14/drivers/spi/spi-mt65xx-dev.c
kernel-4.14/include/linux/platform_data/spi-mt65xx.h

dts

kernel-4.14/arch/arm/boot/dts/...
kernel-4.14/arch/arm64/boot/dts/...

上記のファイルは、次の SPI ドライバー ソフトウェア アーキテクチャに対応しています。

SPIコントローラードライバー

SPI コントローラは、デバイスの特定の機能を考慮する必要はなく、上位層のプロトコル ドライバによって準備されたデータを SPI バスのタイミング要件に従って SPI デバイスに送信することだけを担当します。デバイスから受信したデータを上位層のプロトコルドライバに返す時間がかかるため、カーネルはSPIコントローラのドライバを分離します。

SPI コントローラ ドライバは、DMA や割り込み操作などの特定のコントローラ ハードウェアの制御を担当します。複数の上位層プロトコル ドライバがコントローラを介してデータ転送操作を要求する可能性があるため、SPI コントローラ ドライバはこれらの要求も担当します。先入れ先出しの原則を確保するためにキュー管理を実行します。

SPI 一般インターフェイスのカプセル化層

SPI ドライバーのプログラミング作業を簡素化し、[プロトコル ドライバー] と [コントローラー ドライバー] の間の結合度を減らすために、カーネルはコントローラー ドライバーとプロトコル ドライバーのいくつかの共通操作を標準インターフェイスにカプセル化します。に加えて、いくつかの一般ロジック処理操作が SPI 一般インターフェイスのカプセル化層を構成します。

この利点は、コントローラー ドライバーの場合、プロトコル層ドライバーと直接やり取りすることなく、標準インターフェイス コールバック API を実装して一般インターフェイス層に登録するだけで済むことです。プロトコル層ドライバーは、実装内容を意識することなく、一般インターフェース層が提供するAPIのみでデバイスとドライバーの登録を完了でき、一般インターフェース層が提供するAPIのみでデータ送信を完了できます。 SPI コントローラ ドライバ。

SPIプロトコルドライバー

SPI デバイスの特定の機能は SPI プロトコル ドライバによって実現され、SPI プロトコル ドライバはデバイスの機能と通信データのプロトコル形式を理解します。下位では、プロトコル ドライバーは一般インターフェイス層を介してコントローラーとデータを交換し、上位では、プロトコル ドライバーは通常、デバイスの特定の機能に従ってカーネルの他のサブシステムと対話します。

たとえば、MTD レイヤーと対話して SPI インターフェイスのストレージ デバイスをファイル システムとして実装し、TTY サブシステムと対話して SPI デバイスを TTY デバイスとして実装し、ネットワーク サブシステムと対話して SPI デバイスをネットワークとして実装します。デバイス。独自の SPI デバイスの場合は、デバイスのプロトコル要件に従って独自のプロトコル ドライバーを実装することもできます。

SPI汎用デバイスドライバー

SPI コントローラに接続されるデバイスのばらつきを考慮して、カーネルには対応するプロトコル ドライバが用意されていませんが、この場合、カーネルは一般的な SPI デバイス ドライバを用意しており、これを使用してユーザー空間を制御します。 SPI によって制御される制御インターフェイスでは、特定のデバイスに応じて、特定のプロトコル制御とデータ送信作業がユーザー空間に引き渡され、SPI デバイスとの通信には同期方式のみが使用できるため、通常は同期方式が使用されます。データ量の少ない一部の Simple SPI デバイスで。

2. SPI汎用インターフェース層

  1. SPI 一般インターフェイス層は、特定の SPI デバイス プロトコル ドライバーを SPI コントローラー ドライバーに接続します。

  2. SPI システムと Linux デバイス モデルの初期化を担当します。

  3. プロトコル ドライバーとコントローラー ドライバー用の一連の標準インターフェイス API とデータ構造を提供します。

  4. SPI デバイス、SPI プロトコル ドライバー、および SPI コントローラーのデータ抽象化

  5. データ転送を容易にするために定義されたデータ構造

kernel-4.14/drivers/spi/spi.c

static int __init spi_init(void)
{
 int status;

 buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
 if (!buf) {
  status = -ENOMEM;
  goto err0;
 }
        
  // 创建 /sys/bus/spi 节点
 status = bus_register(&spi_bus_type);
 if (status < 0)
  goto err1;

  //创建 /sys/class/spi_master 节点
 status = class_register(&spi_master_class);
 if (status < 0)
  goto err2;

 if (IS_ENABLED(CONFIG_SPI_SLAVE)) {
  status = class_register(&spi_slave_class);
  if (status < 0)
   goto err3;
 }
        ......
}

ここで SPI バスが作成され、/sys/bus/spi ノードと /sys/class/spi_master ノードが作成されます。

重要なデータ構造:

spi_device
spi_driver
spi_board_info
spi_controller/spi_master
spi_transfer
spi_message

重要な API

spi_message_init
spi_message_add_tail
spi_sync
spi_async
spi_write
spi_read

次に構造とAPIを詳細に分析し、重要な部分のみを説明します完全な分析については公式ドキュメントを参照してください

https://www.kernel.org/doc/html/v4.14//driver-api/spi.html

各構造に何が格納されているかを理解して初めて、SPI モジュールを本当に理解することができます。

spi_master/spi_controller: spi マスターデバイスについて説明します。

struct spi_master {
  //Linux 驱动模型中的设备
 struct device dev;

  //此 spi_master 设备在全局 spi_master 链表中的节点
 struct list_head list;

  //此 spi_master 编号
 s16   bus_num;

  //此 spi_master 支持的片选信号数量
 u16   num_chipselect;

  //dma 地址对齐
 u16   dma_alignment;

  //此 spi_master 支持传输的 mode
 u16   mode_bits;
 u32   bits_per_word_mask;
 /* limits on transfer speed */
 u32   min_speed_hz;
 u32   max_speed_hz;

 /* other constraints relevant to this driver */
 u16   flags;

 /* lock and mutex for SPI bus locking */
 spinlock_t  bus_lock_spinlock;//总线自旋锁
 struct mutex  bus_lock_mutex;//总线互斥锁

  //总线是否处于 lock 状态
 bool   bus_lock_flag;

  //准备传输,设置传输的参数
 int   (*setup)(struct spi_device *spi);

  //传输数据
 int   (*transfer)(struct spi_device *spi,
     struct spi_message *mesg);
  // 设备 release 时的清除工作
 void   (*cleanup)(struct spi_device *spi);

 bool   (*can_dma)(struct spi_master *master,
        struct spi_device *spi,
        struct spi_transfer *xfer);

 bool   queued;//是否采用系统的序列化传输
 struct kthread_worker kworker;//序列化传输时的线程 worker
 struct task_struct *kworker_task;//序列化传输的线程
 struct kthread_work pump_messages;//序列化传输时的处理函数
 spinlock_t  queue_lock;//序列化传输时的queue_lock
 struct list_head queue;//序列化传输时的 msg 队列头
 struct spi_message *cur_msg;//序列化传输时当前的 msg
 bool   idling;
 bool   busy;//序列化传输时线程是否处于busy状态
 bool   running;//序列化传输时线程是否在运行
 bool   rt;//是否实时传输
  ......

 int (*prepare_transfer_hardware)(struct spi_master *master);

  //一个 msg 的传输实现
 int (*transfer_one_message)(struct spi_master *master,
        struct spi_message *mesg);
  ......

 /* gpio chip select */
 int   *cs_gpios;
  ......
};

spi_device: spi スレーブ デバイスについて説明します。

struct spi_device {
  //Linux驱动模型中的设备
 struct device  dev;
 struct spi_master *master;//设备所连接的 spi 主机设备
 u32   max_speed_hz;//该设备最大传输速率
 u8   chip_select;//CS片选信号编号
 u8   bits_per_word;//每次传输长度
 u16   mode;//传输模式
 ......
 int   irq;//软件中断号
 void   *controller_state;//控制器状态
 void   *controller_data;//控制参数
 char   modalias[SPI_NAME_SIZE];//设备名称
  //CS 片选信号对应的 GPIO number
 int   cs_gpio;  /* chip select gpio */

 /* the statistics */
 struct spi_statistics statistics;
};

spi_driver: spi デバイスドライバーを説明します。

struct spi_driver {
  //此driver所支持的 spi 设备 list
 const struct spi_device_id *id_table;
 int   (*probe)(struct spi_device *spi);
 int   (*remove)(struct spi_device *spi);
  //系统 shutdown 时的回调函数
 void   (*shutdown)(struct spi_device *spi);
 struct device_driver driver;
};

spi_board_info: spi スレーブデバイスのボードレベルの情報を記述します。デバイスツリーがない場合に使用されます。

struct spi_board_info {
  //设备名称
 char  modalias[SPI_NAME_SIZE];
 const void *platform_data;//设备的平台数据
 void  *controller_data;//设备的控制器数据
 int  irq;//设备的中断号
 u32  max_speed_hz;//设备支持的最大速率
 u16  bus_num;//设备连接的 spi 总线编号
 u16  chip_select;//设备连接的 CS 信号编号
 u16  mode;//设备使用的传输 mode
};

spi_transfer: spiによって送信される特定のデータを記述します。

struct spi_transfer {

 const void *tx_buf;//spi_transfer 的发送 buf
 void  *rx_buf;//spi_transfer 的接收 buf
 unsigned len;//spi_transfer 发送和接收的长度

 dma_addr_t tx_dma;//tx_buf 对应的 dma 地址
 dma_addr_t rx_dma;//rx_buf 对应的 dma 地址
 struct sg_table tx_sg;
 struct sg_table rx_sg;

  //spi_transfer传输完成后是否要改变 CS 片选信号
 unsigned cs_change:1;
 unsigned tx_nbits:3;
 unsigned rx_nbits:3;
  ......
 u8  bits_per_word;//spi_transfer 中一个 word 占的bits
 u16  delay_usecs;//两个 spi_transfer 直接的等待延迟
 u32  speed_hz;//spi_transfer 的传输速率

 struct list_head transfer_list;//spi_transfer挂载到的 message 节点
};

spi_message: spi 送信を説明する情報

struct spi_message {
  //挂载在此 msg 上的 transfer 链表头
 struct list_head transfers;
  //此 msg 需要通信的 spi 从机设备
 struct spi_device *spi;
  //所使用的地址是否是 dma 地址
 unsigned  is_dma_mapped:1;

 //msg 发送完成后的处理函数
 void   (*complete)(void *context);
 void   *context;//complete函数的参数
 unsigned  frame_length;
 unsigned  actual_length;//此 msg 实际成功发送的字节数
 int   status;//此 msg 的发送状态,0:成功,负数,失败

 struct list_head queue;//此 msg 在所有 msg 中的链表节点
 void   *state;//此 msg 的私有数据
};

順番待ち

SPI データ送信には、同期と非同期の 2 つの方法があります。

同期モード: データ送信の開始者は、この送信の終了を待つ必要があり、その間は他のことはできません コードで説明すると、送信関数を呼び出した後、データ送信が完了するまで関数は戻りません。

非同期モード: データ送信の開始者は送信の終了を待つ必要がありません。データ送信中に他のことができます。コードで説明すると、送信の関数を呼び出した後、すぐに関数が戻ります。データ転送の完了を待たずに、データ転送が完了したことをイニシエータに通知するために、転送完了後に呼び出されるコールバック関数を設定するだけです。

同期方法はシンプルで使いやすく、少量のデータを 1 回だけ転送する場合に非常に適しています。ただし、大量のデータと回数を伴う送信には、非同期方式の方が適しています。

SPI コントローラーの場合、非同期モードをサポートするには、次の 2 つの状況を考慮する必要があります。

  1. 同じデータ送信のイニシエータの場合、非同期メソッドはデータ送信の完了を待たずに戻るため、イニシエータは戻った後、前のメッセージがまだ処理されていない間に、すぐに別のメッセージを開始できます。

  2. 別の異なるイニシエータに対して、同時にメッセージ送信要求を開始することも可能です。

キューイングとは、上記の問題を解決するものであり、送信待ちのメッセージを待ち行列に入れて、送信操作を開始することを指しますが、実際には、該当するメッセージを順番に待ち行列に入れることになります。システムは、キュー内に送信を待っているメッセージがあるかどうかを継続的にチェックし、存在する場合は、データ送信カーネル スレッドを継続的にスケジュールし、キューが空になるまでキュー内のメッセージを 1 つずつ取り出して処理します。SPI 一般インターフェイス層は、キューイングの基本フレームワークを実装します。

spi_message は SPI データ交換のアトミック操作であり、中断することはできません。

3. SPI コントローラードライバー層

SPI コントローラ ドライバ層は最も低いデータ送受信を担当し、主に次の機能があります。

  1. 割り込み、DMA チャネル、DMA メモリ バッファなどの必要なハードウェア リソースを適用します。

  2. 対応するデバイスとデータを正しく交換できるように、SPI コントローラの動作モードとパラメータを設定します。

  3. 一般インターフェース層へのインターフェースを提供し、上位プロトコルドライバーが一般インターフェース層を介してコントローラードライバーにアクセスできるようにします。

  4. 一般インターフェイス層と連携して、メッセージキューが空になるまでデータメッセージキューのキューイングと処理を完了します。

SPI ホスト ドライバーは、SOC の SPI コントローラー ドライバーです。Linux カーネルは、spi_master/spi_controller を使用して SPI ホスト ドライバーを表します。spi_master は、include/linux/spi/spi.h ファイルで定義された構造です。

SPI ホスト ドライバーの核心は、spi_master を適用し、次に spi_master を初期化し、最後に spi_master を Linux カーネルに登録することです。

API は次のとおりです。

spi_alloc_master 函数:申请 spi_master。
spi_master_put 函数:释放 spi_master。

spi_register_master函数:注册 spi_master。
spi_unregister_master 函数:注销 spi_master。

spi_bitbang_start函数:注册 spi_master。
spi_bitbang_stop 函数:注销 spi_master。

SPIホストドライバのロード

MTK を例に挙げると、ソース コードは Xiaomi オープン ソース プロジェクトから取得されています。

https://github.com/MiCode/Xiaomi_Kernel_OpenSource

Xiaomi はプロジェクトを作成するたびに、Linux GPL オープンソース契約に従う必要があるため、カーネル部分を開きます。

デバイスツリーで宣言された[デバイス]

kernel-4.14/arch/arm64/boot/dts/mediatek/mt6885.dts

【ドライブ】

kernel-4.14/drivers/spi/spi-mt65xx.c

一致した後、プローブ関数が実行され、spi_master に適用され、spi_master が初期化され、最後に spi_master が Linux カーネルに登録されます。

4. ソフトウェアプロセス

この図を理解すると、SPI ドライバー フレームワークを完全に理解できるようになります。

1、2、3 は順番に実行され、最初に spi バスの登録、次に spi コントローラー ドライバーのロード、そしてデバイス ドライバーのロードが行われます。違いは、spi コントローラー ドライバーが読み込まれるときに、プラットフォーム バスに依存してデバイス (コントローラー) とドライバーが一致することです。spi デバイス ドライバーがロードされる場合、デバイス (周辺 IC) とドライバーの一致は spi バスに依存します。

初期フロー

spi_register_masterの呼び出しシーケンス図

キューの動作メカニズムとプロセス

プロトコル ドライバーが spi_async を通じてメッセージ要求を開始すると、キューイング スレッドとワーカー スレッドがアクティブ化され、一連の操作がトリガーされ、最終的にメッセージ送信操作が完了します。

spi_sync は spi_async に似ていますが、待機プロセスがあります。

5. SPIデバイスドライバー

デバイスツリーで宣言された[デバイス]

注: デバイスの宣言、スレーブ デバイス ノードは、マウントする &spi ノードの下に含めて、デバイスをマスターにバインドする必要があります。次に、pinctrl で GPIO を指定し、ドライバーで pinctrl ハンドルを操作します。

【ドライバー】デモ

Linux カーネルは、spi_driver 構造体を使用して spi デバイス ドライバーを表すため、SPI デバイス ドライバーを作成するときに spi_driver を実装する必要があります。spi_driver 構造は、include/linux/spi/spi.h ファイルで定義されています。

spi_register_driver:注册 spi_driver
spi_unregister_driver:销掉 spi_driver
/* probe 函数 */
static int xxx_probe(struct spi_device *spi)
{

 /* 具体函数内容 */
 return 0;
}

/* remove 函数 */
static int xxx_remove(struct spi_device *spi)
{

 /* 具体函数内容 */
 return 0;
}

/* 传统匹配方式 ID 列表 */
static const struct spi_device_id xxx_id[] = {

 {"xxx", 0},
 {}
};

/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {

 { .compatible = "xxx" },
 { /* Sentinel */ }
};

/* SPI 驱动结构体 */
static struct spi_driver xxx_driver = {

 .probe = xxx_probe,
 .remove = xxx_remove,
 .driver = {
  .owner = THIS_MODULE,
  .name = "xxx",
  .of_match_table = xxx_of_match,
  },
 .id_table = xxx_id,
};

/* 驱动入口函数 */
static int __init xxx_init(void)
{

 return spi_register_driver(&xxx_driver);
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{

 spi_unregister_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

ドライバーエントリ関数で spi_register_driver を呼び出して、spi_driver を登録します。

ドライバー終了関数で spi_unregister_driver を呼び出して、spi_driver の登録を解除します。

spi データの読み取りおよび書き込みのデモ

/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{
 int ret;
 struct spi_message m;
 
 struct spi_transfer t = {
  .tx_buf = buf,
  .len = len,
 };
 
 spi_message_init(&m); /* 初始化 spi_message */
 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */
 ret = spi_sync(spi, &m); /* 同步传输 */
 return ret;
}
/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{
 int ret;
 struct spi_message m;
 
 struct spi_transfer t = {
  .rx_buf = buf,
  .len = len,
 };
 
 spi_message_init(&m); /* 初始化 spi_message */
 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */
 ret = spi_sync(spi, &m); /* 同步传输 */
 return ret;
}

init、exit、probe、remove、read、write 関数に加えて、その他の関数は要件の実装に依存します。これらは最も基本的なものです。

6. まとめ

Linux はバス、デバイス、ドライバーのフレームワークであり、このフレームワークを理解すれば、すべてのモジュール ドライバーのフレームワークを理解できます。

SPI ドライバーは、I2C ドライバーよりもはるかに単純です。

おすすめ

転載: blog.csdn.net/weixin_41114301/article/details/131070768