I2c驱动小结
前段时间由于工作需要,使用atmeld20g18,cortex-m0,上面有I2C的控制器,atmel提供了SDK,开发速度很快,由于个人的好奇心,也顺便看了下linux下的I2C驱动框架,如下文所示,打个标记。
Linux下的I2C驱动主要的数据结构是围绕i2c_adapter,对于ARM上来说一个i2c控制器就对应一个adapter,如果一个控制器上有多个i2c设备则每个设备就是一个client,所以一个adapter可以对应多个client。
struct i2c_adapter {
……
const struct i2c_algorithm*algo; /* the algorithm to access the bus,I2C总线算法 */
struct device dev; /* the adapter device */
……
};
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
};
struct i2c_client {
unsigned short addr; /* chip address - NOTE: 7bit *///外部I2C设备的地址
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct device dev; /* the device structure */
};
一.字符设备驱动程序
1.1基本的数据结构
I2c属于字符设备,字符设备的操作函数如下:
staticconst struct file_operations i2cdev_fops = {
.owner =THIS_MODULE,
.llseek =no_llseek,
.read =i2cdev_read,
.write =i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open =i2cdev_open,
.release = i2cdev_release,
};
总线bus是i2c_bus_type,定义如下:
structbus_type i2c_bus_type = {
.name ="i2c",
.match =i2c_device_match,
.probe =i2c_device_probe,
.remove =i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
字符设备的注册
int __init i2c_dev_init(void)
{
res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
/* Bind to already existing adapters right away */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);//遍历所有的adapter,即有多个I2C控制器
}
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
adap = to_i2c_adapter(dev);
i2c_dev = get_free_i2c_dev(adap);
cdev_init(&i2c_dev->cdev, &i2cdev_fops);//注册字符设备,即字符设备的操作函数
i2c_dev->cdev.owner = THIS_MODULE;
res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);
//此处注意I2C设备的次设备号adap->nr, adap->nr的出处就是下文中提到的
// i2c->adap.nr = i2c->pdata->bus_num; 然后该adapter->nr通过idr_alloc注册到radix tree 里面去
// id = idr_alloc(&i2c_adapter_idr,adap, adap->nr, adap->nr + 1, GFP_KERNEL);
//i2cdev_open()函数根据次设备号找到adapter
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr), NULL,
"i2c-%d", adap->nr);
}
1.2设备的打开
I2C设备的打开:
static int i2cdev_open(struct inode *inode, struct file *file)
//inode可以根据设备节点找到
{
unsigned int minor = iminor(inode);
struct i2c_client *client;
struct i2c_adapter *adap;
adap = i2c_get_adapter(minor);//根据次设备号找到内存中的i2c_adapter
if (!adap)
return -ENODEV;
client = kzalloc(sizeof(*client),GFP_KERNEL);//分配设备的i2c_client
if (!client) {
i2c_put_adapter(adap);
return -ENOMEM;
}
snprintf(client->name,I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
client->adapter = adap;//连接client和i2c_adapter
file->private_data = client;//该值保存,被所有的file_operations共享。
return 0;
}
structi2c_adapter *i2c_get_adapter(int nr)
{
struct i2c_adapter *adapter;
mutex_lock(&core_lock);
adapter = idr_find(&i2c_adapter_idr,nr);//根据次设备号找到adapter, idr数据结构类似于一种map映射,根据索引找到adapter的值。
if (!adapter)
goto exit;
if (try_module_get(adapter->owner))
get_device(&adapter->dev);
else
adapter = NULL;
exit:
mutex_unlock(&core_lock);
return adapter;
}
1.3设备参数设置
staticlong i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
//该函数完成配置参数的作用,如连接的I2C设备的地址等。
{
struct i2c_client *client =file->private_data;
unsigned long funcs;
dev_dbg(&client->adapter->dev,"ioctl, cmd=0x%02x, arg=0x%02lx\n",
cmd, arg);
switch (cmd) {
case I2C_SLAVE:
case I2C_SLAVE_FORCE:
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == 0)&& arg > 0x7f))
return -EINVAL;
if (cmd == I2C_SLAVE&& i2cdev_check_addr(client->adapter, arg))
return -EBUSY;
client->addr = arg; //设备地址的配置
return 0;
case I2C_TENBIT:
if (arg)
client->flags |=I2C_M_TEN;
else
client->flags&= ~I2C_M_TEN;
return 0;
case I2C_PEC:
if (arg)
client->flags |=I2C_CLIENT_PEC;
else
client->flags&= ~I2C_CLIENT_PEC;
return 0;
case I2C_FUNCS:
funcs =i2c_get_functionality(client->adapter);
return put_user(funcs,(unsigned long __user *)arg);
case I2C_RDWR:
returni2cdev_ioctl_rdwr(client, arg);
case I2C_SMBUS:
return i2cdev_ioctl_smbus(client,arg);
case I2C_RETRIES:
client->adapter->retries= arg;
break;
case I2C_TIMEOUT:
client->adapter->timeout= msecs_to_jiffies(arg * 10);
break;
default:
return -ENOTTY;
}
return 0;
}
1.4设备数据读取
staticssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
loff_t *offset)
{
char *tmp;
int ret;
struct i2c_client *client =file->private_data; //该值在i2cdev_open()函数中被分配。
if (count > 8192)
count = 8192;
tmp = kmalloc(count, GFP_KERNEL);
if (tmp == NULL)
return -ENOMEM;
pr_debug("i2c-dev: i2c-%d reading%zu bytes.\n",
iminor(file_inode(file)),count);
ret = i2c_master_recv(client, tmp,count);//内部转换到adapter的i2c_algorithm *algo
if (ret >= 0)
ret = copy_to_user(buf, tmp,count) ? -EFAULT : ret;
kfree(tmp);
return ret;
}
二.设备及驱动的注册
I2c控制器集成在SOC芯片内部,因此使用SOC内部I2C控制器,是作为平台设备注册的。
staticint __init i2c_init(void)
{
int retval;
…………
retval =bus_register(&i2c_bus_type);//注册总线
if (retval)
return retval;
…………
}
structplatform_device s3c_device_i2c = {
.name = "s3c2410-i2c",
.id = -1,
.num_resources =ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
staticstruct platform_driver s3c24xx_i2c_driver = {
.probe =s3c24xx_i2c_probe,
.remove =s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver ={
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
.of_match_table =of_match_ptr(s3c24xx_i2c_match),
},
};
staticint s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata =NULL;
struct resource *res;
int ret;
if (!pdev->dev.of_node) {
pdata =dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(&pdev->dev,"no platform data\n");
return -EINVAL;
}
}
i2c = devm_kzalloc(&pdev->dev,sizeof(struct s3c24xx_i2c), GFP_KERNEL);
//分配s3c24xx_i2c,内部包含了i2c_adapter
if (!i2c)
return -ENOMEM;
i2c->pdata =devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!i2c->pdata)
return -ENOMEM;
i2c->quirks = s3c24xx_get_device_quirks(pdev);
i2c->sysreg = ERR_PTR(-ENOENT);
if (pdata)
memcpy(i2c->pdata, pdata,sizeof(*pdata));
else
s3c24xx_i2c_parse_dt(pdev->dev.of_node,i2c);
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_DEPRECATED;
i2c->tx_setup = 50;
init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
i2c->dev = &pdev->dev;
i2c->clk =devm_clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev,"cannot get clock\n");
return -ENOENT;
}
dev_dbg(&pdev->dev, "clocksource %p\n", i2c->clk);
/* map the registers */
res = platform_get_resource(pdev,IORESOURCE_MEM, 0);
i2c->regs =devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(i2c->regs))
return PTR_ERR(i2c->regs);
dev_dbg(&pdev->dev,"registers %p (%p)\n",
i2c->regs, res);
/* setup info block for the i2c core */
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent =&pdev->dev;
i2c->pctrl =devm_pinctrl_get_select_default(i2c->dev);
/* inititalise the i2c gpio lines */
if (i2c->pdata->cfg_gpio)
i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));
else if (IS_ERR(i2c->pctrl)&& s3c24xx_i2c_parse_dt_gpio(i2c))
return -EINVAL;
/* initialise the i2c controller */
ret = clk_prepare_enable(i2c->clk);
if (ret) {
dev_err(&pdev->dev,"I2C clock enable failed\n");
return ret;
}
ret = s3c24xx_i2c_init(i2c); //初始化设备
clk_disable(i2c->clk);
if (ret != 0) {
dev_err(&pdev->dev,"I2C controller init failed\n");
clk_unprepare(i2c->clk);
return ret;
}
if (!(i2c->quirks & QUIRK_POLL)){
i2c->irq = ret =platform_get_irq(pdev, 0);
if (ret <= 0) {
dev_err(&pdev->dev,"cannot find IRQ\n");
clk_unprepare(i2c->clk);
return ret;
}
ret =devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq,
0, dev_name(&pdev->dev), i2c);
if (ret != 0) {
dev_err(&pdev->dev,"cannot claim IRQ %d\n", i2c->irq);
clk_unprepare(i2c->clk);
return ret;
}
}
ret =s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0) {
dev_err(&pdev->dev,"failed to register cpufreq notifier\n");
clk_unprepare(i2c->clk);
return ret;
}
i2c->adap.nr =i2c->pdata->bus_num;
///这个bus_num,如果bus_num=-1, 会在下面的i2c_add_numbered_adapter()中动态分配,nr会通过idr_alloc注册到radix tree上
i2c->adap.dev.of_node =pdev->dev.of_node;
platform_set_drvdata(pdev, i2c);
pm_runtime_enable(&pdev->dev);
ret =i2c_add_numbered_adapter(&i2c->adap);
if (ret < 0) {
pm_runtime_disable(&pdev->dev);
s3c24xx_i2c_deregister_cpufreq(i2c);
clk_unprepare(i2c->clk);
return ret;
}
dev_info(&pdev->dev, "%s:S3C I2C adapter\n", dev_name(&i2c->adap.dev));
return 0;
}
inti2c_add_numbered_adapter(struct i2c_adapter *adap)
{
if (adap->nr == -1) /* -1 meansdynamically assign bus id *///此处即上文提到的动态分配adap->nr ,
return i2c_add_adapter(adap);
return__i2c_add_numbered_adapter(adap);
}
staticint __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
int id;
mutex_lock(&core_lock);
id = idr_alloc(&i2c_adapter_idr,adap, adap->nr, adap->nr + 1, GFP_KERNEL);
//前文i2c_get_adapter(intnr)中根据次设备号找到的adapter是从这边注册的, adap->nr会被注册为i2c设备的次设备号;
mutex_unlock(&core_lock);
if (WARN(id < 0, "couldn't getidr"))
return id == -ENOSPC ? -EBUSY: id;
return i2c_register_adapter(adap); //注册adapter
}
static int i2c_register_adapter(struct i2c_adapter *adap)
{
…………
dev_set_name(&adap->dev,"i2c-%d", adap->nr);
adap->dev.bus= &i2c_bus_type;
adap->dev.type= &i2c_adapter_type;
res =device_register(&adap->dev); //注册设备
if (res) {
pr_err("adapter '%s':can't register device (%d)\n", adap->name, res);
goto out_list;
}
…………
}