blob: 3a8f30aa91e92450ddbf9134207b2ca7b50c7ce0 [file] [log] [blame]
/*
* Copyright (C) ST-Ericsson AB 2010
*
* ST-Ericsson MCDE base driver
*
* Author: Marcus Lorentzon <marcus.xm.lorentzon@stericsson.com>
* for ST-Ericsson.
*
* License terms: GNU General Public License (GPL), version 2.
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/err.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/regulator/consumer.h>
#include <linux/clk.h>
#include <linux/slab.h>
#include "dsilink_regs.h"
#include "mcde_regs.h"
#include <video/mcde.h>
#include <mach/prcmu-fw-api.h>
static void disable_channel(struct mcde_chnl_state *chnl);
#define MSEC_TO_JIFFIES(__x) (((__x) * HZ + HZ - 1) / 1000)
#define OVLY_TIMEOUT MSEC_TO_JIFFIES(500)
#define CHNL_TIMEOUT MSEC_TO_JIFFIES(500)
u8 *mcdeio;
u8 **dsiio;
DEFINE_SPINLOCK(mcde_lock); /* REVIEW: Remove or use */
struct platform_device *mcde_dev;
u8 num_dsilinks;
static struct regulator *regulator;
static struct clk *clock_dsi;
static struct clk *clock_mcde;
static struct clk *clock_dsi_lp;
static inline u32 dsi_rreg(int __i, u32 __reg)
{
return readl(dsiio[__i] + __reg);
}
static inline void dsi_wreg(int __i, u32 __reg, u32 __val)
{
writel(__val, dsiio[__i] + __reg);
}
#define dsi_rfld(__i, __reg, __fld) \
((dsi_rreg(__i, __reg) & __reg##_##__fld##_MASK) >> \
__reg##_##__fld##_SHIFT)
#define dsi_wfld(__i, __reg, __fld, __val) \
dsi_wreg(__i, __reg, (dsi_rreg(__i, __reg) & \
~__reg##_##__fld##_MASK) | (((__val) << __reg##_##__fld##_SHIFT) & \
__reg##_##__fld##_MASK))
static inline u32 mcde_rreg(u32 __reg)
{
return readl(mcdeio + __reg);
}
static inline void mcde_wreg(u32 __reg, u32 __val)
{
writel(__val, mcdeio + __reg);
}
#define mcde_rfld(__reg, __fld) \
((mcde_rreg(__reg) & __reg##_##__fld##_MASK) >> \
__reg##_##__fld##_SHIFT)
#define mcde_wfld(__reg, __fld, __val) \
mcde_wreg(__reg, (mcde_rreg(__reg) & \
~__reg##_##__fld##_MASK) | (((__val) << __reg##_##__fld##_SHIFT) & \
__reg##_##__fld##_MASK))
struct ovly_regs {
u8 ch_id;
bool enabled;
u32 baseaddress0;
u32 baseaddress1;
bool reset_buf_id;
u8 bits_per_pixel;
u8 bpp;
bool bgr;
bool bebo;
bool opq;
u8 col_conv;
u8 pixoff;
u16 ppl;
u16 lpf;
u16 cropx;
u16 cropy;
u16 xpos;
u16 ypos;
u8 z;
};
struct mcde_ovly_state {
bool inuse;
u8 idx; /* MCDE overlay index */
struct mcde_chnl_state *chnl; /* Owner channel */
u32 transactionid; /* Apply time stamp */
u32 transactionid_regs; /* Register update time stamp */
u32 transactionid_hw; /* HW completed time stamp */
wait_queue_head_t waitq_hw; /* Waitq for transactionid_hw */
/* Staged settings */
u32 paddr;
u16 stride;
enum mcde_ovly_pix_fmt pix_fmt;
u16 src_x;
u16 src_y;
u16 dst_x;
u16 dst_y;
u16 dst_z;
u16 w;
u16 h;
/* Applied settings */
struct ovly_regs regs;
};
static struct mcde_ovly_state overlays[] = {
{ .idx = 0 },
{ .idx = 1 },
{ .idx = 2 },
{ .idx = 3 },
{ .idx = 4 },
{ .idx = 5 },
};
struct chnl_regs {
bool floen;
u16 x;
u16 y;
u16 ppl;
u16 lpf;
u8 bpp;
bool synchronized_update;
bool roten;
u8 rotdir;
u32 rotbuf1; /* TODO: Replace with eSRAM alloc */
u32 rotbuf2; /* TODO: Replace with eSRAM alloc */
/* DSI */
u8 dsipacking;
};
enum mcde_hw_tv_mode {/* REVIEW: What's wrong with regs defines? */
MCDE_HW_SDTV_MODE = 0,
MCDE_HW_HDTV_MODE_480P = 1,
MCDE_HW_HDTV_MODE_720P = 2,
MCDE_HW_SDTV_MODE_DDR_LSB_1ST = 3,
MCDE_HW_SDTV_MODE_DDR_MSB_1ST = 4,
MCDE_HW_TV_MODE_LEN = 5,
};
struct col_regs {
u16 y_red;
u16 y_green;
u16 y_blue;
u16 cb_red;
u16 cb_green;
u16 cb_blue;
u16 cr_red;
u16 cr_green;
u16 cr_blue;
u16 off_red;
u16 off_green;
u16 off_blue;
};
struct tv_regs {
u16 hbw; /* horizontal blanking width */
/* field 1 */
u16 bel1; /* field total vertical blanking lines */
u16 fsl1; /* field vbp */
/* field 2 */
u16 bel2;
u16 fsl2;
bool interlaced_en;
enum mcde_hw_tv_mode tv_mode;
};
struct mcde_chnl_state {
bool inuse;
enum mcde_chnl id;
enum mcde_fifo fifo;
struct mcde_port port;
struct mcde_ovly_state *ovly0;
struct mcde_ovly_state *ovly1;
const struct chnl_config *cfg;
u32 transactionid;
u32 transactionid_regs;
u32 transactionid_hw;
wait_queue_head_t waitq_hw; /* Waitq for transactionid_hw */
enum mcde_display_power_mode power_mode;
/* Staged settings */
bool synchronized_update;
enum mcde_port_pix_fmt pix_fmt;
struct mcde_video_mode vmode;
enum mcde_display_rotation rotation;
u32 rotbuf1;
u32 rotbuf2;
/* Applied settings */
struct chnl_regs regs;
struct col_regs col_regs;
struct tv_regs tv_regs;
bool continous_running;
};
static struct mcde_chnl_state channels[] = {
{
.id = MCDE_CHNL_A,
.ovly0 = &overlays[0],
.ovly1 = &overlays[1],
},
{
.id = MCDE_CHNL_B,
.ovly0 = &overlays[2],
.ovly1 = &overlays[3],
},
{
.id = MCDE_CHNL_C0,
.ovly0 = &overlays[4],
.ovly1 = NULL,
},
{
.id = MCDE_CHNL_C1,
.ovly0 = &overlays[5],
.ovly1 = NULL,
}
};
struct chnl_config {
/* Key */
enum mcde_chnl_path path;
/* Value */
bool swap_a_c0;
bool swap_a_c0_set;
bool swap_b_c1;
bool swap_b_c1_set;
bool fabmux;
bool fabmux_set;
bool f01mux;
bool f01mux_set;
};
static /* TODO: const, compiler bug? */ struct chnl_config chnl_configs[] = {
/* Channel A */
{ .path = MCDE_CHNLPATH_CHNLA_FIFOA_DPI_0,
.swap_a_c0 = false, .swap_a_c0_set = true },
{ .path = MCDE_CHNLPATH_CHNLA_FIFOA_DSI_IFC0_0,
.swap_a_c0 = false, .swap_a_c0_set = true,
.fabmux = false, .fabmux_set = true },
{ .path = MCDE_CHNLPATH_CHNLA_FIFOA_DSI_IFC0_1,
.swap_a_c0 = false, .swap_a_c0_set = true,
.fabmux = true, .fabmux_set = true },
{ .path = MCDE_CHNLPATH_CHNLA_FIFOC0_DSI_IFC0_2,
.swap_a_c0 = true, .swap_a_c0_set = true,
.f01mux = false, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLA_FIFOC0_DSI_IFC1_0,
.swap_a_c0 = true, .swap_a_c0_set = true,
.f01mux = false, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLA_FIFOC0_DSI_IFC1_1,
.swap_a_c0 = true, .swap_a_c0_set = true,
.f01mux = true, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLA_FIFOA_DSI_IFC1_2,
.swap_a_c0 = false, .swap_a_c0_set = true,
.fabmux = false, .fabmux_set = true },
/* Channel B */
{ .path = MCDE_CHNLPATH_CHNLB_FIFOB_DPI_1,
.swap_b_c1 = false, .swap_b_c1_set = true },
{ .path = MCDE_CHNLPATH_CHNLB_FIFOB_DSI_IFC0_0,
.swap_b_c1 = false, .swap_b_c1_set = true,
.fabmux = true, .fabmux_set = true },
{ .path = MCDE_CHNLPATH_CHNLB_FIFOB_DSI_IFC0_1,
.swap_b_c1 = false, .swap_b_c1_set = true,
.fabmux = false, .fabmux_set = true },
{ .path = MCDE_CHNLPATH_CHNLB_FIFOC1_DSI_IFC0_2,
.swap_b_c1 = true, .swap_b_c1_set = true,
.f01mux = true, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLB_FIFOC1_DSI_IFC1_0,
.swap_b_c1 = true, .swap_b_c1_set = true,
.f01mux = true, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLB_FIFOC1_DSI_IFC1_1,
.swap_b_c1 = true, .swap_b_c1_set = true,
.f01mux = false, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLB_FIFOB_DSI_IFC1_2,
.swap_b_c1 = false, .swap_b_c1_set = true,
.fabmux = true, .fabmux_set = true },
/* Channel C0 */
{ .path = MCDE_CHNLPATH_CHNLC0_FIFOA_DSI_IFC0_0,
.swap_a_c0 = true, .swap_a_c0_set = true,
.fabmux = false, .fabmux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC0_FIFOA_DSI_IFC0_1,
.swap_a_c0 = true, .swap_a_c0_set = true,
.fabmux = true, .fabmux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC0_FIFOC0_DSI_IFC0_2,
.swap_a_c0 = false, .swap_a_c0_set = true,
.f01mux = false, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC0_FIFOC0_DSI_IFC1_0,
.swap_a_c0 = false, .swap_a_c0_set = true,
.f01mux = false, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC0_FIFOC0_DSI_IFC1_1,
.swap_a_c0 = false, .swap_a_c0_set = true,
.f01mux = true, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC0_FIFOA_DSI_IFC1_2,
.swap_a_c0 = true, .swap_a_c0_set = true,
.fabmux = false, .fabmux_set = true },
/* Channel C1 */
{ .path = MCDE_CHNLPATH_CHNLC1_FIFOB_DSI_IFC0_0,
.swap_b_c1 = true, .swap_b_c1_set = true,
.fabmux = true, .fabmux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC1_FIFOB_DSI_IFC0_1,
.swap_b_c1 = true, .swap_b_c1_set = true,
.fabmux = false, .fabmux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC1_FIFOC1_DSI_IFC0_2,
.swap_b_c1 = false, .swap_b_c1_set = true,
.f01mux = true, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC1_FIFOC1_DSI_IFC1_0,
.swap_b_c1 = false, .swap_b_c1_set = true,
.f01mux = true, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC1_FIFOC1_DSI_IFC1_1,
.swap_b_c1 = false, .swap_b_c1_set = true,
.f01mux = false, .f01mux_set = true },
{ .path = MCDE_CHNLPATH_CHNLC1_FIFOB_DSI_IFC1_2,
.swap_b_c1 = true, .swap_b_c1_set = true,
.fabmux = true, .fabmux_set = true },
};
/* TODO: give these a place? *//* REVIEW: Remove, move to top move to use? */
#define MCDE_CONFIG_TVOUT_HBORDER 2
#define MCDE_CONFIG_TVOUT_VBORDER 2
int mcde_chnl_set_video_mode(struct mcde_chnl_state *chnl,
struct mcde_video_mode *vmode)
{
if (chnl == NULL || vmode == NULL)
return -EINVAL;
disable_channel(chnl);
chnl->vmode = *vmode;
return 0;
}
static void tv_video_mode_apply(struct mcde_chnl_state *chnl)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
/* -4 since MCDE doesn't include SAV/EAV, 2 bytes each, to blanking */
chnl->tv_regs.hbw = chnl->vmode.hbp + chnl->vmode.hfp - 4;
chnl->tv_regs.bel1 = chnl->vmode.vbp1 + chnl->vmode.vfp1;
chnl->tv_regs.fsl1 = chnl->vmode.vbp1;
chnl->tv_regs.bel2 = chnl->vmode.vbp2 + chnl->vmode.vfp2;
chnl->tv_regs.fsl2 = chnl->vmode.vbp2;
chnl->tv_regs.interlaced_en = chnl->vmode.interlaced;
if (chnl->port.phy.dpi.num_data_lanes == 4)
chnl->tv_regs.tv_mode = MCDE_TVCRA_TVMODE_SDTV_656P_BE;
else
chnl->tv_regs.tv_mode = MCDE_TVCRA_TVMODE_SDTV_656P;
}
static void update_tv_registers(enum mcde_chnl chnl_id, struct tv_regs *regs)
{
u8 idx = chnl_id;
u8 maj_version;
dev_dbg(&mcde_dev->dev, "%s\n", __func__);
mcde_wreg(MCDE_TVCRA + idx * MCDE_TVCRA_GROUPOFFSET,
MCDE_TVCRA_SEL_MOD(MCDE_TVCRA_SEL_MOD_TV) |
MCDE_TVCRA_INTEREN(regs->interlaced_en) |
MCDE_TVCRA_IFIELD(1) |
MCDE_TVCRA_TVMODE(regs->tv_mode) |
MCDE_TVCRA_SDTVMODE(MCDE_TVCRA_SDTVMODE_Y0CBY1CR) |
MCDE_TVCRA_AVRGEN(0));
/* REVIEW: Magic values */
mcde_wreg(MCDE_TVBLUA + idx * MCDE_TVBLUA_GROUPOFFSET,
MCDE_TVBLUA_TVBLU(0x83) |
MCDE_TVBLUA_TVBCB(0x9c) |
MCDE_TVBLUA_TVBCR(0x2c));
/* Vertical timing registers */
mcde_wreg(MCDE_TVDVOA + idx * MCDE_TVDVOA_GROUPOFFSET,
MCDE_TVDVOA_DVO1(MCDE_CONFIG_TVOUT_VBORDER) |
MCDE_TVDVOA_DVO2(MCDE_CONFIG_TVOUT_VBORDER));
mcde_wreg(MCDE_TVBL1A + idx * MCDE_TVBL1A_GROUPOFFSET,
MCDE_TVBL1A_BEL1(regs->bel1) |
MCDE_TVBL1A_BSL1(MCDE_CONFIG_TVOUT_VBORDER));
mcde_wreg(MCDE_TVBL2A + idx * MCDE_TVBL1A_GROUPOFFSET,
MCDE_TVBL2A_BEL2(regs->bel2) |
MCDE_TVBL2A_BSL2(MCDE_CONFIG_TVOUT_VBORDER));
mcde_wreg(MCDE_TVISLA + idx * MCDE_TVISLA_GROUPOFFSET,
MCDE_TVISLA_FSL1(regs->fsl1) |
MCDE_TVISLA_FSL2(regs->fsl2));
/* Horizontal timing registers */
maj_version = MCDE_REG2VAL(/* REVIEW: Make global and do on init? */
MCDE_PID, MAJOR_VERSION, mcde_rreg(MCDE_PID));
if (maj_version > 3) {
mcde_wreg(MCDE_TVLBALWA + idx * MCDE_TVLBALWA_GROUPOFFSET,
MCDE_TVLBALWA_LBW(regs->hbw) |
MCDE_TVLBALWA_ALW(MCDE_CONFIG_TVOUT_HBORDER));
mcde_wreg(MCDE_TVTIM1A + idx * MCDE_TVTIM1A_GROUPOFFSET,
MCDE_TVTIM1A_DHO(MCDE_CONFIG_TVOUT_HBORDER));
} else {
/* in earlier versions the LBW and DHO fields are swapped */
mcde_wreg(MCDE_TVLBALWA + idx * MCDE_TVLBALWA_GROUPOFFSET,
MCDE_TVLBALWA_LBW(MCDE_CONFIG_TVOUT_HBORDER) |
MCDE_TVLBALWA_ALW(MCDE_CONFIG_TVOUT_HBORDER));
mcde_wreg(MCDE_TVTIM1A + idx * MCDE_TVTIM1A_GROUPOFFSET,
MCDE_TVTIM1A_DHO(regs->hbw));
}
}
static void update_col_registers(enum mcde_chnl chnl_id, struct col_regs *regs)
{
u8 idx = chnl_id;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
mcde_wreg(MCDE_RGBCONV1A + idx * MCDE_RGBCONV1A_GROUPOFFSET,
MCDE_RGBCONV1A_YR_RED(regs->y_red) |
MCDE_RGBCONV1A_YR_GREEN(regs->y_green));
mcde_wreg(MCDE_RGBCONV2A + idx * MCDE_RGBCONV2A_GROUPOFFSET,
MCDE_RGBCONV2A_YR_BLUE(regs->y_blue) |
MCDE_RGBCONV2A_CR_RED(regs->cr_red));
mcde_wreg(MCDE_RGBCONV3A + idx * MCDE_RGBCONV3A_GROUPOFFSET,
MCDE_RGBCONV3A_CR_GREEN(regs->cr_green) |
MCDE_RGBCONV3A_CR_BLUE(regs->cr_blue));
mcde_wreg(MCDE_RGBCONV4A + idx * MCDE_RGBCONV4A_GROUPOFFSET,
MCDE_RGBCONV4A_CB_RED(regs->cb_red) |
MCDE_RGBCONV4A_CB_GREEN(regs->cb_green));
mcde_wreg(MCDE_RGBCONV5A + idx * MCDE_RGBCONV5A_GROUPOFFSET,
MCDE_RGBCONV5A_CB_BLUE(regs->cb_blue) |
MCDE_RGBCONV5A_OFF_RED(regs->off_red));
mcde_wreg(MCDE_RGBCONV6A + idx * MCDE_RGBCONV6A_GROUPOFFSET,
MCDE_RGBCONV6A_OFF_GREEN(regs->off_green) |
MCDE_RGBCONV6A_OFF_BLUE(regs->off_blue));
}
/* MCDE internal helpers */
static u8 portfmt2dsipacking(enum mcde_port_pix_fmt pix_fmt)
{
switch (pix_fmt) {
case MCDE_PORTPIXFMT_DSI_16BPP:
return MCDE_DSIVID0CONF0_PACKING_RGB565;
case MCDE_PORTPIXFMT_DSI_18BPP_PACKED:
return MCDE_DSIVID0CONF0_PACKING_RGB666;
case MCDE_PORTPIXFMT_DSI_18BPP:
case MCDE_PORTPIXFMT_DSI_24BPP:
default:
return MCDE_DSIVID0CONF0_PACKING_RGB888;
case MCDE_PORTPIXFMT_DSI_YCBCR422:
return MCDE_DSIVID0CONF0_PACKING_HDTV;
}
}
static u8 portfmt2bpp(enum mcde_port_pix_fmt pix_fmt)
{
/* TODO: Check DPI spec *//* REVIEW: Remove or check */
switch (pix_fmt) {
case MCDE_PORTPIXFMT_DPI_16BPP_C1:
case MCDE_PORTPIXFMT_DPI_16BPP_C2:
case MCDE_PORTPIXFMT_DPI_16BPP_C3:
case MCDE_PORTPIXFMT_DSI_16BPP:
case MCDE_PORTPIXFMT_DSI_YCBCR422:
return 16;
case MCDE_PORTPIXFMT_DPI_18BPP_C1:
case MCDE_PORTPIXFMT_DPI_18BPP_C2:
case MCDE_PORTPIXFMT_DSI_18BPP_PACKED:
return 18;
case MCDE_PORTPIXFMT_DSI_18BPP:
case MCDE_PORTPIXFMT_DPI_24BPP:
case MCDE_PORTPIXFMT_DSI_24BPP:
return 24;
default:
return 0;/* REVIEW: 1, to reduce ris of div by zero */
}
}
static u32 get_output_fifo_size(enum mcde_fifo fifo)
{
u32 ret = 1; /* Avoid div by zero */
switch (fifo) {
case MCDE_FIFO_A:
case MCDE_FIFO_B:
ret = MCDE_FIFO_AB_SIZE;
break;
case MCDE_FIFO_C0:
case MCDE_FIFO_C1:
ret = MCDE_FIFO_C0C1_SIZE;
break;
default:
dev_vdbg(&mcde_dev->dev, "Unsupported fifo");
break;
}
return ret;
}
static struct mcde_chnl_state *find_channel_by_dsilink(int link)
{
struct mcde_chnl_state *chnl = &channels[0];
for (; chnl < &channels[ARRAY_SIZE(channels)]; chnl++)
if (chnl->inuse && chnl->port.link == link &&
chnl->port.type == MCDE_PORTTYPE_DSI)
return chnl;
return NULL;
}
static irqreturn_t mcde_irq_handler(int irq, void *dev)
{
int i;
u32 irq_status;
/* Handle overlay irqs */
irq_status = mcde_rfld(MCDE_RISOVL, OVLFDRIS);
for (i = 0; i < ARRAY_SIZE(overlays); i++) {
if (irq_status & (1 << i)) {
struct mcde_ovly_state *ovly = &overlays[i];
ovly->transactionid_hw = ovly->transactionid_regs;
wake_up(&ovly->waitq_hw);
}
}
mcde_wfld(MCDE_RISOVL, OVLFDRIS, irq_status);
/* Handle channel irqs */
irq_status = mcde_rreg(MCDE_RISPP);
if (irq_status & MCDE_RISPP_VCMPARIS_MASK) {
struct mcde_chnl_state *chnl = &channels[MCDE_CHNL_A];
chnl->transactionid_hw = chnl->transactionid_regs;
wake_up(&chnl->waitq_hw);
mcde_wfld(MCDE_RISPP, VCMPARIS, 1);
}
if (irq_status & MCDE_RISPP_VCMPBRIS_MASK) {
struct mcde_chnl_state *chnl = &channels[MCDE_CHNL_B];
chnl->transactionid_hw = chnl->transactionid_regs;
wake_up(&chnl->waitq_hw);
mcde_wfld(MCDE_RISPP, VCMPBRIS, 1);
}
if (irq_status & MCDE_RISPP_VCMPC0RIS_MASK) {
struct mcde_chnl_state *chnl = &channels[MCDE_CHNL_C0];
chnl->transactionid_hw = chnl->transactionid_regs;
wake_up(&chnl->waitq_hw);
mcde_wfld(MCDE_RISPP, VCMPC0RIS, 1);
}
if (irq_status & MCDE_RISPP_VCMPC1RIS_MASK) {
struct mcde_chnl_state *chnl = &channels[MCDE_CHNL_C1];
chnl->transactionid_hw = chnl->transactionid_regs;
wake_up(&chnl->waitq_hw);
mcde_wfld(MCDE_RISPP, VCMPC1RIS, 1);
}
for (i = 0; i < num_dsilinks; i++) {
bool trig = false;
struct mcde_chnl_state *chnl;
irq_status = dsi_rfld(i, DSI_DIRECT_CMD_STS_FLAG,
TE_RECEIVED_FLAG);
if (irq_status) {
trig = true;
dsi_wreg(i, DSI_DIRECT_CMD_STS_CLR,
DSI_DIRECT_CMD_STS_CLR_TE_RECEIVED_CLR(true));
dev_vdbg(&mcde_dev->dev, "BTA TE DSI%d\n", i);
}
irq_status = dsi_rfld(i, DSI_CMD_MODE_STS_FLAG, ERR_NO_TE_FLAG);
if (irq_status) {
dsi_wreg(i, DSI_CMD_MODE_STS_CLR,
DSI_CMD_MODE_STS_CLR_ERR_NO_TE_CLR(true));
dev_info(&mcde_dev->dev, "NO_TE DSI%d\n", i);
}
if (!trig)
continue;
chnl = find_channel_by_dsilink(i);
if (chnl) {
mcde_wreg(MCDE_CHNL0SYNCHSW +
chnl->id * MCDE_CHNL0SYNCHSW_GROUPOFFSET,
MCDE_CHNL0SYNCHSW_SW_TRIG(true));
dev_vdbg(&mcde_dev->dev, "SW TRIG DSI%d, chnl=%d\n", i,
chnl->id);
}
}
return IRQ_HANDLED;
}
void wait_for_overlay(struct mcde_ovly_state *ovly)
{
int ret;
ret = wait_event_timeout(ovly->waitq_hw,
ovly->transactionid_hw == ovly->transactionid_regs,
OVLY_TIMEOUT);
if (!ret)
dev_warn(&mcde_dev->dev,
"Wait for overlay timeout (ovly=%d,%d<%d)!\n",
ovly->idx, ovly->transactionid_hw,
ovly->transactionid_regs);
}
void wait_for_channel(struct mcde_chnl_state *chnl)
{
int ret;
ret = wait_event_timeout(chnl->waitq_hw,
chnl->transactionid_hw == chnl->transactionid_regs,
CHNL_TIMEOUT);
if (!ret)
dev_warn(&mcde_dev->dev,
"Wait for channel timeout (chnl=%d,%d<%d)!\n",
chnl->id, chnl->transactionid_hw,
chnl->transactionid_regs);
}
void update_channel_static_registers(struct mcde_chnl_state *chnl)
{
const struct chnl_config *cfg = chnl->cfg;
const struct mcde_port *port = &chnl->port;
/* Fifo & muxing */
if (cfg->swap_a_c0_set)
mcde_wfld(MCDE_CONF0, SWAP_A_C0, cfg->swap_a_c0);
if (cfg->swap_b_c1_set)
mcde_wfld(MCDE_CONF0, SWAP_B_C1, cfg->swap_b_c1);
if (cfg->fabmux_set)
mcde_wfld(MCDE_CR, FABMUX, cfg->fabmux);
if (cfg->f01mux_set)
mcde_wfld(MCDE_CR, F01MUX, cfg->f01mux);
/* Formatter */
if (port->type == MCDE_PORTTYPE_DSI) {
int i = 0;
u8 idx = 2 * port->link + port->ifc;
u8 lnk = port->link;
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, LINK_EN, true);
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, BTA_EN, true);
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, READ_EN, true);
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, REG_TE_EN, true);
dsi_wreg(lnk, DSI_MCTL_DPHY_STATIC,
DSI_MCTL_DPHY_STATIC_UI_X4(port->phy.dsi.ui));
dsi_wreg(lnk, DSI_DPHY_LANES_TRIM,
DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_ENUM(0_90));
dsi_wreg(lnk, DSI_MCTL_DPHY_TIMEOUT,
DSI_MCTL_DPHY_TIMEOUT_CLK_DIV(0xf) |
DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL(0x3fff) |
DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL(0x3fff));
dsi_wreg(lnk, DSI_MCTL_MAIN_PHY_CTL,
DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME(0xf) |
DSI_MCTL_MAIN_PHY_CTL_LANE2_EN(true) |
DSI_MCTL_MAIN_PHY_CTL_CLK_CONTINUOUS(
port->phy.dsi.clk_cont));
dsi_wreg(lnk, DSI_MCTL_ULPOUT_TIME,
DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME(1) |
DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME(1));
/* TODO: make enum */
dsi_wfld(lnk, DSI_CMD_MODE_CTL, ARB_MODE, false);
/* TODO: make enum */
dsi_wfld(lnk, DSI_CMD_MODE_CTL, ARB_PRI, port->ifc == 1);
dsi_wreg(lnk, DSI_MCTL_MAIN_EN,
DSI_MCTL_MAIN_EN_PLL_START(true) |
DSI_MCTL_MAIN_EN_CKLANE_EN(true) |
DSI_MCTL_MAIN_EN_DAT1_EN(true) |
DSI_MCTL_MAIN_EN_DAT2_EN(port->phy.dsi.num_data_lanes
== 2) |
DSI_MCTL_MAIN_EN_IF1_EN(port->ifc == 0) |
DSI_MCTL_MAIN_EN_IF2_EN(port->ifc == 1));
while (dsi_rfld(lnk, DSI_MCTL_MAIN_STS, CLKLANE_READY) == 0 ||
dsi_rfld(lnk, DSI_MCTL_MAIN_STS, DAT1_READY) == 0 ||
dsi_rfld(lnk, DSI_MCTL_MAIN_STS, DAT2_READY) == 0) {
mdelay(1);
if (i++ == 10)
dev_warn(&mcde_dev->dev,
"DSI lane not ready (link=%d)!\n", lnk);
}
if (port->ifc == 0 && port->link == 0)
mcde_wfld(MCDE_CR, DSIVID0_EN, true);
else if (port->ifc == 0 && port->link == 1)
mcde_wfld(MCDE_CR, DSIVID1_EN, true);
else if (port->ifc == 0 && port->link == 2)
mcde_wfld(MCDE_CR, DSIVID2_EN, true);
else if (port->ifc == 1 && port->link == 0)
mcde_wfld(MCDE_CR, DSICMD0_EN, true);
else if (port->ifc == 1 && port->link == 1)
mcde_wfld(MCDE_CR, DSICMD1_EN, true);
else if (port->ifc == 1 && port->link == 2)
mcde_wfld(MCDE_CR, DSICMD2_EN, true);
mcde_wreg(MCDE_DSIVID0CONF0 +
idx * MCDE_DSIVID0CONF0_GROUPOFFSET,
MCDE_DSIVID0CONF0_BLANKING(0) |
MCDE_DSIVID0CONF0_VID_MODE(
port->mode == MCDE_PORTMODE_VID) |
MCDE_DSIVID0CONF0_CMD8(true) |
MCDE_DSIVID0CONF0_BIT_SWAP(false) |
MCDE_DSIVID0CONF0_BYTE_SWAP(false) |
MCDE_DSIVID0CONF0_DCSVID_NOTGEN(true));
if (port->mode == MCDE_PORTMODE_CMD) {
if (port->ifc == 0)
dsi_wfld(port->link, DSI_CMD_MODE_CTL, IF1_ID,
port->phy.dsi.virt_id);
else if (port->ifc == 1)
dsi_wfld(port->link, DSI_CMD_MODE_CTL, IF2_ID,
port->phy.dsi.virt_id);
}
} else if (port->type == MCDE_PORTTYPE_DPI) {
if (port->link == 0)
mcde_wfld(MCDE_CR, DPIA_EN, true);
else if (port->link == 1)
mcde_wfld(MCDE_CR, DPIB_EN, true);
}
/* REVIEW: Magic numbers, define! Calculate? */
if (chnl->fifo == MCDE_FIFO_C0)
mcde_wreg(MCDE_CTRLC0, MCDE_CTRLC0_FIFOWTRMRK(0xa0));
else if (chnl->fifo == MCDE_FIFO_C1)
mcde_wreg(MCDE_CTRLC1, MCDE_CTRLC1_FIFOWTRMRK(0xa0));
else if (port->update_auto_trig && (port->sync_src == MCDE_SYNCSRC_TE0))
mcde_wreg(MCDE_CTRLC0, MCDE_CTRLC0_FIFOWTRMRK(0xa0));
else if (port->update_auto_trig && (port->sync_src == MCDE_SYNCSRC_TE1))
mcde_wreg(MCDE_CTRLC1, MCDE_CTRLC1_FIFOWTRMRK(0xa0));
mcde_wfld(MCDE_CR, MCDEEN, true);
dev_vdbg(&mcde_dev->dev, "Static registers setup, chnl=%d\n", chnl->id);
}
/* REVIEW: Make update_* an mcde_rectangle? */
static void update_overlay_registers(u8 idx, struct ovly_regs *regs,
struct mcde_port *port, enum mcde_fifo fifo,
u16 update_x, u16 update_y, u16 update_w,
u16 update_h, u16 stride, bool interlaced)
{
/* TODO: fix clipping for small overlay */
u32 lmrgn = (regs->cropx + update_x) * regs->bits_per_pixel;
u32 tmrgn = (regs->cropy + update_y) * stride;
u32 ppl = regs->ppl - update_x;
u32 lpf = regs->lpf - update_y;
u32 ljinc = stride;
u32 pixelfetchwtrmrklevel;
u8 nr_of_bufs = 1;
u32 fifo_size;
/* TODO: disable if everything clipped */
if (!regs->enabled) {
u32 temp;
temp = mcde_rreg(MCDE_OVL0CR + idx * MCDE_OVL0CR_GROUPOFFSET);
mcde_wreg(MCDE_OVL0CR + idx * MCDE_OVL0CR_GROUPOFFSET,
(temp & ~MCDE_OVL0CR_OVLEN_MASK) |
MCDE_OVL0CR_OVLEN(false));
return;
}
/*
* TODO: Preferably most of this is done in some apply function instead
* of every update. Problem is however that at overlay apply
* there is no port type info available (and the question is
* whether it is appropriate to add a port type there).
* Note that lpf has a dependency on update_y.
*/
if (port->type == MCDE_PORTTYPE_DPI)
/* REVIEW: Why not for DSI? enable in regs? */
regs->col_conv = MCDE_OVL0CR_COLCCTRL_ENABLED_NO_SAT;
else if (port->type == MCDE_PORTTYPE_DSI) {
if (port->pixel_format == MCDE_PORTPIXFMT_DSI_YCBCR422)
regs->col_conv = MCDE_OVL0CR_COLCCTRL_ENABLED_NO_SAT;
else
regs->col_conv = MCDE_OVL0CR_COLCCTRL_DISABLED;
if (interlaced) {
nr_of_bufs = 2;
lpf = lpf / 2;
ljinc *= 2;
}
}
fifo_size = get_output_fifo_size(fifo);
#ifdef CONFIG_AV8100_SDTV
/* TODO: check if these watermark levels work for HDMI as well. */
pixelfetchwtrmrklevel = MCDE_PIXFETCH_SMALL_WTRMRKLVL;
#else
if ((fifo == MCDE_FIFO_A || fifo == MCDE_FIFO_B) &&
regs->ppl >= fifo_size * 2)
pixelfetchwtrmrklevel = MCDE_PIXFETCH_LARGE_WTRMRKLVL;
else
pixelfetchwtrmrklevel = MCDE_PIXFETCH_MEDIUM_WTRMRKLVL;
#endif /* CONFIG_AV8100_SDTV */
if (regs->reset_buf_id) {
regs->reset_buf_id = false;
mcde_wreg(MCDE_EXTSRC0CONF + idx * MCDE_EXTSRC0CONF_GROUPOFFSET,
MCDE_EXTSRC0CONF_BUF_ID(0) |
MCDE_EXTSRC0CONF_BUF_NB(nr_of_bufs) |
MCDE_EXTSRC0CONF_PRI_OVLID(idx) |
MCDE_EXTSRC0CONF_BPP(regs->bpp) |
MCDE_EXTSRC0CONF_BGR(regs->bgr) |
MCDE_EXTSRC0CONF_BEBO(regs->bebo) |
MCDE_EXTSRC0CONF_BEPO(false));
mcde_wreg(MCDE_EXTSRC0CR + idx * MCDE_EXTSRC0CR_GROUPOFFSET,
MCDE_EXTSRC0CR_SEL_MOD(port->update_auto_trig ?
MCDE_EXTSRC0CR_SEL_MOD_AUTO_TOGGLE :
MCDE_EXTSRC0CR_SEL_MOD_SOFTWARE_SEL) |
MCDE_EXTSRC0CR_MULTIOVL_CTRL_ENUM(PRIMARY) |
MCDE_EXTSRC0CR_FS_DIV_DISABLE(false) |
MCDE_EXTSRC0CR_FORCE_FS_DIV(false));
mcde_wreg(MCDE_OVL0CR + idx * MCDE_OVL0CR_GROUPOFFSET,
MCDE_OVL0CR_OVLEN(true) |
MCDE_OVL0CR_COLCCTRL(regs->col_conv) |
MCDE_OVL0CR_CKEYGEN(false) |
MCDE_OVL0CR_ALPHAPMEN(true) |
MCDE_OVL0CR_OVLF(false) |
MCDE_OVL0CR_OVLR(false) |
MCDE_OVL0CR_OVLB(false) |
MCDE_OVL0CR_FETCH_ROPC(0) |
MCDE_OVL0CR_STBPRIO(0) |
MCDE_OVL0CR_BURSTSIZE(11) | /* TODO: _HW_8W */
/* TODO: enum, get from ovly */
MCDE_OVL0CR_MAXOUTSTANDING(2) |
/* TODO: _HW_8W, calculate? */
MCDE_OVL0CR_ROTBURSTSIZE(11));
mcde_wreg(MCDE_OVL0CONF + idx * MCDE_OVL0CONF_GROUPOFFSET,
MCDE_OVL0CONF_PPL(ppl) |
MCDE_OVL0CONF_EXTSRC_ID(idx) |
MCDE_OVL0CONF_LPF(lpf));
mcde_wreg(MCDE_OVL0CONF2 + idx * MCDE_OVL0CONF2_GROUPOFFSET,
MCDE_OVL0CONF2_BP_ENUM(PER_PIXEL_ALPHA) |
/* TODO: Allow setting? */
MCDE_OVL0CONF2_ALPHAVALUE(0xff) |
MCDE_OVL0CONF2_OPQ(regs->opq) |
MCDE_OVL0CONF2_PIXOFF(lmrgn & 63) |
MCDE_OVL0CONF2_PIXELFETCHERWATERMARKLEVEL(
pixelfetchwtrmrklevel));
mcde_wreg(MCDE_OVL0LJINC + idx * MCDE_OVL0LJINC_GROUPOFFSET,
ljinc);
mcde_wreg(MCDE_OVL0CROP + idx * MCDE_OVL0CROP_GROUPOFFSET,
MCDE_OVL0CROP_TMRGN(tmrgn) |
MCDE_OVL0CROP_LMRGN(lmrgn >> 6));
mcde_wreg(MCDE_OVL0COMP + idx * MCDE_OVL0COMP_GROUPOFFSET,
MCDE_OVL0COMP_XPOS(regs->xpos) |
MCDE_OVL0COMP_CH_ID(regs->ch_id) |
MCDE_OVL0COMP_YPOS(regs->ypos) |
MCDE_OVL0COMP_Z(regs->z));
}
dev_vdbg(&mcde_dev->dev, "Overlay registers setup, idx=%d\n", idx);
}
static void update_overlay_address_registers(u8 idx, struct ovly_regs *regs)
{
mcde_wreg(MCDE_EXTSRC0A0 + idx * MCDE_EXTSRC0A0_GROUPOFFSET,
regs->baseaddress0);
mcde_wreg(MCDE_EXTSRC0A1 + idx * MCDE_EXTSRC0A1_GROUPOFFSET,
regs->baseaddress1);
}
#define MCDE_FLOWEN_MAX_TRIAL 6
static void disable_channel(struct mcde_chnl_state *chnl)
{
int i;
const struct mcde_port *port = &chnl->port;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (port->type == MCDE_PORTTYPE_DSI)
dsi_wfld(port->link, DSI_MCTL_MAIN_PHY_CTL, CLK_CONTINUOUS,
false);
switch (chnl->id) {
case MCDE_CHNL_A:
mcde_wfld(MCDE_CRA0, FLOEN, false);
wait_for_channel(chnl);
for (i = 0; i < MCDE_FLOWEN_MAX_TRIAL; i++) {
msleep(1);
if (!mcde_rfld(MCDE_CRA0, FLOEN)) {
dev_vdbg(&mcde_dev->dev,
"Flow (A) disable after >= %d ms\n", i);
goto break_switch;
}
}
dev_warn(&mcde_dev->dev, "%s: channel A timeout\n", __func__);
break;
case MCDE_CHNL_B:
mcde_wfld(MCDE_CRB0, FLOEN, false);
wait_for_channel(chnl);
for (i = 0; i < MCDE_FLOWEN_MAX_TRIAL; i++) {
msleep(1);
if (!mcde_rfld(MCDE_CRB0, FLOEN)) {
dev_vdbg(&mcde_dev->dev,
"Flow (B) disable after >= %d ms\n", i);
goto break_switch;
}
}
dev_warn(&mcde_dev->dev, "%s: channel B timeout\n", __func__);
break;
case MCDE_CHNL_C0:
mcde_wfld(MCDE_CRC, C1EN, false);
if (!mcde_rfld(MCDE_CRC, C2EN))
mcde_wfld(MCDE_CRC, FLOEN, false);
wait_for_channel(chnl);
break;
case MCDE_CHNL_C1:
mcde_wfld(MCDE_CRC, C2EN, false);
if (!mcde_rfld(MCDE_CRC, C1EN))
mcde_wfld(MCDE_CRC, FLOEN, false);
wait_for_channel(chnl);
break;
}
break_switch:
chnl->continous_running = false;
#undef MCDE_FLOWEN_MAX_TRIAL
}
static void enable_channel(struct mcde_chnl_state *chnl)
{
const struct mcde_port *port = &chnl->port;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (port->type == MCDE_PORTTYPE_DSI)
dsi_wfld(port->link, DSI_MCTL_MAIN_PHY_CTL, CLK_CONTINUOUS,
port->phy.dsi.clk_cont);
switch (chnl->id) {
case MCDE_CHNL_A:
mcde_wfld(MCDE_CRA0, FLOEN, true);
break;
case MCDE_CHNL_B:
mcde_wfld(MCDE_CRB0, FLOEN, true);
break;
case MCDE_CHNL_C0:
mcde_wfld(MCDE_CRC, POWEREN, true);
mcde_wfld(MCDE_CRC, FLOEN, true);
mcde_wfld(MCDE_CRC, C1EN, true);
break;
case MCDE_CHNL_C1:
mcde_wfld(MCDE_CRC, POWEREN, true);
mcde_wfld(MCDE_CRC, FLOEN, true);
mcde_wfld(MCDE_CRC, C2EN, true);
break;
}
}
/* TODO get from register */
#define MCDE_CLK_FREQ_MHZ 160
void update_channel_registers(enum mcde_chnl chnl_id, struct chnl_regs *regs,
struct mcde_port *port, enum mcde_fifo fifo,
struct mcde_video_mode *video_mode)
{
u8 idx = chnl_id;
u32 out_synch_src = MCDE_CHNL0SYNCHMOD_OUT_SYNCH_SRC_FORMATTER;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
/* Channel */
if (port->update_auto_trig && port->type != MCDE_PORTTYPE_DPI) {
out_synch_src = port->sync_src == MCDE_SYNCSRC_TE0 ?
MCDE_CHNL0SYNCHMOD_OUT_SYNCH_SRC_VSYNC0 :
MCDE_CHNL0SYNCHMOD_OUT_SYNCH_SRC_VSYNC1;
}
mcde_wreg(MCDE_CHNL0CONF + idx * MCDE_CHNL0CONF_GROUPOFFSET,
MCDE_CHNL0CONF_PPL(regs->ppl-1) |
MCDE_CHNL0CONF_LPF(regs->lpf-1));
mcde_wreg(MCDE_CHNL0STAT + idx * MCDE_CHNL0STAT_GROUPOFFSET,
MCDE_CHNL0STAT_CHNLBLBCKGND_EN(false) |
MCDE_CHNL0STAT_CHNLRD(true));
mcde_wreg(MCDE_CHNL0SYNCHMOD +
idx * MCDE_CHNL0SYNCHMOD_GROUPOFFSET,
MCDE_CHNL0SYNCHMOD_SRC_SYNCH(port->update_auto_trig ?
MCDE_CHNL0SYNCHMOD_SRC_SYNCH_OUTPUT :
MCDE_CHNL0SYNCHMOD_SRC_SYNCH_SOFTWARE) |
MCDE_CHNL0SYNCHMOD_OUT_SYNCH_SRC(out_synch_src));
mcde_wreg(MCDE_CHNL0BCKGNDCOL + idx * MCDE_CHNL0BCKGNDCOL_GROUPOFFSET,
MCDE_CHNL0BCKGNDCOL_B(255) | /* TODO: Temp */
MCDE_CHNL0BCKGNDCOL_G(0) |
MCDE_CHNL0BCKGNDCOL_R(0));
mcde_wreg(MCDE_CHNL0PRIO + idx * MCDE_CHNL0PRIO_GROUPOFFSET,
MCDE_CHNL0PRIO_CHNLPRIO(0));
switch (chnl_id) {
case MCDE_CHNL_A:
if (port->type == MCDE_PORTTYPE_DPI) {
mcde_wreg(MCDE_CRA1,
MCDE_CRA1_CLKSEL_ENUM(EXT_TV1) |
MCDE_CRA1_OUTBPP_ENUM(24BPP) |
MCDE_CRA1_BCD(1)
);
} else {
mcde_wreg(MCDE_CRA1, MCDE_CRA1_CLKSEL_ENUM(166MHZ));
}
break;
case MCDE_CHNL_B:
if (port->type == MCDE_PORTTYPE_DPI) {
mcde_wreg(MCDE_CRB1,
MCDE_CRB1_CLKSEL_ENUM(EXT_TV2) |
MCDE_CRB1_OUTBPP_ENUM(24BPP) |
MCDE_CRB1_BCD(1)
);
} else {
mcde_wreg(MCDE_CRB1, MCDE_CRB1_CLKSEL_ENUM(166MHZ));
}
break;
default:
break;
}
/* Formatter */
if (port->type == MCDE_PORTTYPE_DSI) {
u8 fidx = 2 * port->link + port->ifc;
u32 temp, packet;
/* pkt_div is used to avoid underflow in output fifo for
* large packets */
u32 pkt_div = 1;
u32 dsi_delay0 = 0;
u32 screen_ppl, screen_lpf;
screen_ppl = video_mode->xres;
screen_lpf = video_mode->yres;
pkt_div = screen_ppl / (get_output_fifo_size(fifo) * 2) + 1;
if (video_mode->interlaced)
screen_lpf /= 2;
/* pkt_delay_progressive = pixelclock * htot /
* (1E12 / 160E6) / pkt_div */
dsi_delay0 = (video_mode->pixclock + 1) *
(video_mode->xres + video_mode->hbp +
video_mode->hfp) /
(1000000 / MCDE_CLK_FREQ_MHZ) / pkt_div;
temp = mcde_rreg(MCDE_DSIVID0CONF0 +
fidx * MCDE_DSIVID0CONF0_GROUPOFFSET);
mcde_wreg(MCDE_DSIVID0CONF0 +
fidx * MCDE_DSIVID0CONF0_GROUPOFFSET,
(temp & ~MCDE_DSIVID0CONF0_PACKING_MASK) |
MCDE_DSIVID0CONF0_PACKING(regs->dsipacking));
/* 1==CMD8 */
packet = ((screen_ppl / pkt_div * regs->bpp) >> 3) + 1;
mcde_wreg(MCDE_DSIVID0FRAME +
fidx * MCDE_DSIVID0FRAME_GROUPOFFSET,
MCDE_DSIVID0FRAME_FRAME(packet * pkt_div * screen_lpf));
mcde_wreg(MCDE_DSIVID0PKT + fidx * MCDE_DSIVID0PKT_GROUPOFFSET,
MCDE_DSIVID0PKT_PACKET(packet));
mcde_wreg(MCDE_DSIVID0SYNC +
fidx * MCDE_DSIVID0SYNC_GROUPOFFSET,
MCDE_DSIVID0SYNC_SW(0) |
MCDE_DSIVID0SYNC_DMA(0));
mcde_wreg(MCDE_DSIVID0CMDW +
fidx * MCDE_DSIVID0CMDW_GROUPOFFSET,
MCDE_DSIVID0CMDW_CMDW_START(DCS_CMD_WRITE_START) |
MCDE_DSIVID0CMDW_CMDW_CONTINUE(DCS_CMD_WRITE_CONTINUE));
mcde_wreg(MCDE_DSIVID0DELAY0 +
fidx * MCDE_DSIVID0DELAY0_GROUPOFFSET,
MCDE_DSIVID0DELAY0_INTPKTDEL(dsi_delay0));
mcde_wreg(MCDE_DSIVID0DELAY1 +
fidx * MCDE_DSIVID0DELAY1_GROUPOFFSET,
MCDE_DSIVID0DELAY1_TEREQDEL(0) |
MCDE_DSIVID0DELAY1_FRAMESTARTDEL(0));
}
if (regs->roten) {
/* TODO: Allocate memory in ESRAM instead of
static allocations. */
mcde_wreg(MCDE_ROTADD0A + chnl_id * MCDE_ROTADD0A_GROUPOFFSET,
MCDE_ROTADD0A_ROTADD0(regs->rotbuf1));
mcde_wreg(MCDE_ROTADD1A + chnl_id * MCDE_ROTADD1A_GROUPOFFSET,
MCDE_ROTADD1A_ROTADD1(regs->rotbuf2));
mcde_wreg(MCDE_ROTACONF + chnl_id * MCDE_ROTACONF_GROUPOFFSET,
MCDE_ROTACONF_ROTBURSTSIZE(
MCDE_ROTACONF_ROTBURSTSIZE_8W) |
MCDE_ROTACONF_ROTBURSTSIZE_HW(1) |
MCDE_ROTACONF_ROTDIR(regs->rotdir) |
MCDE_ROTACONF_STRIP_WIDTH_ENUM(16PIX) |
/* TODO: MAXOUT_ENUM */
MCDE_ROTACONF_RD_MAXOUT(2) |
MCDE_ROTACONF_WR_MAXOUT(3));
if (chnl_id == MCDE_CHNL_A) {
mcde_wfld(MCDE_CRA0, ROTEN, true);
mcde_wfld(MCDE_CRA0, ROTBURSTSIZE,
MCDE_CRA0_ROTBURSTSIZE_8W);
mcde_wfld(MCDE_CRA0, ROTBURSTSIZE_HW, true);
mcde_wfld(MCDE_CRA1, CLKSEL, MCDE_CRA1_CLKSEL_166MHZ);
/* TODO: Fetch port output bpp */
mcde_wfld(MCDE_CRA1, OUTBPP, MCDE_CRA1_OUTBPP_24BPP);
mcde_wfld(MCDE_CRA1, BCD, true);
} else if (chnl_id == MCDE_CHNL_B) {
mcde_wfld(MCDE_CRB0, ROTEN, true);
mcde_wfld(MCDE_CRB0, ROTBURSTSIZE,
MCDE_CRB0_ROTBURSTSIZE_8W);
mcde_wfld(MCDE_CRB0, ROTBURSTSIZE_HW, true);
mcde_wfld(MCDE_CRB1, CLKSEL, MCDE_CRB1_CLKSEL_166MHZ);
/* TODO: Fetch port output bpp */
mcde_wfld(MCDE_CRB1, OUTBPP, MCDE_CRB1_OUTBPP_24BPP);
mcde_wfld(MCDE_CRB1, BCD, true);
}
} else {
if (chnl_id == MCDE_CHNL_A)
mcde_wfld(MCDE_CRA0, ROTEN, false);
else if (chnl_id == MCDE_CHNL_B)
mcde_wfld(MCDE_CRB0, ROTEN, false);
}
dev_vdbg(&mcde_dev->dev, "Channel registers setup, chnl=%d\n", chnl_id);
}
/* DSI */
int mcde_dsi_dcs_write(struct mcde_chnl_state *chnl, u8 cmd, u8* data, int len)
{
int i;
u32 wrdat[4] = { 0, 0, 0, 0 };
u32 settings;
u8 link = chnl->port.link;
u8 virt_id = chnl->port.phy.dsi.virt_id;
/* REVIEW: One command at a time */
/* REVIEW: Allow read/write on unreserved ports */
if (len > MCDE_MAX_DCS_WRITE || chnl->port.type != MCDE_PORTTYPE_DSI)
return -EINVAL;
wrdat[0] = cmd;
for (i = 1; i <= len; i++)
wrdat[i>>2] |= ((u32)data[i-1] << ((i & 3) * 8));
settings = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_ENUM(WRITE) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT(len > 1) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID(virt_id) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE(len+1) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN(true);
if (len == 0)
settings |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_ENUM(
DCS_SHORT_WRITE_0);
else if (len == 1)
settings |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_ENUM(
DCS_SHORT_WRITE_1);
else
settings |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_ENUM(
DCS_LONG_WRITE);
dsi_wreg(link, DSI_DIRECT_CMD_MAIN_SETTINGS, settings);
dsi_wreg(link, DSI_DIRECT_CMD_WRDAT0, wrdat[0]);
if (len > 3)
dsi_wreg(link, DSI_DIRECT_CMD_WRDAT1, wrdat[1]);
if (len > 7)
dsi_wreg(link, DSI_DIRECT_CMD_WRDAT2, wrdat[2]);
if (len > 11)
dsi_wreg(link, DSI_DIRECT_CMD_WRDAT3, wrdat[3]);
dsi_wreg(link, DSI_DIRECT_CMD_STS_CLR, ~0);
dsi_wreg(link, DSI_DIRECT_CMD_SEND, true);
/* TODO: irq wait and error check */
mdelay(10);
dsi_wreg(link, DSI_CMD_MODE_STS_CLR, ~0);
dsi_wreg(link, DSI_DIRECT_CMD_STS_CLR, ~0);
return 0;
}
int mcde_dsi_dcs_read(struct mcde_chnl_state *chnl, u8 cmd, u8* data, int *len)
{
int ret = 0;
u8 link = chnl->port.link;
u8 virt_id = chnl->port.phy.dsi.virt_id;
u32 settings;
int wait = 100;
bool error, ok;
if (*len > MCDE_MAX_DCS_READ || chnl->port.type != MCDE_PORTTYPE_DSI)
return -EINVAL;
dsi_wfld(link, DSI_MCTL_MAIN_DATA_CTL, BTA_EN, true);
dsi_wfld(link, DSI_MCTL_MAIN_DATA_CTL, READ_EN, true);
settings = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_ENUM(READ) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT(false) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID(virt_id) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE(1) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN(true) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_ENUM(DCS_READ);
dsi_wreg(link, DSI_DIRECT_CMD_MAIN_SETTINGS, settings);
dsi_wreg(link, DSI_DIRECT_CMD_WRDAT0, cmd);
dsi_wreg(link, DSI_DIRECT_CMD_STS_CLR, ~0);
dsi_wreg(link, DSI_DIRECT_CMD_RD_STS_CLR, ~0);
dsi_wreg(link, DSI_DIRECT_CMD_SEND, true);
/* TODO */
while (wait-- && !(error = dsi_rfld(link, DSI_DIRECT_CMD_STS,
READ_COMPLETED_WITH_ERR)) && !(ok = dsi_rfld(link,
DSI_DIRECT_CMD_STS, READ_COMPLETED)))
mdelay(10);
if (ok) {
int rdsize;
u32 rddat;
rdsize = dsi_rfld(link, DSI_DIRECT_CMD_RD_PROPERTY, RD_SIZE);
rddat = dsi_rreg(link, DSI_DIRECT_CMD_RDDAT);
if (rdsize < *len)
pr_debug("DCS incomplete read %d<%d (%.8X)\n",
rdsize, *len, rddat);/* REVIEW: dev_dbg */
*len = min(*len, rdsize);
memcpy(data, &rddat, *len);
} else {
pr_err("DCS read failed, err=%d, sts=%X\n",
error, dsi_rreg(link, DSI_DIRECT_CMD_STS));
ret = -EIO;
}
dsi_wreg(link, DSI_CMD_MODE_STS_CLR, ~0);
dsi_wreg(link, DSI_DIRECT_CMD_STS_CLR, ~0);
return ret;
}
static void dsi_te_request(struct mcde_chnl_state *chnl)
{
u8 link = chnl->port.link;
u8 virt_id = chnl->port.phy.dsi.virt_id;
u32 settings;
dev_vdbg(&mcde_dev->dev, "Request BTA TE, chnl=%d\n",
chnl->id);
dsi_wfld(link, DSI_MCTL_MAIN_DATA_CTL, BTA_EN, true);
dsi_wfld(link, DSI_MCTL_MAIN_DATA_CTL, REG_TE_EN, true);
dsi_wfld(link, DSI_CMD_MODE_CTL, TE_TIMEOUT, 0x3FF);
settings = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_ENUM(TE_REQ) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT(false) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID(virt_id) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE(2) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN(true) |
DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_ENUM(DCS_SHORT_WRITE_1);
dsi_wreg(link, DSI_DIRECT_CMD_MAIN_SETTINGS, settings);
dsi_wreg(link, DSI_DIRECT_CMD_WRDAT0, DCS_CMD_SET_TEAR_ON);
dsi_wreg(link, DSI_DIRECT_CMD_STS_CLR,
DSI_DIRECT_CMD_STS_CLR_TE_RECEIVED_CLR(true));
dsi_wfld(link, DSI_DIRECT_CMD_STS_CTL, TE_RECEIVED_EN, true);
dsi_wreg(link, DSI_CMD_MODE_STS_CLR,
DSI_CMD_MODE_STS_CLR_ERR_NO_TE_CLR(true));
dsi_wfld(link, DSI_CMD_MODE_STS_CTL, ERR_NO_TE_EN, true);
dsi_wreg(link, DSI_DIRECT_CMD_SEND, true);
}
/* MCDE channels */
struct mcde_chnl_state *mcde_chnl_get(enum mcde_chnl chnl_id,
enum mcde_fifo fifo, const struct mcde_port *port)
{
int i;
struct mcde_chnl_state *chnl = NULL;
enum mcde_chnl_path path;
const struct chnl_config *cfg = NULL;
/* Allocate channel */
for (i = 0; i < ARRAY_SIZE(channels); i++) {
if (chnl_id == channels[i].id)
chnl = &channels[i];
}
if (!chnl) {
dev_dbg(&mcde_dev->dev, "Invalid channel, chnl=%d\n", chnl_id);
return ERR_PTR(-EINVAL);
}
if (chnl->inuse) {
dev_dbg(&mcde_dev->dev, "Channel in use, chnl=%d\n", chnl_id);
return ERR_PTR(-EBUSY);
}
path = MCDE_CHNLPATH(chnl->id, fifo, port->type, port->ifc, port->link);
for (i = 0; i < ARRAY_SIZE(chnl_configs); i++)
if (chnl_configs[i].path == path) {
cfg = &chnl_configs[i];
break;
}
if (cfg == NULL) {
dev_dbg(&mcde_dev->dev, "Invalid config, chnl=%d,"
" path=0x%.8X\n", chnl_id, path);
chnl = ERR_PTR(-EINVAL);
goto find_config_failed;
} else
dev_info(&mcde_dev->dev, "Config, chnl=%d,"
" path=0x%.8X\n", chnl_id, path);
/* TODO: verify that cfg is ok to activate (check other chnl cfgs) */
chnl->cfg = cfg;
chnl->port = *port;
chnl->fifo = fifo;
chnl->synchronized_update = true;
if (port->type == MCDE_PORTTYPE_DSI)
chnl->pix_fmt = MCDE_PORTPIXFMT_DSI_16BPP;
else if (port->type == MCDE_PORTTYPE_DPI)
chnl->pix_fmt = MCDE_PORTPIXFMT_DPI_16BPP_C1;
mcde_chnl_apply(chnl);
chnl->inuse = true;
update_channel_static_registers(chnl);
goto out;
find_config_failed:
out:
return chnl;
}
int mcde_chnl_set_pixel_format(struct mcde_chnl_state *chnl,
enum mcde_port_pix_fmt pix_fmt)
{
if (!chnl->inuse)
return -EINVAL;
chnl->pix_fmt = pix_fmt;
return 0;
}
void mcde_chnl_set_col_convert(struct mcde_chnl_state *chnl,
struct mcde_col_convert *col_convert)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
chnl->col_regs.y_red = col_convert->matrix[0][0];
chnl->col_regs.y_green = col_convert->matrix[0][1];
chnl->col_regs.y_blue = col_convert->matrix[0][2];
chnl->col_regs.cb_red = col_convert->matrix[1][0];
chnl->col_regs.cb_green = col_convert->matrix[1][1];
chnl->col_regs.cb_blue = col_convert->matrix[1][2];
chnl->col_regs.cr_red = col_convert->matrix[2][0];
chnl->col_regs.cr_green = col_convert->matrix[2][1];
chnl->col_regs.cr_blue = col_convert->matrix[2][2];
chnl->col_regs.off_red = col_convert->offset[0];
chnl->col_regs.off_green = col_convert->offset[1];
chnl->col_regs.off_blue = col_convert->offset[2];
}
int mcde_chnl_set_rotation(struct mcde_chnl_state *chnl,
enum mcde_display_rotation rotation, u32 rotbuf1, u32 rotbuf2)
{
if (!chnl->inuse)
return -EINVAL;
/* TODO: Fix 180 degrees rotation */
if (rotation == MCDE_DISPLAY_ROT_180_CCW ||
(chnl->id != MCDE_CHNL_A && chnl->id != MCDE_CHNL_B))
return -EINVAL;
chnl->rotation = rotation;
chnl->rotbuf1 = rotbuf1;
chnl->rotbuf2 = rotbuf2;
return 0;
}
int mcde_chnl_enable_synchronized_update(struct mcde_chnl_state *chnl,
bool enable)
{
if (!chnl->inuse)
return -EINVAL;
chnl->synchronized_update = enable;
return 0;
}
int mcde_chnl_set_power_mode(struct mcde_chnl_state *chnl,
enum mcde_display_power_mode power_mode)
{
if (!chnl->inuse)
return -EINVAL;
chnl->power_mode = power_mode;
return 0;
}
int mcde_chnl_apply(struct mcde_chnl_state *chnl)
{
/* TODO: lock *//* REVIEW: MCDE locking! */
bool roten = false;
u8 rotdir = 0;
if (!chnl->inuse)
return -EINVAL;
if (chnl->rotation == MCDE_DISPLAY_ROT_90_CCW) {
roten = true;
rotdir = MCDE_ROTACONF_ROTDIR_CCW;
} else if (chnl->rotation == MCDE_DISPLAY_ROT_90_CW) {
roten = true;
rotdir = MCDE_ROTACONF_ROTDIR_CW;
}
/* REVIEW: 180 deg? */
chnl->regs.bpp = portfmt2bpp(chnl->pix_fmt);
chnl->regs.synchronized_update = chnl->synchronized_update;
chnl->regs.roten = roten;
chnl->regs.rotdir = rotdir;
chnl->regs.rotbuf1 = chnl->rotbuf1;
chnl->regs.rotbuf2 = chnl->rotbuf2;
if (chnl->port.type == MCDE_PORTTYPE_DSI)
chnl->regs.dsipacking = portfmt2dsipacking(chnl->pix_fmt);
else if (chnl->port.type == MCDE_PORTTYPE_DPI)
tv_video_mode_apply(chnl);
chnl->transactionid++;
dev_vdbg(&mcde_dev->dev, "Channel applied, chnl=%d\n", chnl->id);
return 0;
}
static void chnl_update_registers(struct mcde_chnl_state *chnl)
{
/* REVIEW: Move content to update_channel_register */
/* and remove this one */
if (chnl->port.type == MCDE_PORTTYPE_DPI)
update_tv_registers(chnl->id, &chnl->tv_regs);
if (chnl->id == MCDE_CHNL_A || chnl->id == MCDE_CHNL_B)
update_col_registers(chnl->id, &chnl->col_regs);
update_channel_registers(chnl->id, &chnl->regs, &chnl->port,
chnl->fifo, &chnl->vmode);
chnl->transactionid_regs = chnl->transactionid;
}
static void chnl_update_continous(struct mcde_chnl_state *chnl)
{
if (!chnl->continous_running) {
if (chnl->transactionid_regs < chnl->transactionid)
chnl_update_registers(chnl);
if (chnl->port.sync_src == MCDE_SYNCSRC_TE0)
mcde_wfld(MCDE_CRC, SYCEN0, true);
else if (chnl->port.sync_src == MCDE_SYNCSRC_TE1)
mcde_wfld(MCDE_CRC, SYCEN1, true);
chnl->continous_running = true;
}
}
static void chnl_update_non_continous(struct mcde_chnl_state *chnl)
{
/* Commit settings to registers */
wait_for_channel(chnl);
if (chnl->transactionid_regs < chnl->transactionid)
chnl_update_registers(chnl);
/* TODO: look at port sync source and synched_update */
if (chnl->regs.synchronized_update &&
chnl->power_mode == MCDE_DISPLAY_PM_ON) {
if (chnl->port.type == MCDE_PORTTYPE_DSI &&
chnl->port.sync_src == MCDE_SYNCSRC_BTA) {
while (dsi_rfld(chnl->port.link, DSI_CMD_MODE_STS,
CSM_RUNNING))
udelay(100);
dsi_te_request(chnl);
}
} else {
mcde_wreg(MCDE_CHNL0SYNCHSW +
chnl->id * MCDE_CHNL0SYNCHSW_GROUPOFFSET,
MCDE_CHNL0SYNCHSW_SW_TRIG(true));
dev_vdbg(&mcde_dev->dev, "Channel update (no sync), chnl=%d\n",
chnl->id);
}
}
static void chnl_update_overlay(struct mcde_chnl_state *chnl,
struct mcde_ovly_state *ovly)
{
if (!ovly || (ovly->transactionid_regs >= ovly->transactionid &&
chnl->transactionid_regs >= chnl->transactionid))
return;
update_overlay_address_registers(ovly->idx, &ovly->regs);
if (ovly->regs.reset_buf_id) {
if (chnl->continous_running)
disable_channel(chnl);
else
wait_for_overlay(ovly);
update_overlay_registers(ovly->idx, &ovly->regs, &chnl->port,
chnl->fifo, chnl->regs.x, chnl->regs.y,
chnl->regs.ppl, chnl->regs.lpf, ovly->stride,
chnl->vmode.interlaced);
ovly->transactionid_regs = ovly->transactionid;
} else if (chnl->continous_running) {
ovly->transactionid_regs = ovly->transactionid;
wait_for_overlay(ovly);
}
}
int mcde_chnl_update(struct mcde_chnl_state *chnl,
struct mcde_rectangle *update_area)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
/* TODO: lock & make wait->trig async */
if (!chnl->inuse || !update_area
|| (update_area->w == 0 && update_area->h == 0)) {
return -EINVAL;
}
chnl->regs.x = update_area->x;
chnl->regs.y = update_area->y;
/* TODO Crop against video_mode.xres and video_mode.yres */
chnl->regs.ppl = update_area->w;
chnl->regs.lpf = update_area->h;
if (chnl->port.type == MCDE_PORTTYPE_DPI) {/* REVIEW: Comment */
chnl->regs.ppl -= 2 * MCDE_CONFIG_TVOUT_HBORDER;
/* subtract double borders, ie. per field */
chnl->regs.lpf -= 4 * MCDE_CONFIG_TVOUT_VBORDER;
} else if (chnl->port.type == MCDE_PORTTYPE_DSI &&
chnl->vmode.interlaced)
chnl->regs.lpf /= 2;
chnl_update_overlay(chnl, chnl->ovly0);
chnl_update_overlay(chnl, chnl->ovly1);
if (chnl->port.update_auto_trig)
chnl_update_continous(chnl);
else
chnl_update_non_continous(chnl);
enable_channel(chnl);
dev_vdbg(&mcde_dev->dev, "Channel updated, chnl=%d\n", chnl->id);
return 0;
}
void mcde_chnl_put(struct mcde_chnl_state *chnl)
{
/* TODO: If last channel, shutdown MCDE */
/* TODO: Release formatter MCDE_CR.DPI/DSI_EN */
if (!chnl->inuse)
return;
mcde_chnl_apply(chnl);
chnl->inuse = false;
update_channel_static_registers(chnl);
}
/* MCDE overlays */
struct mcde_ovly_state *mcde_ovly_get(struct mcde_chnl_state *chnl)
{
struct mcde_ovly_state *ovly;
if (!chnl->inuse)
return ERR_PTR(-EINVAL);
if (!chnl->ovly0->inuse)
ovly = chnl->ovly0;
else if (chnl->ovly1 && !chnl->ovly1->inuse)
ovly = chnl->ovly1;
else
ovly = ERR_PTR(-EBUSY);
if (!IS_ERR(ovly)) {
ovly->inuse = true;
ovly->paddr = 0;
ovly->stride = 0;
ovly->pix_fmt = MCDE_OVLYPIXFMT_RGB565;
ovly->src_x = 0;
ovly->src_y = 0;
ovly->dst_x = 0;
ovly->dst_y = 0;
ovly->dst_z = 0;
ovly->w = 0;
ovly->h = 0;
mcde_ovly_apply(ovly);
}
return ovly;
}
void mcde_ovly_put(struct mcde_ovly_state *ovly)
{
if (!ovly->inuse)
return;
if (ovly->regs.enabled) {
ovly->paddr = 0;
mcde_ovly_apply(ovly);/* REVIEW: API call calling API call! */
}
ovly->inuse = false;
}
void mcde_ovly_set_source_buf(struct mcde_ovly_state *ovly, u32 paddr)
{
if (!ovly->inuse)
return;
ovly->paddr = paddr;
}
void mcde_ovly_set_source_info(struct mcde_ovly_state *ovly,
u32 stride, enum mcde_ovly_pix_fmt pix_fmt)
{
if (!ovly->inuse)
return;
ovly->stride = stride;
ovly->pix_fmt = pix_fmt;
}
void mcde_ovly_set_source_area(struct mcde_ovly_state *ovly,
u16 x, u16 y, u16 w, u16 h)
{
if (!ovly->inuse)
return;
ovly->src_x = x;
ovly->src_y = y;
ovly->w = w;
ovly->h = h;
}
void mcde_ovly_set_dest_pos(struct mcde_ovly_state *ovly, u16 x, u16 y, u8 z)
{
if (!ovly->inuse)
return;
ovly->dst_x = x;
ovly->dst_y = y;
ovly->dst_z = z;
}
void mcde_ovly_apply(struct mcde_ovly_state *ovly)
{
if (!ovly->inuse)
return;
/* TODO: lock */
ovly->regs.ch_id = ovly->chnl->id;
ovly->regs.enabled = ovly->paddr != 0;
ovly->regs.baseaddress0 = ovly->paddr;
ovly->regs.baseaddress1 = ovly->paddr + ovly->stride;
/*TODO set to true if interlaced *//* REVIEW: Video mode interlaced? */
ovly->regs.reset_buf_id = !ovly->chnl->continous_running;
switch (ovly->pix_fmt) {/* REVIEW: Extract to table */
case MCDE_OVLYPIXFMT_RGB565:
ovly->regs.bits_per_pixel = 16;
ovly->regs.bpp = MCDE_EXTSRC0CONF_BPP_RGB565;
ovly->regs.bgr = false;
ovly->regs.bebo = false;
ovly->regs.opq = true;
break;
case MCDE_OVLYPIXFMT_RGBA5551:
ovly->regs.bits_per_pixel = 16;
ovly->regs.bpp = MCDE_EXTSRC0CONF_BPP_IRGB1555;
ovly->regs.bgr = false;
ovly->regs.bebo = false;
ovly->regs.opq = false;
break;
case MCDE_OVLYPIXFMT_RGBA4444:
ovly->regs.bits_per_pixel = 16;
ovly->regs.bpp = MCDE_EXTSRC0CONF_BPP_ARGB4444;
ovly->regs.bgr = false;
ovly->regs.bebo = false;
ovly->regs.opq = false;
break;
case MCDE_OVLYPIXFMT_RGB888:
ovly->regs.bits_per_pixel = 24;
ovly->regs.bpp = MCDE_EXTSRC0CONF_BPP_RGB888;
ovly->regs.bgr = true;
ovly->regs.bebo = false;
ovly->regs.opq = true;
break;
case MCDE_OVLYPIXFMT_RGBX8888:
ovly->regs.bits_per_pixel = 32;
ovly->regs.bpp = MCDE_EXTSRC0CONF_BPP_XRGB8888;
ovly->regs.bgr = true;
ovly->regs.bebo = true;
ovly->regs.opq = true;
break;
case MCDE_OVLYPIXFMT_RGBA8888:
ovly->regs.bits_per_pixel = 32;
ovly->regs.bpp = MCDE_EXTSRC0CONF_BPP_ARGB8888;
ovly->regs.bgr = true;
ovly->regs.bebo = true;
ovly->regs.opq = false;
break;
case MCDE_OVLYPIXFMT_YCbCr422:
ovly->regs.bits_per_pixel = 16;
ovly->regs.bpp = MCDE_EXTSRC0CONF_BPP_YCBCR422;
ovly->regs.bgr = false;
ovly->regs.bebo = false;
ovly->regs.opq = true;
break;
default:
break;
}
ovly->regs.ppl = ovly->w;
ovly->regs.lpf = ovly->h;
ovly->regs.cropx = ovly->src_x;
ovly->regs.cropy = ovly->src_y;
ovly->regs.xpos = ovly->dst_x;
ovly->regs.ypos = ovly->dst_y;
ovly->regs.z = ovly->dst_z > 0; /* 0 or 1 */
ovly->regs.col_conv = MCDE_OVL0CR_COLCCTRL_DISABLED;
ovly->transactionid = ++ovly->chnl->transactionid;
dev_vdbg(&mcde_dev->dev, "Overlay applied, chnl=%d\n", ovly->chnl->id);
}
static int init_clocks_and_power(struct platform_device *pdev)
{
int ret = 0;
struct mcde_platform_data *pdata = pdev->dev.platform_data;
if (pdata->regulator_id) {
regulator = regulator_get(&pdev->dev,
pdata->regulator_id);
if (IS_ERR(regulator)) {
ret = PTR_ERR(regulator);
dev_warn(&pdev->dev,
"%s: Failed to get regulator '%s'\n",
__func__, pdata->regulator_id);
regulator = NULL;
goto regulator_err;
}
} else {
dev_dbg(&pdev->dev, "%s: No regulator id supplied \n",
__func__);
regulator = NULL;
}
clock_dsi = clk_get(&pdev->dev, pdata->clock_dsi_id);
if (IS_ERR(clock_dsi)) {
ret = PTR_ERR(clock_dsi);
dev_warn(&pdev->dev, "%s: Failed to get clock '%s'\n",
__func__, pdata->clock_dsi_id);
goto clk_dsi_err;
}
clock_dsi_lp = clk_get(&pdev->dev, pdata->clock_dsi_lp_id);
if (IS_ERR(clock_dsi_lp)) {
ret = PTR_ERR(clock_dsi_lp);
dev_warn(&pdev->dev, "%s: Failed to get clock '%s'\n",
__func__, pdata->clock_dsi_lp_id);
goto clk_dsi_lp_err;
}
clock_mcde = clk_get(&pdev->dev, pdata->clock_mcde_id);
if (IS_ERR(clock_mcde)) {
ret = PTR_ERR(clock_mcde);
dev_warn(&pdev->dev, "%s: Failed to get clock '%s'\n",
__func__, pdata->clock_mcde_id);
goto clk_mcde_err;
}
return ret;
clk_mcde_err:
clk_put(clock_dsi_lp);
clk_dsi_lp_err:
clk_put(clock_dsi);
clk_dsi_err:
if (regulator)
regulator_put(regulator);
regulator_err:
return ret;
}
static void remove_clocks_and_power(struct platform_device *pdev)
{
/* REVIEW: Release only if exist */
/* REVIEW: Remove make sure MCDE is done */
clk_put(clock_dsi);
clk_put(clock_dsi_lp);
clk_put(clock_mcde);
if (regulator)
regulator_put(regulator);
}
static int enable_clocks_and_power(struct platform_device *pdev)
{
int ret = 0;
if (regulator) {
ret = regulator_enable(regulator);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: regulator_enable failed\n",
__func__);
goto regulator_err;
}
} else {
dev_dbg(&pdev->dev, "%s: No regulator id supplied \n"
, __func__);
}
ret = prcmu_set_hwacc(HW_ACC_ESRAM4, HW_ON);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: prcmu_set_hwacc ESRAM 4 failed"
" ret = %d\n", __func__, ret);
goto prcmu_set_err;
}
ret = prcmu_set_hwacc(HW_ACC_MCDE, HW_ON);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: "
"prcmu_set_hwacc MCDE failed ret = %d\n",
__func__, ret);
goto prcmu_set_err;
}
ret = clk_enable(clock_dsi);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: "
"clk_enable dsi failed ret = %d\n", __func__, ret);
goto clk_dsi_err;
}
ret = clk_enable(clock_dsi_lp);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: "
"clk_enable dsi_lp failed ret = %d\n", __func__, ret);
goto clk_dsi_lp_err;
}
ret = clk_enable(clock_mcde);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: "
"clk_enable mcde failed ret = %d\n", __func__, ret);
goto clk_mcde_err;
}
ret = prcmu_enable_mcde();
if (ret < 0) {
dev_warn(&pdev->dev, "%s: "
"prcmu_enable_mcde failed ret = %d\n", __func__, ret);
goto prcmu_err;
}
return ret;
prcmu_err:
clk_mcde_err:
clk_disable(clock_dsi_lp);
clk_dsi_lp_err:
clk_disable(clock_dsi);
clk_dsi_err:
if (regulator)
regulator_disable(regulator);
regulator_err:
(void)prcmu_set_hwacc(HW_ACC_MCDE, HW_OFF);
prcmu_set_err:
return ret;
}
static int disable_clocks_and_power(struct platform_device *pdev)
{
int ret = 0;
clk_disable(clock_dsi_lp);
clk_disable(clock_mcde);
clk_disable(clock_dsi);
if (regulator) {
ret = regulator_disable(regulator);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: regulator_disable failed\n"
, __func__);
goto regulator_err;
}
} else {
dev_dbg(&pdev->dev, "%s: No regulator id supplied \n"
, __func__);
}
(void)prcmu_set_hwacc(HW_ACC_MCDE, HW_OFF);
return ret;
regulator_err:
clk_enable(clock_dsi_lp);
clk_enable(clock_mcde);
clk_enable(clock_dsi);
return ret;
}
static void update_mcde_registers(void)
{
struct mcde_platform_data *pdata = mcde_dev->dev.platform_data;
/* Setup output muxing */
mcde_wreg(MCDE_CONF0,
MCDE_CONF0_IFIFOCTRLWTRMRKLVL(7) |
MCDE_CONF0_OUTMUX0(pdata->outmux[0]) |
MCDE_CONF0_OUTMUX1(pdata->outmux[1]) |
MCDE_CONF0_OUTMUX2(pdata->outmux[2]) |
MCDE_CONF0_OUTMUX3(pdata->outmux[3]) |
MCDE_CONF0_OUTMUX4(pdata->outmux[4]) |
pdata->syncmux);
/* Enable channel VCMP interrupts */
mcde_wreg(MCDE_IMSCPP,
MCDE_IMSCPP_VCMPAIM(true) |
MCDE_IMSCPP_VCMPBIM(true) |
MCDE_IMSCPP_VCMPC0IM(true) |
MCDE_IMSCPP_VCMPC1IM(true));
/* Enable overlay fetch done interrupts */
mcde_wfld(MCDE_IMSCOVL, OVLFDIM, 0x3f);
/* Setup sync pulse length */
mcde_wreg(MCDE_VSCRC0,
MCDE_VSCRC0_VSPMIN(1) |
MCDE_VSCRC0_VSPMAX(0xff));
mcde_wreg(MCDE_VSCRC1,
MCDE_VSCRC1_VSPMIN(1) |
MCDE_VSCRC1_VSPMAX(0xff));
}
static int __devinit mcde_probe(struct platform_device *pdev)
{
int ret = 0;
int i, irq;
struct resource *res;
struct mcde_platform_data *pdata = pdev->dev.platform_data;
if (!pdata) {
dev_dbg(&pdev->dev, "No platform data\n");
return -EINVAL;
}
num_dsilinks = pdata->num_dsilinks;
mcde_dev = pdev;
dsiio = kzalloc(num_dsilinks * sizeof(*dsiio), GFP_KERNEL);
if (!dsiio) {
ret = -ENOMEM;
goto failed_dsi_alloc;
}
/* Hook up irq */
irq = platform_get_irq(pdev, 0);
if (irq <= 0) {
dev_dbg(&pdev->dev, "No irq defined\n");
ret = -EINVAL;
goto failed_irq_get;
}
ret = request_irq(irq, mcde_irq_handler, 0, "mcde", &pdev->dev);
if (ret) {
dev_dbg(&pdev->dev, "Failed to request irq (irq=%d)\n", irq);
goto failed_request_irq;
}
/* Map I/O */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_dbg(&pdev->dev, "No MCDE io defined\n");
ret = -EINVAL;
goto failed_get_mcde_io;
}
mcdeio = ioremap(res->start, res->end - res->start + 1);
if (!mcdeio) {
dev_dbg(&pdev->dev, "MCDE iomap failed\n");
ret = -EINVAL;
goto failed_map_mcde_io;
}
dev_info(&pdev->dev, "MCDE iomap: 0x%.8X->0x%.8X\n",
(u32)res->start, (u32)mcdeio);
for (i = 0; i < num_dsilinks; i++) {
res = platform_get_resource(pdev, IORESOURCE_MEM, 1+i);
if (!res) {
dev_dbg(&pdev->dev, "No DSI%d io defined\n", i);
ret = -EINVAL;
goto failed_get_dsi_io;
}
dsiio[i] = ioremap(res->start, res->end - res->start + 1);
if (!dsiio[i]) {
dev_dbg(&pdev->dev, "MCDE DSI%d iomap failed\n", i);
ret = -EINVAL;
goto failed_map_dsi_io;
}
dev_info(&pdev->dev, "MCDE DSI%d iomap: 0x%.8X->0x%.8X\n",
i, (u32)res->start, (u32)dsiio[i]);
}
ret = init_clocks_and_power(pdev);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: init_clocks_and_power failed\n"
, __func__);
goto failed_init_clocks;
}
ret = enable_clocks_and_power(pdev);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: enable_clocks_and_power failed\n"
, __func__);
goto failed_enable_clocks;
}
update_mcde_registers();
dev_info(&mcde_dev->dev, "Probe done (v%d.%d.%d.%d)\n",
mcde_rfld(MCDE_PID, MAJOR_VERSION),
mcde_rfld(MCDE_PID, MINOR_VERSION),
mcde_rfld(MCDE_PID, DEVELOPMENT_VERSION),
mcde_rfld(MCDE_PID, METALFIX_VERSION));
goto out;
failed_enable_clocks:
remove_clocks_and_power(pdev);
failed_init_clocks:
failed_map_dsi_io:
failed_get_dsi_io:
for (i = 0; i < num_dsilinks; i++) {
if (dsiio[i])
iounmap(dsiio[i]);
}
iounmap(mcdeio);
failed_map_mcde_io:
failed_get_mcde_io:
free_irq(irq, &pdev->dev);
failed_request_irq:
failed_irq_get:
kfree(dsiio);
dsiio = NULL;
failed_dsi_alloc:
out:
return ret;
}
static int __devexit mcde_remove(struct platform_device *pdev)
{
remove_clocks_and_power(pdev);
return 0;
}
#ifdef CONFIG_PM
static int mcde_resume(struct platform_device *pdev)
{
int ret;
struct mcde_chnl_state *chnl = &channels[0];
dev_info(&pdev->dev, "Resume is called\n");
ret = enable_clocks_and_power(pdev);
if (ret < 0) {
dev_dbg(&pdev->dev, "%s: Enable clocks and power failed\n"
, __func__);
goto clock_err;
}
update_mcde_registers();
for (; chnl < &channels[ARRAY_SIZE(channels)]; chnl++) {
if (chnl->inuse) {
update_channel_static_registers(chnl);
update_channel_registers(chnl->id, &chnl->regs,
&chnl->port, chnl->fifo,
&chnl->vmode);
if (chnl->ovly0)
update_overlay_registers(chnl->ovly0->idx,
&chnl->ovly0->regs,
&chnl->port, chnl->fifo,
chnl->regs.x, chnl->regs.y,
chnl->regs.ppl, chnl->regs.lpf,
chnl->ovly0->stride,
chnl->vmode.interlaced);
if (chnl->ovly1)
update_overlay_registers(chnl->ovly1->idx,
&chnl->ovly1->regs,
&chnl->port, chnl->fifo,
chnl->regs.x, chnl->regs.y,
chnl->regs.ppl, chnl->regs.lpf,
chnl->ovly1->stride,
chnl->vmode.interlaced);
}
}
clock_err:
return ret;
}
#endif
#ifdef CONFIG_PM
static int mcde_suspend(struct platform_device *pdev, pm_message_t state)
{
dev_info(&pdev->dev, "Suspend is called\n");
return disable_clocks_and_power(pdev);
}
#endif
static struct platform_driver mcde_driver = {
.probe = mcde_probe,
.remove = mcde_remove,
#ifdef CONFIG_PM
.suspend = mcde_suspend,
.resume = mcde_resume,
#else
.suspend = NULL,
.resume = NULL,
#endif
.driver = {
.name = "mcde",
},
};
/* REVIEW: __init? */
int mcde_init(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(channels); i++) {
channels[i].ovly0->chnl = &channels[i];
if (channels[i].ovly1)
channels[i].ovly1->chnl = &channels[i];
init_waitqueue_head(&channels[i].waitq_hw);
}
for (i = 0; i < ARRAY_SIZE(overlays); i++)
init_waitqueue_head(&overlays[i].waitq_hw);
return platform_driver_register(&mcde_driver);
}
void mcde_exit(void)
{
/* REVIEW: shutdown MCDE? */
platform_driver_unregister(&mcde_driver);
}