且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

I.MX6 Linux I2C device& driver hacking

更新时间:2022-08-12 20:59:01

/*******************************************************************************************
 *                          I.MX6 Linux I2C device& driver hacking
 * 声明:
 *     1. 本文主要是对Linux I2C驱动进行代码跟踪,主要是为了能够对I2C驱动框架有个全面的了解;
 *     2. 本文源代码来自myzr_android4_2_2_1_1_0.tar.bz2;
 *     3. 如果你有兴趣,请尽量自己去对代码进行跟踪,这样自己会对I2C有一个框架结构上的理解;
 *
 *                                                2015-6-4 晴 深圳 南山平山村 曾剑锋
 ******************************************************************************************/

                     \\\\\\\\\\\\\\\\\-*- 目录 -*-/////////////////                  
                     |   一、跟踪板级文件: 
                     |   二、跟踪mx6q_sabresd_i2c_data参数:
                     |   三、跟踪imx6q_add_imx_i2c()函数:
                     |   四、跟踪imx6q_imx_i2c_data参数:
                     |   五、跟踪imx_add_imx_i2c()函数:
                     |   六、跟踪mxc_i2c0_board_info参数:
                     |   七、跟踪i2c_register_board_info()函数:
                     |   八、I2C adapter(适配器)跟踪:
                     |   九、I2C设备追踪:
                     |   十、跟踪__process_new_driver参数:
                     |   十一、跟踪i2c_for_each_dev()函数:
                     |   十二、跟踪max6875_probe()函数:
                     \\\\\\\\\\\\\\\\\\\\\\\///////////////////////                                        



一、跟踪板级文件: 
    1. cat arch/arm/mach-mx6/board-mx6q_sabresd.c
        ......
        /*
         * initialize __mach_desc_MX6Q_SABRESD data structure.
         */
        MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board")
            /* Maintainer: Freescale Semiconductor, Inc. */
            .boot_params = MX6_PHYS_OFFSET + 0x100,
            .fixup = fixup_mxc_board,
            .map_io = mx6_map_io,
            .init_irq = mx6_init_irq,
            .init_machine = mx6_sabresd_board_init,   //跟踪板级初始化函数
            .timer = &mx6_sabresd_timer,
            .reserve = mx6q_sabresd_reserve,
        MACHINE_END

    2. cat arch/arm/mach-mx6/board-mx6q_sabresd.c
        ......
        /*!
         * Board specific initialization.
         */
        static void __init mx6_sabresd_board_init(void)
        {
            ......
            /**
             * 接下来我们需要向以下4个方向去跟踪代码:
             *     1. 跟踪mx6q_sabresd_i2c_data参数;
             *     2. 跟踪imx6q_add_imx_i2c()函数;
             *     3. 跟踪mxc_i2c0_board_info参数,在里面继续跟踪wm8962_config_data,
             *         因为下面这两行代码修改了mxc_i2c0_board_info第一个元素;
             *     4. 跟踪i2c_register_board_info()函数;
             */
            strcpy(mxc_i2c0_board_info[0].type, "wm8962");
                mxc_i2c0_board_info[0].platform_data = &wm8962_config_data;  //跟踪wm8962_config_data

            /**
             * 这里相当于注册I2C控制器
             */
            imx6q_add_imx_i2c(0, &mx6q_sabresd_i2c_data);   //跟踪参数,函数
            imx6q_add_imx_i2c(1, &mx6q_sabresd_i2c_data);
            imx6q_add_imx_i2c(2, &mx6q_sabresd_i2c_data);

            /**
             * 这里相当于注册I2C设备
             */
            i2c_register_board_info(0, mxc_i2c0_board_info, //跟踪参数,函数
                    ARRAY_SIZE(mxc_i2c0_board_info));
            i2c_register_board_info(1, mxc_i2c1_board_info,
                    ARRAY_SIZE(mxc_i2c1_board_info));
            i2c_register_board_info(2, mxc_i2c2_board_info,
                    ARRAY_SIZE(mxc_i2c2_board_info));
            ......
        }
        ......

