Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

View file

@ -0,0 +1,11 @@
menuconfig MMP_DISP
tristate "Marvell MMP Display Subsystem support"
depends on CPU_PXA910 || CPU_MMP2
help
Marvell Display Subsystem support.
if MMP_DISP
source "drivers/video/fbdev/mmp/hw/Kconfig"
source "drivers/video/fbdev/mmp/panel/Kconfig"
source "drivers/video/fbdev/mmp/fb/Kconfig"
endif

View file

@ -0,0 +1 @@
obj-y += core.o hw/ panel/ fb/

View file

@ -0,0 +1,251 @@
/*
* linux/drivers/video/mmp/common.c
* This driver is a common framework for Marvell Display Controller
*
* Copyright (C) 2012 Marvell Technology Group Ltd.
* Authors: Zhou Zhu <zzhu3@marvell.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/export.h>
#include <video/mmp_disp.h>
static struct mmp_overlay *path_get_overlay(struct mmp_path *path,
int overlay_id)
{
if (path && overlay_id < path->overlay_num)
return &path->overlays[overlay_id];
return NULL;
}
static int path_check_status(struct mmp_path *path)
{
int i;
for (i = 0; i < path->overlay_num; i++)
if (path->overlays[i].status)
return 1;
return 0;
}
/*
* Get modelist write pointer of modelist.
* It also returns modelist number
* this function fetches modelist from phy/panel:
* for HDMI/parallel or dsi to hdmi cases, get from phy
* or get from panel
*/
static int path_get_modelist(struct mmp_path *path,
struct mmp_mode **modelist)
{
BUG_ON(!path || !modelist);
if (path->panel && path->panel->get_modelist)
return path->panel->get_modelist(path->panel, modelist);
return 0;
}
/*
* panel list is used to pair panel/path when path/panel registered
* path list is used for both buffer driver and platdriver
* plat driver do path register/unregister
* panel driver do panel register/unregister
* buffer driver get registered path
*/
static LIST_HEAD(panel_list);
static LIST_HEAD(path_list);
static DEFINE_MUTEX(disp_lock);
/*
* mmp_register_panel - register panel to panel_list and connect to path
* @p: panel to be registered
*
* this function provides interface for panel drivers to register panel
* to panel_list and connect to path which matchs panel->plat_path_name.
* no error returns when no matching path is found as path register after
* panel register is permitted.
*/
void mmp_register_panel(struct mmp_panel *panel)
{
struct mmp_path *path;
mutex_lock(&disp_lock);
/* add */
list_add_tail(&panel->node, &panel_list);
/* try to register to path */
list_for_each_entry(path, &path_list, node) {
if (!strcmp(panel->plat_path_name, path->name)) {
dev_info(panel->dev, "connect to path %s\n",
path->name);
path->panel = panel;
break;
}
}
mutex_unlock(&disp_lock);
}
EXPORT_SYMBOL_GPL(mmp_register_panel);
/*
* mmp_unregister_panel - unregister panel from panel_list and disconnect
* @p: panel to be unregistered
*
* this function provides interface for panel drivers to unregister panel
* from panel_list and disconnect from path.
*/
void mmp_unregister_panel(struct mmp_panel *panel)
{
struct mmp_path *path;
mutex_lock(&disp_lock);
list_del(&panel->node);
list_for_each_entry(path, &path_list, node) {
if (path->panel && path->panel == panel) {
dev_info(panel->dev, "disconnect from path %s\n",
path->name);
path->panel = NULL;
break;
}
}
mutex_unlock(&disp_lock);
}
EXPORT_SYMBOL_GPL(mmp_unregister_panel);
/*
* mmp_get_path - get path by name
* @p: path name
*
* this function checks path name in path_list and return matching path
* return NULL if no matching path
*/
struct mmp_path *mmp_get_path(const char *name)
{
struct mmp_path *path;
int found = 0;
mutex_lock(&disp_lock);
list_for_each_entry(path, &path_list, node) {
if (!strcmp(name, path->name)) {
found = 1;
break;
}
}
mutex_unlock(&disp_lock);
return found ? path : NULL;
}
EXPORT_SYMBOL_GPL(mmp_get_path);
/*
* mmp_register_path - init and register path by path_info
* @p: path info provided by display controller
*
* this function init by path info and register path to path_list
* this function also try to connect path with panel by name
*/
struct mmp_path *mmp_register_path(struct mmp_path_info *info)
{
int i;
size_t size;
struct mmp_path *path = NULL;
struct mmp_panel *panel;
size = sizeof(struct mmp_path)
+ sizeof(struct mmp_overlay) * info->overlay_num;
path = kzalloc(size, GFP_KERNEL);
if (!path)
return NULL;
/* path set */
mutex_init(&path->access_ok);
path->dev = info->dev;
path->id = info->id;
path->name = info->name;
path->output_type = info->output_type;
path->overlay_num = info->overlay_num;
path->plat_data = info->plat_data;
path->ops.set_mode = info->set_mode;
mutex_lock(&disp_lock);
/* get panel */
list_for_each_entry(panel, &panel_list, node) {
if (!strcmp(info->name, panel->plat_path_name)) {
dev_info(path->dev, "get panel %s\n", panel->name);
path->panel = panel;
break;
}
}
dev_info(path->dev, "register %s, overlay_num %d\n",
path->name, path->overlay_num);
/* default op set: if already set by driver, never cover it */
if (!path->ops.check_status)
path->ops.check_status = path_check_status;
if (!path->ops.get_overlay)
path->ops.get_overlay = path_get_overlay;
if (!path->ops.get_modelist)
path->ops.get_modelist = path_get_modelist;
/* step3: init overlays */
for (i = 0; i < path->overlay_num; i++) {
path->overlays[i].path = path;
path->overlays[i].id = i;
mutex_init(&path->overlays[i].access_ok);
path->overlays[i].ops = info->overlay_ops;
}
/* add to pathlist */
list_add_tail(&path->node, &path_list);
mutex_unlock(&disp_lock);
return path;
}
EXPORT_SYMBOL_GPL(mmp_register_path);
/*
* mmp_unregister_path - unregister and destory path
* @p: path to be destoried.
*
* this function registers path and destorys it.
*/
void mmp_unregister_path(struct mmp_path *path)
{
int i;
if (!path)
return;
mutex_lock(&disp_lock);
/* del from pathlist */
list_del(&path->node);
/* deinit overlays */
for (i = 0; i < path->overlay_num; i++)
mutex_destroy(&path->overlays[i].access_ok);
mutex_destroy(&path->access_ok);
kfree(path);
mutex_unlock(&disp_lock);
}
EXPORT_SYMBOL_GPL(mmp_unregister_path);

View file

@ -0,0 +1,13 @@
if MMP_DISP
config MMP_FB
bool "fb driver for Marvell MMP Display Subsystem"
depends on FB
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
default y
help
fb driver for Marvell MMP Display Subsystem
endif

View file

@ -0,0 +1 @@
obj-$(CONFIG_MMP_FB) += mmpfb.o

View file

