您的位置:首页 > 其它

I2C驱动学习笔记

2011-09-08 09:36 239 查看
一、I2C基本知识

i2c总线是philips公司推出的一种串行总线。是具备多主机系统所需的,包括总线裁决和高低带器件同步功能的高性能串行总线。

i2c总线有两根双向信号线,一根是数据线SDA,另一根是时钟线SCL,两线都是开漏输出,要接上拉电阻(典型10K)。

3种数据传输模式:标准模式(100Kb/s),快速模式(400Kb/s),高速模式(3.4Mb/s)

每一个器件都有自己的地址。

工作模式:主发送,主接收,从发送,从接收。

数据格式:SDA线上的数据在SCL的高电平期间必须保持稳定,所心只SDA线上的数据只能在SCL线的低电平时改变。开始信号:SCL为高电平时,SDA线由高电平变为低电平。停止信号:SCL为高电平时,SDA线由低电平变为高电平。I2C总线中数据和地址都以字节为单位传送的,发送器每发送一个字节后,在时钟的第9个时钟脉冲期间释放总线,由从接收器发送一个ACK(SDA拉低)来确认数据传送成功。主机发送STOP来停止通信。主接收器接收到最后一个字节后发送NACK(SDA线为高电平),以确认接收完成。接着发送stop以停止通信。I2C分7位和10位两种寻址方式。通信时,发送的第一个字节是器件地址,无论是哪种寻址,第一个字节的最后一位为0,表示传送的是地址。10位寻址的前5位为11110。传送完地址后,从机会发送ack信号,然后主机再发送字节地址,从机发送ack信号。如果此时要写数据,则直接传送数据,如果为读数据,则要重新发送一次启动信号,和器件地址,但第一个字节变为1,之后再接到ack信号后,开始传送数据。也就是说读数据时,传送了两次设备地址。

二、I2C体系结构

Linux的i2c体系结构有3部分组成

1)I2C核心:提供了总线和设备驱动的注册和注销办法,I2C通信方法等。

2)I2C总线驱动:I2C硬件体系结构中适配器端的实现。主要包含两个数据结构i2c_adapter,algorithm和产生通信信号的函数。经过这一层的驱动代码,可以控制i2c适配器以主控的方式产生开始位、停止位,读写周期等。

3)I2C设备驱动:I2C硬件体系结构中设备端的实现。设备要挂接在受CPU控制的i2c适配器上,通过i2c适配器与CPU交换数据。

i2c设备驱动主要包含了数据结构i2c_driver和i2c_client。在linux2.6内核中,所有的i2c设备都在sysfs文件系统中显示。

4个重要的数据结构

struct i2c_adapter {

struct module *owner;

unsigned int id;

unsigned int class;

const struct i2c_algorithm *algo; /* the algorithm to access the bus */

void *algo_data;

/* --- administration stuff. */

int (*client_register)(struct i2c_client *);

int (*client_unregister)(struct i2c_client *);

/* data fields that are valid for all devices */

u8 level; /* nesting level for lockdep */

struct mutex bus_lock;

struct mutex clist_lock;

int timeout;

int retries;

struct device dev; /* the adapter device */

int nr;

struct list_head clients; /* DEPRECATED */

char name[48];

struct completion dev_released;

};

i2c_adapter对应于物理上的一个适配器

algo,总线通信方法结构体指针

algo_data,私有数据结构指针

client_register,client注册时调用

client_unregister,client注销时调用

retries,重试次数

dev,适配器设备

name,适配器名称

clien,client链表头

struct i2c_algorithm {

int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,

int num);

int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,

unsigned short flags, char read_write,

u8 command, int size, union i2c_smbus_data * data);

/* To determine what the adapter supports */

u32 (*functionality) (struct i2c_adapter *);

};

SMbus_xfer对应SMbus,它是I2C总线的一个子集。

一个i2c适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的周期访问。

master_xfer()用于产生i2c访问周期需要的信号,以i2c_msg为单位。

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_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */

#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 */

};

