LCD driver (FrameBuffer) example development explanation on S3C2440 (Part 2)

Publisher:bettyloveLatest update time:2016-08-14 Source: eefocusKeywords:S3C2440 Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere
My Journey into Embedded Linux mainly describes and summarizes each step I took in learning embedded Linux. First, it summarizes my experience, and second, it hopes to provide convenience for friends who want to get started with embedded Linux. If there are any mistakes, please correct me.

 Development Environment

Host: VMWare--Fedora 9 
Development board: Mini2440--64MB Nand, Kernel:2.6.30.4 
Compiler: arm-linux-gcc-4.3.2
Continued from: Detailed development of LCD driver (FrameBuffer) on S3C2440 (I)

 

4. FrameBuffer device driver example code:

①. Create the driver file: my2440_lcd.c, which is the most basic structure of the driver: the initialization and unloading part of the FrameBuffer driver and others, as follows:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

 

 

#include
#include
#include
#include
#include
#include
#include
#include
#include


/*FrameBuffer device name*/
static char driver_name[] = "my2440_lcd";

/*Define a structure to maintain the variables used in each function in the driver.
  Don't look at the structure to define these members first, you will understand when you see the functions use them*/
struct my2440fb_var
{
int lcd_irq_no; /*Save the LCD interrupt number*/
struct clk *lcd_clock;/*Save the LCD clock obtained from the platform clock queue*/
struct resource *lcd_mem;/*LCD IO space*/
void __iomem *lcd_base;/*LCD IO space mapped to virtual address*/
struct device *dev;

struct s3c2410fb_hw regs;/*Represents 5 LCD configuration registers, s3c2410fb_hw is defined in mach-s3c2410/include/mach/fb.h*/

/*Define an array to act as a palette.
    According to the data sheet, when the TFT screen color bit mode is 8BPP, the length of the palette (color table) is 256, and the starting address of the palette is 0x4D000400*/
    u32 palette_buffer[256];

    u32 pseudo_pal[16];   
unsigned int palette_ready;/*Identifies whether the palette is ready*/
};

/*Used to clear the palette (color table)*/
#define PALETTE_BUFF_CLEAR (0x80000000)

/*LCD platform driver structure, the platform driver structure is defined in platform_device.h, and the structure member interface function is implemented in step ②*/
static struct platform_driver lcd_fb_driver = 
{
.probe = lcd_fb_probe, /*FrameBuffer device detection*/
.remove= __devexit_p(lcd_fb_remove),/*FrameBuffer device removal*/
.suspend = lcd_fb_suspend, /*FrameBuffer device suspend*/
.resume = lcd_fb_resume,/*FrameBuffer device resume*/
.driver = 
{
/*Note that the name here must be consistent with the place where the platform device is defined in the system, so that the platform device can be associated with the driver of the platform device*/
.name = "s3c2410-lcd",
.owner = THIS_MODULE,
},
};

static int __init lcd_init(void)
{
/*In Linux, the frame buffer device is regarded as a platform device, so register the platform device here*/
return platform_driver_register(&lcd_fb_driver);
}

