Linux----用GPIO模拟I2C总线
bili_17547692
编辑于 2023年10月11日 18:12

在drivers/i2c/busses下包含各种I2C总线驱动,如S3C2440的I2C总线驱动i2c-s3c2410.c,使用GPIO模拟I2C总线的驱动i2c-gpio.c,这里只分析i2c-gpio.c。

i2c-gpio.c它是gpio模拟I2C总线的驱动,总线也是个设备,在这里将总线当作平台设备处理,那驱动当然是平台设备驱动,看它的驱动注册和注销函数

代码块
clike
自动换行
复制代码
     static int __init i2c_gpio_init(void)    
     {    
         int ret;    
         
         ret = platform_driver_register(&i2c_gpio_driver);    
         if (ret)    
             printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);    
         
         return ret;    
     }    
     module_init(i2c_gpio_init);    
         
     static void __exit i2c_gpio_exit(void)    
     {    
         platform_driver_unregister(&i2c_gpio_driver);    
     }    
     module_exit(i2c_gpio_exit);    
复制成功

初始化和注销函数就是注册和注销一个平台设备驱动,直接看它的platform_driver结构i2c_gpio_driver

代码块
clike
自动换行
复制代码
 static struct platform_driver i2c_gpio_driver = {    
     .driver     = {    
         .name   = "i2c-gpio",    
         .owner  = THIS_MODULE,    
     },    
      .probe      = i2c_gpio_probe,    
      .remove     = __devexit_p(i2c_gpio_remove),    
 };    
复制成功

平台驱动设备放在arch/arm/mach-xxxx/board-xxx.c中

代码块
clike
自动换行
复制代码
#if defined(CONFIG_I2C_GPIO) | \     
     defined(CONFIG_I2C_GPIO_MODULE)     
 static struct i2c_gpio_platform_data i2c_gpio_adapter_data = {     
     .sda_pin = PINID_GPMI_D05,     
     .scl_pin = PINID_GPMI_D04,     
     .udelay = 5, //100KHz     
     .timeout = 100,     
     .sda_is_open_drain = 1,     
     .scl_is_open_drain = 1,     
 };     
     
 static struct platform_device i2c_gpio = {     
     .name = "i2c-gpio",     
     .id = 0,     
     .dev = {     
         .platform_data = &i2c_gpio_adapter_data,     
         .release = mxs_nop_release,     
         },     
 };     
 #endif    
复制成功

这里 struct platform_device结构中的 name字段要和 struct platform_driver中 driver字段中 name字段要相同,因为平台总线就是通过这个来判断设备和驱动是否匹配的。注意这里的 id将它赋值了 0,至于到底有什么用,后面再来细看。这个结构里面还包含一个最重要的数据 i2c_gpio_adapter_data,它 struct i2c_gpio_platform_data结构类型变量,这个结构体类型定义在 include/linux/i2c-gpio.h中。

代码块
Rust
自动换行
复制代码
struct i2c_gpio_platform_data {    
     unsigned int    sda_pin;    
     unsigned int    scl_pin;    
     int     udelay;    
     int     timeout;    
     unsigned int    sda_is_open_drain:1;    
     unsigned int    scl_is_open_drain:1;    
     unsigned int    scl_is_output_only:1;    
 };  
复制成功

结构体主要描述gpio模拟i2c总线,sda_pin和scl_pin表示使用哪两个IO管脚来模拟I2C总线,udelay和timeout分别为它的时钟频率和超时时间,sda_is_open_drain和scl_is_open_drain表示sda、scl这两个管脚是否是开漏(opendrain)电路,如果是设置为1,scl_is_output_only表示scl这个管脚是否只是作为输出,如果是设置为1。

回到驱动中,看其中最重要的i2c_gpio_probe。