struct i2c_driver {

int id;

unsigned int class;

int (*attach_adapter)(struct i2c_adapter *);

int (*detach_adapter)(struct i2c_adapter *);

int (*detach_client)(struct i2c_client *);

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 *);

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 *, int kind, struct i2c_board_info *);

const struct i2c_client_address_data *address_data;

struct list_head clients;

};

i2c_driver对应一套驱动方法,id_table是该驱动所支持的i2c设备的ID表。

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 list; /* DEPRECATED */

struct list_head detected;

struct completion released;

};

addr,低7位芯片地址

name,设备名称

adapter,依附的i2c_adapter

driver,依附的i2c_driver

irq,设备使用的中断号

i2c_client,对应一个真实的物理设备。每一个i2c设备都需要一个i2c_client来描述。i2c_client信息通常在BSP板文件中通过i2c_board_info填充。

struct i2c_board_info {

char type[I2C_NAME_SIZE];

unsigned short flags;

unsigned short addr;

void *platform_data;

int irq;

};

在i2c总线驱动i2c_bus_type的mach()函数会调用i2c_match_id()函数匹配板文件定义的ID和i2c_driver所支持的ID表

i2c_client依附于i2c_adapter。由于一个适配器可以连接多个I2C设备,所以i2c_adpater也可以被多个i2c_client依附,i2c_adapter中包括依附于它的i2c_client的链表。

三、编写I2C驱动

有两方面工作:

1. 编写总线驱动
n 申请,初始化硬件资源
n i2c_adapter数据结构添加删除
n 提供适配器的algorithm的master_xfer指针
2. 编写设备驱动

n 填充i2c_driver结构,并实现其结构成员。

n 注册或注销i2c_driver

总线驱动,即适配器驱动作用,是将设备挂载到总线,实现设备与总线的交互。而设备自身的操作还是依赖于设备驱动。在总线驱动与设备驱动关联中还有一个i2c核心,提供了linux内核与i2c总线驱动,设备驱动之间联系的实现方法。

关于i2c核心中的几个重要操作函数

增加/删除i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adap);

int i2c_del_adapter(struct i2c_adapter *adap);

增加/删除i2c_driver

int i2c_register_driver(struct module *owner,struct i2c_driver *driver);

int i2c_del_driver(struct i2c_driver *driver);

inline int i2c_add_driver(struct i2c_driver *driver);

增加/删除i2c_client

int i2c_attach_client(struct i2c_client *client);

int i2c_detach_client(struct i2c_client *client);

I2C传输、发送和接收

int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);

int i2c_master_send(struct i2c_client *client,const char *buf,int count);

int i2c_master_recv(struct i2c_client *client,char *buf,int count);

实例

总线驱动(drivers/i2c/busses/i2c-pnx.c)

i2c适配器的加载与卸载

static int __init i2c_adap_pnx_init(void)

{

return platform_driver_register(&i2c_pnx_driver);

}

static void __exit i2c_adap_pnx_exit(void)

{

platform_driver_unregister(&i2c_pnx_driver);

}

i2c_pnx_driver的定义

static struct platform_driver i2c_pnx_driver = {

.driver = {

.name = "pnx-i2c",

.owner = THIS_MODULE,

},

.probe = i2c_pnx_probe,

.remove = __devexit_p(i2c_pnx_remove),

.suspend = i2c_pnx_controller_suspend,

.resume = i2c_pnx_controller_resume,

};

i2c总线驱动被平台驱动封装了一下,那么加载i2c总线时,应在probe方法里。

static int __devinit i2c_pnx_probe(struct platform_device *pdev)

{

unsigned long tmp;

int ret = 0;

/*以下是i2c_pnx_algo_data的定义,这些数据可以自定义

struct i2c_pnx_algo_data {

u32 base;

u32 ioaddr;

int irq;

struct i2c_pnx_mif mif;

int last;

}; */

struct i2c_pnx_algo_data *alg_data;

int freq_mhz;

/*以下是i2c_pnx_data的定义

struct i2c_pnx_data {

int (*suspend) (struct platform_device *pdev, pm_message_t state);

int (*resume) (struct platform_device *pdev);

u32 (*calculate_input_freq) (struct platform_device *pdev);

int (*set_clock_run) (struct platform_device *pdev);

int (*set_clock_stop) (struct platform_device *pdev);

struct i2c_adapter *adapter;

}; */

struct i2c_pnx_data *i2c_pnx = pdev->dev.platform_data;

struct i2c_client *client;

// printk("guojun i2c: %d, %s \n", pdev->id, i2c_pnx->adapter->name);

if (!i2c_pnx || !i2c_pnx->adapter) {

dev_err(&pdev->dev, "%s: no platform data supplied\n",

__func__);

ret = -EINVAL;

goto out;

}

platform_set_drvdata(pdev, i2c_pnx);

/*device结构体有两个指针成员driver_data和platform_data。此时是将platform_data的指针和driver_data的指针指向同一个位置。因为platform_data在BSP板上已经初始化过,所以此处的作用即初始化driver_data */

//总线设备驱动中申请硬件资源。

if (i2c_pnx->calculate_input_freq)

freq_mhz = i2c_pnx->calculate_input_freq(pdev);

/*根据pdev->id成员,获得设备的时钟*/

else {

freq_mhz = PNX_DEFAULT_FREQ;

dev_info(&pdev->dev, "Setting bus frequency to default value: "

"%d MHz\n", freq_mhz);

}

i2c_pnx->adapter->algo = &pnx_algorithm;

//根据pnx_algorithm可以将数据控制成i2c总线要求的数据格式。

alg_data = i2c_pnx->adapter->algo_data;

//这是一个私有数据结构,一般用来传递一些指针

init_timer(&alg_data->mif.timer);

alg_data->mif.timer.function = i2c_pnx_timeout;

alg_data->mif.timer.data = (unsigned long)i2c_pnx->adapter;

//初始化一个定时器,以及和定时器相关联的函数

//申请完时钟,下一步申请I/O

/* Register I/O resource */

if (!request_region(alg_data->base, I2C_PNX_REGION_SIZE, pdev->name)) {

dev_err(&pdev->dev,

"I/O region 0x%08x for I2C already in use.\n",

alg_data->base);

ret = -ENODEV;

goto out_drvdata;

}

//在platform_device结构中,有一个resource结构,它也可以用来记录所需要的I/O资源。

if (!(alg_data->ioaddr =

(u32)ioremap(alg_data->base, I2C_PNX_REGION_SIZE))) {

dev_err(&pdev->dev, "Couldn't ioremap I2C I/O region\n");

ret = -ENOMEM;

goto out_release;

}

i2c_pnx->set_clock_run(pdev);

/*

* Clock Divisor High This value is the number of system clocks

* the serial clock (SCL) will be high.

* For example, if the system clock period is 50 ns and the maximum

* desired serial period is 10000 ns (100 kHz), then CLKHI would be

* set to 0.5*(f_sys/f_i2c)-2=0.5*(20e6/100e3)-2=98. The actual value

* programmed into CLKHI will vary from this slightly due to

* variations in the output pad's rise and fall times as well as

* the deglitching filter length.

*/

tmp = ((freq_mhz * 1000) / I2C_PNX_SPEED_KHZ) / 2 - 2;

iowrite32(tmp, I2C_REG_CKH(alg_data));

iowrite32(tmp, I2C_REG_CKL(alg_data));

iowrite32(mcntrl_reset, I2C_REG_CTL(alg_data));

if (wait_reset(I2C_PNX_TIMEOUT, alg_data)) {

ret = -ENODEV;

goto out_unmap;

}

//将I2C时钟设置为I2C_PNX_SPEED_KHZ

init_completion(&alg_data->mif.complete);

//初始化完成量

ret = request_irq(alg_data->irq, i2c_pnx_interrupt,

0, pdev->name, i2c_pnx->adapter);

if (ret)

goto out_clock;

//申请中断

/* Register this adapter with the I2C subsystem */

i2c_pnx->adapter->dev.parent = &pdev->dev;

//此处将i2c适配器连接到平台设备下

ret = i2c_add_adapter(i2c_pnx->adapter);

//申请完并初始化硬件资源后添加adapter结构体,从而完成I2C总线设备驱动的前两步

if (ret < 0) {

dev_err(&pdev->dev, "I2C: Failed to add bus\n");

goto out_irq;

}

dev_dbg(&pdev->dev, "%s: Master at %#8x, irq %d.\n",

i2c_pnx->adapter->name, alg_data->base, alg_data->irq);

#if 1

if (pdev->id == 1) { //I2C2

struct i2c_board_info info = {

I2C_BOARD_INFO("pcf8563", 0xa3 >> 1),

};

client = i2c_new_device(i2c_pnx->adapter, &info);

if (client == NULL) {

printk("check pcf8563 faile \n");

}

}

#endif

// printk("i2c go on \n");

return 0;

out_irq:

free_irq(alg_data->irq, alg_data);

out_clock:

i2c_pnx->set_clock_stop(pdev);

out_unmap:

iounmap((void *)alg_data->ioaddr);

out_release:

release_region(alg_data->base, I2C_PNX_REGION_SIZE);

out_drvdata:

platform_set_drvdata(pdev, NULL);

out:

return ret;

}

还有第三步,完成i2c_agorithm算法。其实就是完成其中两个结构成员:master_xfer和functionality两个函数

static struct i2c_algorithm pnx_algorithm = {

.master_xfer = i2c_pnx_xfer,

.functionality = i2c_pnx_func,

};

static u32 i2c_pnx_func(struct i2c_adapter *adapter)

{

return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;

}

至于I2C_FUNC_I2C, I2C_FUNC_SMBUS_EMUL具体代表什么样的通信协议,再议。

static int i2c_pnx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

{

struct i2c_msg *pmsg;

int rc = 0, completed = 0, i;

struct i2c_pnx_algo_data *alg_data = adap->algo_data;

u32 stat = ioread32(I2C_REG_STS(alg_data));

//I2C_REG_STS是一个宏,alg_data是一个结构体,最终的效果是查看位于地址

//0x400A0004地址处的i2c状态寄存器

dev_dbg(&adap->dev, "%s(): entering: %d messages, stat = %04x.\n",

__func__, num, ioread32(I2C_REG_STS(alg_data)));

bus_reset_if_active(adap);

/* Process transactions in a loop. */

//为什么要重启一次总线,有待研究

for (i = 0; rc >= 0 && i < num; i++) {

//num为传递过来参数,即msg的数量

u8 addr;

pmsg = &msgs[i];

addr = pmsg->addr;

if (pmsg->flags & I2C_M_TEN) {

dev_err(&adap->dev,

"%s: 10 bits addr not supported!\n",

adap->name);

rc = -EINVAL;

break;

}

// I2C_M_TEN这个标志应该是用来表明地址类型为10位的(未验证)

alg_data->mif.buf = pmsg->buf;

alg_data->mif.len = pmsg->len;

alg_data->mif.mode = (pmsg->flags & I2C_M_RD) ?

I2C_SMBUS_READ : I2C_SMBUS_WRITE;

alg_data->mif.ret = 0;

alg_data->last = (i == num - 1);

//以上数据用来初始化alg_data的mif,并指示未传输的msg.

dev_dbg(&adap->dev, "%s(): mode %d, %d bytes\n", __func__,

alg_data->mif.mode,

alg_data->mif.len);

i2c_pnx_arm_timer(adap);

//运行定时器,定时器处理函数为i2c_pnx_timeout,现将其代码贴出

/*----------------------------------------------------------------------------------------------

static void i2c_pnx_timeout(unsigned long data)

{

struct i2c_adapter *adap = (struct i2c_adapter *)data;

struct i2c_pnx_algo_data *alg_data = adap->algo_data;

u32 ctl;

dev_err(&adap->dev, "Master timed out. stat = %04x, cntrl = %04x. "

"Resetting master...\n",

ioread32(I2C_REG_STS(alg_data)),

ioread32(I2C_REG_CTL(alg_data)));

//读i2c总线的状态和控制寄存器信息,并将其写入stat和cntl

/* Reset master and disable interrupts */

ctl = ioread32(I2C_REG_CTL(alg_data));

ctl &= ~(mcntrl_afie | mcntrl_naie | mcntrl_rffie | mcntrl_drmie);

iowrite32(ctl, I2C_REG_CTL(alg_data));

//设置相关寄存器

ctl |= mcntrl_reset;

iowrite32(ctl, I2C_REG_CTL(alg_data));

//重启以清空RTX_FIFO,7位寻址。

wait_reset(I2C_PNX_TIMEOUT, alg_data);

alg_data->mif.ret = -EIO;

complete(&alg_data->mif.complete);

//唤醒完成量

}

------------------------------------------------------------------------------------------------------------*/

/* initialize the completion var */

init_completion(&alg_data->mif.complete);

//初始化完成量

/* Enable master interrupt */

iowrite32(ioread32(I2C_REG_CTL(alg_data)) | mcntrl_afie |

mcntrl_naie | mcntrl_drmie,

I2C_REG_CTL(alg_data));

//设置相关寄存器

/* Put start-code and slave-address on the bus. */

rc = i2c_pnx_start(addr, adap);

if (rc < 0)

break;

//在i2c_pnx_start函数中有这么一句

//iowrite32((slave_addr << 1) | start_bit | //alg_data->mif.mode,I2C_REG_TX(alg_data));

//作用就是产生发生start条件并发送地址

/* Wait for completion */

wait_for_completion(&alg_data->mif.complete);

//定时的作用应该是等待从机反应。

if (!(rc = alg_data->mif.ret))

completed++;

dev_dbg(&adap->dev, "%s(): Complete, return code = %d.\n",

__func__, rc);

/* Clear TDI and AFI bits in case they are set. */

if ((stat = ioread32(I2C_REG_STS(alg_data))) & mstatus_tdi) {

dev_dbg(&adap->dev,

"%s: TDI still set... clearing now.\n",

adap->name);

iowrite32(stat, I2C_REG_STS(alg_data));

}

if ((stat = ioread32(I2C_REG_STS(alg_data))) & mstatus_afi) {

dev_dbg(&adap->dev,

"%s: AFI still set... clearing now.\n",

adap->name);

iowrite32(stat, I2C_REG_STS(alg_data));

}

}

//最后如果两个中断置1,则清0

bus_reset_if_active(adap);

/* Cleanup to be sure... */

alg_data->mif.buf = NULL;

alg_data->mif.len = 0;

dev_dbg(&adap->dev, "%s(): exiting, stat = %x\n",

__func__, ioread32(I2C_REG_STS(alg_data)));

if (completed != num)

return ((rc < 0) ? rc : -EREMOTEIO);

return num;

}

数据的发送和传输是放在中断函数中完成的。

static irqreturn_t i2c_pnx_interrupt(int irq, void *dev_id)

{

u32 stat, ctl;

struct i2c_adapter *adap = dev_id;

struct i2c_pnx_algo_data *alg_data = adap->algo_data;

dev_dbg(&adap->dev, "%s(): mstat = %x mctrl = %x, mode = %d\n",

__func__,

ioread32(I2C_REG_STS(alg_data)),

ioread32(I2C_REG_CTL(alg_data)),

alg_data->mif.mode);

stat = ioread32(I2C_REG_STS(alg_data));

/* let's see what kind of event this is */

//仲裁失败中断

if (stat & mstatus_afi) {

/* We lost arbitration in the midst of a transfer */

alg_data->mif.ret = -EIO;

/* Disable master interrupts. */

ctl = ioread32(I2C_REG_CTL(alg_data));

ctl &= ~(mcntrl_afie | mcntrl_naie | mcntrl_rffie |

mcntrl_drmie);

iowrite32(ctl, I2C_REG_CTL(alg_data));

/* Stop timer, to prevent timeout. */

del_timer_sync(&alg_data->mif.timer);

complete(&alg_data->mif.complete);

} else if (stat & mstatus_nai) {

//无应答中断时

/* Slave did not acknowledge, generate a STOP */

dev_dbg(&adap->dev, "%s(): "

"Slave did not acknowledge, generating a STOP.\n",

__func__);

i2c_pnx_stop(adap);

/* Disable master interrupts. */

ctl = ioread32(I2C_REG_CTL(alg_data));

ctl &= ~(mcntrl_afie | mcntrl_naie | mcntrl_rffie |

mcntrl_drmie);

iowrite32(ctl, I2C_REG_CTL(alg_data));

/* Our return value. */

alg_data->mif.ret = -EIO;

/* Stop timer, to prevent timeout. */

del_timer_sync(&alg_data->mif.timer);

complete(&alg_data->mif.complete);

} else {

/*

* Two options:

* - Master Tx needs data.

* - There is data in the Rx-fifo

* The latter is only the case if we have requested for data,

* via a dummy write. (See 'i2c_pnx_master_rcv'.)

* We therefore check, as a sanity check, whether that interrupt

* has been enabled.

*/

//主机数据请求中断或从机数据请求中断时

if ((stat & mstatus_drmi) || !(stat & mstatus_rfe)) {

if (alg_data->mif.mode == I2C_SMBUS_WRITE) {

i2c_pnx_master_xmit(adap);

} else if (alg_data->mif.mode == I2C_SMBUS_READ) {

i2c_pnx_master_rcv(adap);

}

}

}

/* Clear TDI and AFI bits */

stat = ioread32(I2C_REG_STS(alg_data));

iowrite32(stat | mstatus_tdi | mstatus_afi, I2C_REG_STS(alg_data));

dev_dbg(&adap->dev, "%s(): exiting, stat = %x ctrl = %x.\n",

__func__, ioread32(I2C_REG_STS(alg_data)),

ioread32(I2C_REG_CTL(alg_data)));

return IRQ_HANDLED;

}

关于remove方法以用中断的具体处理不再分析。分析完毕。

下面分析i2c驱动的设备驱动(drivers/i2c/chips/pcf8574.c)

该设备是以bin_attribute二进制sysfs结点形式出现。大部分I2C设备都会采用这种形式。

模块加载与卸载

static int __init pcf8574_init(void)

{

return i2c_add_driver(&pcf8574_driver);

}

static void __exit pcf8574_exit(void)

{

i2c_del_driver(&pcf8574_driver);

}

加载与卸载模块调用了i2c提供的i2c_driver两个操作函数。

关于pcf8574_driver结构体

static struct i2c_driver pcf8574_driver = {

.driver = {

.name = "pcf8574",

},

.probe = pcf8574_probe,

.remove = pcf8574_remove,

.id_table = pcf8574_id,

.detect = pcf8574_detect,

.address_data = &addr_data,

};

.id_table成员会在i2c_bus_type的mach()函数中检查,以确定和i2c_board_info提供的驱动id是否匹配。

static int pcf8574_probe(struct i2c_client *client,

const struct i2c_device_id *id)

{

struct pcf8574_data *data;

int err;

data = kzalloc(sizeof(struct pcf8574_data), GFP_KERNEL);

if (!data) {

err = -ENOMEM;

goto exit;

}

i2c_set_clientdata(client, data);

/* Initialize the PCF8574 chip */

pcf8574_init_client(client);

/* Register sysfs hooks */

err = sysfs_create_group(&client->dev.kobj, &pcf8574_attr_group);

if (err)

goto exit_free;

return 0;

exit_free:

kfree(data);

exit:

return err;

}

关于其它各函数的实现不再分析。以sysfs节点存在的设备驱动不会在/dev子目录中的相应的设备文件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: