Skip to content

Commit

Permalink
Add an LED driver example using GPIO
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jeremy90307 committed Dec 26, 2024
1 parent 3cb12d6 commit 998113c
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 0 deletions.
1 change: 1 addition & 0 deletions .ci/non-working
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ bh_threaded
intrpt
vkbd
syscall-steal
led
1 change: 1 addition & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
203 changes: 203 additions & 0 deletions examples/led.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/*
* 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 SUCCESS 0
#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;
int 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)
{
pr_info("device_open(%p)\n", file);

return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file)
{
pr_info("device_release(%p,%p)\n", inode, file);

return SUCCESS;
}

/* called when somebody tries to write into our device file. */
static ssize_t device_write(struct file *file, const char __user *buffer,
size_t length, loff_t *offset)
{
pr_info("device_write(%p,%p,%ld)", file, buffer, length);

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("%s failed with %d\n",
"Sorry, registering the character device ", 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");
50 changes: 50 additions & 0 deletions lkmpg.tex
Original file line number Diff line number Diff line change
Expand Up @@ -1816,6 +1816,56 @@ \subsection{Flashing keyboard LEDs}
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 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.

\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 \cpp|input with 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 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$\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.
Expand Down

0 comments on commit 998113c

Please sign in to comment.