Skip to content

Commit 7b0e47a

Browse files
committed
Add an LED driver example using GPIO
Add an LED driver example using GPIO and add related 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
1 parent 3cb12d6 commit 7b0e47a

File tree

4 files changed

+232
-0
lines changed

4 files changed

+232
-0
lines changed

.ci/non-working

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ bh_threaded
33
intrpt
44
vkbd
55
syscall-steal
6+
led

examples/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ obj-m += ioctl.o
3131
obj-m += vinput.o
3232
obj-m += vkbd.o
3333
obj-m += static_key.o
34+
obj-m += led.o
3435

3536
PWD := $(CURDIR)
3637

examples/led.c

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* led.c - Using GPIO to control the LED on/off
3+
*/
4+
5+
#include <linux/cdev.h>
6+
#include <linux/delay.h>
7+
#include <linux/device.h>
8+
#include <linux/fs.h>
9+
#include <linux/gpio.h>
10+
#include <linux/init.h>
11+
#include <linux/module.h>
12+
#include <linux/printk.h>
13+
#include <linux/types.h>
14+
#include <linux/uaccess.h>
15+
#include <linux/version.h>
16+
17+
#include <asm/errno.h>
18+
19+
#define SUCCESS 0
20+
#define DEVICE_NAME "gpio_led"
21+
22+
/* The major device number. We can not rely on dynamic registration
23+
* any more.
24+
*/
25+
#define MAJOR_NUM 100
26+
#define BUF_LEN 2
27+
28+
static char control_signal[BUF_LEN];
29+
static unsigned long device_buffer_size = 0;
30+
31+
static struct class *cls;
32+
static struct device *dev;
33+
34+
/* Define GPIOs for LEDs.
35+
* TODO: Change the numbers for the GPIO on your board.
36+
*/
37+
static struct gpio leds[] = { { 4, GPIOF_OUT_INIT_LOW, "LED 1" } };
38+
39+
/* This is called whenever a process attempts to open the device file */
40+
static int device_open(struct inode *inode, struct file *file)
41+
{
42+
pr_info("device_open(%p)\n", file);
43+
44+
try_module_get(THIS_MODULE);
45+
return SUCCESS;
46+
}
47+
48+
static int device_release(struct inode *inode, struct file *file)
49+
{
50+
pr_info("device_release(%p,%p)\n", inode, file);
51+
52+
module_put(THIS_MODULE);
53+
return SUCCESS;
54+
}
55+
56+
/* called when somebody tries to write into our device file. */
57+
static ssize_t device_write(struct file *file, const char __user *buffer,
58+
size_t length, loff_t *offset)
59+
{
60+
pr_info("device_write(%p,%p,%ld)", file, buffer, length);
61+
62+
device_buffer_size = min(BUF_LEN, length);
63+
64+
if (copy_from_user(control_signal, buffer, device_buffer_size)) {
65+
return -EFAULT;
66+
}
67+
68+
/* Determine the received signal to decide the LED on/off state. */
69+
switch (control_signal[0]) {
70+
case '0':
71+
gpio_set_value(leds[0].gpio, 0);
72+
pr_info("LED OFF");
73+
break;
74+
case '1':
75+
gpio_set_value(leds[0].gpio, 1);
76+
pr_info("LED ON");
77+
break;
78+
default:
79+
pr_warn("Invalid value!\n");
80+
break;
81+
}
82+
83+
*offset += device_buffer_size;
84+
85+
/* Again, return the number of input characters used. */
86+
return device_buffer_size;
87+
}
88+
89+
static struct file_operations fops = {
90+
.write = device_write,
91+
.open = device_open,
92+
.release = device_release, /* a.k.a. close */
93+
};
94+
95+
/* Initialize the module - Register the character device */
96+
static int __init led_init(void)
97+
{
98+
int ret = 0;
99+
100+
/* Register the character device (at least try) */
101+
ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops);
102+
103+
/* Negative values signify an error */
104+
if (ret < 0) {
105+
pr_alert("%s failed with %d\n",
106+
"Sorry, registering the character device ", ret);
107+
return ret;
108+
}
109+
110+
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
111+
cls = class_create(DEVICE_NAME);
112+
#else
113+
cls = class_create(THIS_MODULE, DEVICE_NAME);
114+
#endif
115+
if (IS_ERR(cls)) {
116+
pr_err("Failed to create class for device\n");
117+
ret = PTR_ERR(cls);
118+
goto fail1;
119+
}
120+
121+
dev = device_create(cls, NULL, MKDEV(MAJOR_NUM, 0), NULL, DEVICE_NAME);
122+
if (IS_ERR(dev)) {
123+
pr_err("Failed to create the device file\n");
124+
ret = PTR_ERR(dev);
125+
goto fail2;
126+
}
127+
128+
pr_info("Device created on /dev/%s\n", DEVICE_NAME);
129+
130+
ret = gpio_request(leds[0].gpio, leds[0].label);
131+
132+
if (ret) {
133+
pr_err("Unable to request GPIOs for LEDs: %d\n", ret);
134+
goto fail3;
135+
}
136+
137+
ret = gpio_direction_output(leds[0].gpio, leds[0].flags);
138+
139+
if (ret) {
140+
pr_err("Failed to set GPIO %d direction\n", leds[0].gpio);
141+
goto fail4;
142+
}
143+
144+
return 0;
145+
146+
fail4:
147+
gpio_free(leds[0].gpio);
148+
149+
fail3:
150+
device_destroy(cls, MKDEV(MAJOR_NUM, 0));
151+
152+
fail2:
153+
class_destroy(cls);
154+
155+
fail1:
156+
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
157+
158+
return ret;
159+
}
160+
161+
/* Cleanup - unregister the appropriate file from /proc */
162+
static void __exit led_exit(void)
163+
{
164+
gpio_set_value(leds[0].gpio, 0);
165+
gpio_free(leds[0].gpio);
166+
167+
device_destroy(cls, MKDEV(MAJOR_NUM, 0));
168+
class_destroy(cls);
169+
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
170+
}
171+
172+
module_init(led_init);
173+
module_exit(led_exit);
174+
175+
MODULE_LICENSE("GPL");

lkmpg.tex

+55
Original file line numberDiff line numberDiff line change
@@ -1816,6 +1816,61 @@ \subsection{Flashing keyboard LEDs}
18161816
Adding debug code can change the situation enough to make the bug seem to disappear.
18171817
Thus, you should keep debug code to a minimum and make sure it does not show up in production code.
18181818

1819+
\section{GPIO}
1820+
\label{sec:gpio}
1821+
\subsection{GPIO}
1822+
\label{sec:gpio_introduction}
1823+
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).
1824+
1825+
To implement GPIO, 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.
1826+
1827+
\begin{codebash}
1828+
cat /sys/kernel/debug/gpio
1829+
\end{codebash}
1830+
1831+
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 in Linux v6.10+.
1832+
1833+
When using GPIO, you must set it as either output with \cpp|gpio_direction_output()| or \cpp|input with gpio_direction_input()|.
1834+
1835+
\begin{itemize}
1836+
\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.
1837+
\item when the GPIO is set as input, you can use \cpp|gpio_get_value()| to read whether the voltage is high or low.
1838+
\end{itemize}
1839+
1840+
\subsection{Control the LED's on/off state}
1841+
\label{sec:gpio_led}
1842+
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.
1843+
1844+
In the implementation, a pull-down resistor is used. The positive electrode of the LED is connected to GPIO4, and the negative electrode is connected to GND. The materials used include a Raspberry Pi 5, an LED, single-core wires, and a 220Ω resistor.
1845+
1846+
\samplec{examples/led.c}
1847+
1848+
Make and install the module:
1849+
\begin{codebash}
1850+
make
1851+
sudo insmod led.ko
1852+
\end{codebash}
1853+
1854+
Changes the permissions of a file:
1855+
\begin{codebash}
1856+
sudo chmod 666 > /dev/gpio_led
1857+
\end{codebash}
1858+
1859+
Switch on the LED:
1860+
\begin{codebash}
1861+
echo '1' > /dev/gpio_led
1862+
\end{codebash}
1863+
1864+
Switch off the LED:
1865+
\begin{codebash}
1866+
echo '0' > /dev/gpio_led
1867+
\end{codebash}
1868+
1869+
Finally, remove the test module:
1870+
\begin{codebash}
1871+
sudo rmmod led
1872+
\end{codebash}
1873+
18191874
\section{Scheduling Tasks}
18201875
\label{sec:scheduling_tasks}
18211876
There are two main ways of running tasks: tasklets and work queues.

0 commit comments

Comments
 (0)