LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* How to mmap a shadow framebuffer in virtual memory
@ 2007-01-14  0:24 Bernardo Innocenti
  2007-01-14 11:13 ` [Linux-fbdev-devel] " Geert Uytterhoeven
  0 siblings, 1 reply; 4+ messages in thread
From: Bernardo Innocenti @ 2007-01-14  0:24 UTC (permalink / raw)
  To: lkml, linux-fbdev-devel; +Cc: aleph

Hello,

This is driving me crazy.  I wrote this custom fb driver for an
organic LED display for an embedded ARM system.

The display is connected trough an I2C bus, therefore the display
buffer is not memory mapped.

Anyway, I want to support mmap() and my driver allocates shadow
buffer with __get_free_pages() which gets periodically copied
to the display by a thread. This is unlike most fb drivers which
just point smem_start to the phisical address of their framebuffer.

>From user space, opening /dev/fb0 and writing to it works just
fine.  mmap()'ing the file and writing to it does not have any
effect.

Writing the phisical address in smem_start and letting the
fbgen code do the rest didn't seem to work, so I reimplemented
the fb_mmap hook.

I don't feel confident with the Linux VM, so I tried several
strategies to allocate the shadow buffer, including vmalloc()
and kmalloc().

The virtual framebuffer (vfb) also uses vmalloc() but crashes
calling processes because it confuses physical and virtual
addresses, or so it seems.

Maybe it's just my kernel or my platform... does anybody use
a similar technique?  Can anybody point me to known-good code
that approximates my needs?

If you want to review the code below, look for the allocation in
oledfb_init() and usage in oledfb_mmap().  This code runs on
2.4.19-rmk7 because I can't upgrade to a newer kernel on this
target.



/*
 * linux/drivers/video/oledfb.c -- STV8102 OLED frame buffer device
 *
 * Copyright 2006 Develer S.r.l. (http://www.develer.com/)
 * Author: Bernardo Innocenti <bernie@develer.com>
 * Author: Stefano Fedrigo <aleph@develer.com>
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive
 * for more details.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <asm/semaphore.h>
#include <asm/atomic.h>

#include <video/fbcon.h>

/* Define to either 0 or 1 */
#define OLED_DEBUG  1

/* Driver name used in several places */
#define OLED_NAME "oledfb"

/* Fully qualified driver name for users */
#define OLED_FRIENDLY_NAME "STV8102 OLED"

/* Dimensions in pixels */
#define OLED_WIDTH  128
#define OLED_HEIGHT 33

/* Dimensions in millimeters */
#define OLED_WIDTH_MM  55
#define OLED_HEIGHT_MM 14

/* Size of an horizontal line in bytes */
#define OLED_WIDTH_BYTES  ((OLED_WIDTH + 7) / 8)

/* Framebuffer size in bytes */
#define OLED_MEMSIZE (OLED_WIDTH_BYTES * OLED_HEIGHT)

#define OLED_MEMORDER (get_order(PAGE_ALIGN(OLED_MEMSIZE)))

/* OLED refresh delay in milliseconds */
#define OLED_REFRESH_DELAY 300
#define OLED_REFRESH_JIFFIES ((OLED_REFRESH_DELAY * HZ)/1000)

/* Set to 1 to enable an X11-like backfilling pattern */
#define OLED_BACKFILL_PATTERN 0

/* I2C address of the OLED command/data registers */
#define I2C_DRIVERID_STV8102_CMD  0x3C
#define I2C_DRIVERID_STV8102_DATA 0x3D

/* Use a kernel thread to refresh the OLED periodically */
#define CONFIG_OLED_REFRESH_THREAD 1

/* BROKEN: i2c code sleeps in timer context */
#define CONFIG_OLED_REFRESH_TIMER 0


#define OLED_CMD_XSTART   0x00 /* address in lower 4 bits */
#define OLED_CMD_YSTART   0x40 /* address in lower 4 bits */
#define OLED_CMD_DISPON   0xAF
#define OLED_CMD_DISPOFF  0xAE
#define OLED_CMD_MOVE     0x80 /* effects in lower 4 bits */
#define OLED_CMD_HSPEED   0x90 /* speed in lower 3 bits */
#define OLED_CMD_VSPEED   0x98 /* speed in lower 3 bits */
#define OLED_CMD_HMIN     0xC0
#define OLED_CMD_HMAX     0xC2
#define OLED_CMD_VMIN     0xC6
#define OLED_CMD_VMAX     0xC8

/* Missing utility macros */
#define countof(x) (sizeof(x) / sizeof(x[0]))
#ifndef bool
#	define bool  int
#	define false 0
#	define true  1
#endif

#if OLED_DEBUG == 1
	#define OLED_TRACE printk(KERN_DEBUG "%s:%s()\n", OLED_NAME, __FUNCTION__)
	#define OLED_TRACEMSG(msg,...) printk(KERN_DEBUG "%s:%s(): " msg "\n", \
			OLED_NAME, __FUNCTION__, ## __VA_ARGS__)
#elif OLED_DEBUG == 0
	#define OLED_TRACE do {} while (0)
	#define OLED_TRACEMSG(msg,...)
#else
	#error Define OLED_DEBUG to either 0 or 1
#endif


struct oledfb_info {
	struct fb_info_gen gen;

	/* Shadow buffer for the memory mapped framebuffer */
	uint8_t *shadow;

	/* Second copy of shadow buffer for optimized refesh */
	uint8_t *shadow2;

	/* Physical address of shadow buffer as required by fbmem */
	unsigned long shadow_phys;

	/* I2C client we talk to for OLED command register read/write */
	struct i2c_client i2c_cmd;

	/* I2C client we talk to for OLED data register write */
	struct i2c_client i2c_data;

	bool screensaver_running;

	atomic_t open_cnt;

	#if CONFIG_OLED_REFRESH_THREAD
		int thread_pid;

		/* Used to tell our refresh thread to quit asap */
		/*bool*/ int quitting;

		struct semaphore thread_sem;
	#endif

	#if CONFIG_OLED_REFRESH_TIMER
		/* Timer for pushing shadow buffer through I2C bus */
		struct timer_list refresh_timer;
	#endif
};

struct oledfb_par {
	/*
	 *  The hardware specific data in this structure uniquely defines a video
	 *  mode.
	 *
	 *  If your hardware supports only one video mode, you can leave it empty.
	 */
	char dummy;
};

/*
 *  If your driver supports multiple boards, you should make these arrays,
 *  or allocate them dynamically (using kmalloc()).
 */
static struct oledfb_info fb_info;
static struct oledfb_par current_par;
static int current_par_valid = 0;
static struct display disp;

static struct i2c_driver oled_i2c_driver; /* fwd decl */

/* ------------------- OLED specific functions -------------------------- */

inline void
oledhw_write_cmd(struct oledfb_info *info, uint8_t val)
{
	i2c_smbus_write_byte(&info->i2c_cmd, val);
}

inline void
oledhw_write_data(struct oledfb_info *info, uint8_t val)
{
	i2c_smbus_write_byte(&info->i2c_data, val);
}

static void
oledhw_gotoxy(struct oledfb_info *info, int x, int y)
{
	oledhw_write_cmd(info, (uint8_t)(OLED_CMD_XSTART | (x & 0x0F)));
	oledhw_write_cmd(info, (uint8_t)(OLED_CMD_YSTART | (y & 0x0F)));
}

/* Send initialization sequence to display */
static void
oledhw_init(struct oledfb_info *info)
{
	OLED_TRACE;
	static const uint8_t init_sequence[] = {
		0xce, 0xff, 0x2a, 0xc2, 0x00, 0xc0, 0x00, 0xc4, 0x00,
		0x90, 0xa0, 0xa2, 0x80, 0x13,
		0xd6, 0xd8, 0xb2, 0xd0, 0x00, 0x26, 0x2c, 0xb8, 0xcc,
		0x1f, 0x28, 0x38, 0x2e, 0xba, 0x18, 0xc8, 0xc6, 0xa6, 0x08, 0xca, 0x00, 0x98, 0x00,
		0xaf
	};

	int i;
	for (i = 0; i < countof(init_sequence); ++i)
		oledhw_write_cmd(info, init_sequence[i]);

	/* Clear all display RAM, including invisible parts */
	oledhw_gotoxy(info, 0, 0);
	for (i = 0; i < OLED_WIDTH_BYTES * 64; ++i)
		oledhw_write_data(info, 0);
}

/* Send initialization sequence to display */
static void
oledhw_cleanup(struct oledfb_info *info)
{
	oledhw_write_cmd(info, OLED_CMD_DISPOFF);
}

/* FIXME */
#include "logo_bticino.c"

static void
oledhw_bounce(struct oledfb_info *info)
{
	//uint8_t *bitmap = (uint8_t *)info->shadow_phys;
	uint8_t *bitmap = info->shadow;
	unsigned int y;
	for (y = 0; y < LOGO_HEIGHT; ++y) {
		unsigned int x;
		for (x = 0; x < LOGO_WIDTH / 8; ++x) {
			bitmap[y * OLED_WIDTH_BYTES + x] = logo_bticino[y * (LOGO_WIDTH / 8) + x];
		}
	}
}

/* Fill the display with a background pattern */
static void
oledhw_backfill(struct oledfb_info *info, int frame)
{
#if OLED_BACKFILL_PATTERN
	/* X11-like background pattern */
	static const uint8_t pattern[4] = {0x88, 0x22, 0x44, 0x11};

	uint8_t *bitmap = info->shadow;
	unsigned int y;
	for (y = 0; y < OLED_HEIGHT; ++y) {
		unsigned int x;
		unsigned int index = (y + frame) % countof(pattern);
		for (x = 0; x < OLED_WIDTH_BYTES; ++x) {
			bitmap[y * OLED_WIDTH_BYTES + x] = pattern[index];
		}
	}
#else
	//uint8_t *bitmap = (uint8_t *)info->shadow_phys;
	uint8_t *bitmap = info->shadow;
	memset(bitmap, 0, OLED_HEIGHT * OLED_WIDTH_BYTES);
#endif
}

static void
oledhw_screensaver_start(struct oledfb_info *info)
{
	oledhw_write_cmd(info, OLED_CMD_HSPEED | 1);
	oledhw_write_cmd(info, OLED_CMD_VSPEED | 1);
	oledhw_write_cmd(info, OLED_CMD_MOVE   | 0x04 | 0x03); /* horiz. bounce only, vert. bounce and wrap */
	oledhw_write_cmd(info, OLED_CMD_VMIN); oledhw_write_cmd(info, 0x62);
	oledhw_write_cmd(info, OLED_CMD_VMAX); oledhw_write_cmd(info, 0x1F);
	oledhw_write_cmd(info, OLED_CMD_HMIN); oledhw_write_cmd(info, 0xC0);
	oledhw_write_cmd(info, OLED_CMD_HMAX); oledhw_write_cmd(info, 0x40);
}

static void
oledhw_screensaver_stop(struct oledfb_info *info)
{
	oledhw_write_cmd(info, OLED_CMD_HSPEED | 0);
	oledhw_write_cmd(info, OLED_CMD_VSPEED | 0);
}

/* Copy the shadow buffer to the physical display */
static void
oledhw_refresh(struct oledfb_info *info)
{
	//OLED_TRACE; (too verbose)

	oledhw_gotoxy(info, 0, 0);

	//printk("shadow=%p, shadow_phys=%lx\n", info->shadow, info->shadow_phys);

	//uint8_t *bitmap = (uint8_t *)info->shadow_phys;
	uint8_t *bitmap = info->shadow;

	int i;
	for (i = 0; i < OLED_MEMSIZE; ++i) {
		//info->shadow[i] = i;
		//TODO: optimize to a block transfer
		oledhw_write_data(info, bitmap[i]);
	}
}

#if CONFIG_OLED_REFRESH_THREAD

/* Process to handle background OLED refresh from the shadow buffer. */
static int
oledhw_thread(void *arg)
{
	OLED_TRACE;
	struct oledfb_info *info = (struct oledfb_info *)arg;

	/* Do some setup to look like a real daemon */
	daemonize();
	exit_files(current);
	sprintf(current->comm, OLED_NAME);

	spin_lock_irq(&current->sigmask_lock);
	sigfillset(&current->blocked);
	flush_signals(current);
	spin_unlock_irq(&current->sigmask_lock);

	current->policy = SCHED_OTHER;
	current->nice = 5;
	current->flags |= PF_NOIO;

	int i = 0;
	while (!info->quitting) {
		if (atomic_read(&info->open_cnt) == 0) {
			/* draw waking checkboard pattern when idle */
			static int frame = 0;
			oledhw_backfill(info, frame++);
			oledhw_bounce(info);
			if (!info->screensaver_running) {
				oledhw_screensaver_start(info);
				info->screensaver_running = true;
			}
		} else if (info->screensaver_running) {
			oledhw_screensaver_stop(info);
			info->screensaver_running = false;
		}

		if (((unsigned char *)info->shadow)[0] == 0xAA)
			printk("FOO\n");
		if (((unsigned char *)info->shadow)[0] == 0x55)
			printk("BAR\n");

		/* Optimize refresh: transfer image only when it has changed */
		if (memcmp(info->shadow, info->shadow2, OLED_MEMSIZE)) {
			memcpy(info->shadow2, info->shadow, OLED_MEMSIZE);
			oledhw_refresh(info);
		}

		//msleep(OLED_REFRESH_DELAY);
		set_current_state(TASK_INTERRUPTIBLE);
		schedule_timeout(OLED_REFRESH_JIFFIES);
	}

	/* Tell module we're done */
	//printk(KERN_ERR "thread quitting\n");
	up(&info->thread_sem);
	//printk(KERN_ERR "thread had quit\n");
	return 0;
}

#endif /* CONFIG_OLED_REFRESH_THREAD */

#if CONFIG_OLED_REFRESH_TIMER
#include <linux/timer.h>

/* Callback invoked by timer to perform refresh periodically */
static void
oledhw_timer(unsigned long arg)
{
	OLED_TRACE;
	struct oledfb_info *info = (struct oledfb_info *)arg;

	oledhw_refresh(info);

	/* Retrigger timer */
	info->refresh_timer.expires +=  OLED_REFRESH_DELAY_JIFFIES;
	add_timer(&info->refresh_timer);
}
#endif /* CONFIG_OLED_REFRESH_TIMER */


/* ------------------- Chipset specific functions -------------------------- */

static int
oled_encode_fix(struct fb_fix_screeninfo *fix, const void *par,
		struct fb_info_gen *info)
{
	OLED_TRACE;
	/*
	 *  This function should fill in the 'fix' structure based on the values
	 *  in the `par' structure.
	 */
	strncpy(fix->id, OLED_NAME, sizeof(fix->id));
	fix->smem_start  = (unsigned long)fb_info.shadow_phys;
	printk("phys=%lx p2v=%lx, v=%lx\n", fb_info.shadow_phys, phys_to_virt(fb_info.shadow_phys), fb_info.shadow);
	fix->smem_len    = OLED_MEMSIZE;
	fix->type        = FB_TYPE_PLANES;
	fix->type_aux    = 0;
	fix->visual      = FB_VISUAL_MONO01;
	fix->xpanstep    = 0;
	fix->ypanstep    = 0;
	fix->ywrapstep   = 0;
	fix->line_length = OLED_WIDTH_BYTES;
	fix->mmio_start  = 0;
	fix->mmio_len    = 0;
	fix->accel       = FB_ACCEL_NONE;

	return 0;
}

