【IMX6ULL学习笔记】二十一、SPI驱动和设备

发布者:lambda21最新更新时间:2025-02-28 来源: cnblogs关键字:SPI驱动  设备 手机看文章 扫描二维码
随时随地手机看文章

一、Linux 下 SPI 驱动框架简介

1、SPI 主机驱动

SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。Linux 内核使用 spi_master 表示 SPI 主机驱动,spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件中,内容如下(有缩减):


struct spi_master {

    struct device dev;


    struct list_head list;

......

    s16 bus_num;


    /* chipselects will be integral to many controllers; some others

     * might use board-specific GPIOs.

    */

    u16 num_chipselect;


    /* some SPI controllers pose alignment requirements on DMAable

     * buffers; let protocol drivers know about these requirements.

     */

    u16 dma_alignment;


    /* spi_device.mode flags understood by this controller driver */

    u16 mode_bits;


    /* bitmask of supported bits_per_word for transfers */

    u32 bits_per_word_mask;

......

    /* limits on transfer speed */

    u32 min_speed_hz;

    u32 max_speed_hz;


    /* other constraints relevant to this driver */

    u16 flags;

......

    /* lock and mutex for SPI bus locking */

    spinlock_t bus_lock_spinlock;

    struct mutex bus_lock_mutex;


    /* flag indicating that the SPI bus is locked for exclusive use */

    bool bus_lock_flag;

......

    int (*setup)(struct spi_device *spi);


......

    int (*transfer)(struct spi_device *spi,

                    struct spi_message *mesg);

......


    int (*transfer_one_message)(struct spi_master *master,

                                struct spi_message *mesg);

......

};

第 41 行:transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。

第 45 行:transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去。

也就是 SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。


和 I2C 适配器驱动一样,SPI 主机驱动一般都是 SOC 厂商去编写的,SOC 的使用者不用操心。


SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册 spi_master。


1、spi_master 申请与释放

spi_alloc_master 函数用于申请 spi_master,函数原型如下:


struct spi_master *spi_alloc_master(struct device  *dev,

                                    unsigned       size)

函数参数和返回值含义如下:


dev:设备,一般是 platform_device 中的 dev 成员变量。

size:私有数据大小,可通过 spi_master_get_devdata 函数获取这些私有数据。

返回值:申请到的 spi_master。

spi_master 的释放通过 spi_master_put 函数来完成,当我们删除一个 SPI 主机驱动的时候就需要释放掉前面申请的 spi_master,spi_master_put 函数原型如下:


void spi_master_put(struct spi_master *master)

函数参数和返回值含义如下:


master:要释放的 spi_master。

返回值:无。

2、spi_master 的注册与注销

注册:

当 spi_master 初始化完成以后就需要将其注册到 Linux 内核,spi_master 注册函数为spi_register_master,函数原型如下:


int spi_register_master(struct spi_master *master)

函数参数和返回值含义如下:


master:要注册的 spi_master。

返回值:0,成功;负值,失败。

I.MX6U 的 SPI 主机驱动会采用 spi_bitbang_start 这个 API 函数来完成 spi_master 的注册,spi_bitbang_start 函数内部其实也是通过调用 spi_register_master 函数来完成 spi_master 的注册。


注销:

如果要注销 spi_master 的话可以使用 spi_unregister_master 函数,此函数原型为:


void spi_unregister_master(struct spi_master *master)

函数参数和返回值含义如下:


master:要注销的 spi_master。

返回值:无。

如果使用 spi_bitbang_start 注册 spi_master 的话就要使用 spi_bitbang_stop 来注销掉 spi_master。


2、I.MX6U SPI 主机驱动分析

和 I2C 的适配器驱动一样,SPI 主机驱动一般都由 SOC 厂商编写好了,打开 imx6ull.dtsi 文件,找到如下所示内容:


ecspi3: ecspi@02010000 {

    #address-cells = <1>;

    #size-cells = <0>;

    compatible = 'fsl,imx6ul-ecspi', 'fsl,imx51-ecspi';

    reg = <0x02010000 0x4000>;

    interrupts = ;

    clocks = <&clks IMX6UL_CLK_ECSPI3>,

    <&clks IMX6UL_CLK_ECSPI3>;

    clock-names = 'ipg', 'per';

    dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;

    dma-names = 'rx', 'tx';

    status = 'disabled';

};

