diff options
Diffstat (limited to 'drivers/video/mcde/mcde_hw.c')
-rw-r--r-- | drivers/video/mcde/mcde_hw.c | 3581 |
1 files changed, 3581 insertions, 0 deletions
diff --git a/drivers/video/mcde/mcde_hw.c b/drivers/video/mcde/mcde_hw.c new file mode 100644 index 00000000000..585b22f9111 --- /dev/null +++ b/drivers/video/mcde/mcde_hw.c @@ -0,0 +1,3581 @@ +/* + * 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); +} |