static int
oled_decode_var(const struct fb_var_screeninfo *var, void *par,
		struct fb_info_gen *info)
{
	OLED_TRACE;
	return -EINVAL;
}

static int
oled_encode_var(struct fb_var_screeninfo *var, const void *par,
		struct fb_info_gen *info)
{
	OLED_TRACE;
	/*
	 *  Fill the 'var' structure based on the values in 'par' and maybe other
	 *  values read out of the hardware.
	 */
	memset(var, 0, sizeof(*var));
	var->xres         = OLED_WIDTH;
	var->yres         = OLED_HEIGHT;
	var->xres_virtual = OLED_WIDTH;
	var->yres_virtual = OLED_HEIGHT;
	var->bits_per_pixel = 1;
	var->grayscale    = 1;
	var->width        = OLED_WIDTH_MM;
	var->height       = OLED_HEIGHT_MM;

	return 0;
}

static void
oled_get_par(void *_par, struct fb_info_gen *info)
{
	struct oledfb_par *par = (struct oledfb_par *)_par;
	OLED_TRACE;

	/*
	 *  Fill the hardware's 'par' structure.
	 */

	if (current_par_valid)
		*par = current_par;
	else {
		/* ... */
	}
}

static void
oled_set_par(const void *_par, struct fb_info_gen *info)
{
	struct oledfb_par *par = (struct oledfb_par *)_par;
	OLED_TRACE;

	/*
	 *  Set the hardware according to 'par'.
	 */
	current_par = *par;
	current_par_valid = 1;
	/* ... */
}

