首页 > 系统相关 >linux驱动移植-SPI控制器驱动

linux驱动移植-SPI控制器驱动

时间:2023-02-25 22:45:03浏览次数:51  
标签:i2c s3c24xx SPI hw dev spi linux 控制器驱动

Mini2440裸机开发之SPI(OLED SSD1306)我们介绍了S3C2440这款SOC的I2C结构,其内部只有一个SPI控制器:S3C2440 SPI相关引脚定义:

 

SPI SCLK MOSI MISO SS
SPI0 GPE13 GPE12 GPE11 GPG2
SPI1 GPG7 GPG6 GPG5 GPG3

 

这一节我们将研究S3C2440的SPI控制器驱动,或者说SPI Master驱动。

SPI控制器驱动是基于platform模型的,主要提供一个SPI片选以及SPI协议的收发函数。在platform driver中probe函数中:

  • 动态分配spi_controller,并进行初始化,包括设置set_cs、transfer_one;
  • 初始化GPIO功能复用为SPI;
  • 最后将spi_controller注册到spi_bus_type总线,并且注册时会调用spi_match_controller_to_boardinfo,扫描borad_list链表并使用spi_new_device注册SPI从设备。

一、 platform设备注册(s3c2410-spi)

1.1 s3c_device_spi1

我们定位到arch/arm/plat-samsung/devs.c定义,可以发现有platform设备s3c_device_spi1变量,

static struct resource s3c_spi1_resource[] = {
        [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI1, SZ_32),    
        [1] = DEFINE_RES_IRQ(IRQ_SPI1),
};

struct platform_device s3c_device_spi1 = {    // platform设备信息
        .name           = "s3c2410-spi",
        .id             = 1,
        .num_resources  = ARRAY_SIZE(s3c_spi1_resource),
        .resource       = s3c_spi1_resource,
        .dev            = {
                .dma_mask               = &samsung_device_dma_mask,
                .coherent_dma_mask      = DMA_BIT_MASK(32),
        }
};

我们重点关注一下platform设备资源定义,这里定义了2个资源,第1个是内存资源、第2个是中断资源:

  • 第一个资源是IO内存资源,起始地址是SPI1寄存器寄基地址,即0x59000020,大小位32字节;
  • 第二个资源是中断资源,中断号位IRQ_SPI1,即29;

此外dma_mask设置为:

#define samsung_device_dma_mask (*((u64[]) { DMA_BIT_MASK(32) }))

 

我们在arch/arm/plat-samsung/devs.文件追加如下代码:

 

void __init s3c_spi1_set_platdata(struct s3c2410_spi_info *pd)  // 用于设置SPI平台设备dev.platform_data
{
        struct s3c2410_spi_info *npd;

        npd = s3c_set_platdata(pd, sizeof(*npd), &s3c_device_spi1);  

        if (!npd){
                printk(KERN_ERR "no memory for SPI platform data\n");
        }
} 

这里已经定义了SPI控制器1相关的platform_device设备s3c_device_spi1,并进行了初始化,那platform设备啥时候注册的呢?

linux内核启动的时候会根据uboot中设置的机器id执行相应的初始化工作,比如.init_machine、.init_irq。

我们首先定位到arch/arm/mach-s3c24xx/mach-smdk2440.c:

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <[email protected]> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END

重点关注init_machine,init_machine中保存的是开发板资源注册的初始化代码。

1.2 smdk2440_machine_init

static void __init smdk2440_machine_init(void)
{
        s3c24xx_fb_set_platdata(&smdk2440_fb_info);   
        s3c_i2c0_set_platdata(NULL);   // 初始化s3c_device_i2c0->dev.platform_data

        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));  // s3c2440若干个platform设备注册 usb host controller、lcd、wdt等
        smdk_machine_init();  // s3c24x0系列若干个platform设备注册(通用)
}

这里利用platform_add_devices进行若干个platform设备的注册,我们修改smdk2440_machine_init添加如下代码:

   s3c_spi1_set_platdata(&default_spi1_data);   // 初始化s3c_device_spi1->dev.platform_data

