14-使用平臺設備的LED驅動

 上一節講了平臺驅動簡單實例,但是並沒有添加實際的硬件信息,本節以LED燈爲例,講解平臺驅動

1. 示例代碼

1.1 led_drv.c

 在 led_drv.c 中使用了 led——dev.c 中的資源,本例中使用的是庫函數對LED進行操作,並沒有直接映射 LED管腳的物理地址,關於物理地址和寄存器的操作方法詳見07-ioctl控制LED軟件實現(寄存器操作),在本例中直接申請gpio資源,設備引腳爲輸出模式,並在 ioctl 中對輸出的電平進行控制。

#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/slab.h>

#include "led.h"

#define LED_NUM (4)
#define LED_CHRDEV_NAME ("led_chrdev")
#define LED_MAJOR (255)

struct led_dev{
    dev_t dev_no;
    unsigned int minor;
    struct cdev cdev;
    struct class *cls;
    struct device *dev;
    struct resource *res;
    unsigned int pin;
    char cls_name[20];
    char gpio_name[20];
};
struct led_dev *p_led_dev;

static int led_open(struct inode *inode, struct file *filp)
{
    struct led_dev *t_led_dev = container_of(inode->i_cdev, struct led_dev, cdev);
    filp->private_data = t_led_dev;
    
    printk("%s -- %d.\n", __FUNCTION__, __LINE__);
    
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    printk("%s -- %d.\n", __FUNCTION__, __LINE__);
    
    return 0;
}

static long led_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
    led_node_t led_t;
    int ret, led_num;
    struct led_dev *t_led_dev = filp->private_data;
    
    printk("%s -- %d.\n", __FUNCTION__, __LINE__);

    if ( _IOC_TYPE(cmd) != LED_MAGIC)   // 判斷幻數
    {
        printk("_IOC_TYPE err.\n");
        return -ENOTTY;
    }
    
    led_num = MINOR(t_led_dev->dev_no);
    switch(cmd)
    {
    case LEDON:
        if (led_num == 1)
        {
            printk("led%d on.\n", led_num);
            gpio_set_value(p_led_dev->pin, 1);
        }
        else if (led_num == 2)
        {
            printk("led%d on.\n", led_num);
            gpio_set_value(p_led_dev->pin, 1);
        }
        else if (led_num == 3)
        {
            printk("led%d on.\n", led_num);
            gpio_set_value(p_led_dev->pin, 1);
        }
        else if (led_num == 4)
        {
            printk("led%d on.\n", led_num);
            gpio_set_value(p_led_dev->pin, 1);
        }
        break;
    case LEDOFF:
        if (led_num == 1)
        {
            printk("led%d off.\n", led_num);
            gpio_set_value(p_led_dev->pin, 0);
        }
        else if (led_num == 2)
        {
            printk("led%d off.\n", led_num);
            gpio_set_value(p_led_dev->pin, 0);
        }
        else if (led_num == 3)
        {
            printk("led%d off.\n", led_num);
            gpio_set_value(p_led_dev->pin, 0);
        }
        else if (led_num == 4)
        {
            printk("led%d off.\n", led_num);
            gpio_set_value(p_led_dev->pin, 0);
        }
        break;
    default:
        printk("cmd id error.\n");
    }
    
    return 0;

}

static struct file_operations led_ops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .unlocked_ioctl = led_ioctl,
};

static int led_probe(struct platform_device *pdev)
{
    int ret;

    printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
    p_led_dev = kzalloc(sizeof(struct led_dev), GFP_KERNEL);
    if ( IS_ERR(p_led_dev) )
    {
        printk("kzalloc error.\n");
        ret = PTR_ERR(p_led_dev);
        goto kzalloc_err;
    }

    p_led_dev->dev_no = MKDEV(LED_MAJOR, pdev->id);
    ret = register_chrdev_region(p_led_dev->dev_no, 1, LED_CHRDEV_NAME);
    if ( ret < 0 )
    {
        printk("register_chrdev_region failed.\n");
        goto alloc_chrdev_region_err;
    }
    p_led_dev->minor = MINOR(p_led_dev->dev_no);	// 次設備號
    p_led_dev->pin = *(unsigned int *)(pdev->dev.platform_data);
    
    cdev_init(&p_led_dev->cdev, &led_ops);

    ret = cdev_add(&p_led_dev->cdev, p_led_dev->dev_no, LED_NUM);
    if ( ret < 0 )
    {
        printk("cdev_add error.\n");
        goto cdev_add_err;
    }

    sprintf(p_led_dev->cls_name, "led_cls%d", pdev->id);
    p_led_dev->cls = class_create(THIS_MODULE, p_led_dev->cls_name);
    if ( IS_ERR(p_led_dev->cls) )
    {
        printk("class_create failed.\n");
        ret = PTR_ERR(p_led_dev->cls);
        goto class_create_err;
    }
    
    p_led_dev->dev = device_create(p_led_dev->cls, NULL, p_led_dev->dev_no, NULL, "led_device%d", pdev->id);
    if ( IS_ERR(p_led_dev->dev) )
    {
        printk("device_create failed.\n");
        ret = PTR_ERR(p_led_dev->dev);
        goto device_create_err;
    }
    
    p_led_dev->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if ( IS_ERR(p_led_dev->res) )
    {
        printk("platform_set_drvdata failed.\n");
        ret = PTR_ERR(p_led_dev->res);
        goto platform_set_drvdata_err;
    }

    ret = gpio_is_valid(p_led_dev->pin);
    if (ret == 0)
    {
        printk("gpio is not valid.\n");
        goto gpio_is_valid_err;
    }

    sprintf(p_led_dev->gpio_name, "led_gpio%d", pdev->id);
    ret = gpio_request(p_led_dev->pin, p_led_dev->gpio_name);
    if (ret < 0)
    {
        printk("gpio_request failed.\n");
        goto gpio_request_err;
    }
    
    ret = gpio_direction_output(p_led_dev->pin, 0);	// 將引腳設置爲輸出,並初始化爲低電平
    if ( ret < 0 )
    {
        printk("gpio_direction_output failed.\n");
        goto gpio_direction_output_err;
    }
    
    platform_set_drvdata(pdev, (void *)p_led_dev);
    
    return 0;


gpio_direction_output_err:
    gpio_free(p_led_dev->pin);
gpio_request_err:
gpio_is_valid_err:
platform_set_drvdata_err:
    device_destroy(p_led_dev->cls, p_led_dev->dev_no);
device_create_err:
    class_destroy(p_led_dev->cls);
class_create_err:
    cdev_del(&p_led_dev->cdev);
cdev_add_err:
    unregister_chrdev_region(p_led_dev->dev_no, 1);
alloc_chrdev_region_err:
    kfree(p_led_dev);
kzalloc_err:
    return ret;
}

static int led_remove(struct platform_device *pdev)
{
    struct led_dev *t_led_dev = platform_get_drvdata(pdev);

    printk("%s -- %d.\n", __FUNCTION__, __LINE__);

    gpio_free(t_led_dev->pin);
    
    device_destroy(t_led_dev->cls, t_led_dev->dev_no);
    class_destroy(t_led_dev->cls);
    cdev_del(&t_led_dev->cdev);
    unregister_chrdev_region(t_led_dev->dev_no, 1);
    kfree(t_led_dev);
    
    return 0;
}

static struct platform_driver led_driver = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .name = "335x_led",
        .owner = THIS_MODULE,
    },
};

module_platform_driver(led_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("使用平臺設備的LED驅動");

1.2 led_dev.c

 在 led_dev.c 中定義了硬件設備的資源,定義了led引腳的地址和管腳號。並通過 platform_device_register 和 platform_device_unregister 分別註冊和註銷平臺設備。如果LED的引腳 變化的話,只需修改本文件即可,而不用修改驅動代碼。

#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>

unsigned int led1_pin = (1*32+7);   // GPIO1_7
unsigned int led2_pin = (1*32+6);   // GPIO1_6
unsigned int led3_pin = (1*32+5);   // GPIO1_5
unsigned int led4_pin = (1*32+4);   // GPIO1_4

void led_dev_release(struct device *dev)
{
    printk("%s -- %d.\n", __FUNCTION__, __LINE__);
}

// LED1 GPIO1_7
struct resource led1_resource[] = {
    [0] = DEFINE_RES_MEM(0x44E1081C, 4),    // CONF_GPMC_AD7
    [1] = DEFINE_RES_MEM(0x4804C134, 4),    // GPIO1_OE	
    [2] = DEFINE_RES_MEM(0x4804C13C, 4),    // GPIO1_DATAOUT
};
    
// LED2 GPIO1_6
struct resource led2_resource[] = {
    [0] = DEFINE_RES_MEM(0x44E10818, 4),    // CONF_GPMC_AD6
    [1] = DEFINE_RES_MEM(0x4804C134, 4),    // GPIO1_OE	
    [2] = DEFINE_RES_MEM(0x4804C13C, 4),    // GPIO1_DATAOUT
};

// LED3 GPIO1_5
struct resource led3_resource[] = {
    [0] = DEFINE_RES_MEM(0x44E10814, 4),    // CONF_GPMC_AD5
    [1] = DEFINE_RES_MEM(0x4804C134, 4),    // GPIO1_OE	
    [2] = DEFINE_RES_MEM(0x4804C13C, 4),    // GPIO1_DATAOUT
};

// LED4 GPIO1_4
struct resource led4_resource[] = {
    [0] = DEFINE_RES_MEM(0x44E10810, 4),    // CONF_GPMC_AD4
    [1] = DEFINE_RES_MEM(0x4804C134, 4),    // GPIO1_OE	
    [2] = DEFINE_RES_MEM(0x4804C13C, 4),    // GPIO1_DATAOUT
};

struct platform_device led1_dev = 
{
    .name = "335x_led",
    .id = 1,
    .num_resources = ARRAY_SIZE(led1_resource),
    .resource = led1_resource,
    .dev = {
        .release = led_dev_release,
        .platform_data = &led1_pin,     // void *型,用來向驅動傳遞更多的信息,這裏傳遞的是管腳號
    },
};

struct platform_device led2_dev = 
{
    .name = "335x_led",
    .id = 2,
    .num_resources = ARRAY_SIZE(led2_resource),
    .resource = led2_resource,
    .dev = {
        .release = led_dev_release,
        .platform_data = &led2_pin,
    },
};

struct platform_device led3_dev = 
{
    .name = "335x_led",
    .id = 3,
    .num_resources = ARRAY_SIZE(led3_resource),
    .resource = led3_resource,
    .dev = {
        .release = led_dev_release,
        .platform_data = &led3_pin,
    },
};

struct platform_device led4_dev = 
{
    .name = "335x_led",
    .id = 4,
    .num_resources = ARRAY_SIZE(led4_resource),
    .resource = led4_resource,
    .dev = {
        .release = led_dev_release,
        .platform_data = &led4_pin,
    },
};


static int __init led_dev_init(void)
{
    printk("%s -- %d.\n", __FUNCTION__, __LINE__);
    
    platform_device_register(&led1_dev);
    platform_device_register(&led2_dev);
    platform_device_register(&led3_dev);
    platform_device_register(&led4_dev);

    return 0;
}

static void __exit led_dev_exit(void)
{
    printk("%s -- %d.\n", __FUNCTION__, __LINE__);

    platform_device_unregister(&led1_dev);
    platform_device_unregister(&led2_dev);
    platform_device_unregister(&led3_dev);
    platform_device_unregister(&led4_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

1.3 led.h

 共有頭文件,主要定義了 ioctl 的幻數和開關LED的命令。

#ifndef _LED_H_
#define _LED_H_

typedef struct led_node
{
	int which;
	int status;
}led_node_t;

#define LED_MAGIC 'q'
#define LEDON  _IOW(LED_MAGIC, 0, struct led_node)
#define LEDOFF _IOW(LED_MAGIC, 1, struct led_node)

#endif /* led.h */

1.4 test.c

 在測試代碼中,首先打開了字符設備,然後分別對LED1~4進行控制。

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "led.h"

#define LED_NUM (4)

const char *led_pathname[LED_NUM] = {
    "/dev/led_device1",
    "/dev/led_device2",
    "/dev/led_device3",
    "/dev/led_device4",
};

int main()
{
	int i, fd_led[LED_NUM];

    for (i=0; i<LED_NUM; i++)
    {
        fd_led[i] = open(led_pathname[i], O_RDWR, 0666);
        if (fd_led[i] < 0)
        {
            printf("open led%d failed\n", i);
            return -1;
        }
    }

    for(i=0; i<LED_NUM; i++)
    {
    	ioctl(fd_led[i], LEDON);  // 點亮LED
    	sleep(1);
    		
    	ioctl(fd_led[i], LEDOFF); // 關閉LED
    	sleep(1);
    }	

    for(i=0; i<LED_NUM; i++)
    {
    	close(fd_led[i]);
    }

	return 0;
}

1.5 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)

EXEC = app
OBJS = test.o
CC   = arm-linux-gnueabihf-gcc

$(EXEC):$(OBJS)
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	$(CC) $^ -o $@
.o:.c
	$(CC) -c $<

install:
	sudo cp *.ko  /tftpboot
	sudo cp app  /tftpboot
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += led_drv.o led_dev.o

1.6 測試結果

root@am335x-evm:~# insmod led_drv.ko    // 先加載驅動
root@am335x-evm:~# insmod led_dev.ko    // 後加載設備
[ 2401.930809] led_dev_init -- 94.      // 執行 led_dev.c 中的註冊平臺設備函數
[ 2401.935483] led_probe -- 128.        // 四個設備調用了四次 probe 函數
[ 2401.969199] led_probe -- 128.
[ 2402.001105] led_probe -- 128.
[ 2402.048874] led_probe -- 128.
root@am335x-evm:~# cat /proc/devices    // 查看加載的設備號
Character devices:
255 led_chrdev
255 led_chrdev
255 led_chrdev
255 led_chrdev
  1 mem
  2 pty
... ...
254 gpiochip

Block devices:
  1 ramdisk
259 blkext
  7 loop
 31 mtdblock
179 mmc
254 virtblk
root@am335x-evm:~# ls /sys/bus/platform/devices/335x* -l    // 查看設備
lrwxrwxrwx    1 root     root             0 Jun 28 21:00 /sys/bus/platform/devices/335x_led.1 -> ../../../devices/platform/335x_led.1
lrwxrwxrwx    1 root     root             0 Jun 28 21:00 /sys/bus/platform/devices/335x_led.2 -> ../../../devices/platform/335x_led.2
lrwxrwxrwx    1 root     root             0 Jun 28 21:00 /sys/bus/platform/devices/335x_led.3 -> ../../../devices/platform/335x_led.3
lrwxrwxrwx    1 root     root             0 Jun 28 21:00 /sys/bus/platform/devices/335x_led.4 -> ../../../devices/platform/335x_led.4
root@am335x-evm:~# ls /sys/bus/platform/drivers/335x_led    // 查看驅動
335x_led.1  335x_led.3  bind        uevent
335x_led.2  335x_led.4  module      unbind
root@am335x-evm:~# ./app            // 加載應用程序
[ 2432.286238] led_open -- 36.      // 打開設備
[ 2432.289556] led_open -- 36.
[ 2432.292580] led_open -- 36.
[ 2432.295478] led_open -- 36.
[ 2432.304665] led_ioctl -- 54.     // 控制LED
[ 2432.313150] led1 on.
[ 2433.315962] led_ioctl -- 54.
[ 2433.319044] led1 off.
[ 2434.321565] led_ioctl -- 54.
[ 2434.324534] led2 on.
[ 2435.332933] led_ioctl -- 54.
[ 2435.335932] led2 off.
[ 2436.346285] led_ioctl -- 54.
[ 2436.349428] led3 on.
[ 2437.351803] led_ioctl -- 54.
[ 2437.354771] led3 off.
[ 2438.361130] led_ioctl -- 54.
[ 2438.364301] led4 on.
[ 2439.366741] led_ioctl -- 54.
[ 2439.369712] led4 off.
[ 2440.372329] led_release -- 43.   // 關閉設備
[ 2440.375514] led_release -- 43.
[ 2440.384440] led_release -- 43.
[ 2440.392720] led_release -- 43.
root@am335x-evm:~# rmmod led_dev.ko     // 卸載設備
[ 2447.855638] led_dev_exit -- 106.
[ 2447.859480] led_remove -- 231.       //驅動中的 remove 函數自動調用
[ 2447.875080] led_dev_release -- 12.   
[ 2447.887279] led_remove -- 231.
[ 2447.907634] led_dev_release -- 12.
[ 2447.911344] led_remove -- 231.
[ 2447.944824] led_dev_release -- 12.
[ 2447.968564] led_remove -- 231.
[ 2447.986485] led_dev_release -- 12.
root@am335x-evm:~# cat /proc/devices                    
Character devices:
  1 mem
  2 pty
... ...
254 gpiochip

Block devices:
  1 ramdisk
259 blkext
  7 loop
 31 mtdblock
179 mmc
254 virtblk
root@am335x-evm:~# rmmod led_drv.ko     // 卸載驅動
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章