i.MX27 SLCDC driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,
I would like to write the driver for SmartLCD controller of i.MX27. This kind of interface needs a LCD with an embedded graphic controller partially controlled by GPIO. I want to link it with an OLED DD12832.

How have I to write this driver : One driver for SLCDC and one driver for DD12832 ? How to link them together ?

Maybe use similar philosophy than soc-camera ?

I have written a driver for kernel 2.6.22 but SLCDC and DD12832 are in the same driver.

Thank you for your help.
Gaëtan Carlier

ps : I attach my previous driver for a better overview (this is a working draft for test purpose)
/*
 * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

/*!
 * @defgroup Framebuffer_MX27 Framebuffer Driver for MX27.
 */

/*!
 * @file mx2fb.c
 *
 * @brief Frame buffer driver for MX27 ADS.
 *
 * @ingroup Framebuffer_MX27
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dmapool.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/arch/mxcfb.h>
#include <asm/arch/mx2fb_slcdc.h>

#include <asm/arch/pmic_power.h>
#include "dd12832_oled.h"

#ifdef CONFIG_PM
static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state);
static int mx2fb_resume(struct platform_device *pdev);
#else
#define mx2fb_suspend		0
#define mx2fb_resume		0
#endif

#define MX2FB_TYPE_BG          0
#define MX2FB_TYPE_GW          1

#define floor8(a) (a&(~0x07))
//#define iceil8(a) (((int)((a+7)/8))*8)
#define iceil8(a) ((int)((a & ~((int)0x03)) + 8))



extern void gpio_slcdc_active(void);
extern void gpio_slcdc_inactive(void);

static char *fb_mode;
static int fb_enabled;
static unsigned long default_bpp = 1;
static unsigned char brightness = 255;
static ATOMIC_NOTIFIER_HEAD(mx2fb_notifier_list);
static struct clk *slcdc_clk;
/*!
 * @brief Structure containing the MX2 specific framebuffer parameters.
 */
struct mx2fb_par {
	int type;
	char *id;
	int registered;
	int blank;
	/* Tell if driver compiled with rotate option enabled */
	int rotate;
	/* Contains displayed data in 1 byte / column (8 pixels)
	 * fb data are stored as 1 byte / pixel
	 * !! must be allocated with 128k alignment using dma_pool_create
	 */
	/* FrameBuffer memory map */
	unsigned char* fb_vmem;
	size_t fb_len;
	dma_addr_t fb_pmem;
	/* Oled cgram memory map */
	unsigned long cgram_cmd_vaddr;
	unsigned long cgram_cmd_paddr;
	unsigned long cgram_cmd_len;
	struct dma_pool *cgram_cmd_dma_pool;
	unsigned long cgram_data_vaddr;
	unsigned long cgram_data_paddr;
	unsigned long cgram_data_len;
	struct dma_pool *cgram_data_dma_pool;
};

/* Framebuffer APIs */
/*static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info);
static int mx2fb_set_par(struct fb_info *info);
static void _set_fix(struct fb_info *info);*/

/* Internal functions */
static int __init _init_fbinfo(struct fb_info *info,
			       struct platform_device *pdev);
static int __init _install_fb(struct fb_info *info,
			      struct platform_device *pdev);
static void __exit _uninstall_fb(struct fb_info *info);
static int _map_video_memory(struct fb_info *info);
static void _unmap_video_memory(struct fb_info *info);
/*static void _enable_lcdc(struct fb_info *info);
static void _disable_lcdc(struct fb_info *info);
static void _update_slcdc(struct fb_info *info);*/

/* Oled display information */
struct fb_videomode mxcfb_modedb[] = {
	{
		/* 128x32 */
		"Densitron DD12832", /* name */
		0, /* refresh */
		128, /* xres */
		32, /* yres */
		0, /* pixclock */
		0, /* left_margin */
		0, /* right_margin */
		0, /* upper_margin */
		0, /* lower_margin */
		0, /* hsync_len */
		0, /* vsync_len */
		0, /* sync */
		0, /* mode */
		0}, /* flag */
};
int mxcfb_modedb_sz = ARRAY_SIZE(mxcfb_modedb);

struct mx2fb_par mx2fbp_bg = {
	.type = MX2FB_TYPE_BG,
	.id = "DISP0 BG",
	.registered = 0,
#ifdef CONFIG_FB_MXC_DENSITRON_DD12832_ROTATE
	.rotate = 1,
#else
	.rotate = 0,
#endif
};

/*!
 * @brief Framebuffer information structures.
 * There are up to 3 framebuffers: background, TVout, and graphic window.
 * If graphic window is configured, it must be the last framebuffer.
 */
static struct fb_info mx2fb_info = {
	.par = &mx2fbp_bg,
};

/*!
 * Do a minimal setup of SLCDC to be able to send command to DD12832
 */
static void slcdc_first_init(void)
{
	unsigned long val;
	int i;
	unsigned long *pdata;

	/* Screen start address register */
	__raw_writel(mx2fbp_bg.cgram_data_paddr, SLCDC_REG(SLCDC_DATABASEADDR));
	__raw_writel(mx2fbp_bg.cgram_data_len, SLCDC_REG(SLCDC_DATABUFSIZE));

	/* Copy command array to DMA area */
	pdata = (unsigned long *)mx2fbp_bg.cgram_cmd_vaddr;
	for (i = 0; i < mx2fbp_bg.cgram_cmd_len; i++) {
		if ((i & 0x01) == 0) {
			/* If even offset, command must be left-aligned in
			 * 32-bits memory space */
			*pdata = (unsigned long)_ssd1305_pagecmd_array[i] << 16;
		} else {
			/* If odd offset, command must be right-aligned in
			 * 32-bits memory space */
			*pdata |= (unsigned long)_ssd1305_pagecmd_array[i];
			/* When "right column" is filled, go to next address */
			pdata++;
		}
	}
	/* Set Array of command for page addressing */
	__raw_writel(mx2fbp_bg.cgram_cmd_paddr, SLCDC_REG(SLCDC_COMBASEADDR));
	__raw_writel(mx2fbp_bg.cgram_cmd_len, SLCDC_REG(SLCDC_COMBUFSIZE));

	/* Set number of command (words) that must be send to jump to a
	 * specific page. Size of "word" is define by WORDDEFCOM flag in
	 * LCDTRANSCONFIG register */
	__raw_writel(PAGE_COMMAND_PACK_SIZE, SLCDC_REG(SLCDC_COMSTRINGSIZ));

	/* Define DMA burst */
	__raw_writel(0, SLCDC_REG(SLCDC_FIFOCONFIG));

	/* Define number of column/segment in a page */
	__raw_writel(OLED_WIDTH, SLCDC_REG(SLCDC_LCDCONFIG));

	/* Set transfer configuration */
	val = SLCDC_DATA_8BIT | SLCDC_COMMAND_8BIT | SLCDC_PARALLEL |
		SLCDC_WRITEDATA_8BIT | SLCDC_TRANS_LITLENDIAN_8BIT |
		SLCDC_CSPOL_LOW;
	__raw_writel(val, SLCDC_REG(SLCDC_LCDTRANSCONFIG));

	/* Set control register */
	val = SLCDC_MODE_COMMAND | SLCDC_IRQ_DISABLE | SLCDC_IRQ_FLAGS_MASK;
	__raw_writel(val, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));

	/* Set SLCDC clock divider = HCLK_SLCDC * val / 128 */
	val = 40;
	__raw_writel(val, SLCDC_REG(SLCDC_LCDCLOCKCONFIG));
}


/*
 * Send one command to oled via SLCDC
 */
static int _slcdc_sendcmd_single(unsigned char cmd)
{
	while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK);
	__raw_writel((u32)cmd | WRITE_LCDCMD, SLCDC_REG(SLCDC_LCDWRITEDATA));
	return 0;
}

/*
 * Send one data to oled via SLCDC
 */
static int _slcdc_senddata_single(unsigned char cmd)
{
	while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK);
	__raw_writel((u32)cmd | WRITE_LCDDATA, SLCDC_REG(SLCDC_LCDWRITEDATA));
	return 0;
}

/*
 * Set Start line for Pan function
 */
static void dd12832_set_start_line(unsigned char y)
{
	_slcdc_sendcmd_single(SSD1305_CMD_ROWADDR | (y & 0x3F));
}

/*
 * Set page address
 */
static void dd12832_set_yaddr(unsigned char y)
{
	_slcdc_sendcmd_single(SSD1305_CMD_PAGEADDR | (y & 0x07));
}

/*
 * Set segment address
 */
static void dd12832_set_xaddr(unsigned char x)
{
#ifndef CONFIG_FB_MXC_DENSITRON_DD12832_ROTATE
	_slcdc_sendcmd_single(SSD1305_CMD_HIGHCOLADDR | (x >> 4));
	_slcdc_sendcmd_single(SSD1305_CMD_LOWCOLADDR | (x & 0x0F));
#else
	x += SSD1305_WIDTH - OLED_WIDTH;
	_slcdc_sendcmd_single(SSD1305_CMD_HIGHCOLADDR | (x >> 4));
	_slcdc_sendcmd_single(SSD1305_CMD_LOWCOLADDR | (x & 0x0F));
#endif
}

/*
 * Modify contrast value
 */
static void dd12832_set_brightness(unsigned char level)
{
	if (level == 0) {
		/* If level 0 asked, display must be turned off
		* because Display is still ON when Contrast
		* is set to 0 */
		_slcdc_sendcmd_single(SSD1305_CMD_DISPLAY_POWER_OFF);
	} else {
		/* Apply new contrast */
		_slcdc_sendcmd_single(SSD1305_CMD_BRIGHTNESS_MODE);
		_slcdc_sendcmd_single(level);
		/* Be sure that DISPLAY is ON */
		_slcdc_sendcmd_single(SSD1305_CMD_DISPLAY_POWER_ON);
	}
}

/*
 * Clear internal RAM of controller
 */
static void dd12832_clear_lcd(void)
{
	unsigned long status;
	/* Clear OLED RAM mirror buffer */
	char *pdata = (char *)mx2fbp_bg.cgram_data_vaddr;
	memset(pdata, 0, mx2fbp_bg.cgram_data_len);

	/* Clear FrameBuffer mirror buffer */
	pdata = (char __force *) mx2fbp_bg.fb_vmem;
	memset(pdata, 0, mx2fbp_bg.fb_len);

	dd12832_set_yaddr(0);
	dd12832_set_xaddr(0);
	while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK);
	status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
	status &= ~(SLCDC_SLCDCCTRLSTAT_AUTOMODE_MASK);
	status |= SLCDC_MODE_DATA | SLCDC_START_TRANSFERT;
	__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
}

