LED for driving frame

1, What is a drive frame

1. Who wrote the driver
(1) Drive development engineer
(2) Kernel maintainer

2. Driver programming collaboration requirements
(1) The interface should be standardized. Don't make a mess. There are several sets of interfaces
(2) Minimize the difficulty of driving developers

3. What is the driver framework
(1) The maintainer of the driver part in the kernel designs a set of mature, standard and typical driver implementation for each kind of driver, then extracts the same part of the similar hardware drivers from different manufacturers and implements it by himself, and then leaves the interface for the specific driver development engineer to implement the different parts, which is called the driver framework.

(2) Kernel maintainers have designed some systems for unified management and control of system resources in the kernel, which enable the kernel to uniformly coordinate and allocate the use of resources among various drivers, so as to ensure the stable and healthy operation of the whole kernel. For example, all gpios in the system belong to system resources. If each driver module wants to use a GPIO, it must first call a special interface, apply first, use it after application, and release it after use. For example, the interrupt number is also a resource, and the driver must apply before using it. This is also part of the drive frame.

(3) Some specific interface functions and some specific data structures are the direct expression of the driving framework.

2, Basic situation of LED in kernel driver framework

Provided by kernel maintainer: kernel \ drivers \ LEDs \ led class. C
         kernel\drivers\leds\led-core.c

1. Related documents
(1)drivers/leds directory, which is where the LED hardware driver specified by the driver framework should stay.

(2)led-class.c and led-core.c, which together belong to the first part of the LED driver framework, are provided by kernel developers. They describe the logic of the same part of different LED hardware from all manufacturers in the kernel.

(3) LEDs XXXX. C, this file is the second part of the LED driver framework, which is written and added by the driver engineers of different manufacturers. The driver engineers of manufacturers operate the LED in combination with the different hardware conditions of their own companies, and use the interface provided in the first part to interact with the driver framework, so as to finally realize the function of the driver.

2. led driver in the kernel transplanted from Jiuding science and technology s5pv210 development board
(1) Jiuding actually does not use the led Driver Framework recommended by the kernel
(2) On: Drivers / char / LED / x210 led. C

3. Use of case analysis driven framework
(1) Take leds-s3c24xx.c as an example. leds-s3c24xx.c by calling led_classdev_register to complete the registration of our LED driver, and led_classdev_register is defined in drivers / LEDs / LED class. C. Therefore, in fact, the driver engineers of SoC manufacturers call the interfaces provided by kernel developers in the driver framework to implement their own drivers.

(2) The key point of the driver framework is to distinguish what the kernel developers provide and what the driver developers themselves want to provide

4. Typical driver development industry status
(1) Kernel developers develop, maintain and upgrade the driver framework, corresponding to led-class.c and led-core.c

(2) The driver engineer of SOC manufacturer compiles and debugs the device driver source code and provides the reference version, corresponding to leds-s3c24xx.c

(3) The driver engineer of the product manufacturer will transplant and debug based on the driver source code provided by the SoC manufacturer

3, Preliminary analysis of led Driver Framework source code

1. Documents involved

(1)led-core.c

/*
 * LED Class Core
 *
 * Copyright 2005-2006 Openedhand Ltd.
 *
 * Author: Richard Purdie <rpurdie@openedhand.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/rwsem.h>
#include <linux/leds.h>
#include "leds.h"

DECLARE_RWSEM(leds_list_lock);//Independent of specific functions, this macro declares a
					          //Read / write semaphore name and initialize it
EXPORT_SYMBOL_GPL(leds_list_lock);//The function or symbol pair defined in the tag
                      //All kernel code is public, and a function can be exported in a symbolic way
                      //Output to other modules.

LIST_HEAD(leds_list);
EXPORT_SYMBOL_GPL(leds_list);
1)Linux of EXPORT_SYMBOL and EXPORT_SYMBOL_GPL Use and difference of:
	
		Briefly describe the use method:
		One module mod1 Define a function in func1;In another module mod2 Define a function in func2,func2 call func1. 
		In module mod1 In, EXPORT_SYMBOL(func1);
		In module mod2 In, extern int func1();
		Can be in mod2 Call in func1 Yes.

		Similarly EXPORT_SYMBOL_GPL Use the same.

2)EXPORT_SYMBOL What is the role of?
	EXPORT_SYMBOL The functions or symbols defined in the tag are open to all kernel code without modifying the kernel code
 Call directly in your kernel module, that is, use EXPORT_SYMBOL You can export a function to other modules in a symbolic manner
 Block usage.
	Here we need to talk to System.map Make a comparison: System.map In is the function address at the time of connection. After the connection is completed,
In 2.6 When the kernel is running, it does not know which symbol is at which address.
	EXPORT_SYMBOL The symbol of is to save these symbols and their corresponding addresses, which can be used during the operation of the kernel
 Find the address corresponding to these symbols. During the loading process, the essence of the module is that it can be dynamically connected to the kernel,
	If the symbols of the kernel or other modules are referenced in the module, the EXPORT_SYMBOL These symbols can be found
 Corresponding address connection.

usage method:
	First, use after the module function definition EXPORT_SYMBOL(Function name);
	Second, it is used in the module calling the function extern Statement on;
	Third, first load the module that defines the function, and then load the module that calls the function;

difference:
	EXPORT_SYMBOL(name);
	EXPORT_SYMBOL_GPL(name);
	Both macros are used to export a given symbol outside the module, _GPL Version of the macro definition can only make symbol pairs GPL Licensed module
 Block available.
	Symbols must be exported in the global section of the module file,Cannot export in function,This is because these two macros will be extended to
 A special	And the variable must be global. This variable is stored in a special executable part of the module(one
 individual"ELF paragraph"),During loading, the kernel uses this segment to find the variables exported by the module(Interested readers can read it<linux/module.h>For more details). 


3)DECLARE_RWSEM(name)

This macro declares a read-write semaphore name And initialize it

4)LIST_HEAD(leds_list);
Here is kernel/inclue/linux/types.h Medium list_head Structure definition:
struct list_head {
  struct list_head *next, *prev;
};

The kernel provides the following interfaces to initialize the linked list:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
 
#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)
 
static inline void INIT_LIST_HEAD(struct list_head *list)
{
	WRITE_ONCE(list->next, list);
	list->prev = list;
}

For example, through LIST_HEAD(mylist) Initialize a linked list, mylist of prev and next Pointers point to themselves.
   struct list_head mylist = {&mylist,  &mylist} ;   

(2)led-class.c, the function of this file is to provide a class for the led device
  you should look from bottom to top:

/*
 * LED Class Core
 *
 * Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu>
 * Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/sysdev.h>
#include <linux/timer.h>
#include <linux/err.h>
#include <linux/ctype.h>
#include <linux/leds.h>
#include "leds.h"

static struct class *leds_class;

static void led_update_brightness(struct led_classdev *led_cdev)
{
	if (led_cdev->brightness_get)
		led_cdev->brightness = led_cdev->brightness_get(led_cdev);
	
}

static ssize_t led_brightness_show(struct device *dev, 
		struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	/* no lock needed for this */
	led_update_brightness(led_cdev);

	return sprintf(buf, "%u\n", led_cdev->brightness);
}

static ssize_t led_brightness_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t size)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	ssize_t ret = -EINVAL;
	char *after;
	unsigned long state = simple_strtoul(buf, &after, 10);
	size_t count = after - buf;

	if (isspace(*after))
		count++;

	if (count == size) {
		ret = count;

		if (state == LED_OFF)
			led_trigger_remove(led_cdev);
		led_set_brightness(led_cdev, state);
	}

	return ret;
}

static ssize_t led_max_brightness_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	return sprintf(buf, "%u\n", led_cdev->max_brightness);
}

static struct device_attribute led_class_attrs[] = {
	__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
	__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
	__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
	__ATTR_NULL,
};

/**
 * led_classdev_suspend - suspend an led_classdev.
 * @led_cdev: the led_classdev to suspend.
 */
void led_classdev_suspend(struct led_classdev *led_cdev)
{
	led_cdev->flags |= LED_SUSPENDED;
	led_cdev->brightness_set(led_cdev, 0);
}
EXPORT_SYMBOL_GPL(led_classdev_suspend);

/**
 * led_classdev_resume - resume an led_classdev.
 * @led_cdev: the led_classdev to resume.
 */
void led_classdev_resume(struct led_classdev *led_cdev)
{
	led_cdev->brightness_set(led_cdev, led_cdev->brightness);
	led_cdev->flags &= ~LED_SUSPENDED;
}
EXPORT_SYMBOL_GPL(led_classdev_resume);

static int led_suspend(struct device *dev, pm_message_t state)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
		led_classdev_suspend(led_cdev);

	return 0;
}

static int led_resume(struct device *dev)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
		led_classdev_resume(led_cdev);

	return 0;
}

/**
 * led_classdev_register - register a new object of led_classdev class.
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
	led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
				      "%s", led_cdev->name);//Create LED_ Object of classdev class
	if (IS_ERR(led_cdev->dev))
		return PTR_ERR(led_cdev->dev);

#ifdef CONFIG_LEDS_TRIGGERS
	init_rwsem(&led_cdev->trigger_lock);
#endif
	/* add to the list of leds */
	down_write(&leds_list_lock);
	list_add_tail(&led_cdev->node, &leds_list);
	up_write(&leds_list_lock);

	if (!led_cdev->max_brightness)//Sets the default value for brightness
		led_cdev->max_brightness = LED_FULL;//255

	led_update_brightness(led_cdev);//After setting the brightness of the lamp, update the brightness of the lamp

#ifdef CONFIG_LEDS_TRIGGERS
	led_trigger_set_default(led_cdev);
#endif

	printk(KERN_DEBUG "Registered led device: %s\n",
			led_cdev->name);//The printing information indicates that the registration is successful

	return 0;
}

EXPORT_SYMBOL_GPL(led_classdev_register);

/**
 * led_classdev_unregister - unregisters a object of led_properties class.
 * @led_cdev: the led device to unregister
 *
 * Unregisters a previously registered via led_classdev_register object.
 */
void led_classdev_unregister(struct led_classdev *led_cdev)
{
#ifdef CONFIG_LEDS_TRIGGERS
	down_write(&led_cdev->trigger_lock);
	if (led_cdev->trigger)
		led_trigger_set(led_cdev, NULL);
	up_write(&led_cdev->trigger_lock);
#endif

	device_unregister(led_cdev->dev);

	down_write(&leds_list_lock);
	list_del(&led_cdev->node);
	up_write(&leds_list_lock);
}
EXPORT_SYMBOL_GPL(led_classdev_unregister);

static int __init leds_init(void)
{
	leds_class = class_create(THIS_MODULE, "leds");
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
	leds_class->suspend = led_suspend;  // Associated LED device sleep function
	leds_class->resume = led_resume;    // Associated LED device wake-up function
	leds_class->dev_attrs = led_class_attrs;//Create device property files brightness, max_brightness,trigger

	return 0;
}

static void __exit leds_exit(void)
{
	class_destroy(leds_class);
}

subsys_initcall(leds_init);
module_exit(leds_exit);

MODULE_AUTHOR("John Lenz, Richard Purdie");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LED Class Interface");

2,subsys_initcall

(1) After basic analysis, it is found that the kernel developers in the LED driver framework mainly implement led-class.c

(2) We found that led-class.c is a kernel module. The analysis of led-class.c should follow the basic analysis method of the module from bottom to top. This module is executed when the operating system starts, so we can't compile it into a module. We can only "Y" or "N" when configuring the kernel

(3) Why should the part implemented by kernel developers in the LED driver framework be implemented as a module?

Because kernel developers hope that the driver framework can be loaded / unloaded. In this way, when our kernel users do not need this driver framework, we can completely remove it and add it at any time when necessary.

(4)subsys_initcall is a macro defined in linux/init.h. After expanding this macro, it is found that the function of this macro is to put its declared function into a specific segment:. initcall4.init

kernel\include\linux\init.h. There are two identical macro definitions in this file. Select one according to the comments
: Don't use these in modules

#define subsys_initcall(fn)		__define_initcall("4",fn,4)
#define __define_initcall(level,fn,id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" level ".init"))) = fn


subsys_initcall
	__define_initcall("4",fn,4)

(5) Analysis module_init macro, you can see that it puts the function in the. initcall6.init section.
kernel\include\linux\init.h

#define module_init(x)	__initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)		__define_initcall("6",fn,6)

module_init
	__initcall
		device_initcall
			__define_initcall("6",fn,6)

(6) The kernel needs to do a lot of things in order in the startup process. How can the kernel do a lot of initialization operations in order. The kernel solution is to classify all functions to be called when the kernel starts, and then each class will be called and executed in a certain order. These category names are called. initcalln.init. The value of n ranges from 1 to 8. When kernel developers write kernel code, as long as they set the functions to the appropriate level, these functions will be linked into specific segments. When the kernel starts, they can execute each segment in sequence according to the segment order.

  you can check and analyze kernel / arch / arm / kernel / vmlinux. LDS. S (the link script of kernel source code, but this is realized through assembly file). Compile the kernel first and then check it, otherwise you can't see the following contents:

#Excerpt some codes
SECTIONS
{
 . = 0xC0000000 + 0x00008000;
 .init : { /* Init code and data		*/
  _stext = .;
  _sinittext = .;
   *(.head.text)
   *(.init.text) *(.cpuinit.text) *(.meminit.text)
  _einittext = .;
  __proc_info_begin = .;
   *(.proc.info.init)
  __proc_info_end = .;
  __arch_info_begin = .;
   *(.arch.info.init)
  __arch_info_end = .;
  __tagtable_begin = .;
   *(.taglist.init)
  __tagtable_end = .;
  . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
  __initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
  __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;
  __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;
 
  __init_begin = _stext;
  *(.init.data) *(.cpuinit.data) *(.meminit.data) . = ALIGN(8); __ctors_start = .; *(.ctors) __ctors_end = .; *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata)
 }
 . = ALIGN((1 << 12)); .data..percpu : AT(ADDR(.data..percpu) - 0) { __per_cpu_load = .; __per_cpu_start = .; *(.data..percpu..first) *(.data..percpu..page_aligned) *(.data..percpu) *(.data..percpu..shared_aligned) __per_cpu_end = .; }
 . = ALIGN((1 << 12));
 __init_end = .;

