模拟SPI驱动是一种软件实现的SPI总线驱动。在没有硬件SPI控制器的系统中,通过软件模拟实现SPI总线的功能。它允许在不修改硬件的情况下,通过GPIO(通用输入/输出)引脚模拟SPI总线的通信,从而与SPI设备进行数据交换。
模拟SPI驱动相对于硬件SPI来说,可能会有一定的性能损失,因为软件模拟不如硬件实现的SPI控制器快速和高效。
模拟SPI驱动相比硬件SPI控制器存在一些缺点,包括:
在Linux内核中,SPI子系统提供了用于管理SPI总线和设备的功能和接口。虽然SPI子系统本身不直接提供模拟SPI驱动的功能,但它提供了一些接口和框架,可以用于实现模拟SPI驱动。
spi-gpio
的框架,可使用GPIO引脚模拟SPI总线,gpio模拟spi代码在drivers/spi/spi-gpio.c
中。这个框架允许将GPIO引脚配置为SPI总线的时钟、片选、输入和输出信号,并提供了对应的接口函数供驱动程序使用。spi-bitbang.c
中下面我们分别分析下这drivers/spi/spi-gpio.c
和 spi-bitbang.c
两个文件。
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
节点,这样才能probe成功。
static const struct of_device_id spi_gpio_dt_ids[] = {
{ .compatible = "spi-gpio" },
{}
};
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
使用了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_bitbang_start()
函数启动SPI GPIO的位操作传输。该函数会根据之前设置的参数,开始进行SPI数据的传输。spi_master
结构体的内存空间。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引脚的资源,并根据硬件配置设置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
函数用于分配和配置一个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相关资源。它首先检查特定的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);
}
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 引脚状态,以实现 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 引脚配置。gpio_set_value_cansleep
函数来设置片选信号 GPIO 引脚的电平。根据 spi_device
结构体中的 mode
字段的 SPI_CS_HIGH
位,如果该位为真,则表示片选信号是高电平有效,那么根据 is_active
参数的值直接传递给 gpio_set_value_cansleep
函数;如果该位为假,则表示片选信号是低电平有效,那么取 is_active
参数的反值作为电平状态传递给 gpio_set_value_cansleep
函数。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
的值,表示已经处理了一个数据字。t->len
减去剩余的未处理数据长度 count
,表示成功发送和接收的数据长度。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;
}
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
,表示时间间隔过大。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;
}
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
作用是执行单个传输操作,包括设置传输参数和进行数据的发送和接收。它通过调用位移传输控制结构体中的函数来完成传输操作,并根据传输的结果来设置传输操作的状态。最后,执行收尾工作并返回传输操作的状态。
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;
}
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
封装了对控制器状态结构体中的位移传输函数的调用,以执行数据缓冲区的传输。它通过获取位移传输所需的时间间隔和调用位移传输函数来完成传输操作,并返回传输操作的状态。
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);
}
cs
。cs
中获取位移传输所需的时间间隔 nsecs
。cs
中的 txrx_bufs
函数来执行数据缓冲区的传输。该函数接受 SPI 设备指针 spi
、位移传输函数 txrx_word
、时间间隔 nsecs
和传输结构体指针 t
作为参数,并返回传输操作的状态。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;
}
bitbang
。bitbang->lock
来保护对位移传输控制结构体的访问。首先获取互斥锁。busy
标志设置为 1,表示硬件正在忙于数据传输操作。bitbang->lock
,以允许其他线程访问位移传输控制结构体。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;
}
bitbang
。bitbang->lock
来保护对位移传输控制结构体的访问。首先获取互斥锁。busy
标志设置为 0,表示硬件不再忙于数据传输操作。bitbang->lock
,以允许其他线程访问位移传输控制结构体。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);
}
bitbang
。SPI_CS_HIGH
标志位,以及函数输入参数 enable
的值,来确定是否需要使能片选信号。这是为了确保兼容片选信号的极性。ndelay
函数引入延迟,以确保在改变片选信号状态之前有足够的时间。bitbang
中的 chipselect
函数,以控制片选信号的状态。根据 enable
的值,将片选信号设置为活动状态或非活动状态。ndelay
函数引入延迟,以确保在改变片选信号状态后有足够的时间。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;
}
master
。bitbang->lock
,用于保护对位移传输控制结构体的访问。mode_bits
未设置,则设置为默认的模式位,包括 SPI_CPOL、SPI_CPHA 和位移传输控制结构体中的标志位。spi_bitbang_bufs
。如果主设备结构体中的设置函数未定义,则设置使用默认的设置函数 spi_bitbang_setup
和清理函数 spi_bitbang_cleanup
。spi_bitbang_stop
作用是停止 SPI 位移传输。它通过取消注册 SPI 主设备来停止相关的传输操作。这可以用于在不需要进行 SPI 位移传输时,将相关资源释放并从系统中移除相应的设备。
void spi_bitbang_stop(struct spi_bitbang *bitbang)
{
spi_unregister_master(bitbang->master);
}
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