/*
 * Send init sequence to display controller
 */
static int dd12832_init_controller(void)
{
	/* Use SLCDC to send command block */
	unsigned long i;

	for (i = 0; i < sizeof(_ssd1305_init_array); i++) {
		_slcdc_sendcmd_single(_ssd1305_init_array[i]);
	}
	dd12832_clear_lcd();
	return 0;
}

/*!
 * @brief Enable LCD controller.
 * @param info	framebuffer information pointer
 */
static void _enable_slcdc(struct fb_info *info)
{
	if (!fb_enabled) {
		fb_enabled++;

		if (fb_mode) {
			unsigned long mode = 0;
			if (mode == 0) {
				dd12832_set_brightness(brightness);
			}
		}
	}
}

/*!
 * @brief Disable LCD controller.
 * @param info	framebuffer information pointer
 */
static void _disable_slcdc(struct fb_info *info)
{
	if (fb_enabled) {
		dd12832_set_brightness(0);
		fb_enabled = 0;
	}
}

/*!
 * @brief Update SLCDC registers
 * @param info	framebuffer information pointer
 */
static void _update_slcdc(struct fb_info *info)
{
	unsigned long base;
	unsigned long val;
	struct fb_var_screeninfo *var = &info->var;

	base = (var->yoffset * var->xres_virtual + var->xoffset);
	base += (unsigned long)info->screen_base;

	/* Set number of command (words) that must be send to jump to a
	 * specific page. Size of "word" is define by WORDDEFCOM flag in
	 * LCDTRANSCONFIG register */
	__raw_writel(PAGE_COMMAND_PACK_SIZE, SLCDC_REG(SLCDC_COMSTRINGSIZ));

	/* Define DMA burst */
	__raw_writel(0, SLCDC_REG(SLCDC_FIFOCONFIG));

	/* Define number of column/segment in a page */
	__raw_writel(OLED_WIDTH, SLCDC_REG(SLCDC_LCDCONFIG));

	/* Set transfer configuration */
	val = SLCDC_DATA_8BIT | SLCDC_COMMAND_8BIT | SLCDC_PARALLEL |
		SLCDC_WRITEDATA_8BIT | SLCDC_TRANS_LITLENDIAN_8BIT |
		SLCDC_CSPOL_LOW;
	__raw_writel(val, SLCDC_REG(SLCDC_LCDTRANSCONFIG));

	/* Set SLCDC clock divider = HCLK_SLCDC * val / 128 */
	val = 60;
	__raw_writel(val, SLCDC_REG(SLCDC_LCDCLOCKCONFIG));

	return;
}


/*!
 * @brief Blanks the display.
 *
 * @param blank_mode	The blank mode we want.
 * @param info		Frame buffer structure that represents a single frame buffer
 *
 * @return		Negative errno on error, or zero on success.
 *
 * Blank the screen if blank_mode != 0, else unblank. Return 0 if blanking
 * succeeded, != 0 if un-/blanking failed.
 * blank_mode == 2: suspend vsync
 * blank_mode == 3: suspend hsync
 * blank_mode == 4: powerdown
 */
static int mx2fb_blank(int blank_mode, struct fb_info *info)
{
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;

	dev_dbg(info->device, "blank mode = %d\n", blank_mode);

	mx2fbp->blank = blank_mode;

	switch (blank_mode) {
	case FB_BLANK_POWERDOWN:
	case FB_BLANK_VSYNC_SUSPEND:
	case FB_BLANK_HSYNC_SUSPEND:
	case FB_BLANK_NORMAL:
		_disable_slcdc(info);
		break;
	case FB_BLANK_UNBLANK:
		_enable_slcdc(info);
		break;
	}

	return 0;
}

/*
 * here we start the process of spliting out the fb update into
 * individual blocks of pixels. we end up spliting into 64x64 blocks
 * and finally down to 64x8 pages.
 */
static void dd12832_cgram_update(struct fb_info *info, unsigned int dx,
					unsigned int dy, unsigned int w, unsigned int h)
{
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
	unsigned int startpage, endpage, startseg, endseg;
	unsigned int curpage, curseg;
	unsigned long status;
	int i;
	unsigned char myseg;
	char *cgram;
	unsigned char *ppixel;

	/* align the request first */
	/* Get first line of the page where starting pixel is located */
	startpage = floor8(dy) / 8;
	/* Get first line of the next page where last pixel to update
	 * is located */
	endpage = h + dy - 1;
	endpage = iceil8(endpage) / 8;

	/* First segment to update */
	startseg = dx;
	/* Last segment to update */
	endseg = dx + (w - 1);

	for (curpage = startpage; curpage < endpage; curpage++) {
		cgram = (char*)mx2fbp->cgram_data_vaddr;
		cgram += curpage * info->fix.line_length;
		cgram += startseg;
		for (curseg = startseg; curseg <= endseg; curseg++) {
			/* Get segment to update in video memory */
			myseg = 0;
			ppixel = (unsigned char __force *) mx2fbp->fb_vmem;
			/* There is 8 lines per page */
			ppixel += curpage * info->fix.line_length * 8;
			/* Points to upper pixel of the current segment */
			ppixel += curseg;
			for (i = 0; i < 8; i++) {
				if (*ppixel != 0) {
					myseg |= 1 << i;
				}
				/* Jump to next line to get pixel below */
				ppixel += info->fix.line_length;
			}
			*cgram = myseg;
			cgram++;
		}
	}
	dd12832_set_xaddr(0);
	dd12832_set_yaddr(0);
	while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK);
	status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
	status &= ~(SLCDC_SLCDCCTRLSTAT_AUTOMODE_MASK);
	status |= SLCDC_MODE_DATA | SLCDC_START_TRANSFERT;
	__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
}