static void __exit lcd_exit(void)
{
/*Unregister the platform device*/
    platform_driver_unregister(&lcd_fb_driver);
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huang Gang");
MODULE_DESCRIPTION("My2440 LCD FrameBuffer Driver");


②、Implementation of various interface functions of LCD platform equipment:

/*Implementation of LCD FrameBuffer device detection. Note that a __devinit macro is used here. Go to the implementation of the lcd_fb_remove interface function to explain*/
static int __devinit lcd_fb_probe(struct platform_device *pdev)
{
int i;
int ret;
struct resource *res;/*Used to save LCD resources obtained from the LCD platform device*/
struct fb_info *fbinfo;/*fb_info structure corresponding to the FrameBuffer driver*/
struct s3c2410fb_mach_info *mach_info;/*Save the platform device data obtained from the kernel*/
struct my2440fb_var *fbvar;/*Driver global variable structure defined above*/
struct s3c2410fb_display *display;/*LCD screen configuration information structure, which is defined in mach-s3c2410/include/mach/fb.h*/

/*Get LCD hardware related information data. As mentioned earlier, the kernel uses s3c24xx_fb_set_platdata function to save LCD hardware related information into
     LCD platform data, so here we take it out of the platform data and use it in the driver*/
    mach_info = pdev->dev.platform_data;
if(mach_info == NULL)
{
/*Judge whether the data acquisition is successful*/
        dev_err(&pdev->dev, "no platform data for lcd\n");
return -EINVAL;
}

/*Get the LCD configuration information structure data of the FrameBuffer platform device defined in the kernel*/
    display = mach_info->displays + mach_info->default_display;

 

 

    /*Allocate space for fb_info, the size is the memory of the my2440fb_var structure, framebuffer_alloc is defined in fb.h and implemented in fbsysfs.c*/
    fbinfo = framebuffer_alloc(sizeof(struct my2440fb_var), &pdev->dev);
if(!fbinfo)
{
        dev_err(&pdev->dev, "framebuffer alloc of registers failed\n");
        ret = -ENOMEM;
goto err_noirq;
}
    platform_set_drvdata(pdev, fbinfo);/*Reset the LCD platform device data to fbinfo, so that it can be used in some subsequent functions*/

/*The purpose here is actually to point the member par of fb_info (note that it is a void type pointer) to the private variable structure fbvar here,
     so that the member par of fb_info can be taken out in other interface functions, so that the private variables here can continue to be used*/
    fbvar = fbinfo->par;
    fbvar->dev = &pdev->dev;

/*Get the LCD interrupt number in the system-defined LCD platform device resources, platform_get_irq is defined in platform_device.h*/
    fbvar->lcd_irq_no = platform_get_irq(pdev, 0);
if(fbvar->lcd_irq_no < 0)
{
/*Judge whether the interrupt number is successfully obtained*/
        dev_err(&pdev->dev, "no lcd irq for platform\n");
return -ENOENT;
}

/*Get the IO port resources used by the LCD platform device. Note that the IORESOURCE_MEM flag is consistent with the LCD platform device definition*/
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if(res == NULL)
{
/*Judge whether the resource acquisition is successful*/
        dev_err(&pdev->dev, "failed to get memory region resource\n");
return -ENOENT;
}

/*Apply for LCD IO space occupied by IO port (pay attention to understand the difference between IO space and memory space), request_mem_region is defined in ioport.h*/
    fbvar->lcd_mem = request_mem_region(res->start, res->end - res->start + 1, pdev->name);
if(fbvar->lcd_mem == NULL)
{
/*Judge whether the application for IO space is successful*/
        dev_err(&pdev->dev, "failed to reserve memory region\n");
return -ENOENT;
}

/*Map the IO space occupied by LCD's IO port to the virtual address of memory, ioremap is defined in io.h
     Note: IO space can only be used after mapping, and subsequent operations on virtual addresses are operations on IO space*/
    fbvar->lcd_base = ioremap(res->start, res->end - res->start + 1);
if(fbvar->lcd_base == NULL)
{
/*Judge whether the mapping of virtual address is successful*/
        dev_err(&pdev->dev, "ioremap() of registers failed\n");
        ret = -EINVAL;
goto err_nomem;
}

/*Get the LCD clock from the platform clock queue. Why do we need to get this clock here? From the timing diagram of the LCD screen, the delay of various control signals
     is related to the LCD clock. Some clocks of the system are defined in arch/arm/plat-s3c24xx/s3c2410-clock.c*/
    fbvar->lcd_clock = clk_get(NULL, "lcd");
if(!fbvar->lcd_clock)
{
/*Judge whether the clock acquisition is successful*/
        dev_err(&pdev->dev, "failed to find lcd clock source\n");
        ret = -ENOENT;
goto err_nomap;
}
/*After the clock is acquired, it must be enabled before it can be used. clk_enable is defined in arch/arm/plat-s3c/clock.c*/
    clk_enable(fbvar->lcd_clock);

/*Apply for LCD interrupt service. The interrupt number lcd_fb_irq obtained above uses the fast interrupt mode: IRQF_DISABLED.
     The interrupt service program is:lcd_fb_irq, passes the LCD platform device pdev as a parameter*/
    ret = request_irq(fbvar->lcd_irq_no, lcd_fb_irq, IRQF_DISABLED, pdev->name, fbvar);
if(ret)
{
/*Judge whether the interrupt service request is successful*/
        dev_err(&pdev->dev, "IRQ%d error %d\n", fbvar->lcd_irq_no, ret);
        ret = -EBUSY;
goto err_noclk;
}
/*Okay, the above is the acquisition and setting of the resources to be used. Now start to initialize and fill the fb_info structure*/

/*First initialize the structure fb_fix_screeninfo representing the fixed parameters of LCD in fb_info*/
/*There are 5 mapping relationships between pixel values ​​and display memory, which are defined in fb.h. Now we use the FB_TYPE_PACKED_PIXELS mode. In this mode,
    the pixel value directly corresponds to the memory. For example, when a "1" is written to a unit in the display memory, the pixel value corresponding to the unit will also be "1", which makes it
    very convenient for the application layer to map the display memory to the user space. In Linux, when the LCD is a TFT screen, the display driver manages the display memory based on this method*/
;/*Identifier in string form*/
    fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
    fbinfo->fix.type_aux = 0;/*The following are based on the description in the fb_fix_screeninfo definition. When there is no hardware, they are all set to 0*/
    fbinfo->fix.xpanstep = 0;
    fbinfo->fix.ypanstep = 0;
    fbinfo->fix.ywrapstep= 0;
    fbinfo->fix.accel = FB_ACCEL_NONE;

    /*Next, initialize the structure fb_var_screeninfo in fb_info that represents the variable parameters of LCD*/
    fbinfo->var.nonstd = 0;
    fbinfo->var.activate = FB_ACTIVATE_NOW;
    fbinfo->var.accel_flags = 0;
    fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
    fbinfo->var.xres = display->xres;
    fbinfo->var.yres = display->yres;
    fbinfo->var.bits_per_pixel = display->bpp;

    /*Specify the function pointer for the underlying hardware operation. Since it contains a lot of content, its definition will be discussed in step ③*/
    fbinfo->fbops = &my2440fb_ops;

    fbinfo->flags = FBINFO_FLAG_DEFAULT;

    fbinfo->pseudo_palette = &fbvar->pseudo_pal;

 

    fbinfo->flags = FBINFO_FLAG_DEFAULT;

    fbinfo->pseudo_palette = &fbvar->pseudo_pal;

    /*Initialize the color palette (color table) to be empty*/
for(i = 0; i < 256; i++)
{
        fbvar->palette_buffer[i] = PALETTE_BUFF_CLEAR;
}


for (i = 0; i < mach_info->num_displays; i++) /*length of fb cache*/
{
/*Calculate the maximum size of FrameBuffer cache. The right shift of 3 bits (i.e., divided by 8) here is because the color bit mode BPP is in bits*/
unsigned long smem_len = (mach_info->displays[i].xres * mach_info->displays[i].yres * mach_info->displays[i].bpp) >> 3;

if(fbinfo->fix.smem_len < smem_len)
{
            fbinfo->fix.smem_len = smem_len;
}
}

/*Delay for a while before initializing LCD controller*/
    msleep(1);

/*After initializing fb_info, start to initialize the LCD registers. Its definition will be discussed later*/
    my2440fb_init_registers(fbinfo);

/*After initializing the registers, start checking the variable parameters in fb_info, which will be defined later*/
    my2440fb_check_var(fbinfo);

/*Apply for the display buffer space of the frame buffer device fb_info, which will be defined later*/
    ret = my2440fb_map_video_memory(fbinfo);
if (ret) 
{
        dev_err(&pdev->dev, "failed to allocate video RAM: %d\n", ret);
        ret = -ENOMEM;
goto err_nofb;
}

/*Finally, register this frame buffer device fb_info to the system, register_framebuffer is defined in fb.h and implemented in fbmem.c*/
    ret = register_framebuffer(fbinfo);
if (ret < 0) 
{
        dev_err(&pdev->dev, "failed to register framebuffer device: %d\n", ret);
goto err_video_nomem;
}

/*Support for device file system (For understanding of device file system, please refer to: My Journey in Embedded Linux - Analysis and Use of Device File System)
     Create frambuffer device file, device_create_file is defined in linux/device.h*/
    ret = device_create_file(&pdev->dev, &dev_attr_debug);
if (ret) 
{
        dev_err(&pdev->dev, "failed to add debug attribute\n");
}

return 0;

/*The following is the jump point for the error handling above*/
err_nomem:
    release_resource(fbvar->lcd_mem);
    kfree(fbvar->lcd_mem);

err_nomap:
    iounmap(fbvar->lcd_base);

err_noclk:
    clk_disable(fbvar->lcd_clock);
    clk_put(fbvar->lcd_clock);

err_noirq:
    free_irq(fbvar->lcd_irq_no, fbvar);

err_nofb:
    platform_set_drvdata(pdev, NULL);
    framebuffer_release(fbinfo);

err_video_nomem:
    my2440fb_unmap_video_memory(fbinfo);

return ret;
}

/*LCD interrupt service routine*/
static irqreturn_t lcd_fb_irq(int irq, void *dev_id)
{
struct my2440fb_var *fbvar = dev_id;
void __iomem *lcd_irq_base;
unsigned long lcdirq;

/*LCD interrupt pending register base address*/
    lcd_irq_base = fbvar->lcd_base + S3C2410_LCDINTBASE;

/*Read the value of LCD interrupt pending register*/
    lcdirq = readl(lcd_irq_base + S3C24XX_LCDINTPND);

/*Judge whether it is interrupt pending state*/
if(lcdirq & S3C2410_LCDINT_FRSYNC)
{
/*fill the palette*/
if (fbvar->palette_ready)
{
            my2440fb_write_palette(fbvar);
}

/*Set frame inserted interrupt request*/
        writel(S3C2410_LCDINT_FRSYNC, lcd_irq_base + S3C24XX_LCDINTPND);
        writel(S3C2410_LCDINT_FRSYNC, lcd_irq_base + S3C24XX_LCDSRCPND);
}

return IRQ_HANDLED;
}

/*Fill the palette*/
static void my2440fb_write_palette(struct my2440fb_var *fbvar)
{
unsigned int i;
void __iomem *regs = fbvar->lcd_base;

    fbvar->palette_ready = 0;

for (i = 0; i < 256; i++) 
{
unsigned long ent = fbvar->palette_buffer[i];

if (ent == struct my2440fb_var         * fbvar
= fbinfo- > par
; struct             s3c2410fb_mach_info             ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​*mach_info = fbvar->dev->platform_data; /*Get the base address of the temporary palette register. The S3C2410_TPAL macro is defined in mach-s3c2410/include/mach/regs-lcd.h.     Note that lpcsel is a special register for Samsung TFT screens. If you are not using a Samsung TFT screen, you should ignore it. */     tpal = fbvar->lcd_base + S3C2410_TPAL;     lpcsel = fbvar->lcd_base + S3C2410_LPCSEL; /*Before modifying the following register values, mask the interrupts and save the interrupt status to flags*/     local_irq_save(flags); /*Here is how to configure IO ports C and D to LCD mode as mentioned in the previous chapter*/     modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask);     modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);     modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask);     modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask); /*Restore the masked interrupt*/     local_irq_restore(flags);     writel(0x00, tpal);/*Enable and disable the temporary palette register*/     writel(mach_info->lpcsel, lpcsel);/*As mentioned in the previous article, it is a register of Samsung TFT screen, which can be ignored here*/ return 0; } /*This function implements the modification of the value of the GPIO port. Note that the role of the third parameter mask is to clear the register value to be set first*/ static inline void modify_gpio(void __iomem *reg, unsigned long set, unsigned long mask) { unsigned long tmp;     tmp = readl(reg) & ~mask;     writel(tmp | set, reg); } /*Check the variable parameters in fb_info*/ static int my2440fb_check_var(struct fb_info *fbinfo) { unsigned i;































































/*Get LCD related information data from the platform data set by the lcd_fb_probe detection function*/
struct fb_var_screeninfo *var = &fbinfo->var;/*Variable parameters in fb_info*/
struct my2440fb_var *fbvar = fbinfo->par;/*Private structure data set in the lcd_fb_probe detection function*/
struct s3c2410fb_mach_info *mach_info = fbvar->dev->platform_data;/*LCD configuration structure data, the assignment of this configuration structure is in the previous chapter "3. Frame buffer device as platform device"*/

struct s3c2410fb_display *display = NULL;
struct s3c2410fb_display *default_display = mach_info->displays + mach_info->default_display;
int type = default_display->type;/*LCD type, see the previous chapter "3. Frame buffer device as platform device" in the type assignment is TFT type*/

/*Verify X/Y resolution*/
if (var->yres == default_display->yres && 
        var->xres == default_display->xres && 
        var->bits_per_pixel == default_display->bpp)
{
        display = default_display;
}
else
{
for (i = 0; i < mach_info->num_displays; i++)
{
if (type == mach_info->displays[i].type &&
             var->yres == mach_info->displays[i].yres &&
             var->xres == mach_info->displays[i].xres &&
             var->bits_per_pixel == mach_info->displays[i].bpp) 
{
                display = mach_info->displays + i;
break;
}
}
}

if (!display) 
{
return -EINVAL;
}

/* Configure bits 5-6 in LCD configuration register 1 (configured as TFT type) and configure LCD configuration register 5*/
    fbvar->regs.lcdcon1 = display->type;
    fbvar->regs.lcdcon5 = display->lcdcon5;

/* Set the virtual resolution pixel and height width of the screen*/
    var->xres_virtual = display->xres;
    var->yres_virtual = display->yres;
    var->height = display->height;
    var->width = display->width;

/* Set clock pixel, line and frame switching value, horizontal synchronization and vertical synchronization length value*/
    var->pixclock = display->pixclock;
    var->left_margin = display->left_margin;
    var->right_margin = display->right_margin;
    var->upper_margin = display->upper_margin;
    var->lower_margin = display->lower_margin;
    var->vsync_len = display->vsync_len;
    var->hsync_len = display->hsync_len;

/*Set transparency*/
    var->transp.offset = 0;
    var->transp.length = 0;

/*Set the color bit fields of R, G, and B in the variable parameters according to the color bit mode (BPP). For the setting of these parameter values, please refer to the
    "Display Buffer and Display Point Correspondence Diagram" in the CPU data manual. For example, in the previous chapter, I drew the corresponding relationship diagram for 8BPP and 16BPP*/
switch (var->bits_per_pixel) 
{
case 1:
case 2:
case 4:
            var->red.offset = 0;
            var->red.length = var->bits_per_pixel;
            var->green = var->red;
            var->blue = var->red;
break;
case 8:/* 8 bpp 332 */
if (display->type != S3C2410_LCDCON1_TFT) 
{
                var->red.length = 3;
                var->red.offset = 5;
                var->green.length = 3;
                var->green.offset = 2;
                var-> blue.length = 2;
                var->blue.offset = 0;
}else{
                var->red.offset = 0;
                var->red.length = 8;
                var->green = var->red;
                var->blue = var->red;
}
break;
case 12:/* 12 bpp 444 */
            var->red.length = 4;
            var->red.offset = 8;
            var->green.length = 4;
            var->green .offset = 4;
            var->blue.length = 4;
            var->blue.offset = 0;
break;
case 16:/* 16 bpp */
if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) 
{
/* 565 format */
                var->red.offset = 11;
                var->green.offset = 5;
                var->blue.offset = 0;
                var->red.length = 5;
                var->green. length = 6;
                var->blue.length = 5;
} else {
/* 5551 format */
                var->red.offset = 11;
                var->green.offset = 6;
                var->blue.offset = 1;
                var ->red.length = 5;
                var->green.length = 5;
                var->blue.length = 5;
}
break;
case 32:/* 24 bpp 888 and 8 dummy */
            var->red.length = 8 ;
            var->red.offset = 16;
            var->green.length = 8;
            var->green.offset = 8;
            var->blue.length = 8;
            var->blue.offset = 0;
break;
}

return 0;
}

/*Apply for display buffer space of frame buffer device fb_info*/
static int __init my2440fb_map_video_memory(struct fb_info *fbinfo)
{
    dma_addr_t map_dma;/*Used to save the DMA buffer bus address*/
struct my2440fb_var *fbvar = fbinfo->par;/*Get the private structure data set in the lcd_fb_probe detection function*/
unsigned map_size = PAGE_ALIGN(fbinfo->fix .smem_len);/*Get the size of the FrameBuffer cache, PAGE_ALIGN is defined in mm.h*/

/*Set the allocated write-merge DMA buffer area to the virtual address of the LCD screen (for DMA, please refer to DMA related knowledge)
    dma_alloc_writecombine is defined in arch/arm/mm/dma-mapping.c*/
    fbinfo->screen_base = dma_alloc_writecombine(fbvar->dev, map_size, &map_dma, GFP_KERNEL);

if (fbinfo->screen_base) 
{
/*Set the content of this DMA buffer area to empty*/
memset(fbinfo->screen_base, 0x00, map_size);

/*Set the DMA buffer bus address to the start position of the framebuffer cache in the fb_info immutable parameter*/
        fbinfo->fix.smem_start = map_dma;
}

return fbinfo->screen_base ? 0 : -ENOMEM;
}

