Configuración del Árbol de Dispositivos
Para gestionar los pines GPIO a través del subsistema pinctrl, es necesario definir las configuraciones de los pines dentro del nodo controlador. Añadiremos dos estados diferentes para controlar los LEDs conectados a los pines GPB5 a GPB8: uno para encenderlos todos y otro para apagarlos.
Modificación del archivo pinctrl (s3c2440-pinctrl.dtsi)
En la ruta arch/arm/boot/dts/s3c2440-pinctrl.dtsi, agregamos los siguientes nodos secundarios al controlador de pines:
leds_activos: estado-leds-encendidos {
samsung,pins = "gpb-5","gpb-6","gpb-7","gpb-8";
samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>;
samsung,pin-val = <0x0>; /* Nivel bajo para encender los LEDs */
samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;
};
leds_inactivos: estado-leds-apagados {
samsung,pins = "gpb-5","gpb-6","gpb-7","gpb-8";
samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>;
samsung,pin-val = <0x1>; /* Nivel alto para apagar los LEDs */
samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;
};
El nodo leds_activos configura los pines en modo salida con valor bajo, encendiendo los LEDs. Por el contrario, leds_inactivos establece el valor alto de salida para apagarlos.
Modificación del archivo de la placa (s3c2440-smdk2440.dts)
En la ruta arch/arm/boot/dts/s3c2440-smdk2440.dts, definimos el nodo del dispositivo LED especificando sus estados de pinctrl:
nodo_mis_leds: mis_leds {
compatible = "mis_leds_compat";
status = "okay";
pinctrl-names = "default", "apagado";
pinctrl-0 = <&leds_activos>;
pinctrl-1 = <&leds_inactivos>;
};
Se definen dos estados para el dispositivo: default (que referencia la configuración leds_activos) y apagado (que referencia leds_inactivos).
Desarrollo del Controlador de Carácter
Estructura del dispositivo y operaciones de archivo
#define CANTIDAD_DISPS 1
#define NOMBRE_DRV "mis_leds_compat"
/* Estructuras internas para depuración de pinctrl (no expuestas por defecto) */
struct config_mux_local {
unsigned grupo;
unsigned funcion;
};
struct config_pines_local {
unsigned pin_o_grupo;
unsigned long *configuraciones;
unsigned num_configuraciones;
};
struct ajuste_pinctrl_local {
struct list_head nodo;
enum pinctrl_map_type tipo;
struct pinctrl_dev *pctl_dev;
const char *nombre_dev;
union {
struct config_mux_local mux;
struct config_pines_local configs;
} datos;
};
struct estado_pinctrl_local {
struct list_head nodo;
const char *nombre;
struct list_head ajustes;
};
struct datos_led {
dev_t id_dev;
struct cdev char_dev;
struct class *clase_dev;
struct device *disp_dev;
struct device_node *nd_dev;
struct pinctrl *pctl;
struct pinctrl_state *estado_pctl;
};
static struct datos_led dispositivo_led;
static int led_abrir(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t led_escribir(struct file *file, const char __user *buf, size_t cnt, loff_t *off)
{
int dato_usuario, resultado;
unsigned long bytes_no_copiados = copy_from_user(&dato_usuario, buf, cnt);
if (bytes_no_copiados) return -EFAULT;
if (dato_usuario == 1) {
dispositivo_led.estado_pctl = pinctrl_lookup_state(dispositivo_led.pctl, "default");
if (IS_ERR(dispositivo_led.estado_pctl)) {
return PTR_ERR(dispositivo_led.estado_pctl);
}
resultado = pinctrl_select_state(dispositivo_led.pctl, dispositivo_led.estado_pctl);
if (resultado < 0) return resultado;
} else {
dispositivo_led.estado_pctl = pinctrl_lookup_state(dispositivo_led.pctl, "apagado");
if (IS_ERR(dispositivo_led.estado_pctl)) {
return PTR_ERR(dispositivo_led.estado_pctl);
}
resultado = pinctrl_select_state(dispositivo_led.pctl, dispositivo_led.estado_pctl);
if (resultado < 0) return resultado;
}
return cnt;
}
static struct file_operations fops_led = {
.owner = THIS_MODULE,
.open = led_abrir,
.write = led_escribir,
};
Función Probe del Platform Driver
Durante la inicialización, la función probe obtiene el manejador pinctrl asociado al dispositivo, aplica el estado por defecto y registra el dispositivo de carácter. Cabe destacar que el núcleo ya aplica el estado default automáticamente durante el emparejamiento a través de pinctrl_bind_pins, pero aquí realizamos la búsqueda explícita para depurar la configuración de los pines.
static int led_probe(struct platform_device *pdev)
{
int res = 0, i = 0;
struct ajuste_pinctrl_local *ajuste_actual;
struct dev_pin_info *info_pines;
info_pines = pdev->dev.pins;
dispositivo_led.pctl = info_pines->p;
if (IS_ERR(dispositivo_led.pctl)) return PTR_ERR(dispositivo_led.pctl);
dispositivo_led.estado_pctl = pinctrl_lookup_state(dispositivo_led.pctl, "default");
if (IS_ERR(dispositivo_led.estado_pctl)) return PTR_ERR(dispositivo_led.estado_pctl);
res = pinctrl_select_state(dispositivo_led.pctl, dispositivo_led.estado_pctl);
if (res < 0) return res;
list_for_each_entry(ajuste_actual, &(dispositivo_led.estado_pctl->settings), node) {
if (ajuste_actual->tipo == PIN_MAP_TYPE_MUX_GROUP) {
pr_info("Ajuste MUX: Grupo %d, Func %d\n", ajuste_actual->datos.mux.grupo, ajuste_actual->datos.mux.funcion);
} else {
for(i=0; i<ajuste_actual->datos.configs.num_configuraciones; i++){
pr_info("Ajuste Config: Pin/Grupo %d, Val %lu\n", ajuste_actual->datos.configs.pin_o_grupo, ajuste_actual->datos.configs.configuraciones[i]);
}
}
}
res = alloc_chrdev_region(&dispositivo_led.id_dev, 0, CANTIDAD_DISPS, NOMBRE_DRV);
if (res < 0) goto error_id_dev;
cdev_init(&dispositivo_led.char_dev, &fops_led);
res = cdev_add(&dispositivo_led.char_dev, dispositivo_led.id_dev, CANTIDAD_DISPS);
if (res < 0) goto error_cdev;
dispositivo_led.clase_dev = class_create(THIS_MODULE, NOMBRE_DRV);
if (IS_ERR(dispositivo_led.clase_dev)) {
res = PTR_ERR(dispositivo_led.clase_dev);
goto error_clase;
}
dispositivo_led.disp_dev = device_create(dispositivo_led.clase_dev, NULL, dispositivo_led.id_dev, NULL, NOMBRE_DRV);
if (IS_ERR(dispositivo_led.disp_dev)) {
res = PTR_ERR(dispositivo_led.disp_dev);
goto error_device;
}
return 0;
error_device:
class_destroy(dispositivo_led.clase_dev);
error_clase:
cdev_del(&dispositivo_led.char_dev);
error_cdev:
unregister_chrdev_region(dispositivo_led.id_dev, CANTIDAD_DISPS);
error_id_dev:
return res;
}
</ajuste_actual->
Función Remove y definición del Driver
static int led_remove(struct platform_device *pdev)
{
device_destroy(dispositivo_led.clase_dev, dispositivo_led.id_dev);
class_destroy(dispositivo_led.clase_dev);
cdev_del(&dispositivo_led.char_dev);
unregister_chrdev_region(dispositivo_led.id_dev, CANTIDAD_DISPS);
return 0;
}
static const struct of_device_id tabla_compat_led[] = {
{ .compatible = NOMBRE_DRV, },
{ },
};
static struct platform_driver drv_led = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = NOMBRE_DRV,
.of_match_table = tabla_compat_led,
}
};
module_platform_driver(drv_led);
MODULE_LICENSE("GPL");
Aplicación de Prueba en Espacio de Usuario
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd, comando;
if (argc != 3) {
printf("Uso: %s /dev/mis_leds_compat encender|apagar\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if (fd == -1) {
printf("Error al abrir %s\n", argv[1]);
return 0;
}
if (!strcmp("encender", argv[2])) {
comando = 1;
} else if (!strcmp("apagar", argv[2])) {
comando = 0;
} else {
close(fd);
return 0;
}
write(fd, &comando, sizeof(comando));
close(fd);
return 0;
}
Despliegue y Pruebas
Compilación del Árbol de Dispositivos y el Módulo
Compilar el DTB y copiarlo al servidor TFTP:
make dtbs
cp arch/arm/boot/dts/s3c2440-smdk2440.dtb /srv/tftp/
Compilar el módulo del kernel y la aplicación de usuario, trasladándolos al sistema de archivos raíz NFS:
make -C /ruta/al/kernel/linux-5.2.8 M=$(pwd) modules
cp led_pinctrl_drv.ko /srv/nfs/rootfs/
arm-linux-gcc -o test_led test_led.c
cp test_led /srv/nfs/rootfs/
Arranque y Verificación
Tras cargar el kernel y el DTB en memoria medainte U-Boot, iniciar el sistema. Una vez en la consola Linux, insertar el módulo:
insmod led_pinctrl_drv.ko
Verificar la creación automática del nodo de dispositivo:
ls -l /dev/mis_leds_compat
Ejecutar la aplicación de prueba para observar el cambio de estados físicos en los LEDs:
./test_led /dev/mis_leds_compat encender
./test_led /dev/mis_leds_compat apagar
Finalmente, descargar el módulo del kernel cuando ya no se requiera:
rmmod led_pinctrl_drv