上一节讲了平台驱动简单实例,但是并没有添加实际的硬件信息,本节以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 // 卸载驱动