From: "Gaëtan Carlier" <gcembed@gmail.com>
To: linux-arm-kernel@lists.infradead.org
Subject: i.MX27 SLCDC driver
Date: Fri, 17 Aug 2012 07:07:57 +0000 [thread overview]
Message-ID: <502DEDCD.9050303@gmail.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 612 bytes --]
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)
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: mx2fb-slcd_dd12832_oled.c --]
[-- Type: text/x-csrc; name="mx2fb-slcd_dd12832_oled.c", Size: 35409 bytes --]
/*
* 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");
WARNING: multiple messages have this Message-ID (diff)
From: gcembed@gmail.com (Gaëtan Carlier)
To: linux-arm-kernel@lists.infradead.org
Subject: i.MX27 SLCDC driver
Date: Fri, 17 Aug 2012 09:07:57 +0200 [thread overview]
Message-ID: <502DEDCD.9050303@gmail.com> (raw)
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)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: mx2fb-slcd_dd12832_oled.c
Type: text/x-csrc
Size: 35409 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120817/ea3df386/attachment-0001.bin>
next reply other threads:[~2012-08-17 7:07 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-08-17 7:07 Gaëtan Carlier [this message]
2012-08-17 7:07 ` i.MX27 SLCDC driver Gaëtan Carlier
2012-08-20 15:21 ` Matt Sealey
2012-08-20 15:21 ` Matt Sealey
2012-08-20 16:21 ` Gaëtan Carlier
2012-08-20 16:21 ` Gaëtan Carlier
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=502DEDCD.9050303@gmail.com \
--to=gcembed@gmail.com \
--cc=linux-arm-kernel@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.