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

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

[html] view plaincopyprint?
  1. 1. static int __init i2c_gpio_init(void)
  2. 2. {
  3. 3.     int ret;
  4. 4.
  5. 5.     ret = platform_driver_register(&i2c_gpio_driver);
  6. 6.     if (ret)
  7. 7.         printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
  8. 8.
  9. 9.     return ret;
  10. 10. }
  11. 11. module_init(i2c_gpio_init);
  12. 12.
  13. 13. static void __exit i2c_gpio_exit(void)
  14. 14. {
  15. 15.     platform_driver_unregister(&i2c_gpio_driver);
  16. 16. }
  17. 17. module_exit(i2c_gpio_exit);

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

[html] view plaincopyprint?
  1. 1. static struct platform_driver i2c_gpio_driver = {
  2. 2.     .driver     = {
  3. 3.         .name   = "i2c-gpio",
  4. 4.         .owner  = THIS_MODULE,
  5. 5.     },
  6. 6.     .probe      = i2c_gpio_probe,
  7. 7.     .remove     = __devexit_p(i2c_gpio_remove),
  8. 8. };

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

[html] view plaincopyprint?
  1. 1. #if defined(CONFIG_I2C_GPIO) | \
  2. 2.     defined(CONFIG_I2C_GPIO_MODULE)
  3. 3. static struct i2c_gpio_platform_data i2c_gpio_adapter_data = {
  4. 4.     .sda_pin = PINID_GPMI_D05,
  5. 5.     .scl_pin = PINID_GPMI_D04,
  6. 6.     .udelay = 5, //100KHz
  7. 7.     .timeout = 100,
  8. 8.     .sda_is_open_drain = 1,
  9. 9.     .scl_is_open_drain = 1,
  10. 10. };
  11. 11.
  12. 12. static struct platform_device i2c_gpio = {
  13. 13.     .name = "i2c-gpio",
  14. 14.     .id = 0,
  15. 15.     .dev = {
  16. 16.         .platform_data = &i2c_gpio_adapter_data,
  17. 17.         .release = mxs_nop_release,
  18. 18.         },
  19. 19. };
  20. 20. #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 中。

[html] view plaincopyprint?
  1. 1. struct i2c_gpio_platform_data {
  2. 2.     unsigned int    sda_pin;
  3. 3.     unsigned int    scl_pin;
  4. 4.     int     udelay;
  5. 5.     int     timeout;
  6. 6.     unsigned int    sda_is_open_drain:1;
  7. 7.     unsigned int    scl_is_open_drain:1;
  8. 8.     unsigned int    scl_is_output_only:1;
  9. 9. };

这个结构体主要描述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。

[html] view plaincopyprint?
  1. 1. static int __devinit i2c_gpio_probe(struct platform_device *pdev)
  2. 2. {
  3. 3.     struct i2c_gpio_platform_data *pdata;
  4. 4.     struct i2c_algo_bit_data *bit_data;
  5. 5.     struct i2c_adapter *adap;
  6. 6.     int ret;
  7. 7.
  8. 8.     pdata = pdev->dev.platform_data;
  9. 9.     if (!pdata)
  10. 10.         return -ENXIO;
  11. 11.
  12. 12.     ret = -ENOMEM;
  13. 13.     adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL);
  14. 14.     if (!adap)
  15. 15.         goto err_alloc_adap;
  16. 16.     bit_data = kzalloc(sizeof(struct i2c_algo_bit_data), GFP_KERNEL);
  17. 17.     if (!bit_data)
  18. 18.         goto err_alloc_bit_data;
  19. 19.
  20. 20.     ret = gpio_request(pdata->sda_pin, "sda");
  21. 21.     if (ret)
  22. 22.         goto err_request_sda;
  23. 23.     ret = gpio_request(pdata->scl_pin, "scl");
  24. 24.     if (ret)
  25. 25.         goto err_request_scl;
  26. 26.
  27. 27.     if (pdata->sda_is_open_drain) {
  28. 28.         gpio_direction_output(pdata->sda_pin, 1);
  29. 29.         bit_data->setsda = i2c_gpio_setsda_val;
  30. 30.     } else {
  31. 31.         gpio_direction_input(pdata->sda_pin);
  32. 32.         bit_data->setsda = i2c_gpio_setsda_dir;
  33. 33.     }
  34. 34.
  35. 35.     if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
  36. 36.         gpio_direction_output(pdata->scl_pin, 1);
  37. 37.         bit_data->setscl = i2c_gpio_setscl_val;
  38. 38.     } else {
  39. 39.         gpio_direction_input(pdata->scl_pin);
  40. 40.         bit_data->setscl = i2c_gpio_setscl_dir;
  41. 41.     }
  42. 42.
  43. 43.     if (!pdata->scl_is_output_only)
  44. 44.         bit_data->getscl = i2c_gpio_getscl;
  45. 45.     bit_data->getsda = i2c_gpio_getsda;
  46. 46.
  47. 47.     if (pdata->udelay)
  48. 48.         bit_data->udelay = pdata->udelay;
  49. 49.     else if (pdata->scl_is_output_only)
  50. 50.         bit_data->udelay = 50;           /* 10 kHz */
  51. 51.     else
  52. 52.         bit_data->udelay = 5;            /* 100 kHz */
  53. 53.
  54. 54.     if (pdata->timeout)
  55. 55.         bit_data->timeout = pdata->timeout;
  56. 56.     else
  57. 57.         bit_data->timeout = HZ / 10;     /* 100 ms */
  58. 58.
  59. 59.     bit_data->data = pdata;
  60. 60.
  61. 61.     adap->owner = THIS_MODULE;
  62. 62.     snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);
  63. 63.     adap->algo_data = bit_data;
  64. 64.     adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
  65. 65.     adap->dev.parent = &pdev->dev;
  66. 66.
  67. 67.     /*
  68. 68.      * If "dev->id" is negative we consider it as zero.
  69. 69.      * The reason to do so is to avoid sysfs names that only make
  70. 70.      * sense when there are multiple adapters.
  71. 71.      */
  72. 72.     adap->nr = (pdev->id != -1) ? pdev->id : 0;
  73. 73.     ret = i2c_bit_add_numbered_bus(adap);
  74. 74.     if (ret)
  75. 75.         goto err_add_bus;
  76. 76.
  77. 77.     platform_set_drvdata(pdev, adap);
  78. 78.
  79. 79.     dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",
  80. 80.          pdata->sda_pin, pdata->scl_pin,
  81. 81.          pdata->scl_is_output_only
  82. 82.          ? ", no clock stretching" : "");
  83. 83.
  84. 84.     return 0;
  85. 85.
  86. 86. err_add_bus:
  87. 87.     gpio_free(pdata->scl_pin);
  88. 88. err_request_scl:
  89. 89.     gpio_free(pdata->sda_pin);
  90. 90. err_request_sda:
  91. 91.     kfree(bit_data);
  92. 92. err_alloc_bit_data:
  93. 93.     kfree(adap);
  94. 94. err_alloc_adap:
  95. 95.     return ret;
  96. 96. }

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

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

