Linux驱动之I2C控制器驱动

简介: Linux驱动之I2C控制器驱动

平台: 三星2440
内核版本:4.20

分析将会按照驱动中函数的执行顺序。

一、装载和卸载函数

static const struct platform_device_id s3c24xx_driver_ids[] = {
    {
        .name        = "s3c2410-i2c",
        .driver_data    = 0,
    }, {
        .name        = "s3c2440-i2c",
        .driver_data    = QUIRK_S3C2440,
    },{ },
};
//dt匹配表
static const struct of_device_id s3c24xx_i2c_match[] = {
    { .compatible = "samsung,s3c2410-i2c", .data = (void *)0 },
    { .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 },
    {},
};
MODULE_DEVICE_TABLE(of, s3c24xx_i2c_match);

//I2C控制器属于plaform总线(控制器都是)
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)
{
    //注册platform_driver
    return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);

static void __exit i2c_adap_s3c_exit(void)
{
    //注销platform_driver
    platform_driver_unregister(&s3c24xx_i2c_driver);
}
module_exit(i2c_adap_s3c_exit);

二、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) {
             //获取platform_data, 这些数据一般是跟板级有关的
        pdata = dev_get_platdata(&pdev->dev);
           ......
    }
    //为自定义的i2c结构体申请内存空间,一般驱动都会封装一个结构体,将需要的数据
    //放在一起
    i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);

    //为这些platform_data申请内存空间
    i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);

    //初始化s3c24xx_i2c,自定义的结构体
    i2c->quirks = s3c24xx_get_device_quirks(pdev);
    i2c->sysreg = ERR_PTR(-ENOENT);
    if (pdata)
        memcpy(i2c->pdata, pdata, sizeof(*pdata));
    else
        s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);
    //初始化adapter
    strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
    i2c->adap.owner = THIS_MODULE;
    i2c->adap.algo = &s3c24xx_i2c_algorithm; //通信方法
    i2c->adap.retries = 2; //重复次数
    i2c->adap.class = I2C_CLASS_DEPRECATED;
    i2c->tx_setup = 50;
 
    //初始化等待队列头
    init_waitqueue_head(&i2c->wait);

    //获取时钟并使能
    i2c->dev = &pdev->dev;
    i2c->clk = devm_clk_get(&pdev->dev, "i2c"); 

    /* map the registers */
    //获取IO资源,就是寄存器地址
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
   //寄存器地址映射为内核空间的虚拟地址
   /*
   devm_ioremap_resource包含两个动作:
       申请内存资源:devm_request_mem_region
       映射为虚拟地址:devm_ioremap
   */
    i2c->regs = devm_ioremap_resource(&pdev->dev, res);

    /* setup info block for the i2c core */
    i2c->adap.algo_data = i2c; //algorithm数据
    i2c->adap.dev.parent = &pdev->dev;
    i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);

     //初始化i2c引脚
    if (i2c->pdata->cfg_gpio)
        i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));
    else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c))
        return -EINVAL;

    //初始化I2C控制器
    ret = clk_prepare_enable(i2c->clk);
    ret = s3c24xx_i2c_init(i2c);
    clk_disable(i2c->clk);
 
    //申请中断
    if (!(i2c->quirks & QUIRK_POLL)) {
        i2c->irq = ret = platform_get_irq(pdev, 0);

        ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq,
                       0, dev_name(&pdev->dev), i2c);
    }
    //设置CPU频率
    ret = s3c24xx_i2c_register_cpufreq(i2c);

   // 设置总线数,之前的版本不需要设置,通过i2c_add_adapter()即可
   // 新版本要设置,否则默认为0
    i2c->adap.nr = i2c->pdata->bus_num;
    i2c->adap.dev.of_node = pdev->dev.of_node;

    platform_set_drvdata(pdev, i2c);

    pm_runtime_enable(&pdev->dev);
    // 注册adapter ,使用该函数要设置i2c->adap.nr
    // 如果使用i2c_add_adapter()就不需要设置
    ret = i2c_add_numbered_adapter(&i2c->adap);
    return 0;
}

上面将一些错误判断及Log信息去掉了,只留下关键的部分。

三、I2C引脚初始化

static int s3c24xx_i2c_parse_dt_gpio(struct s3c24xx_i2c *i2c)
{
    int idx, gpio, ret;

    if (i2c->quirks & QUIRK_NO_GPIO)
        return 0;

    for (idx = 0; idx < 2; idx++) {
             //从设备树中获取引脚
        gpio = of_get_gpio(i2c->dev->of_node, idx);

        i2c->gpios[idx] = gpio;
            //申请引脚功能
        ret = gpio_request(gpio, "i2c-bus");
    }
    return 0;

free_gpio:
    while (--idx >= 0)
        gpio_free(i2c->gpios[idx]);
    return -EINVAL;
}

目前大部分的芯片这部分都是直接在dts中配置就行。pinctrl驱动会进行初始化,所以很多控制器驱动中不会看到对引脚的初始化。

四、I2C控制器初始化

static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
    struct s3c2410_platform_i2c *pdata;
    unsigned int freq;
 
    //获取platform_data
    pdata = i2c->pdata;

    //设置从机地址
    writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
    //清空控制器寄存器和状态寄存器
    writel(0, i2c->regs + S3C2410_IICCON);
    writel(0, i2c->regs + S3C2410_IICSTAT);

    /* we need to work out the divisors for the clock... */
    //设置I2C控制器时钟频率
    if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
        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;
}

五、I2C通信方式 --- i2c_algorithm

static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
            struct i2c_msg *msgs, int num)
{
    struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
    int retry;
    int ret;
    //使能I2C时钟
    ret = clk_enable(i2c->clk);
    if (ret)
        return ret;
    //传输失败,可重新传输
    for (retry = 0; retry < adap->retries; retry++) {
            //传输数据
        ret = s3c24xx_i2c_doxfer(i2c, msgs, num);
            //传输成功后关闭时钟
        if (ret != -EAGAIN) {
            clk_disable(i2c->clk);
            return ret;
        }

        dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);

        udelay(100);
    }

    clk_disable(i2c->clk);
    return -EREMOTEIO;
}

static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)
{
    //具备I2C, SMBUS, NOSTART等功能
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART |
        I2C_FUNC_PROTOCOL_MANGLING;
}

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
    .master_xfer        = s3c24xx_i2c_xfer, //传输
    .functionality        = s3c24xx_i2c_func, //具备的功能
};

可以和下面这篇一起配合看!
Linux驱动之I2C驱动架构

相关文章
|
1月前
|
Linux API 调度
Linux系统驱动跟裸机驱动的区别
Linux系统驱动跟裸机驱动的区别
29 0
|
1月前
|
Linux C语言 SoC
嵌入式linux总线设备驱动模型分析
嵌入式linux总线设备驱动模型分析
32 1
|
1月前
|
存储 缓存 Linux
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
35 0
|
1月前
|
分布式计算 关系型数据库 MySQL
Sqoop【部署 01】CentOS Linux release 7.5 安装配置 sqoop-1.4.7 解决警告并验证(附Sqoop1+Sqoop2最新版安装包+MySQL驱动包资源)
【2月更文挑战第8天】Sqoop CentOS Linux release 7.5 安装配置 sqoop-1.4.7 解决警告并验证(附Sqoop1+Sqoop2最新版安装包+MySQL驱动包资源)
99 1
|
10天前
|
Linux Go
Linux命令Top 100驱动人生! 面试必备
探索Linux命令不再迷茫!本文分10部分详解20个基础命令,带你由浅入深掌握文件、目录管理和文本处理。 [1]: <https://cloud.tencent.com/developer/article/2396114> [2]: <https://pan.quark.cn/s/865a0bbd5720> [3]: <https://yv4kfv1n3j.feishu.cn/docx/MRyxdaqz8ow5RjxyL1ucrvOYnnH>
64 0
|
23天前
|
Linux
Linux驱动运行灯 Heartbeat
Linux驱动运行灯 Heartbeat
10 0
|
1月前
|
Linux
Linux内核中USB设备驱动实现
Linux内核中USB设备驱动实现
25 0
|
2月前
|
Ubuntu Linux 芯片
Linux 驱动开发基础知识——设备树的语法驱动开发基础知识(九)
Linux 驱动开发基础知识——设备树的语法驱动开发基础知识(九)
74 1
Linux 驱动开发基础知识——设备树的语法驱动开发基础知识(九)
|
2月前
|
Linux
Linux 驱动开发基础知识——总线设备驱动模型(八)
Linux 驱动开发基础知识——总线设备驱动模型(八)
40 0
Linux 驱动开发基础知识——总线设备驱动模型(八)