This is the 19th day of my participation in the August Wenwen Challenge.More challenges in August

1. Introduction

GPIO driver development may be the simplest and most common direction in Linux kernel device driver development. The keys, LED, buzzer, power control and other modules of the development board may all be realized by USING GPIO. The GPIO subsystem of the Linux kernel has undergone many reconfigurations during the continuous evolution of the kernel. Chapter 2 of this paper introduces the familiar GPIO development mode, and Chapter 3 introduces the latest programming mode of THE GPIO architecture (based on the 4.18 kernel). There is a great relationship between the Linux kernel GPIO subsystem and the Pinctrl subsystem. The pinctrl subsystem can be found in the article, and the discussion about the GPIO subsystem will be summarized in the following article. The basic programming mode for GPIO is discussed below.

2. GPIO programming mode

The GPIO driver Demo described in this chapter is based on Linux Kernel 3.10 and is developed on okMX6ul-C2. The demo function is very simple, the main function is to read the GPIO configuration information through DTS, and then configure each GPIO port according to the configuration information. After that, the application can control the specific behavior of GPIO (IO state reading, IO state output, and so on) through iocTL. The demo driver implementation will be introduced first.

2.1 Programming Interface

The Linux kernel controls GPIO through the GPIOLIB framework. This option must be enabled ** CONFIG_GPIOLIB** during kernel configuration. All of the following interfaces are implemented based on GPIOLIB.

The GPIO programming interfaces involved in this demo are as follows:

(linux/asm-generic/gpio.h):
extern int  gpio_request(unsigned gpio, const char *label);
extern void gpio_free(unsigned gpio);

extern int  gpio_direction_input(unsigned gpio);
extern int  gpio_direction_output(unsigned gpio, int value);

extern int  gpio_set_debounce(unsigned gpio, unsigned debounce);

extern int  gpio_get_value_cansleep(unsigned gpio);
extern void gpio_set_value_cansleep(unsigned gpio, int value);

(linux/gpio.h)
static inline int gpio_get_value(unsigned int gpio);
static inline void gpio_set_value(unsigned int gpio, int value);
Copy the code

2.2 DTS configuration

Demo uses DTS configuration information about GPIO to initialize each port of GPIO. Here is an example of DTS configuration (see article for basic DTS syntax) :

gpios { compatible = "gpio-user"; status = "okay"; /*input*/ gpio0 { label = "in0"; gpios = <&gpio1 1 0>; default-direction = "in"; }; . . /*output*/ gpio17 { label = "out1"; gpios = <&gpio3 3 0>; default-direction = "out"; }; };Copy the code

The above indicates that the system is configured with two GPIO, one as input GPIO and one as output GPIO, where:

  1. Label: identifies the GPIO port.
  2. The definition format of the GPIOS property generally depends on the # gPIO-Cells property field of the GPIO Controler. For this development board, the format of GPIOS is:

<bank_num, offset, default_value>, bank_num is the bank number of GPIO, offset is the offset of GPIO number in bank, defalut_value is the default value of GPIO output state. 3. The default-direction attribute indicates whether GPIO is in input mode or output mode. “in” indicates the input mode and “out” indicates the output mode.

This DMEO is implemented based on the kernel-based MISC device development framework and then registered to the system as platform_driver. The data structure of platform_driver is as follows:

static struct platform_driver gpio_user_driver = {
	.probe = gpio_user_probe,
	.remove = gpio_user_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "gpio-user",
		.of_match_table = of_gpio_user_id_table,
	},
};
Copy the code

Of_gpio_user_id_table defines device compatibility for this driver, which corresponds to the compatible = “gpiO-user “,”gpio-user” fields in Section 2.2. Here is the definition of of_gPIo_user_ID_table:

static const struct of_device_id of_gpio_user_id_table[] = {
	{ .compatible = "gpio-user",},
	{},
};
Copy the code

If the device defined in DTS matches the driver, then gPIo_user_probe will be executed. To get started, we need to examine the private data structure that drives the definition, which is defined as follows:

struct gpio_user_data{ const char *label; //DTS label field bool INPUT; // Whether the input mode is unsigned gpio; // gPIO unsigned DFT; // GPIO output mode default output value}; static struct gpio_misc{ struct miscdevice misc; Struct gpio_user_data *data; //gpio configuration data int gpio_count; } *gpio_misc;Copy the code

Gpio_user_probe is implemented as follows:

static int gpio_user_probe(struct platform_device *pdev) { int index; struct device_node *node = pdev->dev.of_node, *child; gpio_misc = devm_kzalloc(&pdev->dev,sizeof(*gpio_misc),GFP_KERNEL); ------------------>(1) if(! gpio_misc){ return -ENOMEM; } gpio_misc->gpio_count = of_get_available_child_count(node); ------------------>(2) if(! gpio_misc->gpio_count){ return -ENODEV; } if(gpio_misc->gpio_count > MAX_GPIO_NR){ gpio_misc->gpio_count = MAX_GPIO_NR; } gpio_misc->data = devm_kzalloc(&pdev->dev,sizeof(struct gpio_user_data) * gpio_misc->gpio_count,GFP_KERNEL); if(! gpio_misc->data){ return -ENOMEM; } index = 0; for_each_available_child_of_node(node,child){------------------>(3) const char *input; struct gpio_user_data *data = &gpio_misc->data[index++]; data->label = of_get_property(child,"label",NULL) ? : child->name; input = of_get_property(child,"default-direction",NULL) ? : "in"; if(strcmp(input,"in") == 0) data->input = true; data->gpio = of_get_gpio_flags(child,0,&data->dft); } gpio_user_init_default(); ------------------>(4) gpio_misc->misc.name = "gpio"; ------------------>(5) gpio_misc->misc.minor = MISC_DYNAMIC_MINOR; gpio_misc->misc.fops = &gpio_user_fops; return misc_register(&gpio_misc->misc); }Copy the code

The following is a step-by-step analysis:

  • (1) Allocate memory space for GPIo_MISC using devM_kzalloc with memory reclamation function.
  • (2) Obtain the number of GPIO child nodes of DTS by of_get_available_child_count. Determine the validity of this value, then create a GPIO_MISC ->data array based on the number of GPIO child nodes.
  • (3) Initialize the GPIo_misc ->data array using the GPIO child node of THE DTS in for_each_available_child_of_node.
  • (4) Initialize the GPIO configuration. The gPIo_user_init_default function is described in detail below.
  • (5) Initialize misC device information and register GPIO_MISC -> MISC device in the system.

The gpio_user_init_default function is implemented as follows:

static void gpio_user_init_default(void) { int i,ret; struct gpio_user_data *data; data = gpio_misc->data; for(i = 0; i < gpio_misc->gpio_count; i++) { if(! gpio_is_valid(data[i].gpio)) { continue; } ret = gpio_request(data[i].gpio,data[i].label); ------------------>(1) if(ret < 0) { continue; } if(data[i].input) { gpio_direction_input(data[i].gpio); ---------------------->(2) } else { gpio_direction_output(data[i].gpio,data[i].dft); }}}Copy the code

The following is a brief introduction to the implementation steps:

  • (1) Detect the validity of GPIO_NUM, and apply to the system for the right to use GPIO through GPIO_Request (if the application is successful, the GPIO will be marked as occupied, and the function attribute of GPIO is GPIO);

  • (2) Configure the input and output modes of GPIO.

Through the above series of initialization work, the GPIO configured in DTS has been basically configured. Let’s examine the IOCTL interface and explain how to control the behavior of GPIO.

#define GPIO_U_IOCTL_BASE 'x' #define GPIOC_OPS _IOWR(GPIO_U_IOCTL_BASE,0,int) static const struct file_operations gpio_user_fops = { .owner = THIS_MODULE, .open = gpio_user_open, .release = gpio_user_release, .unlocked_ioctl = gpio_user_ioctl, }; static long gpio_user_ioctl(struct file *filp, unsigned int cmd,unsigned long arg) { int no, offset; unsigned long val; unsigned long __user *p = (void __user *)arg; struct gpio_user_data *data; unsigned long get_value; if(! gpio_misc) return -ENODEV; data = gpio_misc->data; if(_IOC_TYPE(cmd) ! = GPIO_U_IOCTL_BASE) return -EINVAL; switch(_IOC_NR(cmd)) { case 0: if(get_user(val,p)) return -EFAULT; no = val & (~(1u << 31)); ------------------>(1) if(data[no].input) { get_value = gpio_get_value(data[no].gpio); ------------------>(2) printk("get_value is %d\n", get_value); offset = data[no].gpio % 32; val = get_value >> offset; printk("val is %d\n",val); put_user(val,p); } else { gpio_set_value(data[no].gpio,val >> 31); ------------------>(3) } break; default: return -ENOTTY; } return 0; }Copy the code

First, an IOCTL operation command is defined with _IOWR :GPIOC_OPS. See the article for details on how _IOWR is used.

The following is a brief introduction to the implementation steps:

  • (1) Check the validity of iocTL control command and obtain GPIO number;
  • (2) If it is GPIO in input mode, read the current value of GPIO and return the status to the user program;
  • (3) If it is GPIO in output mode, set gPIO output value;

The above is both GPIO driver code implementation, the complete code can be downloaded here.

2.3 GPIO test procedure

GPIO test program through the device file (/dev/ GPIO) to complete the GPIO output state configuration and GPIO input state reading function. The code can be downloaded here.

3. Changes of GPIO subsystem

For changes to the GPIO subsystem, refer to subsequent articles, to be continued… .