Linux Framebuffer Layer development
 help / color / mirror / Atom feed
* Re: [RFC 0/5] Generic panel framework
From: Tomi Valkeinen @ 2012-08-17  8:38 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
	Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
	Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>

[-- Attachment #1: Type: text/plain, Size: 4980 bytes --]

Hi,

On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:

> I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If

Oookay, where to start... ;)

A few cosmetic/general comments first.

I find the file naming a bit strange. You have panel.c, which is the
core framework, panel-dbi.c, which is the DBI bus, panel-r61517.c, which
is driver for r61517 panel...

Perhaps something in this direction (in order): panel-core.c,
mipi-dbi-bus.c, panel-r61517.c? And we probably end up with quite a lot
of panel drivers, perhaps we should already divide these into separate
directories, and then we wouldn't need to prefix each panel with
"panel-" at all.

---

Should we aim for DT only solution from the start? DT is the direction
we are going, and I feel the older platform data stuff would be
deprecated soon.

---

Something missing from the intro is how this whole thing should be used.
It doesn't help if we know how to turn on the panel, we also need to
display something on it =). So I think some kind of diagram/example of
how, say, drm would use this thing, and also how the SoC specific DBI
bus driver would be done, would clarify things.

---

We have discussed face to face about the different hardware setups and
scenarios that we should support, but I'll list some of them here for
others:

1) We need to support chains of external display chips and panels. A
simple example is a chip that takes DSI in, and outputs DPI. In that
case we'd have a chain of SoC -> DSI2DPI -> DPI panel.

In final products I think two external devices is the maximum (at least
I've never seen three devices in a row), but in theory and in
development environments the chain can be arbitrarily long. Also the
connections are not necessarily 1-to-1, but a device can take one input
while it has two outputs, or a device can take two inputs.

Now, I think two external devices is a must requirement. I'm not sure if
supporting more is an important requirement. However, if we support two
devices, it could be that it's trivial to change the framework to
support n devices.

2) Panels and display chips are all but standard. They very often have
their own sequences how to do things, have bugs, or implement some
feature in slightly different way than some other panel. This is why the
panel driver should be able to control or define the way things happen.

As an example, Sharp LQ043T1DG01 panel
(www.sharpsme.com/download/LQ043T1DG01-SP-072106pdf). It is enabled with
the following sequence:

- Enable VCC and AVDD regulators
- Wait min 50ms
- Enable full video stream (pck, syncs, pixels) from SoC
- Wait min 0.5ms
- Set DISP GPIO, which turns on the display panel

Here we could split the enabling of panel to two parts, prepare (in this
case starts regulators and waits 50ms) and finish (wait 0.5ms and set
DISP GPIO), and the upper layer would start the video stream in between.

I realize this could be done with the PANEL_ENABLE_* levels in your RFC,
but I don't think the concepts quite match:

- PANEL_ENABLE_BLANK level is needed for "smart panels", as we need to
configure them and send the initial frame at that operating level. With
dummy panels there's really no such level, there's just one enable
sequence that is always done right away.

- I find waiting at the beginning of a function very ugly (what are we
waiting for?) and we'd need that when changing the panel to
PANEL_ENABLE_ON level.

- It's still limited if the panel is a stranger one (see following
example).

Consider the following theoretical panel enable example, taken to absurd
level just to show the general problem:

- Enable regulators
- Enable video stream
- Wait 50ms
- Disable video stream
- Set enable GPIO
- Enable video stream

This one would be rather impossible with the upper layer handling the
enabling of the video stream. Thus I see that the panel driver needs to
control the sequences, and the Sharp panel driver's enable would look
something like:

regulator_enable(...);
sleep();
dpi_enable_video();
sleep();
gpip_set(..);

Note that even with this model we still need the PANEL_ENABLE levels you
have.

---

I'm not sure I understand the panel unload problem you mentioned. Nobody
should have direct references to the panel functions, so there shouldn't
be any automatic references that would prevent module unloading. So when
the user does rmmod panel-mypanel, the panel driver's remove will be
called. It'll unregister itself from the panel framework, which causes
notifications and the display driver will stop using the panel. After
that nobody has pointers to the panel, and it can safely be unloaded.

It could cause some locking issues, though. First the panel's remove
could take a lock, but the remove sequence would cause the display
driver to call disable on the panel, which could again try to take the
same lock...

 Tomi


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* i.MX27 SLCDC driver
From: Gaëtan Carlier @ 2012-08-17  7:07 UTC (permalink / raw)
  To: linux-arm-kernel

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

^ permalink raw reply

* Re: [PATCH v3] da8xx-fb: allow frame to complete after disabling LCDC
From: Sekhar Nori @ 2012-08-17  6:57 UTC (permalink / raw)
  To: linux-fbdev
In-Reply-To: <1344950605-18945-1-git-send-email-prakash.pm@ti.com>

Hi Prakash,

On 8/14/2012 6:53 PM, Manjunathappa, Prakash wrote:
> Wait for active frame transfer to complete after disabling LCDC.
> At the same this wait is not be required when there are sync and
> underflow errors.
> More information on disable and reset sequence can be found in
> section 13.4.6 of AM335x TRM @www.ti.com/am335x.
> 
> Signed-off-by: Manjunathappa, Prakash <prakash.pm@ti.com>
> ---
> Applies on top of fbdev-next of Florian Tobias Schandinat's tree.
> Since v2:
> Optimized the lcd_disable_raster function.
> Since v1:
> Changed the commit message, also added link to hardware specification.
> 
>  drivers/video/da8xx-fb.c |   49 ++++++++++++++++++++++++++++++++++++---------
>  1 files changed, 39 insertions(+), 10 deletions(-)
> 
> diff --git a/drivers/video/da8xx-fb.c b/drivers/video/da8xx-fb.c
> index 7ae9d53..cb696ff 100644
> --- a/drivers/video/da8xx-fb.c
> +++ b/drivers/video/da8xx-fb.c
> @@ -48,6 +48,7 @@
>  #define LCD_PL_LOAD_DONE		BIT(6)
>  #define LCD_FIFO_UNDERFLOW		BIT(5)
>  #define LCD_SYNC_LOST			BIT(2)
> +#define LCD_FRAME_DONE			BIT(0)
>  
>  /* LCD DMA Control Register */
>  #define LCD_DMA_BURST_SIZE(x)		((x) << 4)
> @@ -288,13 +289,41 @@ static inline void lcd_enable_raster(void)
>  }
>  
>  /* Disable the Raster Engine of the LCD Controller */
> -static inline void lcd_disable_raster(void)
> +static inline void lcd_disable_raster(bool wait_for_frame_done)
>  {
>  	u32 reg;
> +	u32 stat_reg = LCD_STAT_REG;
> +	u32 loop_cnt = 0;
>  
>  	reg = lcdc_read(LCD_RASTER_CTRL_REG);
>  	if (reg & LCD_RASTER_ENABLE)
>  		lcdc_write(reg & ~LCD_RASTER_ENABLE, LCD_RASTER_CTRL_REG);
> +
> +	if (lcd_revision = LCD_VERSION_2)
> +		stat_reg = LCD_RAW_STAT_REG;
> +
> +	if (wait_for_frame_done) {
> +		/*
> +		 * 50 milli seconds should be sufficient for a frame to
> +		 * complete
> +		 */
> +		loop_cnt = 50;
> +		while (!(lcdc_read(stat_reg) & LCD_FRAME_DONE)) {
> +			/* Handle timeout */
> +			if (unlikely(0 = --loop_cnt)) {
> +				pr_err("LCD Controller timed out\n");
> +				break;
> +			}
> +			mdelay(1);
> +		}
> +	}

The TRM you referenced in the patch description suggests waiting for
frame done interrupt. Can we actually wait for the interrupt here
instead of busy looping?

Thanks,
Sekhar

^ permalink raw reply

* Re: [RFC 0/5] Generic panel framework
From: Jingoo Han @ 2012-08-17  1:33 UTC (permalink / raw)
  To: 'Laurent Pinchart', linux-fbdev, dri-devel, linux-leds
  Cc: linux-media, 'Bryan Wu', 'Richard Purdie',
	'Tomi Valkeinen', 'Marcus Lorentzon',
	'Sumit Semwal', 'Archit Taneja',
	'Sebastien Guiriec', 'Inki Dae',
	'Kyungmin Park', 'Jingoo Han'
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>

On Friday, August 17, 2012 9:50 AM Laurent Pinchart wrote:
> 
> Hi everybody,
> 
> While working on DT bindings for the Renesas Mobile SoC display controller
> (a.k.a. LCDC) I quickly realized that display panel implementation based on
> board code callbacks would need to be replaced by a driver-based panel
> framework.
> 
> Several driver-based panel support solution already exist in the kernel.
> 
> - The LCD device class is implemented in drivers/video/backlight/lcd.c and
>   exposes a kernel API in include/linux/lcd.h. That API is tied to the FBDEV
>   API for historical reason, uses board code callback for reset and power
>   management, and doesn't include support for standard features available in
>   today's "smart panels".
> 
> - OMAP2+ based systems use custom panel drivers available in
>   drivers/video/omap2/displays. Those drivers are based on OMAP DSS (display
>   controller) specific APIs.
> 
> - Similarly, Exynos based systems use custom panel drivers available in
>   drivers/video/exynos. Only a single driver (s6e8ax0) is currently available.
>   That driver is based on Exynos display controller specific APIs and on the
>   LCD device class API.
> 

Hi Laurent,

I am a Exynos DP maintainer and Samsung FB maintainer.

Actually, eDP (embedded display port) will be faced with this kind of problem.
According to the eDP standard, eDP panel can have their own specific registers
that handle extra operations. In this case, custom panel driver for this eDP panel
will be necessary.

In my opinion, the panel framework would be helpful to solve this problem.


Best regards,
Jingoo Han


> I've brought up the issue with Tomi Valkeinen (OMAP DSS maintainer) and Marcus
> Lorentzon (working on panel support for ST/Linaro), and we agreed that a
> generic panel framework for display devices is needed. These patches implement
> a first proof of concept.
> 
> One of the main reasons for creating a new panel framework instead of adding
> missing features to the LCD framework is to avoid being tied to the FBDEV
> framework. Panels will be used by DRM drivers as well, and their API should
> thus be subsystem-agnostic. Note that the panel framework used the
> fb_videomode structure in its API, this will be replaced by a common video
> mode structure shared across subsystems (there's only so many hours per day).
> 
> Panels, as used in these patches, are defined as physical devices combining a
> matrix of pixels and a controller capable of driving that matrix.
> 
> Panel physical devices are registered as children of the control bus the panel
> controller is connected to (depending on the panel type, we can find platform
> devices for dummy panels with no control bus, or I2C, SPI, DBI, DSI, ...
> devices). The generic panel framework matches registered panel devices with
> panel drivers and call the panel drivers probe method, as done by other device
> classes in the kernel. The driver probe() method is responsible for
> instantiating a struct panel instance and registering it with the generic
> panel framework.
> 
> Display drivers are panel consumers. They register a panel notifier with the
> framework, which then calls the notifier when a matching panel is registered.
> The reason for this asynchronous mode of operation, compared to how drivers
> acquire regulator or clock resources, is that the panel can use resources
> provided by the display driver. For instance a panel can be a child of the DBI
> or DSI bus controlled by the display device, or use a clock provided by that
> device. We can't defer the display device probe until the panel is registered
> and also defer the panel device probe until the display is registered. As
> most display drivers need to handle output devices hotplug (HDMI monitors for
> instance), handling panel through a notification system seemed to be the
> easiest solution.
> 
> Note that this brings a different issue after registration, as display and
> panel drivers would take a reference to each other. Those circular references
> would make driver unloading impossible. I haven't found a good solution for
> that problem yet (hence the RFC state of those patches), and I would
> appreciate your input here. This might also be a hint that the framework
> design is wrong to start with. I guess I can't get everything right on the
> first try ;-)
> 
> Getting hold of the panel is the most complex part. Once done, display drivers
> can call abstract operations provided by panel drivers to control the panel
> operation. These patches implement three of those operations (enable, start
> transfer and get modes). More operations will be needed, and those three
> operations will likely get modified during review. Most of the panels on
> devices I own are dumb panels with no control bus, and are thus not the best
> candidates to design a framework that needs to take complex panels' needs into
> account.
> 
> In addition to the generic panel core, I've implemented MIPI DBI (Display Bus
> Interface, a parallel bus for panels that supports read/write transfers of
> commands and data) bus support, as well as three panel drivers (dummy panels
> with no control bus, and Renesas R61505- and R61517-based panels, both using
> DBI as their control bus). Only the dummy panel driver has been tested as I
> lack hardware for the two other drivers.
> 
> I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If
> you can find a clever way to solve the cyclic references issue described above
> I'll buy you a beer at the next conference we will both attend. If you think
> the proposed solution is too complex, or too simple, I'm all ears. I
> personally already feel that we might need something even more generic to
> support other kinds of external devices connected to display controllers, such
> as external DSI to HDMI converters for instance. Some kind of video entity
> exposing abstract operations like the panels do would make sense, in which
> case panels would "inherit" from that video entity.
> 
> Speaking of conferences, I will attend the KS/LPC in San Diego in a bit more
> than a week, and would be happy to discuss this topic face to face there.
> 
> Laurent Pinchart (5):
>   video: Add generic display panel core
>   video: panel: Add dummy panel support
>   video: panel: Add MIPI DBI bus support
>   video: panel: Add R61505 panel support
>   video: panel: Add R61517 panel support
> 
>  drivers/video/Kconfig              |    1 +
>  drivers/video/Makefile             |    1 +
>  drivers/video/panel/Kconfig        |   37 +++
>  drivers/video/panel/Makefile       |    5 +
>  drivers/video/panel/panel-dbi.c    |  217 +++++++++++++++
>  drivers/video/panel/panel-dummy.c  |  103 +++++++
>  drivers/video/panel/panel-r61505.c |  520 ++++++++++++++++++++++++++++++++++++
>  drivers/video/panel/panel-r61517.c |  408 ++++++++++++++++++++++++++++
>  drivers/video/panel/panel.c        |  269 +++++++++++++++++++
>  include/video/panel-dbi.h          |   92 +++++++
>  include/video/panel-dummy.h        |   25 ++
>  include/video/panel-r61505.h       |   27 ++
>  include/video/panel-r61517.h       |   28 ++
>  include/video/panel.h              |  111 ++++++++
>  14 files changed, 1844 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/video/panel/Kconfig
>  create mode 100644 drivers/video/panel/Makefile
>  create mode 100644 drivers/video/panel/panel-dbi.c
>  create mode 100644 drivers/video/panel/panel-dummy.c
>  create mode 100644 drivers/video/panel/panel-r61505.c
>  create mode 100644 drivers/video/panel/panel-r61517.c
>  create mode 100644 drivers/video/panel/panel.c
>  create mode 100644 include/video/panel-dbi.h
>  create mode 100644 include/video/panel-dummy.h
>  create mode 100644 include/video/panel-r61505.h
>  create mode 100644 include/video/panel-r61517.h
>  create mode 100644 include/video/panel.h
> 
> --
> Regards,
> 
> Laurent Pinchart
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


^ permalink raw reply

* [RFC 5/5] video: panel: Add R61517 panel support
From: Laurent Pinchart @ 2012-08-17  0:49 UTC (permalink / raw)
  To: linux-fbdev, dri-devel, linux-leds
  Cc: linux-media, Bryan Wu, Richard Purdie, Tomi Valkeinen,
	Marcus Lorentzon, Sumit Semwal, Archit Taneja, Sebastien Guiriec,
	Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>

The R61517 is a MIPI DBI panel controller from Renesas.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/video/panel/Kconfig        |    9 +
 drivers/video/panel/Makefile       |    1 +
 drivers/video/panel/panel-r61517.c |  408 ++++++++++++++++++++++++++++++++++++
 include/video/panel-r61517.h       |   28 +++
 4 files changed, 446 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/panel/panel-r61517.c
 create mode 100644 include/video/panel-r61517.h

diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig
index 12d7712..bd643be 100644
--- a/drivers/video/panel/Kconfig
+++ b/drivers/video/panel/Kconfig
@@ -25,4 +25,13 @@ config DISPLAY_PANEL_R61505
 
 	  If you are in doubt, say N.
 
+config DISPLAY_PANEL_R61517
+	tristate "Renesas R61517-based Display Panel"
+	select DISPLAY_PANEL_DBI
+	---help---
+	  Support panels based on the Renesas R61517 panel controller.
+	  Those panels are controlled through a MIPI DBI interface.
+
+	  If you are in doubt, say N.
+
 endif # DISPLAY_PANEL
diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile
index e4fb9fe..3c11d26 100644
--- a/drivers/video/panel/Makefile
+++ b/drivers/video/panel/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_DISPLAY_PANEL) += panel.o
 obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o
 obj-$(CONFIG_DISPLAY_PANEL_DBI) += panel-dbi.o
 obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o
+obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o
diff --git a/drivers/video/panel/panel-r61517.c b/drivers/video/panel/panel-r61517.c
new file mode 100644
index 0000000..6e8d933
--- /dev/null
+++ b/drivers/video/panel/panel-r61517.c
@@ -0,0 +1,408 @@
+/*
+ * Renesas R61517-based Display Panels
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ * Based on KFR2R09 LCD panel support
+ * Copyright (C) 2009 Magnus Damm
+ * Register settings based on the out-of-tree t33fb.c driver
+ * Copyright (C) 2008 Lineo Solutions, Inc.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+
+#include <video/panel-dbi.h>
+#include <video/panel-r61517.h>
+
+struct r61517 {
+	struct panel panel;
+	struct panel_dbi_device *dbi;
+	const struct panel_r61517_platform_data *pdata;
+};
+
+#define to_panel(p)	container_of(p, struct r61517, panel)
+
+/* -----------------------------------------------------------------------------
+ * Read, write and reset
+ */
+
+static void r61517_write_command(struct r61517 *panel, u16 reg)
+{
+	panel_dbi_write_command(panel->dbi, reg);
+}
+
+static void r61517_write_data(struct r61517 *panel, u16 data)
+{
+	panel_dbi_write_data(panel->dbi, data);
+}
+
+static void r61517_write(struct r61517 *panel, u16 reg, u16 data)
+{
+	panel_dbi_write_command(panel->dbi, reg);
+	panel_dbi_write_data(panel->dbi, data);
+}
+
+static u16 r61517_read_data(struct r61517 *panel)
+{
+	return panel_dbi_read_data(panel->dbi);
+}
+
+static void __r61517_write_array(struct r61517 *panel, const u8 *data,
+				 unsigned int len)
+{
+	unsigned int i;
+
+	for (i = 0; i < len; ++i)
+		r61517_write_data(panel, data[i]);
+}
+
+#define r61517_write_array(p, a) \
+	__r61517_write_array(p, a, ARRAY_SIZE(a))
+
+static void r61517_reset(struct r61517 *panel)
+{
+	gpio_set_value(panel->pdata->protect, 0);	/* PROTECT/ -> L */
+	gpio_set_value(panel->pdata->reset, 0);		/* LCD_RST/ -> L */
+	gpio_set_value(panel->pdata->protect, 1);	/* PROTECT/ -> H */
+	usleep_range(1100, 1200);
+	gpio_set_value(panel->pdata->reset, 1);		/* LCD_RST/ -> H */
+	usleep_range(10, 100);
+	gpio_set_value(panel->pdata->protect, 0);	/* PROTECT/ -> L */
+	msleep(20);
+}
+
+/* -----------------------------------------------------------------------------
+ * Configuration
+ */
+
+static const u8 data_frame_if[] = {
+	0x02, /* WEMODE: 1=cont, 0=one-shot */
+	0x00, 0x00,
+	0x00, /* EPF, DFM */
+	0x02, /* RIM[1] : 1 (18bpp) */
+};
+
+static const u8 data_panel[] = {
+	0x0b,
+	0x63, /* 400 lines */
+	0x04, 0x00, 0x00, 0x04, 0x11, 0x00, 0x00,
+};
+
+static const u8 data_timing[] = {
+	0x00, 0x00, 0x13, 0x08, 0x08,
+};
+
+static const u8 data_timing_src[] = {
+	0x11, 0x01, 0x00, 0x01,
+};
+
+static const u8 data_gamma[] = {
+	0x01, 0x02, 0x08, 0x23,	0x03, 0x0c, 0x00, 0x06,	0x00, 0x00,
+	0x01, 0x00, 0x0c, 0x23, 0x03, 0x08, 0x02, 0x06, 0x00, 0x00,
+};
+
+static const u8 data_power[] = {
+	0x07, 0xc5, 0xdc, 0x02,	0x33, 0x0a,
+};
+
+static unsigned long r61517_read_device_code(struct r61517 *panel)
+{
+	/* access protect OFF */
+	r61517_write(panel, 0xb0, 0x00);
+
+	/* deep standby OFF */
+	r61517_write(panel, 0xb1, 0x00);
+
+	/* device code command */
+	r61517_write_command(panel, 0xbf);
+	mdelay(50);
+
+	/* dummy read */
+	r61517_read_data(panel);
+
+	/* read device code */
+	return ((r61517_read_data(panel) & 0xff) << 24) |
+	       ((r61517_read_data(panel) & 0xff) << 16) |
+	       ((r61517_read_data(panel) & 0xff) << 8) |
+	       ((r61517_read_data(panel) & 0xff) << 0);
+}
+
+static void r61517_write_memory_start(struct r61517 *panel)
+{
+	r61517_write_command(panel, 0x2c);
+}
+
+static void r61517_clear_memory(struct r61517 *panel)
+{
+	unsigned int i;
+
+	r61517_write_memory_start(panel);
+
+	for (i = 0; i < (240 * 400); i++)
+		r61517_write_data(panel, 0);
+}
+
+static void r61517_enable_panel(struct r61517 *panel)
+{
+	/* access protect off */
+	r61517_write(panel, 0xb0, 0x00);
+
+	/* exit deep standby mode */
+	r61517_write(panel, 0xb1, 0x00);
+
+	/* frame memory I/F */
+	r61517_write_command(panel, 0xb3);
+	r61517_write_array(panel, data_frame_if);
+
+	/* display mode and frame memory write mode */
+	r61517_write(panel, 0xb4, 0x00); /* DBI, internal clock */
+
+	/* panel */
+	r61517_write_command(panel, 0xc0);
+	r61517_write_array(panel, data_panel);
+
+	/* timing (normal) */
+	r61517_write_command(panel, 0xc1);
+	r61517_write_array(panel, data_timing);
+
+	/* timing (partial) */
+	r61517_write_command(panel, 0xc2);
+	r61517_write_array(panel, data_timing);
+
+	/* timing (idle) */
+	r61517_write_command(panel, 0xc3);
+	r61517_write_array(panel, data_timing);
+
+	/* timing (source/VCOM/gate driving) */
+	r61517_write_command(panel, 0xc4);
+	r61517_write_array(panel, data_timing_src);
+
+	/* gamma (red) */
+	r61517_write_command(panel, 0xc8);
+	r61517_write_array(panel, data_gamma);
+
+	/* gamma (green) */
+	r61517_write_command(panel, 0xc9);
+	r61517_write_array(panel, data_gamma);
+
+	/* gamma (blue) */
+	r61517_write_command(panel, 0xca);
+	r61517_write_array(panel, data_gamma);
+
+	/* power (common) */
+	r61517_write_command(panel, 0xd0);
+	r61517_write_array(panel, data_power);
+
+	/* VCOM */
+	r61517_write_command(panel, 0xd1);
+	r61517_write_data(panel, 0x00);
+	r61517_write_data(panel, 0x0f);
+	r61517_write_data(panel, 0x02);
+
+	/* power (normal) */
+	r61517_write_command(panel, 0xd2);
+	r61517_write_data(panel, 0x63);
+	r61517_write_data(panel, 0x24);
+
+	/* power (partial) */
+	r61517_write_command(panel, 0xd3);
+	r61517_write_data(panel, 0x63);
+	r61517_write_data(panel, 0x24);
+
+	/* power (idle) */
+	r61517_write_command(panel, 0xd4);
+	r61517_write_data(panel, 0x63);
+	r61517_write_data(panel, 0x24);
+
+	r61517_write_command(panel, 0xd8);
+	r61517_write_data(panel, 0x77);
+	r61517_write_data(panel, 0x77);
+
+	/* TE signal */
+	r61517_write(panel, 0x35, 0x00);
+
+	/* TE signal line */
+	r61517_write_command(panel, 0x44);
+	r61517_write_data(panel, 0x00);
+	r61517_write_data(panel, 0x00);
+
+	/* column address */
+	r61517_write_command(panel, 0x2a);
+	r61517_write_data(panel, 0x00);
+	r61517_write_data(panel, 0x00);
+	r61517_write_data(panel, 0x00);
+	r61517_write_data(panel, 0xef);
+
+	/* page address */
+	r61517_write_command(panel, 0x2b);
+	r61517_write_data(panel, 0x00);
+	r61517_write_data(panel, 0x00);
+	r61517_write_data(panel, 0x01);
+	r61517_write_data(panel, 0x8f);
+
+	/* exit sleep mode */
+	r61517_write_command(panel, 0x11);
+
+	mdelay(120);
+
+	/* clear vram */
+	r61517_clear_memory(panel);
+}
+
+static void r61517_disable_panel(struct r61517 *panel)
+{
+	r61517_reset(panel);
+}
+
+static void r61517_display_on(struct r61517 *panel)
+{
+	r61517_write_command(panel, 0x29);
+	mdelay(1);
+}
+
+static void r61517_display_off(struct r61517 *panel)
+{
+	r61517_write_command(panel, 0x28);
+}
+
+/* -----------------------------------------------------------------------------
+ * Panel operations
+ */
+
+static int r61517_enable(struct panel *p, enum panel_enable_mode enable)
+{
+	struct r61517 *panel = to_panel(p);
+
+	switch (enable) {
+	case PANEL_ENABLE_OFF:
+		r61517_disable_panel(panel);
+		break;
+
+	case PANEL_ENABLE_BLANK:
+		if (p->enable = PANEL_ENABLE_OFF)
+			r61517_enable_panel(panel);
+		else
+			r61517_display_off(panel);
+		break;
+
+	case PANEL_ENABLE_ON:
+		if (p->enable = PANEL_ENABLE_OFF)
+			r61517_enable_panel(panel);
+
+		r61517_display_on(panel);
+		break;
+	}
+
+	return 0;
+}
+
+static int r61517_start_transfer(struct panel *p)
+{
+	struct r61517 *panel = to_panel(p);
+
+	r61517_write_memory_start(panel);
+	return 0;
+}
+
+static int r61517_get_modes(struct panel *p, const struct fb_videomode **modes)
+{
+	struct r61517 *panel = to_panel(p);
+
+	*modes = panel->pdata->mode;
+	return 1;
+}
+
+static const struct panel_ops r61517_ops = {
+	.enable = r61517_enable,
+	.start_transfer = r61517_start_transfer,
+	.get_modes = r61517_get_modes,
+};
+
+static void r61517_release(struct panel *p)
+{
+	struct r61517 *panel = to_panel(p);
+
+	kfree(panel);
+}
+
+static int r61517_remove(struct panel_dbi_device *dev)
+{
+	struct r61517 *panel = panel_dbi_get_drvdata(dev);
+
+	panel_dbi_set_drvdata(dev, NULL);
+	panel_unregister(&panel->panel);
+
+	return 0;
+}
+
+static int __devinit r61517_probe(struct panel_dbi_device *dev)
+{
+	const struct panel_r61517_platform_data *pdata = dev->dev.platform_data;
+	struct r61517 *panel;
+	int ret;
+
+	if (pdata = NULL)
+		return -ENODEV;
+
+	panel = kzalloc(sizeof(*panel), GFP_KERNEL);
+	if (panel = NULL)
+		return -ENOMEM;
+
+	panel->pdata = pdata;
+	panel->dbi = dev;
+
+	r61517_reset(panel);
+
+	if (r61517_read_device_code(panel) != 0x01221517) {
+		kfree(panel);
+		return -ENODEV;
+	}
+
+	pr_info("R61517 panel controller detected.\n");
+
+	panel->panel.dev = &dev->dev;
+	panel->panel.release = r61517_release;
+	panel->panel.ops = &r61517_ops;
+	panel->panel.width = pdata->width;
+	panel->panel.height = pdata->height;
+
+	ret = panel_register(&panel->panel);
+	if (ret < 0) {
+		kfree(panel);
+		return ret;
+	}
+
+	panel_dbi_set_drvdata(dev, panel);
+
+	return 0;
+}
+
+static const struct dev_pm_ops r61517_dev_pm_ops = {
+};
+
+static struct panel_dbi_driver r61517_driver = {
+	.probe = r61517_probe,
+	.remove = r61517_remove,
+	.driver = {
+		.name = "panel_r61517",
+		.owner = THIS_MODULE,
+		.pm = &r61517_dev_pm_ops,
+	},
+};
+
+module_panel_dbi_driver(r61517_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas R61517-based Display Panel");
+MODULE_LICENSE("GPL");
diff --git a/include/video/panel-r61517.h b/include/video/panel-r61517.h
new file mode 100644
index 0000000..c9e6ddf
--- /dev/null
+++ b/include/video/panel-r61517.h
@@ -0,0 +1,28 @@
+/*
+ * Renesas R61517-based Display Panels
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __PANEL_R61517_H__
+#define __PANEL_R61517_H__
+
+#include <linux/fb.h>
+#include <video/panel.h>
+
+struct panel_r61517_platform_data {
+	unsigned long width;		/* Panel width in mm */
+	unsigned long height;		/* Panel height in mm */
+	const struct fb_videomode *mode;
+
+	int protect;			/* Protect GPIO */
+	int reset;			/* Reset GPIO */
+};
+
+#endif /* __PANEL_R61517_H__ */
-- 
1.7.8.6


^ permalink raw reply related

* [RFC 4/5] video: panel: Add R61505 panel support
From: Laurent Pinchart @ 2012-08-17  0:49 UTC (permalink / raw)
  To: linux-fbdev, dri-devel, linux-leds
  Cc: linux-media, Bryan Wu, Richard Purdie, Tomi Valkeinen,
	Marcus Lorentzon, Sumit Semwal, Archit Taneja, Sebastien Guiriec,
	Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>

The R61505 is a SYS-80 bus panel controller from Renesas.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/video/panel/Kconfig        |    9 +
 drivers/video/panel/Makefile       |    1 +
 drivers/video/panel/panel-r61505.c |  520 ++++++++++++++++++++++++++++++++++++
 include/video/panel-r61505.h       |   27 ++
 4 files changed, 557 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/panel/panel-r61505.c
 create mode 100644 include/video/panel-r61505.h

diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig
index fd0b3cf..12d7712 100644
--- a/drivers/video/panel/Kconfig
+++ b/drivers/video/panel/Kconfig
@@ -16,4 +16,13 @@ config DISPLAY_PANEL_DBI
 	tristate
 	default n
 
+config DISPLAY_PANEL_R61505
+	tristate "Renesas R61505-based Display Panel"
+	select DISPLAY_PANEL_DBI
+	---help---
+	  Support panels based on the Renesas R61505 panel controller.
+	  Those panels are controlled through a MIPI DBI interface.
+
+	  If you are in doubt, say N.
+
 endif # DISPLAY_PANEL
diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile
index 2ab0520..e4fb9fe 100644
--- a/drivers/video/panel/Makefile
+++ b/drivers/video/panel/Makefile
@@ -1,3 +1,4 @@
 obj-$(CONFIG_DISPLAY_PANEL) += panel.o
 obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o
 obj-$(CONFIG_DISPLAY_PANEL_DBI) += panel-dbi.o
+obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o
diff --git a/drivers/video/panel/panel-r61505.c b/drivers/video/panel/panel-r61505.c
new file mode 100644
index 0000000..e09455e
--- /dev/null
+++ b/drivers/video/panel/panel-r61505.c
@@ -0,0 +1,520 @@
+/*
+ * Renesas R61505-based Display Panels
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ * Based on SuperH MigoR Quarter VGA LCD Panel
+ * Copyright (C) 2008 Magnus Damm
+ * Based on lcd_powertip.c from Kenati Technologies Pvt Ltd.
+ * Copyright (c) 2007 Ujjwal Pande
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <video/panel-r61505.h>
+#include <video/panel-dbi.h>
+
+#define R61505_DEVICE_CODE			0x0000
+#define R61505_DEVICE_CODE_VALUE		0x1505
+#define R61505_DRIVER_OUTPUT_CONTROL		0x0001
+#define R61505_DRIVER_OUTPUT_CONTROL_SM		(1 << 10)
+#define R61505_DRIVER_OUTPUT_CONTROL_SS		(1 << 8)
+#define R61505_LCD_WAVEFORM			0x0002
+#define R61505_LCD_WAVEFORM_BC0			(1 << 9)
+#define R61505_LCD_WAVEFORM_EOR			(1 << 8)
+#define R61505_ENTRY_MODE			0x0003
+#define R61505_ENTRY_MODE_TRIREG		(1 << 15)
+#define R61505_ENTRY_MODE_DFM			(1 << 14)
+#define R61505_ENTRY_MODE_BGR			(1 << 12)
+#define R61505_ENTRY_MODE_HWM			(1 << 9)
+#define R61505_ENTRY_MODE_ORG			(1 << 7)
+#define R61505_ENTRY_MODE_ID1			(1 << 5)
+#define R61505_ENTRY_MODE_ID0			(1 << 4)
+#define R61505_ENTRY_MODE_AM			(1 << 3)
+#define R61505_RESIZE_CONTROL			0x0004
+#define R61505_RESIZE_CONTROL_RCV(n)		(((n) & 3) << 8)
+#define R61505_RESIZE_CONTROL_RCH(n)		(((n) & 3) << 4)
+#define R61505_RESIZE_CONTROL_RSZ_4		(3 << 0)
+#define R61505_RESIZE_CONTROL_RSZ_2		(1 << 0)
+#define R61505_RESIZE_CONTROL_RSZ_1		(0 << 0)
+#define R61505_DISPLAY_CONTROL1			0x0007
+#define R61505_DISPLAY_CONTROL1_PTDE1		(1 << 13)
+#define R61505_DISPLAY_CONTROL1_PTDE0		(1 << 12)
+#define R61505_DISPLAY_CONTROL1_BASEE		(1 << 8)
+#define R61505_DISPLAY_CONTROL1_VON		(1 << 6)
+#define R61505_DISPLAY_CONTROL1_GON		(1 << 5)
+#define R61505_DISPLAY_CONTROL1_DTE		(1 << 4)
+#define R61505_DISPLAY_CONTROL1_COL		(1 << 3)
+#define R61505_DISPLAY_CONTROL1_D1		(1 << 1)
+#define R61505_DISPLAY_CONTROL1_D0		(1 << 0)
+#define R61505_DISPLAY_CONTROL2			0x0008
+#define R61505_DISPLAY_CONTROL2_FP(n)		(((n) & 0xf) << 8)
+#define R61505_DISPLAY_CONTROL2_BP(n)		(((n) & 0xf) << 0)
+#define R61505_DISPLAY_CONTROL3			0x0009
+#define R61505_DISPLAY_CONTROL3_PTS(n)		(((n) & 7) << 8)
+#define R61505_DISPLAY_CONTROL3_PTG(n)		(((n) & 3) << 3)
+#define R61505_DISPLAY_CONTROL3_ICS(n)		(((n) & 0xf) << 0)
+#define R61505_DISPLAY_CONTROL4			0x000a
+#define R61505_DISPLAY_CONTROL4_FMARKOE		(1 << 3)
+#define R61505_DISPLAY_CONTROL4_FMI_6		(5 << 0)
+#define R61505_DISPLAY_CONTROL4_FMI_4		(3 << 0)
+#define R61505_DISPLAY_CONTROL4_FMI_2		(1 << 0)
+#define R61505_DISPLAY_CONTROL4_FMI_1		(0 << 0)
+#define R61505_EXT_DISPLAY_IF_CONTROL1		0x000c
+#define R61505_EXT_DISPLAY_IF_CONTROL1_ENC(n)	(((n) & 7) << 12)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_RM	(1 << 8)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_VSYNC	(2 << 4)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_RGB	(1 << 4)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_ICLK	(0 << 4)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_6	(2 << 0)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_16	(1 << 0)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_18	(0 << 0)
+#define R61505_FRAME_MARKER_CONTROL		0x000d
+#define R61505_FRAME_MARKER_CONTROL_FMP(n)	(((n) & 0x1ff) << 0)
+#define R61505_EXT_DISPLAY_IF_CONTROL2		0x000f
+#define R61505_POWER_CONTROL1			0x0010
+#define R61505_POWER_CONTROL1_SAP		(1 << 12)
+#define R61505_POWER_CONTROL1_BT(n)		(((n) & 0xf) << 8)
+#define R61505_POWER_CONTROL1_APE		(1 << 7)
+#define R61505_POWER_CONTROL1_AP_100		(3 << 4)
+#define R61505_POWER_CONTROL1_AP_075		(2 << 4)
+#define R61505_POWER_CONTROL1_AP_050		(1 << 4)
+#define R61505_POWER_CONTROL1_AP_HALT		(0 << 4)
+#define R61505_POWER_CONTROL1_DSTB		(1 << 2)
+#define R61505_POWER_CONTROL1_SLP		(1 << 1)
+#define R61505_POWER_CONTROL2			0x0011
+#define R61505_POWER_CONTROL2_DC1_HALT		(6 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_256	(4 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_128	(3 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_64	(2 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_32	(1 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_16	(0 << 8)
+#define R61505_POWER_CONTROL2_DC0_HALT		(6 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC_16	(4 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC_8	(3 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC_4	(2 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC_2	(1 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC		(0 << 4)
+#define R61505_POWER_CONTROL2_VC_100		(7 << 0)
+#define R61505_POWER_CONTROL2_VC_076		(4 << 0)
+#define R61505_POWER_CONTROL2_VC_089		(1 << 0)
+#define R61505_POWER_CONTROL2_VC_094		(0 << 0)
+#define R61505_POWER_CONTROL3			0x0012
+#define R61505_POWER_CONTROL3_VCMR		(1 << 8)
+#define R61505_POWER_CONTROL3_PSON		(1 << 5)
+#define R61505_POWER_CONTROL3_PON		(1 << 4)
+#define R61505_POWER_CONTROL3_VRH(n)		(((n) & 0xf) << 0)
+#define R61505_POWER_CONTROL4			0x0013
+#define R61505_POWER_CONTROL4_VDV(n)		(((n) & 0xf) << 8)
+#define R61505_POWER_CONTROL5			0x0015
+#define R61505_POWER_CONTROL5_BLDM		(1 << 12)
+#define R61505_POWER_CONTROL6			0x0017
+#define R61505_POWER_CONTROL6_PSE		(1 << 0)
+#define R61505_RAM_ADDR_HORZ			0x0020
+#define R61505_RAM_ADDR_VERT			0x0021
+#define R61505_RAM_DATA				0x0022
+#define R61505_POWER_CONTROL7			0x0029
+#define R61505_POWER_CONTROL7_VCM1(n)		(((n) & 0x1f) << 0)
+#define R61505_GAMMA_CONTROL1			0x0030
+#define R61505_GAMMA_CONTROL2			0x0031
+#define R61505_GAMMA_CONTROL3			0x0032
+#define R61505_GAMMA_CONTROL4			0x0033
+#define R61505_GAMMA_CONTROL5			0x0034
+#define R61505_GAMMA_CONTROL6			0x0035
+#define R61505_GAMMA_CONTROL7			0x0036
+#define R61505_GAMMA_CONTROL8			0x0037
+#define R61505_GAMMA_CONTROL9			0x0038
+#define R61505_GAMMA_CONTROL10			0x0039
+#define R61505_GAMMA_CONTROL11			0x003a
+#define R61505_GAMMA_CONTROL12			0x003b
+#define R61505_GAMMA_CONTROL13			0x003c
+#define R61505_GAMMA_CONTROL14			0x003d
+#define R61505_WINDOW_HORZ_START		0x0050
+#define R61505_WINDOW_HORZ_END			0x0051
+#define R61505_WINDOW_VERT_START		0x0052
+#define R61505_WINDOW_VERT_END			0x0053
+#define R61505_DRIVER_OUTPUT_CONTROL2		0x0060
+#define R61505_DRIVER_OUTPUT_CONTROL2_GS	(1 << 15)
+#define R61505_DRIVER_OUTPUT_CONTROL2_NL(n)	(((n) & 0x3f) << 8)
+#define R61505_DRIVER_OUTPUT_CONTROL2_SCN(n)	(((n) & 0x3f) << 0)
+#define R61505_BASE_IMG_DISPLAY_CONTROL		0x0061
+#define R61505_BASE_IMG_DISPLAY_CONTROL_NDL	(1 << 2)
+#define R61505_BASE_IMG_DISPLAY_CONTROL_VLE	(1 << 1)
+#define R61505_BASE_IMG_DISPLAY_CONTROL_REV	(1 << 0)
+#define R61505_VERTICAL_SCROLL_CONTROL		0x006a
+#define R61505_PANEL_IF_CONTROL1		0x0090
+#define R61505_PANEL_IF_CONTROL1_DIVI(n)	(((n) & 3) << 8)
+#define R61505_PANEL_IF_CONTROL1_RTNI(n)	(((n) & 0x1f) << 0)
+#define R61505_PANEL_IF_CONTROL2		0x0092
+#define R61505_PANEL_IF_CONTROL2_NOWI(n)	(((n) & 7) << 8)
+#define R61505_PANEL_IF_CONTROL3		0x0093
+#define R61505_PANEL_IF_CONTROL3_MCP(n)		(((n) & 7) << 8)
+#define R61505_PANEL_IF_CONTROL4		0x0095
+#define R61505_PANEL_IF_CONTROL5		0x0097
+#define R61505_PANEL_IF_CONTROL6		0x0098
+#define R61505_OSCILLATION_CONTROL		0x00a4
+#define R61505_OSCILLATION_CONTROL_CALB		(1 << 0)
+
+struct r61505 {
+	struct panel panel;
+	struct panel_dbi_device *dbi;
+	const struct panel_r61505_platform_data *pdata;
+};
+
+#define to_panel(p)	container_of(p, struct r61505, panel)
+
+/* -----------------------------------------------------------------------------
+ * Read, write and reset
+ */
+
+/* DB0-DB7 are connected to D1-D8, and DB8-DB15 to D10-D17 */
+
+static unsigned long adjust_reg18(u16 data)
+{
+	return (((data << 1) | 0x00000001) & 0x000001ff) |
+	       (((data << 2) | 0x00000200) & 0x0003fe00);
+}
+
+static void r61505_write_command(struct r61505 *panel, u16 reg)
+{
+	panel_dbi_write_command(panel->dbi, adjust_reg18(reg));
+}
+
+static void r61505_write(struct r61505 *panel, u16 reg, u16 data)
+{
+	panel_dbi_write_command(panel->dbi, adjust_reg18(reg));
+	panel_dbi_write_data(panel->dbi, adjust_reg18(data));
+}
+
+static u16 r61505_read(struct r61505 *panel, u16 reg)
+{
+	unsigned long data;
+
+	panel_dbi_write_command(panel->dbi, adjust_reg18(reg));
+	data = panel_dbi_read_data(panel->dbi);
+
+	return ((data >> 1) & 0xff) | ((data >> 2) & 0xff00);
+}
+
+static void r61505_write_array(struct r61505 *panel,
+				 const u16 *data, unsigned int len)
+{
+	unsigned int i;
+
+	for (i = 0; i < len; i += 2)
+		r61505_write(panel, data[i], data[i + 1]);
+}
+
+static void r61505_reset(struct r61505 *panel)
+{
+	if (panel->pdata->reset < 0)
+		return;
+
+	gpio_set_value(panel->pdata->reset, 0);
+	usleep_range(2000, 2500);
+	gpio_set_value(panel->pdata->reset, 1);
+	usleep_range(1000, 1500);
+}
+
+/* -----------------------------------------------------------------------------
+ * Configuration
+ */
+
+static const unsigned short sync_data[] = {
+	0x0000, 0x0000,
+	0x0000, 0x0000,
+	0x0000, 0x0000,
+	0x0000, 0x0000,
+};
+
+static const unsigned short magic0_data[] = {
+	R61505_DISPLAY_CONTROL2, R61505_DISPLAY_CONTROL2_FP(8) |
+				 R61505_DISPLAY_CONTROL2_BP(8),
+	R61505_PANEL_IF_CONTROL1, R61505_PANEL_IF_CONTROL1_RTNI(26),
+	R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_D0,
+	R61505_POWER_CONTROL6, R61505_POWER_CONTROL6_PSE,
+	0x0019, 0x0000,
+	R61505_POWER_CONTROL1, R61505_POWER_CONTROL1_SAP |
+			       R61505_POWER_CONTROL1_BT(7) |
+			       R61505_POWER_CONTROL1_APE |
+			       R61505_POWER_CONTROL1_AP_100,
+	R61505_POWER_CONTROL2, R61505_POWER_CONTROL2_DC1_FOSC_32 |
+			       R61505_POWER_CONTROL2_DC0_FOSC_2 | 6,
+	R61505_POWER_CONTROL3, R61505_POWER_CONTROL3_VCMR | 0x80 |
+			       R61505_POWER_CONTROL3_PON |
+			       R61505_POWER_CONTROL3_VRH(8),
+	R61505_POWER_CONTROL4, 0x1000 | R61505_POWER_CONTROL4_VDV(4),
+	R61505_POWER_CONTROL7, R61505_POWER_CONTROL7_VCM1(12),
+	R61505_POWER_CONTROL3, R61505_POWER_CONTROL3_VCMR | 0x80 |
+			       R61505_POWER_CONTROL3_PSON |
+			       R61505_POWER_CONTROL3_PON |
+			       R61505_POWER_CONTROL3_VRH(8),
+};
+
+static const unsigned short magic1_data[] = {
+	R61505_GAMMA_CONTROL1, 0x0307,
+	R61505_GAMMA_CONTROL2, 0x0303,
+	R61505_GAMMA_CONTROL3, 0x0603,
+	R61505_GAMMA_CONTROL4, 0x0202,
+	R61505_GAMMA_CONTROL5, 0x0202,
+	R61505_GAMMA_CONTROL6, 0x0202,
+	R61505_GAMMA_CONTROL7, 0x1f1f,
+	R61505_GAMMA_CONTROL8, 0x0303,
+	R61505_GAMMA_CONTROL9, 0x0303,
+	R61505_GAMMA_CONTROL10, 0x0603,
+	R61505_GAMMA_CONTROL11, 0x0202,
+	R61505_GAMMA_CONTROL12, 0x0102,
+	R61505_GAMMA_CONTROL13, 0x0204,
+	R61505_GAMMA_CONTROL14, 0x0000,
+	R61505_DRIVER_OUTPUT_CONTROL, R61505_DRIVER_OUTPUT_CONTROL_SS,
+	R61505_LCD_WAVEFORM, R61505_LCD_WAVEFORM_BC0 |
+			     R61505_LCD_WAVEFORM_EOR,
+	R61505_ENTRY_MODE, R61505_ENTRY_MODE_DFM |
+			   R61505_ENTRY_MODE_BGR |
+			   R61505_ENTRY_MODE_ID1 |
+			   R61505_ENTRY_MODE_AM,
+	R61505_RAM_ADDR_HORZ, 239,
+	R61505_RAM_ADDR_VERT, 0,
+	R61505_RESIZE_CONTROL, R61505_RESIZE_CONTROL_RCV(0) |
+			       R61505_RESIZE_CONTROL_RCH(0) |
+			       R61505_RESIZE_CONTROL_RSZ_1,
+	R61505_DISPLAY_CONTROL3, R61505_DISPLAY_CONTROL3_PTS(0) |
+				 R61505_DISPLAY_CONTROL3_PTG(0) |
+				 R61505_DISPLAY_CONTROL3_ICS(0),
+	R61505_DISPLAY_CONTROL4, R61505_DISPLAY_CONTROL4_FMARKOE |
+				 R61505_DISPLAY_CONTROL4_FMI_1,
+	R61505_EXT_DISPLAY_IF_CONTROL1, R61505_EXT_DISPLAY_IF_CONTROL1_ENC(0) |
+					R61505_EXT_DISPLAY_IF_CONTROL1_DM_ICLK |
+					R61505_EXT_DISPLAY_IF_CONTROL1_RIM_18,
+	R61505_FRAME_MARKER_CONTROL, R61505_FRAME_MARKER_CONTROL_FMP(0),
+	R61505_POWER_CONTROL5, 0x8000,
+};
+
+static const unsigned short magic2_data[] = {
+	R61505_BASE_IMG_DISPLAY_CONTROL, R61505_BASE_IMG_DISPLAY_CONTROL_REV,
+	R61505_PANEL_IF_CONTROL2, R61505_PANEL_IF_CONTROL2_NOWI(1),
+	R61505_PANEL_IF_CONTROL3, R61505_PANEL_IF_CONTROL3_MCP(1),
+	R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_GON |
+				 R61505_DISPLAY_CONTROL1_D0,
+};
+
+static const unsigned short magic3_data[] = {
+	R61505_POWER_CONTROL1, R61505_POWER_CONTROL1_SAP |
+			       R61505_POWER_CONTROL1_BT(6) |
+			       R61505_POWER_CONTROL1_APE |
+			       R61505_POWER_CONTROL1_AP_100,
+	R61505_POWER_CONTROL2, R61505_POWER_CONTROL2_DC1_FOSC_32 |
+			       R61505_POWER_CONTROL2_DC0_FOSC_2 |
+			       R61505_POWER_CONTROL2_VC_089,
+	R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_VON |
+				 R61505_DISPLAY_CONTROL1_GON |
+				 R61505_DISPLAY_CONTROL1_D0,
+};
+
+static void r61505_enable_panel(struct r61505 *panel)
+{
+	unsigned long xres = panel->pdata->mode->xres;
+	unsigned long yres = panel->pdata->mode->yres;
+	unsigned int i;
+
+	r61505_write_array(panel, sync_data, ARRAY_SIZE(sync_data));
+
+	r61505_write(panel, R61505_OSCILLATION_CONTROL,
+		     R61505_OSCILLATION_CONTROL_CALB);
+	usleep_range(10000, 11000);
+
+	r61505_write(panel, R61505_DRIVER_OUTPUT_CONTROL2,
+		     R61505_DRIVER_OUTPUT_CONTROL2_NL((xres / 8) - 1));
+	r61505_write_array(panel, magic0_data, ARRAY_SIZE(magic0_data));
+	usleep_range(100000, 101000);
+
+	r61505_write_array(panel, magic1_data, ARRAY_SIZE(magic1_data));
+
+	r61505_write(panel, R61505_WINDOW_HORZ_START, 239 - (yres - 1));
+	r61505_write(panel, R61505_WINDOW_HORZ_END, 239);
+	r61505_write(panel, R61505_WINDOW_VERT_START, 0);
+	r61505_write(panel, R61505_WINDOW_VERT_END, xres - 1);
+
+	r61505_write_array(panel, magic2_data, ARRAY_SIZE(magic2_data));
+	usleep_range(10000, 11000);
+
+	r61505_write_array(panel, magic3_data, ARRAY_SIZE(magic3_data));
+	usleep_range(40000, 41000);
+
+	/* Clear GRAM to avoid displaying garbage. */
+	r61505_write(panel, R61505_RAM_ADDR_HORZ, 0);
+	r61505_write(panel, R61505_RAM_ADDR_VERT, 0);
+
+	for (i = 0; i < (xres * 256); i++) /* yes, 256 words per line */
+		r61505_write(panel, R61505_RAM_DATA, 0);
+
+	r61505_write(panel, R61505_RAM_ADDR_HORZ, 0);
+	r61505_write(panel, R61505_RAM_ADDR_VERT, 0);
+}
+
+static void r61505_disable_panel(struct r61505 *panel)
+{
+	r61505_reset(panel);
+}
+
+static void r61505_display_on(struct r61505 *panel)
+{
+	r61505_write(panel, R61505_DISPLAY_CONTROL1,
+		     R61505_DISPLAY_CONTROL1_BASEE |
+		     R61505_DISPLAY_CONTROL1_VON |
+		     R61505_DISPLAY_CONTROL1_GON |
+		     R61505_DISPLAY_CONTROL1_DTE |
+		     R61505_DISPLAY_CONTROL1_D1 |
+		     R61505_DISPLAY_CONTROL1_D0);
+	usleep_range(40000, 41000);
+}
+
+static void r61505_display_off(struct r61505 *panel)
+{
+	r61505_write(panel, R61505_DISPLAY_CONTROL1,
+		     R61505_DISPLAY_CONTROL1_VON |
+		     R61505_DISPLAY_CONTROL1_GON |
+		     R61505_DISPLAY_CONTROL1_D0);
+}
+
+/* -----------------------------------------------------------------------------
+ * Panel operations
+ */
+
+static int r61505_enable(struct panel *p, enum panel_enable_mode enable)
+{
+	struct r61505 *panel = to_panel(p);
+
+	switch (enable) {
+	case PANEL_ENABLE_OFF:
+		r61505_disable_panel(panel);
+		break;
+
+	case PANEL_ENABLE_BLANK:
+		if (p->enable = PANEL_ENABLE_OFF)
+			r61505_enable_panel(panel);
+		else
+			r61505_display_off(panel);
+		break;
+
+	case PANEL_ENABLE_ON:
+		if (p->enable = PANEL_ENABLE_OFF)
+			r61505_enable_panel(panel);
+
+		r61505_display_on(panel);
+		break;
+	}
+
+	return 0;
+}
+
+static int r61505_start_transfer(struct panel *p)
+{
+	struct r61505 *panel = to_panel(p);
+
+	r61505_write_command(panel, R61505_RAM_DATA);
+	usleep_range(100000, 101000);
+
+	return 0;
+}
+
+static int r61505_get_modes(struct panel *p, const struct fb_videomode **modes)
+{
+	struct r61505 *panel = to_panel(p);
+
+	*modes = panel->pdata->mode;
+	return 1;
+}
+
+static const struct panel_ops r61505_ops = {
+	.enable = r61505_enable,
+	.start_transfer = r61505_start_transfer,
+	.get_modes = r61505_get_modes,
+};
+
+static void r61505_release(struct panel *p)
+{
+	struct r61505 *panel = to_panel(p);
+
+	kfree(panel);
+}
+
+static int r61505_remove(struct panel_dbi_device *dev)
+{
+	struct r61505 *panel = panel_dbi_get_drvdata(dev);
+
+	panel_dbi_set_drvdata(dev, NULL);
+	panel_unregister(&panel->panel);
+
+	return 0;
+}
+
+static int __devinit r61505_probe(struct panel_dbi_device *dev)
+{
+	const struct panel_r61505_platform_data *pdata = dev->dev.platform_data;
+	struct r61505 *panel;
+	int ret;
+
+	if (pdata = NULL)
+		return -ENODEV;
+
+	panel = kzalloc(sizeof(*panel), GFP_KERNEL);
+	if (panel = NULL)
+		return -ENOMEM;
+
+	panel->pdata = pdata;
+	panel->dbi = dev;
+
+	r61505_reset(panel);
+	r61505_write_array(panel, sync_data, ARRAY_SIZE(sync_data));
+
+	if (r61505_read(panel, 0) != R61505_DEVICE_CODE_VALUE) {
+		kfree(panel);
+		return -ENODEV;
+	}
+
+	panel->panel.dev = &dev->dev;
+	panel->panel.release = r61505_release;
+	panel->panel.ops = &r61505_ops;
+	panel->panel.width = pdata->width;
+	panel->panel.height = pdata->height;
+
+	ret = panel_register(&panel->panel);
+	if (ret < 0) {
+		kfree(panel);
+		return ret;
+	}
+
+	panel_dbi_set_drvdata(dev, panel);
+
+	return 0;
+}
+
+static const struct dev_pm_ops r61505_dev_pm_ops = {
+};
+
+static struct panel_dbi_driver r61505_driver = {
+	.probe = r61505_probe,
+	.remove = r61505_remove,
+	.driver = {
+		.name = "panel_r61505",
+		.owner = THIS_MODULE,
+		.pm = &r61505_dev_pm_ops,
+	},
+};
+
+module_panel_dbi_driver(r61505_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas R61505-based Display Panel");
+MODULE_LICENSE("GPL");
diff --git a/include/video/panel-r61505.h b/include/video/panel-r61505.h
new file mode 100644
index 0000000..90b3d62
--- /dev/null
+++ b/include/video/panel-r61505.h
@@ -0,0 +1,27 @@
+/*
+ * Renesas R61505-based Display Panels
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __PANEL_R61505_H__
+#define __PANEL_R61505_H__
+
+#include <linux/fb.h>
+#include <video/panel.h>
+
+struct panel_r61505_platform_data {
+	unsigned long width;		/* Panel width in mm */
+	unsigned long height;		/* Panel height in mm */
+	const struct fb_videomode *mode;
+
+	int reset;			/* Reset GPIO */
+};
+
+#endif /* __PANEL_R61505_H__ */
-- 
1.7.8.6