[html] view plaincopyprint?
  1. 1. struct i2c_adapter {
  2. 2.     struct module *owner;
  3. 3.     unsigned int id;
  4. 4.     unsigned int class;       /* classes to allow probing for */
  5. 5.     const struct i2c_algorithm *algo; /* the algorithm to access the bus */
  6. 6.     void *algo_data;
  7. 7.
  8. 8.     /* data fields that are valid for all devices   */
  9. 9.     u8 level;           /* nesting level for lockdep */
  10. 10.     struct mutex bus_lock;
  11. 11.
  12. 12.     int timeout;            /* in jiffies */
  13. 13.     int retries;
  14. 14.     struct device dev;      /* the adapter device */
  15. 15.
  16. 16.     int nr;
  17. 17.     char name[48];
  18. 18.     struct completion dev_released;
  19. 19. };

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

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

[html] view plaincopyprint?
  1. 1. struct i2c_algo_bit_data {
  2. 2.     void *data;     /* private data for lowlevel routines */
  3. 3.     void (*setsda) (void *data, int state);
  4. 4.     void (*setscl) (void *data, int state);
  5. 5.     int  (*getsda) (void *data);
  6. 6.     int  (*getscl) (void *data);
  7. 7.
  8. 8.     /* local settings */
  9. 9.     int udelay;     /* half clock cycle time in us,
  10. 10.                    minimum 2 us for fast-mode I2C,
  11. 11.                    minimum 5 us for standard-mode I2C and SMBus,
  12. 12.                    maximum 50 us for SMBus */
  13. 13.     int timeout;        /* in jiffies */
  14. 14. };

这个结构主要用来定义对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中

[html] view plaincopyprint?
  1. 1. int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
  2. 2. {
  3. 3.     int err;
  4. 4.
  5. 5.     err = i2c_bit_prepare_bus(adap);
  6. 6.     if (err)
  7. 7.         return err;
  8. 8.
  9. 9.     return i2c_add_numbered_adapter(adap);
  10. 0. }

先看 i2c_bit_prepare_bus 函数

[html] view plaincopyprint?
  1. 1. static int i2c_bit_prepare_bus(struct i2c_adapter *adap)
  2. 2. {
  3. 3.     struct i2c_algo_bit_data *bit_adap = adap->algo_data;
  4. 4.
  5. 5.     if (bit_test) {
  6. 6.         int ret = test_bus(bit_adap, adap->name);
  7. 7.         if (ret < 0)
  8. 8.             return -ENODEV;
  9. 9.     }
  10. 10.
  11. 11.     /* register new adapter to i2c module... */
  12. 12.     adap->algo = &i2c_bit_algo;
  13. 13.     adap->retries = 3;
  14. 14.
  15. 15.     return 0;
  16. 16. }

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

来看这个结构定义

[html] view plaincopyprint?
  1. 1. static const struct i2c_algorithm i2c_bit_algo = {
  2. 2.     .master_xfer    = bit_xfer,
  3. 3.     .functionality  = bit_func,
  4. 4. };

先看这个结构类型在哪里定义的 include/linux/i2c.h

[html] view plaincopyprint?
  1. 1. struct i2c_algorithm {
  2. 2.     /* If an adapter algorithm can't do I2C-level access, set master_xfer
  3. 3.        to NULL. If an adapter algorithm can do SMBus access, set
  4. 4.        smbus_xfer. If set to NULL, the SMBus protocol is simulated
  5. 5.        using common I2C messages */
  6. 6.     /* master_xfer should return the number of messages successfully
  7. 7.        processed, or a negative value on error */
  8. 8.     int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
  9. 9.                int num);
  10. 10.     int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
  11. 11.                unsigned short flags, char read_write,
  12. 12.                u8 command, int size, union i2c_smbus_data *data);
  13. 13.
  14. 14.     /* To determine what the adapter supports */
  15. 15.     u32 (*functionality) (struct i2c_adapter *);
  16. 16. };

其实也没什么,就三个函数指针外加一长串注释

这个结构的master_xfer指针为主机的数据传输,具体来看bit_xfer这个函数,这个函数和I2C协议相关,I2C协议规定要先发送起始信号,才能开始进行数据的传输,最后数据传输完成后发送停止信号,看接下来代码对I2C协议要熟悉,所以这里的关键点是I2C协议。

  1. static int bit_xfer(struct i2c_adapter *i2c_adap,
  2. struct i2c_msg msgs[], int num)
  3. {
  4. struct i2c_msg *pmsg;
  5. struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
  6. int i, ret;
  7. unsigned short nak_ok;
  8. bit_dbg(3, &i2c_adap->dev, "emitting start condition\n");
  9. /*发送起始信号*/
  10. i2c_start(adap);
  11. for (i = 0; i < num; i++) {
  12. pmsg = &msgs[i];
  13. nak_ok = pmsg->flags & I2C_M_IGNORE_NAK;
  14. if (!(pmsg->flags & I2C_M_NOSTART)) {
  15. if (i) {
  16. bit_dbg(3, &i2c_adap->dev, "emitting "
  17. "repeated start condition\n");
  18. i2c_repstart(adap);
  19. }
  20. ret = bit_doAddress(i2c_adap, pmsg);
  21. if ((ret != 0) && !nak_ok) {
  22. bit_dbg(1, &i2c_adap->dev, "NAK from "
  23. "device addr 0x%02x msg #%d\n",
  24. msgs[i].addr, i);
  25. goto bailout;
  26. }
  27. }
  28. if (pmsg->flags & I2C_M_RD) {
  29. /* read bytes into buffer*/
  30. ret = readbytes(i2c_adap, pmsg);
  31. if (ret >= 1)
  32. bit_dbg(2, &i2c_adap->dev, "read %d byte%s\n",
  33. ret, ret == 1 ? "" : "s");
  34. if (ret < pmsg->len) {
  35. if (ret >= 0)
  36. ret = -EREMOTEIO;
  37. goto bailout;
  38. }
  39. } else {
  40. /* write bytes from buffer */
  41. ret = sendbytes(i2c_adap, pmsg);
  42. if (ret >= 1)
  43. bit_dbg(2, &i2c_adap->dev, "wrote %d byte%s\n",
  44. ret, ret == 1 ? "" : "s");
  45. if (ret < pmsg->len) {
  46. if (ret >= 0)
  47. ret = -EREMOTEIO;
  48. goto bailout;
  49. }
  50. }
  51. }
  52. ret = i;
  53. bailout:
  54. bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");
  55. i2c_stop(adap);
  56. return ret;
  57. }

1.发送起始信号

i2c_start(adap);

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

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

[html] view plaincopyprint?
  1. 1. static void i2c_start(struct i2c_algo_bit_data *adap)
  2. 2. {
  3. 3.     /* assert: scl, sda are high */
  4. 4.     setsda(adap, 0);
  5. 5.     udelay(adap->udelay);
  6. 6.     scllo(adap);
  7. 7. }

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

  1. #define setsda(adap, val)   adap->setsda(adap->data, val)
  2. #define setscl(adap, val)   adap->setscl(adap->data, val)
  3. #define getsda(adap)        adap->getsda(adap->data)
  4. #define getscl(adap)        adap->getscl(adap->data)

2.往下是个大的for循环

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

[html] view plaincopyprint?
  1. 1. struct i2c_msg {
  2. 2.     __u16 addr; /* slave address            */
  3. 3.     __u16 flags;
  4. 4. #define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
  5. 5. #define I2C_M_RD        0x0001  /* read data, from slave to master */
  6. 6. #define I2C_M_NOSTART       0x4000  /* if I2C_FUNC_PROTOCOL_MANGLING */
  7. 7. #define I2C_M_REV_DIR_ADDR  0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
  8. 8. #define I2C_M_IGNORE_NAK    0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
  9. 9. #define I2C_M_NO_RD_ACK     0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
  10. 10. #define I2C_M_RECV_LEN      0x0400  /* length will be first received byte */
  11. 11.     __u16 len;      /* msg length               */
  12. 12.     __u8 *buf;      /* pointer to msg data          */
  13. 13. };

