Hand in hand to teach you to interrupt the wake-up system

Posted by DigitalDesign on Wed, 29 Dec 2021 21:48:44 +0100

In consumer electronics, power consumption is very important. Even in the later stage of the project, power consumption has been adjusted to see where power can be saved. Therefore, there is a Linux power management subsystem, which includes many aspects: when to drop the frame, when to turn off other CPU core s, if a peripheral is rarely used during system operation, it needs to sleep during operation, and which peripherals should be guaranteed to wake up the system during system sleep.

What bloggers want to discuss today is how a button wakes up the system, which is similar to the power button of a mobile phone.

This function is not a new function, so there is a demo inside Linux that can be used. First teach you how to use the demo, and then compare how to write an interrupt wake-up system driver.

Official demo

demo Directory: / kernel4 14/drivers/input/keyboard/gpio_ keys. c

The driver is specially prepared for keys. It is a veteran driver. It can be used to test key interruption or interrupt wake-up system at any time. It is often more reliable than the driver written by yourself.

To use this driver, first add the following in the Makefile of this directory:

obj-y  += gpio_keys.o

Add in the device tree:

gpio-keys {
  compatible = "gpio-keys";
  #address-cells = <1>;
  #size-cells = <0>;
  autorepeat;
  key0 {
   label = "GPIO Key Enter";
   linux,code = <KEY_ENTER>;
   gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
   gpio-key,wakeup;
  };
};

The compatible attribute is "GPIO keys", GPIO_ keys. The 674 line of the C file will match this attribute, and the driver will run when it is matched.

In Linux, the code attribute is the key value. Linux has a number for all key events, so KEY_ENTER is actually a number, which is a key value reported by the driver to the upper layer.

The gpios attribute indicates which GPIO port is triggered at low level. You can choose a GPIO by yourself.

GPIO key, wakeup means that this GPIO supports interrupt wake-up. You can also write: wakeup source. Just old and new versions.

The modification is so simple, but the syntax should conform to the development board platform in your hands. Then compile the kernel and device tree files and download them to the board. (there will be a. Config file in the root directory of the Linux kernel. Ensure that CONFIG_PM_SLEEP=y is turned on)

If the driver is loaded successfully, you can see in / proc/interrupts:

The first column from left to right is the software interrupt number (unique).

The second column is the CPU, indicating how many times the interrupt is triggered on the CPU, and there will be multiple columns for multi-core.

The third column is the interrupt controller, imx6ull development board root interrupt controller is GPC, external interrupt controller is GPIO MXC, and the two are cascaded.

The fourth column is the hardware interrupt number, that is, the GPIO port number.

The fifth column indicates whether the interrupt is edge triggered or level triggered.

The sixth column is the interrupt name. You can find a GPIO Key Enter. If the driver is loaded successfully, you can see it. If it fails, you can't see it.

Verification method

In the kernel, there are many sleep modes, which can be viewed through the following command

# cat /sys/power/state

The commonly used sleep modes are freeze, standby, mem and disk

Freeze: freeze I/O devices and put them in the low-power state to make the processor enter the idle state. It wakes up the fastest and consumes more power than other standby, MEM and disk modes

standby: in addition to freezing I/O devices, it also pauses the system, wakes up faster and consumes more power than other mem and disk modes

mem: save the operation status data to the memory, turn off the peripherals and enter the waiting mode. The wake-up is slow and the power consumption is higher than that of disk mode

Disk: save the operation status data to the hard disk, then shut down and wake up the slowest

Example:

 # echo mem > /sys/power/state

After the system goes to sleep, the UI and serial port will be stopped basically, and the serial port cannot be operated, as shown in the figure:

Press the key to restore the system:

Of course, the log here is not complete. Enter dmesg to see the complete log:

PM: power manager

What has been done is explained in the figure, which is divided into suspend process and resume process.

In fact, an interrupt allows it to support the wake-up system. The main reason is that there are two more functions: suspend and resume.

The suspend function calls the registered suspend of every peripheral when the whole suspend is in the system. We call enable_ in this function. irq_ Wake, indicating that the interrupt is in the enable state when the system sleeps.

The resume function calls the resume function registered by each peripheral when the whole resume is in the system, and calls disable_ in the resume function. irq_ Wake, indicating that the interrupt is not required when the system is running. The two are used in pairs.

See the following article for details, which is well written:

http://www.wowotech.net/irq_subsystem/irq_handle_procedure.html

