Linux驅動分析之SPI設備

前言

前面我們對SPI控制器驅動進行了分析,接下來來分析SPI設備驅動。我們以DS1302驅動作爲分析對象。DS1302是一款RTC芯片,估計很多人在學單片機時用到過。RTC芯片算是比較簡單的,也方便分析理解。

SPI設備驅動分析

內核:4.20

芯片:DS1302  RTC

下面的代碼分析主要都在註釋中,會按照驅動中函數的執行順序分析。我們不需要去關心RTC的具體內容,因爲它主要是一些讀寫寄存器的過程。應主要關注SPI的通信。

(1) 裝載和卸載函數

//dts匹配表
static const struct of_device_id ds1302_dt_ids[] = {
  { .compatible = "maxim,ds1302", },
  { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ds1302_dt_ids);


static struct spi_driver ds1302_driver = {
  .driver.name  = "rtc-ds1302",
  .driver.of_match_table = of_match_ptr(ds1302_dt_ids),
  .probe    = ds1302_probe,
  .remove    = ds1302_remove,
};
//封裝了spi_register_driver和spi_unregister_driver
module_spi_driver(ds1302_driver);

module_spi_driver宏定義在 include/linux/spi/spi.h, 具體看一下源碼

#define module_spi_driver(__spi_driver) \
  module_driver(__spi_driver, spi_register_driver, \
      spi_unregister_driver)
   
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
  return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
  __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

所以只是對 spi_register_driverspi_unregister_driver 做了封裝。

(2) probe()函數

static int ds1302_probe(struct spi_device *spi)
{
  struct rtc_device  *rtc;
  u8    addr;
  u8    buf[4];
  u8    *bp;
  int    status;


    //檢查是不是8bit傳輸
  if (spi->bits_per_word && (spi->bits_per_word != 8)) {
    dev_err(&spi->dev, "bad word length\n");
    return -EINVAL;
  } else if (spi->max_speed_hz > 2000000) {//檢查最大速率
    dev_err(&spi->dev, "speed is too high\n");
    return -EINVAL;
  } else if (spi->mode & SPI_CPHA) { 
    dev_err(&spi->dev, "bad mode\n");
    return -EINVAL;
  }
    //使用spi讀寫一下寄存器,檢查是否可以寫(DS1302有個寄存器是設置寫保護的)
  addr = RTC_ADDR_CTRL << 1 | RTC_CMD_READ;
  status = spi_write_then_read(spi, &addr, sizeof(addr), buf, 1);
    ......


  spi_set_drvdata(spi, spi);
    //註冊rtc
  rtc = devm_rtc_device_register(&spi->dev, "ds1302",
      &ds1302_rtc_ops, THIS_MODULE);


  return 0;
}

(3) RTC設置和讀取函數

//讀取時間
static int ds1302_rtc_get_time(struct device *dev, struct rtc_time *time)
{
  struct spi_device  *spi = dev_get_drvdata(dev);
  u8    addr = RTC_CLCK_BURST << 1 | RTC_CMD_READ;
  u8    buf[RTC_CLCK_LEN - 1];
  int    status;


   //spi讀取時間
  status = spi_write_then_read(spi, &addr, sizeof(addr),
      buf, sizeof(buf));
  if (status < 0)
    return status;


  /* Decode the registers */
  time->tm_sec = bcd2bin(buf[RTC_ADDR_SEC]);
  time->tm_min = bcd2bin(buf[RTC_ADDR_MIN]);
  time->tm_hour = bcd2bin(buf[RTC_ADDR_HOUR]);
  time->tm_wday = buf[RTC_ADDR_DAY] - 1;
  time->tm_mday = bcd2bin(buf[RTC_ADDR_DATE]);
  time->tm_mon = bcd2bin(buf[RTC_ADDR_MON]) - 1;
  time->tm_year = bcd2bin(buf[RTC_ADDR_YEAR]) + 100;


  return 0;
}
//設置時間
static int ds1302_rtc_set_time(struct device *dev, struct rtc_time *time)
{
  struct spi_device  *spi = dev_get_drvdata(dev);
  u8    buf[1 + RTC_CLCK_LEN];
  u8    *bp;
  int    status;


  /* Enable writing */
  bp = buf;
  *bp++ = RTC_ADDR_CTRL << 1 | RTC_CMD_WRITE;
  *bp++ = RTC_CMD_WRITE_ENABLE;
    //關閉寫保護
  status = spi_write_then_read(spi, buf, 2,
      NULL, 0);
  if (status)
    return status;


  /* Write registers starting at the first time/date address. */
  bp = buf;
  *bp++ = RTC_CLCK_BURST << 1 | RTC_CMD_WRITE;


  *bp++ = bin2bcd(time->tm_sec);
  *bp++ = bin2bcd(time->tm_min);
  *bp++ = bin2bcd(time->tm_hour);
  *bp++ = bin2bcd(time->tm_mday);
  *bp++ = bin2bcd(time->tm_mon + 1);
  *bp++ = time->tm_wday + 1;
  *bp++ = bin2bcd(time->tm_year % 100);
  *bp++ = RTC_CMD_WRITE_DISABLE;


  //只有寫,沒有讀
  return spi_write_then_read(spi, buf, sizeof(buf),
      NULL, 0);
}


static const struct rtc_class_ops ds1302_rtc_ops = {
  .read_time  = ds1302_rtc_get_time,
  .set_time  = ds1302_rtc_set_time,
};

上面讀取和設置都是調用spi_write_then_read來進行Spi通信,這個是Linux幫我們封裝好的接口函數。看一下具體實現:

int spi_write_then_read(struct spi_device *spi,
    const void *txbuf, unsigned n_tx,
    void *rxbuf, unsigned n_rx)
{
  static DEFINE_MUTEX(lock);


  int      status;
  struct spi_message  message;
  struct spi_transfer  x[2];
  u8      *local_buf;


  if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(&lock)) {
    local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx),
            GFP_KERNEL | GFP_DMA);
    if (!local_buf)
      return -ENOMEM;
  } else {
    local_buf = buf;
  }
    //初始化spi_message
  spi_message_init(&message);
    //將要傳的數據放到spi_transfer,然後追加到spi_message
  memset(x, 0, sizeof(x));
  if (n_tx) {
    x[0].len = n_tx;
    spi_message_add_tail(&x[0], &message);
  }
  if (n_rx) {
    x[1].len = n_rx;
    spi_message_add_tail(&x[1], &message);
  }


  memcpy(local_buf, txbuf, n_tx);
  x[0].tx_buf = local_buf;
  x[1].rx_buf = local_buf + n_tx;


  //進行SPI發送
  status = spi_sync(spi, &message);
  if (status == 0)
    memcpy(rxbuf, x[1].rx_buf, n_rx);


  if (x[0].tx_buf == buf)
    mutex_unlock(&lock);
  else
    kfree(local_buf);


  return status;
}

spi_sync最終會調用spi_master->transfer();傳遞給spi_sync函數的參數中有spi_device, 而spi_device中又包含spi_master的指針。所以就能找到了對應的spi控制器進行數據發送。

總結

大部分的SPI設備驅動框架都差不多,大家可以配合下面兩篇文章一起看。這樣更能理解。我們會發現,SPI設備驅動內容其實就是使用SPI控制器(spi_master)去對具體芯片設備進行讀寫。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章