Linux driver development – 07_pinctrl and gpio subsystem combat

Article directory

    • 1 gpio subsystem API functions
    • 2 gpio related OF functions
    • 3 Programming
      • 3.1 Modify the device tree file
      • 3.2 Add LED device node
      • 3.3 Writing LED Light Driver
      • 3.4 Write test APP
    • 4 Compile the driver and test the APP
      • 4.1 Compile the driver
      • 4.2 Compile and test APP
    • 5 run the test

Link:
C/C++ Linux Server Development/Background Architect【ZeroSound Education】-Learning Video Tutorial-Tencent Classroom

1 gpio subsystem API functions

For driver developers, after setting up the device tree, you can use the API functions provided by the gpio subsystem to operate the specified GPIO. The gpio subsystem shields the driver developers from the specific process of reading and writing registers. **This is the benefit of driving layering and separation. Everyone performs their duties and do their own work well.

1. gpio_request function
The gpio_request function is used to apply for a GPIO pin. Before using a GPIO, you must use gpio_request to apply. The function prototype is as follows:

int gpio_request(unsigned gpio, const char *label)
    
Function parameters and return values have the following meanings:
gpio: the gpio label to apply for, use the of_get_named_gpio function to get the specified GPIO attribute information from the device tree, and this function will return the GPIO label.
label: Set a name for gpio.
Return value: 0, the application is successful; other values, the application fails.

2. gpio_free function
If a GPIO is not used, then it can be released by calling the gpio_free function. The function prototype is as follows:

void gpio_free(unsigned gpio)

Function parameters and return values have the following meanings:
gpio: The gpio label to release.
Return value: None.

3. gpio_direction_input function
This function is used to set a GPIO as an input, and the function prototype is as follows:

int gpio_direction_input(unsigned gpio)

Function parameters and return values have the following meanings:
gpio: GPIO label to be set as input.
Return value: 0, the setting is successful; negative value, the setting fails.

4. gpio_direction_output function
This function is used to set a GPIO as an output and set the default output value. The function prototype is as follows:

int gpio_direction_output(unsigned gpio, int value)

Function parameters and return values have the following meanings:
gpio: GPIO number to set as output.
value: GPIO default output value.
Return value: 0, the setting is successful; negative value, the setting fails.

5. gpio_get_value function
This function is used to get the value (0 or 1) of a GPIO, this function is a macro, the definition is as follows:

#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)

Function parameters and return values have the following meanings:
gpio: GPIO label to get.
Return value: non-negative value, the obtained GPIO value; negative value, failure to obtain.

6. gpio_set_value function
This function is used to set the value of a GPIO, this function is a macro, defined as follows

#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)

Function parameters and return values have the following meanings:
gpio: GPIO label to be set.
value: The value to set.
Return value: None

2 gpio related OF functions

of_get_named_gpio function
This function obtains the GPIO number, because the API functions about GPIO in the Linux kernel must use the GPIO number, this function will convert the property information similar to **< & amp; gpio5 7 GPIO_ACTIVE_LOW> in the device tree to the corresponding GPIO number** , this function is frequently used in the driver! The function prototype is as follows:

int of_get_named_gpio(struct device_node *np,
                        const char *propname,
                        int index)
    
Function parameters and return values have the following meanings:
np: device node.
propname: Contains the property name to get GPIO information.
index: GPIO index, because one attribute may contain multiple GPIOs, this parameter specifies which GPIO to obtain
If there is only one GPIO information, this parameter is 0.
Return value: positive value, the obtained GPIO number; negative value, failure.

3 Programming

Use pinctrl and gpio subsystem to complete LED lamp driving

3.1 Modify device tree file

1. Add pinctrl node

The LED light on the development board uses the PIN GPIO1_IO03, open imx6ull-alientekemmc.dts, and create a subnode named “pinctrl_led” under the imx6ul-evk subnode of the iomuxc node

pinctrl_led: ledgrp {<!-- -->
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
        >;
};

Multiplex the PIN of GPIO1_IO03 as GPIO1_IO03, and the electrical attribute value is 0X10B0

3.2 Add LED device node

Create an LED lamp node under the root node “/”, the node name is “gpioled”

gpioled {<!-- -->
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "atkalpha-gpioled";
    pinctrl-names = "default";
    pinctrl-0 = < & amp;pinctrl_led>; /*Set the pinctrl node corresponding to the PIN used by the LED light*/
    led-gpio = < & amp;gpio1 3 GPIO_ACTIVE_LOW>; /* specifies the GPIO used by the LED light, here is IO03 of GPIO1, active low*/
    status = "okay";
};
  • On line 6, the pinctrl-0 attribute sets the pinctrl node corresponding to the PIN used by the LED light.
  • In line 7, the led-gpio attribute specifies the GPIO used by the LED light, here is IO03 of GPIO1, active at low level; when writing the driver program, < strong>Get the content of the led-gpio attribute to get the GPIO number, the API operation function of the gpio subsystem needs the GPIO number.

After the modification of the device tree is completed, enter the following command to recompile imx6ull-alientek-emmc.dts:

make dtbs

After compiling, get imx6ull-alientek-emmc.dtb, use the new imx6ull-alientek-emmc.dtb to start the Linux kernel.

After the Linux startup is successful, enter the /proc/device-tree/ directory to check whether there is a “gpioled” node

/ # cd /proc/device-tree/
/sys/firmware/devicetree/base # ls
#address-cells interrupt-controller@00a01000
#size-cells key
aliases memory
alpha model
backlight name
beep pxp_v4l2
chosen regulators
clocks reserved-memory
compatible sii902x-reset
cpus soc
dtsleds sound
gpio-keys spi4
gpioled

You can enter the gpioled directory in , and check which property files are there.

/sys/firmware/devicetree/base # cd gpioled/
/sys/firmware/devicetree/base/gpioled # ls
#address-cells compatible name pinctrl-names
#size-cells led-gpio pinctrl-0 status

Check whether the compatible, status and other attribute values are consistent with our settings

3.3 LED lamp driver programming

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>


#define GPIOLED_CNT 1 /* Number of device numbers */
#define GPIOLED_NAME "gpioled" /* name */
#define LEDOFF 0 /* turn off the light */
#define LEDON 1 /* turn on the light */

/* gpioled device structure */
struct gpioled_dev{<!-- -->
    dev_t devid; /* device number */
    struct cdev cdev; /* cdev */
    struct class* class; /* class */
    struct device* device; /* device */
    int major; /* major device number */
    int minor; /* Minor device number */
    struct device_node* nd; /* device node */
    int led_gpio; /* GPIO number used by led */
};

struct gpioled_dev gpioled; /* led device */

/*
 * @description : open the device
 * @param - inode : the inode passed to the driver
 * @param - filp : device file, the file structure has a member variable called private_data
 * Generally point private_data to the device structure when opening.
 * @return : 0 success; other failure
 */
static int led_open(struct inode* inode, struct file* filp)
{<!-- -->
    filp->private_data = & gpioled;
    return 0;
}

/*
 * @description : read data from device
 * @param - filp: device file to open (file descriptor)
 * @param - buf : data buffer returned to user space
 * @param - cnt : length of data to be read
 * @param - offt : offset relative to the first address of the file
 * @return : The number of bytes read, if it is negative, it means the read failed
 */
static ssize_t led_read(struct file* filp, char __user* buf, size_t cnt, loff_t* offt)
{<!-- -->

    return 0;
}

/*
 * @description : Write data to the device
 * @param - filp: device file, indicating the open file descriptor
 * @param - buf : the data to be written to the device
 * @param - cnt : length of data to be written
 * @param - offt : offset relative to the first address of the file
 * @return : The number of bytes written, if it is a negative value, it means that the writing failed
 */
static ssize_t led_write(struct file* filp, const char __user* buf, size_t cnt, loff_t* offt)
{<!-- -->
    int ret;
    unsigned char databuf[1];
    struct gpioled_dev *dev = filp->private_data;

    ret = copy_from_user(databuf, buf, cnt);
    if(ret < 0) {<!-- -->
        return -EINVAL;
    }

    if(databuf[0] == LEDON) {<!-- -->
        gpio_set_value(dev->led_gpio, 0);
    } else if(databuf[0] == LEDOFF) {<!-- -->
        gpio_set_value(dev->led_gpio, 1);
    }

    return 0;
}

/*
 * @description : close/release the device
 * @param - filp: the device file to close (file descriptor)
 * @return : 0 success; other failure
 */
static int led_release(struct inode* inode, struct file* filp)
{<!-- -->

    return 0;
}

/* Device operation function */
static struct file_operations gpioled_fops = {<!-- -->
    .owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release
};


/*
 * @description : Driver export function
 * @param : None
 * @return : none
 */
static int __init led_init(void)
{<!-- -->
    int ret = 0;

    /* Register character device driver */
/* 1. Create device number */
if (gpioled.major) {<!-- --> /* defines the device number */
gpioled.devid = MKDEV(gpioled.major, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
} else {<!-- --> /* No device number defined */
alloc_chrdev_region( & amp;gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* Request device number */
gpioled.major = MAJOR(gpioled.devid); /* Get the major device number of the assigned number */
gpioled.minor = MINOR(gpioled.devid); /* Get the minor device number of the assigned number */
}
printk("gpioled major=%d,minor=%d\r\
",gpioled.major,gpioled.minor);
\t
/* 2. Initialize cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init( &gpioled.cdev, &gpioled_fops);
\t
/* 3. Add a cdev */
cdev_add( & gpioled.cdev, gpioled.devid, GPIOLED_CNT);

/* 4. Create class */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled. class)) {<!-- -->
return PTR_ERR(gpioled.class);
}

/* 5. Create device */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if (IS_ERR(gpioled.device)) {<!-- -->
return PTR_ERR(gpioled.device);
}

    /* 1, get the device node */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL) {<!-- -->
        ret = -EINVAL;
        goto fail_findnode;
    }

    /* 2, Get the GPIO corresponding to the LED */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if(gpioled.led_gpio < 0) {<!-- -->
        printk("can't find led gpio\r\
");
        ret = -EINVAL;
        goto fail_findnode;
    }
    printk("led gpio num = %d\r\
", gpioled.led_gpio);

    /* 3, apply for IO */
    ret = gpio_request(gpioled.led_gpio, "led-gpios");
if (ret) {<!-- -->
printk("Failed to request the led gpio\r\
");
ret = -EINVAL;
        goto fail_findnode;
}

    /* 4, use IO, set as output */
    ret = gpio_direction_output(gpioled. led_gpio, 1);
    if (ret) {<!-- -->
goto fail_setoutput;
}

    return 0;

fail_setoutput:
    gpio_free(gpioled. led_gpio);
fail_findnode:
    return ret;
}

/*
 * @description : Driver export function
 * @param : None
 * @return : none
 */
static void __exit led_exit(void)
{<!-- -->
    /* turn off the lights */
    gpio_set_value(gpioled. led_gpio, 1);

    /* Unregister the character device driver */
    cdev_del( & gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled. class);

    /* release IO */
    gpio_free(gpioled.led_gpio);
}

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

3.4 Writing a test app

Write a test APP, and manually create the /dev/led node after the led driver is successfully loaded, and use the APP to complete the control of the LED device by operating the /dev/led file. Write 0 to the /dev/led file to turn off the LED light, and write 1 to turn the LED light on. Create a new ledApp.c file

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

#define LEDOFF 0
#define LEDON 1

/*
 * @description : main main program
 * @param - argc : the number of elements in the argv array
 * @param - argv : Specific parameters
 * @return : 0 success; other failure
 */
int main(int argc, char *argv[])
{<!-- -->
int fd, retvalue;
char *filename;
unsigned char databuf[1];
\t
if(argc != 3){<!-- -->
printf("Error Usage!\r\
");
return -1;
}

filename = argv[1];

/* Turn on the led driver */
fd = open(filename, O_RDWR);
if(fd < 0){<!-- -->
printf("file %s open failed!\r\
", argv[1]);
return -1;
}

databuf[0] = atoi(argv[2]); /* action to perform: open or close */

/* Write data to /dev/led file */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){<!-- -->
printf("LED Control Failed!\r\
");
close(fd);
return -1;
}

retvalue = close(fd); /* close the file */
if(retvalue < 0){<!-- -->
printf("file %s close failed!\r\
", argv[1]);
return -1;
}
return 0;
}

4 Compile the driver and test the APP

4.1 Compile the driver

Write Makefile

KERNELDIR := /home/kaka/linux/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

CURRENT_PATH := $(shell pwd)

obj -m := gpioled.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

Enter the following command to compile the driver module file:

make -j32

After the compilation is successful, a driver module file named “dtsled.ko” will be generated.

4.2 Compile and test APP

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

5Run tests

Copy the compiled led.ko and ledApp files to the rootfs/lib/modules/4.1.15 directory, and enter the following command to load the led.ko driver module:

depmod //This command needs to be run when loading the driver for the first time
modprobe dtsled.ko //load driver

After the driver is loaded successfully, some information will be output in the terminal

/lib/modules/4.1.15 # modprobe gpioled.ko
gpioled major=249, minor=0
led gpio num = 3

After the driver node is successfully created, you can use the ledApp software to test whether the driver works normally, and enter the following command to turn on the LED light:

./ledApp /dev/led 1 //Turn on the LED light

If it is on, it means the driver is working normally. Enter the following command to turn off the LED light:

./ledApp /dev/led 0 //Turn off the LED light

To uninstall the driver, enter the following command:

rmmod gpioled.ko