无论是三星的s3c2410, 还是cavinum 的octeon, AMD的amd8111等等, 任何处理器在linux下添加自己的adapter都是大致的方法, 都是实现自己的driver, 最后调用i2c-core提供的API完成整个注册. 广泛地讲, linux将任何类型的设备, 任何类型的总线等都作为文件来处理, 只不过使用了不同的数据结构的driver和device.
I2c的逻辑简单实用. 在linux精妙的架构下, 代码量非常小. 现在大部分的IC都有I2C接口. 至于spi, uart, can, usb, pci, stat等等各种各样的, 虽然协议不同, 特点不用, 但本质上都是一样的.
至于I2C具体的协议, 时序等请参考其他资料. 这里只做软件上的架构分析.
下面以octeon处理器为例, 重点介绍下octeon_i2c_probe()中部分重要的代码, 在其他处理器的xxx_i2c_probe() 函数中, 无论是获取设备资源, 获取中断, CPU时钟, 配置adapter 等等操作在任何处理器中的步骤大致都是相同的, 也都是不可或缺的.
内核代码drivers/i2c/busses/i2c-octeon.c
/*
i2c 控制器是被集成在CPU上的, 寄存器地址可以被CPU直接寻址到, linux将这个adapter抽象为一个platfrom device , 其驱动使用数据结构: struct platfrom_driver.
一般将usb host, serial 控制器等也做同样处理.
实现好driver后, 使用 platform_driver_register()函数将其注册到linux内核的设备树中.
*/
static int __init octeon_i2c_init(void)
{
int rv;
rv = platform_driver_register(&octeon_i2c_driver);
return rv;
}
static struct platform_driver octeon_i2c_driver = {
.probe = octeon_i2c_probe,
.remove = __devexit_p(octeon_i2c_remove),
.driver = {
.owner = THIS_MODULE,
.name = DRV_NAME,
.of_match_table = octeon_i2c_match,
},
};
#define DRV_NAME "i2c-octeon"
static int __devinit octeon_i2c_probe(struct platform_device *pdev)
{
int irq, result = 0;
struct octeon_i2c *i2c;
struct resource *res_mem;
const __be32 *data;
int len;
/*
获取设备的中断号
*/
/* All adaptors have an irq. */
irq = platform_get_irq(pdev, 0);
/*
为其数据结构分配内存
*/
i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "kzalloc failed\n");
result = -ENOMEM;
goto out;
}
i2c->dev = &pdev->dev;
/* platform_driver_register() 注册时会对所有已注册的所有 platform_device 中的 name 和当前注册的 platform_driver 的driver.name 进行比较,只有找到相同的名称的 platfomr_device 才能注册成功,当注册成功时会调用 platform_driver 结构元素 probe 函数指针,这里就是octeon_i2c_probe(), 当进入 probe 函数后,需要获取设备的资源信息,常用获取资源的函数主要是:
struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
根据参数 type 所指定类型,例如 IORESOURCE_MEM ,来获取指定的资源, 即获取设备的IO资源地址.
*/
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res_mem == NULL) {
dev_err(i2c->dev, "found no memory resource\n");
result = -ENXIO;
goto fail_region;
}
i2c->twsi_phys = res_mem->start;
i2c->regsize = resource_size(res_mem);
data = of_get_property(pdev->dev.of_node, "clock-rate", &len);
if (data && len == sizeof(*data)) {
/*
设置I2C时钟频率
*/
i2c->twsi_freq = be32_to_cpup(data);
} else {
dev_err(i2c->dev, "no I2C 'clock-rate' property\n");
result = -ENXIO;
goto fail_region;
}
/*
设置I2C系统IO时钟频率
*/
i2c->sys_freq = octeon_get_io_clock_rate();
/*
申请IO域
*/
if (!devm_request_mem_region(&pdev->dev, i2c->twsi_phys, i2c->regsize,
res_mem->name)) {
dev_err(i2c->dev, "request_mem_region failed\n");
goto fail_region;
}
/*
申请成功后将其映射到内核空间.
*/
i2c->twsi_base = ioremap(i2c->twsi_phys, i2c->regsize);
/*
初始化I2C的等待队列
*/
init_waitqueue_head(&i2c->queue);
i2c->irq = irq;
/*
注册I2C的中断号
*/
result = request_irq(i2c->irq, octeon_i2c_isr, 0, DRV_NAME, i2c);
if (result < 0) {
dev_err(i2c->dev, "failed to attach interrupt\n");
goto fail_irq;
}
/*
初始化octeon I2C 控制器
*/
result = octeon_i2c_initlowlevel(i2c);
if (result) {
dev_err(i2c->dev, "init low level failed\n");
goto fail_add;
}
/*
设置octeon I2C 时钟
*/
result = octeon_i2c_setclock(i2c);
/*
添加octeon I2C 的寄存器read/write实现方法
*/
i2c->adap = octeon_i2c_ops;
i2c->adap.timeout = msecs_to_jiffies(50);
i2c->adap.dev.parent = &pdev->dev;
i2c->adap.dev.of_node = pdev->dev.of_node;
i2c_set_adapdata(&i2c->adap, i2c);
platform_set_drvdata(pdev, i2c);
/*
调用 i2c-core提供的注册adapter接口API.
*/
result = i2c_add_adapter(&i2c->adap);
if (result < 0) {
dev_err(i2c->dev, "failed to add adapter\n");
goto fail_add;
}
/*
注册adapter成功, 打印出当前版本号
*/
dev_info(i2c->dev, "version %s\n", DRV_VERSION);
/*
of_i2c_register_devices最终调用的是i2c-core提供的i2c_new_device()函数, 建立一个的i2c adapter.
*/
of_i2c_register_devices(&i2c->adap);
..
}
在此, octeon处理器先将定义好其特定的adapter数据结构, 将针对octeon处理器的 i2c 操作(i2c_algorithm)实现方法填充到此adapter结构体中, 最后, 使用 i2c-core提供的adapter注册函数 i2c_add_adapter().
这样, 在用户层的一个i2c访问操作就会体现在octeon处理器层的i2c实现方法上.
/*
注 : 关于: i2c_algorithm
static struct i2c_adapter octeon_i2c_ops = {
.owner = THIS_MODULE,
.name = "OCTEON adapter",
.algo = &octeon_i2c_algo,
};
static const struct i2c_algorithm octeon_i2c_algo = {
.master_xfer = octeon_i2c_xfer,
.functionality = octeon_i2c_functionality,
};
octeon 特定的 master_xfer 的实现就是octeon_i2c_xfer()函数, 在此有操作寄存器的方法.
*/
********* *********
下面介绍关于linux提供的i2c服务, linux 把一切deivce当作文件来处理, 其区别就是device 的属性和driver.
I2C层提供的两个(可看作一个)重要的函数: i2c_add_adapter 和 i2c_add_numbered_adapter
这两个函数是i2c-core提供的注册i2c_adapter 的两个重要的接口函数, 本质是一样的, 最终都调用了i2c_register_adapter()函数.
两者的区别从命名上可以看出来:
i2c_add_numbered_adapter() 增加了一个 numbered(有指定号码) 的 adapter. 可以理解为静态的总线号, 如果总线号被占用或非法等, 那么函数会返回相应的错误值.
而i2c_add_adapter ()函数是由系统自动分配总线号,即使用动态总线号, 如果注册成功, 得到的总线号保存在adapter->nr 成员中.
CPU通过调用i2c_add_adapter 或 i2c_add_numbered_adapter来实现向内核文件系统注册自己的adapter. 比如上面的 octeon_i2c_probe()就使用了i2c_add_adapter, s3c2410最新版本使用的是i2c_add_numbered_adapter.
I2c_add_adapter定义如下:
int i2c_add_adapter(struct i2c_adapter *adapter)
{
int id, res = 0;
retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
return -ENOMEM;
mutex_lock(&core_lock);
/* "above" here means "above or equal to", sigh */
res = idr_get_new_above(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, &id);
mutex_unlock(&core_lock);
if (res < 0) {
if (res == -EAGAIN)
goto retry;
return res;
}
adapter->nr = id;
return i2c_register_adapter(adapter);
}
EXPORT_SYMBOL(i2c_add_adapter);
static int i2c_register_adapter(struct i2c_adapter *adap)
{
...
mutex_init(&adap->bus_lock);
/* Set default timeout to 1 second if not already set */
if (adap->timeout == 0)
adap->timeout = HZ;
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
/*
使用device_register注册该adapter设备
*/
res = device_register(&adap->dev);
....
}
在i2c_register_adapter中使用了两个重要的结构体,
i2c_bus_type 和 i2c_adapter_type,
linux将 adapter作为一个模拟为一个总线设备, 用 i2c_bus_type描述其总线类型, 用i2c_adapter_type描述其设备类型.
struct device_type i2c_adapter_type = {
.groups = i2c_adapter_attr_groups,
.release = i2c_adapter_dev_release,
};
EXPORT_SYMBOL_GPL(i2c_adapter_type);
static const struct attribute_group *i2c_adapter_attr_groups[] = {
&i2c_adapter_attr_group,
NULL
};
static struct attribute_group i2c_adapter_attr_group = {
.attrs = i2c_adapter_attrs,
};
static struct attribute *i2c_adapter_attrs[] = {
&dev_attr_name.attr,
&dev_attr_new_device.attr,
&dev_attr_delete_device.attr,
NULL
};
对于dev_attr_name, dev_attr_new_device, dev_attr_delete_device使用宏函数 DEVICE_ATTR, 将具体读写操作方法函数进行绑定, 请参考 DEVICE_ATTR宏的用法.
I2c-core中:
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
static DEVICE_ATTR(delete_device, S_IWUSR, NULL, i2c_sysfs_delete_device);
定义了show_name() 为结构体dev_attr_name 的show方法.
定义了i2c_sysfs_new_device() 为 结构体 dev_attr_new_device的store方法.
定义了i2c_sysfs_delete_device() 为 结构体dev_attr_delete_device的 store 方法.
i2c_sysfs_new_device:
static ssize_t
i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
/*
获取adapter 数据结构
*/
struct i2c_adapter *adap = to_i2c_adapter(dev);
struct i2c_board_info info;
struct i2c_client *client;
....
client = i2c_new_device(adap, &info);
...
}
i2c_sysfs_new_device主要功能是: 获取adapter, 调用 i2c_new_device()
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
...
client->adapter = adap;
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
..
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
status = device_register(&client->dev);
..
}
i2c_new_device() 主要功能是: 将adapter, irq, i2c_bus_type ,i2c_adapter_type等信息填充到一个 i2c_client中, 最后将i2c_client中的device注册到系统内核中.
上面使用到的另一个结构体 i2c_client_type :
static struct device_type i2c_client_type;
static struct device_type i2c_client_type = {
.groups = i2c_dev_attr_groups,
.uevent = i2c_device_uevent,
.release = i2c_client_dev_release,
};
static const struct attribute_group *i2c_dev_attr_groups[] = {
&i2c_dev_attr_group,
NULL
};
static struct attribute_group i2c_dev_attr_group = {
.attrs = i2c_dev_attrs,
};
static struct attribute *i2c_dev_attrs[] = {
&dev_attr_name.attr,
/* modalias helps coldplug: modprobe $(cat .../modalias) */
&dev_attr_modalias.attr,
NULL
};
两个属性结构体的定义在:
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL);
这个属性的show_name定义为:
static ssize_t
show_name(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", dev->type == &i2c_client_type ?
to_i2c_client(dev)->name : to_i2c_adapter(dev)->name);
}
举一个实际的例子:
在系统中执行:
root@juson:~# cat /sys/bus/i2c/devices/i2c-0/name
OCTEON adapter
显示: OCTEON adapter. 这个名字就是在 struct i2c_adapter octeon_i2c_ops 结构体中定义的:
static struct i2c_adapter octeon_i2c_ops = {
.owner = THIS_MODULE,
.name = "OCTEON adapter",
.algo = &octeon_i2c_algo,
};