/*Release the display buffer space of the frame buffer device fb_info*/
static inline void my2440fb_unmap_video_memory(struct fb_info *fbinfo)
{
struct my2440fb_var *fbvar = fbinfo->par;
unsigned map_size = PAGE_ALIGN(fbinfo->fix.smem_len);

/*Corresponding to the place where DMA is applied*/
    dma_free_writecombine(fbvar->dev, map_size, fbinfo->screen_base, fbinfo->fix.smem_start);
}


/*Implementation of LCD FrameBuffer device removal. Note that a __devexit macro is used here, corresponding to the lcd_fb_probe interface function.
  In the Linux kernel, a large number of different macros are used to mark functions and data structures with different functions. These macros 
  are defined in the include/linux/init.h header file. The compiler can use these macros to optimize the code to the appropriate memory location to reduce memory usage and improve kernel efficiency.
  __devinit and __devexit are among these macros. The __devinit and __devexit macros should be used in the probe() and remove() functions.
  When the remove() function uses the __devexit macro, the __devexit_p macro must be used in the driver structure to reference remove(),
  so in step ①, __devexit_p is used to reference the lcd_fb_remove interface function. */
static int __devexit lcd_fb_remove(struct platform_device *pdev)
{
struct fb_info *fbinfo = platform_get_drvdata(pdev);
struct my2440fb_var *fbvar = fbinfo->par;

/*Unregister the frame buffer device from the system*/
    unregister_framebuffer(fbinfo);

/*Stop the LCD controller*/
    my2440fb_lcd_enable(fbvar, 0);

/*Delay for a while, because it takes a little time to stop the LCD controller*/
    msleep(1);

/*Release the display buffer space of the frame buffer device fb_info*/
    my2440fb_unmap_video_memory(fbinfo);

/*Clear the LCD platform data and release the fb_info space resources*/
    platform_set_drvdata(pdev, NULL);
    framebuffer_release(fbinfo);

/*Release interrupt resources*/
    free_irq(fbvar->lcd_irq_no, fbvar);

/*Release clock resources*/
if (fbvar->lcd_clock) 
{
        clk_disable(fbvar->lcd_clock);
        clk_put(fbvar->lcd_clock);
        fbvar->lcd_clock = NULL;
}

/*Release the virtual memory space mapped by the LCD IO space*/
    iounmap(fbvar->lcd_base);

/*Release the IO space occupied by the requested LCD IO port*/
    release_resource(fbvar->lcd_mem);
    kfree(fbvar->lcd_mem);

return 0;
}

