linux 查看led设备,Linux下LedButton设备驱动——详细设计
數(shù)據(jù)結(jié)構(gòu)
點(diǎn)擊(此處)折疊或打開
struct pca9555_led {
u8 id;
struct i2c_client *client;
char *name;
struct led_classdev ldev;
struct work_struct work;
enum pca9555_state state;
};
struct pca9555_btn {
int irq;
char *name;
u8 id;
int keycode;
struct input_dev idev;
struct i2c_client *client;
};
struct pca9555_platform_data {
struct pca9555_led leds[5];
struct pca9555_btn btns[8];
};
上面的結(jié)構(gòu)體定義在pca9555.h中,PCA9555有16個(gè)I/O,5個(gè)接led,8個(gè)接按鍵,結(jié)構(gòu)體pca9555_platform_data描述了9555的使用情況。結(jié)構(gòu)體類型pca9555_led和pca9555_btn分別用于描述led和Button,他們都屬于i2c設(shè)備,因此都包括結(jié)構(gòu)體指針變量struct i2c_client *client,led需要向led-class中注冊(cè),其注冊(cè)的設(shè)備結(jié)構(gòu)類型為led_classdev,Button為輸入設(shè)備,在設(shè)備結(jié)構(gòu)體中包含向input子系統(tǒng)注冊(cè)的類型input_dev,并且包含中斷號(hào),按鍵碼等信息。
點(diǎn)擊(此處)折疊或打開
static struct i2c_driver pca9555_driver = {
.driver = {
.name = "pca9555",
},
.probe = pca9555_probe, //當(dāng)有i2c_client與i2c_driver匹配時(shí)調(diào)用
.remove = pca9555_remove, //注銷時(shí)調(diào)用
.id_table = pca9555_id, //根據(jù)id進(jìn)行匹配
}
struct pca9555_data{
struct i2c_client *client;
struct pca9555_led leds[5];
struct pca9555_btn btns[8];
struct mutex update_lock;
};
這兩個(gè)結(jié)構(gòu)體定義在驅(qū)動(dòng)文件pca9555.c中,pca9555_driver在i2c驅(qū)動(dòng)注冊(cè)時(shí)作為參數(shù)被調(diào)用。pca9555_data中除了定義leds、btns之外定義了互斥變量update_lock,在通過i2c總線讀寫設(shè)備時(shí)用到。
I2C設(shè)備的注冊(cè)
在Linux2.6內(nèi)核中支持兩種編寫i2c驅(qū)動(dòng)程序的方式(這里所有內(nèi)核版本為linux2.6.28):Adapter方式(LEGACY)和Probe方式(new style)。對(duì)于LEGACY方式的驅(qū)動(dòng)設(shè)備部分在驅(qū)動(dòng)運(yùn)行的時(shí)候動(dòng)態(tài)創(chuàng)建,新式的驅(qū)動(dòng)(probe方式)傾向于向傳統(tǒng)的Linux下設(shè)備驅(qū)動(dòng)看齊,采用靜態(tài)定義的方式來(lái)注冊(cè)設(shè)備。使用接口為:
int __init? i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
該函數(shù)定義在linux2.6.28/driver/i2c/i2c-boardinfo.c中。在平臺(tái)代碼中將會(huì)調(diào)用該函數(shù)完成i2c_board_info的注冊(cè)。注冊(cè)過程會(huì)根據(jù)info參數(shù)提供的設(shè)備信息封裝一個(gè)devinfo的結(jié)構(gòu)體,并添加到全局鏈表_i2c_board_list中。
對(duì)于i.MX233,在linux2.6.28\arch\arm\mach-stmp3xxx\stmp378x_devb.c中,在完成設(shè)備結(jié)構(gòu)體的部分初始化后將會(huì)調(diào)用該接口完成注冊(cè)。相關(guān)代碼如下:
點(diǎn)擊(此處)折疊或打開
static struct pca9555_platform_data imx233_lbtn = {
.leds = {
{
.id = 0;
.name = "led0";
.state = PCA9555_OFF;
},
……
{
id = 11;
.name = "led4"
.state = PCA9555_OFF;
},
}
.btns = {
{
.irq = 17;
.name = "btn1";
.id = 2;
.keycode = KEY_1;
}, ……
{
.irq = 17;
.name = "btn8";
.id = 15;
.keycode = KEY_8;
},
}
};
static struct i2c_board_info __initdata stmp3xxx_i2c_devices[] = {
{ I2C_BOARD_INFO("stfm1000", 0xc0), .flags = I2C_M_TEN },
{ I2C_BOARD_INFO("pca9555",0x20),
.platform_data = &imx233_lbtn,
},
};
static void __init stmp378x_devb_init(void)
{
struct fsl_usb2_platform_data *udata;
stmp3xxx_init();
i2c_register_board_info(0, stmp3xxx_i2c_devices, ARRAY_SIZE(stmp3xxx_i2c_devices));
stmp3xxx_set_mmc_data(&stmp3xxx_mmc.dev);
..
}?????? 其中紅色部分為注冊(cè)pca9555所添加的,0×20為i2c中從設(shè)備pca9555的地址。leds和btns中的id值根據(jù)引腳編號(hào)得到,在驅(qū)動(dòng)中l(wèi)ed和button訪問對(duì)應(yīng)引腳值時(shí)用到。另外btns中的irq為中斷號(hào)。9555中的8個(gè)按鍵共享一個(gè)GPIO中斷,其中斷號(hào)為17。btns中的keycode定義了按鍵碼,將會(huì)上報(bào)到輸入子系統(tǒng)中,當(dāng)中斷產(chǎn)生時(shí)產(chǎn)生相應(yīng)的輸出。
完成i2c_board_info注冊(cè)后,在I2C核心中會(huì)根據(jù)板級(jí)i2c設(shè)備配置信息,創(chuàng)建i2c客戶端設(shè)備(i2c_client)添加到i2c子系統(tǒng)中。i2c驅(qū)動(dòng)
接下來(lái)的部分全部在pca9555.c中實(shí)現(xiàn)。Probe
在數(shù)據(jù)結(jié)構(gòu)中提到static struct i2c_driver pca9555_driver ,在加載驅(qū)動(dòng)模塊時(shí)將會(huì)將其注冊(cè)到i2c子系統(tǒng)中。接口如下:
i2c_add_driver(&pca9555_driver);
在pca9555_driver中定義了一個(gè)probe和remove兩個(gè)回調(diào)函數(shù)。當(dāng)加載驅(qū)動(dòng)模塊后i2c_client和i2c_driver匹配時(shí)將會(huì)調(diào)用pca9555_probe()。
函數(shù)pca9555_probe()所做的工作很簡(jiǎn)單。主要是取得板級(jí)的設(shè)備信息,這里將其保存到變量pca9555_pdata中,然后申請(qǐng)pca9555_data類型變量data空間并完成相關(guān)指針傳遞。接著初始化互斥量update->lock,最后跳轉(zhuǎn)到pca9555_configure()函數(shù)執(zhí)行。
pca9555_configure(client, data, pca9555_pdata);Configure
在pca9555_configure()中所做的工作主要分三個(gè)部分:配置PCA9555引腳功能
點(diǎn)擊(此處)折疊或打開
u8 con[2] = { 0x54,0xf7};
for(i=0;i<2;i++) {
i2c_smbus_write_byte_data(client,PCA9555_REG_PINVERSION(i),0X0);
i2c_smbus_write_byte_data(client,PCA9555_REG_CONF(i),con[i]);
}
將極性反轉(zhuǎn)寄存器配置為全0,然后通過9555的兩個(gè)8位控制寄存器配置I/O引腳的輸入輸出功能。保證接led的引腳為輸出引腳,連有按鍵的為輸入引腳。PCA9555_REG_PINVERSION(i)、PCA9555_REG_CONF(i)定義了操作pca9555特定寄存器的命令字節(jié)的值,在后面出現(xiàn)時(shí)將不解釋。led設(shè)備注冊(cè)
點(diǎn)擊(此處)折疊或打開
for(i=0;i<5;i++) {
struct pca9555_led *led = &data->leds[i];
struct pca9555_led *pled = &pdata->leds[i];
led->client = client;
led->id = pled->id;
led->state = pled->state;
led->name = pled->name;
led->ldev.name = led->name;
led->ldev.brightness_set = pca9555_set_brightness;
err = led_classdev_register(&client->dev,&led->ldev);
if(err<0) {
dev_err(&client->dev,
"couldn't register LED %s \n",led->name);
return -1;
}
}
分別初始化5個(gè)led設(shè)備,并定義了回調(diào)函數(shù)pca9555_set_brightness(),然后將led設(shè)備注冊(cè)到led-class中, 在前面led-class中已經(jīng)介紹。剩下的工作就是實(shí)現(xiàn)回調(diào)函數(shù)pca9555_set_brightness()來(lái)控制led的亮滅。將在后面具體分析。btn設(shè)備注冊(cè)
點(diǎn)擊(此處)折疊或打開
for( i =0;i < 8; i++) {
struct pca9555_btn *pbtn = &pdata->btns[i];
btns[i] = &data->btns[i];
btns[i]->client = client;
btns[i]->name = pbtn->name;
btns[i]->id = pbtn->id;
btns[i]->irq = pbtn->irq;
btns[i]->keycode = pbtn->keycode;;
btns[i]->idev.name = btns[i]->name;
btns[i]->idev.evbit[0] = BIT_MASK(EV_KEY);
set_bit(btns[i]->keycode,btns[i]->idev.keybit);
if(request_irq(btns[i]->irq,button_key_event, IRQF_SHARED,btns[i]->name,&btns[i])) {
printk("button can not be allocate irq");
return -EBUSY;
}
printk(KERN_INFO"%s successfully loaded\n",btns[i]->name);
ret = input_register_device(&btns[i]->idev);
if(ret) {
input_free_device(&btns[i]->idev);
return ret;
}
}
分別初始化8個(gè)按鍵所對(duì)應(yīng)的變量,并設(shè)置idev域,使其支持按鍵事件并設(shè)置對(duì)應(yīng)的按鍵碼。在前面的Linux輸入子系統(tǒng)部分有介紹。接著申請(qǐng)中斷,由于這里的8個(gè)按鍵共享一個(gè)GPIO中斷,中斷類型設(shè)置為IRQF_SHARED,且中斷處理函數(shù)為button_key_event,當(dāng)中斷觸發(fā)時(shí)將會(huì)回調(diào)執(zhí)行該函數(shù)。將會(huì)在后面詳細(xì)分析該函數(shù)。最后注冊(cè)輸入設(shè)備到Linux輸入子系統(tǒng)。回調(diào)函數(shù)
點(diǎn)擊(此處)折疊或打開
pca9555_set_brightness
static void pca9555_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct pca9555_led *led = ldev_to_led(led_cdev);
if(value)
led->state = PCA9555_ON;
else
led->state = PCA9555_OFF;
pca9555_setled(led);}
在/sys/class/leds/中相應(yīng)的led設(shè)備目錄下,通過echo寫入brightness文件的值(0~255)將會(huì)傳遞到value 中,根據(jù)寫入的值是非為0設(shè)置state域,然后調(diào)用pca9555_setled(led)來(lái)點(diǎn)亮或熄滅led設(shè)備。
點(diǎn)擊(此處)折疊或打開
static void pca9555_setled(struct pca9555_led *led)
{
struct i2c_client *client = led->client;
struct pca9555_data *data = i2c_get_clientdata(client);
char reg;
mutex_lock(&data->update_lock);
reg = i2c_smbus_read_byte_data(client,LED_REG(led->id));
if(led->state)
reg = reg & ~(0x1 << ((led->id) % 8));
else
reg = reg | (0x1 <id) %8));
i2c_smbus_write_byte_data(client,LED_REG(led->id),reg);
mutex_unlock(&data->update_lock);
}
在前面提到的led的id值在這里被用到,通過宏LED_REG(led->id)確定控制該led所在引腳的寄存器字節(jié),讀取該寄存器中的值到reg中,然后根據(jù)先前設(shè)置的state域,設(shè)置led引腳所對(duì)應(yīng)的位的值,最后將處理過的reg重新寫入到相應(yīng)的寄存器中。這樣就達(dá)到改變led所在引腳的電位的目的,從而控制led的亮度。另外指出的是在讀寫的時(shí)候需要用到互斥機(jī)制。button_key_event
在configure中申請(qǐng)中斷時(shí),將8個(gè)按鍵共用了一個(gè)GPIO中斷,當(dāng)這些按鍵中的任意一個(gè)產(chǎn)生中斷時(shí)都會(huì)跳轉(zhuǎn)到中斷處理函數(shù)button_key_event中執(zhí)行,這是需要判斷到底是哪個(gè)按鍵被按下,然后向輸入子系統(tǒng)上報(bào)相應(yīng)按鍵對(duì)應(yīng)的事件。
為了確定被按下的按鍵,定義如下兩個(gè)字符型的全局變量inreg1,inreg2,用于保存中斷發(fā)生前反映pca9555輸入引腳電位的兩個(gè)輸入寄存器的值。在發(fā)生第一次中斷之前,在模塊加載中先讀取這連個(gè)寄存器的值。這里放在pca9555_configure()函數(shù)的最后,代碼如下:inreg1 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(0)); inreg2 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(1));
中斷處理程序button_key_event()函數(shù)代碼如下:
點(diǎn)擊(此處)折疊或打開
static irqreturn_t button_key_event(int irq,void *dev_id)
{
//struct pca9555_btn *button = (struct pca9555_btn *)dev_id;
struct i2c_client *client = btns[0]->client;
char linreg1,linreg2;
linreg1 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(0));
linreg2 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(1));
switch((int)inreg1^linreg1) {
case 2:
input_report_key(&btns[0]->idev,btns[0]->keycode,linreg1);
input_sync(&btns[0]->idev);
break;
......
}
switch((int)inreg2^linreg2) {
case 2:
input_report_key(&btns[3]->idev,btns[3]->keycode,linreg2);
input_sync(&btns[3]->idev);
break;
......
}
inreg1 = linreg1;
inreg2 = linreg2;
return IRQ_HANDLED;
}
當(dāng)中斷發(fā)生后,在中斷處理程序中首先讀取按鍵被按下后pca9555中兩個(gè)輸入寄存器中的值,然后與被按下前寄存器中的值inreg1、inreg2比較,確定電位發(fā)生變化的引腳所對(duì)應(yīng)的按鍵,然后通過input_report_key向輸入子系統(tǒng)上報(bào)對(duì)應(yīng)的按鍵事件。接著將這次按鍵被按下后寄存器的值保存到inreg1、inreg2中作為下次按鍵被按下之前的值. 最后退出中斷處理程序。
對(duì)于驅(qū)動(dòng)的設(shè)計(jì)先寫到這,在后面調(diào)試過程中發(fā)現(xiàn)問題再補(bǔ)充和完善。
總結(jié)
以上是生活随笔為你收集整理的linux 查看led设备,Linux下LedButton设备驱动——详细设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Deepfacelab的填坑之旅
- 下一篇: linux强制关机启动后是白屏,解决安装