【驱动】SPI驱动分析(五)

简介

模拟SPI驱动是一种软件实现的SPI总线驱动。在没有硬件SPI控制器的系统中,通过软件模拟实现SPI总线的功能。它允许在不修改硬件的情况下,通过GPIO(通用输入/输出)引脚模拟SPI总线的通信,从而与SPI设备进行数据交换。

模拟SPI驱动相对于硬件SPI来说,可能会有一定的性能损失,因为软件模拟不如硬件实现的SPI控制器快速和高效。

模拟SPI驱动相比硬件SPI控制器存在一些缺点,包括:

  1. 性能较低:软件模拟SPI需要通过GPIO引脚进行数据的输入和输出,并进行相应的时序控制。相比硬件SPI控制器,软件模拟SPI的速度较慢,通信效率较低,特别是在高速数据传输和频繁通信的场景下。
  2. 占用CPU资源:模拟SPI驱动在内核空间运行,需要通过CPU执行软件代码来模拟SPI总线的功能。这会占用一定的CPU资源,可能导致系统性能下降,并且可能影响其他任务的响应时间。
  3. 时序控制的挑战:软件模拟SPI需要准确控制数据的时序,包括数据的传输速率、时钟边沿和信号延迟等。需要仔细处理时序相关的问题,确保正确的数据传输和可靠性。
  4. 受限于GPIO资源:模拟SPI驱动需要使用系统中的GPIO引脚来模拟SPI总线的通信,因此受限于可用的GPIO资源数量。如果系统中可用的GPIO引脚有限,可能会限制同时连接的SPI设备数量或引起硬件扩展的困难。

内核中模拟SPI驱动的实现

在Linux内核中,SPI子系统提供了用于管理SPI总线和设备的功能和接口。虽然SPI子系统本身不直接提供模拟SPI驱动的功能,但它提供了一些接口和框架,可以用于实现模拟SPI驱动。

spi-gpiodrivers/spi/spi-gpio.cspi-bitbang.c
drivers/spi/spi-gpio.cspi-bitbang.c

spi-gpio

platform_driver

spi_gpio_drivercompatiblespi_gpio_dt_idscompatiblespi_gpio_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
static const struct of_device_id spi_gpio_dt_ids[] = {
{ .compatible = "spi-gpio" },
{}
};

spi_gpio_probe_dt

spi_gpio_probe_dtplatform_dataplatform_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
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()platform_dataplatform_dataspi_gpio_request()spi_alloc_master()spi_masterspi_gpioplatform_set_drvdata()spi_gpioplatform_devicedriver_dataspi_masterCONFIG_OFcs-gpiosspi_gpiobitbangspi_masterspi_bitbang_start()spi_master

spi_gpio_remove

spi_gpio_remove
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_devicespi_gpiopdatastruct spi_gpiostruct spi_gpio_platform_dataplatform_get_drvdata(pdev)spi_gpiodev_get_platdata(&pdev->dev)platform_set_drvdata()spi_bitbang_stop(&spi_gpio->bitbang)spi_bitbang_stop()
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)
spi_master_put(spi_gpio->bitbang.master)

spi_gpio_request

spi_gpio_request
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_datapdatalabelu16res_flagsSPI_MOSI_GPIOSPI_GPIO_NO_MOSISPI_MOSI_GPIOSPI_GPIO_NO_MOSIspi_gpio_alloc(SPI_MOSI_GPIO, label, false)valuedoneSPI_MOSI_GPIOSPI_GPIO_NO_MOSI*res_flagsSPI_MASTER_NO_TXSPI_MISO_GPIOSPI_GPIO_NO_MISOSPI_MISO_GPIOSPI_GPIO_NO_MISOspi_gpio_alloc(SPI_MISO_GPIO, label, true)valuefree_mosiSPI_MISO_GPIOSPI_GPIO_NO_MISO*res_flagsSPI_MASTER_NO_RXspi_gpio_alloc(SPI_SCK_GPIO, label, false)valuefree_misodonefree_misogpio_free(SPI_MISO_GPIO)free_mosigpio_free(SPI_MOSI_GPIO)donevalue

spi_gpio_alloc

spi_gpio_allocgpio_request()is_in
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;
}
pinlabelis_ingpio_request(pin, label)gpio_request()valuegpio_request()is_ingpio_direction_input(pin)valueis_ingpio_direction_output(pin, 0)valuevalue

spi_gpio_cleanup

spi_gpio_cleanupspi_bitbang_cleanup(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
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_devicespicsstatusstruct spi_gpiospi_gpiospi_to_spi_gpio(spi)spispi_gpiostruct device_nodenpnpspi_gpio->cs_gpios[spi->chip_select]csnpspi->controller_dataspi->controller_datacsspi->controller_statecsSPI_GPIO_NO_CHIPSELECTgpio_request(cs, dev_name(&spi->dev))statusgpio_request()statusgpio_direction_output(cs, !(spi->mode & SPI_CS_HIGH))SPI_CS_HIGHspi->controller_statestatusstatuscsspi_gpio->cs_gpios[spi->chip_select]spi_bitbang_setup(spi)statusstatus
spi->controller_statecsSPI_GPIO_NO_CHIPSELECTgpio_free(cs)
status

spi_gpio_chipselect

spi_gpio_chipselect
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_chipselectspispi_deviceis_activespi_to_spi_gpiospi_devicespi_gpiospi_gpiospi_devicechip_selectcsis_activeis_activespi_devicemodeSPI_CPOLsetsckcsSPI_GPIO_NO_CHIPSELECTcsgpio_set_value_cansleepspi_devicemodeSPI_CS_HIGHis_activegpio_set_value_cansleepis_activegpio_set_value_cansleep

spi-bitbang

bitbang_txrx_32

bitbang_txrx_32txrx_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;
}
tbitscounttxrxwhilecountwordtxwordtxtxrx_wordspinswordbitstxrx_wordwordrxwordrxcountt->lencount

spi_bitbang_setup_transfer

spi_bitbang_setup_transfert
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;
}
csbits_per_wordhztbits_per_wordhztbits_per_wordhzbits_per_wordspi->bits_per_wordbits_per_wordhzspi->max_speed_hzhznsecsnsecs = (clock period)/2 = (1000000000/2) / hznsecs-EINVAL

spi_bitbang_setup

spi_bitbang_setup
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;
}
csbitbangspi_master_get_devdatabitbangcskzalloccs-ENOMEMcscontroller_statebitbangspi->modetxrx_wordSPI_CPOLSPI_CPHA-EINVALbitbangsetup_transferspiNULLsetup_transferbitbang->lockbitbangbusychipselectndelaybitbang->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;
}
bitbangstatusbitbangsetup_transferspitransferouttransferlenbitbangtxrx_bufsstatustransferlenstatusstatustransferlenstatus-EREMOTEIOoutspi_finalize_current_transferstatus

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);
}
cscsnsecscstxrx_bufsspitxrx_wordnsecst

spi_bitbang_prepare_hardware

spi_bitbang_prepare_hardwarebusy
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;
}
bitbangbitbang->lockbusybitbang->lock

spi_bitbang_unprepare_hardware

spi_bitbang_unprepare_hardwarebusy
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;
}
bitbangbitbang->lockbusybitbang->lock

spi_bitbang_set_cs

spi_bitbang_set_cs
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);
}
bitbangSPI_CS_HIGHenablendelaybitbangchipselectenablendelay

spi_bitbang_start

spi_bitbang_start
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;
}
masterbitbang->lockmode_bitsspi_bitbang_bufsspi_bitbang_setupspi_bitbang_cleanup

spi_bitbang_stop

spi_bitbang_stop
void spi_bitbang_stop(struct spi_bitbang *bitbang)
{
spi_unregister_master(bitbang->master);
}
masterspi_unregister_master

本文参考