L'intégration d'un écran à cristaux liquides (LCD) dans un système embarqué Linux requiert une compréhension approfondie de son fonctionnement matériel et de l'interface logicielle fournie par le noyau. Cet article explore les bases de l'interfaçage d'un LCD via le sous-système Framebuffer de Linux, en se concentrant sur les principes matériels, l'architecture des drivers et un exemple concret d'intégration sur une plateforme S3C2440.
Principes Fondamentaux du LCD
Mécanisme Matériel d'un Écran LCD
Un écran LCD est composé d'une grille de pixels. Chaque pixel peut afficher une couleur résultant du mélange des trois couleurs primaires : Rouge (R), Vert (V) et Bleu (B). Le rendu de l'image est orchestré par un contrôleur qui gère le balayage de ces pixels.
Pour comprendre le fonctionnement, imaginez un "canon électronique" (une abstraction pour la logique d'affichage) qui parcoure l'écran en émettant de la lumière de différentes couleurs. Ce mouvement est synchronisé par plusieurs signaux :
- Signal d'horloge (CLK) : Chaque impulsion de ce signal déplace la position du "canon électronique" d'un pixel sur la ligne actuelle.
- Signaux de couleur (RGB) : L'intensité de chaque couleur primaire est transmise via des lignes dédiées, déterminant la couleur du pixel courant. Ces données proviennent d'une zone spécifique de la mémoire appelée framebuffer.
- Synchronisation horizontale (HSYNC) : Une impulsion sur cette ligne indique la fin d'une ligne de pixels et le début de la suivante, repositionnant le "canon" au début de la ligne suivante.
- Synchronisation verticale (VSYNC) : Une impulsion sur cette ligne signale la fin d'une trame complète (image) et le retour du "canon" au point de départ (coin supérieur gauche) pour une nouvelle trame.
Dans un système embarqué comme le S3C2440, un contrôleur LCD intégré est responsable de la génération de ces signaux et de la lecture des données d'image depuis le framebuffer (une portion de la SDRAM système allouée à cet effet) pour les envoyer à l'écran.
Le Dispositif Framebuffer sous Linux
Sous Linux, pour permettre aux applications graphiques (GUI) d'interagir avec l'écran, le pilote LCD doit s'intégrer au sous-système Framebuffer. Ce dernier offre une interface standardisée pour les périphériques d'affichage, abstraits des différences matérielles sous-jacentes.
Le framebuffer est une abstraction d'un tampon d'affichage. Il permet aux applications utilisateur de manipuler directement le contenu de la mémoire d'affichage en mode graphique. Toute modification dans le framebuffer est immédiatement répercutée sur l'écran LCD.
Il est exposé comme un périphérique de caractères standard sous /dev/fb%d, où %d est un numéro d'instance (généralement /dev/fb0 pour le premier écran). Son numéro majeur est 29. Les applications peuvent lire et écrire dans ce fichier comme s'il s'agissait d'une région mémoire, et le pilote framebuffer se charge de traduire ces opérations en commandes matérielles.
Approche d'Implémentation d'un Driver Framebuffer
Une première approche consisterait à enregistrer un périphérique de caractères, définir ses opérations de fichier (open, write, etc.) et créer un nœud de périphérique. Cependant, cette méthode manque de flexibilité si plusieurs écrans de différents modèles doivent être gérés, chacun ayant des paramètres spécifiques (résolution, synchronisation, etc.) mais partageant des fonctions d'opération similaires.
Le noyau Linux résout ce problème en introduisant une abstraction via la structure fb_info. Cette structure encapsule toutes les informations relatives à un périphérique LCD et ses méthodes d'opération, permettant de gérer différentes configurations de manière cohérente.
Architecture du Driver Framebuffer
Le sous-système Framebuffer de Linux est structuré en deux composants principaux :
fbmem.c: Il fournit l'interface de haut niveau aux applications utilisatuer via des appels système pour les opérations sur le périphérique framebuffer. Il expose également des fonctions d'enregistrement pour les pilotes matériels, commeregister_framebuffer.xxxfb.c(par exemple,s3c2410fb.c) : Ce fichier implémente le pilote spécifique au matériel. Son rôle principal est de remplir la structurefb_infoavec les détails du contrôleur LCD et de ses opérations spécifiques, puis de l'enregistrer auprès defbmem.c.
Cette séparation des responsabilités entre le code générique (fbmem.c) et le code spécifique au matériel (xxxfb.c) garantit la stabilité du noyau et simplifie l'ajout de nouveaux périphériques d'affichage.
Structures de Données Clés
La structure fb_info
Définie dans include/linux/fb.h, struct fb_info est le conteneur principal pour toutes les informations concernant un périphérique Framebuffer. Elle inclut des pointeurs vers les fonctions d'opération du périphérique.
struct fb_info {
atomic_t count;
int node;
int flags;
int fbcon_rotate_hint;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* Framebuffer event queue */
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode */
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
struct backlight_device *bl_dev;
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops;
struct device *device; /* This is the parent */
struct device *dev; /* This is this fb device */
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
union {
char __iomem *screen_base; /* Virtual address */
char *screen_buffer;
};
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */
void *pseudo_palette; /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
void *par; /* From here on everything is device dependent */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
bool skip_vt_switch;
};
Quelques membres importants :
count: Compteur de références defb_info.node: Index global dans le tableau des framebuffers enregistrés.flags: Drapeaux de configuration (accélération matérielle, type de mémoire, etc.).var(struct fb_var_screeninfo) : Paramètres variables de l'écran (résolution visible, Bpp, timings des signaux d'horloge). Configurables par l'utilisateur.fix(struct fb_fix_screeninfo) : Paramètres fixes de l'écran (adresse de début et taille du tampon framebuffer). Non modifiables par l'utilisateur.fbops(struct fb_ops *) : Pointeur vers la structure des opérations spécifiques au framebuffer.screen_base/screen_buffer: Adresse virtuelle du tampon framebuffer.par: Pointeur générique pour les données privées dépendantes du périphérique.
Drapeaux fb_info.flags
Ces drapeaux spécifient les capacités du framebuffer, notamment les fonctions accélérées par le matériel.
/* FBINFO_* = fb_info.flags bit flags */
#define FBINFO_DEFAULT 0
#define FBINFO_HWACCEL_DISABLED 0x0002
/* When FBINFO_HWACCEL_DISABLED is set:
* Hardware acceleration is turned off. Software implementations
* of required functions (copyarea(), fillrect(), and imageblit())
* takes over; acceleration engine should be in a quiescent state */
/* hints */
#define FBINFO_VIRTFB 0x0004 /* FB is System RAM, not device. */
#define FBINFO_PARTIAL_PAN_OK 0x0040 /* otw use pan only for double-buffering */
#define FBINFO_READS_FAST 0x0080 /* soft-copy faster than rendering */
/* hardware supported ops */
/* semantics: when a bit is set, it indicates that the operation is
* accelerated by hardware.
* required functions will still work even if the bit is not set.
* optional functions may not even exist if the flag bit is not set.
*/
#define FBINFO_HWACCEL_NONE 0x0000
#define FBINFO_HWACCEL_COPYAREA 0x0100 /* required */
#define FBINFO_HWACCEL_FILLRECT 0x0200 /* required */
#define FBINFO_HWACCEL_IMAGEBLIT 0x0400 /* required */
#define FBINFO_HWACCEL_ROTATE 0x0800 /* optional */
#define FBINFO_HWACCEL_XPAN 0x1000 /* optional */
#define FBINFO_HWACCEL_YPAN 0x2000 /* optional */
#define FBINFO_HWACCEL_YWRAP 0x4000 /* optional */
#define FBINFO_MISC_USEREVENT 0x10000 /* event request
from userspace */
#define FBINFO_MISC_TILEBLITTING 0x20000 /* use tile blitting */
/* A driver may set this flag to indicate that it does want a set_par to be
* called every time when fbcon_switch is executed. The advantage is that with
* this flag set you can really be sure that set_par is always called before
* any of the functions dependent on the correct hardware state or altering
* that state, even if you are using some broken X releases. The disadvantage
* is that it introduces unwanted delays to every console switch if set_par
* is slow. It is a good idea to try this flag in the drivers initialization
* code whenever there is a bug report related to switching between X and the
* framebuffer console.
*/
#define FBINFO_MISC_ALWAYS_SETPAR 0x40000
/* where the fb is a firmware driver, and can be replaced with a proper one */
#define FBINFO_MISC_FIRMWARE 0x80000
/*
* Host and GPU endianness differ.
*/
#define FBINFO_FOREIGN_ENDIAN 0x100000
/*
* Big endian math. This is the same flags as above, but with different
* meaning, it is set by the fb subsystem depending FOREIGN_ENDIAN flag
* and host endianness. Drivers should not use this flag.
*/
#define FBINFO_BE_MATH 0x100000
/*
* Hide smem_start in the FBIOGET_FSCREENINFO IOCTL. This is used by modern DRM
* drivers to stop userspace from trying to share buffers behind the kernel's
* back. Instead dma-buf based buffer sharing should be used.
*/
#define FBINFO_HIDE_SMEM_START 0x200000
Opérations du Framebuffer (fb_ops)
La structure struct fb_ops contient les pointeurs vers les fonctions d'opération du pilote framebuffer, implémentant des actions comme l'ouverture, la lecture, l'écriture, la configuration des couleurs et le mappage mémoire.
struct fb_ops {
/* open/release and usage marking */
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
/* For framebuffers with strange non linear layouts or that do not
* work with normal memory mapped access
*/
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos);
/* checks var and eventually tweaks it to something supported,
* DO NOT MODIFY PAR */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
/* set the video mode according to info->var */
int (*fb_set_par)(struct fb_info *info);
/* set color register */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
/* set color registers in batch */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
/* blank display */
int (*fb_blank)(int blank, struct fb_info *info);
/* pan display */
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
/* Draws a rectangle */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* Copy data from area to another */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* Draws a image to the display */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
/* Draws cursor */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
/* wait for blit idle, optional */
int (*fb_sync)(struct fb_info *info);
/* perform fb specific ioctl (optional) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
/* Handle 32bit compat ioctl (optional) */
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
unsigned long arg);
/* perform fb specific mmap */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
/* get capability given var */
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
struct fb_var_screeninfo *var);
/* teardown any resources to do with this framebuffer */
void (*fb_destroy)(struct fb_info *info);
/* called at KDB enter and leave time to prepare the console */
int (*fb_debug_enter)(struct fb_info *info);
int (*fb_debug_leave)(struct fb_info *info);
};
Analyse du code source de fbmem.c
fbmem.c, situé dans drivers/video/fbdev/core, est la pierre angulaire du sous-système framebuffer. Il établit l'interface utilisateur et fournit les mécanismes d'enregistrement pour les pilotes matériels.
Les fonctions d'initialisation et de sortie du module sont fbmem_init et fbmem_exit.
Fonction d'initialisation : fbmem_init
Cette fonction est exécutée au démarrage du module et effectue les actions suivantes :
- Crée une entrée
/proc/fbpour exposer des informations sur les framebuffers enregistrés. - Enregistre un périphérique de caractères avec le numéro majeur 29 et le nom "fb", en associant la structure
fb_fops(opérations de fichier). - Crée la classe "graphics" sous
/sys/class, permettant la création de nœuds de périphériques/dev/fb%dpar les pilotes matériels.
À ce stade, seul le périphérique de caractères générique "fb" est créé. Les nœuds spécifiques /dev/fb%d sont créés lors de l'enregistrement de chaque instance de fb_info.
Opérations de fichier (fb_fops)
Cette structure définit les opérations de base accessibles aux applications utilisateur via les appels système.
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \
(defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \
!defined(CONFIG_MMU))
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
.llseek = default_llseek,
};
Analyse des Fonctions Clés
fb_open: Lors de l'ouverture de/dev/fb%d, cette fonction récupère l'instancefb_infocorrespondante à partir d'un tableau global (registered_fb) en utilisant le numéro mineur du périphérique. Elle incrémente le compteur de références et appelle la fonctionfb_opensi définie dansfb_info->fbops.fb_read: Permet aux applications de lire le contenu du framebuffer. Si une fonctionfb_readest spécifiée dansfb_info->fbops, elle est utilisée. Sinon, la fonction générique lit directement depuis l'adresse virtuelleinfo->screen_base.fb_write: Permet aux applications d'écrire dans le framebuffer. Similaire àfb_read, elle utilisefb_info->fbops->fb_writesi disponible, ou écrit directement dansinfo->screen_base.fb_mmap: Cette fonction est cruciale. Elle mappe la région mémoire du framebuffer (située dans l'espace d'adressage du noyau) vers l'espace d'adressage virtuel de l'application utilisateur. Cela permet aux applications d'accéder directement au tampon d'affichage en mémoire, sans passer par des appels système pour chaque opération de dessin.
Enregistrement d'un Framebuffer : register_framebuffer
La fonction register_framebuffer est l'API que les pilotes matériels appellent pour ajouter une nouvelle instance de framebuffer au système. Elle est thread-safe grâce à un verrou.
À l'intérieur de do_register_framebuffer, les étapes clés sont :
- Recherche d'un emplacement libre dans le tableau global
registered_fb. - Initialisation des champs
node,countet des mutex defb_info. - Appel à
device_createpour créer le nœud de périphérique/dev/fb%dsous la classe "graphics" précédemment établie. - Initialisation des structures internes comme
pixmapetmodelist. - Affectation de l'instance
fb_infoau tableauregistered_fb. - Notification du système d'un événement d'enregistrement de framebuffer.
C'est par cette fonction que le noyau prend en charge de multiples périphériques framebuffer, chacun nommé /dev/fb%d.
Enregistrement du Périphérique Plateforme (s3c2410-lcd)
Le contrôleur LCD sur les SoC S3C2440 est un périphérique intégré qui utilise le modèle de pilote plateforme de Linux.
Structures Spécifiques au LCD S3C24xx
Dans arch/arm/plat-samsung/include/plat/fb-s3c2410.h, on trouve des structures décrivant les configurations spécifiques au contrôleur LCD des S3C24xx :
struct s3c2410fb_hw {
unsigned long lcdcon1;
unsigned long lcdcon2;
unsigned long lcdcon3;
unsigned long lcdcon4;
unsigned long lcdcon5;
};
struct s3c2410fb_display {
unsigned type;
unsigned short width;
unsigned short height;
unsigned short xres;
unsigned short yres;
unsigned short bpp;
unsigned pixclock;
unsigned short left_margin;
unsigned short right_margin;
unsigned short hsync_len;
unsigned short upper_margin;
unsigned short lower_margin;
unsigned short vsync_len;
unsigned long lcdcon5;
};
struct s3c2410fb_mach_info {
struct s3c2410fb_display *displays;
unsigned num_displays;
unsigned default_display;
unsigned long gpcup;
unsigned long gpcup_mask;
unsigned long gpccon;
unsigned long gpccon_mask;
unsigned long gpdup;
unsigned long gpdup_mask;
unsigned long gpdcon;
unsigned long gpdcon_mask;
unsigned long lpcsel;
};
s3c2410fb_hw: Contient les valeurs des registres de contrôle du contrôleur LCD.s3c2410fb_display: Décrit les caractéristiques spécifiques de l'écran LCD (résolution, timings, Bpp).s3c2410fb_mach_info: Regroupe les informations sur l'écran et la configuration des GPIOs du SoC nécessaires au LCD.
Définition des Paramètres de l'Écran
Dans arch/arm/mach-s3c24xx/mach-smdk2440.c, les paramètres de l'écran LCD sont définis sous forme de variables globales :
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT,
.width = 240,
.height = 320,
.pixclock = 166667, /* en picosecondes */
.xres = 240,
.yres = 320,
.bpp = 16,
.left_margin = 20,
.right_margin = 8,
.hsync_len = 4,
.upper_margin = 8,
.lower_margin = 7,
.vsync_len = 4,
};
static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
.displays = &smdk2440_lcd_cfg,
.num_displays = 1,
.default_display = 0,
/* ... configuration GPIO ... */
.lpcsel = ((0xCE6) & ~7) | 1<<4,
};
Ces structures sont remplies avec les caractéristiques spécifiques de l'écran connecté à la carte de développement SMDK2440.
Initialisation Machine (smdk2440_machine_init)
Lors de l'initialisation de la machine S3C2440, la fonction smdk2440_machine_init est appelée. Elle prépare les données pour le périphérique LCD :
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
/* ... autres initialisations ... */
}
s3c24xx_fb_set_platdata (dans arch/arm/plat-samsung/devs.c) attache les données spécifiques à la plateforme (smdk2440_fb_info) au périphérique plateforme s3c_device_lcd. Il alloue une copie de smdk2440_fb_info et l'assigne à s3c_device_lcd->dev.platform_data. Le périphérique s3c_device_lcd est défini avec le nom "s3c2410-lcd" et des ressources (adresse mémoire des registres, IRQ).
Enregistrement du Périphérique Plateforme
La fonction platform_add_devices est chargée d'enregistrer une liste de périphériques plateforme, dont s3c_device_lcd. Chaque périphérique est enregistré via platform_device_register, ce qui le rend visible pour le sous-système de pilote plateforme.
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_lcd, /* Notre périphérique LCD */
&s3c_device_wdt,
/* ... autres périphériques ... */
};
Enregistrement du Pilote Plateforme (s3c2410-lcd)
Une fois le périphérique plateforme enregistré, le noyau recherche un pilote correspondant. Le pilote pour le LCD S3C2410 se trouve dans drivers/video/fbdev/s3c2410fb.c.
Les fonctions d'initialisation et de sortie du module sont s3c2410fb_init et s3c2410fb_cleanup.
Fonction d'initialisation : s3c2410fb_init
Cette fonction enregistre le pilote plateforme s3c2410fb_driver :
int __init s3c2410fb_init(void)
{
int ret = platform_driver_register(&s3c2410fb_driver);
if (ret == 0)
ret = platform_driver_register(&s3c2412fb_driver);
return ret;
}
La structure s3c2410fb_driver contient le nom du pilote ("s3c2410-lcd") et les pointeurs vers ses fonctions d'opération, notamment la fonction probe.
static struct platform_driver s3c2410fb_driver = {
.probe = s3c2410fb_probe,
.remove = s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = {
.name = "s3c2410-lcd",
},
};
Fonction s3c2410fb_probe
Lorsque le noyau trouve un périphérique plateforme dont le nom correspond à celui du pilote ("s3c2410-lcd"), la fonction s3c2410fb_probe est appelée. C'est ici que l'initialisation spécifique au matériel du LCD a lieu :
static int s3c24xxfb_probe(struct platform_device *pdev,
enum s3c_drv_type drv_type)
{
struct s3c2410fb_info *info;
struct s3c2410fb_display *display;
struct fb_info *fbinfo;
struct s3c2410fb_mach_info *mach_info;
struct resource *res;
int ret;
int irq;
int i;
int size;
u32 lcdcon1;
mach_info = dev_get_platdata(&pdev->dev); /* Récupère les données plateforme (smdk2440_fb_info) */
if (mach_info == NULL) {
dev_err(&pdev->dev,
"no platform data for lcd, cannot attach\n");
return -EINVAL;
}
if (mach_info->default_display >= mach_info->num_displays) {
dev_err(&pdev->dev, "default is %d but only %d displays\n",
mach_info->default_display, mach_info->num_displays);
return -EINVAL;
}
display = mach_info->displays + mach_info->default_display; /* Obtient les informations de l'écran utilisé */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
}
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); /* Alloue une structure fb_info */
if (!fbinfo)
return -ENOMEM;
platform_set_drvdata(pdev, fbinfo); /* Lie fbinfo au périphérique plateforme */
info = fbinfo->par; /* Obtient le pointeur vers les données privées */
info->dev = &pdev->dev; /* Initialise les membres de la structure privée */
info->drv_type = drv_type;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); /* Récupère la ressource mémoire (registres du LCD) */
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory registers\n");
ret = -ENXIO;
goto dealloc_fb;
}
size = resource_size(res);
info->mem = request_mem_region(res->start, size, pdev->name); /* Demande la région mémoire */
if (info->mem == NULL) {
dev_err(&pdev->dev, "failed to get memory region\n");
ret = -ENOENT;
goto dealloc_fb;
}
info->io = ioremap(res->start, size); /* Mappe les registres du LCD en mémoire virtuelle */
if (info->io == NULL) {
dev_err(&pdev->dev, "ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
}
if (drv_type == DRV_S3C2412)
info->irq_base = info->io + S3C2412_LCDINTBASE;
else
info->irq_base = info->io + S3C2410_LCDINTBASE;
strcpy(fbinfo->fix.id, driver_name); /* Définit l'ID du framebuffer */
/* Arrête la vidéo */
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1); /* Désactive l'affichage */
/* Définit les paramètres fixes du LCD */
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
fbinfo->fix.xpanstep = 0;
fbinfo->fix.ypanstep = 0;
fbinfo->fix.ywrapstep = 0;
fbinfo->fix.accel = FB_ACCEL_NONE;
/* Définit les paramètres variables du LCD */
fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.accel_flags = 0;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
/* Attache les fonctions d'opération du LCD */
fbinfo->fbops = &s3c2410fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &info->pseudo_pal;
/* Efface le tableau de palette */
for (i = 0; i < 256; i++)
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info); /* Demande l'interruption */
if (ret) {
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_regs;
}
info->clk = clk_get(NULL, "lcd"); /* Obtient l'horloge LCD */
if (IS_ERR(info->clk)) {
dev_err(&pdev->dev, "failed to get lcd clock source\n");
ret = PTR_ERR(info->clk);
goto release_irq;
}
clk_prepare_enable(info->clk); /* Active l'horloge */
info->clk_rate = clk_get_rate(info->clk); /* Obtient la fréquence d'horloge */
/* Détermine la taille maximale requise pour la mémoire d'affichage */
for (i = 0; i < mach_info->num_displays; i++) {
unsigned long smem_len = mach_info->displays[i].xres;
smem_len *= mach_info->displays[i].yres;
smem_len *= mach_info->displays[i].bpp;
smem_len >>= 3;
if (fbinfo->fix.smem_len < smem_len)
fbinfo->fix.smem_len = smem_len;
}
/* Initialise la mémoire vidéo */
ret = s3c2410fb_map_video_memory(fbinfo); /* Alloue la mémoire pour le framebuffer */
if (ret) {
dev_err(&pdev->dev, "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;
s3c2410fb_init_registers(fbinfo); /* Configure les GPIOs pour le LCD */
s3c2410fb_check_var(&fbinfo->var, fbinfo);
ret = s3c2410fb_cpufreq_register(info); /* Enregistre le notificateur de fréquence CPU pour les timings LCD */
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register cpufreq\n");
goto free_video_memory;
}
ret = register_framebuffer(fbinfo); /* Enregistre le périphérique framebuffer */
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register framebuffer device: %d\n",
ret);
goto free_cpufreq;
}
/* Crée les fichiers de périphérique */
ret = device_create_file(&pdev->dev, &dev_attr_debug);
if (ret)
dev_err(&pdev->dev, "failed to add debug attribute\n");
dev_info(&pdev->dev, "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id);
return 0;
free_cpufreq:
s3c2410fb_cpufreq_deregister(info);
free_video_memory:
s3c2410fb_unmap_video_memory(fbinfo);
release_clock:
clk_disable_unprepare(info->clk);
clk_put(info->clk);
release_irq:
free_irq(irq, info);
release_regs:
iounmap(info->io);
release_mem:
release_mem_region(res->start, size);
dealloc_fb:
framebuffer_release(fbinfo);
return ret;
}
La structure s3c2410fb_info est une extension privée de fb_info, stockée dans son champ par, et contient des membres comme dev (périphérique parent), clk (horloge du LCD), io (adresse virtuelle des registres du contrôleur) et regs (cache des valeurs des registres du LCD).
struct s3c2410fb_info {
struct device *dev; /* Pointeur vers le périphérique de base */
struct clk *clk; /* Horloge du LCD */
struct resource *mem; /* Adresse mémoire I/O (virtuelle) */
void __iomem *io; /* Adresse de base des registres du contrôleur LCD (virtuelle) */
void __iomem *irq_base;
enum s3c_drv_type drv_type;
struct s3c2410fb_hw regs; /* Registres de contrôle du LCD */
unsigned long clk_rate; /* Fréquence de l'horloge */
unsigned int palette_ready;
#ifdef CONFIG_ARM_S3C24XX_CPUFREQ
struct notifier_block freq_transition;
#endif
/* Conserve ces registres au cas où la palette devrait être réécrite */
u32 palette_buffer[256];
u32 pseudo_pal[16];
};
Configuration des Paramètres de Synchronisation : s3c2410fb_activate_var
La fonction s3c2410fb_cpufreq_register s'inscrit en tant que notificateur de changement de fréquence du CPU. En cas de changement, la fonction s3c2410fb_cpufreq_transition est appelée, qui à son tour déclenche s3c2410fb_activate_var. Cette fonction est responsable du calcul et de l'écriture des valeurs correctes dans les registres de contrôle du LCD (LCDCON1 à LCDCON5) en fonction des paramètres de fb_info->var (résolution, marges, longueurs des impulsions de synchronisation, etc.).
Un aspect crucial est le calcul du diviseur d'horloge (CLKVAL) pour le signal d'horloge des pixels (VCLK) :
$$ CLKVAL = \frac{HCLK \times pixclock}{2 \times 10^{12}} - 1 $$
Où HCLK est la fréquence de l'horloge système (ex: 100MHz) et pixclock est la durée de chaque pixel en picosecondes. Le résultat est ensuite arrondi et utilisé pour configurer le registre LCDCON1.
/* s3c2410fb_activate_var
*
* active (définit) le contrôleur à partir des informations du framebuffer
*/
static void s3c2410fb_activate_var(struct fb_info *info)
{
struct s3c2410fb_info *fbi = info->par;
void __iomem *regs = fbi->io;
int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT;
struct fb_var_screeninfo *var = &info->var;
int clkdiv;
clkdiv = DIV_ROUND_UP(s3c2410fb_calc_pixclk(fbi, var->pixclock), 2);
/* ... messages de debug ... */
if (type == S3C2410_LCDCON1_TFT) { /* Pour écran TFT */
s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs); /* Calcule les valeurs des registres du contrôleur LCD */
--clkdiv;
if (clkdiv < 0)
clkdiv = 0;
} else {
s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);
if (clkdiv < 2)
clkdiv = 2;
}
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(clkdiv);
/* Écrit les nouveaux registres */
writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,
regs + S3C2410_LCDCON1); /* Écrit dans les registres du contrôleur LCD */
writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);
writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);
writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);
writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);
/* Définit les pointeurs d'adresse LCD */
s3c2410fb_set_lcdaddr(info);
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID; /* Active le LCD */
writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
}
s3c2410fb_calculate_tft_lcd_regs
Cette fonction calcule les valeurs des registres de contrôle spécifiques au type d'écran (TFT ou STN) en utilisant les paramètres de synchronisation définis dans fb_var_screeninfo (var->xres, var->yres, var->upper_margin, etc.) et les bits par pixel (var->bits_per_pixel).
/* s3c2410fb_calculate_tft_lcd_regs
*
* calcule les valeurs des registres à partir des paramètres 'var'
*/
static void s3c2410fb_calculate_tft_lcd_regs(const struct fb_info *info,
struct s3c2410fb_hw *regs)
{
const struct s3c2410fb_info *fbi = info->par;
const struct fb_var_screeninfo *var = &info->var;
switch (var->bits_per_pixel) {
case 1:
regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
break;
case 2:
regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
break;
case 4:
regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
break;
case 8:
regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
regs->lcdcon5 |= S3C2410_LCDCON5_BSWP |
S3C2410_LCDCON5_FRM565;
regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP;
break;
case 16:
regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;
regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;
break;
case 32:
regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;
regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP |
S3C2410_LCDCON5_HWSWP |
S3C2410_LCDCON5_BPP24BL);
break;
default:
/* invalid pixel depth */
dev_err(fbi->dev, "invalid bpp %d\n",
var->bits_per_pixel);
}
/* Mise à jour des informations X/Y */
regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1) |
S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |
S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |
S3C2410_LCDCON2_VSPW(var->vsync_len - 1);
regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin - 1) |
S3C2410_LCDCON3_HFPD(var->left_margin - 1) |
S3C2410_LCDCON3_HOZVAL(var->xres - 1);
regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len - 1);
}
Modification du Driver LCD du Noyau
Pour adapter le driver LCD à un écran spécifique, comme un LCD-T35 (TD035STEB4) avec une résolution de 240x320 et 16 BPP, il est nécessaire de modiifer les paramètres définis dans le code d'initialisation de la carte.
Ajustement de smdk2440_lcd_cfg
Les timings et la résolution de l'écran doivent être mis à jour dans arch/arm/mach-s3c24xx/mach-smdk2440.c. Par exemple, pour un LCD-T35 avec une horloge pixel de 6.4 MHz :
/* Paramètres pour LCD T35 (240x320) */
#define LCD_WIDTH 240 /* Largeur de l'écran LCD en pixels */
#define LCD_HEIGHT 320 /* Hauteur de l'écran LCD en pixels */
#define VSPW 1 /* Largeur de l'impulsion VSYNC */
#define VBPD 1 /* Nombre de lignes 'back porch' verticales */
#define LINEVAL (LCD_HEIGHT-1) /* Nombre de lignes d'affichage - 1 */
#define VFPD 1 /* Nombre de lignes 'front porch' verticales */
#define CLKVAL 7 /* Valeur du diviseur d'horloge VCLK */
#define HSPW 9 /* Largeur de l'impulsion HSYNC */
#define HBPD 19 /* Nombre de pixels 'back porch' horizontaux */
#define HOZVAL (LCD_WIDTH-1) /* Nombre de pixels d'affichage - 1 */
#define HFPD 9 /* Nombre de pixels 'front porch' horizontaux */
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT,
.width = LCD_WIDTH,
.height = LCD_HEIGHT,
.pixclock = 156250, /* Chaque pixel dure 156250 ps (10^12 / 6.4MHz) */
.xres = LCD_WIDTH,
.yres = LCD_HEIGHT,
.bpp = 16,
.left_margin = HFPD,
.right_margin = HBPD,
.hsync_len = HSPW,
.upper_margin = VBPD,
.lower_margin = VFPD,
.vsync_len = VSPW,
};
Le CLKVAL est calculé comme suit : CLKVAL = HCLK / VCLK / 2 - 1. Pour HCLK = 100MHz et VCLK = 6.4MHz, CLKVAL = 100/6.4/2 - 1 = 6.8, arrondi à 7. Une fois CLKVAL fixé à 7, le VCLK réel sera 100MHz / (7+1) / 2 = 6.25MHz.
Modification de smdk2440_fb_info
Les registres GPIO doivent également être configurés pour activer les broches de l'écran LCD. De plus, le champ lpcsel pourrait nécessiter des ajustements pour l'orientation de l'écran.
static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
.displays = &smdk2440_lcd_cfg,
.num_displays = 1,
.default_display = 0,
#if 1 /* Activer la configuration GPIO */
.gpccon = 0xaaaaaaaa, /* Configuration des broches GPC pour le LCD */
.gpccon_mask = 0xffffffff,
.gpcup = 0xffffffff, /* Désactivation des pull-ups sur GPC */
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaaaaaaaa, /* Configuration des broches GPD pour le LCD */
.gpdcon_mask = 0xffffffff,
.gpdup = 0xffffffff, /* Désactivation des pull-ups sur GPD */
.gpdup_mask = 0xffffffff,
#endif
.lpcsel = ((0xCE6) & ~7) | 1<<1, /* Sélection de l'orientation 240x320 */
};
Configuration du Logo de Démarrage
Pour afficher un logo au démarrage sur l'écran LCD, il faut activer les options correspondantes dans la configuration du noyau via make menuconfig :
Device Drivers --->
Graphics support --->
[*] Bootup logo --->
Frame buffer Devices --->
<*> Support for frame buffer devices --->
<*> S3C2410 LCD framebuffer support
Après avoir enregistré la configuration (par exemple, sous arch/arm/configs/s3c2440_defconfig), le noyau peut être recompilé.
Compilation et Flashage du Noyau
Les étapes typiques de compilation et de flashage sur une carte SMDK2440 sont :
make distclean
make s3c2440_defconfig
make uImage V=1
cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/
Ensuite, via U-Boot, flasher l'image sur la mémoire NAND de la carte :
SMDK2440 # set ipaddr 192.168.0.105
SMDK2440 # set serverip 192.168.0.200
SMDK2440 # save
SMDK2440 # tftp 30000000 uImage
SMDK2440 # nand erase.part kernel
SMDK2440 # nand write 30000000 kernel
Au redémarrage, le noyau affichera le logo de démarrage sur l'écran LCD. Des messages de printk peuvent être ajoutés dans le driver s3c2410fb.c pour vérifier les valeurs des registres du contrôleur LCD pendant le démarrage.
Vérification et Démonstration
Après le démarrage du système :
- Écrire sur
/dev/tty1(si la console est redirigée vers là) affichera du texte sur l'écran LCD :echo hello > /dev/tty1. - Un pilote de périphérique d'entrée, tel que celui pour des boutons (
button_dev.ko), peut afficher des messages sur la console (donc sur l'écran) lorsque des boutons sont pressés, confirmant le bon fonctionnement du driver LCD. - Pour rediriger l'intégralité de la sortie de la console vers l'écran LCD, les paramètres de démarrage U-Boot peuvent être modifiés :
set bootargs "noinitrd console=tty1 root=/dev/nfs rw nfsroot=192.168.0.200:/work/nfs_root/rootfs ip=192.168.0.105:192.168.0.200:192.168.0.1:255.255.255.0::eth0:off". Cela affichera tous les messages de démarrage du noyau directement sur l'écran.