2017年2月5日 星期日

I2C client 端驅動程式


以上為在 Linux Kernel 中的軟體架構, 其主要分為幾部分:
  • I2C core 為核心程式, 也是管理程式, 做為和 kernel 之間其它驅動要收送 I2C 資炓的介面
  • I2C client 為各個 I2C client 端元件的驅動式, 必須透過 I2C core 來通知 I2C master 收送資料給自己
  • I2C master 為 I2C master 的驅動, 提供給 I2C core 讓 I2C client 來收送資料
  • I2C multiplexer 在同一個 I2C master 之下的 bus, 不能有重覆的 I2C client address, 但有時需要使用多個相同 I2C client 時就需要 I2C multiplexer 來做切換的動作, 而在 multiplexer 中會建立多個 virtual master (adapter)
  • I2C slave 其實和 I2C client 一樣, 都是 client 端驅動程式, 只是這類的驅動會使用 event 的方式來做收送資料, 這類驅動較少使用
I2C bus 是一個 master/slave 的網路架構, 一切的收送均需由 master 來做控制, 而其通訊方式其實是一種 broadcast 的方式, 所以每一個 client (slave) 都會有一個位址來辨認, 所以 client 端的驅動必需要透過 I2C core 來通知 master 的驅動收送資料, 而 client 端的驅動程式則知道自已本身要如何控制, 所以才會有 client 端的驅動程式。

source code directory in kernel source code root
  • kernel-source/drivers/i2c
    • busses 目錄放 master device driver
    • muexs 目錄放 multiplexer device driver
    • algos 目錄放演算法或協議 device driver
    • i2c-core.c i2c 核心程式
    • i2c-dev.c 建立 bus master device node 以及介面程式
    • i2c-mux.c 處理 multiplexer 的核心程式
    • i2c-boardinfo.c 提供建立 i2c board information 的程式 (這是較舊的做法, 現在通常使用 device tree)

使用時機
  • —原則上 slave 端的元件才是我們要使用的元件, 如 EEPROM
  • —client 資料結構中含 adapter data

i2c_client 資料結構說明
/**
* struct i2c_client - represent an I2C slave device
* @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;
* I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking
* @addr: Address used on the I2C bus connected to the parent adapter.
* @name: Indicates the type of the device, usually a chip name that's
* generic enough to hide second-sourcing and compatible revisions.
* @adapter: manages the bus segment hosting this I2C device
* @dev: Driver model device node for the slave.
* @irq: indicates the IRQ generated by this device (if any)
* @detected: member of an i2c_driver.clients list or i2c-core's
* userspace_devices list
* @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter
* calls it to pass on slave events to the slave driver.
*
* An i2c_client identifies a single device (i.e. chip) connected to an
* i2c bus. The behaviour exposed to Linux is defined by the driver
* managing the device.
*/
PS: 這個資料結構在 client 端註冊的 probe call back function 中會傳給它
struct i2c_client {
       unsigned short flags; /* div., see below */
       unsigned short addr; /* chip address - NOTE: 7bit */
                                       /* addresses are stored in the */
                                       /* _LOWER_ 7 bits */
       char name[I2C_NAME_SIZE];
       struct i2c_adapter *adapter; /* the adapter we sit on */
       struct device dev; /* the device structure */
       int irq; /* irq issued by device */
       struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
       i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
#define to_i2c_client(d) container_of(d, struct i2c_client, dev)
PS: 其中 I2C_NAME_SIZE 目前定義為 20

i2c_driver 資料結構說明
* struct i2c_driver - represent an I2C device driver
* @class: What kind of i2c device we instantiate (for detect)
* @attach_adapter: Callback for bus addition (deprecated)
* @probe: Callback for device binding
* @remove: Callback for device unbinding
* @shutdown: Callback for device shutdown
* @alert: Alert callback, for example for the SMBus alert protocol
* @command: Callback for bus-wide signaling (optional)
* @driver: Device driver model driver
* @id_table: List of I2C devices supported by this driver
* @detect: Callback for device detection
* @address_list: The I2C addresses to probe (for detect)
* @clients: List of detected clients we created (for i2c-core use only)
* The driver.owner field should be set to the module owner of this driver.
* The driver.name field should be set to the name of this driver.
* For automatic device detection, both @detect and @address_list must be defined.
* @class should also be set, otherwise only devices forced with module parameters
* will be created. The detect function must fill at least the name field of
* the i2c_board_info structure it is handed upon successful detection, and possibly
* also the flags field. If @detect is missing, the driver will still work fine for enumerated
* devices. Detected devices simply won't be supported. This is expected
* for the many I2C/SMBus devices which can't be detected reliably, and
* the ones which can always be enumerated in practice.
* The i2c_client structure which is handed to the @detect callback is
* not a real i2c_client. It is initialized just enough so that you can
* call i2c_smbus_read_byte_data and friends on it. Don't do anything
* else with it. In particular, calling dev_dbg and friends on it is not allowed.
struct i2c_driver {
       unsigned int class;
       /* Notifies the driver that a new bus has appeared. You should avoid
        * using this, it will be removed in a near future. */
       int (*attach_adapter)(struct i2c_adapter *) __deprecated;
       /* Standard driver model interfaces */
       int (*probe)(struct i2c_client *, const struct i2c_device_id *);
       int (*remove)(struct i2c_client *);
       /* driver model interfaces that don't relate to enumeration */
       void (*shutdown)(struct i2c_client *);
       /* Alert callback, for example for the SMBus alert protocol.
        * The format and meaning of the data value depends on the protocol.
        * For the SMBus alert protocol, there is a single bit of data passed
        * as the alert response‘s low bit (“event flag”). */
       void (*alert)(struct i2c_client *, unsigned int data);
       /* a ioctl like command that can be used to perform specific functions
        * with the device. */
       int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
       struct device_driver driver;
       const struct i2c_device_id *id_table;
       /* Device detection callback for automatic device creation */
       int (*detect)(struct i2c_client *, struct i2c_board_info *);
       const unsigned short *address_list;
       struct list_head clients;
};
#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)
PS: 在 I2C client 端的驅動程式, 必需宣告這個資料, 其中 probe & remove 兩個 call back function 為必需實做的兩個 call back function, 另外 id_table 也是需要宣告的, 以下會有範例說明

I2C core 所提供的 API for client driver
#include <Linux/i2c.h>
  • int i2c_register_driver(struct module *, struct i2c_driver *); // 註冊一個 client 端驅動, 通常在module_init 中呼叫
  • void i2c_del_driver(struct i2c_driver *); // 移除一個 client 諯驅動, 通常在 module_exit 中呼叫
  • #define i2c_add_driver(driver) \ // 簡化上面的 API
           i2c_register_driver(THIS_MODULE, driver)
  • module_i2c_driver(moxaeds_driver); // 簡化 module_init & module_exit

probe call back function example
static int moxaeds_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
       struct i2c_adapter *adap=to_i2c_adapter(client->dev.parent); // my parent master
     enum si5351_variant variant = (enum si5351_variant)id->driver_data; // get private data which from id table defined 
        // allocate private data and initialize it 
        // initialize hardware 
       // keep got client data, 就是將傳進來的 client 資料存在 local, 因為後面整個驅動都會用到
       return 0;
}

remove call back function example
static int moxaeds_remove(struct i2c_client *client)
        // release all resource 
        return 0;
}

id table example in driver
static const struct i2c_device_id moxaeds_id[] = {
       {"moxaeds-i2c-mux", 0 /* private data */}, // 若有很多個, 可以再加
       {} // 一定要加這個為結束
};
MODULE_DEVICE_TABLE(i2c, moxaeds_id);

device tree example in driver
static const struct of_device_id si5351_dt_ids[] = {
       { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A /* private data */, },
       { .compatible = "silabs,si5351a-msop", .data = (void *)SI5351_VARIANT_A3 /* private data */, },
       { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B /* private data */, },
       { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C /* private data */, },
       { }  // 一定要加這個為結束
};
MODULE_DEVICE_TABLE(of, si5351_dt_ids);

device tree example in device tree file
i2c0: i2c@11000 {
         pinctrl-names = "default";
         pinctrl-0 = <&i2c0_pins>;
         // timeout-ms = <3000>;
         /* hardware change to connect after FPAG#9 bus 15
         eeprom@50 {
                 compatible = "atmel,24c64"; // 這是上面 driver 中宣告的 device tree, ',' 之後名字最多20字母
                 pagesize = <32>;
                 reg = <0x50>; // 這是 i2c client address
                 marvell,board_id_reg = <0x7>;
        };
        rtc@6f {
                 compatible = "isl,isl1208"; // 這是上面 driver 中宣告的 device tree
                 reg = <0x6f>; // 這是 i2c client address
        };
        */
        moxaeds-i2c-mux@08 {
                 compatible = "moxa,moxaeds-i2c-mux"; // 這是上面 driver 中宣告的 device tree
                 reg = <0x08>; // 這是 i2c client address
        };
};

另一個 device tree example in device tree file
&i2c0 {
       status = "okay";
       clock-frequency = <100000>;
       si5351: clock-generator {
               compatible = "silabs,si5351a-msop";
               reg = <0x60>;
               #address-cells = <1>;
               #size-cells = <0>;
               #clock-cells = <1>;
               /* connect xtal input to 25MHz reference */
               clocks = <&ref25>;
               clock-names = "xtal";
               /* connect xtal input as source of pll0 and pll1 */
               silabs,pll-source = <0 0>, <1 0>;
               clkout0 {
                       reg = <0>;
                       silabs,drive-strength = <8>;
                       silabs,multisynth-source = <0>;
                       silabs,clock-source = <0>;
                       silabs,pll-master;
               };
               clkout2 {
                       reg = <2>;
                       silabs,drive-strength = <8>;
                       silabs,multisynth-source = <1>;
                       silabs,clock-source = <0>;
                       silabs,pll-master;
               };
       };
};

上面所提 i2c_driver 資料結構範例
static struct i2c_driver moxaeds_driver = {
       .driver = {
               .name = "moxaeds-i2c-mux",
               .owner = THIS_MODULE,
               .of_match_table = of_match_ptr(si5351_dt_ids), // my device tree
       },
       .probe = moxaeds_probe, // 參考前面的範
       .remove = moxaeds_remove, // 參考前面的範
       .id_table = moxaeds_id, // my id table
};

module_init & module_exit example
#if 0 // if you want to debug module initialize function enable here
static int __init moxaeds_init(void)
{
       dgprintk(HIGH_DEBUG_LEVEL, "\n");
       return i2c_add_driver(&moxaeds_driver);
}
module_init(moxaeds_init);

static void __exit moxaeds_exit(void)
{
       dgprintk(HIGH_DEBUG_LEVEL, "\n");
       i2c_del_driver(&moxaeds_driver);
}
module_exit(moxaeds_exit);
#else
module_i2c_driver(moxaeds_driver); // to use i2c module initialize macro
#endif

如何傳送資料在 client 端驅動
  • —call i2c_transfer() or __i2c_transfer(), 前面一個會幫忙做 lock bus 的動作, 可避免同時有在存取相同的 bus master, 而後面的則不會, 必需使用者自己做 lock 的動作
  • —範例如下:
    struct i2c_msg msg[2];
    u8 tbuf[4], rbuf[4];
    int ret;
    msg[0].addr = moxaeds_client_fpga->addr;
    msg[0].flags = 0;
    msg[0].len = 1;
    msg[0].buf = tbuf;
    tbuf[0] = FPGA_SET_BIT_MAP_REG_NO;
    msg[1].addr = moxaeds_client_fpga->addr;
    msg[1].flags = I2C_M_RD;
    msg[1].len = 2;
    msg[1].buf = rbuf;
    ret = i2c_transfer(moxaeds_master_adap, msg, 2); // called i2c_transfer(), not __i2c_transfer(),
    if ( ret != 2 ) {
           myprintk("Read bit map register fail [%d] !\n", ret);
           return 0;
    }
return sprintf(buf, "0x%02x%02x", rbuf[0], rbuf[1]);
// 其中在 i2c_transfer 中第一個參數 moxaeds_master_adap, 在前面所提 probe call back function 所傳進來的 client 中其中的 member

沒有留言:

張貼留言