代码块
Rust
自动换行
复制代码
static int __devinit i2c_gpio_probe(struct platform_device *pdev)    
 {    
     struct i2c_gpio_platform_data *pdata;    
     struct i2c_algo_bit_data *bit_data;    
     struct i2c_adapter *adap;    
     int ret;    
     
     pdata = pdev->dev.platform_data;    
     if (!pdata)    
         return -ENXIO;    
     
    ret = -ENOMEM;    
     adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL);    
     if (!adap)    
         goto err_alloc_adap;    
     bit_data = kzalloc(sizeof(struct i2c_algo_bit_data), GFP_KERNEL);    
     if (!bit_data)    
         goto err_alloc_bit_data;    
     
     ret = gpio_request(pdata->sda_pin, "sda");    
     if (ret)    
         goto err_request_sda;    
     ret = gpio_request(pdata->scl_pin, "scl");    
     if (ret)    
         goto err_request_scl;    
     
     if (pdata->sda_is_open_drain) {    
         gpio_direction_output(pdata->sda_pin, 1);    
         bit_data->setsda = i2c_gpio_setsda_val;    
     } else {    
         gpio_direction_input(pdata->sda_pin);    
         bit_data->setsda = i2c_gpio_setsda_dir;    
     }    
     
     if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {    
         gpio_direction_output(pdata->scl_pin, 1);    
         bit_data->setscl = i2c_gpio_setscl_val;    
     } else {    
         gpio_direction_input(pdata->scl_pin);    
         bit_data->setscl = i2c_gpio_setscl_dir;    
     }    
     
     if (!pdata->scl_is_output_only)    
         bit_data->getscl = i2c_gpio_getscl;    
     bit_data->getsda = i2c_gpio_getsda;    
     
     if (pdata->udelay)    
         bit_data->udelay = pdata->udelay;    
     else if (pdata->scl_is_output_only)    
         bit_data->udelay = 50;           /* 10 kHz */    
     else    
         bit_data->udelay = 5;            /* 100 kHz */    
     
     if (pdata->timeout)    
         bit_data->timeout = pdata->timeout;    
     else    
         bit_data->timeout = HZ / 10;     /* 100 ms */    
     
     bit_data->data = pdata;    
     
     adap->owner = THIS_MODULE;    
     snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);    
     adap->algo_data = bit_data;    
     adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;    
     adap->dev.parent = &pdev->dev;    
     
     /*   
      * If "dev->id" is negative we consider it as zero.   
      * The reason to do so is to avoid sysfs names that only make   
      * sense when there are multiple adapters.   
      */    
     adap->nr = (pdev->id != -1) ? pdev->id : 0;    
     ret = i2c_bit_add_numbered_bus(adap);    
     if (ret)    
         goto err_add_bus;    
     
     platform_set_drvdata(pdev, adap);    
     
     dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",    
          pdata->sda_pin, pdata->scl_pin,    
          pdata->scl_is_output_only    
          ? ", no clock stretching" : "");    
     
     return 0;    
     
 err_add_bus:    
     gpio_free(pdata->scl_pin);    
 err_request_scl:    
     gpio_free(pdata->sda_pin);    
 err_request_sda:    
     kfree(bit_data);    
 err_alloc_bit_data:    
     kfree(adap);    
 err_alloc_adap:    
     return ret;    
 }    
复制成功

这句开始pdata= pdev->dev.platform_data;这不正是我们在平台设备结构中定义的数据吗。然后是使用kzalloc申请两段内存空间,一个是为结构struct i2c_adapter申请的,另一个是为结构structi2c_algo_bit_data申请的。

struct i2c_adapter结构定义在include/linux/i2c.h中

代码块
Rust
自动换行
复制代码
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   */    
     u8 level;           /* nesting level for lockdep */    
     struct mutex bus_lock;    
     
     int timeout;            /* in jiffies */    
     int retries;    
     struct device dev;      /* the adapter device */    
     
     int nr;    
     char name[48];    
     struct completion dev_released;    
 };  
复制成功

在I2C子系统中,I2C适配器使用结构struct i2c_adapter描述,代表一条实际的I2C总线。

struct i2c_algo_bit_data结构定义在include/linux/i2c-algo-bit.h中

代码块
Rust
自动换行
复制代码
 struct i2c_algo_bit_data {    
     void *data;     /* private data for lowlevel routines */    
     void (*setsda) (void *data, int state);    
     void (*setscl) (void *data, int state);    
     int  (*getsda) (void *data);    
     int  (*getscl) (void *data);    
     
     /* local settings */    
     int udelay;     /* half clock cycle time in us,   
                    minimum 2 us for fast-mode I2C,   
                    minimum 5 us for standard-mode I2C and SMBus,   
                    maximum 50 us for SMBus */    
     int timeout;        /* in jiffies */    
 };   
复制成功

结构主要用来定义对GPIO管脚的一些操作,还是回到probe中

