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:
#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 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
/*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);
}
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
- Popular Resources
- Popular amplifiers
- MCU C language programming and Proteus simulation technology (Xu Aijun)
- 100 Examples of Microcontroller C Language Applications (with CD-ROM, 3rd Edition) (Wang Huiliang, Wang Dongfeng, Dong Guanqiang)
- Power Integrated Circuit Technology Theory and Design (Wen Jincai, Chen Keming, Hong Hui, Han Yan)
- Installation and Testing of Analog Electronic Products (Edited by Chen Jingzhong and Tang Mingjun)
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- Innolux's intelligent steer-by-wire solution makes cars smarter and safer
- 8051 MCU - Parity Check
- How to efficiently balance the sensitivity of tactile sensing interfaces
- What should I do if the servo motor shakes? What causes the servo motor to shake quickly?
- 【Brushless Motor】Analysis of three-phase BLDC motor and sharing of two popular development boards
- Midea Industrial Technology's subsidiaries Clou Electronics and Hekang New Energy jointly appeared at the Munich Battery Energy Storage Exhibition and Solar Energy Exhibition
- Guoxin Sichen | Application of ferroelectric memory PB85RS2MC in power battery management, with a capacity of 2M
- Analysis of common faults of frequency converter
- In a head-on competition with Qualcomm, what kind of cockpit products has Intel come up with?
- Dalian Rongke's all-vanadium liquid flow battery energy storage equipment industrialization project has entered the sprint stage before production
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications
- Full range of solutions for embedded cash registers (tax control cash registers) and POS machines (tax control POS machines)
- 【micropython】STM32 will support arbitrary FS
- Global variables in embedded system programming
- MM32F031 Development Board Evaluation 003 FreeRTOS Porting
- PWM generated voltage calculation table
- Help, I need to exchange an E coin, I still need one
- The official WeChat group of the community has been established: you can chat about Microchip live broadcast and PIC microcontrollers
- C6000 Code Generation Tools - Compiler
- Analog Electronics Technology (US) Boylstad
- A graphic guide to UWB's counterattack