同时需要在该文件定义:

struct s3c2410_spi_info default_spi1_data = {  // 用于初始化&s3c_device_spi1->dev.platform_data 
        .pin_cs         = S3C2410_GPG(3),      // 这里我们使用GPG3作为SPI从设备的片选引脚,宏定义在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h
        .num_cs         = 1,                   // 片选数量
        .bus_num        = 1                    // SPI控制器编号
};

smdk2440_machine_init函数通过调用platform_device_register实现platform设备注册。

static struct platform_device *smdk2440_devices[] __initdata = {
        &s3c_device_ohci,
        &s3c_device_lcd,
        &s3c_device_wdt,
        &s3c_device_i2c0,
        &s3c_device_iis,
        &smdk2440_device_eth,
};

我们需要在smdk2440_devices数组添加&s3c_device_spi1(platform设备s3c_device_spi1是通过arch/arm/plat-samsung/include/plat/devs.h头文件引入的)。

二、platform驱动注册(s3c2410-spi)

SPI控制器platform驱动定义位于drivers/spi/spi-s3c24xx.c文件中。

2.1 入口和出口函数

我们在spi-s3c24xx.c文件定位到驱动模块的入口和出口:

MODULE_ALIAS("platform:s3c2410-spi");
static struct platform_driver s3c24xx_spi_driver = {
        .probe          = s3c24xx_spi_probe,
        .remove         = s3c24xx_spi_remove,
        .driver         = {
                .name   = "s3c2410-spi",
                .pm     = S3C24XX_SPI_PMOPS,
        },
};
module_platform_driver(s3c24xx_spi_driver);

在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是s3c24xx_spi_probe函数。

2.2 SPI相关数据结构

在介绍s3c24xx_spi_probe之前,先来看一下s3c24xx_spi结构体定义,其定义在drivers/spi/spi-s3c24xx.c文件:

struct s3c24xx_spi {           // 用来表示SoC的一个SPI控制器
        /* bitbang has to be first */
        struct spi_bitbang       bitbang;   // 位宽,默认为8
        struct completion        done;      // 同步机制

        void __iomem            *regs;  
        int                      irq;       // 中断号
        int                      len;  
        int                      count;

        struct fiq_handler       fiq_handler;
        enum spi_fiq_mode        fiq_mode;       
        unsigned char            fiq_inuse;   
        unsigned char            fiq_claimed;

        void                    (*set_cs)(struct s3c2410_spi_info *spi,    // 设置片选引脚
                                          int cs, int pol);

        /* data buffers */
        const unsigned char     *tx;    // 传输缓冲区
        unsigned char           *rx;    // 接收缓冲区  

        struct clk              *clk;   // 时钟
        struct spi_master       *master;   // SPI控制器
        struct spi_device       *curdev;   // 当前SPI从设备
        struct device           *dev;      / 存储platform设备的dev成员   
        struct s3c2410_spi_info *pdata;    // 私有数据  默认初始化为&default_spi_data
};

s3c2410_spi_info定义在include/linux/spi/s3c24xx.h:

struct s3c2410_spi_info {          
        int                      pin_cs;        /* simple gpio cs 片选引脚 */
        unsigned int             num_cs;        /* total chipselects 片选数量,也就是说当前SPI控制前连接了多少个SPI从设备 */
        int                      bus_num;       /* bus number to use.  SPI控制器编号 */

        unsigned int             use_fiq:1;     /* use fiq,是否使用中断 */

        void (*gpio_setup)(struct s3c2410_spi_info *spi, int enable);     // SPI控制器初始化函数指针,用来设置SPI控制器和工作方式、clock等;    
        void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol);    // 用于控制GPIO发出片选、取消片选信号
};

2.3 s3c24xx_spi_probe

s3c24xx_spi_probe定义在drivers/spi/spi-s3c24xx.c文件:

static int s3c24xx_spi_probe(struct platform_device *pdev)
{
        struct s3c2410_spi_info *pdata;
        struct s3c24xx_spi *hw;
        struct spi_master *master;
        struct resource *res;
        int err = 0;

        master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));   // 动态分配spi_controller,额外分配空间大小为s3c24xx_spi,并赋值给master->dev.driver_data
        if (master == NULL) {
                dev_err(&pdev->dev, "No memory for spi_master\n");
                return -ENOMEM;
        }

        hw = spi_master_get_devdata(master);   // 获取master->dev.driver_data

        hw->master = master;                   // 设置SPI控制器
        hw->pdata = pdata = dev_get_platdata(&pdev->dev);   // 获取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);  // 设置驱动数据 pdev.dev.plat_data = hw
        init_completion(&hw->done);      // 初始化completion

        /* initialise fiq handler */

        s3c24xx_spi_initfiq(hw);     // 初始化hw成员fiq_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;               // SPI控制器编号
        master->bits_per_word_mask = SPI_BPW_MASK(8);   // 设置bit_per_world的掩码,等于bits_per_word-1,比如这里就是7

        /* setup the state for the bitbang driver */

        hw->bitbang.master         = hw->master;
        hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;   // 实现了SPI数据传输
        hw->bitbang.chipselect     = s3c24xx_spi_chipsel;     // 实现了SPI从设备片选  
        hw->bitbang.txrx_bufs      = s3c24xx_spi_txrx;       

        hw->master->setup  = s3c24xx_spi_setup;     // SPI控制器初始化函数指针,用来设置SPI控制器和工作方式、clock等;    

        dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);

        /* find and map our resources */
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   // 获取第一个IO内存资源
        hw->regs = devm_ioremap_resource(&pdev->dev, res);      // 物理地址->虚拟地址映射 映射是SPI控制器1寄存器地址
        if (IS_ERR(hw->regs)) {
                err = PTR_ERR(hw->regs);
                goto err_no_pdata;
        }
        hw->irq = platform_get_irq(pdev, 0);             // 获取中断资源
        if (hw->irq < 0) {                               // 未指定中断 异常
                dev_err(&pdev->dev, "No IRQ specified\n");
                err = -ENOENT;
                goto err_no_pdata;
        }

        err = devm_request_irq(&pdev->dev, hw->irq, s3c24xx_spi_irq, 0,  // 注册中断服务
                                pdev->name, hw);
        if (err) {
                dev_err(&pdev->dev, "Cannot claim IRQ\n");     
                goto err_no_pdata;
        }

        hw->clk = devm_clk_get(&pdev->dev, "spi");     // 获取spi时钟  
        if (IS_ERR(hw->clk)) {
                dev_err(&pdev->dev, "No clock for device\n"); 
                err = PTR_ERR(hw->clk);
                goto err_no_pdata;
        }

        /* setup any gpio we can */

        if (!pdata->set_cs) {         // 如果s3c2410_spi_info中未设置片选/取消片选函数
                if (pdata->pin_cs < 0) {       // 片选引脚无效
                        dev_err(&pdev->dev, "No chipselect pin\n");
                        err = -EINVAL;
                        goto err_register;
                }

                err = devm_gpio_request(&pdev->dev, pdata->pin_cs,   // 为片选引脚申请GPIO资源
                                        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);    // 设置GPIO为输出,输出高电平
        } else
                hw->set_cs = pdata->set_cs;

        s3c24xx_spi_initialsetup(hw);

        /* register our spi controller */

        err = spi_bitbang_start(&hw->bitbang);
        if (err) {
                dev_err(&pdev->dev, "Failed to register SPI master\n");
                goto err_register;
        }

        return 0;

 err_register:
        clk_disable(hw->clk);

 err_no_pdata:
        spi_master_put(hw->master);
        return err;
}

这段代码属实有点长了,让人一眼看过去,就有点想放弃去读的想法,既然都学习到了这一步,我们还是耐着性去分析吧。这里代码理解起来还是比较简单的,主要流程如下:

  • 设置i2c_adapter适配器结构体;
    • 设置name为s3c2410-i2c;
    • 设置algo为s3c24xx_i2c_algorithm;
    • 设置retries为2;
    • 设置algo_data 为i2c;
    • 设置dev.parent为&pdev->dev;
    • 设置nr为i2c->pdata->bus_num;
    • ...
  • 调用s3c_i2c0_cfg_gpio配置I2C总线所使用的GPIO引脚,即配置GPE15为SDA功能,GPE14为SCA功能;
  • 调用s3c24xx_i2c_init初始化S3C2440 I2C相关的寄存器,设置从设备地址、以及I2C时钟预分频系数;
  • 调用devm_request_irq注册I2C中断,中断处理服务函数为s3c24xx_i2c_irq;
  • 调用i2c_add_numbered_adapter注册i2c_adapter适配器;
2.3.1 devm_gpio_request

devm_gpio_request用来申请GPIO资源,定义在drivers/gpio/gpiolib-devres.c文件:

/**
 *      devm_gpio_request - request a GPIO for a managed device
 *      @dev: device to request the GPIO for
 *      @gpio: GPIO to allocate
 *      @label: the name of the requested GPIO
 *
 *      Except for the extra @dev argument, this function takes the
 *      same arguments and performs the same function as
 *      gpio_request().  GPIOs requested with this function will be
 *      automatically freed on driver detach.
 *
 *      If an GPIO allocated with this function needs to be freed
 *      separately, devm_gpio_free() must be used.
 */

int devm_gpio_request(struct device *dev, unsigned gpio, const char *label)
{
        unsigned *dr;
        int rc;

        dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL);
        if (!dr)
                return -ENOMEM;

        rc = gpio_request(gpio, label);
        if (rc) {
                devres_free(dr);
                return rc;
        }

        *dr = gpio;
        devres_add(dev, dr);

        return 0;
}
2.3.2 s3c24xx_spi_gpiocs

s3c24xx_spi_gpiocs提供了片选和取消片选的功能,实际上就是调用gpio_set_value来设置输出高、低电平,定义在drivers/spi/spi-s3c24xx.c文件:

static void s3c24xx_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol)
{
        gpio_set_value(spi->pin_cs, pol);
}
2.3.3 s3c24xx_spi_initialsetup

s3c24xx_spi_initialsetup用于使能spi时钟,初始化S3C2440 SPI1相关的寄存器,设置S3C2410_SPPIN、S3C2410_SPCON,定义在drivers/spi/spi-s3c24xx.c文件:

static void s3c24xx_spi_initialsetup(struct s3c24xx_spi *hw)
{
        /* for the moment, permanently enable the clock */

        clk_enable(hw->clk);

        /* program defaults into the registers */

        writeb(0xff, hw->regs + S3C2410_SPPRE);
        writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN);
        writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON);

        if (hw->pdata) {
                if (hw->set_cs == s3c24xx_spi_gpiocs)     // 默认走这里
                        gpio_direction_output(hw->pdata->pin_cs, 1);   // 设置片选引脚GPIO为输出,输出高电平

                if (hw->pdata->gpio_setup)               // default_spi1_data中未设定该函数指针,不会走这里
                        hw->pdata->gpio_setup(hw->pdata, 1);
        }
}

2.4 s3c24xx_i2c_algorithm

/* i2c bus registration info */
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
        .master_xfer            = s3c24xx_i2c_xfer,      /* 数据传输,这个上一节已经介绍了 */
        .functionality          = s3c24xx_i2c_func,
};

s3c24xx_i2c_xfer函数在上一节已经介绍了,与SOC的I2C控制器寄存器设置相关了,基本是裸机相关的内容,无非就是产生 I2C 通信时序,如开始信号,停止信号、应答信号等。

2.5 s3c24xx_i2c_init

s3c24xx_i2c_init函数主要就是进行I2C控制器的初始化工作:

/*
 * initialise the controller, set the IO lines and frequency
 */
static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
        struct s3c2410_platform_i2c *pdata;
        unsigned int freq;

        /* get the plafrom data */

        pdata = i2c->pdata;

        /* write slave address */

        writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);    // 写入从设备地址

        dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);

        writel(0, i2c->regs + S3C2410_IICCON);                  // 初始化IICCON=0   
        writel(0, i2c->regs + S3C2410_IICSTAT);                 // 初始化IICSTAT=0

        /* we need to work out the divisors for the clock... */
        if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {          // 这个函数主要就是根据当前i2c时钟频率以及预设的I2C控制器目标频率,计算IIC总线发送时钟预分频系数,freq为设置的实际频率
                dev_err(i2c->dev, "cannot meet bus frequency required\n");
                return -EINVAL;
        }

        /* todo - check that the i2c lines aren't being dragged anywhere */

        dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
        dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02x\n",
                readl(i2c->regs + S3C2410_IICCON));

        return 0;
}

2.6 s3c24xx_i2c_irq

s3c24xx_i2c_irq调用下一个字节传输函数i2s_s3c_irq_nextbyte()来传输数据。

/*
 * top level IRQ servicing routine
 */
static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{
        struct s3c24xx_i2c *i2c = dev_id;
        unsigned long status;
        unsigned long tmp;

        status = readl(i2c->regs + S3C2410_IICSTAT);

        if (status & S3C2410_IICSTAT_ARBITR) {  // 仲裁失败
                /* deal with arbitration loss */
                dev_err(i2c->dev, "deal with arbitration loss\n");
        }

        if (i2c->state == STATE_IDLE) {        // 空闲状态
                dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");

                tmp = readl(i2c->regs + S3C2410_IICCON);
                tmp &= ~S3C2410_IICCON_IRQPEND;
                writel(tmp, i2c->regs +  S3C2410_IICCON);  // 清I2C中断挂起 IICCON位[4]
                goto out;
        }

        /*
         * pretty much this leaves us with the fact that we've
         * transmitted or received whatever byte we last sent
         */
        i2c_s3c_irq_nextbyte(i2c, status);  // 继续数据传输

 out:
        return IRQ_HANDLED;
}

参考文章

[1]Linux·SPI驱动分析和实例

标签:i2c,s3c24xx,SPI,hw,dev,spi,linux,控制器驱动
From: https://www.cnblogs.com/zyly/p/17155289.html

相关文章

  • linux用户密码安全策略
    LinuxPAM模块类型linuxPAM配置文件/etc/pam.d/system-auth认证管理auth账号管理account密码管理 password会话管理session#%PAM-1.0#Thisfileisauto......
  • linux配置ip地址
    1.配置ip地址:1cd/etc/sysconfig/network-scripts/2vimifcfg-ens332.刷新网络服务:1servicenetworkrestart-->centos72ifupens33-->centos83.关闭f......
  • Arch/Majaro Linux更换国内源
    无法访问默认源列表:错误:无法从mirror.informatik.tu-freiberg.de:Connectiontimeoutafter10000ms获取文件'core.db'错误:无法从mirror.informatik.tu-freiberg.d......
  • linux之gcc
    #################usr是unixsystemresource缩写,不是user缩写;  /lib,/usr/lib,/usr/local/lib的区别:/lib是内核级的;/usr/lib是系统级的;/usr/local/lib是用户级的......
  • linux 中awk命令 制定 输入行分隔符、列分割符的几种方法
     ……………………     ......
  • linux 中 sed命令中的q选项
     001、  https://blog.csdn.net/weixin_33838310/article/details/117348155  ......
  • Linux Ubuntu配置国内源
    因为众所周知的原因,国外的很多网站在国内是访问不了或者访问极慢的,这其中就包括了Ubuntu的官方源。所以,想要流畅的使用apt安装应用,就需要配置国内源的镜像。市面上Ubuntu......
  • Linux-查看权限控制信息
         ......
  • Linux-- 用户和用户组
           ......
  • linux-nc端口测试工具
    #安装工具yum-yinstallnc#监听端口nc-lp8080#案例:大数据服务走8080端口,外网监听。安全组开过了。但是访问不了。关掉服务,nc开启8080,外网访问,还是访问不了。说明......