static void mx2fb_fillrect(struct fb_info *info,
			   const struct fb_fillrect *rect)
{
	sys_fillrect(info, rect);

	/* update the physical lcd */
	dd12832_cgram_update(info, rect->dx, rect->dy, rect->width, rect->height);
}

static void mx2fb_copyarea(struct fb_info *info,
			   const struct fb_copyarea *area)
{
	sys_copyarea(info, area);

	/* update the physical lcd */
	dd12832_cgram_update(info, area->dx, area->dy, area->width, area->height);
}

static void mx2fb_imageblit(struct fb_info *info, const struct fb_image *image)
{
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
	char *ppixel;
	const char *pdata;
	int j;

	if (image->depth == 1) {
		/* Draw image */
		pdata = image->data;
		ppixel = mx2fbp->fb_vmem;
		ppixel += image->dy * info->fix.line_length;
		ppixel += image->dx;
		for (j = 0; j < image->height; j++) {
			memcpy(ppixel, pdata, image->width);
			pdata += image->width;
			ppixel += info->fix.line_length;
		}
		/* update the physical lcd */
		dd12832_cgram_update(info, image->dx, image->dy, image->width, image->height);
	}
}


/*!
 * @brief Pans the display.
 *
 * @param var	Frame buffer variable screen structure
 * @param info	Frame buffer structure that represents a single frame buffer
 *
 * @return	Negative errno on error, or zero on success.
 *
 * Pan (or wrap, depending on the `vmode' field) the display using the
 * 'xoffset' and 'yoffset' fields of the 'var' structure. If the values
 * don't fit, return -EINVAL.
 */
static int mx2fb_pan_display(struct fb_var_screeninfo *var,
			     struct fb_info *info)
{
	if (info->var.yoffset == var->yoffset) {
		return 0;	/* No change, do nothing */
	}

        if ((var->vmode & FB_VMODE_YWRAP) && (var->yoffset < OLED_WIDTH)
                && (info->var.yres <= OLED_WIDTH)) {
                dd12832_set_start_line(var->yoffset);
                info->var.yoffset = var->yoffset;
                return 0;
        }

        return -EINVAL;
}

static int mx2fb_sync(struct fb_info *info)
{
	dd12832_cgram_update(info, 0, 0, info->fix.line_length, info->var.yres_virtual);
	return 0;
}

/*!
 * @brief Ioctl function to support customized ioctl operations.
 *
 * @param info	Framebuffer structure that represents a single frame buffer
 * @param cmd	The command number
 * @param arg	Argument which depends on cmd
 *
 * @return	Negative errno on error, or zero on success.
 */
static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd,
		       unsigned long arg)
{
	unsigned char level;

	switch (cmd) {
	case MX2FB_SET_BRIGHTNESS:
		if (copy_from_user((void *)&level, (void *)arg, sizeof(level)))
			return -EFAULT;
		brightness = level;
		dd12832_set_brightness(level);
		break;
	case MX2FB_FORCE_SYNC:
		mx2fb_sync(info);
		break;
	default:
		dev_dbg(info->device, "Unknown ioctl command (0x%08X)\n", cmd);
		return -EINVAL;
	}

	return 0;
}

/*!
 * @brief Set fixed framebuffer parameters based on variable settings.
 *
 * @param info	framebuffer information pointer
 * @return	Negative errno on error, or zero on success.
 */
static void _set_fix(struct fb_info *info)
{
	struct fb_fix_screeninfo *fix = &info->fix;
	struct fb_var_screeninfo *var = &info->var;
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;

	strncpy(fix->id, mx2fbp->id, strlen(mx2fbp->id));
	if (var->bits_per_pixel < 8)
		fix->line_length = var->xres_virtual;
	else
		fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
	fix->type = FB_TYPE_PACKED_PIXELS;
	fix->accel = FB_ACCEL_NONE;
	fix->visual = FB_VISUAL_MONO10;
	fix->xpanstep = 0;
	fix->ypanstep = 1;
	fix->ywrapstep = 0;
}

/*!
 * @brief Validates a var passed in.
 *
 * @param var	Frame buffer variable screen structure
 * @param info	Frame buffer structure that represents a single frame buffer
 *
 * @return	Negative errno on error, or zero on success.
 *
 * Checks to see if the hardware supports the state requested by var passed
 * in. This function does not alter the hardware state! If the var passed in
 * is slightly off by what the hardware can support then we alter the var
 * PASSED in to what we can do. If the hardware doesn't support mode change
 * a -EINVAL will be returned by the upper layers.
 *
 */
