LinuxI2C

  1. Linux$I^2C$体系结构
  2. Linux$I^2C$核心
  3. Linux$I^2C$适配器驱动
    1. $I^2C$适配器驱动的注册与注销
    2. $I^2C$总线的通信方法
  4. Linux$I^2C$设备驱动

Linux$I^2C$体系结构

linux$I^2C$体系结构分为三部分:

  • $I^2C$核心

    提供$I^2C$总线驱动和设备驱动的注册、注销等方法;$I^2C$通信方法上层的与具体适配器无关的代码;探测设备、检测设备地址的上层代码等

  • $I^2C$总线驱动

    $I^2C$硬件体系结构中适配器端的实现。主要包含$I^2C$适配器数据结构i2c_adapter、$I^2C$适配器的算法数据结构i2c_algorithm和控制$I^2C$适配器产生通信信号的函数。

  • $I^2C$设备驱动

    $I^2C$硬件体系结构中设备端的实现,$I^2C$设备驱动主要包含i2c_driver和i2c_client两个数据结构,由具体设备各自实现其中的成员。

I2C体系结构

linux$I^2C$子系统相对复杂,面对$I^2C$子系统,应该如何动手写驱动呢?哪些是内核已经提供的,哪些又是我们需要亲自做的呢?

一方面,适配器驱动可能是Linux内核本身还不包含的;另一方面,挂接在适配器上的具体设备驱动可能也是Linux内核还不包含的。因此,工程师要实现的主要工作如下:

  • 提供$I^2C$适配器的硬件驱动,探测、初始化$I^2C$适配器、驱动CPU控制的$I^2C$适配器从硬件上产生各种信号以及处理$I^2C$中断等
  • 提供$I^2C$适配器的Algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针
  • 实现$I^2C$设备驱动中的i2c_driver接口,用具体设备的xxx_probe()、xxx_remove()、xxx_suspend()等和i2c_device_id设备ID表赋值给i2c_driver的相应指针
  • 实现$I^2C$设备所对应类型的具体驱动,i2c_driver只是实现设备和总线的挂接,而挂接在总线上的设备则是千差万别的。

Linux$I^2C$核心

$I^2C$核心实现了一组不依赖于硬件平台的接口函数,这些一般不需要被工程师修改,但需要理解其中主要函数。主要函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
int i2c_add_adapter(struct i2c_adapter *adap);
void i2c_del_adapter(struct i2c_adapter *adap);

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);

int i2c_master_recv(const struct i2c_client *client,
char *buf, int count);
int i2c_master_send(const struct i2c_client *client,
const char *buf, int count)
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);

Linux$I^2C$适配器驱动

$I^2C$适配器驱动的注册与注销

由于$I^2C$总线控制器通常是在内存上的,所以它本身也是连接在platform总线上,要通过platform_driver和platform_device的匹配来执行。因此尽管$I^2C$适配器给别人提供了总线,它自己也被认为是挂接在platform总线上的设备。

由上分析,可得到如下的$I^2C$适配器驱动注册与注销的基本实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
static int
i2c_au1550_probe(struct platform_device *pdev)
{
struct i2c_au1550_data *priv;
struct resource *r;
int ret;

priv = devm_kzalloc(&pdev->dev, sizeof(struct i2c_au1550_data),
GFP_KERNEL);
if (!priv)
return -ENOMEM;

r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->psc_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(priv->psc_base))
return PTR_ERR(priv->psc_base);

priv->xfer_timeout = 200;

priv->adap.nr = pdev->id;
priv->adap.algo = &au1550_algo;
priv->adap.algo_data = priv;
priv->adap.dev.parent = &pdev->dev;
strlcpy(priv->adap.name, "Au1xxx PSC I2C", sizeof(priv->adap.name));

/* Now, set up the PSC for SMBus PIO mode. */
i2c_au1550_setup(priv);

ret = i2c_add_numbered_adapter(&priv->adap);
if (ret) {
i2c_au1550_disable(priv);
return ret;
}

platform_set_drvdata(pdev, priv);
return 0;
}

static int i2c_au1550_remove(struct platform_device *pdev)
{
struct i2c_au1550_data *priv = platform_get_drvdata(pdev);

i2c_del_adapter(&priv->adap);
i2c_au1550_disable(priv);
return 0;
}

static struct platform_driver au1xpsc_smbus_driver = {
.driver = {
.name = "au1xpsc_smbus",
.pm = AU1XPSC_SMBUS_PMOPS,
},
.probe = i2c_au1550_probe,
.remove = i2c_au1550_remove,
};

module_platform_driver(au1xpsc_smbus_driver);

$I^2C$总线的通信方法

要为特定的$I^2C$适配器实现通信方法,主要是实现i2c_algorithm的functionality()函数和master_xfer()函数。

functionality()用于返回algorithm所支持的通信协议。

master_xfer()函数在$I^2C$适配器上完成传递给它的i2c_msg数组中的每个$I^2C$消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static int
au1550_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num)
{
struct i2c_au1550_data *adap = i2c_adap->algo_data;
struct i2c_msg *p;
int i, err = 0;

WR(adap, PSC_CTRL, PSC_CTRL_ENABLE);

for (i = 0; !err && i < num; i++) {
p = &msgs[i];
err = do_address(adap, p->addr, p->flags & I2C_M_RD,
(p->len == 0));
if (err || !p->len)
continue;
if (p->flags & I2C_M_RD)
err = i2c_read(adap, p->buf, p->len);
else
err = i2c_write(adap, p->buf, p->len);
}

/* Return the number of messages processed, or the error code.
*/
if (err == 0)
err = num;

WR(adap, PSC_CTRL, PSC_CTRL_SUSPEND);

return err;
}

Linux$I^2C$设备驱动

$I^2C$设备驱动与$I^2C$适配器驱动实现类似,主要区别是$I^2C$适配器驱动需要挂接到$I^2C$总线上。如下是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
static int max7300_i2c_write(struct device *dev, unsigned int reg,
unsigned int val)
{
struct i2c_client *client = to_i2c_client(dev);

return i2c_smbus_write_byte_data(client, reg, val);
}

static int max7300_i2c_read(struct device *dev, unsigned int reg)
{
struct i2c_client *client = to_i2c_client(dev);

return i2c_smbus_read_byte_data(client, reg);
}

static int max7300_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct max7301 *ts;

if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;

ts = devm_kzalloc(&client->dev, sizeof(struct max7301), GFP_KERNEL);
if (!ts)
return -ENOMEM;

ts->read = max7300_i2c_read;
ts->write = max7300_i2c_write;
ts->dev = &client->dev;

return __max730x_probe(ts);
}

static int max7300_remove(struct i2c_client *client)
{
return __max730x_remove(&client->dev);
}

static const struct i2c_device_id max7300_id[] = {
{ "max7300", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, max7300_id);

static struct i2c_driver max7300_driver = {
.driver = {
.name = "max7300",
},
.probe = max7300_probe,
.remove = max7300_remove,
.id_table = max7300_id,
};

static int __init max7300_init(void)
{
return i2c_add_driver(&max7300_driver);
}
subsys_initcall(max7300_init);

static void __exit max7300_exit(void)
{
i2c_del_driver(&max7300_driver);
}
module_exit(max7300_exit);

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

文章标题:LinuxI2C

本文作者:红尘追风

发布时间:2016-07-13, 19:51:17

原始链接:http://www.micernel.com/2016/07/13/LinuxI2C/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录