目录

  • 一、I2C基本原理
  • 二、linux内核的I2C子系统详解
    • 1、linux内核的I2C驱动框架总览
    • 2、I2C子系统的4个关键结构体(kernel/include/linux/i2c.h)
    • 3、关键文件
    • 4、i2c-core.c初步分析(从后向前看)
    • 5、I2C总线的匹配机制(i2c-core.c)
    • 6、核心层开放给其他部分的注册接口
    • 7、adapter模块的注册
    • 8、s3c24xx_i2c_probe函数分析
    • 9、i2c_algorithm
    • 10、i2c_driver的注册

一、I2C基本原理

(1)三根通信线:SCL、SDA、GND

(2)同步、串行、电平、低速、近距离

(3)总线式结构,支持多个设备挂接在同一条总线上

(4)主从式结构,通信双方必须一个为主(master)一个为从(slave),主设备掌握每次通信的主动权,从设备按照主设备的节奏被动响应。每个从设备在总线中(某条工作的总线上,并不是所有的总线上都是同一个地址)有唯一的地址(slave address),主设备通过从地址找到自己要通信的从设备(本质是广播的方式)。

  收发消息都是广播,总线上的设备都可收到,通过于自己的地址对比确认是否是给自己的信息

(5)I2C主要用途就是主SoC和外围设备之间的通信,最大优势是可以在总线上扩展多个外围设备的支持。常见的各种物联网传感器芯片(如gsensor、温度、湿度、光强度、酸碱度、烟雾浓度、压力等)均使用I2C接口和主SoC进行连接。

(6)电容触摸屏芯片的多个引脚构成2个接口一个接口是I2C的,负责和主SoC连接(本身作为从设备),主SoC通过该接口初始化及控制电容触摸屏芯片、芯片通过该接口向SoC汇报触摸事件的信息(触摸坐标等),我们使用电容触摸屏时重点关注的是这个接口;另一个接口是电容触摸板的管理接口,电容触摸屏芯片通过该接口来控制触摸板硬件。该接口是电容触摸屏公司关心的,他们的触摸屏芯片内部固件编程要处理这部分,我们使用电容触摸屏的人并不关心这里。

二、linux内核的I2C子系统详解

1、linux内核的I2C驱动框架总览

(1)I2C驱动框架的主要目标是:让驱动开发者可以在内核中方便的添加自己的I2C设备的驱动程序,从而可以更容易的在linux下驱动自己的I2C接口硬件

(2)源码中I2C相关的驱动均位于:drivers/i2c目录下。

(3)linux系统提供2种I2C驱动实现方法

  第一种叫i2c-dev,对应drivers/i2c/i2c-dev.c,这种方法只是封装了主机(I2C master,一般是SoC中内置的I2C控制器)的I2C基本操作,并且向应用层提供相应的操作接口,应用层代码需要自己去实现对slave的控制和操作,所以这种I2C驱动相当于只是提供给应用层可以访问slave硬件设备的接口,本身并未对硬件做任何操作,应用需要实现对硬件的操作(操控寄存器进行初始化等),因此写应用的人必须对硬件非常了解,其实相当于传统的驱动中干的活儿丢给应用去做了,所以这种I2C驱动又叫做**“应用层驱动”**,这种方式并不主流,它的优势是把差异化都放在应用中,这样在设备比较难缠(尤其是slave是非标准I2C时)时不用动驱动,而只需要修改应用就可以实现对各种设备的驱动。这种驱动在驱动层很简单(就是i2c-dev.c)我们就不分析了。

  第二种I2C驱动是所有的代码都放在驱动层实现,直接向应用层提供最终结果。应用层甚至不需要知道这里面有I2C存在,譬如电容式触摸屏驱动,直接向应用层提供/dev/input/event1的操作接口,应用层编程的人根本不知道event1中涉及到了I2C。这种是我们后续分析的重点。

2、I2C子系统的4个关键结构体(kernel/include/linux/i2c.h)

210有多个iic接口,每个接口由多个寄存器操控,代表了iic控制器
(1)struct i2c_adapter:用来描述主机的iic控制器(适配器),主控驱动,芯片换了这块代码就要变