(7) After analysis, it can be seen that subsys_initcall and module_ The function of init is the same, except that the functions declared by the former are executed earlier than those of the latter at kernel startup.

static int __init leds_init(void)
{
	leds_class = class_create(THIS_MODULE, "leds");
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
	leds_class->suspend = led_suspend;
	leds_class->resume = led_resume;
	leds_class->dev_attrs = led_class_attrs;
	return 0;
}

  function class_create() creates a new class in the "/ sys/class" directory (i.e. a file in the directory). The class name is "leds" and returns the newly generated class. Then initialize the power management function and class properties of this class. For class initialization, led is defined as:

static struct device_attribute led_class_attrs[] = {
	__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
	__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
	__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
	__ATTR_NULL,
};

As stated in the comment, the structure "device_attribute" is used to export device attributes

#define __ATTR(_name,_mode,_show,_store) { \
	.attr = {.name = __stringify(_name), .mode = _mode },	\
	.show	= _show,					\
	.store	= _store,					\
}
struct device_attribute {
	struct attribute	attr;//User operation authority
	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
			char *buf);//Reading method
	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count);//Writing method
};

   for linux, where all devices are files, the attributes of the device are nothing more than read and write. Therefore, the structures for exporting device attributes include:

Read function [ssize_t (*show)(struct device *dev, struct_device_attribute *attr, chat *buf)] 
Write function [ssize_t (*store)(struct device *dev, struct_device_attribute *attr, chat *buf, size_t count)]

among dev,Corresponding to the to be operated dev, attr Corresponding to the attribute to be read and written, buf What to do with the corresponding equipment, specifically
 The function of device driver also needs to be implemented by device driver developers. For example, enter 1 or on corresponding led Bright, here, 1/on namely
buf The content of, dev yes led, Liang is attr. 

structural morphology struct attribute Is the user's operation permission, because linux Is a multi file system (although there are many embedded devices)
The standby is a single user—— root),All this attribute specifies the access rights of different users and user groups to the device.

3,led_class_attrs

(1) What is an attribute? It corresponds to the contents in the / sys/class/leds / directory in the future, usually files and folders. These files are actually some operation interfaces opened by sysfs to the application layer (very similar to those device files in the / dev / directory)

(2) What is the use of attribute? Its function is to let the application program operate the driver and then operate the hardware device through the attribute file under the / sys/class/leds / directory.

(3)attribute is actually another route to drive implementation, which is different from the line of file_operations mentioned earlier.

4,led_classdev_register

led_classdev_register//Create devices belonging to this class
	device_create

(1) It can be seen from the analysis that the led_classdev_register function is actually to create a device belonging to the leds class. In fact, it is to register a device. Therefore, this function is actually a registered driver interface provided by the kernel developer in the LED driver framework to the SoC manufacturer driver developer.

(2) When we use the LED driver framework to write drivers, the function of led_classdev_register is similar to the function of register_chrdev when we use file_operations to register character device drivers.

struct class {
        const char              *name;     // Class name
        struct module           *owner;    // Class, such as usb module, led module, etc
        struct class_attribute          *class_attrs;      // Class
        
        //const struct attribute_group is in the kernel of version 4.3 and struct device_attribute is in version 2.6.35.7. They are used differently due to different versions
       // Const struct attribute_group * * dev_groups; / / attributes added by the devices contained in the class
        struct device_attribute		*dev_attrs; Class contains properties added by the device
        
        struct kobject                  *dev_kobj;         // Used to identify whether the device contained in the class belongs to block device or character device
 
        int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);    // Used to add environment variables when the device sends uevent messages
        char *(*devnode)(struct device *dev, umode_t *mode);    // Relative pathname of the device node
 
        void (*class_release)(struct class *class);    // The function called when the class is released
        void (*dev_release)(struct device *dev);       // Function called when the device is released
 
        int (*suspend)(struct device *dev, pm_message_t state);    // Functions called when the device sleeps
        int (*resume)(struct device *dev);    // Function called when the device is awakened
 
        const struct kobj_ns_type_operations *ns_type;
        const void *(*namespace)(struct device *dev);
 
        const struct dev_pm_ops *pm;    // Functions for power management
 
        struct subsys_private *p;     // Pointer to class_private structure
};

static int __init leds_init(void)
{
    leds_class = class_create(THIS_MODULE, "leds");    // Create leds_class class
    if (IS_ERR(leds_class))
        return PTR_ERR(leds_class);
    leds_class->suspend = led_suspend;       // Associated LED device sleep function
    leds_class->resume = led_resume;          // Associated LED device wake-up function
    leds_class->dev_attrs = led_class_attrs;    // Create device property files brightness, max_brightness, trigger
    return 0;
}

4, Add or remove a driver from the kernel

1. Remove the LED driver of Jiuding transplantation
(1) The interface of the driver of Jiuding migration in the application layer is in the / sys / devices / platform / x210 led / directory. There are led1, led2, led3 and led4 device files, which manage one led respectively. The following files are used to write the device files to control the light on and off of the development board:

echo 1 > led1 #bright
echo 0 > led1 #Extinguish

(2) To remove the led driver transplanted by Jiuding, remove the option in make menucofig, then make again to get zImage, and then start the new zImage when restarting.

(3) After the new kernel starts, if there is no x210 led directory in / sys/devices/platform / directory, it means that we have successfully removed the driver.

(4) How to remove make menuconfig:

 Device Drivers  ---> 
 	Character devices  --->
 		x210 led driver

Use 'N' to cancel this module so that it will not be compiled. For the specific implementation principle, please refer to the configuration and compilation principle of Linux kernel in the first glimpse of Linux kernel column.

2. Add led Driver Framework Support
(1) There is no LED driver framework in the current kernel. We need to add it.

make menuconfig
	Device Drivers  ---> 
		LED Support  --->    

3. Complete leds-x210.c
Add the corresponding led-s5pv210.c and modify the corresponding makefile and kconfig files

5, Writing led Driver Based on Driver Framework

1. Analysis
(1) Where to refer? drivers/leds/leds-s3c24xx.c
(2) Key points: led_classdev_register
2. Write led driver module
3. Code practice
(1) Debug
(2) Analysis

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>


//Use static mapping to operate io and led
#define GPJ0CON		S5PV210_GPJ0CON
#define GPJ0DAT		S5PV210_GPJ0DAT

 
struct led_classdev led1_cdev;//Defines a structure variable used to describe a device of the led device class
struct led_classdev led2_cdev;//Defines a structure variable used to describe a device of the led device class
struct led_classdev led3_cdev;//Defines a structure variable used to describe a device of the led device class

static void s5pv210_led1_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led1_set\n");
	writel(0x11111111, GPJ0CON);
	
	//Here, the hardware is operated according to the value set by the user
	if (value == LED_OFF)
	{
		//Read and rewrite operation, which not only controls led1, but also does not affect other LEDs
		writel((readl(GPJ0DAT) | (1 << 3)), GPJ0DAT);//led1 off
		
	}
	else
	{
		writel((readl(GPJ0DAT) & ~(1 << 3)), GPJ0DAT);//led1 off
		
	}

}


static void s5pv210_led2_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led2_set\n");
	writel(0x11111111, GPJ0CON);
	
	//Here, the hardware is operated according to the value set by the user
	if (value == LED_OFF)
	{
		//Read and rewrite operation, which not only controls led1, but also does not affect other LEDs
		writel((readl(GPJ0DAT) | (1 << 4)), GPJ0DAT);//led1 off
		
	}
	else
	{
		writel((readl(GPJ0DAT) & ~(1 << 4)), GPJ0DAT);//led1 off
		
	}

}


static void s5pv210_led3_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led3_set\n");
	printk(KERN_INFO "s5pv210_led3_set\n");
	writel(0x11111111, GPJ0CON);
	
	//Here, the hardware is operated according to the value set by the user
	if (value == LED_OFF)
	{
		//Read and rewrite operation, which not only controls led1, but also does not affect other LEDs
		writel((readl(GPJ0DAT) | (1 << 5)), GPJ0DAT);//led1 off
		
	}
	else
	{
		writel((readl(GPJ0DAT) & ~(1 << 5)), GPJ0DAT);//led1 off
		
	}

}


//This function will be called when insmod installs the driver. The main task of this function is to use it
//led driver framework provides a device registration function to register a device
static int __init s5pv210_led_init(void)
{
	int ret = -1;
	
	//Fill in structural variables that describe the properties of led devices
	led1_cdev.name = "led1";
	led1_cdev.brightness = 255;
	led1_cdev.brightness_set = s5pv210_led1_set;
	
	ret = led_classdev_register(NULL, &led1_cdev);//It comes from led-class.c and creates a class that belongs to LED
												 //A device
	if (ret < 0) {								
		printk(KERN_INFO "led_classdev_register failed\n");
		return ret;
	}
	
	//Fill in structural variables that describe the properties of led devices
	led2_cdev.name = "led2";
	led2_cdev.brightness = 255;
	led2_cdev.brightness_set = s5pv210_led2_set;
	
	ret = led_classdev_register(NULL, &led2_cdev);//It comes from led-class.c and creates a class that belongs to LED
												 //A device
	if (ret < 0) {								
		printk(KERN_INFO "led_classdev_register failed\n");
		return ret;
	}	
	
	//Fill in structural variables that describe the properties of led devices
	led3_cdev.name = "led3";
	led3_cdev.brightness = 255;
	led3_cdev.brightness_set = s5pv210_led3_set;
	
	ret = led_classdev_register(NULL, &led3_cdev);//It comes from led-class.c and creates a class that belongs to LED
												 //A device
	if (ret < 0) {								
		printk(KERN_INFO "led_classdev_register failed\n");
		return ret;
	}	

	return 0;
}

static void __exit s5pv210_led_exit(void)
{
	led_classdev_unregister(&led1_cdev);//It comes from led-class.c, and belongs to LED class
									   //A device
	led_classdev_unregister(&led2_cdev);			
	led_classdev_unregister(&led3_cdev);			
}

module_init(s5pv210_led_init); //The function is placed in the. initcall6.init section
module_exit(s5pv210_led_exit); //The function is placed in the specified segments, which are related to the order of execution

/********************MODULE_xxx This macro is used to add module description information*********************/

MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");	//Describe the author of the module
MODULE_DESCRIPTION("s5pv210 LED driver");		//Describe the introduction of the module
MODULE_LICENSE("GPL");         					//Describe the license for the module
MODULE_ALIAS("s5pv210_led");  				 	//Describes the alias information for the module

The analysis shows that:
No. 1: the driver we wrote did work and was loaded. There was indeed a folder representing the device in the / sys/class/leds / directory. There are two corresponding properties brightness and Max for controlling led hardware in the folder_ brightness

Section 2: the brightness method in led-class.c has a show method and a store method. These two methods correspond to the code actually executed when the user directly reads and writes this file in the / sys/class/leds/myled/brightness directory.

When we show brightness, we will actually execute led_brightness_show function
When we echo 1 > brightness, the LED is actually executed_ brightness_ Store function

(3) What the show method actually needs to do is read the LED hardware information, and then return the hardware information to us. Therefore, the show method and store method must be able to manipulate the hardware. However, the led-class.c file is also a file in the driver framework. It itself cannot directly read the specific hardware. Therefore, struct LED is called by using function pointers in the show and store methods_ The corresponding method of reading / writing hardware information in the classdev structure.

(4)struct led_ The functions actually used to read and write hardware information in the classdev structure are provided in our own driver file leds-s5pv210.c.

4. Separate the 4 LED s in the drive
(1) Benefits. The driver layer realizes independent access to each LED device, and shows four operation interfaces led1, led2, led3 and led4 to the application layer, so that the application layer can control the LED completely according to its own needs.

Driven design concept: don't assume the final required function, but just directly operate the hardware. There is a concept: mechanism and strategy. In terms of hardware operation, drivers should only provide mechanisms rather than policies. The policy is done by the application.

eg:
Mechanism: light on
Strategy: how fast is the light on

(2) How
Multiple LED devices are registered in the driver file led-s5pv210.c. The registration process and functions of each LED device are the same.

5. gpiolib introduction
(1) One fact: GPIO will be reused
(2) If the same GPIO is controlled by two drivers at the same time, a bug will occur
(3) Therefore, the kernel provides gpiolib to uniformly manage all gpios in the system
(4)gpiolib itself is a part of the driver framework. Functions can be implemented without this, but it is best to comply with the gpiolib introduction specification

6, gpiolib learning of linux kernel

1. gpiolib learning focus

(1) Establishment process of gpiolib

(2) Usage of gpiolib: application, use and release

(3)gpiolib architecture: which directories and which files are involved

2. Learning methods of gpiolib

(1) Go in with one main line and stick to the main line

(2) Encounter miscellaneous knowledge in the middle, completely solve it, and then continue the main line

(3) Take notes at any time to deepen understanding and memory

(4) Pay attention to the idea of structure during learning and improve the spatial complexity of your brain

3. Mainline 1: establishment of gpiolib

(1) Find objective function

kernel\arch\arm\mach-s5pv210\mach-smdkc110.c
smdkc110_map_io
	s5pv210_gpiolib_init		This function is us gpiolib Initialized function
static void __init smdkc110_map_io(void)
{
	s5p_init_io(NULL, 0, S5P_VA_CHIPID);
	s3c24xx_init_clocks(24000000);
	s5pv210_gpiolib_init();
	s3c24xx_init_uarts(smdkc110_uartcfgs, ARRAY_SIZE(smdkc110_uartcfgs));
	s5p_reserve_bootmem(smdkc110_media_devs, ARRAY_SIZE(smdkc110_media_devs));
#ifdef CONFIG_MTD_ONENAND
	s5pc110_device_onenand.name = "s5pc110-onenand";
#endif
#ifdef CONFIG_MTD_NAND
	s3c_device_nand.name = "s5pv210-nand";
#endif
	s5p_device_rtc.name = "smdkc110-rtc";
}
__init int s5pv210_gpiolib_init(void)
{
	struct s3c_gpio_chip *chip = s5pv210_gpio_4bit;
	int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit);//Get the number of ports in the current system
	int i = 0;

	for (i = 0; i < nr_chips; i++, chip++) {
		if (chip->config == NULL)
			chip->config = &gpio_cfg;
		if (chip->base == NULL)
			chip->base = S5PV210_BANK_BASE(i);
	}

	samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);//Register gpiolib with the kernel

	return 0;
}

kernel\arch\arm\mach-s5pv210\gpiolib.c

4,struct s3c_gpio_chip

kernel\arch\arm\mach-s5pv210\gpiolib.c

struct s3c_gpio_chip {
	struct gpio_chip	chip;//Properties and some setting information
	struct s3c_gpio_cfg	*config;//Configuration related information
	struct s3c_gpio_pm	*pm;//Power management related
	void __iomem		*base;//The base address of the virtual address corresponding to the register of GPIO
	int			eint_offset;
	spinlock_t		 lock;
#ifdef CONFIG_PM
	u32			pm_save[7];
#endif
};

struct gpio_chip {
	const char		*label;//Record the name of GPIO, / sys/class/gpio name /
	struct device		*dev;
	struct module		*owner;

	int			(*request)(struct gpio_chip *chip,//apply
						unsigned offset);
	void			(*free)(struct gpio_chip *chip,//release
						unsigned offset);

	int			(*direction_input)(struct gpio_chip *chip,//Input mode
						unsigned offset);
	int			(*get)(struct gpio_chip *chip,
						unsigned offset);
	int			(*direction_output)(struct gpio_chip *chip,//Output mode
						unsigned offset, int value);
	int			(*set_debounce)(struct gpio_chip *chip,
						unsigned offset, unsigned debounce);

	void			(*set)(struct gpio_chip *chip,
						unsigned offset, int value);

	int			(*to_irq)(struct gpio_chip *chip,//Get the interrupt number of the interrupt corresponding to GPIO
						unsigned offset);

	void			(*dbg_show)(struct seq_file *s,
						struct gpio_chip *chip);
	int			base;//The io port number of the Current io
	u16			ngpio;
	const char		*const *names;
	unsigned		can_sleep:1;
	unsigned		exported:1;
};

struct s3c_gpio_cfg {
	unsigned int	cfg_eint;

	s3c_gpio_pull_t	(*get_pull)(struct s3c_gpio_chip *chip, unsigned offs);
								//Get the status of pull-up or pull-down
	int		(*set_pull)(struct s3c_gpio_chip *chip, unsigned offs,
				    s3c_gpio_pull_t pull);//Set pull-up or pull-down

	int		(*set_pin)(struct s3c_gpio_chip *chip, unsigned offs,
				    s3c_gpio_pull_t level);//set up

	unsigned (*get_config)(struct s3c_gpio_chip *chip, unsigned offs);
						//Get settings content
	int	 (*set_config)(struct s3c_gpio_chip *chip, unsigned offs,
			       unsigned config);//set up
};

(1) This structure is an abstraction of a GPIO port. A variable of this structure can completely describe an IO port.

(2) Port and IO port are two concepts. S5PV210 has many IO ports (about 160). These IO ports are first divided into N ports (port group s), and then each port contains M IO ports. For example, GPA0 is a port, which contains 8 IO ports. We generally record it as GPA0_0 (or GPA0.0), GPA0_1,

(3) Each GPIO is assigned a number in the kernel. The number is a number (for example, when there are 160 IOS, the number can be continuously distributed from 1 to 160). The number allows the program to easily identify each GPIO.

5,s5pv210_gpio_4bit

kernel/arch/arm/mach-s5pv210/gpiolib.c 64 lines
This thing is a structure array, which contains many structs s3c_ gpio_ A variable of type chip.

kernel/arch/arm/mach-s5pv210/gpiolib.c

struct s3c_gpio_chip *chip = s5pv210_gpio_4bit;
int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit);//Get the number of ports in the current system
samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);//Register gpiolib with the kernel

6,S5PV210_GPA0 macro

s5pv210_gpio_4bit Element of structure

.chip	= {
		.base	= S5PV210_GPA0(0),//The base number of the current port
		.ngpio	= S5PV210_GPIO_A0_NR,//Number of IO ports owned by the current port
		.label	= "GPA0",//The name of the current port
		.to_irq = s5p_gpiolib_gpioint_to_irq,//Method for converting IO port number in current port into corresponding interrupt number
	}//Dynamic mapping may be required
#define S5PV210_GPA0(_nr)	(S5PV210_GPIO_A0_START + (_nr))	
enum s5p_gpio_number Enumeration element S5PV210_GPIO_A0_START	= 0,
#define S5PV210_GPA1(_nr)	(S5PV210_GPIO_A1_START + (_nr))

enum s5p_gpio_number Enumeration element S5PV210_GPIO_A1_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_A0),

#define S5PV210_GPIO_NEXT(__gpio) \
	((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)
"##"Is used to connect the left and right characters (such as _gpio and _START).

CONFIG_S3C_GPIO_SPACE the macro can't be found in the source code. It should be found in the file generated by kernel compilation, so you can view the compiled kernel source code in ubuntu to get the value of the macro

(1) The return value of s5pv210_gpa1 (_nr) macro is the number value of an IO port of GPA0 port, and the transmission parameter is the local number of our IO port in GPA0 port.

(2)samsung_ gpiolib_ add_ 4bit_ The chips function is used to register gpiolib. The parameters received by this function are the structure array s5pv210 defined in our current file_ GPIO_ 4bit (in fact, the two parameters are the array name and the number of array elements respectively). This array actually contains the information of all IO ports in the current system (these information includes: port name, number of all gpios in the port, virtual address base address of port operation register group, number of IO ports in the port, configuration function of port pull-down mode, and function of IO port in the port to convert its corresponding interrupt number).

{
		.base	= (S5P_VA_GPIO + 0xC00),//The base address of the virtual address of the register group
		.config	= &gpio_cfg_noint,
		.eint_offset = IRQ_EINT(0),
		.chip	= {
			.base	= S5PV210_GPH0(0),
			.ngpio	= S5PV210_GPIO_H0_NR,
			.label	= "GPH0",
			.to_irq = s5p_gpiolib_eint_to_irq,
		},

   check this array to find that the existing ones have been statically mapped, some have not, and may be dynamically mapped. The size of this array can tell the number of ports in the current system.

__init int s5pv210_gpiolib_init(void)
{
	struct s3c_gpio_chip *chip = s5pv210_gpio_4bit;
	int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit);//Get the number of ports in the current system
	int i = 0;

	for (i = 0; i < nr_chips; i++, chip++) {
		if (chip->config == NULL)
			chip->config = &gpio_cfg;
		if (chip->base == NULL)
			chip->base = S5PV210_BANK_BASE(i);
	}

	samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);//Register gpiolib with the kernel

	return 0;
}
#define S5PV210_BANK_BASE(bank_nr)	(S5P_VA_GPIO + ((bank_nr) * 0x20))
The difference between each two ports is 0 x20

7. Kernel \ arch \ arm \ plat sampling \ gpiolib. C lines 183-199

void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip,
					   int nr_chips)
{
	for (; nr_chips > 0; nr_chips--, chip++) {
		samsung_gpiolib_add_4bit(chip);
		s3c_gpiolib_add(chip);
	}
}

void __init samsung_gpiolib_add_4bit2_chips(struct s3c_gpio_chip *chip,
					    int nr_chips)
{
	for (; nr_chips > 0; nr_chips--, chip++) {
		samsung_gpiolib_add_4bit2(chip);
		s3c_gpiolib_add(chip);
	}
}

8. Several questions

samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);//Register gpiolib with the kernel

(1) Which file in which directory

(2) Why there is a 4bit in the function name: in Samsung's CPU, the CON register of 2440 is 2bit corresponding to an IO port, while in 6410, 210 and later series, the CON register is 4bit corresponding to an IO port. Therefore, gpiolib is different when operating the CON registers of 2440 and 210.

9. Function call relationship

samsung_gpiolib_add_4bit_chips
		samsung_gpiolib_add_4bit(chip);
		s3c_gpiolib_add(chip);

After analysis, it is found that samsung_gpiolib_add_4bit does not register gpiolib, but fills in the operation method that each GPIO is set to input mode / output mode.

void __init samsung_gpiolib_add_4bit(struct s3c_gpio_chip *chip)
{
	chip->chip.direction_input =  samsung_gpiolib_4bit_input;
	chip->chip.direction_output = samsung_gpiolib_4bit_output;
	chip->pm = __gpio_pm(&s3c_gpio_pm_4bit);//Power management, sleep mode related
}
```c
static int samsung_gpiolib_4bit_input(struct gpio_chip *chip,
				      unsigned int offset)
{
	struct s3c_gpio_chip *ourchip = to_s3c_gpio(chip);
	void __iomem *base = ourchip->base;
	unsigned long con;

	con = __raw_readl(base + GPIOCON_OFF);//read
	con &= ~(0xf << con_4bit_shift(offset));//change
	__raw_writel(con, base + GPIOCON_OFF);//Write, all 0 represents the input mode

	gpio_dbg("%s: %p: CON now %08lx\n", __func__, base, con);

	return 0;
}
static int samsung_gpiolib_4bit_output(struct gpio_chip *chip,
				       unsigned int offset, int value)
{
	struct s3c_gpio_chip *ourchip = to_s3c_gpio(chip);
	void __iomem *base = ourchip->base;
	unsigned long con;
	unsigned long dat;

	con = __raw_readl(base + GPIOCON_OFF);//read
	con &= ~(0xf << con_4bit_shift(offset));//change
	con |= 0x1 << con_4bit_shift(offset);//Write, all 1 represents output

	dat = __raw_readl(base + GPIODAT_OFF);

	if (value)
		dat |= 1 << offset;
	else
		dat &= ~(1 << offset);

	__raw_writel(dat, base + GPIODAT_OFF);
	__raw_writel(con, base + GPIOCON_OFF);
	__raw_writel(dat, base + GPIODAT_OFF);

	gpio_dbg("%s: %p: CON %08lx, DAT %08lx\n", __func__, base, con, dat);

	return 0;
}

10,s3c_gpiolib_add

(1) First, check and improve the four methods of chip direction_input / direction_output / set / get

(2) then we call the gpiochip_add method for the real registration operation. In fact, this registration is linked to the chip structure variable that encapsulates all the information of a GPIO port to a lattice in a gpio_desc array defined by the kernel gpiolib module.

__init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)
{
	struct gpio_chip *gc = &chip->chip;
	int ret;

	BUG_ON(!chip->base); //
Assert
	BUG_ON(!gc->label);
	BUG_ON(!gc->ngpio);

	spin_lock_init(&chip->lock);//Spin lock correlation

	if (!gc->direction_input)//It is not empty. samsung_gpiolib_add_4bit() has been bound and initialized
		gc->direction_input = s3c_gpiolib_input;//2bits for each io port
	if (!gc->direction_output)
		gc->direction_output = s3c_gpiolib_output;
	if (!gc->set)
		gc->set = s3c_gpiolib_set;
	if (!gc->get)
		gc->get = s3c_gpiolib_get;

#ifdef CONFIG_PM
	if (chip->pm != NULL) {
		if (!chip->pm->save || !chip->pm->resume)
			printk(KERN_ERR "gpio: %s has missing PM functions\n",
			       gc->label);
	} else
		printk(KERN_ERR "gpio: %s has no PM function\n", gc->label);
#endif

	/* gpiochip_add() prints own failure message on error. */
	ret = gpiochip_add(gc);
	if (ret >= 0)
		s3c_gpiolib_track(chip);
}
ret = gpiochip_add(gc);
gpiochip_add Function:
	/* these GPIO numbers must not be managed by another gpio_chip */
	for (id = base; id < base + chip->ngpio; id++) {
		if (gpio_desc[id].chip != NULL) {
			status = -EBUSY;
			break;

11. Analyze gpiolib from the perspective of driver framework

(1) The previous analysis has come to an end. Up to now, we have made clear the establishment project of gpiolib. However, this is only one part of the establishment of the whole gpiolib, which is the responsibility of the manufacturer's driver engineer; the other part is the driver framework provided by the kernel developer, which is the second main line we will analyze later.

(2) All the functions in the drivers / GPIO / gpiolib.c file (the previous gpiolib.c is different from this one, and the directory content is different) constitute part 2, that is, the gpiolib framework written by the kernel developer. The functions provided in this file mainly include the following parts:

gpiochip_add:	It is the interface opened by the framework for the manufacturer's driver engineers to register our data with the kernel gpiolib

gpio_request: 	It is the interface opened by the framework for use gpiolib To write their own driver for driver engineers,
				If you want to use one in the driver gpio,You must call gpio_request Interface to the kernel
				gpiolib Some applications can only be used after getting permission gpio. 
				
gpio_free:		corresponding gpio_request,Used to release used up after application gpio

gpio_request_one/gpio_request_array: These two are gpio_request Variant of	

gpiochip_is_requested:	Interface is used to determine a gpio Have you been applied

gpio_direction_input/gpio_direction_output: Used to set GPIO For input/Output mode,
				Note that the function does not actually operate on the hardware, but only through chip Structure variable
				Function pointer called future SoC The real operation hardware implementation written by the manufacturer's driver engineer gpio
				The function set to output mode.
				
The above interfaces belong to one class, which are used to write other drivers gpiolib Used by people
 What's left is another kind of function, which is gpiolib Internal code for the implementation of some of its own functions

For the source code of this piece, please refer to the analysis details by yourself.

12. The attribute section of gpiolib

(1)CONFIG_GPIO_SYSFS (lines drivers/gpio/gpiolib.c194)
This macro determines whether gpio has relevant properties and whether relevant information can be viewed in the / sys/class directory

(2) attribute demo for GPIO

/*
 * /sys/class/gpio/gpiochipN/
 *   /base ... matching gpio_chip.base (N)
 *   /label ... matching gpio_chip.label
 *   /ngpio ... matching gpio_chip.ngpio
 */

static ssize_t chip_base_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
{
	const struct gpio_chip	*chip = dev_get_drvdata(dev);

	return sprintf(buf, "%d\n", chip->base);
}
static DEVICE_ATTR(base, 0444, chip_base_show, NULL);

static ssize_t chip_label_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
{
	const struct gpio_chip	*chip = dev_get_drvdata(dev);

	return sprintf(buf, "%s\n", chip->label ? : "");
}
static DEVICE_ATTR(label, 0444, chip_label_show, NULL);

static ssize_t chip_ngpio_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
{
	const struct gpio_chip	*chip = dev_get_drvdata(dev);

	return sprintf(buf, "%u\n", chip->ngpio);
}
static DEVICE_ATTR(ngpio, 0444, chip_ngpio_show, NULL);

static const struct attribute *gpiochip_attrs[] = {
	&dev_attr_base.attr,
	&dev_attr_label.attr,
	&dev_attr_ngpio.attr,
	NULL,
};
static const struct attribute_group gpiochip_attr_group = {
	.attrs = (struct attribute **) gpiochip_attrs,
};

13. Related code analysis

gpiolib_sysfs_init
	gpiochip_export
		sysfs_create_group

View the corresponding number GPIO Properties of
echo 23 > export  
echo 23 > unexport

7, Using gpiolib to complete led driver

1. Process analysis

(1) Step 1: use gpio_request to apply for a GPIO to be used

(2) Step 2: gpio_direction_input/gpio_direction_output set input / output mode

(3) Step 3: set the output value gpio_set_value and obtain the IO port value gpio_get_value

2. Code practice

(1) Write code on led1 and pass the test

(2) The extension supports led2, led3 and led4. You can register separately or use gpio_request_array to register once

(3) Learn how to view gpio usage in linux
  the kernel provides a virtual file system debugfs, which contains a gpio file that provides gpio usage information.
   usage: mount -t debugfs debugfs /tmp, then cat /tmp/gpio can get all the information of gpio. After use, umount /tmp uninstall debugfs

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>

//Use static mapping to operate io and led
#define GPJ0CON		S5PV210_GPJ0CON
#define GPJ0DAT		S5PV210_GPJ0DAT


#define GPIO_LED1 S5PV210_GPJ0(3)
#define GPIO_LED2 S5PV210_GPJ0(4)
#define GPIO_LED3 S5PV210_GPJ0(5)


#Define x210_led_off 1 / / 210 the positive pole of the LED in the development board is connected to the power supply and the negative pole is connected to GPIO
#Define x210_led_on 0 / / so 1 is off and 0 is on
 
struct led_classdev led1_cdev;//Defines a structure variable used to describe a device of the led device class
struct led_classdev led2_cdev;//Defines a structure variable used to describe a device of the led device class
struct led_classdev led3_cdev;//Defines a structure variable used to describe a device of the led device class

static void s5pv210_led1_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led1_set\n");
	
	//Here, the hardware is operated according to the value set by the user
	if (value == LED_OFF)
	{
		gpio_set_value(GPIO_LED1, x210_LED_OFF);
		
	}
	else
	{
		gpio_set_value(GPIO_LED1, x210_LED_ON);
		
	}

}


static void s5pv210_led2_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led2_set\n");
	writel(0x11111111, GPJ0CON);
	
	//Here, the hardware is operated according to the value set by the user
	if (value == LED_OFF)
	{
		gpio_set_value(GPIO_LED2, x210_LED_OFF);
		
	}
	else
	{
		gpio_set_value(GPIO_LED2, x210_LED_ON);
		
	}

}


static void s5pv210_led3_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led3_set\n");
	printk(KERN_INFO "s5pv210_led3_set\n");
	writel(0x11111111, GPJ0CON);
	
	//Here, the hardware is operated according to the value set by the user
	if (value == LED_OFF)
	{
		gpio_set_value(GPIO_LED3, x210_LED_OFF);
		
	}
	else
	{
		gpio_set_value(GPIO_LED3, x210_LED_ON);
		
	}

}


//This function will be called when insmod installs the driver. The main task of this function is to use it
//led driver framework provides a device registration function to register a device
static int __init s5pv210_led_init(void)
{
	int ret = -1;
	
	
	//Here you can apply for various resources used by the driver. The current driver is GPIO resources
	if (gpio_request(GPIO_LED1, "led1_gpj0.3"))
	{
		printk(KERN_INFO "gpio_request failed.\n");
		
	}
	else
	{
		//Set to output mode, and the default output 1 turns off the led
		gpio_direction_output(GPIO_LED1, 1);
	}


	if (gpio_request(GPIO_LED2, "led1_gpj0.4"))
	{
		printk(KERN_INFO "gpio_request failed.\n");
		
	}
	else
	{
		//Set to output mode, and the default output 1 turns off the led
		gpio_direction_output(GPIO_LED2, 1);
	}

	if (gpio_request(GPIO_LED3, "led1_gpj0.5"))
	{
		printk(KERN_INFO "gpio_request failed.\n");
		
	}
	else
	{
		//Set to output mode, and the default output 1 turns off the led
		gpio_direction_output(GPIO_LED3, 1);
	} 
	
	//Fill in structural variables that describe the properties of led devices
	led1_cdev.name = "led1";
	led1_cdev.brightness = 255;
	led1_cdev.brightness_set = s5pv210_led1_set;
	
	ret = led_classdev_register(NULL, &led1_cdev);//It comes from led-class.c and creates a class that belongs to LED
												 //A device
	if (ret < 0) {								
		printk(KERN_INFO "led_classdev_register failed\n");
		return ret;
	}
	
	//Fill in structural variables that describe the properties of led devices
	led2_cdev.name = "led2";
	led2_cdev.brightness = 255;
	led2_cdev.brightness_set = s5pv210_led2_set;
	
	ret = led_classdev_register(NULL, &led2_cdev);//It comes from led-class.c and creates a class that belongs to LED
												 //A device
	if (ret < 0) {								
		printk(KERN_INFO "led_classdev_register failed\n");
		return ret;
	}	
	
	//Fill in structural variables that describe the properties of led devices
	led3_cdev.name = "led3";
	led3_cdev.brightness = 255;
	led3_cdev.brightness_set = s5pv210_led3_set;
	
	ret = led_classdev_register(NULL, &led3_cdev);//It comes from led-class.c and creates a class that belongs to LED
												 //A device
	if (ret < 0) {								
		printk(KERN_INFO "led_classdev_register failed\n");
		return ret;
	}	

	return 0;
}

static void __exit s5pv210_led_exit(void)
{
	led_classdev_unregister(&led1_cdev);//It comes from led-class.c, and belongs to LED class
									   //A device
	led_classdev_unregister(&led2_cdev);			
	led_classdev_unregister(&led3_cdev);

	gpio_free(GPIO_LED1);
	gpio_free(GPIO_LED2);	
	gpio_free(GPIO_LED3);
}

module_init(s5pv210_led_init); //The function is placed in the. initcall6.init section
module_exit(s5pv210_led_exit); //The function is placed in the specified segments, which are related to the order of execution

/********************MODULE_xxx This macro is used to add module description information*********************/

MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");	//Describe the author of the module
MODULE_DESCRIPTION("s5pv210 LED driver");		//Describe the introduction of the module
MODULE_LICENSE("GPL");         					//Describe the license for the module
MODULE_ALIAS("s5pv210_led");  				 	//Describes the alias information for the module

8, Add driver to kernel

1. Existence form of driving

(1) Wild, the advantage is to facilitate debugging and development, so it is the same in the development stage

(2) The advantage of home support is that make menuconfig can decide how to compile the kernel during kernel configuration to facilitate integration

2. General steps to drive development

(1) Write and debug externally in the form of modules
(2) Integrate the debugged driver code into the kernel

3. Practice

(1) Key points: Kconfig, Makefile, make menuconfig
(2) Operation steps:

Step 1: put the written driver source file into the correct directory in the kernel source code
 Step 2: in Makefile Add corresponding dependencies in(led platform )

obj-$(CONFIG_LEDS_S5PV210)              += led-s5pv210.o

Step 3: in Kconfig Add corresponding configuration items in
config LEDS_S5PV210
         tristate "LED Support for Marvell S5PV210(X210))"
         help
            This option enables support for on-board LED drivers on X210

Step 4: make menuconfig Add configuration item
//led-s5pv210.c
#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>


//Use static mapping to operate io and led
#define GPJ0CON		S5PV210_GPJ0CON
#define GPJ0DAT		S5PV210_GPJ0DAT

 
struct led_classdev led1_cdev;//Defines a structure variable used to describe a device of the led device class
struct led_classdev led2_cdev;//Defines a structure variable used to describe a device of the led device class
struct led_classdev led3_cdev;//Defines a structure variable used to describe a device of the led device class

static void s5pv210_led1_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led1_set\n");
	writel(0x11111111, GPJ0CON);
	
	//Here, the hardware is operated according to the value set by the user
	if (value == LED_OFF)
	{
		//Read and rewrite operation, which not only controls led1, but also does not affect other LEDs
		writel((readl(GPJ0DAT) | (1 << 3)), GPJ0DAT);//led1 off
		
	}
	else
	{
		writel((readl(GPJ0DAT) & ~(1 << 3)), GPJ0DAT);//led1 off
		
	}

}


static void s5pv210_led2_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led2_set\n");
	writel(0x11111111, GPJ0CON);
	
	//Here, the hardware is operated according to the value set by the user
	if (value == LED_OFF)
	{
		//Read and rewrite operation, which not only controls led1, but also does not affect other LEDs
		writel((readl(GPJ0DAT) | (1 << 4)), GPJ0DAT);//led1 off
		
	}
	else
	{
		writel((readl(GPJ0DAT) & ~(1 << 4)), GPJ0DAT);//led1 off
		
	}

}


static void s5pv210_led3_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led3_set\n");
	printk(KERN_INFO "s5pv210_led3_set\n");
	writel(0x11111111, GPJ0CON);
	
	//Here, the hardware is operated according to the value set by the user
	if (value == LED_OFF)
	{
		//Read and rewrite operation, which not only controls led1, but also does not affect other LEDs
		writel((readl(GPJ0DAT) | (1 << 5)), GPJ0DAT);//led1 off
		
	}
	else
	{
		writel((readl(GPJ0DAT) & ~(1 << 5)), GPJ0DAT);//led1 off
		
	}

}


//This function will be called when insmod installs the driver. The main task of this function is to use it
//led driver framework provides a device registration function to register a device
static int __init s5pv210_led_init(void)
{
	int ret = -1;
	
	//Fill in structural variables that describe the properties of led devices
	led1_cdev.name = "led1";
	led1_cdev.brightness = 255;
	led1_cdev.brightness_set = s5pv210_led1_set;
	
	ret = led_classdev_register(NULL, &led1_cdev);//It comes from led-class.c and creates a class that belongs to LED
												 //A device
	if (ret < 0) {								
		printk(KERN_INFO "led_classdev_register failed\n");
		return ret;
	}
	
	//Fill in structural variables that describe the properties of led devices
	led2_cdev.name = "led2";
	led2_cdev.brightness = 255;
	led2_cdev.brightness_set = s5pv210_led2_set;
	
	ret = led_classdev_register(NULL, &led2_cdev);//It comes from led-class.c and creates a class that belongs to LED
												 //A device
	if (ret < 0) {								
		printk(KERN_INFO "led_classdev_register failed\n");
		return ret;
	}	
	
	//Fill in structural variables that describe the properties of led devices
	led3_cdev.name = "led3";
	led3_cdev.brightness = 255;
	led3_cdev.brightness_set = s5pv210_led3_set;
	
	ret = led_classdev_register(NULL, &led3_cdev);//It comes from led-class.c and creates a class that belongs to LED
												 //A device
	if (ret < 0) {								
		printk(KERN_INFO "led_classdev_register failed\n");
		return ret;
	}	

	return 0;
}

static void __exit s5pv210_led_exit(void)
{
	led_classdev_unregister(&led1_cdev);//It comes from led-class.c, and belongs to LED class
									   //A device
	led_classdev_unregister(&led2_cdev);			
	led_classdev_unregister(&led3_cdev);			
}

module_init(s5pv210_led_init); //The function is placed in the. initcall6.init section
module_exit(s5pv210_led_exit); //The function is placed in the specified segments, which are related to the order of execution

/********************MODULE_xxx This macro is used to add module description information*********************/

MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");	//Describe the author of the module
MODULE_DESCRIPTION("s5pv210 LED driver");		//Describe the introduction of the module
MODULE_LICENSE("GPL");         					//Describe the license for the module
MODULE_ALIAS("s5pv210_led");  				 	//Describes the alias information for the module
Important directory and file structure:
mach-s5pv210/gpiolib.c 		s5pv210_gpiolib_init
mach-s5pv210/include/mach/gpio.h			#define S5PV210_GPA0(_nr)	(S5PV210_GPIO_A0_START + (_nr))
arch/arm/plat-samsung/gpiolib.c		Inside is 210/6410 This 4 bit CON Operation method of register type
arch/arm/plat-samsung/gpio.c		Inside is 24 XX This 2 bit CON Operation method of register type

drivers/gpio/gpiolib.c				It is provided by kernel developers gpiolib Drive frame part of

4. The difference between / dev, / sys/devices, and / sys/dev

(1) / dev, the storage directory of device files. Applications can access the actual device through reading, writing and controlling these files;

(2) The / sys/devices directory is organized into a hierarchical structure according to the bus type to which the device is attached, and all the devices of the system are saved; It is the most important directory structure of file system management equipment;

   this is the directory structure in which the kernel devices are placed hierarchically according to the bus type. All devices in devices are connected under a certain bus. The symbolic link of each specific device can be found under each specific bus here. It is also a part of the Linux unified device model;

(3) There are two subdirectories under / sys / dev, block and char, which store the primary and secondary numbers of block devices and character devices in the form of (major:minor), which points to the devices under / sys/devices directory.

(4) The mount point of sysfs is the / sys directory. Sysfs is a virtual file system (and other virtual file systems, such as usbfs and procfs). Sysfs exports the data structure of the kernel.

(5)/sys/dev / and / sys/devices are organized by sysfs according to the idea of object-oriented management. Sysfs is mainly used to describe the device driver model in Linux kernel 2.6. The user state background program will dynamically and periodically scan the attribute items in the / sys directory to automatically manage the device files (also known as device nodes), so that the corresponding device files will be established or deleted in the / dev directory.

Note: most of this material is compiled from the course notes of the Internet of things lecture hall of Mr. Zhu, and some other people's blogs are quoted. If there is infringement, please contact and delete it! The level is limited. If there are errors, you are welcome to communicate in the comment area.

Keywords: C Linux IoT Linux Driver ARM

Added by Spitfire on Wed, 15 Sep 2021 01:29:51 +0300