LINUX下IIC子系统分析

    LINUX平台下IIC子系统的经典分布图如下:

LINUX下IIC子系统分析_第1张图片

下面主要针对内核中IIC新模式(new style)进行分析.

    下面以S3C2440平台搭载的24C08进行整个LINUX IIC子系统的分析.

 

1.如何生成用户空间的设备节点:

    1-1.24c08驱动端:

        24c08的驱动代码位于drivers/misc/eeprom/at24.c

        入口函数:

module_init(at24_init);
static int __init at24_init(void)
{
	return i2c_add_driver(&at24_driver);
}

在LINUX内核中,每个IIC驱动对应的结构体为struct i2c_driver.24c08也不例外:

static struct i2c_driver at24_driver = {
	.driver = {
		.name = "at24",
		.owner = THIS_MODULE,
	},
	.probe = at24_probe,
	.remove = __devexit_p(at24_remove),
	.id_table = at24_ids,
};

    这里有一个很重要的域:id_table:

static const struct i2c_device_id at24_ids[] = {
	/* needs 8 addresses as A0-A2 are ignored */
	{ "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
	/* old variants can't be handled with this generic entry! */
	{ "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
	{ "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
	/* spd is a 24c02 in memory DIMMs */
	{ "spd", AT24_DEVICE_MAGIC(2048 / 8,
		AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
	{ "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
	/* 24rf08 quirk is handled at i2c-core */
	{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
	{ "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
	{ "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
	{ "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
	{ "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
	{ "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },
	{ "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
	{ "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
	{ "at24", 0 },
	{ /* END OF LIST */ }
};

    id_table域是探测此驱动支持哪些设备的依据,下面便是整个系统的调用流程追踪,主要是函数i2c_add_driver(&at24_driver):

static inline int i2c_add_driver(struct i2c_driver *driver)
{
	return i2c_register_driver(THIS_MODULE, driver);
}

-->

/*
 * An i2c_driver is used with one or more i2c_client (device) nodes to access
 * i2c slave chips, on a bus instance associated with some i2c_adapter.
 */

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;

	/* Can't register until after driver model init */
	if (unlikely(WARN_ON(!i2c_bus_type.p)))
		return -EAGAIN;

	/* add the driver to the list of i2c drivers in the driver core */
	driver->driver.owner = owner;
	driver->driver.bus = &i2c_bus_type;

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver);
	if (res)
		return res;

	pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);

	INIT_LIST_HEAD(&driver->clients);
	/* Walk the adapters that are already present */
	mutex_lock(&core_lock);
	bus_for_each_dev(&i2c_bus_type, NULL, driver, __attach_adapter);
	mutex_unlock(&core_lock);

	return 0;
}

    linux每个IIC驱动都是挂在IIC这条总线上的,见上面代码:

	driver->driver.bus = &i2c_bus_type;

    类似usb、spi等子系统一样,每个驱动都会标明其所属的总线类型.这种驱动依附总线的一个重要意义在于匹配条件:IIC有IIC的匹配条件、USB和USB的匹配条件等.在这里需要铭记我们这个24c08属于iic driver,而iic driver是隶属IIC BUS的.下面继承跟踪域id_table的意义,在函数i2c_register_driver()函数里面第二个关注的地方语句是:

	res = driver_register(&driver->driver);

    其实,这是LINUX驱动里面一个"核心基类"函数,所有的隶属总线的驱动(像uart、spi、usb等)均路由此函数,因此,可以独立来理解,因为此函数与子系统无关.其最重大的意义在于回调到具体调用此函数的总线的match函数的执行.下面通过代码验证这一点:  

int driver_register(struct device_driver *drv)
{
        ... ...;
	ret = bus_add_driver(drv);
        ... ...;
}

    展开函数bus_add_driver():

int bus_add_driver(struct device_driver *drv)
{
	... ...;

	if (drv->bus->p->drivers_autoprobe) {
		error = driver_attach(drv);
		if (error)
			goto out_unregister;
	}
	
	... ...;
}

    展开函数driver_attach():

int driver_attach(struct device_driver *drv)
{
	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

    函数bus_for_each_dev会遍历挂在此总线上的设备,再根据一定的"匹配条件"(主要通过回调函数__driver_attach)来看这个设备是否被这个驱动所支持.展开如下:

int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
	struct klist_iter i;
	struct device *dev;
	int error = 0;

	if (!bus)
		return -EINVAL;

	klist_iter_init_node(&bus->p->klist_devices, &i,
			     (start ? &start->p->knode_bus : NULL));
	while ((dev = next_device(&i)) && !error)
		error = fn(dev, data);
	klist_iter_exit(&i);
	return error;
}

     下面我们来看函数__driver_attach():

static int __driver_attach(struct device *dev, void *data)
{

	if (!driver_match_device(drv, dev))
		return 0;

	if (!dev->driver)
		driver_probe_device(drv, dev);
}

    这里有两个比较重要的函数:driver_match_device()和driver_probe_device().其中和id_table域相关的函数是driver_match_device()函数;而后者是device和driver相关的函数.

static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

    可以看到,这里将会回调总线上的match函数,对于IIC总线而言,其match函数是:i2c_device_match()

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.suspend	= i2c_device_suspend,
	.resume		= i2c_device_resume,
};

    展开函数i2c_device_match():

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}  

    继续展开函数i2c_match_id():

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0)
			return id;
		id++;
	}
	return NULL;
}

    因此,对于iic设备驱动而言,其匹配的条件是id_table域.这时候一个结构体struct i2c_client横空出世了.这货是从哪来的呢?struct i2c_client其实是表征一个iic设备.下面以mini2440平台来追踪此函数的来龙去脉.

 

    1-2.24c08设备端:

    arch/arm/mach-s3c2440/mach-mini2440.c:

MACHINE_START(MINI2440, "MINI2440")
	/* Maintainer: Michel Pollet  */
	.phys_io	= S3C2410_PA_UART,
	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C2410_SDRAM_PA + 0x100,
	.map_io		= mini2440_map_io,
	.init_machine	= mini2440_init,
	.init_irq	= s3c24xx_init_irq,
	.timer		= &s3c24xx_timer,
MACHINE_END

    这个函数在系统启动过程中必须得被调用,留意到mini2440_init函数:

static void __init mini2440_init(void)
{
	i2c_register_board_info(0, mini2440_i2c_devs,
				ARRAY_SIZE(mini2440_i2c_devs));
}

    mini2440_init()函数里面有对板载iic设备进行注册,见函数i2c_register_board_info().其中有个参数mini2440_i2c_devs便是与我们这个24c08相关的.如下:

static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
	{
		I2C_BOARD_INFO("24c08", 0x50),
		.platform_data = &at24c08,
	},
};

    展开函数i2c_register_board_info():

int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
{
		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
		devinfo->busnum = busnum;
		devinfo->board_info = *info;
		list_add_tail(&devinfo->list, &__i2c_board_list);
}

    这里需要注意两点:

    1).把我们这个24c08这个iic设备信息存放到devinfo结构体里面;

    2).把devinfo连到全局链表头__i2c_board_list里面.
   

    1-2-1.主控端

    要想追踪上述的关于24c08对应的i2c_client的生成,得从主控端的适配器(adapter)代码看起,关于mini2440平台iic适配器端代码位于drivers/i2c/busses/i2c-s3c2410.c:

static int __init i2c_adap_s3c_init(void)
{
	return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);

    这里把iic适配器驱动注册成隶属于平台总线的驱动(平台总线是内核虚拟的一条总线,其地位与iic总线是平级的地位).s3c2440的iic适配器关于平台驱动的信息如下:

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,
	},
};

    分析函数platform_driver_register()和上述的i2c_register_driver()有很大的思维模式类似之处.下面分析之:

int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}

    在上述分析i2c_register_driver()函数时,已经明确:函数driver_register()是LINUX驱动的"核心基类",不隶属于任何子系统,其重大意义在于回调驱动所隶属的总线的match函数.因此,不再累赘分析其过程(上面已经有所分析).这里直接跳到platform_bus_type的match函数:

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

    展开platform_match()函数:

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* match against the id table first */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

    如果我们的平台驱动有定义id_table域,那么匹配依据是id_table;否则就只需要匹配隶属于平台总线的驱动名字和设备名字即可.在这里,s3c24xx_i2c_driver有对id_table域有作定义,因此,这里匹配的依据是id_table:

static struct platform_device_id s3c24xx_driver_ids[] = {
	{
		.name		= "s3c2410-i2c",
		.driver_data	= TYPE_S3C2410,
	}, {
		.name		= "s3c2440-i2c",
		.driver_data	= TYPE_S3C2440,
	}, { },
};

    展开函数platform_match_id():

static const struct platform_device_id *platform_match_id(
			struct platform_device_id *id,
			struct platform_device *pdev)
{
	while (id->name[0]) {
		if (strcmp(pdev->name, id->name) == 0) {
			pdev->id_entry = id;
			return id;
		}
		id++;
	}
	return NULL;
}

    这里还是匹配驱动名字和设备名字.我们这里的驱动名为"s3c2440-i2c",那么,隶属于平台总线的同样命名为"s3c2440-i2c"的平台设备是何时注册进平台总线的呢?见源码文件

arch/arm/mach-s3c2440/mach-mini2440.c有如下代码:

MACHINE_START(MINI2440, "MINI2440")
	/* Maintainer: Michel Pollet  */
	.phys_io	= S3C2410_PA_UART,
	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C2410_SDRAM_PA + 0x100,
	.map_io		= mini2440_map_io,
	.init_machine	= mini2440_init,
	.init_irq	= s3c24xx_init_irq,
	.timer		= &s3c24xx_timer,
MACHINE_END

    其中,语句:

mini2440_init

    此函数的调用里面,完成了所有平台设备的注册(此函数如何被调用暂不作细究).展开如下:

static void __init mini2440_init(void)
{
	platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
}

    其中参数mini2440_devices为mini2440整个开发板的平台设备数组.如下:

static struct platform_device *mini2440_devices[] __initdata = {
	&s3c_device_usb,
	&s3c_device_wdt,
/*	&s3c_device_adc,*/ /* ADC doesn't like living with touchscreen ! */
	&s3c_device_i2c0,
	&s3c_device_rtc,
	&s3c_device_usbgadget,
	&mini2440_device_eth,
	&mini2440_led1,
	&mini2440_led2,
	&mini2440_led3,
	&mini2440_led4,
	&mini2440_button_device,
	&s3c_device_nand,
	&s3c_device_sdi,
	&s3c_device_iis,
	&mini2440_audio,
/*	&s3c_device_timer[0],*/	/* buzzer pwm, no API for it */
	/* remaining devices are optional */
};

    其中关于iic适配器的见上述数组的s3c_device_i2c0:

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,
};

    见到此iic适配器的设备名字为"s3c2410-i2c",和我们之前的适配器的驱动名一样.平台设备的注册由函数platform_add_devices()批量完成:

int platform_add_devices(struct platform_device **devs, int num)
{
	int i, ret = 0;

	for (i = 0; i < num; i++) {
		ret = platform_device_register(devs[i]);
		if (ret) {
			while (--i >= 0)
				platform_device_unregister(devs[i]);
			break;
		}
	}

	return ret;
}



   回到函数platform_match()中,可见是匹配成功的.上述的函数__driver_attach()只分析到driver_match_device(),这里得出的结论是驱动所隶属的总线的match函数被调用.如下:

static int __driver_attach(struct device *dev, void *data)
{

	if (!driver_match_device(drv, dev))
		return 0;

	if (!dev->driver)
		driver_probe_device(drv, dev);
}

    接下来我们继续分析函数driver_probe_device()函数:

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
	ret = really_probe(dev, drv);
}

   在函数really_probe()函数里面,将会导致总线/驱动的probe函数的被调用:

static int really_probe(struct device *dev, struct device_driver *drv)
{
	if (dev->bus->probe) {
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}
}

    在这里的iic适配器这个设备里面,并没有定义probe域,因此,执行的是驱动的probe函数.为了方便记忆,这里重复把s3c24xx_i2c_driver粘贴在这里:

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,
	},
};

    展开函数s3c24xx_i2c_probe():

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
	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;
	}
}

    展开函数i2c_add_numbered_adapter():

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
		status = i2c_register_adapter(adap);
}

    在这里完成适配器的注册.

static int i2c_register_adapter(struct i2c_adapter *adap)
{
	if (adap->nr < __i2c_first_dynamic_bus_num)
		i2c_scan_static_board_info(adap);
}

    展开函数i2c_scan_static_board_info(),完成iic设备的注册:

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
	list_for_each_entry(devinfo, &__i2c_board_list, list) {
		if (devinfo->busnum == adapter->nr
				&& !i2c_new_device(adapter,
						&devinfo->board_info))
			dev_err(&adapter->dev,
				"Can't create device at 0x%02x\n",
				devinfo->board_info.addr);
	}
}

    核心函数i2c_new_device()是动态生成一个iic设备的.而我们在分析24c08的设备端时,分配的devinfo便以链表的方式记录在__i2c_board_list,这里通过链表的方式把具体的iic设备信息抓出来进行iic设备(struct i2c_client)的初始化:

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
	struct i2c_client	*client;
	int			status;

	client = kzalloc(sizeof *client, GFP_KERNEL);
	if (!client)
		return NULL;

	client->adapter = adap;

	client->dev.platform_data = info->platform_data;

	if (info->archdata)
		client->dev.archdata = *info->archdata;

	client->flags = info->flags;
	client->addr = info->addr;
	client->irq = info->irq;

	strlcpy(client->name, info->type, sizeof(client->name));

	/* Check for address business */
	status = i2c_check_addr(adap, client->addr);
	if (status)
		goto out_err;

	client->dev.parent = &client->adapter->dev;
	client->dev.bus = &i2c_bus_type;
	client->dev.type = &i2c_client_type;

	dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
		     client->addr);

	return client;
}

    先回顾我们上述的24c08的设备端:

static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
	{
		I2C_BOARD_INFO("24c08", 0x50),
		.platform_data = &at24c08,
	},
};

    I2C_BOARD_INFO展开:

#define I2C_BOARD_INFO(dev_type, dev_addr) \
	.type = dev_type, .addr = (dev_addr)

    见函数i2c_new_device()语句:

	strlcpy(client->name, info->type, sizeof(client->name));

    因此,对于我们这个24c02来说,此client->name域是"24c08".并且这个i2c_client是被挂载在IIC BUS上的:

client->dev.bus = &i2c_bus_type;

    至此,回溯到之前的i2c_device_match()函数.match成功.为了思维的连续性,下面继续从at24.c到当前的过程再撸一遍,当然,省略struct i2c_client的生成部分:

module_init(at24_init);->i2c_add_driver(&at24_driver);->...->__driver_attach():

    接着上面我们的分析断点处:

static int __driver_attach(struct device *dev, void *data)
{

	if (!driver_match_device(drv, dev))
		return 0;

	if (!dev->driver)
		driver_probe_device(drv, dev);
}

    又见probe,只不过这里对应的是i2c_device_probe()函数:

static int i2c_device_probe(struct device *dev)
{
	status = driver->probe(client, i2c_match_id(driver->id_table, client));
}

    总线的probe又引发了driver下的probe的执行,在这里是at24_probe()函数:

/*-------------------------------------------------------------------------*/

static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	at24->bin.attr.name = "eeprom";
	at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
	at24->bin.read = at24_bin_read;
	at24->bin.size = chip.byte_len;

	at24->macc.read = at24_macc_read;


	err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
	if (err)
		goto err_clients;
}
    可见,关于24c08暴露给上层用户空间的并不是/dev/下的设备节点,而是/sys/目录下的文件.对/sys/目录下的文件操作即引发其相关联的读写函数的执行.如上述的函数:

at24->bin.read = at24_bin_read;
at24->bin.write = at24_bin_write;
   

2.用户空间如何实现和内核驱动的数据交互?

    上述分析,用户空间通过/sys/下的文件(属性)实现和底层交互.以写24c08函数追踪其流程.

static ssize_t at24_bin_write(struct kobject *kobj, struct bin_attribute *attr,
		char *buf, loff_t off, size_t count)
{
	struct at24_data *at24;

	at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
	return at24_write(at24, buf, off, count);
}

    buf即为用户层要写入到24c08的数据,展开函数at24_write():

static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,
			  size_t count)
{
		status = at24_eeprom_write(at24, buf, off, count);
}

    核心函数为at24_eeprom_write().展开如下:

static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,unsigned offset, size_t count)
{
	client = at24_translate_offset(at24, &offset);

	if (!at24->use_smbus) {
		int i = 0;

		msg.addr = client->addr;
		msg.flags = 0;

		/* msg.buf is u8 and casts will mask the values */
		msg.buf = at24->writebuf;
		if (at24->chip.flags & AT24_FLAG_ADDR16)
			msg.buf[i++] = offset >> 8;

		msg.buf[i++] = offset;
		memcpy(&msg.buf[i], buf, count);
		msg.len = i + count;
	}
			status = i2c_transfer(client->adapter, &msg, 1);
}

 

    我们要对一个IIC设备操作,是通过适配器完成的,只需要在msg里面包含这个IIC设备的信息(如IIC设备地址、要读写的数据)即可.那么,这个msg是如何根据我们的24c08完成这个iic消息的构造的呢?这里分两步走,一步是msg的构造,一步是msg是如何在linux iic子系统里面流窜的.

 

    2-1.msg的构造:

        上述函数at24_eeprom_write()中对msg的构造语句如下:

	if (!at24->use_smbus) {
		int i = 0;

		msg.addr = client->addr;
		msg.flags = 0;

		/* msg.buf is u8 and casts will mask the values */
		msg.buf = at24->writebuf;
		if (at24->chip.flags & AT24_FLAG_ADDR16)
			msg.buf[i++] = offset >> 8;

		msg.buf[i++] = offset;
		memcpy(&msg.buf[i], buf, count);
		msg.len = i + count;
	}

    语句msg.addr = client->addr来标明此msg地址,即这个msg将流向哪个iic设备,而每一个iic设备用一个struct i2c_client来表征.在上述的函数i2c_new_device()有下面语句:

	client->addr = info->addr;

    info->addr即0x50.见上述函数i2c_register_board_info()及i2c_adap_s3c_init().

    用户空间传送的数据被缓存在msg.buf,数据量被记录在msg.len.

 

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
			ret = adap->algo->master_xfer(adap, msgs, num);
}

    在这里需要重点关注下结构体adap,它表征一个适配器(即IIC的MASTER).我们要操作一个IIC设备时,构造好msg,然后通过adap对这个msg解析,实现到指定iic设备的数据交互.关于这个adap的来龙去脉下面继续分析:

    i2c-s3c2410.c:

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);
}
subsys_initcall(i2c_adap_s3c_init);

    如何从i2c_adap_s3c_init()到s3c24xx_i2c_probe()函数的调度流程见上述的分析.下面直接看函数s3c24xx_i2c_probe():

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
	struct s3c24xx_i2c *i2c;

	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->adap.retries = 2;
	i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	i2c->tx_setup     = 50;

	i2c->adap.algo_data = i2c;
	i2c->adap.dev.parent = &pdev->dev;

	ret = i2c_add_numbered_adapter(&i2c->adap);
	
}

    见结构体struct s3c24xx_i2c,里面封装了结构体adap,可见,adap是平台无关的.特定的平台只需要用特定平台的信息去初始化相应的adap即可:

struct s3c24xx_i2c {

	struct i2c_msg		*msg;
	
	struct i2c_adapter	adap;
};

    上述函数s3c24xx_i2c_probe()便是对24xx平台的adap初始化.其中语句:

	i2c->adap.algo    = &s3c24xx_i2c_algorithm;

    在上述的i2c_transfer()函数中,将调用这个域的函数master_xfer来实现IIC设备的通讯:

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};

    展开函数s3c24xx_i2c_xfer():

static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{
		ret = s3c24xx_i2c_doxfer(i2c, msgs, num);
}

    展开函数:

static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,struct i2c_msg *msgs, int num)
{
	ret = s3c24xx_i2c_set_master(i2c);//判断IIC BUS是否处于繁忙状态,结合源码和datasheet.
	if (ret != 0) {
		dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
		ret = -EAGAIN;
		goto out;
	}

	i2c->msg     = msgs;				
	i2c->msg_num = num;
	i2c->msg_ptr = 0;
	i2c->msg_idx = 0;
	i2c->state   = STATE_START;

	s3c24xx_i2c_enable_irq(i2c);
	s3c24xx_i2c_message_start(i2c, msgs);//IIC BUS的协议信号,如开始、停止等.此函数和IIC协议密切相关.
}

    其中函数s3c24xx_i2c_message_start()和IIC协议密切相关,如开始信号、设备地址、结束信号、是否需要ACK应答等.如下:

/* s3c24xx_i2c_message_start
 *
 * put the start of a message onto the bus
*/

static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
				      struct i2c_msg *msg)
{
	unsigned int addr = (msg->addr & 0x7f) << 1;
	unsigned long stat;
	unsigned long iiccon;

	stat = 0;
	stat |=  S3C2410_IICSTAT_TXRXEN;

	if (msg->flags & I2C_M_RD) {
		stat |= S3C2410_IICSTAT_MASTER_RX;
		addr |= 1;
	} else
		stat |= S3C2410_IICSTAT_MASTER_TX;

	if (msg->flags & I2C_M_REV_DIR_ADDR)
		addr ^= 1;

	/* todo - check for wether ack wanted or not */
	s3c24xx_i2c_enable_ack(i2c);

	iiccon = readl(i2c->regs + S3C2410_IICCON);
	writel(stat, i2c->regs + S3C2410_IICSTAT);

	dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
	writeb(addr, i2c->regs + S3C2410_IICDS);

	/* delay here to ensure the data byte has gotten onto the bus
	 * before the transaction is started */

	ndelay(i2c->tx_setup);

	dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);
	writel(iiccon, i2c->regs + S3C2410_IICCON);

	stat |= S3C2410_IICSTAT_START;
	writel(stat, i2c->regs + S3C2410_IICSTAT);
}

    具体可结合IIC协议、源码和CPU手册去理解.对于写动作,此函数代码主要实现:

        1).发起IIC起始信号;
        2).根据iic msg的地址域信息配置iic方向,是读iic设备还是写iic设备.

    那么,我们实际的数据是在哪写进iic设备的呢?在S3C2440的IIC中断里面实现.见函数s3c24xx_i2c_probe():

	ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
			  dev_name(&pdev->dev), i2c);

    在函数s3c24xx_i2c_doxfer():

	s3c24xx_i2c_enable_irq(i2c);
	s3c24xx_i2c_message_start(i2c, msgs);

    使能了iic中断,展开中断处理函数:

static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{
	struct s3c24xx_i2c *i2c = dev_id;

	i2s_s3c_irq_nextbyte(i2c, status);
}

    在注册中断函数时,我们把i2c作为参数传递给中断处理函数的.i2c中的相关信息被上层用户传递的信息给封装.通过函数 i2s_s3c_irq_nextbyte(i2c, status);推进:

/* i2s_s3c_irq_nextbyte
 *
 * process an interrupt and work out what to do
 */

static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)
{
	unsigned long tmp;
	unsigned char byte;
	int ret = 0;

	switch (i2c->state) {

	case STATE_IDLE:
		dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);
		goto out;
		break;

	case STATE_STOP:
		dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
		s3c24xx_i2c_disable_irq(i2c);
		goto out_ack;

	case STATE_START:
		/* last thing we did was send a start condition on the
		 * bus, or started a new i2c message
		 */

		if (iicstat & S3C2410_IICSTAT_LASTBIT &&
		    !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
			/* ack was not received... */

			dev_dbg(i2c->dev, "ack was not received\n");
			s3c24xx_i2c_stop(i2c, -ENXIO);
			goto out_ack;
		}

		if (i2c->msg->flags & I2C_M_RD)
			i2c->state = STATE_READ;
		else
			i2c->state = STATE_WRITE;

		/* terminate the transfer if there is nothing to do
		 * as this is used by the i2c probe to find devices. */

		if (is_lastmsg(i2c) && i2c->msg->len == 0) {
			s3c24xx_i2c_stop(i2c, 0);
			goto out_ack;
		}

		if (i2c->state == STATE_READ)
			goto prepare_read;

		/* fall through to the write state, as we will need to
		 * send a byte as well */

	case STATE_WRITE:
		/* we are writing data to the device... check for the
		 * end of the message, and if so, work out what to do
		 */

		if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
			if (iicstat & S3C2410_IICSTAT_LASTBIT) {
				dev_dbg(i2c->dev, "WRITE: No Ack\n");

				s3c24xx_i2c_stop(i2c, -ECONNREFUSED);
				goto out_ack;
			}
		}

 retry_write:

		if (!is_msgend(i2c)) {
			byte = i2c->msg->buf[i2c->msg_ptr++];
			writeb(byte, i2c->regs + S3C2410_IICDS);

			/* delay after writing the byte to allow the
			 * data setup time on the bus, as writing the
			 * data to the register causes the first bit
			 * to appear on SDA, and SCL will change as
			 * soon as the interrupt is acknowledged */

			ndelay(i2c->tx_setup);

		} else if (!is_lastmsg(i2c)) {
			/* we need to go to the next i2c message */

			dev_dbg(i2c->dev, "WRITE: Next Message\n");

			i2c->msg_ptr = 0;
			i2c->msg_idx++;
			i2c->msg++;

			/* check to see if we need to do another message */
			if (i2c->msg->flags & I2C_M_NOSTART) {

				if (i2c->msg->flags & I2C_M_RD) {
					/* cannot do this, the controller
					 * forces us to send a new START
					 * when we change direction */

					s3c24xx_i2c_stop(i2c, -EINVAL);
				}

				goto retry_write;
			} else {
				/* send the new start */
				s3c24xx_i2c_message_start(i2c, i2c->msg);
				i2c->state = STATE_START;
			}

		} else {
			/* send stop */

			s3c24xx_i2c_stop(i2c, 0);
		}
		break;

	case STATE_READ:
		/* we have a byte of data in the data register, do
		 * something with it, and then work out wether we are
		 * going to do any more read/write
		 */

		byte = readb(i2c->regs + S3C2410_IICDS);
		i2c->msg->buf[i2c->msg_ptr++] = byte;

 prepare_read:
		if (is_msglast(i2c)) {
			/* last byte of buffer */

			if (is_lastmsg(i2c))
				s3c24xx_i2c_disable_ack(i2c);

		} else if (is_msgend(i2c)) {
			/* ok, we've read the entire buffer, see if there
			 * is anything else we need to do */

			if (is_lastmsg(i2c)) {
				/* last message, send stop and complete */
				dev_dbg(i2c->dev, "READ: Send Stop\n");

				s3c24xx_i2c_stop(i2c, 0);
			} else {
				/* go to the next transfer */
				dev_dbg(i2c->dev, "READ: Next Transfer\n");

				i2c->msg_ptr = 0;
				i2c->msg_idx++;
				i2c->msg++;
			}
		}

		break;
	}

	/* acknowlegde the IRQ and get back on with the work */

 out_ack:
	tmp = readl(i2c->regs + S3C2410_IICCON);
	tmp &= ~S3C2410_IICCON_IRQPEND;
	writel(tmp, i2c->regs + S3C2410_IICCON);
 out:
	return ret;
}

    我们的数据被保存在i2c msg的buf域里面.在函数i2s_s3c_irq_nextbyte()里面,往iic设备写数据的代码如下:

			byte = i2c->msg->buf[i2c->msg_ptr++];
			writeb(byte, i2c->regs + S3C2410_IICDS);

   

3.小结:

    为了对上述脉络有一个更清晰的架构理解,并对过程中一些比较重要的数据结构进行归纳,从内核软件层的先后顺序进行小结:

    LINUX下IIC子系统分析_第2张图片

    3-1.iic控制器与"运算器":

        iic控制器主要是指与特定平台相关的iic控制器,如这里的struct s3c24xx_i2c;而运算器则指上述的"适配器"--struct i2c_adapter.其中struct i2c_adapter是与具体平台无关的的,它位于linux i2c的核心层.具体的i2c_adapter需要被具体平台的iic控制器初始化.这一步实现了具体平台的iic控制器与软件的i2c_adapter实现了关联;

        而adapter与一般的总线设备注册进内核的方式无异.

 

    3-2.iic设备:

        linux中每一个iic设备用一个struct i2c_client来表征,里面包含了如iic设备的地址等信息.它是动态创建,并通过内核链表的方式去挂载到指定的adapter上;

 

    3-3.struct i2c_msg

        在数据的交互过程中,流窜于整个内核iic子系统的媒体就是"struct i2c_msg",里面至少包含了两个信息:一是这个iic消息的流向(即目标iic设备地址);二是和iic设备交互的内容.

其中获取iic设备地址的过程中,需要借助上述动态创建的表征一个iic设备的结构体struct i2c_client.

 

    3-4.和用户空间交互

        如果采用标准的内核iic子系统,有可能是/sys/下的属性文件,如这里的24c08.当然也可以改写成/dev/下的字符设备.当然还有另外两种存在方式,一种是不用内核iic子系统,只是简单的字符设备,把相关的iic 子系统里面实现iic通讯即可;第二种就是用户空间的iic操作.
















 

 

你可能感兴趣的:(Ldd)