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







2014年12月12日 星期五

寫驅動程式所需的技能

我個人寫過很多種作業系統的驅動程式,但是寫驅動程式對一般寫程式的人員而言是一個非常神秘的事,不知道如何著手,也不知道需要那些技能,這邊就來談談這方面的事,好讓有心要寫驅動程式的人員才知道如何準備。
首先當然 C 要很熟,以前的人在寫 Linux 驅動程式時總是喜歡用較艱深的語法來寫,造成其他的人很難理解,現在則是𣎴會這樣了,大家都會用較簡易的方式來寫,所以如何來檢視自己的 C 是否熟悉,找出 Linux 驅動程式一個約有 100 行的程式碼,在10~20 分鐘之內將其看完,即可了解其程式羅輯,甚至其中若有錯誤也可將其指出,以及該程式的優缺點,那你的 C 就夠熟了。再者驅動程式中用很多的 function pointer 以及資料結構,另外對 pointer 也用的很多,所以這方面的 C 都要很熟悉,將會對於寫驅動程式有很大的幫助。
再來需要的技能就比較有點麻煩,因為是寫驅動程式,所以對硬體要有一點認識,舉例來說如果你要寫的是硬碟驅動程式,你當然得對硬碟的一些硬體動作或者行為必須有所了解,因為驅動程式就是在控制硬體行為,如此你才能知道如何去控制硬體才是正確的,又如你寫的是 Ethernet 驅動程式,你必須對於 Ethernet 的硬體行為以及基本的操作要有所了解,如此當你在寫它的驅動程式時,才能了解為何其硬體行為如何,出來的結果才知道正確或不正確,再者對於通訊協議的驅動程式,你可能對其協議內容也要有所了解,這樣對正確性以及除錯都有所幫助, 另外在做自我測試時才能知道如何架設測試環境,所有的軟體一定都要經過嚴謹的測試,才能確定其一定的正確性,尤其是在硬體的驅動程式,因為硬體有相容性的問題,而這些問題可能都需要經過軟體,以及硬體環境的架設才能得到正確的測試,而對硬體的了解都有助於你開發驅動程式的優質化。
另外的一個知識則是對作業系統的了解,驅動程序它要利用 kernel 所提供的 API 來和系統合作,所以它也可以說是作業系統的一部分,而且它是運行在系統中,因此如果驅動寫的不好,是會造成系統當掉,或者不穩定等等,所以對於該系統的一些內部行為要有所了解,如此也才能和系統好好地合作。另外一個的知識是系統了解,是在開完機之後的系統了解,如何設定系統,如果你的驅動程式是給系統管理用的,那你就要了解如何使用它,如此測試才能確保你的驅動是正確的,你也必須很清楚地了解,你的驅動程式到底是在何時載入系統的,這樣才能確保它可以正常地運行你的驅動程式。
這時則是我們最常説的,需要了解作業業系統的驅動程式要如何撰寫,每個作業系統的驅動程式架構都不太一樣,所以我們必須去了解它,但是在Linux中有一個好處,就是你可以取得所有的驅動程式原始碼,因此你可以參考其他人的程式,方便你快速了解,再者驅動程式所呼叫的API,和應用程式完全不同,因此你需要全部重新了解以及知道。
最後則是應用程式的了解,也就是要會寫應用程式來測試你的驅動程式,前面有提過驅動程式通常是給應用程式用的,為了要確保你的驅動程式是没有問題,你必須自己先寫一些應用測試程式來測你的驅動程式,當然如果你所在的單位有專門的測試單位,你可以讓測試單位來協助你測試,不過我還是建議自己還是先測試有没有問題,再交給測試單位測試,我想這是對自己的程式負責的態度。
結論我再一次條例式列出寫驅程式所需的技巧:
  • C 語言的熟悉
  • 作業系統的了解
  • 硬體的了解
  • 了解如何撰寫驅動程式
  • 作業系統的操作的了解
  • 應用程式的撰寫

以上是我個人認為要寫好一個驅動程式所必要的條件,通常我們要會寫一個驅動程式不太難,但是要寫好一個驅動程式就不太容易,所謂一個好的驅動程式,有幾個必要條件,第一要穩定,就是在任何情況下都不會當掉,第二就是要好維護,不要讓後面接手的人看不懂你的程式,也不知道要如何修改,第三就是要容易除錯,最後希望大家都能夠寫好一個驅動程式。

2012年9月2日 星期日

如何撰寫在Linux Kernel 2.6.x的GPIO驅動程式


前言
GPIO是目前SoC常常會使用到的一個元件,以往的GPIO使用上並不普遍,所以在Linux Kernel並未加以統一寫法,因此以往的寫法則各家自行撰寫,比較多的是架構在misc的驅動之下,但今日新的Linux Kernel則有給予新的統一做法,它讓kernel中有要用GPIO的其它驅動程式可以透過其定義的library API來使用,而GPIO本身則實現其實體層的控制就好,如此一來其它使用GPIO的驅動則在porting更容易了。

什麼是GPIO
對於GPIO我們要先有一些了解,GPIO – Generic Program Input Output,中文說法是可程式輸出輸入的控制訊號,意思是該點可以設定為input或者output訊號,而其輸出的訊號則是單純的數位訊號,不是high就是low,而輸入訊號的偵測也是一樣,而其電位則是TTL訊號,而這樣的東西,通常可以例如模擬I2C HOST,或者拿來讀取RTC的介面,又或接一個LED當做某些事件的指示使用。

Kernel中的GPIO Library
還没有寫驅動程式之前,我們先來了解這個library,它是最近的Linux kernel版本所定義的,是給其它需要用到GPIO的驅動程式所使用,意思就是在Kernel內部定義了義標準的GPIO Library API,如此一來對需要使用到GPIO的驅動程式便可以不用理會其底層的GPIO,也不會因平台不同而各家自行所定義的GPIO API而造成上層的驅動程式而跟著修改,我們可以用以下的軟體加構圖來了解其中的做法:












目前定義較常用的Kernel API有如下:
  • int gpio_direction_output(unsigned gpio, int value);
這是設定該GPIO為output,第一個參數為GPIO number,第二個參數則是設定其為輸出時的初始值。
  • int gpio_set_value(unsigned gpio, int value);
這是針對己經設為output的GPIO設定其值為high或者為low。
  • int gpio_direction_input(unsigned gpio);
這是設定該GPIO為input。
  • int gpio_get_value(unsigned gpio);
這是針對己設為input的GPIO讀回其現在的輸入值為何。
  • int gpio_request_array(struct gpio *array, size_t num);
這是若要使用任何GPIO時,必須先呼叫該API,這個動作有點類似open的動作,以免其它人會佔用到相同的GPIO pin,這個API可以讓你要求不連續任意個存在的GPIO,之後你就可以使用以上的所有API。
struct gpio {
        unsigned        gpio;
        unsigned long   flags;
        const char      *label;
};
flags可以設定的值如下:
#define GPIOF_DIR_OUT   (0 << 0)
#define GPIOF_DIR_IN    (1 << 0)

#define GPIOF_INIT_LOW  (0 << 1)
#define GPIOF_INIT_HIGH (1 << 1)

#define GPIOF_IN             (GPIOF_DIR_IN)
#define GPIOF_OUT_INIT_LOW (GPIOF_DIR_OUT | GPIOF_INIT_LOW)
#define GPIOF_OUT_INIT_HIGH (GPIOF_DIR_OUT | GPIOF_INIT_HIGH)
  • int gpio_free_array(struct gpio *array, size_t num);
上一個若類似open的動作,這一個API就是close的動作。
以上這些kernel API都是給kernel內部其它driver使用的,使用它之前必需確定GPIO驅動已經被載入,否則將會無法使用,所以這時驅動載入的順序是非常重要的。另外由於有這一層的介面,使得其它會使用到GPIO的驅動程式,會統一的介面,不會因為底層的GPIO控制驅動程式不同因而有不同的介面,而產生需要修改程式,這對軟體的維護會是一大負擔。

實體層的GPIO chip controller的驅動程式
這是一個底層的驅動程式,直接控制GPIO的晶片,並接受上層的管理層的指令動作,並以此提供其它需要使用GPIO的驅動程式,可以有統一的介面。該驅動程式會用到一個資料結構,它是宣告在include/asm-generic/gpio.h,並且在kernel中必需將CONFIG_GENERIC_GPIO啓動,其宣告如下:
/**
 * struct gpio_chip - abstract a GPIO controller
 * @label: for diagnostics
 * @dev: optional device providing the GPIOs
 * @owner: helps prevent removal of modules exporting active GPIOs
 * @request: optional hook for chip-specific activation, such as
 *      enabling module power and clock; may sleep
 * @free: optional hook for chip-specific deactivation, such as
 *      disabling module power and clock; may sleep
 * @direction_input: configures signal "offset" as input, or returns error
 * @get: returns value for signal "offset"; for output signals this
 *      returns either the value actually sensed, or zero
 * @direction_output: configures signal "offset" as output, or returns error
 * @set: assigns output value for signal "offset"
 * @to_irq: optional hook supporting non-static gpio_to_irq() mappings;
 *      implementation may not sleep
 * @dbg_show: optional routine to show contents in debugfs; default code
 *      will be used when this is omitted, but custom code can show extra
 *      state (such as pullup/pulldown configuration).
 * @base: identifies the first GPIO number handled by this chip; or, if
 *      negative during registration, requests dynamic ID allocation.
 * @ngpio: the number of GPIOs handled by this controller; the last GPIO
*      handled is (base + ngpio - 1).
 * @can_sleep: flag must be set iff get()/set() methods sleep, as they
 *      must while accessing GPIO expander chips over I2C or SPI
 * @names: if set, must be an array of strings to use as alternative
 *      names for the GPIOs in this chip. Any entry in the array
 *      may be NULL if there is no alias for the GPIO, however the
 *      array must be @ngpio entries long.  A name can include a single printk
 *      format specifier for an unsigned int.  It is substituted by the actual
 *      number of the gpio.
 *
 * A gpio_chip can help platforms abstract various sources of GPIOs so
 * they can all be accessed through a common programing interface.
 * Example sources would be SOC controllers, FPGAs, multifunction
 * chips, dedicated GPIO expanders, and so on.
 *
 * Each chip controls a number of signals, identified in method calls
 * by "offset" values in the range 0..(@ngpio - 1).  When those signals
 * are referenced through calls like gpio_get_value(gpio), the offset
 * is calculated by subtracting @base from the gpio number.
 */
struct gpio_chip {
        const char              *label;
        struct device           *dev;
        struct module           *owner;

        int                     (*request)(struct gpio_chip *chip,
                                                unsigned offset);
        void                    (*free)(struct gpio_chip *chip,
                                                unsigned offset);

        int                     (*direction_input)(struct gpio_chip *chip,
                                                unsigned offset);
        int                     (*get)(struct gpio_chip *chip,
                                                unsigned offset);
        int                     (*direction_output)(struct gpio_chip *chip,
                                                unsigned offset, int value);
        int                     (*set_debounce)(struct gpio_chip *chip,
                                                unsigned offset, unsigned debounce);

        void                    (*set)(struct gpio_chip *chip,
                                                unsigned offset, int value);
        int                     (*to_irq)(struct gpio_chip *chip,
                                                unsigned offset);

        void                    (*dbg_show)(struct seq_file *s,
                                                struct gpio_chip *chip);
        int                     base;
        u16                     ngpio;
        const char              *const *names;
        unsigned                can_sleep:1;
        unsigned                exported:1;

#if defined(CONFIG_OF_GPIO)
        /*
         * If CONFIG_OF is enabled, then all GPIO controllers described in the
         * device tree automatically may have an OF translation
         */
        struct device_node *of_node;
        int of_gpio_n_cells;
        int (*of_xlate)(struct gpio_chip *gc, struct device_node *np,
                        const void *gpio_spec, u32 *flags);
#endif
};
另外向上註冊及移除的API如下:
/* add/remove chips */
extern int gpiochip_add(struct gpio_chip *chip);
extern int __must_check gpiochip_remove(struct gpio_chip *chip);
extern struct gpio_chip *gpiochip_find(void *data, int (*match)(struct gpio_chip *chip, void *data));

程式範例
GPIO控制晶片驅動程式:
struct myart_gpio_module {
struct gpio_chip gpio;
……………………………….
};

int myart_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
struct myart_gpio_module *module;

module = container_of(chip, struct myart_gpio_module, gpio);
return module->intr_start + offset;

}

int myart_gpio_direction_input(struct gpio_chip *chip,unsigned offset)
{
myart_gpio_inout_pin(offset , MCPU_GPIO_INPUT);
return 0;
}

int myart_gpio_get_value(struct gpio_chip *chip, unsigned offset)
{
return myart_gpio_get_pin(offset);
}

int myart_gpio_direction_output(struct gpio_chip *chip,unsigned offset, int value)
{
myart_gpio_inout_pin(offset , MCPU_GPIO_OUTPUT);
return 0;
}

void myart_gpio_set_value(struct gpio_chip *chip, unsigned offset, int value)
{
myart_gpio_set_pin(offset , value);
}

static int __devinit myart_gpio_probe(struct platform_device *pdev)
{
……………………………….
/* Initialize the GPIO data structures */
module->gpio.dev = &pdev->dev;
module->gpio.label = pdev->name;
module->gpio.owner = THIS_MODULE;
module->gpio.direction_input = myart_gpio_direction_input;
module->gpio.get = myart_gpio_get_value;
module->gpio.direction_output = myart_gpio_direction_output;
module->gpio.set = myart_gpio_set_value;
module->gpio.to_irq = myart_gpio_to_irq;
module->gpio.can_sleep = 0;
module->gpio.base = 0;
module->gpio.ngpio = MCPU_GPIO_NUM;
ret = gpiochip_add(&(module->gpio));
……………………………………..
}

static int __devexit my_gpio_remove(struct platform_device *pdev)
{
……………………………..
ret = gpiochip_remove(&module->gpio);
if (ret) {
dev_err(dev, "unable to remove GPIO chip\n");
return ret;
}
………………………..
return 0;
}

static struct platform_driver my_gpio_driver = {
.driver = {
.name = "GPIO Driver",
.owner = THIS_MODULE,
},
.probe = my_gpio_probe,
.remove = __devexit_p(my_gpio_remove),
};

static int __init my_gpio_module_init(void)
{
return platform_driver_register(&my_gpio_driver);
}

static void __exit my_gpio_module_exit(void)
{
platform_driver_unregister(&my_gpio_driver);
}

module_init(my_gpio_module_init);
module_exit(my_gpio_module_exit);

上層GPIO應用驅動程式
上層的GPIO應用驅程式則是使用GPIO Kernel Library所提供的介面來撰寫驅動程式,如RTC, I2C等等都是常看到的使用GPIO來應用的驅動程式,它必須使用gpio_request_array()來向kernel取得所要用的GPIO,並確定它是没被其它驅動程式所使用,使用後並將其佔住不被其它驅動程式所佔用,在該應用驅動程式下載時需要呼叫gpio_free_array()將所佔用的GPIO free給其它的驅動程式使用,在整個驅動程式中則可以使用gpio_direction_input(), gpio_direction_output(), gpio_set_value(), gpio_get_value()來個別控制相對的GPIO為input or output,並且讀回或者設定相對GPIO為high or low。

程式範例
GPIO RTC驅動程式:
static void rtc_write(u8 cmd, u8 Data)
{
………………………
gpio_direction_output(GPIO_RTC_DATA, MCPU_GPIO_LOW);
    gpio_set_value(GPIO_RTC_RESET, MCPU_GPIO_HIGH);
………………………
}

static u8 rtc_read(u8 cmd)
{
…………………
gpio_direction_input(GPIO_RTC_DATA);
………………..
v = gpio_get_value(GPIO_RTC_DATA);
…………………….
}

static int myart_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
……………………..
v = rtc_read(RTC_MONTH_R);
………………………………
}

static int myart_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
……………………
rtc_write(RTC_YEAR_W, v);
……………………..
}

static const struct rtc_class_ops myart_rtc_ops = {
.read_time = myart_rtc_read_time,
.set_time = myart_rtc_set_time,
};

static struct gpio rtc_gpios[] = {
    { GPIO_RTC_RESET, GPIOF_DIR_OUT, "rtc_reset"}, 
    { GPIO_RTC_SCLK, GPIOF_DIR_OUT, "rtc_sclk"}, 
    { GPIO_RTC_DATA, GPIOF_DIR_OUT, "rtc_data"}, 
};

static int __init myart_rtc_probe(struct platform_device *pdev)
{
…………………………..
err = gpio_request_array(rtc_gpios, ARRAY_SIZE(rtc_gpios));
…………………………..
rtc = rtc_device_register("rtc-myart", &pdev->dev, &myart_rtc_ops,
  THIS_MODULE);
…………………………..
}

static int __exit myart_rtc_remove(struct platform_device *pdev)
{
…………………………..
rtc_device_unregister(rtc);
    gpio_free_array(rtc_gpios, ARRAY_SIZE(rtc_gpios)); 
return 0;
}

static struct platform_driver myart_rtc_driver = {
.driver = {
.name = "RTC", 
.owner = THIS_MODULE,
},
.remove = __exit_p(myart_rtc_remove),
};

static int __init myart_rtc_init(void)
{
return platform_driver_probe(&myart_rtc_driver, myart_rtc_probe);
}

static void __exit myart_rtc_exit(void)
{
    platform_driver_unregister(&myart_rtc_driver);
}

module_init(myart_rtc_init);
module_exit(myart_rtc_exit);