在Linux內(nèi)核中更加推薦使用隊列模式的SPI控制器驅(qū)動,而且隊列模式的SPI控制器驅(qū)動也更加簡單,只需要在驅(qū)動中實現(xiàn)單個spi_transfer的傳輸即可,將spi_message拆解為spi_transfer、片選GPIO控制、統(tǒng)計信息更新等均由SPI核心去完成。
編寫驅(qū)動程序步驟
- 實現(xiàn)設(shè)置SPI總線的函數(shù)setup,用于設(shè)置SPI總線,若片選采用GPIO編號模式還需要在這里將GPIO設(shè)置為輸出
- 實現(xiàn)SPI總線數(shù)據(jù)傳輸?shù)暮瘮?shù)transfer_one,用于傳輸SPI的數(shù)據(jù)
- 通過spi_alloc_master分配一個struct spi_master
- 初始化struct spi_master,主要包含設(shè)備樹節(jié)點、支持的模式、支持的最大頻率和最小頻率、片選引腳是GPIO編號模式還是描述符模式、setup函數(shù)(用于設(shè)置SPI總線)、transfer_one函數(shù)(用于傳輸spi_transfer,若提供了transfer_one,且未提供transfer函數(shù)則是隊列模式的SPI控制器驅(qū)動)
- 若片選采用GPIO編號模式還需要對片選引腳進(jìn)行request操作,若片選采用GPIO描述符模式則無該步驟
- 通過spi_register_master注冊SPI控制器驅(qū)動
- 設(shè)備或驅(qū)動卸載時spi_unregister_master注銷SPI控制器
編寫驅(qū)動程
這里編寫一個虛擬的SPI控制器驅(qū)動,通過printk來輸出SPI控制器的工作狀態(tài),同時它提供軟件進(jìn)行回環(huán)(將tx_buf拷貝到rx_buf中)。
設(shè)備樹編寫
在頂層設(shè)備樹根節(jié)點中加入如下節(jié)點:
virtual_spi_master {
compatible = "atk,virtual_spi_master";
status = "okay";
//片選列表,一個spi_master至少有一個片選
cs-gpios = <&gpioh 6 GPIO_ACTIVE_LOW>;
//片選數(shù)量
num-chipselects = <1>;
//reg中地址字段的字?jǐn)?shù),必須為1
#address-cells = <1>;
//reg中地址空間大小的字?jǐn)?shù),必須為0
#size-cells = <0>;
//一個spidev的設(shè)備節(jié)點,以便在應(yīng)用層通過spidev來測試SPI控制器驅(qū)動
virtual_spi_dev: virtual_spi_dev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <100000>;
};
};
用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8編譯設(shè)備樹,用新的.dtb文件啟動系統(tǒng)
驅(qū)動代碼編寫
完整的驅(qū)動代碼如下所示:文章來源:http://www.zghlxwxcb.cn/news/detail-809003.html
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>
#include <linux/of_gpio.h>
static int spi_virtual_setup(struct spi_device *spi_dev)
{
if(!gpio_is_valid(spi_dev->cs_gpio))
{
printk("%d is not a valid gpio\n", spi_dev->cs_gpio);
return -EINVAL;
}
//若采用GPIO編號模式還需要在驅(qū)動中將gpio設(shè)置為輸出
return gpio_direction_output(spi_dev->cs_gpio, !(spi_dev->mode & SPI_CS_HIGH));
}
static int spi_virtual_transfer_one(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer)
{
int status;
//模擬硬件傳輸數(shù)據(jù)
if((transfer->tx_buf || transfer->rx_buf) && (transfer->len > 0))
{
if(transfer->rx_buf && transfer->tx_buf)
memcpy(transfer->rx_buf, transfer->tx_buf, transfer->len);
else if(transfer->rx_buf)
memset(transfer->rx_buf, 0xAA, transfer->len);
printk("transfer one\n");
status = 0;
}
else if((transfer->tx_buf || transfer->rx_buf) && (transfer->len <= 0))
{
printk("transfer invalid\n");
status = -EREMOTEIO;
}
else
status = 0;
//傳輸完成需要調(diào)用此函數(shù)(如果使用中斷應(yīng)該在傳輸完成中斷中調(diào)用)
spi_finalize_current_transfer(master);
return status;
}
static int spi_virtual_probe(struct platform_device *pdev)
{
int result;
int i, num_cs, cs_gpio;
struct spi_master *virtual_master;
printk("%s\r\n", __FUNCTION__);
//分配spi_master
virtual_master = spi_alloc_master(&pdev->dev, 0);
if(!virtual_master)
{
printk("alloc spi_master fail\n");
return -ENOMEM;
}
//設(shè)置平臺設(shè)備的驅(qū)動私有數(shù)據(jù)
pdev->dev.driver_data = (void*)virtual_master;
//初始化spi_master
virtual_master->use_gpio_descriptors = 0;
virtual_master->setup = spi_virtual_setup;
virtual_master->transfer_one = spi_virtual_transfer_one;
virtual_master->dev.of_node = pdev->dev.of_node;
virtual_master->bus_num = pdev->id;
virtual_master->max_speed_hz = 1000000000;
virtual_master->min_speed_hz = 1000;
virtual_master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | SPI_LSB_FIRST | SPI_3WIRE;
//片選引腳采用GPIO編號模式時,SPI驅(qū)動框架僅僅是從設(shè)備樹中獲取片選引腳編號并記錄,未進(jìn)行request引腳
num_cs = of_gpio_named_count(pdev->dev.of_node, "cs-gpios");
for (i = 0; i < num_cs; i++)
{
cs_gpio = of_get_named_gpio(pdev->dev.of_node, "cs-gpios", i);
if (cs_gpio == -EPROBE_DEFER)
{
/* 釋放前面分配的 spi_master,它通過于 spi_master 綁定的 dev 來實現(xiàn)
* 其調(diào)用流程如下:
* spi_master_put
* spi_controller_put
* put_device
* kobject_put
* kref_put
* kobject_release
* kobject_cleanup
* t->release,這里應(yīng)該是device_initialize為其注冊的device_ktype中的device_release
* dev->class->dev_release,這里應(yīng)該是分配spi_master時為dev.class綁定的spi_master_class中的spi_controller_release
*/
spi_master_put(virtual_master);
return -EPROBE_DEFER;
}
if(gpio_is_valid(cs_gpio))
{
result = devm_gpio_request(&pdev->dev, cs_gpio, "virtual_spi_cs");
if(result < 0)
{
spi_master_put(virtual_master);
printk("can't get CS gpio %i\n", cs_gpio);
return result;
}
}
}
//注冊 spi_master
result = spi_register_master(virtual_master);
if (result < 0)
{
printk("register spi_master fail\n");
spi_master_put(virtual_master);
return result;
}
return 0;
}
static int spi_virtual_remove(struct platform_device *pdev)
{
struct spi_master *virtual_master;
printk("%s\r\n", __FUNCTION__);
//提取平臺設(shè)備的驅(qū)動私有數(shù)據(jù)
virtual_master = (struct spi_master*)pdev->dev.driver_data;
//注銷spi_master,在注銷過程中會執(zhí)行put_device操作,所以無需再次執(zhí)行spi_master_put
spi_unregister_master(virtual_master);
return 0;
}
static const struct of_device_id spi_virtual_of_match[] = {
{.compatible = "atk,virtual_spi_master"},
{ /* Sentinel */ }
};
static struct platform_driver spi_virtual_driver = {
.probe = spi_virtual_probe,
.remove = spi_virtual_remove,
.driver = {
.name = "virtual_spi",
.of_match_table = spi_virtual_of_match,
},
};
static int virtual_master_init(void)
{
printk("%s\r\n", __FUNCTION__);
return platform_driver_register(&spi_virtual_driver);
}
static void virtual_master_exit(void)
{
printk("%s\r\n", __FUNCTION__);
platform_driver_unregister(&spi_virtual_driver);
}
module_init(virtual_master_init);
module_exit(virtual_master_exit);
MODULE_DESCRIPTION("virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
驅(qū)動測試程序編寫
驅(qū)動測試程序基于spidev進(jìn)行編寫,它通過ioctl控制SPI總線進(jìn)行數(shù)據(jù)收發(fā),完整的代碼如下所示:文章來源地址http://www.zghlxwxcb.cn/news/detail-809003.html
/* 參考: tools\spi\spidev_fdx.c */
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <errno.h>
/* dac_test /dev/spidevB.D <val> */
int main(int argc, char **argv)
{
int fd;
int status;
struct spi_ioc_transfer xfer[1];
unsigned char tx_buf[1];
unsigned char rx_buf[1];
if(argc != 3)
{
printf("Usage: %s /dev/spidevB.D <val>\n", argv[0]);
return 0;
}
//打開spidev設(shè)備
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
//通過ioctl控制SPI總線發(fā)送并接收一個字節(jié)的數(shù)據(jù)
tx_buf[0] = (unsigned char)strtoul(argv[2], NULL, 0);
rx_buf[0] = 0;
memset(xfer, 0, sizeof xfer);
xfer[0].tx_buf = (unsigned long)tx_buf;
xfer[0].rx_buf = (unsigned long)rx_buf;
xfer[0].len = 2;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
if(status < 0)
{
printf("SPI_IOC_MESSAGE %d\n", errno);
return -1;
}
//打印接收到的數(shù)據(jù)
printf("Pre val = %d\n", rx_buf[0]);
return 0;
}
上機測試
- 修改設(shè)備樹,增加虛擬SPI控制器的設(shè)備樹節(jié)點,并在此節(jié)點中添加一個spidev的子節(jié)點,然后編譯設(shè)備樹,用新的設(shè)備樹啟動設(shè)備
- 從這里下載代碼,使用make進(jìn)行編譯,然后使用make copy拷貝到目標(biāo)板NFS跟文件系統(tǒng)的root目錄中(執(zhí)行make copy時需要確保akefile中NFS根文件系統(tǒng)的路徑正確)
- 在目標(biāo)板中執(zhí)行insmod spi_master.ko加載虛擬SPI控制器驅(qū)動
- 執(zhí)行命令./spi_test.out /dev/spidev1.0 12通過SPI總線發(fā)送1byte數(shù)據(jù),同時將收到的數(shù)據(jù)打印出來(驅(qū)動中默認(rèn)將發(fā)送的數(shù)據(jù)賦給接收
到了這里,關(guān)于12.5在Linux中編寫隊列模式的SPI控制器驅(qū)動的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!