mirror of
https://github.com/sysprog21/lkmpg.git
synced 2025-04-23 21:14:04 +08:00
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:
parent
3cb12d65a5
commit
0beffd5b70
@ -3,3 +3,4 @@ bh_threaded
|
||||
intrpt
|
||||
vkbd
|
||||
syscall-steal
|
||||
led
|
||||
|
@ -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
193
examples/led.c
Normal 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");
|
59
lkmpg.tex
59
lkmpg.tex
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user