接下来使用gpio_request去申请这个两个GPIO管脚,申请的目的是为了防止重复使用管脚。然后是根据struct i2c_gpio_platform_data结构中定义的后面三个数据对struct i2c_algo_bit_data结构中的函数指针做一些赋值操作。接下来是I2C时钟频率和超时设置,如果在struct i2c_gpio_platform_data结构中定义了值,那么就采用定义的值,否则就采用默认的值。然后是对struct i2c_adapter结构的一些赋值操作,比如指定它的父设备为这里的平台设备,前面在平台设备中定义了一个id,这里用到了,赋给了struct i2c_adapter中的nr成员,这个值表示总线号,这里的总线号和硬件无关,只是在软件上的区分。

然后到了最后的主角i2c_bit_add_numbered_bus,这个函数定义在drivers/i2c/algos/i2c-algo-bit.c中

代码块
Rust
自动换行
复制代码
     int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)    
     {    
         int err;    
         
         err = i2c_bit_prepare_bus(adap);    
         if (err)    
             return err;    
         
         return i2c_add_numbered_adapter(adap);    
     }    
复制成功

先看 i2c_bit_prepare_bus函数

代码块
Rust
自动换行
复制代码
 static int i2c_bit_prepare_bus(struct i2c_adapter *adap)    
 {    
     struct i2c_algo_bit_data *bit_adap = adap->algo_data;    
     
     if (bit_test) {    
         int ret = test_bus(bit_adap, adap->name);    
         if (ret < 0)    
             return -ENODEV;    
     }    
     
     /* register new adapter to i2c module... */    
     adap->algo = &i2c_bit_algo;    
     adap->retries = 3;    
     
     return 0;    
 }  
复制成功

bit_test为模块参数,这里不管它,看这样一句adap->algo= &i2c_bit_algo;

代码块
Rust
自动换行
复制代码
static const struct i2c_algorithm i2c_bit_algo = {    
     .master_xfer    = bit_xfer,    
     .functionality  = bit_func,    
 };    
复制成功

这个结构类型定义include/linux/i2c.h

代码块
Rust
自动换行
复制代码
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指针为主机的数据传输,具体来看bit_xfer这个函数,这个函数和I2C协议相关,I2C协议规定要先发送起始信号,才能开始进行数据的传输,最后数据传输完成后发送停止信号,看接下来代码对I2C协议要熟悉,所以这里的关键点是I2C协议。

代码块
Rust
自动换行
复制代码
static int bit_xfer(struct i2c_adapter *i2c_adap,  
            struct i2c_msg msgs[], int num)  
{  
    struct i2c_msg *pmsg;  
    struct i2c_algo_bit_data *adap = i2c_adap->algo_data;  
    int i, ret;  
    unsigned short nak_ok;  
  
    bit_dbg(3, &i2c_adap->dev, "emitting start condition\n");  
    /*发送起始信号*/  
    i2c_start(adap);  
    for (i = 0; i < num; i++) {  
        pmsg = &msgs[i];  
        nak_ok = pmsg->flags & I2C_M_IGNORE_NAK;  
        if (!(pmsg->flags & I2C_M_NOSTART)) {  
            if (i) {  
                bit_dbg(3, &i2c_adap->dev, "emitting "  
                    "repeated start condition\n");  
                i2c_repstart(adap);  
            }  
            ret = bit_doAddress(i2c_adap, pmsg);  
            if ((ret != 0) && !nak_ok) {  
                bit_dbg(1, &i2c_adap->dev, "NAK from "  
                    "device addr 0x%02x msg #%d\n",  
                    msgs[i].addr, i);  
                goto bailout;  
            }  
        }  
        if (pmsg->flags & I2C_M_RD) {  
            /* read bytes into buffer*/  
            ret = readbytes(i2c_adap, pmsg);  
            if (ret >= 1)  
                bit_dbg(2, &i2c_adap->dev, "read %d byte%s\n",  
                    ret, ret == 1 ? "" : "s");  
            if (ret < pmsg->len) {  
                if (ret >= 0)  
                    ret = -EREMOTEIO;  
                goto bailout;  
            }  
        } else {  
            /* write bytes from buffer */  
            ret = sendbytes(i2c_adap, pmsg);  
            if (ret >= 1)  
                bit_dbg(2, &i2c_adap->dev, "wrote %d byte%s\n",  
                    ret, ret == 1 ? "" : "s");  
            if (ret < pmsg->len) {  
                if (ret >= 0)  
                    ret = -EREMOTEIO;  
                goto bailout;  
            }  
        }  
    }  
    ret = i;  
  
bailout:  
    bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");  
    i2c_stop(adap);  
    return ret;  
}  
复制成功

发送起始信号

i2c_start(adap);

看这个函数前,先看I2C协议怎么定义起始信号的

起始信号就是在SCL为高电平期间,SDA从高到低的跳变,再来看代码是怎么实现的

代码块
Rust
自动换行
复制代码
static void i2c_start(struct i2c_algo_bit_data *adap)    
 {    
     /* assert: scl, sda are high */    
     setsda(adap, 0);    
     udelay(adap->udelay);    
     scllo(adap);    
 }    
复制成功

setsda和 setscl这些都是使用的总线的函数,在这里是使用的 i2c-gpio.c中定义的函数,还记得那一系列判断赋值吗。

代码块
CSS
自动换行
复制代码
#define setsda(adap, val)   adap->setsda(adap->data, val)  
#define setscl(adap, val)   adap->setscl(adap->data, val)  
#define getsda(adap)        adap->getsda(adap->data)  
#define getscl(adap)        adap->getscl(adap->data) 
复制成功

往下是个大的for循环

到了这里又不得不说这个struct i2c_msg结构,这个结构定义在include/linux/i2c.h中

代码块
JavaScript
自动换行
复制代码
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          */    
 };    
复制成功

这个结构专门用于数据传输相关的addr为I2C设备地址,flags为一些标志位,len为数据的长度,buf为数据。这里宏定义的一些标志还是需要了解一下。

I2C_M_TEN表示10位设备地址

I2C_M_RD读标志

I2C_M_NOSTART无起始信号标志

I2C_M_IGNORE_NAK忽略应答信号标志

回到for,这里的num代表有几个struct i2c_msg,进入for语句,接下来是个if语句,判断这个设备是否定义了I2C_M_NOSTART标志,这个标志主要用于写操作时,不必重新发送起始信号和设备地址,但是对于读操作就不同了,要调用i2c_repstart这个函数去重新发送起始信号,调用bit_doAddress函数去重新构造设备地址字节,来看这个函数。

代码块
JavaScript
自动换行
复制代码
static int bit_doAddress(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)  
{  
    unsigned short flags = msg->flags;  
    unsigned short nak_ok = msg->flags & I2C_M_IGNORE_NAK;  
    struct i2c_algo_bit_data *adap = i2c_adap->algo_data;  
  
    unsigned char addr;  
    int ret, retries;  
  
    retries = nak_ok ? 0 : i2c_adap->retries;  
  
    if (flags & I2C_M_TEN) {  
        /* a ten bit address */  
        addr = 0xf0 | ((msg->addr >> 7) & 0x03);  
        bit_dbg(2, &i2c_adap->dev, "addr0: %d\n", addr);  
        /* try extended address code...*/  
        ret = try_address(i2c_adap, addr, retries);  
        if ((ret != 1) && !nak_ok)  {  
            dev_err(&i2c_adap->dev,  
                "died at extended address code\n");  
            return -EREMOTEIO;  
        }  
        /* the remaining 8 bit address */  
        ret = i2c_outb(i2c_adap, msg->addr & 0x7f);  
        if ((ret != 1) && !nak_ok) {  
            /* the chip did not ack / xmission error occurred */  
            dev_err(&i2c_adap->dev, "died at 2nd address code\n");  
            return -EREMOTEIO;  
        }  
        if (flags & I2C_M_RD) {  
            bit_dbg(3, &i2c_adap->dev, "emitting repeated "  
                "start condition\n");  
            i2c_repstart(adap);  
            /* okay, now switch into reading mode */  
            addr |= 0x01;  
            ret = try_address(i2c_adap, addr, retries);  
            if ((ret != 1) && !nak_ok) {  
                dev_err(&i2c_adap->dev,  
                    "died at repeated address code\n");  
                return -EREMOTEIO;  
            }  
        }  
    } else {        /* normal 7bit address  */  
        addr = msg->addr << 1;  
        if (flags & I2C_M_RD)  
            addr |= 1;  
        if (flags & I2C_M_REV_DIR_ADDR)  
            addr ^= 1;  
        ret = try_address(i2c_adap, addr, retries);  
        if ((ret != 1) && !nak_ok)  
            return -ENXIO;  
    }  
  
    return 0;  
}  
复制成功

这里先做了一个判断, 10位设备地址和 7位设备地址分别做不同的处理,通常一条 I2C总线上不会挂那么多 I2C设备,所以 10位地址不常用,直接看对 7位地址的处理。 struct i2c_msg中 addr中是真正的设备地址,而这里发送的 addr高 7位才是设备地址,最低位为读写位,如果为读,最低位为 1,如果为写,最低位为 0。所以要将 struct i2c_msg中 addr向左移 1位,如果定义了 I2C_M_RD标志,就将 addr或上 1,前面就说过,这个标志就代表读,如果是写,这里就不用处理,因为最低位本身就是 0。最后调用 try_address函数将这个地址字节发送出去。

代码块
C++
自动换行
复制代码
static int try_address(struct i2c_adapter *i2c_adap,    
                unsigned char addr, int retries)    
 {    
     struct i2c_algo_bit_data *adap = i2c_adap->algo_data;    
     int i, ret = 0;    
     
     for (i = 0; i <= retries; i++) {    
         ret = i2c_outb(i2c_adap, addr);    
         if (ret == 1 || i == retries)    
             break;    
         bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");    
         i2c_stop(adap);    
         udelay(adap->udelay);    
         yield();    
         bit_dbg(3, &i2c_adap->dev, "emitting start condition\n");    
         i2c_start(adap);    
     }    
     if (i && ret)    
         bit_dbg(1, &i2c_adap->dev, "Used %d tries to %s client at "    
             "0x%02x: %s\n", i + 1,    
             addr & 1 ? "read from" : "write to", addr >> 1,    
             ret == 1 ? "success" : "failed, timeout?");    
     return ret;    
 }    
复制成功

最主要的就是调用i2c_outb发送一个字节,retries为重复次数,看前面adap->retries= 3;

如果发送失败,也就是设备没有给出应答信号,那就发送停止信号,发送起始信号,再发送这个地址字节,这就叫retries。来看这个具体的i2c_outb函数

代码块
C++
自动换行
复制代码
static int i2c_outb(struct i2c_adapter *i2c_adap, unsigned char c)    
 {    
     int i;    
     int sb;    
     int ack;    
     struct i2c_algo_bit_data *adap = i2c_adap->algo_data;    
     
     /* assert: scl is low */    
     for (i = 7; i >= 0; i--) {    
         sb = (c >> i) & 1;    
         setsda(adap, sb);    
         udelay((adap->udelay + 1) / 2);    
         if (sclhi(adap) < 0) { /* timed out */    
             bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "    
                 "timeout at bit #%d\n", (int)c, i);    
             return -ETIMEDOUT;    
         }    
         /* FIXME do arbitration here:   
          * if (sb && !getsda(adap)) -> ouch! Get out of here.   
          *   
          * Report a unique code, so higher level code can retry   
         * the whole (combined) message and *NOT* issue STOP.   
          */    
         scllo(adap);    
     }    
     sdahi(adap);    
     if (sclhi(adap) < 0) { /* timeout */    
         bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "    
             "timeout at ack\n", (int)c);    
         return -ETIMEDOUT;    
     }    
     
     /* read ack: SDA should be pulled down by slave, or it may   
      * NAK (usually to report problems with the data we wrote).   
      */    
     ack = !getsda(adap);    /* ack: sda is pulled low -> success */    
     bit_dbg(2, &i2c_adap->dev, "i2c_outb: 0x%02x %s\n", (int)c,    
         ack ? "A" : "NA");    
     
     scllo(adap);    
     return ack;    
     /* assert: scl is low (sda undef) */    
 }    
复制成功

这个函数有两个参数,一个是structi2c_adapter代表I2C主机,一个是发送的字节数据。那么I2C是怎样将一个字节数据发送出去的呢,那再来看看协议。

首先是发送字节数据的最高位,在时钟为高电平期间将一位数据发送出去,最后是发送字节数据的最低位。发送完成之后,我们需要一个ACK信号,要不然我怎么知道发送成功没有,ACK信号就是在第九个时钟周期时数据线为低,所以在一个字节数据传送完成后,还要将数据线拉高,我们看程序中就是这一句sdahi(adap);等待这个ACK信号的到来,这样一个字节数据就发送完成。

bit_xfer函数中,前面只是将设备地址字节发送出去了,那么接下来就是该发送数据了。

注意:这里的数据包括操作设备的基地址

如果是读则调用readbytes函数去读,如果是写则调用sendbytes去写,先看readbytes函数

代码块
C++
自动换行
复制代码
static int readbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)    
 {    
     int inval;    
     int rdcount = 0;    /* counts bytes read */    
     unsigned char *temp = msg->buf;    
     int count = msg->len;    
     const unsigned flags = msg->flags;    
    
     while (count > 0) {    
         inval = i2c_inb(i2c_adap);    
         if (inval >= 0) {    
             *temp = inval;    
             rdcount++;    
         } else {   /* read timed out */    
            break;    
         }    
     
         temp++;    
         count--;    
     
        /* Some SMBus transactions require that we receive the   
            transaction length as the first read byte. */    
         if (rdcount == 1 && (flags & I2C_M_RECV_LEN)) {    
             if (inval <= 0 || inval > I2C_SMBUS_BLOCK_MAX) {    
                 if (!(flags & I2C_M_NO_RD_ACK))    
                     acknak(i2c_adap, 0);    
                 dev_err(&i2c_adap->dev, "readbytes: invalid "    
                     "block length (%d)\n", inval);    
                 return -EREMOTEIO;    
             }    
            /* The original count value accounts for the extra   
                bytes, that is, either 1 for a regular transaction,   
                or 2 for a PEC transaction. */    
             count += inval;    
             msg->len += inval;    
         }    
     
         bit_dbg(2, &i2c_adap->dev, "readbytes: 0x%02x %s\n",    
             inval,    
             (flags & I2C_M_NO_RD_ACK)    
                 ? "(no ack/nak)"    
                 : (count ? "A" : "NA"));    
     
         if (!(flags & I2C_M_NO_RD_ACK)) {    
             inval = acknak(i2c_adap, count);    
             if (inval < 0)    
                 return inval;    
         }    
     }    
     return rdcount;    
 } 
复制成功

其中一个大的while循环,调用i2c_inb去读一个字节,count为数据的长度,单位为多少个字节,

那就来看i2c_inb函数。

代码块
C++
自动换行
复制代码
static int i2c_inb(struct i2c_adapter *i2c_adap)  
{  
    /* read byte via i2c port, without start/stop sequence  */  
    /* acknowledge is sent in i2c_read.         */  
    int i;  
    unsigned char indata = 0;  
    struct i2c_algo_bit_data *adap = i2c_adap->algo_data;  
  
    /* assert: scl is low */  
    sdahi(adap);  
    for (i = 0; i < 8; i++) {  
        if (sclhi(adap) < 0) { /* timeout */  
            bit_dbg(1, &i2c_adap->dev, "i2c_inb: timeout at bit "  
                "#%d\n", 7 - i);  
            return -ETIMEDOUT;  
        }  
        indata *= 2;  
        if (getsda(adap))  
            indata |= 0x01;  
        setscl(adap, 0);  
        udelay(i == 7 ? adap->udelay / 2 : adap->udelay);  
    }  
    /* assert: scl is low */  
    return indata;  
}  
复制成功

再来看 sendbytes函数

代码块
C++
自动换行
复制代码
static int sendbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)    
 {    
     const unsigned char *temp = msg->buf;    
     int count = msg->len;    
     unsigned short nak_ok = msg->flags & I2C_M_IGNORE_NAK;    
     int retval;    
     int wrcount = 0;    
     
     while (count > 0) {    
         retval = i2c_outb(i2c_adap, *temp);    
     
         /* OK/ACK; or ignored NAK */    
         if ((retval > 0) || (nak_ok && (retval == 0))) {    
             count--;    
             temp++;    
             wrcount++;    
     
         /* A slave NAKing the master means the slave didn't like   
          * something about the data it saw.  For example, maybe   
          * the SMBus PEC was wrong.   
          */    
         } else if (retval == 0) {    
             dev_err(&i2c_adap->dev, "sendbytes: NAK bailout.\n");    
             return -EIO;    
     
         /* Timeout; or (someday) lost arbitration   
          *   
          * FIXME Lost ARB implies retrying the transaction from   
          * the first message, after the "winning" master issues   
          * its STOP.  As a rule, upper layer code has no reason   
          * to know or care about this ... it is *NOT* an error.   
          */    
         } else {    
             dev_err(&i2c_adap->dev, "sendbytes: error %d\n",    
                     retval);    
             return retval;    
         }    
     }    
     return wrcount;    
 }    
复制成功

也是一个大的while循环,同发送地址字节一样,也是调用i2c_outb去发送一个字节,count也是数据长度,由于i2c_outb函数在前面发送设备地址那里已经介绍了,这里也就不贴出来了。

回到bit_xfer函数,数据传输完成后,调用i2c_stop函数发送停止信号。我们看停止信号函数怎么去实现的

代码块
Rust
自动换行
复制代码
static void i2c_stop(struct i2c_algo_bit_data *adap)    
 {    
     /* assert: scl is low */    
     sdalo(adap);    
     sclhi(adap);    
     setsda(adap, 1);    
     udelay(adap->udelay);    
 }    
复制成功

看前面发送起始信号的那张图,停止信号就是在时钟为高电平期间,数据线从低到高的跳变。我们看程序是先将数据线拉低,将时钟线拉高,最后将数据拉高,这样就够成了一个停止信号。

回到i2c_bit_add_numbered_bus这个函数中来,看另外一个函数调用i2c_add_numbered_adapter。

代码块
Rust
自动换行
复制代码
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;    
 }    
复制成功

最重要的是这句 i2c_register_adapter,注册这条 I2C总线,进去看看

代码块
C++
自动换行
复制代码
static int i2c_register_adapter(struct i2c_adapter *adap)  
{  
    int res = 0, dummy;  
  
    /* Can't register until after driver model init */  
    if (unlikely(WARN_ON(!i2c_bus_type.p))) {  
        res = -EAGAIN;  
        goto out_list;  
    }  
  
    mutex_init(&adap->bus_lock);  
  
    /* Set default timeout to 1 second if not already set */  
    if (adap->timeout == 0)  
        adap->timeout = HZ;  
  
    dev_set_name(&adap->dev, "i2c-%d", adap->nr);  
    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  
  
    /* create pre-declared device nodes */  
    if (adap->nr < __i2c_first_dynamic_bus_num)  
        i2c_scan_static_board_info(adap);  
  
    /* Notify drivers */  
    mutex_lock(&core_lock);  
    dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,  
                 i2c_do_add_adapter);  
    mutex_unlock(&core_lock);  
  
    return 0;  
  
out_list:  
    mutex_lock(&core_lock);  
    idr_remove(&i2c_adapter_idr, adap->nr);  
    mutex_unlock(&core_lock);  
    return res;  
}  
复制成功

内核代码有时就会这样,会陷入内核代码的汪洋大海中,而拔不出来,直接后果是最后都忘记看这段代码的目的,丧失继续看下去的信心。所以为了避免这样情况出现,所以最好在开始看代码的时候要明确目标,我通过这段代码到底要了解什么东西,主干要抓住,其它枝叶就不要看了。

总结:

在这里我认为主要的有

1.注册这个I2C总线设备

代码块
Rust
自动换行
复制代码
 adap->dev.bus = &i2c_bus_type;    
 adap->dev.type = &i2c_adapter_type;    
 res = device_register(&adap->dev); 
复制成功

这个设备的总线类型为 i2c_bus_type

代码块
Rust
自动换行
复制代码
 struct bus_type i2c_bus_type = {    
     .name       = "i2c",    
     .match      = i2c_device_match,    
     .probe      = i2c_device_probe,    
     .remove     = i2c_device_remove,    
     .shutdown   = i2c_device_shutdown,    
     .suspend    = i2c_device_suspend,    
     .resume     = i2c_device_resume,    
 };    
复制成功

看一下它的 match函数

代码块
Rust
自动换行
复制代码
 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;    
 }    
复制成功

match函数主要用来匹配我们的 I2C设备和 I2C驱动的,如果匹配成功,最后会调用驱动的 probe函数,来看它如何匹配的。

代码块
Rust
自动换行
复制代码
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)    
             return id;    
         id++;    
     }    
     return NULL;    
 }    
复制成功

判断I2C设备的name字段和驱动中id_table中定义的name字段是否相等。

2.往这条总线上添加设备

代码块
Rust
自动换行
复制代码
 static void i2c_scan_static_board_info(struct i2c_adapter *adapter)    
 {    
     struct i2c_devinfo  *devinfo;    
     
     down_read(&__i2c_board_lock);    
     list_for_each_entry(devinfo, &__i2c_board_list, list) {    
         if (devinfo->busnum == adapter->nr    
                 && !i2c_new_device(adapter,    
                         &devinfo->board_info))    
             dev_err(&adapter->dev,    
                 "Can't create device at 0x%02x\n",    
                 devinfo->board_info.addr);    
     }    
     up_read(&__i2c_board_lock);    
 }
复制成功

遍历 __i2c_board_list这条链表,看下面的 if语句,首先要让 struct i2c_devinfo结构中的 busnum等于 struct i2c_adapter中的 nr,我们前面也说了,这个 nr就是 i2c总线的总线号,这里可以理解为是在往这条总线上添加设备。所以,如果我们要向 I2C注册一个 I2C设备的话,直接向 __i2c_board_list添加一个设备信息就可以了,先来看这个设备信息结构是怎么定义的

代码块
Rust
自动换行
复制代码
struct i2c_board_info {    
    char        type[I2C_NAME_SIZE];    
    unsigned short  flags;    
    unsigned short  addr;    
    void        *platform_data;    
    struct dev_archdata *archdata;    
    int     irq;    
};  
复制成功

定义这样一个信息呢一般使用一个宏 I2C_BOARD_INFO

代码块
Rust
自动换行
复制代码
    # #define I2C_BOARD_INFO(dev_type, dev_addr) \    
    #     .type = dev_type, .addr = (dev_addr)   
复制成功
  1. dev_type为设备的名字,前面也说了,这个name一定要和I2C驱动相同。addr为设备的地址。   

  2. 定义了这样一组信息之后呢,接下来当然是往链表添加这些信息了。 

代码块
Rust
自动换行
复制代码
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;    
 }    
复制成功

第一个参数呢需要注意,它是 I2C总线号,一定要和具体的 I2C总线对应。我们看又定义了这样一个结构 struct i2c_devinfo。

最后是调用list_add_tail往__i2c_board_list这条链表添加设备信息。

然后是i2c_new_device

代码块
Rust
自动换行
复制代码
struct i2c_client *    
 i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)    
 {    
     struct i2c_client   *client;    
     int         status;    
     
     /*为I2C设备申请内存*/    
     client = kzalloc(sizeof *client, GFP_KERNEL);    
     if (!client)    
         return NULL;    
     
     /*指定I2C设备的总线*/    
     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; /*I2C设备地址*/    
     client->irq = info->irq;    
     
     strlcpy(client->name, info->type, sizeof(client->name));    
     
     /*检查这个地址有没有被设备占用*/    
     /* Check for address business */    
     status = i2c_check_addr(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;    
     
     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);    
     kfree(client);    
     return NULL;   
复制成功

这个函数的功能是新建一个I2C设备并注册它,在I2C子系统中,I2C设备使用结构structi2c_client描述,那么首先要申请内存空间,I2C设备的主机是谁,必须知道挂载到哪条总线上的,然后就是一些赋值操作,最后就是注册设备,那么这个设备就实实在在的挂在到这条总线上了,这也是新的I2C设备注册方式。

3.i2c_do_add_adapter

代码块
Rust
自动换行
复制代码
static int i2c_do_add_adapter(struct device_driver *d, void *data)    
 {    
     struct i2c_driver *driver = to_i2c_driver(d);    
     struct i2c_adapter *adap = data;    
     
     /* Detect supported devices on that bus, and instantiate them */    
     i2c_detect(adap, driver);    
     
     /* Let legacy drivers scan this bus for matching devices */    
     if (driver->attach_adapter) {    
         /* We ignore the return code; if it fails, too bad */    
         driver->attach_adapter(adap);    
     }    
     return 0;    
 }    
复制成功

前面通过 i2c_scan_static_board_info往 I2C总线上添加设备是新的方式,而这里调用每个 I2C设备驱动的 attach_adapter函数,然后在 attach_adapter函数中去实现设备的注册,这是老的方式, i2c-dev.c中就是采用的这种方式。至此,GPIO模拟总线这块就看完了