blob: 585b22f9111f5dcc88959d450ba5d656e4e40056 [file] [log] [blame]
/*
* Copyright (C) ST-Ericsson SA 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 <linux/jiffies.h>
#include <linux/workqueue.h>
#include <video/mcde.h>
#include "dsilink_regs.h"
#include "mcde_regs.h"
static void disable_channel(struct mcde_chnl_state *chnl);
static void watchdog_auto_sync_timer_function(unsigned long arg);
static int _mcde_chnl_apply(struct mcde_chnl_state *chnl);
static void disable_flow(struct mcde_chnl_state *chnl);
static void enable_channel(struct mcde_chnl_state *chnl);
static void do_softwaretrig(struct mcde_chnl_state *chnl);
static int is_channel_enabled(struct mcde_chnl_state *chnl);
static void dsi_te_poll_req(struct mcde_chnl_state *chnl);
static void dsi_te_poll_set_timer(struct mcde_chnl_state *chnl,
unsigned int timeout);
static void dsi_te_timer_function(unsigned long value);
#define OVLY_TIMEOUT 100
#define CHNL_TIMEOUT 100
#define SCREEN_PPL_HIGH 1920
#define SCREEN_PPL_CEA2 720
#define SCREEN_LPF_CEA2 480
#define DSI_DELAY0_CEA2_ADD 10
#define MCDE_SLEEP_WATCHDOG 500
#define DSI_TE_NO_ANSWER_TIMEOUT_INIT 2500
#define DSI_TE_NO_ANSWER_TIMEOUT 250
static u8 *mcdeio;
static u8 **dsiio;
static struct platform_device *mcde_dev;
static u8 num_dsilinks;
static u8 num_channels;
static u8 num_overlays;
static u8 hardware_version;
static int mcde_irq;
#ifdef CONFIG_REGULATOR
static struct regulator *regulator_vana;
static struct regulator *regulator_mcde_epod;
static struct regulator *regulator_esram_epod;
#endif
static struct clk *clock_dpi;
static struct clk *clock_dsi;
static struct clk *clock_mcde;
static struct clk *clock_dsi_lp;
static u8 mcde_is_enabled;
static struct mutex mcde_hw_lock;
static struct delayed_work hw_timeout_work;
static u8 enable_dsi;
static u8 dsi_is_enabled;
static u8 mcde_dynamic_power_management = true;
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_wreg_fld(__reg, __fld_mask, __fld_shift, __val) \
mcde_wreg(__reg, (mcde_rreg(__reg) & ~(__fld_mask)) |\
(((__val) << (__fld_shift)) & (__fld_mask)))
#define mcde_rfld(__reg, __fld) \
((mcde_rreg(__reg) & __reg##_##__fld##_MASK) >> \
__reg##_##__fld##_SHIFT)
#define mcde_wfld(__reg, __fld, __val) \
mcde_wreg_fld(__reg, __reg##_##__fld##_MASK,\
__reg##_##__fld##_SHIFT, __val)
struct ovly_regs {
u8 ch_id;
bool enabled;
u32 baseaddress0;
u32 baseaddress1;
bool update;
u8 bits_per_pixel;
u8 bpp;
bool bgr;
bool bebo;
bool opq;
u8 col_conv;
u8 alpha_source;
u8 alpha_value;
u8 pixoff;
u16 ppl;
u16 lpf;
u16 cropx;
u16 cropy;
u16 xpos;
u16 ypos;
u8 z;
};
struct mcde_ovly_state {
bool inuse;
bool update;
u8 idx; /* MCDE overlay index */
struct mcde_chnl_state *chnl; /* Owner channel */
/* 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;
u8 alpha_source;
u8 alpha_value;
/* Applied settings */
struct ovly_regs regs;
};
static struct mcde_ovly_state *overlays;
struct chnl_regs {
bool floen;
u16 x;
u16 y;
u16 ppl;
u16 lpf;
u8 bpp;
bool internal_clk; /* CLKTYPE field */
u16 pcd;
u8 clksel;
u8 cdwin;
u16 (*map_r)(u8);
u16 (*map_g)(u8);
u16 (*map_b)(u8);
bool palette_enable;
bool bcd;
bool synchronized_update;
bool roten;
u8 rotdir;
u32 rotbuf1; /* TODO: Replace with eSRAM alloc */
u32 rotbuf2; /* TODO: Replace with eSRAM alloc */
/* Blending */
u8 blend_ctrl;
bool blend_en;
u8 alpha_blend;
/* DSI */
u8 dsipacking;
};
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_y;
u16 off_cb;
u16 off_cr;
};
struct tv_regs {
u16 dho; /* TV mode: left border width; destination horizontal offset */
/* LCD MODE: horizontal back porch */
u16 alw; /* TV mode: right border width */
/* LCD mode: horizontal front porch */
u16 hsw; /* horizontal synch width */
u16 dvo; /* TV mode: top border width; destination horizontal offset */
/* LCD MODE: vertical back porch */
u16 bsl; /* TV mode: bottom border width; blanking start line */
/* LCD MODE: vertical front porch */
/* field 1 */
u16 bel1; /* TV mode: field total vertical blanking lines */
/* LCD mode: vertical sync width */
u16 fsl1; /* field vbp */
/* field 2 */
u16 bel2;
u16 fsl2;
u8 tv_mode;
bool sel_mode_tv;
bool inv_clk;
bool interlaced_en;
u32 lcdtim1;
};
struct mcde_chnl_state {
bool enabled;
bool reserved;
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 */
/* Used as watchdog timer for auto sync feature */
struct timer_list auto_sync_timer;
struct timer_list dsi_te_timer;
enum mcde_display_power_mode power_mode;
/* Staged settings */
u16 (*map_r)(u8);
u16 (*map_g)(u8);
u16 (*map_b)(u8);
bool palette_enable;
bool synchronized_update;
struct mcde_video_mode vmode;
enum mcde_display_rotation rotation;
u32 rotbuf1;
u32 rotbuf2;
struct mcde_col_transform rgb_2_ycbcr;
struct mcde_col_transform ycbcr_2_rgb;
struct mcde_col_transform *transform;
/* Blending */
u8 blend_ctrl;
bool blend_en;
u8 alpha_blend;
/* Applied settings */
struct chnl_regs regs;
struct col_regs col_regs;
struct tv_regs tv_regs;
/* an interlaced digital TV signal generates a VCMP per field */
bool vcmp_per_field;
bool even_vcmp;
bool continous_running;
bool disable_software_trig;
bool formatter_updated;
bool esram_is_enabled;
};
static struct mcde_chnl_state *channels;
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 },
};
#define DSI_READ_TIMEOUT 10
#define DSI_READ_DELAY 100
/*
* Wait for CSM_RUNNING, all data sent for display
*/
static inline void wait_while_dsi_running(int lnk)
{
u8 counter = DSI_READ_TIMEOUT;
while (dsi_rfld(lnk, DSI_CMD_MODE_STS, CSM_RUNNING) && --counter) {
dev_vdbg(&mcde_dev->dev,
"%s: DSI link %u read running state retry %u times\n"
, __func__, lnk, (DSI_READ_TIMEOUT - counter));
udelay(DSI_READ_DELAY);
}
if (!counter)
dev_warn(&mcde_dev->dev,
"%s: DSI link %u read timeout!\n", __func__, lnk);
}
static int enable_clocks_and_power(struct platform_device *pdev)
{
struct mcde_platform_data *pdata = pdev->dev.platform_data;
int ret = 0;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
#ifdef CONFIG_REGULATOR
if (regulator_mcde_epod) {
ret = regulator_enable(regulator_mcde_epod);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: regulator_enable failed\n",
__func__);
return ret;
}
} else {
dev_warn(&pdev->dev, "%s: mcde_epod regulator is null\n"
, __func__);
return -EINVAL;
}
#endif
pdata->platform_set_clocks();
if (enable_dsi > 0) {
pdata->platform_enable_dsipll();
dsi_is_enabled = true;
}
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;
}
return ret;
clk_mcde_err:
#ifdef CONFIG_REGULATOR
if (regulator_mcde_epod)
regulator_disable(regulator_mcde_epod);
#endif
return ret;
}
static int disable_clocks_and_power(struct platform_device *pdev)
{
struct mcde_platform_data *pdata = pdev->dev.platform_data;
int ret = 0;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
clk_disable(clock_mcde);
if (enable_dsi > 0) {
pdata->platform_disable_dsipll();
dsi_is_enabled = false;
}
#ifdef CONFIG_REGULATOR
if (regulator_mcde_epod) {
ret = regulator_disable(regulator_mcde_epod);
if (ret < 0) {
dev_warn(&pdev->dev, "%s: regulator_disable failed\n"
, __func__);
goto regulator_mcde_epod_err;
}
} else {
dev_warn(&pdev->dev, "%s: mcde_epod regulator is null\n"
, __func__);
goto regulator_mcde_epod_err;
}
return ret;
regulator_mcde_epod_err:
clk_enable(clock_mcde);
#endif
return ret;
}
static void update_mcde_registers(void)
{
struct mcde_platform_data *pdata = mcde_dev->dev.platform_data;
if (hardware_version == MCDE_CHIP_VERSION_1_0_4) {
/* Setup output muxing */
mcde_wreg(MCDE_CONF0,
MCDE_CONF0_IFIFOCTRLWTRMRKLVL(7));
mcde_wfld(MCDE_RISOVL, OVLFDRIS, 1);
mcde_wfld(MCDE_RISPP, VCMPARIS, 1);
mcde_wfld(MCDE_RISPP, VCMPBRIS, 1);
/* Enable channel VCMP interrupts */
mcde_wreg(MCDE_IMSCPP,
MCDE_IMSCPP_VCMPAIM(true) |
MCDE_IMSCPP_VCMPBIM(true));
} else {
/* 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);
mcde_wfld(MCDE_RISOVL, OVLFDRIS, 1);
mcde_wfld(MCDE_RISPP, VCMPARIS, 1);
mcde_wfld(MCDE_RISPP, VCMPBRIS, 1);
mcde_wfld(MCDE_RISPP, VCMPC0RIS, 1);
mcde_wfld(MCDE_RISPP, VCMPC1RIS, 1);
/* Enable channel VCMP interrupts */
mcde_wreg(MCDE_IMSCPP,
MCDE_IMSCPP_VCMPAIM(true) |
MCDE_IMSCPP_VCMPBIM(true) |
MCDE_IMSCPP_VCMPC0IM(true) |
MCDE_IMSCPP_VCMPC1IM(true));
#ifdef DEBUG
/* Enable error interrupts */
mcde_wreg(MCDE_IMSCERR,
MCDE_IMSCERR_SCHBLCKDIM(true) |
MCDE_IMSCERR_OVLFERRIM_MASK |
MCDE_IMSCERR_FUAIM(true) |
MCDE_IMSCERR_FUBIM(true) |
MCDE_IMSCERR_FUC0IM(true) |
MCDE_IMSCERR_FUC1IM(true));
/* Enable channel abort interrupts */
mcde_wreg(MCDE_IMSCCHNL, MCDE_IMSCCHNL_CHNLAIM(0xf));
#endif
}
/* 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 void disable_formatter(struct mcde_port *port)
{
if (port->type == MCDE_PORTTYPE_DSI) {
if (port->phy.dsi.clk_dsi)
clk_disable(port->phy.dsi.clk_dsi);
if (port->phy.dsi.clk_dsi_lp)
clk_disable(port->phy.dsi.clk_dsi_lp);
if (port->phy.dsi.reg_vana)
regulator_disable(port->phy.dsi.reg_vana);
}
if (port->type == MCDE_PORTTYPE_DPI) {
if (port->phy.dpi.clk_dpi)
clk_disable(port->phy.dpi.clk_dpi);
}
}
static int disable_mcde_hw(bool force_disable)
{
int i;
int ret;
bool mcde_up = false;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!mcde_is_enabled)
return 0;
for (i = 0; i < num_channels; i++) {
struct mcde_chnl_state *chnl = &channels[i];
if (force_disable ||
(chnl->enabled && !chnl->continous_running)) {
disable_channel(chnl);
if (chnl->port.type == MCDE_PORTTYPE_DSI)
wait_while_dsi_running(chnl->port.link);
if (chnl->formatter_updated) {
disable_formatter(&chnl->port);
chnl->formatter_updated = false;
}
if (chnl->esram_is_enabled) {
int ret;
ret = regulator_disable(chnl->port.reg_esram);
if (ret < 0) {
dev_warn(&mcde_dev->dev,
"%s: disable failed\n",
__func__);
}
chnl->esram_is_enabled = false;
}
} else if (chnl->enabled && chnl->continous_running) {
mcde_up = true;
}
}
if (mcde_up)
return 0;
for (i = 0; i < num_channels; i++) {
struct mcde_chnl_state *chnl = &channels[i];
chnl->formatter_updated = false;
del_timer(&chnl->dsi_te_timer);
del_timer(&chnl->auto_sync_timer);
}
free_irq(mcde_irq, &mcde_dev->dev);
ret = disable_clocks_and_power(mcde_dev);
if (ret < 0) {
dev_dbg(&mcde_dev->dev,
"%s: disable_clocks_and_power failed\n"
, __func__);
return -EINVAL;
}
mcde_is_enabled = false;
return 0;
}
static void dpi_video_mode_apply(struct mcde_chnl_state *chnl)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
chnl->tv_regs.interlaced_en = chnl->vmode.interlaced;
chnl->tv_regs.sel_mode_tv = chnl->port.phy.dpi.tv_mode;
if (chnl->tv_regs.sel_mode_tv) {
/* TV mode */
u32 bel;
/* -4 since hsw is excluding SAV/EAV, 2 bytes each */
chnl->tv_regs.hsw = chnl->vmode.hbp + chnl->vmode.hfp - 4;
/* vbp_field2 = vbp_field1 + 1 */
chnl->tv_regs.fsl1 = chnl->vmode.vbp / 2;
chnl->tv_regs.fsl2 = chnl->vmode.vbp - chnl->tv_regs.fsl1;
/* +1 since vbp_field2 = vbp_field1 + 1 */
bel = chnl->vmode.vbp + chnl->vmode.vfp;
/* in TV mode: bel2 = bel1 + 1 */
chnl->tv_regs.bel1 = bel / 2;
chnl->tv_regs.bel2 = bel - chnl->tv_regs.bel1;
if (chnl->port.phy.dpi.bus_width == 4)
chnl->tv_regs.tv_mode = MCDE_TVCRA_TVMODE_SDTV_656P_BE;
else
chnl->tv_regs.tv_mode = MCDE_TVCRA_TVMODE_SDTV_656P;
if (hardware_version == MCDE_CHIP_VERSION_3_0_8)
chnl->tv_regs.inv_clk = true;
else {
chnl->tv_regs.dho = MCDE_CONFIG_TVOUT_HBORDER;
chnl->tv_regs.alw = MCDE_CONFIG_TVOUT_HBORDER;
chnl->tv_regs.dvo = MCDE_CONFIG_TVOUT_VBORDER;
chnl->tv_regs.bsl = MCDE_CONFIG_TVOUT_VBORDER;
}
} else {
/* LCD mode */
u32 polarity;
chnl->tv_regs.hsw = chnl->vmode.hsw;
chnl->tv_regs.dho = chnl->vmode.hbp;
chnl->tv_regs.alw = chnl->vmode.hfp;
chnl->tv_regs.bel1 = chnl->vmode.vsw;
chnl->tv_regs.bel2 = chnl->tv_regs.bel1;
chnl->tv_regs.dvo = chnl->vmode.vbp;
chnl->tv_regs.bsl = chnl->vmode.vfp;
chnl->tv_regs.fsl1 = 0;
chnl->tv_regs.fsl2 = 0;
polarity = chnl->port.phy.dpi.polarity;
chnl->tv_regs.lcdtim1 |= MCDE_LCDTIM1A_IPC(
(polarity & DPI_ACT_ON_FALLING_EDGE) != 0);
chnl->tv_regs.lcdtim1 = MCDE_LCDTIM1A_IHS(
(polarity & DPI_ACT_LOW_HSYNC) != 0);
chnl->tv_regs.lcdtim1 |= MCDE_LCDTIM1A_IVS(
(polarity & DPI_ACT_LOW_VSYNC) != 0);
chnl->tv_regs.lcdtim1 |= MCDE_LCDTIM1A_IOE(
(polarity & DPI_ACT_LOW_DATA_ENABLE) != 0);
}
}
static void update_dpi_registers(enum mcde_chnl chnl_id, struct tv_regs *regs)
{
u8 idx = chnl_id;
dev_dbg(&mcde_dev->dev, "%s\n", __func__);
mcde_wreg(MCDE_TVCRA + idx * MCDE_TVCRA_GROUPOFFSET,
MCDE_TVCRA_SEL_MOD(regs->sel_mode_tv) |
MCDE_TVCRA_INTEREN(regs->interlaced_en) |
MCDE_TVCRA_IFIELD(0) |
MCDE_TVCRA_TVMODE(regs->tv_mode) |
MCDE_TVCRA_SDTVMODE(MCDE_TVCRA_SDTVMODE_Y0CBY1CR) |
MCDE_TVCRA_CKINV(regs->inv_clk) |
MCDE_TVCRA_AVRGEN(0));
mcde_wreg(MCDE_TVBLUA + idx * MCDE_TVBLUA_GROUPOFFSET,
MCDE_TVBLUA_TVBLU(MCDE_CONFIG_TVOUT_BACKGROUND_LUMINANCE) |
MCDE_TVBLUA_TVBCB(MCDE_CONFIG_TVOUT_BACKGROUND_CHROMINANCE_CB)|
MCDE_TVBLUA_TVBCR(MCDE_CONFIG_TVOUT_BACKGROUND_CHROMINANCE_CR));
/* Vertical timing registers */
mcde_wreg(MCDE_TVDVOA + idx * MCDE_TVDVOA_GROUPOFFSET,
MCDE_TVDVOA_DVO1(regs->dvo) |
MCDE_TVDVOA_DVO2(regs->dvo));
mcde_wreg(MCDE_TVBL1A + idx * MCDE_TVBL1A_GROUPOFFSET,
MCDE_TVBL1A_BEL1(regs->bel1) |
MCDE_TVBL1A_BSL1(regs->bsl));
mcde_wreg(MCDE_TVBL2A + idx * MCDE_TVBL1A_GROUPOFFSET,
MCDE_TVBL2A_BEL2(regs->bel2) |
MCDE_TVBL2A_BSL2(regs->bsl));
mcde_wreg(MCDE_TVISLA + idx * MCDE_TVISLA_GROUPOFFSET,
MCDE_TVISLA_FSL1(regs->fsl1) |
MCDE_TVISLA_FSL2(regs->fsl2));
/* Horizontal timing registers */
if (!regs->sel_mode_tv ||
hardware_version == MCDE_CHIP_VERSION_3_0_8) {
mcde_wreg(MCDE_TVLBALWA + idx * MCDE_TVLBALWA_GROUPOFFSET,
MCDE_TVLBALWA_LBW(regs->hsw) |
MCDE_TVLBALWA_ALW(regs->alw));
mcde_wreg(MCDE_TVTIM1A + idx * MCDE_TVTIM1A_GROUPOFFSET,
MCDE_TVTIM1A_DHO(regs->dho));
} else {
/* in earlier versions the LBW and DHO fields are swapped
* TV mode only
*/
mcde_wreg(MCDE_TVLBALWA + idx * MCDE_TVLBALWA_GROUPOFFSET,
MCDE_TVLBALWA_LBW(regs->dho) |
MCDE_TVLBALWA_ALW(regs->alw));
mcde_wreg(MCDE_TVTIM1A + idx * MCDE_TVTIM1A_GROUPOFFSET,
MCDE_TVTIM1A_DHO(regs->hsw));
}
if (!regs->sel_mode_tv)
mcde_wreg(MCDE_LCDTIM1A + idx * MCDE_LCDTIM1A_GROUPOFFSET,
regs->lcdtim1);
}
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_cr));
mcde_wreg(MCDE_RGBCONV6A + idx * MCDE_RGBCONV6A_GROUPOFFSET,
MCDE_RGBCONV6A_OFF_GREEN(regs->off_y) |
MCDE_RGBCONV6A_OFF_BLUE(regs->off_cb));
}
/* 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 1;
}
}
static u8 bpp2outbpp(u8 bpp)
{
switch (bpp) {
case 16:
return MCDE_CRA1_OUTBPP_16BPP;
case 18:
return MCDE_CRA1_OUTBPP_18BPP;
case 24:
return MCDE_CRA1_OUTBPP_24BPP;
default:
return 0;
}
}
static u8 portfmt2cdwin(enum mcde_port_pix_fmt pix_fmt)
{
switch (pix_fmt) {
case MCDE_PORTPIXFMT_DPI_16BPP_C1:
return MCDE_CRA1_CDWIN_16BPP_C1;
case MCDE_PORTPIXFMT_DPI_16BPP_C2:
return MCDE_CRA1_CDWIN_16BPP_C2;
case MCDE_PORTPIXFMT_DPI_16BPP_C3:
return MCDE_CRA1_CDWIN_16BPP_C3;
case MCDE_PORTPIXFMT_DPI_18BPP_C1:
return MCDE_CRA1_CDWIN_18BPP_C1;
case MCDE_PORTPIXFMT_DPI_18BPP_C2:
return MCDE_CRA1_CDWIN_18BPP_C2;
case MCDE_PORTPIXFMT_DPI_24BPP:
return MCDE_CRA1_CDWIN_24BPP;
default:
/* only DPI formats are relevant */
return 0;
}
}
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 u8 get_dsi_formid(const struct mcde_port *port)
{
if (port->ifc == DSI_VIDEO_MODE && port->link == 0)
return MCDE_CTRLA_FORMID_DSI0VID;
else if (port->ifc == DSI_VIDEO_MODE && port->link == 1)
return MCDE_CTRLA_FORMID_DSI1VID;
else if (port->ifc == DSI_VIDEO_MODE && port->link == 2)
return MCDE_CTRLA_FORMID_DSI2VID;
else if (port->ifc == DSI_CMD_MODE && port->link == 0)
return MCDE_CTRLA_FORMID_DSI0CMD;
else if (port->ifc == DSI_CMD_MODE && port->link == 1)
return MCDE_CTRLA_FORMID_DSI1CMD;
else if (port->ifc == DSI_CMD_MODE && port->link == 2)
return MCDE_CTRLA_FORMID_DSI2CMD;
return 0;
}
static struct mcde_chnl_state *find_channel_by_dsilink(int link)
{
struct mcde_chnl_state *chnl = &channels[0];
for (; chnl < &channels[num_channels]; chnl++)
if (chnl->enabled && chnl->port.link == link &&
chnl->port.type == MCDE_PORTTYPE_DSI)
return chnl;
return NULL;
}
static inline void mcde_handle_vcmp(struct mcde_chnl_state *chnl, u32 int_fld)
{
if (!chnl->vcmp_per_field ||
(chnl->vcmp_per_field && chnl->even_vcmp)) {
chnl->transactionid_hw = chnl->transactionid_regs;
wake_up(&chnl->waitq_hw);
if (chnl->port.update_auto_trig &&
chnl->port.sync_src == MCDE_SYNCSRC_OFF &&
chnl->port.type == MCDE_PORTTYPE_DSI &&
chnl->continous_running) {
if (hardware_version == MCDE_CHIP_VERSION_3_0_8)
enable_channel(chnl);
mcde_wreg(MCDE_CHNL0SYNCHSW +
chnl->id * MCDE_CHNL0SYNCHSW_GROUPOFFSET,
MCDE_CHNL0SYNCHSW_SW_TRIG(true));
if (hardware_version == MCDE_CHIP_VERSION_3_0_8)
disable_flow(chnl);
mod_timer(&chnl->auto_sync_timer,
jiffies +
msecs_to_jiffies(MCDE_AUTO_SYNC_WATCHDOG
* 1000));
}
}
chnl->even_vcmp = !chnl->even_vcmp;
mcde_wreg_fld(MCDE_RISPP, 0x1 << int_fld, int_fld, 1);
#ifdef DEBUG
if (chnl->ovly0 && mcde_rreg(MCDE_OVL0CR +
chnl->ovly0->idx * MCDE_OVL0CR_GROUPOFFSET) &
MCDE_OVL0CR_OVLB_MASK)
dev_warn(&mcde_dev->dev, "Overlay %d blocked\n",
chnl->ovly0->idx);
if (chnl->ovly1 && mcde_rreg(MCDE_OVL0CR +
chnl->ovly1->idx * MCDE_OVL0CR_GROUPOFFSET) &
MCDE_OVL0CR_OVLB_MASK)
dev_warn(&mcde_dev->dev, "Overlay %d blocked\n",
chnl->ovly1->idx);
#endif
}
static irqreturn_t mcde_irq_handler(int irq, void *dev)
{
int i;
u32 irq_status;
#ifdef DEBUG
u32 irq_riserr_status;
u32 irq_rischnl_status;
irq_riserr_status = mcde_rreg(MCDE_RISERR);
irq_rischnl_status = mcde_rreg(MCDE_RISCHNL);
#endif
/* Handle overlay irqs */
irq_status = mcde_rfld(MCDE_RISOVL, OVLFDRIS);
mcde_wfld(MCDE_RISOVL, OVLFDRIS, irq_status);
/* Handle channel irqs */
irq_status = mcde_rreg(MCDE_RISPP);
if (irq_status & MCDE_RISPP_VCMPARIS_MASK) {
mcde_handle_vcmp(&channels[MCDE_CHNL_A],
MCDE_RISPP_VCMPARIS_SHIFT);
#ifdef DEBUG
if (irq_riserr_status & MCDE_RISERR_FUARIS_MASK) {
dev_warn(&mcde_dev->dev, "FIFO A underflow\n");
mcde_wreg(MCDE_RISERR, MCDE_RISERR_FUARIS_MASK);
}
if (irq_rischnl_status & MCDE_RISCHNL_CHNLARIS(0)) {
dev_warn(&mcde_dev->dev, "Channel A abort\n");
mcde_wreg(MCDE_RISCHNL, MCDE_RISCHNL_CHNLARIS(0));
}
#endif
}
if (irq_status & MCDE_RISPP_VCMPBRIS_MASK) {
mcde_handle_vcmp(&channels[MCDE_CHNL_B],
MCDE_RISPP_VCMPBRIS_SHIFT);
#ifdef DEBUG
if (irq_riserr_status & MCDE_RISERR_FUBRIS_MASK) {
dev_warn(&mcde_dev->dev, "FIFO B underflow\n");
mcde_wreg(MCDE_RISERR, MCDE_RISERR_FUBRIS_MASK);
}
if (irq_rischnl_status & MCDE_RISCHNL_CHNLARIS(1)) {
dev_warn(&mcde_dev->dev, "Channel B abort\n");
mcde_wreg(MCDE_RISCHNL, MCDE_RISCHNL_CHNLARIS(1));
}
#endif
}
if (irq_status & MCDE_RISPP_VCMPC0RIS_MASK) {
mcde_handle_vcmp(&channels[MCDE_CHNL_C0],
MCDE_RISPP_VCMPC0RIS_SHIFT);
#ifdef DEBUG
if (irq_riserr_status & MCDE_RISERR_FUC0RIS_MASK) {
dev_warn(&mcde_dev->dev, "FIFO C0 underflow\n");
mcde_wreg(MCDE_RISERR, MCDE_RISERR_FUC0RIS_MASK);
}
if (irq_rischnl_status & MCDE_RISCHNL_CHNLARIS(2)) {
dev_warn(&mcde_dev->dev, "Channel C0 abort\n");
mcde_wreg(MCDE_RISCHNL, MCDE_RISCHNL_CHNLARIS(2));
}
#endif
}
if (irq_status & MCDE_RISPP_VCMPC1RIS_MASK) {
mcde_handle_vcmp(&channels[MCDE_CHNL_C1],
MCDE_RISPP_VCMPC1RIS_SHIFT);
#ifdef DEBUG
if (irq_riserr_status & MCDE_RISERR_FUC1RIS_MASK) {
dev_warn(&mcde_dev->dev, "FIFO C1 underflow\n");
mcde_wreg(MCDE_RISERR, MCDE_RISERR_FUC1RIS_MASK);
}
if (irq_rischnl_status & MCDE_RISCHNL_CHNLARIS(3)) {
dev_warn(&mcde_dev->dev, "Channel C1 abort\n");
mcde_wreg(MCDE_RISCHNL, MCDE_RISCHNL_CHNLARIS(3));
}
#endif
}
#ifdef DEBUG
/* Handle error irqs */
if (irq_riserr_status & MCDE_RISERR_SCHBLCKDRIS_MASK) {
dev_warn(&mcde_dev->dev, "Scheduler blocked\n");
mcde_wreg(MCDE_RISERR, MCDE_RISERR_SCHBLCKDRIS_MASK);
}
if (irq_riserr_status & MCDE_IMSCERR_OVLFERRIM_MASK) {
dev_warn(&mcde_dev->dev, "Overlay fetch error\n");
mcde_wreg(MCDE_RISERR, MCDE_IMSCERR_OVLFERRIM_MASK);
}
#endif
for (i = 0; i < num_dsilinks; i++) {
struct mcde_chnl_state *chnl_from_dsi;
chnl_from_dsi = find_channel_by_dsilink(i);
if (chnl_from_dsi == NULL)
continue;
irq_status = dsi_rfld(i, DSI_DIRECT_CMD_STS_FLAG,
TE_RECEIVED_FLAG);
if (irq_status) {
if (hardware_version == MCDE_CHIP_VERSION_3_0_8)
disable_flow(chnl_from_dsi);
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);
do_softwaretrig(chnl_from_dsi);
dev_vdbg(&mcde_dev->dev, "SW TRIG DSI%d, chnl=%d\n", i,
chnl_from_dsi->id);
}
irq_status = dsi_rfld(i, DSI_CMD_MODE_STS_FLAG, ERR_NO_TE_FLAG);
if (irq_status) {
if (hardware_version == MCDE_CHIP_VERSION_3_0_8)
disable_flow(chnl_from_dsi);
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);
}
irq_status = dsi_rfld(i, DSI_DIRECT_CMD_STS, TRIGGER_RECEIVED);
if (irq_status) {
/* DSI TE polling answer received */
dsi_wreg(i, DSI_DIRECT_CMD_STS_CLR,
DSI_DIRECT_CMD_STS_CLR_TRIGGER_RECEIVED_CLR(
true));
if (chnl_from_dsi->port.sync_src ==
MCDE_SYNCSRC_TE_POLLING)
/* Reset timer */
dsi_te_poll_set_timer(chnl_from_dsi,
DSI_TE_NO_ANSWER_TIMEOUT);
}
}
return IRQ_HANDLED;
}
static void wait_for_channel(struct mcde_chnl_state *chnl)
{
int ret;
u32 id = chnl->transactionid_regs;
if (chnl->transactionid_hw >= id)
return;
ret = wait_event_timeout(chnl->waitq_hw,
chnl->transactionid_hw == id,
msecs_to_jiffies(CHNL_TIMEOUT));
if (!ret)
dev_warn(&mcde_dev->dev,
"Wait for channel timeout (chnl=%d,%d<%d)!\n",
chnl->id, chnl->transactionid_hw,
id);
}
static int update_channel_static_registers(struct mcde_chnl_state *chnl)
{
const struct chnl_config *cfg = chnl->cfg;
const struct mcde_port *port = &chnl->port;
if (hardware_version == MCDE_CHIP_VERSION_3_0_5) {
/* Fifo & muxing */
if (cfg->swap_a_c0_set)
mcde_wfld(MCDE_CONF0, SWAP_A_C0_V1, cfg->swap_a_c0);
if (cfg->swap_b_c1_set)
mcde_wfld(MCDE_CONF0, SWAP_B_C1_V1, cfg->swap_b_c1);
if (cfg->fabmux_set)
mcde_wfld(MCDE_CR, FABMUX_V1, cfg->fabmux);
if (cfg->f01mux_set)
mcde_wfld(MCDE_CR, F01MUX_V1, cfg->f01mux);
if (port->type == MCDE_PORTTYPE_DPI) {
if (port->link == 0)
mcde_wfld(MCDE_CR, DPIA_EN_V1, true);
else if (port->link == 1)
mcde_wfld(MCDE_CR, DPIB_EN_V1, true);
} else if (port->type == MCDE_PORTTYPE_DSI) {
if (port->ifc == DSI_VIDEO_MODE && port->link == 0)
mcde_wfld(MCDE_CR, DSIVID0_EN_V1, true);
else if (port->ifc == DSI_VIDEO_MODE && port->link == 1)
mcde_wfld(MCDE_CR, DSIVID1_EN_V1, true);
else if (port->ifc == DSI_VIDEO_MODE && port->link == 2)
mcde_wfld(MCDE_CR, DSIVID2_EN_V1, true);
else if (port->ifc == DSI_CMD_MODE && port->link == 0)
mcde_wfld(MCDE_CR, DSICMD0_EN_V1, true);
else if (port->ifc == DSI_CMD_MODE && port->link == 1)
mcde_wfld(MCDE_CR, DSICMD1_EN_V1, true);
else if (port->ifc == DSI_CMD_MODE && port->link == 2)
mcde_wfld(MCDE_CR, DSICMD2_EN_V1, true);
}
if (chnl->fifo == MCDE_FIFO_C0)
mcde_wreg(MCDE_CTRLC0, MCDE_CTRLC0_FIFOWTRMRK(
get_output_fifo_size(MCDE_FIFO_C0)));
else if (chnl->fifo == MCDE_FIFO_C1)
mcde_wreg(MCDE_CTRLC1, MCDE_CTRLC1_FIFOWTRMRK(
get_output_fifo_size(MCDE_FIFO_C1)));
else if (port->update_auto_trig &&
(port->sync_src == MCDE_SYNCSRC_TE0))
mcde_wreg(MCDE_CTRLC0, MCDE_CTRLC0_FIFOWTRMRK(
get_output_fifo_size(MCDE_FIFO_C0)));
else if (port->update_auto_trig &&
(port->sync_src == MCDE_SYNCSRC_TE1))
mcde_wreg(MCDE_CTRLC1, MCDE_CTRLC1_FIFOWTRMRK(
get_output_fifo_size(MCDE_FIFO_C1)));
} else if (hardware_version == MCDE_CHIP_VERSION_3_0_8) {
switch (chnl->fifo) {
case MCDE_FIFO_A:
mcde_wreg(MCDE_CHNL0MUXING_V2 + chnl->id *
MCDE_CHNL0MUXING_V2_GROUPOFFSET,
MCDE_CHNL0MUXING_V2_FIFO_ID_ENUM(FIFO_A));
if (port->type == MCDE_PORTTYPE_DPI) {
mcde_wfld(MCDE_CTRLA, FORMTYPE,
MCDE_CTRLA_FORMTYPE_DPITV);
mcde_wfld(MCDE_CTRLA, FORMID, port->link);
mcde_wfld(MCDE_CTRLA, FIFOWTRMRK,
get_output_fifo_size(MCDE_FIFO_A));
} else if (port->type == MCDE_PORTTYPE_DSI) {
mcde_wfld(MCDE_CTRLA, FORMTYPE,
MCDE_CTRLA_FORMTYPE_DSI);
mcde_wfld(MCDE_CTRLA, FORMID,
get_dsi_formid(port));
mcde_wfld(MCDE_CTRLA, FIFOWTRMRK,
get_output_fifo_size(MCDE_FIFO_A));
}
break;
case MCDE_FIFO_B:
mcde_wreg(MCDE_CHNL0MUXING_V2 + chnl->id *
MCDE_CHNL0MUXING_V2_GROUPOFFSET,
MCDE_CHNL0MUXING_V2_FIFO_ID_ENUM(FIFO_B));
if (port->type == MCDE_PORTTYPE_DPI) {
mcde_wfld(MCDE_CTRLB, FORMTYPE,
MCDE_CTRLB_FORMTYPE_DPITV);
mcde_wfld(MCDE_CTRLB, FORMID, port->link);
mcde_wfld(MCDE_CTRLB, FIFOWTRMRK,
get_output_fifo_size(MCDE_FIFO_B));
} else if (port->type == MCDE_PORTTYPE_DSI) {
mcde_wfld(MCDE_CTRLB, FORMTYPE,
MCDE_CTRLB_FORMTYPE_DSI);
mcde_wfld(MCDE_CTRLB, FORMID,
get_dsi_formid(port));
mcde_wfld(MCDE_CTRLB, FIFOWTRMRK,
get_output_fifo_size(MCDE_FIFO_B));
}
break;
case MCDE_FIFO_C0:
mcde_wreg(MCDE_CHNL0MUXING_V2 + chnl->id *
MCDE_CHNL0MUXING_V2_GROUPOFFSET,
MCDE_CHNL0MUXING_V2_FIFO_ID_ENUM(FIFO_C0));
if (port->type == MCDE_PORTTYPE_DPI)
return -EINVAL;
mcde_wfld(MCDE_CTRLC0, FORMTYPE,
MCDE_CTRLC0_FORMTYPE_DSI);
mcde_wfld(MCDE_CTRLC0, FORMID, get_dsi_formid(port));
mcde_wfld(MCDE_CTRLC0, FIFOWTRMRK,
get_output_fifo_size(MCDE_FIFO_C0));
break;
case MCDE_FIFO_C1:
mcde_wreg(MCDE_CHNL0MUXING_V2 + chnl->id *
MCDE_CHNL0MUXING_V2_GROUPOFFSET,
MCDE_CHNL0MUXING_V2_FIFO_ID_ENUM(FIFO_C1));
if (port->type == MCDE_PORTTYPE_DPI)
return -EINVAL;
mcde_wfld(MCDE_CTRLC1, FORMTYPE,
MCDE_CTRLC1_FORMTYPE_DSI);
mcde_wfld(MCDE_CTRLC1, FORMID, get_dsi_formid(port));
mcde_wfld(MCDE_CTRLC1, FIFOWTRMRK,
get_output_fifo_size(MCDE_FIFO_C1));
break;
default:
return -EINVAL;
}
} else if (hardware_version == MCDE_CHIP_VERSION_1_0_4) {
switch (chnl->fifo) {
case MCDE_FIFO_A:
/* only channel A is supported */
if (chnl->id != 0)
return -EINVAL;
if (port->type == MCDE_PORTTYPE_DSI) {
if ((port->link == 1 &&
port->ifc == DSI_VIDEO_MODE) ||
(port->link == 0 && port->ifc == DSI_CMD_MODE))
return -EINVAL;
mcde_wfld(MCDE_CR, DSI0_EN_V3, true);
} else if (port->type == MCDE_PORTTYPE_DPI) {
mcde_wfld(MCDE_CR, DPI_EN_V3, true);
}
mcde_wfld(MCDE_CTRLA, FIFOWTRMRK,
get_output_fifo_size(MCDE_FIFO_A));
break;
case MCDE_FIFO_B:
if (port->type != MCDE_PORTTYPE_DSI)
return -EINVAL;
/* only channel B is supported */
if (chnl->id != 1)
return -EINVAL;
if ((port->link == 0 && port->ifc == DSI_VIDEO_MODE) ||
(port->link == 1 && port->ifc == DSI_CMD_MODE))
return -EINVAL;
mcde_wfld(MCDE_CR, DSI1_EN_V3, true);
mcde_wfld(MCDE_CTRLB, FIFOWTRMRK,
get_output_fifo_size(MCDE_FIFO_B));
break;
default:
return -EINVAL;
}
}
/* Formatter */
if (port->type == MCDE_PORTTYPE_DSI) {
int i = 0;
u8 idx;
u8 lnk = port->link;
int ret;
if (port->phy.dsi.reg_vana) {
ret = regulator_enable(port->phy.dsi.reg_vana);
if (ret < 0) {
dev_warn(&mcde_dev->dev,
"%s: regulator_enable failed\n",
__func__);
goto regulator_vana_err;
}
}
if (port->phy.dsi.clk_dsi) {
ret = clk_enable(port->phy.dsi.clk_dsi);
if (ret < 0) {
dev_warn(&mcde_dev->dev, "%s: "
"clk_enable dsi failed ret = %d\n",
__func__, ret);
goto clk_dsi_err;
}
}
if (port->phy.dsi.clk_dsi_lp) {
ret = clk_enable(port->phy.dsi.clk_dsi_lp);
if (ret < 0) {
dev_warn(&mcde_dev->dev, "%s: "
"clk_enable dsi_lp failed ret = %d\n",
__func__, ret);
goto clk_dsi_lp_err;
}
}
if (hardware_version == MCDE_CHIP_VERSION_1_0_4)
idx = chnl->id;
else
idx = 2 * port->link + port->ifc;
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, LINK_EN, true);
dev_dbg(&mcde_dev->dev, "DSI%d LINK_EN\n", lnk);
if (port->sync_src == MCDE_SYNCSRC_TE_POLLING) {
if (hardware_version == MCDE_CHIP_VERSION_3_0_5) {
dev_err(&mcde_dev->dev,
"DSI TE polling is not supported on this HW\n");
goto dsi_link_error;
}
/* Enable DSI TE polling */
dsi_te_poll_req(chnl);
/* Set timer to detect non TE answer */
dsi_te_poll_set_timer(chnl,
DSI_TE_NO_ANSWER_TIMEOUT_INIT);
} else {
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);
}
if (hardware_version == MCDE_CHIP_VERSION_3_0_5) {
if (port->phy.dsi.data_lanes_swap) {
dev_warn(&mcde_dev->dev,
"DSI %d data lane remap not available!\n",
lnk);
goto dsi_link_error;
}
} else
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, DLX_REMAP_EN,
port->phy.dsi.data_lanes_swap);
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);
goto dsi_link_error;
}
}
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 == DSI_VIDEO_MODE)
dsi_wfld(port->link, DSI_CMD_MODE_CTL, IF1_ID,
port->phy.dsi.virt_id);
else if (port->ifc == DSI_CMD_MODE)
dsi_wfld(port->link, DSI_CMD_MODE_CTL, IF2_ID,
port->phy.dsi.virt_id);
}
}
if (port->type == MCDE_PORTTYPE_DPI) {
if (port->phy.dpi.clk_dpi) {
int ret;
ret = clk_enable(port->phy.dpi.clk_dpi);
if (ret < 0) {
dev_warn(&mcde_dev->dev, "%s: "
"clk_enable dsi_lp failed ret = %d\n",
__func__, ret);
goto clk_dpi_err;
}
}
}
mcde_wfld(MCDE_CR, MCDEEN, true);
chnl->formatter_updated = true;
dev_vdbg(&mcde_dev->dev, "Static registers setup, chnl=%d\n", chnl->id);
return 0;
dsi_link_error:
if (port->phy.dsi.clk_dsi_lp)
clk_disable(port->phy.dsi.clk_dsi_lp);
clk_dsi_lp_err:
if (port->phy.dsi.clk_dsi)
clk_disable(port->phy.dsi.clk_dsi);
clk_dsi_err:
if (port->phy.dsi.reg_vana) {
regulator_disable(port->phy.dsi.reg_vana);
chnl->port.phy.dsi.reg_vana = NULL;
}
regulator_vana_err:
clk_dpi_err:
return -EINVAL;
}
void mcde_chnl_col_convert_apply(struct mcde_chnl_state *chnl,
struct mcde_col_transform *transform)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (chnl->transform != transform) {
chnl->col_regs.y_red = transform->matrix[0][0];
chnl->col_regs.y_green = transform->matrix[0][1];
chnl->col_regs.y_blue = transform->matrix[0][2];
chnl->col_regs.cb_red = transform->matrix[1][0];
chnl->col_regs.cb_green = transform->matrix[1][1];
chnl->col_regs.cb_blue = transform->matrix[1][2];
chnl->col_regs.cr_red = transform->matrix[2][0];
chnl->col_regs.cr_green = transform->matrix[2][1];
chnl->col_regs.cr_blue = transform->matrix[2][2];
chnl->col_regs.off_y = transform->offset[0];
chnl->col_regs.off_cb = transform->offset[1];
chnl->col_regs.off_cr = transform->offset[2];
chnl->transform = transform;
}
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
}
static void chnl_ovly_pixel_format_apply(struct mcde_chnl_state *chnl,
struct mcde_ovly_state *ovly)
{
struct mcde_port *port = &chnl->port;
struct ovly_regs *regs = &ovly->regs;
/* Note: YUV -> YUV: blending YUV overlays will not make sense. */
static struct mcde_col_transform crycb_2_ycbcr = {
/* Note that in MCDE YUV 422 pixels come as VYU pixels */
.matrix = {
{0x0000, 0x0100, 0x0000},
{0x0000, 0x0000, 0x0100},
{0x0100, 0x0000, 0x0000},
},
.offset = {0, 0, 0},
};
if (port->type == MCDE_PORTTYPE_DSI) {
if (port->pixel_format != MCDE_PORTPIXFMT_DSI_YCBCR422) {
if (ovly->pix_fmt != MCDE_OVLYPIXFMT_YCbCr422) {
/* standard case: DSI: RGB -> RGB */
regs->col_conv = MCDE_OVL0CR_COLCCTRL_DISABLED;
} else {
/* DSI: YUV -> RGB */
/* TODO change matrix */
regs->col_conv =
MCDE_OVL0CR_COLCCTRL_ENABLED_SAT;
mcde_chnl_col_convert_apply(chnl,
&chnl->ycbcr_2_rgb);
}
} else {
if (ovly->pix_fmt != MCDE_OVLYPIXFMT_YCbCr422)
/* DSI: RGB -> YUV */
mcde_chnl_col_convert_apply(chnl,
&chnl->rgb_2_ycbcr);
else
/* DSI: YUV -> YUV */
mcde_chnl_col_convert_apply(chnl,
&crycb_2_ycbcr);
regs->col_conv = MCDE_OVL0CR_COLCCTRL_ENABLED_NO_SAT;
}
} else if (port->type == MCDE_PORTTYPE_DPI && port->phy.dpi.tv_mode) {
regs->col_conv = MCDE_OVL0CR_COLCCTRL_ENABLED_NO_SAT;
if (ovly->pix_fmt != MCDE_OVLYPIXFMT_YCbCr422)
mcde_chnl_col_convert_apply(chnl, &chnl->rgb_2_ycbcr);
else
mcde_chnl_col_convert_apply(chnl, &crycb_2_ycbcr);
}
}
/* 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, s16 stride, bool interlaced,
enum mcde_display_rotation rotation)
{
/* 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;
s32 ljinc = stride;
u32 pixelfetchwtrmrklevel;
u8 nr_of_bufs = 1;
u32 fifo_size;
u32 sel_mod = MCDE_EXTSRC0CR_SEL_MOD_SOFTWARE_SEL;
if (rotation == MCDE_DISPLAY_ROT_180_CCW) {
ljinc = -ljinc;
tmrgn += stride * (regs->lpf - 1) / 8;
}
/*
* Preferably most of this is done in some apply function instead of for
* every update. However lpf has a dependency on update_y.
*/
if (interlaced && port->type == MCDE_PORTTYPE_DSI) {
nr_of_bufs = 2;
lpf = lpf / 2;
ljinc *= 2;
}
fifo_size = get_output_fifo_size(fifo);
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;
if (port->update_auto_trig && port->type == MCDE_PORTTYPE_DSI) {
switch (port->sync_src) {
case MCDE_SYNCSRC_OFF:
sel_mod = MCDE_EXTSRC0CR_SEL_MOD_SOFTWARE_SEL;
break;
case MCDE_SYNCSRC_TE0:
case MCDE_SYNCSRC_TE1:
case MCDE_SYNCSRC_TE_POLLING:
default:
sel_mod = MCDE_EXTSRC0CR_SEL_MOD_AUTO_TOGGLE;
break;
}
} else if (port->type == MCDE_PORTTYPE_DPI)
sel_mod = MCDE_EXTSRC0CR_SEL_MOD_SOFTWARE_SEL;
regs->update = 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(sel_mod) |
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(regs->enabled) |
MCDE_OVL0CR_COLCCTRL(regs->col_conv) |
MCDE_OVL0CR_CKEYGEN(false) |
MCDE_OVL0CR_ALPHAPMEN(false) |
MCDE_OVL0CR_OVLF(false) |
MCDE_OVL0CR_OVLR(false) |
MCDE_OVL0CR_OVLB(false) |
MCDE_OVL0CR_FETCH_ROPC(0) |
MCDE_OVL0CR_STBPRIO(0) |
MCDE_OVL0CR_BURSTSIZE_ENUM(HW_8W) |
/* TODO: enum, get from ovly */
MCDE_OVL0CR_MAXOUTSTANDING_ENUM(8_REQ) |
/* TODO: _HW_8W, calculate? */
MCDE_OVL0CR_ROTBURSTSIZE_ENUM(HW_8W));
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(regs->alpha_source) |
MCDE_OVL0CONF2_ALPHAVALUE(regs->alpha_value) |
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));
dev_vdbg(&mcde_dev->dev, "Overlay registers setup, idx=%d\n", idx);
}
static void update_overlay_registers_on_the_fly(u8 idx, struct ovly_regs *regs)
{
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));
mcde_wreg(MCDE_EXTSRC0A0 + idx * MCDE_EXTSRC0A0_GROUPOFFSET,
regs->baseaddress0);
mcde_wreg(MCDE_EXTSRC0A1 + idx * MCDE_EXTSRC0A1_GROUPOFFSET,
regs->baseaddress1);
}
static void do_softwaretrig(struct mcde_chnl_state *chnl)
{
/*
* For main and secondary display,
* FLOWEN has to be set before a SOFTWARE TRIG
* Otherwise no overlay interrupt is triggerd
* However FLOWEN must not be triggered before SOFTWARE TRIG
* if rotation is enabled
*/
if (hardware_version == MCDE_CHIP_VERSION_3_0_8)
enable_channel(chnl);
else if ((!is_channel_enabled(chnl) && !chnl->regs.roten)
|| chnl->power_mode != MCDE_DISPLAY_PM_ON)
enable_channel(chnl);
mcde_wreg(MCDE_CHNL0SYNCHSW +
chnl->id * MCDE_CHNL0SYNCHSW_GROUPOFFSET,
MCDE_CHNL0SYNCHSW_SW_TRIG(true));
if (hardware_version == MCDE_CHIP_VERSION_3_0_8)
disable_flow(chnl);
else
enable_channel(chnl);
}
static void disable_flow(struct mcde_chnl_state *chnl)
{
switch (chnl->id) {
case MCDE_CHNL_A:
mcde_wfld(MCDE_CRA0, FLOEN, false);
break;
case MCDE_CHNL_B:
mcde_wfld(MCDE_CRB0, FLOEN, false);
break;
case MCDE_CHNL_C0:
mcde_wfld(MCDE_CRC, C1EN, false);
break;
case MCDE_CHNL_C1:
mcde_wfld(MCDE_CRC, C2EN, false);
break;
}
}
#define MCDE_FLOWEN_MAX_TRIAL 60
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__);
chnl->disable_software_trig = true;
if (port->type == MCDE_PORTTYPE_DSI) {
dsi_wfld(port->link, DSI_MCTL_MAIN_PHY_CTL, CLK_CONTINUOUS,
false);
if (port->sync_src == MCDE_SYNCSRC_TE_POLLING)
del_timer(&chnl->dsi_te_timer);
}
if (chnl->port.update_auto_trig &&
chnl->port.sync_src == MCDE_SYNCSRC_OFF &&
chnl->port.type == MCDE_PORTTYPE_DSI) {
del_timer(&chnl->auto_sync_timer);
chnl->continous_running = false;
}
if (hardware_version == MCDE_CHIP_VERSION_1_0_4) {
if (is_channel_enabled(chnl)) {
wait_for_channel(chnl);
/*
* Just to make sure that a frame is triggered when
* we try to disable the channel
*/
chnl->transactionid++;
mcde_wreg(MCDE_CHNL0SYNCHSW +
chnl->id * MCDE_CHNL0SYNCHSW_GROUPOFFSET,
MCDE_CHNL0SYNCHSW_SW_TRIG(true));
disable_flow(chnl);
wait_for_channel(chnl);
for (i = 0; i < MCDE_FLOWEN_MAX_TRIAL; i++) {
if (!is_channel_enabled(chnl)) {
dev_vdbg(&mcde_dev->dev,
"Flow %d off after >= %d ms\n",
chnl->id, i);
goto break_switch;
}
msleep(1);
}
} else {
dev_vdbg(&mcde_dev->dev,
"Flow %d disable after >= %d ms\n",
chnl->id, i);
goto break_switch;
}
} else {
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) off 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) off 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);
wait_for_channel(chnl);
for (i = 0; i < MCDE_FLOWEN_MAX_TRIAL; i++) {
msleep(1);
if (!mcde_rfld(MCDE_CRC, C1EN)) {
dev_vdbg(&mcde_dev->dev,
"Flow (C1) off after >= %d ms\n", i);
goto break_switch;
}
}
dev_warn(&mcde_dev->dev, "%s: channel C0 timeout\n",
__func__);
break;
case MCDE_CHNL_C1:
mcde_wfld(MCDE_CRC, C2EN, false);
wait_for_channel(chnl);
for (i = 0; i < MCDE_FLOWEN_MAX_TRIAL; i++) {
msleep(1);
if (!mcde_rfld(MCDE_CRC, C2EN)) {
dev_vdbg(&mcde_dev->dev,
"Flow (C2) off after >= %d ms\n", i);
goto break_switch;
}
}
dev_warn(&mcde_dev->dev, "%s: channel C1 timeout\n",
__func__);
break;
}
}
break_switch:
chnl->continous_running = false;
}
static void enable_channel(struct mcde_chnl_state *chnl)
{
const struct mcde_port *port = &chnl->port;
int i;
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);
for (i = 0; i < MCDE_FLOWEN_MAX_TRIAL; i++) {
if (mcde_rfld(MCDE_CRA0, FLOEN)) {
dev_vdbg(&mcde_dev->dev,
"Flow (A) enable after >= %d ms\n", i);
return;
}
msleep(1);
}
break;
case MCDE_CHNL_B:
mcde_wfld(MCDE_CRB0, FLOEN, true);
for (i = 0; i < MCDE_FLOWEN_MAX_TRIAL; i++) {
if (mcde_rfld(MCDE_CRB0, FLOEN)) {
dev_vdbg(&mcde_dev->dev,
"Flow (B) enable after >= %d ms\n", i);
return;
}
msleep(1);
}
break;
case MCDE_CHNL_C0:
mcde_wfld(MCDE_CRC, C1EN, true);
for (i = 0; i < MCDE_FLOWEN_MAX_TRIAL; i++) {
if (mcde_rfld(MCDE_CRC, C1EN)) {
dev_vdbg(&mcde_dev->dev,
"Flow (C1) enable after >= %d ms\n", i);
return;
}
msleep(1);
}
mcde_wfld(MCDE_CRC, POWEREN, true);
break;
case MCDE_CHNL_C1:
mcde_wfld(MCDE_CRC, C2EN, true);
for (i = 0; i < MCDE_FLOWEN_MAX_TRIAL; i++) {
if (mcde_rfld(MCDE_CRC, C2EN)) {
dev_vdbg(&mcde_dev->dev,
"Flow (C2) enable after >= %d ms\n", i);
return;
}
msleep(1);
}
mcde_wfld(MCDE_CRC, POWEREN, true);
break;
}
}
#undef MCDE_FLOWEN_MAX_TRIAL
static int is_channel_enabled(struct mcde_chnl_state *chnl)
{
switch (chnl->id) {
case MCDE_CHNL_A:
return mcde_rfld(MCDE_CRA0, FLOEN);
case MCDE_CHNL_B:
return mcde_rfld(MCDE_CRB0, FLOEN);
case MCDE_CHNL_C0:
return mcde_rfld(MCDE_CRC, C1EN);
case MCDE_CHNL_C1:
return mcde_rfld(MCDE_CRC, C2EN);
}
return 0;
}
static void watchdog_auto_sync_timer_function(unsigned long arg)
{
int i;
for (i = 0; i < num_channels; i++) {
struct mcde_chnl_state *chnl = &channels[i];
if (chnl->port.update_auto_trig &&
chnl->port.sync_src == MCDE_SYNCSRC_OFF &&
chnl->port.type == MCDE_PORTTYPE_DSI &&
chnl->continous_running) {
mcde_wreg(MCDE_CHNL0SYNCHSW +
chnl->id
* MCDE_CHNL0SYNCHSW_GROUPOFFSET,
MCDE_CHNL0SYNCHSW_SW_TRIG(true));
mod_timer(&chnl->auto_sync_timer,
jiffies +
msecs_to_jiffies(MCDE_AUTO_SYNC_WATCHDOG
* 1000));
}
}
}
static void work_sleep_function(struct work_struct *ptr)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (mutex_trylock(&mcde_hw_lock) == 1) {
if (mcde_dynamic_power_management)
(void)disable_mcde_hw(false);
mutex_unlock(&mcde_hw_lock);
}
}
/* 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;
u32 src_synch = MCDE_CHNL0SYNCHMOD_SRC_SYNCH_SOFTWARE;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
/* Channel */
if (port->update_auto_trig && port->type == MCDE_PORTTYPE_DSI) {
switch (port->sync_src) {
case MCDE_SYNCSRC_TE0:
out_synch_src = MCDE_CHNL0SYNCHMOD_OUT_SYNCH_SRC_VSYNC0;
src_synch = MCDE_CHNL0SYNCHMOD_SRC_SYNCH_OUTPUT;
break;
case MCDE_SYNCSRC_OFF:
src_synch = MCDE_CHNL0SYNCHMOD_SRC_SYNCH_SOFTWARE;
break;
case MCDE_SYNCSRC_TE1:
default:
out_synch_src = MCDE_CHNL0SYNCHMOD_OUT_SYNCH_SRC_VSYNC1;
src_synch = MCDE_CHNL0SYNCHMOD_SRC_SYNCH_OUTPUT;
break;
case MCDE_SYNCSRC_TE_POLLING:
src_synch = MCDE_CHNL0SYNCHMOD_SRC_SYNCH_OUTPUT;
break;
}
} else if (port->type == MCDE_PORTTYPE_DPI) {
src_synch = port->update_auto_trig ?
MCDE_CHNL0SYNCHMOD_SRC_SYNCH_OUTPUT :
MCDE_CHNL0SYNCHMOD_SRC_SYNCH_SOFTWARE;
}
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(src_synch) |
MCDE_CHNL0SYNCHMOD_OUT_SYNCH_SRC(out_synch_src));
mcde_wreg(MCDE_CHNL0BCKGNDCOL + idx * MCDE_CHNL0BCKGNDCOL_GROUPOFFSET,
MCDE_CHNL0BCKGNDCOL_B(0) |
MCDE_CHNL0BCKGNDCOL_G(0) |
MCDE_CHNL0BCKGNDCOL_R(0));
if (chnl_id == MCDE_CHNL_A || chnl_id == MCDE_CHNL_B) {
u32 mcde_crx0;
u32 mcde_crx1;
u32 mcde_pal0x;
u32 mcde_pal1x;
if (chnl_id == MCDE_CHNL_A) {
mcde_crx0 = MCDE_CRA0;
mcde_crx1 = MCDE_CRA1;
mcde_pal0x = MCDE_PAL0A;
mcde_pal1x = MCDE_PAL1A;
} else {
mcde_crx0 = MCDE_CRB0;
mcde_crx1 = MCDE_CRB1;
mcde_pal0x = MCDE_PAL0B;
mcde_pal1x = MCDE_PAL1B;
}
mcde_wreg_fld(mcde_crx0, MCDE_CRA0_ROTEN_MASK,
MCDE_CRA0_ROTEN_SHIFT, regs->roten);
mcde_wreg_fld(mcde_crx0, MCDE_CRA0_PALEN_MASK,
MCDE_CRA0_PALEN_SHIFT, regs->palette_enable);
mcde_wreg(mcde_crx1,
MCDE_CRA1_PCD(regs->pcd) |
MCDE_CRA1_CLKSEL(regs->clksel) |
MCDE_CRA1_CDWIN(regs->cdwin) |
MCDE_CRA1_OUTBPP(bpp2outbpp(regs->bpp)) |
MCDE_CRA1_BCD(regs->bcd) |
MCDE_CRA1_CLKTYPE(regs->internal_clk)
);
if (regs->palette_enable) {
int i;
for (i = 0; i < 256; i++) {
mcde_wreg(mcde_pal0x,
MCDE_PAL0A_GREEN(regs->map_g(i)) |
MCDE_PAL0A_BLUE(regs->map_b(i)));
mcde_wreg(mcde_pal1x,
MCDE_PAL1A_RED(regs->map_r(i)));
}
}
}
/* Formatter */
if (port->type == MCDE_PORTTYPE_DSI) {
u8 fidx;
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;
if (hardware_version == MCDE_CHIP_VERSION_1_0_4)
fidx = chnl_id;
else
fidx = 2 * port->link + port->ifc;
screen_ppl = video_mode->xres;
screen_lpf = video_mode->yres;
if (screen_ppl == SCREEN_PPL_HIGH) {
pkt_div = (screen_ppl - 1) /
get_output_fifo_size(fifo) + 1;
} else {
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;
if ((screen_ppl == SCREEN_PPL_CEA2) &&
(screen_lpf == SCREEN_LPF_CEA2))
dsi_delay0 += DSI_DELAY0_CEA2_ADD;
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,
regs->rotbuf1);
mcde_wreg(MCDE_ROTADD1A + chnl_id * MCDE_ROTADD1A_GROUPOFFSET,
regs->rotbuf2);
mcde_wreg(MCDE_ROTACONF + chnl_id * MCDE_ROTACONF_GROUPOFFSET,
MCDE_ROTACONF_ROTBURSTSIZE_ENUM(8W) |
MCDE_ROTACONF_ROTBURSTSIZE_HW(1) |
MCDE_ROTACONF_ROTDIR(regs->rotdir) |
MCDE_ROTACONF_STRIP_WIDTH_ENUM(16PIX) |
MCDE_ROTACONF_RD_MAXOUT_ENUM(4_REQ) |
MCDE_ROTACONF_WR_MAXOUT_ENUM(8_REQ));
}
/* Blending */
if (chnl_id == MCDE_CHNL_A) {
mcde_wfld(MCDE_CRA0, BLENDEN, regs->blend_en);
mcde_wfld(MCDE_CRA0, BLENDCTRL, regs->blend_ctrl);
mcde_wfld(MCDE_CRA0, ALPHABLEND, regs->alpha_blend);
} else if (chnl_id == MCDE_CHNL_B) {
mcde_wfld(MCDE_CRB0, BLENDEN, regs->blend_en);
mcde_wfld(MCDE_CRB0, BLENDCTRL, regs->blend_ctrl);
mcde_wfld(MCDE_CRB0, ALPHABLEND, regs->alpha_blend);
}
dev_vdbg(&mcde_dev->dev, "Channel registers setup, chnl=%d\n", chnl_id);
}
static int enable_mcde_hw(void)
{
int ret;
int i;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
cancel_delayed_work(&hw_timeout_work);
schedule_delayed_work(&hw_timeout_work,
msecs_to_jiffies(MCDE_SLEEP_WATCHDOG));
if (mcde_is_enabled)
return 0;
ret = enable_clocks_and_power(mcde_dev);
if (ret < 0) {
dev_dbg(&mcde_dev->dev,
"%s: Enable clocks and power failed\n"
, __func__);
cancel_delayed_work(&hw_timeout_work);
return -EINVAL;
}
ret = request_irq(mcde_irq, mcde_irq_handler, 0, "mcde",
&mcde_dev->dev);
if (ret) {
dev_dbg(&mcde_dev->dev, "Failed to request irq (irq=%d)\n",
mcde_irq);
cancel_delayed_work(&hw_timeout_work);
return -EINVAL;
}
update_mcde_registers();
/* update hardware with same settings as before power down*/
for (i = 0; i < num_channels; i++) {
struct mcde_chnl_state *chnl = &channels[i];
if (chnl->enabled) {
if (chnl->ovly0 && chnl->ovly0->inuse &&
chnl->ovly0->regs.enabled)
chnl->ovly0->regs.update = true;
if (chnl->ovly1 && chnl->ovly1->inuse &&
chnl->ovly1->regs.enabled)
chnl->ovly1->regs.update = true;
chnl->transactionid++;
}
}
mcde_is_enabled = true;
return 0;
}
/* DSI */
static int mcde_dsi_direct_cmd_write(struct mcde_chnl_state *chnl,
bool dcs, 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;
u32 ok;
u32 error;
if (len > MCDE_MAX_DSI_DIRECT_CMD_WRITE ||
chnl->port.type != MCDE_PORTTYPE_DSI)
return -EINVAL;
mutex_lock(&mcde_hw_lock);
if (enable_mcde_hw()) {
mutex_unlock(&mcde_hw_lock);
return -EINVAL;
}
if (!chnl->formatter_updated)
(void)update_channel_static_registers(chnl);
wait_for_channel(chnl);
wait_while_dsi_running(chnl->port.link);
if (dcs) {
wrdat[0] = cmd;
for (i = 1; i <= len; i++)
wrdat[i>>2] |= ((u32)data[i-1] << ((i & 3) * 8));
} else {
/* no explicit cmd byte for generic_write, only params */
for (i = 0; i < len; i++)
wrdat[i>>2] |= ((u32)data[i] << ((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 (dcs) {
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);
} else {
if (len == 0)
settings |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_ENUM(
GENERIC_SHORT_WRITE_0);
else if (len == 1)
settings |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_ENUM(
GENERIC_SHORT_WRITE_1);
else if (len == 2)
settings |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_ENUM(
GENERIC_SHORT_WRITE_2);
else
settings |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_ENUM(
GENERIC_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);
ok = dsi_rreg(link, DSI_DIRECT_CMD_STS);
error = dsi_rreg(link, DSI_CMD_MODE_STS);
dev_vdbg(&mcde_dev->dev, "DSI Write ok %x error %x\n", ok, error);
dsi_wreg(link, DSI_CMD_MODE_STS_CLR, ~0);
dsi_wreg(link, DSI_DIRECT_CMD_STS_CLR, ~0);
mutex_unlock(&mcde_hw_lock);
return 0;
}
int mcde_dsi_generic_write(struct mcde_chnl_state *chnl, u8* para, int len)
{
return mcde_dsi_direct_cmd_write(chnl, false, 0, para, len);
}
int mcde_dsi_dcs_write(struct mcde_chnl_state *chnl, u8 cmd, u8* data, int len)
{
return mcde_dsi_direct_cmd_write(chnl, true, cmd, data, len);
}
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;
mutex_lock(&mcde_hw_lock);
if (enable_mcde_hw()) {
mutex_unlock(&mcde_hw_lock);
return -EINVAL;
}
if (!chnl->formatter_updated)
(void)update_channel_static_registers(chnl);
wait_for_channel(chnl);
wait_while_dsi_running(chnl->port.link);
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_err("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);
mutex_unlock(&mcde_hw_lock);
return ret;
}
static void dsi_te_poll_req(struct mcde_chnl_state *chnl)
{
u8 lnk = chnl->port.link;
const struct mcde_port *port = &chnl->port;
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, REG_TE_EN, false);
if (port->ifc == 0)
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, IF1_TE_EN, true);
if (port->ifc == 1)
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, IF2_TE_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, HOST_EOT_GEN, true);
dsi_wfld(lnk, DSI_CMD_MODE_CTL, TE_TIMEOUT, 0x3FF);
dsi_wfld(lnk, DSI_MCTL_MAIN_DATA_CTL, TE_POLLING_EN, true);
}
static void dsi_te_poll_set_timer(struct mcde_chnl_state *chnl,
unsigned int timeout)
{
mod_timer(&chnl->dsi_te_timer,
jiffies +
msecs_to_jiffies(timeout));
}
static void dsi_te_timer_function(unsigned long arg)
{
struct mcde_chnl_state *chnl;
u8 lnk;
if (arg >= num_channels) {
dev_err(&mcde_dev->dev, "%s invalid arg:%ld\n", __func__, arg);
return;
}
chnl = &channels[arg];
if (mcde_is_enabled && chnl->enabled && chnl->formatter_updated) {
lnk = chnl->port.link;
/* No TE answer; force stop */
dsi_wfld(lnk, DSI_MCTL_MAIN_PHY_CTL, FORCE_STOP_MODE, true);
udelay(20);
dsi_wfld(lnk, DSI_MCTL_MAIN_PHY_CTL, FORCE_STOP_MODE, false);
dev_info(&mcde_dev->dev, "DSI%d force stop\n", lnk);
dsi_te_poll_set_timer(chnl, DSI_TE_NO_ANSWER_TIMEOUT);
} else {
dev_info(&mcde_dev->dev, "1:DSI force stop\n");
}
}
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 */
static 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;
static struct mcde_col_transform ycbcr_2_rgb = {
/* Note that in MCDE YUV 422 pixels come as VYU pixels */
.matrix = {
{0xff30, 0x012a, 0xff9c},
{0x0000, 0x012a, 0x0204},
{0x0199, 0x012a, 0x0000},
},
.offset = {0x0088, 0xfeeb, 0xff21},
};
static struct mcde_col_transform rgb_2_ycbcr = {
.matrix = {
{0x0042, 0x0081, 0x0019},
{0xffda, 0xffb6, 0x0070},
{0x0070, 0xffa2, 0xffee},
},
.offset = {0x0010, 0x0080, 0x0080},
};
/* Allocate channel */
for (i = 0; i < num_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->reserved) {
dev_dbg(&mcde_dev->dev, "Channel in use, chnl=%d\n", chnl_id);
return ERR_PTR(-EBUSY);
}
if (hardware_version == MCDE_CHIP_VERSION_3_0_5) {
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);
return ERR_PTR(-EINVAL);
} 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->formatter_updated = false;
chnl->ycbcr_2_rgb = ycbcr_2_rgb;
chnl->rgb_2_ycbcr = rgb_2_ycbcr;
chnl->blend_en = true;
chnl->blend_ctrl = MCDE_CRA0_BLENDCTRL_SOURCE;
chnl->alpha_blend = 0xFF;
_mcde_chnl_apply(chnl);
chnl->reserved = true;
if (chnl->port.type == MCDE_PORTTYPE_DSI) {
#ifdef CONFIG_REGULATOR
chnl->port.phy.dsi.reg_vana = regulator_vana;
#else
chnl->port.phy.dsi.reg_vana = NULL;
#endif
chnl->port.phy.dsi.clk_dsi = clock_dsi;
chnl->port.phy.dsi.clk_dsi_lp = clock_dsi_lp;
enable_dsi++;
if (!dsi_is_enabled && mcde_is_enabled) {
struct mcde_platform_data *pdata =
mcde_dev->dev.platform_data;
pdata->platform_enable_dsipll();
dsi_is_enabled = true;
}
} else if (chnl->port.type == MCDE_PORTTYPE_DPI) {
chnl->port.phy.dpi.clk_dpi = clock_dpi;
if (chnl->port.phy.dpi.tv_mode)
chnl->vcmp_per_field = true;
}
#ifdef CONFIG_REGULATOR
chnl->port.reg_esram = regulator_esram_epod;
#else
chnl->port.reg_esram = NULL;
#endif
return chnl;
}
static int _mcde_chnl_apply(struct mcde_chnl_state *chnl)
{
bool roten = false;
u8 rotdir = 0;
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->port.pixel_format);
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;
chnl->regs.palette_enable = chnl->palette_enable;
chnl->regs.map_r = chnl->map_r;
chnl->regs.map_g = chnl->map_g;
chnl->regs.map_b = chnl->map_b;
if (chnl->port.type == MCDE_PORTTYPE_DSI) {
chnl->regs.clksel = MCDE_CRA1_CLKSEL_166MHZ;
chnl->regs.dsipacking =
portfmt2dsipacking(chnl->port.pixel_format);
} else if (chnl->port.type == MCDE_PORTTYPE_DPI) {
if (chnl->port.phy.dpi.tv_mode) {
chnl->regs.internal_clk = false;
chnl->regs.bcd = true;
if (chnl->id == MCDE_CHNL_A)
chnl->regs.clksel = MCDE_CRA1_CLKSEL_EXT_TV1;
else
chnl->regs.clksel = MCDE_CRA1_CLKSEL_EXT_TV2;
} else {
chnl->regs.internal_clk = true;
chnl->regs.clksel = MCDE_CRA1_CLKSEL_LCD;
chnl->regs.cdwin =
portfmt2cdwin(chnl->port.pixel_format);
chnl->regs.bcd = (chnl->port.phy.dpi.clock_div < 2);
if (!chnl->regs.bcd)
chnl->regs.pcd =
chnl->port.phy.dpi.clock_div - 2;
}
dpi_video_mode_apply(chnl);
}
chnl->regs.blend_ctrl = chnl->blend_ctrl;
chnl->regs.blend_en = chnl->blend_en;
chnl->regs.alpha_blend = chnl->alpha_blend;
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_dpi_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,
bool tripple_buffer)
{
if (chnl->continous_running) {
chnl->transactionid_regs = chnl->transactionid;
if (!tripple_buffer)
wait_for_channel(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) {
if (hardware_version == MCDE_CHIP_VERSION_3_0_8) {
mcde_wfld(MCDE_VSCRC1, VSSEL, 1);
mcde_wfld(MCDE_CRC, SYCEN1, true);
} else {
mcde_wfld(MCDE_VSCRC1, VSSEL, 0);
mcde_wfld(MCDE_CRC, SYCEN0, true);
}
}
chnl->continous_running = true;
/*
* For main and secondary display,
* FLOWEN has to be set before a SOFTWARE TRIG
* Otherwise not overlay interrupt is triggerd
*/
enable_channel(chnl);
if (chnl->port.type == MCDE_PORTTYPE_DSI &&
chnl->port.sync_src == MCDE_SYNCSRC_OFF) {
chnl->disable_software_trig = false;
if (hardware_version == MCDE_CHIP_VERSION_3_0_8) {
mcde_wreg(MCDE_CHNL0SYNCHSW +
chnl->id * MCDE_CHNL0SYNCHSW_GROUPOFFSET,
MCDE_CHNL0SYNCHSW_SW_TRIG(true));
disable_flow(chnl);
}
mod_timer(&chnl->auto_sync_timer,
jiffies +
msecs_to_jiffies(MCDE_AUTO_SYNC_WATCHDOG * 1000));
}
}
}
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) {
if (hardware_version == MCDE_CHIP_VERSION_3_0_8)
enable_channel(chnl);
wait_while_dsi_running(chnl->port.link);
dsi_te_request(chnl);
}
} else {
do_softwaretrig(chnl);
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)
return;
update_overlay_registers_on_the_fly(ovly->idx, &ovly->regs);
if (ovly->regs.update) {
chnl_ovly_pixel_format_apply(chnl, 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, chnl->rotation);
}
}
static int _mcde_chnl_update(struct mcde_chnl_state *chnl,
struct mcde_rectangle *update_area,
bool tripple_buffer)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
/* TODO: lock & make wait->trig async */
if (!chnl->enabled || !update_area
|| (update_area->w == 0 && update_area->h == 0)) {
return -EINVAL;
}
if (chnl->port.update_auto_trig && tripple_buffer)
wait_for_channel(chnl);
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 &&
chnl->port.phy.dpi.tv_mode) {
/* subtract border */
chnl->regs.ppl -= chnl->tv_regs.dho + chnl->tv_regs.alw;
/* subtract double borders, ie. for both fields */
chnl->regs.lpf -= 2 * (chnl->tv_regs.dvo + chnl->tv_regs.bsl);
} 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, tripple_buffer);
else
chnl_update_non_continous(chnl);
dev_vdbg(&mcde_dev->dev, "Channel updated, chnl=%d\n", chnl->id);
return 0;
}
/* API entry points */
/* MCDE channels */
struct mcde_chnl_state *mcde_chnl_get(enum mcde_chnl chnl_id,
enum mcde_fifo fifo, const struct mcde_port *port)
{
struct mcde_chnl_state *chnl;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
chnl = _mcde_chnl_get(chnl_id, fifo, port);
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
return chnl;
}
int mcde_chnl_set_pixel_format(struct mcde_chnl_state *chnl,
enum mcde_port_pix_fmt pix_fmt)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!chnl->reserved)
return -EINVAL;
chnl->port.pixel_format = pix_fmt;
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
return 0;
}
int mcde_chnl_set_palette(struct mcde_chnl_state *chnl,
struct mcde_palette_table *palette)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!chnl->reserved)
return -EINVAL;
if (palette != NULL) {
chnl->map_r = palette->map_col_ch0;
chnl->map_g = palette->map_col_ch1;
chnl->map_b = palette->map_col_ch2;
chnl->palette_enable = true;
} else {
chnl->map_r = NULL;
chnl->map_g = NULL;
chnl->map_b = NULL;
chnl->palette_enable = false;
}
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
return 0;
}
void mcde_chnl_set_col_convert(struct mcde_chnl_state *chnl,
struct mcde_col_transform *transform,
enum mcde_col_convert convert)
{
switch (convert) {
case MCDE_CONVERT_RGB_2_YCBCR:
memcpy(&chnl->rgb_2_ycbcr, transform,
sizeof(struct mcde_col_transform));
/* force update: */
if (chnl->transform == &chnl->rgb_2_ycbcr) {
chnl->transform = NULL;
chnl->ovly0->update = true;
chnl->ovly1->update = true;
}
break;
case MCDE_CONVERT_YCBCR_2_RGB:
memcpy(&chnl->ycbcr_2_rgb, transform,
sizeof(struct mcde_col_transform));
/* force update: */
if (chnl->transform == &chnl->ycbcr_2_rgb) {
chnl->transform = NULL;
chnl->ovly0->update = true;
chnl->ovly1->update = true;
}
break;
default:
/* Trivial transforms are handled internally */
dev_warn(&mcde_dev->dev,
"%s: unsupported col convert\n", __func__);
break;
}
}
int mcde_chnl_set_video_mode(struct mcde_chnl_state *chnl,
struct mcde_video_mode *vmode)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (chnl == NULL || vmode == NULL)
return -EINVAL;
chnl->vmode = *vmode;
if (chnl->ovly0)
chnl->ovly0->update = true;
if (chnl->ovly1)
chnl->ovly1->update = true;
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
return 0;
}
EXPORT_SYMBOL(mcde_chnl_set_video_mode);
int mcde_chnl_set_rotation(struct mcde_chnl_state *chnl,
enum mcde_display_rotation rotation, u32 rotbuf1, u32 rotbuf2)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!chnl->reserved)
return -EINVAL;
if (chnl->id != MCDE_CHNL_A && chnl->id != MCDE_CHNL_B)
return -EINVAL;
chnl->rotation = rotation;
chnl->rotbuf1 = rotbuf1;
chnl->rotbuf2 = rotbuf2;
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
return 0;
}
int mcde_chnl_enable_synchronized_update(struct mcde_chnl_state *chnl,
bool enable)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!chnl->reserved)
return -EINVAL;
chnl->synchronized_update = enable;
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
return 0;
}
int mcde_chnl_set_power_mode(struct mcde_chnl_state *chnl,
enum mcde_display_power_mode power_mode)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!chnl->reserved)
return -EINVAL;
chnl->power_mode = power_mode;
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
return 0;
}
int mcde_chnl_apply(struct mcde_chnl_state *chnl)
{
int ret ;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!chnl->reserved)
return -EINVAL;
mutex_lock(&mcde_hw_lock);
ret = _mcde_chnl_apply(chnl);
mutex_unlock(&mcde_hw_lock);
dev_vdbg(&mcde_dev->dev, "%s exit with ret %d\n", __func__, ret);
return ret;
}
int mcde_chnl_update(struct mcde_chnl_state *chnl,
struct mcde_rectangle *update_area,
bool tripple_buffer)
{
int ret;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!chnl->reserved)
return -EINVAL;
mutex_lock(&mcde_hw_lock);
enable_mcde_hw();
if (!chnl->formatter_updated)
(void)update_channel_static_registers(chnl);
if (chnl->regs.roten && !chnl->esram_is_enabled) {
ret = regulator_enable(chnl->port.reg_esram);
if (ret < 0) {
dev_warn(&mcde_dev->dev, "%s: disable failed\n",
__func__);
}
chnl->esram_is_enabled = true;
}
ret = _mcde_chnl_update(chnl, update_area, tripple_buffer);
mutex_unlock(&mcde_hw_lock);
dev_vdbg(&mcde_dev->dev, "%s exit with ret %d\n", __func__, ret);
return ret;
}
void mcde_chnl_put(struct mcde_chnl_state *chnl)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
chnl->reserved = false;
chnl->port.reg_esram = NULL;
if (chnl->port.type == MCDE_PORTTYPE_DSI) {
chnl->port.phy.dsi.reg_vana = NULL;
chnl->port.phy.dsi.clk_dsi = NULL;
chnl->port.phy.dsi.clk_dsi_lp = NULL;
enable_dsi--;
if (dsi_is_enabled && enable_dsi == 0) {
struct mcde_platform_data *pdata =
mcde_dev->dev.platform_data;
pdata->platform_disable_dsipll();
dsi_is_enabled = false;
}
} else if (chnl->port.type == MCDE_PORTTYPE_DPI) {
chnl->port.phy.dpi.clk_dpi = NULL;
if (chnl->port.phy.dpi.tv_mode) {
chnl->vcmp_per_field = false;
chnl->even_vcmp = false;
}
}
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
}
void mcde_chnl_stop_flow(struct mcde_chnl_state *chnl)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
mutex_lock(&mcde_hw_lock);
if (mcde_is_enabled && chnl->enabled)
disable_channel(chnl);
mutex_unlock(&mcde_hw_lock);
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
}
void mcde_chnl_enable(struct mcde_chnl_state *chnl)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
mutex_lock(&mcde_hw_lock);
chnl->enabled = true;
mutex_unlock(&mcde_hw_lock);
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
}
void mcde_chnl_disable(struct mcde_chnl_state *chnl)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
mutex_lock(&mcde_hw_lock);
cancel_delayed_work(&hw_timeout_work);
/* This channel is disabled here */
chnl->enabled = false;
if (mcde_is_enabled && chnl->formatter_updated
&& chnl->port.type == MCDE_PORTTYPE_DSI)
wait_while_dsi_running(chnl->port.link);
if (chnl->formatter_updated) {
disable_formatter(&chnl->port);
chnl->formatter_updated = false;
}
if (chnl->esram_is_enabled) {
int ret;
ret = regulator_disable(chnl->port.reg_esram);
if (ret < 0) {
dev_warn(&mcde_dev->dev, "%s: disable failed\n",
__func__);
}
chnl->esram_is_enabled = false;
}
del_timer(&chnl->dsi_te_timer);
del_timer(&chnl->auto_sync_timer);
/*
* Check if other channels can be disabled and
* if the hardware can be shutdown
*/
(void)disable_mcde_hw(false);
mutex_unlock(&mcde_hw_lock);
dev_vdbg(&mcde_dev->dev, "%s exit\n", __func__);
}
/* MCDE overlays */
struct mcde_ovly_state *mcde_ovly_get(struct mcde_chnl_state *chnl)
{
struct mcde_ovly_state *ovly;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!chnl->reserved)
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;
ovly->alpha_value = 0xFF;
ovly->alpha_source = MCDE_OVL1CONF2_BP_PER_PIXEL_ALPHA;
ovly->update = true;
mcde_ovly_apply(ovly);
}
return ovly;
}
void mcde_ovly_put(struct mcde_ovly_state *ovly)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
if (!ovly->inuse)
return;
if (ovly->regs.enabled) {
ovly->paddr = 0;
ovly->update = true;
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;
if (paddr == 0 || ovly->paddr == 0)
ovly->update = true;
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;
ovly->update = true;
}
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;
ovly->update = true;
}
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;
mutex_lock(&mcde_hw_lock);
ovly->regs.ch_id = ovly->chnl->id;
ovly->regs.enabled = ovly->paddr != 0;
ovly->regs.baseaddress0 = ovly->paddr;
ovly->regs.baseaddress1 = ovly->regs.baseaddress0 + ovly->stride;
/*TODO set to true if interlaced *//* REVIEW: Video mode interlaced? */
if (!ovly->regs.update)
ovly->regs.update = ovly->update;
ovly->update = false;
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 = false;
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 = false;
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 = false;
ovly->regs.bebo = false;
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->regs.alpha_source = ovly->alpha_source;
ovly->regs.alpha_value = ovly->alpha_value;
ovly->chnl->transactionid++;
mutex_unlock(&mcde_hw_lock);
dev_vdbg(&mcde_dev->dev, "Overlay applied, idx=%d chnl=%d\n",
ovly->idx, 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;
#ifdef CONFIG_REGULATOR
if (pdata->regulator_mcde_epod_id) {
regulator_mcde_epod = regulator_get(&pdev->dev,
pdata->regulator_mcde_epod_id);
if (IS_ERR(regulator_mcde_epod)) {
ret = PTR_ERR(regulator_mcde_epod);
dev_warn(&pdev->dev,
"%s: Failed to get regulator '%s'\n",
__func__, pdata->regulator_mcde_epod_id);
regulator_mcde_epod = NULL;
return ret;
}
} else {
dev_dbg(&pdev->dev, "%s: No regulator id supplied\n",
__func__);
return -EINVAL;
}
if (pdata->regulator_esram_epod_id) {
regulator_esram_epod = regulator_get(&pdev->dev,
pdata->regulator_esram_epod_id);
if (IS_ERR(regulator_esram_epod)) {
ret = PTR_ERR(regulator_esram_epod);
dev_warn(&pdev->dev,
"%s: Failed to get regulator '%s'\n",
__func__, pdata->regulator_esram_epod_id);
regulator_esram_epod = NULL;
goto regulator_esram_err;
}
} else {
dev_dbg(&pdev->dev, "%s: No regulator id supplied\n",
__func__);
}
if (pdata->regulator_vana_id) {
regulator_vana = regulator_get(&pdev->dev,
pdata->regulator_vana_id);
if (IS_ERR(regulator_vana)) {
ret = PTR_ERR(regulator_vana);
dev_warn(&pdev->dev,
"%s: Failed to get regulator '%s'\n",
__func__, pdata->regulator_vana_id);
regulator_vana = NULL;
goto regulator_vana_err;
}
} else {
dev_dbg(&pdev->dev, "%s: No regulator id supplied\n",
__func__);
ret = -EINVAL;
goto regulator_vana_err;
}
#endif
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_dpi = clk_get(&pdev->dev, pdata->clock_dpi_id);
if (IS_ERR(clock_dpi)) {
ret = PTR_ERR(clock_dpi);
dev_warn(&pdev->dev, "%s: Failed to get clock '%s'\n",
__func__, pdata->clock_dpi_id);
goto clk_dpi_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_dpi);
clk_dpi_err:
clk_put(clock_dsi_lp);
clk_dsi_lp_err:
clk_put(clock_dsi);
clk_dsi_err:
#ifdef CONFIG_REGULATOR
if (regulator_vana)
regulator_put(regulator_vana);
regulator_vana_err:
if (regulator_esram_epod)
regulator_put(regulator_esram_epod);
regulator_esram_err:
if (regulator_mcde_epod)
regulator_put(regulator_mcde_epod);
#endif
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_dpi);
clk_put(clock_dsi_lp);
clk_put(clock_dsi);
clk_put(clock_mcde);
#ifdef CONFIG_REGULATOR
if (regulator_vana)
regulator_put(regulator_vana);
if (regulator_mcde_epod)
regulator_put(regulator_mcde_epod);
if (regulator_esram_epod)
regulator_put(regulator_esram_epod);
#endif
}
static int __devinit mcde_probe(struct platform_device *pdev)
{
int ret = 0;
int i;
struct resource *res;
struct mcde_platform_data *pdata = pdev->dev.platform_data;
u8 major_version;
u8 minor_version;
u8 development_version;
if (!pdata) {
dev_dbg(&pdev->dev, "No platform data\n");
return -EINVAL;
}
num_dsilinks = pdata->num_dsilinks;
mcde_dev = pdev;
num_channels = pdata->num_channels;
num_overlays = pdata->num_overlays;
channels = kzalloc(num_channels * sizeof(struct mcde_chnl_state),
GFP_KERNEL);
if (!channels) {
ret = -ENOMEM;
goto failed_channels_alloc;
}
overlays = kzalloc(num_overlays * sizeof(struct mcde_ovly_state),
GFP_KERNEL);
if (!overlays) {
ret = -ENOMEM;
goto failed_overlays_alloc;
}
dsiio = kzalloc(num_dsilinks * sizeof(*dsiio), GFP_KERNEL);
if (!dsiio) {
ret = -ENOMEM;
goto failed_dsi_alloc;
}
/* Hook up irq */
mcde_irq = platform_get_irq(pdev, 0);
if (mcde_irq <= 0) {
dev_dbg(&pdev->dev, "No irq defined\n");
ret = -EINVAL;
goto failed_irq_get;
}
/* 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;
}
INIT_DELAYED_WORK_DEFERRABLE(&hw_timeout_work, work_sleep_function);
ret = enable_clocks_and_power(mcde_dev);
if (ret < 0) {
dev_dbg(&mcde_dev->dev,
"%s: Enable clocks and power failed\n"
, __func__);
goto failed_enable_clocks;
}
update_mcde_registers();
mcde_is_enabled = true;
ret = request_irq(mcde_irq, mcde_irq_handler, 0, "mcde",
&mcde_dev->dev);
if (ret) {
dev_dbg(&mcde_dev->dev, "Failed to request irq (irq=%d)\n",
mcde_irq);
goto failed_request_irq;
}
schedule_delayed_work(&hw_timeout_work,
msecs_to_jiffies(MCDE_SLEEP_WATCHDOG));
major_version = MCDE_REG2VAL(MCDE_PID, MAJOR_VERSION,
mcde_rreg(MCDE_PID));
minor_version = MCDE_REG2VAL(MCDE_PID, MINOR_VERSION,
mcde_rreg(MCDE_PID));
development_version = MCDE_REG2VAL(MCDE_PID, DEVELOPMENT_VERSION,
mcde_rreg(MCDE_PID));
dev_info(&mcde_dev->dev, "MCDE HW revision %u.%u.%u.%u\n",
major_version, minor_version, development_version,
mcde_rfld(MCDE_PID, METALFIX_VERSION));
if (major_version == 3 && minor_version == 0 &&
development_version >= 8) {
hardware_version = MCDE_CHIP_VERSION_3_0_8;
dev_info(&mcde_dev->dev, "V2 HW\n");
} else if (major_version == 3 && minor_version == 0 &&
development_version >= 5) {
hardware_version = MCDE_CHIP_VERSION_3_0_5;
dev_info(&mcde_dev->dev, "V1 HW\n");
} else if (major_version == 1 && minor_version == 0 &&
development_version >= 4) {
hardware_version = MCDE_CHIP_VERSION_1_0_4;
mcde_dynamic_power_management = false;
dev_info(&mcde_dev->dev, "V1_U5500 HW\n");
} else {
dev_err(&mcde_dev->dev, "Unsupported HW version\n");
ret = -ENOTSUPP;
goto failed_hardware_version;
}
for (i = 0; i < num_overlays; i++)
overlays[i].idx = i;
if (hardware_version == MCDE_CHIP_VERSION_1_0_4) {
channels[0].ovly0 = &overlays[0];
channels[0].ovly1 = &overlays[1];
channels[1].ovly0 = &overlays[2];
channels[1].ovly1 = NULL;
} else {
channels[0].ovly0 = &overlays[0];
channels[0].ovly1 = &overlays[1];
channels[1].ovly0 = &overlays[2];
channels[1].ovly1 = &overlays[3];
channels[2].ovly0 = &overlays[4];
channels[2].ovly1 = NULL;
channels[3].ovly0 = &overlays[5];
channels[3].ovly1 = NULL;
}
for (i = 0; i < num_channels; i++) {
channels[i].id = i;
channels[i].ovly0->chnl = &channels[i];
if (channels[i].ovly1)
channels[i].ovly1->chnl = &channels[i];
init_waitqueue_head(&channels[i].waitq_hw);
init_timer(&channels[i].auto_sync_timer);
channels[i].auto_sync_timer.function =
watchdog_auto_sync_timer_function;
init_timer(&channels[i].dsi_te_timer);
channels[i].dsi_te_timer.function =
dsi_te_timer_function;
channels[i].dsi_te_timer.data = i;
}
return 0;
failed_hardware_version:
free_irq(mcde_irq, &pdev->dev);
failed_request_irq:
disable_mcde_hw(true);
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:
failed_irq_get:
kfree(dsiio);
dsiio = NULL;
failed_dsi_alloc:
kfree(overlays);
overlays = NULL;
failed_overlays_alloc:
kfree(channels);
channels = NULL;
failed_channels_alloc:
return ret;
}
static int __devexit mcde_remove(struct platform_device *pdev)
{
struct mcde_chnl_state *chnl = &channels[0];
for (; chnl < &channels[num_channels]; chnl++) {
if (del_timer(&chnl->auto_sync_timer))
dev_vdbg(&mcde_dev->dev,
"%s timer could not be stopped\n"
, __func__);
if (del_timer(&chnl->dsi_te_timer))
dev_vdbg(&mcde_dev->dev,
"%s dsi timer could not be stopped\n"
, __func__);
}
remove_clocks_and_power(pdev);
return 0;
}
#if !defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)
static int mcde_resume(struct platform_device *pdev)
{
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
mutex_lock(&mcde_hw_lock);
if (enable_mcde_hw()) {
mutex_unlock(&mcde_hw_lock);
return -EINVAL;
}
mutex_unlock(&mcde_hw_lock);
return 0;
}
static int mcde_suspend(struct platform_device *pdev, pm_message_t state)
{
int ret;
dev_vdbg(&mcde_dev->dev, "%s\n", __func__);
mutex_lock(&mcde_hw_lock);
cancel_delayed_work(&hw_timeout_work);
if (!mcde_is_enabled) {
mutex_unlock(&mcde_hw_lock);
return 0;
}
(void)disable_mcde_hw(true);
mutex_unlock(&mcde_hw_lock);
return ret;
}
#endif
static struct platform_driver mcde_driver = {
.probe = mcde_probe,
.remove = mcde_remove,
#if !defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)
.suspend = mcde_suspend,
.resume = mcde_resume,
#else
.suspend = NULL,
.resume = NULL,
#endif
.driver = {
.name = "mcde",
},
};
int __init mcde_init(void)
{
mutex_init(&mcde_hw_lock);
return platform_driver_register(&mcde_driver);
}
void mcde_exit(void)
{
/* REVIEW: shutdown MCDE? */
platform_driver_unregister(&mcde_driver);
}