Mini2440裸机开发之I2C(AT24C08)我们介绍了S3C2440这款SOC的I2C结构,其内部只有一个I2C控制器,其中SCL连接GPE14引脚,SDA连接GPE15引脚。

这一节我们将研究S3C2440的I2C控制器驱动,或者说 I2C 总线驱动、I2C 控制器驱动。

I2C适配器驱动是基于platform模型的,主要提供一个algorithm底层的I2C协议的收发函数。在platform driver中probe函数中:

  • 动态分配i2c_adapter,并进行成员初始化,包括设置algo;
  • 初始化I2C总线所使用的的GPIO功能复用为I2C;
  • 初始化S3C2440 I2C控制器相关的寄存器,设置从设备地址、以及I2C时钟预分频系数;
  • 获取资源信息,并注册I2C中断处理函数;
  • 最后将i2c_adapter注册到i2c_bus_type总线,并且注册时会调用i2c_scan_board_info,扫描并使用i2c_new_device注册I2C从设备。

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

1.1 s3c_device_i2c0

向往常一样,我们依然是定位到arch/arm/mach-s3c24xx/mach-smdk2440.c文件,在该文我们可以找到platform设备s3c_device_i2c0变量,其定义在arch/arm/plat-samsung/devs.c文件:

/* I2C */

static struct resource s3c_i2c0_resource[] = {
        [0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K),
        [1] = DEFINE_RES_IRQ(IRQ_IIC),
};

struct platform_device s3c_device_i2c0 = {   // platform设备信息
        .name           = "s3c2410-i2c",
        .id             = 0,
        .num_resources  = ARRAY_SIZE(s3c_i2c0_resource),
        .resource       = s3c_i2c0_resource,
};

struct s3c2410_platform_i2c default_i2c_data __initdata = {  // 用于初始化s3c_device_i2c0.dev.platform_data 
        .flags          = 0,
        .slave_addr     = 0x10,                // 从设备地址
        .frequency      = 100*1000,            // 频率 100kb  
        .sda_delay      = 100,
};

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)  // 用于绑定s3c_device_i2c0.dev.platform_data和default_i2c_data的关系
{
        struct s3c2410_platform_i2c *npd;

        if (!pd) {
                pd = &default_i2c_data;
                pd->bus_num = 0;
        }

        npd = s3c_set_platdata(pd, sizeof(*npd), &s3c_device_i2c0);  // s3c_device_i2c0.dev.platform_data = &default_i2c_data

        if (!npd->cfg_gpio):
                npd->cfg_gpio = s3c_i2c0_cfg_gpio;
}

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

  • 第一个资源是IO内存资源,起始地址是I2C寄存器寄基地址,即0x54000000,大小为4Kb;
  • 第二个资源是中断资源,中断号位IRQ_IIC,即27;

我们已经定义了I2C控制器相关的platform_device设备s3c_device_i2c0,并进行了初始化,那platform设备啥时候注册的呢?

linux内核启动的时候会根据uboot中设置的机器id执行相应的初始化工作,比如.init_machine、.init_irq,我们首先定位到arch/arm/mach-s3c24xx/mach-smdk2440.c:

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .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设备的注册,该函数通过调用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,
};

platform设备s3c_device_i2c0是通过arch/arm/plat-samsung/include/plat/devs.h头文件引入的。

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

I2C控制器platform驱动定义位于drivers/i2c/busses/i2c-s3c2410.c文件中。

2.1 入口和出口函数

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

static const struct platform_device_id s3c24xx_driver_ids[] = {
        {
                .name           = "s3c2410-i2c",
                .driver_data    = 0,
        }, {
                .name           = "s3c2440-i2c",
                .driver_data    = QUIRK_S3C2440,
        }, {
                .name           = "s3c2440-hdmiphy-i2c",
                .driver_data    = QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO,
        }, { },
};


static const struct of_device_id s3c24xx_i2c_match[] = {   // 设备树匹配时使用
        { .compatible = "samsung,s3c2410-i2c", .data = (void *)0 },
        { .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 },
        { .compatible = "samsung,s3c2440-hdmiphy-i2c",
          .data = (void *)(QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO) },
        { .compatible = "samsung,exynos5-sata-phy-i2c",
          .data = (void *)(QUIRK_S3C2440 | QUIRK_POLL | QUIRK_NO_GPIO) },
        {},
};


static struct platform_driver s3c24xx_i2c_driver = {
        .probe          = s3c24xx_i2c_probe,
        .remove         = s3c24xx_i2c_remove,
        .id_table       = s3c24xx_driver_ids,
        .driver         = {
                .name   = "s3c-i2c",
                .pm     = S3C24XX_DEV_PM_OPS,
                .of_match_table = of_match_ptr(s3c24xx_i2c_match),
        },
};

static int __init i2c_adap_s3c_init(void)
{
        return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);

static void __exit i2c_adap_s3c_exit(void)
{
        platform_driver_unregister(&s3c24xx_i2c_driver);
}
module_exit(i2c_adap_s3c_exit);

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

2.2 I2C相关数据结构

2.2.1 s3c24xx_i2c

在介绍s3c24xx_i2c_probe之前,先来看一下s3c24xx_i2c结构体定义,其定义在drivers/i2c/busses/i2c-s3c2410.c文件:

struct s3c24xx_i2c {  // 用来表示SOC的一个I2C控制器
        wait_queue_head_t       wait;   // 等待队列
        kernel_ulong_t          quirks;  // I2C控制器的一些特殊行为

        struct i2c_msg          *msg;     // 存放要发送的I2C数据包
        unsigned int            msg_num;  // 数据包个数  实际只有1个
        unsigned int            msg_idx;  // 当前发送的数据包索引 
        unsigned int            msg_ptr;  // 当前传输的字节在数据包的索引

        unsigned int            tx_setup;  // 用来延时,等待SCL被释放
        unsigned int            irq;       // 中断号

        enum s3c24xx_i2c_state  state;     // I2C传输状态  STATE_START、STATE_IDEL、STATE_STOP、STATE_WRITE、STATE_READ
        unsigned long           clkrate;

        void __iomem            *regs;
        struct clk              *clk;     // 时钟 
        struct device           *dev;     // 存储platform设备的dev成员    
        struct i2c_adapter      adap;     // 描述该I2C控制器的适配器

        struct s3c2410_platform_i2c     *pdata;  // 私有数据 默认初始化为&default_i2c_data
        int                     gpios[2];
        struct pinctrl          *pctrl;
#if defined(CONFIG_ARM_S3C24XX_CPUFREQ)
        struct notifier_block   freq_transition;
#endif
        struct regmap           *sysreg;
        unsigned int            sys_i2c_cfg;
};
2.2.2 s3c2410_platform_i2c

s3c2410_platform_i2c定义在include/linux/platform_data/i2c-s3c2410.h文件:

/**
 *      struct s3c2410_platform_i2c - Platform data for s3c I2C.
 *      @bus_num: The bus number to use (if possible).
 *      @flags: Any flags for the I2C bus (E.g. S3C_IICFLK_FILTER).
 *      @slave_addr: The I2C address for the slave device (if enabled).
 *      @frequency: The desired frequency in Hz of the bus.  This is
 *                  guaranteed to not be exceeded.  If the caller does
 *                  not care, use zero and the driver will select a
 *                  useful default.
 *      @sda_delay: The delay (in ns) applied to SDA edges.
 *      @cfg_gpio: A callback to configure the pins for I2C operation.
 */
struct s3c2410_platform_i2c {
        int             bus_num;    // I2C控制器编号
        unsigned int    flags;
        unsigned int    slave_addr;  // 从设备地址 
        unsigned long   frequency;   // 时钟频率 
        unsigned int    sda_delay;

        void    (*cfg_gpio)(struct platform_device *dev);  // 初始化gpio口 功能复用I2C
};

2.3 s3c24xx_i2c_probe

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
        struct s3c24xx_i2c *i2c;
        struct s3c2410_platform_i2c *pdata = NULL;
        struct resource *res;
        int ret;

        if (!pdev->dev.of_node) {  
                pdata = dev_get_platdata(&pdev->dev);     // &pdev->dev.platform_data即default_i2c_data
                if (!pdata) {
                        dev_err(&pdev->dev, "no platform datan");
                        return -EINVAL;
                }
        }

        i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);  // 为i2c动态申请内存,&pdev->dev卸载时释放
        if (!i2c)
                return -ENOMEM;

        i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);   // 为i2c->pdata动态申请内存,&pdev->dev卸载时释放
        if (!i2c->pdata)
                return -ENOMEM;

        i2c->quirks = s3c24xx_get_device_quirks(pdev);                  // 获得I2C控制器的一些特殊行为
        i2c->sysreg = ERR_PTR(-ENOENT);
        if (pdata)
                memcpy(i2c->pdata, pdata, sizeof(*pdata));               // 走这里,初始化i2c->pdata
        else
                s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);            // 解析设备树

        strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));  // 设置I2C适配器名称
        i2c->adap.owner = THIS_MODULE;                                   
        i2c->adap.algo = &s3c24xx_i2c_algorithm;                         // 设置I2C适配器通信算法   
        i2c->adap.retries = 2;                                           // 重试次数 
        i2c->adap.class = I2C_CLASS_DEPRECATED;                          // 标志位 表示不支持自动检测I2C从设备
        i2c->tx_setup = 50;

        init_waitqueue_head(&i2c->wait);        // 初始化等待队列

        /* find the clock and enable it */
        i2c->dev = &pdev->dev;
        i2c->clk = devm_clk_get(&pdev->dev, "i2c");    // 获取i2c时钟   
        if (IS_ERR(i2c->clk)) {
                dev_err(&pdev->dev, "cannot get clockn");
                return -ENOENT;
        }

        dev_dbg(&pdev->dev, "clock source %pn", i2c->clk);

        /* map the registers */
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  // 获取第一个IO内存资源
        i2c->regs = devm_ioremap_resource(&pdev->dev, res);    // 物理地址->虚拟地址映射 映射是I2C控制器寄存器地址 

        if (IS_ERR(i2c->regs))
                return PTR_ERR(i2c->regs);

        dev_dbg(&pdev->dev, "registers %p (%p)n",
                i2c->regs, res);
        /* setup info block for the i2c core */
        i2c->adap.algo_data = i2c;                   // 私有数据指向i2c
        i2c->adap.dev.parent = &pdev->dev;           // 设置I2C适配器dev.parent为&pdev->dev
        i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);

        /* inititalise the i2c gpio lines */
        if (i2c->pdata->cfg_gpio)   // 配置gpio,初始化为I2C功能
                i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));  //  s3c_i2c0_cfg_gpio 定义在arch/arm/mach-s3c24xx/setup-i2c.c
        else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c))
                return -EINVAL;

        /* initialise the i2c controller */
        ret = clk_prepare_enable(i2c->clk);   // 使能时钟  CLKCON寄存器的bit[6]对应着I2C模块
        if (ret) {
                dev_err(&pdev->dev, "I2C clock enable failedn");
                return ret;
        }

        ret = s3c24xx_i2c_init(i2c);      // 初始化I2C相关的寄存器
        clk_disable(i2c->clk);            // 禁止时钟 
        if (ret != 0) {
                dev_err(&pdev->dev, "I2C controller init failedn");
                clk_unprepare(i2c->clk);
                return ret;
        }

        /*
         * find the IRQ for this unit (note, this relies on the init call to
         * ensure no current IRQs pending
         */
        if (!(i2c->quirks & QUIRK_POLL)) {    // I2C采用中断进行数据传输(非轮询)
                i2c->irq = ret = platform_get_irq(pdev, 0);  // 获取中断资源
                if (ret <= 0) {
                        dev_err(&pdev->dev, "cannot find IRQn");
                        clk_unprepare(i2c->clk);
                        return ret;
                }

                ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq,  // 注册中断服务
                                       0, dev_name(&pdev->dev), i2c);
                if (ret != 0) {
                        dev_err(&pdev->dev, "cannot claim IRQ %dn", i2c->irq);
                        clk_unprepare(i2c->clk);
                        return ret;
                }
        }

        ret = s3c24xx_i2c_register_cpufreq(i2c);  
        if (ret < 0) {
                dev_err(&pdev->dev, "failed to register cpufreq notifiern");
                clk_unprepare(i2c->clk);
                return ret;
        }

        /*
         * Note, previous versions of the driver used i2c_add_adapter()
         * to add the bus at any number. We now pass the bus number via
         * the platform data, so if unset it will now default to always
         * being bus 0.
         */
        i2c->adap.nr = i2c->pdata->bus_num;   // 设置I2C适配器编号
        i2c->adap.dev.of_node = pdev->dev.of_node;

        platform_set_drvdata(pdev, i2c);  // 设置pdev.dev.plat_data = i2c

        pm_runtime_enable(&pdev->dev);

        ret = i2c_add_numbered_adapter(&i2c->adap);  // 注册I2C适配器
        if (ret < 0) {
                pm_runtime_disable(&pdev->dev);
                s3c24xx_i2c_deregister_cpufreq(i2c);
                clk_unprepare(i2c->clk);
                return ret;
        }

        dev_info(&pdev->dev, "%s: S3C I2C adaptern", dev_name(&i2c->adap.dev));
        return 0;
}

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

  • 设置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适配器;

三、s3c24xx_i2c_probe调用栈

由于s3c24xx_i2c_probe函数中调用较多其它函数,这里我们单独介绍各个函数。

3.1 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 通信时序,如开始信号,停止信号、应答信号等。

3.2 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%02xn", 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 requiredn");
                return -EINVAL;
        }

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

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

        return 0;
}

3.3 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 lossn");
        }

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

                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;
}
内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/zyly/p/17133091.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!

相关课程