spi子系统

 

https://blog.csdn.net/alangaixiaoxiao/article/details/84146885  SPI协议通信
CPOL:Clock Polarity,就是时钟的极性
CPHA:Clock Phase,就是时钟的相位

如果主机在上升沿输出数据到MOSI上,从机就只能在下降沿去采样这个数据了。反之如果一方在下降沿输出数据,那么另一方就必须在上升沿采样这个数据
CPHA=1,就表示数据的输出是在一个时钟周期的第一个沿上,至于这个沿是上升沿还是下降沿,这要是CPOL的值而定,CPOL=1那就是下降沿,反之就是上升沿。那么数据的采样自然就是在第二个沿上了
CPHA=0,就表示数据的采样是在一个时钟周期的第一个沿上,同样它是什么沿由CPOL决定。那么数据的输出自然就在第二个沿上了。

也就是说,CPOL为1 CPHA为1 的时候,
代表着,clk是低电平开始数据采样,高电平空闲,MOSI在第一个时钟周期下降沿的时候输出,MISO在第一个周期的上升沿采集。

https://blog.csdn.net/Guet_Kite/article/details/77918002  嵌入式Linux驱动笔记(十二)------通俗易懂式分析了解spi框架
所以说,spi_sync函数是发起一个同步传输的阻塞API,它会通过master->transfer把spi_message结构挂在spi_master的queue字段下,
然后启动专门为spi传输准备的内核工作线程spi_pump_messages,把该spi_message从队列中移除,
然后调用master->prepare_transfer_hardware回调来让控制器驱动准备必要的硬件资源,
最后 master->transfer_one_message来实际处理message的传输工作,然后等待传输的完成后调用mesg->complete,表明传输完成,
以通知协议驱动程序准备下一帧数据,wait_for_completion不需要再等待了。

https://blog.csdn.net/droidphone/article/details/24663659  Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化
定义一个spi_message结构;
用spi_message_init函数初始化spi_message;
定义一个或数个spi_transfer结构,初始化并为数据准备缓冲区并赋值给 spi_ transfer 相应的字段(tx_buf,rx_buf等);
通过spi_message_init函数把这些spi_transfer挂在spi_message结构下;
如果使用同步方式,调用spi_sync(),如果使用异步方式,调用spi_async();

https://blog.csdn.net/orz415678659/article/details/8617647 spi 子系统(spidev.c)

sys/bus/spi
sys/class/spi_master

先看spi-gpio.c
spi_gpio_driver
 spi_gpio_probe
  spi_gpio_probe_dt
   pdata->sck = of_get_named_gpio(np, "gpio-sck", 0);
   pdata->miso = of_get_named_gpio(np, "gpio-miso", 0);
   pdata->mosi = of_get_named_gpio(np, "gpio-mosi", 0);
   pdata->num_chipselect = of_property_read_u32(np, "num-chipselects", &tmp);
   spi_gpio_request
   spi_alloc_master //这里会绑定spi.c的groups 并分配给 master
    device_initialize(&master->dev);
    master->dev.class = &spi_master_class; //这里就是
    spi_master_set_devdata(master, &master[1]);
   master->setup = spi_gpio_setup;
   master->cleanup = spi_gpio_cleanup;
   spi_gpio->bitbang.master = master; //将master 给到bitbang
   spi_gpio->bitbang.chipselect = spi_gpio_chipselect;
   if ((master_flags & (SPI_MASTER_NO_TX | SPI_MASTER_NO_RX)) == 0) {
   spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_txrx_word_mode0;
   spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_txrx_word_mode1;
   spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_txrx_word_mode2;
   spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_txrx_word_mode3;
   } else {
   spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_spec_txrx_word_mode0;
   spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_spec_txrx_word_mode1;
   spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_spec_txrx_word_mode2;
   spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_spec_txrx_word_mode3;
   }
   spi_gpio->bitbang.setup_transfer = spi_bitbang_setup_transfer;
   status = spi_bitbang_start(&spi_gpio->bitbang);//注册bitbang

注意spi mode(0-3)
只看其中一个   
spi_gpio_spec_txrx_word_mode0
 return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);

最终调用到如下函数,主要作用是通过SPI的时序实现SPI的传输
u32 oldbit = (!(word & (1<<(bits-1)))) << 31;//将传入的word和位数的最高位相与后取反后左移31位
/* clock starts at inactive polarity */
for (word <<= (32 - bits); likely(bits); bits--) {

 /* setup MSB (to slave) on trailing edge */
 if ((flags & SPI_MASTER_NO_TX) == 0) {// 如果spi支持TX,对每一位进行移位比较
  if ((word & (1 << 31)) != oldbit) {// 设置传输
   setmosi(spi, word & (1 << 31));
   oldbit = word & (1 << 31);
  }
 }
 spidelay(nsecs); /* T(setup) */

 setsck(spi, !cpol);
 spidelay(nsecs);

 /* sample MSB (from slave) on leading edge */
 word <<= 1;
 if ((flags & SPI_MASTER_NO_RX) == 0)
  word |= getmiso(spi);
 setsck(spi, cpol);
}
return word;
  
所以重点看 spi_bitbang_start 函数
spi_bitbang_start
 master->transfer_one = spi_bitbang_transfer_one; 
 bitbang->txrx_bufs = spi_bitbang_bufs;
 ret = spi_register_master(spi_master_get(master));
  status = of_spi_register_master(master);
   cs[i] = of_get_named_gpio(np, "cs-gpios", i);
  master->bus_num = of_alias_get_id(master->dev.of_node, "spi");
  init_completion(&master->xfer_completion);
   init_waitqueue_head(&x->wait);
  spi_master_initialize_queue(master);
   master->transfer = spi_queued_transfer;
   master->transfer_one_message = spi_transfer_one_message;//可自己实现
   ret = spi_init_queue(master);
    init_kthread_worker(&master->kworker);
    master->kworker_task = kthread_run(kthread_worker_fn, &master->kworker, "%s, dev_name(&master->dev));//创建并启动内核线程
    init_kthread_work(&master->pump_messages, spi_pump_messages);//注意spi_pump_messages
    if (master->rt) {//如果系统要求实时性,就执行如下函数
     sched_setscheduler(master->kworker_task, SCHED_FIFO, &param);
    }
   ret = spi_start_queue(master);
   queue_kthread_work(&master->kworker, &master->pump_messages);
   spi_pump_messages
    __spi_pump_messages(master, true);/* 检查是否存在spi meg在队列中,如果是初始化硬件并传输这些数据 */
     master->unprepare_transfer_hardware(master)
     master->cur_msg =list_first_entry(&master->queue, struct spi_message, queue);//找到并删除这个消息队列
     list_del_init(&master->cur_msg->queue);
     ret = master->prepare_transfer_hardware(master);
     ret = spi_map_msg(master, master->cur_msg);//dma传输的一些映射
     ret = master->transfer_one_message(master, master->cur_msg);
     spi_transfer_one_message
      ret = master->transfer_one(master, msg->spi, xfer);
       spi_bitbang_transfer_one
      ms = wait_for_completion_timeout(&master->xfer_completion,
         msecs_to_jiffies(ms));//注意 调用mesg->complete,使得wait_for_completion不需要再阻塞等待
      
真的传输动作在如下函数
spi_bitbang_transfer_one    
 status = bitbang->setup_transfer(spi, transfer);// spi_bitbang_setup_transfer     
 spi_bitbang_setup_transfer    
  cs->txrx_bufs = bitbang_txrx_8;
  cs->txrx_bufs = bitbang_txrx_16;
  cs->txrx_bufs = bitbang_txrx_32;  
 status = bitbang->txrx_bufs(spi, transfer);    
  word = txrx_word(spi, ns, word, bits);   
  spi_gpio_spec_txrx_word_mode0   
     
综上来看,对于SPI总线驱动的注册主要通过 spi_bitbang_start 进行注册,并需要完成相关回调函数的填充,如txrx_word,setup_transfer等

下面开始看SPI传输的设备端如何实现
spidev.c 注释:Simple synchronous userspace interface to SPI devices
spidev_init     
 status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);   
  spidev_class = class_create(THIS_MODULE, "spidev");    
   status = spi_register_driver(&spidev_spi_driver);
   
spidev_probe   
 device_create
    
spi_register_driver    
 __spi_register_driver(THIS_MODULE, driver) 
  sdrv->driver.bus = &spi_bus_type;
  spi_driver->driver.probe = spi_drv_probe; //注意 spi_driver.probe = spidev_probe,
  driver_register(&sdrv->driver);   
   
再看一个spi 设备的实现
sprd_spi_intf.c    
sprd_spi_probe
 ret = spi_register_driver(&spi_drv);
 spi_device_register(spi, &pdev->dev);
 
sprd_spi_dev_probe
 sprd_spi_config(spi->panel);
 spi_setup(spi_dev);

sprd_spi_refresh
 sprd_spi_transfer
  wait_for_completion(&spi->spi_complete);
  sprd_spi_cmd(panel, 0);
  sprd_spi_data(panel, 1);
   spi_message_add_tail
   spi_sync

从上面可以知道,通过spi_register_driver对spi总线上寻找匹配属性即可匹配spi,从而拿到 spi->spi_dev = spi_dev; 然后调用master里面的kthread_work.kthread_work_func_t 即spi_pump_messages
那么这个总线是怎么来的呢
spi.c
spi_init
 status = bus_register(&spi_bus_type);// bus_register - register a driver-core subsystem
 status = class_register(&spi_master_class);

spi通过设备树的compatible属性确定了spi master是哪个dev节点,从而在调用
 spi_message_add_tail
 spi_sync
的时候,就直接指向的是注册master的kworker

 

 

 

用户空间spi发送接收测试
需要将spidev.c使能,该文件将spi总线暴露给用户空间
测试程序
spidev_test.c

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
 
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
 
static void pabort(const char *s)
{
 perror(s);
 abort();
}
 
static const char *device = "/dev/spidev1.1";
static uint8_t mode;
static uint8_t bits = 8;
static uint32_t speed = 500000;
static uint16_t delay;
 
static void transfer(int fd)
{
 int ret;
 uint8_t tx[] = { //要发送的数据数组
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x95,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xAD,
  0xF0, 0x0D,
 };
 uint8_t rx[ARRAY_SIZE(tx)] = {0, }; //接收的数据数据
 struct spi_ioc_transfer tr = { //声明并初始化spi_ioc_transfer结构体
  .tx_buf = (unsigned long)tx,
  .rx_buf = (unsigned long)rx,
  .len = ARRAY_SIZE(tx),
  .delay_usecs = delay,
  .speed_hz = speed,
  .bits_per_word = bits,
 };
 //SPI_IOC_MESSAGE(1)的1表示spi_ioc_transfer的数量
 ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); //ioctl默认操作,传输数据
 if (ret < 1)
  pabort("can't send spi message");
 
 for (ret = 0; ret < ARRAY_SIZE(tx); ret++) { //打印接收缓冲区
  if (!(ret % 6))  //6个数据为一簇打印
   puts("");
  printf("%.2X ", rx[ret]);
 }
 puts("");
}
 
static void print_usage(const char *prog) //参数错误则打印帮助信息
{
 printf("Usage: %s [-DsbdlHOLC3]\n", prog);
 puts("  -D --device   device to use (default /dev/spidev1.1)\n"
      "  -s --speed    max speed (Hz)\n"
      "  -d --delay    delay (usec)\n"
      "  -b --bpw      bits per word \n"
      "  -l --loop     loopback\n"
      "  -H --cpha     clock phase\n"
      "  -O --cpol     clock polarity\n"
      "  -L --lsb      least significant bit first\n"
      "  -C --cs-high  chip select active high\n"
      "  -3 --3wire    SI/SO signals shared\n");
 exit(1);
}
 
static void parse_opts(int argc, char *argv[])
{
 while (1) {
  static const struct option lopts[] = { //参数命令表
   { "device",  1, 0, 'D' },
   { "speed",   1, 0, 's' },
   { "delay",   1, 0, 'd' },
   { "bpw",     1, 0, 'b' },
   { "loop",    0, 0, 'l' },
   { "cpha",    0, 0, 'H' },
   { "cpol",    0, 0, 'O' },
   { "lsb",     0, 0, 'L' },
   { "cs-high", 0, 0, 'C' },
   { "3wire",   0, 0, '3' },
   { "no-cs",   0, 0, 'N' },
   { "ready",   0, 0, 'R' },
   { NULL, 0, 0, 0 },
  };
  int c;
 
  c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", lopts, NULL);
 
  if (c == -1)
   break;
 
  switch (c) {
  case 'D': //设备名
   device = optarg;
   break;
  case 's': //速率
   speed = atoi(optarg);
   break;
  case 'd': //延时时间
   delay = atoi(optarg);
   break;
  case 'b': //每字含多少位
   bits = atoi(optarg);
   break;
  case 'l': //回送模式
   mode |= SPI_LOOP;
   break;
  case 'H': //时钟相位
   mode |= SPI_CPHA;
   break;
  case 'O': //时钟极性
   mode |= SPI_CPOL;
   break;
  case 'L': //lsb 最低有效位
   mode |= SPI_LSB_FIRST;
   break;
  case 'C': //片选高电平
   mode |= SPI_CS_HIGH;
   break;
  case '3': //3线传输模式
   mode |= SPI_3WIRE;
   break;
  case 'N': //没片选
   mode |= SPI_NO_CS;
   break;
  case 'R': //从机拉低电平停止数据传输
   mode |= SPI_READY;
   break;
  default: //错误的参数
   print_usage(argv[0]);
   break;
  }
 }
}
 
int main(int argc, char *argv[])
{
 int ret = 0;
 int fd;
 
 parse_opts(argc, argv); //解析传递进来的参数
 
 fd = open(device, O_RDWR); //打开设备文件
 if (fd < 0)
  pabort("can't open device");
 
 /*
  * spi mode //设置spi设备模式
  */
 ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); //写模式
 if (ret == -1)
  pabort("can't set spi mode");
 
 ret = ioctl(fd, SPI_IOC_RD_MODE, &mode); //读模式
 if (ret == -1)
  pabort("can't get spi mode");
 
 /*
  * bits per word //设置每个字含多少位
  */
 ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); //写 每个字含多少位
 if (ret == -1)
  pabort("can't set bits per word");
 
 ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits); //读 每个字含多少位
 if (ret == -1)
  pabort("can't get bits per word");
 
 /*
  * max speed hz  //设置速率
  */
 ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); //写速率
 if (ret == -1)
  pabort("can't set max speed hz");
 
 ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed); //读速率
 if (ret == -1)
  pabort("can't get max speed hz");
 //打印模式,每字多少位和速率信息
 printf("spi mode: %d\n", mode);
 printf("bits per word: %d\n", bits);
 printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
 
 transfer(fd); //传输测试
 
 close(fd); //关闭设备
 
 return ret;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值