Add an LED driver example using GPIO

Use GPIO to control LED on/off and add related GPIO knowledge.

Test detail:

- Tested on Raspberry Pi 5B with Raspberry Pi OS (Debian 12, Linux
  version 6.12.1-v8-16k+)

- Verify that LED example compiles and loads successfully

- Verify that LED turns on and off sucessfully
This commit is contained in:
Jeremy90307 2024-12-19 15:36:54 +08:00 committed by jeremy90307
parent 3cb12d65a5
commit 0beffd5b70
4 changed files with 254 additions and 0 deletions

View File

@ -3,3 +3,4 @@ bh_threaded
intrpt
vkbd
syscall-steal
led

View File

@ -31,6 +31,7 @@ obj-m += ioctl.o
obj-m += vinput.o
obj-m += vkbd.o
obj-m += static_key.o
obj-m += led.o
PWD := $(CURDIR)

193
examples/led.c Normal file
View File

@ -0,0 +1,193 @@
/*
* led.c - Using GPIO to control the LED on/off
*/
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include <asm/errno.h>
#define DEVICE_NAME "gpio_led"
#define DEVICE_CNT 1
#define BUF_LEN 2
static char control_signal[BUF_LEN];
static unsigned long device_buffer_size = 0;
struct LED_dev {
dev_t dev_num;
int major_num, minor_num;
struct cdev cdev;
struct class *cls;
struct device *dev;
};
static struct LED_dev led_device;
/* Define GPIOs for LEDs.
* TODO: According to the requirements, search /sys/kernel/debug/gpio to
* find the corresponding GPIO location.
*/
static struct gpio leds[] = { { 4, GPIOF_OUT_INIT_LOW, "LED 1" } };
/* This is called whenever a process attempts to open the device file */
static int device_open(struct inode *inode, struct file *file)
{
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t device_write(struct file *file, const char __user *buffer,
size_t length, loff_t *offset)
{
device_buffer_size = min(BUF_LEN, length);
if (copy_from_user(control_signal, buffer, device_buffer_size)) {
return -EFAULT;
}
/* Determine the received signal to decide the LED on/off state. */
switch (control_signal[0]) {
case '0':
gpio_set_value(leds[0].gpio, 0);
pr_info("LED OFF");
break;
case '1':
gpio_set_value(leds[0].gpio, 1);
pr_info("LED ON");
break;
default:
pr_warn("Invalid value!\n");
break;
}
*offset += device_buffer_size;
/* Again, return the number of input characters used. */
return device_buffer_size;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.write = device_write,
.open = device_open,
.release = device_release,
};
/* Initialize the module - Register the character device */
static int __init led_init(void)
{
int ret = 0;
/* Determine whether dynamic allocation of the device number is needed. */
if (led_device.major_num) {
led_device.dev_num = MKDEV(led_device.major_num, led_device.minor_num);
ret =
register_chrdev_region(led_device.dev_num, DEVICE_CNT, DEVICE_NAME);
} else {
ret = alloc_chrdev_region(&led_device.dev_num, 0, DEVICE_CNT,
DEVICE_NAME);
}
/* Negative values signify an error */
if (ret < 0) {
pr_alert("Failed to register character device, error: %d\n", ret);
return ret;
}
pr_info("Major = %d, Minor = %d\n", MAJOR(led_device.dev_num),
MINOR(led_device.dev_num));
/* Prevents module unloading while operations are in use */
led_device.cdev.owner = THIS_MODULE;
cdev_init(&led_device.cdev, &fops);
ret = cdev_add(&led_device.cdev, led_device.dev_num, 1);
if (ret) {
pr_err("Failed to add the device to the system\n");
goto fail1;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
led_device.cls = class_create(DEVICE_NAME);
#else
led_device.cls = class_create(THIS_MODULE, DEVICE_NAME);
#endif
if (IS_ERR(led_device.cls)) {
pr_err("Failed to create class for device\n");
ret = PTR_ERR(led_device.cls);
goto fail2;
}
led_device.dev = device_create(led_device.cls, NULL, led_device.dev_num,
NULL, DEVICE_NAME);
if (IS_ERR(led_device.dev)) {
pr_err("Failed to create the device file\n");
ret = PTR_ERR(led_device.dev);
goto fail3;
}
pr_info("Device created on /dev/%s\n", DEVICE_NAME);
ret = gpio_request(leds[0].gpio, leds[0].label);
if (ret) {
pr_err("Unable to request GPIOs for LEDs: %d\n", ret);
goto fail4;
}
ret = gpio_direction_output(leds[0].gpio, leds[0].flags);
if (ret) {
pr_err("Failed to set GPIO %d direction\n", leds[0].gpio);
goto fail5;
}
return 0;
fail5:
gpio_free(leds[0].gpio);
fail4:
device_destroy(led_device.cls, led_device.dev_num);
fail3:
class_destroy(led_device.cls);
fail2:
cdev_del(&led_device.cdev);
fail1:
unregister_chrdev_region(led_device.dev_num, DEVICE_CNT);
return ret;
}
static void __exit led_exit(void)
{
gpio_set_value(leds[0].gpio, 0);
gpio_free(leds[0].gpio);
device_destroy(led_device.cls, led_device.dev_num);
class_destroy(led_device.cls);
cdev_del(&led_device.cdev);
unregister_chrdev_region(led_device.dev_num, DEVICE_CNT);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

View File

@ -1816,6 +1816,65 @@ While you have seen lots of stuff that can be used to aid debugging here, there
Adding debug code can change the situation enough to make the bug seem to disappear.
Thus, you should keep debug code to a minimum and make sure it does not show up in production code.
\section{GPIO}
\label{sec:gpio}
\subsection{GPIO}
\label{sec:gpio_introduction}
General Purpose Input/Output (GPIO) appears on the development board as pins.
It acts as a bridge for communication between the development board and external devices.
You can think of it like a switch: users can turn it on or off (Input), and the development board can also turn it on or off (Output).
To implement a GPIO device driver, you use the \cpp|gpio_request()| function to enable a specific GPIO pin.
After successfully enabling it, you can check that the pin is being used by looking at /sys/kernel/debug/gpio.
\begin{codebash}
cat /sys/kernel/debug/gpio
\end{codebash}
There are other ways to register GPIOs.
For example, you can use \cpp|gpio_request_one()| to register a GPIO while setting its direction (input or output) and initial state at the same time.
You can also use \cpp|gpio_request_array()| to register multiple GPIOs at once. However, note that \cpp|gpio_request_array()| has been removed since Linux v6.10+.
When using GPIO, you must set it as either output with \cpp|gpio_direction_output()| or input with \cpp|gpio_direction_input()|.
\begin{itemize}
\item when the GPIO is set as output, you can use \cpp|gpio_set_value()| to choose to set it to high voltage or low voltage.
\item when the GPIO is set as input, you can use \cpp|gpio_get_value()| to read whether the voltage is high or low.
\end{itemize}
\subsection{Control the LED's on/off state}
\label{sec:gpio_led}
In Section \ref{sec:device_files}, we learned how to communicate with device files.
Therefore, we will further use device files to control the LED on and off.
In the implementation, a pull-down resistor is used.
The anode of the LED is connected to GPIO4, and the cathode is connected to GND.
For more details about the Raspberry Pi pin assignments, refer to \href{https://pinout.xyz/}{Raspberry Pi Pinout}.
The materials used include a Raspberry Pi 5, an LED, jumper wires, and a 220$\Omega$ resistor.
\samplec{examples/led.c}
Make and install the module:
\begin{codebash}
make
sudo insmod led.ko
\end{codebash}
Switch on the LED:
\begin{codebash}
echo "1" | sudo tee /dev/gpio_led
\end{codebash}
Switch off the LED:
\begin{codebash}
echo "0" | sudo tee /dev/gpio_led
\end{codebash}
Finally, remove the module:
\begin{codebash}
sudo rmmod led
\end{codebash}
\section{Scheduling Tasks}
\label{sec:scheduling_tasks}
There are two main ways of running tasks: tasklets and work queues.