@ -0,0 +1,689 @@
/*
* linux/drivers/video/mmp/fb/mmpfb.c
* Framebuffer driver for Marvell Display controller.
*
* Copyright (C) 2012 Marvell Technology Group Ltd.
* Authors: Zhou Zhu <zzhu3@marvell.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include "mmpfb.h"
static int var_to_pixfmt(struct fb_var_screeninfo *var)
{
/*
* Pseudocolor mode?
*/
if (var->bits_per_pixel == 8)
return PIXFMT_PSEUDOCOLOR;
/*
* Check for YUV422PLANAR.
*/
if (var->bits_per_pixel == 16 && var->red.length == 8 &&
var->green.length == 4 && var->blue.length == 4) {
if (var->green.offset >= var->blue.offset)
return PIXFMT_YUV422P;
else
return PIXFMT_YVU422P;
}
/*
* Check for YUV420PLANAR.
*/
if (var->bits_per_pixel == 12 && var->red.length == 8 &&
var->green.length == 2 && var->blue.length == 2) {
if (var->green.offset >= var->blue.offset)
return PIXFMT_YUV420P;
else
return PIXFMT_YVU420P;
}
/*
* Check for YUV422PACK.
*/
if (var->bits_per_pixel == 16 && var->red.length == 16 &&
var->green.length == 16 && var->blue.length == 16) {
if (var->red.offset == 0)
return PIXFMT_YUYV;
else if (var->green.offset >= var->blue.offset)
return PIXFMT_UYVY;
else
return PIXFMT_VYUY;
}
/*
* Check for 565/1555.
*/
if (var->bits_per_pixel == 16 && var->red.length <= 5 &&
var->green.length <= 6 && var->blue.length <= 5) {
if (var->transp.length == 0) {
if (var->red.offset >= var->blue.offset)
return PIXFMT_RGB565;
else
return PIXFMT_BGR565;
}
}
/*
* Check for 888/A888.
*/
if (var->bits_per_pixel <= 32 && var->red.length <= 8 &&
var->green.length <= 8 && var->blue.length <= 8) {
if (var->bits_per_pixel == 24 && var->transp.length == 0) {
if (var->red.offset >= var->blue.offset)
return PIXFMT_RGB888PACK;
else
return PIXFMT_BGR888PACK;
}
if (var->bits_per_pixel == 32 && var->transp.offset == 24) {
if (var->red.offset >= var->blue.offset)
return PIXFMT_RGBA888;
else
return PIXFMT_BGRA888;
} else {
if (var->red.offset >= var->blue.offset)
return PIXFMT_RGB888UNPACK;
else
return PIXFMT_BGR888UNPACK;
}
/* fall through */
}
return -EINVAL;
}
static void pixfmt_to_var(struct fb_var_screeninfo *var, int pix_fmt)
{
switch (pix_fmt) {
case PIXFMT_RGB565:
var->bits_per_pixel = 16;
var->red.offset = 11; var->red.length = 5;
var->green.offset = 5; var->green.length = 6;
var->blue.offset = 0; var->blue.length = 5;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_BGR565:
var->bits_per_pixel = 16;
var->red.offset = 0; var->red.length = 5;
var->green.offset = 5; var->green.length = 6;
var->blue.offset = 11; var->blue.length = 5;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_RGB888UNPACK:
var->bits_per_pixel = 32;
var->red.offset = 16; var->red.length = 8;
var->green.offset = 8; var->green.length = 8;
var->blue.offset = 0; var->blue.length = 8;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_BGR888UNPACK:
var->bits_per_pixel = 32;
var->red.offset = 0; var->red.length = 8;
var->green.offset = 8; var->green.length = 8;
var->blue.offset = 16; var->blue.length = 8;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_RGBA888:
var->bits_per_pixel = 32;
var->red.offset = 16; var->red.length = 8;
var->green.offset = 8; var->green.length = 8;
var->blue.offset = 0; var->blue.length = 8;
var->transp.offset = 24; var->transp.length = 8;
break;
case PIXFMT_BGRA888:
var->bits_per_pixel = 32;
var->red.offset = 0; var->red.length = 8;
var->green.offset = 8; var->green.length = 8;
var->blue.offset = 16; var->blue.length = 8;
var->transp.offset = 24; var->transp.length = 8;
break;
case PIXFMT_RGB888PACK:
var->bits_per_pixel = 24;
var->red.offset = 16; var->red.length = 8;
var->green.offset = 8; var->green.length = 8;
var->blue.offset = 0; var->blue.length = 8;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_BGR888PACK:
var->bits_per_pixel = 24;
var->red.offset = 0; var->red.length = 8;
var->green.offset = 8; var->green.length = 8;
var->blue.offset = 16; var->blue.length = 8;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_YUV420P:
var->bits_per_pixel = 12;
var->red.offset = 4; var->red.length = 8;
var->green.offset = 2; var->green.length = 2;
var->blue.offset = 0; var->blue.length = 2;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_YVU420P:
var->bits_per_pixel = 12;
var->red.offset = 4; var->red.length = 8;
var->green.offset = 0; var->green.length = 2;
var->blue.offset = 2; var->blue.length = 2;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_YUV422P:
var->bits_per_pixel = 16;
var->red.offset = 8; var->red.length = 8;
var->green.offset = 4; var->green.length = 4;
var->blue.offset = 0; var->blue.length = 4;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_YVU422P:
var->bits_per_pixel = 16;
var->red.offset = 8; var->red.length = 8;
var->green.offset = 0; var->green.length = 4;
var->blue.offset = 4; var->blue.length = 4;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_UYVY:
var->bits_per_pixel = 16;
var->red.offset = 8; var->red.length = 16;
var->green.offset = 4; var->green.length = 16;
var->blue.offset = 0; var->blue.length = 16;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_VYUY:
var->bits_per_pixel = 16;
var->red.offset = 8; var->red.length = 16;
var->green.offset = 0; var->green.length = 16;
var->blue.offset = 4; var->blue.length = 16;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_YUYV:
var->bits_per_pixel = 16;
var->red.offset = 0; var->red.length = 16;
var->green.offset = 4; var->green.length = 16;
var->blue.offset = 8; var->blue.length = 16;
var->transp.offset = 0; var->transp.length = 0;
break;
case PIXFMT_PSEUDOCOLOR:
var->bits_per_pixel = 8;
var->red.offset = 0; var->red.length = 8;
var->green.offset = 0; var->green.length = 8;
var->blue.offset = 0; var->blue.length = 8;
var->transp.offset = 0; var->transp.length = 0;
break;
}
}
/*
* fb framework has its limitation:
* 1. input color/output color is not seprated
* 2. fb_videomode not include output color
* so for fb usage, we keep a output format which is not changed
* then it's added for mmpmode
*/
static void fbmode_to_mmpmode(struct mmp_mode *mode,
struct fb_videomode *videomode, int output_fmt)
{
u64 div_result = 1000000000000ll;
mode->name = videomode->name;
mode->refresh = videomode->refresh;
mode->xres = videomode->xres;
mode->yres = videomode->yres;
do_div(div_result, videomode->pixclock);
mode->pixclock_freq = (u32)div_result;
mode->left_margin = videomode->left_margin;
mode->right_margin = videomode->right_margin;
mode->upper_margin = videomode->upper_margin;
mode->lower_margin = videomode->lower_margin;
mode->hsync_len = videomode->hsync_len;
mode->vsync_len = videomode->vsync_len;
mode->hsync_invert = !!(videomode->sync & FB_SYNC_HOR_HIGH_ACT);
mode->vsync_invert = !!(videomode->sync & FB_SYNC_VERT_HIGH_ACT);
/* no defined flag in fb, use vmode>>3*/
mode->invert_pixclock = !!(videomode->vmode & 8);
mode->pix_fmt_out = output_fmt;
}
static void mmpmode_to_fbmode(struct fb_videomode *videomode,
struct mmp_mode *mode)
{
u64 div_result = 1000000000000ll;
videomode->name = mode->name;
videomode->refresh = mode->refresh;
videomode->xres = mode->xres;
videomode->yres = mode->yres;
do_div(div_result, mode->pixclock_freq);
videomode->pixclock = (u32)div_result;
videomode->left_margin = mode->left_margin;
videomode->right_margin = mode->right_margin;
videomode->upper_margin = mode->upper_margin;
videomode->lower_margin = mode->lower_margin;
videomode->hsync_len = mode->hsync_len;
videomode->vsync_len = mode->vsync_len;
videomode->sync = (mode->hsync_invert ? FB_SYNC_HOR_HIGH_ACT : 0)
| (mode->vsync_invert ? FB_SYNC_VERT_HIGH_ACT : 0);
videomode->vmode = mode->invert_pixclock ? 8 : 0;
}
static int mmpfb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct mmpfb_info *fbi = info->par;
if (var->bits_per_pixel == 8)
return -EINVAL;
/*
* Basic geometry sanity checks.
*/
if (var->xoffset + var->xres > var->xres_virtual)
return -EINVAL;
if (var->yoffset + var->yres > var->yres_virtual)
return -EINVAL;
/*
* Check size of framebuffer.
*/
if (var->xres_virtual * var->yres_virtual *
(var->bits_per_pixel >> 3) > fbi->fb_size)
return -EINVAL;
return 0;
}
static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
return ((chan & 0xffff) >> (16 - bf->length)) << bf->offset;
}
static u32 to_rgb(u16 red, u16 green, u16 blue)
{
red >>= 8;
green >>= 8;
blue >>= 8;
return (red << 16) | (green << 8) | blue;
}
static int mmpfb_setcolreg(unsigned int regno, unsigned int red,
unsigned int green, unsigned int blue,
unsigned int trans, struct fb_info *info)
{
struct mmpfb_info *fbi = info->par;
u32 val;
if (info->fix.visual == FB_VISUAL_TRUECOLOR && regno < 16) {
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue , &info->var.blue);
fbi->pseudo_palette[regno] = val;
}
if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR && regno < 256) {
val = to_rgb(red, green, blue);
/* TODO */
}
return 0;
}
static int mmpfb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct mmpfb_info *fbi = info->par;
struct mmp_addr addr;
memset(&addr, 0, sizeof(addr));
addr.phys[0] = (var->yoffset * var->xres_virtual + var->xoffset)
* var->bits_per_pixel / 8 + fbi->fb_start_dma;
mmp_overlay_set_addr(fbi->overlay, &addr);
return 0;
}
static int var_update(struct fb_info *info)
{
struct mmpfb_info *fbi = info->par;
struct fb_var_screeninfo *var = &info->var;
struct fb_videomode *m;
int pix_fmt;
/* set pix_fmt */
pix_fmt = var_to_pixfmt(var);
if (pix_fmt < 0)
return -EINVAL;
pixfmt_to_var(var, pix_fmt);
fbi->pix_fmt = pix_fmt;
/* set var according to best video mode*/
m = (struct fb_videomode *)fb_match_mode(var, &info->modelist);
if (!m) {
dev_err(fbi->dev, "set par: no match mode, use best mode\n");
m = (struct fb_videomode *)fb_find_best_mode(var,
&info->modelist);
fb_videomode_to_var(var, m);
}
memcpy(&fbi->mode, m, sizeof(struct fb_videomode));
/* fix to 2* yres */
var->yres_virtual = var->yres * 2;
info->fix.visual = (pix_fmt == PIXFMT_PSEUDOCOLOR) ?
FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8;
info->fix.ypanstep = var->yres;
return 0;
}
static void mmpfb_set_win(struct fb_info *info)
{
struct mmpfb_info *fbi = info->par;
struct fb_var_screeninfo *var = &info->var;
struct mmp_win win;
u32 stride;
memset(&win, 0, sizeof(win));
win.xsrc = win.xdst = fbi->mode.xres;
win.ysrc = win.ydst = fbi->mode.yres;
win.pix_fmt = fbi->pix_fmt;
stride = pixfmt_to_stride(win.pix_fmt);
win.pitch[0] = var->xres_virtual * stride;
win.pitch[1] = win.pitch[2] =
(stride == 1) ? (var->xres_virtual >> 1) : 0;
mmp_overlay_set_win(fbi->overlay, &win);
}
static int mmpfb_set_par(struct fb_info *info)
{
struct mmpfb_info *fbi = info->par;
struct fb_var_screeninfo *var = &info->var;
struct mmp_addr addr;
struct mmp_mode mode;
int ret;
ret = var_update(info);
if (ret != 0)
return ret;
/* set window/path according to new videomode */
fbmode_to_mmpmode(&mode, &fbi->mode, fbi->output_fmt);
mmp_path_set_mode(fbi->path, &mode);
/* set window related info */
mmpfb_set_win(info);
/* set address always */
memset(&addr, 0, sizeof(addr));
addr.phys[0] = (var->yoffset * var->xres_virtual + var->xoffset)
* var->bits_per_pixel / 8 + fbi->fb_start_dma;
mmp_overlay_set_addr(fbi->overlay, &addr);
return 0;
}
static void mmpfb_power(struct mmpfb_info *fbi, int power)
{
struct mmp_addr addr;
struct fb_var_screeninfo *var = &fbi->fb_info->var;
/* for power on, always set address/window again */
if (power) {
/* set window related info */
mmpfb_set_win(fbi->fb_info);
/* set address always */
memset(&addr, 0, sizeof(addr));
addr.phys[0] = fbi->fb_start_dma +
(var->yoffset * var->xres_virtual + var->xoffset)
* var->bits_per_pixel / 8;
mmp_overlay_set_addr(fbi->overlay, &addr);
}
mmp_overlay_set_onoff(fbi->overlay, power);
}
static int mmpfb_blank(int blank, struct fb_info *info)
{
struct mmpfb_info *fbi = info->par;
mmpfb_power(fbi, (blank == FB_BLANK_UNBLANK));
return 0;
}
static struct fb_ops mmpfb_ops = {
.owner = THIS_MODULE,
.fb_blank = mmpfb_blank,
.fb_check_var = mmpfb_check_var,
.fb_set_par = mmpfb_set_par,
.fb_setcolreg = mmpfb_setcolreg,
.fb_pan_display = mmpfb_pan_display,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static int modes_setup(struct mmpfb_info *fbi)
{
struct fb_videomode *videomodes;
struct mmp_mode *mmp_modes;
struct fb_info *info = fbi->fb_info;
int videomode_num, i;
/* get videomodes from path */
videomode_num = mmp_path_get_modelist(fbi->path, &mmp_modes);
if (!videomode_num) {
dev_warn(fbi->dev, "can't get videomode num\n");
return 0;
}
/* put videomode list to info structure */
videomodes = kzalloc(sizeof(struct fb_videomode) * videomode_num,
GFP_KERNEL);
if (!videomodes) {
dev_err(fbi->dev, "can't malloc video modes\n");
return -ENOMEM;
}
for (i = 0; i < videomode_num; i++)
mmpmode_to_fbmode(&videomodes[i], &mmp_modes[i]);
fb_videomode_to_modelist(videomodes, videomode_num, &info->modelist);
/* set videomode[0] as default mode */
memcpy(&fbi->mode, &videomodes[0], sizeof(struct fb_videomode));
fbi->output_fmt = mmp_modes[0].pix_fmt_out;
fb_videomode_to_var(&info->var, &fbi->mode);
mmp_path_set_mode(fbi->path, &mmp_modes[0]);
kfree(videomodes);
return videomode_num;
}
static int fb_info_setup(struct fb_info *info,
struct mmpfb_info *fbi)
{
int ret = 0;
/* Initialise static fb parameters.*/
info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK |
FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN;
info->node = -1;
strcpy(info->fix.id, fbi->name);
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.type_aux = 0;
info->fix.xpanstep = 0;
info->fix.ypanstep = info->var.yres;
info->fix.ywrapstep = 0;
info->fix.accel = FB_ACCEL_NONE;
info->fix.smem_start = fbi->fb_start_dma;
info->fix.smem_len = fbi->fb_size;
info->fix.visual = (fbi->pix_fmt == PIXFMT_PSEUDOCOLOR) ?
FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
info->fix.line_length = info->var.xres_virtual *
info->var.bits_per_pixel / 8;
info->fbops = &mmpfb_ops;
info->pseudo_palette = fbi->pseudo_palette;
info->screen_base = fbi->fb_start;
info->screen_size = fbi->fb_size;
/* For FB framework: Allocate color map and Register framebuffer*/
if (fb_alloc_cmap(&info->cmap, 256, 0) < 0)
ret = -ENOMEM;
return ret;
}
static void fb_info_clear(struct fb_info *info)
{
fb_dealloc_cmap(&info->cmap);
}
static int mmpfb_probe(struct platform_device *pdev)
{
struct mmp_buffer_driver_mach_info *mi;
struct fb_info *info;
struct mmpfb_info *fbi;
int ret, modes_num;
mi = pdev->dev.platform_data;
if (mi == NULL) {
dev_err(&pdev->dev, "no platform data defined\n");
return -EINVAL;
}
/* initialize fb */
info = framebuffer_alloc(sizeof(struct mmpfb_info), &pdev->dev);
if (info == NULL)
return -ENOMEM;
fbi = info->par;
/* init fb */
fbi->fb_info = info;
platform_set_drvdata(pdev, fbi);
fbi->dev = &pdev->dev;
fbi->name = mi->name;
fbi->pix_fmt = mi->default_pixfmt;
pixfmt_to_var(&info->var, fbi->pix_fmt);
mutex_init(&fbi->access_ok);
/* get display path by name */
fbi->path = mmp_get_path(mi->path_name);
if (!fbi->path) {
dev_err(&pdev->dev, "can't get the path %s\n", mi->path_name);
ret = -EINVAL;
goto failed_destroy_mutex;
}
dev_info(fbi->dev, "path %s get\n", fbi->path->name);
/* get overlay */
fbi->overlay = mmp_path_get_overlay(fbi->path, mi->overlay_id);
if (!fbi->overlay) {
ret = -EINVAL;
goto failed_destroy_mutex;
}
/* set fetch used */
mmp_overlay_set_fetch(fbi->overlay, mi->dmafetch_id);
modes_num = modes_setup(fbi);
if (modes_num < 0) {
ret = modes_num;
goto failed_destroy_mutex;
}
/*
* if get modes success, means not hotplug panels, use caculated buffer
* or use default size
*/
if (modes_num > 0) {
/* fix to 2* yres */
info->var.yres_virtual = info->var.yres * 2;
/* Allocate framebuffer memory: size = modes xy *4 */
fbi->fb_size = info->var.xres_virtual * info->var.yres_virtual
* info->var.bits_per_pixel / 8;
} else {
fbi->fb_size = MMPFB_DEFAULT_SIZE;
}
fbi->fb_start = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size),
&fbi->fb_start_dma, GFP_KERNEL);
if (fbi->fb_start == NULL) {
dev_err(&pdev->dev, "can't alloc framebuffer\n");
ret = -ENOMEM;
goto failed_destroy_mutex;
}
memset(fbi->fb_start, 0, fbi->fb_size);
dev_info(fbi->dev, "fb %dk allocated\n", fbi->fb_size/1024);
/* fb power on */
if (modes_num > 0)
mmpfb_power(fbi, 1);
ret = fb_info_setup(info, fbi);
if (ret < 0)
goto failed_free_buff;
ret = register_framebuffer(info);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register fb: %d\n", ret);
ret = -ENXIO;
goto failed_clear_info;
}
dev_info(fbi->dev, "loaded to /dev/fb%d <%s>.\n",
info->node, info->fix.id);
#ifdef CONFIG_LOGO
if (fbi->fb_start) {
fb_prepare_logo(info, 0);
fb_show_logo(info, 0);
}
#endif
return 0;
failed_clear_info:
fb_info_clear(info);
failed_free_buff:
dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size), fbi->fb_start,
fbi->fb_start_dma);
failed_destroy_mutex:
mutex_destroy(&fbi->access_ok);
dev_err(fbi->dev, "mmp-fb: frame buffer device init failed\n");
framebuffer_release(info);
return ret;
}
static struct platform_driver mmpfb_driver = {
.driver = {
.name = "mmp-fb",
.owner = THIS_MODULE,
},
.probe = mmpfb_probe,
};
static int mmpfb_init(void)
{
return platform_driver_register(&mmpfb_driver);
}
module_init(mmpfb_init);
MODULE_AUTHOR("Zhou Zhu <zhou.zhu@marvell.com>");
MODULE_DESCRIPTION("Framebuffer driver for Marvell displays");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,54 @@
/*
* linux/drivers/video/mmp/fb/mmpfb.h
* Framebuffer driver for Marvell Display controller.
*
* Copyright (C) 2012 Marvell Technology Group Ltd.
* Authors: Zhou Zhu <zzhu3@marvell.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef _MMP_FB_H_
#define _MMP_FB_H_
#include <video/mmp_disp.h>
#include <linux/fb.h>
/* LCD controller private state. */
struct mmpfb_info {
struct device *dev;
int id;
const char *name;
struct fb_info *fb_info;
/* basicaly videomode is for output */
struct fb_videomode mode;
int pix_fmt;
void *fb_start;
int fb_size;
dma_addr_t fb_start_dma;
struct mmp_overlay *overlay;
struct mmp_path *path;
struct mutex access_ok;
unsigned int pseudo_palette[16];
int output_fmt;
};
#define MMPFB_DEFAULT_SIZE (PAGE_ALIGN(1920 * 1080 * 4 * 2))
#endif /* _MMP_FB_H_ */