第 4 行:compatible 属性值,compatible 属性有两个值“fsl,imx6ul-ecspi”和“fsl,imx51-ecspi”,在 Linux 内核源码中搜素这两个属性值即可找到 I.MX6U 对应的 ECSPI(SPI)主机驱动。


I.MX6U 的 ECSPI 主机驱动文件为 drivers/spi/spi-imx.c,在此文件中找到如下内容:


static struct platform_device_id spi_imx_devtype[] = {

    {

        .name = 'imx1-cspi',

        .driver_data = (kernel_ulong_t) &imx1_cspi_devtype_data,

    }, {

        .name = 'imx21-cspi',

        .driver_data = (kernel_ulong_t) &imx21_cspi_devtype_data,

        ......

    }, {

        .name = 'imx6ul-ecspi',

        .driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,

    }, {

        /* sentinel */

    }

};


static const struct of_device_id spi_imx_dt_ids[] = {

    { .compatible = 'fsl,imx1-cspi', .data =

                        &imx1_cspi_devtype_data, },

......

    { .compatible = 'fsl,imx6ul-ecspi', .data =

                        &imx6ul_ecspi_devtype_data, },

    { /* sentinel */ }

};

MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);

......

......

......

static struct platform_driver spi_imx_driver = {

    .driver = {

        .name = DRIVER_NAME,

        .of_match_table = spi_imx_dt_ids,

        .pm = IMX_SPI_PM,

    },

    .id_table = spi_imx_devtype,

    .probe = spi_imx_probe,

    .remove = spi_imx_remove,

};

module_platform_driver(spi_imx_driver);

第 10 行:spi_imx_devtype 为 SPI 无设备树匹配表。

第 17 行:spi_imx_dt_ids 为 SPI 设备树匹配表。

第 21 行:“fsl,imx6ul-ecspi”匹配项,因此可知 I.MX6U 的 ECSPI 驱动就是 spi-imx.c 这个文件。

第 29~38 行:platform_driver 驱动框架,和 I2C 的适配器驱动一样,SPI 主机驱动器采用了 platfom 驱动框架。当设备和驱动匹配成功以后 spi_imx_probe 函数就会执行。


spi_imx_probe 函数会从设备树中读取相应的节点属性值,申请并初始化 spi_master,定义在 drives/spi/spi-imx.c 中,内容如下:


static int spi_imx_probe(struct platform_device *pdev)