这个结构专门用于数据传输相关的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函数去重新构造设备地址字节,来看这个函数。

  1. static int bit_doAddress(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
  2. {
  3. unsigned short flags = msg->flags;
  4. unsigned short nak_ok = msg->flags & I2C_M_IGNORE_NAK;
  5. struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
  6. unsigned char addr;
  7. int ret, retries;
  8. retries = nak_ok ? 0 : i2c_adap->retries;
  9. if (flags & I2C_M_TEN) {
  10. /* a ten bit address */
  11. addr = 0xf0 | ((msg->addr >> 7) & 0x03);
  12. bit_dbg(2, &i2c_adap->dev, "addr0: %d\n", addr);
  13. /* try extended address code...*/
  14. ret = try_address(i2c_adap, addr, retries);
  15. if ((ret != 1) && !nak_ok)  {
  16. dev_err(&i2c_adap->dev,
  17. "died at extended address code\n");
  18. return -EREMOTEIO;
  19. }
  20. /* the remaining 8 bit address */
  21. ret = i2c_outb(i2c_adap, msg->addr & 0x7f);
  22. if ((ret != 1) && !nak_ok) {
  23. /* the chip did not ack / xmission error occurred */
  24. dev_err(&i2c_adap->dev, "died at 2nd address code\n");
  25. return -EREMOTEIO;
  26. }
  27. if (flags & I2C_M_RD) {
  28. bit_dbg(3, &i2c_adap->dev, "emitting repeated "
  29. "start condition\n");
  30. i2c_repstart(adap);
  31. /* okay, now switch into reading mode */
  32. addr |= 0x01;
  33. ret = try_address(i2c_adap, addr, retries);
  34. if ((ret != 1) && !nak_ok) {
  35. dev_err(&i2c_adap->dev,
  36. "died at repeated address code\n");
  37. return -EREMOTEIO;
  38. }
  39. }
  40. } else {        /* normal 7bit address  */
  41. addr = msg->addr << 1;
  42. if (flags & I2C_M_RD)
  43. addr |= 1;
  44. if (flags & I2C_M_REV_DIR_ADDR)
  45. addr ^= 1;
  46. ret = try_address(i2c_adap, addr, retries);
  47. if ((ret != 1) && !nak_ok)
  48. return -ENXIO;
  49. }
  50. return 0;
  51. }

这里先做了一个判断, 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 函数将这个地址字节发送出去。

[html] view plaincopyprint?
  1. 1. static int try_address(struct i2c_adapter *i2c_adap,
  2. 2.                unsigned char addr, int retries)
  3. 3. {
  4. 4.     struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
  5. 5.     int i, ret = 0;
  6. 6.
  7. 7.     for (i = 0; i <= retries; i++) {
  8. 8.         ret = i2c_outb(i2c_adap, addr);
  9. 9.         if (ret == 1 || i == retries)
  10. 10.             break;
  11. 11.         bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");
  12. 12.         i2c_stop(adap);
  13. 13.         udelay(adap->udelay);
  14. 14.         yield();
  15. 15.         bit_dbg(3, &i2c_adap->dev, "emitting start condition\n");
  16. 16.         i2c_start(adap);
  17. 17.     }
  18. 18.     if (i && ret)
  19. 19.         bit_dbg(1, &i2c_adap->dev, "Used %d tries to %s client at "
  20. 20.             "0x%02x: %s\n", i + 1,
  21. 21.             addr & 1 ? "read from" : "write to", addr >> 1,
  22. 22.             ret == 1 ? "success" : "failed, timeout?");
  23. 23.     return ret;
  24. 24. }

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

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

[html] view plaincopyprint?
  1. 1. static int i2c_outb(struct i2c_adapter *i2c_adap, unsigned char c)
  2. 2. {
  3. 3.     int i;
  4. 4.     int sb;
  5. 5.     int ack;
  6. 6.     struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
  7. 7.
  8. 8.     /* assert: scl is low */
  9. 9.     for (i = 7; i >= 0; i--) {
  10. 10.         sb = (c >> i) & 1;
  11. 11.         setsda(adap, sb);
  12. 12.         udelay((adap->udelay + 1) / 2);
  13. 13.         if (sclhi(adap) < 0) { /* timed out */
  14. 14.             bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
  15. 15.                 "timeout at bit #%d\n", (int)c, i);
  16. 16.             return -ETIMEDOUT;
  17. 17.         }
  18. 18.         /* FIXME do arbitration here:
  19. 19.          * if (sb && !getsda(adap)) -> ouch! Get out of here.
  20. 20.          *
  21. 21.          * Report a unique code, so higher level code can retry
  22. 22.          * the whole (combined) message and *NOT* issue STOP.
  23. 23.          */
  24. 24.         scllo(adap);
  25. 25.     }
  26. 26.     sdahi(adap);
  27. 27.     if (sclhi(adap) < 0) { /* timeout */
  28. 28.         bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
  29. 29.             "timeout at ack\n", (int)c);
  30. 30.         return -ETIMEDOUT;
  31. 31.     }
  32. 32.
  33. 33.     /* read ack: SDA should be pulled down by slave, or it may
  34. 34.      * NAK (usually to report problems with the data we wrote).
  35. 35.      */
  36. 36.     ack = !getsda(adap);    /* ack: sda is pulled low -> success */
  37. 37.     bit_dbg(2, &i2c_adap->dev, "i2c_outb: 0x%02x %s\n", (int)c,
  38. 38.         ack ? "A" : "NA");
  39. 39.
  40. 40.     scllo(adap);
  41. 41.     return ack;
  42. 42.     /* assert: scl is low (sda undef) */
  43. 43. }

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

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

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

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

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

[html] view plaincopyprint?
  1. 1. static int readbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
  2. 2. {
  3. 3.     int inval;
  4. 4.     int rdcount = 0;    /* counts bytes read */
  5. 5.     unsigned char *temp = msg->buf;
  6. 6.     int count = msg->len;
  7. 7.     const unsigned flags = msg->flags;
  8. 8.
  9. 9.     while (count > 0) {
  10. 10.         inval = i2c_inb(i2c_adap);
  11. 11.         if (inval >= 0) {
  12. 12.             *temp = inval;
  13. 13.             rdcount++;
  14. 14.         } else {   /* read timed out */
  15. 15.             break;
  16. 16.         }
  17. 17.
  18. 18.         temp++;
  19. 19.         count--;
  20. 20.
  21. 21.         /* Some SMBus transactions require that we receive the
  22. 22.            transaction length as the first read byte. */
  23. 23.         if (rdcount == 1 && (flags & I2C_M_RECV_LEN)) {
  24. 24.             if (inval <= 0 || inval > I2C_SMBUS_BLOCK_MAX) {
  25. 25.                 if (!(flags & I2C_M_NO_RD_ACK))
  26. 26.                     acknak(i2c_adap, 0);
  27. 27.                 dev_err(&i2c_adap->dev, "readbytes: invalid "
  28. 28.                     "block length (%d)\n", inval);
  29. 29.                 return -EREMOTEIO;
  30. 30.             }
  31. 31.             /* The original count value accounts for the extra
  32. 32.                bytes, that is, either 1 for a regular transaction,
  33. 33.                or 2 for a PEC transaction. */
  34. 34.             count += inval;
  35. 35.             msg->len += inval;
  36. 36.         }
  37. 37.
  38. 38.         bit_dbg(2, &i2c_adap->dev, "readbytes: 0x%02x %s\n",
  39. 39.             inval,
  40. 40.             (flags & I2C_M_NO_RD_ACK)
  41. 41.                 ? "(no ack/nak)"
  42. 42.                 : (count ? "A" : "NA"));
  43. 43.
  44. 44.         if (!(flags & I2C_M_NO_RD_ACK)) {
  45. 45.             inval = acknak(i2c_adap, count);
  46. 46.             if (inval < 0)
  47. 47.                 return inval;
  48. 48.         }
  49. 49.     }
  50. 50.     return rdcount;
  51. 51. }

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

那就来看i2c_inb函数。

  1. static int i2c_inb(struct i2c_adapter *i2c_adap)
  2. {
  3. /* read byte via i2c port, without start/stop sequence  */
  4. /* acknowledge is sent in i2c_read.         */
  5. int i;
  6. unsigned char indata = 0;
  7. struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
  8. /* assert: scl is low */
  9. sdahi(adap);
  10. for (i = 0; i < 8; i++) {
  11. if (sclhi(adap) < 0) { /* timeout */
  12. bit_dbg(1, &i2c_adap->dev, "i2c_inb: timeout at bit "
  13. "#%d\n", 7 - i);
  14. return -ETIMEDOUT;
  15. }
  16. indata *= 2;
  17. if (getsda(adap))
  18. indata |= 0x01;
  19. setscl(adap, 0);
  20. udelay(i == 7 ? adap->udelay / 2 : adap->udelay);
  21. }
  22. /* assert: scl is low */
  23. return indata;
  24. }

再来看 sendbytes 函数

[html] view plaincopyprint?
  1. 1. static int sendbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
  2. 2. {
  3. 3.     const unsigned char *temp = msg->buf;
  4. 4.     int count = msg->len;
  5. 5.     unsigned short nak_ok = msg->flags & I2C_M_IGNORE_NAK;
  6. 6.     int retval;
  7. 7.     int wrcount = 0;
  8. 8.
  9. 9.     while (count > 0) {
  10. 10.         retval = i2c_outb(i2c_adap, *temp);
  11. 11.
  12. 12.         /* OK/ACK; or ignored NAK */
  13. 13.         if ((retval > 0) || (nak_ok && (retval == 0))) {
  14. 14.             count--;
  15. 15.             temp++;
  16. 16.             wrcount++;
  17. 17.
  18. 18.         /* A slave NAKing the master means the slave didn't like
  19. 19.          * something about the data it saw.  For example, maybe
  20. 20.          * the SMBus PEC was wrong.
  21. 21.          */
  22. 22.         } else if (retval == 0) {
  23. 23.             dev_err(&i2c_adap->dev, "sendbytes: NAK bailout.\n");
  24. 24.             return -EIO;
  25. 25.
  26. 26.         /* Timeout; or (someday) lost arbitration
  27. 27.          *
  28. 28.          * FIXME Lost ARB implies retrying the transaction from
  29. 29.          * the first message, after the "winning" master issues
  30. 30.          * its STOP.  As a rule, upper layer code has no reason
  31. 31.          * to know or care about this ... it is *NOT* an error.
  32. 32.          */
  33. 33.         } else {
  34. 34.             dev_err(&i2c_adap->dev, "sendbytes: error %d\n",
  35. 35.                     retval);
  36. 36.             return retval;
  37. 37.         }
  38. 38.     }
  39. 39.     return wrcount;
  40. 40. }

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

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

[html] view plaincopyprint?
  1. 1. static void i2c_stop(struct i2c_algo_bit_data *adap)
  2. 2. {
  3. 3.     /* assert: scl is low */
  4. 4.     sdalo(adap);
  5. 5.     sclhi(adap);
  6. 6.     setsda(adap, 1);
  7. 7.     udelay(adap->udelay);
  8. 8. }

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

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

[html] view plaincopyprint?
  1. 1. int i2c_add_numbered_adapter(struct i2c_adapter *adap)
  2. 2. {
  3. 3.     int id;
  4. 4.     int status;
  5. 5.
  6. 6.     if (adap->nr & ~MAX_ID_MASK)
  7. 7.         return -EINVAL;
  8. 8.
  9. 9. retry:
  10. 10.     if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
  11. 11.         return -ENOMEM;
  12. 12.
  13. 13.     mutex_lock(&core_lock);
  14. 14.     /* "above" here means "above or equal to", sigh;
  15. 15.      * we need the "equal to" result to force the result
  16. 16.      */
  17. 17.     status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
  18. 18.     if (status == 0 && id != adap->nr) {
  19. 19.         status = -EBUSY;
  20. 20.         idr_remove(&i2c_adapter_idr, id);
  21. 21.     }
  22. 22.     mutex_unlock(&core_lock);
  23. 23.     if (status == -EAGAIN)
  24. 24.         goto retry;
  25. 25.
  26. 26.     if (status == 0)
  27. 27.         status = i2c_register_adapter(adap);
  28. 28.     return status;
  29. 29. }

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

  1. static int i2c_register_adapter(struct i2c_adapter *adap)
  2. {
  3. int res = 0, dummy;
  4. /* Can't register until after driver model init */
  5. if (unlikely(WARN_ON(!i2c_bus_type.p))) {
  6. res = -EAGAIN;
  7. goto out_list;
  8. }
  9. mutex_init(&adap->bus_lock);
  10. /* Set default timeout to 1 second if not already set */
  11. if (adap->timeout == 0)
  12. adap->timeout = HZ;
  13. dev_set_name(&adap->dev, "i2c-%d", adap->nr);
  14. adap->dev.bus = &i2c_bus_type;
  15. adap->dev.type = &i2c_adapter_type;
  16. res = device_register(&adap->dev);
  17. if (res)
  18. goto out_list;
  19. dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
  20. #ifdef CONFIG_I2C_COMPAT
  21. res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
  22. adap->dev.parent);
  23. if (res)
  24. dev_warn(&adap->dev,
  25. "Failed to create compatibility class link\n");
  26. #endif
  27. /* create pre-declared device nodes */
  28. if (adap->nr < __i2c_first_dynamic_bus_num)
  29. i2c_scan_static_board_info(adap);
  30. /* Notify drivers */
  31. mutex_lock(&core_lock);
  32. dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
  33. i2c_do_add_adapter);
  34. mutex_unlock(&core_lock);
  35. return 0;
  36. out_list:
  37. mutex_lock(&core_lock);
  38. idr_remove(&i2c_adapter_idr, adap->nr);
  39. mutex_unlock(&core_lock);
  40. return res;
  41. }

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

在这里我认为主要的有

1.注册这个I2C总线设备

[html] view plaincopyprint?
  1. 1. adap->dev.bus = &i2c_bus_type;
  2. 2. adap->dev.type = &i2c_adapter_type;
  3. 3. res = device_register(&adap->dev);

这个设备的总线类型为 i2c_bus_type

[html] view plaincopyprint?
  1. 1. struct bus_type i2c_bus_type = {
  2. 2.     .name       = "i2c",
  3. 3.     .match      = i2c_device_match,
  4. 4.     .probe      = i2c_device_probe,
  5. 5.     .remove     = i2c_device_remove,
  6. 6.     .shutdown   = i2c_device_shutdown,
  7. 7.     .suspend    = i2c_device_suspend,
  8. 8.     .resume     = i2c_device_resume,
  9. 9. };

看一下它的 match 函数

[html] view plaincopyprint?
  1. 1. static int i2c_device_match(struct device *dev, struct device_driver *drv)
  2. 2. {
  3. 3.     struct i2c_client   *client = i2c_verify_client(dev);
  4. 4.     struct i2c_driver   *driver;
  5. 5.
  6. 6.     if (!client)
  7. 7.         return 0;
  8. 8.
  9. 9.     driver = to_i2c_driver(drv);
  10. 10.     /* match on an id table if there is one */
  11. 11.     if (driver->id_table)
  12. 12.         return i2c_match_id(driver->id_table, client) != NULL;
  13. 13.
  14. 14.     return 0;
  15. 15. }

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

[html] view plaincopyprint?
  1. 1. static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
  2. 2.                         const struct i2c_client *client)
  3. 3. {
  4. 4.     while (id->name[0]) {
  5. 5.         if (strcmp(client->name, id->name) == 0)
  6. 6.             return id;
  7. 7.         id++;
  8. 8.     }
  9. 9.     return NULL;
  10. 0. }

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

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

[html] view plaincopyprint?
  1. 1. static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
  2. 2. {
  3. 3.     struct i2c_devinfo  *devinfo;
  4. 4.
  5. 5.     down_read(&__i2c_board_lock);
  6. 6.     list_for_each_entry(devinfo, &__i2c_board_list, list) {
  7. 7.         if (devinfo->busnum == adapter->nr
  8. 8.                 && !i2c_new_device(adapter,
  9. 9.                         &devinfo->board_info))
  10. 10.             dev_err(&adapter->dev,
  11. 11.                 "Can't create device at 0x%02x\n",
  12. 12.                 devinfo->board_info.addr);
  13. 13.     }
  14. 14.     up_read(&__i2c_board_lock);
  15. 15. }

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

[html] view plaincopyprint?
  1. 1. struct i2c_board_info {
  2. 2.     char        type[I2C_NAME_SIZE];
  3. 3.     unsigned short  flags;
  4. 4.     unsigned short  addr;
  5. 5.     void        *platform_data;
  6. 6.     struct dev_archdata *archdata;
  7. 7.     int     irq;
  8. 8. };

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

[html] view plaincopyprint?
  1. # #define I2C_BOARD_INFO(dev_type, dev_addr) \
  2. #     .type = dev_type, .addr = (dev_addr)
[html] view plaincopyprint?
  1. dev_type为设备的名字,前面也说了,这个name一定要和I2C驱动相同。addr为设备的地址。
  2. 定义了这样一组信息之后呢,接下来当然是往链表添加这些信息了。
[html] view plaincopyprint?
  1. 1. int __init
  2. 2. i2c_register_board_info(int busnum,
  3. 3.     struct i2c_board_info const *info, unsigned len)
  4. 4. {
  5. 5.     int status;
  6. 6.
  7. 7.     down_write(&__i2c_board_lock);
  8. 8.
  9. 9.     /* dynamic bus numbers will be assigned after the last static one */
  10. 10.     if (busnum >= __i2c_first_dynamic_bus_num)
  11. 11.         __i2c_first_dynamic_bus_num = busnum + 1;
  12. 12.
  13. 13.     for (status = 0; len; len--, info++) {
  14. 14.         struct i2c_devinfo  *devinfo;
  15. 15.
  16. 16.         devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
  17. 17.         if (!devinfo) {
  18. 18.             pr_debug("i2c-core: can't register boardinfo!\n");
  19. 19.             status = -ENOMEM;
  20. 20.             break;
  21. 21.         }
  22. 22.
  23. 23.         devinfo->busnum = busnum;
  24. 24.         devinfo->board_info = *info;
  25. 25.         list_add_tail(&devinfo->list, &__i2c_board_list);
  26. 26.     }
  27. 27.
  28. 28.     up_write(&__i2c_board_lock);
  29. 29.
  30. 30.     return status;
  31. 31. }

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

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

然后是i2c_new_device

[html] view plaincopyprint?
  1. # struct i2c_client *
  2. # i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
  3. # {
  4. #     struct i2c_client   *client;
  5. #     int         status;
  6. #
  7. #     /*为I2C设备申请内存*/
  8. #     client = kzalloc(sizeof *client, GFP_KERNEL);
  9. #     if (!client)
  10. #         return NULL;
  11. #
  12. #     /*指定I2C设备的总线*/
  13. #     client->adapter = adap;
  14. #
  15. #     client->dev.platform_data = info->platform_data;
  16. #
  17. #     if (info->archdata)
  18. #         client->dev.archdata = *info->archdata;
  19. #
  20. #     client->flags = info->flags;
  21. #     client->addr = info->addr; /*I2C设备地址*/
  22. #     client->irq = info->irq;
  23. #
  24. #     strlcpy(client->name, info->type, sizeof(client->name));
  25. #
  26. #     /*检查这个地址有没有被设备占用*/
  27. #     /* Check for address business */
  28. #     status = i2c_check_addr(adap, client->addr);
  29. #     if (status)
  30. #         goto out_err;
  31. #
  32. #     client->dev.parent = &client->adapter->dev; /*指定设备的父设备*/
  33. #     client->dev.bus = &i2c_bus_type; /*指定设备的总线类型*/
  34. #     client->dev.type = &i2c_client_type;
  35. #
  36. #     dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
  37. #              client->addr);
  38. #     status = device_register(&client->dev); /*注册设备*/
  39. #     if (status)
  40. #         goto out_err;
  41. #
  42. #     dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
  43. #         client->name, dev_name(&client->dev));
  44. #
  45. #     return client;
  46. #
  47. # out_err:
  48. #     dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
  49. #         "(%d)\n", client->name, client->addr, status);
  50. #     kfree(client);
  51. #     return NULL;

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

3.i2c_do_add_adapter

你看说着说着就跑远了

[html] view plaincopyprint?
  1. 1. static int i2c_do_add_adapter(struct device_driver *d, void *data)
  2. 2. {
  3. 3.     struct i2c_driver *driver = to_i2c_driver(d);
  4. 4.     struct i2c_adapter *adap = data;
  5. 5.
  6. 6.     /* Detect supported devices on that bus, and instantiate them */
  7. 7.     i2c_detect(adap, driver);
  8. 8.
  9. 9.     /* Let legacy drivers scan this bus for matching devices */
  10. 10.     if (driver->attach_adapter) {
  11. 11.         /* We ignore the return code; if it fails, too bad */
  12. 12.         driver->attach_adapter(adap);
  13. 13.     }
  14. 14.     return 0;
  15. 15. }

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

Linux I2C子系统分析之(一) ----- 用GPIO模拟I2C总线相关推荐

  1. Linux I2C子系统分析-I2C设备驱动

    接下来以一个实际的例子来看I2C设备驱动,就以drivers/i2c/i2c-dev.c为例. 先看它的初始化和注销函数 [cpp] view plaincopy static int __init  ...

  2. Linux I2C子系统分析-I2C总线驱动

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

  3. linux IIC子系统分析(九)——实例分析通过设备节点访问I2c设备

    在< linux IIC子系统分析(四)--I2c bus初始化> 中我们创建了I2C 总线驱动,I2C adapter device 和adapter drivers也在这时创建 在&l ...

  4. linux IIC子系统分析(二)—— linux i2c 架构概述

    I2C总线因为它及简单的硬件连接和通讯方式,在现在的很多设备上它是一种不可或缺的通讯总线.如果用当单片机直接操作I2C,其实很简单,只要正确把握IIC的操作时序就可以了.但是在linux系统中,I2C ...

  5. Linux MMC子系统分析(二)——Host分析

    Linux MMC子系统分析(二)--Host分析 前言 通过前面对mmc子系统的模型分析,我们能够知道host是对应于硬件控制器的具体操作.本文将以sdhci-s3c.c为例对host进行简单的分析 ...

  6. Linux input子系统分析

    输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见.同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分 ...

  7. linux源码gpio模拟i2c,linux内核gpio模拟i2c实例.doc

    linux内核gpio模拟i2c实例.doc linux内核GPIO模拟I2C实例2010-10-11作者:cvip302814来源:cvip302814的blog前言:在许多情况下,我们并没有足够的 ...

  8. linux input子系统分析--子系统核心.事件处理层.事件传递过程

    linux input子系统分析--子系统核心.事件处理层.事件传递过程 一.  输入子系统核心分析. 1.输入子系统核心对应与/drivers/input/input.c文件,这个也是作为一个模块注 ...

  9. linux input子系统分析--主要函数

    linux input子系统分析--主要函数 一. 各种注册函数 因为分析一所讲的每种数据结构都代表一类对象,所以每种数据结构都会对应一个注册函数,他们都定义在子系统核心的input.c文件中.主要有 ...

最新文章

  1. 将csv文件导入到mysql数据库
  2. GFS架构启示 | Google File System
  3. [CODEVS1205]单词反转
  4. controller是什么意思_SpringMVC是什么??
  5. Linux运维系统工程师系列---04
  6. linux7配置静态地址,Centos7 配置静态 IP 地址的方法
  7. Git-第N篇碰见的一些问题
  8. zookeeper分布式协调服务的使用一
  9. python photoshop自动化_你会用Python 搞定你的电子签名吗?
  10. DEA数据包络分析python代码记录
  11. 计算机 矩阵乘法,计算机算法:Strassen矩阵相乘算法
  12. Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配
  13. 网络安全行业全领域白皮书
  14. Android 封装标题栏
  15. 目前开发人工日多少钱_开发一个app多少钱啊?
  16. 怎么用微信公众号领取淘宝诶不优惠券购物教程
  17. 【逆向】i春秋入门实战——crack_me
  18. 联想突围:老外主守、本土主攻
  19. 花了10分钟终于明白矩阵的逆怎么用了!
  20. 计算机软件从业证书,互联网行业从业人员证书

热门文章

  1. 锐捷ap怎么设置_WiFi又不稳定?怎么办,看这里
  2. 实现在Windows下安装Lighttpd的方法
  3. MyBatis源码解析【4】反射和动态代理
  4. npm包实现发布正式和测试版
  5. 数组与字符串三(Cocos2d-x 3.x _Array容器)
  6. 前端面试题整理(js)
  7. 2015 编程之美 八卦的小冰
  8. 23个经典JDK设计模式(转)
  9. 测试常用shell语句——数值,数组类型;函数创建
  10. C++二维数组new小结(zz)