struct i2c_adapter {struct module *owner;unsigned int id;unsigned int class;       /* classes to allow probing for */const struct i2c_algorithm *algo; /* the algorithm to access the bus *///通过函数指针可以使用不同的算法    void *algo_data;/* data fields that are valid for all devices   */struct rt_mutex bus_lock;int timeout;         /* in jiffies */int retries;struct device dev;      /* the adapter device */int nr;char name[48];struct completion dev_released;struct list_head userspace_clients;
};

(2)struct i2c_algorithm:用来描述I2C算法,即主从机的通信时序,其被包含在struct i2c_adapter中,同一个主控soc可以有不同的算法,比如从设备是一个标准的iic设备传感器,一个不是标准的iic设备

struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, or a negative value on error */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);/* To determine what the adapter supports */u32 (*functionality) (struct i2c_adapter *);
};

(3)struct i2c_client:描述I2C(从机)设备信息

struct i2c_client {unsigned short flags;     /* div., see below      */unsigned short addr;      /* chip address - NOTE: 7bit    *//* addresses are stored in the    *//* _LOWER_ 7 bits     */char name[I2C_NAME_SIZE];struct i2c_adapter *adapter; /* the adapter we sit on    */struct i2c_driver *driver;    /* and our access routines  */struct device dev;        /* the device structure     */int irq;          /* irq issued by device     */struct list_head detected;
};

(4)struct i2c_driver:描述I2C(从机)设备驱动

struct i2c_driver {unsigned int class;/* Notifies the driver that a new bus has appeared or is about to be* removed. You should avoid using this if you can, it will probably* be removed in a near future.*/int (*attach_adapter)(struct i2c_adapter *);int (*detach_adapter)(struct i2c_adapter *);/* Standard driver model interfaces */int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);/* driver model interfaces that don't relate to enumeration  */void (*shutdown)(struct i2c_client *);int (*suspend)(struct i2c_client *, pm_message_t mesg);int (*resume)(struct i2c_client *);/* Alert callback, for example for the SMBus alert protocol.* The format and meaning of the data value depends on the protocol.* For the SMBus alert protocol, there is a single bit of data passed* as the alert response's low bit ("event flag").*/void (*alert)(struct i2c_client *, unsigned int data);/* a ioctl like command that can be used to perform specific functions* with the device.*/int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);struct device_driver driver;const struct i2c_device_id *id_table;/* Device detection callback for automatic device creation */int (*detect)(struct i2c_client *, struct i2c_board_info *);const unsigned short *address_list;struct list_head clients;
};

  i2c_driver 与 i2c_client在驱动中会进行匹配,当二者匹配成功时,i2c_client就会将自己的硬件信息交给i2c_driver,类似于平台总线的驱动和设备进行匹配。

3、关键文件

(1)kernel/drivers/i2c/i2c-core.c
  iic核心文件,属于内核开发者实现的那部分,与具体硬件无关,属于纯软件。但其内部间接性地调用了许多和硬件操作相关的函数,通过结构体与函数指针实现。

(2)busses目录(kernel/drivers/i2c/)
  放了许多主控芯片的iic控制器相关程序,我们要关注的是i2c-s3c2410.c,2410和210的iic控制器这部分的实现相同

(3)kernel/drivers/i2c/algos,实现的一些算法操作,我们暂时不需要去关注

(4)此外还会涉及到mach-x210.c、kernel/drivers/i2c/i2c-boardinfo.c

4、i2c-core.c初步分析(从后向前看)

(1)smbus代码略过我们涉及不到(1534行之后的代码)
 emsp;其是应用于移动PC和桌面PC系统中的低速率通讯。希望通过一条廉价并且功能强大的总线(由两条线组成),来控制主板上的设备并收集相应的信息。

(2)1179行代码

postcore_initcall(i2c_init);
module_exit(i2c_exit);

总线在内核中也是一个模块,是需要去注册的。

struct bus_type i2c_bus_type = {.name       = "i2c",.match       = i2c_device_match,//用于driver和device进行匹配.probe     = i2c_device_probe,//当driver和device匹配上之后就会执行该函数.remove     = i2c_device_remove,.shutdown  = i2c_device_shutdown,.pm      = &i2c_device_pm_ops,
};
static int __init i2c_init(void)
{int retval;retval = bus_register(&i2c_bus_type);//注册iic,注册后就可以在/sys/bus/目录下看到iicif (retval)return retval;
#ifdef CONFIG_I2C_COMPAT//这个宏应该是没有的i2c_adapter_compat_class = class_compat_register("i2c-adapter");if (!i2c_adapter_compat_class) {retval = -ENOMEM;goto bus_err;}
#endifretval = i2c_add_driver(&dummy_driver);//dummy_driver是一个空的驱动if (retval)goto class_err;return 0;class_err:
#ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endifbus_unregister(&i2c_bus_type);return retval;
}

当新注册driver/device和device/driver匹配上后就会执行XX_probe函数进行初始化,否则就没有驱动。

static void __exit i2c_exit(void)
{i2c_del_driver(&dummy_driver);
#ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class);
#endifbus_unregister(&i2c_bus_type);
}

5、I2C总线的匹配机制(i2c-core.c)

(1)总线的match函数i2c_device_match

#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)static int i2c_device_match(struct device *dev, struct device_driver *drv)
{struct i2c_client  *client = i2c_verify_client(dev);struct i2c_driver *driver;if (!client)return 0;driver = to_i2c_driver(drv);//由结构体成员得到结构体/* match on an id table if there is one */if (driver->id_table)return i2c_match_id(driver->id_table, client) != NULL;return 0;
}
变量成员组成及类型解析:driver->id_table//一个数组
struct i2c_driver   *driver;const struct i2c_device_id *id_table;struct i2c_device_id {char name[I2C_NAME_SIZE];kernel_ulong_t driver_data  /* Data private to the driver */__attribute__((aligned(sizeof(kernel_ulong_t))));};变量成员组成及类型解析:client
struct i2c_clientchar name[I2C_NAME_SIZE];
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)//字符串比较进行driver和device匹配return id;id++;}return NULL;
}

  让device和driver进行匹配,不同的总线有它自己的match函数。一般都是通过名字进行匹配的,当二者匹配上时会执行i2c_device_probe中的。总线的probe函数会去调driver的probe函数。

(2)总线的probe函数i2c_device_probe

static int i2c_device_probe(struct device *dev)
{struct i2c_client  *client = i2c_verify_client(dev);//i2c_client就是devicestruct i2c_driver *driver;//i2c_driver就是driverint status;if (!client)return 0;driver = to_i2c_driver(dev->driver);//找到驱动if (!driver->probe || !driver->id_table)return -ENODEV;client->driver = driver;if (!device_can_wakeup(&client->dev))device_init_wakeup(&client->dev,client->flags & I2C_CLIENT_WAKE);dev_dbg(dev, "probe\n");//当driver和device匹配上之后会去执行driver中的probe函数status = driver->probe(client, i2c_match_id(driver->id_table, client));if (status) {client->driver = NULL;i2c_set_clientdata(client, NULL);}return status;
}

总结:I2C总线上有2条分支:i2c_client链和i2c_driver链,当任何一个driver或者client去注册时,I2C总线都会调用match函数去对client.name和driver.id_table.name进行循环匹配。如果driver.id_table中所有的id都匹配不上则说明client并没有找到一个对应的driver,没了;如果匹配上了则标明client和driver是适用的,那么I2C总线会调用自身的probe函数,自身的probe函数又会调用driver中提供的probe函数,driver中的probe函数会对设备进行硬件初始化和后续工作。

6、核心层开放给其他部分的注册接口

(1)i2c_add_adapter/i2c_add_numbered_adapter:注册adapter(iic适配器,主机控制器)的

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);
}
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{int    id;int  status;if (adap->nr & ~MAX_ID_MASK)return -EINVAL;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;* we need the "equal to" result to force the result*/status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);if (status == 0 && id != adap->nr) {status = -EBUSY;idr_remove(&i2c_adapter_idr, id);}mutex_unlock(&core_lock);if (status == -EAGAIN)goto retry;if (status == 0)status = i2c_register_adapter(adap);return status;
}

(2)i2c_add_driver:注册driver的

static inline int i2c_add_driver(struct i2c_driver *driver)
{return i2c_register_driver(THIS_MODULE, driver);
}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, __process_new_driver);mutex_unlock(&core_lock);return 0;
}

(3)i2c_new_device:注册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 validity */status = i2c_check_client_addr_validity(client);if (status) {dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);goto out_err_silent;}/* Check for address business */status = i2c_check_addr_busy(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;
#ifdef CONFIG_OFclient->dev.of_node = info->of_node;
#endifdev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),client->addr);status = device_register(&client->dev);if (status)goto out_err;dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",client->name, dev_name(&client->dev));return client;out_err:dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x ""(%d)\n", client->name, client->addr, status);
out_err_silent:kfree(client);return NULL;
}

7、adapter模块的注册

kernel/drivers/i2c/busses/i2c-s3c2410.c
(1)平台总线方式注册

static int __init i2c_adap_s3c_init(void)
{return platform_driver_register(&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,},
};//kernel/drivers/base/platform.c
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)//匹配方式1return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);//匹配方式2,若无id table或//者id table未匹配上采用方式2
}
//数组中由两个元素,表明其支持两种soc
static struct platform_device_id s3c24xx_driver_ids[] = {{.name        = "s3c2410-i2c",.driver_data = TYPE_S3C2410,}, {.name       = "s3c2440-i2c",.driver_data = TYPE_S3C2440,}, { },/*若要支持新的芯片,在该数组中添加新元素,其内没有s5pv210,分析猜测应该是走了s3c2410或者s3c2440的路线从而该驱动支持s5pv210,如何确认,查看kernel/arch/arm/mach-s5pv210/mach-x210.c中的platform_device数组*/
};static struct platform_device *smdkc110_devices[] __initdata &s3c_device_i2c0struct platform_device s3c_device_i2c0 = {.name       = "s3c2410-i2c",.id          = 0,.num_resources     = ARRAY_SIZE(s3c_i2c_resource),.resource   = s3c_i2c_resource,};
static struct resource s3c_i2c_resource[] = { //iic控制器用到的一些资源[0] = {.start = S3C_PA_IIC,.end   = S3C_PA_IIC + SZ_4K - 1,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_IIC,.end   = IRQ_IIC,.flags = IORESOURCE_IRQ,},
};

(2)找到driver和device,并且确认其配对过程

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,},
};struct platform_device s3c_device_i2c0 = {.name        = "s3c2410-i2c",.id          = 0,.num_resources     = ARRAY_SIZE(s3c_i2c_resource),.resource   = s3c_i2c_resource,
};

这两个.name选一个与i2c_client中的.name进行匹配。匹配上后调用probe函数

8、s3c24xx_i2c_probe函数分析

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{struct s3c24xx_i2c *i2c;struct s3c2410_platform_i2c *pdata;struct resource *res;int ret;pdata = pdev->dev.platform_data;if (!pdata) {dev_err(&pdev->dev, "no platform data\n");return -EINVAL;}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;spin_lock_init(&i2c->lock);init_waitqueue_head(&i2c->wait);/* 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);//打开时钟,默认一般都是关闭的,省电/* 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);//申请中断,用于收发消息,//从而提高cpu的工作效率而非轮询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->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);clk_disable(i2c->clk);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;
}

(1)填充一个i2c_adapter结构体,并且调用接口去注册之\

struct s3c24xx_i2c {spinlock_t       lock;wait_queue_head_t  wait;unsigned int       suspended:1;struct i2c_msg      *msg;unsigned int       msg_num;unsigned int        msg_idx;unsigned int        msg_ptr;unsigned int        tx_setup;unsigned int       irq;enum s3c24xx_i2c_state  state;unsigned long     clkrate;void __iomem        *regs;struct clk        *clk;struct device      *dev;struct resource        *ioarea;struct i2c_adapter  adap;//该成员是我们所需要的#ifdef CONFIG_CPU_FREQstruct notifier_block    freq_transition;
#endif
};
//定义一个包含i2c_adapter结构体的结构体
struct s3c24xx_i2c *i2c;//进行填充
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;
pdata = pdev->dev.platform_data;
if (!pdata) {dev_err(&pdev->dev, "no platform data\n");return -EINVAL;
}
platform_data是我们从mach-x210.c文件中放进去的:
static void __init smdkc110_machine_init(void)/* i2c */s3c_i2c0_set_platdata(NULL);s3c_i2c1_set_platdata(NULL);s3c_i2c2_set_platdata(NULL);void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd){struct s3c2410_platform_i2c *npd;if (!pd)pd = &default_i2c_data0;npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);if (!npd)printk(KERN_ERR "%s: no memory for platform data\n", __func__);else if (!npd->cfg_gpio)npd->cfg_gpio = s3c_i2c0_cfg_gpio;s3c_device_i2c0.dev.platform_data = npd;}static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {.flags     = 0,.slave_addr    = 0x10,.frequency  = 400*1000,.sda_delay  = S3C2410_IICLC_SDA_DELAY15 | S3C2410_IICLC_FILTER_ON,
};

(2)从platform_device接收硬件信息,做必要的处理(request_mem_region & ioremap、request_irq等)

 //读出相关寄存器所对应的物理地址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;}

(3)对硬件做初始化(直接操作210内部I2C控制器的寄存器),iic控制器相关的寄存器很多都是8位的

ret = s3c24xx_i2c_init(i2c);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));/* write slave address */writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);//writeb操作8位的寄存器//打印调试信息,若定义了debug宏,则可以打印出信息dev_dbg(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);writel(iicon, i2c->regs + S3C2410_IICCON);//writel操作32位的寄存器/* we need to work out the divisors for the clock... */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_dbg(i2c->dev, "bus frequency set to %d KHz\n", freq);dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);return 0;
}

9、i2c_algorithm

函数调用思路:
s3c24xx_i2c_probei2c->adap.algo    = &s3c24xx_i2c_algorithm;
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {.master_xfer     = s3c24xx_i2c_xfer,//负责操作iic主控对外的一些数据传输.functionality      = s3c24xx_i2c_func,//功能描述
};
struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, or a negative value on error */int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);//smbus相关的         int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);/* To determine what the adapter supports */u32 (*functionality) (struct i2c_adapter *);//当前的iic支持的一下功能/*支持的功能去阅读kernel/include/linux/i2c.h文件,520行开始的代码,若想详细研究需阅读iic规范*/
};
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;int retry;int ret;clk_enable(i2c->clk);for (retry = 0; retry < adap->retries; retry++) {//重试,若一次未连接上,//会重复几次尝试连接ret = s3c24xx_i2c_doxfer(i2c, msgs, num);if (ret != -EAGAIN)goto out;dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);udelay(200);}ret = -EREMOTEIO;
out:clk_disable(i2c->clk);return ret;
}static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,struct i2c_msg *msgs, int num)
{unsigned long timeout;int ret;if (i2c->suspended)return -EIO;ret = s3c24xx_i2c_set_master(i2c);//将iic控制器设置为主机模式if (ret != 0) {dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);ret = -EAGAIN;goto out;}spin_lock_irq(&i2c->lock);i2c->msg     = msgs;//发送的消息i2c->msg_num = num;//发送的消息数量i2c->msg_ptr = 0;i2c->msg_idx = 0;i2c->state   = STATE_START;//当前iic在传输过程中处于那种状态s3c24xx_i2c_enable_irq(i2c);//打开中断s3c24xx_i2c_message_start(i2c, msgs);//向从机发送起始信号spin_unlock_irq(&i2c->lock);//进入休眠,让出CPU,主机等待从机的回复,因为从机的速度可能比较慢,同时也可提高//效率,其在iic申请(s3c24xx_i2c_probe函数中)的中断的中断处理程序s3c24xx_i2c_irq//中被唤醒timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);ret = i2c->msg_idx;/* having these next two as dev_err() makes life very* noisy when doing an i2cdetect */if (timeout == 0)dev_dbg(i2c->dev, "timeout\n");else if (ret != num)dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);/* ensure the stop has been through the bus */udelay(10);out:return ret;
}
static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{struct s3c24xx_i2c *i2c = dev_id;unsigned long status;unsigned long tmp;status = readl(i2c->regs + S3C2410_IICSTAT);//读取状态寄存器if (status & S3C2410_IICSTAT_ARBITR) {/* deal with arbitration loss */dev_err(i2c->dev, "deal with arbitration loss\n");}if (i2c->state == STATE_IDLE) {dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");tmp = readl(i2c->regs + S3C2410_IICCON);tmp &= ~S3C2410_IICCON_IRQPEND;writel(tmp, i2c->regs +  S3C2410_IICCON);goto out;}/* pretty much this leaves us with the fact that we've* transmitted or received whatever byte we last sent */i2c_s3c_irq_nextbyte(i2c, status);//在该函数中唤醒out:return IRQ_HANDLED;
}

10、i2c_driver的注册

(1)以gslX680触摸屏的驱动为例
使用文件链接:链接:https://pan.baidu.com/s/1rKDNBwC5vv_25b8gcsxpeg
提取码:x1ts --来自百度网盘超级会员V5的分享

(2)将驱动添加到内核SI项目中
  将gslX680.c、gslX680.h该触摸屏的驱动文件放在kernel/drivers/input/touchscreen

static int __init gsl_ts_init(void)
{int ret;print_info("==gsl_ts_init==\n");ret = i2c_add_driver(&gsl_ts_driver);print_info("ret=%d\n",ret);return ret;
}
static void __exit gsl_ts_exit(void)
{print_info("==gsl_ts_exit==\n");i2c_del_driver(&gsl_ts_driver);return;
}

(3)i2c_driver的基本分析:name和probe

static inline int i2c_add_driver(struct i2c_driver *driver)
{return i2c_register_driver(THIS_MODULE, driver);
}
static const struct i2c_device_id gsl_ts_id[] = {{GSLX680_I2C_NAME, 0},{}
};//使用其去进行driver和device的匹配
static struct i2c_driver gsl_ts_driver = {.driver = {.name = GSLX680_I2C_NAME,.owner = THIS_MODULE,},
#ifndef CONFIG_HAS_EARLYSUSPEND.suspend = gsl_ts_suspend,.resume   = gsl_ts_resume,
#endif.probe        = gsl_ts_probe,.remove     = __devexit_p(gsl_ts_remove),.id_table = gsl_ts_id,
};

进行匹配的主要是i2c-core.c中的i2c_device_match函数:
kernel/drivers/i2c/i2c-core.c65行

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;
直接选用了.id_table  = gsl_ts_id中的.name进行匹配

13、i2c_client从哪里来
(1)直接来源:i2c_register_board_info

kernel/arch/arm/mach-s5pv210/mach-x210.csmdkc110_machine_init  2090行附近i2c_register_board_info注册了三个:i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));

通过开发板原理图可知LCD使用了i2c1

kernel/arch/arm/mach-s5pv210/mach-x210.c  1626行
/* I2C1 */
static struct i2c_board_info i2c_devs1[] __initdata = {#ifdef CONFIG_VIDEO_TV20{I2C_BOARD_INFO("s5p_ddc", (0x74>>1)),},                            //i2c设备的从地址
#endif
};
i2c设备的硬件信息的封装:
struct i2c_board_info {char     type[I2C_NAME_SIZE];            // 设备名unsigned short    flags;                      // 属性unsigned short addr;                       // 设备从地址void        *platform_data;                 // 设备私有数据struct dev_archdata    *archdata;
#ifdef CONFIG_OFstruct device_node *of_node;
#endifint       irq;        // 设备使用的IRQ号,对应CPU的EINT(按下触摸//屏引发一个外部中断通知CPU有数据来了)
//这个irq与adapter的irq不同,这个是iic控制器收到iic消息产生的中断,
//而这里的这个是210引脚的外部中断,让从设备通知主设备210
};
int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
{int status;down_write(&__i2c_board_lock);/* dynamic bus numbers will be assigned after the last static one */if (busnum >= __i2c_first_dynamic_bus_num)__i2c_first_dynamic_bus_num = busnum + 1;for (status = 0; len; len--, info++) {struct i2c_devinfo  *devinfo;devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);if (!devinfo) {pr_debug("i2c-core: can't register boardinfo!\n");status = -ENOMEM;break;}devinfo->busnum = busnum;devinfo->board_info = *info;list_add_tail(&devinfo->list, &__i2c_board_list);}up_write(&__i2c_board_lock);return status;
}

(2)实现原理分析
  内核维护一个链表 __i2c_board_list,这个链表上链接的是I2C总线上挂接的所有硬件设备的信息结构体。也就是说这个链表维护的是一个struct i2c_board_info结构体链表。真正的需要的struct i2c_client在别的地方由__i2c_board_list链表中的各个节点内容来另外构建生成。

函数调用层次:
i2c_add_adapter/i2c_add_numbered_adapter//注册adapteri2c_register_adapter//(iic适配器,主机控制器)的i2c_scan_static_board_infoi2c_new_device//注册client即devicedevice_register

总结:I2C总线的i2c_client的提供是内核通过i2c_add_adapter/i2c_add_numbered_adapter接口调用时自动生成的,生成的原料是mach-x210.c中的i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));

(3)当gslx680的driver与device添加并匹配上后,最终目的就是:

static int __devinit gsl_ts_probe(struct i2c_client *client,const struct i2c_device_id *id)
{struct gsl_ts *ts;int rc;print_info("GSLX680 Enter %s\n", __func__);if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {dev_err(&client->dev, "I2C functionality not supported\n");return -ENODEV;}//检查是否支持i2c功能ts = kzalloc(sizeof(*ts), GFP_KERNEL);if (!ts)return -ENOMEM;print_info("==kzalloc success=\n");ts->client = client;i2c_set_clientdata(client, ts);ts->device_id = id->driver_data;rc = gslX680_ts_init(client, ts);if (rc < 0) {dev_err(&client->dev, "GSLX680 init failed\n");goto error_mutex_destroy;}   gsl_client = client;gslX680_init();init_chip(ts->client);check_mem_data(ts->client);rc=  request_irq(client->irq, gsl_ts_irq, IRQF_TRIGGER_RISING, client->name, ts);if (rc < 0) {print_info( "gsl_probe: request irq failed\n");goto error_req_irq_fail;}/* create debug attribute *///rc = device_create_file(&ts->input->dev, &dev_attr_debug_enable);#ifdef CONFIG_HAS_EARLYSUSPENDts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;//ts->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 1;ts->early_suspend.suspend = gsl_ts_early_suspend;ts->early_suspend.resume = gsl_ts_late_resume;register_early_suspend(&ts->early_suspend);
#endif#ifdef GSL_MONITORprint_info( "gsl_ts_probe () : queue gsl_monitor_workqueue\n");INIT_DELAYED_WORK(&gsl_monitor_work, gsl_monitor_worker);gsl_monitor_workqueue = create_singlethread_workqueue("gsl_monitor_workqueue");queue_delayed_work(gsl_monitor_workqueue, &gsl_monitor_work, 1000);
#endifprint_info("[GSLX680] End %s\n", __func__);return 0;//exit_set_irq_mode:
error_req_irq_fail:free_irq(ts->irq, ts);    error_mutex_destroy:input_free_device(ts->input);kfree(ts);return rc;
}

注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。

