SPI总线由四根通信线组成,全双工、主从方式串行同步通信,一次传输8bit,高位在前,低位在后。
Linux中的主从模式的总线子系统采用的是同一种分离思想,其分离的具体策略大同小异,同样分为设备驱动层、核心层、总线驱动层。具体的分离策略详细分析可参考Linux驱动之I2C子系统剖析中内核对I2C子系统框架的阐述。笔者在这与I2C子系统类比,列出数据结构名。
| I2C | SPI |
---|---|---|
主机适配器(控制器) | struct i2c_adapter | struct spi_master |
机控制器的操作方法 | struct i2c_algorithm | struct spi_bitbang |
从机设备 | struct i2c_client | struct spi_device |
从机设备板卡信息 | struct i2c_board_info | struct spi_board_info |
从机设备驱动 | struct i2c_driver | struct spi_driver |
一次完整的数据包 | struct i2c_msg | struct spi_transfer |
多个完整数据包的封装 | 无 | struct spi_message |
由于子系统架构与I2C等总线类似,所以不会在一些重复部分展开,具体分析可以参考的Linux驱动之I2C子系统剖析中的分析方法。
SPI核心层代码位于drivers/spi/spi.c
中, 从init函数开始分析
static int __init spi_init(void)
{
int status;
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
if (!buf) {
status = -ENOMEM;
goto err0;
}
status = bus_register(&spi_bus_type); /* 注册SPI总线 */
if (status < 0)
goto err1;
status = class_register(&spi_master_class); /* 注册SPI类 */
if (status < 0)
goto err2;
return 0;
err2:
bus_unregister(&spi_bus_type);
err1:
kfree(buf);
buf = NULL;
err0:
return status;
}
spi_init
函数如同I2C核心层中的init函数一样做了两件事,注册SPI总线和创建SPI类,这是内核驱动模型的基本套路,就不比多说了。接下来看下match函数
static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
const struct spi_device *sdev)
{
while (id->name[0]) {
if (!strcmp(sdev->modalias, id->name))
return id;
id++;
}
return NULL;
}
/* SPI总线的match方法 */
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0;
}
可以看到,SPI设备和驱动的匹配是先匹配id_table中的name和设备的modalias,然后匹配驱动的name和设备的modalias。
SPI的控制器驱动,即总线驱动层位于drivers/spi/spi_s3c24xx. c
中,从init函数开始分析。
static int __init s3c24xx_spi_init(void)
{
return platform_driver_probe(&s3c24xx_spi_driver, s3c24xx_spi_probe);
}
会发现SPI控制器驱动并不是用的是platform_driver_register
接口来注册的,而是使用了另一个接口platform_driver_probe
, 其实这是内核提供的不支持热插拔方式的专用平台总线驱动的注册接口,该接口接受两个参数,第一个就是熟知的struct platform_driver
,第二个则是probe函数,当驱动和设备匹配上后就会调用这个probe函数。进入到 s3c24xx_spi_probe
函数进行分析,probe函数的代码比较多,分段进行分析。
struct s3c2410_spi_info *pdata;
struct s3c24xx_spi *hw;
struct spi_master *master;
struct resource *res;
int err = 0;
/* 实例化spi控制器 */
master = spi_alloc_master(&pdev->dev, squdongqudongizeof(struct s3c24xx_spi));
if (master == NULL) {
dev_err(&pdev->dev, "No memory for spi_master\n");
err = -ENOMEM;
goto err_nomem;
}
/* 获取spi的私有数据结构体并初始化为空 */
hw = spi_master_get_devdata(master);
memset(hw, 0, sizeof(struct s3c24xx_spi));
/* 设置spi的私有数据*/
hw->master = spi_master_get(master);
hw->pdata = pdata = pdev->dev.platform_data;
hw->dev = &pdev->dev;
if (pdata == NULL) {
dev_err(&pdev->dev, "No platform data supplied\n");
err = -ENOENT;
goto err_no_pdata;
}
platform_set_drvdata(pdev, hw);
init_completion(&hw->done); /* 初始化completion, 用于IO的同步*/
实例化SPI控制器后设置SPI的私有数据,然后初始化completion。
/* initialise fiq handler */
s3c24xx_spi_initfiq(hw); /* 初始化s3c24xx_spi结构体中的handler,为其绑定中断处理函数 */
/* setup the master state. */
/* the spi->mode bits understood by this driver: */
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
master->num_chipselect = hw->pdata->num_cs;
master->bus_num = pdata->bus_num;
这一段初始化s3c24xx_spi
结构体中的handler,为其绑定中断处理函数,然后设置了主机控制器支持的SPI模式,设置master的片选线编号和总线编号。
hw->bitbang.master = hw->master;
hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;
hw->bitbang.chipselect = s3c24xx_spi_chipsel;
hw->bitbang.txrx_bufs = s3c24xx_spi_txrx;
hw->master->setup = s3c24xx_spi_setup;
hw->master->cleanup = s3c24xx_spi_cleanup;
bitbang表示的是SPI的操作方法,这一段关键是填充了setup_transfer,即传输方法。
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
err = -ENOENT;
goto err_no_iores;
}
hw->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
if (hw->ioarea == NULL) {
dev_err(&pdev->dev, "Cannot reserve region\n");
err = -ENXIO;
goto err_no_iores;
}
hw->regs = ioremap(res->start, resource_size(res));
if (hw->regs == NULL) {
dev_err(&pdev->dev, "Cannot map IO\n");
err = -ENXIO;
goto err_no_iomap;
}
hw->irq = platform_get_irq(pdev, 0);
if (hw->irq < 0) {
dev_err(&pdev->dev, "No IRQ specified\n");
err = -ENOENT;
goto err_no_irq;
}
err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw);
if (err) {
dev_err(&pdev->dev, "Cannot claim IRQ\n");
goto err_no_irq;
}
hw->clk = clk_get(&pdev->dev, "spi");
if (IS_ERR(hw->clk)) {
dev_err(&pdev->dev, "No clock for device\n");
err = PTR_ERR(hw->clk);
goto err_no_clk;
}
/* setup any gpio we can */
if (!pdata->set_cs) {
if (pdata->pin_cs < 0) {
dev_err(&pdev->dev, "No chipselect pin\n");
goto err_register;
}
err = gpio_request(pdata->pin_cs, dev_name(&pdev->dev));
if (err) {
dev_err(&pdev->dev, "Failed to get gpio for cs\n");
goto err_register;
}
hw->set_cs = s3c24xx_spi_gpiocs;
gpio_direction_output(pdata->pin_cs, 1);
} else
hw->set_cs = pdata->set_cs;
s3c24xx_spi_initialsetup(hw);
s3c24xx_spi_initialsetup(hw); /* spi 控制器初始化 */
/* register our spi controller */
/* 内部最后spi_register_master来注册SPI控制器 */
err = spi_bitbang_start(&hw->bitbang);
if (err) {
dev_err(&pdev->dev, "Failed to register SPI master\n");
goto err_register;weiyu
}
这一段是跟具体硬件息息相关的,从获取平台资源开始,然后分别做了IO的映射、中断的申请与中断处理函数的绑定、时钟的初始化和片选的GPIO的申请和拉高电平。最后关键是调用了s3c24xx_spi_initialsetup
函数,该函数内部最后调用了spi_register_master
方法来注册SPI控制器。类比I2C在probe函数中调用的i2c_add_numbered_adapter
函数,其内部会扫描SPI的板卡信息,然后利用板卡信息生成SPI设备,并将控制器spi_master
挂接到spi_device
上,随后在SPI设备驱动层中注册设备驱动后调用probe函数会获取到该spi_device
,然后即可通过spi_device
中挂接的spi_master
来调用控制器的操作方法spi_bitbang_transfer
来传输数据。要注意的是SPI与I2C提供的通用设备驱动不同,其设备节点的生成并不是在注册主机控制器中完成的,而是在通用设备中完成的,这一段从之后设备驱动层的分析可以看出。这一段逻辑类似于I2C,就不参考源码分析了。(好吧,一如既往的懒QAQ)
SPI通用设备驱动位于drivers/spi/spidev.c
中,从init函数开始。
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "spidev",
.owner = THIS_MODULE,
},
.probe = spidev_probe,
.remove = __devexit_p(spidev_remove),
};
static int __init spidev_init(void)
{
int status;
/* Claim our 256 reserved device numbers. Then register a class
* that will key udev/mdev to add/remove /dev nodes. Last, register
* the driver which manages those device numbers.
*/
BUILD_BUG_ON(N_SPI_MINORS > 256);
/* 注册为字符设备驱动,为应用层提供调用接口 */
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
if (status < 0)
return status;
/* 创建spidev类 */
spidev_class = class_create(THIS_MODULE, "spidev");
if (IS_ERR(spidev_class)) {
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
return PTR_ERR(spidev_class);
}
/* 调用核心层提供的接口来注册设备驱动 */
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
return status;
}
有空再写了,先休息啦
本文作者: Ifan Tsai (菜菜)
本文链接: https://cloud.tencent.com/developer/article/2164594
版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!