add: 04_I2C/11_I2C_Adapter驱动框架讲解与编写
181
IMX6ULL/doc_pic/04_I2C/11_I2C_Adapter驱动框架讲解与编写.md
Normal file
@@ -0,0 +1,181 @@
|
||||
## I2C\_Adapter驱动框架讲解与编写
|
||||
|
||||
参考资料:
|
||||
|
||||
* Linux内核文档:
|
||||
* `Linux-4.9.88\Documentation\devicetree\bindings\i2c\i2c-gpio.txt`
|
||||
* `Linux-5.4\Documentation\devicetree\bindings\i2c\i2c-gpio.yaml`
|
||||
|
||||
* Linux内核驱动程序:使用GPIO模拟I2C
|
||||
|
||||
* `Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c`
|
||||
* `Linux-5.4\drivers\i2c\busses\i2c-gpio.c`
|
||||
|
||||
* Linux内核真正的I2C控制器驱动程序
|
||||
|
||||
* IMX6ULL: `Linux-4.9.88\drivers\i2c\busses\i2c-imx.c`
|
||||
* STM32MP157: `Linux-5.4\drivers\i2c\busses\i2c-stm32f7.c`
|
||||
* 本节视频的代码在GIT仓库里
|
||||
* IMX6ULL:`doc_and_source_for_drivers\IMX6ULL\source\04_I2C\05_i2c_adapter_framework`
|
||||
* STM32MP157:`doc_and_source_for_drivers\STM32MP157\source\A7\04_I2C\05_i2c_adapter_framework`
|
||||
|
||||
### 1. 回顾
|
||||
|
||||
#### 1.1 2C驱动程序的层次
|
||||
|
||||

|
||||
|
||||
#### 1.2 I2C总线-设备-驱动模型
|
||||
|
||||

|
||||
|
||||
### 2. I2C_Adapter驱动框架
|
||||
|
||||
#### 2.1 核心的结构体
|
||||
|
||||
##### 1. i2c_adapter
|
||||
|
||||

|
||||
|
||||
##### 2. i2c_algorithm
|
||||

|
||||
|
||||
* master_xfer:这是最重要的函数,它实现了一般的I2C传输,用来传输一个或多个i2c_msg
|
||||
|
||||
* master_xfer_atomic:
|
||||
|
||||
* 可选的函数,功能跟master_xfer一样,在`atomic context`环境下使用
|
||||
* 比如在关机之前、所有中断都关闭的情况下,用来访问电源管理芯片
|
||||
|
||||
* smbus_xfer:实现SMBus传输,如果不提供这个函数,SMBus传输会使用master_xfer来模拟
|
||||
|
||||
* smbus_xfer_atomic:
|
||||
|
||||
* 可选的函数,功能跟smbus_xfer一样,在`atomic context`环境下使用
|
||||
* 比如在关机之前、所有中断都关闭的情况下,用来访问电源管理芯片
|
||||
|
||||
* functionality:返回所支持的flags:各类I2C_FUNC_*
|
||||
|
||||
* reg_slave/unreg_slave:
|
||||
|
||||
* 有些I2C Adapter也可工作与Slave模式,用来实现或模拟一个I2C设备
|
||||
* reg_slave就是让把一个i2c_client注册到I2C Adapter,换句话说就是让这个I2C Adapter模拟该i2c_client
|
||||
* unreg_slave:反注册
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#### 2.2 驱动程序框架
|
||||
|
||||
分配、设置、注册一个i2c_adpater结构体:
|
||||
|
||||
* i2c_adpater的核心是i2c_algorithm
|
||||
* i2c_algorithm的核心是master_xfer函数
|
||||
|
||||
#### 1. 所涉及的函数
|
||||
|
||||
* 分配
|
||||
|
||||
```c
|
||||
struct i2c_adpater *adap = kzalloc(sizeof(struct i2c_adpater), GFP_KERNEL);
|
||||
```
|
||||
|
||||
* 设置
|
||||
|
||||
```c
|
||||
adap->owner = THIS_MODULE;
|
||||
adap->algo = &stm32f7_i2c_algo;
|
||||
```
|
||||
|
||||
* 注册:i2c_add_adapter/i2c_add_numbered_adapter
|
||||
|
||||
```c
|
||||
ret = i2c_add_adapter(adap); // 不管adap->nr原来是什么,都动态设置adap->nr
|
||||
ret = i2c_add_numbered_adapter(adap); // 如果adap->nr == -1 则动态分配nr; 否则使用该nr
|
||||
```
|
||||
|
||||
* 反注册
|
||||
|
||||
```c
|
||||
i2c_del_adapter(adap);
|
||||
```
|
||||
|
||||
#### 2. i2c_algorithm示例
|
||||
|
||||
* Linux-5.4中使用GPIO模拟I2C
|
||||

|
||||
|
||||
|
||||
|
||||
* Linux-5.4中STM32F157的I2C驱动
|
||||

|
||||
|
||||
|
||||
|
||||
* Linux-4.9.88中IMX6ULL的I2C驱动
|
||||

|
||||
|
||||
|
||||
|
||||
### 3. 编写一个框架程序
|
||||
|
||||
#### 3.1 设备树
|
||||
|
||||
在设备树里构造I2C Bus节点
|
||||
|
||||
|
||||
|
||||
#### 3.2 platform_driver
|
||||
|
||||
分配、设置、注册platform_driver结构体。
|
||||
|
||||
核心是probe函数,它要做这几件事:
|
||||
|
||||
* 根据设备树信息设置硬件(引脚、时钟等)
|
||||
* 分配、设置、注册i2c_apdater
|
||||
|
||||
#### 3.3 i2c_apdater
|
||||
|
||||
i2c_apdater核心是master_xfer函数,它的实现取决于硬件,大概代码如下:
|
||||
|
||||
```c
|
||||
static int xxx_master_xfer(struct i2c_adapter *adapter,
|
||||
struct i2c_msg *msgs, int num)
|
||||
{
|
||||
for (i = 0; i < num; i++) {
|
||||
struct i2c_msg *msg = msgs[i];
|
||||
{
|
||||
// 1. 发出S信号: 设置寄存器发出S信号
|
||||
CTLREG = S;
|
||||
|
||||
// 2. 根据Flag发出设备地址和R/W位: 把这8位数据写入某个DATAREG即可发出信号
|
||||
// 判断是否有ACK
|
||||
|
||||
if (!ACK)
|
||||
return ERROR;
|
||||
else {
|
||||
// 3. read / write
|
||||
if (read) {
|
||||
STATUS = XXX; // 这决定读到一个数据后是否发出ACK给对方
|
||||
val = DATAREG; // 这会发起I2C读操作
|
||||
} else if(write) {
|
||||
DATAREG = val; // 这会发起I2C写操作
|
||||
val = STATUS; // 判断是否收到ACK
|
||||
if (!ACK)
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
// 4. 发出P信号
|
||||
CTLREG = P;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
IMX6ULL/doc_pic/04_I2C/11_I2C_Adapter驱动框架讲解与编写.tif
Normal file
BIN
IMX6ULL/doc_pic/04_I2C/pic/04_I2C/058_i2c_algorithm.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
IMX6ULL/doc_pic/04_I2C/pic/04_I2C/059_i2c_bit_algo.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
IMX6ULL/doc_pic/04_I2C/pic/04_I2C/060_stm32f7_i2c_algo.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
IMX6ULL/doc_pic/04_I2C/pic/04_I2C/061_i2c_imx_algo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,8 @@
|
||||
|
||||
/ {
|
||||
i2c-bus-virtual {
|
||||
compatible = "100ask,i2c-bus-virtual";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
104
IMX6ULL/source/04_I2C/05_i2c_adapter_framework/i2c_adapter_drv.c
Normal file
@@ -0,0 +1,104 @@
|
||||
#include <linux/completion.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c-algo-bit.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_data/i2c-gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static struct i2c_adapter *g_adapter;
|
||||
|
||||
static int i2c_bus_virtual_master_xfer(struct i2c_adapter *i2c_adap,
|
||||
struct i2c_msg msgs[], int num)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num; i++)
|
||||
{
|
||||
// do transfer msgs[i];
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
static u32 i2c_bus_virtual_func(struct i2c_adapter *adap)
|
||||
{
|
||||
return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL |
|
||||
I2C_FUNC_SMBUS_READ_BLOCK_DATA |
|
||||
I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
|
||||
I2C_FUNC_PROTOCOL_MANGLING;
|
||||
}
|
||||
|
||||
|
||||
const struct i2c_algorithm i2c_bus_virtual_algo = {
|
||||
.master_xfer = i2c_bus_virtual_master_xfer,
|
||||
.functionality = i2c_bus_virtual_func,
|
||||
};
|
||||
|
||||
|
||||
static int i2c_bus_virtual_probe(struct platform_device *pdev)
|
||||
{
|
||||
/* get info from device tree, to set i2c_adapter/hardware */
|
||||
|
||||
/* alloc, set, register i2c_adapter */
|
||||
g_adapter = kzalloc(sizeof(*g_adapter), GFP_KERNEL);
|
||||
|
||||
g_adapter->owner = THIS_MODULE;
|
||||
g_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
|
||||
g_adapter->nr = -1;
|
||||
snprintf(g_adapter->name, sizeof(g_adapter->name), "i2c-bus-virtual");
|
||||
|
||||
g_adapter->algo = &i2c_bus_virtual_algo;
|
||||
|
||||
i2c_add_adapter(g_adapter); // i2c_add_numbered_adapter(g_adapter);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2c_bus_virtual_remove(struct platform_device *pdev)
|
||||
{
|
||||
i2c_del_adapter(g_adapter);
|
||||
return 0;
|
||||
}
|
||||
static const struct of_device_id i2c_bus_virtual_dt_ids[] = {
|
||||
{ .compatible = "100ask,i2c-bus-virtual", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static struct platform_driver i2c_bus_virtual_driver = {
|
||||
.driver = {
|
||||
.name = "i2c-gpio",
|
||||
.of_match_table = of_match_ptr(i2c_bus_virtual_dt_ids),
|
||||
},
|
||||
.probe = i2c_bus_virtual_probe,
|
||||
.remove = i2c_bus_virtual_remove,
|
||||
};
|
||||
|
||||
|
||||
static int __init i2c_bus_virtual_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = platform_driver_register(&i2c_bus_virtual_driver);
|
||||
if (ret)
|
||||
printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(i2c_bus_virtual_init);
|
||||
|
||||
static void __exit i2c_bus_virtual_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&i2c_bus_virtual_driver);
|
||||
}
|
||||
module_exit(i2c_bus_virtual_exit);
|
||||
|
||||
MODULE_AUTHOR("www.100ask.net");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
@@ -117,6 +117,8 @@ git clone https://e.coding.net/weidongshan/doc_and_source_for_drivers.git
|
||||
|
||||
* 2021.03.01 发布"I2C系统":10_编写设备驱动之i2c_client
|
||||
|
||||
* 2021.03.05 发布"I2C系统":11_I2C_Adapter驱动框架讲解与编写
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
181
STM32MP157/doc_pic/04_I2C/11_I2C_Adapter驱动框架讲解与编写.md
Normal file
@@ -0,0 +1,181 @@
|
||||
## I2C\_Adapter驱动框架讲解与编写
|
||||
|
||||
参考资料:
|
||||
|
||||
* Linux内核文档:
|
||||
* `Linux-4.9.88\Documentation\devicetree\bindings\i2c\i2c-gpio.txt`
|
||||
* `Linux-5.4\Documentation\devicetree\bindings\i2c\i2c-gpio.yaml`
|
||||
|
||||
* Linux内核驱动程序:使用GPIO模拟I2C
|
||||
|
||||
* `Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c`
|
||||
* `Linux-5.4\drivers\i2c\busses\i2c-gpio.c`
|
||||
|
||||
* Linux内核真正的I2C控制器驱动程序
|
||||
|
||||
* IMX6ULL: `Linux-4.9.88\drivers\i2c\busses\i2c-imx.c`
|
||||
* STM32MP157: `Linux-5.4\drivers\i2c\busses\i2c-stm32f7.c`
|
||||
* 本节视频的代码在GIT仓库里
|
||||
* IMX6ULL:`doc_and_source_for_drivers\IMX6ULL\source\04_I2C\05_i2c_adapter_framework`
|
||||
* STM32MP157:`doc_and_source_for_drivers\STM32MP157\source\A7\04_I2C\05_i2c_adapter_framework`
|
||||
|
||||
### 1. 回顾
|
||||
|
||||
#### 1.1 2C驱动程序的层次
|
||||
|
||||

|
||||
|
||||
#### 1.2 I2C总线-设备-驱动模型
|
||||
|
||||

|
||||
|
||||
### 2. I2C_Adapter驱动框架
|
||||
|
||||
#### 2.1 核心的结构体
|
||||
|
||||
##### 1. i2c_adapter
|
||||
|
||||

|
||||
|
||||
##### 2. i2c_algorithm
|
||||

|
||||
|
||||
* master_xfer:这是最重要的函数,它实现了一般的I2C传输,用来传输一个或多个i2c_msg
|
||||
|
||||
* master_xfer_atomic:
|
||||
|
||||
* 可选的函数,功能跟master_xfer一样,在`atomic context`环境下使用
|
||||
* 比如在关机之前、所有中断都关闭的情况下,用来访问电源管理芯片
|
||||
|
||||
* smbus_xfer:实现SMBus传输,如果不提供这个函数,SMBus传输会使用master_xfer来模拟
|
||||
|
||||
* smbus_xfer_atomic:
|
||||
|
||||
* 可选的函数,功能跟smbus_xfer一样,在`atomic context`环境下使用
|
||||
* 比如在关机之前、所有中断都关闭的情况下,用来访问电源管理芯片
|
||||
|
||||
* functionality:返回所支持的flags:各类I2C_FUNC_*
|
||||
|
||||
* reg_slave/unreg_slave:
|
||||
|
||||
* 有些I2C Adapter也可工作与Slave模式,用来实现或模拟一个I2C设备
|
||||
* reg_slave就是让把一个i2c_client注册到I2C Adapter,换句话说就是让这个I2C Adapter模拟该i2c_client
|
||||
* unreg_slave:反注册
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#### 2.2 驱动程序框架
|
||||
|
||||
分配、设置、注册一个i2c_adpater结构体:
|
||||
|
||||
* i2c_adpater的核心是i2c_algorithm
|
||||
* i2c_algorithm的核心是master_xfer函数
|
||||
|
||||
#### 1. 所涉及的函数
|
||||
|
||||
* 分配
|
||||
|
||||
```c
|
||||
struct i2c_adpater *adap = kzalloc(sizeof(struct i2c_adpater), GFP_KERNEL);
|
||||
```
|
||||
|
||||
* 设置
|
||||
|
||||
```c
|
||||
adap->owner = THIS_MODULE;
|
||||
adap->algo = &stm32f7_i2c_algo;
|
||||
```
|
||||
|
||||
* 注册:i2c_add_adapter/i2c_add_numbered_adapter
|
||||
|
||||
```c
|
||||
ret = i2c_add_adapter(adap); // 不管adap->nr原来是什么,都动态设置adap->nr
|
||||
ret = i2c_add_numbered_adapter(adap); // 如果adap->nr == -1 则动态分配nr; 否则使用该nr
|
||||
```
|
||||
|
||||
* 反注册
|
||||
|
||||
```c
|
||||
i2c_del_adapter(adap);
|
||||
```
|
||||
|
||||
#### 2. i2c_algorithm示例
|
||||
|
||||
* Linux-5.4中使用GPIO模拟I2C
|
||||

|
||||
|
||||
|
||||
|
||||
* Linux-5.4中STM32F157的I2C驱动
|
||||

|
||||
|
||||
|
||||
|
||||
* Linux-4.9.88中IMX6ULL的I2C驱动
|
||||

|
||||
|
||||
|
||||
|
||||
### 3. 编写一个框架程序
|
||||
|
||||
#### 3.1 设备树
|
||||
|
||||
在设备树里构造I2C Bus节点
|
||||
|
||||
|
||||
|
||||
#### 3.2 platform_driver
|
||||
|
||||
分配、设置、注册platform_driver结构体。
|
||||
|
||||
核心是probe函数,它要做这几件事:
|
||||
|
||||
* 根据设备树信息设置硬件(引脚、时钟等)
|
||||
* 分配、设置、注册i2c_apdater
|
||||
|
||||
#### 3.3 i2c_apdater
|
||||
|
||||
i2c_apdater核心是master_xfer函数,它的实现取决于硬件,大概代码如下:
|
||||
|
||||
```c
|
||||
static int xxx_master_xfer(struct i2c_adapter *adapter,
|
||||
struct i2c_msg *msgs, int num)
|
||||
{
|
||||
for (i = 0; i < num; i++) {
|
||||
struct i2c_msg *msg = msgs[i];
|
||||
{
|
||||
// 1. 发出S信号: 设置寄存器发出S信号
|
||||
CTLREG = S;
|
||||
|
||||
// 2. 根据Flag发出设备地址和R/W位: 把这8位数据写入某个DATAREG即可发出信号
|
||||
// 判断是否有ACK
|
||||
|
||||
if (!ACK)
|
||||
return ERROR;
|
||||
else {
|
||||
// 3. read / write
|
||||
if (read) {
|
||||
STATUS = XXX; // 这决定读到一个数据后是否发出ACK给对方
|
||||
val = DATAREG; // 这会发起I2C读操作
|
||||
} else if(write) {
|
||||
DATAREG = val; // 这会发起I2C写操作
|
||||
val = STATUS; // 判断是否收到ACK
|
||||
if (!ACK)
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
// 4. 发出P信号
|
||||
CTLREG = P;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
STM32MP157/doc_pic/04_I2C/11_I2C_Adapter驱动框架讲解与编写.tif
Normal file
BIN
STM32MP157/doc_pic/04_I2C/pic/04_I2C/058_i2c_algorithm.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
STM32MP157/doc_pic/04_I2C/pic/04_I2C/059_i2c_bit_algo.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
STM32MP157/doc_pic/04_I2C/pic/04_I2C/060_stm32f7_i2c_algo.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
STM32MP157/doc_pic/04_I2C/pic/04_I2C/061_i2c_imx_algo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,8 @@
|
||||
|
||||
/ {
|
||||
i2c-bus-virtual {
|
||||
compatible = "100ask,i2c-bus-virtual";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#include <linux/completion.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c-algo-bit.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_data/i2c-gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static struct i2c_adapter *g_adapter;
|
||||
|
||||
static int i2c_bus_virtual_master_xfer(struct i2c_adapter *i2c_adap,
|
||||
struct i2c_msg msgs[], int num)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num; i++)
|
||||
{
|
||||
// do transfer msgs[i];
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
static u32 i2c_bus_virtual_func(struct i2c_adapter *adap)
|
||||
{
|
||||
return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL |
|
||||
I2C_FUNC_SMBUS_READ_BLOCK_DATA |
|
||||
I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
|
||||
I2C_FUNC_PROTOCOL_MANGLING;
|
||||
}
|
||||
|
||||
|
||||
const struct i2c_algorithm i2c_bus_virtual_algo = {
|
||||
.master_xfer = i2c_bus_virtual_master_xfer,
|
||||
.functionality = i2c_bus_virtual_func,
|
||||
};
|
||||
|
||||
|
||||
static int i2c_bus_virtual_probe(struct platform_device *pdev)
|
||||
{
|
||||
/* get info from device tree, to set i2c_adapter/hardware */
|
||||
|
||||
/* alloc, set, register i2c_adapter */
|
||||
g_adapter = kzalloc(sizeof(*g_adapter), GFP_KERNEL);
|
||||
|
||||
g_adapter->owner = THIS_MODULE;
|
||||
g_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
|
||||
g_adapter->nr = -1;
|
||||
snprintf(g_adapter->name, sizeof(g_adapter->name), "i2c-bus-virtual");
|
||||
|
||||
g_adapter->algo = &i2c_bus_virtual_algo;
|
||||
|
||||
i2c_add_adapter(g_adapter); // i2c_add_numbered_adapter(g_adapter);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2c_bus_virtual_remove(struct platform_device *pdev)
|
||||
{
|
||||
i2c_del_adapter(g_adapter);
|
||||
return 0;
|
||||
}
|
||||
static const struct of_device_id i2c_bus_virtual_dt_ids[] = {
|
||||
{ .compatible = "100ask,i2c-bus-virtual", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static struct platform_driver i2c_bus_virtual_driver = {
|
||||
.driver = {
|
||||
.name = "i2c-gpio",
|
||||
.of_match_table = of_match_ptr(i2c_bus_virtual_dt_ids),
|
||||
},
|
||||
.probe = i2c_bus_virtual_probe,
|
||||
.remove = i2c_bus_virtual_remove,
|
||||
};
|
||||
|
||||
|
||||
static int __init i2c_bus_virtual_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = platform_driver_register(&i2c_bus_virtual_driver);
|
||||
if (ret)
|
||||
printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(i2c_bus_virtual_init);
|
||||
|
||||
static void __exit i2c_bus_virtual_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&i2c_bus_virtual_driver);
|
||||
}
|
||||
module_exit(i2c_bus_virtual_exit);
|
||||
|
||||
MODULE_AUTHOR("www.100ask.net");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||