static int
oled_getcolreg(unsigned regno, unsigned *red, unsigned *green,
		  unsigned *blue, unsigned *transp, struct fb_info *info)
{
	OLED_TRACE;
	/*
	 *  Read a single color register and split it into colors/transparent.
	 *  The return values must have a 16 bit magnitude.
	 *  Return != 0 for invalid regno.
	 */
	if (regno == 0)
		*red = *green = *blue = *transp = 0;
	else if (regno == 1)
		*red = *green = *blue = 0xFFFF, *transp = 0;
	else
		return -EINVAL;

	return 0;
}

static int
oled_setcolreg(unsigned regno, unsigned red, unsigned green,
		unsigned blue, unsigned transp, struct fb_info *info)
{
	OLED_TRACE;
	return -EINVAL;
}

static int
oled_pan_display(const struct fb_var_screeninfo *var,
		struct fb_info_gen *info)
{
	OLED_TRACE;
	/*
	 *  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.
	 */

	/* ... */
	return 0;
}

static int
oled_blank(int blank_mode, struct fb_info_gen *info)
{
	OLED_TRACE;
	/*
	 *  Blank the screen if blank_mode != 0, else unblank. If blank == NULL
	 *  then the caller blanks by setting the CLUT (Color Look Up Table) to all
	 *  black. Return 0 if blanking succeeded, != 0 if un-/blanking failed due
	 *  to e.g. a video mode which doesn't support it. Implements VESA suspend
	 *  and powerdown modes on hardware that supports disabling hsync/vsync:
	 *    blank_mode == 2: suspend vsync
	 *    blank_mode == 3: suspend hsync
	 *    blank_mode == 4: powerdown
	 */

	/* ... */
	return 0;
}

static void
oled_set_disp(const void *_par, struct display *disp,
		 struct fb_info_gen *_info)
{
	struct oledfb_info *info = (struct oledfb_info *)_info;
	OLED_TRACE;

	/*  Fill in a pointer with the virtual address of the mapped frame buffer. */
	disp->screen_base = info->shadow;
	printk(KERN_ERR "*****AIEEE2: %lx\n", disp->screen_base);

	/*
	 *  Fill in a pointer to appropriate low level text console operations (and
	 *  optionally a pointer to help data) for the video mode `par' of your
	 *  video hardware. These can be generic software routines, or hardware
	 *  accelerated routines specifically tailored for your hardware.
	 *  If you don't have any appropriate operations, you must fill in a
	 *  pointer to dummy operations, and there will be no text output.
	 */
	disp->dispsw = &fbcon_dummy;
}

static void
oled_detect(void)
{
	OLED_TRACE;
}

/* ------------ Interfaces to hardware functions ------------ */

struct fbgen_hwswitch oled_switch = {
	.detect        = oled_detect,
	.encode_fix    = oled_encode_fix,
	.decode_var    = oled_decode_var,
	.encode_var    = oled_encode_var,
	.get_par       = oled_get_par,
	.set_par       = oled_set_par,
	.getcolreg     = oled_getcolreg,
	.setcolreg     = oled_setcolreg,
    .pan_display   = oled_pan_display,
	.blank         = oled_blank,
	.set_disp      = oled_set_disp,
};

/* ------------- Frame buffer operations --------------*/

/* If all you need is that - just don't define ->fb_open */
static int
oledfb_open(struct fb_info *_info, int user)
{
	OLED_TRACE;

	struct oledfb_info *info = (struct oledfb_info *)_info;
	atomic_inc(&info->open_cnt);

	return 0;
}

/* If all you need is that - just don't define ->fb_release */
static int
oledfb_release(struct fb_info *_info, int user)
{
	OLED_TRACE;

	struct oledfb_info *info = (struct oledfb_info *)_info;
	atomic_dec(&info->open_cnt);

	return 0;
}

static int oledfb_mmap(struct fb_info *info, struct file *file, struct vm_area_struct *vma)
{
	unsigned long start,off,len;
	OLED_TRACE;

    if(vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
		return -EINVAL;
	off = vma->vm_pgoff << PAGE_SHIFT;

	/* frame buffer memory */
	start = (unsigned long)fb_info.shadow_phys;
	len   = (unsigned long)PAGE_ALIGN(OLED_MEMSIZE);
	OLED_TRACEMSG("start=%lx",start);
	OLED_TRACEMSG("len=%lx",len);

	start &= PAGE_MASK;
	if ((vma->vm_end - vma->vm_start + off) > len)
		return -EINVAL;

	off += start;
	//bernie: why?
	vma->vm_pgoff = off >> PAGE_SHIFT;

	//vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
	/* This is an IO amp - tell maydump to skip this VMA */
	//vma->vm_flags |= VM_IO;

	OLED_TRACEMSG("vm_start=%lx",vma->vm_start);
	OLED_TRACEMSG("vm_end=%lx",vma->vm_end);
	OLED_TRACEMSG("off=%lx",off);
	OLED_TRACEMSG("len=%lx",len);

	if (io_remap_page_range(vma->vm_start, off,
				vma->vm_end - vma->vm_start, vma->vm_page_prot))
		return -EAGAIN;

	OLED_TRACEMSG("OK");

	return 0;
}

/*
 *  In most cases the `generic' routines (fbgen_*) should be satisfactory.
 *  However, you're free to fill in your own replacements.
 */
static struct fb_ops oledfb_ops = {
	.owner = THIS_MODULE,
	.fb_open = oledfb_open,	/* only if you need it to do something */
	.fb_release = oledfb_release,
	/* only if you need it to do something */
	.fb_get_fix = fbgen_get_fix,
	.fb_get_var = fbgen_get_var,
	.fb_set_var = fbgen_set_var,
	.fb_get_cmap = fbgen_get_cmap,
	.fb_set_cmap = fbgen_set_cmap,
	.fb_pan_display = fbgen_pan_display,
	/* .fb_ioctl = oledfb_ioctl,*/ /* optional */
    .fb_mmap = oledfb_mmap,
};

/* ------------- I2C Driver interface --------------*/

static int oled_attach(struct i2c_adapter *adap, int addr,
		unsigned short flags, int kind)
{
	int result;

	printk(KERN_ERR "oled attach(): addr: %x\n", addr);

	/* Initialize i2c client for the OLED command register */
	strcpy(fb_info.i2c_cmd.name, oled_i2c_driver.name);
	fb_info.i2c_cmd.id = oled_i2c_driver.id;
	fb_info.i2c_cmd.flags = 0;
	fb_info.i2c_cmd.addr = addr;
	fb_info.i2c_cmd.adapter = adap;
	fb_info.i2c_cmd.driver = &oled_i2c_driver;
	fb_info.i2c_cmd.data = NULL;
	if ((result = i2c_attach_client(&fb_info.i2c_cmd)) < 0)
		return result;

	/* Initialize i2c client for the OLED data register */
	memcpy(&fb_info.i2c_data, &fb_info.i2c_cmd, sizeof(fb_info.i2c_data));
	fb_info.i2c_data.addr = addr + 1;
	if ((result = i2c_attach_client(&fb_info.i2c_data)) < 0)
	{
		i2c_detach_client(&fb_info.i2c_cmd);
		return result;
	}

	oledhw_init(&fb_info);
	return 0;
}

static int oled_probe(struct i2c_adapter *adap)
{
	static unsigned short ignore[] = { I2C_CLIENT_END };
//	static unsigned short normal_addr[] = { I2C_DRIVERID_STV8102_CMD, I2C_DRIVERID_STV8102_DATA, I2C_CLIENT_END };
	static unsigned short force_addr[] = { -1, I2C_DRIVERID_STV8102_CMD, I2C_CLIENT_END };
	static struct i2c_client_address_data addr_data = {
//		.normal_i2c =       normal_addr,
		.normal_i2c =       ignore,
		.normal_i2c_range = ignore,
		.probe =            ignore,
		.probe_range =      ignore,
		.ignore =           ignore,
		.ignore_range =     ignore,
//		.force =            ignore,
		.force =            force_addr,
	};

	OLED_TRACE;
	return i2c_probe(adap, &addr_data, oled_attach);
}

static int oled_detach(struct i2c_client *client)
{
	OLED_TRACE;
	oledhw_cleanup(&fb_info);
	i2c_detach_client(client);
	return 0;
}

static struct i2c_driver oled_i2c_driver =
{
	.name =            OLED_NAME,
	.id =              I2C_DRIVERID_STV8102_CMD,
	.flags =           I2C_DF_NOTIFY,
	.attach_adapter =  oled_probe,
	.detach_client =   oled_detach,
};

/* ------------ Frame Buffer interface ------------ */

static int
oledfb_update_var(int unit, struct fb_info *info)
{
	OLED_TRACE;
	return 0;
}

static void
oledfb_blank(int unit, struct fb_info *info)
{
	OLED_TRACE;
}

/* ------------ Module interface ------------ */

int __init
oledfb_init(void)
{
	int result = 0;
	OLED_TRACE;

	if ((result = i2c_add_driver(&oled_i2c_driver)) < 0) {
		printk(KERN_ERR OLED_NAME ": failed to register i2c driver\n");
		return result;
	}

	fb_info.gen.parsize = sizeof(current_par);
	fb_info.gen.fbhw = &oled_switch;
	fb_info.gen.fbhw->detect();
	strcpy(fb_info.gen.info.modename, OLED_FRIENDLY_NAME);
	fb_info.gen.info.node = -1;
	fb_info.gen.info.flags = FBINFO_FLAG_DEFAULT;
	fb_info.gen.info.fbops = &oledfb_ops;
	fb_info.gen.info.disp = &disp;
	fb_info.gen.info.changevar = NULL;
	fb_info.gen.info.switch_con = NULL; /* optional */
	fb_info.gen.info.updatevar = &oledfb_update_var;
	fb_info.gen.info.blank = &oledfb_blank;

	/*
	 * Prepare a shadow buffer for userland to mmap().
	 *
	 * We need to allocate physical memory because the fbmem interface
	 * wants it so.  We also need to map the buffer to a virtual address
	 * to read from it later.
	 */
	fb_info.shadow = NULL;
	fb_info.shadow2 = NULL;
	if (!(fb_info.shadow = (void *)__get_free_pages(GFP_KERNEL, OLED_MEMORDER))
		|| (!(fb_info.shadow2 = (void *)__get_free_pages(GFP_KERNEL, OLED_MEMORDER)))) {

/*FIXME	if (!(fb_info.shadow_phys = __get_free_page(GFP_KERNEL))
		|| !(fb_info.shadow = ioremap(fb_info.shadow_phys, OLED_MEMSIZE))) { */
/*FIXME	if (!(fb_info.shadow_phys = vmalloc(OLED_MEMSIZE))) { */
		printk(KERN_ERR OLED_NAME ": can't allocate shadow buffer");
		result = -1;
		goto out;
	}
	fb_info.shadow_phys = __virt_to_phys((unsigned long)fb_info.shadow);
	printk(KERN_ERR "*****AIEEE1: %p\n", fb_info.shadow);

	/* This should give a reasonable default video mode */
	fbgen_get_var(&disp.var, -1, &fb_info.gen.info);
	fbgen_do_set_var(&disp.var, 1, &fb_info.gen);
	fbgen_set_disp(-1, &fb_info.gen);
	fbgen_install_cmap(0, &fb_info.gen);

	if ((result = register_framebuffer(&fb_info.gen.info)) < 0) {
		printk(KERN_ERR OLED_NAME ": can't register framebuffer\n");
		goto out2;
	}

	#if CONFIG_OLED_REFRESH_THREAD
		fb_info.quitting = 0 /* false */;
		init_MUTEX_LOCKED(&fb_info.thread_sem);
		if ((result = kernel_thread(oledhw_thread, &fb_info, CLONE_FS | CLONE_FILES | CLONE_SIGHAND)) < 0) {
			printk(KERN_ERR OLED_NAME ": can't create refresh thread\n");
			goto out3;
		}
	#elif CONFIG_OLED_REFRESH_TIMER
		init_timer(&fb_info.refresh_timer);
		fb_info.refresh_timer.data     = (unsigned long)&fb_info;
		fb_info.refresh_timer.function = oledhw_timer;
		fb_info.refresh_timer.expires  = jiffies + OLED_REFRESH_DELAY_JIFFIES;
		add_timer(&fb_info.refresh_timer);
	#endif

	printk(KERN_INFO "fb%d: %s frame buffer device\n",
			GET_FB_IDX(fb_info.gen.info.node), fb_info.gen.info.modename);

	return 0;

out3:
	unregister_framebuffer((struct fb_info *)&fb_info);
out2:
	i2c_del_driver(&oled_i2c_driver);
out:
	free_pages((unsigned long)fb_info.shadow, OLED_MEMORDER);
	free_pages((unsigned long)fb_info.shadow2, OLED_MEMORDER);
//FIXME	iounmap(fb_info.shadow);
//FIXME	free_page(fb_info.shadow_phys);
//FIXME	vfree((void *)fb_info.shadow_phys);
	return result;
}

void
oledfb_cleanup(struct fb_info *_info)
{
	struct oledfb_info *info = (struct oledfb_info *)_info;

	OLED_TRACE;

	#if CONFIG_OLED_REFRESH_THREAD
		//printk("killing thread\n");
		info->quitting = 1 /* true */;
		down(&info->thread_sem);
		//printk("thread killed\n");
	#endif
	#if CONFIG_OLED_REFRESH_TIMER
		//del_timer_sync(info->refresh_timer):
		del_timer(&info->refresh_timer);
	#endif
	unregister_framebuffer((struct fb_info *)info);
	i2c_del_driver(&oled_i2c_driver);

	free_pages((unsigned long)fb_info.shadow, OLED_MEMORDER);
	free_pages((unsigned long)fb_info.shadow2, OLED_MEMORDER);
//FIXME	iounmap(info->shadow);
//FIXME	free_page(info->shadow_phys);
//FIXME	vfree((void *)fb_info.shadow_phys);
}

int __init
oledfb_setup(char *options)
{
	OLED_TRACE;
	/* Parse user speficied options (`video=xxxfb:') */

	return 0;
}

/* ------------------------------------------------------------------------- */

/*
 *  Modularization
 */
#ifdef MODULE
MODULE_LICENSE("GPL");
int
init_module(void)
{
	OLED_TRACE;
	return oledfb_init();
}

void
cleanup_module(void)
{
	OLED_TRACE;
	oledfb_cleanup((struct fb_info *)&fb_info);
}
#endif /* MODULE */



-- 
   // Bernardo Innocenti - Develer S.r.l., R&D dept.
 \X/  http://www.develer.com/


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [Linux-fbdev-devel] How to mmap a shadow framebuffer in virtual memory
  2007-01-14  0:24 How to mmap a shadow framebuffer in virtual memory Bernardo Innocenti
@ 2007-01-14 11:13 ` Geert Uytterhoeven
  2007-01-15  6:04   ` Bernardo Innocenti
  0 siblings, 1 reply; 4+ messages in thread
From: Geert Uytterhoeven @ 2007-01-14 11:13 UTC (permalink / raw)
  To: Bernardo Innocenti, Linux Frame Buffer Device Development; +Cc: lkml, aleph

On Sun, 14 Jan 2007, Bernardo Innocenti wrote:
> This is driving me crazy.  I wrote this custom fb driver for an
> organic LED display for an embedded ARM system.
> 
> The display is connected trough an I2C bus, therefore the display
> buffer is not memory mapped.
> 
> Anyway, I want to support mmap() and my driver allocates shadow
> buffer with __get_free_pages() which gets periodically copied
> to the display by a thread. This is unlike most fb drivers which
> just point smem_start to the phisical address of their framebuffer.
> 
> >From user space, opening /dev/fb0 and writing to it works just
> fine.  mmap()'ing the file and writing to it does not have any
> effect.
> 
> Writing the phisical address in smem_start and letting the
> fbgen code do the rest didn't seem to work, so I reimplemented
> the fb_mmap hook.
> 
> I don't feel confident with the Linux VM, so I tried several
> strategies to allocate the shadow buffer, including vmalloc()
> and kmalloc().
> 
> The virtual framebuffer (vfb) also uses vmalloc() but crashes
> calling processes because it confuses physical and virtual
> addresses, or so it seems.
> 
> Maybe it's just my kernel or my platform... does anybody use
> a similar technique?  Can anybody point me to known-good code
> that approximates my needs?

It's known to work for the PS3 frame buffer device driver, which copies the
virtual frame buffer to the GPU on every vsync. Check out ps3fb_mmap() in
http://www.kernel.org/git/?p=linux/kernel/git/geoff/ps3-linux.git;a=blob_plain;f=drivers/video/ps3fb.c;hb=HEAD

Gr{oetje,eeting}s,

						Geert

--
Geert Uytterhoeven -- Sony Network and Software Technology Center Europe (NSCE)
Geert.Uytterhoeven@sonycom.com ------- The Corporate Village, Da Vincilaan 7-D1
Voice +32-2-7008453 Fax +32-2-7008622 ---------------- B-1935 Zaventem, Belgium

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [Linux-fbdev-devel] How to mmap a shadow framebuffer in virtual memory
  2007-01-14 11:13 ` [Linux-fbdev-devel] " Geert Uytterhoeven
@ 2007-01-15  6:04   ` Bernardo Innocenti
  2007-02-06 14:57     ` Bernardo Innocenti
  0 siblings, 1 reply; 4+ messages in thread
From: Bernardo Innocenti @ 2007-01-15  6:04 UTC (permalink / raw)
  To: Geert Uytterhoeven; +Cc: Linux Frame Buffer Device Development, lkml, aleph

Geert Uytterhoeven wrote:

> It's known to work for the PS3 frame buffer device driver, which copies the
> virtual frame buffer to the GPU on every vsync. Check out ps3fb_mmap() in
> http://www.kernel.org/git/?p=linux/kernel/git/geoff/ps3-linux.git;a=blob_plain;f=drivers/video/ps3fb.c;hb=HEAD

I still couldn't solve my problem, but thanks for helping.

The way you map memory in ps3fb_mmap() is basically the same.
In my case, memory is allocated with __get_free_pages() instead
of being at an absolute physical address, but I can't see how
it could make any difference.

-- 
   // Bernardo Innocenti - Develer S.r.l., R&D dept.
 \X/  http://www.develer.com/

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [Linux-fbdev-devel] How to mmap a shadow framebuffer in virtual memory
  2007-01-15  6:04   ` Bernardo Innocenti
@ 2007-02-06 14:57     ` Bernardo Innocenti
  0 siblings, 0 replies; 4+ messages in thread
From: Bernardo Innocenti @ 2007-02-06 14:57 UTC (permalink / raw)
  To: Bernardo Innocenti
  Cc: Geert Uytterhoeven, Linux Frame Buffer Device Development, lkml, aleph

Bernardo Innocenti wrote:

> I still couldn't solve my problem, but thanks for helping.
> 
> The way you map memory in ps3fb_mmap() is basically the same.
> In my case, memory is allocated with __get_free_pages() instead
> of being at an absolute physical address, but I can't see how
> it could make any difference.

A few weeks ago I found the solution, which I report here to help
people who are googling for the same problem.

consistent_alloc() performed all the required magic to allocate
a contiguous buffer in phisical memory:

 -       if (!(fb_info.shadow = (void *)__get_free_pages(GFP_KERNEL, OLED_MEMORDER))
 +       if (!(fb_info.shadow = consistent_alloc(GFP_KERNEL, PAGE_ALIGN(OLED_MEMSIZE),
 +                       &fb_info.shadow_phys, PTE_BUFFERABLE))


The complete patch set is here, just in case:

   http://www.develer.com/patches/linux/pending/

-- 
   // Bernardo Innocenti - Develer S.r.l., R&D dept.
 \X/  http://www.develer.com/


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2007-02-06 14:57 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-01-14  0:24 How to mmap a shadow framebuffer in virtual memory Bernardo Innocenti
2007-01-14 11:13 ` [Linux-fbdev-devel] " Geert Uytterhoeven
2007-01-15  6:04   ` Bernardo Innocenti
2007-02-06 14:57     ` Bernardo Innocenti

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).