/*Stop the LCD controller*/
static void my2440fb_lcd_enable(struct my2440fb_var *fbvar, int enable)
{
unsigned long flags;

/*Shield the interrupt before modifying the register value below and save the interrupt status to flags*/
    local_irq_save(flags);

if (enable)
{
        fbvar->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID;
}
else
{
        fbvar->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
}

    writel(fbvar->regs.lcdcon1, fbvar->lcd_base + S3C2410_LCDCON1);

/*Restore the masked interrupt*/
    local_irq_restore(flags);
}

/*Support for LCD FrameBuffer platform device driver power management, CONFIG_PM macro is defined in the kernel*/
#ifdef CONFIG_PM
/*When power management is selected when configuring the kernel, the platform device driver supports suspend and resume functions*/
static int lcd_fb_suspend(struct platform_device *pdev, pm_message_t state)
{
/*Suspend the LCD device. Note that the various states of the LCD controller are not saved when the LCD is suspended, so the LCD will not continue to display the content before suspension after recovery.
     If you want to continue to display the content before suspension, you need to save the various states of the LCD controller here. I will not talk about this here, and will talk about power management later*/
struct fb_info *fbinfo = platform_get_drvdata(pdev);
struct my2440fb_var *fbvar = fbinfo->par;

/*Stop the LCD controller*/
    my2440fb_lcd_enable(fbvar, 0);

    msleep(1);

/*Stop the clock*/
    clk_disable(fbvar->lcd_clock);

return 0;
}

staticint lcd_fb_resume(struct platform_device *pdev)
{
/*Resume the suspended LCD device*/
struct fb_info *fbinfo = platform_get_drvdata(pdev);
struct my2440fb_var *fbvar = fbinfo->par;

/*Turn on the clock*/
    clk_enable(fbvar->lcd_clock);

/*Delay for a while before initializing the LCD controller*/
    msleep(1);

/*Reinitialize LCD registers when resuming*/
    my2440fb_init_registers(fbinfo);

/*Reactivate all parameter configurations in fb_info. This function definition will be discussed in step ③*/
    my2440fb_activate_var(fbinfo);

/*Just as mentioned during suspend, since the various states of the LCD controller during suspend are not saved, the
    LCD display will be blank after resuming. This function definition will also be discussed in step ③*/
    my2440fb_blank(FB_BLANK_UNBLANK, fbinfo);

return 0;
}
#else
/*If power management is not selected when configuring the kernel, the platform device driver does not support suspend and resume functions, and these two functions do not need to be implemented*/
#define lcd_fb_suspend NULL
#define lcd_fb_resume NULL
#endif


③、The function interface implementation of the frame buffer device driver for the underlying hardware operation (ie: the implementation of my2440fb_ops):

/*Framebuffer underlying hardware operation interface functions*/
static struct fb_ops my2440fb_ops = 
{
.owner = THIS_MODULE,
.fb_check_var = my2440fb_check_var,/*Already implemented in step ②*/
.fb_set_par = my2440fb_set_par,/*Set the parameters in fb_info, mainly the display mode of LCD*/
.fb_blank = my2440fb_blank,/*Display blank (ie: LCD switch control)*/
.fb_setcolreg = my2440fb_setcolreg,/*Set color table*/
/*The following three functions are optional, mainly to provide support for fb_console, which has been implemented in the kernel and can be called directly here*/
.fb_fillrect = cfb_fillrect,/*Defined in drivers/video/cfbfillrect.c*/
.fb_copyarea = cfb_copyarea,/*defined in drivers/video/cfbcopyarea.c*/
.fb_imageblit = cfb_imageblit,/*defined in drivers/video/cfbimgblt.c*/
};

/*Set the parameters in fb_info. Here, adjust the fixed parameter fix according to the variable parameter var set by the user*/
static int my2440fb_set_par(struct fb_info *fbinfo)
{
/*Get the variable parameters in fb_info*/
struct fb_var_screeninfo *var = &fbinfo->var;

/*Judge the color bit mode in the variable parameter and set the color mode according to the color bit mode*/
switch (var->bits_per_pixel) 
{
case 32:
case 16:
case 12:/*When 12BPP, set it to true color (divided into three primary colors: red, green and blue)*/
            fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
break;
case 1:/*1BPP, set to black and white (black and white, FB_VISUAL_MONO01 represents black, FB_VISUAL_MONO10 represents white)*/
            fbinfo->fix.visual = FB_VISUAL_MONO01;
break;
default:/*The default setting is pseudo color, using index color display*/
            fbinfo->fix.visual = FB_VISUAL_PSEUDOCOLOR;
break;
}

/*Set the number of bytes in a row of fixed parameters in fb_info, formula: number of bytes in 1 row = (number of pixels in 1 row * number of bits per pixel BPP) / 8 */
    fbinfo->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;

/*After modifying the above parameters, reactivate the parameter configuration in fb_info (that is, make the modified parameters take effect on the hardware)*/
    my2440fb_activate_var(fbinfo);

return 0;
}

/*Reactivate the parameter configuration in fb_info*/
static void my2440fb_activate_var(struct fb_info *fbinfo)
{
/*Get the structure variable*/
struct my2440fb_var *fbvar = fbinfo->par;
void __iomem *regs = fbvar->lcd_base;

/*Get the variable parameters of fb_info*/
struct fb_var_screeninfo *var = &fbinfo->var;

/*Calculate the CLKVAL value in LCD control register 1. According to the description of this register in the data sheet, the calculation formula is as follows:
    * STN screen: VCLK = HCLK / (CLKVAL * 2), CLKVAL requires >= 2
    * TFT screen: VCLK = HCLK / [(CLKVAL + 1) * 2], CLKVAL requires >= 0*/
int clkdiv = my2440fb_calc_pixclk(fbvar, var->pixclock) / 2;

/*Get the type of screen*/
int type = fbvar->regs.lcdcon1 & S3C2410_LCDCON1_TFT;

if (type == S3C2410_LCDCON1_TFT) 
{
/*Configure LCD control registers 1-5 according to the requirements of the TFT screen according to the data sheet*/
        my2440fb_config_tft_lcd_regs(fbinfo, &fbvar->regs);

--clkdiv;

if (clkdiv < 0)
{
            clkdiv = 0;
}

else 
{
/*Configure LCD control registers 1-5 according to the requirements of the STN screen according to the data sheet*/
        my2440fb_config_stn_lcd_regs(fbinfo, &fbvar->regs);

if (clkdiv < 2)
{
            clkdiv = 2;
}
}

/*Set the calculated CLKVAL value in LCD control register 1*/
    fbvar->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(clkdiv);

/*Write each parameter value to LCD control register 1-5*/
    writel(fbvar->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID, regs + S3C2410_LCDCON1);
    writel(fbvar->regs.lcdcon2, regs + S3C2410_LCDCON2);
    writel(fbvar->regs.lcdcon3, regs + S3C2410_LCDCON3);
    writel(fbvar->regs.lcdcon4, regs + S3C2410_LCDCON4);
    writel(fbvar->regs.lcdcon5, regs + S3C2410_LCDCON5);

/*Configure frame buffer start address registers 1-3*/
    my2440fb_set_lcdaddr(fbinfo);

    fbvar->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
    writel(fbvar->regs.lcdcon1, regs + S3C2410_LCDCON1);
}

/*Calculate the CLKVAL value in LCD control register 1*/
static unsigned int my2440fb_calc_pixclk(struct my2440fb_var *fbvar, unsigned long pixclk)
{
/*Get the LCD clock*/
unsigned long clk = clk_get_rate(fbvar->lcd_clock);

/* The pixel clock unit is picoseconds, and the clock unit is hertz, so the calculation formula is:
     * Hz -> picoseconds is / 10^-12
     */
unsigned long long div = (unsigned long long)clk * pixclk;

div >>= 12;/* div / 2^12 */
    do_div(div, 625 * 625UL * 625); /* div / 5^12, do_div macro definition in asm/div64.h*/

return div;
}

/*Configure LCD control registers 1-5 according to the requirements of the TFT screen according to the data sheet*/
static void my2440fb_config_tft_lcd_regs(const struct fb_info *fbinfo, struct s3c2410fb_hw *regs)
{
const struct my2440fb_var *fbvar = fbinfo->par;
const struct fb_var_screeninfo *var = &fbinfo->var;

/*Set LCD control registers 1 and 5 according to the color bit mode, refer to the data sheet*/
switch (var->bits_per_pixel) 
{
case 1:/*1BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
break;
case 2:/*2BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
break;
case 4:/*4BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
break;
case 8:/*8BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
            regs->lcdcon5 |= S3C2410_LCDCON5_BSWP | S3C2410_LCDCON5_FRM565;
            regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP;
break;
case 16:/*16BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
            regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;
            regs->lcdcon5 |= S3C2410_LC DCON5_HWSWP;
break;
case 32:/*32BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;
            regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP | S3C2410_LCDCON5_HWSWP | S3C2410_LCDCON5_BPP24BL);
break;
default:/*Invalid BPP*/
            dev_err(fbvar->dev, "invalid bpp %d\n", var->bits_per_pixel);
}

/*Set LCD configuration registers 2, 3, 4*/
    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);
}

