您的位置:首页 > 运维架构 > Linux

Linux I2C驱动源码分析(二)

2011-06-09 10:48 471 查看
下面开始分析
linux/drivers/i2c/busses/i2c-s3c2410.c
,在设备与驱动匹配成功后,会执行
s3c24xx_i2c_probe()
函数,其源码如下:

/* s3c24xx_i2c_probe called by the bus driver when a suitable device is found*/

static int s3c24xx_i2c_probe(struct platform_device *pdev)

{

struct s3c24xx_i2c *i2c;

struct s3c2410_platform_i2c *pdata;

struct resource *res;

int ret;

/*
这里
pdev->dev.platform_data


s3c_i2c0_set_platdata()
函数中设置,指向了系统初始化时的设置过的
s3c2410_platform_i2c
结构体
*/

pdata = pdev->dev.platform_data;

if (!pdata) {

dev_err(&pdev->dev, "no platform data/n");

return -EINVAL;

}

/*
申请一段
sizeof(struct s3c24xx_i2c)
的内存,并清
0 */

i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);

if (!i2c) {

dev_err(&pdev->dev, "no memory for state/n");

return -ENOMEM;

}

strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));

i2c->adap.owner
= THIS_MODULE;

i2c->adap.algo
= &s3c24xx_i2c_algorithm;
/*
设置
I2C
总线的通信函数
*/

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);
/*
初始化等待队列头
*/

/* find the clock and enable it */

/*
获得
I2C
设备的时钟,并使能
I2C
控制器时钟,后面会具体分析
*/

i2c->dev = &pdev->dev;

i2c->clk = clk_get(&pdev->dev, "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);

/* map the registers */

/*
获取系统的物理地址,中断等资源信息,并进行物理地址到虚拟地址的映射
*/

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

if (res == NULL) {

dev_err(&pdev->dev, "cannot find IO resource/n");

ret = -ENOENT;

goto err_clk;

}

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);
/*
控制器的初始化
,
后面具体会分析
*/

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);

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;

}

ret = s3c24xx_i2c_register_cpufreq(i2c);

if (ret < 0) {

dev_err(&pdev->dev, "failed to register cpufreq notifier/n");

goto err_irq;

}

/* 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
总线
(
总线号
)
注册
adapter */

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

ret = i2c_add_numbered_adapter(&i2c->adap);

if (ret < 0) {

dev_err(&pdev->dev, "failed to add bus to i2c core/n");

goto err_cpufreq;

}

platform_set_drvdata(pdev, i2c);

dev_info(&pdev->dev, "%s: S3C I2C adapter/n", dev_name(&i2c->adap.dev));

return 0;

err_cpufreq:

s3c24xx_i2c_deregister_cpufreq(i2c);

err_irq:

free_irq(i2c->irq, i2c);

err_iomap:

iounmap(i2c->regs);

err_ioarea:

release_resource(i2c->ioarea);

kfree(i2c->ioarea);

err_clk:

clk_disable(i2c->clk);

clk_put(i2c->clk);

err_noclk:

kfree(i2c);

return ret;

}

系统在初始化时会将系统硬件中的时钟注册进系统,用双向循环连接起来,在
linux/arch/arm/plat-s3c24xx/s3c244x.c
中,
s3c244x_init_clocks ()
函数完成这个操作这个操作。

void __init s3c244x_init_clocks(int xtal)

{

/* initialise the clocks here, to allow other things like the

* console to use them, and to add new ones after the initialisation

*/

s3c24xx_register_baseclocks(xtal);

s3c244x_setup_clocks();

s3c2410_baseclk_add();

}

其中
s3c2410_baseclk_add()
的源码如下:

/* s3c2410_baseclk_add()

* Add all the clocks used by the s3c2410 or compatible CPUs

* such as the S3C2440 and S3C2442.

* We cannot use a system device as we are needed before any

* of the init-calls that initialise the devices are actually

* done.*/

int __init s3c2410_baseclk_add(void)

{

unsigned long clkslow = __raw_readl(S3C2410_CLKSLOW);

unsigned long clkcon
= __raw_readl(S3C2410_CLKCON);

struct clk *clkp;

struct clk *xtal;

int ret;

int ptr;

clk_upll.enable = s3c2410_upll_enable;

if (s3c24xx_register_clock(&clk_usb_bus) < 0)

printk(KERN_ERR "failed to register usb bus clock/n");

/* register clocks from clock array */

clkp = init_clocks;

for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {

/* ensure that we note the clock state */

clkp->usage = clkcon & clkp->ctrlbit ? 1 : 0;

ret = s3c24xx_register_clock(clkp);

if (ret < 0) {

printk(KERN_ERR "Failed to register clock %s (%d)/n",

clkp->name, ret);

}

}

/* We must be careful disabling the clocks we are not intending to

* be using at boot time, as subsystems such as the LCD which do

* their own DMA requests to the bus can cause the system to lockup

* if they where in the middle of requesting bus access.

*

* Disabling the LCD clock if the LCD is active is very dangerous,

* and therefore the bootloader should be careful to not enable

* the LCD clock if it is not needed.

*/

/* install (and disable) the clocks we do not need immediately */

clkp = init_clocks_disable;

for (ptr = 0; ptr < ARRAY_SIZE(init_clocks_disable); ptr++, clkp++) {

ret = s3c24xx_register_clock(clkp);

if (ret < 0) {

printk(KERN_ERR "Failed to register clock %s (%d)/n",

clkp->name, ret);

}

s3c2410_clkcon_enable(clkp, 0);

}

/* show the clock-slow value */

xtal = clk_get(NULL, "xtal");

printk("CLOCK: Slow mode (%ld.%ld MHz), %s, MPLL %s, UPLL %s/n",

print_mhz(clk_get_rate(xtal) /

( 2 * S3C2410_CLKSLOW_GET_SLOWVAL(clkslow))),

(clkslow & S3C2410_CLKSLOW_SLOW) ? "slow" : "fast",

(clkslow & S3C2410_CLKSLOW_MPLL_OFF) ? "off" : "on",

(clkslow & S3C2410_CLKSLOW_UCLK_OFF) ? "off" : "on");

s3c_pwmclk_init();

return 0;

}

根据注释中给的提示,时钟被分成了两部分,
init_clocks

init_clocks_disable
,其中
init_clocks
中的时钟是系统启动时会开启的,而
init_clocks_disable
中的时钟则在系统启动时会关闭。其中函数
s3c24xx_register_clock()
就是实现讲系统中的时钟插入到双向循环链表中。比如我们这里
I2C
的时钟的是定义在
init_clocks_disable
数组中,定义如下:

{

.name
= "i2c
",

.id
= -1,

.parent
= &clk_p,

.enable
= s3c2410_clkcon_enable,

.ctrlbit
= S3C2410_CLKCON_IIC,

}

结构中保存了
I2C
控制器中时钟时能位的位置偏移,时钟名字已经时钟时能的函数等信息。

s3c24xx_i2c_probe
函数中有一段程序就是用来获取时钟信息,并使能
I2C
时钟,即:

/* find the clock and enable it */

i2c->dev = &pdev->dev;

i2c->clk = clk_get(&pdev->dev, "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);

clk_get(&pdev->dev, "i2c")
函数用于获取时钟信息,函数内部会将传入的“
i2c
”字符串和系统中各时钟的名字进行比较,看是否匹配,看上面的分析可知,
I2C
控制器时钟注册时的时钟名也是“
i2c
”,这个过程实际上和
device

driver
的匹配过程是类似的。
clk_get
源码如下:

struct clk *clk_get(struct device *dev, const char *id)

{

struct clk *p;

struct clk *clk = ERR_PTR(-ENOENT);

int idno;

if (dev == NULL || dev->bus != &platform_bus_type)

idno = -1;

else

idno = to_platform_device(dev)->id;

spin_lock(&clocks_lock);

list_for_each_entry(p, &clocks, list) {

if (p->id == idno &&

strcmp(id, p->name)
== 0 &&

try_module_get(p->owner)) {

clk = p;

break;

}

}

s3c24xx_i2c_probe
函数还调用了
s3c24xx_i2c_init(i2c)
函数进行了
S3C2440

I2C
控制器硬件上的初始化,源码如下:

/* s3c24xx_i2c_init initialise the controller, set the IO lines and frequency*/

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));
/*I2C
控制器
IO
的初始化

/* write slave address */

/*

写入从设备的地址

*/

writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);

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

/*
使能接收发送中断和
I2C
总线应答信号

*/

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

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

/*
这里
freq
用来获取实际的
I2C
时钟频率,具体指为
97KHZ
,后面会分析

*/

if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {

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);

return 0;

}

/* s3c24xx_i2c_clockrate

*

* work out a divisor for the user requested frequency setting,

* either by the requested frequency, or scanning the acceptable

* range of frequencies until something is found

*/

static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)

{

struct s3c2410_platform_i2c *pdata = i2c->dev->platform_data;

/*
从系统平台时钟队列中获取
pclk
的时钟频率,大小为
50MHZ */

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 */

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 */

/*
目标频率在前面
default_i2c_data0

frequency

100KHZ
,根据
PCLK
和目标频率计算分频系数
,计算后实际频率为
97KHZ
,即
freq

97K*/

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;
/*
通过传入的指针返回实际频率
*/

/*
根据时钟选择和分频系数配置对应硬件寄存器
*/

iiccon = readl(i2c->regs + S3C2410_IICCON);

iiccon &= ~(S3C2410_IICCON_SCALEMASK | S3C2410_IICCON_TXDIV_512);

iiccon |= (divs-1);

if (div1 == 512)

iiccon |= S3C2410_IICCON_TXDIV_512;

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

/*

判断是否为
S3C2440
*/

if (s3c24xx_i2c_is2440(i2c)) {

unsigned long sda_delay;

if (pdata->sda_delay) {

sda_delay = (freq / 1000) * pdata->sda_delay;

sda_delay /= 1000000;

sda_delay = DIV_ROUND_UP(sda_delay, 5);

if (sda_delay > 3)

sda_delay = 3;

sda_delay |= S3C2410_IICLC_FILTER_ON;

} else

sda_delay = 0;

dev_dbg(i2c->dev, "IICLC=%08lx/n", sda_delay);

writel(sda_delay, i2c->regs + S3C2440_IICLC);

}

return 0;

}

到这里,
I2C
控制器的硬件初始化操作基本上分析完了,接下来该分析
Linux
内核
I2C
总线的通信机制了
~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: