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);

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

2017年2月2日 星期四

I2C 硬體了解

What is I2C (Inter-IC) ?

  • 它是一種半雙工的串列式 bus
  • 它是一種廣播式的 bus
  • 三種通訊速度
    • Standard is 100 Kbps
    • Fast-mode is 400 Kbps
    • High-speed mode supports speeds up to 3.4 Mbps
  • supports 7-bits and 10-bits address
  • Master-slave communication
  • 每一個 slave 都會給一個特定位址, 對於 7-bits address 只能 0x08 ~ 0x77, 10-bits address 可以設定 0x77 以後
  • 假如 slave address 有相同, 硬體上則可能會使用多工器來切換

    硬體實際接法






    硬體訊號

    •When the bus is free, both lines are HIGH.
    • The data on the SDA line must be stable during the HIGH period of the clock.
    • The HIGH or LOW state of the data line can only change when the clock signal on the SCL line is LOW.





    Start and Stop conditions

    •A HIGH to LOW transition on the SDA line while SCL is HIGH is one such unique case. This situation indicates a START condition.
    • A LOW to HIGH transition on the SDA line while SCL is HIGH defines a STOP condition.
    • START and STOP conditions are always generated by the master.
    • The bus is considered to be busy after the START condition.
    • The bus is considered to be free again a certain time after the STOP condition.



    Data Format
    •Every byte put on the SDA line must be 8-bits long.
    • Each byte has to be followed by an acknowledge bit.




    Link level protocol









    Address table


     Two groups of eight addresses (0000XXX and 1111XXX) are reserved for the purposes