/*Configure LCD control registers 1-5 according to the requirements of STN screen according to the data sheet*/
static void my2440fb_config_stn_lcd_regs(const struct fb_info *fbinfo, struct s3c2410fb_hw *regs)
{
const struct my2440fb_var *fbvar = fbinfo->par;
const struct fb_var_screeninfo *var = &fbinfo->var;

int type = regs->lcdcon1 & ~S3C2410_LCDCON1_TFT;
int hs = var->xres >> 2;
unsigned wdly = (var->left_margin >> 4) - 1;
unsigned wlh = (var->hsync_len >> 4) - 1;

if (type != S3C2410_LCDCON1_STN4)
{
        hs >>= 1;
}

/*Set LCD control register 1 according to color bit mode, refer to data sheet*/
switch (var->bits_per_pixel) 
{
case 1:/*1BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN1BPP;
break;
case 2:/*2BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN2GREY;
break;
case 4:/*4BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN4GREY;
break;
case 8:/*8BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN8BPP;
            hs *= 3;
break;
case 12:/*12BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN12BPP;
            hs *= 3;
break;
default:/*Invalid BPP*/
            dev_err(fbvar->dev, "invalid bpp %d\n", var->bits_per_pixel);
}

/*Set LCD configuration registers 2, 3, 4,Refer to the data sheet*/
if (wdly > 3) wdly = 3;
if (wlh > 3) wlh = 3;
    regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1);

    regs->lcdcon3 = S3C2410_LCDCON3_WDLY(wdly) |
            S3C2410_LCDCON3_LINEBLANK(var->right_margin / 8) |
            S3C2410_LCDCON3_HOZVAL(hs - 1);

    regs->lcdcon4 = S3C2410_LCDCON4_WLH(wlh);
}

/*Configure frame buffer start address registers 1-3, refer to the data sheet*/
static void my2440fb_set_lcdaddr(struct fb_info *fbinfo)
{
unsigned long saddr1, saddr2, saddr3;
struct my2440fb_var *fbvar = fbinfo->par;
void __iomem *regs = fbvar->lcd_base;

    saddr1 = fbinfo->fix.smem_start >> 1;
    saddr2 = fbinfo->fix.smem_start;
    saddr2 += fbinfo->fix.line_length * fbinfo->var.yres;
    saddr2 >>= 1;
    saddr3 = S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH((fbinfo->fix.line_length / 2) & 0x3ff);

    writel(saddr1, regs + S3C2410_LCDSADDR1);
    writel(saddr2, regs + S3C2410_LCDSADDR2);
    writel(saddr3, regs + S3C2410_LCDSADDR3);
}

/*Display blank, blank mode has 5 modes, defined in fb.h, it is an enumeration*/
static int my2440fb_blank(int blank_mode, struct fb_info *fbinfo)
{
struct my2440fb_var *fbvar = fbinfo ->par;
void __iomem *regs = fbvar->lcd_base;

/*Set LCD to start or stop according to the blank display mode*/
if (blank_mode == FB_BLANK_POWERDOWN) 
{
        my2440fb_lcd_enable(fbvar, 0);/*In the ② step */

else 
{
        my2440fb_lcd_enable(fbvar, 1);/*Defined in step ②*/
}

/*Control the temporary palette register according to the blank display mode*/
if (blank_mode == FB_BLANK_UNBLANK)
{
/*Temporary palette register is invalid*/
        writel(0x0, regs + S3C2410_TPAL);
}
else 
{
/*Temporary palette register is valid*/
        writel(S3C2410_TPAL_EN, regs + S3C2410_TPAL);
}

return 0;
}

/ *Set the color table*/
static int my2440fb_setcolreg(unsigned regno,unsigned red,unsigned green,unsigned blue,unsigned transp,struct fb_info *fbinfo)
{
unsigned int val;
struct my2440fb_var *fbvar = fbinfo->par;
void __iomem *regs = fbvar->lcd_base;

switch (fbinfo->fix.visual) 
{
case FB_VISUAL_TRUECOLOR:
/*True color*/
if (regno < 16) 
{
                u32 *pal = fbinfo->pseudo_palette;

                val = chan_to_field(red, &fbinfo->var.red);
                val |= chan_to_field(green, &fbinfo->var.green);
                val |= chan_to_field(blue, &fbinfo- >var.blue);

                pal[regno] = val;
}
break;
case FB_VISUAL_PSEUDOCOLOR:
/*pseudo color*/
if (regno < 256) 
{
                val = (red >> 0) & 0xf800;
                val |= (green >> 5) & 0x07e0;
                val |= (blue >> 11) & 0x001f;

                writel(val, regs + S3C2410_TFTPAL(regno));

/*Modify palette*/
                schedule_palette_update(fbvar, regno, val);
}
break;
default :
return 1;
}

return 0;
}

static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
return chan << bf->offset;
}

/*Modify the palette*/
static void schedule_palette_update(struct my2440fb_var *fbvar, unsigned int regno, unsigned int val)
{
unsigned long flags;
unsigned long irqen;

/*LCD interrupt pending register base address*/
void __iomem *lcd_irq_base = fbvar->lcd_base + S3C2410_LCDINTBASE;

/*Mask the interrupt before modifying the interrupt register value and save the interrupt status to flags*/
    local_irq_save(flags);

    fbvar->palette_buffer[regno] = val;

/*Judge whether the palette is ready*/
if (!fbvar->palette_ready) 
{
        fbvar->palette_ready = 1;

/*Enable interrupt mask register*/
        irqen = readl(lcd_irq_base + S3C24XX_LCDINTMSK);
        irqen &= ~S3C2410_LCDINT_FRSYNC;
        writel(irqen, lcd_irq_base + S3C24XX_LCDINTMSK);
}

/*Restore masked interrupt*/
    local_irq_restore(flags);
}

 
5. Describe the structure of the FrameBuffer device driver example code as a whole:
1. The main things done in Part ① of the code are:
   a. Register the LCD device to the system platform device;
   b. Define the LCD platform device structure lcd_fb_driver.
2. The main things done in Part ② of the code are:
   a. Get and set various resources of LCD platform devices;
   b. Allocate space for the fb_info structure;
   c. Initialize the parameters in the fb_info structure;
   d. Initialize LCD controller;
   e. Check the variable parameters in fb_info;
   f. Apply for display buffer space of the frame buffer device;
   g. Register fb_info.
   a. Implement the hardware interface function to check fb_info related parameters;
   b. Implement the hardware interface function for setting the LCD display mode;
   c. Implement the hardware interface function for LCD display switch (blank), etc.
Keywords:S3C2440 Reference address:LCD driver (FrameBuffer) example development explanation on S3C2440 (Part 2)

Previous article:Detailed explanation of LCD driver (FramBuffer) example development on S3C2440 (Part 1)
Next article:Program structure in ARM assembly language

Recommended ReadingLatest update time:2024-11-16 22:46

UBUNTU10.04 uses NFS file system to mount ARM S3C2440 root directory
There are two elements to successfully mount the NFS file system: 1. The NFS server is running 2. The PORTMAP service is running For the specific principles of the NFS file system, please refer to "Bird Brother's Private Recipe - Server Edition" $sudo tar -xjvf s3c2440_recover
[Microcontroller]
About the serial port number of S3C2440
There is a problem opening the serial communication program. Checking the /etc/mdev.conf file and analyzing the file system startup process in the related document "Mini2440 Linux Porting and Development Practical Guide.pdf", it was found that the original serial port file name had changed. "Mini2440 Linux
[Microcontroller]
ARM-Linux s3c2440 interrupt analysis (Part 2)
Software:        The previous article reviewed the hardware knowledge related to the interrupt controller principle of s3c2440. With this foundation, we can analyze the software analysis method in Linux with a clear mind. Facing the vast Linux source code, what exactly is the interrupt of s3c2440, how to handle it, an
[Microcontroller]
Analysis of S3C2440 camera interface CamInit() function initialization
The most common problem encountered when debugging the 2440 camera interface is the CamInit() function. The code is attached below for analysis. /* Description of Parameters CoDstWidth: Destination Width of Codec Path CoDstHeight: Destination Height of Codec Path PrDstWidth: Destination Width of Preview Path PrDst
[Microcontroller]
S3C2440 framework and startup process
Below is a simple block diagram of S3C2440. We can burn the program to NandFlash and then set it to boot from NandFlash, or we can burn the program to NroFlash and then set it to boot from NorFlash. 1. NorFlash startup: The NorFlash base address is zero, the CPU reads the first instruction on Nor and executes it,
[Microcontroller]
S3C2440 framework and startup process
S3C2440 SPI driver framework
Detailed interpretation of S3C2440 SPI driver code: https://www.linuxidc.com/Linux/2012-08/68402p4.htm 一、platform device and board_info /* /arch/arm/plat-s3c24xx*/ static struct resource s3c_spi0_resource = { = { .start = S3C24XX_
[Microcontroller]
s3c2440 bare metal-UART programming (1. UART hardware introduction and transmission principle)
1. Introduction to uart hardware The full name of UART is Universal Asynchronous Receiver and Transmitter. uart is mainly used for: 1. Print debugging 2.Data transmission The serial port only needs three wires, sending, receiving and ground wires.  TxD of pc - RxD of arm (UART write) TxD of arm - RxD of pc (UART rea
[Microcontroller]
s3c2440 bare metal-UART programming (1. UART hardware introduction and transmission principle)
VGA Display Technology Based on ARM Processor S3C2440
At present, many SOC manufacturers' microprocessor chips have integrated LCD controllers, such as Samsung's S3C2410. S3C2440, Intel's Xscale series, etc. Most embedded systems also use popular LCD display technology. However, in places where large-screen display is required and resolution is not high, such as workshops
[Microcontroller]
VGA Display Technology Based on ARM Processor S3C2440
Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号