s5pv210 i2c总线驱动s3c2410.c 完全解析1

前言:

    对于学习嵌入式来说,i2c 协议肯定是必须重点掌握的,平时大家在工作学习中接触的tp,camera,sensor等很多外设都是iic接口的,在调试这些设备之时,我们不用去关心i2c总线驱动,因为芯片厂商已经帮你解决了,但是当你在芯片公司工作,或者为了更好的理解整个i2c协议,那么我们就有必要去自己写一个IIc总线驱动程序, 那么在自己完全写一个或者根据芯片手册完全移植一个总线驱动时,完全理解内核自带的 I2c-s3c2410.c 是最重要的,本文就是通过对 I2c-s3c2410.c的全面解析,结合友善的mach-mini210.c来彻底分析i2c 总线驱动程序,并利用分析的结果自己写一个属于自己的总线驱动。

一.准备:

1.驱动位置,驱动的添加删除

    i2c总线驱动位于目录drivers\i2c\busses\ 目录下,通过查看该目录下的Makefile:

obj-$(CONFIG_I2C_S3C2410)       += i2c-s3c2410.o
然后make menuconfig 搜索CONFIG_I2C_S3C2410这个宏,就可以看到以下信息:

Symbol: I2C_S3C2410 [=y]                                                                                                    |
  Type  : tristate                                                                                                            |
  Prompt: S3C2410 I2C Driver                                                                                                  |
    Defined at drivers/i2c/busses/Kconfig:576                                                                                 |
    Depends on: I2C [=y] && HAVE_S3C2410_I2C [=y]                                                                             |
    Location:                                                                                                                 |
      -> Device Drivers                                                                                                       |
        -> I2C support (I2C [=y])                                                                                             |
          -> I2C Hardware Bus support 
将symbol改为M 就可以该模块方便进行加载调试。

2.打印调试信息

    在文件开头添加 #define DEBUG   1 这个宏,然后在开发板上执行cat /proc/kmsg 就能看到dev_dbg打印出的调试信息了,

可以很方便调试。

#include <linux/kernel.h>
#include <linux/module.h>
#define DEBUG    1
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/platform_device.h>

二 代码分析:

1. 入口函数:

    linux下遵循设备,总线,驱动的这一套规则,对于driver对应I2c-s3c2410.c

static struct platform_device_id s3c24xx_driver_ids[] = {
	{
		.name		= "s3c2410-i2c",  // name 来匹配 dev
		.driver_data	= TYPE_S3C2410,
	}, {
		.name		= "s3c2440-i2c",
		.driver_data	= TYPE_S3C2440,
	}, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);

static struct platform_driver s3c24xx_i2c_driver = {
	.probe		= s3c24xx_i2c_probe, 
	.remove		= s3c24xx_i2c_remove,
	.id_table	= s3c24xx_driver_ids,
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "s3c-i2c",
		.pm	= S3C24XX_DEV_PM_OPS,
	},
};

static int __init i2c_adap_s3c_init(void)
{
	return platform_driver_register(&s3c24xx_i2c_driver); // 注册driver
} 
这里通过platform_driver_register 注册platform_driver 结构体 ,再来看看driver对应的设备 通过搜"s3c2410-i2c",

对应的设备在arch\arm\plat-samsung\Dev-i2c0.c这个文件中:

struct platform_device s3c_device_i2c0 = {
	.name		  = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
	.id		  = 0,
#else
	.id		  = -1,
#endif
	.num_resources	  = ARRAY_SIZE(s3c_i2c_resource),
	.resource	  = s3c_i2c_resource,
};
这个文件中包括了i2c 总线的设备资源包括中断号,寄存器地址,私有数据等。 而注册platform_device 是在arch\arm\mach-s5pv210\Mach-mini210.c文件中注册的:
static struct platform_device *mini210_devices[] __initdata = {
       .....
    &s3c_device_hsmmc3,
    &s3c_device_i2c0,  // i2c 控制器 0
    &s3c_device_i2c1,

    platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices));// 注册设备

有了这些就能调用platform_driver的probe了。



2.probe函数

2.1 i2c_adapter(分配 设置  注册)

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
	struct s3c24xx_i2c *i2c;
	struct s3c2410_platform_i2c *pdata;
	struct resource *res;
	int ret;
	
	pdata = pdev->dev.platform_data; // 获得私有数据 
	if (!pdata) {
		dev_err(&pdev->dev, "no platform data\n");
		return -EINVAL;
	}

	i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL); // 分配s3c24xx_i2c结构体
	if (!i2c) {
		dev_err(&pdev->dev, "no memory for state\n");
		return -ENOMEM;
	}

	/*设置s3c24xx_i2c 结构体 */
	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_HWMON | I2C_CLASS_SPD; // 类
	i2c->tx_setup     = 50;

	/* 辅助变量的初始化 */
	spin_lock_init(&i2c->lock);
	init_waitqueue_head(&i2c->wait); 

一开始呢,probe函数会做一些软件方面的初始化,这里s3c24xx_i2c 这个结构体就是代表我们的i2cbus总线了, 里面有一个结构体i2c_adapter那是相当重要了,称之为i2c适配器,一般就是指i2c控制器,可以说是驱动的核心,这里会对它进行些设置,最重要的是设置i2c_algorithm成员, 这里我们可以把它理解成算法函数。
i2c->adap.algo    = &s3c24xx_i2c_algorithm; // 算法操作

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
    .master_xfer        = s3c24xx_i2c_xfer, // i2c 传输
    .functionality        = s3c24xx_i2c_func,
};
s3c24xx_i2c_xfer这个函数就是i2c_adapter这个i2c控制器数据传输的核心函数,为什么这么说呢?

在我们的linux驱动中,i2c设备比如触摸屏,at24c08数据传输时都会通过i2c_smbus_write_byte_data 或者i2c_transfer等来实现,而master_xfer即s3c24xx_i2c_xfer 的实现就为这些数据传输函数提供了基础: 这里我们可以粗略的看下它们之间的调用关系。

i2c_smbus_write_byte_data // i2c设备读写数据常用函数
	i2c_smbus_xfer    // 下面几个函数都是在i2c-core.c中调用的
		i2c_smbus_xfer_emulated
			i2c_transfer
				ret = adap->algo->master_xfer(adap, msgs, num);//master_xfer是在 i2c_bus总线中设置

所以说,不管你的i2c设备调用什么函数进行数据传输,最终都会调用到i2c_adapter适配器设置的master_xfer即s3c24xx_i2c_xfer函数,您说这个函数重不重要呢? 下面在用张图来形容它们之间的关系:第一此用这个软件,将就点吧。



最后将这个i2c_adapter 注册到系统中去。

	..........
        i2c->adap.nr = pdata->bus_num; // 0 

	ret = i2c_add_numbered_adapter(&i2c->adap); // 注册 i2c_adapter
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to add bus to i2c core\n");
		goto err_cpufreq;
	}

	platform_set_drvdata(pdev, i2c); // 设置 私有数据
 
这时呢,如果你去开发板上ls /dev/i* 就能看到i2c的设备了。


2.2 probe 函数中i2c_bus 硬件的初始化

    在对i2c_adapter 进行相关设置操作之后,还需要对i2c做些硬件的初始化。

	/* find the clock and enable it */
	i2c->dev = &pdev->dev;  // 绑定  dev
	i2c->clk = clk_get(&pdev->dev, "i2c");  // 获得 i2c控制器的时钟资源

	if (IS_ERR(i2c->clk)) {
		dev_err(&pdev->dev, "cannot get clock\n");
		ret = -ENOENT;
		goto err_noclk;
	}

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

	clk_enable(i2c->clk);  // 使能 i2c控制器的时钟

	/* map the registers */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 得到 i2c控制器的寄存器地址
	if (res == NULL) {
		dev_err(&pdev->dev, "cannot find IO resource\n");
		ret = -ENOENT;
		goto err_clk;
	}
	/* request mem region */   //  申请I/O内存的函数是request_mem_region
	/* request_mem_region函数并没有做实际性的映射工作,
	*只是告诉内核要使用一块内存地址,声明占有,也方便内核管理这些资源。*/
	i2c->ioarea = request_mem_region(res->start, resource_size(res),
					 pdev->name);

	if (i2c->ioarea == NULL) {
		dev_err(&pdev->dev, "cannot request IO\n");
		ret = -ENXIO;
		goto err_clk;
	}

	i2c->regs = ioremap(res->start, resource_size(res)); // 这里才真正的映射寄存器  

	if (i2c->regs == NULL) {
		dev_err(&pdev->dev, "cannot map IO\n");
		ret = -ENXIO;
		goto err_ioarea;
	}

	dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
		i2c->regs, i2c->ioarea, res);

	/* setup info block for the i2c core */
	i2c->adap.algo_data = i2c;  // 绑定
	i2c->adap.dev.parent = &pdev->dev;

	    /* initialise the i2c controller */

    ret = s3c24xx_i2c_init(i2c);  // i2c 控制器的初始化
    if (ret != 0)
        goto err_iomap;

    /* find the IRQ for this unit (note, this relies on the init call to
     * ensure no current IRQs pending
     */

    i2c->irq = ret = platform_get_irq(pdev, 0);  // 获得 i2c的中断资源
    if (ret <= 0) {
        dev_err(&pdev->dev, "cannot find IRQ\n");
        goto err_iomap;
    }

    ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED, // 注册中断
              dev_name(&pdev->dev), i2c);

    if (ret != 0) {
        dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
        goto err_iomap;
    }

这些硬件的初始化包括获得和使能时钟源,获得i2c寄存器地址,并映射寄存器, i2c控制器的初始化 以及 注册中断。

我们再来看看i2c控制器的初始化:

static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
	unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;
	struct s3c2410_platform_i2c *pdata;
	unsigned int freq;

	/* get the plafrom data */
	pdata = i2c->dev->platform_data; // 获得私有数据

	/* inititalise the gpio */
	if (pdata->cfg_gpio)
		pdata->cfg_gpio(to_platform_device(i2c->dev)); // 初始化gpio

	/* write slave address */
	writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD); //0x10

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

	writel(iicon, i2c->regs + S3C2410_IICCON); // 中断使能 和 ack使能

	/* we need to work out the divisors for the clock... */

	if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {  // 设置i2c时钟传输频率
		writel(0, i2c->regs + S3C2410_IICCON);
		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%02lx\n", iicon);

	dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);
	 writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);  // 设置 SDA output delay  15clocks

	return 0;
}
初始化中配置了gpio 用于sda scl, 使能了ack 和 中断 , 设置了i2c总线传输频率以及sda 数据线延时(可省略),这些都是通过配置i2c控制器的寄存器来实现的,具体的参考s5pv210的芯片手册p883 。 前面提到了配置gpio的配置,即pdata->cfg_gpio函数来设置,cfg_gpio这个函数是在arch\arm\plat-samsung\Dev-i2c0.c中设置的,还记得我们前面分析的platform_device吗,这里面出了设置了寄存器资源和中断资源外还设置了私有数据,这也就是为什么s3c24xx_i2c_init一开始要得到私有数据的原因了。
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
	struct s3c2410_platform_i2c *npd;

	if (!pd)
		pd = &default_i2c_data0;

	npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);
	if (!npd)
		printk(KERN_ERR "%s: no memory for platform data\n", __func__);
	else if (!npd->cfg_gpio)
		npd->cfg_gpio = s3c_i2c0_cfg_gpio; // 这里设置了 gpio 的配置函数

	s3c_device_i2c0.dev.platform_data = npd;
}


void s3c_i2c0_cfg_gpio(struct platform_device *dev)
{
#if defined(CONFIG_MACH_MINI210)
    s3c_gpio_cfgall_range(S5PV210_GPD1(0), 2,
            S3C_GPIO_SFN(2), S3C_GPIO_PULL_NONE); // 配置gpd1(0) 和 gpd1(1)为scl 和 sda  并且不需要上拉使能
#else
    s3c_gpio_cfgall_range(S5PV210_GPD1(0), 2,
            S3C_GPIO_SFN(2), S3C_GPIO_PULL_UP);
#endif
} 

s3c_gpio_cfgall_range(S5PV210_GPD1(0), 2,S3C_GPIO_SFN(2), S3C_GPIO_PULL_NONE);这个函数挺有趣,有兴趣的朋友可以百度下看看s3c_gpio_cfgall_range这个函数和S3C_GPIO_SFN(2)这个宏的意思。

再回到s3c24xx_i2c_init看看s3c24xx_i2c_clockrate是如何来设置时钟频率的:

/* 根据已有的pclk频率 计算 iic传输频率 并设置相应寄存器 */
static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
{
    struct s3c2410_platform_i2c *pdata = i2c->dev->platform_data;
    unsigned long clkin = clk_get_rate(i2c->clk);
    unsigned int divs, div1;
    unsigned long target_frequency;
    u32 iiccon;
    int freq;

    i2c->clkrate = clkin;
    clkin /= 1000;        /* clkin now in KHz */  // 得到pclk频率
    printk("neo frequency clkin = %lu\n",clkin );
    dev_dbg(i2c->dev, "pdata desired frequency %lu\n", pdata->frequency); 

    target_frequency = pdata->frequency ? pdata->frequency : 100000; 

    target_frequency /= 1000; /* Target frequency now in KHz  400 * 1000 /1000 = 400*/   

    freq = s3c24xx_i2c_calcdivisor(clkin, target_frequency, &div1, &divs);

    if (freq > target_frequency) {
        dev_err(i2c->dev,
            "Unable to achieve desired frequency %luKHz."    \
            " Lowest achievable %dKHz\n", target_frequency, freq);
        return -EINVAL;
    }

    *got = freq;
// 66700 / 16 /11 =378hz
    iiccon = readl(i2c->regs + S3C2410_IICCON);
    iiccon &= ~(S3C2410_IICCON_SCALEMASK | S3C2410_IICCON_TXDIV_512); // 16 分频
    iiccon |= (divs-1); // Tx clock = I2CCLK/(I2CCON[3:0]+1).
    printk("neo frequency div1 = %d , divs = %d\n",div1,divs );

    if (div1 == 512)
        iiccon |= S3C2410_IICCON_TXDIV_512;

    writel(iiccon, i2c->regs + S3C2410_IICCON);

    return 0;
}

这个函数还真有点啰嗦,好在我们有万能的printK,你不是想通过PCLK频率得到计算 IIC总线频率吗,那我就把你的PCLK和IIC总线频率都打印出来,并且打印出div1 ,divs分频数值,通过div1 和div s来设置iiccon 寄存器 。

经过打印,猜出 具体计算公式如下:

PCLK=66700 MHZ   div1 = 16  divs =11   iic总线频率 = 378MHz

iic总线频率 = PCLK / div1 /divs 。


3. 总结:

到此呢probe函数就算分析完了,现在我们来稍微总结下probe做的具体操作:

软件方面:

1. 分配设置注册了s3c24xx_i2c 和 i2c_adapter结构体,其中i2c_adapter这个是重点。

2..初始化了一些辅助变量,这个在后面代码中会用到。

硬件方面:

1. 获得和使能时钟

2. 获得和映射相关寄存器

3. 初始化iic总线控制器

4. 注册中断







  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值