Skip to content

Commit

Permalink
Add an LED driver example using GPIO
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jeremy90307 committed Dec 18, 2024
1 parent 3cb12d6 commit a64cd81
Show file tree
Hide file tree
Showing 4 changed files with 233 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
175 changes: 175 additions & 0 deletions examples/led.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* 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"

/* The major device number. We can not rely on dynamic registration
* any more.
*/
#define MAJOR_NUM 100
#define BUF_LEN 2

static char control_signal[BUF_LEN];
static unsigned long device_buffer_size = 0;

static struct class *cls;
static struct device *dev;

/* Define GPIOs for LEDs.
* TODO: Change the numbers for the GPIO on your board.
*/
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);

try_module_get(THIS_MODULE);
return SUCCESS;
}

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

module_put(THIS_MODULE);
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:
printk("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 = {
.write = device_write,
.open = device_open,
.release = device_release, /* a.k.a. close */
};

/* Initialize the module - Register the character device */
static int __init led_init(void)
{
int ret = 0;

/* Register the character device (at least try) */
ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops);

/* Negative values signify an error */
if (ret < 0) {
pr_alert("%s failed with %d\n",
"Sorry, registering the character device ", ret);
return ret;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
cls = class_create(DEVICE_NAME);
#else
cls = class_create(THIS_MODULE, DEVICE_NAME);
#endif
if (IS_ERR(cls)) {
pr_err("Failed to create class for device\n");
ret = PTR_ERR(cls);
goto fail1;
}

dev = device_create(cls, NULL, MKDEV(MAJOR_NUM, 0), NULL, DEVICE_NAME);
if (IS_ERR(dev)) {
pr_err("Failed to create the device file\n");
ret = PTR_ERR(dev);
goto fail2;
}

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 fail3;
}

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 fail4;
}

return 0;

fail4:
gpio_free(leds[0].gpio);

fail3:
device_destroy(cls, MKDEV(MAJOR_NUM, 0));

fail2:
class_destroy(cls);

fail1:
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

return ret;
}

/* Cleanup - unregister the appropriate file from /proc */
static void __exit led_exit(void)
{
gpio_set_value(leds[0].gpio, 0);
gpio_free(leds[0].gpio);

device_destroy(cls, MKDEV(MAJOR_NUM, 0));
class_destroy(cls);
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
56 changes: 56 additions & 0 deletions lkmpg.tex
Original file line number Diff line number Diff line change
Expand Up @@ -1816,6 +1816,61 @@ \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 in 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Ω resistor.

\samplec{examples/led.c}

Make and install the module:
\begin{codebash}
make
sudo insmod led.ko
\end{codebash}

Changes the permissions of a file:
\begin{codebash}
sudo chmod 666 > /dev/gpio_led
\end{codebash}

Switch on the LED:
\begin{codebash}
echo '1' > /dev/gpio_led
\end{codebash}

Switch off the LED:
\begin{codebash}
echo '0' > /dev/gpio_led
\end{codebash}

Finally, remove the test 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 Expand Up @@ -2173,4 +2228,5 @@ \section{Where To Go From Here?}
Pull requests are greatly appreciated.

Happy hacking!

\end{document}

0 comments on commit a64cd81

Please sign in to comment.