platform总线框架+FramBuffer设备驱动框架模板

内核版本: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");

你可能感兴趣的:(#,Device,Drivers,linux)