Linux SPI框架

水平有限,描述不当之处还请指出,转载请注明出处http://blog.csdn.net/vanbreaker/article/details/7733476

====

Linux的SPI子系统采用主机驱动和外设驱动分离的思想,首先主机SPI控制器是一种平台设备,因此它以platform的方式注册进内核,外设的信息是以boardinfo形式静态定义的,在创建spi_master时,会根据外设的bus_num和主机的bus_num是否相等,来选择是否将该外设挂接在该SPI主控制器下。先看SPI子系统中几个关键的数据结构:

struct spi_master用来描述一个SPI主控制器

struct spi_master {  struct device    dev;  s16    bus_num; /*总线编号*/  u16    num_chipselect;/*支持的外设数量*/  u16    dma_alignment;  int   (*transfer)(struct spi_device *spi, struct spi_message *mesg);/*用于将消息添加到队列*/  void  (*cleanup)(struct spi_device *spi);  
};  
struct spi_device用来描述一个SPI从设备
struct spi_device {  struct device       dev;  struct spi_master   *master;                 /*从设备所属的SPI主控器*/  u32         max_speed_hz;   /*最大传输频率*/  u8          chip_select;    /*片选号,用于区别其他从设备*/  u8          mode;           /*传输模式*/  
/*各个mode的定义*/  
#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 */  u8          bits_per_word; /*每个字的比特数*/  int         irq;           /*所使用的中断*/  void            *controller_state;  void            *controller_data;  char            modalias[32];  /*设备名,在和从设备驱动匹配时会用到*/  
};  
struct spi_driver用来描述一个SPI从设备的驱动,它的形式和struct platform_driver是一致的
struct spi_driver {  int         (*probe)(struct spi_device *spi);  int         (*remove)(struct spi_device *spi);  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;  
};  
SPI子系统初始化的第一步就是将SPI总线注册进内核,并且在/sys下创建一个spi_master的类,以后注册的从设备都将挂接在该总线下
static int __init spi_init(void)  
{  int status;  buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);  if (!buf) {  status = -ENOMEM;  goto err0;  }  status = bus_register(&spi_bus_type);//注册SPI总线  if (status < 0)  goto err1;  status = class_register(&spi_master_class);//注册spi_master类  if (status < 0)  goto err2;  return 0;  

err2: bus_unregister(
&spi_bus_type); err1: kfree(buf); buf = NULL; err0: return status; }

我们来看spi_bus_type的定义

struct bus_type spi_bus_type = {  .name       = "spi",  .dev_attrs  = spi_dev_attrs,  .match      = spi_match_device,  .uevent     = spi_uevent,  .suspend    = spi_suspend,  .resume     = spi_resume,  
};  

来看挂接在SPI总线下的从设备和从设备驱动是如何匹配的,也就是spi_match_device函数

static int spi_match_device(struct device *dev, struct device_driver *drv)  
{  const struct spi_device *spi = to_spi_device(dev);  return strcmp(spi->modalias, drv->name) == 0;  
}  

这里可以看到是将struct device_driver中的name字段与struct spi_device中的modalias字段进行匹配

这里已经完成了SPI子系统初始化的第一步,也就是注册SPI总线,这一步是和平台无关的,第二步是和平台相关的初始化,下一节再做介绍。

 ====

上节介绍了SPI子系统中的一些重要数据结构和SPI子系统初始化的第一步,也就是注册SPI总线。这节介绍针对于s3c24xx平台的SPI子系统初始化,在看具体的代码之前,先上一张自己画的图,帮助理清初始化的主要步骤

显然,SPI是一种平台特定的资源,所以它是以platform平台设备的方式注册进 内核的,因此它的struct platform_device结构是已经静态定义好了的,现在只待它的struct platform_driver注册,然后和platform_device匹配。

初始化的入口:

static int __init s3c24xx_spi_init(void)  
{  return platform_driver_probe(&s3c24xx_spi_driver, s3c24xx_spi_probe);  
}  

platform_driver_probe()会调用 platform_driver_register()来注册驱动,然后在注册的过程中寻求匹配的platform_device,一旦匹配成功,便会调 用probe函数,也就是s3c24xx_spi_probe(),在看这个函数之前,还得介绍几个相关的数据结构。

struct s3c2410_spi_info是一个板级结构,也是在移植时就定义好的,在初始化spi_master时用到,platform_device-->dev-->platform_data会指向这个结构。

struct s3c2410_spi_info {  int          pin_cs;    /* simple gpio cs */  unsigned int         num_cs;    /* total chipselects */  int          bus_num;/* bus number to use. */  void (*gpio_setup)(struct s3c2410_spi_info *spi, int enable);  void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol);  
};  

