目錄
- 一、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_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, 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/目錄下看到iic
if (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;
}
#endif
retval = i2c_add_driver(&dummy_driver);//dummy_driver是一個空的驅動
if (retval)
goto class_err;
return 0;
class_err:
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
bus_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_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
#endif
bus_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_client
char 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就是device
struct i2c_driver *driver;//i2c_driver就是driver
int 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_OF
client->dev.of_node = info->of_node;
#endif
dev_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)//匹配方式1
return 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_i2c0
struct 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_FREQ
struct 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_probe
i2c->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_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, 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.c
smdkc110_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_OF
struct device_node *of_node;
#endif
int 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//注冊adapter
i2c_register_adapter//(iic配接器,主機控制器)的
i2c_scan_static_board_info
i2c_new_device//注冊client即device
device_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_EARLYSUSPEND
ts->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_MONITOR
print_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);
#endif
print_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;
}
注:本資料大部分由朱老師物聯網大講堂課程筆記整理而來并且參考了部分他人博客的內容,如有侵權,聯系洗掉!水平有限,如有錯誤,歡迎各位在評論區交流,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/321433.html
標籤:其他
