实验目的

点亮龙芯开发板上面的用户自定义LED灯,编写LED驱动以及测试用例验证实现效果,LED位于开发板左下方(靠近USB口)第二个

LED点亮.jpg

原理图

LED通过电阻上拉至电源P3V3,低电平时LED被点亮

led原理图.png

设备树

打开arch/loongarch/boot/dts/loongson/loongson_2k0300_pai_99.dts,将72-150行的i2c0和i2c1两个节点注释掉,如下所示

注释i2c节点.png

将loongson_2k0300_pai_99设备树编译进内核,编译完后将内核scp到开发板的/boot目录下,然后重启开发板

编译进内核.png

驱动示例

LED驱动

LED对应的GPIO号为83

#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define GPIOLED_CNT			1		  
#define GPIOLED_NAME		"led"	
#define OFF 				0		
#define ON 					1		
#define GPIO_LED 			83

struct my_led_dev{
	dev_t dev_id;		
	struct cdev cdev;	
	struct class *class;	
	struct device *device;	
	int major;			
	int minor;			
	struct device_node	*nd; 
	int led;		
};

struct my_led_dev led_dev;	

static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &led_dev; 
	return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int ret;
	unsigned char data[1];
	unsigned char ledstat;
	struct my_led_dev *dev = filp->private_data;

	ret = copy_from_user(data, buf, cnt);
	if(ret < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = data[0];	

	if(ledstat == ON) {	
		gpio_set_value(dev->led, 0);	
	} else if(ledstat == OFF) {
		gpio_set_value(dev->led, 1);	
	}
	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static struct file_operations led_dev_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

static int __init led_init(void)
{
	int ret = 0;

	led_dev.led = GPIO_LED;
	if(led_dev.led < 0) {
		printk("can't get led-gpio");
		return -EINVAL;
	}

	ret = gpio_request(led_dev.led, "LED-GPIO");
    if (ret) {
        printk(KERN_ERR "led_dev: Failed to request led-gpio\n");
        return ret;
	}

	ret = gpio_direction_output(led_dev.led, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	if (led_dev.major) {		
		led_dev.dev_id = MKDEV(led_dev.major, 0);
		ret = register_chrdev_region(led_dev.dev_id, GPIOLED_CNT, GPIOLED_NAME);
		if(ret < 0) {
			pr_err("cannot register %s char driver [ret=%d]\n", GPIOLED_NAME, GPIOLED_CNT);
			goto free_gpio;
		}
	} else {					
		ret = alloc_chrdev_region(&led_dev.dev_id, 0, GPIOLED_CNT, GPIOLED_NAME);
		if(ret < 0) {
			pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", GPIOLED_NAME, ret);
			goto free_gpio;
		}
		led_dev.major = MAJOR(led_dev.dev_id);	
		led_dev.minor = MINOR(led_dev.dev_id);	
	}
	
	led_dev.cdev.owner = THIS_MODULE;
	cdev_init(&led_dev.cdev, &led_dev_fops);
	cdev_add(&led_dev.cdev, led_dev.dev_id, GPIOLED_CNT);
	if(ret < 0)
		goto del_unregister;
		
	led_dev.class = class_create(THIS_MODULE, GPIOLED_NAME);
	if (IS_ERR(led_dev.class)) {
		goto del_cdev;
	}

	led_dev.device = device_create(led_dev.class, NULL, led_dev.dev_id, NULL, GPIOLED_NAME);
	if (IS_ERR(led_dev.device)) {
		goto destroy_class;
	}
	return 0;
	
destroy_class:
	class_destroy(led_dev.class);
del_cdev:
	cdev_del(&led_dev.cdev);
del_unregister:
	unregister_chrdev_region(led_dev.dev_id, GPIOLED_CNT);
free_gpio:
	gpio_free(led_dev.led);
	return -EIO;
}

static void __exit led_exit(void)
{
	cdev_del(&led_dev.cdev);
	unregister_chrdev_region(led_dev.dev_id, GPIOLED_CNT); 
	device_destroy(led_dev.class, led_dev.dev_id);
	class_destroy(led_dev.class);
	gpio_free(led_dev.led); 
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

Makefile

跟之前一篇文章的Makefile差不多,这里要改一下驱动名称

obj-m += led.o 
KDIR:=/home/asensing/loongson/linux-4.19
ARCH=loongarch 
CROSS_COMPILE=loongarch64-linux-gnu-
PWD?=$(shell pwd) 
all:
	make -C $(KDIR) M=$(PWD) modules 

build脚本

分别编译测试用例和驱动模块

export PATH=$PATH:/home/asensing/loongson/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.3-1/bin
make -j8
loongarch64-linux-gnu-gcc test.c -o test
FILE=$PWD/$(basename $PWD).ko
scp $FILE test root@192.168.137.128:/home/root

测试用例

c代码

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "time.h"

int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    unsigned char data[1];

    if(argc != 2){
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("file %s open failed!\r\n", argv[1]);
        return -1;
    }

    while(1) {
        data[0] = 1;
        ret = write(fd, data, sizeof(data));
        if(ret < 0){
            printf("LED Control Failed!\r\n");
            close(fd);
            return -1;
        }
        usleep(500000);

        data[0] = 0;
        ret = write(fd, data, sizeof(data));
        if(ret < 0){
            printf("LED Control Failed!\r\n");
            close(fd);
            return -1;
        }
        usleep(500000);
    }

    ret = close(fd);
    if(ret < 0){
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

① 通过执行上面的build脚本,生成led.ko和test文件

② 插入内核驱动模块led.ko

③ 检查/dev目录是否已经生成设备节点

④ 执行test用例

命令行

在led.ko没有插入的情况下,也可以用以下脚本循环点亮LED灯

while true; 
do 
	echo 1 > /sys/class/gpio/gpio48/value; 
	sleep 0.5; 
	echo 0 > /sys/class/gpio/gpio48/value; 
	sleep 0.5; 
done;

实验效果

可以看到板子左下角第二颗LED被循环点亮