struct s3c24xx_spi用来具体描述s3c24xx平台上一个SPI控制器

struct s3c24xx_spi {  /* bitbang has to be first */  struct spi_bitbang   bitbang;  struct completion    done;  void __iomem        *regs;  int          irq;  int          len;  int          count;  void            (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol);  /* data buffers */  const unsigned char *tx;  unsigned char       *rx;  struct clk      *clk;  struct resource     *ioarea;  struct spi_master   *master;  struct spi_device   *curdev;  struct device       *dev;  struct s3c2410_spi_info *pdata;  
};  

struct spi_bitbang用于控制实际的数据传输

struct spi_bitbang {  struct workqueue_struct *workqueue;  /*工作队列*/  struct work_struct  work;  spinlock_t      lock;  struct list_head    queue;  u8          busy;  u8          use_dma;  u8          flags;      /* extra spi->mode support */  struct spi_master   *master;         /*bitbang所属的master*/  /*用于设置设备传输时的时钟,字长等*/  int (*setup_transfer)(struct spi_device *spi, struct spi_transfer *t);  void (*chipselect)(struct spi_device *spi, int is_on);  
#define BITBANG_CS_ACTIVE   1   /* normally nCS, active low */  
#define BITBANG_CS_INACTIVE 0  /*针对于平台的传输控制函数*/  int (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t);  /* txrx_word[SPI_MODE_*]() just looks like a shift register */  u32 (*txrx_word[4])(struct spi_device *spi,  unsigned nsecs,  u32 word, u8 bits);  
};  

 下面来看s3c24xx_spi_probe()函数的实现

static int __init s3c24xx_spi_probe(struct platform_device *pdev)  
{  struct s3c2410_spi_info *pdata;  struct s3c24xx_spi *hw;  struct spi_master *master;  struct resource *res;  int err = 0;  /*创建spi_master,并将spi_master->private_data指向s3c24xx_spi*/  master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));  if (master == NULL) {  dev_err(&pdev->dev, "No memory for spi_master\n");  err = -ENOMEM;  goto err_nomem;  }  hw = spi_master_get_devdata(master);//获取s3c24xx_spimemset(hw, 0, sizeof(struct s3c24xx_spi));  hw->master = spi_master_get(master);  hw->pdata = pdata = pdev->dev.platform_data;  hw->dev = &pdev->dev;  if (pdata == NULL) {  dev_err(&pdev->dev, "No platform data supplied\n");  err = -ENOENT;  goto err_no_pdata;  }  platform_set_drvdata(pdev, hw);  init_completion(&hw->done);  /* setup the master state. */  /*片选数和SPI主控制器编号是在platform_data中已经定义好了的*/  master->num_chipselect = hw->pdata->num_cs;  master->bus_num = pdata->bus_num;  /* setup the state for the bitbang driver */  /*设置bitbang的所属master和控制传输的相关函数*/  hw->bitbang.master         = hw->master;  hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;  hw->bitbang.chipselect     = s3c24xx_spi_chipsel;  hw->bitbang.txrx_bufs      = s3c24xx_spi_txrx;  hw->bitbang.master->setup  = s3c24xx_spi_setup;  dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);  /* find and map our resources */  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  if (res == NULL) {  dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");  err = -ENOENT;  goto err_no_iores;  }  hw->ioarea = request_mem_region(res->start, (res->end - res->start)+1, pdev->name);  if (hw->ioarea == NULL) {  dev_err(&pdev->dev, "Cannot reserve region\n");  err = -ENXIO;  goto err_no_iores;  }  /*映射SPI控制寄存器*/  hw->regs = ioremap(res->start, (res->end - res->start)+1);  if (hw->regs == NULL) {  dev_err(&pdev->dev, "Cannot map IO\n");  err = -ENXIO;  goto err_no_iomap;  }  /*获取中断号*/  hw->irq = platform_get_irq(pdev, 0);  if (hw->irq < 0) {  dev_err(&pdev->dev, "No IRQ specified\n");  err = -ENOENT;  goto err_no_irq;  }  /*注册中断*/  err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw);  if (err) {  dev_err(&pdev->dev, "Cannot claim IRQ\n");  goto err_no_irq;  }  hw->clk = clk_get(&pdev->dev, "spi");  if (IS_ERR(hw->clk)) {  dev_err(&pdev->dev, "No clock for device\n");  err = PTR_ERR(hw->clk);  goto err_no_clk;  }  /* setup any gpio we can */  if (!pdata->set_cs) {  if (pdata->pin_cs < 0) {  dev_err(&pdev->dev, "No chipselect pin\n");  goto err_register;  }  err = gpio_request(pdata->pin_cs, dev_name(&pdev->dev));  if (err) {  dev_err(&pdev->dev, "Failed to get gpio for cs\n");  goto err_register;  }  hw->set_cs = s3c24xx_spi_gpiocs;//设定片选函数gpio_direction_output(pdata->pin_cs, 1);  } else  hw->set_cs = pdata->set_cs;  
s3c24xx_spi_initialsetup(hw);
/* register our spi controller */ /* 注册主机SPI控制器 */ err = spi_bitbang_start(&hw->bitbang); if (err) { dev_err(&pdev->dev, "Failed to register SPI master\n"); goto err_register; } return 0; err_register: if (hw->set_cs == s3c24xx_spi_gpiocs) gpio_free(pdata->pin_cs); \
clk_disable(hw
->clk); clk_put(hw->clk); err_no_clk: free_irq(hw->irq, hw); err_no_irq: iounmap(hw->regs);
}

-

int spi_bitbang_start(struct spi_bitbang *bitbang)  
{  int status;  if (!bitbang->master || !bitbang->chipselect)  return -EINVAL;  /*初始化一个struct work,处理函数为bitbang_work*/  INIT_WORK(&bitbang->work, bitbang_work);  spin_lock_init(&bitbang->lock);  INIT_LIST_HEAD(&bitbang->queue);  /*检测bitbang中的函数是否都定义了,如果没定义,则默认使用spi_bitbang_xxx*/  if (!bitbang->master->transfer)  bitbang->master->transfer = spi_bitbang_transfer;  if (!bitbang->txrx_bufs) {  bitbang->use_dma = 0;  bitbang->txrx_bufs = spi_bitbang_bufs;  if (!bitbang->master->setup) {  if (!bitbang->setup_transfer)  bitbang->setup_transfer = spi_bitbang_setup_transfer;  bitbang->master->setup = spi_bitbang_setup;  bitbang->master->cleanup = spi_bitbang_cleanup;  }  } else if (!bitbang->master->setup)  return -EINVAL;  /* this task is the only thing to touch the SPI bits */  bitbang->busy = 0;  /*创建bitbang的工作队列*/  bitbang->workqueue = create_singlethread_workqueue(  dev_name(bitbang->master->dev.parent));  if (bitbang->workqueue == NULL) {  status = -EBUSY;  goto err1;  }  /* driver may get busy before register() returns, especially * if someone registered boardinfo for devices */  /*注册spi_master*/  status = spi_register_master(bitbang->master);  if (status < 0)  goto err2;  return status;  err2:  destroy_workqueue(bitbang->workqueue);  err1:  return status;  
}  
下一个关键函数就是spi_register_master(),用于注册spi_master
int spi_register_master(struct spi_master *master)  
{  static atomic_t     dyn_bus_id = ATOMIC_INIT((1<<15) - 1);  struct device       *dev = master->dev.parent;  int         status = -ENODEV;  int         dynamic = 0;  

if (!dev) return -ENODEV; /* even if it's just one always-selected device, there must * be at least one chipselect */ if (master->num_chipselect == 0)//片选数不能为0 return -EINVAL; /* convention: dynamically assigned bus IDs count down from the max */ if (master->bus_num < 0) { /* FIXME switch to an IDR based scheme, something like * I2C now uses, so we can't run out of "dynamic" IDs */ master->bus_num = atomic_dec_return(&dyn_bus_id); dynamic = 1; } /* register the device, then userspace will see it. * registration fails if the bus ID is in use. */ dev_set_name(&master->dev, "spi%u", master->bus_num); status = device_add(&master->dev);//添加spi_master设备 if (status < 0) goto done; dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev), dynamic ? " (dynamic)" : ""); /* populate children from any spi device tables */ scan_boardinfo(master);//遍历板级信息,寻找可以挂接在该spi_master下的从设备 status = 0; done: return status; }

-

static void scan_boardinfo(struct spi_master *master)  
{  struct boardinfo    *bi;  mutex_lock(&board_lock);  list_for_each_entry(bi, &board_list, list) {  struct spi_board_info   *chip = bi->board_info;  unsigned        n;  for (n = bi->n_board_info; n > 0; n--, chip++) {  if (chip->bus_num != master->bus_num)  continue;  /* NOTE: this relies on spi_new_device to * issue diagnostics when given bogus inputs */  /*bus_num相等则创建新设备*/  (void) spi_new_device(master, chip);  }  }  mutex_unlock(&board_lock);  
}  

spi_board_info是板级信息,是在移植时就写好的,并且要将其注册

struct spi_board_info {  char        modalias[32];  /*名字*/  const void  *platform_data;  void        *controller_data;  int     irq;          /*中断号*/  u32     max_speed_hz; /*最高传输速率*/  u16     bus_num;      /*所属的spi_master编号*/  u16     chip_select;  /*片选号*/  u8      mode;         /*传输模式*/  
};  

最后一步就是将相应的从设备注册进内核

struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip)  
{  struct spi_device   *proxy;  int         status;  /* NOTE:  caller did any chip->bus_num checks necessary. * * Also, unless we change the return value convention to use * error-or-pointer (not NULL-or-pointer), troubleshootability * suggests syslogged diagnostics are best here (ugh). */  /*创建SPI_device*/  proxy = spi_alloc_device(master);  if (!proxy)return NULL;  
WARN_ON(strlen(chip
->modalias) >= sizeof(proxy->modalias)); /*初始化*/ proxy->chip_select = chip->chip_select; proxy->max_speed_hz = chip->max_speed_hz; proxy->mode = chip->mode; proxy->irq = chip->irq; strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias)); proxy->dev.platform_data = (void *) chip->platform_data; proxy->controller_data = chip->controller_data; proxy->controller_state = NULL; /*将新设备添加进内核*/ status = spi_add_device(proxy); if (status < 0) { spi_dev_put(proxy); return NULL; } return proxy; }
本节以spidev设备驱动为例,来阐述SPI数据传输的过程。spidev是内核中一个通用的设备驱动,我们注册的从设备都可以使用该驱动,只需在注册 时将从设备的modalias字段设置为"spidev",这样才能和spidev驱动匹配成功。我们要传输的数据有时需要分为一段一段的(比如先发送, 后读取,就需要两个字段),每个字段都被封装成一个transfer,N个transfer可以被添加到message中,作为一个消息包进行传输。当用 户发出传输数据的请求时,message并不会立刻传输到从设备,而是由之前定义的transfer()函数将message放入一个等待队列中,这些 message会以FIFO的方式有workqueue调度进行传输,这样能够避免SPI从设备同一时间对主SPI控制器的竞争。和之前一样,还是习惯先 画一张图来描述数据传输的主要过程。在使用spidev设备驱动时,需要先初始化spidev. spidev是以字符设备的形式注册进内核的。
static int __init spidev_init(void)  
{  int status;  /* Claim our 256 reserved device numbers.  Then register a class * that will key udev/mdev to add/remove /dev nodes.  Last, register * the driver which manages those device numbers. */  BUILD_BUG_ON(N_SPI_MINORS > 256);  /*将spidev作为字符设备注册*/  status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);  if (status < 0)  return status;  /*创建spidev类*/  spidev_class = class_create(THIS_MODULE, "spidev");  if (IS_ERR(spidev_class)) {  unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);  return PTR_ERR(spidev_class);  }  /*注册spidev的driver,可与modalias字段为"spidev"的spi_device匹配*/  status = spi_register_driver(&spidev_spi);  if (status < 0) {  class_destroy(spidev_class);  unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);  }  return status;  
}  
与相应的从设备匹配成功后,则调用spidev中的probe函数
static int spidev_probe(struct spi_device *spi)  
{  struct spidev_data  *spidev;  int         status;  unsigned long       minor;  /* Allocate driver data */  spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);  if (!spidev)  return -ENOMEM;  /* Initialize the driver data */  spidev->spi = spi;//设定spi  spin_lock_init(&spidev->spi_lock);  mutex_init(&spidev->buf_lock);  INIT_LIST_HEAD(&spidev->device_entry);  /* If we can allocate a minor number, hook up this device. * Reusing minors is fine so long as udev or mdev is working. */  mutex_lock(&device_list_lock);  minor = find_first_zero_bit(minors, N_SPI_MINORS);//寻找没被占用的次设备号  if (minor < N_SPI_MINORS) {  struct device *dev;  /*计算设备号*/  spidev->devt = MKDEV(SPIDEV_MAJOR, minor);  /*在spidev_class下创建设备*/  dev = device_create(spidev_class, &spi->dev, spidev->devt,  spidev, "spidev%d.%d",  spi->master->bus_num, spi->chip_select);  status = IS_ERR(dev) ? PTR_ERR(dev) : 0;  } else {  dev_dbg(&spi->dev, "no minor number available!\n");  status = -ENODEV;  }  if (status == 0) {  set_bit(minor, minors);//将minors的相应位置位,表示该位对应的次设备号已被占用 list_add(&spidev->device_entry, &device_list);//将创建的spidev添加到device_list  }  mutex_unlock(&device_list_lock);  if (status == 0)  spi_set_drvdata(spi, spidev);  elsekfree(spidev);  return status;  
}  
然后就可以利用spidev模块提供的接口来实现主从设备之间的数据传输了。我们以spidev_write()函数为例来分析数据传输的过程,实际上spidev_read()和其是差不多的,只是前面的一些步骤不一样,可以参照上图。
static ssize_t 
spidev_write(
struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct spidev_data *spidev; ssize_t status = 0; unsigned long missing; /* chipselect only toggles at start or end of operation */ if (count > bufsiz) return -EMSGSIZE; spidev = filp->private_data; mutex_lock(&spidev->buf_lock); //将用户要发送的数据拷贝到spidev->buffer missing = copy_from_user(spidev->buffer, buf, count); if (missing == 0) {//全部拷贝成功,则调用spidev_sysn_write() status = spidev_sync_write(spidev, count); } else status = -EFAULT; mutex_unlock(&spidev->buf_lock); return status; }
-
static inline ssize_t spidev_sync_write(struct spidev_data *spidev, size_t len)  
{  struct spi_transfer t = {//设置传输字段  .tx_buf     = spidev->buffer,  .len        = len,  };  struct spi_message   m;//创建message  spi_message_init(&m);  spi_message_add_tail(&t, &m);//将transfer添加到message中  return spidev_sync(spidev, &m);  
}  
我们来看看struct spi_transfer和struct 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;//发送缓冲区  void        *rx_buf;//接收缓冲区  unsigned    len;    //传输数据的长度  dma_addr_t  tx_dma;  dma_addr_t  rx_dma;  unsigned    cs_change:1; //该位如果为1,则表示当该transfer传输完后,改变片选信号  u8      bits_per_word;//字比特数  u16     delay_usecs;  //传输后的延时   u32     speed_hz;  //指定的时钟  struct list_head transfer_list;//用于将该transfer链入message  
};  
-
struct spi_message {  struct list_head    transfers;//用于链接spi_transfer  struct spi_device   *spi;      //指向目的从设备  unsigned        is_dma_mapped:1;  /* 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;  /* 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; //用于将该message链入bitbang等待队列  void            *state;  
};  
继续跟踪源码,进入spidev_sync(),从这一步开始,read和write就完全一样了
spidev_sync(struct spidev_data *spidev, struct spi_message *message)  
{DECLARE_COMPLETION_ONSTACK(done);  int status;  message->complete = spidev_complete;//设置回调函数  message->context = &done;              spin_lock_irq(&spidev->spi_lock);  if (spidev->spi == NULL)  status = -ESHUTDOWN;  else  status = spi_async(spidev->spi, message);//调用spi核心层的函数spi_async()  
spin_unlock_irq(&spidev->spi_lock);  if (status == 0) {  wait_for_completion(&done);  status = message->status;  if (status == 0)  status = message->actual_length;  }  return status;  
}
-
 
static inline int spi_async(struct spi_device *spi, struct spi_message *message)  
{message->spi = spi;  /*调用master的transfer函数将message放入等待队列*/  return spi->master->transfer(spi, message);  
}  
s3c24xx平台下的transfer函数是在bitbang_start()函数中定义的,为bitbang_transfer()
int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m) 
{  struct spi_bitbang  *bitbang;  unsigned long       flags;  int         status = 0;  m->actual_length = 0;  m->status = -EINPROGRESS;  bitbang = spi_master_get_devdata(spi->master);  spin_lock_irqsave(&bitbang->lock, flags);  if (!spi->max_speed_hz)  status = -ENETDOWN;  else {  list_add_tail(&m->queue, &bitbang->queue);//将message添加到bitbang的等待队列  queue_work(bitbang->workqueue, &bitbang->work);//调度运行work  }  spin_unlock_irqrestore(&bitbang->lock, flags);  return status;  
}  
这里可以看到transfer函数不负责实际的数据传输,而是将message添加到 等待队列中。同样在spi_bitbang_start()中,有这样一个定义INIT_WORK(&bitbang->work, bitbang_work);因此bitbang_work()函数会被调度运行,类似于底半部机制
static void bitbang_work(struct work_struct *work)  
{  struct spi_bitbang  *bitbang = container_of(work, struct spi_bitbang, work);//获取bitbang  unsigned long       flags;  spin_lock_irqsave(&bitbang->lock, flags);  bitbang->busy = 1;  while (!list_empty(&bitbang->queue)) {//等待队列不为空  struct spi_message  *m;  struct spi_device   *spi;  unsigned        nsecs;  struct spi_transfer *t = NULL;  unsigned        tmp;  unsigned        cs_change;  int         status;  int         (*setup_transfer)(struct spi_device *, struct spi_transfer *);  /*取出等待队列中的的第一个message*/  m = container_of(bitbang->queue.next, struct spi_message, queue);  list_del_init(&m->queue);//将message从队列中删除  spin_unlock_irqrestore(&bitbang->lock, flags);  /* FIXME this is made-up ... the correct value is known to * word-at-a-time bitbang code, and presumably chipselect() * should enforce these requirements too? */  nsecs = 100;  spi = m->spi;  tmp = 0;  cs_change = 1;  status = 0;  setup_transfer = NULL;  /*遍历message中的所有传输字段,逐一进行传输*/  list_for_each_entry (t, &m->transfers, transfer_list) {  /* override or restore speed and wordsize */  if (t->speed_hz || t->bits_per_word) {  setup_transfer = bitbang->setup_transfer;  if (!setup_transfer) {  status = -ENOPROTOOPT;  break;  }  }  /*调用setup_transfer根据transfer中的信息进行时钟、字比特数的设定*/  if (setup_transfer) {  status = setup_transfer(spi, t);  if (status < 0)  break;  }  /* set up default clock polarity, and activate chip; * this implicitly updates clock and spi modes as * previously recorded for this device via setup(). * (and also deselects any other chip that might be * selected ...) */  if (cs_change) {//使能外设的片选  bitbang->chipselect(spi, BITBANG_CS_ACTIVE);  ndelay(nsecs);  }  cs_change = t->cs_change;//这里确定进行了这个字段的传输后是否要改变片选状态  
if (!t->tx_buf && !t->rx_buf && t->len) { status = -EINVAL; break; } /* transfer data. the lower level code handles any * new dma mappings it needs. our caller always gave * us dma-safe buffers. */ if (t->len) { /* REVISIT dma API still needs a designated * DMA_ADDR_INVALID; ~0 might be better. */ if (!m->is_dma_mapped) t->rx_dma = t->tx_dma = 0; /*调用针对于平台的传输函数txrx_bufs*/ status = bitbang->txrx_bufs(spi, t); } if (status > 0) m->actual_length += status; if (status != t->len) { /* always report some kind of error */ if (status >= 0) status = -EREMOTEIO; break; } status = 0; /* protocol tweaks before next transfer */ /*如果要求在传输完一个字段后进行delay,则进行delay*/ if (t->delay_usecs) udelay(t->delay_usecs); if (!cs_change) continue; /*最后一个字段传输完毕了,则跳出循环*/ if (t->transfer_list.next == &m->transfers) break; /* sometimes a short mid-message deselect of the chip * may be needed to terminate a mode or command */ ndelay(nsecs); bitbang->chipselect(spi, BITBANG_CS_INACTIVE); ndelay(nsecs); } m->status = status; m->complete(m->context); /* restore speed and wordsize */ if (setup_transfer) setup_transfer(spi, NULL); /* normally deactivate chipselect ... unless no error and * cs_change has hinted that the next message will probably * be for this chip too. */ if (!(status == 0 && cs_change)) { ndelay(nsecs); bitbang->chipselect(spi, BITBANG_CS_INACTIVE); ndelay(nsecs); } spin_lock_irqsave(&bitbang->lock, flags); } bitbang->busy = 0; spin_unlock_irqrestore(&bitbang->lock, flags); }
只要bitbang->queue等待队列不为空,就表示相应的SPI主控制器上还有 传输任务没有完成,因此bitbang_work()会被不断地调度执行。 bitbang_work()中的工作主要是两个循环,外循环遍历等待队列中的message,内循环遍历message中的transfer,在 bitbang_work()中,传输总是以transfer为单位的。当选定了一个transfer后,便会调用transfer_txrx()函数, 进行实际的数据传输,显然这个函数是针对于平台的SPI控制器而实现的,在s3c24xx平台中,该函数为s3c24xx_spi_txrx();

static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)  
{  struct s3c24xx_spi *hw = to_hw(spi);  
dev_dbg(
&spi->dev, "txrx: tx %p, rx %p, len %d\n", t->tx_buf, t->rx_buf, t->len); hw->tx = t->tx_buf;//获取发送缓冲区 hw->rx = t->rx_buf;//获取读取缓存区 hw->len = t->len; //获取数据长度 hw->count = 0; init_completion(&hw->done);//初始化完成量 /* send the first byte */ /*只发送第一个字节,其他的在中断中发送(读取)*/ writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT); wait_for_completion(&hw->done); return hw->count; }
-
static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count)  
{  /*如果tx不为空,也就是说当前是从主机向从机发送数据,则直接将tx[count]发送过去, 如果tx为空,也就是说当前是从从机向主机发送数据,则向从机写入0*/  return hw->tx ? hw->tx[count] : 0;  
}  
负责SPI数据传输的中断函数:

static irqreturn_t s3c24xx_spi_irq(int irq, void *dev)  
{  struct s3c24xx_spi *hw = dev;  unsigned int spsta = readb(hw->regs + S3C2410_SPSTA);  unsigned int count = hw->count;  /*冲突检测*/  if (spsta & S3C2410_SPSTA_DCOL) {  dev_dbg(hw->dev, "data-collision\n");  complete(&hw->done);  goto irq_done;  }  /*设备忙检测*/  if (!(spsta & S3C2410_SPSTA_READY)) {  dev_dbg(hw->dev, "spi not ready for tx?\n");  complete(&hw->done);  goto irq_done;  }  hw->count++;  if (hw->rx)//读取数据到缓冲区  hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT);  count++;  if (count < hw->len)//向从机写入数据  writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT);  else//count == len,一个字段发送完成,唤醒完成量  complete(&hw->done);  irq_done:  return IRQ_HANDLED;  
}  
这里可以看到一点,即使tx为空,也就是说用户申请的是从从设备读取数据,也要不断地向从设备写入数据,只不过写入从设备的是无效数据(0),这样做得目的是为了维持SPI总线上的时钟。至此,SPI框架已分析完毕。

 

转载于:https://www.cnblogs.com/tfanalysis/articles/3766212.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/376940.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重构——解决过长参数列表(long parameter list)

目录1、Replace Param with Query2、Preserve Whole Object3、Introduce Param Object4、Remove Flag Argument5、Combine Functions into ClassReference当我们需要在超长函数中提炼子函数时&#xff0c;如果函数内有大量的参数和临时变量&#xff0c;这将会对函数的提炼形成很…

从uptime、stress、mpstat、pidstat观察CPU密集型、IO密集型、进程密集型切换的系统性能

uptime dyydyy-Lenovo-ThinkBook-14-IIL:~$ uptime10:27:10 up 7 min, 1 user, load average: 1.32, 0.99, 0.49结果分别对应&#xff1a;当前时间、系统运行时间、当前用户数目、过去 1 分钟、5 分钟、15 分钟的平均负载(Load Average) 平均负载是指单位时间内&#xff0c…

多台计算机共享内存_共享内存多处理器和指令执行| 计算机架构

多台计算机共享内存共享内存多处理器 (Shared Memory Multiprocessor) There are three types of shared memory multiprocessor: 共有三种类型的共享内存多处理器&#xff1a; UMA (Uniform Memory Access) UMA(统一内存访问) NUMA (Non- uniform Memory Access) NUMA(非统一…

方法重写,隐藏在子类父类中的各种调用实践

一.子类和父类方法之间的关系 1.当子类和父类有方法完全相同的方法 namespace ConsoleApplication2 {class Program{static void Main(string[] args){B b new B();A a new A();A c new B();b.Show();a.Show();c.Show();Console.Read();}}public class A{public void Show()…

(解决)从同事那里取来的工程不能编译运行,出现以下错误,求帮助

错误 6 未能从程序集 C:\Program Files (x86)\MSBuild\Microsoft\Silverlight for Phone\v4.0\Microsoft.Phone.Build.Tasks.dll 加载任务“Microsoft.Phone.Build.Tasks.ValidateWMAppManifest”。 Could not load file or assembly Microsoft.Build.Utilities, Version2.0.0…

vmstat、sysbench、/proc/interrupts,性能压测

如何查看系统的上下文切换情况 vmstat 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数。 # 每隔 5 秒输出 1 组数据 vmstat 5procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----r …

Linux系统上的程序调优思路概要

目录文件系统Linux内核应用程序架构设计性能监控性能测试CPU内存网络磁盘IO文件系统 Linux内核 应用程序 架构设计 性能监控 性能测试 CPU 内存 网络 磁盘IO

观察者模式Java实现

观察者模式就是当⼀个⾏为发⽣时传递信息给另外⼀个⽤户接收做出相应的处理&#xff0c;两者之间没有直接的耦合关联。 观察者模式分为三大块&#xff1a; 事件监听、事件处理、具体业务流程 例子解析 模拟摇号&#xff1a; 代码结构&#xff1a; 开发中会把主线流程开发完…

JavaScript | 声明数组并在每个循环中使用的代码

Declare an array and we have to print its elements/items using for each loop in JavaScript. 声明一个数组&#xff0c;我们必须使用JavaScript中的每个循环来打印其元素/项目。 Code: 码&#xff1a; <html><head><script>var fruits ["apple&…

拾牙的2021年秋招总结(大概会有帮助?)

目录秋招面试经历秋招面经参考基础部分面经常见问题对秋招一些经验最后收获后续安排秋招面试经历 时间公司岗位面试轮次是否完成2021年7月2日 07:00禾赛嵌入式软件工程师提前批一面pass2021年7月7日 16:00图森未来软件研发工程师-Linux应用提前批一面not pass2021年7月9日华为…

CPU使用率的查看以及性能分析(perf top/record/report)

目录CPU使用率查看CPU使用率&#xff08;top、pidstat解释&#xff09;CPU使用率过高perf topperf record 和 perf reportCPU使用率 Linux通过/proc虚拟文件系统&#xff0c;向用户空间提供了系统内部状态的信息。 /proc/stat提供的就是系统的CPU和任务统计信息。 执行命令cat…

如何从JavaScript数组中获取多个随机唯一元素?

The JavaScript is a very versatile language and it has a function almost everything that you want. JavaScript是一种非常通用的语言&#xff0c;它几乎具有您想要的所有功能。 Here, we will show you how to generate random unique elements from an array in JavaSc…

什么是ACID理论(二阶段、三阶段提交、TCC)

目录二阶段提交协议TCC&#xff08;Try-Confirm-Cancel&#xff09;预留成功预留失败三阶段提交协议总结Some questionsreferenceACID理论时对事务特性的抽象和总结&#xff0c;想要实现ACID需要掌握二阶段提交协议以及TCC 这里是有关协议的论文PDF链接&#xff1a; CONCURRENC…

oracle安装后新建数据库实例及配置

ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务 的解决方法 (2011-01-20 13:50:37) 转载▼标签&#xff1a; it 分类&#xff1a; 技术早上同事用PL/SQL连接虚拟机中的Oracle数据库&#xff0c;发现又报了“ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务…

html5游戏开发--动静结合(二)-用地图块拼成大地图 初探lufylegend

一、前言 本次教程将向大家讲解如何用html5将小地图块拼成大地图&#xff0c;以及如何用现有的高级html5游戏开发库件lufylegend.js开发游戏。 首先让我们来了解了解如何用html5实现动画&#xff0c;毕竟“动静结合”是先有动再有静。看了上一章的内容&#xff0c;或许你就有了…

BASE理论(基本可用策略+ 最终一致性实现)

目录实现基本可用的几个策略1、流量削峰&#xff08;不同地区售票时间错峰出售&#xff09;2、延迟响应&#xff0c;异步处理&#xff08;买票排队&#xff0c;基于队列先收到用户买票请求&#xff0c;排队异步处理&#xff0c;延迟响应&#xff09;3、体验降级&#xff08;看到…

Paxos算法(Basic Paxos 与 Multi-Paxos思想)

目录Basic Paxos三个角色达成共识的方法对于Basic Paxos的总结Multi-Paxos领导者优化 Basic Paxos 执行referencePaxos 算法包含 2 个部分&#xff1a; 1、Basic Paxos &#xff1a; 描述多节点之间如何就某个值达成共识 2、Multi-Paxos &#xff1a; 描述执行多个Basic Paxos实…

vs2012下调试mvc4源代码

当前流行的应该是mvc3才对。然后在研究mvc3的源代码时候&#xff0c;Html这个属性下的扩展方法Partial()都没有。IntelliSense不会提示该方法&#xff0c;找了半天的资料也问了一些博友&#xff0c;没看到好的解决棒法。最后没辙另辟蹊跷&#xff0c;就开始着手研究mvc4的源代码…

JAVA UDP网络编程学习笔记

一、UDP网络编程概述 采用TCP协议通信时&#xff0c;客户端的Socket必须先与服务器建立连接&#xff0c;连接建立成功后&#xff0c;服务器端也会持有客户端连接的Socket&#xff0c;客户端的Socket与服务器端的Socket是对应的&#xff0c;它们构成了两个端点之间的虚拟通信链路…