linux驱动之I2C子系统相关推荐

  1. Linux驱动分析——I2C子系统

    stm32mp157  盘古开发板  Linux内核版本4.19 目录 1.朱有鹏老师视频笔记 2.I2C子系统的4个关键结构体 3.关键文件 4.i2c-core.c初步分析 ​4.1.smbus代 ...

  2. 【Linux驱动】I2C子系统与触摸屏驱动

    由于学习触摸屏驱动涉及到中断以及I2C相关的知识,因此先介绍一下I2C的驱动框架. 触摸屏与I2C总线的关系 关于I2C的基础概念和原理参考我的这篇博客:[裸机]嵌入式相关问题汇总(二.I2C通信概念 ...

  3. linux驱动之i2c子系统mpu6050设备驱动

    以下是mpu6050简单的驱动实现,mpu6050是I2C接口的6轴传感器,可以作为字符设备注册到内核,本代码运行环境是3.4.2内核,4.3.2版本的编译链,12.04版本的Ubuntu,硬件环境是 ...

  4. linux i2c子系统看不懂啊,Linux 下的I2C子系统

    Linux 下的I2C子系统 2013.7.16 本文分为两部分,一.设备模型 二.平台相关 . ================================================ 第一 ...

  5. linux内核的I2C子系统详解1——I2C总线概览、驱动框架概览

    以下内容源于朱有鹏<物联网大讲堂>课程的学习,如有侵权,请告知删除. 1.I2C总线汇总概览 (1)三根通信线:SCL.SDA.GND: (2)同步.串行.电平.低速(几百k).近距离: ...

  6. 【linux驱动】USB子系统分析

    本文针对Linux内核下USB子系统进行分析,主要会涉及一下几个方面: USB基础知识:介绍USB设备相关的基础知识 Linux USB子系统分析:分析USB系统框架,USB HCD/ROOT HUB ...

  7. linux驱动之i2c学习

    最近在研究linux的i2c驱动,从最底层i2c控制器初始化到应用层与i2c设备交互基本打通了. 一.linux的i2c架构可以用下图表示: IIC适配器对应一条i2c总线,linux里面用i2c_a ...

  8. linux按键驱动中的结构体,linux 驱动之input子系统(gpio-keys)实现

    1.概述 Gpio-keys 是基于input子系统实现的一个通用按键驱动,该驱动也符合linux驱动实现模型,即driver和device分离模型.一般按键驱动,都是基于gpio-keys进行开发的 ...

  9. linux驱动之I2C

    include/linux/i2c.h struct i2c_msg; struct i2c_algorithm; struct i2c_adapter; struct i2c_client; str ...

最新文章

  1. php方行图片裁剪为圆形,如何将图片裁成圆形、矩形……各种形状?
  2. Python实例 -- .git格式转为.png格式
  3. 从PM到GAN——LSTM之父Schmidhuber横跨22年的怨念(文字版)
  4. head rush ajax chapter1 Ajax
  5. Java反梯形图案_梯形法求定积分(一)设计梯形法求积分的类模板,梯形法
  6. 哪里有2003系统服务器,服务器2003系统
  7. PHP 查找链表倒数第i个节点
  8. if函数判断单元格颜色_excel中的if函数,实现自动判断
  9. 【Flink】Discard registration from TaskExecutor SplitFetcher received unexpected exception
  10. LIS (nlogn)的算法
  11. redis 和 memcached的区别
  12. 切换至 root 身份
  13. java.util.concurrent.atomic.AtomicBoolean 源码
  14. 如何将PDF转换成可以直接编辑的CAD图纸
  15. 2021第十届小美赛-“认证杯”数学中国数学建模国际赛
  16. 什么软件硬盘测试修复最好,什么软件检测、修复硬盘坏道最好?
  17. 【Arduino】步进电机驱动程序Stepper_Motor_TB6600
  18. 中职 计算机 教案,中职计算机基础教案.doc
  19. 云平台、面向服务的体系结构和云编程
  20. 华人操作系统项目列表

热门文章

  1. 计算机仿真技术相关论文,计算机仿真技术论文.doc
  2. 【每日一题】031 神奇的6位数
  3. C++ Qt高仿QQ影音视频播放器 (三)
  4. 分子动力学(MD)模拟,模拟体系构建经验总结
  5. Java对接微信支付详解
  6. js中的 与 | 运算
  7. ASM AAM能用来做什么
  8. 大恒普信携手昇思推出眼健康AI智能分析系统,为眼科医疗行业数字化转型升级助力
  9. ### Error building SqlSession. ### The error may exist in com/itrs/mapper/UserMapper.xml ### Cause:
  10. 必看,国家公务员考试报考时间|选岗技巧|报名证件照要求