You can also study gpio_keys.c. The driver looks complex, but it's perfect. After all, it's experienced and takes into account all factors. Use it for testing!

demo written by Blogger

The blogger below gives a simplified version, and the self-test is OK. Share it with you. You can copy it if necessary in the future

xxx.c

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <linux/cdev.h>

static int gpionum = 0;
static int irqnum = 0;

static irqreturn_t my_handler(int irq, void *dev_id)
{
 printk("%s\r\n",__FUNCTION__);
 return IRQ_HANDLED;
}

static int gpio_keys_probe(struct platform_device *pdev)
{
 int ret = 0;
 struct device_node *node = NULL;; /* Device node*/
 
 node = of_find_compatible_node(NULL,NULL,"atkalpha-key");
 if (node == NULL){
  printk("%s:atkalpha-key node not find!\r\n",__FUNCTION__);
  return -EINVAL;
 }
 
 /* Extract GPIO */
 gpionum = of_get_named_gpio(node,"key-gpio", 0);
 if (gpionum < 0) {
   printk("of_get_named_gpio can't get key\r\n");
 }
 
 /* Initialize the IO used by the key and set it to interrupt mode */
 gpio_request(gpionum, "key-gpio");
 gpio_direction_input(gpionum); 
 
 irqnum = gpio_to_irq(gpionum);
 
 ret = request_irq(irqnum,my_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "my-key", NULL);
 if(ret < 0){
  printk("irq %d request failed!\r\n", irqnum);
  return -EFAULT;
 }
 return 0;
}


static const struct of_device_id gpio_keys_of_match[] = {
 { .compatible = "atkalpha-key", },
 { },
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);

static int gpio_keys_remove(struct platform_device *pdev)
{
 return 0;
}

static int gpio_keys_suspend(struct device *dev)
{
 printk("%s\r\n",__FUNCTION__);
 enable_irq_wake(irqnum);
 return 0;
}

static int gpio_keys_resume(struct device *dev)
{
 printk("%s\r\n",__FUNCTION__);
 disable_irq_wake(irqnum);
 return 0;
}

static SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend, gpio_keys_resume);

static struct platform_driver gpio_keys_device_driver = {
 .probe  = gpio_keys_probe,
 .remove  = gpio_keys_remove,
 .driver  = {
  .name = "my-key",
  .pm = &gpio_keys_pm_ops,
  .of_match_table = of_match_ptr(gpio_keys_of_match),
 }
};

static int __init gpio_keys_init(void)
{
 return platform_driver_register(&gpio_keys_device_driver);
}

static void __exit gpio_keys_exit(void)
{
 platform_driver_unregister(&gpio_keys_device_driver);
}

module_init(gpio_keys_init);
module_exit(gpio_keys_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jason");
MODULE_DESCRIPTION("Keyboard driver for GPIOs");
MODULE_ALIAS("platform:gpio-keys");

xxx.dts

key {
  #address-cells = <1>;
  #size-cells = <1>;
  compatible = "atkalpha-key";
  key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
  interrupt-parent = <&gpio1>;
  interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
  gpio-key,wakeup;
  status = "okay";
};

Finally, let's sum up: the difference between interrupt wake-up system and ordinary driver is that there are two functions: suspend and resume, calling enable_ in suspend function. irq_ Wake, indicating that the interrupt number is also in the enable state when the system sleeps, and the interrupt can be triggered. In the resume function, call disable_. irq_ Wake, restore the original interrupt trigger path.

Then use simple_ DEV_ PM_ The OPS macro registers the suspend and resume functions with GPIO_ keys_ pm_ The OPS operation set is finally registered in the system by the platform. After this, the suspend function registered by the device will be called during system sleep, and the resume function registered by the device will be called during system wake-up.

As for the writing of probe functions, I have talked about the use of these functions in a series of articles on GPIO subsystem and interrupt subsystem. You can check them on my website:

http://www.linuxer.vip

note: This demo is only used to wake up the system. If your interrupt is in I2C and other device drivers, I2C communication should be carried out in the interrupt processing function immediately after waking up the system. The writing method is different, but the framework is the same.

In addition, nothing is done in the interrupt processing function of the driver, so it will sleep after waking up and executing the interrupt processing function. If you want the interrupt to wake up the system and keep the system awake, please use it in the interrupt handler function__ pm_ stay_ Wake () and__ pm_relax() function.