Orange Pi (orangePiZero2) 2: Driver development, writing code to control the level of the IO port

1. View the chip manual

Go to the official website to download the chip manual and view the manual.

1. As can be seen from the chip manual, the base address of GPIO (0x0300B000):

2. You can see the PC offset (0x0048) from the chip manual:

The address of GPIOx is the GPIO base address + the offset of GPIOx, so the address of GPIOPC is: 0x0300B000 + 0x0048 = 0x0300B048.

3. Find the port information we need to modify the control:

4. Use the manual to understand how to operate the GPIO data bits:

If the port is configured as an input or output, the state of the pin is the same as the corresponding bit. The bit value read is the value set by software.

If the port is configured as a functional pin, an undefined value will be read.

2. Write code to perform IO operations

Write driver code pin5driver.c:

#include <linux/fs.h> //file_operations statement
#include <linux/module.h> //module_init module_exit statement
#include <linux/init.h> //__init__exit macro declaration
#include <linux/device.h> //class device declaration
#include <linux/uaccess.h> //copy_from_user header file
#include <linux/types.h> //Device number dev_t type declaration
#include <asm/io.h> //ioremap iounmap header file


static struct class *pin5_class;
static struct device *pin5_class_dev;

static dev_t devno; //device number
static int major =231; //Main device number
static int minor =0; //Minor device number
static char *module_name="pin5"; //Module name

//volatile function: will not be omitted due to compiler optimization, and requires direct reading every time
volatile unsigned int* GPIOBASE = NULL;
volatile unsigned int* GPIOPC = NULL;
volatile unsigned int* GPIODAT = NULL;


        
//ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
static ssize_t pin5_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
        printk("pin5_read\\
");
        return 0;
}

static int pin5_open(struct inode *inode,struct file *file)
{

        printk("pin5_open\\
"); //Kernel printing function, similar to printf

        //Configure bit22~bit20 to 001, which is the output mode
        *GPIOPC & amp;= ~(0x6 << 20); //Configure bit22 and bit21 to 0
        *GPIOPC |= (0x1 << 20); //Configure bit20 to 1

        return 0;
}

static ssize_t pin5_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
        char user_cmd;

        printk("pin5_write\\
");

        //Get the value of user space write
        copy_from_user( & amp;user_cmd,buf,count);

        //Control the io port according to the value, which is to output high level or low level to the relevant IO
        if(user_cmd == '1'){
                *GPIODAT |= 0x01 << 5;
                printk("pin5_set\\
");
        }else if(user_cmd == '0'){
                *GPIODAT & amp;= ~(0x01 << 5);
                printk("pin5_reset\\
");
        }else{
                printk("undo\\
");
        }
        
        return 0;
}

static struct file_operations pin5_fops = {
        .owner = THIS_MODULE,
        .open = pin5_open,
        .write = pin5_write,
        .read = pin5_read,
};

static int pin5_drv_init(void)
{
        int ret;
    
        printk("insmod driver pin5 success...\\
");
    
        devno = MKDEV(major,minor); //Create device number
        ret = register_chrdev(major, module_name, & amp;pin5_fops); //Register the driver and tell the kernel to add this driver to the kernel linked list

        pin5_class=class_create(THIS_MODULE,"myfirstdemo"); //Let the code automatically generate the device in dev
        pin5_class_dev =device_create(pin5_class,NULL,devno,NULL,module_name); //Create device file


        /****************************************************** **********
        * (1) 0x300B000, 0x0300B048, 0x0300B058 are all physical addresses
        * (2) The physical address needs to be mapped to a virtual address through the ioremap function
        * (3) Map the IO port register into an ordinary memory unit for access
        *************************************************** **********/

        /************************Map virtual address********************** **/
        
        //GPIO base address
        GPIOBASE = (volatile unsigned int *)ioremap(0x0300B000,4);
        
        //GPIOPC address
        GPIOPC = (volatile unsigned int *)ioremap(0x0300B048,4);
        
        //GPIO data address
        GPIODAT = (volatile unsigned int *)ioremap(0x0300B058,4);
        
        /****************************************************** **********/
    
        return 0;
}

static void pin5_drv_exit(void)
{
        //Unmap
        iounmap(GPIOBASE);
        iounmap(GPIOPC);
        iounmap(GPIODAT);

        device_destroy(pin5_class,devno);
        class_destroy(pin5_class);
        unregister_chrdev(major, module_name);
}

module_init(pin5_drv_init);
module_exit(pin5_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SHUN-GE");

3. Driver code debugging

1. First, we can see the driver code we wrote:

2. Modify the Makefile and add the following content:

3. Then we need to go back to the source code directory of the kernel and compile the module, that is, under the path orangepi-build-main/kernel/orange-pi-4.9-sun50iw9:

Enter the following command to compile into a module:

make modules ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu-

Compilation is as follows:

4. Copy the generated pin5driver.ko module to the development board:

5. Load the driver module in the development board:

Just enter the following command:

sudo insmod pin5driver.ko

6. Check whether the pin5 device driver has been generated:

Enter the following command:

ls /dev/pin5 -l

The result looks like this:

7. Modify the user permissions of pin5 driver:

Enter the following command:

sudo chmod 666 /dev/pin5

View Results:

8. Write an application for testing:

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

int main()
{
    int fd;
    int cmd;
    fd = open("/dev/pin5",O_RDWR);
    if(fd < 0){
        printf("open fail\\
");
    }else {
        printf("open success\\
");
    }
    printf("input command: 1/0\\
");
    printf("1: set pin5 high\\
");
    printf("0: set pin5 low\\
");
    scanf("%d", & amp;cmd);
    if(cmd==1){
        fd = write(fd,"1",1);
        printf("%d=cmd \\
",cmd);
    }else if(cmd == 0){
        fd = write(fd,"0",1);
        printf("%d=cmd \\
",cmd);
    }
}

7. Test results:

(1) PC5 is in OUT mode, and the output level is 1:

(2) PC5 is in OUT mode, and the output level is 0:

(3) Use the dmesg command to view the information printed by the kernel layer driver:

Above, the driver development of the IO port is completed.