Linux驱动之I2C控制器驱动

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u012041204/article/details/102468320

平台: 三星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驱动架构

欢迎大家关注我的微信公众号!!

猜你喜欢

转载自blog.csdn.net/u012041204/article/details/102468320