^ permalink raw reply related

* [RFC 3/5] video: panel: Add MIPI DBI bus support
From: Laurent Pinchart @ 2012-08-17  0:49 UTC (permalink / raw)
  To: linux-fbdev, dri-devel, linux-leds
  Cc: linux-media, Bryan Wu, Richard Purdie, Tomi Valkeinen,
	Marcus Lorentzon, Sumit Semwal, Archit Taneja, Sebastien Guiriec,
	Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/video/panel/Kconfig     |    4 +
 drivers/video/panel/Makefile    |    1 +
 drivers/video/panel/panel-dbi.c |  217 +++++++++++++++++++++++++++++++++++++++
 include/video/panel-dbi.h       |   92 +++++++++++++++++
 4 files changed, 314 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/panel/panel-dbi.c
 create mode 100644 include/video/panel-dbi.h

diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig
index 36fb9ca..fd0b3cf 100644
--- a/drivers/video/panel/Kconfig
+++ b/drivers/video/panel/Kconfig
@@ -12,4 +12,8 @@ config DISPLAY_PANEL_DUMMY
 
 	  If you are in doubt, say N.
 
+config DISPLAY_PANEL_DBI
+	tristate
+	default n
+
 endif # DISPLAY_PANEL
diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile
index 9fc05c2..2ab0520 100644
--- a/drivers/video/panel/Makefile
+++ b/drivers/video/panel/Makefile
@@ -1,2 +1,3 @@
 obj-$(CONFIG_DISPLAY_PANEL) += panel.o
 obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o
+obj-$(CONFIG_DISPLAY_PANEL_DBI) += panel-dbi.o
diff --git a/drivers/video/panel/panel-dbi.c b/drivers/video/panel/panel-dbi.c
new file mode 100644
index 0000000..0511997
--- /dev/null
+++ b/drivers/video/panel/panel-dbi.c
@@ -0,0 +1,217 @@
+/*
+ * MIPI DBI Bus
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+
+#include <video/panel-dbi.h>
+
+/* -----------------------------------------------------------------------------
+ * Bus operations
+ */
+
+void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long cmd)
+{
+	dev->bus->ops->write_command(dev->bus, cmd);
+}
+EXPORT_SYMBOL_GPL(panel_dbi_write_command);
+
+void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long data)
+{
+	dev->bus->ops->write_data(dev->bus, data);
+}
+EXPORT_SYMBOL_GPL(panel_dbi_write_data);
+
+unsigned long panel_dbi_read_data(struct panel_dbi_device *dev)
+{
+	return dev->bus->ops->read_data(dev->bus);
+}
+EXPORT_SYMBOL_GPL(panel_dbi_read_data);
+
+/* -----------------------------------------------------------------------------
+ * Bus type
+ */
+
+static const struct panel_dbi_device_id *
+panel_dbi_match_id(const struct panel_dbi_device_id *id,
+		   struct panel_dbi_device *dev)
+{
+	while (id->name[0]) {
+		if (strcmp(dev->name, id->name) = 0) {
+			dev->id_entry = id;
+			return id;
+		}
+		id++;
+	}
+	return NULL;
+}
+
+static int panel_dbi_match(struct device *_dev, struct device_driver *_drv)
+{
+	struct panel_dbi_device *dev = to_panel_dbi_device(_dev);
+	struct panel_dbi_driver *drv = to_panel_dbi_driver(_drv);
+
+	if (drv->id_table)
+		return panel_dbi_match_id(drv->id_table, dev) != NULL;
+
+	return (strcmp(dev->name, _drv->name) = 0);
+}
+
+static ssize_t modalias_show(struct device *_dev, struct device_attribute *a,
+			     char *buf)
+{
+	struct panel_dbi_device *dev = to_panel_dbi_device(_dev);
+	int len = snprintf(buf, PAGE_SIZE, PANEL_DBI_MODULE_PREFIX "%s\n",
+			   dev->name);
+
+	return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
+}
+
+static struct device_attribute panel_dbi_dev_attrs[] = {
+	__ATTR_RO(modalias),
+	__ATTR_NULL,
+};
+
+static int panel_dbi_uevent(struct device *_dev, struct kobj_uevent_env *env)
+{
+	struct panel_dbi_device *dev = to_panel_dbi_device(_dev);
+
+	add_uevent_var(env, "MODALIAS=%s%s", PANEL_DBI_MODULE_PREFIX,
+		       dev->name);
+	return 0;
+}
+
+static const struct dev_pm_ops panel_dbi_dev_pm_ops = {
+	.runtime_suspend = pm_generic_runtime_suspend,
+	.runtime_resume = pm_generic_runtime_resume,
+	.runtime_idle = pm_generic_runtime_idle,
+	.suspend = pm_generic_suspend,
+	.resume = pm_generic_resume,
+	.freeze = pm_generic_freeze,
+	.thaw = pm_generic_thaw,
+	.poweroff = pm_generic_poweroff,
+	.restore = pm_generic_restore,
+};
+
+static struct bus_type panel_dbi_bus_type = {
+	.name		= "mipi-dbi",
+	.dev_attrs	= panel_dbi_dev_attrs,
+	.match		= panel_dbi_match,
+	.uevent		= panel_dbi_uevent,
+	.pm		= &panel_dbi_dev_pm_ops,
+};
+
+/* -----------------------------------------------------------------------------
+ * Device and driver (un)registration
+ */
+
+/**
+ * panel_dbi_device_register - register a DBI device
+ * @dev: DBI device we're registering
+ */
+int panel_dbi_device_register(struct panel_dbi_device *dev,
+			      struct panel_dbi_bus *bus)
+{
+	device_initialize(&dev->dev);
+
+	dev->bus = bus;
+	dev->dev.bus = &panel_dbi_bus_type;
+	dev->dev.parent = bus->dev;
+
+	if (dev->id != -1)
+		dev_set_name(&dev->dev, "%s.%d", dev->name,  dev->id);
+	else
+		dev_set_name(&dev->dev, "%s", dev->name);
+
+	return device_add(&dev->dev);
+}
+EXPORT_SYMBOL_GPL(panel_dbi_device_register);
+
+/**
+ * panel_dbi_device_unregister - unregister a DBI device
+ * @dev: DBI device we're unregistering
+ */
+void panel_dbi_device_unregister(struct panel_dbi_device *dev)
+{
+	device_del(&dev->dev);
+	put_device(&dev->dev);
+}
+EXPORT_SYMBOL_GPL(panel_dbi_device_unregister);
+
+static int panel_dbi_drv_probe(struct device *_dev)
+{
+	struct panel_dbi_driver *drv = to_panel_dbi_driver(_dev->driver);
+	struct panel_dbi_device *dev = to_panel_dbi_device(_dev);
+
+	return drv->probe(dev);
+}
+
+static int panel_dbi_drv_remove(struct device *_dev)
+{
+	struct panel_dbi_driver *drv = to_panel_dbi_driver(_dev->driver);
+	struct panel_dbi_device *dev = to_panel_dbi_device(_dev);
+
+	return drv->remove(dev);
+}
+
+/**
+ * panel_dbi_driver_register - register a driver for DBI devices
+ * @drv: DBI driver structure
+ */
+int panel_dbi_driver_register(struct panel_dbi_driver *drv)
+{
+	drv->driver.bus = &panel_dbi_bus_type;
+	if (drv->probe)
+		drv->driver.probe = panel_dbi_drv_probe;
+	if (drv->remove)
+		drv->driver.remove = panel_dbi_drv_remove;
+
+	return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(panel_dbi_driver_register);
+
+/**
+ * panel_dbi_driver_unregister - unregister a driver for DBI devices
+ * @drv: DBI driver structure
+ */
+void panel_dbi_driver_unregister(struct panel_dbi_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(panel_dbi_driver_unregister);
+
+/* -----------------------------------------------------------------------------
+ * Init/exit
+ */
+
+static int __init panel_dbi_init(void)
+{
+	return bus_register(&panel_dbi_bus_type);
+}
+
+static void __exit panel_dbi_exit(void)
+{
+	bus_unregister(&panel_dbi_bus_type);
+}
+
+module_init(panel_dbi_init);
+module_exit(panel_dbi_exit)
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("MIPI DBI Bus");
+MODULE_LICENSE("GPL");
diff --git a/include/video/panel-dbi.h b/include/video/panel-dbi.h
new file mode 100644
index 0000000..799ac41
--- /dev/null
+++ b/include/video/panel-dbi.h
@@ -0,0 +1,92 @@
+/*
+ * MIPI DBI Bus
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __PANEL_DBI_H__
+#define __PANEL_DBI_H__
+
+#include <linux/device.h>
+#include <video/panel.h>
+
+struct panel_dbi_bus;
+
+struct panel_dbi_bus_ops {
+	void (*write_command)(struct panel_dbi_bus *bus, unsigned long cmd);
+	void (*write_data)(struct panel_dbi_bus *bus, unsigned long data);
+	unsigned long (*read_data)(struct panel_dbi_bus *bus);
+};
+
+struct panel_dbi_bus {
+	struct device *dev;
+	const struct panel_dbi_bus_ops *ops;
+};
+
+#define PANEL_DBI_MODULE_PREFIX		"mipi-dbi:"
+#define PANEL_DBI_NAME_SIZE		32
+
+struct panel_dbi_device_id {
+	char name[PANEL_DBI_NAME_SIZE];
+	kernel_ulong_t driver_data	/* Data private to the driver */
+			__aligned(sizeof(kernel_ulong_t));
+};
+
+struct panel_dbi_device {
+	const char *name;
+	int id;
+	struct device dev;
+
+	const struct panel_dbi_device_id *id_entry;
+	struct panel_dbi_bus *bus;
+};
+
+#define to_panel_dbi_device(d)	container_of(d, struct panel_dbi_device, dev)
+
+int panel_dbi_device_register(struct panel_dbi_device *dev,
+			      struct panel_dbi_bus *bus);
+void panel_dbi_device_unregister(struct panel_dbi_device *dev);
+
+struct panel_dbi_driver {
+	int(*probe)(struct panel_dbi_device *);
+	int(*remove)(struct panel_dbi_device *);
+	struct device_driver driver;
+	const struct panel_dbi_device_id *id_table;
+};
+
+#define to_panel_dbi_driver(d)	container_of(d, struct panel_dbi_driver, driver)
+
+int panel_dbi_driver_register(struct panel_dbi_driver *drv);
+void panel_dbi_driver_unregister(struct panel_dbi_driver *drv);
+
+static inline void *panel_dbi_get_drvdata(const struct panel_dbi_device *dev)
+{
+	return dev_get_drvdata(&dev->dev);
+}
+
+static inline void panel_dbi_set_drvdata(struct panel_dbi_device *dev,
+					 void *data)
+{
+	dev_set_drvdata(&dev->dev, data);
+}
+
+/* module_panel_dbi_driver() - Helper macro for drivers that don't do
+ * anything special in module init/exit.  This eliminates a lot of
+ * boilerplate.  Each module may only use this macro once, and
+ * calling it replaces module_init() and module_exit()
+ */
+#define module_panel_dbi_driver(__panel_dbi_driver) \
+	module_driver(__panel_dbi_driver, panel_dbi_driver_register, \
+			panel_dbi_driver_unregister)
+
+void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long cmd);
+void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long data);
+unsigned long panel_dbi_read_data(struct panel_dbi_device *dev);
+
+#endif /* __PANEL_DBI__ */
-- 
1.7.8.6


^ permalink raw reply related

* [RFC 2/5] video: panel: Add dummy panel support
From: Laurent Pinchart @ 2012-08-17  0:49 UTC (permalink / raw)
  To: linux-fbdev, dri-devel, linux-leds
  Cc: linux-media, Bryan Wu, Richard Purdie, Tomi Valkeinen,
	Marcus Lorentzon, Sumit Semwal, Archit Taneja, Sebastien Guiriec,
	Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/video/panel/Kconfig       |   11 ++++
 drivers/video/panel/Makefile      |    1 +
 drivers/video/panel/panel-dummy.c |  103 +++++++++++++++++++++++++++++++++++++
 include/video/panel-dummy.h       |   25 +++++++++
 4 files changed, 140 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/panel/panel-dummy.c
 create mode 100644 include/video/panel-dummy.h

diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig
index 26b1787..36fb9ca 100644
--- a/drivers/video/panel/Kconfig
+++ b/drivers/video/panel/Kconfig
@@ -2,3 +2,14 @@ menuconfig DISPLAY_PANEL
 	tristate "Display Panel"
 	---help---
 	  Support for display panels for graphics devices.
+
+if DISPLAY_PANEL
+
+config DISPLAY_PANEL_DUMMY
+	tristate "Dummy Display Panel"
+	---help---
+	  Support dummy panels with no control bus.
+
+	  If you are in doubt, say N.
+
+endif # DISPLAY_PANEL
diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile
index cf5c5e2..9fc05c2 100644
--- a/drivers/video/panel/Makefile
+++ b/drivers/video/panel/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_DISPLAY_PANEL) += panel.o
+obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o
diff --git a/drivers/video/panel/panel-dummy.c b/drivers/video/panel/panel-dummy.c
new file mode 100644
index 0000000..9ba1447
--- /dev/null
+++ b/drivers/video/panel/panel-dummy.c
@@ -0,0 +1,103 @@
+/*
+ * Dummy Display Panel
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <video/panel-dummy.h>
+
+struct panel_dummy {
+	struct panel panel;
+	const struct panel_dummy_platform_data *pdata;
+};
+
+#define to_panel_dummy(p)	container_of(p, struct panel_dummy, panel)
+
+static int panel_dummy_get_modes(struct panel *p,
+				 const struct fb_videomode **modes)
+{
+	struct panel_dummy *panel = to_panel_dummy(p);
+
+	*modes = panel->pdata->mode;
+	return 1;
+}
+
+static const struct panel_ops panel_dummy_ops = {
+	.get_modes = panel_dummy_get_modes,
+};
+
+static void panel_dummy_release(struct panel *p)
+{
+	struct panel_dummy *panel = to_panel_dummy(p);
+
+	kfree(panel);
+}
+
+static int panel_dummy_remove(struct platform_device *pdev)
+{
+	struct panel_dummy *panel = platform_get_drvdata(pdev);
+
+	platform_set_drvdata(pdev, NULL);
+	panel_unregister(&panel->panel);
+
+	return 0;
+}
+
+static int __devinit panel_dummy_probe(struct platform_device *pdev)
+{
+	const struct panel_dummy_platform_data *pdata = pdev->dev.platform_data;
+	struct panel_dummy *panel;
+	int ret;
+
+	if (pdata = NULL)
+		return -ENODEV;
+
+	panel = kzalloc(sizeof(*panel), GFP_KERNEL);
+	if (panel = NULL)
+		return -ENOMEM;
+
+	panel->pdata = pdata;
+	panel->panel.dev = &pdev->dev;
+	panel->panel.release = panel_dummy_release;
+	panel->panel.ops = &panel_dummy_ops;
+	panel->panel.width = pdata->width;
+	panel->panel.height = pdata->height;
+
+	ret = panel_register(&panel->panel);
+	if (ret < 0) {
+		kfree(panel);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, panel);
+
+	return 0;
+}
+
+static const struct dev_pm_ops panel_dummy_dev_pm_ops = {
+};
+
+static struct platform_driver panel_dummy_driver = {
+	.probe = panel_dummy_probe,
+	.remove = panel_dummy_remove,
+	.driver = {
+		.name = "panel_dummy",
+		.owner = THIS_MODULE,
+		.pm = &panel_dummy_dev_pm_ops,
+	},
+};
+
+module_platform_driver(panel_dummy_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Dummy Display Panel");
+MODULE_LICENSE("GPL");
diff --git a/include/video/panel-dummy.h b/include/video/panel-dummy.h
new file mode 100644
index 0000000..558a297
--- /dev/null
+++ b/include/video/panel-dummy.h
@@ -0,0 +1,25 @@
+/*
+ * Dummy Display Panel
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __PANEL_DUMMY_H__
+#define __PANEL_DUMMY_H__
+
+#include <linux/fb.h>
+#include <video/panel.h>
+
+struct panel_dummy_platform_data {
+	unsigned long width;		/* Panel width in mm */
+	unsigned long height;		/* Panel height in mm */
+	const struct fb_videomode *mode;
+};
+
+#endif /* __PANEL_DUMMY_H__ */
-- 
1.7.8.6


^ permalink raw reply related

* [RFC 1/5] video: Add generic display panel core
From: Laurent Pinchart @ 2012-08-17  0:49 UTC (permalink / raw)
  To: linux-fbdev, dri-devel, linux-leds
  Cc: linux-media, Bryan Wu, Richard Purdie, Tomi Valkeinen,
	Marcus Lorentzon, Sumit Semwal, Archit Taneja, Sebastien Guiriec,
	Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/video/Kconfig        |    1 +
 drivers/video/Makefile       |    1 +
 drivers/video/panel/Kconfig  |    4 +
 drivers/video/panel/Makefile |    1 +
 drivers/video/panel/panel.c  |  269 ++++++++++++++++++++++++++++++++++++++++++
 include/video/panel.h        |  111 +++++++++++++++++
 6 files changed, 387 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/panel/Kconfig
 create mode 100644 drivers/video/panel/Makefile
 create mode 100644 drivers/video/panel/panel.c
 create mode 100644 include/video/panel.h

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 0217f74..2cc394e 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2448,6 +2448,7 @@ source "drivers/video/omap/Kconfig"
 source "drivers/video/omap2/Kconfig"
 source "drivers/video/exynos/Kconfig"
 source "drivers/video/backlight/Kconfig"
+source "drivers/video/panel/Kconfig"
 
 if VT
 	source "drivers/video/console/Kconfig"
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index ee8dafb..577240c 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -14,6 +14,7 @@ fb-objs                           := $(fb-y)
 obj-$(CONFIG_VT)		  += console/
 obj-$(CONFIG_LOGO)		  += logo/
 obj-y				  += backlight/
+obj-y				  += panel/
 
 obj-$(CONFIG_EXYNOS_VIDEO)     += exynos/
 
diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig
new file mode 100644
index 0000000..26b1787
--- /dev/null
+++ b/drivers/video/panel/Kconfig
@@ -0,0 +1,4 @@
+menuconfig DISPLAY_PANEL
+	tristate "Display Panel"
+	---help---
+	  Support for display panels for graphics devices.
diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile
new file mode 100644
index 0000000..cf5c5e2
--- /dev/null
+++ b/drivers/video/panel/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_DISPLAY_PANEL) += panel.o
diff --git a/drivers/video/panel/panel.c b/drivers/video/panel/panel.c
new file mode 100644
index 0000000..cfca804
--- /dev/null
+++ b/drivers/video/panel/panel.c
@@ -0,0 +1,269 @@
+/*
+ * Display Panel
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <video/panel.h>
+
+static LIST_HEAD(panel_list);
+static LIST_HEAD(panel_notifiers);
+static DEFINE_MUTEX(panel_mutex);
+
+/**
+ * panel_enable - Set the panel operation mode
+ * @panel: The panel
+ * @enable: Panel operation mode
+ *
+ * - PANEL_ENABLE_OFF turns the panel off completely, possibly including its
+ *   power supplies. Communication with a panel in that mode is not possible.
+ * - PANEL_ENABLE_BLANK turns the panel on but keep the output blanked. Full
+ *   communication with the panel is supported, including pixel data transfer.
+ * - PANEL_ENABLE_ON turns the whole panel on, including the output.
+ *
+ * Return 0 on success or a negative error code otherwise.
+ */
+int panel_enable(struct panel *panel, enum panel_enable_mode enable)
+{
+	int ret;
+
+	if (panel->enable = enable)
+		return 0;
+
+	if (!panel->ops || !panel->ops->enable)
+		return 0;
+
+	ret = panel->ops->enable(panel, enable);
+	if (ret < 0)
+		return ret;
+
+	panel->enable = enable;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(panel_enable);
+
+/**
+ * panel_start_transfer - Start frame transfer
+ * @panel: The panel
+ *
+ * Make the panel ready to receive pixel data and start frame transfer. This
+ * operation can only be called if the panel is in BLANK or ON mode.
+ *
+ * Return 0 on success or a negative error code otherwise.
+ */
+int panel_start_transfer(struct panel *panel)
+{
+	if (!panel->ops || !panel->ops->start_transfer)
+		return 0;
+
+	return panel->ops->start_transfer(panel);
+}
+
+/**
+ * panel_get_modes - Get video modes supported by the panel
+ * @panel: The panel
+ * @modes: Pointer to an array of modes
+ *
+ * Fill the modes argument with a pointer to an array of video modes. The array
+ * is owned by the panel.
+ *
+ * Return the number of supported modes on success (including 0 if no mode is
+ * supported) or a negative error code otherwise.
+ */
+int panel_get_modes(struct panel *panel, const struct fb_videomode **modes)
+{
+	if (!panel->ops || !panel->ops->get_modes)
+		return 0;
+
+	return panel->ops->get_modes(panel, modes);
+}
+EXPORT_SYMBOL_GPL(panel_get_modes);
+
+static void panel_release(struct kref *ref)
+{
+	struct panel *panel = container_of(ref, struct panel, ref);
+
+	if (panel->release)
+		panel->release(panel);
+}
+
+/**
+ * panel_get - get a reference to a panel
+ * @panel: the panel
+ *
+ * Return the panel pointer.
+ */
+struct panel *panel_get(struct panel *panel)
+{
+	if (panel = NULL)
+		return NULL;
+
+	kref_get(&panel->ref);
+	return panel;
+}
+EXPORT_SYMBOL_GPL(panel_get);
+
+/**
+ * panel_put - release a reference to a panel
+ * @panel: the panel
+ *
+ * Releasing the last reference to a panel releases the panel itself.
+ */
+void panel_put(struct panel *panel)
+{
+	kref_put(&panel->ref, panel_release);
+}
+EXPORT_SYMBOL_GPL(panel_put);
+
+static int panel_notifier_match(struct panel *panel,
+				struct panel_notifier *notifier)
+{
+	return notifier->dev = NULL ||
+	       notifier->dev = panel->dev;
+}
+
+/**
+ * panel_register_notifier - register a display panel notifier
+ * @notifier: panel notifier structure we want to register
+ *
+ * Panel notifiers are called to notify drivers of panel-related events for
+ * matching panels.
+ *
+ * Notifiers and panels are matched through the device they correspond to. If
+ * the notifier dev field is equal to the panel dev field the notifier will be
+ * called when an event is reported. Notifiers with a NULL dev field act as
+ * catch-all and will be called for all panels.
+ *
+ * Supported events are
+ *
+ * - PANEL_NOTIFIER_CONNECT reports panel connection and is sent at panel or
+ *   notifier registration time
+ * - PANEL_NOTIFIER_DISCONNECT reports panel disconnection and is sent at panel
+ *   unregistration time
+ *
+ * Registering a notifier sends PANEL_NOTIFIER_CONNECT events for all previously
+ * registered panels that match the notifiers.
+ *
+ * Return 0 on success.
+ */
+int panel_register_notifier(struct panel_notifier *notifier)
+{
+	struct panel *panel;
+
+	mutex_lock(&panel_mutex);
+	list_add_tail(&notifier->list, &panel_notifiers);
+
+	list_for_each_entry(panel, &panel_list, list) {
+		if (!panel_notifier_match(panel, notifier))
+			continue;
+
+		if (notifier->notify(notifier, panel, PANEL_NOTIFIER_CONNECT))
+			break;
+	}
+	mutex_unlock(&panel_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(panel_register_notifier);
+
+/**
+ * panel_unregister_notifier - unregister a display panel notifier
+ * @notifier: panel notifier to be unregistered
+ *
+ * Unregistration guarantees that the notifier will never be called upon return
+ * of this function.
+ */
+void panel_unregister_notifier(struct panel_notifier *notifier)
+{
+	mutex_lock(&panel_mutex);
+	list_del(&notifier->list);
+	mutex_unlock(&panel_mutex);
+}
+EXPORT_SYMBOL_GPL(panel_unregister_notifier);
+
+/**
+ * panel_register - register a display panel
+ * @panel: panel to be registered
+ *
+ * Register the panel and send the PANEL_NOTIFIER_CONNECT event to all
+ * matching registered notifiers.
+ *
+ * Return 0 on success.
+ */
+int __must_check __panel_register(struct panel *panel, struct module *owner)
+{
+	struct panel_notifier *notifier;
+
+	kref_init(&panel->ref);
+	panel->owner = owner;
+	panel->enable = PANEL_ENABLE_OFF;
+
+	mutex_lock(&panel_mutex);
+	list_add(&panel->list, &panel_list);
+
+	list_for_each_entry(notifier, &panel_notifiers, list) {
+		if (!panel_notifier_match(panel, notifier))
+			continue;
+
+		if (notifier->notify(notifier, panel, PANEL_NOTIFIER_CONNECT))
+			break;
+	}
+	mutex_unlock(&panel_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(__panel_register);
+
+/**
+ * panel_unregister - unregister a display panel
+ * @panel: panel to be unregistered
+ *
+ * Unregister the panel and send the PANEL_NOTIFIER_DISCONNECT event to all
+ * matching registered notifiers.
+ */
+void panel_unregister(struct panel *panel)
+{
+	struct panel_notifier *notifier;
+
+	mutex_lock(&panel_mutex);
+	list_for_each_entry(notifier, &panel_notifiers, list) {
+		if (!panel_notifier_match(panel, notifier))
+			continue;
+
+		notifier->notify(notifier, panel, PANEL_NOTIFIER_DISCONNECT);
+	}
+
+	list_del(&panel->list);
+	mutex_unlock(&panel_mutex);
+
+	panel_put(panel);
+}
+EXPORT_SYMBOL_GPL(panel_unregister);
+
+static int __init panel_init(void)
+{
+	return 0;
+}
+
+static void __exit panel_exit(void)
+{
+}
+
+module_init(panel_init);
+module_exit(panel_exit)
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Display Panel Core");
+MODULE_LICENSE("GPL");
diff --git a/include/video/panel.h b/include/video/panel.h
new file mode 100644
index 0000000..bb11141
--- /dev/null
+++ b/include/video/panel.h
@@ -0,0 +1,111 @@
+/*
+ * Display Panel
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __PANEL_H__
+#define __PANEL_H__
+
+#include <linux/fb.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+
+struct panel;
+
+#define PANEL_NOTIFIER_CONNECT		1
+#define PANEL_NOTIFIER_DISCONNECT	2
+
+struct panel_notifier {
+	int (*notify)(struct panel_notifier *, struct panel *, int);
+	struct device *dev;
+	struct list_head list;
+};
+
+enum panel_enable_mode {
+	PANEL_ENABLE_OFF,
+	PANEL_ENABLE_BLANK,
+	PANEL_ENABLE_ON,
+};
+
+struct panel_ops {
+	int (*enable)(struct panel *panel, enum panel_enable_mode enable);
+	int (*start_transfer)(struct panel *panel);
+	int (*get_modes)(struct panel *panel,
+			 const struct fb_videomode **modes);
+};
+
+struct panel {
+	struct list_head list;
+	struct device *dev;
+	struct module *owner;
+	struct kref ref;
+
+	const struct panel_ops *ops;
+	void(*release)(struct panel *panel);
+
+	unsigned int width;
+	unsigned int height;
+
+	enum panel_enable_mode enable;
+};
+
+/**
+ * panel_enable - Set the panel operation mode
+ * @panel: The panel
+ * @enable: Panel operation mode
+ *
+ * - PANEL_ENABLE_OFF turns the panel off completely, possibly including its
+ *   power supplies. Communication with a panel in that mode is not possible.
+ * - PANEL_ENABLE_BLANK turns the panel on but keep the output blanked. Full
+ *   communication with the panel is supported, including pixel data transfer.
+ * - PANEL_ENABLE_ON turns the whole panel on, including the output.
+ *
+ * Return 0 on success or a negative error code otherwise.
+ */
+int panel_enable(struct panel *panel, enum panel_enable_mode enable);
+
+/**
+ * panel_start_transfer - Start frame transfer
+ * @panel: The panel
+ *
+ * Make the panel ready to receive pixel data and start frame transfer. This
+ * operation can only be called if the panel is in BLANK or ON mode.
+ *
+ * Return 0 on success or a negative error code otherwise.
+ */
+int panel_start_transfer(struct panel *panel);
+
+/**
+ * panel_get_modes - Get video modes supported by the panel
+ * @panel: The panel
+ * @modes: Pointer to an array of modes
+ *
+ * Fill the modes argument with a pointer to an array of video modes. The array
+ * is owned by the panel.
+ *
+ * Return the number of supported modes on success (including 0 if no mode is
+ * supported) or a negative error code otherwise.
+ */
+int panel_get_modes(struct panel *panel, const struct fb_videomode **modes);
+
+struct panel *panel_get(struct panel *panel);
+void panel_put(struct panel *panel);
+
+int __must_check __panel_register(struct panel *panel, struct module *owner);
+void panel_unregister(struct panel *panel);
+
+int panel_register_notifier(struct panel_notifier *notifier);
+void panel_unregister_notifier(struct panel_notifier *notifier);
+
+#define panel_register(panel) \
+	__panel_register(panel, THIS_MODULE)
+
+#endif /* __PANEL_H__ */
-- 
1.7.8.6


^ permalink raw reply related

* [RFC 0/5] Generic panel framework
From: Laurent Pinchart @ 2012-08-17  0:49 UTC (permalink / raw)
  To: linux-fbdev, dri-devel, linux-leds
  Cc: linux-media, Bryan Wu, Richard Purdie, Tomi Valkeinen,
	Marcus Lorentzon, Sumit Semwal, Archit Taneja, Sebastien Guiriec,
	Inki Dae, Kyungmin Park

Hi everybody,

While working on DT bindings for the Renesas Mobile SoC display controller
(a.k.a. LCDC) I quickly realized that display panel implementation based on
board code callbacks would need to be replaced by a driver-based panel
framework.

Several driver-based panel support solution already exist in the kernel.

- The LCD device class is implemented in drivers/video/backlight/lcd.c and
  exposes a kernel API in include/linux/lcd.h. That API is tied to the FBDEV
  API for historical reason, uses board code callback for reset and power
  management, and doesn't include support for standard features available in
  today's "smart panels".

- OMAP2+ based systems use custom panel drivers available in
  drivers/video/omap2/displays. Those drivers are based on OMAP DSS (display
  controller) specific APIs.

- Similarly, Exynos based systems use custom panel drivers available in
  drivers/video/exynos. Only a single driver (s6e8ax0) is currently available.
  That driver is based on Exynos display controller specific APIs and on the
  LCD device class API.

I've brought up the issue with Tomi Valkeinen (OMAP DSS maintainer) and Marcus
Lorentzon (working on panel support for ST/Linaro), and we agreed that a
generic panel framework for display devices is needed. These patches implement
a first proof of concept.

One of the main reasons for creating a new panel framework instead of adding
missing features to the LCD framework is to avoid being tied to the FBDEV
framework. Panels will be used by DRM drivers as well, and their API should
thus be subsystem-agnostic. Note that the panel framework used the
fb_videomode structure in its API, this will be replaced by a common video
mode structure shared across subsystems (there's only so many hours per day).

Panels, as used in these patches, are defined as physical devices combining a
matrix of pixels and a controller capable of driving that matrix.

Panel physical devices are registered as children of the control bus the panel
controller is connected to (depending on the panel type, we can find platform
devices for dummy panels with no control bus, or I2C, SPI, DBI, DSI, ...
devices). The generic panel framework matches registered panel devices with
panel drivers and call the panel drivers probe method, as done by other device
classes in the kernel. The driver probe() method is responsible for
instantiating a struct panel instance and registering it with the generic
panel framework.

Display drivers are panel consumers. They register a panel notifier with the
framework, which then calls the notifier when a matching panel is registered.
The reason for this asynchronous mode of operation, compared to how drivers
acquire regulator or clock resources, is that the panel can use resources
provided by the display driver. For instance a panel can be a child of the DBI
or DSI bus controlled by the display device, or use a clock provided by that
device. We can't defer the display device probe until the panel is registered
and also defer the panel device probe until the display is registered. As
most display drivers need to handle output devices hotplug (HDMI monitors for
instance), handling panel through a notification system seemed to be the
easiest solution.

Note that this brings a different issue after registration, as display and
panel drivers would take a reference to each other. Those circular references
would make driver unloading impossible. I haven't found a good solution for
that problem yet (hence the RFC state of those patches), and I would
appreciate your input here. This might also be a hint that the framework
design is wrong to start with. I guess I can't get everything right on the
first try ;-)

Getting hold of the panel is the most complex part. Once done, display drivers
can call abstract operations provided by panel drivers to control the panel
operation. These patches implement three of those operations (enable, start
transfer and get modes). More operations will be needed, and those three
operations will likely get modified during review. Most of the panels on
devices I own are dumb panels with no control bus, and are thus not the best
candidates to design a framework that needs to take complex panels' needs into
account.

In addition to the generic panel core, I've implemented MIPI DBI (Display Bus
Interface, a parallel bus for panels that supports read/write transfers of
commands and data) bus support, as well as three panel drivers (dummy panels
with no control bus, and Renesas R61505- and R61517-based panels, both using
DBI as their control bus). Only the dummy panel driver has been tested as I
lack hardware for the two other drivers.

I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If
you can find a clever way to solve the cyclic references issue described above
I'll buy you a beer at the next conference we will both attend. If you think
the proposed solution is too complex, or too simple, I'm all ears. I
personally already feel that we might need something even more generic to
support other kinds of external devices connected to display controllers, such
as external DSI to HDMI converters for instance. Some kind of video entity
exposing abstract operations like the panels do would make sense, in which
case panels would "inherit" from that video entity.

Speaking of conferences, I will attend the KS/LPC in San Diego in a bit more
than a week, and would be happy to discuss this topic face to face there.

Laurent Pinchart (5):
  video: Add generic display panel core
  video: panel: Add dummy panel support
  video: panel: Add MIPI DBI bus support
  video: panel: Add R61505 panel support
  video: panel: Add R61517 panel support

 drivers/video/Kconfig              |    1 +
 drivers/video/Makefile             |    1 +
 drivers/video/panel/Kconfig        |   37 +++
 drivers/video/panel/Makefile       |    5 +
 drivers/video/panel/panel-dbi.c    |  217 +++++++++++++++
 drivers/video/panel/panel-dummy.c  |  103 +++++++
 drivers/video/panel/panel-r61505.c |  520 ++++++++++++++++++++++++++++++++++++
 drivers/video/panel/panel-r61517.c |  408 ++++++++++++++++++++++++++++
 drivers/video/panel/panel.c        |  269 +++++++++++++++++++
 include/video/panel-dbi.h          |   92 +++++++
 include/video/panel-dummy.h        |   25 ++
 include/video/panel-r61505.h       |   27 ++
 include/video/panel-r61517.h       |   28 ++
 include/video/panel.h              |  111 ++++++++
 14 files changed, 1844 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/panel/Kconfig
 create mode 100644 drivers/video/panel/Makefile
 create mode 100644 drivers/video/panel/panel-dbi.c
 create mode 100644 drivers/video/panel/panel-dummy.c
 create mode 100644 drivers/video/panel/panel-r61505.c
 create mode 100644 drivers/video/panel/panel-r61517.c
 create mode 100644 drivers/video/panel/panel.c
 create mode 100644 include/video/panel-dbi.h
 create mode 100644 include/video/panel-dummy.h
 create mode 100644 include/video/panel-r61505.h
 create mode 100644 include/video/panel-r61517.h
 create mode 100644 include/video/panel.h

-- 
Regards,

Laurent Pinchart


^ permalink raw reply

* Re: [PATCH v4 0/3] Runtime Interpreted Power Sequences
From: Rafael J. Wysocki @ 2012-08-16 21:47 UTC (permalink / raw)
  To: Alexandre Courbot
  Cc: linux-fbdev-u79uwXL29TY76Z2rM5mHXA, Mark Brown, Stephen Warren,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Anton Vorontsov,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA, David Woodhouse,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ
In-Reply-To: <1345097337-24170-1-git-send-email-acourbot-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>

On Thursday, August 16, 2012, Alexandre Courbot wrote:
> Overdue revision of this new feature, some changes required additional thought
> and rework.
> 
> The most important change is in the way power sequences are expressed in the
> device tree. In order to avoid having to specify #address-cells, #size-cells and
> reg properties, the @ notation in the step names is dropped, and instead a
> fixed, sequential naming is adopted. The type of the resource used by a step is
> decided by the presence of some recognized properties:
> 
> power-on-sequence {
> 	step0 {
> 		regulator = "power";
> 		enable;
> 	};
> 	step1 {
> 		delay = <10000>;
> 	};
> 	step2 {
> 		pwm = "backlight";
> 		enable;
> 	};
> 	...
> 
> To me this looks safe, clear and close to the platform data representation, but
> needs approval from DT experts.
> 
> Resources are still referenced by name instead of having their phandles defined
> directly inside the sequences, as previous discussion came to the conclusion
> that doing so would require controversial changes to the regulator and PWM
> frameworks, and that having the resources declared at the device level was
> making sense logically speaking.
> 
> Other changes/fixes since last revision:
> * Move to drivers/power/ (hope this is ok with the maintainers?)
> * Use microseconds for delay
> * Use devm for PWM resources and remove cleanup function as all resources are
>   devm-managed
> * Remove "-gpio" suffix for GPIO reference in the driver
> * Remove params structure
> * Make power_seq structure private
> * Number of steps in a sequence is explicitly stated instead of resorting to a
>   "stop" sequence step
> * Delays are a step instead of being a step parameter
> * Use flexible member arrays to limit number of memory allocations
> * Add documentation to DT bindings
> 
> There was a lot of feedback on the previous version (thanks!) so if I forgot
> to address some important point, please bring it to my attention again.
> 
> Alexandre Courbot (3):
>   Runtime Interpreted Power Sequences
>   pwm_backlight: use power sequences
>   tegra: add pwm backlight device tree nodes

May I ask that the next version of this patchset be CCed to
linux-pm@vger.kernel.org?

Thanks,
Rafael

^ permalink raw reply

* Re: [PATCH v4 1/3] Runtime Interpreted Power Sequences
From: Mitch Bradley @ 2012-08-16 21:10 UTC (permalink / raw)
  To: Stephen Warren
  Cc: linux-fbdev-u79uwXL29TY76Z2rM5mHXA, Stephen Warren,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ, Mark Brown,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Anton Vorontsov,
	Alexandre Courbot, linux-tegra-u79uwXL29TY76Z2rM5mHXA,
	David Woodhouse
In-Reply-To: <502D3E29.1010501-3lzwWm7+Weoh9ZMKESR00Q@public.gmane.org>

On 8/16/2012 8:38 AM, Stephen Warren wrote:
> On 08/16/2012 12:08 AM, Alexandre Courbot wrote:
>> Some device drivers (panel backlights especially) need to follow precise
>> sequences for powering on and off, involving gpios, regulators, PWMs
>> with a precise powering order and delays to respect between each steps.
>> These sequences are board-specific, and do not belong to a particular
>> driver - therefore they have been performed by board-specific hook
>> functions to far.
>>
>> With the advent of the device tree and of ARM kernels that are not
>> board-tied, we cannot rely on these board-specific hooks anymore but
>> need a way to implement these sequences in a portable manner. This patch
>> introduces a simple interpreter that can execute such power sequences
>> encoded either as platform data or within the device tree.
> 
>> diff --git a/Documentation/devicetree/bindings/power_seq/power_seq.txt b/Documentation/devicetree/bindings/power_seq/power_seq.txt
> 
>> +Specifying Power Sequences in the Device Tree
>> +======================>> +In the device tree, power sequences are specified as sub-nodes of the device
>> +node and reference resources declared by that device.
>> +
>> +For an introduction about runtime interpreted power sequences, see
>> +Documentation/power/power_seq.txt and include/linux/power_seq.h.
> 
> Device tree bindings shouldn't reference Linux documentation; the
> bindings are supposed to be OS-agnostic.


While it is true that bindings should try to be OS-agnostic, there is
the practical matter of where to put documentation so that it is widely
accessible.  The Linux source tree is one of the most accessible things
there is, considering how widely it is replicated.

As the original instigator of the policy that the device tree should
describe the hardware "OS-neutrally", I personally don't have a problem
with bindings referring to Linux documentation.  I wouldn't like
references to proprietary and inaccessible documentation.

> 
>> +Power Sequences Structure
>> +-------------------------
>> +Power sequences are sub-nodes that are named such as the device driver can find
>> +them. The driver's documentation should list the sequence names it recognizes.
> 
> That's a little roundabout. That might be better as simply:
> 
> Valid power sequence names are defined by each device's binding. For a
> power sequence named "foo", a node named "foo-power-sequence" defines
> that sequence.
> 
>> +Inside a power sequence node are sub-nodes that describe the different steps
>> +of the sequence. Each step must be named sequentially, with the first step
>> +named step0, the second step1, etc. Failure to follow this rule will result in a
>> +parsing error.
> 
> Node names shouldn't be interpreted by the code; nodes should all be
> named after the type of object the represent. Hence, every step should
> be named just "step" for example.
> 
> The node's unit address (@0) should be used to distinguish the nodes.
> This requires reg properties within each node to match the unit address,
> and hence #address-cells and #size-cells properties in the power
> sequence itself.
> 
> Note that this somewhat conflicts with accessing the top-level power
> sequence by name too; perhaps that should be re-thought too. I must
> admit this DT rule kinda sucks.
> 
>> +Power Sequences Steps
>> +---------------------
>> +Every step of a sequence describes an action to be performed on a resource. It
>> +generally includes a property named after the resource type, and which value
>> +references the resource to be used. Depending on the resource type, additional
>> +properties can be defined to control the action to be performed.
> 
> I think you need to add a property that indicates what type of resource
> each step applies to. Sure, this is implicit in that if a "gpio"
> property exists, the step is a GPIO step, but in order to make that
> work, you'd have to search each node for each possible resource type's
> property name. It'd be far better to read a single type="gpio" property,
> then parse the step based on that.
> 
>> +Example
>> +-------
>> +Here are example sequences declared within a backlight device that use all the
>> +supported resources types:
>> +
>> +	backlight {
>> +		compatible = "pwm-backlight";
>> +		...
>> +
>> +		/* resources used by the sequences */
>> +		pwms = <&pwm 2 5000000>;
>> +		pwm-names = "backlight";
>> +		power-supply = <&backlight_reg>;
>> +		enable-gpio = <&gpio 28 0>;
>> +
>> +		power-on-sequence {
>> +			step0 {
>> +				regulator = "power";
>> +				enable;
>> +			};
>> +			step1 {
>> +				delay = <10000>;
>> +			};
>> +			step2 {
>> +				pwm = "backlight";
>> +				enable;
>> +			};
>> +			step3 {
>> +				gpio = "enable";
>> +				enable;
>> +			};
>> +		};
>> +
>> +		power-off-sequence {
>> +			step0 {
>> +				gpio = "enable";
>> +				disable;
>> +			};
>> +			step1 {
>> +				pwm = "backlight";
>> +				disable;
>> +			};
>> +			step2 {
>> +				delay = <10000>;
>> +			};
>> +			step3 {
>> +				regulator = "power";
>> +				disable;
>> +			};
>> +		};
>> +	};
> 
> I notice that for clocks, pwms, and interrupts, the initial step of the
> lookup is via a single property that lists all know resources of that
> type. Regulators and GPIOs don't follow this style though. Using the
> same mechanism for power-sequences would yield something like the
> following, which would avoid the "node names must be significant" issue
> I mention above, although it does make everything rather more wordy.
> 
> [start my proposal]
>> 	backlight {
>> 		compatible = "pwm-backlight";
>>
>> 		/* resources used by the sequences */
>> 		pwms = <&pwm 2 5000000>;
>> 		pwm-names = "backlight";
>> 		power-supply = <&backlight_reg>;
>> 		bl-enable-gpio = <&gpio 28 0>;
>> 		pwr-seqs = <&bl_on &bl_off>;
>> 		pwr-seq-names = "on", "off";
>>
>> 		#address-cells = <1>;
>> 		#size-cells = <0>;
>>
>> 		bl_on: pwr-seq@0 {
>> 			reg = <0>;
>> 			#address-cells = <1>;
>> 			#size-cells = <0>;
>> 			step@0 {
>> 				reg = <0>;
>> 				type = "regulator";
>> 				regulator = "power";
>> 				enable;
>> 			};
>> 			step@1 {
>> 				reg = <1>;
>> 				type = "delay";
>> 				delay = <10000>;
>> 			};
>> 			step@2 {
>> 				reg = <2>;
>> 				type = "pwm";
>> 				pwm = "backlight";
>> 				enable;
>> 			};
>> 			step@3 {
>> 				reg = <3>;
>> 				type = "gpio";
>> 				gpio = "bl-enable";
>> 				enable;
>> 			};
>> 		};
>>
>> 		bl_off: pwr-seq@1 {
>> 			reg = <1>;
>> 			#address-cells = <1>;
>> 			#size-cells = <0>;
>> 			step@0 {
>> 				reg = <0>;
>> 				type = "gpio";
>> 				gpio = "bl-enable";
>> 				disable;
>> 			};
>> 			step@1 {
>> 				reg = <1>;
>> 				type = "pwm";
>> 				pwm = "backlight";
>> 				disable;
>> 			};
>> 			step@2 {
>> 				reg = <2>;
>> 				type = "delay";
>> 				delay = <10000>;
>> 			};
>> 			step@3 {
>> 				reg = <3>;
>> 				type = "regulator";
>> 				regulator = "power";
>> 				disable;
>> 			};
>> 		};
>> 	};
>>
> [end my proposal]
> 
>> diff --git a/Documentation/power/power_seq.txt b/Documentation/power/power_seq.txt
> 
>> +Usage by Drivers and Resources Management
>> +-----------------------------------------
>> +Power sequences make use of resources that must be properly allocated and
>> +managed. The power_seq_build() function builds a power sequence from the
>> +platform data. It also takes care of resolving and allocating the resources
>> +referenced by the sequence if needed:
>> +
>> +  struct power_seq *power_seq_build(struct device *dev, struct list_head *ress,
>> +                                    struct platform_power_seq *pseq);
>> +
>> +The 'dev' argument is the device in the name of which the resources are to be
>> +allocated.
>> +
>> +The 'ress' argument is a list to which the resolved resources are appended. This
>> +avoids allocating a resource referenced in several power sequences multiple
>> +times.
> 
> I see in other parts of the thread, there has been discussion re:
> needing the separate ress parameter, and requiring the driver to pass
> this in to multiple power_seq_build calls.
> 
> The way the pinctrl subsystem solved this was to have a single function
> that parsed all pinctrl setting (c.f. all power sequences) at once, and
> return a single handle. Later, the driver passes this handle
> pinctrl_lookup_state(), along with the requested state (c.f. sequence
> name) to search for, and finally passes that handle to
> pinctrl_select_state(). Doing something similar here would result in:
> 
> struct power_seqs *power_seq_get(struct device *dev);
> void power_seq_put(struct power_seqs *seqs);
> struct power_seq *power_seq_lookup(struct power_seqs *seqs,
> 				const char *name);
> int power_seq_executestruct power_seq *seq);
> 
> struct power_seqs *devm_power_seq_get(struct device *dev);
> void devm_power_seq_put(struct power_seqs *seqs);
> 
>> +On success, the function returns a devm allocated resolved sequence that is
> 
> Perhaps the function should be named devm_power_seq_build(), in order to
> make this obvious to people who only read the client code, and not this
> documentation.
> 
>> +ready to be passed to power_seq_run(). In case of failure, and error code is
>> +returned.
>> +
>> +A resolved power sequence returned by power_seq_build can be run by
>> +power_run_run():
>> +
>> +  int power_seq_run(power_seq *seq);
>> +
>> +It returns 0 if the sequence has successfully been run, or an error code if a
>> +problem occured.
>> +
>> +There is no need to explicitly free the resources used by the sequence as they
>> +are devm-allocated.
> 
> _______________________________________________
> devicetree-discuss mailing list
> devicetree-discuss@lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/devicetree-discuss
> 

^ permalink raw reply

* Re: [PATCH v4 1/3] Runtime Interpreted Power Sequences
From: Thierry Reding @ 2012-08-16 19:49 UTC (permalink / raw)
  To: Stephen Warren
  Cc: Alexandre Courbot, Stephen Warren, Simon Glass, Grant Likely,
	Rob Herring, Mark Brown, Anton Vorontsov, David Woodhouse,
	Arnd Bergmann, Leela Krishna Amudala, linux-tegra, linux-kernel,
	linux-fbdev, devicetree-discuss, linux-doc
In-Reply-To: <502D3E29.1010501@wwwdotorg.org>

[-- Attachment #1: Type: text/plain, Size: 2258 bytes --]

On Thu, Aug 16, 2012 at 12:38:33PM -0600, Stephen Warren wrote:
> On 08/16/2012 12:08 AM, Alexandre Courbot wrote:
> > diff --git a/Documentation/power/power_seq.txt b/Documentation/power/power_seq.txt
> 
> > +Usage by Drivers and Resources Management
> > +-----------------------------------------
> > +Power sequences make use of resources that must be properly allocated and
> > +managed. The power_seq_build() function builds a power sequence from the
> > +platform data. It also takes care of resolving and allocating the resources
> > +referenced by the sequence if needed:
> > +
> > +  struct power_seq *power_seq_build(struct device *dev, struct list_head *ress,
> > +                                    struct platform_power_seq *pseq);
> > +
> > +The 'dev' argument is the device in the name of which the resources are to be
> > +allocated.
> > +
> > +The 'ress' argument is a list to which the resolved resources are appended. This
> > +avoids allocating a resource referenced in several power sequences multiple
> > +times.
> 
> I see in other parts of the thread, there has been discussion re:
> needing the separate ress parameter, and requiring the driver to pass
> this in to multiple power_seq_build calls.
> 
> The way the pinctrl subsystem solved this was to have a single function
> that parsed all pinctrl setting (c.f. all power sequences) at once, and
> return a single handle. Later, the driver passes this handle
> pinctrl_lookup_state(), along with the requested state (c.f. sequence
> name) to search for, and finally passes that handle to
> pinctrl_select_state(). Doing something similar here would result in:
> 
> struct power_seqs *power_seq_get(struct device *dev);
> void power_seq_put(struct power_seqs *seqs);
> struct power_seq *power_seq_lookup(struct power_seqs *seqs,
> 				const char *name);
> int power_seq_executestruct power_seq *seq);
> 
> struct power_seqs *devm_power_seq_get(struct device *dev);
> void devm_power_seq_put(struct power_seqs *seqs);

Hehe, this looks very much like what I had in mind when I proposed this
during the last version of this series. The nice thing about this is
that the API is very much in line with how other subsystems work.

Thierry

[-- Attachment #2: Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* Re: [PATCH v4 1/3] Runtime Interpreted Power Sequences
From: Stephen Warren @ 2012-08-16 19:35 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-fbdev-u79uwXL29TY76Z2rM5mHXA, Stephen Warren,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Anton Vorontsov,
	Alexandre Courbot, linux-tegra-u79uwXL29TY76Z2rM5mHXA,
	David Woodhouse
In-Reply-To: <502D428F.1010901-3lzwWm7+Weoh9ZMKESR00Q@public.gmane.org>

On 08/16/2012 12:57 PM, Stephen Warren wrote:
> On 08/16/2012 12:47 PM, Mark Brown wrote:
>> On Thu, Aug 16, 2012 at 12:38:33PM -0600, Stephen Warren wrote:
>>
>>> Note that this somewhat conflicts with accessing the top-level power
>>> sequence by name too; perhaps that should be re-thought too. I must
>>> admit this DT rule kinda sucks.
>>
>> Given that currently the information there is useless and ignored is
>> there any reason we can't just change the rule?  It'd be rather more
>> constructive...
> 
> I'll start a new thread on that topic and see. I would simplify matters
> a lot...

In case anyone wants to follow it, it's at:

https://lists.ozlabs.org/pipermail/devicetree-discuss/2012-August/018502.html


^ permalink raw reply

* Re: [PATCH v4 1/3] Runtime Interpreted Power Sequences
From: Stephen Warren @ 2012-08-16 18:57 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-fbdev-u79uwXL29TY76Z2rM5mHXA, Stephen Warren,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Anton Vorontsov,
	Alexandre Courbot, linux-tegra-u79uwXL29TY76Z2rM5mHXA,
	David Woodhouse, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ
In-Reply-To: <20120816184703.GP15365-yzvPICuk2AATkU/dhu1WVueM+bqZidxxQQ4Iyu8u01E@public.gmane.org>

On 08/16/2012 12:47 PM, Mark Brown wrote:
> On Thu, Aug 16, 2012 at 12:38:33PM -0600, Stephen Warren wrote:
> 
>> Note that this somewhat conflicts with accessing the top-level power
>> sequence by name too; perhaps that should be re-thought too. I must
>> admit this DT rule kinda sucks.
> 
> Given that currently the information there is useless and ignored is
> there any reason we can't just change the rule?  It'd be rather more
> constructive...

I'll start a new thread on that topic and see. I would simplify matters
a lot...


^ permalink raw reply

* Re: [PATCH v4 1/3] Runtime Interpreted Power Sequences
From: Mark Brown @ 2012-08-16 18:47 UTC (permalink / raw)
  To: Stephen Warren
  Cc: Alexandre Courbot, Stephen Warren, Thierry Reding, Simon Glass,
	Grant Likely, Rob Herring, Anton Vorontsov, David Woodhouse,
	Arnd Bergmann, Leela Krishna Amudala,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fbdev-u79uwXL29TY76Z2rM5mHXA,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-doc-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <502D3E29.1010501-3lzwWm7+Weoh9ZMKESR00Q@public.gmane.org>

On Thu, Aug 16, 2012 at 12:38:33PM -0600, Stephen Warren wrote:

> Note that this somewhat conflicts with accessing the top-level power
> sequence by name too; perhaps that should be re-thought too. I must
> admit this DT rule kinda sucks.

Given that currently the information there is useless and ignored is
there any reason we can't just change the rule?  It'd be rather more
constructive...

^ permalink raw reply

* Re: [PATCH v4 3/3] tegra: add pwm backlight device tree nodes
From: Stephen Warren @ 2012-08-16 18:45 UTC (permalink / raw)
  To: Alexandre Courbot
  Cc: linux-fbdev-u79uwXL29TY76Z2rM5mHXA, Mark Brown, Stephen Warren,
	linux-doc-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Anton Vorontsov,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA, David Woodhouse,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ
In-Reply-To: <1345097337-24170-4-git-send-email-acourbot-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>

On 08/16/2012 12:08 AM, Alexandre Courbot wrote:

> +++ b/arch/arm/boot/dts/tegra20-ventana.dts

> +	backlight_reg: fixedregulator@176 {
> +		compatible = "regulator-fixed";
> +		regulator-name = "backlight_regulator";
> +		regulator-min-microvolt = <1800000>;
> +		regulator-max-microvolt = <1800000>;
> +		gpio = <&gpio 176 0>;
> +		startup-delay-us = <0>;
> +		enable-active-high;
> +		regulator-boot-off;
> +	};

Rather than add that as a separate node at the top-level, I think just
add another sub-node to the "regulators" node. Oh, in fact it's already
there in next-20120816; you just need to add a label.

> +++ b/arch/arm/boot/dts/tegra20.dtsi

> -	pwm {
> +	pwm: pwm {
>  		compatible = "nvidia,tegra20-pwm";
>  		reg = <0x7000a000 0x100>;

It's pretty trivial I know, but I'd almost be tempted to make that a
separate patch so that it could be cherry-picked somewhere without a
tegra20-ventana.dts. But, perhaps that's silly.

^ permalink raw reply

* Re: [PATCH v4 2/3] pwm_backlight: use power sequences
From: Stephen Warren @ 2012-08-16 18:42 UTC (permalink / raw)
  To: Alexandre Courbot
  Cc: Stephen Warren, Thierry Reding, Simon Glass, Grant Likely,
	Rob Herring, Mark Brown, Anton Vorontsov, David Woodhouse,
	Arnd Bergmann, Leela Krishna Amudala, linux-tegra, linux-kernel,
	linux-fbdev, devicetree-discuss, linux-doc
In-Reply-To: <1345097337-24170-3-git-send-email-acourbot@nvidia.com>

On 08/16/2012 12:08 AM, Alexandre Courbot wrote:
> Make use of the power sequences specified in the device tree or platform
> data to control how the backlight is powered on and off.

> +++ b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt

>  Required properties:
>    - compatible: "pwm-backlight"
> -  - pwms: OF device-tree PWM specification (see PWM binding[0])
>    - brightness-levels: Array of distinct brightness levels. Typically these
>        are in the range from 0 to 255, but any range starting at 0 will do.
>        The actual brightness level (PWM duty cycle) will be interpolated
> @@ -10,19 +9,72 @@ Required properties:
>        last value in the array represents a 100% duty cycle (brightest).
>    - default-brightness-level: the default brightness level (index into the
>        array defined by the "brightness-levels" property)
> +  - pwms: OF device-tree PWM specification (see PWM binding[0]). Exactly one PWM
> +      must be specified

There's probably no need to move that entry just to change it?

>  Optional properties:
> -  - pwm-names: a list of names for the PWM devices specified in the
> -               "pwms" property (see PWM binding[0])

> +  - *-supply: regulators used within a power sequence
> +  - *-gpio: GPIOs used within a power sequence

I don't think these really warrant mentioning here; such properties are
part of the standard regulator and GPIO bindings, and are required as a
side-effect of a power sequence using a resource of those types, rather
than being something actively defined or needed directly by the
pwm-backlight binding.

> +  - pwm-names: name for the PWM device specified in the "pwms" property (see PWM
> +      binding[0]). Necessary if power sequences are used

> +  - power-on-sequence: Power sequence (see Power sequences[1]) used to bring the
> +      backlight on. This sequence must reference the PWM specified in the pwms
> +      property by its name. It can also reference other resources supported by
> +      the power sequences mechanism
> +  - power-off-sequence: Power sequence (see Power sequences[1]) used to bring
> +      the backlight off. This sequence must reference the PWM specified in the
> +      pwms property by its name. It can also reference other resources supported
> +      by the power sequences mechanism

For these two, I would personally simply say that the pwm-backlight
binding requires that two power sequences named "on" and "off" must
exists, and are to defined according to [1].

>  [0]: Documentation/devicetree/bindings/pwm/pwm.txt
> +[1]: Documentation/devicetree/bindings/power_seq/power_seq.txt

^ permalink raw reply

* Re: [PATCH v4 1/3] Runtime Interpreted Power Sequences
From: Stephen Warren @ 2012-08-16 18:38 UTC (permalink / raw)
  To: Alexandre Courbot
  Cc: Stephen Warren, Thierry Reding, Simon Glass, Grant Likely,
	Rob Herring, Mark Brown, Anton Vorontsov, David Woodhouse,
	Arnd Bergmann, Leela Krishna Amudala, linux-tegra, linux-kernel,
	linux-fbdev, devicetree-discuss, linux-doc
In-Reply-To: <1345097337-24170-2-git-send-email-acourbot@nvidia.com>

On 08/16/2012 12:08 AM, Alexandre Courbot wrote:
> Some device drivers (panel backlights especially) need to follow precise
> sequences for powering on and off, involving gpios, regulators, PWMs
> with a precise powering order and delays to respect between each steps.
> These sequences are board-specific, and do not belong to a particular
> driver - therefore they have been performed by board-specific hook
> functions to far.
> 
> With the advent of the device tree and of ARM kernels that are not
> board-tied, we cannot rely on these board-specific hooks anymore but
> need a way to implement these sequences in a portable manner. This patch
> introduces a simple interpreter that can execute such power sequences
> encoded either as platform data or within the device tree.

> diff --git a/Documentation/devicetree/bindings/power_seq/power_seq.txt b/Documentation/devicetree/bindings/power_seq/power_seq.txt

> +Specifying Power Sequences in the Device Tree
> +======================> +In the device tree, power sequences are specified as sub-nodes of the device
> +node and reference resources declared by that device.
> +
> +For an introduction about runtime interpreted power sequences, see
> +Documentation/power/power_seq.txt and include/linux/power_seq.h.

Device tree bindings shouldn't reference Linux documentation; the
bindings are supposed to be OS-agnostic.

> +Power Sequences Structure
> +-------------------------
> +Power sequences are sub-nodes that are named such as the device driver can find
> +them. The driver's documentation should list the sequence names it recognizes.

That's a little roundabout. That might be better as simply:

Valid power sequence names are defined by each device's binding. For a
power sequence named "foo", a node named "foo-power-sequence" defines
that sequence.

> +Inside a power sequence node are sub-nodes that describe the different steps
> +of the sequence. Each step must be named sequentially, with the first step
> +named step0, the second step1, etc. Failure to follow this rule will result in a
> +parsing error.

Node names shouldn't be interpreted by the code; nodes should all be
named after the type of object the represent. Hence, every step should
be named just "step" for example.

The node's unit address (@0) should be used to distinguish the nodes.
This requires reg properties within each node to match the unit address,
and hence #address-cells and #size-cells properties in the power
sequence itself.

Note that this somewhat conflicts with accessing the top-level power
sequence by name too; perhaps that should be re-thought too. I must
admit this DT rule kinda sucks.

> +Power Sequences Steps
> +---------------------
> +Every step of a sequence describes an action to be performed on a resource. It
> +generally includes a property named after the resource type, and which value
> +references the resource to be used. Depending on the resource type, additional
> +properties can be defined to control the action to be performed.

I think you need to add a property that indicates what type of resource
each step applies to. Sure, this is implicit in that if a "gpio"
property exists, the step is a GPIO step, but in order to make that
work, you'd have to search each node for each possible resource type's
property name. It'd be far better to read a single type="gpio" property,
then parse the step based on that.

> +Example
> +-------
> +Here are example sequences declared within a backlight device that use all the
> +supported resources types:
> +
> +	backlight {
> +		compatible = "pwm-backlight";
> +		...
> +
> +		/* resources used by the sequences */
> +		pwms = <&pwm 2 5000000>;
> +		pwm-names = "backlight";
> +		power-supply = <&backlight_reg>;
> +		enable-gpio = <&gpio 28 0>;
> +
> +		power-on-sequence {
> +			step0 {
> +				regulator = "power";
> +				enable;
> +			};
> +			step1 {
> +				delay = <10000>;
> +			};
> +			step2 {
> +				pwm = "backlight";
> +				enable;
> +			};
> +			step3 {
> +				gpio = "enable";
> +				enable;
> +			};
> +		};
> +
> +		power-off-sequence {
> +			step0 {
> +				gpio = "enable";
> +				disable;
> +			};
> +			step1 {
> +				pwm = "backlight";
> +				disable;
> +			};
> +			step2 {
> +				delay = <10000>;
> +			};
> +			step3 {
> +				regulator = "power";
> +				disable;
> +			};
> +		};
> +	};

I notice that for clocks, pwms, and interrupts, the initial step of the
lookup is via a single property that lists all know resources of that
type. Regulators and GPIOs don't follow this style though. Using the
same mechanism for power-sequences would yield something like the
following, which would avoid the "node names must be significant" issue
I mention above, although it does make everything rather more wordy.

[start my proposal]
> 	backlight {
> 		compatible = "pwm-backlight";
> 
> 		/* resources used by the sequences */
> 		pwms = <&pwm 2 5000000>;
> 		pwm-names = "backlight";
> 		power-supply = <&backlight_reg>;
> 		bl-enable-gpio = <&gpio 28 0>;
> 		pwr-seqs = <&bl_on &bl_off>;
> 		pwr-seq-names = "on", "off";
> 
> 		#address-cells = <1>;
> 		#size-cells = <0>;
> 
> 		bl_on: pwr-seq@0 {
> 			reg = <0>;
> 			#address-cells = <1>;
> 			#size-cells = <0>;
> 			step@0 {
> 				reg = <0>;
> 				type = "regulator";
> 				regulator = "power";
> 				enable;
> 			};
> 			step@1 {
> 				reg = <1>;
> 				type = "delay";
> 				delay = <10000>;
> 			};
> 			step@2 {
> 				reg = <2>;
> 				type = "pwm";
> 				pwm = "backlight";
> 				enable;
> 			};
> 			step@3 {
> 				reg = <3>;
> 				type = "gpio";
> 				gpio = "bl-enable";
> 				enable;
> 			};
> 		};
> 
> 		bl_off: pwr-seq@1 {
> 			reg = <1>;
> 			#address-cells = <1>;
> 			#size-cells = <0>;
> 			step@0 {
> 				reg = <0>;
> 				type = "gpio";
> 				gpio = "bl-enable";
> 				disable;
> 			};
> 			step@1 {
> 				reg = <1>;
> 				type = "pwm";
> 				pwm = "backlight";
> 				disable;
> 			};
> 			step@2 {
> 				reg = <2>;
> 				type = "delay";
> 				delay = <10000>;
> 			};
> 			step@3 {
> 				reg = <3>;
> 				type = "regulator";
> 				regulator = "power";
> 				disable;
> 			};
> 		};
> 	};
> 
[end my proposal]

> diff --git a/Documentation/power/power_seq.txt b/Documentation/power/power_seq.txt

> +Usage by Drivers and Resources Management
> +-----------------------------------------
> +Power sequences make use of resources that must be properly allocated and
> +managed. The power_seq_build() function builds a power sequence from the
> +platform data. It also takes care of resolving and allocating the resources
> +referenced by the sequence if needed:
> +
> +  struct power_seq *power_seq_build(struct device *dev, struct list_head *ress,
> +                                    struct platform_power_seq *pseq);
> +
> +The 'dev' argument is the device in the name of which the resources are to be
> +allocated.
> +
> +The 'ress' argument is a list to which the resolved resources are appended. This
> +avoids allocating a resource referenced in several power sequences multiple
> +times.

I see in other parts of the thread, there has been discussion re:
needing the separate ress parameter, and requiring the driver to pass
this in to multiple power_seq_build calls.

The way the pinctrl subsystem solved this was to have a single function
that parsed all pinctrl setting (c.f. all power sequences) at once, and
return a single handle. Later, the driver passes this handle
pinctrl_lookup_state(), along with the requested state (c.f. sequence
name) to search for, and finally passes that handle to
pinctrl_select_state(). Doing something similar here would result in:

struct power_seqs *power_seq_get(struct device *dev);
void power_seq_put(struct power_seqs *seqs);
struct power_seq *power_seq_lookup(struct power_seqs *seqs,
				const char *name);
int power_seq_executestruct power_seq *seq);

struct power_seqs *devm_power_seq_get(struct device *dev);
void devm_power_seq_put(struct power_seqs *seqs);

> +On success, the function returns a devm allocated resolved sequence that is

Perhaps the function should be named devm_power_seq_build(), in order to
make this obvious to people who only read the client code, and not this
documentation.

> +ready to be passed to power_seq_run(). In case of failure, and error code is
> +returned.
> +
> +A resolved power sequence returned by power_seq_build can be run by
> +power_run_run():
> +
> +  int power_seq_run(power_seq *seq);
> +
> +It returns 0 if the sequence has successfully been run, or an error code if a
> +problem occured.
> +
> +There is no need to explicitly free the resources used by the sequence as they
> +are devm-allocated.


^ permalink raw reply

* Re: [PATCH 0/7] HID: picoLCD updates
From: Jiri Kosina @ 2012-08-16 16:47 UTC (permalink / raw)
  To: Bruno Prémont; +Cc: linux-input, linux-kernel, linux-fbdev
In-Reply-To: <20120816183021.68c37f37@neptune.home>

On Thu, 16 Aug 2012, Bruno Prémont wrote:

> > I don't really understand this explanation. Once usb_kill_urb() returns, 
> > the URB should be available for future use (and therefore all queues 
> > completely drained).
> 
> I won't have time today to check, though my guess is that on each
> echo $usb-id > bind; echo $usb-id > unbind
> under /sys/bus/hid/drivers/hid-picolcd/ the USB urb queue fills a bit does
> not get cleared.
> 
> Is usb_kill_urb() called when unbinding just the specific hid driver? 

Yup, through hid_hw_stop() -> usbhid_stop().

> If so my short timing between bind/unbind must be triggering something 
> else...
> 
> Otherwise I'm missing something as at first time I got no "output queue full"
> messages, but as I repeated the bind/unbind sequences the prints per bind/unbind
> iteration increased in number.
> 
> Anyhow, on Friday evening/week-end I will continue digging and report back with my
> findings.

Thank you, Bruno.

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH 0/7] HID: picoLCD updates
From: Bruno Prémont @ 2012-08-16 16:30 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: linux-input, linux-kernel, linux-fbdev
In-Reply-To: <alpine.LNX.2.00.1208152329580.7026@pobox.suse.cz>

On Wed, 15 August 2012 Jiri Kosina <jkosina@suse.cz> wrote:
> On Wed, 15 Aug 2012, Bruno Prémont wrote:
> > > I see. Alan Stern has fixed a huge pile of things in this area in 3.6-rc1. 
> > > I have expected all of those to actually be on theoretical problems not 
> > > ever having happened in the wild, but it might be that you are actually 
> > > chasing on of those.
> > >
> > > Could you please retest with latest Linus' tree (or at least eb055fd0560b) 
> > > to see whether this hasn't actually been fixed already by Alan's series?
> > 
> > I've started trying that out, it seems Alan's work improved things.
> > 
> > For the first few attempts I have not seen SLAB corruptions, though after
> > a few rounds I hit accumulation of the following messages:
> > [  297.174828] hid-picolcd 0003:04D8:C002.0003: output queue full
> > [  297.181098] hid-picolcd 0003:04D8:C002.0003: output queue full
> > [  297.187820] hid-picolcd 0003:04D8:C002.0003: output queue full
> > [  297.194087] hid-picolcd 0003:04D8:C002.0003: output queue full
> > 
> > with sporadically in between:
> > [  292.668019] hid-picolcd 0003:04D8:C002.0003: usb_submit_urb(out) failed: -1
> > 
> > At first glance I think the queue filling up and never draining is caused
> > by hid_hw_stop() stalling the queue and the time between both being just too
> > short.
> 
> I don't really understand this explanation. Once usb_kill_urb() returns, 
> the URB should be available for future use (and therefore all queues 
> completely drained).

I won't have time today to check, though my guess is that on each
echo $usb-id > bind; echo $usb-id > unbind
under /sys/bus/hid/drivers/hid-picolcd/ the USB urb queue fills a bit does
not get cleared.

Is usb_kill_urb() called when unbinding just the specific hid driver? If so
my short timing between bind/unbind must be triggering something else...

Otherwise I'm missing something as at first time I got no "output queue full"
messages, but as I repeated the bind/unbind sequences the prints per bind/unbind
iteration increased in number.

Anyhow, on Friday evening/week-end I will continue digging and report back with my
findings.

Thanks,
Bruno

^ permalink raw reply

* Re: [PATCH v4 1/3] Runtime Interpreted Power Sequences
From: Mark Brown @ 2012-08-16 14:14 UTC (permalink / raw)
  To: Alexandre Courbot
  Cc: Stephen Warren, Thierry Reding, Simon Glass, Grant Likely,
	Rob Herring, Anton Vorontsov, David Woodhouse, Arnd Bergmann,
	Leela Krishna Amudala, linux-tegra, linux-kernel, linux-fbdev,
	devicetree-discuss, linux-doc
In-Reply-To: <1345097337-24170-2-git-send-email-acourbot@nvidia.com>

On Thu, Aug 16, 2012 at 03:08:55PM +0900, Alexandre Courbot wrote:

> +		power-off-sequence {
> +			step0 {
> +				gpio = "enable";
> +				disable;

I'd change the name to "reset" or something in the example - avoids any
confusion with the action.

> +#ifdef CONFIG_REGULATOR
> +	case POWER_SEQ_REGULATOR:
> +		if (pdata->regulator.enable)
> +			err = regulator_enable(step->resource->regulator);
> +		else
> +			err = regulator_disable(step->resource->regulator);
> +		break;

The regulator and GPIO APIs should stub themselves out adequately to not
need the ifdefs at least for regulator I'd use the stubs since...

> +	/*
> +	 * should never happen unless the sequence includes a step which
> +	 * type does not have support compiled in
> +	 */
> +	default:
> +		return -EINVAL;

...this probably isn't what's meant.  It might also be nice to have
support for bulk_enable() but that's definitely something that could be
added later.

> +		err = power_seq_step_run(&seq->steps[cpt]);
> +		if (err) {
> +			dev_err(dev, "error %d while running power sequence!\n",
> +				err);
> +			return err;

I'd also log at least the step number, it'll make diagnostics easier.

^ permalink raw reply

* Re: [PATCH 6/6] OMAPDSS: VENC: Maintian copy of video output polarity in private data
From: Tomi Valkeinen @ 2012-08-16 13:09 UTC (permalink / raw)
  To: Archit Taneja; +Cc: linux-omap, linux-fbdev
In-Reply-To: <502CE727.6070906@ti.com>

[-- Attachment #1: Type: text/plain, Size: 4329 bytes --]

On Thu, 2012-08-16 at 17:57 +0530, Archit Taneja wrote:
> On Thursday 16 August 2012 05:08 PM, Tomi Valkeinen wrote:
> > On Thu, 2012-08-16 at 13:06 +0530, Archit Taneja wrote:
> >> The VENC driver currently relies on the omap_dss_device struct to configure the
> >> video output polarity. This makes the VENC interface driver dependent on the
> >> omap_dss_device struct.
> >>
> >> Make the VENC driver data maintain it's own polarity field. A panel driver
> >> is expected to call omapdss_venc_set_vid_out_polarity() before enabling the
> >> interface.
> >>
> >> Signed-off-by: Archit Taneja <archit@ti.com>
> >> ---
> >>   drivers/video/omap2/dss/dss.h        |    2 ++
> >>   drivers/video/omap2/dss/venc.c       |   13 ++++++++++++-
> >>   drivers/video/omap2/dss/venc_panel.c |    6 ++++++
> >>   3 files changed, 20 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/drivers/video/omap2/dss/dss.h b/drivers/video/omap2/dss/dss.h
> >> index c17d298..b2cf5530 100644
> >> --- a/drivers/video/omap2/dss/dss.h
> >> +++ b/drivers/video/omap2/dss/dss.h
> >> @@ -479,6 +479,8 @@ u32 omapdss_venc_get_wss(struct omap_dss_device *dssdev);
> >>   int omapdss_venc_set_wss(struct omap_dss_device *dssdev, u32 wss);
> >>   void omapdss_venc_set_type(struct omap_dss_device *dssdev,
> >>   		enum omap_dss_venc_type type);
> >> +void omapdss_venc_set_vid_out_polarity(struct omap_dss_device *dssdev,
> >> +		enum omap_dss_signal_level vid_out_pol);
> >>   int venc_panel_init(void);
> >>   void venc_panel_exit(void);
> >>
> >> diff --git a/drivers/video/omap2/dss/venc.c b/drivers/video/omap2/dss/venc.c
> >> index 2d90fcf..8cb372f 100644
> >> --- a/drivers/video/omap2/dss/venc.c
> >> +++ b/drivers/video/omap2/dss/venc.c
> >> @@ -303,6 +303,7 @@ static struct {
> >>
> >>   	struct omap_video_timings timings;
> >>   	enum omap_dss_venc_type type;
> >> +	enum omap_dss_signal_level vid_out_pol;
> >>   } venc;
> >>
> >>   static inline void venc_write_reg(int idx, u32 val)
> >> @@ -447,7 +448,7 @@ static int venc_power_on(struct omap_dss_device *dssdev)
> >>   	else /* S-Video */
> >>   		l |= (1 << 0) | (1 << 2);
> >>
> >> -	if (dssdev->phy.venc.invert_polarity == false)
> >> +	if (venc.vid_out_pol == OMAPDSS_SIG_ACTIVE_HIGH)
> >>   		l |= 1 << 3;
> >
> > Are you sure this is correct? I know practically nothing about analog
> > TV, but the TRM doesn't seem to say much about that bit, except it can
> > be used to "invert the video output". It doesn't say there's an
> > active/inactive level for the signal.
> 
> Well, the code works :), I'm also not totally sure about whether it 

You could put there APPLE and ORANGE enum values, and it'd still work =)

> should be represented as a 2-level signal, it seemed like it would be 
> nicer to give it a signal level rather than keeping it as a bool, which 
> could vary for other SoC's?

It may be really a bool. TRM says "This may be used to
correct for inversion in an external video amplifier". I don't think
there are any digital on/off signals in analog video output, so I'm
guessing it's really inverting (some parts of) the analog signal. If so,
a boolean invert field sounds correct to me.

Actually, check this out:

http://books.google.fi/books?id=P6BlcWaizHUC&pg=PT81&lpg=PT81&dq=composite+video+polarity&source=bl&ots=-gsl0Exv5R&sig=gMylEnoV9ozRwM4RX2iQFqhRpP8&hl=en&sa=X&ei=0u8sUIe3KYXh4QTf9YDQBA&redir_esc=y#v=onepage&q=composite%20video%20polarity&f=false

A monster url, here's a tinyurl version: http://tinyurl.com/clceb6t

2.16 section there shows signal polarities. I'm not sure if it's the
same one that we're discussing, but sounds like it.

I don't think ACTIVE_LOW/HIGH fits into that kind of polarity. Perhaps a
bool is not quite right for it either, as I'm not sure there's a
"normal" polarity. But I'd go forward with out current bool, and fix it
when somebody who understands this stuff tells us what to do =).

> I am considering not to pass this via the panel driver for now, I don't 
> know if all VENC IPs needs to do this, maybe it's okay to leave this 
> dssdev reference for now?

I don't know if other VENC IPs support this or not, but the TRM refers
to external amplifier, so it sounds like a thing that would exist on
other IPs also.

 Tomi


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* [PATCH 19/19] fbdev: sh_mobile_lcdc: Make sh_mobile_lcdc_sys_bus_ops static
From: Laurent Pinchart @ 2012-08-16 13:00 UTC (permalink / raw)
  To: linux-fbdev

The structure isn't used outside of its compilation unit, make it
static.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/video/sh_mobile_lcdcfb.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c
index dd8dd41..e78fe4b 100644
--- a/drivers/video/sh_mobile_lcdcfb.c
+++ b/drivers/video/sh_mobile_lcdcfb.c
@@ -438,7 +438,7 @@ static unsigned long lcdc_sys_read_data(void *handle)
 	return lcdc_read(ch->lcdc, _LDDRDR) & LDDRDR_DRD_MASK;
 }
 
-struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
+static struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
 	lcdc_sys_write_index,
 	lcdc_sys_write_data,
 	lcdc_sys_read_data,
-- 
1.7.8.6


^ permalink raw reply related

* [PATCH 18/19] sh: kfr2r09: Use the backlight API for brightness control
From: Laurent Pinchart @ 2012-08-16 13:00 UTC (permalink / raw)
  To: linux-fbdev

Don't hook up brightness control in the display on/off operations, use
the backlight API instead.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 arch/sh/boards/mach-kfr2r09/lcd_wqvga.c     |   16 +++-------------
 arch/sh/boards/mach-kfr2r09/setup.c         |    7 +++++--
 arch/sh/include/mach-kfr2r09/mach/kfr2r09.h |    6 ++----
 3 files changed, 10 insertions(+), 19 deletions(-)

diff --git a/arch/sh/boards/mach-kfr2r09/lcd_wqvga.c b/arch/sh/boards/mach-kfr2r09/lcd_wqvga.c
index c148b36..c620503 100644
--- a/arch/sh/boards/mach-kfr2r09/lcd_wqvga.c
+++ b/arch/sh/boards/mach-kfr2r09/lcd_wqvga.c
@@ -283,7 +283,7 @@ void kfr2r09_lcd_start(void *sohandle, struct sh_mobile_lcdc_sys_bus_ops *so)
 #define MAIN_MLED4      0x40
 #define MAIN_MSW        0x80
 
-static int kfr2r09_lcd_backlight(int on)
+int kfr2r09_lcd_set_brightness(int brightness)
 {
 	struct i2c_adapter *a;
 	struct i2c_msg msg;
@@ -295,7 +295,7 @@ static int kfr2r09_lcd_backlight(int on)
 		return -ENODEV;
 
 	buf[0] = 0x00;
-	if (on)
+	if (brightness)
 		buf[1] = CTRL_CPSW | CTRL_C10 | CTRL_CKSW;
 	else
 		buf[1] = 0;
@@ -309,7 +309,7 @@ static int kfr2r09_lcd_backlight(int on)
 		return -ENODEV;
 
 	buf[0] = 0x01;
-	if (on)
+	if (brightness)
 		buf[1] = MAIN_MSW | MAIN_MLED4 | 0x0c;
 	else
 		buf[1] = 0;
@@ -324,13 +324,3 @@ static int kfr2r09_lcd_backlight(int on)
 
 	return 0;
 }
-
-void kfr2r09_lcd_on(void)
-{
-	kfr2r09_lcd_backlight(1);
-}
-
-void kfr2r09_lcd_off(void)
-{
-	kfr2r09_lcd_backlight(0);
-}
diff --git a/arch/sh/boards/mach-kfr2r09/setup.c b/arch/sh/boards/mach-kfr2r09/setup.c
index 158c917..ecf40b8 100644
--- a/arch/sh/boards/mach-kfr2r09/setup.c
+++ b/arch/sh/boards/mach-kfr2r09/setup.c
@@ -156,8 +156,11 @@ static struct sh_mobile_lcdc_info kfr2r09_sh_lcdc_info = {
 			.height = 58,
 			.setup_sys = kfr2r09_lcd_setup,
 			.start_transfer = kfr2r09_lcd_start,
-			.display_on = kfr2r09_lcd_on,
-			.display_off = kfr2r09_lcd_off,
+		},
+		.bl_info = {
+			.name = "sh_mobile_lcdc_bl",
+			.max_brightness = 1,
+			.set_brightness = kfr2r09_lcd_set_brightness,
 		},
 		.sys_bus_cfg = {
 			.ldmt2r = 0x07010904,
diff --git a/arch/sh/include/mach-kfr2r09/mach/kfr2r09.h b/arch/sh/include/mach-kfr2r09/mach/kfr2r09.h
index ba3d93d..c20c9e5 100644
--- a/arch/sh/include/mach-kfr2r09/mach/kfr2r09.h
+++ b/arch/sh/include/mach-kfr2r09/mach/kfr2r09.h
@@ -4,15 +4,13 @@
 #include <video/sh_mobile_lcdc.h>
 
 #if defined(CONFIG_FB_SH_MOBILE_LCDC) || defined(CONFIG_FB_SH_MOBILE_LCDC_MODULE)
-void kfr2r09_lcd_on(void);
-void kfr2r09_lcd_off(void);
+int kfr2r09_lcd_set_brightness(int brightness);
 int kfr2r09_lcd_setup(void *sys_ops_handle,
 		      struct sh_mobile_lcdc_sys_bus_ops *sys_ops);
 void kfr2r09_lcd_start(void *sys_ops_handle,
 		       struct sh_mobile_lcdc_sys_bus_ops *sys_ops);
 #else
-static void kfr2r09_lcd_on(void) {}
-static void kfr2r09_lcd_off(void) {}
+static int kfr2r09_lcd_set_brightness(int brightness) {}
 static int kfr2r09_lcd_setup(void *sys_ops_handle,
 				struct sh_mobile_lcdc_sys_bus_ops *sys_ops)
 {
-- 
1.7.8.6


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox