简介
模拟SPI驱动是一种软件实现的SPI总线驱动。在没有硬件SPI控制器的系统中,通过软件模拟实现SPI总线的功能。它允许在不修改硬件的情况下,通过GPIO(通用输入/输出)引脚模拟SPI总线的通信,从而与SPI设备进行数据交换。
模拟SPI驱动相对于硬件SPI来说,可能会有一定的性能损失,因为软件模拟不如硬件实现的SPI控制器快速和高效。
模拟SPI驱动相比硬件SPI控制器存在一些缺点,包括:
- 性能较低:软件模拟SPI需要通过GPIO引脚进行数据的输入和输出,并进行相应的时序控制。相比硬件SPI控制器,软件模拟SPI的速度较慢,通信效率较低,特别是在高速数据传输和频繁通信的场景下。
- 占用CPU资源:模拟SPI驱动在内核空间运行,需要通过CPU执行软件代码来模拟SPI总线的功能。这会占用一定的CPU资源,可能导致系统性能下降,并且可能影响其他任务的响应时间。
- 时序控制的挑战:软件模拟SPI需要准确控制数据的时序,包括数据的传输速率、时钟边沿和信号延迟等。需要仔细处理时序相关的问题,确保正确的数据传输和可靠性。
- 受限于GPIO资源:模拟SPI驱动需要使用系统中的GPIO引脚来模拟SPI总线的通信,因此受限于可用的GPIO资源数量。如果系统中可用的GPIO引脚有限,可能会限制同时连接的SPI设备数量或引起硬件扩展的困难。
内核中模拟SPI驱动的实现
在Linux内核中,SPI子系统提供了用于管理SPI总线和设备的功能和接口。虽然SPI子系统本身不直接提供模拟SPI驱动的功能,但它提供了一些接口和框架,可以用于实现模拟SPI驱动。
- SPI GPIO框架:SPI子系统提供了一个名为
spi-gpio
的框架,可使用GPIO引脚模拟SPI总线,gpio模拟spi代码在drivers/spi/spi-gpio.c
中。这个框架允许将GPIO引脚配置为SPI总线的时钟、片选、输入和输出信号,并提供了对应的接口函数供驱动程序使用。 - spi-bitbang:spi-bitbang是Linux内核中提供的一个通用框架,用于在没有硬件SPI控制器或需要灵活控制SPI时序和配置的系统中模拟SPI总线的通信。代码在
spi-bitbang.c
中
下面我们分别分析下这drivers/spi/spi-gpio.c
和 spi-bitbang.c
两个文件。
spi-gpio
platform_driver
spi_gpio_driver
属于总线设备驱动模型中的一种。当设备树中的compatible
字段与spi_gpio_dt_ids
的compatible
匹配时,spi_gpio_probe
将被调用,在probe函数中初始化并注册设备。
static struct platform_driver spi_gpio_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = of_match_ptr(spi_gpio_dt_ids),
},
.probe = spi_gpio_probe,
.remove = spi_gpio_remove,
};
module_platform_driver(spi_gpio_driver);
spi_gpio_dt_ids
设备树需要添加 spi-gpio
节点,这样才能probe成功。
static const struct of_device_id spi_gpio_dt_ids[] = {
{ .compatible = "spi-gpio" },
{}
};
spi_gpio_probe_dt
spi_gpio_probe_dt
主要作用是解析设备树中的SPI GPIO设备信息,并将其存储在platform_data
结构体中。这样,在SPI GPIO驱动的探测函数中,可以通过pdev设备的platform_data
字段获取这些信息,并根据需要进行相应的配置和操作。
static int spi_gpio_probe_dt(struct platform_device *pdev)
{
int ret;
u32 tmp;
struct spi_gpio_platform_data *pdata;
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *of_id =
of_match_device(spi_gpio_dt_ids, &pdev->dev);
if (!of_id)
return 0;
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
ret = of_get_named_gpio(np, "gpio-sck", 0);
if (ret < 0) {
dev_err(&pdev->dev, "gpio-sck property not found\n");
goto error_free;
}
pdata->sck = ret;
ret = of_get_named_gpio(np, "gpio-miso", 0);
if (ret < 0) {
dev_info(&pdev->dev, "gpio-miso property not found, switching to no-rx mode\n");
pdata->miso = SPI_GPIO_NO_MISO;
} else
pdata->miso = ret;
ret = of_get_named_gpio(np, "gpio-mosi", 0);
if (ret < 0) {
dev_info(&pdev->dev, "gpio-mosi property not found, switching to no-tx mode\n");
pdata->mosi = SPI_GPIO_NO_MOSI;
} else
pdata->mosi = ret;
ret = of_property_read_u32(np, "num-chipselects", &tmp);
if (ret < 0) {
dev_err(&pdev->dev, "num-chipselects property not found\n");
goto error_free;
}
pdata->num_chipselect = tmp;
pdev->dev.platform_data = pdata;
return 1;
error_free:
devm_kfree(&pdev->dev, pdata);
return ret;
}
spi_gpio_probe
spi_gpio_probe
使用了bitbang模式实现SPI协议的位操作传输,在Linux内核的SPI子系统中注册并初始化一个SPI GPIO设备。
static int spi_gpio_probe(struct platform_device *pdev)
{
int status;
struct spi_master *master;
struct spi_gpio *spi_gpio;
struct spi_gpio_platform_data *pdata;
u16 master_flags = 0;
bool use_of = 0;
int num_devices;
// 解析设备树中的SPI GPIO设备信息并初始化platform_data结构体
status = spi_gpio_probe_dt(pdev);
if (status < 0)
return status;
if (status > 0)
use_of = 1;
// 获取设备的platform_data结构体
pdata = dev_get_platdata(&pdev->dev);
#ifdef GENERIC_BITBANG
// 如果没有platform_data或者设备树中没有定义num_chipselect属性,返回错误码
if (!pdata || (!use_of && !pdata->num_chipselect))
return -ENODEV;
#endif
if (use_of && !SPI_N_CHIPSEL)
num_devices = 1;
else
num_devices = SPI_N_CHIPSEL;
// 请求和配置SPI GPIO相关的GPIO资源
status = spi_gpio_request(pdata, dev_name(&pdev->dev), &master_flags);
if (status < 0)
return status;
// 分配spi_master结构体,并保存spi_gpio结构体指针
master = spi_alloc_master(&pdev->dev, sizeof(*spi_gpio) +
(sizeof(unsigned long) * num_devices));
if (!master) {
status = -ENOMEM;
goto gpio_free;
}
spi_gpio = spi_master_get_devdata(master);
platform_set_drvdata(pdev, spi_gpio);
spi_gpio->pdev = pdev;
if (pdata)
spi_gpio->pdata = *pdata;
// 设置spi_master结构体的一些字段
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
master->flags = master_flags;
master->bus_num = pdev->id;
master->num_chipselect = num_devices;
master->setup = spi_gpio_setup;
master->cleanup = spi_gpio_cleanup;
#ifdef CONFIG_OF
master->dev.of_node = pdev->dev.of_node;
if (use_of) {
int i;
struct device_node *np = pdev->dev.of_node;
/*
* In DT environments, take the CS GPIO from the "cs-gpios"
* property of the node.
*/
if (!SPI_N_CHIPSEL)
spi_gpio->cs_gpios[0] = SPI_GPIO_NO_CHIPSELECT;
else
for (i = 0; i < SPI_N_CHIPSEL; i++) {
status = of_get_named_gpio(np, "cs-gpios", i);
if (status < 0) {
dev_err(&pdev->dev,
"invalid cs-gpios property\n");
goto gpio_free;
}
spi_gpio->cs_gpios[i] = status;
}
}
#endif
spi_gpio->bitbang.master = master;
spi_gpio->bitbang.chipselect = spi_gpio_chipselect;
// 设置SPI传输相关的回调函数
if ((master_flags & (SPI_MASTER_NO_TX | SPI_MASTER_NO_RX)) == 0) {
spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_txrx_word_mode0;
spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_txrx_word_mode1;
spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_txrx_word_mode2;
spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_txrx_word_mode3;
} else {
spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_spec_txrx_word_mode0;
spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_spec_txrx_word_mode1;
spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_spec_txrx_word_mode2;
spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_spec_txrx_word_mode3;
}
spi_gpio->bitbang.setup_transfer = spi_bitbang_setup_transfer;
spi_gpio->bitbang.flags = SPI_CS_HIGH;
// 启动SPI GPIO位操作传输
status = spi_bitbang_start(&spi_gpio->bitbang);
if (status < 0) {
gpio_free:
if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)
gpio_free(SPI_MISO_GPIO);
if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)
gpio_free(SPI_MOSI_GPIO);
gpio_free(SPI_SCK_GPIO);
spi_master_put(master);
}
return status;
}
函数所做工作如下:
- 首先,函数调用
spi_gpio_probe_dt()
函数来解析设备树(Device Tree)中的SPI GPIO设备信息,并初始化platform_data
结构体。设备树中包含了SPI GPIO设备的属性,如时钟引脚、数据输入引脚、数据输出引脚等。 - 接下来,函数获取设备的
platform_data
结构体,并进行一些合法性检查。 - 函数通过调用
spi_gpio_request()
函数请求和配置SPI GPIO相关的GPIO资源。该函数会申请所需的GPIO引脚,并设置引脚的方向和电平。 - 然后,函数使用
spi_alloc_master()
函数分配一个spi_master
结构体,并保存了指向spi_gpio
结构体的指针。同时,通过调用platform_set_drvdata()
函数将spi_gpio
结构体指针保存在platform_device
结构体的driver_data
字段中。 - 接着,函数对
spi_master
结构体的各个字段进行设置。这些字段包括SPI传输的参数,如数据位宽、传输模式、片选信号数量等。此外,还会设置回调函数,用于数据传输的配置和清理。 - 如果设备树支持(即配置了
CONFIG_OF
),函数会获取设备树中的片选信号的GPIO引脚信息。通过遍历设备树中的cs-gpios
属性,获取每个片选信号的GPIO引脚。 - 接下来,函数设置
spi_gpio
结构体中的bitbang
字段,将之前设置的spi_master
结构体以及自定义的片选信号处理函数指定给它。 - 根据SPI的传输模式,函数选择不同的回调函数进行数据的传输。如果SPI设备同时支持数据发送和接收,则使用通用的传输函数;否则,使用特定的传输函数。
- 最后,函数调用
spi_bitbang_start()
函数启动SPI GPIO的位操作传输。该函数会根据之前设置的参数,开始进行SPI数据的传输。 - 如果启动失败,函数会释放之前请求的GPIO资源,并释放分配的
spi_master
结构体的内存空间。
spi_gpio_remove
spi_gpio_remove
函数,它的目的是从SPI GPIO平台设备中移除驱动程序并释放相关的资源,如GPIO引脚和SPI主设备。
static int spi_gpio_remove(struct platform_device *pdev)
{
struct spi_gpio *spi_gpio;
struct spi_gpio_platform_data *pdata;
spi_gpio = platform_get_drvdata(pdev);
pdata = dev_get_platdata(&pdev->dev);
/* stop() unregisters child devices too */
spi_bitbang_stop(&spi_gpio->bitbang);
if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)
gpio_free(SPI_MISO_GPIO);
if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)
gpio_free(SPI_MOSI_GPIO);
gpio_free(SPI_SCK_GPIO);
spi_master_put(spi_gpio->bitbang.master);
return 0;
}
-
首先,函数接受一个指向
struct platform_device
类型的指针pdev作为参数。 -
接下来定义了两个指针变量
spi_gpio
和pdata
,分别指向struct spi_gpio
和struct spi_gpio_platform_data
类型的数据结构。 -
platform_get_drvdata(pdev)
用于获取存储在平台设备中的私有数据指针,将其赋值给spi_gpio
指针。这个私有数据指针通常在设备的probe函数中设置。 -
dev_get_platdata(&pdev->dev)
用于获取与设备相关的平台数据,将其赋值给pdata指针。平台数据是在设备的设备树绑定或者通过platform_set_drvdata()
函数设置的。 -
spi_bitbang_stop(&spi_gpio->bitbang)
调用函数spi_bitbang_stop()
,停止SPI位操作的传输。这个函数将注销子设备。 -
接下来的一系列if语句用于检查是否为每个GPIO引脚分配了一个有效的引脚号,并释放这些引脚。
-
if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)
检查是否为MISO引脚分配了一个非零的引脚号,如果是,则调用gpio_free(SPI_MISO_GPIO)
释放该引脚。 -
if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)
检查是否为MOSI引脚分配了一个非零的引脚号,如果是,则调用gpio_free(SPI_MOSI_GPIO)
释放该引脚。 -
最后,调用gpio_free(SPI_SCK_GPIO)释放SCK引脚。
-
spi_master_put(spi_gpio->bitbang.master)
调用函数spi_master_put(),释放对SPI主设备的引用。 -
最后,函数返回0,表示成功执行函数。
spi_gpio_request
spi_gpio_request
的函数,它用于请求并分配SPI GPIO引脚的资源,并根据硬件配置设置SPI主设备的传输和接收功能标志。如果引脚分配成功,函数返回0,否则返回一个非零值表示分配失败。
static int spi_gpio_request(struct spi_gpio_platform_data *pdata,
const char *label, u16 *res_flags)
{
int value;
/* NOTE: SPI_*_GPIO symbols may reference "pdata" */
if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI) {
value = spi_gpio_alloc(SPI_MOSI_GPIO, label, false);
if (value)
goto done;
} else {
/* HW configuration without MOSI pin */
*res_flags |= SPI_MASTER_NO_TX;
}
if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO) {
value = spi_gpio_alloc(SPI_MISO_GPIO, label, true);
if (value)
goto free_mosi;
} else {
/* HW configuration without MISO pin */
*res_flags |= SPI_MASTER_NO_RX;
}
value = spi_gpio_alloc(SPI_SCK_GPIO, label, false);
if (value)
goto free_miso;
goto done;
free_miso:
if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)
gpio_free(SPI_MISO_GPIO);
free_mosi:
if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)
gpio_free(SPI_MOSI_GPIO);
done:
return value;
}
- 首先,函数接受一个指向
struct spi_gpio_platform_data
类型的指针pdata
、一个指向字符常量的指针label
和一个指向u16
类型的指针res_flags
作为参数。 - 首先,通过比较
SPI_MOSI_GPIO
和SPI_GPIO_NO_MOSI
的值来判断是否为MOSI引脚分配了一个有效的引脚号。- 如果
SPI_MOSI_GPIO
不等于SPI_GPIO_NO_MOSI
,表示为MOSI引脚分配了一个有效的引脚号,接下来调用spi_gpio_alloc(SPI_MOSI_GPIO, label, false)
函数分配该引脚,并将返回值赋给value
变量。如果返回值不为0,则表示分配失败,直接跳转到done
标签处。 - 如果
SPI_MOSI_GPIO
等于SPI_GPIO_NO_MOSI
,表示硬件配置中没有使用MOSI引脚,这时将设置*res_flags
中的SPI_MASTER_NO_TX
标志,表示SPI主设备没有传输功能。
- 如果
- 接下来,通过比较
SPI_MISO_GPIO
和SPI_GPIO_NO_MISO
的值来判断是否为MISO引脚分配了一个有效的引脚号。- 如果
SPI_MISO_GPIO
不等于SPI_GPIO_NO_MISO
,表示为MISO引脚分配了一个有效的引脚号,接下来调用spi_gpio_alloc(SPI_MISO_GPIO, label, true)
函数分配该引脚,并将返回值赋给value
变量。如果返回值不为0,则表示分配失败,直接跳转到free_mosi
标签处。 - 如果
SPI_MISO_GPIO
等于SPI_GPIO_NO_MISO
,表示硬件配置中没有使用MISO引脚,这时将设置*res_flags
中的SPI_MASTER_NO_RX
标志,表示SPI主设备没有接收功能。
- 如果
- 最后,调用
spi_gpio_alloc(SPI_SCK_GPIO, label, false)
函数分配SCK引脚,并将返回值赋给value
变量。如果返回值不为0,则表示分配失败,直接跳转到free_miso
标签处。 - 如果程序成功执行到这里,表示所有引脚分配都成功,直接跳转到
done
标签处。 free_miso
标签处,如果之前为MISO引脚分配了一个有效的引脚号,调用gpio_free(SPI_MISO_GPIO)
函数释放该引脚。free_mosi
标签处,如果之前为MOSI引脚分配了一个有效的引脚号,调用gpio_free(SPI_MOSI_GPIO)
函数释放该引脚。done
标签处,函数返回value
变量的值,表示引脚分配的结果。如果返回值为0,表示成功执行函数,否则表示分配引脚失败。
spi_gpio_alloc
spi_gpio_alloc
函数用于分配和配置一个GPIO引脚的资源。它首先通过gpio_request()
函数请求分配GPIO资源,并根据is_in
参数来配置引脚的输入或输出模式。如果分配和配置成功,函数返回0,否则返回一个非零值表示分配和配置失败。
static int spi_gpio_alloc(unsigned pin, const char *label, bool is_in)
{
int value;
value = gpio_request(pin, label);
if (value == 0) {
if (is_in)
value = gpio_direction_input(pin);
else
value = gpio_direction_output(pin, 0);
}
return value;
}
- 首先,函数接受一个无符号整数
pin
作为GPIO引脚号,一个指向字符常量的指针label
作为引脚的标签,以及一个布尔值is_in
来指示引脚是否用于输入。 gpio_request(pin, label)
调用函数gpio_request()
来请求分配指定引脚号的GPIO资源,并将返回值赋给value
变量。如果返回值为0,表示成功分配GPIO资源;如果返回值不为0,表示分配失败。- 如果
gpio_request()
成功执行,进入条件判断语句块:- 如果
is_in
为真,表示该引脚是一个输入引脚,调用gpio_direction_input(pin)
函数将该引脚配置为输入模式,并将返回值赋给value
变量。如果返回值为0,表示成功配置引脚为输入模式;如果返回值不为0,表示配置失败。 - 如果
is_in
为假,表示该引脚是一个输出引脚,调用gpio_direction_output(pin, 0)
函数将该引脚配置为输出模式,并将输出电平设置为低电平(0),将返回值赋给value
变量。如果返回值为0,表示成功配置引脚为输出模式并设置输出电平;如果返回值不为0,表示配置失败。
- 如果
- 最后,函数返回
value
变量的值,表示引脚分配和配置的结果。如果返回值为0,表示成功执行函数;如果返回值不为0,表示分配和配置引脚失败。
spi_gpio_cleanup
spi_gpio_cleanup
的函数,用于清理和释放SPI GPIO相关资源。它首先检查特定的SPI片选引脚是否分配了有效的GPIO资源,如果是,则释放该GPIO资源。然后,调用spi_bitbang_cleanup(spi)
函数清理和释放与SPI位操作相关的资源。
static void spi_gpio_cleanup(struct spi_device *spi)
{
struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi);
unsigned long cs = spi_gpio->cs_gpios[spi->chip_select];
if (cs != SPI_GPIO_NO_CHIPSELECT)
gpio_free(cs);
spi_bitbang_cleanup(spi);
}
spi_gpio_setup
pi_gpio_setup
的函数,用于设置SPI GPIO相关的配置。它首先根据设备树环境或SPI控制器数据获取片选引脚的值,然后根据情况请求分配并配置片选引脚的GPIO资源。如果片选引脚成功分配和配置,将进行SPI位操作的设置。如果设置失败,将释放之前分配的GPIO资源。函数最终返回设置的状态,0表示成功,非零表示失败。
static int spi_gpio_setup(struct spi_device *spi)
{
unsigned long cs;
int status = 0;
struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi);
struct device_node *np = spi->master->dev.of_node;
if (np) {
/*
* In DT environments, the CS GPIOs have already been
* initialized from the "cs-gpios" property of the node.
*/
cs = spi_gpio->cs_gpios[spi->chip_select];
} else {
/*
* ... otherwise, take it from spi->controller_data
*/
cs = (uintptr_t) spi->controller_data;
}
if (!spi->controller_state) {
if (cs != SPI_GPIO_NO_CHIPSELECT) {
status = gpio_request(cs, dev_name(&spi->dev));
if (status)
return status;
status = gpio_direction_output(cs,
!(spi->mode & SPI_CS_HIGH));
}
}
if (!status) {
/* in case it was initialized from static board data */
spi_gpio->cs_gpios[spi->chip_select] = cs;
status = spi_bitbang_setup(spi);
}
if (status) {
if (!spi->controller_state && cs != SPI_GPIO_NO_CHIPSELECT)
gpio_free(cs);
}
return status;
}
- 首先,函数接受一个指向
struct spi_device
类型的指针spi
作为参数。 - 代码中定义了一个无符号长整型变量
cs
,用于存储片选引脚(Chip Select,CS)的值。 - 定义了一个整型变量
status
,用于存储函数执行的状态,默认为0。 - 定义了一个指向
struct spi_gpio
类型的指针spi_gpio
,通过spi_to_spi_gpio(spi)
宏将spi
转换为spi_gpio
结构体。 - 定义了一个指向
struct device_node
类型的指针np
,用于存储SPI主设备的设备树节点。 - 如果
np
非空(非NULL),表示在设备树(Device Tree)环境中,片选引脚已经从节点的"cs-gpios"属性中进行了初始化。- 将
spi_gpio->cs_gpios[spi->chip_select]
的值赋给cs
,表示获取对应片选引脚的GPIO资源。
- 将
- 如果
np
为空,表示不在设备树环境中,从spi->controller_data
中获取片选引脚的值。- 将
spi->controller_data
的值转换为无符号整数并赋给cs
,表示获取对应片选引脚的GPIO资源。
- 将
- 如果
spi->controller_state
为空,表示SPI控制器的状态未初始化。- 如果
cs
不等于SPI_GPIO_NO_CHIPSELECT
,表示为该片选引脚分配了有效的GPIO资源。- 调用
gpio_request(cs, dev_name(&spi->dev))
函数请求分配片选引脚的GPIO资源,并将返回值赋给status
。 - 如果
gpio_request()
执行失败(返回值非零),直接返回status
表示设置失败。 - 调用
gpio_direction_output(cs, !(spi->mode & SPI_CS_HIGH))
函数将片选引脚配置为输出模式,并根据SPI模式中的SPI_CS_HIGH
标志设置输出电平。
- 调用
- 如果
spi->controller_state
为空且status
仍然为0,表示片选引脚成功分配和配置。
- 如果
- 如果
status
为0,表示片选引脚成功分配和配置。- 将
cs
的值存储到spi_gpio->cs_gpios[spi->chip_select]
中,以便后续使用。 - 调用
spi_bitbang_setup(spi)
函数进行SPI位操作相关的设置,将返回值赋给status
。
- 将
- 如果
status
非零,表示片选引脚或SPI位操作设置发生错误。
- 如果
spi->controller_state
为空且cs
不等于SPI_GPIO_NO_CHIPSELECT
,表示之前为片选引脚分配了GPIO资源,现在需要释放它。- 调用
gpio_free(cs)
函数释放片选引脚的GPIO资源。
- 调用
- 最后,函数返回
status
,表示设置的结果。如果返回值为0,表示成功执行函数;如果返回值非零,表示设置失败。
spi_gpio_chipselect
spi_gpio_chipselect
作用是根据传入的参数控制 SPI 设备的片选信号的 GPIO 引脚状态,以实现 SPI 通信中的片选功能。同时,根据 SPI 协议的要求,可以设置初始时钟极性。
static void spi_gpio_chipselect(struct spi_device *spi, int is_active)
{
struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi);
unsigned long cs = spi_gpio->cs_gpios[spi->chip_select];
/* set initial clock polarity */
if (is_active)
setsck(spi, spi->mode & SPI_CPOL);
if (cs != SPI_GPIO_NO_CHIPSELECT) {
/* SPI is normally active-low */
gpio_set_value_cansleep(cs, (spi->mode & SPI_CS_HIGH) ? is_active : !is_active);
}
}
- 首先,代码定义了一个静态函数
spi_gpio_chipselect
,该函数接受两个参数:spi
是指向spi_device
结构体的指针,表示要控制的 SPI 设备;is_active
是一个整数,表示片选信号的状态(激活或非激活)。 - 接下来,代码通过使用
spi_to_spi_gpio
函数将spi_device
结构体转换为spi_gpio
结构体,并将结果保存在spi_gpio
变量中。这个转换是为了获取与 GPIO 控制相关的信息。 - 然后,代码使用
spi_device
结构体中的chip_select
字段来确定当前片选信号对应的 GPIO 引脚编号,并将其保存在cs
变量中。 - 代码接着根据
is_active
参数来设置初始时钟极性。如果is_active
为非零值(表示片选信号激活),则通过spi_device
结构体中的mode
字段的SPI_CPOL
位来确定初始时钟极性,然后调用setsck
函数进行设置。 - 接下来,代码检查
cs
变量是否等于SPI_GPIO_NO_CHIPSELECT
。如果cs
不等于该值,说明有有效的片选信号 GPIO 引脚配置。 - 在 SPI 协议中,通常片选信号是低电平有效的。因此,代码使用
gpio_set_value_cansleep
函数来设置片选信号 GPIO 引脚的电平。根据spi_device
结构体中的mode
字段的SPI_CS_HIGH
位,如果该位为真,则表示片选信号是高电平有效,那么根据is_active
参数的值直接传递给gpio_set_value_cansleep
函数;如果该位为假,则表示片选信号是低电平有效,那么取is_active
参数的反值作为电平状态传递给gpio_set_value_cansleep
函数。
spi-bitbang
bitbang_txrx_32
bitbang_txrx_32
用于进行 SPI 位移传输。通过调用传入的函数指针 txrx_word
实现实际的发送和接收操作。
static unsigned bitbang_txrx_32(
struct spi_device *spi,
u32 (*txrx_word)(struct spi_device *spi,
unsigned nsecs,
u32 word, u8 bits),
unsigned ns,
struct spi_transfer *t
) {
unsigned bits = t->bits_per_word;
unsigned count = t->len;
const u32 *tx = t->tx_buf;
u32 *rx = t->rx_buf;
while (likely(count > 3)) {
u32 word = 0;
if (tx)
word = *tx++;
word = txrx_word(spi, ns, word, bits);
if (rx)
*rx++ = word;
count -= 4;
}
return t->len - count;
}
- 函数开始时,从传输结构体
t
中获取位数bits
,数据长度count
,发送缓冲区指针tx
和接收缓冲区指针rx
。 - 进入
while
循环,当数据长度count
大于 3(32 位数据的字长)时,循环继续。 - 在每次循环中,首先定义一个变量
word
,用于保存当前要发送或接收的数据字。 - 如果发送缓冲区存在 (
tx
不为空),则将发送缓冲区中的数据字赋值给word
,并将指针tx
向后移动。 - 调用函数指针
txrx_word
,将 SPI 设备指针spi
、传输时间ns
、数据字word
和位数bits
作为参数传递给该函数。函数txrx_word
将执行实际的发送和接收操作,并返回接收到的数据字,该数据字将被赋值给word
。 - 如果接收缓冲区存在 (
rx
不为空),则将word
的值存储到接收缓冲区中,并将指针rx
向后移动。 - 减少数据长度
count
的值,表示已经处理了一个数据字。 - 循环回到第 2 步,继续处理下一个数据字,直到剩余数据长度不足以组成完整的 32 位数据。
- 返回传输结构体的长度
t->len
减去剩余的未处理数据长度count
,表示成功发送和接收的数据长度。
spi_bitbang_setup_transfer
spi_bitbang_setup_transfer
用于设置 SPI 位移传输的参数的函数。根据传入的传输结构体 t
中的参数设置位移传输的位数和速率,并选择相应的位移传输函数进行数据的发送和接收。
int spi_bitbang_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
{
struct spi_bitbang_cs *cs = spi->controller_state;
u8 bits_per_word;
u32 hz;
if (t) {
bits_per_word = t->bits_per_word;
hz = t->speed_hz;
} else {
bits_per_word = 0;
hz = 0;
}
/* spi_transfer level calls that work per-word */
if (!bits_per_word)
bits_per_word = spi->bits_per_word;
if (bits_per_word <= 8)
cs->txrx_bufs = bitbang_txrx_8;
else if (bits_per_word <= 16)
cs->txrx_bufs = bitbang_txrx_16;
else if (bits_per_word <= 32)
cs->txrx_bufs = bitbang_txrx_32;
else
return -EINVAL;
/* nsecs = (clock period)/2 */
if (!hz)
hz = spi->max_speed_hz;
if (hz) {
cs->nsecs = (1000000000/2) / hz;
if (cs->nsecs > (MAX_UDELAY_MS * 1000 * 1000))
return -EINVAL;
}
return 0;
}
- 首先,从 SPI 设备结构体中获取位移传输相关的控制器状态结构体
cs
。 - 接着,定义变量
bits_per_word
和hz
,用于保存位数和传输速率。 - 如果传输结构体
t
不为空,将从传输结构体中获取位数和速率,并分别赋值给bits_per_word
和hz
。如果t
为空,则将bits_per_word
和hz
设置为 0。 - 如果位数
bits_per_word
为零,将从 SPI 设备结构体中获取默认的位数spi->bits_per_word
。 - 根据位数
bits_per_word
的大小,决定使用不同的位移传输函数。 - 如果传输速率
hz
为零,将从 SPI 设备结构体中获取最大速率spi->max_speed_hz
。 - 如果速率
hz
不为零,计算每个位移传输的时间间隔nsecs
。这里假设时钟周期为传输速率的倒数的一半(即半周期)。计算公式为nsecs = (clock period)/2 = (1000000000/2) / hz
。 - 如果计算得到的时间间隔
nsecs
超过最大延迟时间,则返回错误码-EINVAL
,表示时间间隔过大。 - 如果都设置成功,返回 0,表示设置传输参数的函数执行成功。
spi_bitbang_setup
spi_bitbang_setup
在进行实际的数据传输之前,设置 SPI 设备的位移传输相关参数。它通过获取位移传输函数、调用传输参数设置函数、设置片选信号状态等步骤来完成设置。
int spi_bitbang_setup(struct spi_device *spi)
{
struct spi_bitbang_cs *cs = spi->controller_state;
struct spi_bitbang *bitbang;
bitbang = spi_master_get_devdata(spi->master);
if (!cs) {
cs = kzalloc(sizeof(*cs), GFP_KERNEL);
if (!cs)
return -ENOMEM;
spi->controller_state = cs;
}
/* per-word shift register access, in hardware or bitbanging */
cs->txrx_word = bitbang->txrx_word[spi->mode & (SPI_CPOL|SPI_CPHA)];
if (!cs->txrx_word)
return -EINVAL;
if (bitbang->setup_transfer) {
int retval = bitbang->setup_transfer(spi, NULL);
if (retval < 0)
return retval;
}
dev_dbg(&spi->dev, "%s, %u nsec/bit\n", __func__, 2 * cs->nsecs);
/* NOTE we _need_ to call chipselect() early, ideally with adapter
* setup, unless the hardware defaults cooperate to avoid confusion
* between normal (active low) and inverted chipselects.
*/
/* deselect chip (low or high) */
mutex_lock(&bitbang->lock);
if (!bitbang->busy) {
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
ndelay(cs->nsecs);
}
mutex_unlock(&bitbang->lock);
return 0;
}
- 首先,从 SPI 设备结构体中获取控制器状态结构体
cs
和位移传输相关的控制结构体bitbang
。 - 使用函数
spi_master_get_devdata
从 SPI 主设备结构体中获取位移传输控制结构体bitbang
。 - 如果控制器状态结构体
cs
为空,说明还没有为该 SPI 设备分配控制器状态结构体,此时需要为其分配内存并初始化。使用函数kzalloc
分配内存,并将分配的内存赋值给cs
。如果内存分配失败,则返回错误码-ENOMEM
。 - 将控制器状态结构体
cs
赋值给 SPI 设备结构体中的controller_state
字段。 - 从位移传输控制结构体
bitbang
中根据 SPI 设备的模式(spi->mode
)获取相应的位移传输函数txrx_word
。根据 SPI 设备的模式中的SPI_CPOL
(时钟极性)和SPI_CPHA
(时钟相位)位进行位运算,获取相应的位移传输函数。如果获取的函数为空,则返回错误码-EINVAL
。 - 如果位移传输控制结构体
bitbang
中的setup_transfer
函数存在,则调用该函数设置传输参数。传输参数通过传递 SPI 设备指针spi
和空指针NULL
来实现。如果setup_transfer
函数返回的值小于 0,则表示设置传输参数失败,此时返回该错误码。 - 在进行实际的数据传输之前,需要先选择片选信号,并确保片选信号处于非活动状态。
- 通过互斥锁
bitbang->lock
来保护对位移传输控制结构体的访问。首先获取互斥锁。 - 检查位移传输控制结构体
bitbang
中的busy
标志。如果该标志为假,则表示当前没有其他数据传输操作正在进行,此时调用chipselect
函数将片选信号设置为非活动状态,并通过ndelay
函数延迟一段时间,以确保片选信号稳定。 - 最后,释放互斥锁
bitbang->lock
。
spi_bitbang_transfer_one
spi_bitbang_transfer_one
作用是执行单个传输操作,包括设置传输参数和进行数据的发送和接收。它通过调用位移传输控制结构体中的函数来完成传输操作,并根据传输的结果来设置传输操作的状态。最后,执行收尾工作并返回传输操作的状态。
static int spi_bitbang_transfer_one(struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *transfer)
{
struct spi_bitbang *bitbang = spi_master_get_devdata(master);
int status = 0;
if (bitbang->setup_transfer) {
status = bitbang->setup_transfer(spi, transfer);
if (status < 0)
goto out;
}
if (transfer->len)
status = bitbang->txrx_bufs(spi, transfer);
if (status == transfer->len)
status = 0;
else if (status >= 0)
status = -EREMOTEIO;
out:
spi_finalize_current_transfer(master);
return status;
}
- 首先,从 SPI 主设备结构体中获取位移传输相关的控制结构体
bitbang
。 - 定义变量
status
来保存传输操作的状态,默认为 0。 - 如果位移传输控制结构体
bitbang
中的setup_transfer
函数存在,则调用该函数设置传输参数。传输参数通过传递 SPI 设备指针spi
和传输结构体指针transfer
来实现。如果设置传输参数的函数返回的值小于 0,则表示设置传输参数失败,此时跳转到标签out
处进行处理。 - 检查传输结构体
transfer
的数据长度len
是否非零。如果非零,则调用位移传输控制结构体bitbang
中的txrx_bufs
函数进行数据的发送和接收。 - 判断传输操作的状态
status
是否等于传输结构体transfer
的长度len
。如果相等,则表示传输操作成功完成,将状态status
设置为 0。如果状态status
大于等于 0 但不等于传输结构体transfer
的长度len
,则表示传输操作未完成,将状态status
设置为-EREMOTEIO
。 - 跳转到标签
out
处,执行spi_finalize_current_transfer
函数,以完成当前传输的收尾工作。 - 返回传输操作的状态
status
。
spi_bitbang_bufs
spi_bitbang_bufs
封装了对控制器状态结构体中的位移传输函数的调用,以执行数据缓冲区的传输。它通过获取位移传输所需的时间间隔和调用位移传输函数来完成传输操作,并返回传输操作的状态。
static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t)
{
struct spi_bitbang_cs *cs = spi->controller_state;
unsigned nsecs = cs->nsecs;
return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t);
}
- 首先,从 SPI 设备结构体中获取控制器状态结构体
cs
。 - 从控制器状态结构体
cs
中获取位移传输所需的时间间隔nsecs
。 - 调用控制器状态结构体
cs
中的txrx_bufs
函数来执行数据缓冲区的传输。该函数接受 SPI 设备指针spi
、位移传输函数txrx_word
、时间间隔nsecs
和传输结构体指针t
作为参数,并返回传输操作的状态。 - 将传输操作的状态作为函数的返回值。
spi_bitbang_prepare_hardware
spi_bitbang_prepare_hardware
作用是在进行数据传输之前,准备 SPI 硬件。它通过设置位移传输控制结构体中的 busy
标志来指示硬件正在忙于数据传输操作。这样做可以确保在进行数据传输之前,其他线程不会干扰硬件的正常操作。
static int spi_bitbang_prepare_hardware(struct spi_master *spi)
{
struct spi_bitbang *bitbang;
bitbang = spi_master_get_devdata(spi);
mutex_lock(&bitbang->lock);
bitbang->busy = 1;
mutex_unlock(&bitbang->lock);
return 0;
}
- 首先,从 SPI 主设备结构体中获取位移传输相关的控制结构体
bitbang
。 - 使用互斥锁
bitbang->lock
来保护对位移传输控制结构体的访问。首先获取互斥锁。 - 将位移传输控制结构体中的
busy
标志设置为 1,表示硬件正在忙于数据传输操作。 - 释放互斥锁
bitbang->lock
,以允许其他线程访问位移传输控制结构体。 - 返回 0,表示硬件准备操作执行成功。
spi_bitbang_unprepare_hardware
spi_bitbang_unprepare_hardware
在完成数据传输后释放 SPI 硬件资源。通过将位移传输控制结构体中的 busy
标志设置为 0,表示硬件不再忙于数据传输操作。这样做可以确保在释放硬件资源之前,其他线程可以正常访问硬件。
static int spi_bitbang_unprepare_hardware(struct spi_master *spi)
{
struct spi_bitbang *bitbang;
bitbang = spi_master_get_devdata(spi);
mutex_lock(&bitbang->lock);
bitbang->busy = 0;
mutex_unlock(&bitbang->lock);
return 0;
}
- 首先,从 SPI 主设备结构体中获取位移传输相关的控制结构体
bitbang
。 - 使用互斥锁
bitbang->lock
来保护对位移传输控制结构体的访问。首先获取互斥锁。 - 将位移传输控制结构体中的
busy
标志设置为 0,表示硬件不再忙于数据传输操作。 - 释放互斥锁
bitbang->lock
,以允许其他线程访问位移传输控制结构体。 - 返回 0,表示硬件释放操作执行成功。
spi_bitbang_set_cs
spi_bitbang_set_cs
作用是根据输入参数的值来控制 SPI 设备的片选信号。它通过检查 SPI 设备的传输模式和输入参数的值,判断是否需要使能片选信号,并调用位移传输控制结构体中的函数来设置片选信号的状态。同时,使用延迟函数确保在改变片选信号状态前后有适当的延迟时间。
static void spi_bitbang_set_cs(struct spi_device *spi, bool enable)
{
struct spi_bitbang *bitbang = spi_master_get_devdata(spi->master);
/* SPI core provides CS high / low, but bitbang driver
* expects CS active
* spi device driver takes care of handling SPI_CS_HIGH
*/
enable = (!!(spi->mode & SPI_CS_HIGH) == enable);
ndelay(SPI_BITBANG_CS_DELAY);
bitbang->chipselect(spi, enable ? BITBANG_CS_ACTIVE :
BITBANG_CS_INACTIVE);
ndelay(SPI_BITBANG_CS_DELAY);
}
- 首先,从 SPI 设备结构体的主设备结构体中获取位移传输相关的控制结构体
bitbang
。 - 通过检查 SPI 设备的传输模式中的
SPI_CS_HIGH
标志位,以及函数输入参数enable
的值,来确定是否需要使能片选信号。这是为了确保兼容片选信号的极性。 - 使用
ndelay
函数引入延迟,以确保在改变片选信号状态之前有足够的时间。 - 调用位移传输控制结构体
bitbang
中的chipselect
函数,以控制片选信号的状态。根据enable
的值,将片选信号设置为活动状态或非活动状态。 - 再次使用
ndelay
函数引入延迟,以确保在改变片选信号状态后有足够的时间。
spi_bitbang_start
spi_bitbang_start
进行了一系列的设置和配置,包括初始化互斥锁、设置主设备结构体中的函数指针、注册 SPI 主设备等。通过这些操作,可以使得 SPI 位移传输准备就绪,并可以进行数据传输操作。
int spi_bitbang_start(struct spi_bitbang *bitbang)
{
struct spi_master *master = bitbang->master;
int ret;
if (!master || !bitbang->chipselect)
return -EINVAL;
mutex_init(&bitbang->lock);
if (!master->mode_bits)
master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;
if (master->transfer || master->transfer_one_message)
return -EINVAL;
master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;
master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;
master->transfer_one = spi_bitbang_transfer_one;
master->set_cs = spi_bitbang_set_cs;
if (!bitbang->txrx_bufs) {
bitbang->use_dma = 0;
bitbang->txrx_bufs = spi_bitbang_bufs;
if (!master->setup) {
if (!bitbang->setup_transfer)
bitbang->setup_transfer =
spi_bitbang_setup_transfer;
master->setup = spi_bitbang_setup;
master->cleanup = spi_bitbang_cleanup;
}
}
/* driver may get busy before register() returns, especially
* if someone registered boardinfo for devices
*/
ret = spi_register_master(spi_master_get(master));
if (ret)
spi_master_put(master);
return ret;
}
- 首先,从位移传输控制结构体中获取 SPI 主设备结构体
master
。 - 检查主设备结构体和位移传输控制结构体中的片选信号控制函数是否存在,如果不存在则返回错误码 -EINVAL。
- 初始化互斥锁
bitbang->lock
,用于保护对位移传输控制结构体的访问。 - 如果主设备结构体中的传输模式位字段
mode_bits
未设置,则设置为默认的模式位,包括 SPI_CPOL、SPI_CPHA 和位移传输控制结构体中的标志位。 - 检查主设备结构体中的传输函数是否已经定义,如果已定义则返回错误码 -EINVAL。
- 设置主设备结构体中的准备硬件传输函数、释放硬件传输函数、单次传输函数和片选信号控制函数,分别对应位移传输控制结构体中的对应函数。
- 如果位移传输控制结构体中的数据缓冲区传输函数未定义,则设置使用 DMA 标志为 0,并将数据缓冲区传输函数设置为默认的位移传输函数
spi_bitbang_bufs
。如果主设备结构体中的设置函数未定义,则设置使用默认的设置函数spi_bitbang_setup
和清理函数spi_bitbang_cleanup
。 - 注册 SPI 主设备,将其添加到系统中。如果注册失败,则释放主设备结构体并返回错误码。
- 返回注册结果,成功时返回 0。
spi_bitbang_stop
spi_bitbang_stop
作用是停止 SPI 位移传输。它通过取消注册 SPI 主设备来停止相关的传输操作。这可以用于在不需要进行 SPI 位移传输时,将相关资源释放并从系统中移除相应的设备。
void spi_bitbang_stop(struct spi_bitbang *bitbang)
{
spi_unregister_master(bitbang->master);
}
- 从位移传输控制结构体中获取 SPI 主设备结构体
master
。 - 调用
spi_unregister_master
函数来取消注册 SPI 主设备。这将从系统中移除该 SPI 主设备。
本文参考
https://blog.csdn.net/qq_16054639/article/details/106733956
https://www.cnblogs.com/TWL123/p/9516269.html
https://whycan.com/t_5012.html
https://www.imooc.com/article/33911
https://blog.csdn.net/Creator_Ly/article/details/109640572
标签:bitbang,GPIO,SPI,引脚,spi,驱动,gpio,模拟 From: https://www.cnblogs.com/dongxb/p/17868586.html