二、跟踪mx6q_sabresd_i2c_data参数:
    1. cat arch/arm/mach-mx6/board-mx6q_sabresd.c
        ......
        static struct imxi2c_platform_data mx6q_sabresd_i2c_data = {  //跟踪结构体
            .bitrate = 100000,
        };
        ......

    2. cat arch/arm/plat-mxc/include/mach/i2c.h
        ......
        struct imxi2c_platform_data {
            int (*init)(struct device *dev);
            void (*exit)(struct device *dev);
            int bitrate;
        };
        ......

三、跟踪imx6q_add_imx_i2c()函数:
    cat arch/arm/mach-mx6/devices-imx6q.h
        ......
        /**
         * 这里需要跟踪2个方向:
         *     1. 跟踪imx6q_imx_i2c_data参数;
         *     2. 跟踪imx_add_imx_i2c()函数;
         */
        extern const struct imx_imx_i2c_data imx6q_imx_i2c_data[] __initconst;
        #define imx6q_add_imx_i2c(id, pdata)    \
            imx_add_imx_i2c(&imx6q_imx_i2c_data[id], pdata)   //跟踪结构体,函数
        ......

四、跟踪imx6q_imx_i2c_data参数:
    1. cat arch/arm/plat-mxc/devices/platform-imx-i2c.c
        ......
        #ifdef CONFIG_SOC_IMX6Q
        const struct imx_imx_i2c_data imx6q_imx_i2c_data[] __initconst = {
        #define imx6q_imx_i2c_data_entry(_id, _hwid)                \
            imx_imx_i2c_data_entry(MX6Q, _id, _hwid, SZ_4K)
            imx6q_imx_i2c_data_entry(0, 1),     //跟踪这个宏
            imx6q_imx_i2c_data_entry(1, 2),
            imx6q_imx_i2c_data_entry(2, 3),
        };
        #endif /* ifdef CONFIG_SOC_IMX6Q */
        ......

    2. cat arch/arm/plat-mxc/include/mach/devices-common.h
        ......
        #include <mach/i2c.h>
        struct imx_imx_i2c_data {
            int id;
            resource_size_t iobase;
            resource_size_t iosize;
            resource_size_t irq;
        };
        ......

    3. cat arch/arm/plat-mxc/devices/platform-imx-i2c.c
        ......
        /**
         *  1. 如果传入参数是:
         *      1. soc = MX6Q;
         *      2. _id = 0;
         *      3. _hwid = 1;
         *      4. size = SZ_4K;
         *  2. .iobase = soc ## _I2C ## _hwid ## _BASE_ADDR合成结果:
         *      .iobase = MX6Q_I2C1_BASE_ADDR = 0x021A0000 (看后面的跟踪代码推演计算)
         *  3. .irq = soc ## _INT_I2C ## _hwid合成结果:
         *      .irq = MX6Q_INT_I2C1 = 68 
         */
        #define imx_imx_i2c_data_entry_single(soc, _id, _hwid, _size)        \
            {                                \
                .id = _id,                        \
                .iobase = soc ## _I2C ## _hwid ## _BASE_ADDR,        \   //跟踪合成后的宏
                .iosize = _size,                    \
                .irq = soc ## _INT_I2C ## _hwid,            \
            }

        #define imx_imx_i2c_data_entry(soc, _id, _hwid, _size)            \
            [_id] = imx_imx_i2c_data_entry_single(soc, _id, _hwid, _size)
        ......

    4. cat arch/arm/plat-mxc/include/mach/mx6.h
        ......
        /**
         * 1. I2C1首地址推演计算:
         *      MX6Q_I2C1_BASE_ADDR = (AIPS2_OFF_BASE_ADDR + 0x20000)
         *      MX6Q_I2C1_BASE_ADDR = ((ATZ2_BASE_ADDR + 0x80000) + 0x20000)
         *      MX6Q_I2C1_BASE_ADDR = ((AIPS2_ARB_BASE_ADDR + 0x80000) + 0x20000)
         *      MX6Q_I2C1_BASE_ADDR = ((0x02100000 + 0x80000) + 0x20000)
         *      MX6Q_I2C1_BASE_ADDR = (0x02100000 + 0x80000 + 0x20000)
         *      MX6Q_I2C1_BASE_ADDR = (0x02100000 + 0x80000 + 0x20000)
         *      MX6Q_I2C1_BASE_ADDR = 0x021A0000 (符合下面参考书给出的首地址)
         * 2. 参考书IMX6DQRM_revC.pdf给出的I2C1的首地址:
         *      -------------------------------------------------------------
         *      | Start Address | End Address | Region | Allocation  | Size |
         *      +---------------+-------------+--------+-------------+------+
         *      |   021A_0000   |  021A_3FFF  | AIPS-2 |    I2C1     | 16KB |
         *      -------------------------------------------------------------
         */
        #define MX6Q_I2C1_BASE_ADDR        (AIPS2_OFF_BASE_ADDR + 0x20000)
        ......
        /* ATZ#2- Off Platform */
        #define AIPS2_OFF_BASE_ADDR        (ATZ2_BASE_ADDR + 0x80000)
        ......
        #define ATZ2_BASE_ADDR            AIPS2_ARB_BASE_ADDR
        ......
        #define AIPS2_ARB_BASE_ADDR        0x02100000
        ......

    5. cat arch/arm/plat-mxc/include/mach/mx6.h
        ......
        /**
         *  1. 参考书IMX6DQRM_revC.pdf给出的I2C1的中断号:
         *      ---------------------------------------------
         *      |IRQ | Interrupt  | Interrupt Description   |
         *      |    |  Source    |                         |
         *      +----+------------+-------------------------+
         *      |68  | ECSPI1I2C1 | I2C1 interrupt request. |
         *      ---------------------------------------------
         *  2. 非常精准的符合参好书  :)
         */
        #define MX6Q_INT_I2C1            68
        ......

五、跟踪imx_add_imx_i2c()函数:
    1. cat arch/arm/plat-mxc/devices/platform-imx-i2c.c
        ......
        struct platform_device *__init imx_add_imx_i2c(
                const struct imx_imx_i2c_data *data,
                const struct imxi2c_platform_data *pdata)
        {
            //利用传入参数合成平台资源数据
            struct resource res[] = {
                {
                    .start = data->iobase,
                    .end = data->iobase + data->iosize - 1,
                    .flags = IORESOURCE_MEM,
                }, {
                    .start = data->irq,
                    .end = data->irq,
                    .flags = IORESOURCE_IRQ,
                },
            };

            /**
             * 从这里可以知道设备匹配时候的名字,跟踪该函数
             */
            return imx_add_platform_device("imx-i2c", data->id,
                    res, ARRAY_SIZE(res),
                    pdata, sizeof(*pdata));         
        }
        ......

    2. cat arch/arm/plat-mxc/include/mach/devices-common.h
        ......
        static inline struct platform_device *imx_add_platform_device(
                const char *name, int id,
                const struct resource *res, unsigned int num_resources,
                const void *data, size_t size_data)
        {
            //跟踪该函数
            return imx_add_platform_device_dmamask(
                    name, id, res, num_resources, data, size_data, 0);
        }
        ......

    3. cat arch/arm/plat-mxc/devices.c
        ......
        struct platform_device *__init imx_add_platform_device_dmamask(
                const char *name, int id,
                const struct resource *res, unsigned int num_resources,
                const void *data, size_t size_data, u64 dmamask)
        {
            int ret = -ENOMEM;
            struct platform_device *pdev;

            pdev = platform_device_alloc(name, id);
            if (!pdev)
                goto err;
            ......
            if (res) {
                ret = platform_device_add_resources(pdev, res, num_resources);
                if (ret)
                    goto err;
            }

            if (data) {
                ret = platform_device_add_data(pdev, data, size_data);
                if (ret)
                    goto err;
            }

            ret = platform_device_add(pdev);    //设备注册
            if (ret) {
        err:
                if (dmamask)
                    kfree(pdev->dev.dma_mask);
                platform_device_put(pdev);
                return ERR_PTR(ret);
            }

            return pdev;
        }
        ......

六、跟踪mxc_i2c0_board_info参数:
    1. cat arch/arm/mach-mx6/board-mx6q_sabresd.c
        ......
        static struct i2c_board_info mxc_i2c0_board_info[] __initdata = {  //跟踪结构体
            /**
             * 这是板级初始化函数中的另一部分:
             *  strcpy(mxc_i2c0_board_info[0].type, "wm8962");
             *      mxc_i2c0_board_info[0].platform_data = &wm8962_config_data;  //跟踪目标wm8962_config_data
             *
             *  通过这里可以知道wm8962的I2C地址是:0x1a
             */
            {
                I2C_BOARD_INFO("wm89**", 0x1a),
            },
            {
                I2C_BOARD_INFO("ov564x", 0x3c),
                .platform_data = (void *)&camera_data,
            },
            {
                I2C_BOARD_INFO("mma8451", 0x1d),
                .platform_data = (void *)&mma8451_position,
            },
            {
                I2C_BOARD_INFO("isl1208", 0x6f),
            },
        };
        ......

    2. cat include/linux/i2c.h
        ......
        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;
            int        irq;
        };
        ......

    3. cat arch/arm/mach-mx6/board-mx6q_sabresd.c
        ......
        static struct wm8962_pdata wm8962_config_data = { //跟踪结构体
            .gpio_init = {
                [2] = WM8962_GPIO_FN_DMICCLK,
                [4] = 0x8000 | WM8962_GPIO_FN_DMICDAT,
            },
        };
        ......

    4. cat include/sound/wm8962.h
        ......
        struct wm8962_pdata {
            int gpio_base;
            u32 gpio_init[WM8962_MAX_GPIO];

            /* Setup for microphone detection, raw value to be written to
             * R48(0x30) - only microphone related bits will be updated.
             * Detection may be enabled here for use with signals brought
             * out on the GPIOs. */
            u32 mic_cfg;

            bool irq_active_low;

            bool spk_mono;   /* Speaker outputs tied together as mono */
        };
        ......

七、跟踪i2c_register_board_info()函数:
    1. cat drivers/i2c/i2c-boardinfo.c
        ......
        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); //跟踪__i2c_board_list
            }

            up_write(&__i2c_board_lock);

            return status;
        }
        ......

    2. cat drivers/i2c/i2c-boardinfo.c
        ......
        LIST_HEAD(__i2c_board_list);    一个公用的I2C链表头
        EXPORT_SYMBOL_GPL(__i2c_board_list);
        ......

八、I2C adapter(适配器)跟踪:
    1. cat drivers/i2c/busses/i2c-imx.c
        ......
        /* This will be the driver name the kernel reports */
        #define DRIVER_NAME "imx-i2c"       //adapter和I2C控制器匹配的名字

        /* Default value */
        #define IMX_I2C_BIT_RATE    100000    /* 100kHz */

        static struct platform_driver i2c_imx_driver = {
            .remove        = __exit_p(i2c_imx_remove),
            .driver    = {
                .name    = DRIVER_NAME,
                .owner    = THIS_MODULE,
            }
        };

        static int __init i2c_adap_imx_init(void)
        {
            return platform_driver_probe(&i2c_imx_driver, i2c_imx_probe);  //跟踪函数,参数
        }
        subsys_initcall(i2c_adap_imx_init);

        static void __exit i2c_adap_imx_exit(void)
        {
            platform_driver_unregister(&i2c_imx_driver);
        }
        module_exit(i2c_adap_imx_exit);

        MODULE_LICENSE("GPL");
        MODULE_AUTHOR("Darius Augulis");
        MODULE_DESCRIPTION("I2C adapter driver for IMX I2C bus");  //这里说明了这个驱动的作用
        MODULE_ALIAS("platform:" DRIVER_NAME);

    2. cat drivers/base/platform.c
        ......
        int __init_or_module platform_driver_probe(struct platform_driver *drv,
                int (*probe)(struct platform_device *))
        {
            int retval, code;

            /* make sure driver won't have bind/unbind attributes */
            drv->driver.suppress_bind_attrs = true;

            /* temporary section violation during probe() */
            /**
             * 主要注意下面这行代码
             */
            drv->probe = probe;
            retval = code = platform_driver_register(drv);

            /*
             * Fixup that section violation, being paranoid about code scanning
             * the list of drivers in order to probe new devices.  Check to see
             * if the probe was successful, and make sure any forced probes of
             * new devices fail.
             */
            spin_lock(&drv->driver.bus->p->klist_drivers.k_lock);
            drv->probe = NULL;
            if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list))
                retval = -ENODEV;
            drv->driver.probe = platform_drv_probe_fail;
            spin_unlock(&drv->driver.bus->p->klist_drivers.k_lock);

            if (code != retval)
                platform_driver_unregister(drv);
            return retval;
        }
        EXPORT_SYMBOL_GPL(platform_driver_probe);
        ......

    3. cat drivers/i2c/busses/i2c-imx.c
        ......
        static int __init i2c_imx_probe(struct platform_device *pdev)
        {
            struct imx_i2c_struct *i2c_imx;
            struct resource *res;
            struct imxi2c_platform_data *pdata;
            void __iomem *base;
            resource_size_t res_size;
            int irq;
            int ret;

            dev_dbg(&pdev->dev, "<%s>\n", __func__);

            res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
            if (!res) {
                dev_err(&pdev->dev, "can't get device resources\n");
                return -ENOENT;
            }
            irq = platform_get_irq(pdev, 0);
            if (irq < 0) {
                dev_err(&pdev->dev, "can't get irq number\n");
                return -ENOENT;
            }

            pdata = pdev->dev.platform_data;

            if (pdata && pdata->init) {
                ret = pdata->init(&pdev->dev);
                if (ret)
                    return ret;
            }

            res_size = resource_size(res);

            if (!request_mem_region(res->start, res_size, DRIVER_NAME)) {
                ret = -EBUSY;
                goto fail0;
            }

            base = ioremap(res->start, res_size);
            if (!base) {
                dev_err(&pdev->dev, "ioremap failed\n");
                ret = -EIO;
                goto fail1;
            }

            i2c_imx = kzalloc(sizeof(struct imx_i2c_struct), GFP_KERNEL);
            if (!i2c_imx) {
                dev_err(&pdev->dev, "can't allocate interface\n");
                ret = -ENOMEM;
                goto fail2;
            }

            /* Setup i2c_imx driver structure */
            strcpy(i2c_imx->adapter.name, pdev->name);
            i2c_imx->adapter.owner        = THIS_MODULE;
            i2c_imx->adapter.algo        = &i2c_imx_algo;
            i2c_imx->adapter.dev.parent    = &pdev->dev;
            i2c_imx->adapter.nr         = pdev->id;
            i2c_imx->irq            = irq;
            i2c_imx->base            = base;
            i2c_imx->res            = res;

            /* Get I2C clock */
            i2c_imx->clk = clk_get(&pdev->dev, "i2c_clk");
            if (IS_ERR(i2c_imx->clk)) {
                ret = PTR_ERR(i2c_imx->clk);
                dev_err(&pdev->dev, "can't get I2C clock\n");
                goto fail3;
            }

            /* Request IRQ */
            ret = request_irq(i2c_imx->irq, i2c_imx_isr, 0, pdev->name, i2c_imx);
            if (ret) {
                dev_err(&pdev->dev, "can't claim irq %d\n", i2c_imx->irq);
                goto fail4;
            }

            /* Init queue */
            init_waitqueue_head(&i2c_imx->queue);

            /* Set up adapter data */
            i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);

            /* Set up clock divider */
            if (pdata && pdata->bitrate)
                i2c_imx_set_clk(i2c_imx, pdata->bitrate);
            else
                i2c_imx_set_clk(i2c_imx, IMX_I2C_BIT_RATE);

            /* Set up chip registers to defaults */
            writeb(0, i2c_imx->base + IMX_I2C_I2CR);
            writeb(0, i2c_imx->base + IMX_I2C_I2SR);

            /* Add I2C adapter */
            ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
            if (ret < 0) {
                dev_err(&pdev->dev, "registration failed\n");
                goto fail5;
            }

            /* Set up platform driver data */
            platform_set_drvdata(pdev, i2c_imx);

            dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", i2c_imx->irq);
            dev_dbg(&i2c_imx->adapter.dev, "device resources from 0x%x to 0x%x\n",
                i2c_imx->res->start, i2c_imx->res->end);
            dev_dbg(&i2c_imx->adapter.dev, "allocated %d bytes at 0x%x \n",
                res_size, i2c_imx->res->start);
            dev_dbg(&i2c_imx->adapter.dev, "adapter name: \"%s\"\n",
                i2c_imx->adapter.name);
            dev_dbg(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n");

            return 0;   /* Return OK */

        fail5:
            free_irq(i2c_imx->irq, i2c_imx);
        fail4:
            clk_put(i2c_imx->clk);
        fail3:
            kfree(i2c_imx);
        fail2:
            iounmap(base);
        fail1:
            release_mem_region(res->start, resource_size(res));
        fail0:
            if (pdata && pdata->exit)
                pdata->exit(&pdev->dev);
            return ret; /* Return error number */
        }
        ......

九、I2C设备追踪:
    1. cat drivers/misc/eeprom/max6875.c
        ......
        static const struct i2c_device_id max6875_id[] = {
            { "max6875", 0 },
            { }
        };

        static struct i2c_driver max6875_driver = {
            .driver = {
                .name    = "max6875",
            },
            .probe        = max6875_probe,    //跟踪函数
            .remove        = max6875_remove,
            .id_table    = max6875_id,
        };

        static int __init max6875_init(void)
        {
            return i2c_add_driver(&max6875_driver); //跟踪函数
        }

        static void __exit max6875_exit(void)
        {
            i2c_del_driver(&max6875_driver);
        }


        MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>");
        MODULE_DESCRIPTION("MAX6875 driver");
        MODULE_LICENSE("GPL");

        module_init(max6875_init);
        module_exit(max6875_exit);
               
    2. cat include/linux/i2c.h
        ......
        static inline int i2c_add_driver(struct i2c_driver *driver)
        {
            return i2c_register_driver(THIS_MODULE, driver);    //跟踪函数
        }
        ......

    3. cat drivers/i2c/i2c-core.c
        ......
        int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
        {
            ......
            driver->driver.owner = owner;
            driver->driver.bus = &i2c_bus_type;     //跟踪数据结构

            /* When registration returns, the driver core
             * will have called probe() for all matching-but-unbound devices.
             */
            res = driver_register(&driver->driver); //这里就跟踪到这里结束了

            ......  

            /* Walk the adapters that are already present */
            i2c_for_each_dev(driver, __process_new_driver);   //跟踪这个函数

            return 0;
        }
        EXPORT_SYMBOL(i2c_register_driver);
        ......

    4. cat drivers/i2c/i2c-core.c
        ...... 
        struct bus_type i2c_bus_type = {
            .name        = "i2c",
            .match        = i2c_device_match,
            .probe        = i2c_device_probe,
            .remove        = i2c_device_remove,
            .shutdown    = i2c_device_shutdown,
            .pm        = &i2c_device_pm_ops,
        };
        EXPORT_SYMBOL_GPL(i2c_bus_type);
        ......
        
十、跟踪__process_new_driver参数:
    1. cat drivers/i2c/i2c-core.c
        ......
        static int __process_new_driver(struct device *dev, void *data)
        {
            if (dev->type != &i2c_adapter_type)
                return 0;
            //跟踪to_i2c_adapter参数,部队i2c_do_add_adapter()函数进行跟踪,到这里结束
            return i2c_do_add_adapter(data, to_i2c_adapter(dev)); 
        }
        ......

   2. cat include/linux/i2c.h
        ......
        #define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)
        ......

十一、跟踪i2c_for_each_dev()函数:
    1. cat drivers/i2c/i2c-core.c
        ......
        int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))
        {
            int res;

            mutex_lock(&core_lock);
            res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);  //跟踪函数
            mutex_unlock(&core_lock);

            return res;
        }
        EXPORT_SYMBOL_GPL(i2c_for_each_dev);
        ......

   2. cat drivers/base/bus.c
        ......
        /**
         * 这一部分只跟踪到这里,因为从函数名,你可以知道没必要再跟下去了
         */
        int bus_for_each_dev(struct bus_type *bus, struct device *start,
                     void *data, int (*fn)(struct device *, void *))
        {
            /**
             *
             *  struct bus_type {
             *      ......
             *
             *      struct subsys_private *p;
             *  };
             *
             *  struct subsys_private {
             *      ......
             *      struct klist klist_devices;
             *      struct klist klist_drivers;
             *      ......
             *  };
             *
             *  struct device {
             *      ......
             *      struct device_private    *p;
             *      ......
             *  };
             *
             *  struct device_private {
             *      ......
             *      struct klist_node knode_bus;
             *      void *driver_data;
             *      struct device *device;
             *  };
             */
            struct klist_iter i;
            struct device *dev;
            ......

            klist_iter_init_node(&bus->p->klist_devices, &i,
                         (start ? &start->p->knode_bus : NULL));
            while ((dev = next_device(&i)) && !error)    
                error = fn(dev, data);
            klist_iter_exit(&i);
            return error;
        }
        EXPORT_SYMBOL_GPL(bus_for_each_dev);
        ......

十二、跟踪max6875_probe()函数:
    cat drivers/misc/eeprom/max6875.c
        ......
        static int max6875_probe(struct i2c_client *client,
                     const struct i2c_device_id *id)
        {
            struct i2c_adapter *adapter = client->adapter;
            struct max6875_data *data;
            int err;

            if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA
                             | I2C_FUNC_SMBUS_READ_BYTE))
                return -ENODEV;

            /* Only bind to even addresses */
            if (client->addr & 1)
                return -ENODEV;

            if (!(data = kzalloc(sizeof(struct max6875_data), GFP_KERNEL)))
                return -ENOMEM;

            /* A fake client is created on the odd address */
            data->fake_client = i2c_new_dummy(client->adapter, client->addr + 1);
            if (!data->fake_client) {
                err = -ENOMEM;
                goto exit_kfree;
            }

            /* Init real i2c_client */
            i2c_set_clientdata(client, data);
            mutex_init(&data->update_lock);

            err = sysfs_create_bin_file(&client->dev.kobj, &user_eeprom_attr);
            if (err)
                goto exit_remove_fake;

            return 0;

        exit_remove_fake:
            i2c_unregister_device(data->fake_client);
        exit_kfree:
            kfree(data);
            return err;
        }