内核版本:4.14.0
基于设备树
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "xilinx_vtc.h"
#define DEVICE_CNT 1 /* Number of device id */
#define DEVICE_NAME "LCD" /* Device name */
#define COMPAT_PROPT "xilinx,vdmafb" /* Compatible property of the device matched with this driver. */
#define FIX_ID "navigator-lcd" /* Identification string */
/* Device information structure. */
struct fb_lcd_info {
struct fb_info *fb; /* describes a Frame Buffer device */
struct platform_device *pdev; /* platform device */
struct clk *pclk; /* LCD pixel clock */
struct xilinx_vtc *vtc; /* video timing controler */
struct dma_chan *vdma_chan; /* VDMA channel */
int bl_gpio; /* LCD back light gpio */
};
static int fb_lcd_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *fb)
{
u32 tmp;
if (regno >= 16)
return 1;
/* Take the high 8 bits of the 16 bits R, G and B colors passed in */
red >>= 8;
green >>= 8;
blue >>= 8;
tmp = (red << 16) | (green << 8) | blue;
((u32*)(fb->pseudo_palette))[regno] = tmp;
return 0;
}
/*
* Used to check whether the incoming variable parameters are available, because
* the application layer can modify the variable parameters, then after the modification
* is completed, it needs to be checked for reasonableness, @var parameter is the incoming
* variable parameters that need to be checked, @fb means the pointer to the fb_info object
* corresponding to the FrameBuffer device.
*/
static int fb_lcd_check_var(struct fb_var_screeninfo *var, struct fb_info *fb)
{
struct fb_var_screeninfo *fb_var = &fb->var;
memcpy(var, fb_var, sizeof(struct fb_var_screeninfo));
return 0;
}
/* The device operation function structure. */
static struct fb_ops fb_lcd_fops = {
.owner = THIS_MODULE,
/* set color register */
.fb_setcolreg = fb_lcd_setcolreg,
/* checks var and eventually tweaks it to something supported, DO NOT MODIFY PAR */
.fb_check_var = fb_lcd_check_var,
/* Draws a rectangle */
.fb_fillrect = cfb_fillrect,
/* Copy data from area to another */
.fb_copyarea = cfb_copyarea,
/* Draws a image to the display */
.fb_imageblit = cfb_imageblit,
};
static int init_fb_lcd_info_dt(struct fb_lcd_info *fb_lcd, struct videomode *vmode)
{
struct device *dev = &fb_lcd->pdev->dev;
int display_timing;
int ret;
display_timing = 0;
ret = of_get_videomode(dev->of_node, vmode, display_timing);
if (ret < 0)
{
dev_err(dev, "Failed to get videomode from device tree!\n");
return ret;
}
return 0;
}
static int init_fb_lcd_info(struct fb_lcd_info *fb_lcd, struct videomode *vmode)
{
struct device *dev = &fb_lcd->pdev->dev;
struct fb_info *fb = fb_lcd->fb;
unsigned fb_memsize; /* video memory size */
void *fb_virtbase; /* video memory virt base addr */
dma_addr_t fb_physbase; /* video memory phys base addr */
struct fb_videomode fb_vmode = {0};
int ret;
/* Parsing the device tree for LCD timing parameters */
ret = init_fb_lcd_info_dt(fb_lcd, vmode);
if (ret < 0 )
return ret;
/* Request video memory */
fb_memsize = vmode->hactive * vmode->vactive * 3; /* 3 bytes per pixel point */
fb_virtbase = dma_alloc_wc(dev, PAGE_ALIGN(fb_memsize), &fb_physbase, GFP_KERNEL);
if (!fb_virtbase)
return -ENOMEM;
memset(fb_virtbase, 0, fb_memsize);
/* Init struct fb_info */
fb->fbops = &fb_lcd_fops;
fb->flags = FBINFO_FLAG_DEFAULT;
fb->screen_base = fb_virtbase;
fb->screen_size = fb_memsize;
strcpy(fb->fix.id, FIX_ID);
fb->fix.type = FB_TYPE_PACKED_PIXELS;
fb->fix.visual = FB_VISUAL_TRUECOLOR;
fb->fix.accel = FB_ACCEL_NONE;
fb->fix.line_length = vmode->hactive * 3; /* Number of bytes in a line */
fb->fix.smem_start = fb_physbase;
fb->fix.smem_len = fb_memsize;
fb->var.grayscale = 0; /* colourful */
fb->var.nonstd = 0; /* Standard pixel format */
fb->var.activate = FB_ACTIVATE_NOW;
fb->var.accel_flags = FB_ACCEL_NONE;
fb->var.bits_per_pixel = 8 * 3; /* Pixel depth (bits) */
//fb->var.width = xxx; /* Physical width of the LCD screen (in mm) */
//fb->var.height = yyy; /* Physical height of the LCD screen (in mm) */
/* resolution */
fb->var.xres = fb->var.xres_virtual = vmode->hactive;
fb->var.yres = fb->var.yres_virtual = vmode->vactive;
fb->var.xoffset = fb->var.yoffset = 0;
/* bitfield in fb mem if true color,else only length is significant */
fb->var.red.offset = 0;
fb->var.red.length = 8;
fb->var.green.offset = 8;
fb->var.green.length = 8;
fb->var.blue.offset = 16;
fb->var.blue.length = 8;
/* transparency */
fb->var.transp.offset = 0;
fb->var.transp.length = 0;
fb_videomode_from_videomode(vmode, &fb_vmode);
fb_videomode_to_var(&fb->var, &fb_vmode);
return 0;
}
static int init_vtc(struct fb_lcd_info *fb_lcd, struct videomode *vmode)
{
struct device_node *node;
struct device *dev = &fb_lcd->pdev->dev;
/* Parse the device tree to get the vtc node */
node = of_parse_phandle(dev->of_node, "vtc", 0);
if (!node)
{
dev_err(dev, "Failed to parse VTC phandle!\n");
return -ENODEV;
}
/* Get vtc */
fb_lcd->vtc = xilinx_vtc_probe(dev, node);
of_node_put(node); /* Decrement refcount of a node */
if (IS_ERR(fb_lcd->vtc))
{
dev_err(dev, "Failed to probe VTC!\n");
return PTR_ERR(fb_lcd->vtc);
}
/* reset vtc */
xilinx_vtc_reset(fb_lcd->vtc);
/* disable vtc */
xilinx_vtc_disable(fb_lcd->vtc);
/* config vtc */
xilinx_vtc_config_sig(fb_lcd->vtc, vmode);
/* enable vtc */
xilinx_vtc_enable(fb_lcd->vtc);
return 0;
}
static int init_vdma(struct fb_lcd_info *fb_lcd)
{
struct device *dev = &fb_lcd->pdev->dev;
struct dma_interleaved_template dma_template = {0};
struct dma_async_tx_descriptor *tx_desc;
struct xilinx_vdma_config vdma_config = {0};
struct fb_info *fb = fb_lcd->fb;
/* request VDMA channel */
fb_lcd->vdma_chan = of_dma_request_slave_channel(dev->of_node, "lcd_vdma");
if (IS_ERR(fb_lcd->vdma_chan))
{
dev_err(dev, "Fail to request VDMA channel!\n");
return PTR_ERR(fb_lcd->vdma_chan);
}
/* Terminate VDMA channel data transfer */
dmaengine_terminate_all(fb_lcd->vdma_chan);
/* Init VDMA channel */
dma_template.dir = DMA_MEM_TO_DEV; /* Specifies the type of Source and Destination */
dma_template.numf = fb->var.yres; /* Number of frames/lines in this template */
dma_template.sgl[0].size = fb->fix.line_length; /* Number of bytes to read from source */
dma_template.frame_size = 1; /* Number of chunks in a frame/line i.e, size of sgl[] */
dma_template.sgl[0].icg = 0; /* Number of bytes to jump after last src/dst address of
* this chunk and before first src/dst address for next chunk */
dma_template.src_start = fb->fix.smem_start; /* video memory phys base addr */
dma_template.src_sgl = 1; /* If the 'icg' of sgl[] applies to Source (scattered read).
* Otherwise, source is read contiguously (icg ignored). I
* gnored if src_inc is false */
dma_template.src_inc = 1; /* If the source address increments after reading from it */
dma_template.dst_inc = 0; /* If the destination address increments after writing to it */
dma_template.dst_sgl = 0; /* If the 'icg' of sgl[] applies to Destination (scattered write).
* Otherwise, destination is filled contiguously (icg ignored).
* Ignored if dst_inc is false */
tx_desc = dmaengine_prep_interleaved_dma(fb_lcd->vdma_chan, &dma_template, DMA_CTRL_ACK|DMA_PREP_INTERRUPT);
if (!tx_desc)
{
dev_err(dev, "Failed to prepare DMA descriptor!\n");
dma_release_channel(fb_lcd->vdma_chan);
return -1;
}
/* Configure VDMA channel */
vdma_config.park = 1; /* Whether wants to park */
xilinx_vdma_channel_set_config(fb_lcd->vdma_chan, &vdma_config);
/* Start VDMA channel data transfer */
dmaengine_submit(tx_desc);
dma_async_issue_pending(fb_lcd->vdma_chan);
return 0;
}
/*
* @description : Probe function of the platform, it will be executed when the
* platform driver and platform device matching successfully.
* @param -pdev : Pointer to platform device.
* @return : 0: Successful; Others: Failed.
*/
static int framebuf_probe(struct platform_device *pdev)
{
int ret;
struct fb_info *fb;
struct fb_lcd_info *fb_lcd;
struct videomode vmode;
printk(KERN_INFO "%s: driver and device has matched!\n", DEVICE_NAME);
/*
*Instantiates an fb_info structure object, which describes a Frame Buffer device.
* The function has two parameters, the first parameter indicates the size of the
* additional memory requested, and the second parameter indicates the device object
* pointer; the successful request will use fb_info->par to point to the additional
* memory requested.
*/
fb = framebuffer_alloc(sizeof(struct fb_lcd_info), &pdev->dev);
if (!fb)
{
dev_err(&pdev->dev, "Failed to allocate memory for struct fb_info!\n");
return -ENOMEM;
}
fb_lcd = fb->par;
fb_lcd->fb = fb;
fb_lcd->pdev = pdev;
/* Get the required pixel clock for the LCD */
fb_lcd->pclk = devm_clk_get(&pdev->dev, "lcd_pclk");
if (IS_ERR(fb_lcd->pclk))
{
dev_err(&pdev->dev, "Failed to get pixel clock!\n");
ret = PTR_ERR(fb_lcd->pclk);
goto out1;
}
/* Disable output of pixel clock */
clk_disable_unprepare(fb_lcd->pclk);
/* Init struct fb_lcd_info */
ret = init_fb_lcd_info(fb_lcd, &vmode);
if (ret)
goto out1;
/*
* fb_alloc_cmap - allocate a colormap
* @cmap: frame buffer colormap structure
* @len: length of @cmap
* @transp: boolean, 1 if there is transparency, 0 otherwise
* @flags: flags for kmalloc memory allocation
*
* Allocates memory for a colormap @cmap. @len is the
* number of entries in the palette.
*
* Returns negative errno on error, or zero on success.
*/
ret = fb_alloc_cmap(&fb->cmap, 256, 0);
if (ret < 0)
{
dev_err(&pdev->dev, "Failed to allocate color map!\n");
goto out2;
}
/* Fake palette of 16 colors */
fb->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32)*16, GFP_KERNEL);
if (!fb->pseudo_palette)
{
ret = -ENOMEM;
goto out3;
}
/* Set LCD pixel clock, enable clock */
clk_set_rate(fb_lcd->pclk, PICOS2KHZ(fb->var.pixclock)*1000);
clk_prepare_enable(fb_lcd->pclk);
msleep(5);
/* Init video timing controler */
ret = init_vtc(fb_lcd, &vmode);
if (ret)
goto out4;
/* Init LCD VDMA */
ret = init_vdma(fb_lcd);
if (ret)
goto out5;
/* Register FrameBuffer device */
ret = register_framebuffer(fb);
if (ret < 0)
{
dev_err(&pdev->dev, "Failed to register framebuffer device!\n");
goto out6;
}
/* Turn on the LCD backlight */
fb_lcd->bl_gpio = of_get_named_gpio(pdev->dev.of_node, "bl-gpio", 0);
if (!gpio_is_valid(fb_lcd->bl_gpio))
{
dev_err(&pdev->dev, "Failed to get lcd backlight gpio!\n");
ret = fb_lcd->bl_gpio;
goto out7;
}
ret = devm_gpio_request_one(&pdev->dev, fb_lcd->bl_gpio, GPIOF_OUT_INIT_HIGH, "lcd backlight");
if (ret < 0)
goto out7;
platform_set_drvdata(pdev, fb_lcd);
return 0;
out7:
/* Uninstall the FrameBuffer device */
unregister_framebuffer(fb);
out6:
dmaengine_terminate_all(fb_lcd->vdma_chan); /* Terminates all data transfers on the VDMA channel */
dma_release_channel(fb_lcd->vdma_chan); /* Release VDMA channel */
out5:
/* disable video timing controler */
xilinx_vtc_disable(fb_lcd->vtc);
out4:
/* disable output of pixel clock */
clk_disable_unprepare(fb_lcd->pclk);
out3:
/* destroy colormap */
fb_dealloc_cmap(&fb->cmap);
out2:
/* Release video memory */
dma_free_wc(&pdev->dev, fb->screen_size, fb->screen_base, fb->fix.smem_start);
out1:
/* Release struct fb_info */
framebuffer_release(fb);
return ret;
}
/*
* @description : Release some resources. This function will be executed when the platform
* driver module is unloaded.
* @param -dev : Pointer to platform device.
* @return : 0: Successful; Others: Failed.
*/
static int framebuf_remove(struct platform_device *pdev)
{
struct fb_lcd_info *fb_lcd = platform_get_drvdata(pdev);
struct fb_info *fb = fb_lcd->fb;
/* Uninstall the FrameBuffer device */
unregister_framebuffer(fb);
/* Terminates all data transfers on the VDMA channel */
dmaengine_terminate_all(fb_lcd->vdma_chan);
/* Release VDMA channel */
dma_release_channel(fb_lcd->vdma_chan);
/* disable video timing controler */
xilinx_vtc_disable(fb_lcd->vtc);
/* disable output of pixel clock */
clk_disable_unprepare(fb_lcd->pclk);
/* destroy colormap */
fb_dealloc_cmap(&fb->cmap);
/* Release video memory */
dma_free_wc(&pdev->dev, fb->screen_size, fb->screen_base, fb->fix.smem_start);
/* Release struct fb_info */
framebuffer_release(fb);
return 0;
}
static void framebuf_shutdown(struct platform_device *pdev)
{
struct fb_lcd_info *fb_lcd = platform_get_drvdata(pdev);
xilinx_vtc_disable(fb_lcd->vtc);
clk_disable_unprepare(fb_lcd->pclk);
}
/* Match table */
static const struct of_device_id framebuf_of_match[] = {
{.compatible = COMPAT_PROPT},
{/* Sentinel */}
};
/*
* Declare device matching table. Note that this macro is generally used to dynamically
* load and unload drivers for hot-pluggable devices such as USB devices.
*/
MODULE_DEVICE_TABLE(of, framebuf_of_match);
/* Platform driver struct */
static struct platform_driver framebuf_driver = {
.driver = {
.name = DEVICE_NAME, //Drive name, used to match device who has the same name.
.of_match_table = framebuf_of_match, //Used to match the device tree who has the same compatible property.
},
.probe = framebuf_probe, //probe function
.remove = framebuf_remove, //remove function
.shutdown = framebuf_shutdown, //shutdown is called when the system is shutdown/restarted
};
/*
* Register or unregister platform driver,
* and Register the entry and exit functions of the Module.
*/
module_platform_driver(framebuf_driver);
/*
* Author, driver information and LICENSE.
*/
MODULE_AUTHOR("蒋楼丶");
MODULE_DESCRIPTION(DEVICE_NAME" Platform Driver");
MODULE_LICENSE("GPL");