{

struct device_node *np = pdev->dev.of_node;

const struct of_device_id *of_id =

of_match_device(spi_imx_dt_ids, &pdev->dev);

struct spi_imx_master *mxc_platform_info =

dev_get_platdata(&pdev->dev);

struct spi_master *master;

struct spi_imx_data *spi_imx;

struct resource *res;

int i, ret, num_cs, irq;


if (!np && !mxc_platform_info) {

dev_err(&pdev->dev, 'can't get the platform datan');

return -EINVAL;

}


ret = of_property_read_u32(np, 'fsl,spi-num-chipselects', &num_cs);

if (ret < 0) {

if (mxc_platform_info)

num_cs = mxc_platform_info->num_chipselect;

else

return ret;

}


master = spi_alloc_master(&pdev->dev,

sizeof(struct spi_imx_data) + sizeof(int) * num_cs);

if (!master)

return -ENOMEM;


platform_set_drvdata(pdev, master);


master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);

master->bus_num = pdev->id;

master->num_chipselect = num_cs;


spi_imx = spi_master_get_devdata(master);

spi_imx->bitbang.master = master;


for (i = 0; i < master->num_chipselect; i++) {

int cs_gpio = of_get_named_gpio(np, 'cs-gpios', i);

if (!gpio_is_valid(cs_gpio) && mxc_platform_info)

cs_gpio = mxc_platform_info->chipselect[i];


spi_imx->chipselect[i] = cs_gpio;

if (!gpio_is_valid(cs_gpio))

continue;


ret = devm_gpio_request(&pdev->dev, spi_imx->chipselect[i],

DRIVER_NAME);

if (ret) {

dev_err(&pdev->dev, 'can't get cs gpiosn');

goto out_master_put;

}

}


spi_imx->bitbang.chipselect = spi_imx_chipselect;

spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;

spi_imx->bitbang.txrx_bufs = spi_imx_transfer;

spi_imx->bitbang.master->setup = spi_imx_setup;

spi_imx->bitbang.master->cleanup = spi_imx_cleanup;

spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message;

spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message;

spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;


init_completion(&spi_imx->xfer_done);


spi_imx->devtype_data = of_id ? of_id->data :

(struct spi_imx_devtype_data *) pdev->id_entry->driver_data;


res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

spi_imx->base = devm_ioremap_resource(&pdev->dev, res);

if (IS_ERR(spi_imx->base)) {

ret = PTR_ERR(spi_imx->base);

goto out_master_put;

}


irq = platform_get_irq(pdev, 0);

if (irq < 0) {

ret = irq;

goto out_master_put;

}


ret = devm_request_irq(&pdev->dev, irq, spi_imx_isr, 0,

       dev_name(&pdev->dev), spi_imx);

if (ret) {

dev_err(&pdev->dev, 'can't get irq%d: %dn', irq, ret);

goto out_master_put;

}


spi_imx->clk_ipg = devm_clk_get(&pdev->dev, 'ipg');

if (IS_ERR(spi_imx->clk_ipg)) {

ret = PTR_ERR(spi_imx->clk_ipg);

goto out_master_put;

}


spi_imx->clk_per = devm_clk_get(&pdev->dev, 'per');

if (IS_ERR(spi_imx->clk_per)) {

ret = PTR_ERR(spi_imx->clk_per);

goto out_master_put;

}


ret = clk_prepare_enable(spi_imx->clk_per);

if (ret)

goto out_master_put;


ret = clk_prepare_enable(spi_imx->clk_ipg);

if (ret)

goto out_put_per;


spi_imx->spi_clk = clk_get_rate(spi_imx->clk_per);

/*

* Only validated on i.mx6 now, can remove the constrain if validated on

* other chips.

*/

if ((spi_imx->devtype_data == &imx51_ecspi_devtype_data

    || spi_imx->devtype_data == &imx6ul_ecspi_devtype_data)

    && spi_imx_sdma_init(&pdev->dev, spi_imx, master, res))

dev_err(&pdev->dev, 'dma setup error,use pio insteadn');


spi_imx->devtype_data->reset(spi_imx);


spi_imx->devtype_data->intctrl(spi_imx, 0);


master->dev.of_node = pdev->dev.of_node;

ret = spi_bitbang_start(&spi_imx->bitbang);

if (ret) {

dev_err(&pdev->dev, 'bitbang start failed with %dn', ret);

goto out_clk_put;

}


dev_info(&pdev->dev, 'probedn');

[1] [2] [3]
关键字:SPI驱动  设备 引用地址:【IMX6ULL学习笔记】二十一、SPI驱动和设备

上一篇:【IMX6ULL学习笔记】二十、IIC驱动和设备
下一篇:【IMX6ULL学习笔记】十八、Platform 驱动框架

推荐阅读最新更新时间:2026-03-25 10:19

IMX6ULL学习笔记】十六、设备树下LED驱动
一、修改设备树文件 在根节点“/”下创建一个名为“alphaled”的子节点,打开 imx6ull-alientek-emmc.dts 文件,在根节点“/”最后面输入如下所示内容: alphaled { #address-cells = 1 ; #size-cells = 1 ; compatible = atkalpha-led ; status = okay ; reg = 0X020C406C 0X04 /* CCM_CCGR1_BASE */ 0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */ 0X020E02F4 0X04 /* SW_PAD_GPIO1_IO0
[单片机]
STM32 HAL库 模拟SPI驱动 DAC8560
网上关于DAC8560相关的模拟SPI驱动的资料少的可怜,甚至连DAC8560的中文资料都不多,更不要提用HAL库进行编程的了,这实在是一块冷门芯片,本文主要是介绍讲解STM32 HAL库使用模拟SPI驱动DAC8560。 首先是SPI相关的简介: SPI通信 高冠避役 · 800阅读 然后是用STM32CubeMx初始化模拟SPI的引脚: STM32CubeMx的GPIO输出的配置可以参考: STM32CubeMx GPIO口输出 高冠避役 · 960阅读 这里之所以是只用开三个GPIO输出来模拟SPI是因为它只用到了半双工通信。 我所用到的三个输出口分别对应8560上的三个SPI通信口。 SYNC:PA6 SCLK
[单片机]
STM32 HAL库 模拟<font color='red'>SPI</font><font color='red'>驱动</font> DAC8560
STM32 HAL库 模拟SPI驱动 DAC8564
之前写过DAC8560的模拟SPI驱动,相比较而言DAC8564能够在网上找到的内容更加的丰富,本文主要是介绍讲解STM32 HAL库使用模拟SPI驱动DAC8564。 DAC8560 https://www.bilibili.com/opus/519334650730238240 SPI相关的简介: https://www.bilibili.com/opus/515342298243272188 然后是用STM32CubeMx初始化模拟SPI的引脚: STM32CubeMx的GPIO输出的配置可以参考: https://www.bilibili.com/opus/484177792198506654 这里
[单片机]
S3C2440 SPI驱动框架
S3C2440 SPI驱动代码详细解读: https://www.linuxidc.com/Linux/2012-08/68402p4.htm 一、platform device and board_info /* /arch/arm/plat-s3c24xx*/ static struct resource s3c_spi0_resource = { = { .start = S3C24XX_PA_SPI, .end = S3C24XX_PA_SPI + 0x1f, .flags = IORESOURCE_MEM, }, = { .star
[单片机]
s3c2440裸机-spi编程-3-gpio模拟spi驱动OLED
操作OLED,通过三条线(SCK、DO、CS)与OLED相连,这里没有DI是因为2440只会向OLED传数据而不用接收数据。 gpio_spi.c来实现gpio模拟spi,负责spi通讯。对于OLED,有专门的指令和数据格式,要传输的数据内容,在oled.c这一层来实现,负责组织数据。 因此,我们需要实现以上两个文件。 1.SPI初始化 新建一个gpio_spi.c文件,实现SPI初始化SPIInt() 1.1 GPIO init(pinmux管脚等配置) 上图J3为板子pin2pin到OLED的底座。 GPF1作为OLED片选引脚,设置为输出; GPG4作为OLED的数据(Data)/命令(Command)选择引脚,
[单片机]
基于ARM9芯片的S3C2440和Linux操作系统设计SPI驱动程序
  在嵌入式开发过程中,许多系统通常使用串口驱动来满足通信要求,但在实际应用中,使用SPI通信方式会更加高效和快捷[2]。SPI接口是一种高速、高效的串行接口技术,因而SPI设备在数据通信应用中十分方便[3]。本文基于ARM9芯片的S3C2440和Linux操作系统,设计了一种SPI驱动程序,该驱动程序功能可靠灵活、易于移植,可应用于多种嵌入式平台,实现ARM与设备之间的通信。   1 硬件说明   1.1 S3C2440开发平台   采用三星公司的SoC芯片S3C2440[4]作为核心处理器,主频为400 MHz,并与64 MB SDRAM和64 MB NAND Flash共同组成核心部分。此外,该平台也为用户提供了大量的通
[单片机]
基于ARM9芯片的S3C2440和Linux操作系统设计<font color='red'>SPI</font><font color='red'>驱动</font>程序
STM32F103标准库开发---SPI实验---底层驱动程序
一、SPI 实验----I/O端口配置 在本次 SPI 实验中,STM32芯片做主控,主要使用 SPI1 功能。 具体引脚配置如下: 具体引脚初始化程序如下: /* SPI引脚初始化配置 **PA4------CS **PA5------SCLK **PA6------MISO **PA7------MOSI */ void SPI1_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );/
[单片机]
STM32F103标准库开发---<font color='red'>SPI</font>实验---底层<font color='red'>驱动</font>程序
STC12硬件SPI驱动MAX7219点阵LED
max7219是一个用于驱动8位7段数字LED或者8x8点阵LED的驱动芯片, 以列扫描的方式, 用16个pin管理64个发光点, 显示8个数字时刷新率为500-1300Hz, 典型值为800Hz. pin脚功能 DIG0 - DIG7: 这个8个pin, 分别代表一个7段数字+点号 SEGA - SEGG, DP: A-G每个代表7段数字里的一段, DP代表数字间的小数点 每一段的驱动电流是40mA, 如果负载需要更大的电流, 需要外接硬件驱动 V+, GND: 电压和接地 DIN: 串行数据输入 CS: 片选, 当电平下拉后从串行口按时脉移入移位寄存器, 当电平上拉后锁存 DOUT: 串行数据输出, 这个口用于级联 级联的
[单片机]
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved