2017年2月25日 星期六

I2C master (adapter) device driver

在 Linux kernel I2C device driver 中稱呼 master 為 adapter, 每一個 I2C bus 中只能有一個 master, 而在 embedded 系統中通常是 SoC 內建, 一樣受 I2C core 中所管理, 另外在 i2c_dev.c 中會為每一個 master 建立一個 device node 為 /dev/i2c-xxx 其中 xxx 為 bus number, 應用程式可以使用這個 device node 控制 master 收送資料, 進而可以取代 client device driver。

資料結構
struct i2c_adapter {
       struct module *owner;
       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 */
       struct rt_mutex bus_lock;
       int timeout; /* in jiffies */
       int retries;
       struct device dev; /* the adapter device */
       int nr;
       char name[48];
       struct completion dev_released;
       struct mutex userspace_clients_lock;
       struct list_head userspace_clients;
       struct i2c_bus_recovery_info *bus_recovery_info;
       const struct i2c_adapter_quirks *quirks;
};
#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev) // d is device

以上是在驅動一開始要註冊時所需資料結構, 其內容需給予準備好以備註冊一個 master (adapter) device driver, 其中有一個 algo member 為收送資料的 call back function, 通常是讓 client 驅動所呼叫

algo 資料結構
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,
          smbus_xfer. If set to NULL, the SMBus protocol is
          using common I2C messages */
     /* master_xfer should return the number of messages
         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 *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
           int (*reg_slave)(struct i2c_client *client);
           int (*unreg_slave)(struct i2c_client *client);
#endif
};
其中的 master_xfer & smbus_xfer 就是給 client 驅動所呼叫的 call back function 來收送資料


I2C core 所提供 Kernel API
  • —#include <linux/i2c.h> // 驅動需要的 include file
  • —static inline void *i2c_get_adapdata(const struct i2c_adapter *dev) // 使用 i2c_adapter 的資料結構來取得你自行設定的私有資料結構
  • —static inline void i2c_set_adapdata(struct i2c_adapter *dev, void *data)// 使用 i2c_adapter 的資料結構來設定你自行設定的私有資料結構
  • —void i2c_lock_adapter(struct i2c_adapter *); // 鎖定這個 adapter bus, 以確保只有一個人在收送資料, 這是一個很重要的動作, 因為 I2C
  • —void i2c_unlock_adapter(struct i2c_adapter *);— // 解鎖 adapter bus, 這必需和上面的鎖定成對使用
  • static inline struct i2c_adapter *i2c_parent_is_i2c_adapter(const struct i2c_adapter *adapter) // 取得上一層的 adapter data, 這在如果有多工器時會更需要用到
  • —extern int i2c_add_adapter(struct i2c_adapter *); // 增加一個 adapter or master, 這不指定 bus number, 由系統自動 assign
  • —extern void i2c_del_adapter(struct i2c_adapter *); // 移除一個 adapter
  • —extern int i2c_add_numbered_adapter(struct i2c_adapter *); // 增加一個指定 bus number 的 adapter
  • —int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); // 這是給 client 驅動所呼叫的的程式來要求 adapter 收送資料給 client, 而 master 則是轉成呼叫上面所提的 master_xfer & smbus_xfer call back function
範例 - id table
static const struct of_device_id mv64xxx_i2c_of_match_table[] = {
       { .compatible = "allwinner,sun4i-a10-i2c", .data = &mv64xxx_i2c_regs_sun4i},
       { .compatible = "allwinner,sun6i-a31-i2c", .data = &mv64xxx_i2c_regs_sun4i},
       { .compatible = "marvell,mv64xxx-i2c", .data = &mv64xxx_i2c_regs_mv64xxx},
       { .compatible = "marvell,mv78230-i2c", .data = &mv64xxx_i2c_regs_mv64xxx},
       { .compatible = "marvell,mv78230-a0-i2c", .data = &mv64xxx_i2c_regs_mv64xxx},
       {} // 一定要有這個做為結束
};
MODULE_DEVICE_TABLE(of, mv64xxx_i2c_of_match_table);

adapter algo example
static int
mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
      struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap);
      // just do it
      // return how many messages are sent
}

static u32
mv64xxx_i2c_functionality(struct i2c_adapter *adap)
{
       return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_EMUL;
}

static const struct i2c_algorithm mv64xxx_i2c_algo = {
       .master_xfer = mv64xxx_i2c_xfer,
       .functionality = mv64xxx_i2c_functionality,
};

example – probe call back function
static int mv64xxx_i2c_probe(struct platform_device *pd) {
     // prepare i2c_adapter data structure
     adapter.name= “myname”;
     adapter.timeout = msecs_to_jiffies(timeout);
     adapter.dev.parent = &pd->dev;
     adapter.algo = &mv64xxx_i2c_algo;
     adapter.owner = THIS_MODULE;
     adapter.class = I2C_CLASS_DEPRECATED;
     adapter.nr = pd->id; // bus number
     adapter.dev.of_node = pd->dev.of_node;
     // call i2c_add_numbered_adapter(&adapter)
}

example – remove call back function & driver register
static int mv64xxx_i2c_remove(struct platform_device *dev) {};
static struct platform_driver mv64xxx_i2c_driver = {
       .probe = mv64xxx_i2c_probe,
       .remove = mv64xxx_i2c_remove,
       .driver = {
               .name = MV64XXX_I2C_CTLR_NAME,
               .of_match_table = mv64xxx_i2c_of_match_table,
       },
};
module_platform_driver(mv64xxx_i2c_driver);

如何加入一個 slave device
  • 可以在 device tree 中描述,並準備好驅動程式即可,以下為範例:
    i2c0: i2c@11000 {
                                   pinctrl-names = "default";
                                   pinctrl-0 = <&i2c0_pins>;
                                   // timeout-ms = <3000>;

                                   eeprom@50 {
                                           compatible = "atmel,24c64";
                                           pagesize = <32>;
                                           reg = <0x50>;
                                           marvell,board_id_reg = <0x7>;
                                   };

                                   rtc@6f {
                                           compatible = "isl,isl1208";
                                           reg = <0x6f>;
                                   };

                                   moxaeds-i2c-mux@08 {
                                           compatible = "moxa,moxaeds-i2c-mux";
                                           reg = <0x08>;
                                   };
        };
  • 可以在 adapter (master) 驅動中加入,以下為範例:
    static int moxaeds_create_client(struct moxaeds_priv_struct *priv, u32 addr, const char *type, int index)
    {
           struct i2c_board_info info;
           struct dev_archdata dev_ad;
        memset(&info, 0, sizeof(info));
           memset(&dev_ad, 0, sizeof(dev_ad));
           info.addr = addr;
           if ( addr > 0x7f )
                   info.flags |= I2C_CLIENT_TEN; // for 10 bits address slave device
           strncpy(info.type, type, I2C_NAME_SIZE);
           info.archdata = &dev_ad;
           priv->client[index] = i2c_new_device(priv->madap, &info);
           if ( priv->client[index] == NULL )
                   return -ENODEV;
        return 0;
    }
    ret = moxaeds_create_client(fpga9_son_priv[busnum], 0x50, "24c64", EEPROM_CLIENT);