本文承接hello驱动的模板, 这里先看下原理图和硬件操作方法
原理图:
对应的GPIO:
可知led1~4分别对应的GPIO是GPM4_0, GPM4_1,GPM4_2,GPM4_3,从datasheet可以抽象为如下结构
/*
* resource
*
* led1 gpm4 0
* led2 gpm4 1
* led3 gpm4 2
* led4 gpm4 3
*
* Note(s) : here use 'led1'
*/
struct _gpio
{
unsigned long con; /* GPM0 configuration register */
unsigned long data; /* GPM0 data register */
unsigned long pud; /* GPM0 pull-up/ pull-down register */
unsigned long drv; /* GPM0 drive strength control register */
unsigned long conpdn; /* GPM0 power down mode configuration register */
unsigned long pudpdn; /* GPM0 power down mode pull-up/ pull-down register 0x000 */
};
#define LED_GPIOM_CON_ADDR 0x110002E0
static struct _gpio *led_gpio = NULL;
地址映射
led_gpio = (struct _gpio *)ioremap(LED_GPIOM_CON_ADDR, 0x20);
完整代码:
/*
* led driver on linux-4.19.27(without dt)
* arm-linux-gcc-6.2.1
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* kmalloc, */
#include
#include /* copy_from_usr, */
#include /* ioremap, */
#define DEV_NAME "LED"
struct led_dev
{
struct cdev *cdev;
dev_t dev;
int major;
char name[32];
struct class *led_class;
};
static struct led_dev *led_dev = NULL;
/*
* resource
*
* led1 gpm4 0
* led2 gpm4 1
* led3 gpm4 2
* led4 gpm4 3
*
* Note(s) : here use 'led1'
*/
struct _gpio
{
unsigned long con; /* GPM0 configuration register */
unsigned long data; /* GPM0 data register */
unsigned long pud; /* GPM0 pull-up/ pull-down register */
unsigned long drv; /* GPM0 drive strength control register */
unsigned long conpdn; /* GPM0 power down mode configuration register */
unsigned long pudpdn; /* GPM0 power down mode pull-up/ pull-down register 0x000 */
};
#define LED_GPIOM_CON_ADDR 0x110002E0
static struct _gpio *led_gpio = NULL;
static int led_open (struct inode *inode, struct file *file)
{
printk(KERN_INFO "open \n");
/* set GPM4_0 as output */
led_gpio->con |= (1 << 0);
return 0;
}
static int led_close (struct inode *inode, struct file *file)
{
printk(KERN_INFO "close \n");
return 0;
}
static ssize_t led_write (struct file *file, const char __user *usrbuf, size_t len, loff_t *off)
{
char cmd,opt;
char rev_buf[8] = {0};
printk(KERN_INFO "write \n");
if(copy_from_user(rev_buf,usrbuf,8))
return -EFAULT;
cmd = rev_buf[0];
opt = rev_buf[1];
printk(KERN_NOTICE "cmd : %d opt : %d \n", cmd, opt);
if(cmd == 0)
{
// off
led_gpio->data |= (1<<0);
}
else if(cmd == 1)
{
// on
led_gpio->data &= ~(1<<0);
}
else
{
// do nothing.
}
return 0;
}
static long led_ioctl (struct file *file, unsigned int cmd, unsigned long arg)
{
printk(KERN_INFO "ioctl \n");
return 0;
}
const struct file_operations led_ops =
{
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
.unlocked_ioctl = led_ioctl,
.write = led_write,
};
static int led_drv_init(void)
{
int ret = 0;
printk(KERN_INFO "led drv init.\n");
led_dev = kmalloc(sizeof(struct led_dev),GFP_KERNEL);
if(!led_dev)
return -ENOMEM;
strcpy(led_dev->name, DEV_NAME);
ret = alloc_chrdev_region(&led_dev->dev, 0, 1, led_dev->name);
led_dev->major = MAJOR(led_dev->dev);
if(ret < 0)
{
kfree(led_dev);
return ret;
}
led_dev->cdev = cdev_alloc();
if(!led_dev->cdev)
{
unregister_chrdev_region(led_dev->dev,1);
kfree(led_dev);
return -EFAULT;
}
cdev_init(led_dev->cdev,&led_ops);
led_dev->cdev->owner = THIS_MODULE;
led_dev->cdev->ops = &led_ops;
cdev_add(led_dev->cdev,led_dev->dev,1);
led_dev->led_class = class_create(THIS_MODULE,led_dev->name);
ret = PTR_ERR(led_dev->led_class);
if (IS_ERR(led_dev->led_class))
{
cdev_del(led_dev->cdev);
unregister_chrdev_region(led_dev->dev,1);
kfree(led_dev);
return -EFAULT;
}
device_create(led_dev->led_class,NULL,led_dev->dev,NULL,led_dev->name,led_dev);
led_gpio = (struct _gpio *)ioremap(LED_GPIOM_CON_ADDR, 0x20);
return 0;
}
static void led_drv_exit(void)
{
printk(KERN_INFO "led drv exit.\n");
iounmap(led_gpio);
device_destroy(led_dev->led_class, led_dev->dev);
class_destroy(led_dev->led_class);
cdev_del(led_dev->cdev);
unregister_chrdev_region(led_dev->dev,1);
kfree(led_dev);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
Makefile
TARGET := led
obj-m += $(TARGET).o
ROOTFS = /home/flinn/tmp/rootfs
KERNEL = /home/flinn/tiny4412-SDK/linux-4.19.27
all:
make -C $(KERNEL) M=`pwd` modules
clean:
make -C $(KERNEL) M=`pwd` clean
install:
#make -C $(KERNEL) M=`pwd` modules_install INSTALL_MOD_PATH=$(ROOTFS)
sudo cp $(TARGET).ko $(ROOTFS)
此外,这里还写了一个工具:
driver_test_tool.c
#include
#include
#include
#include
#include
#include
#include
#include
static char device_name[32] = "/dev/xxx";
#define MAX_ARGS 8
static int cmd_args[MAX_ARGS];
static char *short_options = "c:o:hv";
static struct option long_options[] =
{
{ "open", required_argument, NULL, 'o' },
{ "close", required_argument, NULL, 'c' },
{ "Help", no_argument, NULL, 'H' },
{ "version", no_argument, NULL, 'V' },
{ 0, 0, 0, 0 }
};
static int g_help_flag = 0;
static int g_version_flag = 0;
static int major = 0;
static int minor = 1;
static void version()
{
printf("version-%d.%d \r\n", major, minor);
}
static void usage()
{
fprintf(stderr,
"Usage: driver_test_tool [Device] [cmd] [data] ...\n\n"
"Options:\n"
" -o --open open the device of offset, \n"
" -c --close close \n"
" -h, --help help information \n"
" -v, --version version information \n"
"example: \n"
" driver_test_tool %s -o 0 1 (cmd 0, args : 1) \n\n" , device_name);
}
static int drv_parse_cls(int argc , char **argv)
{
char *dev_name = device_name;
while(1)
{
int c;
c = getopt_long(argc,argv,short_options,long_options,NULL);
if(c == -1)
break;
switch(c)
{
case 'h':
case 'H':
case '?':
g_help_flag = 1;
return 0;
case 'v':
g_version_flag = 1;
return 0;
case 'o':
sscanf(optarg,"%x",&cmd_args[0]);
break;
case 'c':
sscanf(optarg,"%x",&cmd_args[0]);
break;
}
}
if(optind >= argc)
{
printf("No device found \n");
return -1;
}
memcpy(dev_name, argv[optind], strlen(argv[optind]));
++optind;
int count = 1;
while(optind < argc)
{
char *opt = argv[optind++];
char *endptr;
int cmd = strtol(opt,&endptr,16);
if(*opt == '\0')
{
printf("Invalid command byte '%s'\n",opt);
return -1;
}
if(count >= MAX_ARGS)
{
break;
}
cmd_args[count] = cmd;
++count;
}
return 0;
}
int main(int argc , char **argv)
{
int ret ;
int fd = -1;
ret = drv_parse_cls(argc, argv);
if(ret != 0)
{
usage();
return -1;
}
if(1 == g_help_flag)
{
usage();
return 0;
}
else if(1 == g_version_flag)
{
version();
return 0;
}
fd = open(device_name, O_RDWR);
if(fd < 0)
{
printf("open %s fail . fd = %d \n", device_name, fd);
return -1;
}
write(fd, cmd_args , MAX_ARGS);
close(fd);
return 0;
}
使用如下:
例如:
./driver_test_tool /dev/LED -o 1 0 # 表示点亮LED
注意,对于driver_test_tool工具来说,使用arm-linux-gcc-4.3.2(低版本比较好)