顯示具有 driver 標籤的文章。 顯示所有文章
顯示具有 driver 標籤的文章。 顯示所有文章

2012年7月20日 星期五

2012年7月18日 星期三

I2C簡介(二)

前一篇簡單說明了如果寫I2C device的driver。這篇主要要介紹I2C adapter(controller的Driver)。在這裡我們直接以Tegra3的I2C Controller為例來說明。要注意的是,一個板子可能會有很多controller,而一個controller可能會有多條bus。每一個bus會對應一個adapter。

  • Board-Specific Code
Tegra3的board-specific code在arch/arm/mach-tegra/device.c 及arch/arm/mach-tegra/board-cardhu.c中。從source code我們可以看出Tegra 3有五組I2C controller。我們先看device.c的部份,在這裡主要是填寫platform_device的資料如下:
//arch/arm/mach-tegra/device.c
static struct resource i2c_resource1[] = {
 [0] = {
  .start = INT_I2C,
  .end = INT_I2C,
  .flags = IORESOURCE_IRQ,
 },
 [1] = {
  .start = TEGRA_I2C_BASE,
  .end = TEGRA_I2C_BASE + TEGRA_I2C_SIZE-1,
  .flags = IORESOURCE_MEM,
 },
};
                               .
                               .
                               .

static struct resource i2c_resource5[] = {
 [0] = {
  .start  = INT_I2C5,
  .end    = INT_I2C5,
  .flags  = IORESOURCE_IRQ,
 },
 [1] = {
  .start = TEGRA_I2C5_BASE,
  .end = TEGRA_I2C5_BASE + TEGRA_I2C5_SIZE-1,
  .flags = IORESOURCE_MEM,
 },
};
#endif

static struct tegra_i2c_platform_data tegra_i2c1_platform_data = {
 .bus_clk_rate   = { 400000 },
};
                               .
                               .
                               .

static struct tegra_i2c_platform_data tegra_dvc_platform_data = {
 .bus_clk_rate   = { 400000 },
};

struct platform_device tegra_i2c_device1 = {
 .name  = "tegra-i2c",
 .id  = 0,
 .resource = i2c_resource1,
 .num_resources = ARRAY_SIZE(i2c_resource1),
 .dev = {
  .platform_data = &tegra_i2c1_platform_data,
 },
};
                               .
                               .
                               .

struct platform_device tegra_i2c_device5 = {
 .name  = "tegra-i2c",
 .id  = 4,
 .resource = i2c_resource5,
 .num_resources = ARRAY_SIZE(i2c_resource5),
 .dev = {
  .platform_data = 0,
 },
};
接著我們繼續看board-cardhu.c。不知道為什麼,在這邊又把platform_data蓋掉@@。
static struct tegra_i2c_platform_data cardhu_i2c1_platform_data = {
 .adapter_nr = 0,
        //設定這個controller有幾個bus
 .bus_count = 1,
 .bus_clk_rate = { 100000, 0 },
 .scl_gpio  = {TEGRA_GPIO_PC4, 0},
 .sda_gpio  = {TEGRA_GPIO_PC5, 0},
 .arb_recovery = arb_lost_recovery,
};

                               .
                               .
                               .

static struct tegra_i2c_platform_data cardhu_i2c5_platform_data = {
 .adapter_nr = 4,
 .bus_count = 1,
 .bus_clk_rate = { 400000, 0 },
 .scl_gpio  = {TEGRA_GPIO_PZ6, 0},
 .sda_gpio  = {TEGRA_GPIO_PZ7, 0},
 .arb_recovery = arb_lost_recovery,
};

static struct i2c_board_info __initdata cardhu_codec_wm8903_info = {
 I2C_BOARD_INFO("wm8903", 0x1a),
 .irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_CDC_IRQ),
 .platform_data = &cardhu_wm8903_pdata,
};

static struct i2c_board_info __initdata cardhu_codec_aic326x_info = {
 I2C_BOARD_INFO("aic3262-codec", 0x18),
 .irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_CDC_IRQ),
};

static struct i2c_board_info __initdata cardhu_codec_max98095_info = {
 I2C_BOARD_INFO("max98095", 0x10),
 .irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_CDC_IRQ),
 .platform_data = &cardhu_max98095_pdata,
};

static void cardhu_i2c_init(void)
{
 tegra_i2c_device1.dev.platform_data = &cardhu_i2c1_platform_data;
                        .
                               .
                               .

 tegra_i2c_device5.dev.platform_data = &cardhu_i2c5_platform_data;

 platform_device_register(&tegra_i2c_device5);
                        .
                               .
                               .

 platform_device_register(&tegra_i2c_device1);

        
}

  •  I2C Controller Driver

看完了platform_device的宣告後,我們理所當然的去看platform_driver的地方。實作的位置在driver/i2c/busses/i2c-tegra.c。這裡我們從tegra_i2c_init_driver()看起。
static int tegra_i2c_probe(struct platform_device *pdev)
{
 struct tegra_i2c_dev *i2c_dev;
 struct tegra_i2c_platform_data *plat = pdev->dev.platform_data;
 struct resource *res;
 struct resource *iomem;
 struct clk *div_clk;
 struct clk *fast_clk = NULL;
 const unsigned int *prop;
 void *base;
 int irq;
 int nbus;
 int i = 0;
 int ret = 0;

        //取得定義在device.c的platform_device 的resource且flag為IORESOURCE_MEM 的資料
 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 
        //檢查從res->start~res->start+ resource_size(res)是否可用
        iomem = request_mem_region(res->start, resource_size(res), pdev->name);
 
        //mapping到kernel虛擬空間
 base = ioremap(iomem->start, resource_size(iomem));
 
 div_clk = clk_get(&pdev->dev, "i2c-div");
 
 fast_clk = clk_get(&pdev->dev, "i2c-fast");
 
        i2c_dev = kzalloc(sizeof(struct tegra_i2c_dev) +
     (nbus-1) * sizeof(struct tegra_i2c_bus), GFP_KERNEL);
 

 i2c_dev->base = base;
 i2c_dev->div_clk = div_clk;
 i2c_dev->fast_clk = fast_clk;
 i2c_dev->iomem = iomem;
 i2c_dev->irq = irq;
 i2c_dev->cont_id = pdev->id;
 i2c_dev->dev = &pdev->dev;
 i2c_dev->is_clkon_always = plat->is_clkon_always;

 i2c_dev->last_bus_clk_rate = 100000; /* default clock rate */
 i2c_dev->last_bus_clk_rate = plat->bus_clk_rate[0];
        i2c_dev->is_high_speed_enable = plat->is_high_speed_enable;
 i2c_dev->last_bus_clk_rate = plat->bus_clk_rate[0] ?: 100000;
 i2c_dev->msgs = NULL;
 i2c_dev->msgs_num = 0;
 rt_mutex_init(&i2c_dev->dev_lock);
 spin_lock_init(&i2c_dev->fifo_lock);

 i2c_dev->slave_addr = plat->slave_addr;
 i2c_dev->hs_master_code = plat->hs_master_code;
 i2c_dev->is_dvc = plat->is_dvc;
 i2c_dev->arb_recovery = plat->arb_recovery;
 init_completion(&i2c_dev->msg_complete);

 if (irq == INT_I2C || irq == INT_I2C2 || irq == INT_I2C3)
  i2c_dev->is_slave = true;

 platform_set_drvdata(pdev, i2c_dev);

 if (i2c_dev->is_clkon_always)
  tegra_i2c_clock_enable(i2c_dev);

 ret = tegra_i2c_init(i2c_dev);
 if (ret) {
  dev_err(&pdev->dev, "Failed to initialize i2c controller");
  goto err_free;
 }

 

 for (i = 0; i < nbus; i++) {
  struct tegra_i2c_bus *i2c_bus = &i2c_dev->busses[i];

  i2c_bus->dev = i2c_dev;
  i2c_bus->mux = plat->bus_mux[i];
  i2c_bus->mux_len = plat->bus_mux_len[i];
  i2c_bus->bus_clk_rate = plat->bus_clk_rate[i] ?: 100000;

  i2c_bus->scl_gpio = plat->scl_gpio[i];
  i2c_bus->sda_gpio = plat->sda_gpio[i];

  i2c_bus->adapter.dev.of_node = pdev->dev.of_node;
  i2c_bus->adapter.algo = &tegra_i2c_algo;
  i2c_set_adapdata(&i2c_bus->adapter, i2c_bus);
  i2c_bus->adapter.owner = THIS_MODULE;
  i2c_bus->adapter.class = I2C_CLASS_HWMON;
  strlcpy(i2c_bus->adapter.name, "Tegra I2C adapter",
   sizeof(i2c_bus->adapter.name));
  i2c_bus->adapter.dev.parent = &pdev->dev;
  i2c_bus->adapter.nr = plat->adapter_nr + i;

                //!!將此bus註冊進kernel中
  ret = i2c_add_numbered_adapter(&i2c_bus->adapter);
                //註冊此bus的adapter
  of_i2c_register_devices(&i2c_bus->adapter);

  i2c_dev->bus_count++;
 }

 return 0;
        ...
}

static struct platform_driver tegra_i2c_driver = {
 .probe   = tegra_i2c_probe,
 .remove  = tegra_i2c_remove,
 .driver  = {
  .name  = "tegra-i2c",
  .owner = THIS_MODULE,
  .of_match_table = tegra_i2c_of_match,
  .pm    = TEGRA_I2C_DEV_PM_OPS,
 },
};

static int __init tegra_i2c_init_driver(void)
{
        //註冊tegra_i2c_driver,跳到tegra_i2c_probe繼續看
 return platform_driver_register(&tegra_i2c_driver);
}
subsys_initcall(tegra_i2c_init_driver);
  • Reference
  1. Introduction to I2C
  2. Linux I2C driver: what's new-style !?
  3. I2C总线使用方法继续研究
  4. nVidia Tegra Kernel Source

I2C簡介(一)

  • I2C簡介
I2C 全名是Inter IC,原先是Phillips發展的一個two-wire 的serial bus protocol。他是一種半雙工同步多組設備匯流排。只需要兩條線:串列資料線(SDA)與串列時脈線(SCL or SCK)。I2C 提供兩種定址模式與三種傳輸模式。定址方面分成10-bit(允許連接1024個裝置)與 7-bit (允許連接128個裝置)。傳輸方面則有10kbps 的低速模式、100kbps 的標準模式、及 400kbps 的快速模式。
I2C連接的裝置都是Open Drain的接腳,也就是說導通的時候電位是low,而不導通時為high。I2C通常用在嵌入式系統與不同的裝置溝通。在PC,I2C通常用來與不同的sensor 連結例如取得系統風扇的轉速、CPU的溫度等等。
  • I2C in Linux
I2C 在kernel 2.x的時候開始支援。在Linux Kernel中,I2C的code拆成四個部份: I2C core、I2C bus drivers、I2C algorithm drivers 和 I2C chip drivers。

舉一個例子,一個PMU(power management unit)利用I2C與SOC連結。要使PMU能夠正確運作我們必須先撰寫I2C adapter(controller)的driver,讓I2C bus可以正常運作。I2C adapter(controller)可能是外接的chip也可能是在SOC裡頭例如Tegra3擁有五組I2C controller。再來,我們必須撰寫PMU的driver,註冊進正確的I2C bus中。透過adapter(controller)與device溝通。
這一篇的介紹是在於如何撰寫下圖的i2c client driver來控制(R/W)i2c device。
一個板子可能會有很多controller,而一個controller可能會有多條bus。每一個bus會對應一個adapter。
  • 撰寫I2C Device Driver
在這裡我們直接以TI的PMU tps6591為例子說明。原始碼一樣可以透過下面 reference中抓取。
我們首先先看看tps6591 board-specific code的地方。我們看cardhu_regulator_init()。
//arch/arm/mach-tegra/board-cardhu-power.c
static struct tps6591x_platform_data tps_platform = {
        //pmu的irq起始編號
 .irq_base = TPS6591X_IRQ_BASE,
        //gpio的起始編號
 .gpio_base = TPS6591X_GPIO_BASE,
 .dev_slp_en = true,
 .slp_keepon = &tps_slp_keepon,
 .use_power_off = true,
};

static struct i2c_board_info __initdata cardhu_regulators[] = {
    {
        //設定pmu的i2c device number
        I2C_BOARD_INFO("tps6591x", 0x2D),
        .irq    = INT_EXTERNAL_PMU,
        .platform_data = &tps_platform,
    },
};

int __init cardhu_regulator_init(void){
    ...
    /**註冊i2c device進list: __i2c_board_list
      *bus number=4  
      *device info=cardhu_regulators
      *#device=1 
    */
    i2c_register_board_info(4, cardhu_regulators, 1);
    ...
}
接著在tps6591的driver中,我們從tps6591x_init()看起。
//driver/mfd/tps6591x.c

static int __devinit tps6591x_i2c_probe(struct i2c_client *client,
     const struct i2c_device_id *id)
{
 struct tps6591x_platform_data *pdata = client->dev.platform_data;
 struct tps6591x *tps6591x;
 int ret;

 if (!pdata) {
  dev_err(&client->dev, "tps6591x requires platform data\n");
  return -ENOTSUPP;
 }

 ret = i2c_smbus_read_byte_data(client, TPS6591X_VERNUM);
 if (ret < 0) {
  dev_err(&client->dev, "Silicon version number read"
    " failed: %d\n", ret);
  return -EIO;
 }

 dev_info(&client->dev, "VERNUM is %02x\n", ret);

 
        //same as dev_set_drvdata(&dev->dev, data);
 i2c_set_clientdata(client, tps6591x);

 mutex_init(&tps6591x->lock);

 ...

 tps6591x_i2c_client = client;

 return 0;

err_add_devs:
 if (client->irq)
  free_irq(client->irq, tps6591x);
err_irq_init:
 kfree(tps6591x);
 return ret;
}

static struct i2c_driver tps6591x_driver = {
 .driver = {
  .name = "tps6591x",
  .owner = THIS_MODULE,
 },
 .probe  = tps6591x_i2c_probe,
 .remove  = __devexit_p(tps6591x_i2c_remove),
#ifdef CONFIG_PM
 .suspend = tps6591x_i2c_suspend,
 .resume  = tps6591x_i2c_resume,
#endif
 .id_table = tps6591x_id_table,
};

static int __init tps6591x_init(void)
{
        /**註冊I2C device driver
         *將這個driver與bus做配對並呼叫probe
        */
 return i2c_add_driver(&tps6591x_driver);
}
  • R/W VIA I2C
如何從I2C device讀取資料呢?我們以 tps6591x_read為例:
static inline int __tps6591x_read(struct i2c_client *client,
      int reg, uint8_t *val)
{
 int ret;
        //read data from address:reg
 ret = i2c_smbus_read_byte_data(client, reg);
 if (ret < 0) {
  dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
  return ret;
 }

 *val = (uint8_t)ret;

 return 0;
}


讀取資料呢?我們以 tps6591x_write為例:
static inline int __tps6591x_write(struct i2c_client *client,
     int reg, uint8_t val)
{
 int ret;
        //write value:val to address:reg
 ret = i2c_smbus_write_byte_data(client, reg, val);
 if (ret < 0) {
  dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
    val, reg);
  return ret;
 }

 return 0;
}

其他的 read/write可以參考driver/i2c/i2c-core.c
  • Reference
  1. Introduction to I2C
  2. Linux I2C driver: what's new-style !?
  3. I2C总线使用方法继续研究
  4. nVidia Tegra Kernel Source

2012年7月11日 星期三

Linux Kernel Portability-Data type & Data Structure in Linux kernel (二)

在上一篇著重於Linux Kernel 的Portability。這一篇將著重於Linux Kernel的Data Structure。這裡要介紹的Data Structure包括了Linked lists、Queues、Maps、及Binary trees。

  • Linked List
這裡直接講用法。Linux Kernel的Linked List是Double Linked List,而且List裡面的元素必須是list_head的structure。舉個例好了,我們要做到如下圖(A B C都是struct sample好了):
struct sample{
    struct list_head link;
    vold *content;
}
struct sample *A,*B,*C;
現在我們要把ABC串起來。首先我們建立一個叫list_head的Linked list的變數,並初始化它。常見的方法有兩種,結果是一樣的。
/**
*1. LIST_HEAD(name)
*2. INIT_LIST_HEAD
*/
//method 1
static LIST_HEAD(list_head);

//method 2
struct list_head list_head;
INIT_LIST_HEAD(&list_head);
接著假設我們要先加入A和C,利用list_add()將A加入到list_head後面。然後在將C加入到A後面。
//Add A after list_head,list_head is the head of the list
//e.g list_head->A
list_add(&A->link,&list_head);

//Add C after A e.g list_head->A->C
list_add(&C->link,&A->link);
接著,我們要利用list_add_tail將B加在C之前
//Add B before C e.g list_head->A->B->C
list_add_tail(&B->link,&C->link);
加完了之後我們要怎麼列出List裡面的項目呢?可以利用list_for_each來達成。
//list_for_each(struct list_head *cursor, struct list_head *list)
list_for_each(&A->link,&list_head){

}
更多的用法可見<linux/list.h>

  • Queue 
Linux Kernel的queue叫做kfifo實做在kernel/kfifo.c 宣告在<linux/kfifo.h>。與我們所知的queue相同,kfifo支援enqueue(函式命名成inqueue)跟dequeue。同樣的,kfifo有兩個offset分別是記錄下一個插入位置的enqueue offset跟下一個取出位置的dequeue offset。
接下來介紹kfifo的使用方式分為 1. Declare  2. Enqueue/Dequeue 3. Other operation。
Declare 
a. 利用kfifo_alloc() 動態宣告   
struct kfifo fifo;
int ret;
      //宣告一個大小為PAGE_SIZE的queue
ret = kfifo_alloc(&kifo, PAGE_SIZE, GFP_KERNEL);
if (ret)
return ret;

b. 利用DECLARE_KFIFO(name, size)建立 INIT_KFIFO(name)初始化。
Enqueue/Dequeue
kfifo_in(fifo, buf, n) 將n byte的buf讓入fifo裡。如果fifo free的大小<n 則只會copy free 的size的資料 
kfifo_out(fifo, buf, n) copy n byte到buf中。如果fifo的大小<n 則只會copy fifo大小的data
kfifo_out_peek(fifo, buf, n) kfifo_out執行後data就會從fifo中刪除。如果想保留可以使用kfifo_out_peek
Other Operation 
kfifo_size(fifo) 取得fifo大小
kfifo_len(fifo) 取得fifo已使用大小
kfifo_avail(fifo) 取得fifo可使用大小
kfifo_is_empty(fifo) 判斷fifo是否為空
kfifo_is_full(fifo) 判斷fifo是否已滿
kfifo_reset(fifo) 清空fifo。
kfifo_free(fifo) free掉fifo
實際的操作範例
struct kfifo fifo;
int ret;

//宣告一個大小為PAGE_SIZE的queue
ret = kfifo_alloc(&kifo, PAGE_SIZE, GFP_KERNEL);
if (ret)
    return ret;
int i;
//插入32個size為sizeof(int)的[0,31]到fifo中
for (i = 0; i < 32; i++)
    kfifo_in(fifo, &i; sizeof(i));

unsigned int val;
int ret;

//從fifo讀取一個integer到val 中。資料不會被刪除
ret = kfifo_out_peek(fifo, &val, sizeof(val));
if (ret != sizeof(val))
    return -EINVAL;

printk(KERN_INFO “%u\n”, val); /* should print 0 */


/* while there is data in the queue ... */
while (kfifo_avail(fifo)) {
    unsigned int val;
    int ret;
    /* ... read it, one integer at a time */
    ret = kfifo_out(fifo, &val, sizeof(val));
    if (ret != sizeof(val))
        return -EINVAL;

    printk(KERN_INFO “%u\n”, val);
}
  • Red-Black Tree
紅黑樹是一種semi-balanced binary tree(自平衡二元樹)。主要有以下六種性質:
  1. 節點不是紅色就是黑色。
  2. 根節點是黑色。
  3. 所有葉子都是黑色(葉子是NIL節點 e.g不包含資料)。
  4. 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
  5. 從任一節點到其每個葉子的所有最短路徑都包含相同數目的黑色節點。

紅黑樹在Linux Kernel實作的地方是lib/rbtree.c宣告的地方在<linux/rbtree.h>。這裡直接以 driver/media/video/tegra/avp/avp.c裡面的實作來解釋rbtree的使用。avp.c定義了一個tegra_avp_info的struct,以下擷取其實做。要使用rbtree必須要宣告一個rb_root的變數如下:
struct tegra_avp_info {
 ...
 struct rb_root  endpoints;
        ...
};
要開始使用endpoints這個rbtree之前必須給他初始的值"RB_ROOT"如下:
static int tegra_avp_probe(struct platform_device *pdev)
{
    struct   *avp;
    avp = kzalloc(sizeof(struct tegra_avp_info), GFP_KERNEL);
    ...
    avp->endpoints = RB_ROOT;
    ...
}
Linux Kernel的rbtree不包含插入及搜尋的函式,下面同樣以avp的"插入"來說明。
static struct remote_info *remote_find(struct tegra_avp_info *avp, u32 local_id)
{
 struct rb_node *n = avp->endpoints.rb_node;
 struct remote_info *rinfo;

 while (n) {
  rinfo = rb_entry(n, struct remote_info, rb_node);

  if (local_id < rinfo->loc_id)
   n = n->rb_left;
  else if (local_id > rinfo->loc_id)
   n = n->rb_right;
  else
   return rinfo;
 }
 return NULL;
}
以上例,remote_find()實作搜尋的功能。while loop會traverse整個tree 直到找到相同的id。
static int remote_insert(struct tegra_avp_info *avp, struct remote_info *rinfo)
{
 struct rb_node **p;
 struct rb_node *parent;
 struct remote_info *tmp;

 p = &avp->endpoints.rb_node;
 parent = NULL;
 while (*p) {
  parent = *p;
  tmp = rb_entry(parent, struct remote_info, rb_node);

  if (rinfo->loc_id < tmp->loc_id)
   p = &(*p)->rb_left;
  else if (rinfo->loc_id > tmp->loc_id)
   p = &(*p)->rb_right;
  else {
   pr_info("%s: avp endpoint id=%x (%s) already exists\n",
    __func__, rinfo->loc_id,
    trpc_name(rinfo->trpc_ep));
   return -EEXIST;
  }
 }
 rb_link_node(&rinfo->rb_node, parent, p);
 rb_insert_color(&rinfo->rb_node, &avp->endpoints);
 rinfo_get(rinfo);
 return 0;
}

以上例,while loop會traverse rbtree去尋找適合的頁節點,然後透過rb_link_node()去link parent。接著再透過rb_insert_color()上色。
static void remote_remove(struct tegra_avp_info *avp, struct remote_info *rinfo)
{
 rb_erase(&rinfo->rb_node, &avp->endpoints);
 rinfo_put(rinfo);
}
刪除節點很簡單,只要呼叫rb_erase()即可。

  • Radix Tree
Radix Tree使用相對於Red Block Tree來說簡單許多。這裡以之前提過得irq實做為例看看Radix Tree怎麼使用。
Radix Tree實做在lib/radix- tree.c,下面以kernel/irqdesc.c來了解。
//Declare a radix tree called irq_desc_tree
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);

static void irq_insert_desc(unsigned int irq, struct irq_desc *desc)
{
        //Insert  into irq_desc_tree
 radix_tree_insert(&irq_desc_tree, irq, desc);
}

struct irq_desc *irq_to_desc(unsigned int irq)
{
        //取得key為irq的value
 return radix_tree_lookup(&irq_desc_tree, irq);
}

static void delete_irq_desc(unsigned int irq)
{
        //刪除key為irq的pair
 radix_tree_delete(&irq_desc_tree, irq);
}

  • Reference
上一篇:Linux Kernel Portability-Data type & Data Structure in Linux kernel (一)

2012年7月10日 星期二

Linux Kernel Portability-Data type & Data Structure in Linux kernel (一)

  • 前言
Linux作為一個跨平台(處理器)的作業系統必須兼容許多不同的CPU 架構設計。也因此,Linux Kernel裡面有許多是為了Portability而產生的包含衍生的資料型態及提供的資料結構。
為了讓我們的Kernel 的module可以順利在我們的Target上或在各平台上順利執行,我們勢必就需要了解哪些方面會是Platform-Independent,Linux Kernel如何解決這個問題。
  • The Size of Data Types
在說明Kernel專有的資料型態前,我們先了解標準C資料型態在各平台的差異。以下圖為例,在常見的平台上,char、short、int、long long大小是一致的。但是long、ptr、word的大小則會因平台不同而有所差別。因此,如何在各平台確切的得到我們希望的變數大小就非常重要了。換句話說,假設我們希望某個變數他是8個bits,他就不會因為平台的不同而變成立如4個bits。
因此,Linux Kernel就定義了幾個標準大小的資料型態在include/linux/types.h。在include/linux/types.h裡他include了asm/types.h。以ARM平台為例,arch/arm/include/asm/types.h include了asm-generic/int-ll64.h。在int-ll64.h就定義了: 
u8    unsigned byte (8 bits)
u16   unsigned word (16 bits)
u32   unsigned 32-bit value
u64   unsigned 64-bit value
s8    signed byte (8 bits)
s16   signed word (16 bits)
s32   signed 32-bit value
s64   signed 64-bit value
u代表的是unsigned而s則是代表signed分別定義了8~64bits的標準大小型態。假設在user space的程式要使用固定長度的型別,記得前面要加上__例如"__u8"。

然而,C99規範了標準大小的資料型態uintN_t及intN_t對應著uN及sN定義在stdint.h。N代表8、16、 32、64。因此,不管是Kernel Module或是user space 的program特別是如果你的程式是想跨平台(OS)的,就盡量使用C99的資料型態而非Linux Kernel的資料型態。
  • Interface-Specific Types
Linux Kernel為了移植性也定義了許多_t結尾的變數在 <include/types.h>。舉例來說常見的size_t、ssize_t、pid_t、time_t等等根據特定意義的資料型態就稱為Interface-Specific Type。以process 的id pid_t為例,在x86來說是 int,但是假設我們直接將例如getpid()的回傳值用 int用 接收,哪天另外一個平台的pid是long long的話勢必我們程式必須作修改才能正確運行。因此,Interface-Specific Type可以避免移植的問題。 
  • Data Alignment
大多數的CPU都會要求變數擺在特定的記憶體上。例如Intel x86的CPU要求4 byte的integer是放置在4的倍數的記憶體位置。這個就叫做Memory Alignment。但假設不是擺在4的倍數上呢?那就必須要讀取兩次才能得到這個變數。大多數的CPU都可以自己處理這個狀況,但是有些CPU沒辦法處理這個問題,必須要產生exception靠exception handler來處理這個問題,但是這樣效率會非常的慢。有個方法是使用get_unaligned(ptr)取得資料及set_unaligned(val,ptr)寫入資料。例如,在driver/power/bq27x00_battery.c裡面
if (!single)
    ret = get_unaligned_le16(data);
else
    ret = data[0];
如果資料是1個byte就直接讀取否則用get_unaligned_le16()。le代表little-endian。

順帶一提,compiler在做最佳化的時候可能會自動做Memory Aligned的動作。但也許我們定義的資料結構變數間的位址是必須連續的(的動作可能會在變數間填補一些變數讓memory可以對齊),這個時候我們可以利用__attribute__ ((packed))告訴編譯器不要亂動 。
struct{
    u16 uid;
    u64 gid;
    u16 val0;
    u32 val1;
}__attribute__ ((packed))structA;
  • Byte Order
假設有一個4 byte的int不同的CPU可能會有不同的記憶體擺法,例如低位的值先放(Little-Endian)還是大的先放(Big-Endian)?舉例來說,假設有一個整數值是0x12345678 Little-Endian的擺法就會是:
address        value
0x00000001     0x78
0x00000002     0x56
0x00000003     0x34
0x00000004     0x12
而如果是Big-Endian的話則是:
address        value
0x00000001     0x12
0x00000002     0x34
0x00000003     0x56
0x00000004     0x78
如果我們要得到Little-Endian順序的值可以使用u32 cpu_to_le32(u32),更多的轉換函式可以在找到。
  • Time
HZ定義了一秒鐘有幾次 Interrupt。他可以是100、250、300或是1000。預設的值可能會隨著 Kernel的版本或編譯Kernel 的設定而改變。因此,我們不可以假設HZ==1000(舊版的Kernel預設是1000)。
順帶一提,Tick是HZ的倒數,因此假設HZ=1000則一個tick就是1毫秒。
Jiffies是Linux Kernel用來紀錄系統自開機以來觸發了幾次Interrupt(開機時並非歸0)。Linux Kernelchewing每秒鐘會將jiffies加1HZ次。我們可以利用Linux Kernel定義的macro(timer_after、time_after_eq、time_before和time_before_eq)來判斷時間。因此,假設我們要判斷時間是否過了三秒可以:
//設定時間是現在的jiffies加上3秒的HZ次
unsigned long timeout = jiffies + (3*HZ);  
while (hwgroup->busy) {  
     ...
  /*如果迴圈超過三秒(也就是說現在的jiffies數超過三秒前的jiffies+3*HZ次)
   *則return -EBUSY
   */
  if (time_after(jiffies, timeout)) {  
    return -EBUSY;  
  }  
  ...  
}  
return SUCCESS; 
  • Page Size
