Linux 驱动框架---i2c驱动框架
i2c驱动在Linux通过一个周的学习后发现i2c总线的驱动框架还是和Linux整体的驱动框架是相同的,思想并不特殊比较复杂的内容如i2c核心的内容都是内核驱动框架实现完成的,今天我们暂时只分析驱动开发需要完成的部分。iic驱动驱动开发主要是完成四个部分的内容,struct i2c_adapter(适配器),struct i2c_algorithm (通讯接口),i2c_driver(设备驱动),struct i2c_client(iic设备),其中适配器和通讯接口的实现一般SOC厂商都会针对自家的i2c控制器IP实现在Linux 内核驱动中,也就是可以参考。即使没有驱动开发人员也可以自己实现主要就是按照框架需求实现主要的接口函数就可以了。所以这里需要先明确一点的就是I2C驱动是分为总线驱动和设备驱动的,设备在I2C架构中由i2c_client描述,用户空间也可以直接使用总线驱动完成和总线上的设备的数据交换只是有些设备的i2c操作时序很复杂,如果在用户空间来操作i2c设备效率和软件分层上也是不符合常用习惯的。所以I2C的拓扑图大就是总线驱动,设备驱动和设备,设备和设备驱动都是挂接在具体的总线上的这是由硬件决定的,有了设备驱动对i2c设备的操作就编程读写普通文件一样容易从而降低了用户控件的程序开发的复杂程度并提高了执行效率。
总线驱动
i2c_adapter 主要是用来抽像的描述i2c控制器的,结合其中algo通讯接口部分一起组成i2c总线驱动,其中适配器具体结构体如下:
struct i2c_adapter {
struct module *owner;
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 mutex userspace_clients_lock;
struct list_head userspace_clients; struct i2c_bus_recovery_info *bus_recovery_info;
};
其中的algo 指向适配对应到的具体的硬件的操作接口封装。除此之外其中还有一些属性如retries重试次数,class 保存i2c的一些特有的属性标志。总线驱动的适配器的部分内容比较简单基本就是一些软件部分内容的实现设置;要完成i2c总线驱动部分最主要的还是硬件通讯接口的实现,这一部分是和具体的硬件相关的所以也是驱动开发的重点。i2c总线驱动将硬件操作接口抽象封装如下:
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 *);
};
其中master_xfer接口就是i2c主机驱动传输数据的接口,包括收和发。其中smbus_xfer 用于smbus时序发送数据。因为i2c接口和smbus是相同的有些SOC的i2c外设控制器同时支持这两种时序发送数据。最后就是functionality 接口他会返回当前总线支持的通讯协议和特性。三星的实现如下,其中具体的标志的含义可以参考具体说明文档。I2C_FUNC_I2C 指示支持I2C时序,I2C_FUNC_SMBUS_EMUL 应该是支持smbus时序。
/* declare our i2c functionality */
static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART |
I2C_FUNC_PROTOCOL_MANGLING;
}
i2c总线驱动的的定义一步骤如下(前提是已经实现了接口封装中的各个通讯函数)
- 分配内存。
- 然后开始配置具体的成员变量。
- 在绑定具体的接口函数即algo。
最后就是总线驱动的注册接口了由i2c内核实现,完成向内核注册增加一个I2C总线适配器。
增加适配器
int i2c_add_adapter(struct i2c_adapter * adap);
int i2c_add_adapter(struct i2c_adapter *adapter)
{
struct device *dev = &adapter->dev;
int id;
//设备树的方式添加设备
if (dev->of_node) {
id = of_alias_get_id(dev->of_node, "i2c");
if (id >= 0) {
adapter->nr = id;
return __i2c_add_numbered_adapter(adapter);
}
}
//普通将设备写在板级文件中的方式
mutex_lock(&core_lock);
//分配一个总线ID 这个ID有总线驱动子系统维护
id = idr_alloc(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
mutex_unlock(&core_lock);
if (id < 0)
return id; adapter->nr = id;
//适配器的注册,前提适配器有id
return i2c_register_adapter(adapter);
}
整个执行流程基本分为如下的过程,如果未指定适配器id则先申请ID然后就是调用i2c_register_adapter
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = 0; /* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p))) {
res = -EAGAIN;
goto out_list;
} /* Sanity checks */
if (unlikely(adap->name[0] == '\0')) {
pr_err("i2c-core: Attempt to register an adapter with "
"no name!\n");
return -EINVAL;
}
if (unlikely(!adap->algo)) {
pr_err("i2c-core: Attempt to register adapter '%s' with "
"no algo!\n", adap->name);
return -EINVAL;
} rt_mutex_init(&adap->bus_lock);
//这里说明用户空间是可以直接使用总线设备 来操作总线上的设备的
mutex_init(&adap->userspace_clients_lock);
INIT_LIST_HEAD(&adap->userspace_clients);
//默认超时时间为一个系统心跳时间
/* Set default timeout to 1 second if not already set */
if (adap->timeout == 0)
adap->timeout = HZ;
//前面申请的Id,所以说ID是必须的
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
//i2c_bus_type i2c_adapter_type 都是内核实现的一些操作的封装
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
//和普通的设备注册相同,有个疑问就是驱动何时注册的?
res = device_register(&adap->dev);
if (res)
goto out_list; dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
//兼容性相关
#ifdef CONFIG_I2C_COMPAT
res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
adap->dev.parent);
if (res)
dev_warn(&adap->dev,
"Failed to create compatibility class link\n");
#endif
//总线恢复相关
/* bus recovery specific initialization */
if (adap->bus_recovery_info) {
struct i2c_bus_recovery_info *bri = adap->bus_recovery_info; if (!bri->recover_bus) {
dev_err(&adap->dev, "No recover_bus() found, not using recovery\n");
adap->bus_recovery_info = NULL;
goto exit_recovery;
} /* Generic GPIO recovery */
if (bri->recover_bus == i2c_generic_gpio_recovery) {
if (!gpio_is_valid(bri->scl_gpio)) {
dev_err(&adap->dev, "Invalid SCL gpio, not using recovery\n");
adap->bus_recovery_info = NULL;
goto exit_recovery;
} if (gpio_is_valid(bri->sda_gpio))
bri->get_sda = get_sda_gpio_value;
else
bri->get_sda = NULL; bri->get_scl = get_scl_gpio_value;
bri->set_scl = set_scl_gpio_value;
} else if (!bri->set_scl || !bri->get_scl) {
/* Generic SCL recovery */
dev_err(&adap->dev, "No {get|set}_gpio() found, not using recovery\n");
adap->bus_recovery_info = NULL;
}
}
添加的过程删除了异常处理的部分,执行过程大致就是先初始化适配器中的核心成员包括超时,设备链表,指定总线和设备类型后注册适配器设备。剩下的就是总线恢复的一些内容可以暂时不看。除此之外上面还发现一个问题就是添加总线适配器的过程发现不知道在何时添加的总线适配器驱动。根据前面对Linux设备注册过程的了解这里先看一下设备注册过程的绑定的总线type中的match接口:
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; /* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1; /* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1; 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;
}
发现他在不使用设备树的情况下是通过驱动的id_table来完成设备和驱动的匹配的。继续找最后找到如下的代码在drivers\i2c\i2c-core.c 中:
static int __init i2c_init(void)
{
int retval; retval = bus_register(&i2c_bus_type);
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);
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;
} /* We must initialize early, because some subsystems register i2c drivers
* in subsys_initcall() code, but are linked (and initialized) before i2c.
*/
postcore_initcall(i2c_init); #define i2c_add_driver(driver) \
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;
INIT_LIST_HEAD(&driver->clients); /* 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; /* Drivers should switch to dev_pm_ops instead. */
if (driver->suspend)
pr_warn("i2c-core: driver [%s] using legacy suspend method\n",
driver->driver.name);
if (driver->resume)
pr_warn("i2c-core: driver [%s] using legacy resume method\n",
driver->driver.name); pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
//如果设备先注册,则这里负责匹配
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver); return 0;
}
//设备注册过程的一个关键代码
static int __device_attach(struct device_driver *drv, void *data)
{
struct device *dev = data;
/* 如果总线有定义mach函数则 调用总线的mach函数 这个接口由总线提供 */
if (!driver_match_device(drv, dev))
return 0;
/* 前面的匹配未成功 进一步探测*/
return driver_probe_device(drv, dev);
}
上面的代码注册了一个虚拟的I2C设备,他的作用就是产生对应的总线设备如i2c-0,i2c-1等,也就完善了Linux的驱动框架的内容有了设备(I2C适配器)和驱动(dummy_driver)。综合前面的Linux设备注册流程可以知道,适配器设备注册时会先执行总线的match接口(红色部分代码)也就是上面的i2c_device_match
接口,再看底下虚拟驱动的定义可以知道总线的匹配会失败最终执行的就是驱动自己的probe接口这里就是driver_probe_device dummy_probe接口。
static const struct i2c_device_id dummy_id[] = {
{ "dummy", 0 },
{ },
}; static int dummy_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
return 0;
} static int dummy_remove(struct i2c_client *client)
{
return 0;
} static struct i2c_driver dummy_driver = {
.driver.name = "dummy",
.probe = dummy_probe,
.remove = dummy_remove,
.id_table = dummy_id,
};
删除适配器
void i2c_del_adapter(struct i2c_adapter * adap)
void i2c_del_adapter(struct i2c_adapter *adap)
{
struct i2c_adapter *found;
struct i2c_client *client, *next; /* First make sure that this adapter was ever added */
mutex_lock(&core_lock);
found = idr_find(&i2c_adapter_idr, adap->nr);
mutex_unlock(&core_lock);
if (found != adap) {
pr_debug("i2c-core: attempting to delete unregistered "
"adapter [%s]\n", adap->name);
return;
} acpi_i2c_remove_space_handler(adap);
/* Tell drivers about this removal */
mutex_lock(&core_lock);
bus_for_each_drv(&i2c_bus_type, NULL, adap,
__process_removed_adapter);
mutex_unlock(&core_lock); /* Remove devices instantiated from sysfs */
mutex_lock_nested(&adap->userspace_clients_lock,
i2c_adapter_depth(adap));
list_for_each_entry_safe(client, next, &adap->userspace_clients,
detected) {
dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name,
client->addr);
list_del(&client->detected);
i2c_unregister_device(client);
}
mutex_unlock(&adap->userspace_clients_lock); /* Detach any active clients. This can't fail, thus we do not
* check the returned value. This is a two-pass process, because
* we can't remove the dummy devices during the first pass: they
* could have been instantiated by real devices wishing to clean
* them up properly, so we give them a chance to do that first. */
device_for_each_child(&adap->dev, NULL, __unregister_client);
device_for_each_child(&adap->dev, NULL, __unregister_dummy); #ifdef CONFIG_I2C_COMPAT
class_compat_remove_link(i2c_adapter_compat_class, &adap->dev,
adap->dev.parent);
#endif /* device name is gone after device_unregister */
dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name); /* wait until all references to the device are gone
*
* FIXME: This is old code and should ideally be replaced by an
* alternative which results in decoupling the lifetime of the struct
* device from the i2c_adapter, like spi or netdev do. Any solution
* should be throughly tested with DEBUG_KOBJECT_RELEASE enabled!
*/
init_completion(&adap->dev_released);
device_unregister(&adap->dev);
wait_for_completion(&adap->dev_released); /* free bus id */
mutex_lock(&core_lock);
idr_remove(&i2c_adapter_idr, adap->nr);
mutex_unlock(&core_lock); /* Clear the device structure in case this adapter is ever going to be
added again */
memset(&adap->dev, 0, sizeof(adap->dev));
}
基本的执行过程和设备的删除类似,但是增加了驱动的卸载,因为有些驱动是绑定到到要删除的适配器上的,适配器已经删除了自然需要删除相关设备。剩下的两部分就是i2c具体的设备驱动和设备的相关内容了。
i2c设备驱动和设备
不通过具体的设备驱动也是可以访问i2c设备,但是应用层就需要处理i2c设备驱动本应该处理的内容,但是此时用户空间的代码的读写写逻辑的比较复杂需要考虑设备的读写时序的问题了,这样就不符合驱动的分层考虑了。不通过具体设备直接当问i2c总线上的设备需要使用i2c核心定义一个结构体来完成对i2c具体操作的定义那就是struct i2c_msg,因为通过上面的i2c总线驱动的通讯接口部分的int(master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num)接口可以看出来i2c的数据传输过程就是以struct i2c_msg为单位的,这个结构在用户空间也是可以使用的,所以用户可以直接通过总线完成和总线上挂接的设备进行数据交互(有条件的)。这里先解释正常情况下的设备操作方式---通过对应的设备驱动来操作设备,而设备驱动实际上最后也是调用的适配器的algo 句柄指向的master_xfer接口来实现数据传输。i2c设备的注册在系统的板级文件中实现通过struct i2c_board_info 结构进行定义然后通过i2c_register_board_info 进行注册需要注意的是设备的添加要遭遇驱动的注册,因为阅读源码发现设备添加时未进行驱动匹配代码如下
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;
}
然后就是struct i2c_board_info和struct i2c_msg的定义了如下:
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct acpi_dev_node acpi_node;
int irq;
};
这里可以参考后面的struct i2c_client 的结构进行分析。
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
很简单就三个成员,然后再来看i2c设备驱动的部分。
i2c设备驱动就是针对一个特定的设备定义一种操作接口由用户接口通过vfs接口调用从而完成对指定设备的读写操作,读写寄存器或存储空间的内容。他的定义如下
struct i2c_driver {
unsigned int class; /* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated; /* 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;
};
看起来是不是和设备驱动很相似,确实这也是linux内核驱动框架的抽象意义。如果熟悉前面的platform_driver的初始化和添加流程这里也是类似的。基本上也是用户完成一些接口的实现然后绑定到指定的驱动成员上最后调用驱动注册接口。如下这是我自己实现的At24cxx系列eeprom的i2c设备驱动。
static struct i2c_driver eerom_driver = {
.driver = {
.name = "at24xx",
},
.id_table = at24c0xx_idtable,
.probe = at24c0xx_probe,
.remove = at24c0xx_remove,
}; int __init eeprom_i2c_driver_init(void)
{
int retval; retval=i2c_add_driver(&eerom_driver);
if(retval)
{
printk(KERN_INFO "eeprom_i2c_driver_init fail\n");
}
return 0;
}
重点是驱动和设备的匹配操作这里和platform驱动和设备的match还是有一定的的差别的,通过i2c_add_driver 添加设备驱动到i2c总线,所有的i2c适配器都在同一个总线,由设备信息确定设备在哪一个适配器上。当驱动注册后会执行总线的match函数具体如下
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; /* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1; /* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1; 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;
}
通过代码可以发现匹配规则除了类似platform总线的设备树和acpi模式外只支持id_table的方式匹配设备。match完成匹配后就会执行i2c总线的探测函数探测驱动和设备。总线的probe函数最后还是会调用driver的probe接口完成设备的探测。
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status; if (!client)
return 0; driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV; if (!device_can_wakeup(&client->dev))
device_init_wakeup(&client->dev,
client->flags & I2C_CLIENT_WAKE);
dev_dbg(dev, "probe\n"); acpi_dev_pm_attach(&client->dev, true);
status = driver->probe(client, i2c_match_id(driver->id_table, client));
if (status)
acpi_dev_pm_detach(&client->dev, true); return status;
}
具体的probe接口就可以定义软件接口进行一系列的接口配置将用户的vfs接口映射到i2c总线的读写。下面拿我自己实现的一个eeprom的读写驱动接口实例来分析。主要看probe接口
#define BUFF_SIZE 64
#define EEPROM_PAGE_SIZE 8
#define LEN_GET(index,len) \
(((index+EEPROM_PAGE_SIZE)<(len))?(EEPROM_PAGE_SIZE):(len-index)) enum type
{
at24c01x,
at24c02x,
at24c04x,
at24c08x,
at24c16x,
}; enum mem_size
{
AT24C01=(1<<10),
AT24C02=(2<<10),
AT24C04=(4<<10),
AT24C08=(8<<10),
AT24C16=(16<<10),
}; struct eeprom_chip {
struct miscdevice devmisc;
struct i2c_client *client;
struct iic_ops *ops;
struct device *dev;
struct mutex eeprom_mutex;
char buf[BUFF_SIZE];
enum mem_size memsize;
uint8_t is_open;
kernel_ulong_t eeprom_type; }; struct iic_ops{
int (*send)(struct eeprom_chip *, size_t,short);
int (*recv)(struct eeprom_chip *, size_t,short);
};
probe接口的实现主要是通过增加中间层使用了杂项设备驱动,将vfs的读写接口传递到i2c总线上的读写接口从而实现读写eeprom就如同读写一个普通文件一样简单,从而简化了用户空间的调用逻辑。
static int at24c0xx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int rc;
struct eeprom_chip *eeprom;
struct device *dev = &client->dev;
/* check function*/
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -ENODEV;
eeprom = devm_kzalloc(&client->dev,sizeof(struct eeprom_chip), GFP_KERNEL);
if (eeprom == NULL) {
printk(KERN_INFO "failed to create our eeprom_data\n");
return -ENOMEM;
}
eeprom->eeprom_type = id->driver_data;
eeprom->dev=get_device(dev);
eeprom->client = client;
eeprom->ops = &iic_ops;
eeprom->devmisc.fops = &eeprom_ops;
eeprom->devmisc.minor = 223;
eeprom->devmisc.name=client->name;
eeprom->devmisc.parent = eeprom->dev;
eeprom->memsize = get_eeprom_memsize(eeprom->eeprom_type);
// printk(KERN_INFO "I2C:at24c0x:%d byte\n",eeprom->memsize);
mutex_init(&eeprom->eeprom_mutex);
rc = misc_register(&eeprom->devmisc);
if (rc) {
printk(KERN_INFO "%s client created misc_register Fail\n",client->name);
}
i2c_set_clientdata(client, eeprom);
/* rest of the initialisation goes here. */
// printk(KERN_INFO "%s client created\n",client->name); return 0;
}
全部驱动代码,设备就是Linux3-16-57版本内核的三星的smdkv210的板级文件添加的修改了一下兼容属性,从而使当前驱动可以匹配上。
1 #include <linux/module.h>
2 #include <linux/kernel.h>
3 #include <linux/device.h>
4 #include <linux/miscdevice.h>
5 #include <linux/delay.h>
6 #include <linux/errno.h>
7 #include <linux/gpio.h>
8 #include <linux/slab.h>
9 #include <linux/i2c.h>
10 #include <linux/init.h>
11 #include <linux/fcntl.h>
12 #include <linux/io.h>
13 #include <linux/fs.h>
14 #include <linux/types.h>
15 #include <linux/mutex.h>
16 #include <linux/completion.h>
17 #include <linux/hardirq.h>
18 #include <linux/irqflags.h>
19 #include <linux/rwsem.h>
20 #include <linux/pm_runtime.h>
21 #include <asm/uaccess.h>
22
23 #define BUFF_SIZE 64
24 #define EEPROM_PAGE_SIZE 8
25 #define LEN_GET(index,len) \
26 (((index+EEPROM_PAGE_SIZE)<(len))?(EEPROM_PAGE_SIZE):(len-index))
27
28 enum type
29 {
30 at24c01x,
31 at24c02x,
32 at24c04x,
33 at24c08x,
34 at24c16x,
35 };
36
37 enum mem_size
38 {
39 AT24C01=(1<<10),
40 AT24C02=(2<<10),
41 AT24C04=(4<<10),
42 AT24C08=(8<<10),
43 AT24C16=(16<<10),
44 };
45
46 struct eeprom_chip {
47 struct miscdevice devmisc;
48 struct i2c_client *client;
49 struct iic_ops *ops;
50 struct device *dev;
51 struct mutex eeprom_mutex;
52 char buf[BUFF_SIZE];
53 enum mem_size memsize;
54 uint8_t is_open;
55 kernel_ulong_t eeprom_type;
56
57 };
58
59 struct iic_ops{
60 int (*send)(struct eeprom_chip *, size_t,short);
61 int (*recv)(struct eeprom_chip *, size_t,short);
62 };
63
64 static enum mem_size get_eeprom_memsize(enum type type)
65 {
66 enum mem_size mem_size;
67 switch(type)
68 {
69 case at24c01x:
70 mem_size = AT24C01;
71 break;
72 case at24c02x:
73 mem_size = AT24C02;
74 break;
75 case at24c04x:
76 mem_size = AT24C04;
77 break;
78 case at24c08x:
79 mem_size = AT24C08;
80 break;
81 case at24c16x:
82 mem_size = AT24C16;
83 break;
84 default:
85 mem_size = AT24C01;
86 break;
87 }
88 return mem_size;
89 }
90
91 static short get_device_addr(struct eeprom_chip *chip,short pos)
92 {
93 short addr_dataaddr;
94 struct i2c_client *client = chip->client;
95 addr_dataaddr =client->addr;
96
97 if(chip->eeprom_type <= at24c04x)
98 {
99 addr_dataaddr =addr_dataaddr|(pos&0x100);
100 }else if (chip->eeprom_type == at24c08x){
101 addr_dataaddr =addr_dataaddr|(pos&0x300);
102 }else if (chip->eeprom_type == at24c16x){
103 addr_dataaddr =addr_dataaddr|(pos&0x700);
104 }
105 return addr_dataaddr;
106 }
107 static int i2c_eeprom_write(struct eeprom_chip *chip, size_t len,short pos)
108 {
109 char w_buf[10];
110 int status,i=0,ret=0;
111 short page_cnt,write_index=0;
112 short align_pos = ((pos+EEPROM_PAGE_SIZE-1)/8)*8;
113 struct i2c_client *client = chip->client;
114 struct i2c_msg msg={
115 .flags = I2C_M_NOSTART,
116 .buf = w_buf,
117 };
118 if(align_pos == pos){
119 page_cnt = ((len+EEPROM_PAGE_SIZE-1)/8);
120 }else{
121 page_cnt = 1+((len-(align_pos-pos)+EEPROM_PAGE_SIZE-1)/8);
122 }
123 for(i=0;i<page_cnt;i++)
124 {
125 if((align_pos != pos)&&!i){
126 msg.len = align_pos-pos+1;
127 }else{
128 msg.len = LEN_GET(write_index,len)+1;
129 }
130 msg.addr =get_device_addr(chip,pos+write_index);
131 w_buf[0] = pos+write_index;
132 memcpy(&w_buf[1],&chip->buf[write_index],msg.len);
133 write_index += msg.len-1;
134 // w_buf[msg.len] = '\0';
135 // printk(KERN_INFO "%02x\n",w_buf[0]);
136 // printk(KERN_INFO "%s\n",&w_buf[1]);
137 status = i2c_transfer(client->adapter,&msg,1);
138 if(status)
139 {
140 ret+=msg.len-1;
141 }
142 mdelay(1);
143 }
144 memset(chip->buf,0,BUFF_SIZE);
145 return ret;
146 }
147
148 static int i2c_eeprom_read(struct eeprom_chip *chip, size_t count,short pos)
149 {
150 int rc;
151 u8 data_addr = 0xFF&pos;
152 struct i2c_client *client = chip->client;
153
154 struct i2c_msg msg[]={
155 [0]={
156 .addr = get_device_addr(chip,pos),
157 .flags = client->flags & I2C_M_TEN,
158 .len = 1,
159 .buf = &data_addr,
160 },
161 [1]={
162 .addr = get_device_addr(chip,pos),
163 .flags = I2C_M_RD,
164 .len = count,
165 .buf = chip->buf,
166 },
167 };
168 rc= i2c_transfer(client->adapter,msg,2);
169 if(rc==2)
170 return count;
171 return 0;
172 }
173
174 static int eeprom_open(struct inode *inode,struct file *filp)
175 {
176 struct miscdevice *misc = filp->private_data;
177 struct eeprom_chip* chip = container_of(misc, struct eeprom_chip,
178 devmisc);
179 // printk(KERN_INFO "eeprom_open\n");
180 if(chip->is_open){
181 // printk(KERN_INFO "eeprom_open busy\n");
182 return -EBUSY;
183 }
184 filp->private_data = chip;
185 chip->is_open = 1;
186 return 0;
187 }
188
189 static ssize_t eeprom_read(struct file* filp,char* __user buf,size_t cnt,loff_t *offt)
190 {
191 int ret,count;
192 loff_t new_pos = *offt +cnt;
193 struct eeprom_chip* chip = filp->private_data;
194 // printk(KERN_INFO "eeprom_read\n");
195 if(new_pos>chip->memsize)
196 {
197 // printk(KERN_INFO "eeprom_open mem\n");
198 return -ENOMEM;
199 }
200 if(cnt > BUFF_SIZE)
201 {
202 // printk(KERN_INFO "eeprom_open mem\n");
203 return -ENOMEM;
204 }
205 // printk(KERN_INFO "position is %ld\n",(size_t)*offt);
206 mutex_lock(&chip->eeprom_mutex);
207 count=chip->ops->recv(chip,cnt, *offt);
208 // printk(KERN_INFO "chip->ops->recv return %d\n",count);
209 ret = copy_to_user((void*)buf,(void*)chip->buf,count);
210 if(ret){
211 return -EIO;
212 }
213 mutex_unlock(&chip->eeprom_mutex);
214 *offt += count;
215 return count;
216 }
217
218 static ssize_t eeprom_write(struct file* filp,const char*__user buf,size_t cnt ,loff_t *offt)
219 {
220 int ret;
221 loff_t new_pos = *offt +cnt;
222 struct eeprom_chip* chip = filp->private_data;
223 // printk(KERN_INFO "eeprom_write\n");
224 if(new_pos>chip->memsize)
225 {
226 // printk(KERN_INFO "eeprom_open mem\n");
227 return -ENOMEM;
228 }
229 if(cnt>BUFF_SIZE)
230 {
231 // printk(KERN_INFO "eeprom_open mem\n");
232 return -ENOMEM;
233 }
234 // printk(KERN_INFO "position is %ld\n",(size_t)*offt);
235 ret=copy_from_user((void*)chip->buf,(void*)buf,cnt);
236 if(ret){
237 return -EIO;
238 }
239 mutex_lock(&chip->eeprom_mutex);
240 ret=chip->ops->send(chip,cnt,*offt);
241 // printk(KERN_INFO "chip->ops-> send %d\n",ret);
242 mutex_unlock(&chip->eeprom_mutex);
243 *offt += ret;
244 return ret;
245 }
246
247 static loff_t eeprom_llseek(struct file *filp,loff_t vel,int orig )
248 {
249
250 struct eeprom_chip* chip = filp->private_data;
251 mutex_lock(&chip->eeprom_mutex);
252 switch (orig)
253 {
254 //cur
255 case 1:
256 if( (filp->f_pos + vel) < 0 || (filp->f_pos + vel)>chip->memsize)
257 return -ESPIPE;
258 filp->f_pos += vel;
259 /* code */
260 break;
261 //end
262 case 2:
263 if(vel > 0||(vel+filp->f_pos)<0)
264 return -ESPIPE;
265 filp->f_pos += vel;
266 /* code */
267 break;
268 //set
269 default:
270 if(vel>chip->memsize || vel<0)
271 return -ESPIPE;
272 filp->f_pos = vel;
273 break;
274 }
275 mutex_unlock(&chip->eeprom_mutex);
276 return 0;
277 }
278
279 static int eeprom_close(struct inode *inode,struct file *filp)
280 {
281
282 struct eeprom_chip* chip = filp->private_data;
283 filp->private_data = NULL;
284 // printk(KERN_INFO "eeprom_close\n");
285 chip->is_open = 0;
286
287 return 0;
288 }
289
290 struct file_operations eeprom_ops={
291 .owner = THIS_MODULE,
292 .open = eeprom_open,
293 .write = eeprom_write,
294 .read = eeprom_read,
295 .llseek = eeprom_llseek,
296 .release = eeprom_close ,
297 };
298
299 struct iic_ops iic_ops={
300 .recv = i2c_eeprom_read,
301 .send = i2c_eeprom_write,
302 };
303 static struct i2c_device_id at24c0xx_idtable[] = {
304 { "24c04x", at24c04x},
305 { "24c08x",at24c08x},
306 { "24c16x",at24c16x},
307 { }
308 };
309
310 static int at24c0xx_probe(struct i2c_client *client,
311 const struct i2c_device_id *id)
312 {
313 int rc;
314 struct eeprom_chip *eeprom;
315 struct device *dev = &client->dev;
316 /* check function*/
317 if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
318 return -ENODEV;
319 eeprom = devm_kzalloc(&client->dev,sizeof(struct eeprom_chip), GFP_KERNEL);
320 if (eeprom == NULL) {
321 printk(KERN_INFO "failed to create our eeprom_data\n");
322 return -ENOMEM;
323 }
324 eeprom->eeprom_type = id->driver_data;
325 eeprom->dev=get_device(dev);
326 eeprom->client = client;
327 eeprom->ops = &iic_ops;
328 eeprom->devmisc.fops = &eeprom_ops;
329 eeprom->devmisc.minor = 223;
330 eeprom->devmisc.name=client->name;
331 eeprom->devmisc.parent = eeprom->dev;
332 eeprom->memsize = get_eeprom_memsize(eeprom->eeprom_type);
333 // printk(KERN_INFO "I2C:at24c0x:%d byte\n",eeprom->memsize);
334 mutex_init(&eeprom->eeprom_mutex);
335 rc = misc_register(&eeprom->devmisc);
336 if (rc) {
337 printk(KERN_INFO "%s client created misc_register Fail\n",client->name);
338 }
339 i2c_set_clientdata(client, eeprom);
340 /* rest of the initialisation goes here. */
341 // printk(KERN_INFO "%s client created\n",client->name);
342
343 return 0;
344 }
345
346 static int at24c0xx_remove(struct i2c_client *client)
347 {
348 struct eeprom_chip *eeprom = i2c_get_clientdata(client);
349 struct device *dev = &client->dev;
350 misc_deregister(&eeprom->devmisc);
351 put_device(dev);
352 return 0;
353 }
354
355 static struct i2c_driver eerom_driver = {
356 .driver = {
357 .name = "at24xx",
358 },
359 .id_table = at24c0xx_idtable,
360 .probe = at24c0xx_probe,
361 .remove = at24c0xx_remove,
362 /* if device autodetection is needed: */
363 // .class = I2C_CLASS_SOMETHING,
364 // .detect = at24_detect,
365 // .address_list = normal_i2c,
366 };
367
368 int __init eeprom_i2c_driver_init(void)
369 {
370 int retval;
371
372 retval=i2c_add_driver(&eerom_driver);
373 if(retval)
374 {
375 printk(KERN_INFO "eeprom_i2c_driver_init fail\n");
376 }
377 // printk(KERN_INFO "i2c_add_driver eerom_driver\n");
378 return 0;
379 }
380
381 void __exit eeprom_i2c_driver_exit(void)
382 {
383 i2c_del_driver(&eerom_driver);
384 }
385
386 module_init(eeprom_i2c_driver_init);
387 module_exit(eeprom_i2c_driver_exit);
388
389 MODULE_LICENSE("GPL");
390 MODULE_DESCRIPTION("This is an general driver of atmel eeprom 24xx");
391 MODULE_VERSION("0.1");
392 MODULE_AUTHOR("smile");
i2c-at24c0x_driver.c
设备添加部分
static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
{ I2C_BOARD_INFO("24c08x", 0x50), }, /* Samsung S524AD0XD1 */
{ I2C_BOARD_INFO("wm8580", 0x1b), },
};
通过这一段的学习发现Linux驱动基本都是多元部分的组合实现,学习过程是一个拼积木的过程各个部分的小知识点的不断累计最后才能看明白内核中别人实现好的一个驱动,所以后面准备回头在去看看基础的部分,如内核内部的并发控制,同步异步IO的实现,阻塞和非阻塞、中断等一些基本但是非常通用的知识点内容从而巩固整体驱动开发知识体系的完善,加深Linux驱动框架的理解,从而在驱动开发上更加自由灵活也对应用的开发提供内核实现机制的深入理解基础。
2020-07-26 10:24:18
Linux 驱动框架---i2c驱动框架的更多相关文章
- Linux的操作系统I2C驱动架构解说
Linux的操作系统I2C驱动架构解说 发布时间:2006.10.16 04:52 来源:赛迪网技术社区 作者:LoneStar 最近因为工作需要涉及到了I2C总线.虽然我过去用过I2c,但看了 Li ...
- Linux内核调用I2C驱动_驱动嵌套驱动方法
禁止转载!!!! Linux内核调用I2C驱动_以MPU6050为例 0. 导语 最近一段时间都在恶补数据结构和C++,加上导师的事情比较多,Linux内核驱动的学习进程总是被阻碍.不过,十一假期终于 ...
- 【Linux高级驱动】I2C驱动框架分析
1.i2c-dev.c(i2c设备驱动组件层) 功能:1)给用户提供接口 i2c_dev_init //入口函数 /*申请主设备号*/ register_chrdev(I2C_MAJOR(), &q ...
- Linux驱动:I2C驱动编写要点
继续上一篇博文没讲完的内容“针对 RepStart 型i2c设备的驱动模型”,其中涉及的内容有:i2c_client 的注册.i2c_driver 的注册.驱动程序的编写. 一.i2c 设备的注册分析 ...
- linux内核中i2c驱动中slave模式接口的调用
1. 关注unreg_slave接口 1.1 这个接口在哪里被调用呢? 在drivers/i2c/i2c-core-slave.c中 int i2c_slave_unregister(struct i ...
- linux i2c驱动架构-dm368 i2c驱动分析
linux i2c驱动架构-dm368 i2c驱动分析 在阅读本文最好先熟悉一种i2c设备的驱动程序,并且浏览一下i2c-core.c以及芯片提供商的提供的i2c总线驱动(i2c-davinc ...
- 【驱动】linux下I2C驱动架构全面分析
I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...
- linux驱动学习(八) i2c驱动架构(史上最全) davinc dm368 i2c驱动分析【转】
转自:http://blog.csdn.net/ghostyu/article/details/8094049 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 预备知识 lin ...
- linux下I2C驱动架构全面分析【转】
本文转载自:http://blog.csdn.net/wangpengqi/article/details/17711165 I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一 ...
随机推荐
- pytorch——预测值转换为概率,单层感知机
softmax函数,可以将算出来的预测值转换成0-1之间的概率形式 导数的形式 import torch import torch.nn.functional as F x=torch.tensor( ...
- 并发条件队列之Condition 精讲
1. 条件队列的意义 Condition将Object监控器方法( wait , notify和notifyAll )分解为不同的对象,从而通过与任意Lock实现结合使用,从而使每个对象具有多个等待集 ...
- linux--关于JVM CPU资源占用过高的问题排查
一.背景: 先执行一个java程序里面开了两个线程分别都在while循环做打印操作. # java -cp ./test-threads.jar com.spiro.Main 二.现象: 通过top命 ...
- javax.servlet.ServletException: No adapter for handler
问题描述: 我的web.xml如下: <?xml version="1.0" encoding="UTF-8"?> <web-app xmln ...
- 自导自演的面试现场,趣学MySQL的10种文件
导读 Hi,大家好!我是白日梦!本文是MySQL专题的第 24 篇. 今天我要跟你分享的MySQL话题是:"自导自演的数据库面试现场--谈谈MySQL的10种文件" 换一种写作风格 ...
- 解决 minicom 不能接收键盘输入问题
今天突然minicom 不能接受键盘输入了.早上的时候在其他设备上不能识别usb转串口的设备,重新启动电脑后,恢复正常了.下午又出现minicom 不接收键盘输入. 百度了一下解决了. 解决方法 由于 ...
- 在Golang中如何正确地使用database/sql包访问数据库
本文记录了我在实际工作中关于数据库操作上一些小经验,也是新手入门golang时我认为一定会碰到问题,没有什么高大上的东西,所以希望能抛砖引玉,也算是对这个问题的一次总结. 其实我也是一个新手,机缘巧合 ...
- Java Socket实战之七 使用Socket通信传输文件
http://blog.csdn.net/kongxx/article/details/7319410 package com.googlecode.garbagecan.test.socket.ni ...
- 阿里云服务器centos7,docker部署mysql+Redis+vue+springboot+Nginx+fastdfs,亲测可用
一.购买云服务器 我是今年双十一期间在阿里云购买的服务器, 简单配置2核_4G_40G_3M,三年用了不到800块,不过当时我记得腾讯云更便宜,个人感觉,阿里的云服务器更加的稳定, 毕竟身经百战, 经 ...
- MSSQL 注入笔记
前置知识: 登录名:登录sql server服务器的用户,而不是操作"数据库用户名". 固定服务器角色:就是上面登录名所属的权限组.其中重要的就是"sysadmin&qu ...