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");