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驱动架构

欢迎大家关注我的微信公众号!!
已标记关键词 清除标记
课程简介: 历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页