不要假設Page Size是4KB。用Linux Kernel提供的PAGE_SIZE 來取得系統的page size及PAGE_SHIFT取得 要左移的bits數達到其memory page的number。

下一篇:Linux Kernel Portability-Data type & Data Structure in Linux kernel (二)

2012年7月5日 星期四

Linux Interrupt 整理 (一)

  • 前言
CPU的速度很快,假設每每都必須等待週邊的硬體動作完成CPU才能繼續工作的話,效率未免太低了。因此,我們的系統設計了中斷(Interrupt),當有 event 發生(例如 工作完成)就觸發一個Interrupt告訴CPU,這時候CPU就會"馬上"去處理。這麼一來就可以避免等待時間的浪費。
我們可以針對裝置的Interrupt建立Handler,讓不同的Device Interrupt發生後去執行相對應的"程序" ,這個程序就稱為 Interrupt Service Routine(ISR)。如果沒有相對應的ISR則僅會回覆ACK而不作任何事。
傳統電腦是利用兩個Programmable Interrupt Controller (PIC)提供15組Interrupt Line連接Device和CPU。Device 觸發中斷請求(Interrupt Request)給PIC然後在傳至CPU,也因為數量有限所以很寶貴。現代的設計是使用Advanced Programmable Interrupt Controller(APIC) 
在Linux Kernel 中存在一個資料結構叫irq_desc 實作在kernel/irq用來鍊結IRQ及ISR,另外也建立Radix treer叫irq_desc_tree來存放irq_desc結構的變數。
  • 建立IRQ
要使用IRQ必須要向核心註冊。註冊的方式則是利用request_irq長相如下:
//include/linux/interrupt.h
/**
 *第一個參數是IRQ的號碼
 *第二個參數是ISR的pointer
 *第三個參數
 *    IRQF_SHARED         Interrupt is shared
 *    IRQF_SAMPLE_RANDOM  代表這個interrupt可以作為系統random的依據(有固定週期的interrupt
 *                        不適合當成random依據)
 *    IRQF_TRIGGER_*      Specify active edge(s) or level
 *第四個參數是顯示在例如/proc/interrupts的名稱
 *第五個參數用途可以參考http://www.cnblogs.com/ITmelody/archive/2012/05/12/2497018.html
 *一般直接填NULL
*/
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
     const char *name, void *dev)
{
    //request_threaded_irq第三個參數是irq_handler_t thread_fn
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
從這裡我們也可以發現其實request_irq()會直接call request_threaded_irq()。因此我們也會看到有人就直接使用request_threaded_irq()。直接舉nVidia的mmc的例子。
//driver/mmc/host/sdhci-tegra.c
static irqreturn_t carddetect_irq(int irq, void *data)
{
 ...
}

static int __devinit sdhci_tegra_probe(struct platform_device *pdev)
{
    struct sdhci_host *host;
    ...
    rc = request_threaded_irq(gpio_to_irq(plat->cd_gpio), NULL,
     carddetect_irq,
     IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
     mmc_hostname(host->mmc), host);
    ...
}
上述的例子是利用GPIO的變化來當成Interrupt的條件。IRQ number是利用gpio_to_irq取得對應的IRQ;ISR是carddetect_irq(),觸發的條件是當此GPIO rising/falling都會觸發Interrupt。

當我們remove module的時候,我們可能需要free掉這個IRQ,這個時候就可以呼叫free_irq來達成。
static int __devexit sdhci_tegra_remove(struct platform_device *pdev)
{
    ...
    if (gpio_is_valid(plat->cd_gpio)) {
        free_irq(gpio_to_irq(plat->cd_gpio), host);
        ...
    }
    ...
}
  • ISR Top and Bottom Halves
Interrupt設計的目的就是不希望卡在攏長的處理。因此,收到中斷後,我們也會希望可以快速處理完要作的事情。然而,常常我們需要作的事情就是這麼多無法快速的做完造成系統的遲鈍。因此,Linux Kernel提出了將ISR拆成top half 跟bottom half。當Interrupt發生後一定要馬上作的事情就屬於Top half直接寫在原本的ISR,而可以晚點做的就屬於Bottom half。我們可以利用SoftIRQ、Tasklet或Workqueu來排程bottom half的部份。下表比較了三種的差異。
下面我們分別介紹Tasklet及Workqueue。
 Tasklet
Tasklets 與Linux Kernel的timer類似,他們都在Interrupt time(Atomic time)執行而且會執行在schedule他的CPU上。不同於timer,我們無法要求tasklet的task在我們期望的時間執行 。同樣的,我們使用nVidia的mmc driver為例來說明Tasklet。
首先,我們必須先註冊一個tasklet,指定這個tasklet的名字、func及data。
//mmc/host/sdhci.c
static void sdhci_tasklet_card(unsigned long param)
{
   ...
}

int sdhci_add_host(struct sdhci_host *host){
   ...
/*
 * Init tasklet.
 *void tasklet_init(struct tasklet_struct *t,
 *                  void (*func)(unsigned long), unsigned long data)
 *簡單來說就是tasklet_init(name, func,data)
*/
    tasklet_init(&host->card_tasklet,
                 sdhci_tasklet_card, (unsigned long)host);
 
   ...
}

接著,這個task必須要在呼叫schedule_tasklet之後才會被排程執行。理所當然,我們會在top half執行完之後去啟動bottom half的部份。因此我們回到top half(carddetect_irq())看看我們如何去啟動bottom half。
//driver/mmc/host/sdhci-tegra.c
static irqreturn_t carddetect_irq(int irq, void *data)
{
    ...
    tasklet_schedule(&sdhost->card_tasklet);
    
    return IRQ_HANDLED;
};


Workqueue
Workqueue的部份則利用omap的mmc driver來舉例。如同tasklet,我們必須利用INIT_WORK(name,func)先註冊一個workqueue。以下面的範例,利用schedule_work()將這個workqueue交給一個叫"event"的公用work thread來執行。
首先我們來看註冊 workqueue的部份。
//driver/mmc/omap_hsmmc.c
static void omap_hsmmc_detect(struct work_struct *work)
{
   ...
}

static int __init omap_hsmmc_probe(struct platform_device *pdev)
{
   ...
   INIT_WORK(&host->mmc_carddetect_work, omap_hsmmc_detect);
   ...
}
而去啟動他的部份則是在top half的omap_hsmmc_cd_handler()則是...
//driver/mmc/omap_hsmmc.c

static irqreturn_t omap_hsmmc_cd_handler(int irq, void *dev_id)
{
 struct omap_hsmmc_host *host = (struct omap_hsmmc_host *)dev_id;

 if (host->suspended)
  return IRQ_HANDLED;
 //啟動Workqueue 
 schedule_work(&host->mmc_carddetect_work);

 return IRQ_HANDLED;
}

  • Reference
Linux Kernel Development Third Edition 
Linux Device Drivers, Third Edition
Intel APIC Architecture
Interrupt_request
Essential Linux Device Drivers

2012年7月4日 星期三

GPIO整理-GPIOLIB (二)

上一篇介紹了GPIO的基礎,在這篇將詳細介紹 GPIOLIB
  • 前言
在以前,假設我們chip上面都有提供GPIO。假設我們今天要 call soc上面的GPIO我們可以使用gpio_direction_output()。但是如果要使用audio chip上面的GPIO以下圖為例,我們可能就得呼叫audio_gpio_direction_output() depends on chip 的driver實作。
為什麼呢?因為每個chip如何去控制他們的GPIO作法可能會不一樣,並且要填的memory位址也不一樣。這麼一來會造成什麼現象呢?如果chip一多,要分別控制上面的GPIO 我們可能就會看到一堆 audio_gpio_direction_output、 pmu_gpio_direction_output、 charger_gpio_direction_output。而且,以gpio_direction_output為例,可能每一個driver的實作都會有一些重複的code例如檢查是否valid等等。
因此,GPIOLIB的引入可以解決這個問題。每一個chip都使用gpio_chip結構來表示,每一個chip driver實作可以定義GPIO的個數、號碼及相關的callback function。 
以gpio_direction_output為例。當我們呼叫gpio_direction_output(number)之後,gpio_direction_output實做在GPIOLIB中,它會去檢查這個number的GPIO是屬於哪個chip然後去call 該chip的driver的request實作。
  • GPIOLIB分析
註冊gpio_chip 
我們從nVidia的git抓下來的kernel裡面隨便找driver/gpio/wm8994-gpio.c為例子trace一下。他的probe如下:
static struct gpio_chip template_chip = {
 .label   = "wm8994",
 .owner   = THIS_MODULE,
/**
 *如果執行gpio_direction_input的GPIO number屬於這個chip
 *則執行wm8994_gpio_direction_in這個callback function
 *以下同義
*/
 .direction_input = wm8994_gpio_direction_in,
 .get   = wm8994_gpio_get,
 .direction_output = wm8994_gpio_direction_out,
 .set   = wm8994_gpio_set,
 .dbg_show  = wm8994_gpio_dbg_show,
 .can_sleep  = 1,
};

static int __devinit wm8994_gpio_probe(struct platform_device *pdev)
{
    struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent);
    //取得platform_device定義的platform_data
    struct wm8994_pdata *pdata = wm8994->dev->platform_data;
    struct wm8994_gpio *wm8994_gpio;
    int ret;
    wm8994_gpio = kzalloc(sizeof(*wm8994_gpio), GFP_KERNEL);
    if (wm8994_gpio == NULL)
 return -ENOMEM;
    
    wm8994_gpio->wm8994 = wm8994;
    /**
     *我們剛剛有說每一個chip使用gpio_chip結構表示。
     *上面宣告了template_chip的gpio_chip結構。
    */
    wm8994_gpio->gpio_chip = template_chip;
    wm8994_gpio->gpio_chip.ngpio = WM8994_GPIO_MAX;
    wm8994_gpio->gpio_chip.dev = &pdev->dev;

    //取得gpio_base。意思是這個chip的GPIO number從多少開始編號 
    if (pdata && pdata->gpio_base)
 wm8994_gpio->gpio_chip.base = pdata->gpio_base;
    else
 wm8994_gpio->gpio_chip.base = -1;
    
    //將這個gpio_chip 註冊進去。待會將分析gpiochip_add
    ret = gpiochip_add(&wm8994_gpio->gpio_chip);
    if (ret < 0) {
 dev_err(&pdev->dev, "Could not register gpiochip, %d\n",
         ret);
 goto err;
    }

    platform_set_drvdata(pdev, wm8994_gpio);
    return ret;

err:
 kfree(wm8994_gpio);
 return ret;
}
接著我們看一下gpiochip_add() 做了什麼事。
//擷取gpio_desc的定義。gpio_desc定義了GPIO是屬於哪個chip,他的flag及label。
struct gpio_desc {
 struct gpio_chip *chip;
 unsigned long  flags;
#ifdef CONFIG_DEBUG_FS
 const char  *label;
#endif
};
//gpio_desc陣列存放著所有gpio的定義
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
int gpiochip_add(struct gpio_chip *chip)
{
 unsigned long flags;
 int  status = 0;
 unsigned id;
 int  base = chip->base;

 if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))
   && base >= 0) {
  status = -EINVAL;
  goto fail;
 }

 spin_lock_irqsave(&gpio_lock, flags);

 if (base < 0) {
  base = gpiochip_find_base(chip->ngpio);
  if (base < 0) {
   status = base;
   goto unlock;
  }
  chip->base = base;
 }

 /**
          *these GPIO numbers must not be managed by another gpio_chip 
          *換句話說就是沒有重複到其他gpio_chip定義的號碼
        */
 for (id = base; id < base + chip->ngpio; id++) {
  if (gpio_desc[id].chip != NULL) {
   status = -EBUSY;
   break;
  }
 }
 if (status == 0) {
     for (id = base; id < base + chip->ngpio; id++) {
         //將這個chip上的所有GPIO的定義加入gpio_desc
                gpio_desc[id].chip = chip;

  /* REVISIT:  most hardware initializes GPIOs as
   * inputs (often with pullups enabled) so power
   * usage is minimized.  Linux code should set the
   * gpio direction first thing; but until it does,
   * we may expose the wrong direction in sysfs.
   */
  gpio_desc[id].flags = !chip->direction_input
   ? (1 << FLAG_IS_OUT)
   : 0;
     }
 }

 of_gpiochip_add(chip);

unlock:
 spin_unlock_irqrestore(&gpio_lock, flags);

 if (status)
  goto fail;

 status = gpiochip_export(chip);
 if (status)
  goto fail;

 return 0;
fail:
 /* failures here can mean systems won't boot... */
 pr_err("gpiochip_add: gpios %d..%d (%s) failed to register\n",
  chip->base, chip->base + chip->ngpio - 1,
  chip->label ? : "generic");
 return status;
}
EXPORT_SYMBOL_GPL(gpiochip_add);
到這裡,我們的kernel已經多了wm8994這個chip的GPIO可以使用了。但是從我們呼叫gpio_direction_input是怎麼跑到driver定義的wm8994_gpio_direction_in呢?
GPIOLIB與CHIP DRIVER結合 
我們看一下gpio_direction_input() 的code。
int gpio_direction_input(unsigned gpio)
{
 unsigned long  flags;
 struct gpio_chip *chip;
 struct gpio_desc *desc = &gpio_desc[gpio];
 int   status = -EINVAL;

 spin_lock_irqsave(&gpio_lock, flags);
        //先檢查這個gpio是否valid
 if (!gpio_is_valid(gpio))
  goto fail;
        //找到這個gpio屬於哪個chip的
 chip = desc->chip;
        //如果chip不存在或這個gpio不能設為input則return fail
 if (!chip || !chip->get || !chip->direction_input)
  goto fail;
        
        //下面兩行檢查該GPIO的編號是否在該chip的範圍內
 gpio -= chip->base;
 if (gpio >= chip->ngpio)
  goto fail;
 status = gpio_ensure_requested(desc, gpio);
 if (status < 0)
  goto fail;

 /* now we know the gpio is valid and chip won't vanish */

 spin_unlock_irqrestore(&gpio_lock, flags);

 might_sleep_if(chip->can_sleep);

 if (status) {
                //呼叫chip定義的request檢查是否可用
  status = chip->request(chip, gpio);
  if (status < 0) {
   pr_debug("GPIO-%d: chip request fail, %d\n",
    chip->base + gpio, status);
   /* and it's not available to anyone else ...
    * gpio_request() is the fully clean solution.
    */
   goto lose;
  }
 }
        //如果是可用的,call chip的direction_input()
 status = chip->direction_input(chip, gpio);
 if (status == 0)
  clear_bit(FLAG_IS_OUT, &desc->flags);

 trace_gpio_direction(chip->base + gpio, 1, status);
lose:
 return status;
fail:
 spin_unlock_irqrestore(&gpio_lock, flags);
 if (status)
  pr_debug("%s: gpio-%d status %d\n",
   __func__, gpio, status);
 return status;
}
EXPORT_SYMBOL_GPL(gpio_direction_input);
至此,應該就都串起來了吧!簡單來說就如下圖:
  • Reference

2012年7月3日 星期二

GPIO 整理(一)

  • 什麼是GPIO?
GPIO全名是General Purpose Input/Output。他是一個有彈性的軟體可控制的數位信號。在許多chip例如CPU或是PMU(Power Management Unit)等等的裝置上都提供了GPIO的設計。每一個GPIO用一個bit(0/1) 來表示他的狀態。GPIO可以拿來做什麼事呢?
  1. Output Value
      輸出的值是可寫的(high=1, low=0)。例如果們可以利用 GPIO來控制LED的亮/暗。
  2. Input Value
      輸入的值是可讀的(1/0)。當然我們也可以藉由讀取GPIO來知道現在例如 LED的亮/暗
  3. Used as IRQ signal
      可以把GPIO拿來當IRQ的信號,例如利用GPIO來判斷 SDcard的"插入/拔出"的動作或是按鍵的"按/放"
  4. Alternative Use
      或甚至,我們可以利用GPIO來實做成SPI等等應用
  • 使用GPIO
在Driver裡面如果要使用GPIO可以簡單分成五個步驟:
  1. Include Header
      在Driver裡面include linux/gpio.h
      #include <linux/gpio.h>
  2. Check Valid(Optional)
      每一個chip上面的GPIO數量是有限制的(而且GPIO號碼一定>=0),為了防止指定的GPIO號碼是錯的,可以先使用gpio_is_valid來檢查。
      int gpio_is_valid(int number);//return true if valid.
  3. Request GPIO(Optional, but recommended)
      gpio_request 可以檢查GPIO number是否超出範圍或<0 及 指定的GPIO是否正在使用。有時候別的地方也控制著同一個GPIO 可能會發生程式預期外的結果
      /**
       *return -EINVAL if GPIO is not valid(same as gpio_is_valid)
       *return -EBUSY if GPIO is already used
       *return 0 if GPIO if fine to use
       *因此我們只要檢查回傳質是否>=0就可以判斷是否可用。
       *第一個參數是填入GPIO的號碼、第二個參數是想要對這個GPIO的命名。
       *$ cat /sys/kernel/debug/gpio就可以看到 
      */
      int gpio_request(unsigned gpio, const char *label);
  4. Write/Read Value
      接著我們選擇要讀值還是寫值。
      //return 1 or 0
      int gpio_direction_input(unsigned gpio);
      /**
       *gpio_direction_output會作許多檢查,例如該GPIO是否可以設high?low?
       *也會呼叫gpio_is_valid來檢查是否可用等等
       *第一個參數是GPIO號碼
       *第二個參數是要寫入的值(0/1)
      */
      int gpio_direction_output(unsigned gpio, int value);
  5. Free GPIO
      當使用完GPIO之後利用gpio_free來釋放GPIO 。
      int gpio_free(unsigned gpio);
將上述串起來以對CARD_DETECT_GPIO寫入1為例:
#include <linux/gpio.h>

...

int ret = gpio_request(CARD_DETECT_GPIO, "CARD_DETECT");
if (ret<0)
    pr_err("%s: gpio_request failed for gpio %d\n",
         __func__, CARD_DETECT_GPIO);
else
    gpio_direction_output(CARD_DETECT_GPIO, 1);
gpio_free(CARD_DETECT_GPIO);
  • 其他函式
/**
 *當我們對某個GPIO設定direction_output之後
 *我們可以直接透過gpio_set_value去改變output的值
*/
void gpio_set_value(unsigned gpio, int value)


/**
 *某些裝置像omap允許設置debounce time(不是所有裝置都有支援)。
*/
int gpio_set_debounce(unsigned gpio, unsigned debounce)


/**
 *對於外部的GPIO controller裝置可能是透過SOC的I2C或其他bus來連接,要
 *控制裝置上的GPIO就必須透過I2C/其他bus來傳送命令。如果這個裝置是sleepable
 *這個時候就必須使用_cansleep了。
*/
int gpio_get_value_cansleep(unsigned gpio);
void gpio_set_value_cansleep(unsigned gpio, int value);


/**
 *gpio_request_*簡化了gpio_request和gpio_direction_output/input。
 *透過一個function就可以完成 request及設定direction及初始值。
 *第一個參數和第三個參數同gpio_request, 第二個參數如下:
 *     GPIOF_DIR_IN         - to configure direction as input
 *     GPIOF_OUT_INIT_LOW - configured as output, initial level LOW
 *     GPIOF_OUT_INIT_HIGH - configured as output, initial level HIGH
*/

//request/set_direction one GPIO in a single call
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);


/**
 *如果我們要同時request多個GPIO的話,gpiolib也定義了一個GPIO的struct
 *可讓我們建成一個array丟進gpio_request_array。Struct的組成如下
*/
struct gpio {
    unsigned     gpio;
    unsigned long   flags;
    const char     *label;
};

//request/set_direction multi-GPIO in a single call
int gpio_request_array(struct gpio *array, size_t num);

//free multi-GPIO in a single call
void gpio_free_array(struct gpio *array, size_t num);
下面的範例示範了如何使用gpio_request_one及 同時設定多組GPIO
static struct gpio leds_gpios[] = {
    { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
    { 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* default to OFF */
    { 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* default to OFF */
    { 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* default to OFF */
    { ... },
};
//request/set_direction in a single call
err = gpio_request_one(31, GPIOF_IN, "Reset Button");
if (err)
 ...

//request/set_direction multi-GPIO in a single call
err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
if (err)
    ...
gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));

  • Reference:

2012年3月23日 星期五

Android LED Implementation


  1. 在config裡勾選
    CONFIG_NEW_LEDS=y
    CONFIG_LEDS_CLASS=y
    CONFIG_LEDS_GPIO=y
    CONFIG_LEDS_TRIGGERS=y
    CONFIG_LEDS_TRIGGER_TIMER=y 

  2. 在board-*.c 裡面建立platform_device 
    static struct gpio_led gpio_leds[] = {
     {
     .name = "WHITE_LED",//should change to your own
     .gpio = _GPIO_1,    //should change to your own
     .active_low = 1,
     .default_trigger = "timer", 
     },
    };
    
    static struct gpio_led_platform_data gpio_led_info = {
     .leds = gpio_leds,
     .num_leds = ARRAY_SIZE(gpio_leds),
    };
    
    static struct platform_device leds_gpio = {
     .name = "leds-gpio",
     .id = -1,
     .dev = {
     .platform_data = &gpio_led_info,
     },
    };
    //記得要在類似init加入-->
    platform_device_register(&leds_gpio);
    

  3. 實做platform_driver
    基本上依我的板子為例,platform_driver的實做已經在driver/leds/leds-gpio.c裡面做好了 不需要更改

  4. 修改LED HAL
    以我的版為例,修改的是libleds.c
    在
    static int open_lights(const struct hw_module_t *module, char const *name,
             struct hw_device_t **device)
    裡面新增了
    }else if(0 == strcmp(LIGHT_ID_BATTERY, name)){
        set_light = set_light_battery;
    }
    
    
    然後因為我的led是gpio led, 只支援on/off ,沒有brightness level, Android定義了不同的level會有不同的顏色或閃爍
    因此我的實做為
    
    
    static int set_light_battery(struct light_device_t *dev,
              struct light_state_t const *state)
    {
     int err = 0;
     int flashmode=state->flashMode;
     
     pthread_mutex_lock(&g_lock);
            
            //set flash
     if(flashmode==1){
      err = write_str("/sys/class/leds/PMU_LED/trigger","timer");
      err = write_int("/sys/class/leds/PMU_LED/delay_on",250);
      err = write_int("/sys/class/leds/PMU_LED/delay_off",150);
     
            }else if(state->color ==0xffffff00||state->color ==0xffff0000||state->color ==-256){
      err = write_str("/sys/class/leds/PMU_LED/trigger","none");
      err = write_int("/sys/class/leds/PMU_LED/brightness",0);
     }else if(state->color ==0xff00ff00||state->color ==0x0){
      err = write_str("/sys/class/leds/PMU_LED/trigger","none");
      err = write_int("/sys/class/leds/PMU_LED/brightness",255);
     }
     pthread_mutex_unlock(&g_lock);
    
     return err;
    }
    

2012年3月22日 星期四

completion

在Linux kernel  中我們可以使用completion來等待某樣事情完成
ex.


#include linux/completion.h
struct completion comp;
void module_init{

         //初始化my_completion, set comp.done=0
         init_completion(&comp);
}


ssiz​​e_t complete_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    //等待所有的comp 都被complete. 
    wait_for_completion(&comp);
    printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
    return 0; /* EOF */
}

ssiz​​e_t complete_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
 
    complete(&comp);
    return count; /* succeed, to avoid retrial */
}

kzalloc()


  • static  inline  void  *kzalloc(size_t size, gfp_t flags): allocate memory. The memory is set to zero.
  • Flag: 
           size: 欲申請大小
           flags: same as kmalloc, see kmalloc



static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}

Reference:
include/linux/slab.h

kmalloc()


  • void * kmalloc (size_t size, int flags) : 在kernel配置記憶體位置
  • INCLUDE    :include/linux/slab.h
  • FLAG: 
          size:欲分配大小
          flags:

Action Modifiers( 通常使用後面提到的type modifier)    
__GFP_WAIT
The allocator can sleep.
__GFP_HIGH
The allocator can access emergency pools.
__GFP_IO
The allocator can start physical I/O.
__GFP_FS
The allocator can start filesystem I/O.
__GFP_COLD
The allocator should use cache cold pages.
__GFP_NOWARN
The allocator will not print failure warnings.
__GFP_REPEAT
The allocator will repeat the allocation if it fails, but the allocation can potentially fail. This depends upon the particular VM implementation.
__GFP_NOFAIL
The allocator will indefinitely repeat the allocation. The allocation cannot fail.
__GFP_NORETRY
The allocator will never retry if the allocation fails.
__GFP_COMP
Add compound page metadata. Used internally by thehugetlb code.
__GFP_ZERO
Add compound page metadata. Used internally by thehugetlb code.

__GFP_NOMEMALLOC
Don't use emergency reserves.
__GFP_HARDWALL
Enforce hardwall cpuset memory allocs.
__GFP_THISNODE
No fallback, no policies.
__GFP_RECLAIMABLE
Page is reclaimable.
__GFP_NOTRACK
Don't track with kmemcheck.
__GFP_NO_KSWAPD
__GFP_OTHER_NODE
On behalf of other node.

Example:

ptr = kmalloc(size, __GFP_WAIT | __GFP_IO | __GFP_FS);
 
Zone Modifiers ( 選擇要從哪分配空間 預設是從ZONE_NORMAL)

__GFP_DMA
Allocate only from ZONE_DMA
__GFP_HIGHMEM
Allocate from ZONE_HIGHMEM or ZONE_NORMAL
__GFP_DMA32

__GFP_MOVABLE

 

Type Flags( 定義了action 及 zone modifier)


GFP_ATOMIC
The allocation is high priority and must not sleep. This is the flag to use in interrupt handlers, in bottom halves, while holding a spinlock, and in other situations where you cannot sleep.
GFP_NOIO
This allocation can block, but must not initiate disk I/O. This is the flag to use in block I/O code when you cannot cause more disk I/O, which might lead to some unpleasant recursion.
GFP_NOFS
This allocation can block and can initiate disk I/O, if it must, but will not initiate a filesystem operation. This is the flag to use in filesystem code when you cannot start another filesystem operation.
GFP_KERNEL
This is a normal allocation and might block. This is the flag to use in process context code when it is safe to sleep. The kernel will do whatever it has to in order to obtain the memory requested by the caller. This flag should be your first choice.
GFP_USER
This is a normal allocation and might block. This flag is used to allocate memory for user-space processes.
GFP_HIGHUSER
This is an allocation from ZONE_HIGHMEM and might block. This flag is used to allocate memory for user-space processes.
GFP_DMA
This is an allocation from ZONE_DMA. Device drivers that need DMA-able memory use this flag, usually in combination with one of the above.

以下列出各Type modifier的組合:

GFP_ATOMIC
__GFP_HIGH
GFP_NOIO
__GFP_WAIT
GFP_NOFS
(__GFP_WAIT | __GFP_IO)
GFP_KERNEL
(__GFP_WAIT | __GFP_IO | __GFP_FS)
GFP_USER
(__GFP_WAIT | __GFP_IO | __GFP_FS)
GFP_HIGHUSER
(__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HIGHMEM)
GFP_DMA
__GFP_DMA



建議的用法:

Situation
Solution
Process context, can sleep
Use GFP_KERNEL
Process context, cannot sleep
Use GFP_ATOMIC, or perform your allocations with GFP_KERNEL at an earlier or later point when you can sleep
Interrupt handler
Use GFP_ATOMIC
Softirq
Use GFP_ATOMIC
Tasklet
Use GFP_ATOMIC
Need DMA-able memory, can sleep
Use (GFP_DMA | GFP_KERNEL)
Need DMA-able memory, cannot sleep
Use (GFP_DMA | GFP_ATOMIC), or perform your allocation at an earlier point when you can sleep


Example:

char *reloc_lp0;
reloc_lp0 = kmalloc(size,GFP_KERNEL);
WARN_ON(!reloc_lp0);
if (!reloc_lp0) {
    pr_err("%s: Failed to allocate reloc_lp0\n",__func__);
   goto out;
}
...
kfree(reloc_lp0);

Reference:
include/linux/gfp.h
http://www.makelinux.net/books/lkd2/ch11lev1sec4
http://www.google.com.tw/url?sa=t&rct=j&q=&esrc=s&source=web&cd=5&ved=0CF8QFjAE&url=http%3A%2F%2Fant.comm.ccu.edu.tw%2Fcourse%2F96_Driver%2F0_Lectures%2FChap7-Getting%2520Hold%2520of%2520Memory_milk.ppt&ei=TMFqT_ysHqWfmQW5obyWBg&usg=AFQjCNGvVEk9-Io481khM_SqoFd-WsvwqQ&sig2=qvOIAsidZzvw15vpq0JlrQ