static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
	if (var->xres_virtual < var->xres)
		var->xres_virtual = var->xres;
	if (var->yres_virtual < var->yres)
		var->yres_virtual = var->yres;

	if (var->xoffset < 0)
		var->xoffset = 0;

	if (var->yoffset < 0)
		var->yoffset = 0;

	if (var->xoffset + info->var.xres > info->var.xres_virtual)
		var->xoffset = info->var.xres_virtual - info->var.xres;

	if (var->yoffset + info->var.yres > info->var.yres_virtual)
		var->yoffset = info->var.yres_virtual - info->var.yres;

	if (var->bits_per_pixel != default_bpp)
		var->bits_per_pixel = default_bpp;

	if (var->pixclock != 0)
		var->pixclock = 0;
	var->red.length = var->green.length = var->blue.length = 1;
	var->red.offset = var->green.offset = var->blue.offset = 0;
	var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0;

	var->transp.length = 0;
	var->transp.offset = 0;
	var->transp.msb_right = 0;

	var->height = -1;
	var->width = -1;
	var->grayscale = 0;

	/* Copy nonstd field to/from sync for fbset usage */
	var->sync |= var->nonstd;
	var->nonstd |= var->sync;

	return 0;
}

/*!
 * @brief Alters the hardware state.
 *
 * @param info	Frame buffer structure that represents a single frame buffer
 *
 * @return Zero on success others on failure
 *
 * Using the fb_var_screeninfo in fb_info we set the resolution of this
 * particular framebuffer. This function alters the fb_fix_screeninfo stored
 * in fb_info. It doesn't not alter var in fb_info since we are using that
 * data. This means we depend on the data in var inside fb_info to be
 * supported by the hardware. mx2fb_check_var is always called before
 * mx2fb_set_par to ensure this.
 */
static int mx2fb_set_par(struct fb_info *info)
{
	unsigned long len;
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;

	_set_fix(info);

	len = info->var.yres_virtual * info->fix.line_length;
	if (len > info->fix.smem_len) {
		if (info->fix.smem_start)
			_unmap_video_memory(info);

		/* Memory allocation for framebuffer */
		if (_map_video_memory(info)) {
			dev_err(info->device, "Unable to allocate fb memory\n");
			return -ENOMEM;
		}
	}

	_update_slcdc(info);

	mx2fb_blank(mx2fbp->blank, info);

	return 0;
}

/*
 * this is the access path from userspace. they can seek and write to
 * the fb. it's inefficient for them to do anything less than 64*8
 * writes since we update the lcd in each write() anyway.
 */
static ssize_t mx2fb_write(struct fb_info *info, const char __user *buf,
			   size_t count, loff_t *ppos)
{
	/* modded from epson 1355 */

	unsigned long p;
	int err=-EINVAL;
	unsigned int fbmemlength, x, y, w, h, startpage, endpage;
	struct arcfb_par *par;
	unsigned int xres;

	p = *ppos;
	par = info->par;
	xres = info->var.xres;
	fbmemlength = info->fix.smem_len;

	if (p > fbmemlength)
		return -ENOSPC;

	err = 0;
	if ((count + p) > fbmemlength) {
		count = fbmemlength - p;
		err = -ENOSPC;
	}

	if (count) {
		char *base_addr;

		base_addr = (char __force *)info->screen_base;
		count -= copy_from_user(base_addr + p, buf, count);
		*ppos += count;
		err = -EFAULT;
	}

	/* Check how many page are affected */
	startpage = floor8(p) / 8;
	endpage = iceil8((p + count)) / 8;
	x = p % info->fix.line_length;
	y = p / info->fix.line_length;
	w = count % info->fix.line_length;
	h = count / info->fix.line_length;
	if (startpage != (endpage - 1)) {
		/* If several page affected, update complete line
		 * of all affected pages */
		dd12832_cgram_update(info, 0, y, info->fix.line_length, h);
	} else {
		/* One page has been affected, only updates modified segment
		 */
		dd12832_cgram_update(info, x, y, w, h);
	}
	if (p+count > info->fix.line_length) {
		/* Several line must be updated */
	}
	if (count)
		return count;
	return err;
}

/* fb_mmap
 * Map video memory in user space. We don't use the generic fb_mmap method
 * mainly
 * to allow the use of the TLB streaming flag (CCA=6)
 */
int mx2fb_mmap(struct fb_info *info /*fbi*/, struct vm_area_struct *vma)
{
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
	unsigned int len;
	unsigned long start=0, off;

	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) {
		return -EINVAL;
	}

	start = mx2fbp->fb_pmem & PAGE_MASK;
	len = PAGE_ALIGN((start & ~PAGE_MASK) + mx2fbp->fb_len);

	off = vma->vm_pgoff << PAGE_SHIFT;

	if ((vma->vm_end - vma->vm_start + off) > len) {
		return -EINVAL;
	}

	off += start;
	vma->vm_pgoff = off >> PAGE_SHIFT;

	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
	pgprot_val(vma->vm_page_prot) |= (6 << 9);

	vma->vm_flags |= VM_IO;

	if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
				vma->vm_end - vma->vm_start,
				vma->vm_page_prot)) {
		return -EAGAIN;
	}
	return 0;
}


/*!
 * @brief Framebuffer file operations
 */
static struct fb_ops mx2fb_ops = {
	.owner = THIS_MODULE,
	//.fb_read        = fb_sys_read,
	.fb_write 	= mx2fb_write,
	.fb_check_var 	= mx2fb_check_var,
	.fb_set_par 	= mx2fb_set_par,
	.fb_blank 	= mx2fb_blank,
	.fb_pan_display	= mx2fb_pan_display,
	.fb_fillrect	= mx2fb_fillrect,
	.fb_copyarea	= mx2fb_copyarea,
	.fb_imageblit	= mx2fb_imageblit,
	.fb_ioctl	= mx2fb_ioctl,
	.fb_sync	= mx2fb_sync,
	.fb_mmap	= mx2fb_mmap,
};

/*!
 * @brief Initialize framebuffer information structure.
 *
 * @param info	framebuffer information pointer
 * @param pdev	pointer to struct device
 * @return	Negative errno on error, or zero on success.
 */
static int __init _init_fbinfo(struct fb_info *info,
			       struct platform_device *pdev)
{
	info->device = &pdev->dev;
	info->var.activate = FB_ACTIVATE_NOW;
	info->fbops = &mx2fb_ops;
	info->flags = FBINFO_FLAG_DEFAULT;
	info->pseudo_palette = NULL;

	return 0;
}

/*!
 * @brief Install framebuffer into the system.
 *
 * @param info	framebuffer information pointer
 * @param pdev  pointer to struct device
 * @return	Negative errno on error, or zero on success.
 */
static int __init _install_fb(struct fb_info *info,
			      struct platform_device *pdev)
{
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;

	if (_init_fbinfo(info, pdev))
		return -EINVAL;

	if (fb_mode == 0)
		fb_mode = pdev->dev.platform_data;

	if (!fb_find_mode(&info->var, info, fb_mode, mxcfb_modedb,
			  mxcfb_modedb_sz, NULL, default_bpp)) {
		return -EBUSY;
	}

	/* Default Y virtual size is 2x panel size */
	/* info->var.yres_virtual = info->var.yres << 1; */

	if (mx2fbp->type == MX2FB_TYPE_GW)
		mx2fbp->blank = FB_BLANK_NORMAL;
	else
		mx2fbp->blank = FB_BLANK_UNBLANK;

	if (mx2fb_set_par(info)) {
		return -EINVAL;
	}

	if (register_framebuffer(info) < 0) {
		_unmap_video_memory(info);
		return -EINVAL;
	}

	mx2fbp->registered = 1;

	dev_info(info->device, "fb%d registered successfully on %s (rotate = %d).\n",
		 info->node, fb_mode, mx2fbp->rotate);
	return 0;
}

/*!
 * @brief Uninstall framebuffer from the system.
 *
 * @param info	framebuffer information pointer
 */
static void __exit _uninstall_fb(struct fb_info *info)
{
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;

	if (!mx2fbp->registered)
		return;

	unregister_framebuffer(info);
	_unmap_video_memory(info);
	if (&info->cmap)
		fb_dealloc_cmap(&info->cmap);

	mx2fbp->registered = 0;
}

/*!
 * @brief Allocate memory for framebuffer.
 *
 * @param info	framebuffer information pointer
 * @return	Negative errno on error, or zero on success.
 */
static int _map_video_memory(struct fb_info *info)
{
	unsigned long page;
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
	mx2fbp->fb_len = info->fix.line_length * info->var.yres_virtual;

	mx2fbp->fb_vmem = dma_alloc_coherent(info->device, PAGE_ALIGN(mx2fbp->fb_len),
			&mx2fbp->fb_pmem, GFP_KERNEL);
	if (mx2fbp->fb_vmem == 0) {
		dev_err(info->device, "fail to allocate frambuffer (size: %dK))",
				mx2fbp->fb_len / 1024);
		return -ENOMEM;
	}

	info->screen_base = mx2fbp->fb_vmem;
	info->fix.smem_start = mx2fbp->fb_pmem;
	info->fix.smem_len = mx2fbp->fb_len;

	dev_dbg(info->device, "Allocated fb @ paddr=0x%08lX, size=%d.\n",
		info->fix.smem_start, info->fix.smem_len);

	info->screen_size = info->fix.smem_len;

	/* Clear the screen */
	memset((char *)mx2fbp->fb_vmem, 0x00, mx2fbp->fb_len);

	/*
	 * Set page reserved so that mmap will work. This is necessary
	 * since we'll be remapping normal memory.
	 */
	for (page = (unsigned long)mx2fbp->fb_vmem;
	     page < PAGE_ALIGN((unsigned long)mx2fbp->fb_vmem + mx2fbp->fb_len);
	     page += PAGE_SIZE) {
#ifdef CONFIG_DMA_NONCOHERENT
		SetPageReserved(virt_to_page(CAC_ADDR(page)));
#else
		SetPageReserved(virt_to_page(page));
#endif
	}

	return 0;
}

/*!
 * @brief Release memory for framebuffer.
 * @param info	framebuffer information pointer
 */
static void _unmap_video_memory(struct fb_info *info)
{
	struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
	if (mx2fbp->fb_vmem) {
		dma_free_noncoherent(info->device, mx2fbp->fb_len, mx2fbp->fb_vmem, mx2fbp->fb_pmem);
	}
	mx2fbp->fb_len = 0;
	mx2fbp->fb_pmem = 0;
	mx2fbp->fb_vmem = 0;
	info->screen_base = 0;
	info->fix.smem_start = 0;
	info->fix.smem_len = 0;
	/* Free DMA of SLCDC transfert */
	dma_pool_free(mx2fbp->cgram_data_dma_pool, (void *)mx2fbp->cgram_data_vaddr, (dma_addr_t) mx2fbp->cgram_data_paddr);
	dma_pool_destroy(mx2fbp->cgram_data_dma_pool);
	mx2fbp->cgram_data_dma_pool = 0;
	mx2fbp->cgram_data_vaddr = 0;
	mx2fbp->cgram_data_paddr = 0;
	mx2fbp->cgram_data_len = 0;
	/* Free DMA of SLCDC transfert */
	dma_pool_free(mx2fbp->cgram_cmd_dma_pool, (void *)mx2fbp->cgram_cmd_vaddr, (dma_addr_t) mx2fbp->cgram_cmd_paddr);
	dma_pool_destroy(mx2fbp->cgram_cmd_dma_pool);
	mx2fbp->cgram_cmd_dma_pool = 0;
	mx2fbp->cgram_cmd_vaddr = 0;
	mx2fbp->cgram_cmd_paddr = 0;
	mx2fbp->cgram_cmd_len = 0;
}

/*
 * @brief LCDC interrupt handler
 */
static irqreturn_t mx2fb_isr(int irq, void *dev_id)
{
	struct fb_event event;
	unsigned long status;

	status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
	status &= SLCDC_IRQ_FLAGS_MASK;

	if (status & SLCDC_IRQ_FLAG) {
		printk("Oled xfer done : ");
		if (status == SLCDC_IRQ_FLAG) {
			printk("Successful\n");
		}
		if (status & SLCDC_IRQ_TEA_FLAG) {
			printk("DMA error\n");
			event.info = &mx2fb_info;
			atomic_notifier_call_chain(&mx2fb_notifier_list,
					FB_EVENT_MXC_DMA_ERROR, &event);
		}
		if (status & SLCDC_IRQ_UNDRFLOW_FLAG) {
			printk("Underflow occurs\n");
			event.info = &mx2fb_info;
			atomic_notifier_call_chain(&mx2fb_notifier_list,
					FB_EVENT_MXC_UNDERFLOW, &event);
		}
	}

	/* Write 1 to clear the status */
	status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
	status |= SLCDC_IRQ_FLAGS_MASK;
	__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
	
	return IRQ_HANDLED;
}

/*!
 * @brief Config and request LCDC interrupt
 */
static void _request_irq(void)
{
	unsigned long status;
	unsigned long flags;

	/* Write 1 to clear the status */
	status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
	status |= SLCDC_IRQ_FLAGS_MASK;
	__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));

	if (request_irq(INT_SLCDC, mx2fb_isr, 0, "SLCDC", 0))
		pr_info("Request LCDC IRQ failed.\n");
	else {
		spin_lock_irqsave(&mx2fb_notifier_list.lock, flags);

		/* Enable interrupt in case client has registered */
		//if (mx2fb_notifier_list.head != NULL) {
			//unsigned long status;
			//unsigned long ints = MX2FB_INT_EOF;

			//ints |= MX2FB_INT_GW_EOF;

			/* Enable interrupt in case client has registered */
			//if (mx2fb_notifier_list.head != NULL) {
				/* Write 1 to clear the status */
				status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
				status |= SLCDC_IRQ_FLAGS_MASK;
				__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));

				/* Enable SLCDC interrupt */
				status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
				status |= SLCDC_IRQ_ENABLE;
				__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
			//}
		//}

		spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags);
	}
}

/*!
 * @brief Free LCDC interrupt handler
 */
static void _free_irq(void)
{
	unsigned long status;
	status =__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
	/* Write 1 to clear the status */
	status |= SLCDC_IRQ_FLAGS_MASK;
	/* Disable all LCDC interrupt */
	status &= ~SLCDC_IRQ_ENABLE;
	__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));

	free_irq(INT_SLCDC, 0);
}

/*!
 * @brief Register a client notifier
 * @param nb	notifier block to callback on events
 */
int mx2fb_register_client(struct notifier_block *nb)
{
	unsigned long flags, status;
	int ret;

	ret = atomic_notifier_chain_register(&mx2fb_notifier_list, nb);

	spin_lock_irqsave(&mx2fb_notifier_list.lock, flags);

	/* Enable interrupt in case client has registered */
	if (mx2fb_notifier_list.head != NULL) {
		/* Write 1 to clear the status */
		status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
		status |= SLCDC_IRQ_FLAGS_MASK;
		__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));

		/* Enable SLCDC interrupt */
		status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
		status |= SLCDC_IRQ_ENABLE;
		__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
	}
	spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags);

	return ret;
}

/*!
 * @brief Unregister a client notifier
 * @param nb	notifier block to callback on events
 */
int mx2fb_unregister_client(struct notifier_block *nb)
{
	unsigned long flags, status;
	int ret;

	ret = atomic_notifier_chain_unregister(&mx2fb_notifier_list, nb);

	spin_lock_irqsave(&mx2fb_notifier_list.lock, flags);

	/* Mask interrupt in case no client registered */
	if (mx2fb_notifier_list.head == NULL) {
		/* Enable SLCDC interrupt */
		status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
		status &= ~(SLCDC_IRQ_ENABLE);
		__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
	}

	spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags);

	return ret;
}

#ifdef CONFIG_PM
/*
 * Power management hooks. Note that we won't be called from IRQ context,
 * unlike the blank functions above, so we may sleep.
 */

/*!
 * @brief Suspends the framebuffer and blanks the screen.
 * Power management support
 */
#ifdef CONFIG_FB_MXC_EPSON_L4F0024
static int mx2fb_spi_suspend(struct spi_device *spi, pm_message_t state)
{
#else
static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state)
{
#endif
	_disable_slcdc(&mx2fb_info);

	return 0;
}

/*!
 * @brief Resumes the framebuffer and unblanks the screen.
 * Power management support
 */
#ifdef CONFIG_FB_MXC_EPSON_L4F0024
static int mx2fb_spi_resume(struct spi_device *spi)
#else
static int mx2fb_resume(struct platform_device *pdev)
#endif
{
	_enable_slcdc(&mx2fb_info);
	return 0;
}
#endif				/* CONFIG_PM */

/*!
 * @brief Probe routine for the framebuffer driver. It is called during the
 *        driver binding process.
 *
 * @return Appropriate error code to the kernel common code
 */
static int mx2fb_probe(struct platform_device *pdev)
{
	int ret;

	slcdc_clk = clk_get(&pdev->dev, "slcdc_clk");
	clk_enable(slcdc_clk);

	gpio_slcdc_active();

	/* Memory allocation of display data for DMA transfert */
	/* convert 1 byte / pixel length to 1 byte / column (8 pixels) */
	mx2fbp_bg.cgram_data_len = OLED_WIDTH*OLED_HEIGHT / 8;
	/* Allocate memory for CGRAM mirror with 128k boundary
	 * (cf note SLCDC in Ch. 44.2 of i.MX27 RM) aligned on 1 byte */
	mx2fbp_bg.cgram_data_dma_pool = dma_pool_create("SLCDC_DMA_DATA", &pdev->dev, mx2fbp_bg.cgram_data_len, 1, 128*1024);
	if (mx2fbp_bg.cgram_data_dma_pool == NULL) {
		dev_err(&pdev->dev, "Unable to allocated DMA Pool.\n");
		return -ENOMEM;
	}
	mx2fbp_bg.cgram_data_vaddr = (unsigned long)dma_pool_alloc(mx2fbp_bg.cgram_data_dma_pool, GFP_DMA | GFP_KERNEL, (dma_addr_t *) &mx2fbp_bg.cgram_data_paddr);
	if ((void *)mx2fbp_bg.cgram_data_vaddr == NULL) {
		dev_err(&pdev->dev, "Unable to allocated DMA memory.\n");
		return -ENOMEM;
	}

	/* Memory allocation of display commands for DMA transfert */
	/* a page address on LCD is defined by 3 three commands.
	 * Each command must be joined with a byte containing the state
	 * of RS pin to apply (cf Fig 44-5 from SLCDC chapter in i.MX27 RM).
	 * Array must be defined as unsigned short */
	mx2fbp_bg.cgram_cmd_len = sizeof(_ssd1305_pagecmd_array)/sizeof(_ssd1305_pagecmd_array[0]);
	/* Allocate memory for CGRAM mirror with 128k boundary
	 * (cf note SLCDC in Ch. 44.2 of i.MX27 RM) aligned on 2 bytes (array of shorts) */
	mx2fbp_bg.cgram_cmd_dma_pool = dma_pool_create("SLCDC_DMA_CMD", &pdev->dev, mx2fbp_bg.cgram_cmd_len/(sizeof(int)/sizeof(_ssd1305_pagecmd_array[0])), 2, 128*1024);
	if (mx2fbp_bg.cgram_cmd_dma_pool == NULL) {
		dev_err(&pdev->dev, "Unable to allocated DMA Pool.\n");
		return -ENOMEM;
	}
	mx2fbp_bg.cgram_cmd_vaddr = (unsigned long)dma_pool_alloc(mx2fbp_bg.cgram_cmd_dma_pool, GFP_DMA | GFP_KERNEL, (dma_addr_t *) &mx2fbp_bg.cgram_cmd_paddr);
	if ((void *)mx2fbp_bg.cgram_cmd_vaddr == NULL) {
		dev_err(&pdev->dev, "Unable to allocated DMA memory.\n");
		return -ENOMEM;
	}

	slcdc_first_init();
	dd12832_init_controller();

	ret = _install_fb(&mx2fb_info, pdev);
	if (ret) {
		dev_err(&pdev->dev,
			"Failed to register framebuffer\n");
		return ret;
	}

	//_request_irq();

	return 0;
}

/*!
 * @brief This structure contains pointers to the power management
 * callback functions.
 */
static struct platform_driver mx2fb_driver = {
	.driver = {
		   .name = "mxc_sdc_fb",
		   .owner = THIS_MODULE,
		   .bus = &platform_bus_type,
		   },
	.probe = mx2fb_probe,
	.suspend = mx2fb_suspend,
	.resume = mx2fb_resume,
};

/*!
 * @brief Initialization
 */
int __init mx2fb_init(void)
{
	int ret = 0;
	ret = platform_driver_register(&mx2fb_driver);
	return ret;
}

/*!
 * @brief Cleanup
 */
void __exit mx2fb_exit(void)
{
	_free_irq();
	_uninstall_fb(&mx2fb_info);

	platform_driver_unregister(&mx2fb_driver);
}

/* Modularization */
module_init(mx2fb_init);
module_exit(mx2fb_exit);

EXPORT_SYMBOL(mx2_gw_set);
EXPORT_SYMBOL(mx2fb_register_client);
EXPORT_SYMBOL(mx2fb_unregister_client);

MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MX2 framebuffer driver");
MODULE_LICENSE("GPL");

[Index of Archives]     [Video for Linux]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Tourism]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux