wordpress vip 插件網(wǎng)站seo推廣seo教程
前言
Linux的spi接口驅(qū)動實現(xiàn)目錄在kernel\drivers\spi
下。這個目錄和一些層次比較明顯的驅(qū)動目錄布局不同,全放在這個文件夾下,因此還是只好通過看Kconfig 和 Makefile來找找思路
先看Makefile,里面關(guān)鍵幾行:
obj-$(CONFIG_SPI_MASTER) += spi.o
//這個是針對有spi控制器的soc選項,一般的soc都有spi控制器吧。
# SPI master controller drivers (bus)
//下面的這些就是針對不同soc上的spi控制器的驅(qū)動了,我們可以通過make menuconfig的時候選上自己對應(yīng)平臺的
# SPI master controller drivers (bus)
obj-$(CONFIG_SPI_ALTERA) += spi-altera.o
obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o
............
obj-$(CONFIG_SPI_AXI_SPI_ENGINE) += spi-axi-spi-engine.o
下面這些就是針對于主機作為spi從設(shè)備的時候用的,暫時貌似沒支持,畢竟現(xiàn)實中幾乎沒有用過,而是作為master端出現(xiàn)
# SPI slave protocol handlers
obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o
obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL) += spi-slave-system-control.o
再看Kconfig,第一個SPI選項我覺得有必要貼一下,首先只有選擇它了才能進行后面的配置,其次它的help對spi的描述說的很清楚!
#
# SPI driver configuration
#
menuconfig SPIbool "SPI support"depends on HAS_IOMEMhelpThe "Serial Peripheral Interface" is a low level synchronousprotocol. Chips that support SPI can have data transfer ratesup to several tens of Mbit/sec. Chips are addressed with acontroller and a chipselect. Most SPI slaves don't supportdynamic device discovery; some are even write-only or read-only.SPI is widely used by microcontrollers to talk with sensors,eeprom and flash memory, codecs and various other controllerchips, analog to digital (and d-to-a) converters, and more.MMC and SD cards can be accessed using SPI protocol; and forDataFlash cards used in MMC sockets, SPI must always be used.SPI is one of a family of similar protocols using a four wireinterface (select, clock, data in, data out) including Microwire(half duplex), SSP, SSI, and PSP. This driver framework shouldwork with most such devices and controllers.
我們其次需要配上的選項就是SPI_MASTER
和CONFIG_SPI_ROCKCHIP
(我手上的是RK的SDK)。
config SPI_MASTER
# bool "SPI Master Support"booldefault SPIhelpIf your system has an master-capable SPI controller (whichprovides the clock and chipselect), you can enable thatcontroller and the protocol drivers for the SPI slave chipsthat are connected.config SPI_ROCKCHIPtristate "Rockchip SPI controller driver"helpThis selects a driver for Rockchip SPI controller.If you say yes to this option, support will be included forRK3066, RK3188 and RK3288 families of SPI controller.Rockchip SPI controller support DMA transport and PIO mode.The main usecase of this controller is to use spi flash as bootdevice.
于是從Makefile里得到如下語句和我們相關(guān):
obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o
于是我們要分析的代碼主要有:spi.c spi-rockchip.c
,瞬間沒壓力了,就兩個文件,呵呵
下面主要從三個方面來分析spi框架
- spi控制器驅(qū)動的實現(xiàn)(畢竟spi控制器的驅(qū)動還是有可能要接觸的)
- spi設(shè)備的驅(qū)動(我們更多的是編寫設(shè)備的驅(qū)動,還是以eeprom為例吧,雖然我很想以spi接口的nor flash驅(qū)動為例,但是那又會牽涉出mtd子系統(tǒng),這個留在mtd子系統(tǒng)分析吧)
- spi核心層的實現(xiàn)(上面1、2都是以各自的驅(qū)動實現(xiàn)為目標(biāo),并不深入到spi核心層,也就是至于spi核心層怎么為我們提供的服務(wù)不去關(guān)心,只需要按spi核心層使用它提供的服務(wù)就是了。所以現(xiàn)在統(tǒng)一分析spi核心層,看它是怎么提供的服務(wù))
spi控制器驅(qū)動的實現(xiàn)
以spi-rockchip.c
為例,直接看module_platform_driver
:
static struct platform_driver rockchip_spi_driver = {.driver = {.name = DRIVER_NAME,.pm = &rockchip_spi_pm,.of_match_table = of_match_ptr(rockchip_spi_dt_match),},.probe = rockchip_spi_probe,.remove = rockchip_spi_remove,
};
平臺驅(qū)動的內(nèi)部流程就不分析了,直接看匹配成功后rockchip_spi_probe
的調(diào)用,但這里還是插入平臺spi控制器設(shè)備端相關(guān)的代碼:
- 使用
spi_alloc_master
函數(shù)為平臺設(shè)備pdev
分配一個SPI主設(shè)備結(jié)構(gòu)體,并將其大小設(shè)置為sizeof(struct rockchip_spi)
。這個函數(shù)會分配內(nèi)存并初始化主設(shè)備結(jié)構(gòu)體的各個字段。調(diào)用platform_set_drvdata
函數(shù)將主設(shè)備結(jié)構(gòu)體指針保存在平臺設(shè)備的私有數(shù)據(jù)中,以便后續(xù)在驅(qū)動的其他函數(shù)中可以訪問該指針。使用spi_master_get_devdata
函數(shù)獲取之前保存在私有數(shù)據(jù)中的主設(shè)備結(jié)構(gòu)體指針rs
。
master = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi));if (!master)return -ENOMEM;platform_set_drvdata(pdev, master);rs = spi_master_get_devdata(master);
- 使用
platform_get_resource
函數(shù)獲取SPI控制器的IO資源。這些資源包括寄存器地址、中斷號等信息,向操作系統(tǒng)請求資源空間并建立起映射為以后所用。
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);rs->regs = devm_ioremap_resource(&pdev->dev, mem);if (IS_ERR(rs->regs)) {ret = PTR_ERR(rs->regs);goto err_ioremap_resource;}
- 使用
devm_clk_get
函數(shù)獲取SPI控制器所需的時鐘,包括"apb_pclk"和"spiclk"。這些時鐘用于控制SPI控制器的時序和傳輸速率。
rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk");if (IS_ERR(rs->apb_pclk)) {dev_err(&pdev->dev, "Failed to get apb_pclk\n");ret = PTR_ERR(rs->apb_pclk);goto err_ioremap_resource;}rs->spiclk = devm_clk_get(&pdev->dev, "spiclk");if (IS_ERR(rs->spiclk)) {dev_err(&pdev->dev, "Failed to get spi_pclk\n");ret = PTR_ERR(rs->spiclk);goto err_ioremap_resource;}
- 使用
clk_prepare_enable
函數(shù)使獲取到的時鐘生效,確保SPI控制器可以正常工作。
ret = clk_prepare_enable(rs->apb_pclk);if (ret) {dev_err(&pdev->dev, "Failed to enable apb_pclk\n");goto err_ioremap_resource;}ret = clk_prepare_enable(rs->spiclk);if (ret) {dev_err(&pdev->dev, "Failed to enable spi_clk\n");goto err_spiclk_enable;}
- 調(diào)用
spi_enable_chip
函數(shù)使SPI芯片處于可用狀態(tài)。這個函數(shù)會執(zhí)行一些特定的SPI控制器寄存器的配置,以便使芯片可以正常通信。設(shè)置SPI主設(shè)備的屬性。這些屬性包括SPI總線類型、主設(shè)備指針、設(shè)備指針、最大頻率等。
spi_enable_chip(rs, 0);rs->type = SSI_MOTO_SPI;rs->master = master;rs->dev = &pdev->dev;rs->max_freq = clk_get_rate(rs->spiclk);
- 使用
of_property_read_u32
函數(shù)從設(shè)備節(jié)點中讀取屬性值。在這個例子中,它讀取了"rx-sample-delay-ns"屬性,并將其存儲在rsd_nsecs
變量中。調(diào)用get_fifo_len
函數(shù)獲取FIFO的長度。FIFO用于在SPI傳輸過程中暫存數(shù)據(jù)。
if (!of_property_read_u32(pdev->dev.of_node, "rx-sample-delay-ns",&rsd_nsecs))rs->rsd_nsecs = rsd_nsecs;rs->fifo_len = get_fifo_len(rs);
if (!rs->fifo_len) {dev_err(&pdev->dev, "Failed to get fifo length\n");ret = -EINVAL;goto err_get_fifo_len;
}
- 初始化自旋鎖。自旋鎖用于保護共享資源,防止多個進程同時訪問造成沖突。設(shè)置設(shè)備的運行時PM狀態(tài)為活動,并啟用運行時PM。允許系統(tǒng)在不需要SPI設(shè)備時將其置于低功耗狀態(tài)。
spin_lock_init(&rs->lock);pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
- 設(shè)置主設(shè)備的一些屬性,例如自動運行時PM、總線號、模式位、芯片選擇數(shù)量、設(shè)備節(jié)點等。同時設(shè)置主設(shè)備的回調(diào)函數(shù)。這些回調(diào)函數(shù)將在SPI傳輸中的不同階段被調(diào)用,以執(zhí)行相應(yīng)的操作
master->auto_runtime_pm = true;master->bus_num = pdev->id;master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_LSB_FIRST;master->num_chipselect = 2;master->dev.of_node = pdev->dev.of_node;master->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8);master->set_cs = rockchip_spi_set_cs;master->prepare_message = rockchip_spi_prepare_message;master->unprepare_message = rockchip_spi_unprepare_message;master->transfer_one = rockchip_spi_transfer_one;master->handle_err = rockchip_spi_handle_err;
- 使用
dma_request_slave_channel
函數(shù)請求DMA通道,如果同時成功請求到了TX和RX的DMA通道,則設(shè)置DMA傳輸?shù)牡刂泛头较?#xff0c;并將相應(yīng)的DMA通道設(shè)置為主設(shè)備的屬性。
rs->dma_tx.ch = dma_request_slave_channel(rs->dev, "tx");if (IS_ERR_OR_NULL(rs->dma_tx.ch)) {/* Check tx to see if we need defer probing driver */if (PTR_ERR(rs->dma_tx.ch) == -EPROBE_DEFER) {ret = -EPROBE_DEFER;goto err_get_fifo_len;}dev_warn(rs->dev, "Failed to request TX DMA channel\n");}rs->dma_rx.ch = dma_request_slave_channel(rs->dev, "rx");if (!rs->dma_rx.ch) {if (rs->dma_tx.ch) {dma_release_channel(rs->dma_tx.ch);rs->dma_tx.ch = NULL;}dev_warn(rs->dev, "Failed to request RX DMA channel\n");}
- 使用
pinctrl_lookup_state
函數(shù)查找高速模式的引腳控制狀態(tài)。檢查查找高速模式的引腳控制狀態(tài)是否成功。
rs->high_speed_state = pinctrl_lookup_state(rs->dev->pins->p,"high_speed");if (IS_ERR_OR_NULL(rs->high_speed_state)) {dev_warn(&pdev->dev, "no high_speed pinctrl state\n");rs->high_speed_state = NULL;}
- 使用
devm_spi_register_master
函數(shù)注冊SPI主設(shè)備。
ret = devm_spi_register_master(&pdev->dev, master);if (ret) {dev_err(&pdev->dev, "Failed to register master\n");goto err_register_master;}
暫時不進入到spi核心層分析,這里我們只需要知道調(diào)用核心層的注冊函數(shù)后,核心層會遍歷所有注冊到核心層的設(shè)備(實際最開始是加入到一個全局鏈表里,和I2C核心層的實現(xiàn)類似),然后嘗試著添加每一個總線號為該控制器衍生出的總線號的設(shè)備到該spi控制器上,當(dāng)然如果該spi控制器不支持某一個設(shè)備,那就取消添加這個設(shè)備,如果添加成功,那么該設(shè)備將會添加到spi總線上去,這條總線是spi核心層注冊的,用來管理spi接口的設(shè)備和spi接口的驅(qū)動。
總結(jié)下probe函數(shù)的主要工作:
- 分配和初始化SPI主設(shè)備結(jié)構(gòu)體。
- 獲取并映射IO資源。
- 獲取和使能時鐘。
- 設(shè)置SPI主設(shè)備的屬性和回調(diào)函數(shù)。
- 請求并設(shè)置DMA通道。
- 注冊SPI主設(shè)備。
spi設(shè)備的驅(qū)動
以eeprom為例,我們分析下文件at25.c:
同樣的,driver的注冊過程我們就不深入了解了,其實就是一個總線設(shè)備驅(qū)動模型。我們直接看probe函數(shù)做了什么。
static const struct of_device_id at25_of_match[] = {{ .compatible = "atmel,at25", },{ }
};
MODULE_DEVICE_TABLE(of, at25_of_match);static struct spi_driver at25_driver = {.driver = {.name = "at25",.of_match_table = at25_of_match,},.probe = at25_probe,.remove = at25_remove,
};
- 檢查
spi->dev.platform_data
是否存在,如果不存在,則調(diào)用at25_fw_to_chip
函數(shù)將固件信息轉(zhuǎn)換為芯片描述,并將其存儲在chip
結(jié)構(gòu)體中。如果存在,則直接將spi->dev.platform_data
強制類型轉(zhuǎn)換為spi_eeprom
結(jié)構(gòu)體,并將其賦值給chip
。
/* Chip description */if (!spi->dev.platform_data) {err = at25_fw_to_chip(&spi->dev, &chip);if (err)return err;} elsechip = *(struct spi_eeprom *)spi->dev.platform_data;
- 根據(jù)
chip
結(jié)構(gòu)體中的標(biāo)志位判斷EEPROM的地址長度是8位、16位還是24位,并將相應(yīng)的值賦給addrlen
變量。
/* For now we only support 8/16/24 bit addressing */if (chip.flags & EE_ADDR1)addrlen = 1;else if (chip.flags & EE_ADDR2)addrlen = 2;else if (chip.flags & EE_ADDR3)addrlen = 3;else {dev_dbg(&spi->dev, "unsupported address type\n");return -EINVAL;}
- 通過發(fā)送
AT25_RDSR
指令讀取EEPROM的狀態(tài)寄存器。如果讀取失敗或狀態(tài)寄存器中的AT25_SR_nRDY
位為1,表示EEPROM不可用,返回錯誤碼-ENXIO
。
/* Ping the chip ... the status register is pretty portable,* unlike probing manufacturer IDs. We do expect that system* firmware didn't write it in the past few milliseconds!*/sr = spi_w8r8(spi, AT25_RDSR);if (sr < 0 || sr & AT25_SR_nRDY) {dev_dbg(&spi->dev, "rdsr --> %d (%02x)\n", sr, sr);return -ENXIO;}
- 使用
devm_kzalloc
函數(shù)為at25_data
結(jié)構(gòu)體分配內(nèi)存,并使用GFP_KERNEL
標(biāo)志指定內(nèi)存分配的上下文。初始化互斥鎖at25->lock
,用于保護共享資源的訪問。將chip
結(jié)構(gòu)體和spi
設(shè)備保存在at25_data
結(jié)構(gòu)體中。使用spi_set_drvdata
函數(shù)將at25_data
結(jié)構(gòu)體指針存儲在spi
設(shè)備的私有數(shù)據(jù)中,以便在后續(xù)的函數(shù)中可以方便地訪問。將地址長度addrlen
保存在at25_data
結(jié)構(gòu)體的addrlen
字段中。
at25 = devm_kzalloc(&spi->dev, sizeof(struct at25_data), GFP_KERNEL);if (!at25)return -ENOMEM;mutex_init(&at25->lock);at25->chip = chip;at25->spi = spi_dev_get(spi);spi_set_drvdata(spi, at25);at25->addrlen = addrlen;
- 創(chuàng)建應(yīng)用層用來操作的文件,使用
sysfs_bin_attr_init
函數(shù)初始化at25->bin
成員變量,其中at25->bin
是struct bin_attribute
類型的變量。設(shè)置at25->bin
的屬性名稱為"eeprom",訪問權(quán)限為用戶只讀模式(S_IRUSR
)。設(shè)置at25->bin
的讀回調(diào)函數(shù)為at25_bin_read
,寫回調(diào)函數(shù)為at25_bin_write
。
sysfs_bin_attr_init(&at25->bin);at25->bin.attr.name = "eeprom";at25->bin.attr.mode = S_IRUSR;at25->bin.read = at25_bin_read;at25->mem.read = at25_mem_read;
- 根據(jù)
chip
的只讀標(biāo)志位(EE_READONLY
),確定是否將寫回調(diào)函數(shù)和寫權(quán)限添加到at25->bin
。
at25->bin.size = at25->chip.byte_len;if (!(chip.flags & EE_READONLY)) {at25->bin.write = at25_bin_write;at25->bin.attr.mode |= S_IWUSR;at25->mem.write = at25_mem_write;}
- 使用
sysfs_create_bin_file
函數(shù)將at25->bin
添加到SPI設(shè)備的內(nèi)核對象(spi->dev.kobj
)中,以便將EEPROM字節(jié)通過sysfs導(dǎo)出。
err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin);if (err)return err;
- 如果
chip
的setup
字段不為空,將調(diào)用chip.setup
函數(shù),并將at25->mem
和chip.context
作為參數(shù)傳遞。使用dev_info
函數(shù)打印一條設(shè)備信息消息,包括EEPROM的大小、名稱、是否只讀以及頁面大小。
if (chip.setup)chip.setup(&at25->mem, chip.context);dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n",(at25->bin.size < 1024)? at25->bin.size: (at25->bin.size / 1024),(at25->bin.size < 1024) ? "Byte" : "KByte",at25->chip.name,(chip.flags & EE_READONLY) ? " (readonly)" : "",at25->chip.page_size);
該代碼的功能是在SPI設(shè)備上探測并初始化一個EEPROM芯片,然后將EEPROM的字節(jié)通過sysfs導(dǎo)出,以便其他內(nèi)核代碼或用戶空間程序可以方便地訪問和操作EEPROM數(shù)據(jù)。
spi核心層的實現(xiàn)
主要看spi.c文件:
static int __init spi_init(void);
postcore_initcall(spi_init);
從這里可以知道spi_init
的調(diào)用(也就是spi核心層的初始化)是在驅(qū)動加載前的。
調(diào)用kmalloc
函數(shù)為SPI子系統(tǒng)分配一個大小為SPI_BUFSIZ
的內(nèi)核內(nèi)存緩沖區(qū),并將返回的指針賦值給buf
。
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
if (!buf) {status = -ENOMEM;goto err0;
}
調(diào)用bus_register
函數(shù)注冊SPI總線類型,將其添加到系統(tǒng)總線列表中。如果注冊失敗,將status
設(shè)置為返回的錯誤碼,并跳轉(zhuǎn)到err1
標(biāo)簽處進行錯誤處理。
status = bus_register(&spi_bus_type);
if (status < 0)goto err1;
調(diào)用class_register
函數(shù)注冊SPI主控制器類,將其添加到系統(tǒng)設(shè)備類列表中。如果注冊失敗,將status
設(shè)置為返回的錯誤碼,并跳轉(zhuǎn)到err2
標(biāo)簽處進行錯誤處理。
status = class_register(&spi_master_class);
if (status < 0)goto err2;
所有注冊到核心層的spi控制器都屬于這個class。