View file

@ -0,0 +1,20 @@
if MMP_DISP
config MMP_DISP_CONTROLLER
bool "mmp display controller hw support"
depends on CPU_PXA910 || CPU_MMP2
default n
help
Marvell MMP display hw controller support
this controller is used on Marvell PXA910 and
MMP2 chips
config MMP_DISP_SPI
bool "mmp display controller spi port"
depends on MMP_DISP_CONTROLLER && SPI_MASTER
default y
help
Marvell MMP display hw controller spi port support
will register as a spi master for panel usage
endif

View file

@ -0,0 +1,2 @@
obj-$(CONFIG_MMP_DISP_CONTROLLER) += mmp_ctrl.o
obj-$(CONFIG_MMP_DISP_SPI) += mmp_spi.o

View file

@ -0,0 +1,588 @@
/*
* linux/drivers/video/mmp/hw/mmp_ctrl.c
* Marvell MMP series Display Controller support
*
* Copyright (C) 2012 Marvell Technology Group Ltd.
* Authors: Guoqing Li <ligq@marvell.com>
* Lisa Du <cldu@marvell.com>
* Zhou Zhu <zzhu3@marvell.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/vmalloc.h>
#include <linux/uaccess.h>
#include <linux/kthread.h>
#include <linux/io.h>
#include "mmp_ctrl.h"
static irqreturn_t ctrl_handle_irq(int irq, void *dev_id)
{
struct mmphw_ctrl *ctrl = (struct mmphw_ctrl *)dev_id;
u32 isr, imask, tmp;
isr = readl_relaxed(ctrl->reg_base + SPU_IRQ_ISR);
imask = readl_relaxed(ctrl->reg_base + SPU_IRQ_ENA);
do {
/* clear clock only */
tmp = readl_relaxed(ctrl->reg_base + SPU_IRQ_ISR);
if (tmp & isr)
writel_relaxed(~isr, ctrl->reg_base + SPU_IRQ_ISR);
} while ((isr = readl_relaxed(ctrl->reg_base + SPU_IRQ_ISR)) & imask);
return IRQ_HANDLED;
}
static u32 fmt_to_reg(struct mmp_overlay *overlay, int pix_fmt)
{
u32 rbswap = 0, uvswap = 0, yuvswap = 0,
csc_en = 0, val = 0,
vid = overlay_is_vid(overlay);
switch (pix_fmt) {
case PIXFMT_RGB565:
case PIXFMT_RGB1555:
case PIXFMT_RGB888PACK:
case PIXFMT_RGB888UNPACK:
case PIXFMT_RGBA888:
rbswap = 1;
break;
case PIXFMT_VYUY:
case PIXFMT_YVU422P:
case PIXFMT_YVU420P:
uvswap = 1;
break;
case PIXFMT_YUYV:
yuvswap = 1;
break;
default:
break;
}
switch (pix_fmt) {
case PIXFMT_RGB565:
case PIXFMT_BGR565:
break;
case PIXFMT_RGB1555:
case PIXFMT_BGR1555:
val = 0x1;
break;
case PIXFMT_RGB888PACK:
case PIXFMT_BGR888PACK:
val = 0x2;
break;
case PIXFMT_RGB888UNPACK:
case PIXFMT_BGR888UNPACK:
val = 0x3;
break;
case PIXFMT_RGBA888:
case PIXFMT_BGRA888:
val = 0x4;
break;
case PIXFMT_UYVY:
case PIXFMT_VYUY:
case PIXFMT_YUYV:
val = 0x5;
csc_en = 1;
break;
case PIXFMT_YUV422P:
case PIXFMT_YVU422P:
val = 0x6;
csc_en = 1;
break;
case PIXFMT_YUV420P:
case PIXFMT_YVU420P:
val = 0x7;
csc_en = 1;
break;
default:
break;
}
return (dma_palette(0) | dma_fmt(vid, val) |
dma_swaprb(vid, rbswap) | dma_swapuv(vid, uvswap) |
dma_swapyuv(vid, yuvswap) | dma_csc(vid, csc_en));
}
static void dmafetch_set_fmt(struct mmp_overlay *overlay)
{
u32 tmp;
struct mmp_path *path = overlay->path;
tmp = readl_relaxed(ctrl_regs(path) + dma_ctrl(0, path->id));
tmp &= ~dma_mask(overlay_is_vid(overlay));
tmp |= fmt_to_reg(overlay, overlay->win.pix_fmt);
writel_relaxed(tmp, ctrl_regs(path) + dma_ctrl(0, path->id));
}
static void overlay_set_win(struct mmp_overlay *overlay, struct mmp_win *win)
{
struct lcd_regs *regs = path_regs(overlay->path);
/* assert win supported */
memcpy(&overlay->win, win, sizeof(struct mmp_win));
mutex_lock(&overlay->access_ok);
if (overlay_is_vid(overlay)) {
writel_relaxed(win->pitch[0], &regs->v_pitch_yc);
writel_relaxed(win->pitch[2] << 16 |
win->pitch[1], &regs->v_pitch_uv);
writel_relaxed((win->ysrc << 16) | win->xsrc, &regs->v_size);
writel_relaxed((win->ydst << 16) | win->xdst, &regs->v_size_z);
writel_relaxed(win->ypos << 16 | win->xpos, &regs->v_start);
} else {
writel_relaxed(win->pitch[0], &regs->g_pitch);
writel_relaxed((win->ysrc << 16) | win->xsrc, &regs->g_size);
writel_relaxed((win->ydst << 16) | win->xdst, &regs->g_size_z);
writel_relaxed(win->ypos << 16 | win->xpos, &regs->g_start);
}
dmafetch_set_fmt(overlay);
mutex_unlock(&overlay->access_ok);
}
static void dmafetch_onoff(struct mmp_overlay *overlay, int on)
{
u32 mask = overlay_is_vid(overlay) ? CFG_DMA_ENA_MASK :
CFG_GRA_ENA_MASK;
u32 enable = overlay_is_vid(overlay) ? CFG_DMA_ENA(1) : CFG_GRA_ENA(1);
u32 tmp;
struct mmp_path *path = overlay->path;
mutex_lock(&overlay->access_ok);
tmp = readl_relaxed(ctrl_regs(path) + dma_ctrl(0, path->id));
tmp &= ~mask;
tmp |= (on ? enable : 0);
writel(tmp, ctrl_regs(path) + dma_ctrl(0, path->id));
mutex_unlock(&overlay->access_ok);
}
static void path_enabledisable(struct mmp_path *path, int on)
{
u32 tmp;
mutex_lock(&path->access_ok);
tmp = readl_relaxed(ctrl_regs(path) + LCD_SCLK(path));
if (on)
tmp &= ~SCLK_DISABLE;
else
tmp |= SCLK_DISABLE;
writel_relaxed(tmp, ctrl_regs(path) + LCD_SCLK(path));
mutex_unlock(&path->access_ok);
}
static void path_onoff(struct mmp_path *path, int on)
{
if (path->status == on) {
dev_info(path->dev, "path %s is already %s\n",
path->name, stat_name(path->status));
return;
}
if (on) {
path_enabledisable(path, 1);
if (path->panel && path->panel->set_onoff)
path->panel->set_onoff(path->panel, 1);
} else {
if (path->panel && path->panel->set_onoff)
path->panel->set_onoff(path->panel, 0);
path_enabledisable(path, 0);
}
path->status = on;
}
static void overlay_set_onoff(struct mmp_overlay *overlay, int on)
{
if (overlay->status == on) {
dev_info(overlay_to_ctrl(overlay)->dev, "overlay %s is already %s\n",
overlay->path->name, stat_name(overlay->status));
return;
}
overlay->status = on;
dmafetch_onoff(overlay, on);
if (overlay->path->ops.check_status(overlay->path)
!= overlay->path->status)
path_onoff(overlay->path, on);
}
static void overlay_set_fetch(struct mmp_overlay *overlay, int fetch_id)
{
overlay->dmafetch_id = fetch_id;
}
static int overlay_set_addr(struct mmp_overlay *overlay, struct mmp_addr *addr)
{
struct lcd_regs *regs = path_regs(overlay->path);
/* FIXME: assert addr supported */
memcpy(&overlay->addr, addr, sizeof(struct mmp_addr));
if (overlay_is_vid(overlay)) {
writel_relaxed(addr->phys[0], &regs->v_y0);
writel_relaxed(addr->phys[1], &regs->v_u0);
writel_relaxed(addr->phys[2], &regs->v_v0);
} else
writel_relaxed(addr->phys[0], &regs->g_0);
return overlay->addr.phys[0];
}
static void path_set_mode(struct mmp_path *path, struct mmp_mode *mode)
{
struct lcd_regs *regs = path_regs(path);
u32 total_x, total_y, vsync_ctrl, tmp, sclk_src, sclk_div,
link_config = path_to_path_plat(path)->link_config,
dsi_rbswap = path_to_path_plat(path)->link_config;
/* FIXME: assert videomode supported */
memcpy(&path->mode, mode, sizeof(struct mmp_mode));
mutex_lock(&path->access_ok);
/* polarity of timing signals */
tmp = readl_relaxed(ctrl_regs(path) + intf_ctrl(path->id)) & 0x1;
tmp |= mode->vsync_invert ? 0 : 0x8;
tmp |= mode->hsync_invert ? 0 : 0x4;
tmp |= link_config & CFG_DUMBMODE_MASK;
tmp |= CFG_DUMB_ENA(1);
writel_relaxed(tmp, ctrl_regs(path) + intf_ctrl(path->id));
/* interface rb_swap setting */
tmp = readl_relaxed(ctrl_regs(path) + intf_rbswap_ctrl(path->id)) &
(~(CFG_INTFRBSWAP_MASK));
tmp |= dsi_rbswap & CFG_INTFRBSWAP_MASK;
writel_relaxed(tmp, ctrl_regs(path) + intf_rbswap_ctrl(path->id));
writel_relaxed((mode->yres << 16) | mode->xres, &regs->screen_active);
writel_relaxed((mode->left_margin << 16) | mode->right_margin,
&regs->screen_h_porch);
writel_relaxed((mode->upper_margin << 16) | mode->lower_margin,
&regs->screen_v_porch);
total_x = mode->xres + mode->left_margin + mode->right_margin +
mode->hsync_len;
total_y = mode->yres + mode->upper_margin + mode->lower_margin +
mode->vsync_len;
writel_relaxed((total_y << 16) | total_x, &regs->screen_size);
/* vsync ctrl */
if (path->output_type == PATH_OUT_DSI)
vsync_ctrl = 0x01330133;
else
vsync_ctrl = ((mode->xres + mode->right_margin) << 16)
| (mode->xres + mode->right_margin);
writel_relaxed(vsync_ctrl, &regs->vsync_ctrl);
/* set pixclock div */
sclk_src = clk_get_rate(path_to_ctrl(path)->clk);
sclk_div = sclk_src / mode->pixclock_freq;
if (sclk_div * mode->pixclock_freq < sclk_src)
sclk_div++;
dev_info(path->dev, "%s sclk_src %d sclk_div 0x%x pclk %d\n",
__func__, sclk_src, sclk_div, mode->pixclock_freq);
tmp = readl_relaxed(ctrl_regs(path) + LCD_SCLK(path));
tmp &= ~CLK_INT_DIV_MASK;
tmp |= sclk_div;
writel_relaxed(tmp, ctrl_regs(path) + LCD_SCLK(path));
mutex_unlock(&path->access_ok);
}
static struct mmp_overlay_ops mmphw_overlay_ops = {
.set_fetch = overlay_set_fetch,
.set_onoff = overlay_set_onoff,
.set_win = overlay_set_win,
.set_addr = overlay_set_addr,
};
static void ctrl_set_default(struct mmphw_ctrl *ctrl)
{
u32 tmp, irq_mask;
/*
* LCD Global control(LCD_TOP_CTRL) should be configed before
* any other LCD registers read/write, or there maybe issues.
*/
tmp = readl_relaxed(ctrl->reg_base + LCD_TOP_CTRL);
tmp |= 0xfff0;
writel_relaxed(tmp, ctrl->reg_base + LCD_TOP_CTRL);
/* disable all interrupts */
irq_mask = path_imasks(0) | err_imask(0) |
path_imasks(1) | err_imask(1);
tmp = readl_relaxed(ctrl->reg_base + SPU_IRQ_ENA);
tmp &= ~irq_mask;
tmp |= irq_mask;
writel_relaxed(tmp, ctrl->reg_base + SPU_IRQ_ENA);
}
static void path_set_default(struct mmp_path *path)
{
struct lcd_regs *regs = path_regs(path);
u32 dma_ctrl1, mask, tmp, path_config;
path_config = path_to_path_plat(path)->path_config;
/* Configure IOPAD: should be parallel only */
if (PATH_OUT_PARALLEL == path->output_type) {
mask = CFG_IOPADMODE_MASK | CFG_BURST_MASK | CFG_BOUNDARY_MASK;
tmp = readl_relaxed(ctrl_regs(path) + SPU_IOPAD_CONTROL);
tmp &= ~mask;
tmp |= path_config;
writel_relaxed(tmp, ctrl_regs(path) + SPU_IOPAD_CONTROL);
}
/* Select path clock source */
tmp = readl_relaxed(ctrl_regs(path) + LCD_SCLK(path));
tmp &= ~SCLK_SRC_SEL_MASK;
tmp |= path_config;
writel_relaxed(tmp, ctrl_regs(path) + LCD_SCLK(path));
/*
* Configure default bits: vsync triggers DMA,
* power save enable, configure alpha registers to
* display 100% graphics, and set pixel command.
*/
dma_ctrl1 = 0x2032ff81;
dma_ctrl1 |= CFG_VSYNC_INV_MASK;
writel_relaxed(dma_ctrl1, ctrl_regs(path) + dma_ctrl(1, path->id));
/* Configure default register values */
writel_relaxed(0x00000000, &regs->blank_color);
writel_relaxed(0x00000000, &regs->g_1);
writel_relaxed(0x00000000, &regs->g_start);
/*
* 1.enable multiple burst request in DMA AXI
* bus arbiter for faster read if not tv path;
* 2.enable horizontal smooth filter;
*/
mask = CFG_GRA_HSMOOTH_MASK | CFG_DMA_HSMOOTH_MASK | CFG_ARBFAST_ENA(1);
tmp = readl_relaxed(ctrl_regs(path) + dma_ctrl(0, path->id));
tmp |= mask;
if (PATH_TV == path->id)
tmp &= ~CFG_ARBFAST_ENA(1);
writel_relaxed(tmp, ctrl_regs(path) + dma_ctrl(0, path->id));
}
static int path_init(struct mmphw_path_plat *path_plat,
struct mmp_mach_path_config *config)
{
struct mmphw_ctrl *ctrl = path_plat->ctrl;
struct mmp_path_info *path_info;
struct mmp_path *path = NULL;
dev_info(ctrl->dev, "%s: %s\n", __func__, config->name);
/* init driver data */
path_info = kzalloc(sizeof(struct mmp_path_info), GFP_KERNEL);
if (!path_info) {
dev_err(ctrl->dev, "%s: unable to alloc path_info for %s\n",
__func__, config->name);
return 0;
}
path_info->name = config->name;
path_info->id = path_plat->id;
path_info->dev = ctrl->dev;
path_info->overlay_num = config->overlay_num;
path_info->overlay_ops = &mmphw_overlay_ops;
path_info->set_mode = path_set_mode;
path_info->plat_data = path_plat;
/* create/register platform device */
path = mmp_register_path(path_info);
if (!path) {
kfree(path_info);
return 0;
}
path_plat->path = path;
path_plat->path_config = config->path_config;
path_plat->link_config = config->link_config;
path_plat->dsi_rbswap = config->dsi_rbswap;
path_set_default(path);
kfree(path_info);
return 1;
}
static void path_deinit(struct mmphw_path_plat *path_plat)
{
if (!path_plat)
return;
if (path_plat->path)
mmp_unregister_path(path_plat->path);
}
static int mmphw_probe(struct platform_device *pdev)
{
struct mmp_mach_plat_info *mi;
struct resource *res;
int ret, i, size, irq;
struct mmphw_path_plat *path_plat;
struct mmphw_ctrl *ctrl = NULL;
/* get resources from platform data */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "%s: no IO memory defined\n", __func__);
ret = -ENOENT;
goto failed;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "%s: no IRQ defined\n", __func__);
ret = -ENOENT;
goto failed;
}
/* get configs from platform data */
mi = pdev->dev.platform_data;
if (mi == NULL || !mi->path_num || !mi->paths) {
dev_err(&pdev->dev, "%s: no platform data defined\n", __func__);
ret = -EINVAL;
goto failed;
}
/* allocate */
size = sizeof(struct mmphw_ctrl) + sizeof(struct mmphw_path_plat) *
mi->path_num;
ctrl = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
if (!ctrl) {
ret = -ENOMEM;
goto failed;
}
ctrl->name = mi->name;
ctrl->path_num = mi->path_num;
ctrl->dev = &pdev->dev;
ctrl->irq = irq;
platform_set_drvdata(pdev, ctrl);
mutex_init(&ctrl->access_ok);
/* map registers.*/
if (!devm_request_mem_region(ctrl->dev, res->start,
resource_size(res), ctrl->name)) {
dev_err(ctrl->dev,
"can't request region for resource %pR\n", res);
ret = -EINVAL;
goto failed;
}
ctrl->reg_base = devm_ioremap_nocache(ctrl->dev,
res->start, resource_size(res));
if (ctrl->reg_base == NULL) {
dev_err(ctrl->dev, "%s: res %x - %x map failed\n", __func__,
res->start, res->end);
ret = -ENOMEM;
goto failed;
}
/* request irq */
ret = devm_request_irq(ctrl->dev, ctrl->irq, ctrl_handle_irq,
IRQF_SHARED, "lcd_controller", ctrl);
if (ret < 0) {
dev_err(ctrl->dev, "%s unable to request IRQ %d\n",
__func__, ctrl->irq);
ret = -ENXIO;
goto failed;
}
/* get clock */
ctrl->clk = devm_clk_get(ctrl->dev, mi->clk_name);
if (IS_ERR(ctrl->clk)) {
dev_err(ctrl->dev, "unable to get clk %s\n", mi->clk_name);
ret = -ENOENT;
goto failed;
}
clk_prepare_enable(ctrl->clk);
/* init global regs */
ctrl_set_default(ctrl);
/* init pathes from machine info and register them */
for (i = 0; i < ctrl->path_num; i++) {
/* get from config and machine info */
path_plat = &ctrl->path_plats[i];
path_plat->id = i;
path_plat->ctrl = ctrl;
/* path init */
if (!path_init(path_plat, &mi->paths[i])) {
ret = -EINVAL;
goto failed_path_init;
}
}
#ifdef CONFIG_MMP_DISP_SPI
ret = lcd_spi_register(ctrl);
if (ret < 0)
goto failed_path_init;
#endif
dev_info(ctrl->dev, "device init done\n");
return 0;
failed_path_init:
for (i = 0; i < ctrl->path_num; i++) {
path_plat = &ctrl->path_plats[i];
path_deinit(path_plat);
}
clk_disable_unprepare(ctrl->clk);
failed:
dev_err(&pdev->dev, "device init failed\n");
return ret;
}
static struct platform_driver mmphw_driver = {
.driver = {
.name = "mmp-disp",
.owner = THIS_MODULE,
},
.probe = mmphw_probe,
};
static int mmphw_init(void)
{
return platform_driver_register(&mmphw_driver);
}
module_init(mmphw_init);
MODULE_AUTHOR("Li Guoqing<ligq@marvell.com>");
MODULE_DESCRIPTION("Framebuffer driver for mmp");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,180 @@
/*
* linux/drivers/video/mmp/hw/mmp_spi.c
* using the spi in LCD controler for commands send
*
* Copyright (C) 2012 Marvell Technology Group Ltd.
* Authors: Guoqing Li <ligq@marvell.com>
* Lisa Du <cldu@marvell.com>
* Zhou Zhu <zzhu3@marvell.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/spi/spi.h>
#include "mmp_ctrl.h"
/**
* spi_write - write command to the SPI port
* @data: can be 8/16/32-bit, MSB justified data to write.
* @len: data length.
*
* Wait bus transfer complete IRQ.
* The caller is expected to perform the necessary locking.
*
* Returns:
* %-ETIMEDOUT timeout occurred
* 0 success
*/
static inline int lcd_spi_write(struct spi_device *spi, u32 data)
{
int timeout = 100000, isr, ret = 0;
u32 tmp;
void *reg_base =
*(void **)spi_master_get_devdata(spi->master);
/* clear ISR */
writel_relaxed(~SPI_IRQ_MASK, reg_base + SPU_IRQ_ISR);
switch (spi->bits_per_word) {
case 8:
writel_relaxed((u8)data, reg_base + LCD_SPU_SPI_TXDATA);
break;
case 16:
writel_relaxed((u16)data, reg_base + LCD_SPU_SPI_TXDATA);
break;
case 32:
writel_relaxed((u32)data, reg_base + LCD_SPU_SPI_TXDATA);
break;
default:
dev_err(&spi->dev, "Wrong spi bit length\n");
}
/* SPI start to send command */
tmp = readl_relaxed(reg_base + LCD_SPU_SPI_CTRL);
tmp &= ~CFG_SPI_START_MASK;
tmp |= CFG_SPI_START(1);
writel(tmp, reg_base + LCD_SPU_SPI_CTRL);
isr = readl_relaxed(reg_base + SPU_IRQ_ISR);
while (!(isr & SPI_IRQ_ENA_MASK)) {
udelay(100);
isr = readl_relaxed(reg_base + SPU_IRQ_ISR);
if (!--timeout) {
ret = -ETIMEDOUT;
dev_err(&spi->dev, "spi cmd send time out\n");
break;
}
}
tmp = readl_relaxed(reg_base + LCD_SPU_SPI_CTRL);
tmp &= ~CFG_SPI_START_MASK;
tmp |= CFG_SPI_START(0);
writel_relaxed(tmp, reg_base + LCD_SPU_SPI_CTRL);
writel_relaxed(~SPI_IRQ_MASK, reg_base + SPU_IRQ_ISR);
return ret;
}
static int lcd_spi_setup(struct spi_device *spi)
{
void *reg_base =
*(void **)spi_master_get_devdata(spi->master);
u32 tmp;
tmp = CFG_SCLKCNT(16) |
CFG_TXBITS(spi->bits_per_word) |
CFG_SPI_SEL(1) | CFG_SPI_ENA(1) |
CFG_SPI_3W4WB(1);
writel(tmp, reg_base + LCD_SPU_SPI_CTRL);
/*
* After set mode it need a time to pull up the spi singals,
* or it would cause the wrong waveform when send spi command,
* especially on pxa910h
*/
tmp = readl_relaxed(reg_base + SPU_IOPAD_CONTROL);
if ((tmp & CFG_IOPADMODE_MASK) != IOPAD_DUMB18SPI)
writel_relaxed(IOPAD_DUMB18SPI |
(tmp & ~CFG_IOPADMODE_MASK),
reg_base + SPU_IOPAD_CONTROL);
udelay(20);
return 0;
}
static int lcd_spi_one_transfer(struct spi_device *spi, struct spi_message *m)
{
struct spi_transfer *t;
int i;
list_for_each_entry(t, &m->transfers, transfer_list) {
switch (spi->bits_per_word) {
case 8:
for (i = 0; i < t->len; i++)
lcd_spi_write(spi, ((u8 *)t->tx_buf)[i]);
break;
case 16:
for (i = 0; i < t->len/2; i++)
lcd_spi_write(spi, ((u16 *)t->tx_buf)[i]);
break;
case 32:
for (i = 0; i < t->len/4; i++)
lcd_spi_write(spi, ((u32 *)t->tx_buf)[i]);
break;
default:
dev_err(&spi->dev, "Wrong spi bit length\n");
}
}
m->status = 0;
if (m->complete)
m->complete(m->context);
return 0;
}
int lcd_spi_register(struct mmphw_ctrl *ctrl)
{
struct spi_master *master;
void **p_regbase;
int err;
master = spi_alloc_master(ctrl->dev, sizeof(void *));
if (!master) {
dev_err(ctrl->dev, "unable to allocate SPI master\n");
return -ENOMEM;
}
p_regbase = spi_master_get_devdata(master);
*p_regbase = ctrl->reg_base;
/* set bus num to 5 to avoid conflict with other spi hosts */
master->bus_num = 5;
master->num_chipselect = 1;
master->setup = lcd_spi_setup;
master->transfer = lcd_spi_one_transfer;
err = spi_register_master(master);
if (err < 0) {
dev_err(ctrl->dev, "unable to register SPI master\n");
spi_master_put(master);
return err;
}
dev_info(&master->dev, "registered\n");
return 0;
}

View file

@ -0,0 +1,6 @@
config MMP_PANEL_TPOHVGA
bool "tpohvga panel TJ032MD01BW support"
depends on SPI_MASTER
default n
help
tpohvga panel support

View file

@ -0,0 +1 @@
obj-$(CONFIG_MMP_PANEL_TPOHVGA) += tpo_tj032md01bw.o

View file

@ -0,0 +1,186 @@
/*
* linux/drivers/video/mmp/panel/tpo_tj032md01bw.c
* active panel using spi interface to do init
*
* Copyright (C) 2012 Marvell Technology Group Ltd.
* Authors: Guoqing Li <ligq@marvell.com>
* Lisa Du <cldu@marvell.com>
* Zhou Zhu <zzhu3@marvell.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/spi/spi.h>
#include <video/mmp_disp.h>
static u16 init[] = {
0x0801,
0x0800,
0x0200,
0x0304,
0x040e,
0x0903,
0x0b18,
0x0c53,
0x0d01,
0x0ee0,
0x0f01,
0x1058,
0x201e,
0x210a,
0x220a,
0x231e,
0x2400,
0x2532,
0x2600,
0x27ac,
0x2904,
0x2aa2,
0x2b45,
0x2c45,
0x2d15,
0x2e5a,
0x2fff,
0x306b,
0x310d,
0x3248,
0x3382,
0x34bd,
0x35e7,
0x3618,
0x3794,
0x3801,
0x395d,
0x3aae,
0x3bff,
0x07c9,
};
static u16 poweroff[] = {
0x07d9,
};
struct tpohvga_plat_data {
void (*plat_onoff)(int status);
struct spi_device *spi;
};
static void tpohvga_onoff(struct mmp_panel *panel, int status)
{
struct tpohvga_plat_data *plat = panel->plat_data;
int ret;
if (status) {
plat->plat_onoff(1);
ret = spi_write(plat->spi, init, sizeof(init));
if (ret < 0)
dev_warn(panel->dev, "init cmd failed(%d)\n", ret);
} else {
ret = spi_write(plat->spi, poweroff, sizeof(poweroff));
if (ret < 0)
dev_warn(panel->dev, "poweroff cmd failed(%d)\n", ret);
plat->plat_onoff(0);
}
}
static struct mmp_mode mmp_modes_tpohvga[] = {
[0] = {
.pixclock_freq = 10394400,
.refresh = 60,
.xres = 320,
.yres = 480,
.hsync_len = 10,
.left_margin = 15,
.right_margin = 10,
.vsync_len = 2,
.upper_margin = 4,
.lower_margin = 2,
.invert_pixclock = 1,
.pix_fmt_out = PIXFMT_RGB565,
},
};
static int tpohvga_get_modelist(struct mmp_panel *panel,
struct mmp_mode **modelist)
{
*modelist = mmp_modes_tpohvga;
return 1;
}
static struct mmp_panel panel_tpohvga = {
.name = "tpohvga",
.panel_type = PANELTYPE_ACTIVE,
.get_modelist = tpohvga_get_modelist,
.set_onoff = tpohvga_onoff,
};
static int tpohvga_probe(struct spi_device *spi)
{
struct mmp_mach_panel_info *mi;
int ret;
struct tpohvga_plat_data *plat_data;
/* get configs from platform data */
mi = spi->dev.platform_data;
if (mi == NULL) {
dev_err(&spi->dev, "%s: no platform data defined\n", __func__);
return -EINVAL;
}
/* setup spi related info */
spi->bits_per_word = 16;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi setup failed %d", ret);
return ret;
}
plat_data = kzalloc(sizeof(*plat_data), GFP_KERNEL);
if (plat_data == NULL)
return -ENOMEM;
plat_data->spi = spi;
plat_data->plat_onoff = mi->plat_set_onoff;
panel_tpohvga.plat_data = plat_data;
panel_tpohvga.plat_path_name = mi->plat_path_name;
panel_tpohvga.dev = &spi->dev;
mmp_register_panel(&panel_tpohvga);
return 0;
}
static struct spi_driver panel_tpohvga_driver = {
.driver = {
.name = "tpo-hvga",
.owner = THIS_MODULE,
},
.probe = tpohvga_probe,
};
module_spi_driver(panel_tpohvga_driver);
MODULE_AUTHOR("Lisa Du<cldu@marvell.com>");
MODULE_DESCRIPTION("Panel driver for tpohvga");
MODULE_LICENSE("GPL");