水準有限,描述不當之處還請之處,轉載請注明出處http://blog.csdn.net/vanbreaker/article/details/7737833
本節以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);
else
kfree(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就完全一樣了
static ssize_t
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架構已分析完畢。